Home | History | Annotate | Download | only in x509
      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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    256     RDN(int i) { assertion = new AVA[i]; }
    257 
    258     public RDN(AVA ava) {
    259         if (ava == null) {
    260             throw new NullPointerException();
    261         }
    262         assertion = new AVA[] { ava };
    263     }
    264 
    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      */
    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      */
    289     public int size() {
    290         return assertion.length;
    291     }
    292 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    469     private AVAComparator() {
    470         // empty
    471     }
    472 
    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      */
    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