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