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