Skip to content

Commit 5156b31

Browse files
committed
Backport OCSP update time validation and thread-safety fixes from main
WE2-1132 Signed-off-by: Mart Somermaa <[email protected]>
1 parent 12336a4 commit 5156b31

13 files changed

+127
-165
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
1616
<java.version>1.8</java.version>
1717
<jjwt.version>0.11.5</jjwt.version>
18-
<jackson.version>2.13.4.2</jackson.version>
18+
<jackson.version>2.18.5</jackson.version>
1919
<slf4j.version>1.7.36</slf4j.version>
2020
<bouncycastle.version>1.70</bouncycastle.version>
2121
<okhttp.version>4.10.0</okhttp.version>

src/main/java/eu/webeid/security/util/DateAndTime.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@ public static void requirePositiveDuration(Duration duration, String fieldName)
4545

4646
public static class DefaultClock implements Clock {
4747

48-
public static final Clock INSTANCE = new DefaultClock();
48+
// Allows mocking of time-dependent behavior with Mockito.mockStatic() in tests.
49+
private static final Clock instance = new DefaultClock();
4950

51+
public static Clock getInstance() {
52+
return instance;
53+
}
54+
55+
@Override
5056
public Date now() {
5157
return new Date();
5258
}

src/main/java/eu/webeid/security/validator/AuthTokenValidationConfiguration.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838

3939
import static eu.webeid.security.util.Collections.newHashSet;
4040
import static eu.webeid.security.util.DateAndTime.requirePositiveDuration;
41-
import static eu.webeid.security.validator.ocsp.OcspUrl.AIA_ESTEID_2015;
4241

4342
/**
4443
* Stores configuration parameters for {@link AuthTokenValidatorImpl}.
@@ -49,6 +48,8 @@ public final class AuthTokenValidationConfiguration {
4948
private Collection<X509Certificate> trustedCACertificates = new HashSet<>();
5049
private boolean isUserCertificateRevocationCheckWithOcspEnabled = true;
5150
private Duration ocspRequestTimeout = Duration.ofSeconds(5);
51+
private Duration allowedOcspResponseTimeSkew = Duration.ofMinutes(15);
52+
private Duration maxOcspResponseThisUpdateAge = Duration.ofMinutes(2);
5253
private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
5354
// Don't allow Estonian Mobile-ID policy by default.
5455
private Collection<ASN1ObjectIdentifier> disallowedSubjectCertificatePolicies = newHashSet(
@@ -57,8 +58,7 @@ public final class AuthTokenValidationConfiguration {
5758
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
5859
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY
5960
);
60-
// Disable OCSP nonce extension for EstEID 2015 cards by default.
61-
private Collection<URI> nonceDisabledOcspUrls = newHashSet(AIA_ESTEID_2015);
61+
private Collection<URI> nonceDisabledOcspUrls = new HashSet<>();
6262

6363
AuthTokenValidationConfiguration() {
6464
}
@@ -68,6 +68,8 @@ private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other)
6868
this.trustedCACertificates = Collections.unmodifiableSet(new HashSet<>(other.trustedCACertificates));
6969
this.isUserCertificateRevocationCheckWithOcspEnabled = other.isUserCertificateRevocationCheckWithOcspEnabled;
7070
this.ocspRequestTimeout = other.ocspRequestTimeout;
71+
this.allowedOcspResponseTimeSkew = other.allowedOcspResponseTimeSkew;
72+
this.maxOcspResponseThisUpdateAge = other.maxOcspResponseThisUpdateAge;
7173
this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
7274
this.disallowedSubjectCertificatePolicies = Collections.unmodifiableSet(new HashSet<>(other.disallowedSubjectCertificatePolicies));
7375
this.nonceDisabledOcspUrls = Collections.unmodifiableSet(new HashSet<>(other.nonceDisabledOcspUrls));
@@ -101,6 +103,22 @@ void setOcspRequestTimeout(Duration ocspRequestTimeout) {
101103
this.ocspRequestTimeout = ocspRequestTimeout;
102104
}
103105

106+
public Duration getAllowedOcspResponseTimeSkew() {
107+
return allowedOcspResponseTimeSkew;
108+
}
109+
110+
public void setAllowedOcspResponseTimeSkew(Duration allowedOcspResponseTimeSkew) {
111+
this.allowedOcspResponseTimeSkew = allowedOcspResponseTimeSkew;
112+
}
113+
114+
public Duration getMaxOcspResponseThisUpdateAge() {
115+
return maxOcspResponseThisUpdateAge;
116+
}
117+
118+
public void setMaxOcspResponseThisUpdateAge(Duration maxOcspResponseThisUpdateAge) {
119+
this.maxOcspResponseThisUpdateAge = maxOcspResponseThisUpdateAge;
120+
}
121+
104122
public DesignatedOcspServiceConfiguration getDesignatedOcspServiceConfiguration() {
105123
return designatedOcspServiceConfiguration;
106124
}
@@ -130,6 +148,8 @@ void validate() {
130148
throw new IllegalArgumentException("At least one trusted certificate authority must be provided");
131149
}
132150
requirePositiveDuration(ocspRequestTimeout, "OCSP request timeout");
151+
requirePositiveDuration(allowedOcspResponseTimeSkew, "Allowed OCSP response time-skew");
152+
requirePositiveDuration(maxOcspResponseThisUpdateAge, "Max OCSP response thisUpdate age");
133153
}
134154

135155
AuthTokenValidationConfiguration copy() {

src/main/java/eu/webeid/security/validator/AuthTokenValidatorImpl.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
package eu.webeid.security.validator;
2424

2525
import com.fasterxml.jackson.databind.ObjectMapper;
26+
import com.fasterxml.jackson.databind.ObjectReader;
2627
import eu.webeid.security.authtoken.WebEidAuthToken;
2728
import eu.webeid.security.certificate.CertificateLoader;
2829
import eu.webeid.security.certificate.CertificateValidator;
2930
import eu.webeid.security.exceptions.JceException;
3031
import eu.webeid.security.exceptions.AuthTokenParseException;
3132
import eu.webeid.security.exceptions.AuthTokenException;
32-
import eu.webeid.security.validator.certvalidators.SubjectCertificateExpiryValidator;
3333
import eu.webeid.security.validator.certvalidators.SubjectCertificateNotRevokedValidator;
3434
import eu.webeid.security.validator.certvalidators.SubjectCertificatePolicyValidator;
3535
import eu.webeid.security.validator.certvalidators.SubjectCertificatePurposeValidator;
@@ -57,7 +57,7 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
5757
private static final int TOKEN_MAX_LENGTH = 10000;
5858
private static final Logger LOG = LoggerFactory.getLogger(AuthTokenValidatorImpl.class);
5959

60-
private static final ObjectMapper objectMapper = new ObjectMapper();
60+
private static final ObjectReader OBJECT_READER = new ObjectMapper().readerFor(WebEidAuthToken.class);
6161

6262
private final AuthTokenValidationConfiguration configuration;
6363
private final SubjectCertificateValidatorBatch simpleSubjectCertificateValidators;
@@ -84,7 +84,6 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
8484
trustedCACertificateCertStore = CertificateValidator.buildCertStoreFromCertificates(configuration.getTrustedCACertificates());
8585

8686
simpleSubjectCertificateValidators = SubjectCertificateValidatorBatch.createFrom(
87-
new SubjectCertificateExpiryValidator(trustedCACertificateAnchors)::validateCertificateExpiry,
8887
SubjectCertificatePurposeValidator::validateCertificatePurpose,
8988
new SubjectCertificatePolicyValidator(configuration.getDisallowedSubjectCertificatePolicies())::validateCertificatePolicies
9089
);
@@ -109,7 +108,7 @@ public WebEidAuthToken parse(String authToken) throws AuthTokenException {
109108
validateTokenLength(authToken);
110109
return parseToken(authToken);
111110
} catch (Exception e) {
112-
// Generally "log and rethrow" is an anti-pattern, but it fits with the surrounding logging style.
111+
// Generally "log and rethrow" is an antipattern, but it fits with the surrounding logging style.
113112
LOG.warn("Token parsing was interrupted:", e);
114113
throw e;
115114
}
@@ -121,7 +120,7 @@ public X509Certificate validate(WebEidAuthToken authToken, String currentChallen
121120
LOG.info("Starting token validation");
122121
return validateToken(authToken, currentChallengeNonce);
123122
} catch (Exception e) {
124-
// Generally "log and rethrow" is an anti-pattern, but it fits with the surrounding logging style.
123+
// Generally "log and rethrow" is an antipattern, but it fits with the surrounding logging style.
125124
LOG.warn("Token validation was interrupted:", e);
126125
throw e;
127126
}
@@ -138,7 +137,7 @@ private void validateTokenLength(String authToken) throws AuthTokenParseExceptio
138137

139138
private WebEidAuthToken parseToken(String authToken) throws AuthTokenParseException {
140139
try {
141-
final WebEidAuthToken token = objectMapper.readValue(authToken, WebEidAuthToken.class);
140+
final WebEidAuthToken token = OBJECT_READER.readValue(authToken);
142141
if (token == null) {
143142
throw new AuthTokenParseException("Web eID authentication token is null");
144143
}
@@ -185,7 +184,11 @@ private SubjectCertificateValidatorBatch getCertTrustValidators() {
185184
return SubjectCertificateValidatorBatch.createFrom(
186185
certTrustedValidator::validateCertificateTrusted
187186
).addOptional(configuration.isUserCertificateRevocationCheckWithOcspEnabled(),
188-
new SubjectCertificateNotRevokedValidator(certTrustedValidator, ocspClient, ocspServiceProvider)::validateCertificateNotRevoked
187+
new SubjectCertificateNotRevokedValidator(certTrustedValidator,
188+
ocspClient, ocspServiceProvider,
189+
configuration.getAllowedOcspResponseTimeSkew(),
190+
configuration.getMaxOcspResponseThisUpdateAge()
191+
)::validateCertificateNotRevoked
189192
);
190193
}
191194

src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateExpiryValidator.java

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateNotRevokedValidator.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424

2525
import eu.webeid.security.exceptions.AuthTokenException;
2626
import eu.webeid.security.exceptions.UserCertificateOCSPCheckFailedException;
27-
import eu.webeid.security.validator.ocsp.*;
27+
import eu.webeid.security.util.DateAndTime;
28+
import eu.webeid.security.validator.ocsp.DigestCalculatorImpl;
29+
import eu.webeid.security.validator.ocsp.OcspClient;
30+
import eu.webeid.security.validator.ocsp.OcspRequestBuilder;
31+
import eu.webeid.security.validator.ocsp.OcspResponseValidator;
32+
import eu.webeid.security.validator.ocsp.OcspServiceProvider;
2833
import eu.webeid.security.validator.ocsp.service.OcspService;
2934
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
3035
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
@@ -41,39 +46,41 @@
4146
import org.bouncycastle.operator.OperatorCreationException;
4247
import org.slf4j.Logger;
4348
import org.slf4j.LoggerFactory;
44-
import eu.webeid.security.validator.ocsp.Digester;
45-
import eu.webeid.security.validator.ocsp.OcspClient;
46-
import eu.webeid.security.validator.ocsp.OcspRequestBuilder;
47-
import eu.webeid.security.validator.ocsp.OcspServiceProvider;
4849

4950
import java.io.IOException;
5051
import java.math.BigInteger;
5152
import java.security.Security;
5253
import java.security.cert.CertificateEncodingException;
5354
import java.security.cert.CertificateException;
5455
import java.security.cert.X509Certificate;
56+
import java.time.Duration;
5557
import java.util.Date;
5658
import java.util.Objects;
5759

5860
public final class SubjectCertificateNotRevokedValidator {
5961

6062
private static final Logger LOG = LoggerFactory.getLogger(SubjectCertificateNotRevokedValidator.class);
61-
private static final DigestCalculator DIGEST_CALCULATOR = Digester.sha1();
6263

6364
private final SubjectCertificateTrustedValidator trustValidator;
6465
private final OcspClient ocspClient;
6566
private final OcspServiceProvider ocspServiceProvider;
67+
private final Duration allowedOcspResponseTimeSkew;
68+
private final Duration maxOcspResponseThisUpdateAge;
6669

6770
static {
6871
Security.addProvider(new BouncyCastleProvider());
6972
}
7073

7174
public SubjectCertificateNotRevokedValidator(SubjectCertificateTrustedValidator trustValidator,
7275
OcspClient ocspClient,
73-
OcspServiceProvider ocspServiceProvider) {
76+
OcspServiceProvider ocspServiceProvider,
77+
Duration allowedOcspResponseTimeSkew,
78+
Duration maxOcspResponseThisUpdateAge) {
7479
this.trustValidator = trustValidator;
7580
this.ocspClient = ocspClient;
7681
this.ocspServiceProvider = ocspServiceProvider;
82+
this.allowedOcspResponseTimeSkew = allowedOcspResponseTimeSkew;
83+
this.maxOcspResponseThisUpdateAge = maxOcspResponseThisUpdateAge;
7784
}
7885

7986
/**
@@ -86,10 +93,6 @@ public void validateCertificateNotRevoked(X509Certificate subjectCertificate) th
8693
try {
8794
OcspService ocspService = ocspServiceProvider.getService(subjectCertificate);
8895

89-
if (!ocspService.doesSupportNonce()) {
90-
LOG.debug("Disabling OCSP nonce extension");
91-
}
92-
9396
final CertificateID certificateId = getCertificateId(subjectCertificate,
9497
Objects.requireNonNull(trustValidator.getSubjectCertificateIssuerCertificate()));
9598

@@ -98,6 +101,10 @@ public void validateCertificateNotRevoked(X509Certificate subjectCertificate) th
98101
.enableOcspNonce(ocspService.doesSupportNonce())
99102
.build();
100103

104+
if (!ocspService.doesSupportNonce()) {
105+
LOG.debug("Disabling OCSP nonce extension");
106+
}
107+
101108
LOG.debug("Sending OCSP request");
102109
final OCSPResp response = Objects.requireNonNull(ocspClient.request(ocspService.getAccessLocation(), request));
103110
if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
@@ -156,8 +163,9 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
156163
// 4. The signer is currently authorized to provide a response for the
157164
// certificate in question.
158165

159-
final Date producedAt = basicResponse.getProducedAt();
160-
ocspService.validateResponderCertificate(responderCert, producedAt);
166+
// Use the clock instance so that the date can be mocked in tests.
167+
final Date now = DateAndTime.DefaultClock.getInstance().now();
168+
ocspService.validateResponderCertificate(responderCert, now);
161169

162170
// 5. The time at which the status being indicated is known to be
163171
// correct (thisUpdate) is sufficiently recent.
@@ -166,7 +174,7 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
166174
// be available about the status of the certificate (nextUpdate) is
167175
// greater than the current time.
168176

169-
OcspResponseValidator.validateCertificateStatusUpdateTime(certStatusResponse, producedAt);
177+
OcspResponseValidator.validateCertificateStatusUpdateTime(certStatusResponse, allowedOcspResponseTimeSkew, maxOcspResponseThisUpdateAge);
170178

171179
// Now we can accept the signed response as valid and validate the certificate status.
172180
OcspResponseValidator.validateSubjectCertificateStatus(certStatusResponse);
@@ -188,7 +196,8 @@ private static void checkNonce(OCSPReq request, BasicOCSPResp response) throws U
188196

189197
private static CertificateID getCertificateId(X509Certificate subjectCertificate, X509Certificate issuerCertificate) throws CertificateEncodingException, IOException, OCSPException {
190198
final BigInteger serial = subjectCertificate.getSerialNumber();
191-
return new CertificateID(DIGEST_CALCULATOR,
199+
final DigestCalculator digestCalculator = DigestCalculatorImpl.sha1();
200+
return new CertificateID(digestCalculator,
192201
new X509CertificateHolder(issuerCertificate.getEncoded()), serial);
193202
}
194203

0 commit comments

Comments
 (0)