trustedCACertificateAnchors,
- CertStore trustedCACertificateCertStore,
- Date now) throws CertificateNotTrustedException, JceException, CertificateNotYetValidException, CertificateExpiredException {
+ /**
+ * Validates that the provided {@code certificate} is trusted and performs certificate revocation checking
+ * depending on {@code revocationMode}.
+ *
+ * Trust validation is performed by building a certification path from {@code certificate} to one of the
+ * configured {@code trustedCACertificateAnchors} using the supplied {@code trustedCACertificateCertStore}.
+ * The effective validation time is {@code now}. In addition, the trust anchor certificate's validity period
+ * is explicitly validated.
+ *
+ * Revocation behavior is controlled by {@code revocationMode}:
+ *
+ * - {@link RevocationMode#DISABLED} - no revocation checking is performed. Both
+ * {@code ocspCertificateRevocationChecker} and {@code customPkixRevocationChecker} must be {@code null}.
+ * - {@link RevocationMode#CUSTOM_OCSP} - revocation is checked by the provided
+ * {@code ocspCertificateRevocationChecker}. Platform (provider default) revocation checking is disabled.
+ * {@code customPkixRevocationChecker} must be {@code null}.
+ * - {@link RevocationMode#CUSTOM_PKIX} - revocation is checked by the provided
+ * {@code customPkixRevocationChecker} installed as the (only) PKIX cert path checker. Provider default
+ * revocation checking is disabled. {@code ocspCertificateRevocationChecker} must be {@code null}.
+ * - {@link RevocationMode#PLATFORM_OCSP} - revocation is checked using the platform PKIX revocation checker
+ * configured to enforce OCSP checking for the subject certificate with no fallback to CRLs
+ * ({@link PKIXRevocationChecker.Option#ONLY_END_ENTITY} and {@link PKIXRevocationChecker.Option#NO_FALLBACK}).
+ * Provider default revocation checking is disabled. Both custom checker parameters must be {@code null}.
+ *
+ *
+ * @param certificate the subject certificate to validate
+ * @param trustedCACertificateAnchors trust anchors used for PKIX path building (Web eID typically configures issuing intermediates)
+ * @param trustedCACertificateCertStore certificate store containing trusted CA/intermediate certificates used during path building
+ * @param now validation time used for certificate validity and PKIX path building
+ * @param revocationMode revocation checking mode
+ * @param ocspCertificateRevocationChecker custom OCSP revocation checker (required only for {@code CUSTOM_OCSP})
+ * @param customPkixRevocationChecker custom PKIX revocation checker (required only for {@code CUSTOM_PKIX})
+ * @return a list of {@link RevocationInfo} objects; the list is non-null and may be empty.
+ * It is populated for {@link RevocationMode#CUSTOM_OCSP}, and may be populated for
+ * {@link RevocationMode#CUSTOM_PKIX} when the provided {@code customPkixRevocationChecker}
+ * has an explicit OCSP responder URI configured; otherwise it is empty.
+ *
+ * @throws NullPointerException if any required parameter is {@code null}
+ * @throws IllegalArgumentException if the supplied checker parameters are inconsistent with {@code revocationMode}
+ * @throws CertificateNotYetValidException if the subject or trust anchor certificate is not yet valid at {@code now}
+ * @throws CertificateExpiredException if the subject or trust anchor certificate is expired at {@code now}
+ * @throws CertificateNotTrustedException if no valid certification path can be built to the configured trust anchors
+ * @throws JceException if the underlying JCA/JCE implementation fails unexpectedly
+ * @throws AuthTokenException if a custom revocation checker fails or reports the certificate as revoked
+ */
+ public static List validateCertificateTrustAndRevocation(X509Certificate certificate,
+ Set trustedCACertificateAnchors,
+ CertStore trustedCACertificateCertStore,
+ Date now,
+ RevocationMode revocationMode,
+ OcspCertificateRevocationChecker ocspCertificateRevocationChecker,
+ PKIXRevocationChecker customPkixRevocationChecker) throws AuthTokenException {
+
+ requireNonNull(certificate, "certificate");
+ requireNonNull(trustedCACertificateAnchors, "trustedCACertificateAnchors");
+ requireNonNull(trustedCACertificateCertStore, "trustedCACertificateCertStore");
+ requireNonNull(now, "now");
+ requireNonNull(revocationMode, "revocationMode");
+
certificateIsValidOnDate(certificate, now, "User");
final X509CertSelector selector = new X509CertSelector();
@@ -67,21 +131,86 @@ public static X509Certificate validateIsSignedByTrustedCA(X509Certificate certif
try {
final PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters(trustedCACertificateAnchors, selector);
- // Certificate revocation check is intentionally disabled as we do the OCSP check with SubjectCertificateNotRevokedValidator ourselves.
- pkixBuilderParameters.setRevocationEnabled(false);
pkixBuilderParameters.setDate(now);
pkixBuilderParameters.addCertStore(trustedCACertificateCertStore);
// See the comment in buildCertStoreFromCertificates() below why we use the default JCE provider.
final CertPathBuilder certPathBuilder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType());
+
+ List revocationInfoList = List.of();
+
+ switch (revocationMode) {
+ case DISABLED -> {
+ if (customPkixRevocationChecker != null || ocspCertificateRevocationChecker != null) {
+ throw new IllegalArgumentException("customPkixRevocationChecker and ocspCertificateRevocationChecker must be null when revocationMode is DISABLED");
+ }
+ pkixBuilderParameters.setRevocationEnabled(false);
+ }
+
+ case CUSTOM_OCSP -> {
+ if (customPkixRevocationChecker != null) {
+ throw new IllegalArgumentException("customPkixRevocationChecker must be null when revocationMode is CUSTOM_OCSP");
+ }
+ if (ocspCertificateRevocationChecker == null) {
+ throw new IllegalArgumentException("ocspCertificateRevocationChecker must be provided when revocationMode is CUSTOM_OCSP");
+ }
+ // OcspCertificateRevocationChecker performs revocation checking, disable platform's revocation checks.
+ pkixBuilderParameters.setRevocationEnabled(false);
+ }
+
+ case CUSTOM_PKIX -> {
+ if (ocspCertificateRevocationChecker != null) {
+ throw new IllegalArgumentException("ocspCertificateRevocationChecker must be null when revocationMode is CUSTOM_PKIX");
+ }
+ if (customPkixRevocationChecker == null) {
+ throw new IllegalArgumentException("customPkixRevocationChecker must be provided when revocationMode is CUSTOM_PKIX");
+ }
+ // Setting RevocationEnabled is not required for the checker to run, but disable
+ // the provider default revocation just in case to avoid surprises across providers.
+ pkixBuilderParameters.setRevocationEnabled(false);
+ pkixBuilderParameters.setCertPathCheckers(List.of(customPkixRevocationChecker));
+
+ if (customPkixRevocationChecker.getOcspResponder() != null) {
+ revocationInfoList = List.of(new RevocationInfo(customPkixRevocationChecker.getOcspResponder(), null));
+ }
+ }
+
+ case PLATFORM_OCSP -> {
+ if (customPkixRevocationChecker != null || ocspCertificateRevocationChecker != null) {
+ throw new IllegalArgumentException("customPkixRevocationChecker and ocspCertificateRevocationChecker must be null when revocationMode is PLATFORM_OCSP");
+ }
+
+ final PKIXRevocationChecker checker = buildOcspEnforcedPkixRevocationChecker(certPathBuilder);
+ // See the comment in CUSTOM_PKIX case above.
+ pkixBuilderParameters.setRevocationEnabled(false);
+ pkixBuilderParameters.setCertPathCheckers(List.of(checker));
+ }
+
+ default -> throw new IllegalStateException("Unhandled revocationMode: " + revocationMode);
+ }
+
final PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) certPathBuilder.build(pkixBuilderParameters);
final X509Certificate trustedCACert = result.getTrustAnchor().getTrustedCert();
+ if (trustedCACert == null) {
+ throw new IllegalStateException("TrustAnchor.getTrustedCert() returned null, it must contain a trusted certificate");
+ }
- // Verify that the trusted CA cert is presently valid before returning the result.
+ // PKIX path building does not validate trust anchor validity period, do it ourselves.
certificateIsValidOnDate(trustedCACert, now, "Trusted CA");
- return trustedCACert;
+ if (revocationMode == RevocationMode.CUSTOM_OCSP) {
+ if (!certificate.getIssuerX500Principal().equals(trustedCACert.getSubjectX500Principal())) {
+ throw new IllegalStateException(
+ "Trust anchor is not the issuer of the subject certificate, check your configured certificate authorities. " +
+ "Subject issuer=" + certificate.getIssuerX500Principal() +
+ ", trust anchor subject=" + trustedCACert.getSubjectX500Principal()
+ );
+ }
+ revocationInfoList = ocspCertificateRevocationChecker.validateCertificateNotRevoked(certificate, trustedCACert);
+ }
+
+ return revocationInfoList;
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
throw new JceException(e);
@@ -96,7 +225,7 @@ public static Set buildTrustAnchorsFromCertificates(Collection certificates) throws JceException {
- // We use the default JCE provider as there is no reason to use Bouncy Castle, moreover BC requires
+ // Use the default JCE provider as there is no reason to use Bouncy Castle, moreover BC requires
// the validated certificate to be in the certificate store which breaks the clean immutable usage of
// trustedCACertificateCertStore in SubjectCertificateTrustedValidator.
try {
@@ -106,6 +235,20 @@ public static CertStore buildCertStoreFromCertificates(Collection trustedCACertificates = new HashSet<>();
- private boolean isUserCertificateRevocationCheckWithOcspEnabled = true;
- private Duration ocspRequestTimeout = Duration.ofSeconds(5);
- private Duration allowedOcspResponseTimeSkew = Duration.ofMinutes(15);
- private Duration maxOcspResponseThisUpdateAge = Duration.ofMinutes(2);
- private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
// Don't allow Estonian Mobile-ID policy by default.
private Collection disallowedSubjectCertificatePolicies = newHashSet(
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1,
@@ -58,7 +53,10 @@ public final class AuthTokenValidationConfiguration {
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY
);
- private Collection nonceDisabledOcspUrls = new HashSet<>();
+ private boolean isUserCertificateRevocationCheckEnabled = true;
+ private OcspCertificateRevocationChecker ocspCertificateRevocationChecker;
+ private PKIXRevocationChecker pkixRevocationChecker;
+ private RevocationMode revocationMode = RevocationMode.PLATFORM_OCSP;
AuthTokenValidationConfiguration() {
}
@@ -66,13 +64,11 @@ public final class AuthTokenValidationConfiguration {
private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other) {
this.siteOrigin = other.siteOrigin;
this.trustedCACertificates = Set.copyOf(other.trustedCACertificates);
- this.isUserCertificateRevocationCheckWithOcspEnabled = other.isUserCertificateRevocationCheckWithOcspEnabled;
- this.ocspRequestTimeout = other.ocspRequestTimeout;
- this.allowedOcspResponseTimeSkew = other.allowedOcspResponseTimeSkew;
- this.maxOcspResponseThisUpdateAge = other.maxOcspResponseThisUpdateAge;
- this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
this.disallowedSubjectCertificatePolicies = Set.copyOf(other.disallowedSubjectCertificatePolicies);
- this.nonceDisabledOcspUrls = Set.copyOf(other.nonceDisabledOcspUrls);
+ this.isUserCertificateRevocationCheckEnabled = other.isUserCertificateRevocationCheckEnabled;
+ this.ocspCertificateRevocationChecker = other.ocspCertificateRevocationChecker;
+ this.pkixRevocationChecker = other.pkixRevocationChecker;
+ this.revocationMode = other.revocationMode;
}
void setSiteOrigin(URI siteOrigin) {
@@ -87,52 +83,36 @@ Collection getTrustedCACertificates() {
return trustedCACertificates;
}
- boolean isUserCertificateRevocationCheckWithOcspEnabled() {
- return isUserCertificateRevocationCheckWithOcspEnabled;
- }
-
- void setUserCertificateRevocationCheckWithOcspDisabled() {
- isUserCertificateRevocationCheckWithOcspEnabled = false;
- }
-
- public Duration getOcspRequestTimeout() {
- return ocspRequestTimeout;
- }
-
- void setOcspRequestTimeout(Duration ocspRequestTimeout) {
- this.ocspRequestTimeout = ocspRequestTimeout;
- }
-
- public Duration getAllowedOcspResponseTimeSkew() {
- return allowedOcspResponseTimeSkew;
+ public Collection getDisallowedSubjectCertificatePolicies() {
+ return disallowedSubjectCertificatePolicies;
}
- public void setAllowedOcspResponseTimeSkew(Duration allowedOcspResponseTimeSkew) {
- this.allowedOcspResponseTimeSkew = allowedOcspResponseTimeSkew;
+ boolean isUserCertificateRevocationCheckEnabled() {
+ return isUserCertificateRevocationCheckEnabled;
}
- public Duration getMaxOcspResponseThisUpdateAge() {
- return maxOcspResponseThisUpdateAge;
+ void setUserCertificateRevocationCheckDisabled() {
+ isUserCertificateRevocationCheckEnabled = false;
}
- public void setMaxOcspResponseThisUpdateAge(Duration maxOcspResponseThisUpdateAge) {
- this.maxOcspResponseThisUpdateAge = maxOcspResponseThisUpdateAge;
+ public void setOcspCertificateRevocationChecker(OcspCertificateRevocationChecker ocspCertificateRevocationChecker) {
+ this.ocspCertificateRevocationChecker = ocspCertificateRevocationChecker;
}
- public DesignatedOcspServiceConfiguration getDesignatedOcspServiceConfiguration() {
- return designatedOcspServiceConfiguration;
+ public OcspCertificateRevocationChecker getOcspCertificateRevocationChecker() {
+ return ocspCertificateRevocationChecker;
}
- public void setDesignatedOcspServiceConfiguration(DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration) {
- this.designatedOcspServiceConfiguration = designatedOcspServiceConfiguration;
+ public void setPkixRevocationChecker(PKIXRevocationChecker pkixRevocationChecker) {
+ this.pkixRevocationChecker = pkixRevocationChecker;
}
- public Collection getDisallowedSubjectCertificatePolicies() {
- return disallowedSubjectCertificatePolicies;
+ public PKIXRevocationChecker getPkixRevocationChecker() {
+ return pkixRevocationChecker;
}
- public Collection getNonceDisabledOcspUrls() {
- return nonceDisabledOcspUrls;
+ public RevocationMode getRevocationMode() {
+ return revocationMode;
}
/**
@@ -147,9 +127,7 @@ void validate() {
if (trustedCACertificates.isEmpty()) {
throw new IllegalArgumentException("At least one trusted certificate authority must be provided");
}
- requirePositiveDuration(ocspRequestTimeout, "OCSP request timeout");
- requirePositiveDuration(allowedOcspResponseTimeSkew, "Allowed OCSP response time-skew");
- requirePositiveDuration(maxOcspResponseThisUpdateAge, "Max OCSP response thisUpdate age");
+ validateRevocationConfiguration();
}
AuthTokenValidationConfiguration copy() {
@@ -179,4 +157,43 @@ public static void validateIsOriginURL(URI uri) throws IllegalArgumentException
}
}
+ /**
+ * Validates that the revocation check configuration is consistent and derives the {@link RevocationMode} from it.
+ *
+ * Configuration is inconsistent if revocation checking is disabled but a checker is configured or if both
+ * checkers are configured simultaneously.
+ *
+ * @throws IllegalArgumentException if configuration is inconsistent
+ */
+ private void validateRevocationConfiguration() {
+ final boolean hasOcspChecker = ocspCertificateRevocationChecker != null;
+ final boolean hasPkixChecker = pkixRevocationChecker != null;
+
+ if (!isUserCertificateRevocationCheckEnabled) {
+ // If revocation check is disabled, no checker is allowed.
+ if (hasOcspChecker || hasPkixChecker) {
+ throw new IllegalArgumentException(
+ "User certificate revocation check is disabled, but a revocation checker was configured. " +
+ "Do not combine withoutUserCertificateRevocationCheck() with withOcspCertificateRevocationChecker(...) " +
+ "or withPKIXRevocationChecker(...)."
+ );
+ }
+ revocationMode = RevocationMode.DISABLED;
+ } else {
+ // Revocation check enabled, at most one checker allowed, if no checker provided, use default PKIX revocation checker in OCSP mode.
+ if (hasOcspChecker && hasPkixChecker) {
+ throw new IllegalArgumentException(
+ "Only one of OcspCertificateRevocationChecker or PKIXRevocationChecker may be configured. " +
+ "Do not combine withOcspCertificateRevocationChecker(...) with withPKIXRevocationChecker(...)."
+ );
+ }
+ if (hasOcspChecker) {
+ revocationMode = RevocationMode.CUSTOM_OCSP;
+ } else if (hasPkixChecker) {
+ revocationMode = RevocationMode.CUSTOM_PKIX;
+ } else {
+ revocationMode = RevocationMode.PLATFORM_OCSP;
+ }
+ }
+ }
}
diff --git a/src/main/java/eu/webeid/security/validator/AuthTokenValidator.java b/src/main/java/eu/webeid/security/validator/AuthTokenValidator.java
index 3476ea41..8cb95a8b 100644
--- a/src/main/java/eu/webeid/security/validator/AuthTokenValidator.java
+++ b/src/main/java/eu/webeid/security/validator/AuthTokenValidator.java
@@ -57,6 +57,6 @@ public interface AuthTokenValidator {
* @return validated subject certificate
* @throws AuthTokenException when validation fails
*/
- X509Certificate validate(WebEidAuthToken authToken, String currentChallengeNonce) throws AuthTokenException;
+ ValidationInfo validate(WebEidAuthToken authToken, String currentChallengeNonce) throws AuthTokenException;
}
diff --git a/src/main/java/eu/webeid/security/validator/AuthTokenValidatorBuilder.java b/src/main/java/eu/webeid/security/validator/AuthTokenValidatorBuilder.java
index 9122ee67..6c99649e 100644
--- a/src/main/java/eu/webeid/security/validator/AuthTokenValidatorBuilder.java
+++ b/src/main/java/eu/webeid/security/validator/AuthTokenValidatorBuilder.java
@@ -23,16 +23,14 @@
package eu.webeid.security.validator;
import eu.webeid.security.exceptions.JceException;
-import eu.webeid.security.validator.ocsp.OcspClient;
-import eu.webeid.security.validator.ocsp.OcspClientImpl;
-import eu.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
+import eu.webeid.security.validator.revocationcheck.OcspCertificateRevocationChecker;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
+import java.security.cert.PKIXRevocationChecker;
import java.security.cert.X509Certificate;
-import java.time.Duration;
import java.util.Collections;
import java.util.stream.Collectors;
@@ -44,7 +42,6 @@ public class AuthTokenValidatorBuilder {
private static final Logger LOG = LoggerFactory.getLogger(AuthTokenValidatorBuilder.class);
private final AuthTokenValidationConfiguration configuration = new AuthTokenValidationConfiguration();
- private OcspClient ocspClient;
/**
* Sets the expected site origin, i.e. the domain that the application is running on.
@@ -98,105 +95,50 @@ public AuthTokenValidatorBuilder withDisallowedCertificatePolicies(ASN1ObjectIde
}
/**
- * Turns off user certificate revocation check with OCSP.
+ * Turns off user certificate revocation check (with OCSP and/or CRL).
*
- * Turning off user certificate revocation check with OCSP is dangerous and should be
- * used only in exceptional circumstances.
+ * Turning off user certificate revocation check is dangerous and should be used only in
+ * exceptional circumstances.
* By default, the revocation check is turned on.
*
* @return the builder instance for method chaining.
*/
- public AuthTokenValidatorBuilder withoutUserCertificateRevocationCheckWithOcsp() {
- configuration.setUserCertificateRevocationCheckWithOcspDisabled();
- LOG.warn("User certificate revocation check with OCSP is disabled, " +
+ public AuthTokenValidatorBuilder withoutUserCertificateRevocationCheck() {
+ configuration.setUserCertificateRevocationCheckDisabled();
+ LOG.warn("User certificate revocation check is disabled, " +
"you should turn off the revocation check only in exceptional circumstances");
return this;
}
/**
- * Sets both the connection and response timeout of user certificate revocation check OCSP requests.
+ * Configures a custom OCSP revocation checker for validating user certificate revocation status.
*
- * This is an optional configuration parameter, the default is 5 seconds.
+ * When set, the platform (provider default) revocation mechanism is disabled and revocation checking
+ * delegated to the given {@link OcspCertificateRevocationChecker}. This option is mutually exclusive with
+ * {@link #withPKIXRevocationChecker(PKIXRevocationChecker)} and {@link #withoutUserCertificateRevocationCheck()}.
*
- * @param ocspRequestTimeout the duration of OCSP request connection and response timeout
- * @return the builder instance for method chaining.
- */
- public AuthTokenValidatorBuilder withOcspRequestTimeout(Duration ocspRequestTimeout) {
- configuration.setOcspRequestTimeout(ocspRequestTimeout);
- LOG.debug("OCSP request timeout set to {}", ocspRequestTimeout);
- return this;
- }
-
- /**
- * Sets the allowed time skew for OCSP response's thisUpdate and nextUpdate times.
- * This parameter is used to allow discrepancies between the system clock and the OCSP responder's clock,
- * which may occur due to clock drift, network delays or revocation updates that are not published in real time.
- *
- * This is an optional configuration parameter, the default is 15 minutes.
- * The relatively long default is specifically chosen to account for one particular OCSP responder that used
- * CRLs for authoritative revocation info, these CRLs were updated every 15 minutes.
- *
- * @param allowedTimeSkew the allowed time skew
- * @return the builder instance for method chaining.
- */
- public AuthTokenValidatorBuilder withAllowedOcspResponseTimeSkew(Duration allowedTimeSkew) {
- configuration.setAllowedOcspResponseTimeSkew(allowedTimeSkew);
- LOG.debug("Allowed OCSP response time skew set to {}", allowedTimeSkew);
- return this;
- }
-
- /**
- * Sets the maximum age of the OCSP response's thisUpdate time before it is considered too old.
- *
- * This is an optional configuration parameter, the default is 2 minutes.
- *
- * @param maxThisUpdateAge the maximum age of the OCSP response's thisUpdate time
- * @return the builder instance for method chaining.
- */
- public AuthTokenValidatorBuilder withMaxOcspResponseThisUpdateAge(Duration maxThisUpdateAge) {
- configuration.setMaxOcspResponseThisUpdateAge(maxThisUpdateAge);
- LOG.debug("Maximum OCSP response thisUpdate age set to {}", maxThisUpdateAge);
- return this;
- }
-
- /**
- * Adds the given URLs to the list of OCSP URLs for which the nonce protocol extension will be disabled.
- * The OCSP URL is extracted from the user certificate and some OCSP services don't support the nonce extension.
- *
- * @param urls OCSP URLs for which the nonce protocol extension will be disabled
- * @return the builder instance for method chaining
- */
- public AuthTokenValidatorBuilder withNonceDisabledOcspUrls(URI... urls) {
- Collections.addAll(configuration.getNonceDisabledOcspUrls(), urls);
- LOG.debug("OCSP URLs for which the nonce protocol extension is disabled set to {}", configuration.getNonceDisabledOcspUrls());
- return this;
- }
-
- /**
- * Activates the provided designated OCSP service for user certificate revocation check with OCSP.
- * The designated service is only used for checking the status of the certificates whose issuers are
- * supported by the service, falling back to the default OCSP service access location from
- * the certificate's AIA extension if not.
- *
- * @param serviceConfiguration configuration of the designated OCSP service
+ * @param customChecker custom OCSP revocation checker implementation
* @return the builder instance for method chaining
*/
- public AuthTokenValidatorBuilder withDesignatedOcspServiceConfiguration(DesignatedOcspServiceConfiguration serviceConfiguration) {
- configuration.setDesignatedOcspServiceConfiguration(serviceConfiguration);
- LOG.debug("Using designated OCSP service configuration");
+ public AuthTokenValidatorBuilder withOcspCertificateRevocationChecker(OcspCertificateRevocationChecker customChecker) {
+ configuration.setOcspCertificateRevocationChecker(customChecker);
return this;
}
/**
- * Uses the provided OCSP client instance during user certificate revocation check with OCSP.
- * The provided client instance must be thread-safe.
+ * Configures a custom {@link PKIXRevocationChecker} to be used as the revocation mechanism during user certificate
+ * validation with platform PKIX.
+ *
+ * When set, this checker replaces the platform (provider default) {@link PKIXRevocationChecker}. This option is
+ * mutually exclusive with {@link #withOcspCertificateRevocationChecker(OcspCertificateRevocationChecker)}
+ * and {@link #withoutUserCertificateRevocationCheck()}.
*
- * @param ocspClient OCSP client instance
+ * @param customChecker custom PKIX revocation checker
* @return the builder instance for method chaining
+ * @throws NullPointerException if {@code customChecker} is null
*/
- public AuthTokenValidatorBuilder withOcspClient(OcspClient ocspClient) {
- this.ocspClient = ocspClient;
- LOG.debug("Using the OCSP client provided by API consumer");
+ public AuthTokenValidatorBuilder withPKIXRevocationChecker(PKIXRevocationChecker customChecker) {
+ configuration.setPkixRevocationChecker(customChecker);
return this;
}
@@ -211,10 +153,7 @@ public AuthTokenValidatorBuilder withOcspClient(OcspClient ocspClient) {
*/
public AuthTokenValidator build() throws NullPointerException, IllegalArgumentException, JceException {
configuration.validate();
- if (configuration.isUserCertificateRevocationCheckWithOcspEnabled() && ocspClient == null) {
- ocspClient = OcspClientImpl.build(configuration.getOcspRequestTimeout());
- }
- return new AuthTokenValidatorImpl(configuration, ocspClient);
+ return new AuthTokenValidatorImpl(configuration);
}
}
diff --git a/src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java b/src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java
index 14cf3e78..9aae53f2 100644
--- a/src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java
+++ b/src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java
@@ -30,14 +30,12 @@
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.exceptions.AuthTokenParseException;
import eu.webeid.security.exceptions.JceException;
-import eu.webeid.security.validator.certvalidators.SubjectCertificateNotRevokedValidator;
+import eu.webeid.security.util.DateAndTime;
import eu.webeid.security.validator.certvalidators.SubjectCertificatePolicyValidator;
import eu.webeid.security.validator.certvalidators.SubjectCertificatePurposeValidator;
-import eu.webeid.security.validator.certvalidators.SubjectCertificateTrustedValidator;
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
-import eu.webeid.security.validator.ocsp.OcspClient;
-import eu.webeid.security.validator.ocsp.OcspServiceProvider;
-import eu.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
+import eu.webeid.ocsp.service.OcspServiceProvider;
+import eu.webeid.security.validator.revocationcheck.RevocationInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +43,8 @@
import java.security.cert.CertStore;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
-import java.util.Objects;
+import java.util.Date;
+import java.util.List;
import java.util.Set;
/**
@@ -65,15 +64,13 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
private final CertStore trustedCACertificateCertStore;
// OcspClient uses built-in HttpClient internally by default.
// A single HttpClient instance is reused for all HTTP calls to utilize connection and thread pools.
- private OcspClient ocspClient;
private OcspServiceProvider ocspServiceProvider;
private final AuthTokenSignatureValidator authTokenSignatureValidator;
/**
* @param configuration configuration parameters for the token validator
- * @param ocspClient client for communicating with the OCSP service
*/
- AuthTokenValidatorImpl(AuthTokenValidationConfiguration configuration, OcspClient ocspClient) throws JceException {
+ AuthTokenValidatorImpl(AuthTokenValidationConfiguration configuration) throws JceException {
// Copy the configuration object to make AuthTokenValidatorImpl immutable and thread-safe.
this.configuration = configuration.copy();
@@ -86,16 +83,6 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
new SubjectCertificatePolicyValidator(configuration.getDisallowedSubjectCertificatePolicies())::validateCertificatePolicies
);
- if (configuration.isUserCertificateRevocationCheckWithOcspEnabled()) {
- // The OCSP client may be provided by the API consumer.
- this.ocspClient = Objects.requireNonNull(ocspClient, "OCSP client must not be null when OCSP check is enabled");
- ocspServiceProvider = new OcspServiceProvider(
- configuration.getDesignatedOcspServiceConfiguration(),
- new AiaOcspServiceConfiguration(configuration.getNonceDisabledOcspUrls(),
- trustedCACertificateAnchors,
- trustedCACertificateCertStore));
- }
-
authTokenSignatureValidator = new AuthTokenSignatureValidator(configuration.getSiteOrigin());
}
@@ -113,7 +100,7 @@ public WebEidAuthToken parse(String authToken) throws AuthTokenException {
}
@Override
- public X509Certificate validate(WebEidAuthToken authToken, String currentChallengeNonce) throws AuthTokenException {
+ public ValidationInfo validate(WebEidAuthToken authToken, String currentChallengeNonce) throws AuthTokenException {
try {
LOG.info("Starting token validation");
return validateToken(authToken, currentChallengeNonce);
@@ -145,49 +132,41 @@ private WebEidAuthToken parseToken(String authToken) throws AuthTokenParseExcept
}
}
- private X509Certificate validateToken(WebEidAuthToken token, String currentChallengeNonce) throws AuthTokenException {
- if (token.getFormat() == null || !token.getFormat().startsWith(CURRENT_TOKEN_FORMAT_VERSION)) {
+ private ValidationInfo validateToken(WebEidAuthToken token, String currentChallengeNonce) throws AuthTokenException {
+ if (token.format() == null || !token.format().startsWith(CURRENT_TOKEN_FORMAT_VERSION)) {
throw new AuthTokenParseException("Only token format version '" + CURRENT_TOKEN_FORMAT_VERSION +
"' is currently supported");
}
- if (token.getUnverifiedCertificate() == null || token.getUnverifiedCertificate().isEmpty()) {
+ if (token.unverifiedCertificate() == null || token.unverifiedCertificate().isEmpty()) {
throw new AuthTokenParseException("'unverifiedCertificate' field is missing, null or empty");
}
- final X509Certificate subjectCertificate = CertificateLoader.decodeCertificateFromBase64(token.getUnverifiedCertificate());
+ final X509Certificate subjectCertificate = CertificateLoader.decodeCertificateFromBase64(token.unverifiedCertificate());
simpleSubjectCertificateValidators.executeFor(subjectCertificate);
- getCertTrustValidators().executeFor(subjectCertificate);
+
+ // Use the clock instance so that the date can be mocked in tests.
+ final Date now = DateAndTime.DefaultClock.getInstance().now();
+
+ final List revocationInfoList = CertificateValidator.validateCertificateTrustAndRevocation(
+ subjectCertificate,
+ trustedCACertificateAnchors,
+ trustedCACertificateCertStore,
+ now,
+ configuration.getRevocationMode(),
+ configuration.getOcspCertificateRevocationChecker(),
+ configuration.getPkixRevocationChecker()
+ );
+ LOG.debug("Subject certificate is valid and signed by a trusted CA");
// It is guaranteed that if the signature verification succeeds, then the origin and challenge
// have been implicitly and correctly verified without the need to implement any additional checks.
- authTokenSignatureValidator.validate(token.getAlgorithm(),
- token.getSignature(),
+ authTokenSignatureValidator.validate(token.algorithm(),
+ token.signature(),
subjectCertificate.getPublicKey(),
- currentChallengeNonce);
-
- return subjectCertificate;
- }
-
- /**
- * Creates the certificate trust validators batch.
- * As SubjectCertificateTrustedValidator has mutable state that SubjectCertificateNotRevokedValidator depends on,
- * they cannot be reused/cached in an instance variable in a multi-threaded environment. Hence, they are
- * re-created for each validation run for thread safety.
- *
- * @return certificate trust validator batch
- */
- private SubjectCertificateValidatorBatch getCertTrustValidators() {
- final SubjectCertificateTrustedValidator certTrustedValidator =
- new SubjectCertificateTrustedValidator(trustedCACertificateAnchors, trustedCACertificateCertStore);
- return SubjectCertificateValidatorBatch.createFrom(
- certTrustedValidator::validateCertificateTrusted
- ).addOptional(configuration.isUserCertificateRevocationCheckWithOcspEnabled(),
- new SubjectCertificateNotRevokedValidator(certTrustedValidator,
- ocspClient, ocspServiceProvider,
- configuration.getAllowedOcspResponseTimeSkew(),
- configuration.getMaxOcspResponseThisUpdateAge()
- )::validateCertificateNotRevoked
+ currentChallengeNonce
);
+
+ return new ValidationInfo(subjectCertificate, revocationInfoList);
}
}
diff --git a/src/main/java/eu/webeid/security/validator/ValidationInfo.java b/src/main/java/eu/webeid/security/validator/ValidationInfo.java
new file mode 100644
index 00000000..11a61561
--- /dev/null
+++ b/src/main/java/eu/webeid/security/validator/ValidationInfo.java
@@ -0,0 +1,15 @@
+package eu.webeid.security.validator;
+
+import eu.webeid.security.validator.revocationcheck.RevocationInfo;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+public record ValidationInfo(X509Certificate subjectCertificate, List revocationInfoList) {
+ public ValidationInfo {
+ requireNonNull(subjectCertificate, "subjectCertificate");
+ requireNonNull(revocationInfoList, "revocationInfoList");
+ }
+}
diff --git a/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateTrustedValidator.java b/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateTrustedValidator.java
deleted file mode 100644
index a706ffc4..00000000
--- a/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateTrustedValidator.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2020-2025 Estonian Information System Authority
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package eu.webeid.security.validator.certvalidators;
-
-import eu.webeid.security.certificate.CertificateValidator;
-import eu.webeid.security.exceptions.AuthTokenException;
-import eu.webeid.security.exceptions.CertificateExpiredException;
-import eu.webeid.security.exceptions.CertificateNotTrustedException;
-import eu.webeid.security.exceptions.CertificateNotYetValidException;
-import eu.webeid.security.util.DateAndTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.security.cert.CertStore;
-import java.security.cert.TrustAnchor;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Set;
-
-public final class SubjectCertificateTrustedValidator {
-
- private static final Logger LOG = LoggerFactory.getLogger(SubjectCertificateTrustedValidator.class);
-
- private final Set trustedCACertificateAnchors;
- private final CertStore trustedCACertificateCertStore;
- private X509Certificate subjectCertificateIssuerCertificate;
-
- public SubjectCertificateTrustedValidator(Set trustedCACertificateAnchors, CertStore trustedCACertificateCertStore) {
- this.trustedCACertificateAnchors = trustedCACertificateAnchors;
- this.trustedCACertificateCertStore = trustedCACertificateCertStore;
- }
-
- /**
- * Checks that the user certificate from the authentication token is valid and signed by
- * a trusted certificate authority. Also checks the validity of the user certificate's
- * trusted CA certificate.
- *
- * @param subjectCertificate user certificate to be validated
- * @throws CertificateNotTrustedException when user certificate is not signed by a trusted CA
- * @throws CertificateNotYetValidException when a CA certificate in the chain or the user certificate is not yet valid
- * @throws CertificateExpiredException when a CA certificate in the chain or the user certificate is expired
- */
- public void validateCertificateTrusted(X509Certificate subjectCertificate) throws AuthTokenException {
- // Use the clock instance so that the date can be mocked in tests.
- final Date now = DateAndTime.DefaultClock.getInstance().now();
- subjectCertificateIssuerCertificate = CertificateValidator.validateIsSignedByTrustedCA(
- subjectCertificate,
- trustedCACertificateAnchors,
- trustedCACertificateCertStore,
- now
- );
- LOG.debug("Subject certificate is valid and signed by a trusted CA");
- }
-
- public X509Certificate getSubjectCertificateIssuerCertificate() {
- return subjectCertificateIssuerCertificate;
- }
-}
diff --git a/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateValidatorBatch.java b/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateValidatorBatch.java
index 264135e0..64f4765f 100644
--- a/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateValidatorBatch.java
+++ b/src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateValidatorBatch.java
@@ -45,13 +45,6 @@ public void executeFor(X509Certificate subjectCertificate) throws AuthTokenExcep
}
}
- public SubjectCertificateValidatorBatch addOptional(boolean condition, SubjectCertificateValidator optionalValidator) {
- if (condition) {
- validatorList.add(optionalValidator);
- }
- return this;
- }
-
private SubjectCertificateValidatorBatch(List validatorList) {
this.validatorList = validatorList;
}
diff --git a/src/main/java/eu/webeid/security/validator/revocationcheck/OcspCertificateRevocationChecker.java b/src/main/java/eu/webeid/security/validator/revocationcheck/OcspCertificateRevocationChecker.java
new file mode 100644
index 00000000..bb0421cf
--- /dev/null
+++ b/src/main/java/eu/webeid/security/validator/revocationcheck/OcspCertificateRevocationChecker.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020-2025 Estonian Information System Authority
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package eu.webeid.security.validator.revocationcheck;
+
+import eu.webeid.security.exceptions.AuthTokenException;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+public interface OcspCertificateRevocationChecker {
+
+ List validateCertificateNotRevoked(X509Certificate subjectCertificate,
+ X509Certificate issuerCertificate) throws AuthTokenException;
+
+}
\ No newline at end of file
diff --git a/src/main/java/eu/webeid/security/validator/revocationcheck/RevocationInfo.java b/src/main/java/eu/webeid/security/validator/revocationcheck/RevocationInfo.java
new file mode 100644
index 00000000..3147ddd0
--- /dev/null
+++ b/src/main/java/eu/webeid/security/validator/revocationcheck/RevocationInfo.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020-2025 Estonian Information System Authority
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package eu.webeid.security.validator.revocationcheck;
+
+import java.net.URI;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+public record RevocationInfo(URI ocspResponderUri, Map ocspResponseAttributes) {
+
+ public static final String KEY_OCSP_RESPONSE = "OCSP_RESPONSE";
+ public static final String KEY_OCSP_ERROR = "OCSP_ERROR";
+
+}
\ No newline at end of file
diff --git a/src/main/java/eu/webeid/security/validator/revocationcheck/RevocationMode.java b/src/main/java/eu/webeid/security/validator/revocationcheck/RevocationMode.java
new file mode 100644
index 00000000..e9236873
--- /dev/null
+++ b/src/main/java/eu/webeid/security/validator/revocationcheck/RevocationMode.java
@@ -0,0 +1,5 @@
+package eu.webeid.security.validator.revocationcheck;
+
+public enum RevocationMode {
+ PLATFORM_OCSP, CUSTOM_OCSP, CUSTOM_PKIX, DISABLED
+}
diff --git a/src/test/java/eu/webeid/security/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.java b/src/test/java/eu/webeid/ocsp/DefaultOcspCertificateRevocationCheckerTest.java
similarity index 71%
rename from src/test/java/eu/webeid/security/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.java
rename to src/test/java/eu/webeid/ocsp/DefaultOcspCertificateRevocationCheckerTest.java
index 771c3018..bdfcd8d9 100644
--- a/src/test/java/eu/webeid/security/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.java
+++ b/src/test/java/eu/webeid/ocsp/DefaultOcspCertificateRevocationCheckerTest.java
@@ -20,17 +20,20 @@
* SOFTWARE.
*/
-package eu.webeid.security.validator.certvalidators;
+package eu.webeid.ocsp;
import eu.webeid.security.exceptions.CertificateExpiredException;
import eu.webeid.security.exceptions.CertificateNotTrustedException;
import eu.webeid.security.exceptions.JceException;
-import eu.webeid.security.exceptions.UserCertificateOCSPCheckFailedException;
-import eu.webeid.security.exceptions.UserCertificateRevokedException;
+import eu.webeid.ocsp.exceptions.UserCertificateOCSPCheckFailedException;
+import eu.webeid.ocsp.exceptions.UserCertificateRevokedException;
+import eu.webeid.security.testutil.AbstractTestWithValidator;
+import eu.webeid.security.testutil.AuthTokenValidators;
import eu.webeid.security.util.DateAndTime;
-import eu.webeid.security.validator.ocsp.OcspClient;
-import eu.webeid.security.validator.ocsp.OcspClientImpl;
-import eu.webeid.security.validator.ocsp.OcspServiceProvider;
+import eu.webeid.ocsp.client.OcspClient;
+import eu.webeid.ocsp.client.OcspClientImpl;
+import eu.webeid.ocsp.service.OcspServiceProvider;
+import eu.webeid.security.validator.AuthTokenValidator;
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPResp;
@@ -40,7 +43,6 @@
import java.io.IOException;
import java.io.InputStream;
-import java.lang.reflect.Field;
import java.net.ConnectException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -56,33 +58,39 @@
import static eu.webeid.security.testutil.Certificates.getJaakKristjanEsteid2018Cert;
import static eu.webeid.security.testutil.Certificates.getTestEsteid2018CA;
import static eu.webeid.security.testutil.DateMocker.mockDate;
-import static eu.webeid.security.testutil.OcspServiceMaker.getAiaOcspServiceProvider;
-import static eu.webeid.security.testutil.OcspServiceMaker.getDesignatedOcspServiceProvider;
-import static eu.webeid.security.validator.AuthTokenValidatorBuilderTest.CONFIGURATION;
+import static eu.webeid.ocsp.service.OcspServiceMaker.getAiaOcspServiceProvider;
+import static eu.webeid.ocsp.service.OcspServiceMaker.getDesignatedOcspServiceProvider;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
-class SubjectCertificateNotRevokedValidatorTest {
+class DefaultOcspCertificateRevocationCheckerTest extends AbstractTestWithValidator {
private final OcspClient ocspClient = OcspClientImpl.build(Duration.ofSeconds(5));
- private SubjectCertificateTrustedValidator trustedValidator;
private X509Certificate estEid2018Cert;
+ private X509Certificate testEsteid2018CA;
@BeforeEach
void setUp() throws Exception {
- trustedValidator = new SubjectCertificateTrustedValidator(null, null);
- setSubjectCertificateIssuerCertificate(trustedValidator);
estEid2018Cert = getJaakKristjanEsteid2018Cert();
+ testEsteid2018CA = getTestEsteid2018CA();
+ }
+
+ @Test
+ void whenValidDefaultConfiguration_thenSucceeds() throws Exception {
+ final AuthTokenValidator validator = getAuthTokenValidatorWithDefaultOcspCertificateRevocationChecker();
+ assertThatCode(() -> validator.validate(validAuthToken, VALID_CHALLENGE_NONCE))
+ .doesNotThrowAnyException();
}
@Test
void whenValidAiaOcspResponderConfiguration_thenSucceeds() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidator(ocspClient, getAiaOcspServiceProvider());
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationChecker(ocspClient, getAiaOcspServiceProvider());
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.doesNotThrowAnyException();
}
@@ -90,9 +98,9 @@ void whenValidAiaOcspResponderConfiguration_thenSucceeds() throws Exception {
@Disabled("As new designated test OCSP responder certificates are issued more frequently now, it is no longer feasible to keep the certificates up to date")
void whenValidDesignatedOcspResponderConfiguration_thenSucceeds() throws Exception {
final OcspServiceProvider ocspServiceProvider = getDesignatedOcspServiceProvider();
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidator(ocspServiceProvider);
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationChecker(ocspServiceProvider);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.doesNotThrowAnyException();
}
@@ -100,18 +108,18 @@ void whenValidDesignatedOcspResponderConfiguration_thenSucceeds() throws Excepti
@Disabled("As new designated test OCSP responder certificates are issued more frequently now, it is no longer feasible to keep the certificates up to date")
void whenValidOcspNonceDisabledConfiguration_thenSucceeds() throws Exception {
final OcspServiceProvider ocspServiceProvider = getDesignatedOcspServiceProvider(false);
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidator(ocspServiceProvider);
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationChecker(ocspServiceProvider);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.doesNotThrowAnyException();
}
@Test
void whenOcspUrlIsInvalid_thenThrows() throws Exception {
final OcspServiceProvider ocspServiceProvider = getDesignatedOcspServiceProvider("http://invalid.invalid");
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidator(ocspServiceProvider);
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationChecker(ocspServiceProvider);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
.cause()
.isInstanceOf(ConnectException.class);
@@ -120,9 +128,9 @@ void whenOcspUrlIsInvalid_thenThrows() throws Exception {
@Test
void whenOcspRequestFails_thenThrows() throws Exception {
final OcspServiceProvider ocspServiceProvider = getDesignatedOcspServiceProvider("http://demo.sk.ee/ocsps");
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidator(ocspServiceProvider);
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationChecker(ocspServiceProvider);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
.cause()
.isInstanceOf(IOException.class)
@@ -131,11 +139,11 @@ void whenOcspRequestFails_thenThrows() throws Exception {
@Test
void whenOcspRequestHasInvalidBody_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse("invalid".getBytes())
);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
.cause()
.isInstanceOf(IOException.class)
@@ -144,44 +152,44 @@ void whenOcspRequestHasInvalidBody_thenThrows() throws Exception {
@Test
void whenOcspResponseIsNotSuccessful_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(buildOcspResponseBodyWithInternalErrorStatus())
);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate revocation check has failed: Response status: internal error");
}
@Test
void whenOcspResponseHasInvalidCertificateId_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(buildOcspResponseBodyWithInvalidCertificateId())
);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate revocation check has failed: OCSP responded with certificate ID that differs from the requested ID");
}
@Test
void whenOcspResponseHasInvalidSignature_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(buildOcspResponseBodyWithInvalidSignature())
);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate revocation check has failed: OCSP response signature is invalid");
}
@Test
void whenOcspResponseHasInvalidResponderCert_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(buildOcspResponseBodyWithInvalidResponderCert())
);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
.cause()
.isInstanceOf(OCSPException.class)
@@ -190,11 +198,11 @@ void whenOcspResponseHasInvalidResponderCert_thenThrows() throws Exception {
@Test
void whenOcspResponseHasInvalidTag_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(buildOcspResponseBodyWithInvalidTag())
);
assertThatCode(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
.cause()
.isInstanceOf(OCSPException.class)
@@ -203,36 +211,36 @@ void whenOcspResponseHasInvalidTag_thenThrows() throws Exception {
@Test
void whenOcspResponseHas2CertResponses_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(getOcspResponseBytesFromResources("ocsp_response_with_2_responses.der"))
);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate revocation check has failed: OCSP response must contain one response, received 2 responses instead");
}
@Disabled("It is difficult to make Python and Java CertId equal, needs more work")
void whenOcspResponseHas2ResponderCerts_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(getOcspResponseBytesFromResources("ocsp_response_with_2_responder_certs.der"))
);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate revocation check has failed: OCSP response must contain one responder certificate, received 2 certificates instead");
}
@Test
void whenOcspResponseRevoked_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(getOcspResponseBytesFromResources("ocsp_response_revoked.der"))
);
try (var mockedClock = mockStatic(DateAndTime.DefaultClock.class)) {
mockDate("2021-09-18", mockedClock);
assertThatExceptionOfType(UserCertificateRevokedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate has been revoked: Revocation reason: 0");
}
}
@@ -241,55 +249,79 @@ void whenOcspResponseRevoked_thenThrows() throws Exception {
void whenOcspResponseUnknown_thenThrows() throws Exception {
final OcspServiceProvider ocspServiceProvider = getDesignatedOcspServiceProvider("https://web-eid-test.free.beeceptor.com");
final HttpResponse response = getMockedResponse(getOcspResponseBytesFromResources("ocsp_response_unknown.der"));
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidator(getMockClient(response), ocspServiceProvider);
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationChecker(getMockClient(response), ocspServiceProvider);
try (var mockedClock = mockStatic(DateAndTime.DefaultClock.class)) {
mockDate("2021-09-18T00:16:25", mockedClock);
assertThatExceptionOfType(UserCertificateRevokedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate has been revoked: Unknown status");
}
}
@Test
void whenOcspResponseCACertNotTrusted_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(getOcspResponseBytesFromResources("ocsp_response_unknown.der"))
);
try (var mockedClock = mockStatic(DateAndTime.DefaultClock.class)) {
mockDate("2021-09-18T00:16:25", mockedClock);
assertThatExceptionOfType(CertificateNotTrustedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("Certificate EMAILADDRESS=pki@sk.ee, CN=TEST of SK OCSP RESPONDER 2020, OU=OCSP, O=AS Sertifitseerimiskeskus, C=EE is not trusted");
}
}
@Test
void whenOcspResponseCACertExpired_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(getOcspResponseBytesFromResources("ocsp_response_unknown.der"))
);
assertThatExceptionOfType(CertificateExpiredException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("AIA OCSP responder certificate has expired");
}
@Test
void whenNonceDiffers_thenThrows() throws Exception {
- final SubjectCertificateNotRevokedValidator validator = getSubjectCertificateNotRevokedValidatorWithAiaOcsp(
+ final DefaultOcspCertificateRevocationChecker validator = getOcspCertificateRevocationCheckerWithAiaOcsp(
getMockedResponse(getOcspResponseBytesFromResources())
);
try (var mockedClock = mockStatic(DateAndTime.DefaultClock.class)) {
mockDate("2021-09-17T18:25:24", mockedClock);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validator.validateCertificateNotRevoked(estEid2018Cert))
+ validator.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA))
.withMessage("User certificate revocation check has failed: OCSP request and response nonces differ, possible replay attack");
}
}
+ @Test
+ void whenInvalidOcspResponseTimeSkew_thenThrows() {
+ assertThatThrownBy(() -> getOcspCertificateRevocationCheckerWithTimeSkewAndUpdateAge(Duration.ofMinutes(-1), Duration.ofMinutes(1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("allowedOcspResponseTimeSkew must be greater than zero");
+ }
+
+ @Test
+ void whenInvalidMaxOcspResponseThisUpdateAge_thenThrows() {
+ assertThatThrownBy(() -> getOcspCertificateRevocationCheckerWithTimeSkewAndUpdateAge(Duration.ofMinutes(1), Duration.ZERO))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("maxOcspResponseThisUpdateAge must be greater than zero");
+ }
+
+ private static AuthTokenValidator getAuthTokenValidatorWithDefaultOcspCertificateRevocationChecker() throws CertificateException, JceException, IOException {
+ return AuthTokenValidators.getDefaultAuthTokenValidatorBuilder()
+ .withOcspCertificateRevocationChecker(new DefaultOcspCertificateRevocationChecker(
+ OcspClientImpl.build(Duration.ofSeconds(5)),
+ getAiaOcspServiceProvider(),
+ DefaultOcspCertificateRevocationChecker.DEFAULT_TIME_SKEW,
+ DefaultOcspCertificateRevocationChecker.DEFAULT_THIS_UPDATE_AGE
+ )).build();
+ }
+
private static byte[] buildOcspResponseBodyWithInternalErrorStatus() throws IOException {
final byte[] ocspResponseBytes = getOcspResponseBytesFromResources();
final int STATUS_OFFSET = 6;
@@ -338,22 +370,20 @@ private static byte[] getOcspResponseBytesFromResources(String resource) throws
}
}
- private SubjectCertificateNotRevokedValidator getSubjectCertificateNotRevokedValidatorWithAiaOcsp(HttpResponse response) throws JceException {
- return getSubjectCertificateNotRevokedValidator(getMockClient(response), getAiaOcspServiceProvider());
+ private DefaultOcspCertificateRevocationChecker getOcspCertificateRevocationCheckerWithAiaOcsp(HttpResponse response) throws JceException {
+ return getOcspCertificateRevocationChecker(getMockClient(response), getAiaOcspServiceProvider());
}
- private SubjectCertificateNotRevokedValidator getSubjectCertificateNotRevokedValidator(OcspServiceProvider ocspServiceProvider) {
- return getSubjectCertificateNotRevokedValidator(ocspClient, ocspServiceProvider);
+ private DefaultOcspCertificateRevocationChecker getOcspCertificateRevocationChecker(OcspServiceProvider ocspServiceProvider) {
+ return getOcspCertificateRevocationChecker(ocspClient, ocspServiceProvider);
}
- private SubjectCertificateNotRevokedValidator getSubjectCertificateNotRevokedValidator(OcspClient client, OcspServiceProvider ocspServiceProvider) {
- return new SubjectCertificateNotRevokedValidator(trustedValidator, client, ocspServiceProvider, CONFIGURATION.getAllowedOcspResponseTimeSkew(), CONFIGURATION.getMaxOcspResponseThisUpdateAge());
+ private DefaultOcspCertificateRevocationChecker getOcspCertificateRevocationChecker(OcspClient client, OcspServiceProvider ocspServiceProvider) {
+ return new DefaultOcspCertificateRevocationChecker(client, ocspServiceProvider, DefaultOcspCertificateRevocationChecker.DEFAULT_TIME_SKEW, DefaultOcspCertificateRevocationChecker.DEFAULT_THIS_UPDATE_AGE);
}
- private static void setSubjectCertificateIssuerCertificate(SubjectCertificateTrustedValidator trustedValidator) throws NoSuchFieldException, IllegalAccessException, CertificateException, IOException {
- final Field field = trustedValidator.getClass().getDeclaredField("subjectCertificateIssuerCertificate");
- field.setAccessible(true);
- field.set(trustedValidator, getTestEsteid2018CA());
+ private void getOcspCertificateRevocationCheckerWithTimeSkewAndUpdateAge(Duration timeSkew, Duration updateAge) throws JceException {
+ new DefaultOcspCertificateRevocationChecker(ocspClient, getAiaOcspServiceProvider(), timeSkew, updateAge);
}
private HttpResponse getMockedResponse(byte[] bodyContent) throws URISyntaxException {
diff --git a/src/test/java/eu/webeid/security/validator/ocsp/OcspClientOverrideTest.java b/src/test/java/eu/webeid/ocsp/client/OcspClientOverrideTest.java
similarity index 66%
rename from src/test/java/eu/webeid/security/validator/ocsp/OcspClientOverrideTest.java
rename to src/test/java/eu/webeid/ocsp/client/OcspClientOverrideTest.java
index 4cd1d951..529b7c34 100644
--- a/src/test/java/eu/webeid/security/validator/ocsp/OcspClientOverrideTest.java
+++ b/src/test/java/eu/webeid/ocsp/client/OcspClientOverrideTest.java
@@ -20,8 +20,9 @@
* SOFTWARE.
*/
-package eu.webeid.security.validator.ocsp;
+package eu.webeid.ocsp.client;
+import eu.webeid.ocsp.DefaultOcspCertificateRevocationChecker;
import eu.webeid.security.exceptions.JceException;
import eu.webeid.security.testutil.AbstractTestWithValidator;
import eu.webeid.security.testutil.AuthTokenValidators;
@@ -37,6 +38,7 @@
import java.security.cert.CertificateException;
import java.time.Duration;
+import static eu.webeid.ocsp.service.OcspServiceMaker.getAiaOcspServiceProvider;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -44,22 +46,41 @@ class OcspClientOverrideTest extends AbstractTestWithValidator {
@Test
void whenOcspClientIsOverridden_thenItIsUsed() throws JceException, CertificateException, IOException {
- final AuthTokenValidator validator = AuthTokenValidators.getAuthTokenValidatorWithOverriddenOcspClient(new OcpClientThatThrows());
+ final AuthTokenValidator validator = getAuthTokenValidatorWithOverriddenOcspClient(new OcpClientThatThrows());
assertThatThrownBy(() -> validator.validate(validAuthToken, VALID_CHALLENGE_NONCE))
.cause()
.isInstanceOf(OcpClientThatThrowsException.class);
}
@Test
- @Disabled("Demonstrates how to configure the built-in HttpClient instance for OcspClientImpl")
+ void whenInvalidOcspRequestTimeout_thenThrows() throws Exception {
+ assertThatThrownBy(() -> OcspClientImpl.build(Duration.ofMinutes(-1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("ocspRequestTimeout must be greater than zero");
+ }
+
+ /**
+ * Demonstrates how to configure the built-in HttpClient instance for OcspClientImpl.
+ */
+ @Test
void whenOcspClientIsConfiguredWithCustomHttpClient_thenOcspCallSucceeds() throws JceException, CertificateException, IOException {
- final AuthTokenValidator validator = AuthTokenValidators.getAuthTokenValidatorWithOverriddenOcspClient(
+ final AuthTokenValidator validator = getAuthTokenValidatorWithOverriddenOcspClient(
new OcspClientImpl(HttpClient.newBuilder().build(), Duration.ofSeconds(5))
);
assertThatCode(() -> validator.validate(validAuthToken, VALID_CHALLENGE_NONCE))
.doesNotThrowAnyException();
}
+ private static AuthTokenValidator getAuthTokenValidatorWithOverriddenOcspClient(OcspClient ocspClient) throws CertificateException, JceException, IOException {
+ return AuthTokenValidators.getDefaultAuthTokenValidatorBuilder()
+ .withOcspCertificateRevocationChecker(new DefaultOcspCertificateRevocationChecker(
+ ocspClient,
+ getAiaOcspServiceProvider(),
+ DefaultOcspCertificateRevocationChecker.DEFAULT_TIME_SKEW,
+ DefaultOcspCertificateRevocationChecker.DEFAULT_THIS_UPDATE_AGE
+ )).build();
+ }
+
private static class OcpClientThatThrows implements OcspClient {
@Override
public OCSPResp request(URI url, OCSPReq request) throws IOException {
diff --git a/src/test/java/eu/webeid/security/validator/ocsp/OcspResponseValidatorTest.java b/src/test/java/eu/webeid/ocsp/protocol/OcspResponseValidatorTest.java
similarity index 87%
rename from src/test/java/eu/webeid/security/validator/ocsp/OcspResponseValidatorTest.java
rename to src/test/java/eu/webeid/ocsp/protocol/OcspResponseValidatorTest.java
index a8e80ddf..2952f583 100644
--- a/src/test/java/eu/webeid/security/validator/ocsp/OcspResponseValidatorTest.java
+++ b/src/test/java/eu/webeid/ocsp/protocol/OcspResponseValidatorTest.java
@@ -20,19 +20,20 @@
* SOFTWARE.
*/
-package eu.webeid.security.validator.ocsp;
+package eu.webeid.ocsp.protocol;
-import eu.webeid.security.exceptions.UserCertificateOCSPCheckFailedException;
+import eu.webeid.ocsp.DefaultOcspCertificateRevocationChecker;
+import eu.webeid.ocsp.exceptions.UserCertificateOCSPCheckFailedException;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.junit.jupiter.api.Test;
+import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
-import static eu.webeid.security.validator.AuthTokenValidatorBuilderTest.CONFIGURATION;
-import static eu.webeid.security.validator.ocsp.OcspResponseValidator.validateCertificateStatusUpdateTime;
+import static eu.webeid.ocsp.protocol.OcspResponseValidator.validateCertificateStatusUpdateTime;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
@@ -40,8 +41,9 @@
class OcspResponseValidatorTest {
- private static final Duration TIME_SKEW = CONFIGURATION.getAllowedOcspResponseTimeSkew();
- private static final Duration THIS_UPDATE_AGE = CONFIGURATION.getMaxOcspResponseThisUpdateAge();
+ private static final Duration TIME_SKEW = DefaultOcspCertificateRevocationChecker.DEFAULT_TIME_SKEW;
+ private static final Duration THIS_UPDATE_AGE = DefaultOcspCertificateRevocationChecker.DEFAULT_THIS_UPDATE_AGE;
+ private static final URI OCSP_URL = URI.create("https://example.org");
@Test
void whenThisAndNextUpdateWithinSkew_thenValidationSucceeds() {
@@ -51,7 +53,7 @@ void whenThisAndNextUpdateWithinSkew_thenValidationSucceeds() {
var nextUpdateWithinAgeLimit = Date.from(now.minus(THIS_UPDATE_AGE.minusSeconds(2)));
when(mockResponse.getThisUpdate()).thenReturn(thisUpdateWithinAgeLimit);
when(mockResponse.getNextUpdate()).thenReturn(nextUpdateWithinAgeLimit);
- assertThatCode(() -> validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE))
+ assertThatCode(() -> validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE, OCSP_URL))
.doesNotThrowAnyException();
}
@@ -65,7 +67,7 @@ void whenNextUpdateBeforeThisUpdate_thenThrows() {
when(mockResponse.getNextUpdate()).thenReturn(beforeThisUpdate);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE))
+ validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE, OCSP_URL))
.withMessageStartingWith("User certificate revocation check has failed: "
+ "Certificate status update time check failed: "
+ "nextUpdate '" + beforeThisUpdate.toInstant() + "' is before thisUpdate '" + thisUpdateWithinAgeLimit.toInstant() + "'");
@@ -79,7 +81,7 @@ void whenThisUpdateHalfHourBeforeNow_thenThrows() {
when(mockResponse.getThisUpdate()).thenReturn(halfHourBeforeNow);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE))
+ validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE, OCSP_URL))
.withMessageStartingWith("User certificate revocation check has failed: "
+ "Certificate status update time check failed: "
+ "thisUpdate '" + halfHourBeforeNow.toInstant() + "' is too old, minimum time allowed: ");
@@ -93,7 +95,7 @@ void whenThisUpdateHalfHourAfterNow_thenThrows() {
when(mockResponse.getThisUpdate()).thenReturn(halfHourAfterNow);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE))
+ validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE, OCSP_URL))
.withMessageStartingWith("User certificate revocation check has failed: "
+ "Certificate status update time check failed: "
+ "thisUpdate '" + halfHourAfterNow.toInstant() + "' is too far in the future, latest allowed: ");
@@ -109,7 +111,7 @@ void whenNextUpdateHalfHourBeforeNow_thenThrows() {
when(mockResponse.getNextUpdate()).thenReturn(halfHourBeforeNow);
assertThatExceptionOfType(UserCertificateOCSPCheckFailedException.class)
.isThrownBy(() ->
- validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE))
+ validateCertificateStatusUpdateTime(mockResponse, TIME_SKEW, THIS_UPDATE_AGE, OCSP_URL))
.withMessage("User certificate revocation check has failed: "
+ "Certificate status update time check failed: "
+ "nextUpdate '" + halfHourBeforeNow.toInstant() + "' is in the past");
diff --git a/src/test/java/eu/webeid/security/validator/ocsp/OcspUrlTest.java b/src/test/java/eu/webeid/ocsp/protocol/OcspUrlTest.java
similarity index 96%
rename from src/test/java/eu/webeid/security/validator/ocsp/OcspUrlTest.java
rename to src/test/java/eu/webeid/ocsp/protocol/OcspUrlTest.java
index 95b5759e..247c18a4 100644
--- a/src/test/java/eu/webeid/security/validator/ocsp/OcspUrlTest.java
+++ b/src/test/java/eu/webeid/ocsp/protocol/OcspUrlTest.java
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
-package eu.webeid.security.validator.ocsp;
+package eu.webeid.ocsp.protocol;
import org.junit.jupiter.api.Test;
@@ -30,7 +30,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static eu.webeid.security.validator.ocsp.OcspUrl.getOcspUri;
+import static eu.webeid.ocsp.protocol.OcspUrl.getOcspUri;
class OcspUrlTest {
diff --git a/src/test/java/eu/webeid/security/testutil/OcspServiceMaker.java b/src/test/java/eu/webeid/ocsp/service/OcspServiceMaker.java
similarity index 93%
rename from src/test/java/eu/webeid/security/testutil/OcspServiceMaker.java
rename to src/test/java/eu/webeid/ocsp/service/OcspServiceMaker.java
index 8f559c4b..9a99ca30 100644
--- a/src/test/java/eu/webeid/security/testutil/OcspServiceMaker.java
+++ b/src/test/java/eu/webeid/ocsp/service/OcspServiceMaker.java
@@ -20,14 +20,11 @@
* SOFTWARE.
*/
-package eu.webeid.security.testutil;
+package eu.webeid.ocsp.service;
import eu.webeid.security.certificate.CertificateValidator;
import eu.webeid.security.exceptions.JceException;
-import eu.webeid.security.exceptions.OCSPCertificateException;
-import eu.webeid.security.validator.ocsp.OcspServiceProvider;
-import eu.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
-import eu.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
+import eu.webeid.ocsp.exceptions.OCSPCertificateException;
import java.io.IOException;
import java.net.URI;
diff --git a/src/test/java/eu/webeid/security/validator/ocsp/OcspServiceProviderTest.java b/src/test/java/eu/webeid/ocsp/service/OcspServiceProviderTest.java
similarity index 72%
rename from src/test/java/eu/webeid/security/validator/ocsp/OcspServiceProviderTest.java
rename to src/test/java/eu/webeid/ocsp/service/OcspServiceProviderTest.java
index 00337fd9..123f996c 100644
--- a/src/test/java/eu/webeid/security/validator/ocsp/OcspServiceProviderTest.java
+++ b/src/test/java/eu/webeid/ocsp/service/OcspServiceProviderTest.java
@@ -20,20 +20,25 @@
* SOFTWARE.
*/
-package eu.webeid.security.validator.ocsp;
+package eu.webeid.ocsp.service;
import org.bouncycastle.cert.X509CertificateHolder;
import org.junit.jupiter.api.Test;
-import eu.webeid.security.exceptions.OCSPCertificateException;
-import eu.webeid.security.validator.ocsp.service.OcspService;
+import eu.webeid.ocsp.exceptions.OCSPCertificateException;
import java.net.URI;
import java.util.Date;
-import static org.assertj.core.api.Assertions.*;
-import static eu.webeid.security.testutil.Certificates.*;
-import static eu.webeid.security.testutil.OcspServiceMaker.getAiaOcspServiceProvider;
-import static eu.webeid.security.testutil.OcspServiceMaker.getDesignatedOcspServiceProvider;
+import static eu.webeid.ocsp.service.OcspServiceMaker.getAiaOcspServiceProvider;
+import static eu.webeid.ocsp.service.OcspServiceMaker.getDesignatedOcspServiceProvider;
+import static eu.webeid.security.testutil.Certificates.getJaakKristjanEsteid2018Cert;
+import static eu.webeid.security.testutil.Certificates.getMariliisEsteid2015Cert;
+import static eu.webeid.security.testutil.Certificates.getTestEsteid2015CA;
+import static eu.webeid.security.testutil.Certificates.getTestEsteid2018CA;
+import static eu.webeid.security.testutil.Certificates.getTestSkOcspResponder2020;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
class OcspServiceProviderTest {
@@ -83,3 +88,16 @@ void whenAiaOcspServiceConfigurationDoesNotHaveResponderCertTrustedCA_thenThrows
}
}
+
+// Old disabled example AuthTokenValidator test with designated OCSP check.
+//
+// @Test
+// @Disabled("A new designated test OCSP responder certificate was issued whose validity period no longer overlaps with the revoked certificate")
+// void whenCertificateIsRevoked_thenOcspCheckWithDesignatedOcspServiceFails() throws Exception {
+// mockDate("2020-01-01", mockedClock);
+// final AuthTokenValidator validatorWithOcspCheck = AuthTokenValidators.getAuthTokenValidatorWithDesignatedOcspCheck();
+// final WebEidAuthToken token = replaceTokenField(AUTH_TOKEN, "X5C", REVOKED_CERT);
+// assertThatThrownBy(() -> validatorWithOcspCheck
+// .validate(token, VALID_CHALLENGE_NONCE))
+// .isInstanceOf(UserCertificateRevokedException.class);
+// }
\ No newline at end of file
diff --git a/src/test/java/eu/webeid/security/testutil/AuthTokenValidators.java b/src/test/java/eu/webeid/security/testutil/AuthTokenValidators.java
index ec977e71..faa28060 100644
--- a/src/test/java/eu/webeid/security/testutil/AuthTokenValidators.java
+++ b/src/test/java/eu/webeid/security/testutil/AuthTokenValidators.java
@@ -24,19 +24,14 @@
import eu.webeid.security.certificate.CertificateLoader;
import eu.webeid.security.exceptions.JceException;
-import eu.webeid.security.exceptions.OCSPCertificateException;
import eu.webeid.security.validator.AuthTokenValidator;
import eu.webeid.security.validator.AuthTokenValidatorBuilder;
-import eu.webeid.security.validator.ocsp.OcspClient;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import java.io.IOException;
import java.net.URI;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
-import java.time.Duration;
-
-import static eu.webeid.security.testutil.OcspServiceMaker.getDesignatedOcspServiceConfiguration;
public final class AuthTokenValidators {
@@ -53,16 +48,7 @@ public static AuthTokenValidator getAuthTokenValidator(String url) throws Certif
public static AuthTokenValidator getAuthTokenValidator(String url, X509Certificate... certificates) throws JceException {
return getAuthTokenValidatorBuilder(url, certificates)
- // Assure that all builder methods are covered with tests.
- .withOcspRequestTimeout(Duration.ofSeconds(1))
- .withNonceDisabledOcspUrls(URI.create("http://example.org"))
- .withoutUserCertificateRevocationCheckWithOcsp()
- .build();
- }
-
- public static AuthTokenValidator getAuthTokenValidatorWithOverriddenOcspClient(OcspClient ocspClient) throws CertificateException, JceException, IOException {
- return getAuthTokenValidatorBuilder(TOKEN_ORIGIN_URL, getCACertificates())
- .withOcspClient(ocspClient)
+ .withoutUserCertificateRevocationCheck()
.build();
}
@@ -71,12 +57,6 @@ public static AuthTokenValidator getAuthTokenValidatorWithOcspCheck() throws Cer
.build();
}
- public static AuthTokenValidator getAuthTokenValidatorWithDesignatedOcspCheck() throws CertificateException, JceException, IOException, OCSPCertificateException {
- return getAuthTokenValidatorBuilder(TOKEN_ORIGIN_URL, getCACertificates())
- .withDesignatedOcspServiceConfiguration(getDesignatedOcspServiceConfiguration())
- .build();
- }
-
public static AuthTokenValidator getAuthTokenValidatorWithWrongTrustedCA() throws CertificateException, JceException, IOException {
return getAuthTokenValidator(TOKEN_ORIGIN_URL,
CertificateLoader.loadCertificatesFromResources("ESTEID2018.cer"));
@@ -90,7 +70,7 @@ public static AuthTokenValidator getAuthTokenValidatorWithJuly2024ExpiredUnrelat
public static AuthTokenValidator getAuthTokenValidatorWithDisallowedESTEIDPolicy() throws CertificateException, JceException, IOException {
return getAuthTokenValidatorBuilder(TOKEN_ORIGIN_URL, getCACertificates())
.withDisallowedCertificatePolicies(EST_IDEMIA_POLICY)
- .withoutUserCertificateRevocationCheckWithOcsp()
+ .withoutUserCertificateRevocationCheck()
.build();
}
diff --git a/src/test/java/eu/webeid/security/validator/AuthTokenCertificateTest.java b/src/test/java/eu/webeid/security/validator/AuthTokenCertificateTest.java
index 14ed0666..0ded31e7 100644
--- a/src/test/java/eu/webeid/security/validator/AuthTokenCertificateTest.java
+++ b/src/test/java/eu/webeid/security/validator/AuthTokenCertificateTest.java
@@ -32,7 +32,7 @@
import eu.webeid.security.exceptions.CertificateNotYetValidException;
import eu.webeid.security.exceptions.UserCertificateDisallowedPolicyException;
import eu.webeid.security.exceptions.UserCertificateMissingPurposeException;
-import eu.webeid.security.exceptions.UserCertificateRevokedException;
+import eu.webeid.ocsp.exceptions.UserCertificateRevokedException;
import eu.webeid.security.exceptions.UserCertificateWrongPurposeException;
import eu.webeid.security.testutil.AbstractTestWithValidator;
import eu.webeid.security.testutil.AuthTokenValidators;
@@ -281,17 +281,6 @@ void whenCertificateIsRevoked_thenOcspCheckFails() throws Exception {
.isInstanceOf(UserCertificateRevokedException.class);
}
- @Test
- @Disabled("A new designated test OCSP responder certificate was issued whose validity period no longer overlaps with the revoked certificate")
- void whenCertificateIsRevoked_thenOcspCheckWithDesignatedOcspServiceFails() throws Exception {
- mockDate("2020-01-01", mockedClock);
- final AuthTokenValidator validatorWithOcspCheck = AuthTokenValidators.getAuthTokenValidatorWithDesignatedOcspCheck();
- final WebEidAuthToken token = replaceTokenField(AUTH_TOKEN, "X5C", REVOKED_CERT);
- assertThatThrownBy(() -> validatorWithOcspCheck
- .validate(token, VALID_CHALLENGE_NONCE))
- .isInstanceOf(UserCertificateRevokedException.class);
- }
-
@Test
void whenCertificateCaIsNotPartOfTrustChain_thenValidationFails() throws Exception {
final AuthTokenValidator validatorWithWrongTrustedCA = AuthTokenValidators.getAuthTokenValidatorWithWrongTrustedCA();
diff --git a/src/test/java/eu/webeid/security/validator/AuthTokenSignatureTest.java b/src/test/java/eu/webeid/security/validator/AuthTokenSignatureTest.java
index 3f596858..70b9c89e 100644
--- a/src/test/java/eu/webeid/security/validator/AuthTokenSignatureTest.java
+++ b/src/test/java/eu/webeid/security/validator/AuthTokenSignatureTest.java
@@ -48,7 +48,7 @@ class AuthTokenSignatureTest extends AbstractTestWithValidator {
@Test
void whenValidTokenAndNonce_thenValidationSucceeds() throws Exception {
- final X509Certificate result = validator.validate(validAuthToken, VALID_CHALLENGE_NONCE);
+ final X509Certificate result = validator.validate(validAuthToken, VALID_CHALLENGE_NONCE).subjectCertificate();
assertThat(CertificateData.getSubjectCN(result).orElseThrow())
.isEqualTo("JÕEORG\\,JAAK-KRISTJAN\\,38001085718");
diff --git a/src/test/java/eu/webeid/security/validator/AuthTokenSignatureValidatorTest.java b/src/test/java/eu/webeid/security/validator/AuthTokenSignatureValidatorTest.java
index fc7edd0c..35ca01b3 100644
--- a/src/test/java/eu/webeid/security/validator/AuthTokenSignatureValidatorTest.java
+++ b/src/test/java/eu/webeid/security/validator/AuthTokenSignatureValidatorTest.java
@@ -51,10 +51,10 @@ void whenValidES384Signature_thenSucceeds() throws Exception {
new AuthTokenSignatureValidator(URI.create("https://ria.ee"));
final WebEidAuthToken authToken = OBJECT_READER.readValue(VALID_AUTH_TOKEN);
- final X509Certificate x509Certificate = CertificateLoader.decodeCertificateFromBase64(authToken.getUnverifiedCertificate());
+ final X509Certificate x509Certificate = CertificateLoader.decodeCertificateFromBase64(authToken.unverifiedCertificate());
assertThatCode(() -> signatureValidator
- .validate("ES384", authToken.getSignature(), x509Certificate.getPublicKey(), VALID_CHALLENGE_NONCE))
+ .validate("ES384", authToken.signature(), x509Certificate.getPublicKey(), VALID_CHALLENGE_NONCE))
.doesNotThrowAnyException();
}
@@ -64,10 +64,10 @@ void whenValidRS256Signature_thenSucceeds() throws Exception {
new AuthTokenSignatureValidator(URI.create("https://ria.ee"));
final WebEidAuthToken authToken = OBJECT_READER.readValue(VALID_RS256_AUTH_TOKEN);
- final X509Certificate x509Certificate = CertificateLoader.decodeCertificateFromBase64(authToken.getUnverifiedCertificate());
+ final X509Certificate x509Certificate = CertificateLoader.decodeCertificateFromBase64(authToken.unverifiedCertificate());
assertThatCode(() -> signatureValidator
- .validate("RS256", authToken.getSignature(), x509Certificate.getPublicKey(), VALID_CHALLENGE_NONCE))
+ .validate("RS256", authToken.signature(), x509Certificate.getPublicKey(), VALID_CHALLENGE_NONCE))
.doesNotThrowAnyException();
}
diff --git a/src/test/java/eu/webeid/security/validator/AuthTokenValidatorBuilderTest.java b/src/test/java/eu/webeid/security/validator/AuthTokenValidatorBuilderTest.java
index 8e58466f..46abda72 100644
--- a/src/test/java/eu/webeid/security/validator/AuthTokenValidatorBuilderTest.java
+++ b/src/test/java/eu/webeid/security/validator/AuthTokenValidatorBuilderTest.java
@@ -86,30 +86,6 @@ void testValidatorOriginNotValidSyntax() {
.hasMessageStartingWith("An URI syntax exception occurred");
}
- @Test
- void testInvalidOcspResponseTimeSkew() throws Exception {
- final AuthTokenValidatorBuilder builderWithInvalidOcspResponseTimeSkew = AuthTokenValidators.getDefaultAuthTokenValidatorBuilder()
- .withAllowedOcspResponseTimeSkew(Duration.ofMinutes(-1));
- assertThatThrownBy(builderWithInvalidOcspResponseTimeSkew::build)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageStartingWith("Allowed OCSP response time-skew must be greater than zero");
- }
-
- @Test
- void testInvalidMaxOcspResponseThisUpdateAge() throws Exception {
- final AuthTokenValidatorBuilder builderWithInvalidOcspResponseTimeSkew = AuthTokenValidators.getDefaultAuthTokenValidatorBuilder()
- .withMaxOcspResponseThisUpdateAge(Duration.ZERO);
- assertThatThrownBy(builderWithInvalidOcspResponseTimeSkew::build)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageStartingWith("Max OCSP response thisUpdate age must be greater than zero");
- }
+ // TODO: add tests for revocation config
- @Test
- void testInvalidOcspRequestTimeout() throws Exception {
- final AuthTokenValidatorBuilder builderWithInvalidOcspResponseTimeSkew = AuthTokenValidators.getDefaultAuthTokenValidatorBuilder()
- .withOcspRequestTimeout(Duration.ofMinutes(-1));
- assertThatThrownBy(builderWithInvalidOcspResponseTimeSkew::build)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageStartingWith("OCSP request timeout must be greater than zero");
- }
}