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 }