diff --git a/base/src/main/java/org/mozilla/jss/provider/javax/crypto/JSSTrustManager.java b/base/src/main/java/org/mozilla/jss/provider/javax/crypto/JSSTrustManager.java index dd4626284..ece53f48c 100644 --- a/base/src/main/java/org/mozilla/jss/provider/javax/crypto/JSSTrustManager.java +++ b/base/src/main/java/org/mozilla/jss/provider/javax/crypto/JSSTrustManager.java @@ -20,10 +20,13 @@ package org.mozilla.jss.provider.javax.crypto; import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Enumeration; import java.util.List; import javax.net.ssl.X509TrustManager; @@ -32,6 +35,9 @@ import org.mozilla.jss.NotInitializedException; import org.mozilla.jss.netscape.security.util.Cert; import org.mozilla.jss.pkcs11.PK11Cert; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback.ValidityItem; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback.ValidityStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,11 +49,20 @@ public class JSSTrustManager implements X509TrustManager { public static final String CLIENT_AUTH_OID = "1.3.6.1.5.5.7.3.2"; private boolean allowMissingExtendedKeyUsage = false; + private SSLCertificateApprovalCallback callback; public void configureAllowMissingExtendedKeyUsage(boolean allow) { allowMissingExtendedKeyUsage = allow; } + public SSLCertificateApprovalCallback getCallback() { + return callback; + } + + public void setCallback(SSLCertificateApprovalCallback certCallback) { + this.callback = certCallback; + } + public void checkCertChain(X509Certificate[] certChain, String keyUsage) throws Exception { logger.debug("JSSTrustManager: checkCertChain(" + keyUsage + ")"); @@ -59,13 +74,47 @@ public void checkCertChain(X509Certificate[] certChain, String keyUsage) throws logger.debug("JSSTrustManager: - " + cert.getSubjectX500Principal()); } + X509Certificate leafCert = certChain[certChain.length - 1]; + + ValidityStatus status = new ValidityStatus(); + checkCertChain(certChain, keyUsage, status); + + Enumeration reasons = status.getReasons(); + if (!reasons.hasMoreElements()) { + logger.debug("JSSTrustManager: Trusted cert: " + leafCert.getSubjectX500Principal()); + return; + } + + if (callback != null && callback.approve(leafCert, status)) { + logger.debug("JSSTrustManager: Approved cert: " + leafCert.getSubjectX500Principal()); + return; + } + + // throw an exception based on the first issue + ValidityItem issue = reasons.nextElement(); + + // TODO: use enum + switch (issue.getReason()) { + case ValidityStatus.EXPIRED_CERTIFICATE: + throw new CertificateExpiredException("Expired certificate: " + leafCert.getSubjectX500Principal()); + case ValidityStatus.INADEQUATE_KEY_USAGE: + throw new CertificateException("Inadequate key usage: " + leafCert.getSubjectX500Principal()); + case ValidityStatus.UNTRUSTED_ISSUER: + throw new CertificateException("Untrusted issuer: " + leafCert.getSubjectX500Principal()); + default: + throw new CertificateException("Invalid certificate: " + leafCert.getSubjectX500Principal()); + } + } + + public void checkCertChain(X509Certificate[] certChain, String keyUsage, ValidityStatus status) throws Exception { + if (!isTrustedPeer(certChain)) { - checkIssuerTrusted(certChain); + checkIssuerTrusted(certChain, status); } - checkValidityDates(certChain); + checkValidityDates(certChain, status); - checkKeyUsage(certChain, keyUsage); + checkKeyUsage(certChain, keyUsage, status); } public boolean isTrustedPeer(X509Certificate[] certChain) throws Exception { @@ -89,21 +138,21 @@ public boolean isTrustedPeer(X509Certificate[] certChain) throws Exception { sslTrust); } - public void checkIssuerTrusted(X509Certificate[] certChain) throws Exception { + public void checkIssuerTrusted(X509Certificate[] certChain, ValidityStatus status) throws Exception { // get CA certs X509Certificate[] caCerts = getAcceptedIssuers(); // validating signature from root to leaf for (X509Certificate cert : certChain) { - checkSignature(cert, caCerts); + checkSignature(cert, caCerts, status); // use the current cert as the CA cert for the next cert in the chain caCerts = new X509Certificate[] { cert }; } } - public void checkSignature(X509Certificate cert, X509Certificate[] caCerts) throws Exception { + public void checkSignature(X509Certificate cert, X509Certificate[] caCerts, ValidityStatus status) throws Exception { logger.debug("JSSTrustManager: Checking signature of cert 0x" + cert.getSerialNumber().toString(16)); logger.debug("JSSTrustManager: - subject: " + cert.getSubjectX500Principal()); @@ -131,13 +180,18 @@ public void checkSignature(X509Certificate cert, X509Certificate[] caCerts) thro } if (issuer == null) { - throw new CertificateException("Unable to validate signature: " + cert.getSubjectX500Principal()); + logger.debug("JSSTrustManager: Untrusted issuer: " + cert.getIssuerX500Principal()); + + // TODO: fix depth param + status.addReason(ValidityStatus.UNTRUSTED_ISSUER, cert, 0); + + return; } - logger.debug("JSSTrustManager: cert signed by " + issuer.getSubjectX500Principal()); + logger.debug("JSSTrustManager: Trusted issuer: " + issuer.getSubjectX500Principal()); } - public void checkValidityDates(X509Certificate[] certChain) throws Exception { + public void checkValidityDates(X509Certificate[] certChain, ValidityStatus status) throws Exception { for (X509Certificate cert : certChain) { @@ -145,11 +199,26 @@ public void checkValidityDates(X509Certificate[] certChain) throws Exception { logger.debug("JSSTrustManager: - not before: " + cert.getNotBefore()); logger.debug("JSSTrustManager: - not after: " + cert.getNotAfter()); - cert.checkValidity(); + try { + cert.checkValidity(); + + } catch (CertificateNotYetValidException e) { + logger.debug("JSSTrustManager: Cert not yet valid: " + cert.getSubjectX500Principal()); + + // NSS uses EXPIRED_CERTIFICATE for this case in CERT_CheckCertValidTimes() + // TODO: fix depth param + status.addReason(ValidityStatus.EXPIRED_CERTIFICATE, cert, 0); + + } catch (CertificateExpiredException e) { + logger.debug("JSSTrustManager: Cert has expired: " + cert.getSubjectX500Principal()); + + // TODO: fix depth param + status.addReason(ValidityStatus.EXPIRED_CERTIFICATE, cert, 0); + } } } - public void checkKeyUsage(X509Certificate[] certChain, String keyUsage) throws Exception { + public void checkKeyUsage(X509Certificate[] certChain, String keyUsage, ValidityStatus status) throws Exception { // validating key usage on leaf cert only X509Certificate cert = certChain[certChain.length - 1]; @@ -175,25 +244,15 @@ public void checkKeyUsage(X509Certificate[] certChain, String keyUsage) throws E return; } - String msg = "Missing EKU: " + keyUsage + - ". Certificate with subject DN `" + cert.getSubjectX500Principal() + "` had "; - if (extendedKeyUsages == null) { - msg += "no EKU extension"; + logger.debug("JSSTrustManager: Missing extended key usage extension"); } else { - msg += "EKUs { "; - boolean first = true; - for (String eku : extendedKeyUsages) { - if (!first) msg += " , "; - msg += eku; - first = false; - } - msg += " }"; + logger.debug("JSSTrustManager: Missing " + keyUsage + " key usage"); } - msg += ". class = " + cert.getClass(); - throw new CertificateException(msg); + // TODO: fix depth param + status.addReason(ValidityStatus.INADEQUATE_KEY_USAGE, cert, 0); } @Override