Home | History | Annotate | Download | only in cert
      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.provider.cert;
     24 
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.security.cert.CertPath;
     28 import java.security.cert.CertificateEncodingException;
     29 import java.security.cert.CertificateException;
     30 import java.security.cert.X509Certificate;
     31 import java.util.ArrayList;
     32 import java.util.Arrays;
     33 import java.util.Collection;
     34 import java.util.Collections;
     35 import java.util.Iterator;
     36 import java.util.List;
     37 import org.apache.harmony.security.asn1.ASN1Any;
     38 import org.apache.harmony.security.asn1.ASN1Explicit;
     39 import org.apache.harmony.security.asn1.ASN1Implicit;
     40 import org.apache.harmony.security.asn1.ASN1Oid;
     41 import org.apache.harmony.security.asn1.ASN1Sequence;
     42 import org.apache.harmony.security.asn1.ASN1SequenceOf;
     43 import org.apache.harmony.security.asn1.ASN1Type;
     44 import org.apache.harmony.security.asn1.BerInputStream;
     45 import org.apache.harmony.security.pkcs7.ContentInfo;
     46 import org.apache.harmony.security.pkcs7.SignedData;
     47 import org.apache.harmony.security.x509.Certificate;
     48 
     49 /**
     50  * This class is an implementation of X.509 CertPath. This implementation
     51  * provides ability to create the instance of X.509 Certification Path
     52  * by several means:<br>
     53  *
     54  * &nbsp;  1. It can be created over the list of X.509 certificates
     55  * (implementations of X509Certificate class) provided in constructor.<br>
     56  *
     57  * &nbsp;  2. It can be created by means of <code>getInstance</code> methods
     58  * on the base of the following ASN.1 DER encoded forms:<br>
     59  *
     60  * &nbsp;&nbsp;  - PkiPath as defined in
     61  * ITU-T Recommendation X.509(2000) Corrigendum 1(2001)
     62  * (can be seen at
     63  * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution/TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX.509/8%7CX.509-TC1(4th).pdf)
     64  * <br>
     65  * &nbsp;&nbsp;  - PKCS #7 SignedData object provided in the form of
     66  * ContentInfo structure. CertPath object is generated on the base of
     67  * certificates presented in <code>certificates</code> field of the SignedData
     68  * object which in its turn is retrieved from ContentInfo structure.
     69  * (see http://www.ietf.org/rfc/rfc2315.txt
     70  * for more info on PKCS #7)
     71  * <br>
     72  * &nbsp;
     73  */
     74 public class X509CertPathImpl extends CertPath {
     75 
     76     /**
     77      * @serial
     78      */
     79     private static final long serialVersionUID = 7989755106209515436L;
     80 
     81     // supported encoding types:
     82     public static final int PKI_PATH = 0;
     83     public static final int PKCS7 = 1;
     84 
     85     // supported encoding names
     86     private static final String[] encodingsArr =
     87                                         new String[] {"PkiPath", "PKCS7"};
     88     static final List encodings = Collections.unmodifiableList(
     89                                             Arrays.asList(encodingsArr));
     90     // the list of certificates representing this certification path
     91     private final List certificates;
     92     // PkiPath encoding of the certification path
     93     private byte[] pkiPathEncoding;
     94     // PKCS7 encoding of the certification path
     95     private byte[] pkcs7Encoding;
     96 
     97     /**
     98      * Creates an instance of X.509 Certification Path over the specified
     99      * list of certificates.
    100      * @throws CertificateException if some of the object in the list
    101      * is not an instance of subclass of X509Certificate.
    102      */
    103     public X509CertPathImpl(List certs) throws CertificateException {
    104         super("X.509");
    105         int size = certs.size();
    106         certificates = new ArrayList(size);
    107         for (int i=0; i<size; i++) {
    108             Object cert = certs.get(i);
    109             if (!(cert instanceof X509Certificate) ) {
    110                 throw new CertificateException("One of the provided certificates is not an X509 certificate");
    111             }
    112             certificates.add(cert);
    113         }
    114     }
    115 
    116     /*
    117      * Internally used constructor.
    118      * Creates an X.509 Certification Path over the specified
    119      * list of certificates and their encoded form of specified type.
    120      * @param certs - the list of certificates
    121      * @param type - the type of the encoded form on the base of which
    122      * this list of certificates had been built.
    123      * @param encoding - encoded form of certification path.
    124      */
    125     private X509CertPathImpl(List certs, int type, byte[] encoding) {
    126         super("X.509");
    127         if (type == PKI_PATH) {
    128             this.pkiPathEncoding = encoding;
    129         } else { // PKCS7
    130             this.pkcs7Encoding = encoding;
    131         }
    132         // We do not need the type check and list cloning here,
    133         // because it has been done during decoding.
    134         certificates = certs;
    135     }
    136 
    137     /**
    138      * Generates certification path object on the base of PkiPath
    139      * encoded form provided via input stream.
    140      * @throws CertificateException if some problems occurred during
    141      * the decoding.
    142      */
    143     public static X509CertPathImpl getInstance(InputStream in)
    144                                         throws CertificateException {
    145         try {
    146             return (X509CertPathImpl) ASN1.decode(in);
    147         } catch (IOException e) {
    148             throw new CertificateException("Incorrect encoded form: " + e.getMessage());
    149         }
    150     }
    151 
    152     /**
    153      * Generates certification path object on the base of encoding provided via
    154      * input stream. The format of provided encoded form is specified by
    155      * parameter <code>encoding</code>.
    156      * @throws CertificateException if specified encoding form is not supported,
    157      * or some problems occurred during the decoding.
    158      */
    159     public static X509CertPathImpl getInstance(InputStream in, String encoding)
    160         throws CertificateException {
    161         if (!encodings.contains(encoding)) {
    162             throw new CertificateException("Unsupported encoding");
    163         }
    164         try {
    165             if (encodingsArr[0].equals(encoding)) {
    166                 // generate the object from PkiPath encoded form
    167                 return (X509CertPathImpl) ASN1.decode(in);
    168             } else {
    169                 // generate the object from PKCS #7 encoded form
    170                 ContentInfo ci = (ContentInfo) ContentInfo.ASN1.decode(in);
    171                 SignedData sd = ci.getSignedData();
    172                 if (sd == null) {
    173                     throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data");
    174                 }
    175                 List certs = sd.getCertificates();
    176                 if (certs == null) {
    177                     // empty chain of certificates
    178                     certs = new ArrayList();
    179                 }
    180                 List result = new ArrayList();
    181                 for (int i=0; i<certs.size(); i++) {
    182                     result.add(new X509CertImpl((Certificate) certs.get(i)));
    183                 }
    184                 return new X509CertPathImpl(result, PKCS7, ci.getEncoded());
    185             }
    186         } catch (IOException e) {
    187             throw new CertificateException("Incorrect encoded form: " + e.getMessage());
    188         }
    189     }
    190 
    191     /**
    192      * Generates certification path object on the base of PkiPath
    193      * encoded form provided via array of bytes.
    194      * @throws CertificateException if some problems occurred during
    195      * the decoding.
    196      */
    197     public static X509CertPathImpl getInstance(byte[] in)
    198                                         throws CertificateException {
    199         try {
    200             return (X509CertPathImpl) ASN1.decode(in);
    201         } catch (IOException e) {
    202             throw new CertificateException("Incorrect encoded form: " + e.getMessage());
    203         }
    204     }
    205 
    206     /**
    207      * Generates certification path object on the base of encoding provided via
    208      * array of bytes. The format of provided encoded form is specified by
    209      * parameter <code>encoding</code>.
    210      * @throws CertificateException if specified encoding form is not supported,
    211      * or some problems occurred during the decoding.
    212      */
    213     public static X509CertPathImpl getInstance(byte[] in, String encoding)
    214         throws CertificateException {
    215         if (!encodings.contains(encoding)) {
    216             throw new CertificateException("Unsupported encoding");
    217         }
    218         try {
    219             if (encodingsArr[0].equals(encoding)) {
    220                 // generate the object from PkiPath encoded form
    221                 return (X509CertPathImpl) ASN1.decode(in);
    222             } else {
    223                 // generate the object from PKCS #7 encoded form
    224                 ContentInfo ci = (ContentInfo) ContentInfo.ASN1.decode(in);
    225                 SignedData sd = ci.getSignedData();
    226                 if (sd == null) {
    227                     throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data");
    228                 }
    229                 List certs = sd.getCertificates();
    230                 if (certs == null) {
    231                     certs = new ArrayList();
    232                 }
    233                 List result = new ArrayList();
    234                 for (int i=0; i<certs.size(); i++) {
    235                     result.add(new X509CertImpl((Certificate) certs.get(i)));
    236                 }
    237                 return new X509CertPathImpl(result, PKCS7, ci.getEncoded());
    238             }
    239         } catch (IOException e) {
    240             throw new CertificateException("Incorrect encoded form: " + e.getMessage());
    241         }
    242     }
    243 
    244     // ---------------------------------------------------------------------
    245     // ---- java.security.cert.CertPath abstract method implementations ----
    246     // ---------------------------------------------------------------------
    247 
    248     /**
    249      * @see java.security.cert.CertPath#getCertificates()
    250      * method documentation for more info
    251      */
    252     public List getCertificates() {
    253         return Collections.unmodifiableList(certificates);
    254     }
    255 
    256     /**
    257      * @see java.security.cert.CertPath#getEncoded()
    258      * method documentation for more info
    259      */
    260     public byte[] getEncoded() throws CertificateEncodingException {
    261         if (pkiPathEncoding == null) {
    262             pkiPathEncoding = ASN1.encode(this);
    263         }
    264         byte[] result = new byte[pkiPathEncoding.length];
    265         System.arraycopy(pkiPathEncoding, 0, result, 0, pkiPathEncoding.length);
    266         return result;
    267     }
    268 
    269     /**
    270      * @see java.security.cert.CertPath#getEncoded(String)
    271      * method documentation for more info
    272      */
    273     public byte[] getEncoded(String encoding) throws CertificateEncodingException {
    274         if (!encodings.contains(encoding)) {
    275             throw new CertificateEncodingException("Unsupported encoding");
    276         }
    277         if (encodingsArr[0].equals(encoding)) {
    278             // PkiPath encoded form
    279             return getEncoded();
    280         } else {
    281             // PKCS7 encoded form
    282             if (pkcs7Encoding == null) {
    283                 pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this);
    284             }
    285             byte[] result = new byte[pkcs7Encoding.length];
    286             System.arraycopy(pkcs7Encoding, 0, result, 0,
    287                                         pkcs7Encoding.length);
    288             return result;
    289         }
    290     }
    291 
    292     /**
    293      * @see java.security.cert.CertPath#getEncodings()
    294      * method documentation for more info
    295      */
    296     public Iterator getEncodings() {
    297         return encodings.iterator();
    298     }
    299 
    300     /**
    301      * ASN.1 DER Encoder/Decoder for PkiPath structure.
    302      */
    303     public static final ASN1SequenceOf ASN1 =
    304                                     new ASN1SequenceOf(ASN1Any.getInstance()) {
    305 
    306         /**
    307          * Builds the instance of X509CertPathImpl on the base of the list
    308          * of ASN.1 encodings of X.509 certificates provided via
    309          * PkiPath structure.
    310          * This method participates in decoding process.
    311          */
    312         public Object getDecodedObject(BerInputStream in) throws IOException {
    313             // retrieve the decoded content
    314             List encodings = (List) in.content;
    315             int size = encodings.size();
    316             List certificates = new ArrayList(size);
    317             for (int i=0; i<size; i++) {
    318                 // create the X.509 certificate on the base of its encoded form
    319                 // and add it to the list.
    320                 certificates.add(
    321                     new X509CertImpl((Certificate)
    322                         Certificate.ASN1.decode((byte[]) encodings.get(i))));
    323             }
    324             // create and return the resulting object
    325             return new X509CertPathImpl(
    326                     certificates, PKI_PATH, in.getEncoded());
    327         }
    328 
    329         /**
    330          * Returns the Collection of the encoded form of certificates contained
    331          * in the X509CertPathImpl object to be encoded.
    332          * This method participates in encoding process.
    333          */
    334         public Collection getValues(Object object) {
    335             // object to be encoded
    336             X509CertPathImpl cp = (X509CertPathImpl) object;
    337             // if it has no certificates in it - create the sequence of size 0
    338             if (cp.certificates == null) {
    339                 return new ArrayList();
    340             }
    341             int size = cp.certificates.size();
    342             List encodings = new ArrayList(size);
    343             try {
    344                 for (int i=0; i<size; i++) {
    345                     // get the encoded form of certificate and place it into the
    346                     // list to be encoded in PkiPath format
    347                     encodings.add(((X509Certificate) cp.certificates.get(i)).getEncoded());
    348                 }
    349             } catch (CertificateEncodingException e) {
    350                 throw new IllegalArgumentException("Encoding Error occurred");
    351             }
    352             return encodings;
    353         }
    354     };
    355 
    356 
    357     //
    358     // encoder for PKCS#7 SignedData
    359     // it is assumed that only certificate field is important
    360     // all other fields contain precalculated encodings:
    361     //
    362     // encodes X509CertPathImpl objects
    363     //
    364     private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence(
    365             new ASN1Type[] {
    366                     // version ,digestAlgorithms, content info
    367                     ASN1Any.getInstance(),
    368                     // certificates
    369                     new ASN1Implicit(0, ASN1),
    370                     // set of crls is optional and is missed here
    371                     ASN1Any.getInstance(),// signers info
    372             }) {
    373 
    374         // precalculated ASN.1 encodings for
    375         // version ,digestAlgorithms, content info field of SignedData
    376         private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02, 0x01,
    377                 0x01,// version (v1)
    378                 0x31, 0x00,// empty set of DigestAlgorithms
    379                 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0
    380         };
    381 
    382         // precalculated empty set of SignerInfos
    383         private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 };
    384 
    385         protected void getValues(Object object, Object[] values) {
    386             values[0] = PRECALCULATED_HEAD;
    387             values[1] = object; // pass X509CertPathImpl object
    388             values[2] = SIGNERS_INFO;
    389         }
    390 
    391         // stub to prevent using the instance as decoder
    392         public Object decode(BerInputStream in) throws IOException {
    393             throw new RuntimeException(
    394                     "Invalid use of encoder for PKCS#7 SignedData object");
    395         }
    396     };
    397 
    398     private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence(
    399             new ASN1Type[] { ASN1Any.getInstance(), // contentType
    400                     new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData
    401             }) {
    402 
    403         // precalculated ASN.1 encoding for SignedData object oid
    404         private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance().encode(
    405                 ContentInfo.SIGNED_DATA);
    406 
    407         protected void getValues(Object object, Object[] values) {
    408             values[0] = SIGNED_DATA_OID;
    409             values[1] = object; // pass X509CertPathImpl object
    410         }
    411 
    412         // stub to prevent using the instance as decoder
    413         public Object decode(BerInputStream in) throws IOException {
    414             throw new RuntimeException(
    415                     "Invalid use of encoder for PKCS#7 SignedData object");
    416         }
    417     };
    418 }
    419