/*
* oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2014, Gluu
*/
package org.xdi.oxauth.cert.validation;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathValidator;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xdi.oxauth.cert.validation.model.ValidationStatus;
import org.xdi.oxauth.cert.validation.model.ValidationStatus.CertificateValidity;
import org.xdi.oxauth.cert.validation.model.ValidationStatus.ValidatorSourceType;
import org.xdi.oxauth.model.util.SecurityProviderUtility;
/**
* Chain certificate verifier
*
* @author Yuriy Movchan
* @version March 11, 2016
*/
public class PathCertificateVerifier implements CertificateVerifier {
private static final Logger log = LoggerFactory.getLogger(PathCertificateVerifier.class);
private boolean verifySelfSignedCertificate;
public PathCertificateVerifier(boolean verifySelfSignedCert) {
SecurityProviderUtility.installBCProvider(true);
this.verifySelfSignedCertificate = verifySelfSignedCert;
}
@Override
public ValidationStatus validate(X509Certificate certificate, List<X509Certificate> issuers, Date validationDate) {
X509Certificate issuer = issuers.get(0);
ValidationStatus status = new ValidationStatus(certificate, issuer, validationDate, ValidatorSourceType.CHAIN, CertificateValidity.UNKNOWN);
try {
ArrayList<X509Certificate> chains = new ArrayList<X509Certificate>();
chains.add(certificate);
chains.addAll(issuers);
Principal subjectX500Principal = certificate.getSubjectX500Principal();
PKIXCertPathBuilderResult certPathResult = verifyCertificate(certificate, chains);
if (certPathResult == null) {
log.warn("Chain status is not valid for '" + subjectX500Principal + "'");
status.setValidity(CertificateValidity.INVALID);
return status;
}
log.debug("Chain status is valid for '" + subjectX500Principal + "'");
status.setValidity(CertificateValidity.VALID);
} catch (Exception ex) {
log.error("OCSP exception: ", ex);
}
return status;
}
public PKIXCertPathBuilderResult verifyCertificate(X509Certificate certificate, List<X509Certificate> additionalCerts) {
try {
// Check for self-signed certificate
if (!verifySelfSignedCertificate && isSelfSigned(certificate)) {
log.error("The certificate is self-signed!");
return null;
}
// Prepare a set of trusted root CA certificates and a set of
// intermediate certificates
Set<X509Certificate> trustedRootCerts = new HashSet<X509Certificate>();
Set<X509Certificate> intermediateCerts = new HashSet<X509Certificate>();
for (X509Certificate additionalCert : additionalCerts) {
if (isSelfSigned(additionalCert)) {
trustedRootCerts.add(additionalCert);
} else {
intermediateCerts.add(additionalCert);
}
}
// Attempt to build the certification chain and verify it
PKIXCertPathBuilderResult certPathBuilderResult = verifyCertificate(certificate, trustedRootCerts, intermediateCerts);
// Check that first certificate is an EE certificate
CertPath certPath = certPathBuilderResult.getCertPath();
List<? extends Certificate> certList = certPath.getCertificates();
X509Certificate cert = (X509Certificate) certList.get(0);
if (cert.getBasicConstraints() != -1) {
log.error("Target certificate is not an EE certificate!");
return null;
}
// The chain is verified. Return it as a result
return certPathBuilderResult;
} catch (CertPathBuilderException ex) {
log.error("Failed to build certificate path", ex);
} catch (GeneralSecurityException ex) {
log.error("Failed to build certificate path", ex);
}
return null;
}
public static boolean isSelfSigned(X509Certificate certificate) throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
try {
// Try to verify certificate signature with its own public key
PublicKey key = certificate.getPublicKey();
certificate.verify(key);
return true;
} catch (SignatureException ex) {
// Not self-signed
return false;
} catch (InvalidKeyException ex) {
// Not self-signed
return false;
}
}
/**
* Attempts to build a certification chain for given certificate to verify
* it. Relies on a set of root CA certificates (trust anchors) and a set of
* intermediate certificates (to be used as part of the chain).
*/
private PKIXCertPathBuilderResult verifyCertificate(X509Certificate certificate, Set<X509Certificate> trustedRootCerts, Set<X509Certificate> intermediateCerts)
throws GeneralSecurityException {
// Create the selector that specifies the starting certificate
X509CertSelector selector = new X509CertSelector();
selector.setBasicConstraints(-2);
selector.setCertificate(certificate);
// Create the trust anchors (set of root CA certificates)
Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
for (X509Certificate trustedRootCert : trustedRootCerts) {
trustAnchors.add(new TrustAnchor(trustedRootCert, null));
}
// Configure the PKIX certificate builder algorithm parameters
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
// Turn off default revocation-checking mechanism
pkixParams.setRevocationEnabled(false);
// Specify a list of intermediate certificates
CertStore intermediateCertStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(intermediateCerts));
pkixParams.addCertStore(intermediateCertStore);
// Build and verify the certification chain
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
PKIXCertPathBuilderResult certPathBuilderResult = (PKIXCertPathBuilderResult) builder.build(pkixParams);
// Additional check to Verify cert path
CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
PKIXCertPathValidatorResult certPathValidationResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPathBuilderResult.getCertPath(), pkixParams);
return certPathBuilderResult;
}
@Override
public void destroy() {
}
}