OSDN Git Service

2004-09-21 Andreas Tobler <a.tobler@schweiz.ch>
[pf3gnuchains/gcc-fork.git] / libjava / javax / security / auth / x500 / X500Principal.java
1 /* X500Principal.java -- X.500 principal.
2    Copyright (C) 2003, 2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10  
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.security.auth.x500;
40
41 import gnu.java.security.OID;
42 import gnu.java.security.der.BitString;
43 import gnu.java.security.der.DER;
44 import gnu.java.security.der.DEREncodingException;
45 import gnu.java.security.der.DERReader;
46 import gnu.java.security.der.DERValue;
47
48 import java.io.ByteArrayInputStream;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.EOFException;
52 import java.io.NotActiveException;
53 import java.io.ObjectInputStream;
54 import java.io.ObjectOutputStream;
55 import java.io.Reader;
56 import java.io.Serializable;
57 import java.io.StringReader;
58
59 import java.security.Principal;
60
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.HashSet;
64 import java.util.Iterator;
65 import java.util.LinkedHashMap;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.Locale;
69 import java.util.Map;
70 import java.util.Set;
71 import java.util.TreeMap;
72
73 public final class X500Principal implements Principal, Serializable
74 {
75   private static final long serialVersionUID = -500463348111345721L;
76
77   // Constants and fields.
78   // ------------------------------------------------------------------------
79
80   public static final String CANONICAL = "CANONICAL";
81   public static final String RFC1779 = "RFC1779";
82   public static final String RFC2253 = "RFC2253";
83
84   private static final OID CN         = new OID("2.5.4.3");
85   private static final OID C          = new OID("2.5.4.6");
86   private static final OID L          = new OID("2.5.4.7");
87   private static final OID ST         = new OID("2.5.4.8");
88   private static final OID STREET     = new OID("2.5.4.9");
89   private static final OID O          = new OID("2.5.4.10");
90   private static final OID OU         = new OID("2.5.4.11");
91   private static final OID DC         = new OID("0.9.2342.19200300.100.1.25");
92   private static final OID UID        = new OID("0.9.2342.19200300.100.1.1");
93
94   private transient List components;
95   private transient Map currentRdn;
96   private transient boolean fixed;
97   private transient byte[] encoded;
98
99   // Constructors.
100   // ------------------------------------------------------------------------
101
102   private X500Principal()
103   {
104     components = new LinkedList();
105     currentRdn = new LinkedHashMap();
106     components.add (currentRdn);
107   }
108
109   public X500Principal (String name)
110   {
111     this();
112     if (name == null)
113       throw new NullPointerException();
114     try
115       {
116         parseString (name);
117       }
118     catch (IOException ioe)
119       {
120         IllegalArgumentException iae = new IllegalArgumentException("malformed name");
121         iae.initCause (ioe);
122         throw iae;
123       }
124       }
125
126   public X500Principal (byte[] encoded)
127   {
128     this(new ByteArrayInputStream (encoded));
129   }
130
131   public X500Principal (InputStream encoded)
132   {
133     this();
134     try
135       {
136         parseDer (encoded);
137       }
138     catch (IOException ioe)
139       {
140         throw new IllegalArgumentException (ioe.toString());
141       }
142   }
143
144   // Instance methods.
145   // ------------------------------------------------------------------------
146
147   public boolean equals(Object o)
148   {
149     if (!(o instanceof X500Principal))
150       return false;
151     if (size() != ((X500Principal) o).size())
152       return false;
153     for (int i = 0; i < size(); i++)
154       {
155         Map m = (Map) components.get (i);
156         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
157           {
158             Map.Entry e = (Map.Entry) it2.next();
159             OID oid = (OID) e.getKey();
160             String v1 = (String) e.getValue();
161             String v2 = ((X500Principal) o).getComponent (oid, i);
162             if (v2 == null)
163               return false;
164             if (!compressWS (v1).equalsIgnoreCase (compressWS (v2)))
165               return false;
166           }
167       }
168     return true;
169   }
170
171   public byte[] getEncoded()
172   {
173     if (encoded == null)
174       encodeDer();
175     return (byte[]) encoded.clone();
176   }
177
178   public String getName()
179   {
180     return getName (RFC2253);
181   }
182
183   public String getName (final String format)
184   {
185     boolean rfc2253 = RFC2253.equalsIgnoreCase (format) ||
186       CANONICAL.equalsIgnoreCase (format);
187     boolean rfc1779 = RFC1779.equalsIgnoreCase (format);
188     boolean canon   = CANONICAL.equalsIgnoreCase (format);
189     if (! (rfc2253 || rfc1779 || canon))
190       throw new IllegalArgumentException ("unsupported format " + format);
191     StringBuffer str = new StringBuffer();
192     for (Iterator it = components.iterator(); it.hasNext(); )
193       {
194         Map m = (Map) it.next();
195         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
196           {
197             Map.Entry entry = (Map.Entry) it2.next();
198             OID oid = (OID) entry.getKey();
199             String value = (String) entry.getValue();
200             if (oid.equals (CN))
201               str.append ("CN");
202             else if (oid.equals (C))
203               str.append ("C");
204             else if (oid.equals (L))
205               str.append ("L");
206             else if (oid.equals (ST))
207               str.append ("ST");
208             else if (oid.equals (STREET))
209               str.append ("STREET");
210             else if (oid.equals (O))
211               str.append ("O");
212             else if (oid.equals (OU))
213               str.append ("OU");
214             else if (oid.equals (DC) && rfc2253)
215               str.append ("DC");
216             else if (oid.equals ("UID") && rfc2253)
217               str.append ("UID");
218             else
219               str.append (oid.toString());
220             str.append('=');
221             str.append(value);
222             if (it2.hasNext())
223               str.append('+');
224           }
225         if (it.hasNext())
226           str.append(',');
227       }
228     if (canon)
229       return str.toString().toUpperCase (Locale.US).toLowerCase (Locale.US);
230     return str.toString();
231   }
232
233   public String toString()
234   {
235     return getName (RFC2253);
236   }
237
238   // Serialization methods.
239   // ------------------------------------------------------------------------
240
241   private void writeObject (ObjectOutputStream out) throws IOException
242   {
243     if (encoded != null)
244       encodeDer();
245     out.writeObject (encoded);
246   }
247
248   private void readObject (ObjectInputStream in)
249     throws IOException, NotActiveException, ClassNotFoundException
250   {
251     byte[] buf = (byte[]) in.readObject();
252     parseDer (new ByteArrayInputStream (buf));
253   }
254
255   // Own methods.
256   // -------------------------------------------------------------------------
257
258   private int size()
259   {
260     return components.size();
261   }
262
263   private String getComponent(OID oid, int rdn)
264   {
265     if (rdn >= size())
266       return null;
267     return (String) ((Map) components.get (rdn)).get (oid);
268   }
269
270   private void encodeDer()
271   {
272     ArrayList name = new ArrayList(components.size());
273     for (Iterator it = components.iterator(); it.hasNext(); )
274       {
275         Map m = (Map) it.next();
276         if (m.isEmpty())
277           continue;
278         Set rdn = new HashSet();
279         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
280           {
281             Map.Entry e = (Map.Entry) it.next();
282             ArrayList atav = new ArrayList(2);
283             atav.add(new DERValue(DER.OBJECT_IDENTIFIER, e.getKey()));
284             atav.add(new DERValue(DER.UTF8_STRING, e.getValue()));
285             rdn.add(new DERValue(DER.SEQUENCE|DER.CONSTRUCTED, atav));
286           }
287         name.add(new DERValue(DER.SET|DER.CONSTRUCTED, rdn));
288       }
289     DERValue val = new DERValue(DER.SEQUENCE|DER.CONSTRUCTED, name);
290     encoded = val.getEncoded();
291   }
292
293   private int sep;
294
295   private void parseString(String str) throws IOException
296   {
297     Reader in = new StringReader(str);
298     while (true)
299       {
300         String key = readAttributeType(in);
301         if (key == null)
302           break;
303         String value = readAttributeValue(in);
304         putComponent(key, value);
305         if (sep == ',')
306           newRelativeDistinguishedName();
307       }
308   }
309
310   private String readAttributeType(Reader in) throws IOException
311   {
312     StringBuffer buf = new StringBuffer();
313     int ch;
314     while ((ch = in.read()) != '=')
315       {
316         if (ch == -1)
317           {
318             if (buf.length() > 0)
319               throw new EOFException();
320             return null;
321           }
322         if (ch > 127)
323           throw new IOException("Invalid char: " + (char) ch);
324         if (Character.isLetterOrDigit((char) ch) || ch == '-' || ch == '.')
325           buf.append((char) ch);
326         else
327           throw new IOException("Invalid char: " + (char) ch);
328       }
329     return buf.toString();
330   }
331
332   private String readAttributeValue(Reader in) throws IOException
333   {
334     StringBuffer buf = new StringBuffer();
335     int ch = in.read();
336     if (ch == '#')
337       {
338         while (true)
339           {
340             ch = in.read();
341             if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
342                 || Character.isDigit((char) ch))
343               buf.append((char) ch);
344             else if (ch == '+' || ch == ',')
345               {
346                 sep = ch;
347                 String hex = buf.toString();
348                 return new String(toByteArray(hex));
349               }
350             else
351               throw new IOException("illegal character: " + (char) ch);
352           }
353       }
354     else if (ch == '"')
355       {
356         while (true)
357           {
358             ch = in.read();
359             if (ch == '"')
360               break;
361             else if (ch == '\\')
362               {
363                 ch = in.read();
364                 if (ch == -1)
365                   throw new EOFException();
366                 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
367                     || Character.isDigit((char) ch))
368                   {
369                     int i = Character.digit((char) ch, 16) << 4;
370                     ch = in.read();
371                     if (!(('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
372                           || Character.isDigit((char) ch)))
373                       throw new IOException("illegal hex char");
374                     i |= Character.digit((char) ch, 16);
375                     buf.append((char) i);
376                   }
377                 else
378                   buf.append((char) ch);
379               }
380             else
381               buf.append((char) ch);
382           }
383         sep = in.read();
384         if (sep != '+' || sep != ',')
385           throw new IOException("illegal character: " + (char) ch);
386         return buf.toString();
387       }
388     else
389       {
390         while (true)
391           {
392             switch (ch)
393               {
394               case '+':
395               case ',':
396                 sep = ch;
397                 return buf.toString();
398               case '\\':
399                 ch = in.read();
400                 if (ch == -1)
401                   throw new EOFException();
402                 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
403                     || Character.isDigit((char) ch))
404                   {
405                     int i = Character.digit((char) ch, 16) << 4;
406                     ch = in.read();
407                     if (!(('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
408                           || Character.isDigit((char) ch)))
409                       throw new IOException("illegal hex char");
410                     i |= Character.digit((char) ch, 16);
411                     buf.append((char) i);
412                   }
413                 else
414                   buf.append((char) ch);
415                 break;
416               case '=':
417               case '<':
418               case '>':
419               case '#':
420               case ';':
421                 throw new IOException("illegal character: " + (char) ch);
422               case -1:
423                 throw new EOFException();
424               default:
425                 buf.append((char) ch);
426               }
427           }
428       }
429   }
430
431   private void parseDer (InputStream encoded) throws IOException
432   {
433     DERReader der = new DERReader (encoded);
434     DERValue name = der.read();
435     if (!name.isConstructed())
436       throw new IOException ("malformed Name");
437     this.encoded = name.getEncoded();
438     int len = 0;
439     while (len < name.getLength())
440       {
441         DERValue rdn = der.read();
442         if (!rdn.isConstructed())
443           throw new IOException ("badly formed RDNSequence");
444         int len2 = 0;
445         while (len2 < rdn.getLength())
446           {
447             DERValue atav = der.read();
448             if (!atav.isConstructed())
449               throw new IOException ("badly formed AttributeTypeAndValue");
450             DERValue val = der.read();
451             if (val.getTag() != DER.OBJECT_IDENTIFIER)
452               throw new IOException ("badly formed AttributeTypeAndValue");
453             OID oid = (OID) val.getValue();
454             val = der.read();
455             if (!(val.getValue() instanceof String))
456               throw new IOException ("badly formed AttributeTypeAndValue");
457             String value = (String) val.getValue();
458             putComponent(oid, value);
459             len2 += atav.getEncodedLength();
460           }
461         len += rdn.getEncodedLength();
462         if (len < name.getLength())
463           newRelativeDistinguishedName();
464       }
465   }
466
467   private void newRelativeDistinguishedName()
468   {
469     currentRdn = new LinkedHashMap();
470     components.add(currentRdn);
471   }
472
473   private void putComponent(OID oid, String value)
474   {
475     currentRdn.put(oid, value);
476   }
477
478   private void putComponent(String name, String value)
479   {
480     name = name.trim().toLowerCase();
481     if (name.equals("cn"))
482       putComponent(CN, value);
483     else if (name.equals("c"))
484       putComponent(C, value);
485     else if (name.equals("l"))
486       putComponent(L, value);
487     else if (name.equals("street"))
488       putComponent(STREET, value);
489     else if (name.equals("st"))
490       putComponent(ST, value);
491     else if (name.equals("dc"))
492       putComponent(DC, value);
493     else if (name.equals("uid"))
494       putComponent(UID, value);
495     else
496       putComponent(new OID(name), value);
497   }
498
499   private static String compressWS(String str)
500   {
501     StringBuffer buf = new StringBuffer();
502     char lastChar = 0;
503     for (int i = 0; i < str.length(); i++)
504       {
505         char c = str.charAt(i);
506         if (Character.isWhitespace(c))
507           {
508             if (!Character.isWhitespace(lastChar))
509               buf.append(' ');
510           }
511         else
512           buf.append(c);
513         lastChar = c;
514       }
515     return buf.toString().trim();
516   }
517
518   private static byte[] toByteArray (String str)
519   {
520     int limit = str.length();
521     byte[] result = new byte[((limit + 1) / 2)];
522     int i = 0, j = 0;
523     if ((limit % 2) == 1)
524       {
525         result[j++] = (byte) Character.digit (str.charAt(i++), 16);
526       }
527     while (i < limit)
528       {
529         result[j  ]  = (byte) (Character.digit (str.charAt(i++), 16) << 4);
530         result[j++] |= (byte)  Character.digit (str.charAt(i++), 16);
531       }
532     return result;
533   }
534 }