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