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