Home | History | Annotate | Download | only in x509
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 package sun.security.x509;
     28 
     29 import java.io.ByteArrayOutputStream;
     30 import java.io.IOException;
     31 import java.io.OutputStream;
     32 import java.io.Reader;
     33 import java.security.AccessController;
     34 import java.text.Normalizer;
     35 import java.util.*;
     36 
     37 import sun.security.action.GetBooleanAction;
     38 import sun.security.util.*;
     39 import sun.security.pkcs.PKCS9Attribute;
     40 
     41 
     42 /**
     43  * X.500 Attribute-Value-Assertion (AVA):  an attribute, as identified by
     44  * some attribute ID, has some particular value.  Values are as a rule ASN.1
     45  * printable strings.  A conventional set of type IDs is recognized when
     46  * parsing (and generating) RFC 1779, 2253 or 4514 syntax strings.
     47  *
     48  * <P>AVAs are components of X.500 relative names.  Think of them as being
     49  * individual fields of a database record.  The attribute ID is how you
     50  * identify the field, and the value is part of a particular record.
     51  * <p>
     52  * Note that instances of this class are immutable.
     53  *
     54  * @see X500Name
     55  * @see RDN
     56  *
     57  *
     58  * @author David Brownell
     59  * @author Amit Kapoor
     60  * @author Hemma Prafullchandra
     61  */
     62 public class AVA implements DerEncoder {
     63 
     64     private static final Debug debug = Debug.getInstance("x509", "\t[AVA]");
     65     // See CR 6391482: if enabled this flag preserves the old but incorrect
     66     // PrintableString encoding for DomainComponent. It may need to be set to
     67     // avoid breaking preexisting certificates generated with sun.security APIs.
     68     private static final boolean PRESERVE_OLD_DC_ENCODING =
     69         AccessController.doPrivileged(new GetBooleanAction
     70             ("com.sun.security.preserveOldDCEncoding"));
     71 
     72     /**
     73      * DEFAULT format allows both RFC1779 and RFC2253 syntax and
     74      * additional keywords.
     75      */
     76     final static int DEFAULT = 1;
     77     /**
     78      * RFC1779 specifies format according to RFC1779.
     79      */
     80     final static int RFC1779 = 2;
     81     /**
     82      * RFC2253 specifies format according to RFC2253.
     83      */
     84     final static int RFC2253 = 3;
     85 
     86     // currently not private, accessed directly from RDN
     87     final ObjectIdentifier oid;
     88     final DerValue value;
     89 
     90     /*
     91      * If the value has any of these characters in it, it must be quoted.
     92      * Backslash and quote characters must also be individually escaped.
     93      * Leading and trailing spaces, also multiple internal spaces, also
     94      * call for quoting the whole string.
     95      */
     96     private static final String specialChars1779 = ",=\n+<>#;\\\"";
     97 
     98     /*
     99      * In RFC2253, if the value has any of these characters in it, it
    100      * must be quoted by a preceding \.
    101      */
    102     private static final String specialChars2253 = ",=+<>#;\\\"";
    103 
    104     /*
    105      * includes special chars from RFC1779 and RFC2253, as well as ' ' from
    106      * RFC 4514.
    107      */
    108     private static final String specialCharsDefault = ",=\n+<>#;\\\" ";
    109     private static final String escapedDefault = ",+<>;\"";
    110 
    111     /*
    112      * Values that aren't printable strings are emitted as BER-encoded
    113      * hex data.
    114      */
    115     private static final String hexDigits = "0123456789ABCDEF";
    116 
    117     public AVA(ObjectIdentifier type, DerValue val) {
    118         if ((type == null) || (val == null)) {
    119             throw new NullPointerException();
    120         }
    121         oid = type;
    122         value = val;
    123     }
    124 
    125     /**
    126      * Parse an RFC 1779, 2253 or 4514 style AVA string:  CN=fee fie foe fum
    127      * or perhaps with quotes.  Not all defined AVA tags are supported;
    128      * of current note are X.400 related ones (PRMD, ADMD, etc).
    129      *
    130      * This terminates at unescaped AVA separators ("+") or RDN
    131      * separators (",", ";"), and removes cosmetic whitespace at the end of
    132      * values.
    133      */
    134     AVA(Reader in) throws IOException {
    135         this(in, DEFAULT);
    136     }
    137 
    138     /**
    139      * Parse an RFC 1779, 2253 or 4514 style AVA string:  CN=fee fie foe fum
    140      * or perhaps with quotes. Additional keywords can be specified in the
    141      * keyword/OID map.
    142      *
    143      * This terminates at unescaped AVA separators ("+") or RDN
    144      * separators (",", ";"), and removes cosmetic whitespace at the end of
    145      * values.
    146      */
    147     AVA(Reader in, Map<String, String> keywordMap) throws IOException {
    148         this(in, DEFAULT, keywordMap);
    149     }
    150 
    151     /**
    152      * Parse an AVA string formatted according to format.
    153      */
    154     AVA(Reader in, int format) throws IOException {
    155         this(in, format, Collections.<String, String>emptyMap());
    156     }
    157 
    158     /**
    159      * Parse an AVA string formatted according to format.
    160      *
    161      * @param in Reader containing AVA String
    162      * @param format parsing format
    163      * @param keywordMap a Map where a keyword String maps to a corresponding
    164      *   OID String. Each AVA keyword will be mapped to the corresponding OID.
    165      *   If an entry does not exist, it will fallback to the builtin
    166      *   keyword/OID mapping.
    167      * @throws IOException if the AVA String is not valid in the specified
    168      *   format or an OID String from the keywordMap is improperly formatted
    169      */
    170     AVA(Reader in, int format, Map<String, String> keywordMap)
    171         throws IOException {
    172         // assume format is one of DEFAULT or RFC2253
    173 
    174         StringBuilder   temp = new StringBuilder();
    175         int             c;
    176 
    177         /*
    178          * First get the keyword indicating the attribute's type,
    179          * and map it to the appropriate OID.
    180          */
    181         while (true) {
    182             c = readChar(in, "Incorrect AVA format");
    183             if (c == '=') {
    184                 break;
    185             }
    186             temp.append((char)c);
    187         }
    188 
    189         oid = AVAKeyword.getOID(temp.toString(), format, keywordMap);
    190 
    191         /*
    192          * Now parse the value.  "#hex", a quoted string, or a string
    193          * terminated by "+", ",", ";".  Whitespace before or after
    194          * the value is stripped away unless format is RFC2253.
    195          */
    196         temp.setLength(0);
    197         if (format == RFC2253) {
    198             // read next character
    199             c = in.read();
    200             if (c == ' ') {
    201                 throw new IOException("Incorrect AVA RFC2253 format - " +
    202                                       "leading space must be escaped");
    203             }
    204         } else {
    205             // read next character skipping whitespace
    206             do {
    207                 c = in.read();
    208             } while ((c == ' ') || (c == '\n'));
    209         }
    210         if (c == -1) {
    211             // empty value
    212             value = new DerValue("");
    213             return;
    214         }
    215 
    216         if (c == '#') {
    217             value = parseHexString(in, format);
    218         } else if ((c == '"') && (format != RFC2253)) {
    219             value = parseQuotedString(in, temp);
    220         } else {
    221             value = parseString(in, c, format, temp);
    222         }
    223     }
    224 
    225     /**
    226      * Get the ObjectIdentifier of this AVA.
    227      */
    228     public ObjectIdentifier getObjectIdentifier() {
    229         return oid;
    230     }
    231 
    232     /**
    233      * Get the value of this AVA as a DerValue.
    234      */
    235     public DerValue getDerValue() {
    236         return value;
    237     }
    238 
    239     /**
    240      * Get the value of this AVA as a String.
    241      *
    242      * @exception RuntimeException if we could not obtain the string form
    243      *    (should not occur)
    244      */
    245     public String getValueString() {
    246         try {
    247             String s = value.getAsString();
    248             if (s == null) {
    249                 throw new RuntimeException("AVA string is null");
    250             }
    251             return s;
    252         } catch (IOException e) {
    253             // should not occur
    254             throw new RuntimeException("AVA error: " + e, e);
    255         }
    256     }
    257 
    258     private static DerValue parseHexString
    259         (Reader in, int format) throws IOException {
    260 
    261         int c;
    262         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    263         byte b = 0;
    264         int cNdx = 0;
    265         while (true) {
    266             c = in.read();
    267 
    268             if (isTerminator(c, format)) {
    269                 break;
    270             }
    271 
    272             // BEGIN Android-added: AVA: Support DerValue hex strings that contain ' ' or '\n'
    273             if (c == ' ' || c == '\n') {
    274                 do {
    275                     if (c != ' ' && c != '\n') {
    276                         throw new IOException("AVA parse, invalid hex " + "digit: "+ (char)c);
    277                     }
    278                     c = in.read();
    279                 } while (!isTerminator(c, format));
    280                 break;
    281             }
    282             // END Android-added: AVA: Support DerValue hex strings that contain ' ' or '\n'
    283             int cVal = hexDigits.indexOf(Character.toUpperCase((char)c));
    284 
    285             if (cVal == -1) {
    286                 throw new IOException("AVA parse, invalid hex " +
    287                                               "digit: "+ (char)c);
    288             }
    289 
    290             if ((cNdx % 2) == 1) {
    291                 b = (byte)((b * 16) + (byte)(cVal));
    292                 baos.write(b);
    293             } else {
    294                 b = (byte)(cVal);
    295             }
    296             cNdx++;
    297         }
    298 
    299         // throw exception if no hex digits
    300         if (cNdx == 0) {
    301             throw new IOException("AVA parse, zero hex digits");
    302         }
    303 
    304         // throw exception if odd number of hex digits
    305         if (cNdx % 2 == 1) {
    306             throw new IOException("AVA parse, odd number of hex digits");
    307         }
    308 
    309         return new DerValue(baos.toByteArray());
    310     }
    311 
    312     private DerValue parseQuotedString
    313         (Reader in, StringBuilder temp) throws IOException {
    314 
    315         // RFC1779 specifies that an entire RDN may be enclosed in double
    316         // quotes. In this case the syntax is any sequence of
    317         // backslash-specialChar, backslash-backslash,
    318         // backslash-doublequote, or character other than backslash or
    319         // doublequote.
    320         int c = readChar(in, "Quoted string did not end in quote");
    321 
    322         List<Byte> embeddedHex = new ArrayList<Byte>();
    323         boolean isPrintableString = true;
    324         while (c != '"') {
    325             if (c == '\\') {
    326                 c = readChar(in, "Quoted string did not end in quote");
    327 
    328                 // check for embedded hex pairs
    329                 Byte hexByte = null;
    330                 if ((hexByte = getEmbeddedHexPair(c, in)) != null) {
    331 
    332                     // always encode AVAs with embedded hex as UTF8
    333                     isPrintableString = false;
    334 
    335                     // append consecutive embedded hex
    336                     // as single string later
    337                     embeddedHex.add(hexByte);
    338                     c = in.read();
    339                     continue;
    340                 }
    341 
    342                 if (specialChars1779.indexOf((char)c) < 0) {
    343                     throw new IOException
    344                         ("Invalid escaped character in AVA: " +
    345                         (char)c);
    346                 }
    347             }
    348 
    349             // add embedded hex bytes before next char
    350             if (embeddedHex.size() > 0) {
    351                 String hexString = getEmbeddedHexString(embeddedHex);
    352                 temp.append(hexString);
    353                 embeddedHex.clear();
    354             }
    355 
    356             // check for non-PrintableString chars
    357             isPrintableString &= DerValue.isPrintableStringChar((char)c);
    358             temp.append((char)c);
    359             c = readChar(in, "Quoted string did not end in quote");
    360         }
    361 
    362         // add trailing embedded hex bytes
    363         if (embeddedHex.size() > 0) {
    364             String hexString = getEmbeddedHexString(embeddedHex);
    365             temp.append(hexString);
    366             embeddedHex.clear();
    367         }
    368 
    369         do {
    370             c = in.read();
    371         } while ((c == '\n') || (c == ' '));
    372         if (c != -1) {
    373             throw new IOException("AVA had characters other than "
    374                     + "whitespace after terminating quote");
    375         }
    376 
    377         // encode as PrintableString unless value contains
    378         // non-PrintableString chars
    379         if (this.oid.equals((Object)PKCS9Attribute.EMAIL_ADDRESS_OID) ||
    380             (this.oid.equals((Object)X500Name.DOMAIN_COMPONENT_OID) &&
    381                 PRESERVE_OLD_DC_ENCODING == false)) {
    382             // EmailAddress and DomainComponent must be IA5String
    383             return new DerValue(DerValue.tag_IA5String,
    384             // Android-changed: Do not trim() DerValue strings.
    385             //                            temp.toString().trim());
    386                                         temp.toString());
    387         } else if (isPrintableString) {
    388             // Android-changed: Do not trim() DerValue strings.
    389             //return new DerValue(temp.toString().trim());
    390             return new DerValue(temp.toString());
    391         } else {
    392             return new DerValue(DerValue.tag_UTF8String,
    393             // Android-changed: Do not trim() DerValue strings.
    394             //                            temp.toString().trim());
    395                                         temp.toString());
    396         }
    397     }
    398 
    399     private DerValue parseString
    400         (Reader in, int c, int format, StringBuilder temp) throws IOException {
    401 
    402         List<Byte> embeddedHex = new ArrayList<>();
    403         boolean isPrintableString = true;
    404         boolean escape = false;
    405         boolean leadingChar = true;
    406         int spaceCount = 0;
    407         do {
    408             escape = false;
    409             if (c == '\\') {
    410                 escape = true;
    411                 c = readChar(in, "Invalid trailing backslash");
    412 
    413                 // check for embedded hex pairs
    414                 Byte hexByte = null;
    415                 if ((hexByte = getEmbeddedHexPair(c, in)) != null) {
    416 
    417                     // always encode AVAs with embedded hex as UTF8
    418                     isPrintableString = false;
    419 
    420                     // append consecutive embedded hex
    421                     // as single string later
    422                     embeddedHex.add(hexByte);
    423                     c = in.read();
    424                     leadingChar = false;
    425                     continue;
    426                 }
    427 
    428                 // check if character was improperly escaped
    429                 if (format == DEFAULT &&
    430                        specialCharsDefault.indexOf((char)c) == -1) {
    431                     throw new IOException
    432                         ("Invalid escaped character in AVA: '" +
    433                         (char)c + "'");
    434                 } else if (format == RFC2253) {
    435                     if (c == ' ') {
    436                         // only leading/trailing space can be escaped
    437                         if (!leadingChar && !trailingSpace(in)) {
    438                             throw new IOException
    439                                     ("Invalid escaped space character " +
    440                                     "in AVA.  Only a leading or trailing " +
    441                                     "space character can be escaped.");
    442                         }
    443                     } else if (c == '#') {
    444                         // only leading '#' can be escaped
    445                         if (!leadingChar) {
    446                             throw new IOException
    447                                 ("Invalid escaped '#' character in AVA.  " +
    448                                 "Only a leading '#' can be escaped.");
    449                         }
    450                     } else if (specialChars2253.indexOf((char)c) == -1) {
    451                         throw new IOException
    452                                 ("Invalid escaped character in AVA: '" +
    453                                 (char)c + "'");
    454                     }
    455                 }
    456             } else {
    457                 // check if character should have been escaped
    458                 if (format == RFC2253) {
    459                     if (specialChars2253.indexOf((char)c) != -1) {
    460                         throw new IOException
    461                                 ("Character '" + (char)c +
    462                                  "' in AVA appears without escape");
    463                     }
    464                 }
    465                 // BEGIN Android-removed
    466                 // Added in jdk860. rev/d1c04dac850d
    467                 // If present breaks X500PrincipalTest#testIllegalInputName_07
    468                 // else if (escapedDefault.indexOf((char)c) != -1) {
    469                 //    throw new IOException
    470                 //            ("Character '" + (char)c +
    471                 //            "' in AVA appears without escape");
    472                 // }
    473             }
    474 
    475             // add embedded hex bytes before next char
    476             if (embeddedHex.size() > 0) {
    477                 // add space(s) before embedded hex bytes
    478                 for (int i = 0; i < spaceCount; i++) {
    479                     temp.append(" ");
    480                 }
    481                 spaceCount = 0;
    482 
    483                 String hexString = getEmbeddedHexString(embeddedHex);
    484                 temp.append(hexString);
    485                 embeddedHex.clear();
    486             }
    487 
    488             // check for non-PrintableString chars
    489             isPrintableString &= DerValue.isPrintableStringChar((char)c);
    490             if (c == ' ' && escape == false) {
    491                 // do not add non-escaped spaces yet
    492                 // (non-escaped trailing spaces are ignored)
    493                 spaceCount++;
    494             } else {
    495                 // add space(s)
    496                 for (int i = 0; i < spaceCount; i++) {
    497                     temp.append(" ");
    498                 }
    499                 spaceCount = 0;
    500                 temp.append((char)c);
    501             }
    502             c = in.read();
    503             leadingChar = false;
    504         } while (isTerminator(c, format) == false);
    505 
    506         if (format == RFC2253 && spaceCount > 0) {
    507             throw new IOException("Incorrect AVA RFC2253 format - " +
    508                                         "trailing space must be escaped");
    509         }
    510 
    511         // add trailing embedded hex bytes
    512         if (embeddedHex.size() > 0) {
    513             String hexString = getEmbeddedHexString(embeddedHex);
    514             temp.append(hexString);
    515             embeddedHex.clear();
    516         }
    517 
    518         // encode as PrintableString unless value contains
    519         // non-PrintableString chars
    520         if (this.oid.equals((Object)PKCS9Attribute.EMAIL_ADDRESS_OID) ||
    521             (this.oid.equals((Object)X500Name.DOMAIN_COMPONENT_OID) &&
    522                 PRESERVE_OLD_DC_ENCODING == false)) {
    523             // EmailAddress and DomainComponent must be IA5String
    524             return new DerValue(DerValue.tag_IA5String, temp.toString());
    525         } else if (isPrintableString) {
    526             return new DerValue(temp.toString());
    527         } else {
    528             return new DerValue(DerValue.tag_UTF8String, temp.toString());
    529         }
    530     }
    531 
    532     private static Byte getEmbeddedHexPair(int c1, Reader in)
    533         throws IOException {
    534 
    535         if (hexDigits.indexOf(Character.toUpperCase((char)c1)) >= 0) {
    536             int c2 = readChar(in, "unexpected EOF - " +
    537                         "escaped hex value must include two valid digits");
    538 
    539             if (hexDigits.indexOf(Character.toUpperCase((char)c2)) >= 0) {
    540                 int hi = Character.digit((char)c1, 16);
    541                 int lo = Character.digit((char)c2, 16);
    542                 return new Byte((byte)((hi<<4) + lo));
    543             } else {
    544                 throw new IOException
    545                         ("escaped hex value must include two valid digits");
    546             }
    547         }
    548         return null;
    549     }
    550 
    551     private static String getEmbeddedHexString(List<Byte> hexList)
    552                                                 throws IOException {
    553         int n = hexList.size();
    554         byte[] hexBytes = new byte[n];
    555         for (int i = 0; i < n; i++) {
    556                 hexBytes[i] = hexList.get(i).byteValue();
    557         }
    558         return new String(hexBytes, "UTF8");
    559     }
    560 
    561     private static boolean isTerminator(int ch, int format) {
    562         switch (ch) {
    563         case -1:
    564         case '+':
    565         case ',':
    566             return true;
    567         case ';':
    568             return format != RFC2253;
    569         default:
    570             return false;
    571         }
    572     }
    573 
    574     private static int readChar(Reader in, String errMsg) throws IOException {
    575         int c = in.read();
    576         if (c == -1) {
    577             throw new IOException(errMsg);
    578         }
    579         return c;
    580     }
    581 
    582     private static boolean trailingSpace(Reader in) throws IOException {
    583 
    584         boolean trailing = false;
    585 
    586         if (!in.markSupported()) {
    587             // oh well
    588             return true;
    589         } else {
    590             // make readAheadLimit huge -
    591             // in practice, AVA was passed a StringReader from X500Name,
    592             // and StringReader ignores readAheadLimit anyways
    593             in.mark(9999);
    594             while (true) {
    595                 int nextChar = in.read();
    596                 if (nextChar == -1) {
    597                     trailing = true;
    598                     break;
    599                 } else if (nextChar == ' ') {
    600                     continue;
    601                 } else if (nextChar == '\\') {
    602                     int followingChar = in.read();
    603                     if (followingChar != ' ') {
    604                         trailing = false;
    605                         break;
    606                     }
    607                 } else {
    608                     trailing = false;
    609                     break;
    610                 }
    611             }
    612 
    613             in.reset();
    614             return trailing;
    615         }
    616     }
    617 
    618     AVA(DerValue derval) throws IOException {
    619         // Individual attribute value assertions are SEQUENCE of two values.
    620         // That'd be a "struct" outside of ASN.1.
    621         if (derval.tag != DerValue.tag_Sequence) {
    622             throw new IOException("AVA not a sequence");
    623         }
    624         oid = X500Name.intern(derval.data.getOID());
    625         value = derval.data.getDerValue();
    626 
    627         if (derval.data.available() != 0) {
    628             throw new IOException("AVA, extra bytes = "
    629                 + derval.data.available());
    630         }
    631     }
    632 
    633     AVA(DerInputStream in) throws IOException {
    634         this(in.getDerValue());
    635     }
    636 
    637     public boolean equals(Object obj) {
    638         if (this == obj) {
    639             return true;
    640         }
    641         if (obj instanceof AVA == false) {
    642             return false;
    643         }
    644         AVA other = (AVA)obj;
    645         return this.toRFC2253CanonicalString().equals
    646                                 (other.toRFC2253CanonicalString());
    647     }
    648 
    649     /**
    650      * Returns a hashcode for this AVA.
    651      *
    652      * @return a hashcode for this AVA.
    653      */
    654     public int hashCode() {
    655         return toRFC2253CanonicalString().hashCode();
    656     }
    657 
    658     /*
    659      * AVAs are encoded as a SEQUENCE of two elements.
    660      */
    661     public void encode(DerOutputStream out) throws IOException {
    662         derEncode(out);
    663     }
    664 
    665     /**
    666      * DER encode this object onto an output stream.
    667      * Implements the <code>DerEncoder</code> interface.
    668      *
    669      * @param out
    670      * the output stream on which to write the DER encoding.
    671      *
    672      * @exception IOException on encoding error.
    673      */
    674     public void derEncode(OutputStream out) throws IOException {
    675         DerOutputStream         tmp = new DerOutputStream();
    676         DerOutputStream         tmp2 = new DerOutputStream();
    677 
    678         tmp.putOID(oid);
    679         value.encode(tmp);
    680         tmp2.write(DerValue.tag_Sequence, tmp);
    681         out.write(tmp2.toByteArray());
    682     }
    683 
    684     private String toKeyword(int format, Map<String, String> oidMap) {
    685         return AVAKeyword.getKeyword(oid, format, oidMap);
    686     }
    687 
    688     /**
    689      * Returns a printable form of this attribute, using RFC 1779
    690      * syntax for individual attribute/value assertions.
    691      */
    692     public String toString() {
    693         return toKeywordValueString
    694             (toKeyword(DEFAULT, Collections.<String, String>emptyMap()));
    695     }
    696 
    697     /**
    698      * Returns a printable form of this attribute, using RFC 1779
    699      * syntax for individual attribute/value assertions. It only
    700      * emits standardised keywords.
    701      */
    702     public String toRFC1779String() {
    703         return toRFC1779String(Collections.<String, String>emptyMap());
    704     }
    705 
    706     /**
    707      * Returns a printable form of this attribute, using RFC 1779
    708      * syntax for individual attribute/value assertions. It
    709      * emits standardised keywords, as well as keywords contained in the
    710      * OID/keyword map.
    711      */
    712     public String toRFC1779String(Map<String, String> oidMap) {
    713         return toKeywordValueString(toKeyword(RFC1779, oidMap));
    714     }
    715 
    716     /**
    717      * Returns a printable form of this attribute, using RFC 2253
    718      * syntax for individual attribute/value assertions. It only
    719      * emits standardised keywords.
    720      */
    721     public String toRFC2253String() {
    722         return toRFC2253String(Collections.<String, String>emptyMap());
    723     }
    724 
    725     /**
    726      * Returns a printable form of this attribute, using RFC 2253
    727      * syntax for individual attribute/value assertions. It
    728      * emits standardised keywords, as well as keywords contained in the
    729      * OID/keyword map.
    730      */
    731     public String toRFC2253String(Map<String, String> oidMap) {
    732         /*
    733          * Section 2.3: The AttributeTypeAndValue is encoded as the string
    734          * representation of the AttributeType, followed by an equals character
    735          * ('=' ASCII 61), followed by the string representation of the
    736          * AttributeValue. The encoding of the AttributeValue is given in
    737          * section 2.4.
    738          */
    739         StringBuilder typeAndValue = new StringBuilder(100);
    740         typeAndValue.append(toKeyword(RFC2253, oidMap));
    741         typeAndValue.append('=');
    742 
    743         /*
    744          * Section 2.4: Converting an AttributeValue from ASN.1 to a String.
    745          * If the AttributeValue is of a type which does not have a string
    746          * representation defined for it, then it is simply encoded as an
    747          * octothorpe character ('#' ASCII 35) followed by the hexadecimal
    748          * representation of each of the bytes of the BER encoding of the X.500
    749          * AttributeValue.  This form SHOULD be used if the AttributeType is of
    750          * the dotted-decimal form.
    751          */
    752         if ((typeAndValue.charAt(0) >= '0' && typeAndValue.charAt(0) <= '9') ||
    753             !isDerString(value, false))
    754         {
    755             byte[] data = null;
    756             try {
    757                 data = value.toByteArray();
    758             } catch (IOException ie) {
    759                 throw new IllegalArgumentException("DER Value conversion");
    760             }
    761             typeAndValue.append('#');
    762             for (int j = 0; j < data.length; j++) {
    763                 byte b = data[j];
    764                 typeAndValue.append(Character.forDigit(0xF & (b >>> 4), 16));
    765                 typeAndValue.append(Character.forDigit(0xF & b, 16));
    766             }
    767         } else {
    768             /*
    769              * 2.4 (cont): Otherwise, if the AttributeValue is of a type which
    770              * has a string representation, the value is converted first to a
    771              * UTF-8 string according to its syntax specification.
    772              *
    773              * NOTE: this implementation only emits DirectoryStrings of the
    774              * types returned by isDerString().
    775              */
    776             String valStr = null;
    777             try {
    778                 valStr = new String(value.getDataBytes(), "UTF8");
    779             } catch (IOException ie) {
    780                 throw new IllegalArgumentException("DER Value conversion");
    781             }
    782 
    783             /*
    784              * 2.4 (cont): If the UTF-8 string does not have any of the
    785              * following characters which need escaping, then that string can be
    786              * used as the string representation of the value.
    787              *
    788              *   o   a space or "#" character occurring at the beginning of the
    789              *       string
    790              *   o   a space character occurring at the end of the string
    791              *   o   one of the characters ",", "+", """, "\", "<", ">" or ";"
    792              *
    793              * Implementations MAY escape other characters.
    794              *
    795              * NOTE: this implementation also recognizes "=" and "#" as
    796              * characters which need escaping, and null which is escaped as
    797              * '\00' (see RFC 4514).
    798              *
    799              * If a character to be escaped is one of the list shown above, then
    800              * it is prefixed by a backslash ('\' ASCII 92).
    801              *
    802              * Otherwise the character to be escaped is replaced by a backslash
    803              * and two hex digits, which form a single byte in the code of the
    804              * character.
    805              */
    806             final String escapees = ",=+<>#;\"\\";
    807             StringBuilder sbuffer = new StringBuilder();
    808 
    809             for (int i = 0; i < valStr.length(); i++) {
    810                 char c = valStr.charAt(i);
    811                 if (DerValue.isPrintableStringChar(c) ||
    812                     escapees.indexOf(c) >= 0) {
    813 
    814                     // escape escapees
    815                     if (escapees.indexOf(c) >= 0) {
    816                         sbuffer.append('\\');
    817                     }
    818 
    819                     // append printable/escaped char
    820                     sbuffer.append(c);
    821 
    822                 } else if (c == '\u0000') {
    823                     // escape null character
    824                     sbuffer.append("\\00");
    825 
    826                 } else if (debug != null && Debug.isOn("ava")) {
    827 
    828                     // embed non-printable/non-escaped char
    829                     // as escaped hex pairs for debugging
    830                     byte[] valueBytes = null;
    831                     try {
    832                         valueBytes = Character.toString(c).getBytes("UTF8");
    833                     } catch (IOException ie) {
    834                         throw new IllegalArgumentException
    835                                         ("DER Value conversion");
    836                     }
    837                     for (int j = 0; j < valueBytes.length; j++) {
    838                         sbuffer.append('\\');
    839                         char hexChar = Character.forDigit
    840                                 (0xF & (valueBytes[j] >>> 4), 16);
    841                         sbuffer.append(Character.toUpperCase(hexChar));
    842                         hexChar = Character.forDigit
    843                                 (0xF & (valueBytes[j]), 16);
    844                         sbuffer.append(Character.toUpperCase(hexChar));
    845                     }
    846                 } else {
    847 
    848                     // append non-printable/non-escaped char
    849                     sbuffer.append(c);
    850                 }
    851             }
    852 
    853             char[] chars = sbuffer.toString().toCharArray();
    854             sbuffer = new StringBuilder();
    855 
    856             // Find leading and trailing whitespace.
    857             int lead;   // index of first char that is not leading whitespace
    858             for (lead = 0; lead < chars.length; lead++) {
    859                 if (chars[lead] != ' ' && chars[lead] != '\r') {
    860                     break;
    861                 }
    862             }
    863             int trail;  // index of last char that is not trailing whitespace
    864             for (trail = chars.length - 1; trail >= 0; trail--) {
    865                 if (chars[trail] != ' ' && chars[trail] != '\r') {
    866                     break;
    867                 }
    868             }
    869 
    870             // escape leading and trailing whitespace
    871             for (int i = 0; i < chars.length; i++) {
    872                 char c = chars[i];
    873                 if (i < lead || i > trail) {
    874                     sbuffer.append('\\');
    875                 }
    876                 sbuffer.append(c);
    877             }
    878             typeAndValue.append(sbuffer.toString());
    879         }
    880         return typeAndValue.toString();
    881     }
    882 
    883     public String toRFC2253CanonicalString() {
    884         /*
    885          * Section 2.3: The AttributeTypeAndValue is encoded as the string
    886          * representation of the AttributeType, followed by an equals character
    887          * ('=' ASCII 61), followed by the string representation of the
    888          * AttributeValue. The encoding of the AttributeValue is given in
    889          * section 2.4.
    890          */
    891         StringBuilder typeAndValue = new StringBuilder(40);
    892         typeAndValue.append
    893             (toKeyword(RFC2253, Collections.<String, String>emptyMap()));
    894         typeAndValue.append('=');
    895 
    896         /*
    897          * Section 2.4: Converting an AttributeValue from ASN.1 to a String.
    898          * If the AttributeValue is of a type which does not have a string
    899          * representation defined for it, then it is simply encoded as an
    900          * octothorpe character ('#' ASCII 35) followed by the hexadecimal
    901          * representation of each of the bytes of the BER encoding of the X.500
    902          * AttributeValue.  This form SHOULD be used if the AttributeType is of
    903          * the dotted-decimal form.
    904          */
    905         if ((typeAndValue.charAt(0) >= '0' && typeAndValue.charAt(0) <= '9') ||
    906             // Android-changed: AVA: Support DerValue hex strings that contain ' ' or '\n'
    907             //!isDerString(value, true))
    908             (!isDerString(value, true) && value.tag != DerValue.tag_T61String))
    909         {
    910             byte[] data = null;
    911             try {
    912                 data = value.toByteArray();
    913             } catch (IOException ie) {
    914                 throw new IllegalArgumentException("DER Value conversion");
    915             }
    916             typeAndValue.append('#');
    917             for (int j = 0; j < data.length; j++) {
    918                 byte b = data[j];
    919                 typeAndValue.append(Character.forDigit(0xF & (b >>> 4), 16));
    920                 typeAndValue.append(Character.forDigit(0xF & b, 16));
    921             }
    922         } else {
    923             /*
    924              * 2.4 (cont): Otherwise, if the AttributeValue is of a type which
    925              * has a string representation, the value is converted first to a
    926              * UTF-8 string according to its syntax specification.
    927              *
    928              * NOTE: this implementation only emits DirectoryStrings of the
    929              * types returned by isDerString().
    930              */
    931             String valStr = null;
    932             try {
    933                 valStr = new String(value.getDataBytes(), "UTF8");
    934             } catch (IOException ie) {
    935                 throw new IllegalArgumentException("DER Value conversion");
    936             }
    937 
    938             /*
    939              * 2.4 (cont): If the UTF-8 string does not have any of the
    940              * following characters which need escaping, then that string can be
    941              * used as the string representation of the value.
    942              *
    943              *   o   a space or "#" character occurring at the beginning of the
    944              *       string
    945              *   o   a space character occurring at the end of the string
    946              *
    947              *   o   one of the characters ",", "+", """, "\", "<", ">" or ";"
    948              *
    949              * If a character to be escaped is one of the list shown above, then
    950              * it is prefixed by a backslash ('\' ASCII 92).
    951              *
    952              * Otherwise the character to be escaped is replaced by a backslash
    953              * and two hex digits, which form a single byte in the code of the
    954              * character.
    955              */
    956             final String escapees = ",+<>;\"\\";
    957             StringBuilder sbuffer = new StringBuilder();
    958             boolean previousWhite = false;
    959 
    960             for (int i = 0; i < valStr.length(); i++) {
    961                 char c = valStr.charAt(i);
    962 
    963                 if (DerValue.isPrintableStringChar(c) ||
    964                     escapees.indexOf(c) >= 0 ||
    965                     (i == 0 && c == '#')) {
    966 
    967                     // escape leading '#' and escapees
    968                     if ((i == 0 && c == '#') || escapees.indexOf(c) >= 0) {
    969                         sbuffer.append('\\');
    970                     }
    971 
    972                     // convert multiple whitespace to single whitespace
    973                     if (!Character.isWhitespace(c)) {
    974                         previousWhite = false;
    975                         sbuffer.append(c);
    976                     } else {
    977                         if (previousWhite == false) {
    978                             // add single whitespace
    979                             previousWhite = true;
    980                             sbuffer.append(c);
    981                         } else {
    982                             // ignore subsequent consecutive whitespace
    983                             continue;
    984                         }
    985                     }
    986 
    987                 } else if (debug != null && Debug.isOn("ava")) {
    988 
    989                     // embed non-printable/non-escaped char
    990                     // as escaped hex pairs for debugging
    991 
    992                     previousWhite = false;
    993 
    994                     byte valueBytes[] = null;
    995                     try {
    996                         valueBytes = Character.toString(c).getBytes("UTF8");
    997                     } catch (IOException ie) {
    998                         throw new IllegalArgumentException
    999                                         ("DER Value conversion");
   1000                     }
   1001                     for (int j = 0; j < valueBytes.length; j++) {
   1002                         sbuffer.append('\\');
   1003                         sbuffer.append(Character.forDigit
   1004                                         (0xF & (valueBytes[j] >>> 4), 16));
   1005                         sbuffer.append(Character.forDigit
   1006                                         (0xF & (valueBytes[j]), 16));
   1007                     }
   1008                 } else {
   1009 
   1010                     // append non-printable/non-escaped char
   1011 
   1012                     previousWhite = false;
   1013                     sbuffer.append(c);
   1014                 }
   1015             }
   1016 
   1017             // remove leading and trailing whitespace from value
   1018             typeAndValue.append(sbuffer.toString().trim());
   1019         }
   1020 
   1021         String canon = typeAndValue.toString();
   1022         canon = canon.toUpperCase(Locale.US).toLowerCase(Locale.US);
   1023         return Normalizer.normalize(canon, Normalizer.Form.NFKD);
   1024     }
   1025 
   1026     /*
   1027      * Return true if DerValue can be represented as a String.
   1028      */
   1029     private static boolean isDerString(DerValue value, boolean canonical) {
   1030         if (canonical) {
   1031             switch (value.tag) {
   1032                 case DerValue.tag_PrintableString:
   1033                 case DerValue.tag_UTF8String:
   1034                     return true;
   1035                 default:
   1036                     return false;
   1037             }
   1038         } else {
   1039             switch (value.tag) {
   1040                 case DerValue.tag_PrintableString:
   1041                 case DerValue.tag_T61String:
   1042                 case DerValue.tag_IA5String:
   1043                 case DerValue.tag_GeneralString:
   1044                 case DerValue.tag_BMPString:
   1045                 case DerValue.tag_UTF8String:
   1046                     return true;
   1047                 default:
   1048                     return false;
   1049             }
   1050         }
   1051     }
   1052 
   1053     boolean hasRFC2253Keyword() {
   1054         return AVAKeyword.hasKeyword(oid, RFC2253);
   1055     }
   1056 
   1057     private String toKeywordValueString(String keyword) {
   1058         /*
   1059          * Construct the value with as little copying and garbage
   1060          * production as practical.  First the keyword (mandatory),
   1061          * then the equals sign, finally the value.
   1062          */
   1063         StringBuilder   retval = new StringBuilder(40);
   1064 
   1065         retval.append(keyword);
   1066         retval.append("=");
   1067 
   1068         try {
   1069             String valStr = value.getAsString();
   1070 
   1071             if (valStr == null) {
   1072 
   1073                 // rfc1779 specifies that attribute values associated
   1074                 // with non-standard keyword attributes may be represented
   1075                 // using the hex format below.  This will be used only
   1076                 // when the value is not a string type
   1077 
   1078                 byte    data [] = value.toByteArray();
   1079 
   1080                 retval.append('#');
   1081                 for (int i = 0; i < data.length; i++) {
   1082                     retval.append(hexDigits.charAt((data [i] >> 4) & 0x0f));
   1083                     retval.append(hexDigits.charAt(data [i] & 0x0f));
   1084                 }
   1085 
   1086             } else {
   1087 
   1088                 boolean quoteNeeded = false;
   1089                 StringBuilder sbuffer = new StringBuilder();
   1090                 boolean previousWhite = false;
   1091                 final String escapees = ",+=\n<>#;\\\"";
   1092 
   1093                 /*
   1094                  * Special characters (e.g. AVA list separators) cause strings
   1095                  * to need quoting, or at least escaping.  So do leading or
   1096                  * trailing spaces, and multiple internal spaces.
   1097                  */
   1098                 int length = valStr.length();
   1099                 boolean alreadyQuoted =
   1100                     (length > 1 && valStr.charAt(0) == '\"'
   1101                      && valStr.charAt(length - 1) == '\"');
   1102 
   1103                 for (int i = 0; i < length; i++) {
   1104                     char c = valStr.charAt(i);
   1105                     if (alreadyQuoted && (i == 0 || i == length - 1)) {
   1106                         sbuffer.append(c);
   1107                         continue;
   1108                     }
   1109                     if (DerValue.isPrintableStringChar(c) ||
   1110                         escapees.indexOf(c) >= 0) {
   1111 
   1112                         // quote if leading whitespace or special chars
   1113                         if (!quoteNeeded &&
   1114                             ((i == 0 && (c == ' ' || c == '\n')) ||
   1115                                 escapees.indexOf(c) >= 0)) {
   1116                             quoteNeeded = true;
   1117                         }
   1118 
   1119                         // quote if multiple internal whitespace
   1120                         if (!(c == ' ' || c == '\n')) {
   1121                             // escape '"' and '\'
   1122                             if (c == '"' || c == '\\') {
   1123                                 sbuffer.append('\\');
   1124                             }
   1125                             previousWhite = false;
   1126                         } else {
   1127                             if (!quoteNeeded && previousWhite) {
   1128                                 quoteNeeded = true;
   1129                             }
   1130                             previousWhite = true;
   1131                         }
   1132 
   1133                         sbuffer.append(c);
   1134 
   1135                     } else if (debug != null && Debug.isOn("ava")) {
   1136 
   1137                         // embed non-printable/non-escaped char
   1138                         // as escaped hex pairs for debugging
   1139 
   1140                         previousWhite = false;
   1141 
   1142                         // embed escaped hex pairs
   1143                         byte[] valueBytes =
   1144                                 Character.toString(c).getBytes("UTF8");
   1145                         for (int j = 0; j < valueBytes.length; j++) {
   1146                             sbuffer.append('\\');
   1147                             char hexChar = Character.forDigit
   1148                                         (0xF & (valueBytes[j] >>> 4), 16);
   1149                             sbuffer.append(Character.toUpperCase(hexChar));
   1150                             hexChar = Character.forDigit
   1151                                         (0xF & (valueBytes[j]), 16);
   1152                             sbuffer.append(Character.toUpperCase(hexChar));
   1153                         }
   1154                     } else {
   1155 
   1156                         // append non-printable/non-escaped char
   1157 
   1158                         previousWhite = false;
   1159                         sbuffer.append(c);
   1160                     }
   1161                 }
   1162 
   1163                 // quote if trailing whitespace
   1164                 if (sbuffer.length() > 0) {
   1165                     char trailChar = sbuffer.charAt(sbuffer.length() - 1);
   1166                     if (trailChar == ' ' || trailChar == '\n') {
   1167                         quoteNeeded = true;
   1168                     }
   1169                 }
   1170 
   1171                 // Emit the string ... quote it if needed
   1172                 // if string is already quoted, don't re-quote
   1173                 if (!alreadyQuoted && quoteNeeded) {
   1174                     retval.append("\"" + sbuffer.toString() + "\"");
   1175                 } else {
   1176                     retval.append(sbuffer.toString());
   1177                 }
   1178             }
   1179         } catch (IOException e) {
   1180             throw new IllegalArgumentException("DER Value conversion");
   1181         }
   1182 
   1183         return retval.toString();
   1184     }
   1185 
   1186 }
   1187 
   1188 /**
   1189  * Helper class that allows conversion from String to ObjectIdentifier and
   1190  * vice versa according to RFC1779, RFC2253, and an augmented version of
   1191  * those standards.
   1192  */
   1193 class AVAKeyword {
   1194 
   1195     private static final Map<ObjectIdentifier,AVAKeyword> oidMap;
   1196     private static final Map<String,AVAKeyword> keywordMap;
   1197 
   1198     private String keyword;
   1199     private ObjectIdentifier oid;
   1200     private boolean rfc1779Compliant, rfc2253Compliant;
   1201 
   1202     private AVAKeyword(String keyword, ObjectIdentifier oid,
   1203                boolean rfc1779Compliant, boolean rfc2253Compliant) {
   1204         this.keyword = keyword;
   1205         this.oid = oid;
   1206         this.rfc1779Compliant = rfc1779Compliant;
   1207         this.rfc2253Compliant = rfc2253Compliant;
   1208 
   1209         // register it
   1210         oidMap.put(oid, this);
   1211         keywordMap.put(keyword, this);
   1212     }
   1213 
   1214     private boolean isCompliant(int standard) {
   1215         switch (standard) {
   1216         case AVA.RFC1779:
   1217             return rfc1779Compliant;
   1218         case AVA.RFC2253:
   1219             return rfc2253Compliant;
   1220         case AVA.DEFAULT:
   1221             return true;
   1222         default:
   1223             // should not occur, internal error
   1224             throw new IllegalArgumentException("Invalid standard " + standard);
   1225         }
   1226     }
   1227 
   1228     /**
   1229      * Get an object identifier representing the specified keyword (or
   1230      * string encoded object identifier) in the given standard.
   1231      *
   1232      * @param keywordMap a Map where a keyword String maps to a corresponding
   1233      *   OID String. Each AVA keyword will be mapped to the corresponding OID.
   1234      *   If an entry does not exist, it will fallback to the builtin
   1235      *   keyword/OID mapping.
   1236      * @throws IOException If the keyword is not valid in the specified standard
   1237      *   or the OID String to which a keyword maps to is improperly formatted.
   1238      */
   1239     static ObjectIdentifier getOID
   1240         (String keyword, int standard, Map<String, String> extraKeywordMap)
   1241             throws IOException {
   1242 
   1243         keyword = keyword.toUpperCase(Locale.ENGLISH);
   1244         if (standard == AVA.RFC2253) {
   1245             if (keyword.startsWith(" ") || keyword.endsWith(" ")) {
   1246                 throw new IOException("Invalid leading or trailing space " +
   1247                         "in keyword \"" + keyword + "\"");
   1248             }
   1249         } else {
   1250             keyword = keyword.trim();
   1251         }
   1252 
   1253         // check user-specified keyword map first, then fallback to built-in
   1254         // map
   1255         String oidString = extraKeywordMap.get(keyword);
   1256         if (oidString == null) {
   1257             AVAKeyword ak = keywordMap.get(keyword);
   1258             if ((ak != null) && ak.isCompliant(standard)) {
   1259                 return ak.oid;
   1260             }
   1261         } else {
   1262             return new ObjectIdentifier(oidString);
   1263         }
   1264 
   1265         // no keyword found, check if OID string
   1266         if (standard == AVA.DEFAULT && keyword.startsWith("OID.")) {
   1267             keyword = keyword.substring(4);
   1268         }
   1269 
   1270         boolean number = false;
   1271         if (keyword.length() != 0) {
   1272             char ch = keyword.charAt(0);
   1273             if ((ch >= '0') && (ch <= '9')) {
   1274                 number = true;
   1275             }
   1276         }
   1277         if (number == false) {
   1278             throw new IOException("Invalid keyword \"" + keyword + "\"");
   1279         }
   1280         return new ObjectIdentifier(keyword);
   1281     }
   1282 
   1283     /**
   1284      * Get a keyword for the given ObjectIdentifier according to standard.
   1285      * If no keyword is available, the ObjectIdentifier is encoded as a
   1286      * String.
   1287      */
   1288     static String getKeyword(ObjectIdentifier oid, int standard) {
   1289         return getKeyword
   1290             (oid, standard, Collections.<String, String>emptyMap());
   1291     }
   1292 
   1293     /**
   1294      * Get a keyword for the given ObjectIdentifier according to standard.
   1295      * Checks the extraOidMap for a keyword first, then falls back to the
   1296      * builtin/default set. If no keyword is available, the ObjectIdentifier
   1297      * is encoded as a String.
   1298      */
   1299     static String getKeyword
   1300         (ObjectIdentifier oid, int standard, Map<String, String> extraOidMap) {
   1301 
   1302         // check extraOidMap first, then fallback to built-in map
   1303         String oidString = oid.toString();
   1304         String keywordString = extraOidMap.get(oidString);
   1305         if (keywordString == null) {
   1306             AVAKeyword ak = oidMap.get(oid);
   1307             if ((ak != null) && ak.isCompliant(standard)) {
   1308                 return ak.keyword;
   1309             }
   1310         } else {
   1311             if (keywordString.length() == 0) {
   1312                 throw new IllegalArgumentException("keyword cannot be empty");
   1313             }
   1314             keywordString = keywordString.trim();
   1315             char c = keywordString.charAt(0);
   1316             if (c < 65 || c > 122 || (c > 90 && c < 97)) {
   1317                 throw new IllegalArgumentException
   1318                     ("keyword does not start with letter");
   1319             }
   1320             for (int i=1; i<keywordString.length(); i++) {
   1321                 c = keywordString.charAt(i);
   1322                 if ((c < 65 || c > 122 || (c > 90 && c < 97)) &&
   1323                     (c < 48 || c > 57) && c != '_') {
   1324                     throw new IllegalArgumentException
   1325                     ("keyword character is not a letter, digit, or underscore");
   1326                 }
   1327             }
   1328             return keywordString;
   1329         }
   1330         // no compliant keyword, use OID
   1331         if (standard == AVA.RFC2253) {
   1332             return oidString;
   1333         } else {
   1334             return "OID." + oidString;
   1335         }
   1336     }
   1337 
   1338     /**
   1339      * Test if oid has an associated keyword in standard.
   1340      */
   1341     static boolean hasKeyword(ObjectIdentifier oid, int standard) {
   1342         AVAKeyword ak = oidMap.get(oid);
   1343         if (ak == null) {
   1344             return false;
   1345         }
   1346         return ak.isCompliant(standard);
   1347     }
   1348 
   1349     static {
   1350         oidMap = new HashMap<ObjectIdentifier,AVAKeyword>();
   1351         keywordMap = new HashMap<String,AVAKeyword>();
   1352 
   1353         // NOTE if multiple keywords are available for one OID, order
   1354         // is significant!! Preferred *LAST*.
   1355         new AVAKeyword("CN",           X500Name.commonName_oid,   true,  true);
   1356         new AVAKeyword("C",            X500Name.countryName_oid,  true,  true);
   1357         new AVAKeyword("L",            X500Name.localityName_oid, true,  true);
   1358         new AVAKeyword("S",            X500Name.stateName_oid,    false, false);
   1359         new AVAKeyword("ST",           X500Name.stateName_oid,    true,  true);
   1360         new AVAKeyword("O",            X500Name.orgName_oid,      true,  true);
   1361         new AVAKeyword("OU",           X500Name.orgUnitName_oid,  true,  true);
   1362         new AVAKeyword("T",            X500Name.title_oid,        false, false);
   1363         new AVAKeyword("IP",           X500Name.ipAddress_oid,    false, false);
   1364         new AVAKeyword("STREET",       X500Name.streetAddress_oid,true,  true);
   1365         new AVAKeyword("DC",           X500Name.DOMAIN_COMPONENT_OID,
   1366                                                                   false, true);
   1367         new AVAKeyword("DNQUALIFIER",  X500Name.DNQUALIFIER_OID,  false, false);
   1368         new AVAKeyword("DNQ",          X500Name.DNQUALIFIER_OID,  false, false);
   1369         new AVAKeyword("SURNAME",      X500Name.SURNAME_OID,      false, false);
   1370         new AVAKeyword("GIVENNAME",    X500Name.GIVENNAME_OID,    false, false);
   1371         new AVAKeyword("INITIALS",     X500Name.INITIALS_OID,     false, false);
   1372         new AVAKeyword("GENERATION",   X500Name.GENERATIONQUALIFIER_OID,
   1373                                                                   false, false);
   1374         new AVAKeyword("EMAIL", PKCS9Attribute.EMAIL_ADDRESS_OID, false, false);
   1375         new AVAKeyword("EMAILADDRESS", PKCS9Attribute.EMAIL_ADDRESS_OID,
   1376                                                                   false, false);
   1377         new AVAKeyword("UID",          X500Name.userid_oid,       false, true);
   1378         new AVAKeyword("SERIALNUMBER", X500Name.SERIALNUMBER_OID, false, false);
   1379     }
   1380 }
   1381