1package org.bouncycastle.jce.provider;
2
3import java.io.IOException;
4import java.security.InvalidAlgorithmParameterException;
5import java.security.PublicKey;
6import java.security.cert.*;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.HashSet;
10import java.util.Iterator;
11import java.util.List;
12import java.util.Set;
13
14import javax.security.auth.x500.X500Principal;
15
16/**
17 * Implements the PKIX CertPathBuilding algorithem for BouncyCastle.
18 * <br />
19 * <b>MAYBE: implement more CertPath validation whil build path to omit invalid pathes</b>
20 *
21 * @see CertPathBuilderSpi
22 **/
23public class PKIXCertPathBuilderSpi
24    extends CertPathBuilderSpi
25{
26    /**
27     * Build and validate a CertPath using the given parameter.
28     *
29     * @param params PKIXBuilderParameters object containing all
30     * information to build the CertPath
31     **/
32    public CertPathBuilderResult engineBuild(
33        CertPathParameters params)
34        throws CertPathBuilderException, InvalidAlgorithmParameterException
35    {
36        if (!(params instanceof PKIXBuilderParameters))
37        {
38            throw new InvalidAlgorithmParameterException("params must be a PKIXBuilderParameters instance");
39        }
40
41        PKIXBuilderParameters pkixParams = (PKIXBuilderParameters)params;
42
43        Collection targets;
44        Iterator targetIter;
45        List certPathList = new ArrayList();
46        X509Certificate cert;
47        Collection      certs;
48        CertPath        certPath = null;
49        Exception       certPathException = null;
50
51        // search target certificates
52        CertSelector certSelect = pkixParams.getTargetCertConstraints();
53        if (certSelect == null)
54        {
55            throw new CertPathBuilderException("targetCertConstraints must be non-null for CertPath building");
56        }
57
58        try
59        {
60            targets = findCertificates(certSelect, pkixParams.getCertStores());
61        }
62        catch (CertStoreException e)
63        {
64            throw new CertPathBuilderException(e);
65        }
66
67        if (targets.isEmpty())
68        {
69            throw new CertPathBuilderException("no certificate found matching targetCertContraints");
70        }
71
72        CertificateFactory  cFact;
73        CertPathValidator   validator;
74
75        try
76        {
77            cFact = CertificateFactory.getInstance("X.509", "BC");
78            validator = CertPathValidator.getInstance("PKIX", "BC");
79        }
80        catch (Exception e)
81        {
82            throw new CertPathBuilderException("exception creating support classes: " + e);
83        }
84
85        //
86        // check all potential target certificates
87        targetIter = targets.iterator();
88        while (targetIter.hasNext())
89        {
90            cert = (X509Certificate)targetIter.next();
91            certPathList.clear();
92            while (cert != null)
93            {
94                // add cert to the certpath
95                certPathList.add(cert);
96
97                // check wether the issuer of <cert> is a TrustAnchor
98                if (findTrustAnchor(cert, pkixParams.getTrustAnchors()) != null)
99                {
100                    try
101                    {
102                        certPath = cFact.generateCertPath(certPathList);
103
104                        PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult)validator.validate(certPath, pkixParams);
105
106                        return new PKIXCertPathBuilderResult(certPath,
107                                     result.getTrustAnchor(),
108                                     result.getPolicyTree(),
109                                     result.getPublicKey());
110                    }
111                    catch (CertificateException ex)
112                    {
113                        certPathException = ex;
114                    }
115                    catch (CertPathValidatorException ex)
116                    {
117                        certPathException = ex;
118                    }
119                    // if validation failed go to next certificate
120                    cert = null;
121                }
122                else
123                {
124                    // try to get the issuer certificate from one
125                    // of the CertStores
126                    try
127                    {
128                        X509Certificate issuer = findIssuer(cert, pkixParams.getCertStores());
129                        if (issuer.equals(cert))
130                        {
131                            cert = null;
132                        }
133                        else
134                        {
135                            cert = issuer;
136                        }
137                    }
138                    catch (CertPathValidatorException ex)
139                    {
140                        certPathException = ex;
141                        cert = null;
142                    }
143                }
144            }
145        }
146
147        if (certPath != null)
148        {
149            throw new CertPathBuilderException("found certificate chain, but could not be validated", certPathException);
150        }
151
152        throw new CertPathBuilderException("unable to find certificate chain");
153    }
154
155    /**
156     * Search the given Set of TrustAnchor's for one that is the
157     * issuer of the fiven X509 certificate.
158     *
159     * @param cert the X509 certificate
160     * @param trustAnchors a Set of TrustAnchor's
161     *
162     * @return the <code>TrustAnchor</code> object if found or
163     * <code>null</code> if not.
164     *
165     * @exception CertPathValidatorException if a TrustAnchor  was
166     * found but the signature verificytion on the given certificate
167     * has thrown an exception. This Exception can be obtainted with
168     * <code>getCause()</code> method.
169     **/
170    final TrustAnchor findTrustAnchor(
171        X509Certificate cert,
172        Set             trustAnchors)
173        throws CertPathBuilderException
174    {
175        Iterator iter = trustAnchors.iterator();
176        TrustAnchor trust = null;
177        PublicKey trustPublicKey = null;
178        Exception invalidKeyEx = null;
179
180        X509CertSelector certSelectX509 = new X509CertSelector();
181
182        try
183        {
184            certSelectX509.setSubject(cert.getIssuerX500Principal().getEncoded());
185        }
186        catch (IOException ex)
187        {
188            throw new CertPathBuilderException("can't get trust anchor principal",null);
189        }
190
191        while (iter.hasNext() && trust == null)
192        {
193            trust = (TrustAnchor)iter.next();
194            if (trust.getTrustedCert() != null)
195            {
196                if (certSelectX509.match(trust.getTrustedCert()))
197                {
198                    trustPublicKey = trust.getTrustedCert().getPublicKey();
199                }
200                else
201                {
202                    trust = null;
203                }
204            }
205            else if (trust.getCAName() != null
206                        && trust.getCAPublicKey() != null)
207            {
208                try
209                {
210                    X500Principal certIssuer = cert.getIssuerX500Principal();
211                    X500Principal caName = new X500Principal(trust.getCAName());
212                    if (certIssuer.equals(caName))
213                    {
214                        trustPublicKey = trust.getCAPublicKey();
215                    }
216                    else
217                    {
218                        trust = null;
219                    }
220                }
221                catch (IllegalArgumentException ex)
222                {
223                    trust = null;
224                }
225            }
226            else
227            {
228                trust = null;
229            }
230
231            if (trustPublicKey != null)
232            {
233                try
234                {
235                    cert.verify(trustPublicKey);
236                }
237                catch (Exception ex)
238                {
239                    invalidKeyEx = ex;
240                    trust = null;
241                }
242            }
243        }
244
245        if (trust == null && invalidKeyEx != null)
246        {
247            throw new CertPathBuilderException("TrustAnchor found put certificate validation failed",invalidKeyEx);
248        }
249
250        return trust;
251    }
252
253    /**
254     * Return a Collection of all certificates found in the
255     * CertStore's that are matching the certSelect criteriums.
256     *
257     * @param certSelector a {@link CertSelector CertSelector}
258     * object that will be used to select the certificates
259     * @param certStores a List containing only {@link CertStore
260     * CertStore} objects. These are used to search for
261     * certificates
262     *
263     * @return a Collection of all found {@link Certificate Certificate}
264     * objects. May be empty but never <code>null</code>.
265     **/
266    private final Collection findCertificates(
267        CertSelector    certSelect,
268        List            certStores)
269        throws CertStoreException
270    {
271        Set certs = new HashSet();
272        Iterator iter = certStores.iterator();
273
274        while (iter.hasNext())
275        {
276            CertStore   certStore = (CertStore)iter.next();
277
278            certs.addAll(certStore.getCertificates(certSelect));
279        }
280
281        return certs;
282    }
283
284    /**
285     * Find the issuer certificate of the given certificate.
286     *
287     * @param cert the certificate hows issuer certificate should
288     * be found.
289     * @param certStores a list of <code>CertStore</code> object
290     * that will be searched
291     *
292     * @return then <code>X509Certificate</code> object containing
293     * the issuer certificate or <code>null</code> if not found
294     *
295     * @exception CertPathValidatorException if a TrustAnchor  was
296     * found but the signature verificytion on the given certificate
297     * has thrown an exception. This Exception can be obtainted with
298     * <code>getCause()</code> method.
299     **/
300    private final X509Certificate findIssuer(
301        X509Certificate cert,
302        List certStores)
303        throws CertPathValidatorException
304    {
305        Exception invalidKeyEx = null;
306        X509CertSelector certSelect = new X509CertSelector();
307        try
308        {
309            certSelect.setSubject(cert.getIssuerX500Principal().getEncoded());
310        }
311        catch (IOException ex)
312        {
313            throw new CertPathValidatorException("Issuer not found", null, null, -1);
314        }
315
316        Iterator iter;
317        try
318        {
319            iter = findCertificates(certSelect, certStores).iterator();
320        }
321        catch (CertStoreException e)
322        {
323            throw new CertPathValidatorException(e);
324        }
325
326        X509Certificate issuer = null;
327        while (iter.hasNext() && issuer == null)
328        {
329            issuer = (X509Certificate)iter.next();
330            try
331            {
332                cert.verify(issuer.getPublicKey());
333            }
334            catch (Exception ex)
335            {
336                invalidKeyEx = ex;
337                issuer = null;
338            }
339        }
340
341        if (issuer == null && invalidKeyEx == null)
342        {
343           throw new CertPathValidatorException("Issuer not found", null, null, -1);
344        }
345
346        if (issuer == null && invalidKeyEx != null)
347        {
348            throw new CertPathValidatorException("issuer found but certificate validation failed",invalidKeyEx,null,-1);
349        }
350
351        return issuer;
352    }
353}
354