8010748: Add PKIXRevocationChecker NO_FALLBACK option and improve SOFT_FAIL option

Reviewed-by: vinnie
This commit is contained in:
Sean Mullan 2013-07-25 10:58:00 -04:00
parent 6788137f6f
commit 74a0cc9f37
8 changed files with 286 additions and 203 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -50,21 +50,26 @@ import java.util.Set;
* status of certificates with OCSP and CRLs. By default, OCSP is the * status of certificates with OCSP and CRLs. By default, OCSP is the
* preferred mechanism for checking revocation status, with CRLs as the * preferred mechanism for checking revocation status, with CRLs as the
* fallback mechanism. However, this preference can be switched to CRLs with * fallback mechanism. However, this preference can be switched to CRLs with
* the {@link Option#PREFER_CRLS PREFER_CRLS} option. * the {@link Option#PREFER_CRLS PREFER_CRLS} option. In addition, the fallback
* mechanism can be disabled with the {@link Option#NO_FALLBACK NO_FALLBACK}
* option.
* *
* <p>A {@code PKIXRevocationChecker} is obtained by calling the * <p>A {@code PKIXRevocationChecker} is obtained by calling the
* {@link CertPathValidator#getRevocationChecker getRevocationChecker} method * {@link CertPathValidator#getRevocationChecker getRevocationChecker} method
* of a PKIX {@code CertPathValidator}. Additional parameters and options * of a PKIX {@code CertPathValidator}. Additional parameters and options
* specific to revocation can be set (by calling {@link #setOCSPResponder} * specific to revocation can be set (by calling the
* method for instance). The {@code PKIXRevocationChecker} is added to * {@link #setOcspResponder setOcspResponder} method for instance). The
* a {@code PKIXParameters} object using the * {@code PKIXRevocationChecker} is added to a {@code PKIXParameters} object
* {@link PKIXParameters#addCertPathChecker addCertPathChecker} * using the {@link PKIXParameters#addCertPathChecker addCertPathChecker}
* or {@link PKIXParameters#setCertPathCheckers setCertPathCheckers} method, * or {@link PKIXParameters#setCertPathCheckers setCertPathCheckers} method,
* and then the {@code PKIXParameters} is passed along with the {@code CertPath} * and then the {@code PKIXParameters} is passed along with the {@code CertPath}
* to be validated to the {@link CertPathValidator#validate validate} method * to be validated to the {@link CertPathValidator#validate validate} method
* of a PKIX {@code CertPathValidator}. When supplying a revocation checker in * of a PKIX {@code CertPathValidator}. When supplying a revocation checker in
* this manner, it will be used to check revocation irrespective of the setting * this manner, it will be used to check revocation irrespective of the setting
* of the {@link PKIXParameters#isRevocationEnabled RevocationEnabled} flag. * of the {@link PKIXParameters#isRevocationEnabled RevocationEnabled} flag.
* Similarly, a {@code PKIXRevocationChecker} may be added to a
* {@code PKIXBuilderParameters} object for use with a PKIX
* {@code CertPathBuilder}.
* *
* <p>Note that when a {@code PKIXRevocationChecker} is added to * <p>Note that when a {@code PKIXRevocationChecker} is added to
* {@code PKIXParameters}, it clones the {@code PKIXRevocationChecker}; * {@code PKIXParameters}, it clones the {@code PKIXRevocationChecker};
@ -83,6 +88,13 @@ import java.util.Set;
* need not synchronize. * need not synchronize.
* *
* @since 1.8 * @since 1.8
*
* @see <a href="http://www.ietf.org/rfc/rfc2560.txt"><i>RFC&nbsp;2560: X.509
* Internet Public Key Infrastructure Online Certificate Status Protocol -
* OCSP</i></a>, <br><a
* href="http://www.ietf.org/rfc/rfc5280.txt"><i>RFC&nbsp;5280: Internet X.509
* Public Key Infrastructure Certificate and Certificate Revocation List (CRL)
* Profile</i></a>
*/ */
public abstract class PKIXRevocationChecker extends PKIXCertPathChecker { public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
private URI ocspResponder; private URI ocspResponder;
@ -101,7 +113,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* *
* @param uri the responder URI * @param uri the responder URI
*/ */
public void setOCSPResponder(URI uri) { public void setOcspResponder(URI uri) {
this.ocspResponder = uri; this.ocspResponder = uri;
} }
@ -114,7 +126,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* *
* @return the responder URI, or {@code null} if not set * @return the responder URI, or {@code null} if not set
*/ */
public URI getOCSPResponder() { public URI getOcspResponder() {
return ocspResponder; return ocspResponder;
} }
@ -126,7 +138,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* *
* @param cert the responder's certificate * @param cert the responder's certificate
*/ */
public void setOCSPResponderCert(X509Certificate cert) { public void setOcspResponderCert(X509Certificate cert) {
this.ocspResponderCert = cert; this.ocspResponderCert = cert;
} }
@ -140,7 +152,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* *
* @return the responder's certificate, or {@code null} if not set * @return the responder's certificate, or {@code null} if not set
*/ */
public X509Certificate getOCSPResponderCert() { public X509Certificate getOcspResponderCert() {
return ocspResponderCert; return ocspResponderCert;
} }
@ -151,7 +163,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* @param extensions a list of extensions. The list is copied to protect * @param extensions a list of extensions. The list is copied to protect
* against subsequent modification. * against subsequent modification.
*/ */
public void setOCSPExtensions(List<Extension> extensions) public void setOcspExtensions(List<Extension> extensions)
{ {
this.ocspExtensions = (extensions == null) this.ocspExtensions = (extensions == null)
? Collections.<Extension>emptyList() ? Collections.<Extension>emptyList()
@ -161,10 +173,10 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
/** /**
* Gets the optional OCSP request extensions. * Gets the optional OCSP request extensions.
* *
* @return an unmodifiable list of extensions. Returns an empty list if no * @return an unmodifiable list of extensions. The list is empty if no
* extensions have been specified. * extensions have been specified.
*/ */
public List<Extension> getOCSPExtensions() { public List<Extension> getOcspExtensions() {
return Collections.unmodifiableList(ocspExtensions); return Collections.unmodifiableList(ocspExtensions);
} }
@ -177,7 +189,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* DER-encoded OCSP response for that certificate. A deep copy of * DER-encoded OCSP response for that certificate. A deep copy of
* the map is performed to protect against subsequent modification. * the map is performed to protect against subsequent modification.
*/ */
public void setOCSPResponses(Map<X509Certificate, byte[]> responses) public void setOcspResponses(Map<X509Certificate, byte[]> responses)
{ {
if (responses == null) { if (responses == null) {
this.ocspResponses = Collections.<X509Certificate, byte[]>emptyMap(); this.ocspResponses = Collections.<X509Certificate, byte[]>emptyMap();
@ -200,7 +212,7 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
* the map is returned to protect against subsequent modification. * the map is returned to protect against subsequent modification.
* Returns an empty map if no responses have been specified. * Returns an empty map if no responses have been specified.
*/ */
public Map<X509Certificate, byte[]> getOCSPResponses() { public Map<X509Certificate, byte[]> getOcspResponses() {
Map<X509Certificate, byte[]> copy = new HashMap<>(ocspResponses.size()); Map<X509Certificate, byte[]> copy = new HashMap<>(ocspResponses.size());
for (Map.Entry<X509Certificate, byte[]> e : ocspResponses.entrySet()) { for (Map.Entry<X509Certificate, byte[]> e : ocspResponses.entrySet()) {
copy.put(e.getKey(), e.getValue().clone()); copy.put(e.getKey(), e.getValue().clone());
@ -223,15 +235,31 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
/** /**
* Gets the revocation options. * Gets the revocation options.
* *
* @return an unmodifiable set of revocation options, or an empty set if * @return an unmodifiable set of revocation options. The set is empty if
* none are specified * no options have been specified.
*/ */
public Set<Option> getOptions() { public Set<Option> getOptions() {
return Collections.unmodifiableSet(options); return Collections.unmodifiableSet(options);
} }
/**
* Returns a list containing the exceptions that are ignored by the
* revocation checker when the {@link Option#SOFT_FAIL SOFT_FAIL} option
* is set. The list is cleared each time {@link #init init} is called.
* The list is ordered in ascending order according to the certificate
* index returned by {@link CertPathValidatorException#getIndex getIndex}
* method of each entry.
* <p>
* An implementation of {@code PKIXRevocationChecker} is responsible for
* adding the ignored exceptions to the list.
*
* @return an unmodifiable list containing the ignored exceptions. The list
* is empty if no exceptions have been ignored.
*/
public abstract List<CertPathValidatorException> getSoftFailExceptions();
@Override @Override
public Object clone() { public PKIXRevocationChecker clone() {
PKIXRevocationChecker copy = (PKIXRevocationChecker)super.clone(); PKIXRevocationChecker copy = (PKIXRevocationChecker)super.clone();
copy.ocspExtensions = new ArrayList<>(ocspExtensions); copy.ocspExtensions = new ArrayList<>(ocspExtensions);
copy.ocspResponses = new HashMap<>(ocspResponses); copy.ocspResponses = new HashMap<>(ocspResponses);
@ -262,9 +290,26 @@ public abstract class PKIXRevocationChecker extends PKIXCertPathChecker {
*/ */
PREFER_CRLS, PREFER_CRLS,
/** /**
* Ignore network failures. The default behavior is to consider it a * Disable the fallback mechanism.
* failure if the revocation status of a certificate cannot be obtained */
* due to a network error. This option applies to both OCSP and CRLs. NO_FALLBACK,
/**
* Allow revocation check to succeed if the revocation status cannot be
* determined for one of the following reasons:
* <p><ul>
* <li>The CRL or OCSP response cannot be obtained because of a
* network error.
* <li>The OCSP responder returns one of the following errors
* specified in section 2.3 of RFC 2560: internalError, tryLater,
* or unauthorized.
* </ul><br>
* Note that these conditions apply to both OCSP and CRLs, and unless
* the {@code NO_FALLBACK} option is set, the revocation check is
* allowed to succeed only if both mechanisms fail under one of the
* conditions as stated above.
* Exceptions that cause the network errors are ignored but can be
* later retrieved by calling the
* {@link #getSoftFailExceptions getSoftFailExceptions} method.
*/ */
SOFT_FAIL SOFT_FAIL
} }

View File

@ -255,7 +255,9 @@ public final class OCSP {
} }
response = Arrays.copyOf(response, total); response = Arrays.copyOf(response, total);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new NetworkFailureException(ioe); throw new CertPathValidatorException(
"Unable to determine revocation status due to network error",
ioe, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
} finally { } finally {
if (in != null) { if (in != null) {
try { try {
@ -355,17 +357,4 @@ public final class OCSP {
*/ */
Map<String, Extension> getSingleExtensions(); Map<String, Extension> getSingleExtensions();
} }
static class NetworkFailureException extends CertPathValidatorException {
private static final long serialVersionUID = 0l;
NetworkFailureException(Throwable t) {
super(t);
}
@Override
public CertPathValidatorException.Reason getReason() {
return BasicReason.UNDETERMINED_REVOCATION_STATUS;
}
}
} }

View File

@ -30,6 +30,7 @@ import java.security.*;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.CertPathValidatorException; import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.CRLReason; import java.security.cert.CRLReason;
import java.security.cert.TrustAnchor; import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -121,8 +122,8 @@ public final class OCSPResponse {
public enum ResponseStatus { public enum ResponseStatus {
SUCCESSFUL, // Response has valid confirmations SUCCESSFUL, // Response has valid confirmations
MALFORMED_REQUEST, // Illegal confirmation request MALFORMED_REQUEST, // Illegal request
INTERNAL_ERROR, // Internal error in issuer INTERNAL_ERROR, // Internal error in responder
TRY_LATER, // Try again later TRY_LATER, // Try again later
UNUSED, // is not used UNUSED, // is not used
SIG_REQUIRED, // Must sign the request SIG_REQUIRED, // Must sign the request
@ -381,9 +382,18 @@ public final class OCSPResponse {
Date date, byte[] nonce) Date date, byte[] nonce)
throws CertPathValidatorException throws CertPathValidatorException
{ {
if (responseStatus != ResponseStatus.SUCCESSFUL) { switch (responseStatus) {
throw new CertPathValidatorException case SUCCESSFUL:
("OCSP response error: " + responseStatus); break;
case UNAUTHORIZED:
case TRY_LATER:
case INTERNAL_ERROR:
throw new CertPathValidatorException(
"OCSP response error: " + responseStatus, null, null, -1,
BasicReason.UNDETERMINED_REVOCATION_STATUS);
default:
throw new CertPathValidatorException("OCSP response error: " +
responseStatus);
} }
// Check that the response includes a response for all of the // Check that the response includes a response for all of the

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -193,12 +193,17 @@ public final class PKIXCertPathValidator extends CertPathValidatorSpi {
List<PKIXCertPathChecker> checkers = params.certPathCheckers(); List<PKIXCertPathChecker> checkers = params.certPathCheckers();
for (PKIXCertPathChecker checker : checkers) { for (PKIXCertPathChecker checker : checkers) {
if (checker instanceof PKIXRevocationChecker) { if (checker instanceof PKIXRevocationChecker) {
if (revCheckerAdded) {
throw new CertPathValidatorException(
"Only one PKIXRevocationChecker can be specified");
}
revCheckerAdded = true; revCheckerAdded = true;
// if it's our own, initialize it // if it's our own, initialize it
if (checker instanceof RevocationChecker) if (checker instanceof RevocationChecker) {
((RevocationChecker)checker).init(anchor, params); ((RevocationChecker)checker).init(anchor, params);
} }
} }
}
// only add a RevocationChecker if revocation is enabled and // only add a RevocationChecker if revocation is enabled and
// a PKIXRevocationChecker has not already been added // a PKIXRevocationChecker has not already been added
if (params.revocationEnabled() && !revCheckerAdded) { if (params.revocationEnabled() && !revCheckerAdded) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -30,6 +30,7 @@ import java.security.PublicKey;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertPathValidatorException; import java.security.cert.CertPathValidatorException;
import java.security.cert.PKIXCertPathChecker; import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.TrustAnchor; import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
@ -235,9 +236,16 @@ class ReverseState implements State {
for (PKIXCertPathChecker checker : userCheckers) { for (PKIXCertPathChecker checker : userCheckers) {
if (checker instanceof AlgorithmChecker) { if (checker instanceof AlgorithmChecker) {
((AlgorithmChecker)checker).trySetTrustAnchor(anchor); ((AlgorithmChecker)checker).trySetTrustAnchor(anchor);
} else if (checker instanceof RevocationChecker) { } else if (checker instanceof PKIXRevocationChecker) {
if (revCheckerAdded) {
throw new CertPathValidatorException(
"Only one PKIXRevocationChecker can be specified");
}
// if it's our own, initialize it
if (checker instanceof RevocationChecker) {
((RevocationChecker)checker).init(anchor, buildParams); ((RevocationChecker)checker).init(anchor, buildParams);
((RevocationChecker)checker).init(false); }
((PKIXRevocationChecker)checker).init(false);
revCheckerAdded = true; revCheckerAdded = true;
} }
} }

View File

@ -38,14 +38,7 @@ import java.security.Security;
import java.security.cert.CertPathValidatorException.BasicReason; import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.Extension; import java.security.cert.Extension;
import java.security.cert.*; import java.security.cert.*;
import java.util.Arrays; import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import static sun.security.provider.certpath.OCSP.*; import static sun.security.provider.certpath.OCSP.*;
@ -70,13 +63,16 @@ class RevocationChecker extends PKIXRevocationChecker {
private Map<X509Certificate, byte[]> ocspResponses; private Map<X509Certificate, byte[]> ocspResponses;
private List<Extension> ocspExtensions; private List<Extension> ocspExtensions;
private boolean legacy; private boolean legacy;
private LinkedList<CertPathValidatorException> softFailExceptions =
new LinkedList<>();
// state variables // state variables
private X509Certificate issuerCert; private X509Certificate issuerCert;
private PublicKey prevPubKey; private PublicKey prevPubKey;
private boolean crlSignFlag; private boolean crlSignFlag;
private int certIndex;
private enum Mode { PREFER_OCSP, PREFER_CRLS, ONLY_CRLS }; private enum Mode { PREFER_OCSP, PREFER_CRLS, ONLY_CRLS, ONLY_OCSP };
private Mode mode = Mode.PREFER_OCSP; private Mode mode = Mode.PREFER_OCSP;
private static class RevocationProperties { private static class RevocationProperties {
@ -104,9 +100,9 @@ class RevocationChecker extends PKIXRevocationChecker {
throws CertPathValidatorException throws CertPathValidatorException
{ {
RevocationProperties rp = getRevocationProperties(); RevocationProperties rp = getRevocationProperties();
URI uri = getOCSPResponder(); URI uri = getOcspResponder();
responderURI = (uri == null) ? toURI(rp.ocspUrl) : uri; responderURI = (uri == null) ? toURI(rp.ocspUrl) : uri;
X509Certificate cert = getOCSPResponderCert(); X509Certificate cert = getOcspResponderCert();
responderCert = (cert == null) responderCert = (cert == null)
? getResponderCert(rp, params.trustAnchors(), ? getResponderCert(rp, params.trustAnchors(),
params.certStores()) params.certStores())
@ -117,31 +113,38 @@ class RevocationChecker extends PKIXRevocationChecker {
case ONLY_END_ENTITY: case ONLY_END_ENTITY:
case PREFER_CRLS: case PREFER_CRLS:
case SOFT_FAIL: case SOFT_FAIL:
case NO_FALLBACK:
break; break;
default: default:
throw new CertPathValidatorException( throw new CertPathValidatorException(
"Unrecognized revocation parameter option: " + option); "Unrecognized revocation parameter option: " + option);
} }
} }
softFail = options.contains(Option.SOFT_FAIL);
// set mode, only end entity flag // set mode, only end entity flag
if (legacy) { if (legacy) {
mode = (rp.ocspEnabled) ? Mode.PREFER_OCSP : Mode.ONLY_CRLS; mode = (rp.ocspEnabled) ? Mode.PREFER_OCSP : Mode.ONLY_CRLS;
onlyEE = rp.onlyEE; onlyEE = rp.onlyEE;
} else { } else {
if (options.contains(Option.NO_FALLBACK)) {
if (options.contains(Option.PREFER_CRLS)) { 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; mode = Mode.PREFER_CRLS;
} }
onlyEE = options.contains(Option.ONLY_END_ENTITY); onlyEE = options.contains(Option.ONLY_END_ENTITY);
} }
softFail = options.contains(Option.SOFT_FAIL);
if (legacy) { if (legacy) {
crlDP = rp.crlDPEnabled; crlDP = rp.crlDPEnabled;
} else { } else {
crlDP = true; crlDP = true;
} }
ocspResponses = getOCSPResponses(); ocspResponses = getOcspResponses();
ocspExtensions = getOCSPExtensions(); ocspExtensions = getOcspExtensions();
this.anchor = anchor; this.anchor = anchor;
this.params = params; this.params = params;
@ -297,14 +300,19 @@ class RevocationChecker extends PKIXRevocationChecker {
if (forward) { if (forward) {
throw new throw new
CertPathValidatorException("forward checking not supported"); CertPathValidatorException("forward checking not supported");
} else { }
if (anchor != null) { if (anchor != null) {
issuerCert = anchor.getTrustedCert(); issuerCert = anchor.getTrustedCert();
prevPubKey = (issuerCert != null) ? issuerCert.getPublicKey() prevPubKey = (issuerCert != null) ? issuerCert.getPublicKey()
: anchor.getCAPublicKey(); : anchor.getCAPublicKey();
} }
crlSignFlag = true; crlSignFlag = true;
if (params.certPath() != null) {
certIndex = params.certPath().getCertificates().size() - 1;
} else {
certIndex = -1;
} }
softFailExceptions.clear();
} }
@Override @Override
@ -317,28 +325,35 @@ class RevocationChecker extends PKIXRevocationChecker {
return null; return null;
} }
@Override
public List<CertPathValidatorException> getSoftFailExceptions() {
return Collections.unmodifiableList(softFailExceptions);
}
@Override @Override
public void check(Certificate cert, Collection<String> unresolvedCritExts) public void check(Certificate cert, Collection<String> unresolvedCritExts)
throws CertPathValidatorException throws CertPathValidatorException
{ {
X509Certificate xcert = (X509Certificate)cert; check((X509Certificate)cert, unresolvedCritExts,
if (onlyEE && xcert.getBasicConstraints() != -1) { prevPubKey, crlSignFlag);
if (debug != null) {
debug.println("Skipping revocation check, not end entity cert");
}
} else {
check(xcert, unresolvedCritExts, prevPubKey, crlSignFlag);
}
updateState(xcert);
} }
void check(X509Certificate xcert, Collection<String> unresolvedCritExts, private void check(X509Certificate xcert,
Collection<String> unresolvedCritExts,
PublicKey pubKey, boolean crlSignFlag) PublicKey pubKey, boolean crlSignFlag)
throws CertPathValidatorException throws CertPathValidatorException
{ {
try { try {
if (onlyEE && xcert.getBasicConstraints() != -1) {
if (debug != null) {
debug.println("Skipping revocation check, not end " +
"entity cert");
}
return;
}
switch (mode) { switch (mode) {
case PREFER_OCSP: case PREFER_OCSP:
case ONLY_OCSP:
checkOCSP(xcert, unresolvedCritExts); checkOCSP(xcert, unresolvedCritExts);
break; break;
case PREFER_CRLS: case PREFER_CRLS:
@ -351,14 +366,17 @@ class RevocationChecker extends PKIXRevocationChecker {
if (e.getReason() == BasicReason.REVOKED) { if (e.getReason() == BasicReason.REVOKED) {
throw e; throw e;
} }
CertPathValidatorException cause = e; boolean eSoftFail = isSoftFailException(e);
if (softFail && e instanceof NetworkFailureException) { if (eSoftFail) {
if (mode == Mode.ONLY_CRLS) return; if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) {
return;
} }
// Rethrow the exception if ONLY_CRLS } else {
if (mode == Mode.ONLY_CRLS) { if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) {
throw e; throw e;
} }
}
CertPathValidatorException cause = e;
// Otherwise, failover // Otherwise, failover
if (debug != null) { if (debug != null) {
debug.println("RevocationChecker.check() " + e.getMessage()); debug.println("RevocationChecker.check() " + e.getMessage());
@ -382,20 +400,33 @@ class RevocationChecker extends PKIXRevocationChecker {
if (x.getReason() == BasicReason.REVOKED) { if (x.getReason() == BasicReason.REVOKED) {
throw x; throw x;
} }
if (cause != null) { if (!isSoftFailException(x)) {
if (softFail && cause instanceof NetworkFailureException) {
return;
} else {
cause.addSuppressed(x); cause.addSuppressed(x);
throw cause; throw cause;
} else {
// only pass if both exceptions were soft failures
if (!eSoftFail) {
throw cause;
} }
} }
if (softFail && x instanceof NetworkFailureException) {
return;
} }
throw x; } finally {
updateState(xcert);
} }
} }
private boolean isSoftFailException(CertPathValidatorException e) {
if (softFail &&
e.getReason() == BasicReason.UNDETERMINED_REVOCATION_STATUS)
{
// recreate exception with correct index
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) private void updateState(X509Certificate cert)
@ -411,6 +442,9 @@ class RevocationChecker extends PKIXRevocationChecker {
} }
prevPubKey = pubKey; prevPubKey = pubKey;
crlSignFlag = certCanSignCrl(cert); crlSignFlag = certCanSignCrl(cert);
if (certIndex > 0) {
certIndex--;
}
} }
// Maximum clock skew in milliseconds (15 minutes) allowed when checking // Maximum clock skew in milliseconds (15 minutes) allowed when checking
@ -457,7 +491,7 @@ class RevocationChecker extends PKIXRevocationChecker {
CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW); CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW);
// First, check user-specified CertStores // First, check user-specified CertStores
NetworkFailureException nfe = null; CertPathValidatorException networkFailureException = null;
for (CertStore store : certStores) { for (CertStore store : certStores) {
try { try {
for (CRL crl : store.getCRLs(sel)) { for (CRL crl : store.getCRLs(sel)) {
@ -468,10 +502,13 @@ class RevocationChecker extends PKIXRevocationChecker {
debug.println("RevocationChecker.checkCRLs() " + debug.println("RevocationChecker.checkCRLs() " +
"CertStoreException: " + e.getMessage()); "CertStoreException: " + e.getMessage());
} }
if (softFail && nfe == null && if (networkFailureException == null &&
CertStoreHelper.isCausedByNetworkIssue(store.getType(),e)) { CertStoreHelper.isCausedByNetworkIssue(store.getType(),e)) {
// save this exception, we may need to throw it later // save this exception, we may need to throw it later
nfe = new NetworkFailureException(e); networkFailureException = new CertPathValidatorException(
"Unable to determine revocation status due to " +
"network error", e, null, -1,
BasicReason.UNDETERMINED_REVOCATION_STATUS);
} }
} }
} }
@ -508,14 +545,17 @@ class RevocationChecker extends PKIXRevocationChecker {
approvedCRLs.addAll(DistributionPointFetcher.getCRLs( approvedCRLs.addAll(DistributionPointFetcher.getCRLs(
sel, signFlag, prevKey, sel, signFlag, prevKey,
params.sigProvider(), certStores, params.sigProvider(), certStores,
reasonsMask, anchors, params.date())); reasonsMask, anchors, null));
} }
} catch (CertStoreException e) { } catch (CertStoreException e) {
if (softFail && e instanceof CertStoreTypeException) { if (e instanceof CertStoreTypeException) {
CertStoreTypeException cste = (CertStoreTypeException)e; CertStoreTypeException cste = (CertStoreTypeException)e;
if (CertStoreHelper.isCausedByNetworkIssue(cste.getType(), if (CertStoreHelper.isCausedByNetworkIssue(cste.getType(),
e)) { e)) {
throw new NetworkFailureException(e); throw new CertPathValidatorException(
"Unable to determine revocation status due to " +
"network error", e, null, -1,
BasicReason.UNDETERMINED_REVOCATION_STATUS);
} }
} }
throw new CertPathValidatorException(e); throw new CertPathValidatorException(e);
@ -531,25 +571,25 @@ class RevocationChecker extends PKIXRevocationChecker {
stackedCerts); stackedCerts);
return; return;
} catch (CertPathValidatorException cpve) { } catch (CertPathValidatorException cpve) {
if (nfe != null) { if (networkFailureException != null) {
// if a network issue previously prevented us from // if a network issue previously prevented us from
// retrieving a CRL from one of the user-specified // retrieving a CRL from one of the user-specified
// CertStores and SOFT_FAIL is enabled, throw it now // CertStores, throw it now so it can be handled
// so it can be handled appropriately // appropriately
throw nfe; throw networkFailureException;
} }
throw cpve; throw cpve;
} }
} else { } else {
if (nfe != null) { if (networkFailureException != null) {
// if a network issue previously prevented us from // if a network issue previously prevented us from
// retrieving a CRL from one of the user-specified // retrieving a CRL from one of the user-specified
// CertStores and SOFT_FAIL is enabled, throw it now // CertStores, throw it now so it can be handled
// so it can be handled appropriately // appropriately
throw nfe; throw networkFailureException;
} }
throw new CertPathValidatorException throw new CertPathValidatorException(
("Could not determine revocation status", null, null, -1, "Could not determine revocation status", null, null, -1,
BasicReason.UNDETERMINED_REVOCATION_STATUS); BasicReason.UNDETERMINED_REVOCATION_STATUS);
} }
} }
@ -595,14 +635,9 @@ class RevocationChecker extends PKIXRevocationChecker {
unresCritExts.remove(ReasonCode_Id.toString()); unresCritExts.remove(ReasonCode_Id.toString());
unresCritExts.remove(CertificateIssuer_Id.toString()); unresCritExts.remove(CertificateIssuer_Id.toString());
if (!unresCritExts.isEmpty()) { if (!unresCritExts.isEmpty()) {
if (debug != null) { throw new CertPathValidatorException(
debug.println("Unrecognized " "Unrecognized critical extension(s) in revoked " +
+ "critical extension(s) in revoked CRL entry: " "CRL entry");
+ unresCritExts);
}
throw new CertPathValidatorException
("Could not determine revocation status", null, null,
-1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
} }
} }
@ -610,11 +645,14 @@ class RevocationChecker extends PKIXRevocationChecker {
if (reasonCode == null) { if (reasonCode == null) {
reasonCode = CRLReason.UNSPECIFIED; reasonCode = CRLReason.UNSPECIFIED;
} }
Throwable t = new CertificateRevokedException Date revocationDate = entry.getRevocationDate();
(entry.getRevocationDate(), reasonCode, if (revocationDate.before(params.date())) {
Throwable t = new CertificateRevokedException(
revocationDate, reasonCode,
crl.getIssuerX500Principal(), entry.getExtensions()); crl.getIssuerX500Principal(), entry.getExtensions());
throw new CertPathValidatorException(t.getMessage(), t, throw new CertPathValidatorException(
null, -1, BasicReason.REVOKED); t.getMessage(), t, null, -1, BasicReason.REVOKED);
}
} }
} }
} }
@ -630,9 +668,6 @@ class RevocationChecker extends PKIXRevocationChecker {
throw new CertPathValidatorException(ce); throw new CertPathValidatorException(ce);
} }
URI responderURI = (this.responderURI != null)
? this.responderURI : getOCSPServerURI(currCert);
X509Certificate respCert = (responderCert == null) ? issuerCert X509Certificate respCert = (responderCert == null) ? issuerCert
: responderCert; : responderCert;
@ -671,23 +706,38 @@ class RevocationChecker extends PKIXRevocationChecker {
params.date(), nonce); params.date(), nonce);
} else { } 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), response = OCSP.check(Collections.singletonList(certId),
responderURI, respCert, params.date(), responderURI, respCert, null,
ocspExtensions); ocspExtensions);
} }
} catch (IOException e) { } catch (IOException e) {
throw new CertPathValidatorException(e); throw new CertPathValidatorException(
"Unable to determine revocation status due to network error",
e, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
} }
RevocationStatus rs = RevocationStatus rs =
(RevocationStatus)response.getSingleResponse(certId); (RevocationStatus)response.getSingleResponse(certId);
RevocationStatus.CertStatus certStatus = rs.getCertStatus(); RevocationStatus.CertStatus certStatus = rs.getCertStatus();
if (certStatus == RevocationStatus.CertStatus.REVOKED) { if (certStatus == RevocationStatus.CertStatus.REVOKED) {
Date revocationTime = rs.getRevocationTime();
if (revocationTime.before(params.date())) {
Throwable t = new CertificateRevokedException( Throwable t = new CertificateRevokedException(
rs.getRevocationTime(), rs.getRevocationReason(), revocationTime, rs.getRevocationReason(),
respCert.getSubjectX500Principal(), rs.getSingleExtensions()); respCert.getSubjectX500Principal(),
rs.getSingleExtensions());
throw new CertPathValidatorException(t.getMessage(), t, null, throw new CertPathValidatorException(t.getMessage(), t, null,
-1, BasicReason.REVOKED); -1, BasicReason.REVOKED);
}
} else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) { } else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) {
throw new CertPathValidatorException( throw new CertPathValidatorException(
"Certificate's revocation status is unknown", null, "Certificate's revocation status is unknown", null,
@ -711,34 +761,6 @@ class RevocationChecker extends PKIXRevocationChecker {
return hexNumber.toString(); return hexNumber.toString();
} }
private static URI getOCSPServerURI(X509CertImpl cert)
throws CertPathValidatorException
{
// Examine the certificate's AuthorityInfoAccess extension
AuthorityInfoAccessExtension aia =
cert.getAuthorityInfoAccessExtension();
if (aia == null) {
throw new CertPathValidatorException(
"Must specify the location of an OCSP Responder");
}
List<AccessDescription> descriptions = aia.getAccessDescriptions();
for (AccessDescription description : descriptions) {
if (description.getAccessMethod().equals((Object)
AccessDescription.Ad_OCSP_Id)) {
GeneralName generalName = description.getAccessLocation();
if (generalName.getType() == GeneralNameInterface.NAME_URI) {
URIName uri = (URIName)generalName.getName();
return uri.getURI();
}
}
}
throw new CertPathValidatorException(
"Cannot find the location of the OCSP Responder");
}
/** /**
* Checks that a cert can be used to verify a CRL. * Checks that a cert can be used to verify a CRL.
* *
@ -870,8 +892,8 @@ class RevocationChecker extends PKIXRevocationChecker {
" circular dependency"); " circular dependency");
} }
throw new CertPathValidatorException throw new CertPathValidatorException
("Could not determine revocation status", null, null, ("Could not determine revocation status", null, null, -1,
-1, BasicReason.UNDETERMINED_REVOCATION_STATUS); BasicReason.UNDETERMINED_REVOCATION_STATUS);
} }
// Try to find another key that might be able to sign // Try to find another key that might be able to sign
@ -1067,6 +1089,15 @@ class RevocationChecker extends PKIXRevocationChecker {
} }
} }
@Override
public RevocationChecker clone() {
RevocationChecker copy = (RevocationChecker)super.clone();
// we don't deep-copy the exceptions, but that is ok because they
// are never modified after they are instantiated
copy.softFailExceptions = new LinkedList<>(softFailExceptions);
return copy;
}
/* /*
* This inner class extends the X509CertSelector to add an additional * This inner class extends the X509CertSelector to add an additional
* check to make sure the subject public key isn't on a particular list. * check to make sure the subject public key isn't on a particular list.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -422,7 +422,6 @@ public final class SunCertPathBuilder extends CertPathBuilderSpi {
buildParams.anyPolicyInhibited(), buildParams.anyPolicyInhibited(),
buildParams.policyQualifiersRejected(), buildParams.policyQualifiersRejected(),
rootNode); rootNode);
checkers.add(policyChecker); checkers.add(policyChecker);
// add the algorithm checker // add the algorithm checker
@ -455,13 +454,18 @@ public final class SunCertPathBuilder extends CertPathBuilderSpi {
List<PKIXCertPathChecker> ckrs = buildParams.certPathCheckers(); List<PKIXCertPathChecker> ckrs = buildParams.certPathCheckers();
for (PKIXCertPathChecker ckr : ckrs) { for (PKIXCertPathChecker ckr : ckrs) {
if (ckr instanceof PKIXRevocationChecker) { if (ckr instanceof PKIXRevocationChecker) {
if (revCheckerAdded) {
throw new CertPathValidatorException(
"Only one PKIXRevocationChecker can be specified");
}
revCheckerAdded = true; revCheckerAdded = true;
// if it's our own, initialize it // if it's our own, initialize it
if (ckr instanceof RevocationChecker) if (ckr instanceof RevocationChecker) {
((RevocationChecker)ckr).init(builder.trustAnchor, ((RevocationChecker)ckr).init(builder.trustAnchor,
buildParams); buildParams);
} }
} }
}
// only add a RevocationChecker if revocation is enabled and // only add a RevocationChecker if revocation is enabled and
// a PKIXRevocationChecker has not already been added // a PKIXRevocationChecker has not already been added
if (buildParams.revocationEnabled() && !revCheckerAdded) { if (buildParams.revocationEnabled() && !revCheckerAdded) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/** /**
* @test * @test
* @bug 6854712 7171570 * @bug 6854712 7171570 8010748
* @summary Basic unit test for PKIXRevocationChecker * @summary Basic unit test for PKIXRevocationChecker
*/ */
@ -32,19 +32,9 @@ import java.io.InputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URI; import java.net.URI;
import java.security.cert.CertificateFactory; import java.security.cert.*;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathChecker;
import java.security.cert.CertPathValidator;
import java.security.cert.Extension;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.PKIXRevocationChecker.Option; import java.security.cert.PKIXRevocationChecker.Option;
import java.security.cert.X509Certificate; import java.util.*;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class UnitTest { public class UnitTest {
@ -56,22 +46,23 @@ public class UnitTest {
System.out.println("Testing that get methods return null or " + System.out.println("Testing that get methods return null or " +
"empty lists/sets/maps"); "empty lists/sets/maps");
requireNull(prc.getOCSPResponder(), "getOCSPResponder()"); requireNull(prc.getOcspResponder(), "getOcspResponder()");
requireNull(prc.getOCSPResponderCert(), "getOCSPResponderCert()"); requireNull(prc.getOcspResponderCert(), "getOcspResponderCert()");
requireEmpty(prc.getOCSPExtensions(), "getOCSPExtensions()"); requireEmpty(prc.getOcspExtensions(), "getOcspExtensions()");
requireEmpty(prc.getOCSPResponses(), "getOCSPResponses()"); requireEmpty(prc.getOcspResponses(), "getOcspResponses()");
requireEmpty(prc.getOptions(), "getOptions()"); requireEmpty(prc.getOptions(), "getOptions()");
requireEmpty(prc.getSoftFailExceptions(), "getSoftFailExceptions()");
System.out.println("Testing that get methods return same parameters " + System.out.println("Testing that get methods return same parameters " +
"that are passed to set methods"); "that are passed to set methods");
URI uri = new URI("http://localhost"); URI uri = new URI("http://localhost");
prc.setOCSPResponder(uri); prc.setOcspResponder(uri);
requireEquals(uri, prc.getOCSPResponder(), "getOCSPResponder()"); requireEquals(uri, prc.getOcspResponder(), "getOcspResponder()");
X509Certificate cert = getCert(); X509Certificate cert = getCert();
prc.setOCSPResponderCert(cert); prc.setOcspResponderCert(cert);
requireEquals(cert, prc.getOCSPResponderCert(), requireEquals(cert, prc.getOcspResponderCert(),
"getOCSPResponderCert()"); "getOcspResponderCert()");
List<Extension> exts = new ArrayList<>(); List<Extension> exts = new ArrayList<>();
for (String oid : cert.getNonCriticalExtensionOIDs()) { for (String oid : cert.getNonCriticalExtensionOIDs()) {
@ -79,8 +70,8 @@ public class UnitTest {
exts.add(new ExtensionImpl(oid, exts.add(new ExtensionImpl(oid,
cert.getExtensionValue(oid), false)); cert.getExtensionValue(oid), false));
} }
prc.setOCSPExtensions(exts); prc.setOcspExtensions(exts);
requireEquals(exts, prc.getOCSPExtensions(), "getOCSPExtensions()"); requireEquals(exts, prc.getOcspExtensions(), "getOcspExtensions()");
Set<Option> options = EnumSet.of(Option.ONLY_END_ENTITY); Set<Option> options = EnumSet.of(Option.ONLY_END_ENTITY);
prc.setOptions(options); prc.setOptions(options);
@ -88,14 +79,14 @@ public class UnitTest {
System.out.println("Testing that parameters are re-initialized to " + System.out.println("Testing that parameters are re-initialized to " +
"default values if null is passed to set methods"); "default values if null is passed to set methods");
prc.setOCSPResponder(null); prc.setOcspResponder(null);
requireNull(prc.getOCSPResponder(), "getOCSPResponder()"); requireNull(prc.getOcspResponder(), "getOcspResponder()");
prc.setOCSPResponderCert(null); prc.setOcspResponderCert(null);
requireNull(prc.getOCSPResponderCert(), "getOCSPResponderCert()"); requireNull(prc.getOcspResponderCert(), "getOcspResponderCert()");
prc.setOCSPExtensions(null); prc.setOcspExtensions(null);
requireEmpty(prc.getOCSPExtensions(), "getOCSPExtensions()"); requireEmpty(prc.getOcspExtensions(), "getOcspExtensions()");
prc.setOCSPResponses(null); prc.setOcspResponses(null);
requireEmpty(prc.getOCSPResponses(), "getOCSPResponses()"); requireEmpty(prc.getOcspResponses(), "getOcspResponses()");
prc.setOptions(null); prc.setOptions(null);
requireEmpty(prc.getOptions(), "getOptions()"); requireEmpty(prc.getOptions(), "getOptions()");