package org.bouncycastle.jce.provider; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.PublicKey; import java.security.cert.*; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; /** * Implements the PKIX CertPathBuilding algorithem for BouncyCastle. * <br /> * <b>MAYBE: implement more CertPath validation whil build path to omit invalid pathes</b> * * @see CertPathBuilderSpi **/ public class PKIXCertPathBuilderSpi extends CertPathBuilderSpi { /** * Build and validate a CertPath using the given parameter. * * @param params PKIXBuilderParameters object containing all * information to build the CertPath **/ public CertPathBuilderResult engineBuild( CertPathParameters params) throws CertPathBuilderException, InvalidAlgorithmParameterException { if (!(params instanceof PKIXBuilderParameters)) { throw new InvalidAlgorithmParameterException("params must be a PKIXBuilderParameters instance"); } PKIXBuilderParameters pkixParams = (PKIXBuilderParameters)params; Collection targets; Iterator targetIter; List certPathList = new ArrayList(); X509Certificate cert; Collection certs; CertPath certPath = null; Exception certPathException = null; // search target certificates CertSelector certSelect = pkixParams.getTargetCertConstraints(); if (certSelect == null) { throw new CertPathBuilderException("targetCertConstraints must be non-null for CertPath building"); } try { targets = findCertificates(certSelect, pkixParams.getCertStores()); } catch (CertStoreException e) { throw new CertPathBuilderException(e); } if (targets.isEmpty()) { throw new CertPathBuilderException("no certificate found matching targetCertContraints"); } CertificateFactory cFact; CertPathValidator validator; try { cFact = CertificateFactory.getInstance("X.509", "BC"); validator = CertPathValidator.getInstance("PKIX", "BC"); } catch (Exception e) { throw new CertPathBuilderException("exception creating support classes: " + e); } // // check all potential target certificates targetIter = targets.iterator(); while (targetIter.hasNext()) { cert = (X509Certificate)targetIter.next(); certPathList.clear(); while (cert != null) { // add cert to the certpath certPathList.add(cert); // check wether the issuer of <cert> is a TrustAnchor if (findTrustAnchor(cert, pkixParams.getTrustAnchors()) != null) { try { certPath = cFact.generateCertPath(certPathList); PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult)validator.validate(certPath, pkixParams); return new PKIXCertPathBuilderResult(certPath, result.getTrustAnchor(), result.getPolicyTree(), result.getPublicKey()); } catch (CertificateException ex) { certPathException = ex; } catch (CertPathValidatorException ex) { certPathException = ex; } // if validation failed go to next certificate cert = null; } else { // try to get the issuer certificate from one // of the CertStores try { X509Certificate issuer = findIssuer(cert, pkixParams.getCertStores()); if (issuer.equals(cert)) { cert = null; } else { cert = issuer; } } catch (CertPathValidatorException ex) { certPathException = ex; cert = null; } } } } if (certPath != null) { throw new CertPathBuilderException("found certificate chain, but could not be validated", certPathException); } throw new CertPathBuilderException("unable to find certificate chain"); } /** * Search the given Set of TrustAnchor's for one that is the * issuer of the fiven X509 certificate. * * @param cert the X509 certificate * @param trustAnchors a Set of TrustAnchor's * * @return the <code>TrustAnchor</code> object if found or * <code>null</code> if not. * * @exception CertPathValidatorException if a TrustAnchor was * found but the signature verificytion on the given certificate * has thrown an exception. This Exception can be obtainted with * <code>getCause()</code> method. **/ final TrustAnchor findTrustAnchor( X509Certificate cert, Set trustAnchors) throws CertPathBuilderException { Iterator iter = trustAnchors.iterator(); TrustAnchor trust = null; PublicKey trustPublicKey = null; Exception invalidKeyEx = null; X509CertSelector certSelectX509 = new X509CertSelector(); try { certSelectX509.setSubject(cert.getIssuerX500Principal().getEncoded()); } catch (IOException ex) { throw new CertPathBuilderException("can't get trust anchor principal",null); } while (iter.hasNext() && trust == null) { trust = (TrustAnchor)iter.next(); if (trust.getTrustedCert() != null) { if (certSelectX509.match(trust.getTrustedCert())) { trustPublicKey = trust.getTrustedCert().getPublicKey(); } else { trust = null; } } else if (trust.getCAName() != null && trust.getCAPublicKey() != null) { try { X500Principal certIssuer = cert.getIssuerX500Principal(); X500Principal caName = new X500Principal(trust.getCAName()); if (certIssuer.equals(caName)) { trustPublicKey = trust.getCAPublicKey(); } else { trust = null; } } catch (IllegalArgumentException ex) { trust = null; } } else { trust = null; } if (trustPublicKey != null) { try { cert.verify(trustPublicKey); } catch (Exception ex) { invalidKeyEx = ex; trust = null; } } } if (trust == null && invalidKeyEx != null) { throw new CertPathBuilderException("TrustAnchor found put certificate validation failed",invalidKeyEx); } return trust; } /** * Return a Collection of all certificates found in the * CertStore's that are matching the certSelect criteriums. * * @param certSelector a {@link CertSelector CertSelector} * object that will be used to select the certificates * @param certStores a List containing only {@link CertStore * CertStore} objects. These are used to search for * certificates * * @return a Collection of all found {@link Certificate Certificate} * objects. May be empty but never <code>null</code>. **/ private final Collection findCertificates( CertSelector certSelect, List certStores) throws CertStoreException { Set certs = new HashSet(); Iterator iter = certStores.iterator(); while (iter.hasNext()) { CertStore certStore = (CertStore)iter.next(); certs.addAll(certStore.getCertificates(certSelect)); } return certs; } /** * Find the issuer certificate of the given certificate. * * @param cert the certificate hows issuer certificate should * be found. * @param certStores a list of <code>CertStore</code> object * that will be searched * * @return then <code>X509Certificate</code> object containing * the issuer certificate or <code>null</code> if not found * * @exception CertPathValidatorException if a TrustAnchor was * found but the signature verificytion on the given certificate * has thrown an exception. This Exception can be obtainted with * <code>getCause()</code> method. **/ private final X509Certificate findIssuer( X509Certificate cert, List certStores) throws CertPathValidatorException { Exception invalidKeyEx = null; X509CertSelector certSelect = new X509CertSelector(); try { certSelect.setSubject(cert.getIssuerX500Principal().getEncoded()); } catch (IOException ex) { throw new CertPathValidatorException("Issuer not found", null, null, -1); } Iterator iter; try { iter = findCertificates(certSelect, certStores).iterator(); } catch (CertStoreException e) { throw new CertPathValidatorException(e); } X509Certificate issuer = null; while (iter.hasNext() && issuer == null) { issuer = (X509Certificate)iter.next(); try { cert.verify(issuer.getPublicKey()); } catch (Exception ex) { invalidKeyEx = ex; issuer = null; } } if (issuer == null && invalidKeyEx == null) { throw new CertPathValidatorException("Issuer not found", null, null, -1); } if (issuer == null && invalidKeyEx != null) { throw new CertPathValidatorException("issuer found but certificate validation failed",invalidKeyEx,null,-1); } return issuer; } }