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    }