Home | History | Annotate | Download | only in x509
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Alexander Y. Kleymenov
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.x509;
     24 
     25 import java.io.IOException;
     26 import java.util.ArrayList;
     27 import java.util.Arrays;
     28 import java.util.Collection;
     29 import java.util.Collections;
     30 import java.util.HashMap;
     31 import java.util.HashSet;
     32 import java.util.List;
     33 import java.util.Set;
     34 import javax.security.auth.x500.X500Principal;
     35 import org.apache.harmony.security.asn1.ASN1SequenceOf;
     36 import org.apache.harmony.security.asn1.ASN1Type;
     37 import org.apache.harmony.security.asn1.BerInputStream;
     38 
     39 /**
     40  * The class encapsulates the ASN.1 DER encoding/decoding work
     41  * with the Extensions part of X.509 certificate
     42  * (as specified in RFC 3280 -
     43  *  Internet X.509 Public Key Infrastructure.
     44  *  Certificate and Certificate Revocation List (CRL) Profile.
     45  *  http://www.ietf.org/rfc/rfc3280.txt):
     46  *
     47  * <pre>
     48  *  Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
     49  * </pre>
     50  */
     51 public final class Extensions {
     52 
     53     // Supported critical extensions oids:
     54     private static List SUPPORTED_CRITICAL = Arrays.asList(
     55             "2.5.29.15", "2.5.29.19", "2.5.29.32", "2.5.29.17",
     56             "2.5.29.30", "2.5.29.36", "2.5.29.37", "2.5.29.54");
     57 
     58     // the values of extensions of the structure
     59     private final List<Extension> extensions;
     60 
     61     // to speed up access, the following fields cache values computed
     62     // from the extensions field, initialized using the "single-check
     63     // idiom".
     64 
     65     private volatile Set<String> critical;
     66     private volatile Set<String> noncritical;
     67     // the flag showing is there any unsupported critical extension
     68     // in the list of extensions or not.
     69     private volatile Boolean hasUnsupported;
     70 
     71     // map containing the oid of extensions as a keys and
     72     // Extension objects as values
     73     private volatile HashMap<String, Extension> oidMap;
     74 
     75     // the ASN.1 encoded form of Extensions
     76     private byte[] encoding;
     77 
     78     /**
     79      * Constructs an object representing the value of Extensions.
     80      */
     81     public Extensions() {
     82         this.extensions = null;
     83     }
     84 
     85     public Extensions(List<Extension> extensions) {
     86         this.extensions = extensions;
     87     }
     88 
     89     public int size() {
     90         return (extensions == null) ? 0 : extensions.size();
     91     }
     92 
     93     /**
     94      * Returns the list of critical extensions.
     95      */
     96     public Set<String> getCriticalExtensions() {
     97         Set<String> resultCritical = critical;
     98         if (resultCritical == null) {
     99             makeOidsLists();
    100             resultCritical = critical;
    101         }
    102         return resultCritical;
    103     }
    104 
    105     /**
    106      * Returns the list of critical extensions.
    107      */
    108     public Set<String> getNonCriticalExtensions() {
    109         Set<String> resultNoncritical = noncritical;
    110         if (resultNoncritical == null) {
    111             makeOidsLists();
    112             resultNoncritical = noncritical;
    113         }
    114         return resultNoncritical;
    115     }
    116 
    117     public boolean hasUnsupportedCritical() {
    118         Boolean resultHasUnsupported = hasUnsupported;
    119         if (resultHasUnsupported == null) {
    120             makeOidsLists();
    121             resultHasUnsupported = hasUnsupported;
    122         }
    123         return resultHasUnsupported.booleanValue();
    124     }
    125 
    126     //
    127     // Makes the separated lists with oids of critical
    128     // and non-critical extensions
    129     //
    130     private void makeOidsLists() {
    131         if (extensions == null) {
    132             return;
    133         }
    134         int size = extensions.size();
    135         Set<String> localCritical = new HashSet<String>(size);
    136         Set<String> localNoncritical = new HashSet<String>(size);
    137         Boolean localHasUnsupported = Boolean.FALSE;
    138         for (Extension extension : extensions) {
    139             String oid = extension.getExtnID();
    140             if (extension.getCritical()) {
    141                 if (!SUPPORTED_CRITICAL.contains(oid)) {
    142                     localHasUnsupported = Boolean.TRUE;
    143                 }
    144                 localCritical.add(oid);
    145             } else {
    146                 localNoncritical.add(oid);
    147             }
    148         }
    149         this.critical = localCritical;
    150         this.noncritical = localNoncritical;
    151         this.hasUnsupported = localHasUnsupported;
    152     }
    153 
    154     /**
    155      * Returns the values of extensions.
    156      */
    157     public Extension getExtensionByOID(String oid) {
    158         if (extensions == null) {
    159             return null;
    160         }
    161         HashMap<String, Extension> localOidMap = oidMap;
    162         if (localOidMap == null) {
    163             localOidMap = new HashMap<String, Extension>();
    164             for (Extension extension : extensions) {
    165                 localOidMap.put(extension.getExtnID(), extension);
    166             }
    167             this.oidMap = localOidMap;
    168         }
    169         return localOidMap.get(oid);
    170     }
    171 
    172 
    173     /**
    174      * Returns the value of Key Usage extension (OID == 2.5.29.15).
    175      * The ASN.1 definition of Key Usage Extension is:
    176      *
    177      * <pre>
    178      * id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
    179      *
    180      * KeyUsage ::= BIT STRING {
    181      *     digitalSignature        (0),
    182      *     nonRepudiation          (1),
    183      *     keyEncipherment         (2),
    184      *     dataEncipherment        (3),
    185      *     keyAgreement            (4),
    186      *     keyCertSign             (5),
    187      *     cRLSign                 (6),
    188      *     encipherOnly            (7),
    189      *     decipherOnly            (8)
    190      * }
    191      * </pre>
    192      * (as specified in RFC 3280)
    193      *
    194      * @return the value of Key Usage Extension if it is in the list,
    195      * and null if there is no such extension or its value can not be decoded
    196      * otherwise. Note, that the length of returned array can be greater
    197      * than 9.
    198      */
    199     public boolean[] valueOfKeyUsage() {
    200         Extension extension = getExtensionByOID("2.5.29.15");
    201         KeyUsage kUsage;
    202         if ((extension == null) || ((kUsage = extension.getKeyUsageValue()) == null)) {
    203             return null;
    204         }
    205         return kUsage.getKeyUsage();
    206     }
    207 
    208     /**
    209      * Returns the value of Extended Key Usage extension (OID == 2.5.29.37).
    210      * The ASN.1 definition of Extended Key Usage Extension is:
    211      *
    212      * <pre>
    213      *  id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
    214      *
    215      *  ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
    216      *
    217      *  KeyPurposeId ::= OBJECT IDENTIFIER
    218      * </pre>
    219      * (as specified in RFC 3280)
    220      *
    221      * @return the list with string representations of KeyPurposeId's OIDs
    222      * and null
    223      * @throws IOException if extension was incorrectly encoded.
    224      */
    225     public List<String> valueOfExtendedKeyUsage() throws IOException {
    226         Extension extension = getExtensionByOID("2.5.29.37");
    227         if (extension == null) {
    228             return null;
    229         }
    230         return ((ExtendedKeyUsage) extension.getDecodedExtensionValue()).getExtendedKeyUsage();
    231     }
    232 
    233     /**
    234      * Returns the value of Basic Constraints Extension (OID = 2.5.29.19).
    235      * The ASN.1 definition of Basic Constraints Extension is:
    236      *
    237      * <pre>
    238      *   id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
    239      *
    240      *   BasicConstraints ::= SEQUENCE {
    241      *        cA                      BOOLEAN DEFAULT FALSE,
    242      *        pathLenConstraint       INTEGER (0..MAX) OPTIONAL
    243      *   }
    244      * </pre>
    245      * (as specified in RFC 3280)
    246      *
    247      * @return-1 if the Basic Constraints Extension is not present or
    248      * it is present but it indicates the certificate is not a
    249      * certificate authority. If the certificate is a certificate
    250      * authority, returns the path length constraint if present, or
    251      * Integer.MAX_VALUE if it is not.
    252      */
    253     public int valueOfBasicConstraints() {
    254         Extension extension = getExtensionByOID("2.5.29.19");
    255         if (extension == null) {
    256             return -1;
    257         }
    258         BasicConstraints bc = extension.getBasicConstraintsValue();
    259         if (bc == null || !bc.getCa()) {
    260             return -1;
    261         }
    262         return bc.getPathLenConstraint();
    263     }
    264 
    265     /**
    266      * Returns the value of Subject Alternative Name (OID = 2.5.29.17).
    267      * The ASN.1 definition for Subject Alternative Name is:
    268      *
    269      * <pre>
    270      *  id-ce-subjectAltName OBJECT IDENTIFIER ::=  { id-ce 17 }
    271      *
    272      *  SubjectAltName ::= GeneralNames
    273      * </pre>
    274      * (as specified in RFC 3280)
    275      *
    276      * @return Returns the collection of pairs:
    277      * (Integer (tag), Object (name value)) if extension presents, and
    278      * null if does not.
    279      */
    280     public Collection<List<?>> valueOfSubjectAlternativeName() throws IOException {
    281         return decodeGeneralNames(getExtensionByOID("2.5.29.17"));
    282     }
    283 
    284     /**
    285      * Returns the value of Issuer Alternative Name Extension (OID = 2.5.29.18).
    286      * The ASN.1 definition for Issuer Alternative Name is:
    287      *
    288      * <pre>
    289      *   id-ce-issuerAltName OBJECT IDENTIFIER ::=  { id-ce 18 }
    290      *
    291      *   IssuerAltName ::= GeneralNames
    292      * </pre>
    293      * (as specified in RFC 3280)
    294      *
    295      * @return Returns the collection of pairs:
    296      * (Integer (tag), Object (name value)) if extension presents, and
    297      * null if does not.
    298      */
    299     public Collection<List<?>> valueOfIssuerAlternativeName() throws IOException {
    300         return decodeGeneralNames(getExtensionByOID("2.5.29.18"));
    301     }
    302 
    303     /**
    304      * Given an X.509 extension that encodes GeneralNames, return it in the
    305      * format expected by APIs.
    306      */
    307     private static Collection<List<?>> decodeGeneralNames(Extension extension)
    308             throws IOException {
    309         if (extension == null) {
    310             return null;
    311         }
    312 
    313         Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension
    314                 .getExtnValue())).getPairsList();
    315 
    316         /*
    317          * If the extension had any invalid entries, we may have an empty
    318          * collection at this point, so just return null.
    319          */
    320         if (collection.size() == 0) {
    321             return null;
    322         }
    323 
    324         return Collections.unmodifiableCollection(collection);
    325     }
    326 
    327     /**
    328      * Returns the value of Certificate Issuer Extension (OID = 2.5.29.29).
    329      * It is a CRL entry extension and contains the GeneralNames describing
    330      * the issuer of revoked certificate. Its ASN.1 notation is as follows:
    331      * <pre>
    332      *   id-ce-certificateIssuer   OBJECT IDENTIFIER ::= { id-ce 29 }
    333      *
    334      *   certificateIssuer ::=     GeneralNames
    335      * </pre>
    336      * (as specified in RFC 3280)
    337      *
    338      * @return the value of Certificate Issuer Extension
    339      */
    340     public X500Principal valueOfCertificateIssuerExtension() throws IOException {
    341         Extension extension = getExtensionByOID("2.5.29.29");
    342         if (extension == null) {
    343             return null;
    344         }
    345         return ((CertificateIssuer) extension.getDecodedExtensionValue()).getIssuer();
    346     }
    347 
    348     /**
    349      * Returns ASN.1 encoded form of this X.509 Extensions value.
    350      */
    351     public byte[] getEncoded() {
    352         if (encoding == null) {
    353             encoding = ASN1.encode(this);
    354         }
    355         return encoding;
    356     }
    357 
    358     @Override public boolean equals(Object other) {
    359         if (!(other instanceof Extensions)) {
    360             return false;
    361         }
    362         Extensions that = (Extensions) other;
    363         return (this.extensions == null || this.extensions.isEmpty())
    364                     ? (that.extensions == null || that.extensions.isEmpty())
    365                     : (this.extensions.equals(that.extensions));
    366     }
    367 
    368     @Override public int hashCode() {
    369         int hashCode = 0;
    370         if (extensions != null) {
    371             hashCode = extensions.hashCode();
    372         }
    373         return hashCode;
    374     }
    375 
    376     public void dumpValue(StringBuilder sb, String prefix) {
    377         if (extensions == null) {
    378             return;
    379         }
    380         int num = 1;
    381         for (Extension extension: extensions) {
    382             sb.append('\n').append(prefix).append('[').append(num++).append("]: ");
    383             extension.dumpValue(sb, prefix);
    384         }
    385     }
    386 
    387     /**
    388      * Custom X.509 Extensions decoder.
    389      */
    390     public static final ASN1Type ASN1 = new ASN1SequenceOf(Extension.ASN1) {
    391         @Override public Object getDecodedObject(BerInputStream in) {
    392             return new Extensions((List<Extension>) in.content);
    393         }
    394 
    395         @Override public Collection getValues(Object object) {
    396             Extensions extensions = (Extensions) object;
    397             return (extensions.extensions == null) ? new ArrayList() : extensions.extensions;
    398         }
    399     };
    400 }
    401