001: /*
002: * @(#)RDN.java 1.6 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package sun.security.x509;
028:
029: import java.lang.reflect.*;
030: import java.io.IOException;
031: import java.io.StringReader;
032: import java.security.PrivilegedExceptionAction;
033: import java.security.AccessController;
034: import java.security.Principal;
035: import java.util.*;
036:
037: import sun.security.util.*;
038: import sun.security.pkcs.PKCS9Attribute;
039: import javax.security.auth.x500.X500Principal;
040:
041: /**
042: * RDNs are a set of {attribute = value} assertions. Some of those
043: * attributes are "distinguished" (unique w/in context). Order is
044: * never relevant.
045: *
046: * Some X.500 names include only a single distinguished attribute
047: * per RDN. This style is currently common.
048: *
049: * Note that DER-encoded RDNs sort AVAs by assertion OID ... so that
050: * when we parse this data we don't have to worry about canonicalizing
051: * it, but we'll need to sort them when we expose the RDN class more.
052: * <p>
053: * The ASN.1 for RDNs is:
054: * <pre>
055: * RelativeDistinguishedName ::=
056: * SET OF AttributeTypeAndValue
057: *
058: * AttributeTypeAndValue ::= SEQUENCE {
059: * type AttributeType,
060: * value AttributeValue }
061: *
062: * AttributeType ::= OBJECT IDENTIFIER
063: *
064: * AttributeValue ::= ANY DEFINED BY AttributeType
065: * </pre>
066: *
067: * Note that instances of this class are immutable.
068: *
069: * @version 1.6, 10/10/06
070: */
071: public class RDN {
072:
073: // currently not private, accessed directly from X500Name
074: final AVA[] assertion;
075:
076: // cached immutable List of the AVAs
077: private volatile List avaList;
078:
079: // cache canonical String form
080: private volatile String canonicalString;
081:
082: /**
083: * Constructs an RDN from its printable representation.
084: *
085: * An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
086: * using '+' as a separator.
087: * If the '+' should be considered part of an AVA value, it must be
088: * preceded by '\'.
089: *
090: * @param name String form of RDN
091: * @throws IOException on parsing error
092: */
093: public RDN(String name) throws IOException {
094: int quoteCount = 0;
095: int searchOffset = 0;
096: int avaOffset = 0;
097: Vector avaVec = new Vector(3);
098: int nextPlus = name.indexOf('+');
099: while (nextPlus >= 0) {
100: quoteCount += X500Name.countQuotes(name, searchOffset,
101: nextPlus);
102: /*
103: * We have encountered an AVA delimiter (plus sign).
104: * If the plus sign in the RDN under consideration is
105: * preceded by a backslash (escape), or by a double quote, it
106: * is part of the AVA. Otherwise, it is used as a separator, to
107: * delimit the AVA under consideration from any subsequent AVAs.
108: */
109: if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\'
110: && quoteCount != 1) {
111: /*
112: * Plus sign is a separator
113: */
114: String avaString = name.substring(avaOffset, nextPlus);
115: if (avaString.length() == 0) {
116: throw new IOException("empty AVA in RDN \"" + name
117: + "\"");
118: }
119:
120: // Parse AVA, and store it in vector
121: AVA ava = new AVA(new StringReader(avaString));
122: avaVec.addElement(ava);
123:
124: // Increase the offset
125: avaOffset = nextPlus + 1;
126:
127: // Set quote counter back to zero
128: quoteCount = 0;
129: }
130: searchOffset = nextPlus + 1;
131: nextPlus = name.indexOf('+', searchOffset);
132: }
133:
134: // parse last or only AVA
135: String avaString = name.substring(avaOffset);
136: if (avaString.length() == 0) {
137: throw new IOException("empty AVA in RDN \"" + name + "\"");
138: }
139: AVA ava = new AVA(new StringReader(avaString));
140: avaVec.addElement(ava);
141:
142: assertion = (AVA[]) avaVec.toArray(new AVA[avaVec.size()]);
143: }
144:
145: /*
146: * Constructs an RDN from its printable representation.
147: *
148: * An RDN may consist of one or multiple Attribute Value Assertions (AVAs),
149: * using '+' as a separator.
150: * If the '+' should be considered part of an AVA value, it must be
151: * preceded by '\'.
152: *
153: * @param name String form of RDN
154: * @throws IOException on parsing error
155: */
156: RDN(String name, String format) throws IOException {
157: if (format.equalsIgnoreCase("RFC2253") == false) {
158: throw new IOException("Unsupported format " + format);
159: }
160: int searchOffset = 0;
161: int avaOffset = 0;
162: Vector avaVec = new Vector(3);
163: int nextPlus = name.indexOf('+');
164: while (nextPlus >= 0) {
165: /*
166: * We have encountered an AVA delimiter (plus sign).
167: * If the plus sign in the RDN under consideration is
168: * preceded by a backslash (escape), or by a double quote, it
169: * is part of the AVA. Otherwise, it is used as a separator, to
170: * delimit the AVA under consideration from any subsequent AVAs.
171: */
172: if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\') {
173: /*
174: * Plus sign is a separator
175: */
176: String avaString = name.substring(avaOffset, nextPlus);
177: if (avaString.length() == 0) {
178: throw new IOException("empty AVA in RDN \"" + name
179: + "\"");
180: }
181:
182: // Parse AVA, and store it in vector
183: AVA ava = new AVA(new StringReader(avaString),
184: AVA.RFC2253);
185: avaVec.addElement(ava);
186:
187: // Increase the offset
188: avaOffset = nextPlus + 1;
189: }
190: searchOffset = nextPlus + 1;
191: nextPlus = name.indexOf('+', searchOffset);
192: }
193:
194: // parse last or only AVA
195: String avaString = name.substring(avaOffset);
196: if (avaString.length() == 0) {
197: throw new IOException("empty AVA in RDN \"" + name + "\"");
198: }
199: AVA ava = new AVA(new StringReader(avaString), AVA.RFC2253);
200: avaVec.addElement(ava);
201:
202: assertion = (AVA[]) avaVec.toArray(new AVA[avaVec.size()]);
203: }
204:
205: /*
206: * Constructs an RDN from an ASN.1 encoded value. The encoding
207: * of the name in the stream uses DER (a BER/1 subset).
208: *
209: * @param value a DER-encoded value holding an RDN.
210: * @throws IOException on parsing error.
211: */
212: RDN(DerValue rdn) throws IOException {
213: if (rdn.tag != DerValue.tag_Set) {
214: throw new IOException("X500 RDN");
215: }
216: DerInputStream dis = new DerInputStream(rdn.toByteArray());
217: DerValue[] avaset = dis.getSet(5);
218:
219: assertion = new AVA[avaset.length];
220: for (int i = 0; i < avaset.length; i++) {
221: assertion[i] = new AVA(avaset[i]);
222: }
223: }
224:
225: /*
226: * Creates an empty RDN with slots for specified
227: * number of AVAs.
228: *
229: * @param i number of AVAs to be in RDN
230: */
231: RDN(int i) {
232: assertion = new AVA[i];
233: }
234:
235: public RDN(AVA ava) {
236: if (ava == null) {
237: throw new NullPointerException();
238: }
239: assertion = new AVA[] { ava };
240: }
241:
242: public RDN(AVA[] avas) {
243: assertion = (AVA[]) avas.clone();
244: for (int i = 0; i < assertion.length; i++) {
245: if (assertion[i] == null) {
246: throw new NullPointerException();
247: }
248: }
249: }
250:
251: /**
252: * Return an immutable List of the AVAs in this RDN.
253: */
254: public List avas() {
255: List list = avaList;
256: if (list == null) {
257: list = Collections.unmodifiableList(Arrays
258: .asList(assertion));
259: avaList = list;
260: }
261: return list;
262: }
263:
264: /**
265: * Return the number of AVAs in this RDN.
266: */
267: public int size() {
268: return assertion.length;
269: }
270:
271: public boolean equals(Object obj) {
272: if (this == obj) {
273: return true;
274: }
275: if (obj instanceof RDN == false) {
276: return false;
277: }
278: RDN other = (RDN) obj;
279: if (this .assertion.length != other.assertion.length) {
280: return false;
281: }
282: String this Canon = this .toRFC2253String(true);
283: String otherCanon = other.toRFC2253String(true);
284: return this Canon.equals(otherCanon);
285: }
286:
287: /*
288: * Calculates a hash code value for the object. Objects
289: * which are equal will also have the same hashcode.
290: *
291: * @returns int hashCode value
292: */
293: public int hashCode() {
294: return toRFC2253String(true).hashCode();
295: }
296:
297: /*
298: * return specified attribute value from RDN
299: *
300: * @params oid ObjectIdentifier of attribute to be found
301: * @returns DerValue of attribute value; null if attribute does not exist
302: */
303: DerValue findAttribute(ObjectIdentifier oid) {
304: for (int i = 0; i < assertion.length; i++) {
305: if (assertion[i].oid.equals(oid)) {
306: return assertion[i].value;
307: }
308: }
309: return null;
310: }
311:
312: /*
313: * Encode the RDN in DER-encoded form.
314: *
315: * @param out DerOutputStream to which RDN is to be written
316: * @throws IOException on error
317: */
318: void encode(DerOutputStream out) throws IOException {
319: out.putOrderedSetOf(DerValue.tag_Set, assertion);
320: }
321:
322: /*
323: * Returns a printable form of this RDN, using RFC 1779 style catenation
324: * of attribute/value assertions, and emitting attribute type keywords
325: * from RFC 1779, 2253, and 2459.
326: */
327: public String toString() {
328: if (assertion.length == 1) {
329: return assertion[0].toString();
330: }
331:
332: StringBuffer sb = new StringBuffer();
333: for (int i = 0; i < assertion.length; i++) {
334: if (i != 0) {
335: sb.append(" + ");
336: }
337: sb.append(assertion[i].toString());
338: }
339: return sb.toString();
340: }
341:
342: /*
343: * Returns a printable form of this RDN using the algorithm defined in
344: * RFC 1779. Only RFC 1779 attribute type keywords are emitted.
345: */
346: public String toRFC1779String() {
347: if (assertion.length == 1) {
348: return assertion[0].toRFC1779String();
349: }
350:
351: StringBuffer sb = new StringBuffer();
352: for (int i = 0; i < assertion.length; i++) {
353: if (i != 0) {
354: sb.append(" + ");
355: }
356: sb.append(assertion[i].toRFC1779String());
357: }
358: return sb.toString();
359: }
360:
361: /*
362: * Returns a printable form of this RDN using the algorithm defined in
363: * RFC 2253. Only RFC 2253 attribute type keywords are emitted.
364: */
365: public String toRFC2253String() {
366: return toRFC2253StringInternal(false);
367: }
368:
369: /*
370: * Returns a printable form of this RDN using the algorithm defined in
371: * RFC 2253. Only RFC 2253 attribute type keywords are emitted.
372: * If canonical is true, then additional canonicalizations
373: * documented in X500Principal.getName are performed.
374: */
375: public String toRFC2253String(boolean canonical) {
376: if (canonical == false) {
377: return toRFC2253StringInternal(false);
378: }
379: String c = canonicalString;
380: if (c == null) {
381: c = toRFC2253StringInternal(true);
382: canonicalString = c;
383: }
384: return c;
385: }
386:
387: private String toRFC2253StringInternal(boolean canonical) {
388: /*
389: * Section 2.2: When converting from an ASN.1 RelativeDistinguishedName
390: * to a string, the output consists of the string encodings of each
391: * AttributeTypeAndValue (according to 2.3), in any order.
392: *
393: * Where there is a multi-valued RDN, the outputs from adjoining
394: * AttributeTypeAndValues are separated by a plus ('+' ASCII 43)
395: * character.
396: */
397:
398: // normally, an RDN only contains one AVA
399: if (assertion.length == 1) {
400: return canonical ? assertion[0].toRFC2253CanonicalString()
401: : assertion[0].toRFC2253String();
402: }
403:
404: StringBuffer relname = new StringBuffer();
405: if (!canonical) {
406: for (int i = 0; i < assertion.length; i++) {
407: if (i > 0) {
408: relname.append('+');
409: }
410: relname.append(assertion[i].toRFC2253String());
411: }
412: } else {
413: // order the string type AVA's alphabetically,
414: // followed by the oid type AVA's numerically
415: List avaList = new ArrayList(assertion.length);
416: for (int i = 0; i < assertion.length; i++) {
417: avaList.add(assertion[i]);
418: }
419: java.util.Collections.sort(avaList, AVAComparator
420: .getInstance());
421:
422: for (int i = 0; i < avaList.size(); i++) {
423: if (i > 0) {
424: relname.append('+');
425: }
426: relname.append(((AVA) avaList.get(i))
427: .toRFC2253CanonicalString());
428: }
429: }
430: return new String(relname);
431: }
432:
433: }
434:
435: class AVAComparator implements Comparator {
436:
437: private static final Comparator INSTANCE = new AVAComparator();
438:
439: private AVAComparator() {
440: // empty
441: }
442:
443: static Comparator getInstance() {
444: return INSTANCE;
445: }
446:
447: /**
448: * AVA's containing a standard keyword are ordered alphabetically,
449: * followed by AVA's containing an OID keyword, ordered numerically
450: */
451: public int compare(Object o1, Object o2) {
452: AVA a1 = (AVA) o1;
453: AVA a2 = (AVA) o2;
454:
455: boolean a1Has2253 = a1.hasRFC2253Keyword();
456: boolean a2Has2253 = a2.hasRFC2253Keyword();
457:
458: if (a1Has2253 == a2Has2253) {
459: return a1.toRFC2253CanonicalString().compareTo(
460: a2.toRFC2253CanonicalString());
461: } else {
462: if (a1Has2253) {
463: return -1;
464: } else {
465: return 1;
466: }
467: }
468: }
469:
470: }
|