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 }