• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AVA(ObjectIdentifier type, DerValue val)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      */
AVA(Reader in)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      */
AVA(Reader in, Map<String, String> keywordMap)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      */
AVA(Reader in, int format)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      */
AVA(Reader in, int format, Map<String, String> keywordMap)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      */
getObjectIdentifier()232     public ObjectIdentifier getObjectIdentifier() {
233         return oid;
234     }
235 
236     /**
237      * Get the value of this AVA as a DerValue.
238      */
getDerValue()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      */
getValueString()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 
parseHexString(Reader in, int format)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 
parseQuotedString(Reader in, StringBuilder temp)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 
parseString(Reader in, int c, int format, StringBuilder temp)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 
getEmbeddedHexPair(int c1, Reader in)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 
getEmbeddedHexString(List<Byte> hexList)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 
isTerminator(int ch, int format)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 
readChar(Reader in, String errMsg)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 
trailingSpace(Reader in)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 
AVA(DerValue derval)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 
AVA(DerInputStream in)632     AVA(DerInputStream in) throws IOException {
633         this(in.getDerValue());
634     }
635 
equals(Object obj)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      */
hashCode()653     public int hashCode() {
654         return toRFC2253CanonicalString().hashCode();
655     }
656 
657     /*
658      * AVAs are encoded as a SEQUENCE of two elements.
659      */
encode(DerOutputStream out)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      */
derEncode(OutputStream out)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 
toKeyword(int format, Map<String, String> oidMap)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      */
toString()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      */
toRFC1779String()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      */
toRFC1779String(Map<String, String> oidMap)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      */
toRFC2253String()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      */
toRFC2253String(Map<String, String> oidMap)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 
toRFC2253CanonicalString()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      */
isDerString(DerValue value, boolean canonical)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 
hasRFC2253Keyword()1050     boolean hasRFC2253Keyword() {
1051         return AVAKeyword.hasKeyword(oid, RFC2253);
1052     }
1053 
toKeywordValueString(String keyword)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 
AVAKeyword(String keyword, ObjectIdentifier oid, boolean rfc1779Compliant, boolean rfc2253Compliant)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 
isCompliant(int standard)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      */
getOID(String keyword, int standard)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      */
getOID(String keyword, int standard, Map<String, String> extraKeywordMap)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      */
getKeyword(ObjectIdentifier oid, int standard)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      */
getKeyword(ObjectIdentifier oid, int standard, Map<String, String> extraOidMap)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      */
hasKeyword(ObjectIdentifier oid, int standard)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