• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 2002, 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.IOException;
30 import java.io.StringReader;
31 import java.util.Arrays;
32 import java.util.StringJoiner;
33 import java.util.*;
34 
35 import sun.security.util.*;
36 
37 /**
38  * RDNs are a set of {attribute = value} assertions.  Some of those
39  * attributes are "distinguished" (unique w/in context).  Order is
40  * never relevant.
41  *
42  * Some X.500 names include only a single distinguished attribute
43  * per RDN.  This style is currently common.
44  *
45  * Note that DER-encoded RDNs sort AVAs by assertion OID ... so that
46  * when we parse this data we don't have to worry about canonicalizing
47  * it, but we'll need to sort them when we expose the RDN class more.
48  * <p>
49  * The ASN.1 for RDNs is:
50  * <pre>
51  * RelativeDistinguishedName ::=
52  *   SET OF AttributeTypeAndValue
53  *
54  * AttributeTypeAndValue ::= SEQUENCE {
55  *   type     AttributeType,
56  *   value    AttributeValue }
57  *
58  * AttributeType ::= OBJECT IDENTIFIER
59  *
60  * AttributeValue ::= ANY DEFINED BY AttributeType
61  * </pre>
62  *
63  * Note that instances of this class are immutable.
64  *
65  */
66 public class RDN {
67 
68     // currently not private, accessed directly from X500Name
69     final AVA[] assertion;
70 
71     // cached immutable List of the AVAs
72     private volatile List<AVA> avaList;
73 
74     // cache canonical String form
75     private volatile String canonicalString;
76 
77     /**
78      * Constructs an RDN from its printable representation.
79      *
80      * An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
81      * using '+' as a separator.
82      * If the '+' should be considered part of an AVA value, it must be
83      * preceded by '\'.
84      *
85      * @param name String form of RDN
86      * @throws IOException on parsing error
87      */
RDN(String name)88     public RDN(String name) throws IOException {
89         this(name, Collections.<String, String>emptyMap());
90     }
91 
92     /**
93      * Constructs an RDN from its printable representation.
94      *
95      * An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
96      * using '+' as a separator.
97      * If the '+' should be considered part of an AVA value, it must be
98      * preceded by '\'.
99      *
100      * @param name String form of RDN
101      * @param keyword an additional mapping of keywords to OIDs
102      * @throws IOException on parsing error
103      */
RDN(String name, Map<String, String> keywordMap)104     public RDN(String name, Map<String, String> keywordMap) throws IOException {
105         int quoteCount = 0;
106         int searchOffset = 0;
107         int avaOffset = 0;
108         List<AVA> avaVec = new ArrayList<AVA>(3);
109         int nextPlus = name.indexOf('+');
110         while (nextPlus >= 0) {
111             quoteCount += X500Name.countQuotes(name, searchOffset, nextPlus);
112             /*
113              * We have encountered an AVA delimiter (plus sign).
114              * If the plus sign in the RDN under consideration is
115              * preceded by a backslash (escape), or by a double quote, it
116              * is part of the AVA. Otherwise, it is used as a separator, to
117              * delimit the AVA under consideration from any subsequent AVAs.
118              */
119             if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\'
120                 && quoteCount != 1) {
121                 /*
122                  * Plus sign is a separator
123                  */
124                 String avaString = name.substring(avaOffset, nextPlus);
125                 if (avaString.length() == 0) {
126                     throw new IOException("empty AVA in RDN \"" + name + "\"");
127                 }
128 
129                 // Parse AVA, and store it in vector
130                 AVA ava = new AVA(new StringReader(avaString), keywordMap);
131                 avaVec.add(ava);
132 
133                 // Increase the offset
134                 avaOffset = nextPlus + 1;
135 
136                 // Set quote counter back to zero
137                 quoteCount = 0;
138             }
139             searchOffset = nextPlus + 1;
140             nextPlus = name.indexOf('+', searchOffset);
141         }
142 
143         // parse last or only AVA
144         String avaString = name.substring(avaOffset);
145         if (avaString.length() == 0) {
146             throw new IOException("empty AVA in RDN \"" + name + "\"");
147         }
148         AVA ava = new AVA(new StringReader(avaString), keywordMap);
149         avaVec.add(ava);
150 
151         assertion = avaVec.toArray(new AVA[avaVec.size()]);
152     }
153 
154     /*
155      * Constructs an RDN from its printable representation.
156      *
157      * An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
158      * using '+' as a separator.
159      * If the '+' should be considered part of an AVA value, it must be
160      * preceded by '\'.
161      *
162      * @param name String form of RDN
163      * @throws IOException on parsing error
164      */
RDN(String name, String format)165     RDN(String name, String format) throws IOException {
166         this(name, format, Collections.<String, String>emptyMap());
167     }
168 
169     /*
170      * Constructs an RDN from its printable representation.
171      *
172      * An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
173      * using '+' as a separator.
174      * If the '+' should be considered part of an AVA value, it must be
175      * preceded by '\'.
176      *
177      * @param name String form of RDN
178      * @param keyword an additional mapping of keywords to OIDs
179      * @throws IOException on parsing error
180      */
RDN(String name, String format, Map<String, String> keywordMap)181     RDN(String name, String format, Map<String, String> keywordMap)
182         throws IOException {
183         if (format.equalsIgnoreCase("RFC2253") == false) {
184             throw new IOException("Unsupported format " + format);
185         }
186         int searchOffset = 0;
187         int avaOffset = 0;
188         List<AVA> avaVec = new ArrayList<AVA>(3);
189         int nextPlus = name.indexOf('+');
190         while (nextPlus >= 0) {
191             /*
192              * We have encountered an AVA delimiter (plus sign).
193              * If the plus sign in the RDN under consideration is
194              * preceded by a backslash (escape), or by a double quote, it
195              * is part of the AVA. Otherwise, it is used as a separator, to
196              * delimit the AVA under consideration from any subsequent AVAs.
197              */
198             if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\' ) {
199                 /*
200                  * Plus sign is a separator
201                  */
202                 String avaString = name.substring(avaOffset, nextPlus);
203                 if (avaString.length() == 0) {
204                     throw new IOException("empty AVA in RDN \"" + name + "\"");
205                 }
206 
207                 // Parse AVA, and store it in vector
208                 AVA ava = new AVA
209                     (new StringReader(avaString), AVA.RFC2253, keywordMap);
210                 avaVec.add(ava);
211 
212                 // Increase the offset
213                 avaOffset = nextPlus + 1;
214             }
215             searchOffset = nextPlus + 1;
216             nextPlus = name.indexOf('+', searchOffset);
217         }
218 
219         // parse last or only AVA
220         String avaString = name.substring(avaOffset);
221         if (avaString.length() == 0) {
222             throw new IOException("empty AVA in RDN \"" + name + "\"");
223         }
224         AVA ava = new AVA(new StringReader(avaString), AVA.RFC2253, keywordMap);
225         avaVec.add(ava);
226 
227         assertion = avaVec.toArray(new AVA[avaVec.size()]);
228     }
229 
230     /*
231      * Constructs an RDN from an ASN.1 encoded value.  The encoding
232      * of the name in the stream uses DER (a BER/1 subset).
233      *
234      * @param value a DER-encoded value holding an RDN.
235      * @throws IOException on parsing error.
236      */
RDN(DerValue rdn)237     RDN(DerValue rdn) throws IOException {
238         if (rdn.tag != DerValue.tag_Set) {
239             throw new IOException("X500 RDN");
240         }
241         DerInputStream dis = new DerInputStream(rdn.toByteArray());
242         DerValue[] avaset = dis.getSet(5);
243 
244         assertion = new AVA[avaset.length];
245         for (int i = 0; i < avaset.length; i++) {
246             assertion[i] = new AVA(avaset[i]);
247         }
248     }
249 
250     /*
251      * Creates an empty RDN with slots for specified
252      * number of AVAs.
253      *
254      * @param i number of AVAs to be in RDN
255      */
RDN(int i)256     RDN(int i) { assertion = new AVA[i]; }
257 
RDN(AVA ava)258     public RDN(AVA ava) {
259         if (ava == null) {
260             throw new NullPointerException();
261         }
262         assertion = new AVA[] { ava };
263     }
264 
RDN(AVA[] avas)265     public RDN(AVA[] avas) {
266         assertion = avas.clone();
267         for (int i = 0; i < assertion.length; i++) {
268             if (assertion[i] == null) {
269                 throw new NullPointerException();
270             }
271         }
272     }
273 
274     /**
275      * Return an immutable List of the AVAs in this RDN.
276      */
avas()277     public List<AVA> avas() {
278         List<AVA> list = avaList;
279         if (list == null) {
280             list = Collections.unmodifiableList(Arrays.asList(assertion));
281             avaList = list;
282         }
283         return list;
284     }
285 
286     /**
287      * Return the number of AVAs in this RDN.
288      */
size()289     public int size() {
290         return assertion.length;
291     }
292 
equals(Object obj)293     public boolean equals(Object obj) {
294         if (this == obj) {
295             return true;
296         }
297         if (obj instanceof RDN == false) {
298             return false;
299         }
300         RDN other = (RDN)obj;
301         if (this.assertion.length != other.assertion.length) {
302             return false;
303         }
304         String thisCanon = this.toRFC2253String(true);
305         String otherCanon = other.toRFC2253String(true);
306         return thisCanon.equals(otherCanon);
307     }
308 
309     /*
310      * Calculates a hash code value for the object.  Objects
311      * which are equal will also have the same hashcode.
312      *
313      * @returns int hashCode value
314      */
hashCode()315     public int hashCode() {
316         return toRFC2253String(true).hashCode();
317     }
318 
319     /*
320      * return specified attribute value from RDN
321      *
322      * @params oid ObjectIdentifier of attribute to be found
323      * @returns DerValue of attribute value; null if attribute does not exist
324      */
findAttribute(ObjectIdentifier oid)325     DerValue findAttribute(ObjectIdentifier oid) {
326         for (int i = 0; i < assertion.length; i++) {
327             if (assertion[i].oid.equals((Object)oid)) {
328                 return assertion[i].value;
329             }
330         }
331         return null;
332     }
333 
334     /*
335      * Encode the RDN in DER-encoded form.
336      *
337      * @param out DerOutputStream to which RDN is to be written
338      * @throws IOException on error
339      */
encode(DerOutputStream out)340     void encode(DerOutputStream out) throws IOException {
341         out.putOrderedSetOf(DerValue.tag_Set, assertion);
342     }
343 
344     /*
345      * Returns a printable form of this RDN, using RFC 1779 style catenation
346      * of attribute/value assertions, and emitting attribute type keywords
347      * from RFCs 1779, 2253, and 3280.
348      */
toString()349     public String toString() {
350         if (assertion.length == 1) {
351             return assertion[0].toString();
352         }
353 
354         StringBuilder sb = new StringBuilder();
355         for (int i = 0; i < assertion.length; i++) {
356             if (i != 0) {
357                 sb.append(" + ");
358             }
359             sb.append(assertion[i].toString());
360         }
361         return sb.toString();
362     }
363 
364     /*
365      * Returns a printable form of this RDN using the algorithm defined in
366      * RFC 1779. Only RFC 1779 attribute type keywords are emitted.
367      */
toRFC1779String()368     public String toRFC1779String() {
369         return toRFC1779String(Collections.<String, String>emptyMap());
370     }
371 
372     /*
373      * Returns a printable form of this RDN using the algorithm defined in
374      * RFC 1779. RFC 1779 attribute type keywords are emitted, as well
375      * as keywords contained in the OID/keyword map.
376      */
toRFC1779String(Map<String, String> oidMap)377     public String toRFC1779String(Map<String, String> oidMap) {
378         if (assertion.length == 1) {
379             return assertion[0].toRFC1779String(oidMap);
380         }
381 
382         StringBuilder sb = new StringBuilder();
383         for (int i = 0; i < assertion.length; i++) {
384             if (i != 0) {
385                 sb.append(" + ");
386             }
387             sb.append(assertion[i].toRFC1779String(oidMap));
388         }
389         return sb.toString();
390     }
391 
392     /*
393      * Returns a printable form of this RDN using the algorithm defined in
394      * RFC 2253. Only RFC 2253 attribute type keywords are emitted.
395      */
toRFC2253String()396     public String toRFC2253String() {
397         return toRFC2253StringInternal
398             (false, Collections.<String, String>emptyMap());
399     }
400 
401     /*
402      * Returns a printable form of this RDN using the algorithm defined in
403      * RFC 2253. RFC 2253 attribute type keywords are emitted, as well as
404      * keywords contained in the OID/keyword map.
405      */
toRFC2253String(Map<String, String> oidMap)406     public String toRFC2253String(Map<String, String> oidMap) {
407         return toRFC2253StringInternal(false, oidMap);
408     }
409 
410     /*
411      * Returns a printable form of this RDN using the algorithm defined in
412      * RFC 2253. Only RFC 2253 attribute type keywords are emitted.
413      * If canonical is true, then additional canonicalizations
414      * documented in X500Principal.getName are performed.
415      */
toRFC2253String(boolean canonical)416     public String toRFC2253String(boolean canonical) {
417         if (canonical == false) {
418             return toRFC2253StringInternal
419                 (false, Collections.<String, String>emptyMap());
420         }
421         String c = canonicalString;
422         if (c == null) {
423             c = toRFC2253StringInternal
424                 (true, Collections.<String, String>emptyMap());
425             canonicalString = c;
426         }
427         return c;
428     }
429 
toRFC2253StringInternal(boolean canonical, Map<String, String> oidMap)430     private String toRFC2253StringInternal
431         (boolean canonical, Map<String, String> oidMap) {
432         /*
433          * Section 2.2: When converting from an ASN.1 RelativeDistinguishedName
434          * to a string, the output consists of the string encodings of each
435          * AttributeTypeAndValue (according to 2.3), in any order.
436          *
437          * Where there is a multi-valued RDN, the outputs from adjoining
438          * AttributeTypeAndValues are separated by a plus ('+' ASCII 43)
439          * character.
440          */
441 
442         // normally, an RDN only contains one AVA
443         if (assertion.length == 1) {
444             return canonical ? assertion[0].toRFC2253CanonicalString() :
445                                assertion[0].toRFC2253String(oidMap);
446         }
447 
448         AVA[] toOutput = assertion;
449         if (canonical) {
450             // order the string type AVA's alphabetically,
451             // followed by the oid type AVA's numerically
452             toOutput = assertion.clone();
453             Arrays.sort(toOutput, AVAComparator.getInstance());
454         }
455         StringJoiner sj = new StringJoiner("+");
456         for (AVA ava : toOutput) {
457             sj.add(canonical ? ava.toRFC2253CanonicalString()
458                              : ava.toRFC2253String(oidMap));
459         }
460         return sj.toString();
461     }
462 
463 }
464 
465 class AVAComparator implements Comparator<AVA> {
466 
467     private static final Comparator<AVA> INSTANCE = new AVAComparator();
468 
AVAComparator()469     private AVAComparator() {
470         // empty
471     }
472 
getInstance()473     static Comparator<AVA> getInstance() {
474         return INSTANCE;
475     }
476 
477     /**
478      * AVA's containing a standard keyword are ordered alphabetically,
479      * followed by AVA's containing an OID keyword, ordered numerically
480      */
compare(AVA a1, AVA a2)481     public int compare(AVA a1, AVA a2) {
482         boolean a1Has2253 = a1.hasRFC2253Keyword();
483         boolean a2Has2253 = a2.hasRFC2253Keyword();
484 
485         // BEGIN Android-changed: Keep sort order of RDN from Android M.
486         if (a1Has2253) {
487             if (a2Has2253) {
488                 return a1.toRFC2253CanonicalString().compareTo
489                         (a2.toRFC2253CanonicalString());
490             } else {
491                 return -1;
492             }
493         } else {
494             if (a2Has2253) {
495                 return 1;
496             } else {
497                 int[] a1Oid = a1.getObjectIdentifier().toIntArray();
498                 int[] a2Oid = a2.getObjectIdentifier().toIntArray();
499                 int pos = 0;
500                 int len = (a1Oid.length > a2Oid.length) ? a2Oid.length :
501                         a1Oid.length;
502                 while (pos < len && a1Oid[pos] == a2Oid[pos]) {
503                   ++pos;
504                 }
505                 return (pos == len) ? a1Oid.length - a2Oid.length :
506                         a1Oid[pos] - a2Oid[pos];
507             }
508         }
509         // END Android-changed: Keep sort order of RDN from Android M.
510     }
511 
512 }
513