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