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