Skip to content

Commit 3738cd0

Browse files
committed
refactor(OCSP): simplify AIA OCSP configuration, use single truststore for subject and OCSP responder certificates
Signed-off-by: Mart Somermaa <mrts@users.noreply.github.com>
1 parent c360a3c commit 3738cd0

19 files changed

+375
-224
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ import java.security.cert.X509Certificate;
145145

146146
## 5. Add trusted OCSP responder certificates
147147

148+
FIXME: describe the fallback scheme. As everything works out of the box, move below under andvanced config.
148149
- AIA
149150
- Designated
150151

src/main/java/org/webeid/security/certificate/CertificateData.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,49 @@
3232
import java.security.cert.CertificateEncodingException;
3333
import java.security.cert.X509Certificate;
3434
import java.util.Arrays;
35+
import java.util.Optional;
3536
import java.util.stream.Collectors;
3637

3738
public final class CertificateData {
3839

3940
public static String getSubjectCN(X509Certificate certificate) throws CertificateEncodingException {
40-
return getField(certificate, BCStyle.CN);
41+
return getSubjectField(certificate, BCStyle.CN);
42+
}
43+
44+
public static Optional<String> getSubjectCNOptional(X509Certificate certificate) {
45+
try {
46+
return Optional.of(getSubjectField(certificate, BCStyle.CN));
47+
} catch (CertificateEncodingException e) {
48+
return Optional.empty();
49+
}
4150
}
4251

4352
public static String getSubjectSurname(X509Certificate certificate) throws CertificateEncodingException {
44-
return getField(certificate, BCStyle.SURNAME);
53+
return getSubjectField(certificate, BCStyle.SURNAME);
4554
}
4655

4756
public static String getSubjectGivenName(X509Certificate certificate) throws CertificateEncodingException {
48-
return getField(certificate, BCStyle.GIVENNAME);
57+
return getSubjectField(certificate, BCStyle.GIVENNAME);
4958
}
5059

5160
public static String getSubjectIdCode(X509Certificate certificate) throws CertificateEncodingException {
52-
return getField(certificate, BCStyle.SERIALNUMBER);
61+
return getSubjectField(certificate, BCStyle.SERIALNUMBER);
5362
}
5463

5564
public static String getSubjectCountryCode(X509Certificate certificate) throws CertificateEncodingException {
56-
return getField(certificate, BCStyle.C);
65+
return getSubjectField(certificate, BCStyle.C);
66+
}
67+
68+
private static String getSubjectField(X509Certificate certificate, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
69+
return getField(new JcaX509CertificateHolder(certificate).getSubject(), fieldId);
5770
}
5871

59-
private static String getField(X509Certificate certificate, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
60-
final X500Name x500Name = new JcaX509CertificateHolder(certificate).getSubject();
72+
private static String getField(X500Name x500Name, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
6173
// Example value: [C=EE, CN=JÕEORG\,JAAK-KRISTJAN\,38001085718, 2.5.4.4=#0c074ac395454f5247, 2.5.4.42=#0c0d4a41414b2d4b524953544a414e, 2.5.4.5=#1311504e4f45452d3338303031303835373138]
6274
final RDN[] rdns = x500Name.getRDNs(fieldId);
75+
if (rdns.length == 0 || rdns[0].getFirst() == null) {
76+
throw new CertificateEncodingException("X500 name RDNs empty or first element is null");
77+
}
6378
return Arrays.stream(rdns)
6479
.map(rdn -> IETFUtils.valueToString(rdn.getFirst().getValue()))
6580
.collect(Collectors.joining(", "));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.webeid.security.util;
2+
3+
import java.net.URI;
4+
5+
public class AiaOcspUrls {
6+
7+
public static final URI ESTEID_2015 = URI.create("http://aia.sk.ee/esteid2015");
8+
9+
private AiaOcspUrls() {
10+
throw new IllegalStateException("Constants class");
11+
}
12+
13+
}

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

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Objects;
3939

4040
import static org.webeid.security.nonce.NonceGeneratorBuilder.requirePositiveDuration;
41+
import static org.webeid.security.util.AiaOcspUrls.ESTEID_2015;
4142
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY;
4243
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1;
4344
import static org.webeid.security.util.SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V2;
@@ -46,15 +47,14 @@
4647
/**
4748
* Stores configuration parameters for {@link AuthTokenValidatorImpl}.
4849
*/
49-
final class AuthTokenValidationConfiguration {
50+
public final class AuthTokenValidationConfiguration {
5051

5152
private URI siteOrigin;
5253
private Cache<String, ZonedDateTime> nonceCache;
5354
private Collection<X509Certificate> trustedCACertificates = new HashSet<>();
5455
private boolean isUserCertificateRevocationCheckWithOcspEnabled = true;
5556
private Duration ocspRequestTimeout = Duration.ofSeconds(5);
5657
private Duration allowedClientClockSkew = Duration.ofMinutes(3);
57-
private AiaOcspServiceConfiguration aiaOcspServiceConfiguration;
5858
private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
5959
private boolean isSiteCertificateFingerprintValidationEnabled = false;
6060
private String siteCertificateSha256Fingerprint;
@@ -65,6 +65,8 @@ final class AuthTokenValidationConfiguration {
6565
ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
6666
ESTEID_SK_2015_MOBILE_ID_POLICY
6767
);
68+
// Disable OCSP nonce extension for EstEID 2015 cards by default.
69+
private Collection<URI> nonceDisabledOcspUrls = Sets.newHashSet(ESTEID_2015);
6870

6971
AuthTokenValidationConfiguration() {
7072
}
@@ -76,11 +78,11 @@ private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other)
7678
this.isUserCertificateRevocationCheckWithOcspEnabled = other.isUserCertificateRevocationCheckWithOcspEnabled;
7779
this.ocspRequestTimeout = other.ocspRequestTimeout;
7880
this.allowedClientClockSkew = other.allowedClientClockSkew;
79-
this.aiaOcspServiceConfiguration = other.aiaOcspServiceConfiguration;
8081
this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
8182
this.isSiteCertificateFingerprintValidationEnabled = other.isSiteCertificateFingerprintValidationEnabled;
8283
this.siteCertificateSha256Fingerprint = other.siteCertificateSha256Fingerprint;
8384
this.disallowedSubjectCertificatePolicies = new HashSet<>(other.disallowedSubjectCertificatePolicies);
85+
this.nonceDisabledOcspUrls = new HashSet<>(other.nonceDisabledOcspUrls);
8486
}
8587

8688
void setSiteOrigin(URI siteOrigin) {
@@ -127,14 +129,6 @@ Duration getAllowedClientClockSkew() {
127129
return allowedClientClockSkew;
128130
}
129131

130-
public AiaOcspServiceConfiguration getAiaOcspServiceConfiguration() {
131-
return aiaOcspServiceConfiguration;
132-
}
133-
134-
public void setAiaOcspServiceConfiguration(AiaOcspServiceConfiguration aiaOcspServiceConfiguration) {
135-
this.aiaOcspServiceConfiguration = aiaOcspServiceConfiguration;
136-
}
137-
138132
public DesignatedOcspServiceConfiguration getDesignatedOcspServiceConfiguration() {
139133
return designatedOcspServiceConfiguration;
140134
}
@@ -160,6 +154,10 @@ public Collection<ASN1ObjectIdentifier> getDisallowedSubjectCertificatePolicies(
160154
return disallowedSubjectCertificatePolicies;
161155
}
162156

157+
public Collection<URI> getNonceDisabledOcspUrls() {
158+
return nonceDisabledOcspUrls;
159+
}
160+
163161
/**
164162
* Checks that the configuration parameters are valid.
165163
*
@@ -173,18 +171,6 @@ void validate() {
173171
if (trustedCACertificates.isEmpty()) {
174172
throw new IllegalArgumentException("At least one trusted certificate authority must be provided");
175173
}
176-
if (isUserCertificateRevocationCheckWithOcspEnabled) {
177-
if (aiaOcspServiceConfiguration == null && designatedOcspServiceConfiguration == null) {
178-
throw new IllegalArgumentException("Either AIA or designated OCSP service configuration must be provided");
179-
}
180-
if (aiaOcspServiceConfiguration != null && designatedOcspServiceConfiguration != null) {
181-
throw new IllegalArgumentException("AIA and designated OCSP service configuration cannot provided together, " +
182-
"please provide either one or the other");
183-
}
184-
} else if (aiaOcspServiceConfiguration != null || designatedOcspServiceConfiguration != null) {
185-
throw new IllegalArgumentException("When user certificate OCSP check is disabled, " +
186-
"AIA or designated OCSP service configuration should not be provided");
187-
}
188174
requirePositiveDuration(ocspRequestTimeout, "OCSP request timeout");
189175
requirePositiveDuration(allowedClientClockSkew, "Allowed client clock skew");
190176
if (isSiteCertificateFingerprintValidationEnabled) {

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

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ public AuthTokenValidatorBuilder withNonceCache(Cache<String, ZonedDateTime> cac
7575
}
7676

7777
/**
78-
* Adds the given certificates to the list of trusted subject certificate intermediate Certificate Authorities.
79-
* In order for the user certificate to be considered valid, the certificate of the issuer of the user certificate
80-
* must be present in this list.
78+
* Adds the given certificates to the list of trusted intermediate Certificate Authorities
79+
* used during validation of subject and OCSP responder certificates.
80+
* In order for a user or OCSP responder certificate to be considered valid, the certificate
81+
* of the issuer of the certificate must be present in this list.
8182
* <p>
8283
* At least one trusted intermediate Certificate Authority must be provided as a mandatory configuration parameter.
8384
*
@@ -134,12 +135,6 @@ public AuthTokenValidatorBuilder withOcspRequestTimeout(Duration ocspRequestTime
134135
return this;
135136
}
136137

137-
public AuthTokenValidatorBuilder withAiaOcspServiceConfiguration(AiaOcspServiceConfiguration serviceConfiguration) {
138-
configuration.setAiaOcspServiceConfiguration(serviceConfiguration);
139-
LOG.debug("Using AIA OCSP service configuration");
140-
return this;
141-
}
142-
143138
public AuthTokenValidatorBuilder withDesignatedOcspServiceConfiguration(DesignatedOcspServiceConfiguration serviceConfiguration) {
144139
configuration.setDesignatedOcspServiceConfiguration(serviceConfiguration);
145140
LOG.debug("Using designated OCSP service configuration");

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
package org.webeid.security.validator;
2424

25-
import com.google.common.base.Suppliers;
2625
import org.slf4j.Logger;
2726
import org.slf4j.LoggerFactory;
2827
import org.webeid.security.exceptions.JceException;
@@ -31,13 +30,20 @@
3130
import org.webeid.security.validator.ocsp.OcspClient;
3231
import org.webeid.security.validator.ocsp.OcspClientImpl;
3332
import org.webeid.security.validator.ocsp.OcspServiceProvider;
34-
import org.webeid.security.validator.validators.*;
33+
import org.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
34+
import org.webeid.security.validator.validators.FunctionalSubjectCertificateValidators;
35+
import org.webeid.security.validator.validators.NonceValidator;
36+
import org.webeid.security.validator.validators.OriginValidator;
37+
import org.webeid.security.validator.validators.SiteCertificateFingerprintValidator;
38+
import org.webeid.security.validator.validators.SubjectCertificateNotRevokedValidator;
39+
import org.webeid.security.validator.validators.SubjectCertificatePolicyValidator;
40+
import org.webeid.security.validator.validators.SubjectCertificateTrustedValidator;
41+
import org.webeid.security.validator.validators.ValidatorBatch;
3542

3643
import java.security.cert.CertStore;
3744
import java.security.cert.TrustAnchor;
3845
import java.security.cert.X509Certificate;
3946
import java.util.Set;
40-
import java.util.function.Supplier;
4147

4248
import static org.webeid.security.certificate.CertificateValidator.buildCertStoreFromCertificates;
4349
import static org.webeid.security.certificate.CertificateValidator.buildTrustAnchorsFromCertificates;
@@ -52,14 +58,15 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
5258
private static final Logger LOG = LoggerFactory.getLogger(AuthTokenValidatorImpl.class);
5359

5460
private final AuthTokenValidationConfiguration configuration;
55-
// OkHttp performs best when a single OkHttpClient instance is created and reused for all HTTP calls.
56-
// This is because each client holds its own connection pool and thread pools.
57-
// Reusing connections and threads reduces latency and saves memory.
58-
private final Supplier<OcspClient> ocspClientSupplier;
5961
private final ValidatorBatch simpleSubjectCertificateValidators;
6062
private final ValidatorBatch tokenBodyValidators;
6163
private final Set<TrustAnchor> trustedCACertificateAnchors;
6264
private final CertStore trustedCACertificateCertStore;
65+
// OcspClient uses OkHttp internally.
66+
// OkHttp performs best when a single OkHttpClient instance is created and reused for all HTTP calls.
67+
// This is because each client holds its own connection pool and thread pools.
68+
// Reusing connections and threads reduces latency and saves memory.
69+
private OcspClient ocspClientSupplier;
6370
private OcspServiceProvider ocspServiceProvider;
6471

6572
/**
@@ -68,11 +75,6 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
6875
AuthTokenValidatorImpl(AuthTokenValidationConfiguration configuration) throws JceException {
6976
// Copy the configuration object to make AuthTokenValidatorImpl immutable and thread-safe.
7077
this.configuration = configuration.copy();
71-
// Lazy initialization, avoid constructing the OkHttpClient object when certificate revocation check is not enabled.
72-
// Returns a supplier which caches the instance retrieved during the first call to get() and returns
73-
// that value on subsequent calls to get(). The returned supplier is thread-safe.
74-
// The OkHttpClient build() method will be invoked at most once.
75-
this.ocspClientSupplier = Suppliers.memoize(() -> OcspClientImpl.build(configuration.getOcspRequestTimeout()));
7678

7779
simpleSubjectCertificateValidators = ValidatorBatch.createFrom(
7880
FunctionalSubjectCertificateValidators::validateCertificateExpiry,
@@ -86,14 +88,17 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
8688
new SiteCertificateFingerprintValidator(configuration.getSiteCertificateSha256Fingerprint())::validateSiteCertificateFingerprint
8789
);
8890

89-
// Create and cache trusted CA certificate JCA objects for SubjectCertificateTrustedValidator.
91+
// Create and cache trusted CA certificate JCA objects for SubjectCertificateTrustedValidator and AiaOcspService.
9092
trustedCACertificateAnchors = buildTrustAnchorsFromCertificates(configuration.getTrustedCACertificates());
9193
trustedCACertificateCertStore = buildCertStoreFromCertificates(configuration.getTrustedCACertificates());
9294

9395
if (configuration.isUserCertificateRevocationCheckWithOcspEnabled()) {
94-
ocspServiceProvider = configuration.getAiaOcspServiceConfiguration() != null ?
95-
new OcspServiceProvider(configuration.getAiaOcspServiceConfiguration()) :
96-
new OcspServiceProvider(configuration.getDesignatedOcspServiceConfiguration());
96+
ocspClientSupplier = OcspClientImpl.build(configuration.getOcspRequestTimeout());
97+
ocspServiceProvider = new OcspServiceProvider(
98+
configuration.getDesignatedOcspServiceConfiguration(),
99+
new AiaOcspServiceConfiguration(configuration.getNonceDisabledOcspUrls(),
100+
trustedCACertificateAnchors,
101+
trustedCACertificateCertStore));
97102
}
98103
}
99104

@@ -145,7 +150,7 @@ private ValidatorBatch getCertTrustValidators() {
145150
return ValidatorBatch.createFrom(
146151
certTrustedValidator::validateCertificateTrusted
147152
).addOptional(configuration.isUserCertificateRevocationCheckWithOcspEnabled(),
148-
new SubjectCertificateNotRevokedValidator(certTrustedValidator, ocspClientSupplier.get(), ocspServiceProvider)::validateCertificateNotRevoked
153+
new SubjectCertificateNotRevokedValidator(certTrustedValidator, ocspClientSupplier, ocspServiceProvider)::validateCertificateNotRevoked
149154
);
150155
}
151156
}

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,36 @@
77
import org.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
88
import org.webeid.security.validator.ocsp.service.OcspService;
99

10+
import java.security.cert.CertificateEncodingException;
1011
import java.security.cert.X509Certificate;
12+
import java.util.Objects;
1113

1214
public class OcspServiceProvider {
1315

14-
private final AiaOcspServiceConfiguration aiaOcspServiceConfiguration;
1516
private final DesignatedOcspService designatedOcspService;
17+
private final AiaOcspServiceConfiguration aiaOcspServiceConfiguration;
1618

17-
public OcspServiceProvider(DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration) {
18-
this.designatedOcspService = new DesignatedOcspService(designatedOcspServiceConfiguration);
19-
this.aiaOcspServiceConfiguration = null;
20-
}
21-
22-
public OcspServiceProvider(AiaOcspServiceConfiguration aiaOcspServiceConfiguration) {
23-
this.aiaOcspServiceConfiguration = aiaOcspServiceConfiguration;
24-
this.designatedOcspService = null;
19+
public OcspServiceProvider(DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration, AiaOcspServiceConfiguration aiaOcspServiceConfiguration) {
20+
designatedOcspService = designatedOcspServiceConfiguration != null ?
21+
new DesignatedOcspService(designatedOcspServiceConfiguration)
22+
: null;
23+
this.aiaOcspServiceConfiguration = Objects.requireNonNull(aiaOcspServiceConfiguration, "aiaOcspServiceConfiguration");
2524
}
2625

27-
public OcspService getService(X509Certificate certificate) throws TokenValidationException {
28-
return designatedOcspService != null ?
29-
designatedOcspService :
30-
new AiaOcspService(aiaOcspServiceConfiguration, certificate);
26+
/**
27+
* A static factory method that returns either the designated or AIA OCSP service instance depending on whether
28+
* the designated OCSP service is configured and supports the issuer of the certificate.
29+
*
30+
* @param certificate subject certificate that is to be checked with OCSP
31+
* @return either the designated or AIA OCSP service instance
32+
* @throws TokenValidationException when AIA URL is not found in certificate
33+
* @throws CertificateEncodingException when certificate is invalid
34+
*/
35+
public OcspService getService(X509Certificate certificate) throws TokenValidationException, CertificateEncodingException {
36+
if (designatedOcspService != null && designatedOcspService.supportsIssuerOf(certificate)) {
37+
return designatedOcspService;
38+
}
39+
return new AiaOcspService(aiaOcspServiceConfiguration, certificate);
3140
}
3241

3342
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.nio.charset.StandardCharsets;
5555
import java.security.cert.CertificateParsingException;
5656
import java.security.cert.X509Certificate;
57+
import java.util.Objects;
5758

5859
public final class OcspUtils {
5960

@@ -71,10 +72,11 @@ public final class OcspUtils {
7172
private static final ASN1ObjectIdentifier OCSP_RESPONDER_OID
7273
= new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1").intern();
7374

74-
public static void validateHasSigningExtension(X509Certificate cert) throws OCSPCertificateException {
75+
public static void validateHasSigningExtension(X509Certificate certificate) throws OCSPCertificateException {
76+
Objects.requireNonNull(certificate, "certificate");
7577
try {
76-
if (cert.getExtendedKeyUsage() == null || !cert.getExtendedKeyUsage().contains(OID_OCSP_SIGNING)) {
77-
throw new OCSPCertificateException("Certificate " + cert.getSubjectDN() +
78+
if (certificate.getExtendedKeyUsage() == null || !certificate.getExtendedKeyUsage().contains(OID_OCSP_SIGNING)) {
79+
throw new OCSPCertificateException("Certificate " + certificate.getSubjectDN() +
7880
" does not contain the key usage extension for OCSP response signing");
7981
}
8082
} catch (CertificateParsingException e) {
@@ -85,7 +87,8 @@ public static void validateHasSigningExtension(X509Certificate cert) throws OCSP
8587
/**
8688
* Returns the OCSP responder {@link URI} or {@code null} if it doesn't have one.
8789
*/
88-
public static URI ocspUri(X509Certificate certificate) {
90+
public static URI getOcspUri(X509Certificate certificate) {
91+
Objects.requireNonNull(certificate, "certificate");
8992
final byte[] value = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
9093
if (value == null) {
9194
return null;

0 commit comments

Comments
 (0)