Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/x509/AVA.java
38831 views
/*1* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.x509;2627import java.io.ByteArrayOutputStream;28import java.io.IOException;29import java.io.OutputStream;30import java.io.Reader;31import java.security.AccessController;32import java.text.Normalizer;33import java.util.*;3435import sun.security.action.GetBooleanAction;36import sun.security.util.*;37import sun.security.pkcs.PKCS9Attribute;383940/**41* X.500 Attribute-Value-Assertion (AVA): an attribute, as identified by42* some attribute ID, has some particular value. Values are as a rule ASN.143* printable strings. A conventional set of type IDs is recognized when44* parsing (and generating) RFC 1779, 2253 or 4514 syntax strings.45*46* <P>AVAs are components of X.500 relative names. Think of them as being47* individual fields of a database record. The attribute ID is how you48* identify the field, and the value is part of a particular record.49* <p>50* Note that instances of this class are immutable.51*52* @see X500Name53* @see RDN54*55*56* @author David Brownell57* @author Amit Kapoor58* @author Hemma Prafullchandra59*/60public class AVA implements DerEncoder {6162private static final Debug debug = Debug.getInstance("x509", "\t[AVA]");63// See CR 6391482: if enabled this flag preserves the old but incorrect64// PrintableString encoding for DomainComponent. It may need to be set to65// avoid breaking preexisting certificates generated with sun.security APIs.66private static final boolean PRESERVE_OLD_DC_ENCODING =67AccessController.doPrivileged(new GetBooleanAction68("com.sun.security.preserveOldDCEncoding"));6970/**71* DEFAULT format allows both RFC1779 and RFC2253 syntax and72* additional keywords.73*/74final static int DEFAULT = 1;75/**76* RFC1779 specifies format according to RFC1779.77*/78final static int RFC1779 = 2;79/**80* RFC2253 specifies format according to RFC2253.81*/82final static int RFC2253 = 3;8384// currently not private, accessed directly from RDN85final ObjectIdentifier oid;86final DerValue value;8788/*89* If the value has any of these characters in it, it must be quoted.90* Backslash and quote characters must also be individually escaped.91* Leading and trailing spaces, also multiple internal spaces, also92* call for quoting the whole string.93*/94private static final String specialChars1779 = ",=\n+<>#;\\\"";9596/*97* In RFC2253, if the value has any of these characters in it, it98* must be quoted by a preceding \.99*/100private static final String specialChars2253 = ",=+<>#;\\\"";101102/*103* includes special chars from RFC1779 and RFC2253, as well as ' ' from104* RFC 4514.105*/106private static final String specialCharsDefault = ",=\n+<>#;\\\" ";107private static final String escapedDefault = ",+<>;\"";108109/*110* Values that aren't printable strings are emitted as BER-encoded111* hex data.112*/113private static final String hexDigits = "0123456789ABCDEF";114115public AVA(ObjectIdentifier type, DerValue val) {116if ((type == null) || (val == null)) {117throw new NullPointerException();118}119oid = type;120value = val;121}122123/**124* Parse an RFC 1779, 2253 or 4514 style AVA string: CN=fee fie foe fum125* or perhaps with quotes. Not all defined AVA tags are supported;126* of current note are X.400 related ones (PRMD, ADMD, etc).127*128* This terminates at unescaped AVA separators ("+") or RDN129* separators (",", ";"), and removes cosmetic whitespace at the end of130* values.131*/132AVA(Reader in) throws IOException {133this(in, DEFAULT);134}135136/**137* Parse an RFC 1779, 2253 or 4514 style AVA string: CN=fee fie foe fum138* or perhaps with quotes. Additional keywords can be specified in the139* keyword/OID map.140*141* This terminates at unescaped AVA separators ("+") or RDN142* separators (",", ";"), and removes cosmetic whitespace at the end of143* values.144*/145AVA(Reader in, Map<String, String> keywordMap) throws IOException {146this(in, DEFAULT, keywordMap);147}148149/**150* Parse an AVA string formatted according to format.151*/152AVA(Reader in, int format) throws IOException {153this(in, format, Collections.<String, String>emptyMap());154}155156/**157* Parse an AVA string formatted according to format.158*159* @param in Reader containing AVA String160* @param format parsing format161* @param keywordMap a Map where a keyword String maps to a corresponding162* OID String. Each AVA keyword will be mapped to the corresponding OID.163* If an entry does not exist, it will fallback to the builtin164* keyword/OID mapping.165* @throws IOException if the AVA String is not valid in the specified166* format or an OID String from the keywordMap is improperly formatted167*/168AVA(Reader in, int format, Map<String, String> keywordMap)169throws IOException {170// assume format is one of DEFAULT or RFC2253171172StringBuilder temp = new StringBuilder();173int c;174175/*176* First get the keyword indicating the attribute's type,177* and map it to the appropriate OID.178*/179while (true) {180c = readChar(in, "Incorrect AVA format");181if (c == '=') {182break;183}184temp.append((char)c);185}186187oid = AVAKeyword.getOID(temp.toString(), format, keywordMap);188189/*190* Now parse the value. "#hex", a quoted string, or a string191* terminated by "+", ",", ";". Whitespace before or after192* the value is stripped away unless format is RFC2253.193*/194temp.setLength(0);195if (format == RFC2253) {196// read next character197c = in.read();198if (c == ' ') {199throw new IOException("Incorrect AVA RFC2253 format - " +200"leading space must be escaped");201}202} else {203// read next character skipping whitespace204do {205c = in.read();206} while ((c == ' ') || (c == '\n'));207}208if (c == -1) {209// empty value210value = new DerValue("");211return;212}213214if (c == '#') {215value = parseHexString(in, format);216} else if ((c == '"') && (format != RFC2253)) {217value = parseQuotedString(in, temp);218} else {219value = parseString(in, c, format, temp);220}221}222223/**224* Get the ObjectIdentifier of this AVA.225*/226public ObjectIdentifier getObjectIdentifier() {227return oid;228}229230/**231* Get the value of this AVA as a DerValue.232*/233public DerValue getDerValue() {234return value;235}236237/**238* Get the value of this AVA as a String.239*240* @exception RuntimeException if we could not obtain the string form241* (should not occur)242*/243public String getValueString() {244try {245String s = value.getAsString();246if (s == null) {247throw new RuntimeException("AVA string is null");248}249return s;250} catch (IOException e) {251// should not occur252throw new RuntimeException("AVA error: " + e, e);253}254}255256private static DerValue parseHexString257(Reader in, int format) throws IOException {258259int c;260ByteArrayOutputStream baos = new ByteArrayOutputStream();261byte b = 0;262int cNdx = 0;263while (true) {264c = in.read();265266if (isTerminator(c, format)) {267break;268}269270int cVal = hexDigits.indexOf(Character.toUpperCase((char)c));271272if (cVal == -1) {273throw new IOException("AVA parse, invalid hex " +274"digit: "+ (char)c);275}276277if ((cNdx % 2) == 1) {278b = (byte)((b * 16) + (byte)(cVal));279baos.write(b);280} else {281b = (byte)(cVal);282}283cNdx++;284}285286// throw exception if no hex digits287if (cNdx == 0) {288throw new IOException("AVA parse, zero hex digits");289}290291// throw exception if odd number of hex digits292if (cNdx % 2 == 1) {293throw new IOException("AVA parse, odd number of hex digits");294}295296return new DerValue(baos.toByteArray());297}298299private DerValue parseQuotedString300(Reader in, StringBuilder temp) throws IOException {301302// RFC1779 specifies that an entire RDN may be enclosed in double303// quotes. In this case the syntax is any sequence of304// backslash-specialChar, backslash-backslash,305// backslash-doublequote, or character other than backslash or306// doublequote.307int c = readChar(in, "Quoted string did not end in quote");308309List<Byte> embeddedHex = new ArrayList<Byte>();310boolean isPrintableString = true;311while (c != '"') {312if (c == '\\') {313c = readChar(in, "Quoted string did not end in quote");314315// check for embedded hex pairs316Byte hexByte = null;317if ((hexByte = getEmbeddedHexPair(c, in)) != null) {318319// always encode AVAs with embedded hex as UTF8320isPrintableString = false;321322// append consecutive embedded hex323// as single string later324embeddedHex.add(hexByte);325c = in.read();326continue;327}328329if (specialChars1779.indexOf((char)c) < 0) {330throw new IOException331("Invalid escaped character in AVA: " +332(char)c);333}334}335336// add embedded hex bytes before next char337if (embeddedHex.size() > 0) {338String hexString = getEmbeddedHexString(embeddedHex);339temp.append(hexString);340embeddedHex.clear();341}342343// check for non-PrintableString chars344isPrintableString &= DerValue.isPrintableStringChar((char)c);345temp.append((char)c);346c = readChar(in, "Quoted string did not end in quote");347}348349// add trailing embedded hex bytes350if (embeddedHex.size() > 0) {351String hexString = getEmbeddedHexString(embeddedHex);352temp.append(hexString);353embeddedHex.clear();354}355356do {357c = in.read();358} while ((c == '\n') || (c == ' '));359if (c != -1) {360throw new IOException("AVA had characters other than "361+ "whitespace after terminating quote");362}363364// encode as PrintableString unless value contains365// non-PrintableString chars366if (this.oid.equals((Object)PKCS9Attribute.EMAIL_ADDRESS_OID) ||367(this.oid.equals((Object)X500Name.DOMAIN_COMPONENT_OID) &&368PRESERVE_OLD_DC_ENCODING == false)) {369// EmailAddress and DomainComponent must be IA5String370return new DerValue(DerValue.tag_IA5String,371temp.toString().trim());372} else if (isPrintableString) {373return new DerValue(temp.toString().trim());374} else {375return new DerValue(DerValue.tag_UTF8String,376temp.toString().trim());377}378}379380private DerValue parseString381(Reader in, int c, int format, StringBuilder temp) throws IOException {382383List<Byte> embeddedHex = new ArrayList<>();384boolean isPrintableString = true;385boolean escape = false;386boolean leadingChar = true;387int spaceCount = 0;388do {389escape = false;390if (c == '\\') {391escape = true;392c = readChar(in, "Invalid trailing backslash");393394// check for embedded hex pairs395Byte hexByte = null;396if ((hexByte = getEmbeddedHexPair(c, in)) != null) {397398// always encode AVAs with embedded hex as UTF8399isPrintableString = false;400401// append consecutive embedded hex402// as single string later403embeddedHex.add(hexByte);404c = in.read();405leadingChar = false;406continue;407}408409// check if character was improperly escaped410if (format == DEFAULT &&411specialCharsDefault.indexOf((char)c) == -1) {412throw new IOException413("Invalid escaped character in AVA: '" +414(char)c + "'");415} else if (format == RFC2253) {416if (c == ' ') {417// only leading/trailing space can be escaped418if (!leadingChar && !trailingSpace(in)) {419throw new IOException420("Invalid escaped space character " +421"in AVA. Only a leading or trailing " +422"space character can be escaped.");423}424} else if (c == '#') {425// only leading '#' can be escaped426if (!leadingChar) {427throw new IOException428("Invalid escaped '#' character in AVA. " +429"Only a leading '#' can be escaped.");430}431} else if (specialChars2253.indexOf((char)c) == -1) {432throw new IOException433("Invalid escaped character in AVA: '" +434(char)c + "'");435}436}437} else {438// check if character should have been escaped439if (format == RFC2253) {440if (specialChars2253.indexOf((char)c) != -1) {441throw new IOException442("Character '" + (char)c +443"' in AVA appears without escape");444}445} else if (escapedDefault.indexOf((char)c) != -1) {446throw new IOException447("Character '" + (char)c +448"' in AVA appears without escape");449}450}451452// add embedded hex bytes before next char453if (embeddedHex.size() > 0) {454// add space(s) before embedded hex bytes455for (int i = 0; i < spaceCount; i++) {456temp.append(" ");457}458spaceCount = 0;459460String hexString = getEmbeddedHexString(embeddedHex);461temp.append(hexString);462embeddedHex.clear();463}464465// check for non-PrintableString chars466isPrintableString &= DerValue.isPrintableStringChar((char)c);467if (c == ' ' && escape == false) {468// do not add non-escaped spaces yet469// (non-escaped trailing spaces are ignored)470spaceCount++;471} else {472// add space(s)473for (int i = 0; i < spaceCount; i++) {474temp.append(" ");475}476spaceCount = 0;477temp.append((char)c);478}479c = in.read();480leadingChar = false;481} while (isTerminator(c, format) == false);482483if (format == RFC2253 && spaceCount > 0) {484throw new IOException("Incorrect AVA RFC2253 format - " +485"trailing space must be escaped");486}487488// add trailing embedded hex bytes489if (embeddedHex.size() > 0) {490String hexString = getEmbeddedHexString(embeddedHex);491temp.append(hexString);492embeddedHex.clear();493}494495// encode as PrintableString unless value contains496// non-PrintableString chars497if (this.oid.equals((Object)PKCS9Attribute.EMAIL_ADDRESS_OID) ||498(this.oid.equals((Object)X500Name.DOMAIN_COMPONENT_OID) &&499PRESERVE_OLD_DC_ENCODING == false)) {500// EmailAddress and DomainComponent must be IA5String501return new DerValue(DerValue.tag_IA5String, temp.toString());502} else if (isPrintableString) {503return new DerValue(temp.toString());504} else {505return new DerValue(DerValue.tag_UTF8String, temp.toString());506}507}508509private static Byte getEmbeddedHexPair(int c1, Reader in)510throws IOException {511512if (hexDigits.indexOf(Character.toUpperCase((char)c1)) >= 0) {513int c2 = readChar(in, "unexpected EOF - " +514"escaped hex value must include two valid digits");515516if (hexDigits.indexOf(Character.toUpperCase((char)c2)) >= 0) {517int hi = Character.digit((char)c1, 16);518int lo = Character.digit((char)c2, 16);519return new Byte((byte)((hi<<4) + lo));520} else {521throw new IOException522("escaped hex value must include two valid digits");523}524}525return null;526}527528private static String getEmbeddedHexString(List<Byte> hexList)529throws IOException {530int n = hexList.size();531byte[] hexBytes = new byte[n];532for (int i = 0; i < n; i++) {533hexBytes[i] = hexList.get(i).byteValue();534}535return new String(hexBytes, "UTF8");536}537538private static boolean isTerminator(int ch, int format) {539switch (ch) {540case -1:541case '+':542case ',':543return true;544case ';':545return format != RFC2253;546default:547return false;548}549}550551private static int readChar(Reader in, String errMsg) throws IOException {552int c = in.read();553if (c == -1) {554throw new IOException(errMsg);555}556return c;557}558559private static boolean trailingSpace(Reader in) throws IOException {560561boolean trailing = false;562563if (!in.markSupported()) {564// oh well565return true;566} else {567// make readAheadLimit huge -568// in practice, AVA was passed a StringReader from X500Name,569// and StringReader ignores readAheadLimit anyways570in.mark(9999);571while (true) {572int nextChar = in.read();573if (nextChar == -1) {574trailing = true;575break;576} else if (nextChar == ' ') {577continue;578} else if (nextChar == '\\') {579int followingChar = in.read();580if (followingChar != ' ') {581trailing = false;582break;583}584} else {585trailing = false;586break;587}588}589590in.reset();591return trailing;592}593}594595AVA(DerValue derval) throws IOException {596// Individual attribute value assertions are SEQUENCE of two values.597// That'd be a "struct" outside of ASN.1.598if (derval.tag != DerValue.tag_Sequence) {599throw new IOException("AVA not a sequence");600}601oid = derval.data.getOID();602value = derval.data.getDerValue();603604if (derval.data.available() != 0) {605throw new IOException("AVA, extra bytes = "606+ derval.data.available());607}608}609610AVA(DerInputStream in) throws IOException {611this(in.getDerValue());612}613614public boolean equals(Object obj) {615if (this == obj) {616return true;617}618if (obj instanceof AVA == false) {619return false;620}621AVA other = (AVA)obj;622return this.toRFC2253CanonicalString().equals623(other.toRFC2253CanonicalString());624}625626/**627* Returns a hashcode for this AVA.628*629* @return a hashcode for this AVA.630*/631public int hashCode() {632return toRFC2253CanonicalString().hashCode();633}634635/*636* AVAs are encoded as a SEQUENCE of two elements.637*/638public void encode(DerOutputStream out) throws IOException {639derEncode(out);640}641642/**643* DER encode this object onto an output stream.644* Implements the <code>DerEncoder</code> interface.645*646* @param out647* the output stream on which to write the DER encoding.648*649* @exception IOException on encoding error.650*/651public void derEncode(OutputStream out) throws IOException {652DerOutputStream tmp = new DerOutputStream();653DerOutputStream tmp2 = new DerOutputStream();654655tmp.putOID(oid);656value.encode(tmp);657tmp2.write(DerValue.tag_Sequence, tmp);658out.write(tmp2.toByteArray());659}660661private String toKeyword(int format, Map<String, String> oidMap) {662return AVAKeyword.getKeyword(oid, format, oidMap);663}664665/**666* Returns a printable form of this attribute, using RFC 1779667* syntax for individual attribute/value assertions.668*/669public String toString() {670return toKeywordValueString671(toKeyword(DEFAULT, Collections.<String, String>emptyMap()));672}673674/**675* Returns a printable form of this attribute, using RFC 1779676* syntax for individual attribute/value assertions. It only677* emits standardised keywords.678*/679public String toRFC1779String() {680return toRFC1779String(Collections.<String, String>emptyMap());681}682683/**684* Returns a printable form of this attribute, using RFC 1779685* syntax for individual attribute/value assertions. It686* emits standardised keywords, as well as keywords contained in the687* OID/keyword map.688*/689public String toRFC1779String(Map<String, String> oidMap) {690return toKeywordValueString(toKeyword(RFC1779, oidMap));691}692693/**694* Returns a printable form of this attribute, using RFC 2253695* syntax for individual attribute/value assertions. It only696* emits standardised keywords.697*/698public String toRFC2253String() {699return toRFC2253String(Collections.<String, String>emptyMap());700}701702/**703* Returns a printable form of this attribute, using RFC 2253704* syntax for individual attribute/value assertions. It705* emits standardised keywords, as well as keywords contained in the706* OID/keyword map.707*/708public String toRFC2253String(Map<String, String> oidMap) {709/*710* Section 2.3: The AttributeTypeAndValue is encoded as the string711* representation of the AttributeType, followed by an equals character712* ('=' ASCII 61), followed by the string representation of the713* AttributeValue. The encoding of the AttributeValue is given in714* section 2.4.715*/716StringBuilder typeAndValue = new StringBuilder(100);717typeAndValue.append(toKeyword(RFC2253, oidMap));718typeAndValue.append('=');719720/*721* Section 2.4: Converting an AttributeValue from ASN.1 to a String.722* If the AttributeValue is of a type which does not have a string723* representation defined for it, then it is simply encoded as an724* octothorpe character ('#' ASCII 35) followed by the hexadecimal725* representation of each of the bytes of the BER encoding of the X.500726* AttributeValue. This form SHOULD be used if the AttributeType is of727* the dotted-decimal form.728*/729if ((typeAndValue.charAt(0) >= '0' && typeAndValue.charAt(0) <= '9') ||730!isDerString(value, false))731{732byte[] data = null;733try {734data = value.toByteArray();735} catch (IOException ie) {736throw new IllegalArgumentException("DER Value conversion");737}738typeAndValue.append('#');739for (int j = 0; j < data.length; j++) {740byte b = data[j];741typeAndValue.append(Character.forDigit(0xF & (b >>> 4), 16));742typeAndValue.append(Character.forDigit(0xF & b, 16));743}744} else {745/*746* 2.4 (cont): Otherwise, if the AttributeValue is of a type which747* has a string representation, the value is converted first to a748* UTF-8 string according to its syntax specification.749*750* NOTE: this implementation only emits DirectoryStrings of the751* types returned by isDerString().752*/753String valStr = null;754try {755valStr = new String(value.getDataBytes(), "UTF8");756} catch (IOException ie) {757throw new IllegalArgumentException("DER Value conversion");758}759760/*761* 2.4 (cont): If the UTF-8 string does not have any of the762* following characters which need escaping, then that string can be763* used as the string representation of the value.764*765* o a space or "#" character occurring at the beginning of the766* string767* o a space character occurring at the end of the string768* o one of the characters ",", "+", """, "\", "<", ">" or ";"769*770* Implementations MAY escape other characters.771*772* NOTE: this implementation also recognizes "=" and "#" as773* characters which need escaping, and null which is escaped as774* '\00' (see RFC 4514).775*776* If a character to be escaped is one of the list shown above, then777* it is prefixed by a backslash ('\' ASCII 92).778*779* Otherwise the character to be escaped is replaced by a backslash780* and two hex digits, which form a single byte in the code of the781* character.782*/783final String escapees = ",=+<>#;\"\\";784StringBuilder sbuffer = new StringBuilder();785786for (int i = 0; i < valStr.length(); i++) {787char c = valStr.charAt(i);788if (DerValue.isPrintableStringChar(c) ||789escapees.indexOf(c) >= 0) {790791// escape escapees792if (escapees.indexOf(c) >= 0) {793sbuffer.append('\\');794}795796// append printable/escaped char797sbuffer.append(c);798799} else if (c == '\u0000') {800// escape null character801sbuffer.append("\\00");802803} else if (debug != null && Debug.isOn("ava")) {804805// embed non-printable/non-escaped char806// as escaped hex pairs for debugging807byte[] valueBytes = null;808try {809valueBytes = Character.toString(c).getBytes("UTF8");810} catch (IOException ie) {811throw new IllegalArgumentException812("DER Value conversion");813}814for (int j = 0; j < valueBytes.length; j++) {815sbuffer.append('\\');816char hexChar = Character.forDigit817(0xF & (valueBytes[j] >>> 4), 16);818sbuffer.append(Character.toUpperCase(hexChar));819hexChar = Character.forDigit820(0xF & (valueBytes[j]), 16);821sbuffer.append(Character.toUpperCase(hexChar));822}823} else {824825// append non-printable/non-escaped char826sbuffer.append(c);827}828}829830char[] chars = sbuffer.toString().toCharArray();831sbuffer = new StringBuilder();832833// Find leading and trailing whitespace.834int lead; // index of first char that is not leading whitespace835for (lead = 0; lead < chars.length; lead++) {836if (chars[lead] != ' ' && chars[lead] != '\r') {837break;838}839}840int trail; // index of last char that is not trailing whitespace841for (trail = chars.length - 1; trail >= 0; trail--) {842if (chars[trail] != ' ' && chars[trail] != '\r') {843break;844}845}846847// escape leading and trailing whitespace848for (int i = 0; i < chars.length; i++) {849char c = chars[i];850if (i < lead || i > trail) {851sbuffer.append('\\');852}853sbuffer.append(c);854}855typeAndValue.append(sbuffer.toString());856}857return typeAndValue.toString();858}859860public String toRFC2253CanonicalString() {861/*862* Section 2.3: The AttributeTypeAndValue is encoded as the string863* representation of the AttributeType, followed by an equals character864* ('=' ASCII 61), followed by the string representation of the865* AttributeValue. The encoding of the AttributeValue is given in866* section 2.4.867*/868StringBuilder typeAndValue = new StringBuilder(40);869typeAndValue.append870(toKeyword(RFC2253, Collections.<String, String>emptyMap()));871typeAndValue.append('=');872873/*874* Section 2.4: Converting an AttributeValue from ASN.1 to a String.875* If the AttributeValue is of a type which does not have a string876* representation defined for it, then it is simply encoded as an877* octothorpe character ('#' ASCII 35) followed by the hexadecimal878* representation of each of the bytes of the BER encoding of the X.500879* AttributeValue. This form SHOULD be used if the AttributeType is of880* the dotted-decimal form.881*/882if ((typeAndValue.charAt(0) >= '0' && typeAndValue.charAt(0) <= '9') ||883!isDerString(value, true))884{885byte[] data = null;886try {887data = value.toByteArray();888} catch (IOException ie) {889throw new IllegalArgumentException("DER Value conversion");890}891typeAndValue.append('#');892for (int j = 0; j < data.length; j++) {893byte b = data[j];894typeAndValue.append(Character.forDigit(0xF & (b >>> 4), 16));895typeAndValue.append(Character.forDigit(0xF & b, 16));896}897} else {898/*899* 2.4 (cont): Otherwise, if the AttributeValue is of a type which900* has a string representation, the value is converted first to a901* UTF-8 string according to its syntax specification.902*903* NOTE: this implementation only emits DirectoryStrings of the904* types returned by isDerString().905*/906String valStr = null;907try {908valStr = new String(value.getDataBytes(), "UTF8");909} catch (IOException ie) {910throw new IllegalArgumentException("DER Value conversion");911}912913/*914* 2.4 (cont): If the UTF-8 string does not have any of the915* following characters which need escaping, then that string can be916* used as the string representation of the value.917*918* o a space or "#" character occurring at the beginning of the919* string920* o a space character occurring at the end of the string921*922* o one of the characters ",", "+", """, "\", "<", ">" or ";"923*924* If a character to be escaped is one of the list shown above, then925* it is prefixed by a backslash ('\' ASCII 92).926*927* Otherwise the character to be escaped is replaced by a backslash928* and two hex digits, which form a single byte in the code of the929* character.930*/931final String escapees = ",+<>;\"\\";932StringBuilder sbuffer = new StringBuilder();933boolean previousWhite = false;934935for (int i = 0; i < valStr.length(); i++) {936char c = valStr.charAt(i);937938if (DerValue.isPrintableStringChar(c) ||939escapees.indexOf(c) >= 0 ||940(i == 0 && c == '#')) {941942// escape leading '#' and escapees943if ((i == 0 && c == '#') || escapees.indexOf(c) >= 0) {944sbuffer.append('\\');945}946947// convert multiple whitespace to single whitespace948if (!Character.isWhitespace(c)) {949previousWhite = false;950sbuffer.append(c);951} else {952if (previousWhite == false) {953// add single whitespace954previousWhite = true;955sbuffer.append(c);956} else {957// ignore subsequent consecutive whitespace958continue;959}960}961962} else if (debug != null && Debug.isOn("ava")) {963964// embed non-printable/non-escaped char965// as escaped hex pairs for debugging966967previousWhite = false;968969byte valueBytes[] = null;970try {971valueBytes = Character.toString(c).getBytes("UTF8");972} catch (IOException ie) {973throw new IllegalArgumentException974("DER Value conversion");975}976for (int j = 0; j < valueBytes.length; j++) {977sbuffer.append('\\');978sbuffer.append(Character.forDigit979(0xF & (valueBytes[j] >>> 4), 16));980sbuffer.append(Character.forDigit981(0xF & (valueBytes[j]), 16));982}983} else {984985// append non-printable/non-escaped char986987previousWhite = false;988sbuffer.append(c);989}990}991992// remove leading and trailing whitespace from value993typeAndValue.append(sbuffer.toString().trim());994}995996String canon = typeAndValue.toString();997canon = canon.toUpperCase(Locale.US).toLowerCase(Locale.US);998return Normalizer.normalize(canon, Normalizer.Form.NFKD);999}10001001/*1002* Return true if DerValue can be represented as a String.1003*/1004private static boolean isDerString(DerValue value, boolean canonical) {1005if (canonical) {1006switch (value.tag) {1007case DerValue.tag_PrintableString:1008case DerValue.tag_UTF8String:1009return true;1010default:1011return false;1012}1013} else {1014switch (value.tag) {1015case DerValue.tag_PrintableString:1016case DerValue.tag_T61String:1017case DerValue.tag_IA5String:1018case DerValue.tag_GeneralString:1019case DerValue.tag_BMPString:1020case DerValue.tag_UTF8String:1021return true;1022default:1023return false;1024}1025}1026}10271028boolean hasRFC2253Keyword() {1029return AVAKeyword.hasKeyword(oid, RFC2253);1030}10311032private String toKeywordValueString(String keyword) {1033/*1034* Construct the value with as little copying and garbage1035* production as practical. First the keyword (mandatory),1036* then the equals sign, finally the value.1037*/1038StringBuilder retval = new StringBuilder(40);10391040retval.append(keyword);1041retval.append("=");10421043try {1044String valStr = value.getAsString();10451046if (valStr == null) {10471048// rfc1779 specifies that attribute values associated1049// with non-standard keyword attributes may be represented1050// using the hex format below. This will be used only1051// when the value is not a string type10521053byte data [] = value.toByteArray();10541055retval.append('#');1056for (int i = 0; i < data.length; i++) {1057retval.append(hexDigits.charAt((data [i] >> 4) & 0x0f));1058retval.append(hexDigits.charAt(data [i] & 0x0f));1059}10601061} else {10621063boolean quoteNeeded = false;1064StringBuilder sbuffer = new StringBuilder();1065boolean previousWhite = false;1066final String escapees = ",+=\n<>#;\\\"";10671068/*1069* Special characters (e.g. AVA list separators) cause strings1070* to need quoting, or at least escaping. So do leading or1071* trailing spaces, and multiple internal spaces.1072*/1073int length = valStr.length();1074boolean alreadyQuoted =1075(length > 1 && valStr.charAt(0) == '\"'1076&& valStr.charAt(length - 1) == '\"');10771078for (int i = 0; i < length; i++) {1079char c = valStr.charAt(i);1080if (alreadyQuoted && (i == 0 || i == length - 1)) {1081sbuffer.append(c);1082continue;1083}1084if (DerValue.isPrintableStringChar(c) ||1085escapees.indexOf(c) >= 0) {10861087// quote if leading whitespace or special chars1088if (!quoteNeeded &&1089((i == 0 && (c == ' ' || c == '\n')) ||1090escapees.indexOf(c) >= 0)) {1091quoteNeeded = true;1092}10931094// quote if multiple internal whitespace1095if (!(c == ' ' || c == '\n')) {1096// escape '"' and '\'1097if (c == '"' || c == '\\') {1098sbuffer.append('\\');1099}1100previousWhite = false;1101} else {1102if (!quoteNeeded && previousWhite) {1103quoteNeeded = true;1104}1105previousWhite = true;1106}11071108sbuffer.append(c);11091110} else if (debug != null && Debug.isOn("ava")) {11111112// embed non-printable/non-escaped char1113// as escaped hex pairs for debugging11141115previousWhite = false;11161117// embed escaped hex pairs1118byte[] valueBytes =1119Character.toString(c).getBytes("UTF8");1120for (int j = 0; j < valueBytes.length; j++) {1121sbuffer.append('\\');1122char hexChar = Character.forDigit1123(0xF & (valueBytes[j] >>> 4), 16);1124sbuffer.append(Character.toUpperCase(hexChar));1125hexChar = Character.forDigit1126(0xF & (valueBytes[j]), 16);1127sbuffer.append(Character.toUpperCase(hexChar));1128}1129} else {11301131// append non-printable/non-escaped char11321133previousWhite = false;1134sbuffer.append(c);1135}1136}11371138// quote if trailing whitespace1139if (sbuffer.length() > 0) {1140char trailChar = sbuffer.charAt(sbuffer.length() - 1);1141if (trailChar == ' ' || trailChar == '\n') {1142quoteNeeded = true;1143}1144}11451146// Emit the string ... quote it if needed1147// if string is already quoted, don't re-quote1148if (!alreadyQuoted && quoteNeeded) {1149retval.append("\"" + sbuffer.toString() + "\"");1150} else {1151retval.append(sbuffer.toString());1152}1153}1154} catch (IOException e) {1155throw new IllegalArgumentException("DER Value conversion");1156}11571158return retval.toString();1159}11601161}11621163/**1164* Helper class that allows conversion from String to ObjectIdentifier and1165* vice versa according to RFC1779, RFC2253, and an augmented version of1166* those standards.1167*/1168class AVAKeyword {11691170private static final Map<ObjectIdentifier,AVAKeyword> oidMap;1171private static final Map<String,AVAKeyword> keywordMap;11721173private String keyword;1174private ObjectIdentifier oid;1175private boolean rfc1779Compliant, rfc2253Compliant;11761177private AVAKeyword(String keyword, ObjectIdentifier oid,1178boolean rfc1779Compliant, boolean rfc2253Compliant) {1179this.keyword = keyword;1180this.oid = oid;1181this.rfc1779Compliant = rfc1779Compliant;1182this.rfc2253Compliant = rfc2253Compliant;11831184// register it1185oidMap.put(oid, this);1186keywordMap.put(keyword, this);1187}11881189private boolean isCompliant(int standard) {1190switch (standard) {1191case AVA.RFC1779:1192return rfc1779Compliant;1193case AVA.RFC2253:1194return rfc2253Compliant;1195case AVA.DEFAULT:1196return true;1197default:1198// should not occur, internal error1199throw new IllegalArgumentException("Invalid standard " + standard);1200}1201}12021203/**1204* Get an object identifier representing the specified keyword (or1205* string encoded object identifier) in the given standard.1206*1207* @param keywordMap a Map where a keyword String maps to a corresponding1208* OID String. Each AVA keyword will be mapped to the corresponding OID.1209* If an entry does not exist, it will fallback to the builtin1210* keyword/OID mapping.1211* @throws IOException If the keyword is not valid in the specified standard1212* or the OID String to which a keyword maps to is improperly formatted.1213*/1214static ObjectIdentifier getOID1215(String keyword, int standard, Map<String, String> extraKeywordMap)1216throws IOException {12171218keyword = keyword.toUpperCase(Locale.ENGLISH);1219if (standard == AVA.RFC2253) {1220if (keyword.startsWith(" ") || keyword.endsWith(" ")) {1221throw new IOException("Invalid leading or trailing space " +1222"in keyword \"" + keyword + "\"");1223}1224} else {1225keyword = keyword.trim();1226}12271228// check user-specified keyword map first, then fallback to built-in1229// map1230String oidString = extraKeywordMap.get(keyword);1231if (oidString == null) {1232AVAKeyword ak = keywordMap.get(keyword);1233if ((ak != null) && ak.isCompliant(standard)) {1234return ak.oid;1235}1236} else {1237return new ObjectIdentifier(oidString);1238}12391240// no keyword found, check if OID string1241if (standard == AVA.DEFAULT && keyword.startsWith("OID.")) {1242keyword = keyword.substring(4);1243}12441245boolean number = false;1246if (keyword.length() != 0) {1247char ch = keyword.charAt(0);1248if ((ch >= '0') && (ch <= '9')) {1249number = true;1250}1251}1252if (number == false) {1253throw new IOException("Invalid keyword \"" + keyword + "\"");1254}1255return new ObjectIdentifier(keyword);1256}12571258/**1259* Get a keyword for the given ObjectIdentifier according to standard.1260* If no keyword is available, the ObjectIdentifier is encoded as a1261* String.1262*/1263static String getKeyword(ObjectIdentifier oid, int standard) {1264return getKeyword1265(oid, standard, Collections.<String, String>emptyMap());1266}12671268/**1269* Get a keyword for the given ObjectIdentifier according to standard.1270* Checks the extraOidMap for a keyword first, then falls back to the1271* builtin/default set. If no keyword is available, the ObjectIdentifier1272* is encoded as a String.1273*/1274static String getKeyword1275(ObjectIdentifier oid, int standard, Map<String, String> extraOidMap) {12761277// check extraOidMap first, then fallback to built-in map1278String oidString = oid.toString();1279String keywordString = extraOidMap.get(oidString);1280if (keywordString == null) {1281AVAKeyword ak = oidMap.get(oid);1282if ((ak != null) && ak.isCompliant(standard)) {1283return ak.keyword;1284}1285} else {1286if (keywordString.length() == 0) {1287throw new IllegalArgumentException("keyword cannot be empty");1288}1289keywordString = keywordString.trim();1290char c = keywordString.charAt(0);1291if (c < 65 || c > 122 || (c > 90 && c < 97)) {1292throw new IllegalArgumentException1293("keyword does not start with letter");1294}1295for (int i=1; i<keywordString.length(); i++) {1296c = keywordString.charAt(i);1297if ((c < 65 || c > 122 || (c > 90 && c < 97)) &&1298(c < 48 || c > 57) && c != '_') {1299throw new IllegalArgumentException1300("keyword character is not a letter, digit, or underscore");1301}1302}1303return keywordString;1304}1305// no compliant keyword, use OID1306if (standard == AVA.RFC2253) {1307return oidString;1308} else {1309return "OID." + oidString;1310}1311}13121313/**1314* Test if oid has an associated keyword in standard.1315*/1316static boolean hasKeyword(ObjectIdentifier oid, int standard) {1317AVAKeyword ak = oidMap.get(oid);1318if (ak == null) {1319return false;1320}1321return ak.isCompliant(standard);1322}13231324static {1325oidMap = new HashMap<ObjectIdentifier,AVAKeyword>();1326keywordMap = new HashMap<String,AVAKeyword>();13271328// NOTE if multiple keywords are available for one OID, order1329// is significant!! Preferred *LAST*.1330new AVAKeyword("CN", X500Name.commonName_oid, true, true);1331new AVAKeyword("C", X500Name.countryName_oid, true, true);1332new AVAKeyword("L", X500Name.localityName_oid, true, true);1333new AVAKeyword("S", X500Name.stateName_oid, false, false);1334new AVAKeyword("ST", X500Name.stateName_oid, true, true);1335new AVAKeyword("O", X500Name.orgName_oid, true, true);1336new AVAKeyword("OU", X500Name.orgUnitName_oid, true, true);1337new AVAKeyword("T", X500Name.title_oid, false, false);1338new AVAKeyword("IP", X500Name.ipAddress_oid, false, false);1339new AVAKeyword("STREET", X500Name.streetAddress_oid,true, true);1340new AVAKeyword("DC", X500Name.DOMAIN_COMPONENT_OID,1341false, true);1342new AVAKeyword("DNQUALIFIER", X500Name.DNQUALIFIER_OID, false, false);1343new AVAKeyword("DNQ", X500Name.DNQUALIFIER_OID, false, false);1344new AVAKeyword("SURNAME", X500Name.SURNAME_OID, false, false);1345new AVAKeyword("GIVENNAME", X500Name.GIVENNAME_OID, false, false);1346new AVAKeyword("INITIALS", X500Name.INITIALS_OID, false, false);1347new AVAKeyword("GENERATION", X500Name.GENERATIONQUALIFIER_OID,1348false, false);1349new AVAKeyword("EMAIL", PKCS9Attribute.EMAIL_ADDRESS_OID, false, false);1350new AVAKeyword("EMAILADDRESS", PKCS9Attribute.EMAIL_ADDRESS_OID,1351false, false);1352new AVAKeyword("UID", X500Name.userid_oid, false, true);1353new AVAKeyword("SERIALNUMBER", X500Name.SERIALNUMBER_OID, false, false);1354}1355}135613571358