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