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