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