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