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.HashMap;
     30 import java.util.HashSet;
     31 import java.util.Iterator;
     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 
     52 public class Extensions {
     53 
     54     // Supported critical extensions oids:
     55     private static List SUPPORTED_CRITICAL = Arrays.asList(
     56             new String[] {"2.5.29.15", "2.5.29.19", "2.5.29.32", "2.5.29.17",
     57                 "2.5.29.30", "2.5.29.36", "2.5.29.37", "2.5.29.54"});
     58 
     59     // the values of extensions of the structure
     60     private List<Extension> extensions;
     61     private Set critical;
     62     private Set noncritical;
     63     // the flag showing is there any unsupported critical extension
     64     // in the list of extensions or not.
     65     private boolean hasUnsupported;
     66     // map containing the oid of extensions as a keys and
     67     // Extension objects as values
     68     private HashMap oidMap;
     69     // the ASN.1 encoded form of Extensions
     70     private byte[] encoding;
     71 
     72     /**
     73      * Constructs an object representing the value of Extensions.
     74      */
     75     public Extensions() {}
     76 
     77     /**
     78      * TODO
     79      * @param   extensions: List
     80      */
     81     public Extensions(List extensions) {
     82         this.extensions = extensions;
     83     }
     84 
     85     /**
     86      * Returns the values of extensions.
     87      * @return  extensions
     88      */
     89     public List getExtensions() {
     90         return extensions;
     91     }
     92 
     93     public int size() {
     94         return (extensions == null)
     95                         ? 0
     96                         : extensions.size();
     97     }
     98 
     99     /**
    100      * Returns the list of critical extensions.
    101      * @return  extensions
    102      */
    103     public Set getCriticalExtensions() {
    104         if (critical == null) {
    105             makeOidsLists();
    106         }
    107         return critical;
    108     }
    109 
    110     /**
    111      * Returns the list of critical extensions.
    112      * @return  extensions
    113      */
    114     public Set getNonCriticalExtensions() {
    115         if (noncritical == null) {
    116             makeOidsLists();
    117         }
    118         return noncritical;
    119     }
    120 
    121     public boolean hasUnsupportedCritical() {
    122         if (critical == null) {
    123             makeOidsLists();
    124         }
    125         return hasUnsupported;
    126     }
    127 
    128     //
    129     // Makes the separated lists with oids of critical
    130     // and non-critical extensions
    131     //
    132     private void makeOidsLists() {
    133         if (extensions == null) {
    134             return;
    135         }
    136         int size = extensions.size();
    137         critical = new HashSet(size);
    138         noncritical = new HashSet(size);
    139         for (int i=0; i<size; i++) {
    140             Extension extn = (Extension) extensions.get(i);
    141             String oid = extn.getExtnID();
    142             if (extn.getCritical()) {
    143                 if (!SUPPORTED_CRITICAL.contains(oid)) {
    144                     hasUnsupported = true;
    145                 }
    146                 critical.add(oid);
    147             } else {
    148                 noncritical.add(oid);
    149             }
    150         }
    151     }
    152 
    153     /**
    154      * Returns the values of extensions.
    155      * @param oid - the OID of needed extension.
    156      * @return  extensions
    157      */
    158     public Extension getExtensionByOID(String oid) {
    159         if (extensions == null) {
    160             return null;
    161         }
    162         if (oidMap == null) {
    163             oidMap = new HashMap();
    164             Iterator it = extensions.iterator();
    165             while (it.hasNext()) {
    166                 Extension extn = (Extension) it.next();
    167                 oidMap.put(extn.getExtnID(), extn);
    168             }
    169         }
    170         return (Extension) oidMap.get(oid);
    171     }
    172 
    173 
    174     /**
    175      * Returns the value of Key Usage extension (OID == 2.5.29.15).
    176      * The ASN.1 definition of Key Usage Extension is:
    177      *
    178      * <pre>
    179      * id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
    180      *
    181      * KeyUsage ::= BIT STRING {
    182      *     digitalSignature        (0),
    183      *     nonRepudiation          (1),
    184      *     keyEncipherment         (2),
    185      *     dataEncipherment        (3),
    186      *     keyAgreement            (4),
    187      *     keyCertSign             (5),
    188      *     cRLSign                 (6),
    189      *     encipherOnly            (7),
    190      *     decipherOnly            (8)
    191      * }
    192      * </pre>
    193      * (as specified in RFC 3280)
    194      *
    195      * @return the value of Key Usage Extension if it is in the list,
    196      * and null if there is no such extension or its value can not be decoded
    197      * otherwise. Note, that the length of returned array can be greater
    198      * than 9.
    199      */
    200     public boolean[] valueOfKeyUsage() {
    201         Extension extn = getExtensionByOID("2.5.29.15");
    202         KeyUsage kUsage = null;
    203         if ((extn == null) || ((kUsage = extn.getKeyUsageValue()) == null)) {
    204             return null;
    205         }
    206         return kUsage.getKeyUsage();
    207     }
    208 
    209     /**
    210      * Returns the value of Extended Key Usage extension (OID == 2.5.29.37).
    211      * The ASN.1 definition of Extended Key Usage Extension is:
    212      *
    213      * <pre>
    214      *  id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
    215      *
    216      *  ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
    217      *
    218      *  KeyPurposeId ::= OBJECT IDENTIFIER
    219      * </pre>
    220      * (as specified in RFC 3280)
    221      *
    222      * @return the list with string representations of KeyPurposeId's OIDs
    223      * and null
    224      * @throws IOException if extension was incorrectly encoded.
    225      */
    226     public List valueOfExtendedKeyUsage() throws IOException {
    227         Extension extn = getExtensionByOID("2.5.29.37");
    228         if (extn == null) {
    229             return null;
    230         }
    231         return ((ExtendedKeyUsage)
    232                 extn.getDecodedExtensionValue()).getExtendedKeyUsage();
    233     }
    234 
    235     /**
    236      * Returns the value of Basic Constraints Extension (OID = 2.5.29.19).
    237      * The ASN.1 definition of Basic Constraints Extension is:
    238      *
    239      * <pre>
    240      *   id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
    241      *
    242      *   BasicConstraints ::= SEQUENCE {
    243      *        cA                      BOOLEAN DEFAULT FALSE,
    244      *        pathLenConstraint       INTEGER (0..MAX) OPTIONAL
    245      *   }
    246      * </pre>
    247      * (as specified in RFC 3280)
    248      *
    249      * @return the value of pathLenConstraint field if extension presents,
    250      * and Integer.MAX_VALUE if does not.
    251      */
    252     public int valueOfBasicConstrains() {
    253         Extension extn = getExtensionByOID("2.5.29.19");
    254         BasicConstraints bc = null;
    255         if ((extn == null)
    256                 || ((bc = extn.getBasicConstraintsValue()) == null)) {
    257             return Integer.MAX_VALUE;
    258         }
    259         return bc.getPathLenConstraint();
    260     }
    261 
    262     /**
    263      * Returns the value of Subject Alternative Name (OID = 2.5.29.17).
    264      * The ASN.1 definition for Subject Alternative Name is:
    265      *
    266      * <pre>
    267      *  id-ce-subjectAltName OBJECT IDENTIFIER ::=  { id-ce 17 }
    268      *
    269      *  SubjectAltName ::= GeneralNames
    270      * </pre>
    271      * (as specified in RFC 3280)
    272      *
    273      * @return Returns the collection of pairs:
    274      * (Integer (tag), Object (name value)) if extension presents, and
    275      * null if does not.
    276      */
    277     public List valueOfSubjectAlternativeName() throws IOException {
    278         Extension extn = getExtensionByOID("2.5.29.17");
    279         if (extn == null) {
    280             return null;
    281         }
    282         return ((GeneralNames) GeneralNames.ASN1.decode(extn.getExtnValue()))
    283                 .getPairsList();
    284     }
    285 
    286     /**
    287      * Returns the value of Issuer Alternative Name Extension (OID = 2.5.29.18).
    288      * The ASN.1 definition for Issuer Alternative Name is:
    289      *
    290      * <pre>
    291      *   id-ce-issuerAltName OBJECT IDENTIFIER ::=  { id-ce 18 }
    292      *
    293      *   IssuerAltName ::= GeneralNames
    294      * </pre>
    295      * (as specified in RFC 3280)
    296      *
    297      * @return Returns the collection of pairs:
    298      * (Integer (tag), Object (name value)) if extension presents, and
    299      * null if does not.
    300      */
    301     public List valueOfIssuerAlternativeName() throws IOException {
    302         Extension extn = getExtensionByOID("2.5.29.18");
    303         if (extn == null) {
    304             return null;
    305         }
    306         return ((GeneralNames)
    307                 GeneralNames.ASN1.decode(extn.getExtnValue())).getPairsList();
    308     }
    309 
    310     /**
    311      * Returns the value of Certificate Issuer Extension (OID = 2.5.29.29).
    312      * It is a CRL entry extension and contains the GeneralNames describing
    313      * the issuer of revoked certificate. Its ASN.1 notation is as follows:
    314      * <pre>
    315      *   id-ce-certificateIssuer   OBJECT IDENTIFIER ::= { id-ce 29 }
    316      *
    317      *   certificateIssuer ::=     GeneralNames
    318      * </pre>
    319      * (as specified in RFC 3280)
    320      *
    321      * @return the value of Certificate Issuer Extension
    322      */
    323     public X500Principal valueOfCertificateIssuerExtension()
    324                                                         throws IOException {
    325         Extension extn = getExtensionByOID("2.5.29.29");
    326         if (extn == null) {
    327             return null;
    328         }
    329         return ((CertificateIssuer)
    330                 extn.getDecodedExtensionValue()).getIssuer();
    331     }
    332 
    333     /**
    334      * TODO
    335      * @param   extn:  Extension
    336      * @return
    337      */
    338     public void addExtension(Extension extn) {
    339         encoding = null;
    340         if (extensions == null) {
    341             extensions = new ArrayList();
    342         }
    343         extensions.add(extn);
    344         if (oidMap != null) {
    345             oidMap.put(extn.getExtnID(), extn);
    346         }
    347         if (critical != null) {
    348             String oid = extn.getExtnID();
    349             if (extn.getCritical()) {
    350                 if (!SUPPORTED_CRITICAL.contains(oid)) {
    351                     hasUnsupported = true;
    352                 }
    353                 critical.add(oid);
    354             } else {
    355                 noncritical.add(oid);
    356             }
    357         }
    358     }
    359 
    360     /**
    361      * Returns ASN.1 encoded form of this X.509 Extensions value.
    362      * @return a byte array containing ASN.1 encode form.
    363      */
    364     public byte[] getEncoded() {
    365         if (encoding == null) {
    366             encoding = ASN1.encode(this);
    367         }
    368         return encoding;
    369     }
    370 
    371     public boolean equals(Object exts) {
    372         if (!(exts instanceof Extensions)) {
    373             return false;
    374         }
    375         Extensions extns = (Extensions) exts;
    376         return ((extensions == null) || (extensions.size() == 0)
    377                     ? ((extns.extensions == null)
    378                             || (extns.extensions.size() == 0))
    379                     : ((extns.extensions == null)
    380                             || (extns.extensions.size() == 0))
    381                         ? false
    382                         : (extensions.containsAll(extns.extensions)
    383                             && (extensions.size() == extns.extensions.size()))
    384                 );
    385     }
    386 
    387     public int hashCode() {
    388     	int hashcode = 0;
    389     	if (extensions != null) {
    390     		hashcode = extensions.hashCode();
    391     	}
    392     	return hashcode;
    393     }
    394 
    395     /**
    396      * Places the string representation into the StringBuffer object.
    397      */
    398     public void dumpValue(StringBuffer buffer, String prefix) {
    399         if (extensions == null) {
    400             return;
    401         }
    402         int num = 1;
    403         for (Extension extension: extensions) {
    404             buffer.append('\n').append(prefix)
    405                 .append('[').append(num++).append("]: ");
    406             extension.dumpValue(buffer, prefix);
    407         }
    408     }
    409 
    410     /**
    411      * Custom X.509 Extensions decoder.
    412      */
    413     public static final ASN1Type ASN1 = new ASN1SequenceOf(Extension.ASN1) {
    414 
    415         public Object getDecodedObject(BerInputStream in) {
    416             return new Extensions((List)in.content);
    417         }
    418 
    419         public Collection getValues(Object object) {
    420             Extensions exts = (Extensions) object;
    421             return (exts.extensions == null) ? new ArrayList() : exts.extensions;
    422         }
    423     };
    424 }
    425 
    426