1 module hunt.security.x500.X500Principal; 2 3 import hunt.security.x509.X500Name; 4 import hunt.security.Principal; 5 6 import hunt.collection; 7 import hunt.Exceptions; 8 import hunt.text.Common; 9 10 import std.array; 11 12 13 /** 14 * <p> This class represents an X.500 {@code Principal}. 15 * {@code X500Principal}s are represented by distinguished names such as 16 * "CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US". 17 * 18 * <p> This class can be instantiated by using a string representation 19 * of the distinguished name, or by using the ASN.1 DER encoded byte 20 * representation of the distinguished name. The current specification 21 * for the string representation of a distinguished name is defined in 22 * <a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253: Lightweight 23 * Directory Access Protocol (v3): UTF-8 string Representation of 24 * Distinguished Names</a>. This class, however, accepts string formats from 25 * both RFC 2253 and <a href="http://www.ietf.org/rfc/rfc1779.txt">RFC 1779: 26 * A string Representation of Distinguished Names</a>, and also recognizes 27 * attribute type keywords whose OIDs (Object Identifiers) are defined in 28 * <a href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280: Internet X.509 29 * Public Key Infrastructure Certificate and CRL Profile</a>. 30 * 31 * <p> The string representation for this {@code X500Principal} 32 * can be obtained by calling the {@code getName} methods. 33 * 34 * <p> Note that the {@code getSubjectX500Principal} and 35 * {@code getIssuerX500Principal} methods of 36 * {@code X509Certificate} return X500Principals representing the 37 * issuer and subject fields of the certificate. 38 * 39 * @see java.security.cert.X509Certificate 40 * @since 1.4 41 */ 42 final class X500Principal : Principal { 43 44 // private static final long serialVersionUID = -500463348111345721L; 45 46 /** 47 * RFC 1779 string format of Distinguished Names. 48 */ 49 enum string RFC1779 = "RFC1779"; 50 /** 51 * RFC 2253 string format of Distinguished Names. 52 */ 53 enum string RFC2253 = "RFC2253"; 54 /** 55 * Canonical string format of Distinguished Names. 56 */ 57 enum string CANONICAL = "CANONICAL"; 58 59 /** 60 * The X500Name representing this principal. 61 * 62 * NOTE: this field is reflectively accessed from within X500Name. 63 */ 64 private X500Name thisX500Name; 65 66 /** 67 * Creates an X500Principal by wrapping an X500Name. 68 * 69 * NOTE: The constructor is package private. It is intended to be accessed 70 * using privileged reflection from classes in sun.security.*. 71 * Currently referenced from sun.security.x509.X500Name.asX500Principal(). 72 */ 73 this(X500Name x500Name) { 74 thisX500Name = x500Name; 75 } 76 77 /** 78 * Creates an {@code X500Principal} from a string representation of 79 * an X.500 distinguished name (ex: 80 * "CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US"). 81 * The distinguished name must be specified using the grammar defined in 82 * RFC 1779 or RFC 2253 (either format is acceptable). 83 * 84 * <p>This constructor recognizes the attribute type keywords 85 * defined in RFC 1779 and RFC 2253 86 * (and listed in {@link #getName(string format) getName(string format)}), 87 * as well as the T, DNQ or DNQUALIFIER, SURNAME, GIVENNAME, INITIALS, 88 * GENERATION, EMAILADDRESS, and SERIALNUMBER keywords whose Object 89 * Identifiers (OIDs) are defined in RFC 3280 and its successor. 90 * Any other attribute type must be specified as an OID. 91 * 92 * <p>This implementation enforces a more restrictive OID syntax than 93 * defined in RFC 1779 and 2253. It uses the more correct syntax defined in 94 * <a href="http://www.ietf.org/rfc/rfc4512.txt">RFC 4512</a>, which 95 * specifies that OIDs contain at least 2 digits: 96 * 97 * <p>{@code numericoid = number 1*( DOT number ) } 98 * 99 * @param name an X.500 distinguished name in RFC 1779 or RFC 2253 format 100 * @exception NullPointerException if the {@code name} 101 * is {@code null} 102 * @exception IllegalArgumentException if the {@code name} 103 * is improperly specified 104 */ 105 this(string name) { 106 this(name, Collections.emptyMap!(string, string)()); 107 } 108 109 /** 110 * Creates an {@code X500Principal} from a string representation of 111 * an X.500 distinguished name (ex: 112 * "CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US"). 113 * The distinguished name must be specified using the grammar defined in 114 * RFC 1779 or RFC 2253 (either format is acceptable). 115 * 116 * <p> This constructor recognizes the attribute type keywords specified 117 * in {@link #X500Principal(string)} and also recognizes additional 118 * keywords that have entries in the {@code keywordMap} parameter. 119 * Keyword entries in the keywordMap take precedence over the default 120 * keywords recognized by {@code X500Principal(string)}. Keywords 121 * MUST be specified in all upper-case, otherwise they will be ignored. 122 * Improperly specified keywords are ignored; however if a keyword in the 123 * name maps to an improperly specified Object Identifier (OID), an 124 * {@code IllegalArgumentException} is thrown. It is permissible to 125 * have 2 different keywords that map to the same OID. 126 * 127 * <p>This implementation enforces a more restrictive OID syntax than 128 * defined in RFC 1779 and 2253. It uses the more correct syntax defined in 129 * <a href="http://www.ietf.org/rfc/rfc4512.txt">RFC 4512</a>, which 130 * specifies that OIDs contain at least 2 digits: 131 * 132 * <p>{@code numericoid = number 1*( DOT number ) } 133 * 134 * @param name an X.500 distinguished name in RFC 1779 or RFC 2253 format 135 * @param keywordMap an attribute type keyword map, where each key is a 136 * keyword string that maps to a corresponding object identifier in string 137 * form (a sequence of nonnegative integers separated by periods). The map 138 * may be empty but never {@code null}. 139 * @exception NullPointerException if {@code name} or 140 * {@code keywordMap} is {@code null} 141 * @exception IllegalArgumentException if the {@code name} is 142 * improperly specified or a keyword in the {@code name} maps to an 143 * OID that is not in the correct form 144 * @since 1.6 145 */ 146 this(string name, Map!(string, string) keywordMap) { 147 if (name.empty) { 148 throw new NullPointerException("provided.null.name"); 149 } 150 if (keywordMap is null) { 151 throw new NullPointerException("provided.null.keyword.map"); 152 } 153 154 try { 155 thisX500Name = new X500Name(name, keywordMap); 156 } catch (Exception e) { 157 IllegalArgumentException iae = new IllegalArgumentException 158 ("improperly specified input name: " ~ name, e); 159 throw iae; 160 } 161 } 162 163 /** 164 * Creates an {@code X500Principal} from a distinguished name in 165 * ASN.1 DER encoded form. The ASN.1 notation for this structure is as 166 * follows. 167 * <pre>{@code 168 * Name ::= CHOICE { 169 * RDNSequence } 170 * 171 * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName 172 * 173 * RelativeDistinguishedName ::= 174 * SET SIZE (1 .. MAX) OF AttributeTypeAndValue 175 * 176 * AttributeTypeAndValue ::= SEQUENCE { 177 * type AttributeType, 178 * value AttributeValue } 179 * 180 * AttributeType ::= OBJECT IDENTIFIER 181 * 182 * AttributeValue ::= ANY DEFINED BY AttributeType 183 * .... 184 * DirectoryString ::= CHOICE { 185 * teletexString TeletexString (SIZE (1..MAX)), 186 * printableString PrintableString (SIZE (1..MAX)), 187 * universalString UniversalString (SIZE (1..MAX)), 188 * utf8String UTF8String (SIZE (1.. MAX)), 189 * bmpString BMPString (SIZE (1..MAX)) } 190 * }</pre> 191 * 192 * @param name a byte array containing the distinguished name in ASN.1 193 * DER encoded form 194 * @throws IllegalArgumentException if an encoding error occurs 195 * (incorrect form for DN) 196 */ 197 this(byte[] name) { 198 try { 199 thisX500Name = new X500Name(name); 200 } catch (Exception e) { 201 IllegalArgumentException iae = new IllegalArgumentException 202 ("improperly specified input name", e); 203 throw iae; 204 } 205 } 206 207 /** 208 * Creates an {@code X500Principal} from an {@code InputStream} 209 * containing the distinguished name in ASN.1 DER encoded form. 210 * The ASN.1 notation for this structure is supplied in the 211 * documentation for 212 * {@link #X500Principal(byte[] name) X500Principal(byte[] name)}. 213 * 214 * <p> The read position of the input stream is positioned 215 * to the next available byte after the encoded distinguished name. 216 * 217 * @param is an {@code InputStream} containing the distinguished 218 * name in ASN.1 DER encoded form 219 * 220 * @exception NullPointerException if the {@code InputStream} 221 * is {@code null} 222 * @exception IllegalArgumentException if an encoding error occurs 223 * (incorrect form for DN) 224 */ 225 // this(InputStream inputStream) { 226 // if (inputStream is null) { 227 // throw new NullPointerException("provided null input stream"); 228 // } 229 230 // try { 231 // if (inputStream.markSupported()) 232 // inputStream.mark(inputStream.available() + 1); 233 // DerValue der = new DerValue(inputStream); 234 // thisX500Name = new X500Name(der.data); 235 // } catch (Exception e) { 236 // if (inputStream.markSupported()) { 237 // try { 238 // inputStream.reset(); 239 // } catch (IOException ioe) { 240 // IllegalArgumentException iae = new IllegalArgumentException 241 // ("improperly specified input stream " ~ 242 // ("and unable to reset input stream"), e); 243 // throw iae; 244 // } 245 // } 246 // IllegalArgumentException iae = new IllegalArgumentException 247 // ("improperly specified input stream", e); 248 // throw iae; 249 // } 250 // } 251 252 /** 253 * Returns a string representation of the X.500 distinguished name using 254 * the format defined in RFC 2253. 255 * 256 * <p>This method is equivalent to calling 257 * {@code getName(X500Principal.RFC2253)}. 258 * 259 * @return the distinguished name of this {@code X500Principal} 260 */ 261 string getName() { 262 return getName(X500Principal.RFC2253); 263 } 264 265 /** 266 * Returns a string representation of the X.500 distinguished name 267 * using the specified format. Valid values for the format are 268 * "RFC1779", "RFC2253", and "CANONICAL" (case insensitive). 269 * 270 * <p> If "RFC1779" is specified as the format, 271 * this method emits the attribute type keywords defined in 272 * RFC 1779 (CN, L, ST, O, OU, C, STREET). 273 * Any other attribute type is emitted as an OID. 274 * 275 * <p> If "RFC2253" is specified as the format, 276 * this method emits the attribute type keywords defined in 277 * RFC 2253 (CN, L, ST, O, OU, C, STREET, DC, UID). 278 * Any other attribute type is emitted as an OID. 279 * Under a strict reading, RFC 2253 only specifies a UTF-8 string 280 * representation. The string returned by this method is the 281 * Unicode string achieved by decoding this UTF-8 representation. 282 * 283 * <p> If "CANONICAL" is specified as the format, 284 * this method returns an RFC 2253 conformant string representation 285 * with the following additional canonicalizations: 286 * 287 * <ol> 288 * <li> Leading zeros are removed from attribute types 289 * that are encoded as dotted decimal OIDs 290 * <li> DirectoryString attribute values of type 291 * PrintableString and UTF8String are not 292 * output in hexadecimal format 293 * <li> DirectoryString attribute values of types 294 * other than PrintableString and UTF8String 295 * are output in hexadecimal format 296 * <li> Leading and trailing white space characters 297 * are removed from non-hexadecimal attribute values 298 * (unless the value consists entirely of white space characters) 299 * <li> Internal substrings of one or more white space characters are 300 * converted to a single space in non-hexadecimal 301 * attribute values 302 * <li> Relative Distinguished Names containing more than one 303 * Attribute Value Assertion (AVA) are output in the 304 * following order: an alphabetical ordering of AVAs 305 * containing standard keywords, followed by a numeric 306 * ordering of AVAs containing OID keywords. 307 * <li> The only characters in attribute values that are escaped are 308 * those which section 2.4 of RFC 2253 states must be escaped 309 * (they are escaped using a preceding backslash character) 310 * <li> The entire name is converted to upper case 311 * using {@code string.toUpperCase(Locale.US)} 312 * <li> The entire name is converted to lower case 313 * using {@code string.toLowerCase(Locale.US)} 314 * <li> The name is finally normalized using normalization form KD, 315 * as described in the Unicode Standard and UAX #15 316 * </ol> 317 * 318 * <p> Additional standard formats may be introduced in the future. 319 * 320 * @param format the format to use 321 * 322 * @return a string representation of this {@code X500Principal} 323 * using the specified format 324 * @throws IllegalArgumentException if the specified format is invalid 325 * or null 326 */ 327 string getName(string format) { 328 if (format !is null) { 329 if (format.equalsIgnoreCase(RFC1779)) { 330 return thisX500Name.getRFC1779Name(); 331 } else if (format.equalsIgnoreCase(RFC2253)) { 332 return thisX500Name.getRFC2253Name(); 333 } else if (format.equalsIgnoreCase(CANONICAL)) { 334 return thisX500Name.getRFC2253CanonicalName(); 335 } 336 } 337 throw new IllegalArgumentException("invalid format specified"); 338 } 339 340 /** 341 * Returns a string representation of the X.500 distinguished name 342 * using the specified format. Valid values for the format are 343 * "RFC1779" and "RFC2253" (case insensitive). "CANONICAL" is not 344 * permitted and an {@code IllegalArgumentException} will be thrown. 345 * 346 * <p>This method returns Strings in the format as specified in 347 * {@link #getName(string)} and also emits additional attribute type 348 * keywords for OIDs that have entries in the {@code oidMap} 349 * parameter. OID entries in the oidMap take precedence over the default 350 * OIDs recognized by {@code getName(string)}. 351 * Improperly specified OIDs are ignored; however if an OID 352 * in the name maps to an improperly specified keyword, an 353 * {@code IllegalArgumentException} is thrown. 354 * 355 * <p> Additional standard formats may be introduced in the future. 356 * 357 * <p> Warning: additional attribute type keywords may not be recognized 358 * by other implementations; therefore do not use this method if 359 * you are unsure if these keywords will be recognized by other 360 * implementations. 361 * 362 * @param format the format to use 363 * @param oidMap an OID map, where each key is an object identifier in 364 * string form (a sequence of nonnegative integers separated by periods) 365 * that maps to a corresponding attribute type keyword string. 366 * The map may be empty but never {@code null}. 367 * @return a string representation of this {@code X500Principal} 368 * using the specified format 369 * @throws IllegalArgumentException if the specified format is invalid, 370 * null, or an OID in the name maps to an improperly specified keyword 371 * @throws NullPointerException if {@code oidMap} is {@code null} 372 * @since 1.6 373 */ 374 string getName(string format, Map!(string, string) oidMap) { 375 if (oidMap is null) { 376 throw new NullPointerException("provided.null.OID.map"); 377 } 378 if (format !is null) { 379 if (format.equalsIgnoreCase(RFC1779)) { 380 return thisX500Name.getRFC1779Name(oidMap); 381 } else if (format.equalsIgnoreCase(RFC2253)) { 382 return thisX500Name.getRFC2253Name(oidMap); 383 } 384 } 385 throw new IllegalArgumentException("invalid format specified"); 386 } 387 388 /** 389 * Returns the distinguished name in ASN.1 DER encoded form. The ASN.1 390 * notation for this structure is supplied in the documentation for 391 * {@link #X500Principal(byte[] name) X500Principal(byte[] name)}. 392 * 393 * <p>Note that the byte array returned is cloned to protect against 394 * subsequent modifications. 395 * 396 * @return a byte array containing the distinguished name in ASN.1 DER 397 * encoded form 398 */ 399 byte[] getEncoded() { 400 try { 401 return thisX500Name.getEncoded(); 402 } catch (IOException e) { 403 throw new RuntimeException("unable to get encoding", e); 404 } 405 } 406 407 /** 408 * Return a user-friendly string representation of this 409 * {@code X500Principal}. 410 * 411 * @return a string representation of this {@code X500Principal} 412 */ 413 override string toString() { 414 return thisX500Name.toString(); 415 } 416 417 /** 418 * Compares the specified {@code Object} with this 419 * {@code X500Principal} for equality. 420 * 421 * <p> Specifically, this method returns {@code true} if 422 * the {@code Object} <i>o</i> is an {@code X500Principal} 423 * and if the respective canonical string representations 424 * (obtained via the {@code getName(X500Principal.CANONICAL)} method) 425 * of this object and <i>o</i> are equal. 426 * 427 * <p> This implementation is compliant with the requirements of RFC 3280. 428 * 429 * @param o Object to be compared for equality with this 430 * {@code X500Principal} 431 * 432 * @return {@code true} if the specified {@code Object} is equal 433 * to this {@code X500Principal}, {@code false} otherwise 434 */ 435 override bool opEquals(Object o) { 436 if (this is o) { 437 return true; 438 } 439 if (typeid(o) != typeid(X500Principal)) { 440 return false; 441 } 442 X500Principal other = cast(X500Principal)o; 443 return this.thisX500Name == other.thisX500Name; 444 } 445 446 /** 447 * Return a hash code for this {@code X500Principal}. 448 * 449 * <p> The hash code is calculated via: 450 * {@code getName(X500Principal.CANONICAL).hashCode()} 451 * 452 * @return a hash code for this {@code X500Principal} 453 */ 454 override size_t toHash() @trusted nothrow { 455 return thisX500Name.toHash(); 456 } 457 458 // /** 459 // * Save the X500Principal object to a stream. 460 // * 461 // * @serialData this {@code X500Principal} is serialized 462 // * by writing out its DER-encoded form 463 // * (the value of {@code getEncoded} is serialized). 464 // */ 465 // private void writeObject(java.io.ObjectOutputStream s) { 466 // s.writeObject(thisX500Name.getEncodedInternal()); 467 // } 468 469 // /** 470 // * Reads this object from a stream (i.e., deserializes it). 471 // */ 472 // private void readObject(java.io.ObjectInputStream s) { 473 // // re-create thisX500Name 474 // thisX500Name = new X500Name(cast(byte[])s.readObject()); 475 // } 476 }