Skip to content

Commit 5d2b4b5

Browse files
committed
refactor(OCSP): simplify AIA OCSP configuration, use single truststore for subject and OCSP responder certificates, add truste CA certifcates validation
WE2-432 Signed-off-by: Mart Somermaa <[email protected]>
1 parent c360a3c commit 5d2b4b5

39 files changed

+985
-458
lines changed

README.md

Lines changed: 2 additions & 11 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

@@ -350,6 +351,7 @@ String nonce = nonceGenerator.generateAndStoreNonce();
350351
The `generateAndStoreNonce()` method both generates the nonce and stores it in the cache.
351352
352353
## Extended configuration
354+
353355
The following additional configuration options are available in `NonceGeneratorBuilder`:
354356
355357
- `withNonceTtl(Duration duration)` – overrides the default nonce time-to-live duration. When the time-to-live passes, the nonce is considered to be expired. Default nonce time-to-live is 5 minutes.
@@ -363,14 +365,3 @@ NonceGenerator generator = new NonceGeneratorBuilder()
363365
.withSecureRandom(customSecureRandom)
364366
.build();
365367
```
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).

pom.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<modelVersion>4.0.0</modelVersion>
66
<artifactId>authtoken-validation</artifactId>
77
<groupId>org.webeid.security</groupId>
8-
<version>2.0.0</version>
8+
<version>1.2.0</version>
99
<packaging>jar</packaging>
1010
<name>authtoken-validation</name>
1111
<description>Web eID authentication token validation library for Java</description>
@@ -16,10 +16,11 @@
1616
<java.version>1.8</java.version>
1717
<jjwt.version>0.11.2</jjwt.version>
1818
<slf4j.version>1.7.30</slf4j.version>
19-
<bouncycastle.version>1.65</bouncycastle.version>
19+
<bouncycastle.version>1.69</bouncycastle.version>
2020
<caffeine.version>2.8.5</caffeine.version>
2121
<junit-jupiter.version>5.6.2</junit-jupiter.version>
2222
<assertj.version>3.17.2</assertj.version>
23+
<mockito.version>3.12.4</mockito.version>
2324
<jacoco.version>0.8.5</jacoco.version>
2425
<sonar.coverage.jacoco.xmlReportPaths>
2526
${project.basedir}/../jacoco-coverage-report/target/site/jacoco-aggregate/jacoco.xml
@@ -96,6 +97,12 @@
9697
<version>${assertj.version}</version>
9798
<scope>test</scope>
9899
</dependency>
100+
<dependency>
101+
<groupId>org.mockito</groupId>
102+
<artifactId>mockito-core</artifactId>
103+
<version>${mockito.version}</version>
104+
<scope>test</scope>
105+
</dependency>
99106
<dependency>
100107
<groupId>org.slf4j</groupId>
101108
<artifactId>slf4j-simple</artifactId>

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,35 @@
3737
public final class CertificateData {
3838

3939
public static String getSubjectCN(X509Certificate certificate) throws CertificateEncodingException {
40-
return getField(certificate, BCStyle.CN);
40+
return getSubjectField(certificate, BCStyle.CN);
4141
}
4242

4343
public static String getSubjectSurname(X509Certificate certificate) throws CertificateEncodingException {
44-
return getField(certificate, BCStyle.SURNAME);
44+
return getSubjectField(certificate, BCStyle.SURNAME);
4545
}
4646

4747
public static String getSubjectGivenName(X509Certificate certificate) throws CertificateEncodingException {
48-
return getField(certificate, BCStyle.GIVENNAME);
48+
return getSubjectField(certificate, BCStyle.GIVENNAME);
4949
}
5050

5151
public static String getSubjectIdCode(X509Certificate certificate) throws CertificateEncodingException {
52-
return getField(certificate, BCStyle.SERIALNUMBER);
52+
return getSubjectField(certificate, BCStyle.SERIALNUMBER);
5353
}
5454

5555
public static String getSubjectCountryCode(X509Certificate certificate) throws CertificateEncodingException {
56-
return getField(certificate, BCStyle.C);
56+
return getSubjectField(certificate, BCStyle.C);
5757
}
5858

59-
private static String getField(X509Certificate certificate, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
60-
final X500Name x500Name = new JcaX509CertificateHolder(certificate).getSubject();
59+
private static String getSubjectField(X509Certificate certificate, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
60+
return getField(new JcaX509CertificateHolder(certificate).getSubject(), fieldId);
61+
}
62+
63+
private static String getField(X500Name x500Name, ASN1ObjectIdentifier fieldId) throws CertificateEncodingException {
6164
// 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]
6265
final RDN[] rdns = x500Name.getRDNs(fieldId);
66+
if (rdns.length == 0 || rdns[0].getFirst() == null) {
67+
throw new CertificateEncodingException("X500 name RDNs empty or first element is null");
68+
}
6369
return Arrays.stream(rdns)
6470
.map(rdn -> IETFUtils.valueToString(rdn.getFirst().getValue()))
6571
.collect(Collectors.joining(", "));

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

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,41 @@
22

33
import org.webeid.security.exceptions.CertificateNotTrustedException;
44
import org.webeid.security.exceptions.JceException;
5-
import org.webeid.security.exceptions.UserCertificateExpiredException;
6-
import org.webeid.security.exceptions.UserCertificateNotYetValidException;
5+
import org.webeid.security.exceptions.CertificateExpiredException;
6+
import org.webeid.security.exceptions.CertificateNotYetValidException;
77

88
import java.security.GeneralSecurityException;
99
import java.security.InvalidAlgorithmParameterException;
1010
import java.security.NoSuchAlgorithmException;
1111
import java.security.cert.CertPathBuilder;
1212
import java.security.cert.CertPathBuilderException;
1313
import java.security.cert.CertStore;
14-
import java.security.cert.CertificateExpiredException;
15-
import java.security.cert.CertificateNotYetValidException;
1614
import java.security.cert.CollectionCertStoreParameters;
1715
import java.security.cert.PKIXBuilderParameters;
1816
import java.security.cert.PKIXCertPathBuilderResult;
1917
import java.security.cert.TrustAnchor;
2018
import java.security.cert.X509CertSelector;
2119
import java.security.cert.X509Certificate;
2220
import java.util.Collection;
23-
import java.util.Collections;
2421
import java.util.Date;
2522
import java.util.Set;
2623
import java.util.stream.Collectors;
2724

2825
public final class CertificateValidator {
2926

30-
/**
31-
* Checks whether the certificate was valid on the given date.
32-
*/
33-
public static void certificateIsValidOnDate(X509Certificate cert, Date date) throws UserCertificateNotYetValidException, UserCertificateExpiredException {
27+
public static void certificateIsValidOnDate(X509Certificate cert, Date date, String subject) throws CertificateNotYetValidException, CertificateExpiredException {
3428
try {
3529
cert.checkValidity(date);
36-
} catch (CertificateNotYetValidException e) {
37-
throw new UserCertificateNotYetValidException(e);
38-
} catch (CertificateExpiredException e) {
39-
throw new UserCertificateExpiredException(e);
30+
} catch (java.security.cert.CertificateNotYetValidException e) {
31+
throw new CertificateNotYetValidException(subject, e);
32+
} catch (java.security.cert.CertificateExpiredException e) {
33+
throw new CertificateExpiredException(subject, e);
34+
}
35+
}
36+
37+
public static void trustedCACertificatesAreValidOnDate(Set<TrustAnchor> trustedCACertificateAnchors, Date date) throws CertificateNotYetValidException, CertificateExpiredException {
38+
for (TrustAnchor cert : trustedCACertificateAnchors) {
39+
certificateIsValidOnDate(cert.getTrustedCert(), date, "Trusted CA");
4040
}
4141
}
4242

@@ -70,10 +70,6 @@ public static Set<TrustAnchor> buildTrustAnchorsFromCertificates(Collection<X509
7070
.collect(Collectors.toSet());
7171
}
7272

73-
public static Set<TrustAnchor> buildTrustAnchorsFromCertificate(X509Certificate certificate) {
74-
return buildTrustAnchorsFromCertificates(Collections.singleton(certificate));
75-
}
76-
7773
public static CertStore buildCertStoreFromCertificates(Collection<X509Certificate> certificates) throws JceException {
7874
// We use the default JCE provider as there is no reason to use Bouncy Castle, moreover BC requires
7975
// the validated certificate to be in the certificate store which breaks the clean immutable usage of
@@ -85,10 +81,6 @@ public static CertStore buildCertStoreFromCertificates(Collection<X509Certificat
8581
}
8682
}
8783

88-
public static CertStore buildCertStoreFromCertificate(X509Certificate certificate) throws JceException {
89-
return buildCertStoreFromCertificates(Collections.singleton(certificate));
90-
}
91-
9284
private CertificateValidator() {
9385
throw new IllegalStateException("Utility class");
9486
}

src/main/java/org/webeid/security/exceptions/AiaOcspResponderConfigurationException.java

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

src/main/java/org/webeid/security/exceptions/UserCertificateExpiredException.java renamed to src/main/java/org/webeid/security/exceptions/CertificateExpiredException.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
/**
2626
* Thrown when the user certificate valid until date is in the past.
2727
*/
28-
public class UserCertificateExpiredException extends TokenValidationException {
29-
public UserCertificateExpiredException(Throwable cause) {
30-
super("User certificate has expired", cause);
28+
public class CertificateExpiredException extends TokenValidationException {
29+
public CertificateExpiredException(String subject, Throwable cause) {
30+
super(subject + " certificate has expired", cause);
3131
}
3232
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
package org.webeid.security.exceptions;
2424

2525
/**
26-
* Thrown when the user certificate valid from date is in the future.
26+
* Thrown when the certificate's valid from date is in the future.
2727
*/
28-
public class UserCertificateNotYetValidException extends TokenValidationException {
29-
public UserCertificateNotYetValidException(Throwable cause) {
30-
super("User certificate is not yet valid", cause);
28+
public class CertificateNotYetValidException extends TokenValidationException {
29+
public CertificateNotYetValidException(String subject, Throwable cause) {
30+
super(subject + " certificate is not yet valid", cause);
3131
}
3232
}

src/main/java/org/webeid/security/exceptions/UserCertificateInvalidPolicyException.java

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

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

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import com.google.common.collect.Sets;
2626
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
27-
import org.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
2827
import org.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
2928
import org.webeid.security.validator.validators.OriginValidator;
3029

@@ -38,23 +37,20 @@
3837
import java.util.Objects;
3938

4039
import static org.webeid.security.nonce.NonceGeneratorBuilder.requirePositiveDuration;
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;
40+
import static org.webeid.security.validator.ocsp.OcspUrl.AIA_ESTEID_2015;
41+
import static org.webeid.security.util.SubjectCertificatePolicies.*;
4542

4643
/**
4744
* Stores configuration parameters for {@link AuthTokenValidatorImpl}.
4845
*/
49-
final class AuthTokenValidationConfiguration {
46+
public final class AuthTokenValidationConfiguration {
5047

5148
private URI siteOrigin;
5249
private Cache<String, ZonedDateTime> nonceCache;
5350
private Collection<X509Certificate> trustedCACertificates = new HashSet<>();
5451
private boolean isUserCertificateRevocationCheckWithOcspEnabled = true;
5552
private Duration ocspRequestTimeout = Duration.ofSeconds(5);
5653
private Duration allowedClientClockSkew = Duration.ofMinutes(3);
57-
private AiaOcspServiceConfiguration aiaOcspServiceConfiguration;
5854
private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
5955
private boolean isSiteCertificateFingerprintValidationEnabled = false;
6056
private String siteCertificateSha256Fingerprint;
@@ -65,6 +61,8 @@ final class AuthTokenValidationConfiguration {
6561
ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
6662
ESTEID_SK_2015_MOBILE_ID_POLICY
6763
);
64+
// Disable OCSP nonce extension for EstEID 2015 cards by default.
65+
private Collection<URI> nonceDisabledOcspUrls = Sets.newHashSet(AIA_ESTEID_2015);
6866

6967
AuthTokenValidationConfiguration() {
7068
}
@@ -76,11 +74,11 @@ private AuthTokenValidationConfiguration(AuthTokenValidationConfiguration other)
7674
this.isUserCertificateRevocationCheckWithOcspEnabled = other.isUserCertificateRevocationCheckWithOcspEnabled;
7775
this.ocspRequestTimeout = other.ocspRequestTimeout;
7876
this.allowedClientClockSkew = other.allowedClientClockSkew;
79-
this.aiaOcspServiceConfiguration = other.aiaOcspServiceConfiguration;
8077
this.designatedOcspServiceConfiguration = other.designatedOcspServiceConfiguration;
8178
this.isSiteCertificateFingerprintValidationEnabled = other.isSiteCertificateFingerprintValidationEnabled;
8279
this.siteCertificateSha256Fingerprint = other.siteCertificateSha256Fingerprint;
8380
this.disallowedSubjectCertificatePolicies = new HashSet<>(other.disallowedSubjectCertificatePolicies);
81+
this.nonceDisabledOcspUrls = new HashSet<>(other.nonceDisabledOcspUrls);
8482
}
8583

8684
void setSiteOrigin(URI siteOrigin) {
@@ -127,14 +125,6 @@ Duration getAllowedClientClockSkew() {
127125
return allowedClientClockSkew;
128126
}
129127

130-
public AiaOcspServiceConfiguration getAiaOcspServiceConfiguration() {
131-
return aiaOcspServiceConfiguration;
132-
}
133-
134-
public void setAiaOcspServiceConfiguration(AiaOcspServiceConfiguration aiaOcspServiceConfiguration) {
135-
this.aiaOcspServiceConfiguration = aiaOcspServiceConfiguration;
136-
}
137-
138128
public DesignatedOcspServiceConfiguration getDesignatedOcspServiceConfiguration() {
139129
return designatedOcspServiceConfiguration;
140130
}
@@ -160,6 +150,10 @@ public Collection<ASN1ObjectIdentifier> getDisallowedSubjectCertificatePolicies(
160150
return disallowedSubjectCertificatePolicies;
161151
}
162152

153+
public Collection<URI> getNonceDisabledOcspUrls() {
154+
return nonceDisabledOcspUrls;
155+
}
156+
163157
/**
164158
* Checks that the configuration parameters are valid.
165159
*
@@ -173,18 +167,6 @@ void validate() {
173167
if (trustedCACertificates.isEmpty()) {
174168
throw new IllegalArgumentException("At least one trusted certificate authority must be provided");
175169
}
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-
}
188170
requirePositiveDuration(ocspRequestTimeout, "OCSP request timeout");
189171
requirePositiveDuration(allowedClientClockSkew, "Allowed client clock skew");
190172
if (isSiteCertificateFingerprintValidationEnabled) {

0 commit comments

Comments
 (0)