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