001: /*
002: *
003: *
004: * Copyright 1990-2007 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 com.sun.satsa.pki;
028:
029: import java.io.ByteArrayOutputStream;
030: import java.io.UnsupportedEncodingException;
031: import com.sun.satsa.util.*;
032:
033: /**
034: * This class provides support for distinguished name formatted
035: * according to RFC 2253.
036: */
037: public class RFC2253Name {
038:
039: /**
040: * In RFC2253, if the value has any of these characters in it, it
041: * must be quoted by a preceding \.
042: */
043: private static final String specialChars2253 = ",+\"\\<>;=";
044:
045: /**
046: * DomainComponent type OID.
047: */
048: private static final byte[] dcOID = { 9, (byte) 0x92, (byte) 0x26,
049: (byte) 0x89, (byte) 0x93, (byte) 0xF2, (byte) 0x2C,
050: (byte) 0x64, (byte) 0x01, (byte) 0x19 };
051:
052: /**
053: * Component type names.
054: */
055: private static String[] names = { "CN", "C", "L", "ST", "STREET",
056: "O", "OU", "DC", "UID" };
057:
058: /**
059: * Component type OIDs.
060: */
061: private static byte[][] OIDs = {
062: { 85, 4, 3 },
063: { 85, 4, 6 },
064: { 85, 4, 7 },
065: { 85, 4, 8 },
066: { 85, 4, 9 },
067: { 85, 4, 10 },
068: { 85, 4, 11 },
069: dcOID,
070: { 9, (byte) 0x92, (byte) 0x26, (byte) 0x89, (byte) 0x93,
071: (byte) 0xF2, (byte) 0x2C, (byte) 0x64, (byte) 0x01,
072: (byte) 0x01 } };
073:
074: /**
075: * Original name.
076: */
077: private String src;
078: /**
079: * Current offset in the name.
080: */
081: private int index;
082: /**
083: * Is RelativeDistinguishedName complete?
084: */
085: private boolean complete;
086: /**
087: * Is value hex encoded (starts with #)?
088: */
089: private boolean encoded;
090: /**
091: * Temporary string buffer for parser.
092: */
093: private StringBuffer tmp = new StringBuffer();
094:
095: /**
096: * This method converts distinguished name from string to DER
097: * encoding.
098: * @param name distinguished name
099: * @return DER encoded name
100: * @throws IllegalArgumentException on any error.
101: */
102: public static byte[] toDER(String name) {
103:
104: try {
105: return new RFC2253Name(name).encode().getDERData();
106: } catch (TLVException tlve) {
107: throw new IllegalArgumentException(name);
108: } catch (StringIndexOutOfBoundsException siobe) {
109: throw new IllegalArgumentException(name);
110: } catch (IllegalArgumentException iae) {
111: throw new IllegalArgumentException(name);
112: }
113: }
114:
115: /**
116: * This method converts distinguished name from string to TLV object.
117: * @param name distinguished name
118: * @return DER encoded name
119: * @throws IllegalArgumentException on any error.
120: */
121: public static TLV toTLV(String name) {
122:
123: try {
124: return new RFC2253Name(name).encode();
125: } catch (TLVException e) {
126: throw new IllegalArgumentException(name);
127: } catch (StringIndexOutOfBoundsException siobe) {
128: throw new IllegalArgumentException(name);
129: } catch (IllegalArgumentException iae) {
130: throw new IllegalArgumentException(name);
131: }
132: }
133:
134: /**
135: * Creates a new RFC2253Name instance.
136: * @param name distinguished name
137: */
138: private RFC2253Name(String name) {
139: src = name;
140: }
141:
142: /**
143: * Parses the name and returns TLV object.
144: * @return DER encoded name
145: */
146: private TLV encode() throws TLVException {
147:
148: if (src == null || src.trim().length() == 0) {
149: throw new IllegalArgumentException();
150: }
151:
152: ByteArrayOutputStream buf = null;
153: TLV atvSet = null;
154: TLV atvSetTail = null;
155: TLV rdnSequence = null;
156:
157: while (index < src.length()) {
158:
159: String id = getName();
160: String s = getValue();
161:
162: int i = -1;
163: for (int j = 0; j < names.length; j++) {
164: if (id.equals(names[j])) {
165: i = j;
166: break;
167: }
168: }
169:
170: TLV type;
171:
172: if (i == -1) {
173: if (id.startsWith("OID.")) {
174: id = id.substring(4);
175: }
176: type = TLV.createOID(id);
177: } else {
178: type = new TLV(TLV.OID_TYPE, OIDs[i]);
179: }
180:
181: TLV value = null;
182:
183: if (encoded) {
184:
185: if (buf == null) {
186: buf = new ByteArrayOutputStream(s.length() / 2);
187: } else {
188: buf.reset();
189: }
190:
191: for (i = 0; i < s.length(); i += 2) {
192: buf.write(Integer.parseInt(s.substring(i, i + 2),
193: 16));
194: }
195:
196: value = new TLV(buf.toByteArray(), 0);
197: if (value.getDERSize() != (s.length() + 1) / 2) {
198: throw new IllegalArgumentException();
199: }
200:
201: } else {
202: byte[] typeOID = type.getValue();
203: if (Utils.byteMatch(dcOID, 0, dcOID.length, typeOID, 0,
204: typeOID.length)) {
205: value = TLV.createIA5String(s);
206: } else {
207: value = TLV.createUTF8String(s);
208: }
209: }
210:
211: TLV tv = TLV.createSequence();
212: tv.setChild(type).setNext(value);
213:
214: if (atvSet == null) {
215: atvSet = tv;
216: } else {
217: atvSetTail.setNext(tv);
218: }
219: atvSetTail = tv;
220:
221: if (complete) {
222: TLV set = new TLV(TLV.SET_TYPE);
223: set.setChild(atvSet);
224: set.setNext(rdnSequence);
225: rdnSequence = set;
226: atvSet = null;
227: }
228: }
229:
230: if (!complete) {
231: throw new IllegalArgumentException();
232: }
233:
234: TLV name = TLV.createSequence();
235: name.setChild(rdnSequence);
236:
237: return name;
238: }
239:
240: /**
241: * Returns the next attribute name.
242: * @return name
243: */
244: private String getName() {
245:
246: int i = index;
247: index = src.indexOf('=', index);
248: if (index == -1) {
249: throw new IllegalArgumentException();
250: }
251: return src.substring(i, index++).trim().toUpperCase();
252: }
253:
254: /**
255: * Returns the next attribute value.
256: * @return attribute value
257: */
258: private String getValue() {
259:
260: tmp.delete(0, tmp.length());
261: complete = true;
262: encoded = false;
263: boolean quote = false;
264: int expectedLength = -1;
265: boolean firstSpace = false;
266:
267: while (index < src.length()) {
268:
269: char c = src.charAt(index++);
270:
271: if (c == '\"') {
272: if (quote) {
273: expectedLength = tmp.length();
274: } else if (tmp.length() != 0) {
275: throw new IllegalArgumentException();
276: }
277: quote = !quote;
278: continue;
279: }
280:
281: if (!quote && tmp.length() == 0) {
282: if (c == ' ') {
283: continue;
284: }
285: if (c == '#') {
286: encoded = true;
287: continue;
288: }
289: }
290:
291: if (c == '\\') {
292:
293: c = src.charAt(index++);
294:
295: if (c == '#' || specialChars2253.indexOf(c) != -1) {
296: tmp.append(c);
297: continue;
298: }
299:
300: if (!quote && c == ' ') {
301: if (expectedLength > 1) {
302: // only the 1st and the last space can be escaped
303: throw new IllegalArgumentException();
304: }
305: tmp.append(c);
306: expectedLength = tmp.length();
307: firstSpace = (tmp.length() == 1);
308: continue;
309: }
310:
311: tmp.append((char) Integer.parseInt(src.substring(
312: index - 1, index + 1), 16));
313: index++;
314: continue;
315: }
316:
317: if (!quote && (specialChars2253.indexOf(c) != -1)) {
318: if (c == ',' || c == ';') {
319: if (index == src.length()) {
320: throw new IllegalArgumentException();
321: }
322: break;
323: }
324: if (c == '+') {
325: complete = false;
326: break;
327: }
328: throw new IllegalArgumentException();
329: }
330:
331: tmp.append(c);
332: }
333:
334: if (quote) {
335: throw new IllegalArgumentException();
336: }
337:
338: int i = tmp.length();
339: while (i > 0 && i-- > expectedLength && tmp.charAt(i) == ' ') {
340: tmp.deleteCharAt(i);
341: }
342:
343: if (!(expectedLength == -1 || firstSpace || tmp.length() == expectedLength)) {
344: throw new IllegalArgumentException();
345: }
346:
347: return tmp.toString();
348: }
349:
350: /**
351: * Compares two names in TLV form.
352: * @param name1 the 1st name
353: * @param name2 the 2nd name
354: * @return true if the names are equal
355: * @exception IllegalArgumentException in case of any error
356: */
357: public static boolean compare(TLV name1, TLV name2) {
358:
359: try {
360: TLV set1 = name1.child;
361: TLV set2 = name2.child;
362:
363: while (true) {
364:
365: if (set1 == null || set2 == null) {
366: return (set1 == set2);
367: }
368:
369: TLV seq1 = set1.child;
370: TLV seq2 = set2.child;
371:
372: while (true) {
373:
374: if (seq1 == null && seq2 == null) {
375: break;
376: }
377:
378: if (seq1 == null || seq2 == null) {
379: return false;
380: }
381:
382: TLV v1 = seq1.child;
383: TLV v2 = seq2.child;
384:
385: if (!v1.match(v2)) {
386: return false;
387: }
388:
389: v1 = v1.next;
390: v2 = v2.next;
391:
392: if (!(v1.match(v2) || stringsMatch(v1, v2))) {
393: return false;
394: }
395:
396: seq1 = seq1.next;
397: seq2 = seq2.next;
398: }
399:
400: set1 = set1.next;
401: set2 = set2.next;
402: }
403: } catch (IllegalArgumentException iae) {
404: throw iae;
405: } catch (NullPointerException npe) {
406: throw new IllegalArgumentException("Invalid TLV");
407: } catch (IndexOutOfBoundsException iobe) {
408: throw new IllegalArgumentException("Invalid TLV");
409: }
410: }
411:
412: /**
413: * Compares two attribute values ignoring multiple spaces and
414: * register.
415: * @param v1 1st attribute value
416: * @param v2 2nd attribute value
417: * @return true if equal
418: */
419: private static boolean stringsMatch(TLV v1, TLV v2) {
420:
421: if (!(v1.isString() && v2.isString())) {
422: return false;
423: }
424:
425: char[] c1 = getChars(v1);
426: char[] c2 = getChars(v2);
427:
428: int i1 = 0;
429: int i2 = 0;
430:
431: while (true) {
432:
433: if (i1 == c1.length || i2 == c2.length) {
434: return (i1 == c1.length && i2 == c2.length);
435: }
436:
437: while (c1[i1] == ' ' && c1[i1 + 1] == ' ') {
438: i1++;
439: }
440:
441: while (c2[i2] == ' ' && c2[i2 + 1] == ' ') {
442: i2++;
443: }
444:
445: if (c1[i1++] != c2[i2++]) {
446: return false;
447: }
448: }
449: }
450:
451: /**
452: * Returns array of characters for comparison.
453: * @param v attribute value
454: * @return array of characters
455: * @throws IllegalArgumentException if conversion to
456: * utf-8 fails
457: */
458: private static char[] getChars(TLV v) {
459:
460: try {
461: return (new String(v.data, v.valueOffset, v.length,
462: Utils.utf8)).trim().toUpperCase().toCharArray();
463: } catch (UnsupportedEncodingException e) {
464: throw new IllegalArgumentException("invalid encoding");
465: }
466: }
467:
468: /**
469: * Returns string representation for parsed RFC2253 name.
470: * @param name the name
471: * @return string representation of the name or null in case of error
472: */
473: public static String NameToString(TLV name) {
474:
475: try {
476: String result = "";
477: name = name.child;
478: while (name != null) {
479:
480: String s = "";
481: TLV rdn = name.child;
482: name = name.next;
483:
484: while (rdn != null) {
485:
486: if (s.length() != 0) {
487: s = s + " + ";
488: }
489:
490: TLV t = rdn.child;
491: rdn = rdn.next;
492:
493: boolean encodeValue = true;
494:
495: for (int i = 0; i < OIDs.length; i++) {
496: if (Utils.byteMatch(OIDs[i], 0, OIDs[i].length,
497: t.data, t.valueOffset, t.length)) {
498: s += names[i];
499: encodeValue = false;
500: break;
501: }
502: }
503:
504: if (encodeValue) {
505: s += "OID."
506: + Utils.OIDtoString(t.data,
507: t.valueOffset, t.length);
508: }
509:
510: t = t.next;
511:
512: String value;
513: try {
514: value = new String(t.data, t.valueOffset,
515: t.length, Utils.utf8);
516: } catch (UnsupportedEncodingException uee) {
517: encodeValue = true;
518: value = null;
519: }
520:
521: if (encodeValue) {
522: byte[] data = t.getDERData();
523: value = "#"
524: + Utils.hexNumber(data, 0, data.length);
525: } else {
526: boolean special = value.startsWith(" ")
527: || value.startsWith("#")
528: || value.endsWith(" ");
529: for (int i = 0; i < specialChars2253.length(); i++) {
530: special |= value.indexOf(specialChars2253
531: .charAt(i)) > -1;
532: }
533:
534: if (special) {
535: int i = value.length();
536: while (i-- > 0) {
537: char c = value.charAt(i);
538: if (c == '"' || c == '\\') {
539: value = value.substring(0, i)
540: + "\\" + value.substring(i);
541: }
542: }
543: value = "\"" + value + "\"";
544: }
545:
546: int i = value.length();
547: while (i-- > 0) {
548: char c = value.charAt(i);
549: if (c > -1 && c < 32) {
550: value = value.substring(0, i) + "\\"
551: + Utils.hexByte(c)
552: + value.substring(i + 1);
553: }
554: }
555: }
556: s += "=" + value;
557: }
558: result = result.length() == 0 ? s : s + ", " + result;
559: }
560: return result;
561: } catch (NullPointerException npe) {
562: return null;
563: } catch (IndexOutOfBoundsException iobe) {
564: return null;
565: } catch (IllegalArgumentException iae) {
566: return null;
567: }
568: }
569: }
|