Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.security.certificate.validator; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CRLException; import java.security.cert.CertPath; import java.security.cert.CertPathBuilderException; import java.security.cert.CertPathBuilderResult; import java.security.cert.Certificate; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import mitm.common.security.SecurityFactoryFactoryException; import mitm.common.security.certpath.CertificatePathBuilder; import mitm.common.security.certpath.CertificatePathBuilderFactory; import mitm.common.security.certstore.CertStoreUtils; import mitm.common.security.crl.RevocationChecker; import mitm.common.security.crl.RevocationResult; import mitm.common.security.crl.RevocationStatus; import mitm.common.security.ctl.CTL; import mitm.common.security.ctl.CTLException; import mitm.common.security.ctl.CTLValidity; import mitm.common.security.ctl.CTLValidityResult; import mitm.common.util.Check; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This CertificateValidator is used to check the trust of a certificate (ie. if a valid certificate chain * can be build) and that the certificate is not revoked. * * This class if not thread safe. * * @author Martijn Brinkers * */ public class PKITrustCheckCertificateValidatorImpl implements PKITrustCheckCertificateValidator { private final static Logger logger = LoggerFactory.getLogger(PKITrustCheckCertificateValidatorImpl.class); /* * Default acceptable revocation status. This is the default policy for this class. * */ private final static RevocationStatus[] DEFAULT_ACCEPTABLE_REVOCATION_STATUS = { RevocationStatus.NOT_REVOKED, RevocationStatus.UNKNOWN, RevocationStatus.EXPIRED }; /* * Name of this CertificateValidator. */ private final String name; /* * Used for the creation of Certificate path builders */ private final CertificatePathBuilderFactory certificatePathBuilderFactory; /* * Used to check the revocation state (using CRL's) of the certificate. */ private final RevocationChecker revocationChecker; /* * Certificate Trust List */ private final CTL ctl; /* * The revocation reasons that are accepted as valid ie. flag the certificate as not revoked. */ private final RevocationStatus[] acceptableRevocationStatus; /* * Certificates that will be added to the path builder */ private Set<Certificate> additionalCertificates; /* * The date used for path building and revocation checking. If not set the current date will be used. */ private Date date; /* * True if the last result was valid */ boolean valid; /* * True if a valid path could be build. */ private boolean trusted; /* * True if one of the certificates in the chain is considered revoked (based on acceptedRevocationStatus). */ private boolean revoked; /* * True if one of the certificates in the chain is black listed */ private boolean blackListed; /* * True if the certificate is white listed */ private boolean whiteListed; /* * Gives some info about the cause of the failure (should only contain relevant info when the last call * to isValid returned false). */ private String failureMessage = ""; protected PKITrustCheckCertificateValidatorImpl(String name, CertificatePathBuilderFactory certificatePathBuilderFactory, RevocationChecker revocationChecker, CTL ctl, Collection<? extends Certificate> additionalCertificates) { Check.notNull(certificatePathBuilderFactory, "certificatePathBuilderFactory"); Check.notNull(revocationChecker, "revocationChecker"); this.name = name; this.certificatePathBuilderFactory = certificatePathBuilderFactory; this.revocationChecker = revocationChecker; this.ctl = ctl; this.acceptableRevocationStatus = DEFAULT_ACCEPTABLE_REVOCATION_STATUS; if (additionalCertificates != null && additionalCertificates.size() > 0) { /* * Clone the additionalCertificates */ this.additionalCertificates = new HashSet<Certificate>(additionalCertificates); } } public PKITrustCheckCertificateValidatorImpl(CertificatePathBuilderFactory certificatePathBuilderFactory, RevocationChecker revocationChecker, CTL ctl, Collection<? extends Certificate> additionalCertificates) { this("PKITrustCheckCertificateValidator", certificatePathBuilderFactory, revocationChecker, ctl, additionalCertificates); } @Override public String getName() { return name; } /** * True if the last time isValid(certificate) returned true, false otherwise. The certificate * is valid when a complete trusted path can be build up to a trusted root and the * certificate is not revoked. */ @Override public boolean isValid() { return valid; } /** * True if a path could be build (ie. up to a trusted root). trusted does not mean the certificate is not * revoked, it only means that a complete chain could be build from the certificate to a trusted root. */ @Override public boolean isTrusted() { return trusted; } /** * True if one of the certificates in the chain is revoked (based on acceptedRevocationStatus). This value is * only valid if isTrusted is true. If isTrusted is false isRevoked will always be false. Revocation checking * cannot be done when the chain is incomplete. We will therefore return false when the chain in incomplete * because we do not know better. */ @Override public boolean isRevoked() { return revoked; } /** * True if one of the certificates in the chain is black listed. Blacklist checking is only done when * the certificate is valid. */ @Override public boolean isBlackListed() { return blackListed; } /** * True if the certificate is whitelisted. A white list check is only done when the certificate is * invalid. */ @Override public boolean isWhiteListed() { return whiteListed; } @Override public String getFailureMessage() { return failureMessage; } /** * Sets the date used for path building and revocation checking. If not set the current date will be used. */ @Override public void setDate(Date date) { this.date = date; } private Date getDate() { if (date != null) { return date; } return new Date(); } private boolean isBlackListed(CertPath certPath) throws CTLException { if (ctl == null) { return false; } List<? extends Certificate> certificates = certPath.getCertificates(); for (int i = 0; i < certificates.size(); i++) { Certificate certificate = certificates.get(i); if (!(certificate instanceof X509Certificate)) { logger.warn("Only X509Certificates can be black listed."); continue; } CTLValidityResult result = ctl.checkValidity((X509Certificate) certificate); if (CTLValidity.INVALID == result.getValidity()) { /* * If the certificate is the first it's not an intermediate. */ String failureMessage = i == 0 ? result.getMessage() : "Intermediate " + result.getMessage(); reportFailure(failureMessage); return true; } } return false; } private boolean isWhiteListed(X509Certificate certificate) throws CTLException { boolean whiteListed = false; if (ctl != null) { CTLValidityResult result = ctl.checkValidity((X509Certificate) certificate); switch (result.getValidity()) { case VALID: whiteListed = true; break; case NOT_LISTED: break; default: reportFailure(result.getMessage()); } } return whiteListed; } @Override public boolean isValid(Certificate certificate) { valid = false; trusted = false; revoked = false; blackListed = false; whiteListed = false; if (!(certificate instanceof X509Certificate)) { failureMessage = "Certificate is not a X509Certificate"; return false; } failureMessage = ""; X509Certificate x509Certificate = (X509Certificate) certificate; try { CertPathAndAnchor certPathAndAnchor = getCertPathAndAnchor(x509Certificate); CertPath certPath = certPathAndAnchor.getCertPath(); TrustAnchor trustAnchor = certPathAndAnchor.getTrustAnchor(); if (certPath != null && trustAnchor != null) { trusted = true; revoked = isRevoked(certPath, trustAnchor); if (!revoked) { /* * Chain is valid, not expired, not revoked. We now need to check * whether a certificate in the chain is not BlackListed. */ blackListed = isBlackListed(certPath); valid = !blackListed; } else { valid = false; } } else { throw new CertPathBuilderException("A valid CertPath could not be built."); } } catch (CertPathBuilderException e) { /* * A valid certificate chain could not be built. This can happen because of a lot of reasons: * an intermediate or root certificate is not trusted, a certificate in the chain has expired, * a certificate in the chain is invalid etc. We now check whether the certificate is white listed. * * Note: Because the chain is not valid we cannot check the revocation status so white listing * a certificate should be done with care. */ logger.debug("CertPathBuilderException", e); try { whiteListed = isWhiteListed(x509Certificate); valid = whiteListed; } catch (CTLException ctle) { logger.error("Error checking the CTL.", ctle); } if (!valid) { /* * We do not want a complete stack trace on a CertPathBuilderException because this exception can be thrown * quite often. CertPathBuilderException is also thrown when a path validator exception occurs. We will * therefore try to extract the root cause. */ Throwable cause = ExceptionUtils.getRootCause(e); if (cause == null) { cause = e; } reportFailure("Error building certPath. " + cause.getMessage()); } } catch (InvalidAlgorithmParameterException e) { reportFailure("Error building certPath.", e); } catch (NoSuchAlgorithmException e) { reportFailure("Error building certPath.", e); } catch (NoSuchProviderException e) { reportFailure("Error building certPath.", e); } catch (SecurityFactoryFactoryException e) { reportFailure("Error building certPath.", e); } catch (CTLException e) { reportFailure("Error checking CTL status.", e); } if (!valid) { logger.debug("Failure message: " + failureMessage); } return valid; } private void reportFailure(String message, Throwable t) { Throwable cause = ExceptionUtils.getRootCause(t); if (cause == null) { cause = t; } failureMessage = StringUtils.isNotBlank(failureMessage) ? failureMessage + "; " + message : message; if (cause != null) { failureMessage = failureMessage + " Exception: " + cause.getMessage(); logger.error(message, cause); } else { logger.debug(message); } } private void reportFailure(String message) { reportFailure(message, null); } protected void modifyPathBuilder(CertificatePathBuilder pathBuilder) { /* * Subclasses can use this to add CertPathChecker's etc. */ } private CertPathAndAnchor getCertPathAndAnchor(X509Certificate certificate) throws CertPathBuilderException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, SecurityFactoryFactoryException { CertificatePathBuilder pathBuilder = certificatePathBuilderFactory.createCertificatePathBuilder(); modifyPathBuilder(pathBuilder); /* * Add the x509Certificate to the stores used for path building to make sure the * certificate is found by the path builder. */ pathBuilder.addCertStore(CertStoreUtils.createCertStore(certificate)); /* * Add the additional certificates if there are any */ if (additionalCertificates != null) { pathBuilder.addCertStore(CertStoreUtils.createCertStore(additionalCertificates)); } pathBuilder.setDate(getDate()); CertPathBuilderResult pathBuilderResult = pathBuilder.buildPath(certificate); CertPath certPath = pathBuilderResult.getCertPath(); TrustAnchor trustAnchor = null; if (pathBuilderResult instanceof PKIXCertPathBuilderResult) { PKIXCertPathBuilderResult pkixResult = (PKIXCertPathBuilderResult) pathBuilderResult; trustAnchor = pkixResult.getTrustAnchor(); } return new CertPathAndAnchor(certPath, trustAnchor); } private boolean isRevoked(CertPath certPath, TrustAnchor trustAnchor) { boolean revoked = true; try { /* * check if the certificate is revoked */ RevocationResult revocationResult = revocationChecker.getRevocationStatus(certPath, trustAnchor, getDate()); if (revocationResult != null) { /* * Check if the returned revocation status is acceptable. What an acceptable revocation * status is is determined by a policy. Currently we will use a hardcoded policy. See * DEFAULT_ACCEPTABLE_REVOCATION_STATUS. */ for (RevocationStatus acceptableStatus : acceptableRevocationStatus) { if (acceptableStatus == revocationResult.getStatus()) { revoked = false; break; } } if (revoked) { reportFailure("Certificate not accepted. Revocation status :" + revocationResult.getStatus()); } } } catch (CRLException e) { reportFailure("Error while checking revocation status.", e); } return revoked; } /* * Helper class so we can return a CertPath and TrustAnchor */ private static class CertPathAndAnchor { private final CertPath certPath; private final TrustAnchor trustAnchor; public CertPathAndAnchor(CertPath certPath, TrustAnchor trustAnchor) { this.certPath = certPath; this.trustAnchor = trustAnchor; } public CertPath getCertPath() { return certPath; } public TrustAnchor getTrustAnchor() { return trustAnchor; } } }