1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package sun.security.x509; 28 29 import java.lang.reflect.*; 30 import java.io.IOException; 31 import java.io.StringReader; 32 import java.security.PrivilegedExceptionAction; 33 import java.security.AccessController; 34 import java.security.Principal; 35 import java.util.*; 36 37 import sun.security.util.*; 38 import sun.security.pkcs.PKCS9Attribute; 39 import javax.security.auth.x500.X500Principal; 40 41 /** 42 * RDNs are a set of {attribute = value} assertions. Some of those 43 * attributes are "distinguished" (unique w/in context). Order is 44 * never relevant. 45 * 46 * Some X.500 names include only a single distinguished attribute 47 * per RDN. This style is currently common. 48 * 49 * Note that DER-encoded RDNs sort AVAs by assertion OID ... so that 50 * when we parse this data we don't have to worry about canonicalizing 51 * it, but we'll need to sort them when we expose the RDN class more. 52 * <p> 53 * The ASN.1 for RDNs is: 54 * <pre> 55 * RelativeDistinguishedName ::= 56 * SET OF AttributeTypeAndValue 57 * 58 * AttributeTypeAndValue ::= SEQUENCE { 59 * type AttributeType, 60 * value AttributeValue } 61 * 62 * AttributeType ::= OBJECT IDENTIFIER 63 * 64 * AttributeValue ::= ANY DEFINED BY AttributeType 65 * </pre> 66 * 67 * Note that instances of this class are immutable. 68 * 69 */ 70 public class RDN { 71 72 // currently not private, accessed directly from X500Name 73 final AVA[] assertion; 74 75 // cached immutable List of the AVAs 76 private volatile List<AVA> avaList; 77 78 // cache canonical String form 79 private volatile String canonicalString; 80 81 /** 82 * Constructs an RDN from its printable representation. 83 * 84 * An RDN may consist of one or multiple Attribute Value Assertions (AVAs), 85 * using '+' as a separator. 86 * If the '+' should be considered part of an AVA value, it must be 87 * preceded by '\'. 88 * 89 * @param name String form of RDN 90 * @throws IOException on parsing error 91 */ 92 public RDN(String name) throws IOException { 93 this(name, Collections.<String, String>emptyMap()); 94 } 95 96 /** 97 * Constructs an RDN from its printable representation. 98 * 99 * An RDN may consist of one or multiple Attribute Value Assertions (AVAs), 100 * using '+' as a separator. 101 * If the '+' should be considered part of an AVA value, it must be 102 * preceded by '\'. 103 * 104 * @param name String form of RDN 105 * @param keyword an additional mapping of keywords to OIDs 106 * @throws IOException on parsing error 107 */ 108 public RDN(String name, Map<String, String> keywordMap) throws IOException { 109 int quoteCount = 0; 110 int searchOffset = 0; 111 int avaOffset = 0; 112 List<AVA> avaVec = new ArrayList<AVA>(3); 113 int nextPlus = name.indexOf('+'); 114 while (nextPlus >= 0) { 115 quoteCount += X500Name.countQuotes(name, searchOffset, nextPlus); 116 /* 117 * We have encountered an AVA delimiter (plus sign). 118 * If the plus sign in the RDN under consideration is 119 * preceded by a backslash (escape), or by a double quote, it 120 * is part of the AVA. Otherwise, it is used as a separator, to 121 * delimit the AVA under consideration from any subsequent AVAs. 122 */ 123 if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\' 124 && quoteCount != 1) { 125 /* 126 * Plus sign is a separator 127 */ 128 String avaString = name.substring(avaOffset, nextPlus); 129 if (avaString.length() == 0) { 130 throw new IOException("empty AVA in RDN \"" + name + "\""); 131 } 132 133 // Parse AVA, and store it in vector 134 AVA ava = new AVA(new StringReader(avaString), keywordMap); 135 avaVec.add(ava); 136 137 // Increase the offset 138 avaOffset = nextPlus + 1; 139 140 // Set quote counter back to zero 141 quoteCount = 0; 142 } 143 searchOffset = nextPlus + 1; 144 nextPlus = name.indexOf('+', searchOffset); 145 } 146 147 // parse last or only AVA 148 String avaString = name.substring(avaOffset); 149 if (avaString.length() == 0) { 150 throw new IOException("empty AVA in RDN \"" + name + "\""); 151 } 152 AVA ava = new AVA(new StringReader(avaString), keywordMap); 153 avaVec.add(ava); 154 155 assertion = avaVec.toArray(new AVA[avaVec.size()]); 156 } 157 158 /* 159 * Constructs an RDN from its printable representation. 160 * 161 * An RDN may consist of one or multiple Attribute Value Assertions (AVAs), 162 * using '+' as a separator. 163 * If the '+' should be considered part of an AVA value, it must be 164 * preceded by '\'. 165 * 166 * @param name String form of RDN 167 * @throws IOException on parsing error 168 */ 169 RDN(String name, String format) throws IOException { 170 this(name, format, Collections.<String, String>emptyMap()); 171 } 172 173 /* 174 * Constructs an RDN from its printable representation. 175 * 176 * An RDN may consist of one or multiple Attribute Value Assertions (AVAs), 177 * using '+' as a separator. 178 * If the '+' should be considered part of an AVA value, it must be 179 * preceded by '\'. 180 * 181 * @param name String form of RDN 182 * @param keyword an additional mapping of keywords to OIDs 183 * @throws IOException on parsing error 184 */ 185 RDN(String name, String format, Map<String, String> keywordMap) 186 throws IOException { 187 if (format.equalsIgnoreCase("RFC2253") == false) { 188 throw new IOException("Unsupported format " + format); 189 } 190 int searchOffset = 0; 191 int avaOffset = 0; 192 List<AVA> avaVec = new ArrayList<AVA>(3); 193 int nextPlus = name.indexOf('+'); 194 while (nextPlus >= 0) { 195 /* 196 * We have encountered an AVA delimiter (plus sign). 197 * If the plus sign in the RDN under consideration is 198 * preceded by a backslash (escape), or by a double quote, it 199 * is part of the AVA. Otherwise, it is used as a separator, to 200 * delimit the AVA under consideration from any subsequent AVAs. 201 */ 202 if (nextPlus > 0 && name.charAt(nextPlus - 1) != '\\' ) { 203 /* 204 * Plus sign is a separator 205 */ 206 String avaString = name.substring(avaOffset, nextPlus); 207 if (avaString.length() == 0) { 208 throw new IOException("empty AVA in RDN \"" + name + "\""); 209 } 210 211 // Parse AVA, and store it in vector 212 AVA ava = new AVA 213 (new StringReader(avaString), AVA.RFC2253, keywordMap); 214 avaVec.add(ava); 215 216 // Increase the offset 217 avaOffset = nextPlus + 1; 218 } 219 searchOffset = nextPlus + 1; 220 nextPlus = name.indexOf('+', searchOffset); 221 } 222 223 // parse last or only AVA 224 String avaString = name.substring(avaOffset); 225 if (avaString.length() == 0) { 226 throw new IOException("empty AVA in RDN \"" + name + "\""); 227 } 228 AVA ava = new AVA(new StringReader(avaString), AVA.RFC2253, keywordMap); 229 avaVec.add(ava); 230 231 assertion = avaVec.toArray(new AVA[avaVec.size()]); 232 } 233 234 /* 235 * Constructs an RDN from an ASN.1 encoded value. The encoding 236 * of the name in the stream uses DER (a BER/1 subset). 237 * 238 * @param value a DER-encoded value holding an RDN. 239 * @throws IOException on parsing error. 240 */ 241 RDN(DerValue rdn) throws IOException { 242 if (rdn.tag != DerValue.tag_Set) { 243 throw new IOException("X500 RDN"); 244 } 245 DerInputStream dis = new DerInputStream(rdn.toByteArray()); 246 DerValue[] avaset = dis.getSet(5); 247 248 assertion = new AVA[avaset.length]; 249 for (int i = 0; i < avaset.length; i++) { 250 assertion[i] = new AVA(avaset[i]); 251 } 252 } 253 254 /* 255 * Creates an empty RDN with slots for specified 256 * number of AVAs. 257 * 258 * @param i number of AVAs to be in RDN 259 */ 260 RDN(int i) { assertion = new AVA[i]; } 261 262 public RDN(AVA ava) { 263 if (ava == null) { 264 throw new NullPointerException(); 265 } 266 assertion = new AVA[] { ava }; 267 } 268 269 public RDN(AVA[] avas) { 270 assertion = avas.clone(); 271 for (int i = 0; i < assertion.length; i++) { 272 if (assertion[i] == null) { 273 throw new NullPointerException(); 274 } 275 } 276 } 277 278 /** 279 * Return an immutable List of the AVAs in this RDN. 280 */ 281 public List<AVA> avas() { 282 List<AVA> list = avaList; 283 if (list == null) { 284 list = Collections.unmodifiableList(Arrays.asList(assertion)); 285 avaList = list; 286 } 287 return list; 288 } 289 290 /** 291 * Return the number of AVAs in this RDN. 292 */ 293 public int size() { 294 return assertion.length; 295 } 296 297 public boolean equals(Object obj) { 298 if (this == obj) { 299 return true; 300 } 301 if (obj instanceof RDN == false) { 302 return false; 303 } 304 RDN other = (RDN)obj; 305 if (this.assertion.length != other.assertion.length) { 306 return false; 307 } 308 String thisCanon = this.toRFC2253String(true); 309 String otherCanon = other.toRFC2253String(true); 310 return thisCanon.equals(otherCanon); 311 } 312 313 /* 314 * Calculates a hash code value for the object. Objects 315 * which are equal will also have the same hashcode. 316 * 317 * @returns int hashCode value 318 */ 319 public int hashCode() { 320 return toRFC2253String(true).hashCode(); 321 } 322 323 /* 324 * return specified attribute value from RDN 325 * 326 * @params oid ObjectIdentifier of attribute to be found 327 * @returns DerValue of attribute value; null if attribute does not exist 328 */ 329 DerValue findAttribute(ObjectIdentifier oid) { 330 for (int i = 0; i < assertion.length; i++) { 331 if (assertion[i].oid.equals(oid)) { 332 return assertion[i].value; 333 } 334 } 335 return null; 336 } 337 338 /* 339 * Encode the RDN in DER-encoded form. 340 * 341 * @param out DerOutputStream to which RDN is to be written 342 * @throws IOException on error 343 */ 344 void encode(DerOutputStream out) throws IOException { 345 out.putOrderedSetOf(DerValue.tag_Set, assertion); 346 } 347 348 /* 349 * Returns a printable form of this RDN, using RFC 1779 style catenation 350 * of attribute/value assertions, and emitting attribute type keywords 351 * from RFCs 1779, 2253, and 3280. 352 */ 353 public String toString() { 354 if (assertion.length == 1) { 355 return assertion[0].toString(); 356 } 357 358 StringBuilder sb = new StringBuilder(); 359 for (int i = 0; i < assertion.length; i++) { 360 if (i != 0) { 361 sb.append(" + "); 362 } 363 sb.append(assertion[i].toString()); 364 } 365 return sb.toString(); 366 } 367 368 /* 369 * Returns a printable form of this RDN using the algorithm defined in 370 * RFC 1779. Only RFC 1779 attribute type keywords are emitted. 371 */ 372 public String toRFC1779String() { 373 return toRFC1779String(Collections.<String, String>emptyMap()); 374 } 375 376 /* 377 * Returns a printable form of this RDN using the algorithm defined in 378 * RFC 1779. RFC 1779 attribute type keywords are emitted, as well 379 * as keywords contained in the OID/keyword map. 380 */ 381 public String toRFC1779String(Map<String, String> oidMap) { 382 if (assertion.length == 1) { 383 return assertion[0].toRFC1779String(oidMap); 384 } 385 386 StringBuilder sb = new StringBuilder(); 387 for (int i = 0; i < assertion.length; i++) { 388 if (i != 0) { 389 sb.append(" + "); 390 } 391 sb.append(assertion[i].toRFC1779String(oidMap)); 392 } 393 return sb.toString(); 394 } 395 396 /* 397 * Returns a printable form of this RDN using the algorithm defined in 398 * RFC 2253. Only RFC 2253 attribute type keywords are emitted. 399 */ 400 public String toRFC2253String() { 401 return toRFC2253StringInternal 402 (false, Collections.<String, String>emptyMap()); 403 } 404 405 /* 406 * Returns a printable form of this RDN using the algorithm defined in 407 * RFC 2253. RFC 2253 attribute type keywords are emitted, as well as 408 * keywords contained in the OID/keyword map. 409 */ 410 public String toRFC2253String(Map<String, String> oidMap) { 411 return toRFC2253StringInternal(false, oidMap); 412 } 413 414 /* 415 * Returns a printable form of this RDN using the algorithm defined in 416 * RFC 2253. Only RFC 2253 attribute type keywords are emitted. 417 * If canonical is true, then additional canonicalizations 418 * documented in X500Principal.getName are performed. 419 */ 420 public String toRFC2253String(boolean canonical) { 421 if (canonical == false) { 422 return toRFC2253StringInternal 423 (false, Collections.<String, String>emptyMap()); 424 } 425 String c = canonicalString; 426 if (c == null) { 427 c = toRFC2253StringInternal 428 (true, Collections.<String, String>emptyMap()); 429 canonicalString = c; 430 } 431 return c; 432 } 433 434 private String toRFC2253StringInternal 435 (boolean canonical, Map<String, String> oidMap) { 436 /* 437 * Section 2.2: When converting from an ASN.1 RelativeDistinguishedName 438 * to a string, the output consists of the string encodings of each 439 * AttributeTypeAndValue (according to 2.3), in any order. 440 * 441 * Where there is a multi-valued RDN, the outputs from adjoining 442 * AttributeTypeAndValues are separated by a plus ('+' ASCII 43) 443 * character. 444 */ 445 446 // normally, an RDN only contains one AVA 447 if (assertion.length == 1) { 448 return canonical ? assertion[0].toRFC2253CanonicalString() : 449 assertion[0].toRFC2253String(oidMap); 450 } 451 452 StringBuilder relname = new StringBuilder(); 453 if (!canonical) { 454 for (int i = 0; i < assertion.length; i++) { 455 if (i > 0) { 456 relname.append('+'); 457 } 458 relname.append(assertion[i].toRFC2253String(oidMap)); 459 } 460 } else { 461 // order the string type AVA's alphabetically, 462 // followed by the oid type AVA's numerically 463 List<AVA> avaList = new ArrayList<AVA>(assertion.length); 464 for (int i = 0; i < assertion.length; i++) { 465 avaList.add(assertion[i]); 466 } 467 java.util.Collections.sort(avaList, AVAComparator.getInstance()); 468 469 for (int i = 0; i < avaList.size(); i++) { 470 if (i > 0) { 471 relname.append('+'); 472 } 473 relname.append(avaList.get(i).toRFC2253CanonicalString()); 474 } 475 } 476 return relname.toString(); 477 } 478 479 } 480 481 class AVAComparator implements Comparator<AVA> { 482 483 private static final Comparator<AVA> INSTANCE = new AVAComparator(); 484 485 private AVAComparator() { 486 // empty 487 } 488 489 static Comparator<AVA> getInstance() { 490 return INSTANCE; 491 } 492 493 /** 494 * AVA's containing a standard keyword are ordered alphabetically, 495 * followed by AVA's containing an OID keyword, ordered numerically 496 */ 497 @Override 498 public int compare(AVA a1, AVA a2) { 499 boolean a1Has2253 = a1.hasRFC2253Keyword(); 500 boolean a2Has2253 = a2.hasRFC2253Keyword(); 501 502 if (a1Has2253) { 503 if (a2Has2253) { 504 return a1.toRFC2253CanonicalString().compareTo 505 (a2.toRFC2253CanonicalString()); 506 } else { 507 return -1; 508 } 509 } else { 510 if (a2Has2253) { 511 return 1; 512 } else { 513 int[] a1Oid = a1.getObjectIdentifier().toIntArray(); 514 int[] a2Oid = a2.getObjectIdentifier().toIntArray(); 515 int pos = 0; 516 int len = (a1Oid.length > a2Oid.length) ? a2Oid.length : 517 a1Oid.length; 518 while (pos < len && a1Oid[pos] == a2Oid[pos]) { 519 ++pos; 520 } 521 return (pos == len) ? a1Oid.length - a2Oid.length : 522 a1Oid[pos] - a2Oid[pos]; 523 } 524 } 525 } 526 527 } 528