Source for gnu.java.security.x509.X500DistinguishedName

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