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