001 /* X500Principal.java -- X.500 principal.
002 Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath 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 for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.security.auth.x500;
040
041 import gnu.java.lang.CPStringBuilder;
042
043 import gnu.java.security.OID;
044 import gnu.java.security.der.DER;
045 import gnu.java.security.der.DERReader;
046 import gnu.java.security.der.DERValue;
047
048 import java.io.ByteArrayInputStream;
049 import java.io.EOFException;
050 import java.io.IOException;
051 import java.io.InputStream;
052 import java.io.NotActiveException;
053 import java.io.ObjectInputStream;
054 import java.io.ObjectOutputStream;
055 import java.io.Reader;
056 import java.io.Serializable;
057 import java.io.StringReader;
058
059 import java.security.Principal;
060
061 import java.util.ArrayList;
062 import java.util.HashSet;
063 import java.util.Iterator;
064 import java.util.LinkedHashMap;
065 import java.util.LinkedList;
066 import java.util.List;
067 import java.util.Locale;
068 import java.util.Map;
069 import java.util.Set;
070
071 public final class X500Principal implements Principal, Serializable
072 {
073 private static final long serialVersionUID = -500463348111345721L;
074
075 // Constants and fields.
076 // ------------------------------------------------------------------------
077
078 public static final String CANONICAL = "CANONICAL";
079 public static final String RFC1779 = "RFC1779";
080 public static final String RFC2253 = "RFC2253";
081
082 private static final OID CN = new OID("2.5.4.3");
083 private static final OID C = new OID("2.5.4.6");
084 private static final OID L = new OID("2.5.4.7");
085 private static final OID ST = new OID("2.5.4.8");
086 private static final OID STREET = new OID("2.5.4.9");
087 private static final OID O = new OID("2.5.4.10");
088 private static final OID OU = new OID("2.5.4.11");
089 private static final OID DC = new OID("0.9.2342.19200300.100.1.25");
090 private static final OID UID = new OID("0.9.2342.19200300.100.1.1");
091
092 private transient List components;
093 private transient Map currentRdn;
094 private transient boolean fixed;
095 private transient byte[] encoded;
096
097 // Constructors.
098 // ------------------------------------------------------------------------
099
100 private X500Principal()
101 {
102 components = new LinkedList();
103 currentRdn = new LinkedHashMap();
104 components.add (currentRdn);
105 }
106
107 public X500Principal (String name)
108 {
109 this();
110 if (name == null)
111 throw new NullPointerException();
112 try
113 {
114 parseString (name);
115 }
116 catch (IOException ioe)
117 {
118 IllegalArgumentException iae = new IllegalArgumentException("malformed name");
119 iae.initCause (ioe);
120 throw iae;
121 }
122 }
123
124 public X500Principal (byte[] encoded)
125 {
126 this(new ByteArrayInputStream (encoded));
127 }
128
129 public X500Principal (InputStream encoded)
130 {
131 this();
132 try
133 {
134 parseDer (encoded);
135 }
136 catch (IOException ioe)
137 {
138 throw new IllegalArgumentException (ioe.toString());
139 }
140 }
141
142 // Instance methods.
143 // ------------------------------------------------------------------------
144
145 public int hashCode()
146 {
147 int result = size();
148 for (int i = 0; i < size(); ++i)
149 {
150 Map m = (Map) components.get(i);
151 for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
152 {
153 Map.Entry e = (Map.Entry) it2.next();
154 // We don't bother looking at the value of the entry.
155 result = result * 31 + ((OID) e.getKey()).hashCode();
156 }
157 }
158 return result;
159 }
160
161 public boolean equals(Object o)
162 {
163 if (!(o instanceof X500Principal))
164 return false;
165 if (size() != ((X500Principal) o).size())
166 return false;
167 for (int i = 0; i < size(); i++)
168 {
169 Map m = (Map) components.get (i);
170 for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
171 {
172 Map.Entry e = (Map.Entry) it2.next();
173 OID oid = (OID) e.getKey();
174 String v1 = (String) e.getValue();
175 String v2 = ((X500Principal) o).getComponent (oid, i);
176 if (v2 == null)
177 return false;
178 if (!compressWS (v1).equalsIgnoreCase (compressWS (v2)))
179 return false;
180 }
181 }
182 return true;
183 }
184
185 public byte[] getEncoded()
186 {
187 if (encoded == null)
188 encodeDer();
189 return (byte[]) encoded.clone();
190 }
191
192 public String getName()
193 {
194 return getName (RFC2253);
195 }
196
197 public String getName (final String format)
198 {
199 boolean rfc2253 = RFC2253.equalsIgnoreCase (format) ||
200 CANONICAL.equalsIgnoreCase (format);
201 boolean rfc1779 = RFC1779.equalsIgnoreCase (format);
202 boolean canon = CANONICAL.equalsIgnoreCase (format);
203 if (! (rfc2253 || rfc1779 || canon))
204 throw new IllegalArgumentException ("unsupported format " + format);
205 CPStringBuilder str = new CPStringBuilder();
206 for (Iterator it = components.iterator(); it.hasNext(); )
207 {
208 Map m = (Map) it.next();
209 for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
210 {
211 Map.Entry entry = (Map.Entry) it2.next();
212 OID oid = (OID) entry.getKey();
213 String value = (String) entry.getValue();
214 if (oid.equals (CN))
215 str.append ("CN");
216 else if (oid.equals (C))
217 str.append ("C");
218 else if (oid.equals (L))
219 str.append ("L");
220 else if (oid.equals (ST))
221 str.append ("ST");
222 else if (oid.equals (STREET))
223 str.append ("STREET");
224 else if (oid.equals (O))
225 str.append ("O");
226 else if (oid.equals (OU))
227 str.append ("OU");
228 else if (oid.equals (DC) && rfc2253)
229 str.append ("DC");
230 else if (oid.equals (UID) && rfc2253)
231 str.append ("UID");
232 else
233 str.append (oid.toString());
234 str.append('=');
235 str.append(value);
236 if (it2.hasNext())
237 str.append('+');
238 }
239 if (it.hasNext())
240 str.append(',');
241 }
242 if (canon)
243 return str.toString().toUpperCase (Locale.US).toLowerCase (Locale.US);
244 return str.toString();
245 }
246
247 public String toString()
248 {
249 return getName (RFC2253);
250 }
251
252 // Serialization methods.
253 // ------------------------------------------------------------------------
254
255 private void writeObject (ObjectOutputStream out) throws IOException
256 {
257 if (encoded != null)
258 encodeDer();
259 out.writeObject (encoded);
260 }
261
262 private void readObject (ObjectInputStream in)
263 throws IOException, NotActiveException, ClassNotFoundException
264 {
265 byte[] buf = (byte[]) in.readObject();
266 parseDer (new ByteArrayInputStream (buf));
267 }
268
269 // Own methods.
270 // -------------------------------------------------------------------------
271
272 private int size()
273 {
274 return components.size();
275 }
276
277 private String getComponent(OID oid, int rdn)
278 {
279 if (rdn >= size())
280 return null;
281 return (String) ((Map) components.get (rdn)).get (oid);
282 }
283
284 private void encodeDer()
285 {
286 ArrayList name = new ArrayList(components.size());
287 for (Iterator it = components.iterator(); it.hasNext(); )
288 {
289 Map m = (Map) it.next();
290 if (m.isEmpty())
291 continue;
292 Set rdn = new HashSet();
293 for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
294 {
295 Map.Entry e = (Map.Entry) it2.next();
296 ArrayList atav = new ArrayList(2);
297 atav.add(new DERValue(DER.OBJECT_IDENTIFIER, e.getKey()));
298 atav.add(new DERValue(DER.UTF8_STRING, e.getValue()));
299 rdn.add(new DERValue(DER.SEQUENCE|DER.CONSTRUCTED, atav));
300 }
301 name.add(new DERValue(DER.SET|DER.CONSTRUCTED, rdn));
302 }
303 DERValue val = new DERValue(DER.SEQUENCE|DER.CONSTRUCTED, name);
304 encoded = val.getEncoded();
305 }
306
307 private int sep;
308
309 private void parseString(String str) throws IOException
310 {
311 Reader in = new StringReader(str);
312 while (true)
313 {
314 String key = readAttributeType(in);
315 if (key == null)
316 break;
317 String value = readAttributeValue(in);
318 putComponent(key, value);
319 if (sep == ',')
320 newRelativeDistinguishedName();
321 if (sep == -1)
322 break;
323 }
324 }
325
326 private String readAttributeType(Reader in) throws IOException
327 {
328 CPStringBuilder buf = new CPStringBuilder();
329 int ch;
330 while ((ch = in.read()) != '=')
331 {
332 if (ch == -1)
333 {
334 if (buf.length() > 0)
335 throw new EOFException("partial name read: " + buf);
336 return null;
337 }
338 if (ch > 127)
339 throw new IOException("Invalid char: " + (char) ch);
340 if (Character.isLetterOrDigit((char) ch) || ch == '-' || ch == '.')
341 buf.append((char) ch);
342 else
343 throw new IOException("Invalid char: " + (char) ch);
344 }
345 return buf.toString();
346 }
347
348 private String readAttributeValue(Reader in) throws IOException
349 {
350 CPStringBuilder buf = new CPStringBuilder();
351 int ch = in.read();
352 if (ch == '#')
353 {
354 while (true)
355 {
356 ch = in.read();
357 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
358 || Character.isDigit((char) ch))
359 buf.append((char) ch);
360 else if (ch == '+' || ch == ',')
361 {
362 sep = ch;
363 String hex = buf.toString();
364 return new String(toByteArray(hex));
365 }
366 else
367 throw new IOException("illegal character: " + (char) ch);
368 }
369 }
370 else if (ch == '"')
371 {
372 while (true)
373 {
374 ch = in.read();
375 if (ch == '"')
376 break;
377 else if (ch == '\\')
378 {
379 ch = in.read();
380 if (ch == -1)
381 throw new EOFException();
382 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
383 || Character.isDigit((char) ch))
384 {
385 int i = Character.digit((char) ch, 16) << 4;
386 ch = in.read();
387 if (!(('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
388 || Character.isDigit((char) ch)))
389 throw new IOException("illegal hex char");
390 i |= Character.digit((char) ch, 16);
391 buf.append((char) i);
392 }
393 else
394 buf.append((char) ch);
395 }
396 else
397 buf.append((char) ch);
398 }
399 sep = in.read();
400 if (sep != '+' && sep != ',')
401 throw new IOException("illegal character: " + (char) ch);
402 return buf.toString();
403 }
404 else
405 {
406 while (true)
407 {
408 switch (ch)
409 {
410 case '+':
411 case ',':
412 sep = ch;
413 return buf.toString();
414 case '\\':
415 ch = in.read();
416 if (ch == -1)
417 throw new EOFException();
418 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
419 || Character.isDigit((char) ch))
420 {
421 int i = Character.digit((char) ch, 16) << 4;
422 ch = in.read();
423 if (!(('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
424 || Character.isDigit((char) ch)))
425 throw new IOException("illegal hex char");
426 i |= Character.digit((char) ch, 16);
427 buf.append((char) i);
428 }
429 else
430 buf.append((char) ch);
431 break;
432 case '=':
433 case '<':
434 case '>':
435 case '#':
436 case ';':
437 throw new IOException("illegal character: " + (char) ch);
438 case -1:
439 sep = -1;
440 return buf.toString ();
441 default:
442 buf.append((char) ch);
443 }
444 ch = in.read ();
445 }
446 }
447 }
448
449 private void parseDer (InputStream encoded) throws IOException
450 {
451 DERReader der = new DERReader (encoded);
452 DERValue name = der.read();
453 if (!name.isConstructed())
454 throw new IOException ("malformed Name");
455 this.encoded = name.getEncoded();
456 int len = 0;
457 while (len < name.getLength())
458 {
459 DERValue rdn = der.read();
460 if (!rdn.isConstructed())
461 throw new IOException ("badly formed RDNSequence");
462 int len2 = 0;
463 while (len2 < rdn.getLength())
464 {
465 DERValue atav = der.read();
466 if (!atav.isConstructed())
467 throw new IOException ("badly formed AttributeTypeAndValue");
468 DERValue val = der.read();
469 if (val.getTag() != DER.OBJECT_IDENTIFIER)
470 throw new IOException ("badly formed AttributeTypeAndValue");
471 OID oid = (OID) val.getValue();
472 val = der.read();
473 if (!(val.getValue() instanceof String))
474 throw new IOException ("badly formed AttributeTypeAndValue");
475 String value = (String) val.getValue();
476 putComponent(oid, value);
477 len2 += atav.getEncodedLength();
478 }
479 len += rdn.getEncodedLength();
480 if (len < name.getLength())
481 newRelativeDistinguishedName();
482 }
483 }
484
485 private void newRelativeDistinguishedName()
486 {
487 currentRdn = new LinkedHashMap();
488 components.add(currentRdn);
489 }
490
491 private void putComponent(OID oid, String value)
492 {
493 currentRdn.put(oid, value);
494 }
495
496 private void putComponent(String name, String value)
497 {
498 name = name.trim().toLowerCase();
499 if (name.equals("cn"))
500 putComponent(CN, value);
501 else if (name.equals("c"))
502 putComponent(C, value);
503 else if (name.equals("l"))
504 putComponent(L, value);
505 else if (name.equals("street"))
506 putComponent(STREET, value);
507 else if (name.equals("st"))
508 putComponent(ST, value);
509 else if (name.equals ("o"))
510 putComponent (O, value);
511 else if (name.equals ("ou"))
512 putComponent (OU, value);
513 else if (name.equals("dc"))
514 putComponent(DC, value);
515 else if (name.equals("uid"))
516 putComponent(UID, value);
517 else
518 putComponent(new OID(name), value);
519 }
520
521 private static String compressWS(String str)
522 {
523 CPStringBuilder buf = new CPStringBuilder();
524 char lastChar = 0;
525 for (int i = 0; i < str.length(); i++)
526 {
527 char c = str.charAt(i);
528 if (Character.isWhitespace(c))
529 {
530 if (!Character.isWhitespace(lastChar))
531 buf.append(' ');
532 }
533 else
534 buf.append(c);
535 lastChar = c;
536 }
537 return buf.toString().trim();
538 }
539
540 private static byte[] toByteArray (String str)
541 {
542 int limit = str.length();
543 byte[] result = new byte[((limit + 1) / 2)];
544 int i = 0, j = 0;
545 if ((limit % 2) == 1)
546 {
547 result[j++] = (byte) Character.digit (str.charAt(i++), 16);
548 }
549 while (i < limit)
550 {
551 result[j ] = (byte) (Character.digit (str.charAt(i++), 16) << 4);
552 result[j++] |= (byte) Character.digit (str.charAt(i++), 16);
553 }
554 return result;
555 }
556 }