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 }