|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.provider.certpath; |
|
|
|
import java.io.IOException; |
|
import java.math.BigInteger; |
|
import java.net.URI; |
|
import java.net.URISyntaxException; |
|
import java.security.AccessController; |
|
import java.security.InvalidAlgorithmParameterException; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.security.PrivilegedAction; |
|
import java.security.PublicKey; |
|
import java.security.Security; |
|
import java.security.cert.CertPathValidatorException.BasicReason; |
|
import java.security.cert.Extension; |
|
import java.security.cert.*; |
|
import java.util.*; |
|
import javax.security.auth.x500.X500Principal; |
|
|
|
import static sun.security.provider.certpath.OCSP.*; |
|
import static sun.security.provider.certpath.PKIX.*; |
|
import sun.security.x509.*; |
|
import static sun.security.x509.PKIXExtensions.*; |
|
import sun.security.util.Debug; |
|
|
|
class RevocationChecker extends PKIXRevocationChecker { |
|
|
|
private static final Debug debug = Debug.getInstance("certpath"); |
|
|
|
private TrustAnchor anchor; |
|
private ValidatorParams params; |
|
private boolean onlyEE; |
|
private boolean softFail; |
|
private boolean crlDP; |
|
private URI responderURI; |
|
private X509Certificate responderCert; |
|
private List<CertStore> certStores; |
|
private Map<X509Certificate, byte[]> ocspResponses; |
|
private List<Extension> ocspExtensions; |
|
private final boolean legacy; |
|
private LinkedList<CertPathValidatorException> softFailExceptions = |
|
new LinkedList<>(); |
|
|
|
|
|
private OCSPResponse.IssuerInfo issuerInfo; |
|
private PublicKey prevPubKey; |
|
private boolean crlSignFlag; |
|
private int certIndex; |
|
|
|
private enum Mode { PREFER_OCSP, PREFER_CRLS, ONLY_CRLS, ONLY_OCSP }; |
|
private Mode mode = Mode.PREFER_OCSP; |
|
|
|
private static class RevocationProperties { |
|
boolean onlyEE; |
|
boolean ocspEnabled; |
|
boolean crlDPEnabled; |
|
String ocspUrl; |
|
String ocspSubject; |
|
String ocspIssuer; |
|
String ocspSerial; |
|
} |
|
|
|
RevocationChecker() { |
|
legacy = false; |
|
} |
|
|
|
RevocationChecker(TrustAnchor anchor, ValidatorParams params) |
|
throws CertPathValidatorException |
|
{ |
|
legacy = true; |
|
init(anchor, params); |
|
} |
|
|
|
void init(TrustAnchor anchor, ValidatorParams params) |
|
throws CertPathValidatorException |
|
{ |
|
RevocationProperties rp = getRevocationProperties(); |
|
URI uri = getOcspResponder(); |
|
responderURI = (uri == null) ? toURI(rp.ocspUrl) : uri; |
|
X509Certificate cert = getOcspResponderCert(); |
|
responderCert = (cert == null) |
|
? getResponderCert(rp, params.trustAnchors(), |
|
params.certStores()) |
|
: cert; |
|
Set<Option> options = getOptions(); |
|
for (Option option : options) { |
|
switch (option) { |
|
case ONLY_END_ENTITY: |
|
case PREFER_CRLS: |
|
case SOFT_FAIL: |
|
case NO_FALLBACK: |
|
break; |
|
default: |
|
throw new CertPathValidatorException( |
|
"Unrecognized revocation parameter option: " + option); |
|
} |
|
} |
|
softFail = options.contains(Option.SOFT_FAIL); |
|
|
|
|
|
if (legacy) { |
|
mode = (rp.ocspEnabled) ? Mode.PREFER_OCSP : Mode.ONLY_CRLS; |
|
onlyEE = rp.onlyEE; |
|
} else { |
|
if (options.contains(Option.NO_FALLBACK)) { |
|
if (options.contains(Option.PREFER_CRLS)) { |
|
mode = Mode.ONLY_CRLS; |
|
} else { |
|
mode = Mode.ONLY_OCSP; |
|
} |
|
} else if (options.contains(Option.PREFER_CRLS)) { |
|
mode = Mode.PREFER_CRLS; |
|
} |
|
onlyEE = options.contains(Option.ONLY_END_ENTITY); |
|
} |
|
if (legacy) { |
|
crlDP = rp.crlDPEnabled; |
|
} else { |
|
crlDP = true; |
|
} |
|
ocspResponses = getOcspResponses(); |
|
ocspExtensions = getOcspExtensions(); |
|
|
|
this.anchor = anchor; |
|
this.params = params; |
|
this.certStores = new ArrayList<>(params.certStores()); |
|
try { |
|
this.certStores.add(CertStore.getInstance("Collection", |
|
new CollectionCertStoreParameters(params.certificates()))); |
|
} catch (InvalidAlgorithmParameterException | |
|
NoSuchAlgorithmException e) { |
|
// should never occur but not necessarily fatal, so log it, |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker: " + |
|
"error creating Collection CertStore: " + e); |
|
} |
|
} |
|
} |
|
|
|
private static URI toURI(String uriString) |
|
throws CertPathValidatorException |
|
{ |
|
try { |
|
if (uriString != null) { |
|
return new URI(uriString); |
|
} |
|
return null; |
|
} catch (URISyntaxException e) { |
|
throw new CertPathValidatorException( |
|
"cannot parse ocsp.responderURL property", e); |
|
} |
|
} |
|
|
|
private static RevocationProperties getRevocationProperties() { |
|
return AccessController.doPrivileged( |
|
new PrivilegedAction<RevocationProperties>() { |
|
public RevocationProperties run() { |
|
RevocationProperties rp = new RevocationProperties(); |
|
String onlyEE = Security.getProperty( |
|
"com.sun.security.onlyCheckRevocationOfEECert"); |
|
rp.onlyEE = onlyEE != null |
|
&& onlyEE.equalsIgnoreCase("true"); |
|
String ocspEnabled = Security.getProperty("ocsp.enable"); |
|
rp.ocspEnabled = ocspEnabled != null |
|
&& ocspEnabled.equalsIgnoreCase("true"); |
|
rp.ocspUrl = Security.getProperty("ocsp.responderURL"); |
|
rp.ocspSubject |
|
= Security.getProperty("ocsp.responderCertSubjectName"); |
|
rp.ocspIssuer |
|
= Security.getProperty("ocsp.responderCertIssuerName"); |
|
rp.ocspSerial |
|
= Security.getProperty("ocsp.responderCertSerialNumber"); |
|
rp.crlDPEnabled |
|
= Boolean.getBoolean("com.sun.security.enableCRLDP"); |
|
return rp; |
|
} |
|
} |
|
); |
|
} |
|
|
|
private static X509Certificate getResponderCert(RevocationProperties rp, |
|
Set<TrustAnchor> anchors, |
|
List<CertStore> stores) |
|
throws CertPathValidatorException |
|
{ |
|
if (rp.ocspSubject != null) { |
|
return getResponderCert(rp.ocspSubject, anchors, stores); |
|
} else if (rp.ocspIssuer != null && rp.ocspSerial != null) { |
|
return getResponderCert(rp.ocspIssuer, rp.ocspSerial, |
|
anchors, stores); |
|
} else if (rp.ocspIssuer != null || rp.ocspSerial != null) { |
|
throw new CertPathValidatorException( |
|
"Must specify both ocsp.responderCertIssuerName and " + |
|
"ocsp.responderCertSerialNumber properties"); |
|
} |
|
return null; |
|
} |
|
|
|
private static X509Certificate getResponderCert(String subject, |
|
Set<TrustAnchor> anchors, |
|
List<CertStore> stores) |
|
throws CertPathValidatorException |
|
{ |
|
X509CertSelector sel = new X509CertSelector(); |
|
try { |
|
sel.setSubject(new X500Principal(subject)); |
|
} catch (IllegalArgumentException e) { |
|
throw new CertPathValidatorException( |
|
"cannot parse ocsp.responderCertSubjectName property", e); |
|
} |
|
return getResponderCert(sel, anchors, stores); |
|
} |
|
|
|
private static X509Certificate getResponderCert(String issuer, |
|
String serial, |
|
Set<TrustAnchor> anchors, |
|
List<CertStore> stores) |
|
throws CertPathValidatorException |
|
{ |
|
X509CertSelector sel = new X509CertSelector(); |
|
try { |
|
sel.setIssuer(new X500Principal(issuer)); |
|
} catch (IllegalArgumentException e) { |
|
throw new CertPathValidatorException( |
|
"cannot parse ocsp.responderCertIssuerName property", e); |
|
} |
|
try { |
|
sel.setSerialNumber(new BigInteger(stripOutSeparators(serial), 16)); |
|
} catch (NumberFormatException e) { |
|
throw new CertPathValidatorException( |
|
"cannot parse ocsp.responderCertSerialNumber property", e); |
|
} |
|
return getResponderCert(sel, anchors, stores); |
|
} |
|
|
|
private static X509Certificate getResponderCert(X509CertSelector sel, |
|
Set<TrustAnchor> anchors, |
|
List<CertStore> stores) |
|
throws CertPathValidatorException |
|
{ |
|
|
|
for (TrustAnchor anchor : anchors) { |
|
X509Certificate cert = anchor.getTrustedCert(); |
|
if (cert == null) { |
|
continue; |
|
} |
|
if (sel.match(cert)) { |
|
return cert; |
|
} |
|
} |
|
|
|
for (CertStore store : stores) { |
|
try { |
|
Collection<? extends Certificate> certs = |
|
store.getCertificates(sel); |
|
if (!certs.isEmpty()) { |
|
return (X509Certificate)certs.iterator().next(); |
|
} |
|
} catch (CertStoreException e) { |
|
|
|
if (debug != null) { |
|
debug.println("CertStore exception:" + e); |
|
} |
|
continue; |
|
} |
|
} |
|
throw new CertPathValidatorException( |
|
"Cannot find the responder's certificate " + |
|
"(set using the OCSP security properties)."); |
|
} |
|
|
|
@Override |
|
public void init(boolean forward) throws CertPathValidatorException { |
|
if (forward) { |
|
throw new |
|
CertPathValidatorException("forward checking not supported"); |
|
} |
|
if (anchor != null) { |
|
issuerInfo = new OCSPResponse.IssuerInfo(anchor); |
|
prevPubKey = issuerInfo.getPublicKey(); |
|
|
|
} |
|
crlSignFlag = true; |
|
if (params != null && params.certPath() != null) { |
|
certIndex = params.certPath().getCertificates().size() - 1; |
|
} else { |
|
certIndex = -1; |
|
} |
|
softFailExceptions.clear(); |
|
} |
|
|
|
@Override |
|
public boolean isForwardCheckingSupported() { |
|
return false; |
|
} |
|
|
|
@Override |
|
public Set<String> getSupportedExtensions() { |
|
return null; |
|
} |
|
|
|
@Override |
|
public List<CertPathValidatorException> getSoftFailExceptions() { |
|
return Collections.unmodifiableList(softFailExceptions); |
|
} |
|
|
|
@Override |
|
public void check(Certificate cert, Collection<String> unresolvedCritExts) |
|
throws CertPathValidatorException |
|
{ |
|
check((X509Certificate)cert, unresolvedCritExts, |
|
prevPubKey, crlSignFlag); |
|
} |
|
|
|
private void check(X509Certificate xcert, |
|
Collection<String> unresolvedCritExts, |
|
PublicKey pubKey, boolean crlSignFlag) |
|
throws CertPathValidatorException |
|
{ |
|
if (debug != null) { |
|
debug.println("RevocationChecker.check: checking cert" + |
|
"\n SN: " + Debug.toHexString(xcert.getSerialNumber()) + |
|
"\n Subject: " + xcert.getSubjectX500Principal() + |
|
"\n Issuer: " + xcert.getIssuerX500Principal()); |
|
} |
|
try { |
|
if (onlyEE && xcert.getBasicConstraints() != -1) { |
|
if (debug != null) { |
|
debug.println("Skipping revocation check; cert is not " + |
|
"an end entity cert"); |
|
} |
|
return; |
|
} |
|
switch (mode) { |
|
case PREFER_OCSP: |
|
case ONLY_OCSP: |
|
checkOCSP(xcert, unresolvedCritExts); |
|
break; |
|
case PREFER_CRLS: |
|
case ONLY_CRLS: |
|
checkCRLs(xcert, unresolvedCritExts, null, |
|
pubKey, crlSignFlag); |
|
break; |
|
} |
|
} catch (CertPathValidatorException e) { |
|
if (e.getReason() == BasicReason.REVOKED) { |
|
throw e; |
|
} |
|
boolean eSoftFail = isSoftFailException(e); |
|
if (eSoftFail) { |
|
if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { |
|
return; |
|
} |
|
} else { |
|
if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { |
|
throw e; |
|
} |
|
} |
|
CertPathValidatorException cause = e; |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.check() " + e.getMessage()); |
|
debug.println("RevocationChecker.check() preparing to failover"); |
|
} |
|
try { |
|
switch (mode) { |
|
case PREFER_OCSP: |
|
checkCRLs(xcert, unresolvedCritExts, null, |
|
pubKey, crlSignFlag); |
|
break; |
|
case PREFER_CRLS: |
|
checkOCSP(xcert, unresolvedCritExts); |
|
break; |
|
} |
|
} catch (CertPathValidatorException x) { |
|
if (debug != null) { |
|
debug.println("RevocationChecker.check() failover failed"); |
|
debug.println("RevocationChecker.check() " + x.getMessage()); |
|
} |
|
if (x.getReason() == BasicReason.REVOKED) { |
|
throw x; |
|
} |
|
if (!isSoftFailException(x)) { |
|
cause.addSuppressed(x); |
|
throw cause; |
|
} else { |
|
|
|
if (!eSoftFail) { |
|
throw cause; |
|
} |
|
} |
|
} |
|
} finally { |
|
updateState(xcert); |
|
} |
|
} |
|
|
|
private boolean isSoftFailException(CertPathValidatorException e) { |
|
if (softFail && |
|
e.getReason() == BasicReason.UNDETERMINED_REVOCATION_STATUS) |
|
{ |
|
|
|
CertPathValidatorException e2 = new CertPathValidatorException( |
|
e.getMessage(), e.getCause(), params.certPath(), certIndex, |
|
e.getReason()); |
|
softFailExceptions.addFirst(e2); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
private void updateState(X509Certificate cert) |
|
throws CertPathValidatorException |
|
{ |
|
issuerInfo = new OCSPResponse.IssuerInfo(anchor, cert); |
|
|
|
|
|
PublicKey pubKey = cert.getPublicKey(); |
|
if (PKIX.isDSAPublicKeyWithoutParams(pubKey)) { |
|
|
|
pubKey = BasicChecker.makeInheritedParamsKey(pubKey, prevPubKey); |
|
} |
|
prevPubKey = pubKey; |
|
crlSignFlag = certCanSignCrl(cert); |
|
if (certIndex > 0) { |
|
certIndex--; |
|
} |
|
} |
|
|
|
// Maximum clock skew in milliseconds (15 minutes) allowed when checking |
|
|
|
private static final long MAX_CLOCK_SKEW = 900000; |
|
private void checkCRLs(X509Certificate cert, |
|
Collection<String> unresolvedCritExts, |
|
Set<X509Certificate> stackedCerts, |
|
PublicKey pubKey, boolean signFlag) |
|
throws CertPathValidatorException |
|
{ |
|
checkCRLs(cert, pubKey, null, signFlag, true, |
|
stackedCerts, params.trustAnchors()); |
|
} |
|
|
|
static boolean isCausedByNetworkIssue(String type, CertStoreException cse) { |
|
boolean result; |
|
Throwable t = cse.getCause(); |
|
|
|
switch (type) { |
|
case "LDAP": |
|
if (t != null) { |
|
|
|
String cn = t.getClass().getName(); |
|
result = (cn.equals("javax.naming.ServiceUnavailableException") || |
|
cn.equals("javax.naming.CommunicationException")); |
|
} else { |
|
result = false; |
|
} |
|
break; |
|
case "SSLServer": |
|
result = (t != null && t instanceof IOException); |
|
break; |
|
case "URI": |
|
result = (t != null && t instanceof IOException); |
|
break; |
|
default: |
|
|
|
return false; |
|
} |
|
return result; |
|
} |
|
|
|
private void checkCRLs(X509Certificate cert, PublicKey prevKey, |
|
X509Certificate prevCert, boolean signFlag, |
|
boolean allowSeparateKey, |
|
Set<X509Certificate> stackedCerts, |
|
Set<TrustAnchor> anchors) |
|
throws CertPathValidatorException |
|
{ |
|
if (debug != null) { |
|
debug.println("RevocationChecker.checkCRLs()" + |
|
" ---checking revocation status ..."); |
|
} |
|
|
|
// Reject circular dependencies - RFC 5280 is not explicit on how |
|
// to handle this, but does suggest that they can be a security |
|
|
|
if (stackedCerts != null && stackedCerts.contains(cert)) { |
|
if (debug != null) { |
|
debug.println("RevocationChecker.checkCRLs()" + |
|
" circular dependency"); |
|
} |
|
throw new CertPathValidatorException |
|
("Could not determine revocation status", null, null, -1, |
|
BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
|
|
Set<X509CRL> possibleCRLs = new HashSet<>(); |
|
Set<X509CRL> approvedCRLs = new HashSet<>(); |
|
X509CRLSelector sel = new X509CRLSelector(); |
|
sel.setCertificateChecking(cert); |
|
CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW); |
|
|
|
|
|
CertPathValidatorException networkFailureException = null; |
|
for (CertStore store : certStores) { |
|
try { |
|
for (CRL crl : store.getCRLs(sel)) { |
|
possibleCRLs.add((X509CRL)crl); |
|
} |
|
} catch (CertStoreException e) { |
|
if (debug != null) { |
|
debug.println("RevocationChecker.checkCRLs() " + |
|
"CertStoreException: " + e.getMessage()); |
|
} |
|
if (networkFailureException == null && |
|
isCausedByNetworkIssue(store.getType(),e)) { |
|
|
|
networkFailureException = new CertPathValidatorException( |
|
"Unable to determine revocation status due to " + |
|
"network error", e, null, -1, |
|
BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
} |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.checkCRLs() " + |
|
"possible crls.size() = " + possibleCRLs.size()); |
|
} |
|
boolean[] reasonsMask = new boolean[9]; |
|
if (!possibleCRLs.isEmpty()) { |
|
// Now that we have a list of possible CRLs, see which ones can |
|
|
|
approvedCRLs.addAll(verifyPossibleCRLs(possibleCRLs, cert, prevKey, |
|
signFlag, reasonsMask, |
|
anchors)); |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.checkCRLs() " + |
|
"approved crls.size() = " + approvedCRLs.size()); |
|
} |
|
|
|
// make sure that we have at least one CRL that _could_ cover |
|
|
|
if (!approvedCRLs.isEmpty() && |
|
Arrays.equals(reasonsMask, ALL_REASONS)) |
|
{ |
|
checkApprovedCRLs(cert, approvedCRLs); |
|
} else { |
|
// Check Distribution Points |
|
|
|
try { |
|
if (crlDP) { |
|
approvedCRLs.addAll(DistributionPointFetcher.getCRLs( |
|
sel, signFlag, prevKey, prevCert, |
|
params.sigProvider(), certStores, reasonsMask, |
|
anchors, null, params.variant(), anchor)); |
|
} |
|
} catch (CertStoreException e) { |
|
if (e instanceof CertStoreTypeException) { |
|
CertStoreTypeException cste = (CertStoreTypeException)e; |
|
if (isCausedByNetworkIssue(cste.getType(), e)) { |
|
throw new CertPathValidatorException( |
|
"Unable to determine revocation status due to " + |
|
"network error", e, null, -1, |
|
BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
} |
|
throw new CertPathValidatorException(e); |
|
} |
|
if (!approvedCRLs.isEmpty() && |
|
Arrays.equals(reasonsMask, ALL_REASONS)) |
|
{ |
|
checkApprovedCRLs(cert, approvedCRLs); |
|
} else { |
|
if (allowSeparateKey) { |
|
try { |
|
verifyWithSeparateSigningKey(cert, prevKey, signFlag, |
|
stackedCerts); |
|
return; |
|
} catch (CertPathValidatorException cpve) { |
|
if (networkFailureException != null) { |
|
// if a network issue previously prevented us from |
|
// retrieving a CRL from one of the user-specified |
|
// CertStores, throw it now so it can be handled |
|
|
|
throw networkFailureException; |
|
} |
|
throw cpve; |
|
} |
|
} else { |
|
if (networkFailureException != null) { |
|
// if a network issue previously prevented us from |
|
// retrieving a CRL from one of the user-specified |
|
// CertStores, throw it now so it can be handled |
|
|
|
throw networkFailureException; |
|
} |
|
throw new CertPathValidatorException( |
|
"Could not determine revocation status", null, null, -1, |
|
BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void checkApprovedCRLs(X509Certificate cert, |
|
Set<X509CRL> approvedCRLs) |
|
throws CertPathValidatorException |
|
{ |
|
|
|
if (debug != null) { |
|
BigInteger sn = cert.getSerialNumber(); |
|
debug.println("RevocationChecker.checkApprovedCRLs() " + |
|
"starting the final sweep..."); |
|
debug.println("RevocationChecker.checkApprovedCRLs()" + |
|
" cert SN: " + sn.toString()); |
|
} |
|
|
|
CRLReason reasonCode = CRLReason.UNSPECIFIED; |
|
X509CRLEntryImpl entry = null; |
|
for (X509CRL crl : approvedCRLs) { |
|
X509CRLEntry e = crl.getRevokedCertificate(cert); |
|
if (e != null) { |
|
try { |
|
entry = X509CRLEntryImpl.toImpl(e); |
|
} catch (CRLException ce) { |
|
throw new CertPathValidatorException(ce); |
|
} |
|
if (debug != null) { |
|
debug.println("RevocationChecker.checkApprovedCRLs()" |
|
+ " CRL entry: " + entry.toString()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
Set<String> unresCritExts = entry.getCriticalExtensionOIDs(); |
|
if (unresCritExts != null && !unresCritExts.isEmpty()) { |
|
|
|
unresCritExts.remove(ReasonCode_Id.toString()); |
|
unresCritExts.remove(CertificateIssuer_Id.toString()); |
|
if (!unresCritExts.isEmpty()) { |
|
throw new CertPathValidatorException( |
|
"Unrecognized critical extension(s) in revoked " + |
|
"CRL entry"); |
|
} |
|
} |
|
|
|
reasonCode = entry.getRevocationReason(); |
|
if (reasonCode == null) { |
|
reasonCode = CRLReason.UNSPECIFIED; |
|
} |
|
Date revocationDate = entry.getRevocationDate(); |
|
if (revocationDate.before(params.date())) { |
|
Throwable t = new CertificateRevokedException( |
|
revocationDate, reasonCode, |
|
crl.getIssuerX500Principal(), entry.getExtensions()); |
|
throw new CertPathValidatorException( |
|
t.getMessage(), t, null, -1, BasicReason.REVOKED); |
|
} |
|
} |
|
} |
|
} |
|
|
|
private void checkOCSP(X509Certificate cert, |
|
Collection<String> unresolvedCritExts) |
|
throws CertPathValidatorException |
|
{ |
|
X509CertImpl currCert = null; |
|
try { |
|
currCert = X509CertImpl.toImpl(cert); |
|
} catch (CertificateException ce) { |
|
throw new CertPathValidatorException(ce); |
|
} |
|
|
|
// The algorithm constraints of the OCSP trusted responder certificate |
|
// does not need to be checked in this code. The constraints will be |
|
// checked when the responder's certificate is validated. |
|
|
|
OCSPResponse response = null; |
|
CertId certId = null; |
|
try { |
|
certId = new CertId(issuerInfo.getName(), issuerInfo.getPublicKey(), |
|
currCert.getSerialNumberObject()); |
|
|
|
|
|
byte[] responseBytes = ocspResponses.get(cert); |
|
if (responseBytes != null) { |
|
if (debug != null) { |
|
debug.println("Found cached OCSP response"); |
|
} |
|
response = new OCSPResponse(responseBytes); |
|
|
|
|
|
byte[] nonce = null; |
|
for (Extension ext : ocspExtensions) { |
|
if (ext.getId().equals("1.3.6.1.5.5.7.48.1.2")) { |
|
nonce = ext.getValue(); |
|
} |
|
} |
|
response.verify(Collections.singletonList(certId), issuerInfo, |
|
responderCert, params.date(), nonce, params.variant()); |
|
|
|
} else { |
|
URI responderURI = (this.responderURI != null) |
|
? this.responderURI |
|
: OCSP.getResponderURI(currCert); |
|
if (responderURI == null) { |
|
throw new CertPathValidatorException( |
|
"Certificate does not specify OCSP responder", null, |
|
null, -1); |
|
} |
|
|
|
response = OCSP.check(Collections.singletonList(certId), |
|
responderURI, issuerInfo, responderCert, null, |
|
ocspExtensions, params.variant()); |
|
} |
|
} catch (IOException e) { |
|
throw new CertPathValidatorException( |
|
"Unable to determine revocation status due to network error", |
|
e, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
|
|
RevocationStatus rs = |
|
(RevocationStatus)response.getSingleResponse(certId); |
|
RevocationStatus.CertStatus certStatus = rs.getCertStatus(); |
|
if (certStatus == RevocationStatus.CertStatus.REVOKED) { |
|
Date revocationTime = rs.getRevocationTime(); |
|
if (revocationTime.before(params.date())) { |
|
Throwable t = new CertificateRevokedException( |
|
revocationTime, rs.getRevocationReason(), |
|
response.getSignerCertificate().getSubjectX500Principal(), |
|
rs.getSingleExtensions()); |
|
throw new CertPathValidatorException(t.getMessage(), t, null, |
|
-1, BasicReason.REVOKED); |
|
} |
|
} else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) { |
|
throw new CertPathValidatorException( |
|
"Certificate's revocation status is unknown", null, |
|
params.certPath(), -1, |
|
BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final String HEX_DIGITS = "0123456789ABCDEFabcdef"; |
|
private static String stripOutSeparators(String value) { |
|
char[] chars = value.toCharArray(); |
|
StringBuilder hexNumber = new StringBuilder(); |
|
for (int i = 0; i < chars.length; i++) { |
|
if (HEX_DIGITS.indexOf(chars[i]) != -1) { |
|
hexNumber.append(chars[i]); |
|
} |
|
} |
|
return hexNumber.toString(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static boolean certCanSignCrl(X509Certificate cert) { |
|
// if the cert doesn't include the key usage ext, or |
|
// the key usage ext asserts cRLSigning, return true, |
|
|
|
boolean[] keyUsage = cert.getKeyUsage(); |
|
if (keyUsage != null) { |
|
return keyUsage[6]; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final boolean[] ALL_REASONS = |
|
{true, true, true, true, true, true, true, true, true}; |
|
private Collection<X509CRL> verifyPossibleCRLs(Set<X509CRL> crls, |
|
X509Certificate cert, |
|
PublicKey prevKey, |
|
boolean signFlag, |
|
boolean[] reasonsMask, |
|
Set<TrustAnchor> anchors) |
|
throws CertPathValidatorException |
|
{ |
|
try { |
|
X509CertImpl certImpl = X509CertImpl.toImpl(cert); |
|
if (debug != null) { |
|
debug.println("RevocationChecker.verifyPossibleCRLs: " + |
|
"Checking CRLDPs for " |
|
+ certImpl.getSubjectX500Principal()); |
|
} |
|
CRLDistributionPointsExtension ext = |
|
certImpl.getCRLDistributionPointsExtension(); |
|
List<DistributionPoint> points = null; |
|
if (ext == null) { |
|
// assume a DP with reasons and CRLIssuer fields omitted |
|
// and a DP name of the cert issuer. |
|
|
|
X500Name certIssuer = (X500Name)certImpl.getIssuerDN(); |
|
DistributionPoint point = new DistributionPoint( |
|
new GeneralNames().add(new GeneralName(certIssuer)), |
|
null, null); |
|
points = Collections.singletonList(point); |
|
} else { |
|
points = ext.get(CRLDistributionPointsExtension.POINTS); |
|
} |
|
Set<X509CRL> results = new HashSet<>(); |
|
for (DistributionPoint point : points) { |
|
for (X509CRL crl : crls) { |
|
if (DistributionPointFetcher.verifyCRL( |
|
certImpl, point, crl, reasonsMask, signFlag, |
|
prevKey, null, params.sigProvider(), anchors, |
|
certStores, params.date(), params.variant(), anchor)) |
|
{ |
|
results.add(crl); |
|
} |
|
} |
|
if (Arrays.equals(reasonsMask, ALL_REASONS)) |
|
break; |
|
} |
|
return results; |
|
} catch (CertificateException | CRLException | IOException e) { |
|
if (debug != null) { |
|
debug.println("Exception while verifying CRL: "+e.getMessage()); |
|
e.printStackTrace(); |
|
} |
|
return Collections.emptySet(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void verifyWithSeparateSigningKey(X509Certificate cert, |
|
PublicKey prevKey, |
|
boolean signFlag, |
|
Set<X509Certificate> stackedCerts) |
|
throws CertPathValidatorException |
|
{ |
|
String msg = "revocation status"; |
|
if (debug != null) { |
|
debug.println( |
|
"RevocationChecker.verifyWithSeparateSigningKey()" + |
|
" ---checking " + msg + "..."); |
|
} |
|
|
|
// Reject circular dependencies - RFC 5280 is not explicit on how |
|
// to handle this, but does suggest that they can be a security |
|
|
|
if ((stackedCerts != null) && stackedCerts.contains(cert)) { |
|
if (debug != null) { |
|
debug.println( |
|
"RevocationChecker.verifyWithSeparateSigningKey()" + |
|
" circular dependency"); |
|
} |
|
throw new CertPathValidatorException |
|
("Could not determine revocation status", null, null, -1, |
|
BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
|
|
// Try to find another key that might be able to sign |
|
// CRLs vouching for this cert. |
|
// If prevKey wasn't trusted, maybe we just didn't have the right |
|
|
|
if (!signFlag) { |
|
buildToNewKey(cert, null, stackedCerts); |
|
} else { |
|
buildToNewKey(cert, prevKey, stackedCerts); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final boolean [] CRL_SIGN_USAGE = |
|
{ false, false, false, false, false, false, true }; |
|
private void buildToNewKey(X509Certificate currCert, |
|
PublicKey prevKey, |
|
Set<X509Certificate> stackedCerts) |
|
throws CertPathValidatorException |
|
{ |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.buildToNewKey()" + |
|
" starting work"); |
|
} |
|
Set<PublicKey> badKeys = new HashSet<>(); |
|
if (prevKey != null) { |
|
badKeys.add(prevKey); |
|
} |
|
X509CertSelector certSel = new RejectKeySelector(badKeys); |
|
certSel.setSubject(currCert.getIssuerX500Principal()); |
|
certSel.setKeyUsage(CRL_SIGN_USAGE); |
|
|
|
Set<TrustAnchor> newAnchors = anchor == null ? |
|
params.trustAnchors() : |
|
Collections.singleton(anchor); |
|
|
|
PKIXBuilderParameters builderParams; |
|
try { |
|
builderParams = new PKIXBuilderParameters(newAnchors, certSel); |
|
} catch (InvalidAlgorithmParameterException iape) { |
|
throw new RuntimeException(iape); |
|
} |
|
builderParams.setInitialPolicies(params.initialPolicies()); |
|
builderParams.setCertStores(certStores); |
|
builderParams.setExplicitPolicyRequired |
|
(params.explicitPolicyRequired()); |
|
builderParams.setPolicyMappingInhibited |
|
(params.policyMappingInhibited()); |
|
builderParams.setAnyPolicyInhibited(params.anyPolicyInhibited()); |
|
// Policy qualifiers must be rejected, since we don't have |
|
// any way to convey them back to the application. |
|
|
|
builderParams.setDate(params.date()); |
|
builderParams.setCertPathCheckers(params.certPathCheckers()); |
|
builderParams.setSigProvider(params.sigProvider()); |
|
|
|
// Skip revocation during this build to detect circular |
|
// references. But check revocation afterwards, using the |
|
|
|
builderParams.setRevocationEnabled(false); |
|
|
|
|
|
if (Builder.USE_AIA == true) { |
|
X509CertImpl currCertImpl = null; |
|
try { |
|
currCertImpl = X509CertImpl.toImpl(currCert); |
|
} catch (CertificateException ce) { |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.buildToNewKey: " + |
|
"error decoding cert: " + ce); |
|
} |
|
} |
|
AuthorityInfoAccessExtension aiaExt = null; |
|
if (currCertImpl != null) { |
|
aiaExt = currCertImpl.getAuthorityInfoAccessExtension(); |
|
} |
|
if (aiaExt != null) { |
|
List<AccessDescription> adList = aiaExt.getAccessDescriptions(); |
|
if (adList != null) { |
|
for (AccessDescription ad : adList) { |
|
CertStore cs = URICertStore.getInstance(ad); |
|
if (cs != null) { |
|
if (debug != null) { |
|
debug.println("adding AIAext CertStore"); |
|
} |
|
builderParams.addCertStore(cs); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
CertPathBuilder builder = null; |
|
try { |
|
builder = CertPathBuilder.getInstance("PKIX"); |
|
} catch (NoSuchAlgorithmException nsae) { |
|
throw new CertPathValidatorException(nsae); |
|
} |
|
while (true) { |
|
try { |
|
if (debug != null) { |
|
debug.println("RevocationChecker.buildToNewKey()" + |
|
" about to try build ..."); |
|
} |
|
PKIXCertPathBuilderResult cpbr = |
|
(PKIXCertPathBuilderResult)builder.build(builderParams); |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.buildToNewKey()" + |
|
" about to check revocation ..."); |
|
} |
|
// Now check revocation of all certs in path, assuming that |
|
|
|
if (stackedCerts == null) { |
|
stackedCerts = new HashSet<X509Certificate>(); |
|
} |
|
stackedCerts.add(currCert); |
|
TrustAnchor ta = cpbr.getTrustAnchor(); |
|
PublicKey prevKey2 = ta.getCAPublicKey(); |
|
if (prevKey2 == null) { |
|
prevKey2 = ta.getTrustedCert().getPublicKey(); |
|
} |
|
boolean signFlag = true; |
|
List<? extends Certificate> cpList = |
|
cpbr.getCertPath().getCertificates(); |
|
try { |
|
for (int i = cpList.size() - 1; i >= 0; i--) { |
|
X509Certificate cert = (X509Certificate) cpList.get(i); |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.buildToNewKey()" |
|
+ " index " + i + " checking " |
|
+ cert); |
|
} |
|
checkCRLs(cert, prevKey2, null, signFlag, true, |
|
stackedCerts, newAnchors); |
|
signFlag = certCanSignCrl(cert); |
|
prevKey2 = cert.getPublicKey(); |
|
} |
|
} catch (CertPathValidatorException cpve) { |
|
|
|
badKeys.add(cpbr.getPublicKey()); |
|
continue; |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("RevocationChecker.buildToNewKey()" + |
|
" got key " + cpbr.getPublicKey()); |
|
} |
|
// Now check revocation on the current cert using that key and |
|
// the corresponding certificate. |
|
// If it doesn't check out, try to find a different key. |
|
|
|
PublicKey newKey = cpbr.getPublicKey(); |
|
X509Certificate newCert = cpList.isEmpty() ? |
|
null : (X509Certificate) cpList.get(0); |
|
try { |
|
checkCRLs(currCert, newKey, newCert, |
|
true, false, null, params.trustAnchors()); |
|
|
|
return; |
|
} catch (CertPathValidatorException cpve) { |
|
|
|
if (cpve.getReason() == BasicReason.REVOKED) { |
|
throw cpve; |
|
} |
|
// Otherwise, ignore the exception and |
|
// try to get another key. |
|
} |
|
badKeys.add(newKey); |
|
} catch (InvalidAlgorithmParameterException iape) { |
|
throw new CertPathValidatorException(iape); |
|
} catch (CertPathBuilderException cpbe) { |
|
throw new CertPathValidatorException |
|
("Could not determine revocation status", null, null, |
|
-1, BasicReason.UNDETERMINED_REVOCATION_STATUS); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static class RejectKeySelector extends X509CertSelector { |
|
private final Set<PublicKey> badKeySet; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
RejectKeySelector(Set<PublicKey> badPublicKeys) { |
|
this.badKeySet = badPublicKeys; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean match(Certificate cert) { |
|
if (!super.match(cert)) |
|
return(false); |
|
|
|
if (badKeySet.contains(cert.getPublicKey())) { |
|
if (debug != null) |
|
debug.println("RejectKeySelector.match: bad key"); |
|
return false; |
|
} |
|
|
|
if (debug != null) |
|
debug.println("RejectKeySelector.match: returning true"); |
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public String toString() { |
|
StringBuilder sb = new StringBuilder(); |
|
sb.append("RejectKeySelector: [\n"); |
|
sb.append(super.toString()); |
|
sb.append(badKeySet); |
|
sb.append("]"); |
|
return sb.toString(); |
|
} |
|
} |
|
} |