1package org.bouncycastle.jcajce.provider.asymmetric.x509;
2
3import java.io.BufferedInputStream;
4import java.io.ByteArrayInputStream;
5import java.io.ByteArrayOutputStream;
6import java.io.IOException;
7import java.io.InputStream;
8import java.io.OutputStreamWriter;
9import java.security.NoSuchProviderException;
10import java.security.cert.CertPath;
11import java.security.cert.Certificate;
12import java.security.cert.CertificateEncodingException;
13import java.security.cert.CertificateException;
14import java.security.cert.CertificateFactory;
15import java.security.cert.X509Certificate;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.Enumeration;
19import java.util.Iterator;
20import java.util.List;
21import java.util.ListIterator;
22
23import javax.security.auth.x500.X500Principal;
24
25import org.bouncycastle.asn1.ASN1Encodable;
26import org.bouncycastle.asn1.ASN1EncodableVector;
27import org.bouncycastle.asn1.ASN1Encoding;
28import org.bouncycastle.asn1.ASN1InputStream;
29import org.bouncycastle.asn1.ASN1Integer;
30import org.bouncycastle.asn1.ASN1Primitive;
31import org.bouncycastle.asn1.ASN1Sequence;
32import org.bouncycastle.asn1.DERSequence;
33import org.bouncycastle.asn1.DERSet;
34import org.bouncycastle.asn1.pkcs.ContentInfo;
35import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
36import org.bouncycastle.asn1.pkcs.SignedData;
37import org.bouncycastle.jcajce.util.BCJcaJceHelper;
38import org.bouncycastle.jcajce.util.JcaJceHelper;
39import org.bouncycastle.util.io.pem.PemObject;
40// BEGIN android-removed
41// import org.bouncycastle.util.io.pem.PemWriter;
42// END android-removed
43
44/**
45 * CertPath implementation for X.509 certificates.
46 * <br />
47 **/
48public  class PKIXCertPath
49    extends CertPath
50{
51    private final JcaJceHelper helper = new BCJcaJceHelper();
52
53    static final List certPathEncodings;
54
55    static
56    {
57        List encodings = new ArrayList();
58        encodings.add("PkiPath");
59        // BEGIN android-removed
60        // encodings.add("PEM");
61        // END android-removed
62        encodings.add("PKCS7");
63        certPathEncodings = Collections.unmodifiableList(encodings);
64    }
65
66    private List certificates;
67
68    /**
69     * @param certs
70     */
71    private List sortCerts(
72        List certs)
73    {
74        if (certs.size() < 2)
75        {
76            return certs;
77        }
78
79        X500Principal issuer = ((X509Certificate)certs.get(0)).getIssuerX500Principal();
80        boolean         okay = true;
81
82        for (int i = 1; i != certs.size(); i++)
83        {
84            X509Certificate cert = (X509Certificate)certs.get(i);
85
86            if (issuer.equals(cert.getSubjectX500Principal()))
87            {
88                issuer = ((X509Certificate)certs.get(i)).getIssuerX500Principal();
89            }
90            else
91            {
92                okay = false;
93                break;
94            }
95        }
96
97        if (okay)
98        {
99            return certs;
100        }
101
102        // find end-entity cert
103        List retList = new ArrayList(certs.size());
104        List orig = new ArrayList(certs);
105
106        for (int i = 0; i < certs.size(); i++)
107        {
108            X509Certificate cert = (X509Certificate)certs.get(i);
109            boolean         found = false;
110
111            X500Principal subject = cert.getSubjectX500Principal();
112
113            for (int j = 0; j != certs.size(); j++)
114            {
115                X509Certificate c = (X509Certificate)certs.get(j);
116                if (c.getIssuerX500Principal().equals(subject))
117                {
118                    found = true;
119                    break;
120                }
121            }
122
123            if (!found)
124            {
125                retList.add(cert);
126                certs.remove(i);
127            }
128        }
129
130        // can only have one end entity cert - something's wrong, give up.
131        if (retList.size() > 1)
132        {
133            return orig;
134        }
135
136        for (int i = 0; i != retList.size(); i++)
137        {
138            issuer = ((X509Certificate)retList.get(i)).getIssuerX500Principal();
139
140            for (int j = 0; j < certs.size(); j++)
141            {
142                X509Certificate c = (X509Certificate)certs.get(j);
143                if (issuer.equals(c.getSubjectX500Principal()))
144                {
145                    retList.add(c);
146                    certs.remove(j);
147                    break;
148                }
149            }
150        }
151
152        // make sure all certificates are accounted for.
153        if (certs.size() > 0)
154        {
155            return orig;
156        }
157
158        return retList;
159    }
160
161    PKIXCertPath(List certificates)
162    {
163        super("X.509");
164        this.certificates = sortCerts(new ArrayList(certificates));
165    }
166
167    /**
168     * Creates a CertPath of the specified type.
169     * This constructor is protected because most users should use
170     * a CertificateFactory to create CertPaths.
171     **/
172    PKIXCertPath(
173        InputStream inStream,
174        String encoding)
175        throws CertificateException
176    {
177        super("X.509");
178        try
179        {
180            if (encoding.equalsIgnoreCase("PkiPath"))
181            {
182                ASN1InputStream derInStream = new ASN1InputStream(inStream);
183                ASN1Primitive derObject = derInStream.readObject();
184                if (!(derObject instanceof ASN1Sequence))
185                {
186                    throw new CertificateException("input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath");
187                }
188                Enumeration e = ((ASN1Sequence)derObject).getObjects();
189                certificates = new ArrayList();
190                CertificateFactory certFactory = helper.createCertificateFactory("X.509");
191                while (e.hasMoreElements())
192                {
193                    ASN1Encodable element = (ASN1Encodable)e.nextElement();
194                    byte[] encoded = element.toASN1Primitive().getEncoded(ASN1Encoding.DER);
195                    certificates.add(0, certFactory.generateCertificate(
196                        new ByteArrayInputStream(encoded)));
197                }
198            }
199            else if (encoding.equalsIgnoreCase("PKCS7") || encoding.equalsIgnoreCase("PEM"))
200            {
201                inStream = new BufferedInputStream(inStream);
202                certificates = new ArrayList();
203                CertificateFactory certFactory= helper.createCertificateFactory("X.509");
204                Certificate cert;
205                while ((cert = certFactory.generateCertificate(inStream)) != null)
206                {
207                    certificates.add(cert);
208                }
209            }
210            else
211            {
212                throw new CertificateException("unsupported encoding: " + encoding);
213            }
214        }
215        catch (IOException ex)
216        {
217            throw new CertificateException("IOException throw while decoding CertPath:\n" + ex.toString());
218        }
219        catch (NoSuchProviderException ex)
220        {
221            throw new CertificateException("BouncyCastle provider not found while trying to get a CertificateFactory:\n" + ex.toString());
222        }
223
224        this.certificates = sortCerts(certificates);
225    }
226
227    /**
228     * Returns an iteration of the encodings supported by this
229     * certification path, with the default encoding
230     * first. Attempts to modify the returned Iterator via its
231     * remove method result in an UnsupportedOperationException.
232     *
233     * @return an Iterator over the names of the supported encodings (as Strings)
234     **/
235    public Iterator getEncodings()
236    {
237        return certPathEncodings.iterator();
238    }
239
240    /**
241     * Returns the encoded form of this certification path, using
242     * the default encoding.
243     *
244     * @return the encoded bytes
245     * @exception java.security.cert.CertificateEncodingException if an encoding error occurs
246     **/
247    public byte[] getEncoded()
248        throws CertificateEncodingException
249    {
250        Iterator iter = getEncodings();
251        if (iter.hasNext())
252        {
253            Object enc = iter.next();
254            if (enc instanceof String)
255            {
256            return getEncoded((String)enc);
257            }
258        }
259        return null;
260    }
261
262    /**
263     * Returns the encoded form of this certification path, using
264     * the specified encoding.
265     *
266     * @param encoding the name of the encoding to use
267     * @return the encoded bytes
268     * @exception java.security.cert.CertificateEncodingException if an encoding error
269     * occurs or the encoding requested is not supported
270     *
271     **/
272    public byte[] getEncoded(String encoding)
273        throws CertificateEncodingException
274    {
275        if (encoding.equalsIgnoreCase("PkiPath"))
276        {
277            ASN1EncodableVector v = new ASN1EncodableVector();
278
279            ListIterator iter = certificates.listIterator(certificates.size());
280            while (iter.hasPrevious())
281            {
282                v.add(toASN1Object((X509Certificate)iter.previous()));
283            }
284
285            return toDEREncoded(new DERSequence(v));
286        }
287        else if (encoding.equalsIgnoreCase("PKCS7"))
288        {
289            ContentInfo encInfo = new ContentInfo(PKCSObjectIdentifiers.data, null);
290
291            ASN1EncodableVector v = new ASN1EncodableVector();
292            for (int i = 0; i != certificates.size(); i++)
293            {
294                v.add(toASN1Object((X509Certificate)certificates.get(i)));
295            }
296
297            SignedData sd = new SignedData(
298                                     new ASN1Integer(1),
299                                     new DERSet(),
300                                     encInfo,
301                                     new DERSet(v),
302                                     null,
303                                     new DERSet());
304
305            return toDEREncoded(new ContentInfo(
306                    PKCSObjectIdentifiers.signedData, sd));
307        }
308        // BEGIN android-removed
309        // else if (encoding.equalsIgnoreCase("PEM"))
310        // {
311        //     ByteArrayOutputStream bOut = new ByteArrayOutputStream();
312        //     PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
313        //
314        //     try
315        //     {
316        //         for (int i = 0; i != certificates.size(); i++)
317        //         {
318        //             pWrt.writeObject(new PemObject("CERTIFICATE", ((X509Certificate)certificates.get(i)).getEncoded()));
319        //         }
320        //
321        //         pWrt.close();
322        //     }
323        //     catch (Exception e)
324        //     {
325        //         throw new CertificateEncodingException("can't encode certificate for PEM encoded path");
326        //     }
327        //
328        //     return bOut.toByteArray();
329        // }
330        // END android-removed
331        else
332        {
333            throw new CertificateEncodingException("unsupported encoding: " + encoding);
334        }
335    }
336
337    /**
338     * Returns the list of certificates in this certification
339     * path. The List returned must be immutable and thread-safe.
340     *
341     * @return an immutable List of Certificates (may be empty, but not null)
342     **/
343    public List getCertificates()
344    {
345        return Collections.unmodifiableList(new ArrayList(certificates));
346    }
347
348    /**
349     * Return a DERObject containing the encoded certificate.
350     *
351     * @param cert the X509Certificate object to be encoded
352     *
353     * @return the DERObject
354     **/
355    private ASN1Primitive toASN1Object(
356        X509Certificate cert)
357        throws CertificateEncodingException
358    {
359        try
360        {
361            return new ASN1InputStream(cert.getEncoded()).readObject();
362        }
363        catch (Exception e)
364        {
365            throw new CertificateEncodingException("Exception while encoding certificate: " + e.toString());
366        }
367    }
368
369    private byte[] toDEREncoded(ASN1Encodable obj)
370        throws CertificateEncodingException
371    {
372        try
373        {
374            return obj.toASN1Primitive().getEncoded(ASN1Encoding.DER);
375        }
376        catch (IOException e)
377        {
378            throw new CertificateEncodingException("Exception thrown: " + e);
379        }
380    }
381}
382