Skip to content

Commit f6b2118

Browse files
committed
feat(OCSP): implement configurable OCSP service URL, verify OCSP responder certificate and response signature
WE2-432 Signed-off-by: Mart Somermaa <mrts@users.noreply.github.com>
1 parent 9275777 commit f6b2118

18 files changed

+558
-107
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ import java.security.cert.X509Certificate;
143143
...
144144
```
145145

146+
## 5. Add trusted OCSP responder certificates
147+
148+
- AIA
149+
- Designated
150+
146151
## 5. Configure the authentication token validator
147152

148153
Once the prerequisites have been met, the authentication token validator itself can be configured.
@@ -235,6 +240,8 @@ try {
235240
- [Nonce generation](#nonce-generation)
236241
- [Basic usage](#basic-usage-1)
237242
- [Extended configuration](#extended-configuration-1)
243+
- [Frequently asked questions](#frequently-asked-questions)
244+
- [How can I find the AIA OCSP service URLs?](#how-can-i-find-the-aia-ocsp-service-urls)
238245
239246
# Introduction
240247
@@ -356,3 +363,14 @@ NonceGenerator generator = new NonceGeneratorBuilder()
356363
.withSecureRandom(customSecureRandom)
357364
.build();
358365
```
366+
367+
## Frequently asked questions
368+
369+
### How can I find the AIA OCSP service URLs?
370+
371+
You can find the AIA OCSP service URLs from the electronic ID certificate profile documents, in the section that describes certificate extensions.
372+
The AIA OCSP extension OID is 1.3.6.1.5.5.7.48.1.
373+
374+
For example, the EstEID AIA URLs are specified in the documents
375+
[*Certificate, CRL and OCSP Profile for identification documents of the Republic of Estonia*](https://www.skidsolutions.eu/upload/files/SK-CPR-ESTEID-EN-v8_4-20200630.pdf) and
376+
[*Certificate, CRL and OCSP Profile for ID-1 Format Identity Documents Issued by the Republic of Estonia*](https://www.skidsolutions.eu/upload/files/SK-CPR-ESTEID2018-EN-v1_2_20200630.pdf).

src/main/java/org/webeid/security/util/OcspUrls.java

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

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

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import com.google.common.collect.Sets;
2626
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
27+
import org.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
28+
import org.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
2729
import org.webeid.security.validator.validators.OriginValidator;
2830

2931
import javax.cache.Cache;
@@ -36,8 +38,10 @@
3638
import java.util.Objects;
3739

3840
import static org.webeid.security.nonce.NonceGeneratorBuilder.requirePositiveDuration;
39-
import static org.webeid.security.util.OcspUrls.ESTEID_2015;
40-
import static org.webeid.security.util.SubjectCertificatePolicies.*;
41+
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY;
42+
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1;
43+
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V2;
44+
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V3;
4145

4246
/**
4347
* Stores configuration parameters for {@link AuthTokenValidatorImpl}.
@@ -50,6 +54,8 @@ final class AuthTokenValidationConfiguration {
5054
private boolean isUserCertificateRevocationCheckWithOcspEnabled = true;
5155
private Duration ocspRequestTimeout = Duration.ofSeconds(5);
5256
private Duration allowedClientClockSkew = Duration.ofMinutes(3);
57+
private AiaOcspServiceConfiguration aiaOcspServiceConfiguration = new AiaOcspServiceConfiguration();
58+
private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
5359
private boolean isSiteCertificateFingerprintValidationEnabled = false;
5460
private String siteCertificateSha256Fingerprint;
5561
// Don't allow Estonian Mobile-ID policy by default.
@@ -59,8 +65,6 @@ final class AuthTokenValidationConfiguration {
5965
ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
6066
ESTEID_SK_2015_MOBILE_ID_POLICY
6167
);
62-
// Disable OCSP nonce extension for EstEID 2015 cards by default.
63-
private Collection<URI> nonceDisabledOcspUrls = Sets.newHashSet(ESTEID_2015);
6468

6569
AuthTokenValidationConfiguration() {
6670
}
@@ -72,10 +76,11 @@ private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other)
7276
this.isUserCertificateRevocationCheckWithOcspEnabled = other.isUserCertificateRevocationCheckWithOcspEnabled;
7377
this.ocspRequestTimeout = other.ocspRequestTimeout;
7478
this.allowedClientClockSkew = other.allowedClientClockSkew;
79+
this.aiaOcspServiceConfiguration = other.aiaOcspServiceConfiguration;
80+
this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
7581
this.isSiteCertificateFingerprintValidationEnabled = other.isSiteCertificateFingerprintValidationEnabled;
7682
this.siteCertificateSha256Fingerprint = other.siteCertificateSha256Fingerprint;
7783
this.disallowedSubjectCertificatePolicies = new HashSet<>(other.disallowedSubjectCertificatePolicies);
78-
this.nonceDisabledOcspUrls = new HashSet<>(other.nonceDisabledOcspUrls);
7984
}
8085

8186
void setSiteOrigin(URI siteOrigin) {
@@ -122,6 +127,22 @@ Duration getAllowedClientClockSkew() {
122127
return allowedClientClockSkew;
123128
}
124129

130+
public AiaOcspServiceConfiguration getAiaOcspServiceConfiguration() {
131+
return aiaOcspServiceConfiguration;
132+
}
133+
134+
public void setAiaOcspServiceConfiguration(AiaOcspServiceConfiguration aiaOcspServiceConfiguration) {
135+
this.aiaOcspServiceConfiguration = aiaOcspServiceConfiguration;
136+
}
137+
138+
public DesignatedOcspServiceConfiguration getDesignatedOcspServiceConfiguration() {
139+
return designatedOcspServiceConfiguration;
140+
}
141+
142+
public void setDesignatedOcspServiceConfiguration(DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration) {
143+
this.designatedOcspServiceConfiguration = designatedOcspServiceConfiguration;
144+
}
145+
125146
boolean isSiteCertificateFingerprintValidationEnabled() {
126147
return isSiteCertificateFingerprintValidationEnabled;
127148
}
@@ -139,10 +160,6 @@ public Collection<ASN1ObjectIdentifier> getDisallowedSubjectCertificatePolicies(
139160
return disallowedSubjectCertificatePolicies;
140161
}
141162

142-
public Collection<URI> getNonceDisabledOcspUrls() {
143-
return nonceDisabledOcspUrls;
144-
}
145-
146163
/**
147164
* Checks that the configuration parameters are valid.
148165
*
@@ -156,6 +173,13 @@ void validate() {
156173
if (trustedCACertificates.isEmpty()) {
157174
throw new IllegalArgumentException("At least one trusted certificate authority must be provided");
158175
}
176+
if (aiaOcspServiceConfiguration == null && designatedOcspServiceConfiguration == null) {
177+
throw new IllegalArgumentException("Either AIA or designated OCSP service configuration must be provided");
178+
}
179+
if (aiaOcspServiceConfiguration != null && designatedOcspServiceConfiguration != null) {
180+
throw new IllegalArgumentException("AIA and designated OCSP service configuration cannot provided together, " +
181+
"please provide either one or the other");
182+
}
159183
requirePositiveDuration(ocspRequestTimeout, "OCSP request timeout");
160184
requirePositiveDuration(allowedClientClockSkew, "Allowed client clock skew");
161185
if (isSiteCertificateFingerprintValidationEnabled) {

src/main/java/org/webeid/security/validator/AuthTokenValidatorBuilder.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import org.slf4j.Logger;
2727
import org.slf4j.LoggerFactory;
2828
import org.webeid.security.exceptions.JceException;
29+
import org.webeid.security.validator.ocsp.service.AiaOcspService;
30+
import org.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
31+
import org.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
2932

3033
import javax.cache.Cache;
3134
import java.net.URI;
@@ -84,7 +87,10 @@ public AuthTokenValidatorBuilder withNonceCache(Cache<String, ZonedDateTime> cac
8487
*/
8588
public AuthTokenValidatorBuilder withTrustedCertificateAuthorities(X509Certificate... certificates) {
8689
Collections.addAll(configuration.getTrustedCACertificates(), certificates);
87-
LOG.debug("Trusted intermediate certificate authorities set to {}", configuration.getTrustedCACertificates());
90+
if (LOG.isDebugEnabled()) {
91+
LOG.debug("Trusted intermediate certificate authorities set to {}",
92+
configuration.getTrustedCACertificates().stream().map(X509Certificate::getSubjectDN));
93+
}
8894
return this;
8995
}
9096

@@ -125,20 +131,19 @@ public AuthTokenValidatorBuilder withoutUserCertificateRevocationCheckWithOcsp()
125131
*/
126132
public AuthTokenValidatorBuilder withOcspRequestTimeout(Duration ocspRequestTimeout) {
127133
configuration.setOcspRequestTimeout(ocspRequestTimeout);
128-
LOG.debug("OCSP request timeout set to {}.", ocspRequestTimeout);
134+
LOG.debug("OCSP request timeout set to {}", ocspRequestTimeout);
129135
return this;
130136
}
131137

132-
/**
133-
* Adds the given URLs to the list of OCSP URLs for which the nonce protocol extension will be disabled.
134-
* The OCSP URL is extracted from the user certificate and some OCSP services don't support the nonce extension.
135-
*
136-
* @param urls OCSP URLs for which the nonce protocol extension will be disabled
137-
* @return the builder instance for method chaining
138-
*/
139-
public AuthTokenValidatorBuilder withNonceDisabledOcspUrls(URI... urls) {
140-
Collections.addAll(configuration.getNonceDisabledOcspUrls(), urls);
141-
LOG.debug("OCSP URLs for which the nonce protocol extension is disabled set to {}", configuration.getNonceDisabledOcspUrls());
138+
public AuthTokenValidatorBuilder withAiaOcspServiceConfiguration(AiaOcspServiceConfiguration serviceConfiguration) {
139+
configuration.setAiaOcspServiceConfiguration(serviceConfiguration);
140+
LOG.debug("Using AIA OCSP service configuration");
141+
return this;
142+
}
143+
144+
public AuthTokenValidatorBuilder withDesignatedOcspServiceConfiguration(DesignatedOcspServiceConfiguration serviceConfiguration) {
145+
configuration.setDesignatedOcspServiceConfiguration(serviceConfiguration);
146+
LOG.debug("Using designated OCSP service configuration");
142147
return this;
143148
}
144149

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@
2929
import org.webeid.security.exceptions.JceException;
3030
import org.webeid.security.exceptions.TokenParseException;
3131
import org.webeid.security.exceptions.TokenValidationException;
32-
import org.webeid.security.validator.validators.*;
32+
import org.webeid.security.validator.ocsp.OcspServiceProvider;
33+
import org.webeid.security.validator.validators.FunctionalSubjectCertificateValidators;
34+
import org.webeid.security.validator.validators.NonceValidator;
35+
import org.webeid.security.validator.validators.OriginValidator;
36+
import org.webeid.security.validator.validators.SiteCertificateFingerprintValidator;
37+
import org.webeid.security.validator.validators.SubjectCertificateNotRevokedValidator;
38+
import org.webeid.security.validator.validators.SubjectCertificatePolicyValidator;
39+
import org.webeid.security.validator.validators.SubjectCertificateTrustedValidator;
40+
import org.webeid.security.validator.validators.ValidatorBatch;
3341

3442
import java.security.GeneralSecurityException;
3543
import java.security.cert.CertStore;
@@ -60,6 +68,7 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
6068
private final ValidatorBatch tokenBodyValidators;
6169
private final Set<TrustAnchor> trustedCACertificateAnchors;
6270
private final CertStore trustedCACertificateCertStore;
71+
private final OcspServiceProvider ocspServiceProvider;
6372

6473
/**
6574
* @param configuration configuration parameters for the token validator
@@ -103,6 +112,9 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
103112
} catch (GeneralSecurityException e) {
104113
throw new JceException(e);
105114
}
115+
ocspServiceProvider = configuration.getAiaOcspServiceConfiguration() != null ?
116+
new OcspServiceProvider(configuration.getAiaOcspServiceConfiguration()) :
117+
new OcspServiceProvider(configuration.getDesignatedOcspServiceConfiguration());
106118
}
107119

108120
@Override
@@ -153,7 +165,7 @@ private ValidatorBatch getCertTrustValidators() {
153165
return ValidatorBatch.createFrom(
154166
certTrustedValidator::validateCertificateTrusted
155167
).addOptional(configuration.isUserCertificateRevocationCheckWithOcspEnabled(),
156-
new SubjectCertificateNotRevokedValidator(certTrustedValidator, httpClientSupplier.get(), configuration.getNonceDisabledOcspUrls())::validateCertificateNotRevoked
168+
new SubjectCertificateNotRevokedValidator(certTrustedValidator, httpClientSupplier.get(), ocspServiceProvider)::validateCertificateNotRevoked
157169
);
158170
}
159171
}

src/main/java/org/webeid/security/validator/ocsp/OcspRequestBuilder.java

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -66,29 +66,11 @@ public final class OcspRequestBuilder {
6666

6767
private static final SecureRandom GENERATOR = new SecureRandom();
6868

69-
private SecureRandom randomGenerator = GENERATOR;
70-
private DigestCalculator digestCalculator = Digester.sha1();
71-
private X509Certificate subjectCertificate;
72-
private X509Certificate issuerCertificate;
7369
private boolean ocspNonceEnabled = true;
70+
private CertificateID certificateId;
7471

75-
public OcspRequestBuilder generator(SecureRandom generator) {
76-
this.randomGenerator = generator;
77-
return this;
78-
}
79-
80-
public OcspRequestBuilder calculator(DigestCalculator calculator) {
81-
this.digestCalculator = calculator;
82-
return this;
83-
}
84-
85-
public OcspRequestBuilder certificate(X509Certificate certificate) {
86-
this.subjectCertificate = certificate;
87-
return this;
88-
}
89-
90-
public OcspRequestBuilder issuer(X509Certificate issuer) {
91-
this.issuerCertificate = issuer;
72+
public OcspRequestBuilder withCertificateId(CertificateID certificateId) {
73+
this.certificateId = certificateId;
9274
return this;
9375
}
9476

@@ -98,21 +80,12 @@ public OcspRequestBuilder enableOcspNonce(boolean ocspNonceEnabled) {
9880
}
9981

10082
/**
101-
* ATTENTION: The returned {@link OCSPReq} is not re-usable/cacheable! It contains a one-time nonce
102-
* and CA's will (should) reject subsequent requests that have the same nonce value.
83+
* The returned {@link OCSPReq} is not re-usable/cacheable. It contains a one-time nonce
84+
* and responders will reject subsequent requests that have the same nonce value.
10385
*/
10486
public OCSPReq build() throws OCSPException, IOException, CertificateEncodingException {
105-
final DigestCalculator calculator = Objects.requireNonNull(this.digestCalculator, "digestCalculator");
106-
final X509Certificate certificate = Objects.requireNonNull(this.subjectCertificate, "subjectCertificate");
107-
final X509Certificate issuer = Objects.requireNonNull(this.issuerCertificate, "issuerCertificate");
108-
109-
final BigInteger serial = certificate.getSerialNumber();
110-
111-
final CertificateID certId = new CertificateID(calculator,
112-
new X509CertificateHolder(issuer.getEncoded()), serial);
113-
11487
final OCSPReqBuilder builder = new OCSPReqBuilder();
115-
builder.addRequest(certId);
88+
builder.addRequest(Objects.requireNonNull(certificateId, "certificateId"));
11689

11790
if (ocspNonceEnabled) {
11891
addNonce(builder);
@@ -122,16 +95,13 @@ public OCSPReq build() throws OCSPException, IOException, CertificateEncodingExc
12295
}
12396

12497
private void addNonce(OCSPReqBuilder builder) {
125-
final SecureRandom generator = Objects.requireNonNull(this.randomGenerator, "randomGenerator");
126-
12798
final byte[] nonce = new byte[8];
128-
generator.nextBytes(nonce);
99+
GENERATOR.nextBytes(nonce);
129100

130101
final Extension[] extensions = new Extension[]{
131102
new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
132103
new DEROctetString(nonce))
133104
};
134-
135105
builder.setRequestExtensions(new Extensions(extensions));
136106
}
137107

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.webeid.security.validator.ocsp;
2+
3+
import org.webeid.security.exceptions.UserCertificateRevocationCheckFailedException;
4+
import org.webeid.security.validator.ocsp.service.AiaOcspService;
5+
import org.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
6+
import org.webeid.security.validator.ocsp.service.DesignatedOcspService;
7+
import org.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
8+
import org.webeid.security.validator.ocsp.service.OcspService;
9+
10+
import java.io.IOException;
11+
import java.security.cert.X509Certificate;
12+
13+
public class OcspServiceProvider {
14+
15+
private final DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
16+
private final AiaOcspServiceConfiguration aiaOcspServiceConfiguration;
17+
18+
public OcspServiceProvider(DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration) {
19+
this.designatedOcspServiceConfiguration = designatedOcspServiceConfiguration;
20+
this.aiaOcspServiceConfiguration = null;
21+
}
22+
23+
public OcspServiceProvider(AiaOcspServiceConfiguration aiaOcspServiceConfiguration) {
24+
this.aiaOcspServiceConfiguration = aiaOcspServiceConfiguration;
25+
this.designatedOcspServiceConfiguration = null;
26+
}
27+
28+
public OcspService getService(X509Certificate certificate) throws UserCertificateRevocationCheckFailedException, IOException {
29+
if (designatedOcspServiceConfiguration != null) {
30+
return new DesignatedOcspService(designatedOcspServiceConfiguration);
31+
} else {
32+
return new AiaOcspService(aiaOcspServiceConfiguration, certificate);
33+
}
34+
}
35+
36+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.webeid.security.validator.ocsp.service;
2+
3+
import java.net.URI;
4+
import java.security.cert.X509Certificate;
5+
6+
public class AiaOcspResponderConfiguration {
7+
8+
private final URI responderUrl;
9+
private final X509Certificate responderTrustedCACertificate;
10+
private final boolean doesSupportNonce;
11+
12+
public AiaOcspResponderConfiguration(URI responderUrl, X509Certificate responderTrustedCACertificate, boolean doesSupportNonce) {
13+
this.responderUrl = responderUrl;
14+
this.responderTrustedCACertificate = responderTrustedCACertificate;
15+
this.doesSupportNonce = doesSupportNonce;
16+
}
17+
18+
public AiaOcspResponderConfiguration(URI responderUrl, X509Certificate responderTrustedCACertificate) {
19+
this(responderUrl, responderTrustedCACertificate, true);
20+
}
21+
22+
public boolean doesSupportNonce() {
23+
return doesSupportNonce;
24+
}
25+
26+
public X509Certificate getResponderTrustedCACertificate() {
27+
return responderTrustedCACertificate;
28+
}
29+
30+
public URI getResponderUrl() {
31+
return responderUrl;
32+
}
33+
}

0 commit comments

Comments
 (0)