Source for gnu.java.security.x509.X500DistinguishedName

   1: /* X500DistinguishedName.java -- X.500 distinguished name.
   2:    Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 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 gnu.java.security.x509;
  40: 
  41: import gnu.java.security.OID;
  42: import gnu.java.security.der.DER;
  43: import gnu.java.security.der.DERReader;
  44: import gnu.java.security.der.DERValue;
  45: 
  46: import java.io.EOFException;
  47: import java.io.IOException;
  48: import java.io.InputStream;
  49: import java.io.Reader;
  50: import java.io.StringReader;
  51: import java.security.Principal;
  52: import java.util.ArrayList;
  53: import java.util.Collections;
  54: import java.util.HashSet;
  55: import java.util.Iterator;
  56: import java.util.LinkedHashMap;
  57: import java.util.LinkedList;
  58: import java.util.List;
  59: import java.util.Map;
  60: import java.util.Set;
  61: 
  62: public class X500DistinguishedName implements Principal
  63: {
  64: 
  65:   // Constants and fields.
  66:   // -------------------------------------------------------------------------
  67: 
  68:   public static final OID CN         = new OID("2.5.4.3");
  69:   public static final OID C          = new OID("2.5.4.6");
  70:   public static final OID L          = new OID("2.5.4.7");
  71:   public static final OID ST         = new OID("2.5.4.8");
  72:   public static final OID STREET     = new OID("2.5.4.9");
  73:   public static final OID O          = new OID("2.5.4.10");
  74:   public static final OID OU         = new OID("2.5.4.11");
  75:   public static final OID T          = new OID("2.5.4.12");
  76:   public static final OID DNQ        = new OID("2.5.4.46");
  77:   public static final OID NAME       = new OID("2.5.4.41");
  78:   public static final OID GIVENNAME  = new OID("2.5.4.42");
  79:   public static final OID INITIALS   = new OID("2.5.4.43");
  80:   public static final OID GENERATION = new OID("2.5.4.44");
  81:   public static final OID EMAIL      = new OID("1.2.840.113549.1.9.1");
  82:   public static final OID DC         = new OID("0.9.2342.19200300.100.1.25");
  83:   public static final OID UID        = new OID("0.9.2342.19200300.100.1.1");
  84: 
  85:   private List components;
  86:   private Map currentRdn;
  87:   private boolean fixed;
  88:   private String stringRep;
  89:   private byte[] encoded;
  90: 
  91:   // Constructors.
  92:   // -------------------------------------------------------------------------
  93: 
  94:   public X500DistinguishedName()
  95:   {
  96:     components = new LinkedList();
  97:     currentRdn = new LinkedHashMap();
  98:     components.add(currentRdn);
  99:   }
 100: 
 101:   public X500DistinguishedName(String name)
 102:   {
 103:     this();
 104:     try
 105:       {
 106:         parseString(name);
 107:       }
 108:     catch (IOException ioe)
 109:       {
 110:         throw new IllegalArgumentException(ioe.toString());
 111:       }
 112:   }
 113: 
 114:   public X500DistinguishedName(byte[] encoded) throws IOException
 115:   {
 116:     this();
 117:     parseDer(new DERReader(encoded));
 118:   }
 119: 
 120:   public X500DistinguishedName(InputStream encoded) throws IOException
 121:   {
 122:     this();
 123:     parseDer(new DERReader(encoded));
 124:   }
 125: 
 126:   // Instance methods.
 127:   // -------------------------------------------------------------------------
 128: 
 129:   public String getName()
 130:   {
 131:     return toString();
 132:   }
 133: 
 134:   public void newRelativeDistinguishedName()
 135:   {
 136:     if (fixed || currentRdn.isEmpty()) return;
 137:     currentRdn = new LinkedHashMap();
 138:     components.add(currentRdn);
 139:   }
 140: 
 141:   public int size()
 142:   {
 143:     return components.size();
 144:   }
 145: 
 146:   public int countComponents()
 147:   {
 148:     int count = 0;
 149:     for (Iterator it = components.iterator(); it.hasNext(); )
 150:       {
 151:         count += ((Map) it.next()).size();
 152:       }
 153:     return count;
 154:   }
 155: 
 156:   public boolean containsComponent(OID oid, String value)
 157:   {
 158:     for (Iterator it = components.iterator(); it.hasNext(); )
 159:       {
 160:         Map rdn = (Map) it.next();
 161:         String s = (String) rdn.get(oid);
 162:         if (s == null)
 163:           continue;
 164:         if (compressWS(value).equalsIgnoreCase(compressWS(s)))
 165:           return true;
 166:       }
 167:     return false;
 168:   }
 169: 
 170:   public String getComponent(OID oid)
 171:   {
 172:     for (Iterator it = components.iterator(); it.hasNext(); )
 173:       {
 174:         Map rdn = (Map) it.next();
 175:         if (rdn.containsKey(oid))
 176:           return (String) rdn.get(oid);
 177:       }
 178:     return null;
 179:   }
 180: 
 181:   public String getComponent(OID oid, int rdn)
 182:   {
 183:     if (rdn >= size())
 184:       return null;
 185:     return (String) ((Map) components.get(rdn)).get(oid);
 186:   }
 187: 
 188:   public void putComponent(OID oid, String value)
 189:   {
 190:     currentRdn.put(oid, value);
 191:   }
 192: 
 193:   public void putComponent(String name, String value)
 194:   {
 195:     name = name.trim().toLowerCase();
 196:     if (name.equals("cn"))
 197:       putComponent(CN, value);
 198:     else if (name.equals("c"))
 199:       putComponent(C, value);
 200:     else if (name.equals("l"))
 201:       putComponent(L, value);
 202:     else if (name.equals("street"))
 203:       putComponent(STREET, value);
 204:     else if (name.equals("st"))
 205:       putComponent(ST, value);
 206:     else if (name.equals("t"))
 207:       putComponent(T, value);
 208:     else if (name.equals("dnq"))
 209:       putComponent(DNQ, value);
 210:     else if (name.equals("name"))
 211:       putComponent(NAME, value);
 212:     else if (name.equals("givenname"))
 213:       putComponent(GIVENNAME, value);
 214:     else if (name.equals("initials"))
 215:       putComponent(INITIALS, value);
 216:     else if (name.equals("generation"))
 217:       putComponent(GENERATION, value);
 218:     else if (name.equals("email"))
 219:       putComponent(EMAIL, value);
 220:     else if (name.equals("dc"))
 221:       putComponent(DC, value);
 222:     else if (name.equals("uid"))
 223:       putComponent(UID, value);
 224:     else
 225:       putComponent(new OID(name), value);
 226:   }
 227: 
 228:   public void setUnmodifiable()
 229:   {
 230:     if (fixed) return;
 231:     fixed = true;
 232:     List newComps = new ArrayList(components.size());
 233:     for (Iterator it = components.iterator(); it.hasNext(); )
 234:       {
 235:         Map rdn = (Map) it.next();
 236:         rdn = Collections.unmodifiableMap(rdn);
 237:         newComps.add(rdn);
 238:       }
 239:     components = Collections.unmodifiableList(newComps);
 240:     currentRdn = Collections.EMPTY_MAP;
 241:   }
 242: 
 243:   public int hashCode()
 244:   {
 245:     int sum = 0;
 246:     for (Iterator it = components.iterator(); it.hasNext(); )
 247:       {
 248:         Map m = (Map) it.next();
 249:         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
 250:           {
 251:             Map.Entry e = (Map.Entry) it2.next();
 252:             sum += e.getKey().hashCode();
 253:             sum += e.getValue().hashCode();
 254:           }
 255:       }
 256:     return sum;
 257:   }
 258: 
 259:   public boolean equals(Object o)
 260:   {
 261:     if (!(o instanceof X500DistinguishedName))
 262:       return false;
 263:     if (size() != ((X500DistinguishedName) o).size())
 264:       return false;
 265:     for (int i = 0; i < size(); i++)
 266:       {
 267:         Map m = (Map) components.get(i);
 268:         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
 269:           {
 270:             Map.Entry e = (Map.Entry) it2.next();
 271:             OID oid = (OID) e.getKey();
 272:             String v1 = (String) e.getValue();
 273:             String v2 = ((X500DistinguishedName) o).getComponent(oid, i);
 274:             if (!compressWS(v1).equalsIgnoreCase(compressWS(v2)))
 275:               return false;
 276:           }
 277:       }
 278:     return true;
 279:   }
 280: 
 281:   public String toString()
 282:   {
 283:     if (fixed && stringRep != null)
 284:       return stringRep;
 285:     StringBuffer str = new StringBuffer();
 286:     for (Iterator it = components.iterator(); it.hasNext(); )
 287:       {
 288:         Map m = (Map) it.next();
 289:         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
 290:           {
 291:             Map.Entry entry = (Map.Entry) it2.next();
 292:             OID oid = (OID) entry.getKey();
 293:             String value = (String) entry.getValue();
 294:             if (oid.equals(CN))
 295:               str.append("CN");
 296:             else if (oid.equals(C))
 297:               str.append("C");
 298:             else if (oid.equals(L))
 299:               str.append("L");
 300:             else if (oid.equals(ST))
 301:               str.append("ST");
 302:             else if (oid.equals(STREET))
 303:               str.append("STREET");
 304:             else if (oid.equals(O))
 305:               str.append("O");
 306:             else if (oid.equals(OU))
 307:               str.append("OU");
 308:             else if (oid.equals(T))
 309:               str.append("T");
 310:             else if (oid.equals(DNQ))
 311:               str.append("DNQ");
 312:             else if (oid.equals(NAME))
 313:               str.append("NAME");
 314:             else
 315:               str.append(oid.toString());
 316:             str.append('=');
 317:             str.append(value);
 318:             if (it2.hasNext())
 319:               str.append("+");
 320:           }
 321:         if (it.hasNext())
 322:           str.append(',');
 323:       }
 324:     return (stringRep = str.toString());
 325:   }
 326: 
 327:   public byte[] getDer()
 328:   {
 329:     if (fixed && encoded != null)
 330:       return (byte[]) encoded.clone();
 331:     ArrayList name = new ArrayList(components.size());
 332:     for (Iterator it = components.iterator(); it.hasNext(); )
 333:       {
 334:         Map m = (Map) it.next();
 335:         if (m.isEmpty())
 336:           continue;
 337:         Set rdn = new HashSet();
 338:         for (Iterator it2 = m.entrySet().iterator(); it2.hasNext(); )
 339:           {
 340:             Map.Entry e = (Map.Entry) it.next();
 341:             ArrayList atav = new ArrayList(2);
 342:             atav.add(new DERValue(DER.OBJECT_IDENTIFIER, e.getKey()));
 343:             atav.add(new DERValue(DER.UTF8_STRING, e.getValue()));
 344:             rdn.add(new DERValue(DER.SEQUENCE|DER.CONSTRUCTED, atav));
 345:           }
 346:         name.add(new DERValue(DER.SET|DER.CONSTRUCTED, rdn));
 347:       }
 348:     DERValue val = new DERValue(DER.SEQUENCE|DER.CONSTRUCTED, name);
 349:     return (byte[]) (encoded = val.getEncoded()).clone();
 350:   }
 351: 
 352:   // Own methods.
 353:   // -------------------------------------------------------------------------
 354: 
 355:   private int sep;
 356: 
 357:   private void parseString(String str) throws IOException
 358:   {
 359:     Reader in = new StringReader(str);
 360:     while (true)
 361:       {
 362:         String key = readAttributeType(in);
 363:         if (key == null)
 364:           break;
 365:         String value = readAttributeValue(in);
 366:         putComponent(key, value);
 367:         if (sep == ',')
 368:           newRelativeDistinguishedName();
 369:       }
 370:     setUnmodifiable();
 371:   }
 372: 
 373:   private String readAttributeType(Reader in) throws IOException
 374:   {
 375:     StringBuffer buf = new StringBuffer();
 376:     int ch;
 377:     while ((ch = in.read()) != '=')
 378:       {
 379:         if (ch == -1)
 380:           {
 381:             if (buf.length() > 0)
 382:               throw new EOFException();
 383:             return null;
 384:           }
 385:         if (ch > 127)
 386:           throw new IOException("Invalid char: " + (char) ch);
 387:         if (Character.isLetterOrDigit((char) ch) || ch == '-' || ch == '.')
 388:           buf.append((char) ch);
 389:         else
 390:           throw new IOException("Invalid char: " + (char) ch);
 391:       }
 392:     return buf.toString();
 393:   }
 394: 
 395:   private String readAttributeValue(Reader in) throws IOException
 396:   {
 397:     StringBuffer buf = new StringBuffer();
 398:     int ch = in.read();
 399:     if (ch == '#')
 400:       {
 401:         while (true)
 402:           {
 403:             ch = in.read();
 404:             if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
 405:                 || Character.isDigit((char) ch))
 406:               buf.append((char) ch);
 407:             else if (ch == '+' || ch == ',')
 408:               {
 409:                 sep = ch;
 410:                 String hex = buf.toString();
 411:                 return new String(Util.toByteArray(hex));
 412:               }
 413:             else
 414:               throw new IOException("illegal character: " + (char) ch);
 415:           }
 416:       }
 417:     else if (ch == '"')
 418:       {
 419:         while (true)
 420:           {
 421:             ch = in.read();
 422:             if (ch == '"')
 423:               break;
 424:             else if (ch == '\\')
 425:               {
 426:                 ch = in.read();
 427:                 if (ch == -1)
 428:                   throw new EOFException();
 429:                 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
 430:                     || Character.isDigit((char) ch))
 431:                   {
 432:                     int i = Character.digit((char) ch, 16) << 4;
 433:                     ch = in.read();
 434:                     if (!(('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
 435:                           || Character.isDigit((char) ch)))
 436:                       throw new IOException("illegal hex char");
 437:                     i |= Character.digit((char) ch, 16);
 438:                     buf.append((char) i);
 439:                   }
 440:                 else
 441:                   buf.append((char) ch);
 442:               }
 443:             else
 444:               buf.append((char) ch);
 445:           }
 446:         sep = in.read();
 447:         if (sep != '+' || sep != ',')
 448:           throw new IOException("illegal character: " + (char) ch);
 449:         return buf.toString();
 450:       }
 451:     else
 452:       {
 453:         while (true)
 454:           {
 455:             switch (ch)
 456:               {
 457:               case '+':
 458:               case ',':
 459:                 sep = ch;
 460:                 return buf.toString();
 461:               case '\\':
 462:                 ch = in.read();
 463:                 if (ch == -1)
 464:                   throw new EOFException();
 465:                 if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
 466:                     || Character.isDigit((char) ch))
 467:                   {
 468:                     int i = Character.digit((char) ch, 16) << 4;
 469:                     ch = in.read();
 470:                     if (!(('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')
 471:                           || Character.isDigit((char) ch)))
 472:                       throw new IOException("illegal hex char");
 473:                     i |= Character.digit((char) ch, 16);
 474:                     buf.append((char) i);
 475:                   }
 476:                 else
 477:                   buf.append((char) ch);
 478:                 break;
 479:               case '=':
 480:               case '<':
 481:               case '>':
 482:               case '#':
 483:               case ';':
 484:                 throw new IOException("illegal character: " + (char) ch);
 485:               case -1:
 486:                 throw new EOFException();
 487:               default:
 488:                 buf.append((char) ch);
 489:               }
 490:           }
 491:       }
 492:   }
 493: 
 494:   private void parseDer(DERReader der) throws IOException
 495:   {
 496:     DERValue name = der.read();
 497:     if (!name.isConstructed())
 498:       throw new IOException("malformed Name");
 499:     encoded = name.getEncoded();
 500:     int len = 0;
 501:     while (len < name.getLength())
 502:       {
 503:         DERValue rdn = der.read();
 504:         if (!rdn.isConstructed())
 505:           throw new IOException("badly formed RDNSequence");
 506:         int len2 = 0;
 507:         while (len2 < rdn.getLength())
 508:           {
 509:             DERValue atav = der.read();
 510:             if (!atav.isConstructed())
 511:               throw new IOException("badly formed AttributeTypeAndValue");
 512:             DERValue val = der.read();
 513:             if (val.getTag() != DER.OBJECT_IDENTIFIER)
 514:               throw new IOException("badly formed AttributeTypeAndValue");
 515:             OID oid = (OID) val.getValue();
 516:             val = der.read();
 517:             if (!(val.getValue() instanceof String))
 518:               throw new IOException("badly formed AttributeTypeAndValue");
 519:             String value = (String) val.getValue();
 520:             putComponent(oid, value);
 521:             len2 += atav.getEncodedLength();
 522:           }
 523:         len += rdn.getEncodedLength();
 524:         if (len < name.getLength())
 525:           newRelativeDistinguishedName();
 526:       }
 527:     setUnmodifiable();
 528:   }
 529: 
 530:   private static String compressWS(String str)
 531:   {
 532:     StringBuffer buf = new StringBuffer();
 533:     char lastChar = 0;
 534:     for (int i = 0; i < str.length(); i++)
 535:       {
 536:         char c = str.charAt(i);
 537:         if (Character.isWhitespace(c))
 538:           {
 539:             if (!Character.isWhitespace(lastChar))
 540:               buf.append(' ');
 541:           }
 542:         else
 543:           buf.append(c);
 544:         lastChar = c;
 545:       }
 546:     return buf.toString().trim();
 547:   }
 548: }