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