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