Skip to content

Commit 2e783f3

Browse files
committed
Use plaform OCSP implementation by default, move custom OCSP implementation to eu.webeid.ocsp and make it optional
WE2-1030 Signed-off-by: Mart Somermaa <[email protected]>
1 parent 4379d59 commit 2e783f3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+706
-588
lines changed

.github/workflows/coverity-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: actions/setup-java@v4
2121
with:
2222
distribution: zulu
23-
java-version: 11
23+
java-version: 17
2424

2525
- name: Cache Maven packages
2626
uses: actions/cache@v4

.github/workflows/maven-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: actions/setup-java@v4
2121
with:
2222
distribution: zulu
23-
java-version: 11
23+
java-version: 17
2424

2525
- name: Cache Maven packages
2626
uses: actions/cache@v4

.github/workflows/maven-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- uses: actions/setup-java@v4
1515
with:
1616
distribution: zulu
17-
java-version: 11
17+
java-version: 17
1818

1919
- name: Cache Maven packages
2020
uses: actions/cache@v4

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
<modelVersion>4.0.0</modelVersion>
66
<artifactId>authtoken-validation</artifactId>
77
<groupId>eu.webeid.security</groupId>
8-
<version>3.2.0</version>
8+
<version>4.0.0-SNAPSHOT</version>
99
<packaging>jar</packaging>
1010
<name>authtoken-validation</name>
1111
<description>Web eID authentication token validation library for Java</description>
1212

1313
<properties>
14-
<java.version>11</java.version>
14+
<java.version>17</java.version>
1515
<jjwt.version>0.12.6</jjwt.version>
1616
<bouncycastle.version>1.81</bouncycastle.version>
1717
<jackson.version>2.19.1</jackson.version>

src/main/java/eu/webeid/security/validator/certvalidators/SubjectCertificateNotRevokedValidator.java renamed to src/main/java/eu/webeid/ocsp/DefaultOcspCertificateRevocationChecker.java

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.security.validator.certvalidators;
23+
package eu.webeid.ocsp;
2424

25+
import eu.webeid.ocsp.client.OcspClient;
26+
import eu.webeid.ocsp.protocol.DigestCalculatorImpl;
27+
import eu.webeid.ocsp.protocol.OcspRequestBuilder;
28+
import eu.webeid.ocsp.protocol.OcspResponseValidator;
2529
import eu.webeid.security.exceptions.AuthTokenException;
26-
import eu.webeid.security.exceptions.UserCertificateOCSPCheckFailedException;
30+
import eu.webeid.ocsp.exceptions.UserCertificateOCSPCheckFailedException;
2731
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;
33-
import eu.webeid.security.validator.ocsp.service.OcspService;
32+
import eu.webeid.ocsp.service.OcspServiceProvider;
33+
import eu.webeid.ocsp.service.OcspService;
34+
import eu.webeid.security.validator.revocationcheck.OcspCertificateRevocationChecker;
35+
import eu.webeid.security.validator.revocationcheck.RevocationInfo;
3436
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
3537
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
3638
import org.bouncycastle.asn1.x509.Extension;
@@ -49,52 +51,64 @@
4951

5052
import java.io.IOException;
5153
import java.math.BigInteger;
54+
import java.net.URI;
5255
import java.security.Security;
5356
import java.security.cert.CertificateEncodingException;
5457
import java.security.cert.CertificateException;
5558
import java.security.cert.X509Certificate;
5659
import java.time.Duration;
5760
import java.util.Date;
58-
import java.util.Objects;
61+
import java.util.List;
62+
import java.util.Map;
5963

60-
public final class SubjectCertificateNotRevokedValidator {
64+
import static eu.webeid.security.util.DateAndTime.requirePositiveDuration;
65+
import static java.util.Objects.requireNonNull;
6166

62-
private static final Logger LOG = LoggerFactory.getLogger(SubjectCertificateNotRevokedValidator.class);
67+
public final class DefaultOcspCertificateRevocationChecker implements OcspCertificateRevocationChecker {
68+
69+
public static final Duration DEFAULT_TIME_SKEW = Duration.ofMinutes(15);
70+
public static final Duration DEFAULT_THIS_UPDATE_AGE = Duration.ofMinutes(2);
71+
72+
private static final Logger LOG = LoggerFactory.getLogger(DefaultOcspCertificateRevocationChecker.class);
6373

64-
private final SubjectCertificateTrustedValidator trustValidator;
6574
private final OcspClient ocspClient;
6675
private final OcspServiceProvider ocspServiceProvider;
6776
private final Duration allowedOcspResponseTimeSkew;
6877
private final Duration maxOcspResponseThisUpdateAge;
6978

7079
static {
71-
Security.addProvider(new BouncyCastleProvider());
80+
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
81+
Security.addProvider(new BouncyCastleProvider());
82+
}
7283
}
7384

74-
public SubjectCertificateNotRevokedValidator(SubjectCertificateTrustedValidator trustValidator,
75-
OcspClient ocspClient,
76-
OcspServiceProvider ocspServiceProvider,
77-
Duration allowedOcspResponseTimeSkew,
78-
Duration maxOcspResponseThisUpdateAge) {
79-
this.trustValidator = trustValidator;
80-
this.ocspClient = ocspClient;
81-
this.ocspServiceProvider = ocspServiceProvider;
82-
this.allowedOcspResponseTimeSkew = allowedOcspResponseTimeSkew;
83-
this.maxOcspResponseThisUpdateAge = maxOcspResponseThisUpdateAge;
85+
public DefaultOcspCertificateRevocationChecker(OcspClient ocspClient,
86+
OcspServiceProvider ocspServiceProvider,
87+
Duration allowedOcspResponseTimeSkew,
88+
Duration maxOcspResponseThisUpdateAge) {
89+
this.ocspClient = requireNonNull(ocspClient, "ocspClient");
90+
this.ocspServiceProvider = requireNonNull(ocspServiceProvider, "ocspServiceProvider");
91+
this.allowedOcspResponseTimeSkew = requirePositiveDuration(allowedOcspResponseTimeSkew, "allowedOcspResponseTimeSkew");
92+
this.maxOcspResponseThisUpdateAge = requirePositiveDuration(maxOcspResponseThisUpdateAge, "maxOcspResponseThisUpdateAge");
8493
}
8594

8695
/**
87-
* Validates that the user certificate from the authentication token is not revoked with OCSP.
96+
* Validates with OCSP that the user certificate from the authentication token is not revoked.
8897
*
8998
* @param subjectCertificate user certificate to be validated
9099
* @throws AuthTokenException when user certificate is revoked or revocation check fails.
91100
*/
92-
public void validateCertificateNotRevoked(X509Certificate subjectCertificate) throws AuthTokenException {
101+
@Override
102+
public List<RevocationInfo> validateCertificateNotRevoked(X509Certificate subjectCertificate, X509Certificate issuerCertificate) throws AuthTokenException {
103+
requireNonNull(subjectCertificate, "subjectCertificate");
104+
requireNonNull(issuerCertificate, "issuerCertificate");
105+
106+
URI ocspResponderUri = null;
93107
try {
94108
OcspService ocspService = ocspServiceProvider.getService(subjectCertificate);
109+
ocspResponderUri = requireNonNull(ocspService.getAccessLocation(), "ocspResponderUri");
95110

96-
final CertificateID certificateId = getCertificateId(subjectCertificate,
97-
Objects.requireNonNull(trustValidator.getSubjectCertificateIssuerCertificate()));
111+
final CertificateID certificateId = getCertificateId(subjectCertificate, issuerCertificate);
98112

99113
final OCSPReq request = new OcspRequestBuilder()
100114
.withCertificateId(certificateId)
@@ -106,21 +120,27 @@ public void validateCertificateNotRevoked(X509Certificate subjectCertificate) th
106120
}
107121

108122
LOG.debug("Sending OCSP request");
109-
final OCSPResp response = Objects.requireNonNull(ocspClient.request(ocspService.getAccessLocation(), request));
123+
final OCSPResp response = requireNonNull(ocspClient.request(ocspResponderUri, request), "OCSPResp");
110124
if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
111-
throw new UserCertificateOCSPCheckFailedException("Response status: " + ocspStatusToString(response.getStatus()));
125+
throw new UserCertificateOCSPCheckFailedException("Response status: " + ocspStatusToString(response.getStatus()), ocspResponderUri);
112126
}
113127

114128
final BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
115129
if (basicResponse == null) {
116-
throw new UserCertificateOCSPCheckFailedException("Missing Basic OCSP Response");
130+
throw new UserCertificateOCSPCheckFailedException("Missing Basic OCSP Response", ocspResponderUri);
117131
}
132+
LOG.debug("OCSP response received successfully");
133+
118134
verifyOcspResponse(basicResponse, ocspService, certificateId);
119135
if (ocspService.doesSupportNonce()) {
120-
checkNonce(request, basicResponse);
136+
checkNonce(request, basicResponse, ocspResponderUri);
121137
}
138+
LOG.debug("OCSP response verified successfully");
139+
140+
return List.of(new RevocationInfo(ocspResponderUri, Map.of(RevocationInfo.KEY_OCSP_RESPONSE, response)));
141+
122142
} catch (OCSPException | CertificateException | OperatorCreationException | IOException e) {
123-
throw new UserCertificateOCSPCheckFailedException(e);
143+
throw new UserCertificateOCSPCheckFailedException(e, ocspResponderUri);
124144
}
125145
}
126146

@@ -137,11 +157,12 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
137157
// As we sent the request for only a single certificate, we expect only a single response.
138158
if (basicResponse.getResponses().length != 1) {
139159
throw new UserCertificateOCSPCheckFailedException("OCSP response must contain one response, "
140-
+ "received " + basicResponse.getResponses().length + " responses instead");
160+
+ "received " + basicResponse.getResponses().length + " responses instead", ocspService.getAccessLocation());
141161
}
142162
final SingleResp certStatusResponse = basicResponse.getResponses()[0];
143163
if (!requestCertificateId.equals(certStatusResponse.getCertID())) {
144-
throw new UserCertificateOCSPCheckFailedException("OCSP responded with certificate ID that differs from the requested ID");
164+
throw new UserCertificateOCSPCheckFailedException("OCSP responded with certificate ID that differs from the requested ID",
165+
ocspService.getAccessLocation());
145166
}
146167

147168
// 2. The signature on the response is valid.
@@ -151,11 +172,11 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
151172
// is standard practice.
152173
if (basicResponse.getCerts().length < 1) {
153174
throw new UserCertificateOCSPCheckFailedException("OCSP response must contain the responder certificate, "
154-
+ "but none was provided");
175+
+ "but none was provided", ocspService.getAccessLocation());
155176
}
156177
// The first certificate is the responder certificate, other certificates, if given, are the certificate's chain.
157178
final X509CertificateHolder responderCert = basicResponse.getCerts()[0];
158-
OcspResponseValidator.validateResponseSignature(basicResponse, responderCert);
179+
OcspResponseValidator.validateResponseSignature(basicResponse, responderCert, ocspService.getAccessLocation());
159180

160181
// 3. The identity of the signer matches the intended recipient of the
161182
// request.
@@ -174,23 +195,23 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
174195
// be available about the status of the certificate (nextUpdate) is
175196
// greater than the current time.
176197

177-
OcspResponseValidator.validateCertificateStatusUpdateTime(certStatusResponse, allowedOcspResponseTimeSkew, maxOcspResponseThisUpdateAge);
198+
OcspResponseValidator.validateCertificateStatusUpdateTime(certStatusResponse, allowedOcspResponseTimeSkew, maxOcspResponseThisUpdateAge, ocspService.getAccessLocation());
178199

179200
// Now we can accept the signed response as valid and validate the certificate status.
180-
OcspResponseValidator.validateSubjectCertificateStatus(certStatusResponse);
201+
OcspResponseValidator.validateSubjectCertificateStatus(certStatusResponse, ocspService.getAccessLocation());
181202
LOG.debug("OCSP check result is GOOD");
182203
}
183204

184-
private static void checkNonce(OCSPReq request, BasicOCSPResp response) throws UserCertificateOCSPCheckFailedException {
205+
private static void checkNonce(OCSPReq request, BasicOCSPResp response, URI ocspResponderUri) throws UserCertificateOCSPCheckFailedException {
185206
final Extension requestNonce = request.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
186207
final Extension responseNonce = response.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
187208
if (requestNonce == null || responseNonce == null) {
188209
throw new UserCertificateOCSPCheckFailedException("OCSP request or response nonce extension missing, " +
189-
"possible replay attack");
210+
"possible replay attack", ocspResponderUri);
190211
}
191212
if (!requestNonce.equals(responseNonce)) {
192213
throw new UserCertificateOCSPCheckFailedException("OCSP request and response nonces differ, " +
193-
"possible replay attack");
214+
"possible replay attack", ocspResponderUri);
194215
}
195216
}
196217

@@ -202,20 +223,14 @@ private static CertificateID getCertificateId(X509Certificate subjectCertificate
202223
}
203224

204225
private static String ocspStatusToString(int status) {
205-
switch (status) {
206-
case OCSPResp.MALFORMED_REQUEST:
207-
return "malformed request";
208-
case OCSPResp.INTERNAL_ERROR:
209-
return "internal error";
210-
case OCSPResp.TRY_LATER:
211-
return "service unavailable";
212-
case OCSPResp.SIG_REQUIRED:
213-
return "request signature missing";
214-
case OCSPResp.UNAUTHORIZED:
215-
return "unauthorized";
216-
default:
217-
return "unknown";
218-
}
226+
return switch (status) {
227+
case OCSPResp.MALFORMED_REQUEST -> "malformed request";
228+
case OCSPResp.INTERNAL_ERROR -> "internal error";
229+
case OCSPResp.TRY_LATER -> "service unavailable";
230+
case OCSPResp.SIG_REQUIRED -> "request signature missing";
231+
case OCSPResp.UNAUTHORIZED -> "unauthorized";
232+
default -> "unknown";
233+
};
219234
}
220235

221236
}

src/main/java/eu/webeid/security/validator/ocsp/OcspClient.java renamed to src/main/java/eu/webeid/ocsp/client/OcspClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.security.validator.ocsp;
23+
package eu.webeid.ocsp.client;
2424

2525
import org.bouncycastle.cert.ocsp.OCSPReq;
2626
import org.bouncycastle.cert.ocsp.OCSPResp;

src/main/java/eu/webeid/security/validator/ocsp/OcspClientImpl.java renamed to src/main/java/eu/webeid/ocsp/client/OcspClientImpl.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.security.validator.ocsp;
23+
package eu.webeid.ocsp.client;
2424

2525
import org.bouncycastle.cert.ocsp.OCSPReq;
2626
import org.bouncycastle.cert.ocsp.OCSPResp;
@@ -34,6 +34,9 @@
3434
import java.net.http.HttpResponse;
3535
import java.time.Duration;
3636

37+
import static eu.webeid.security.util.DateAndTime.requirePositiveDuration;
38+
import static java.util.Objects.requireNonNull;
39+
3740
public class OcspClientImpl implements OcspClient {
3841

3942
private static final Logger LOG = LoggerFactory.getLogger(OcspClientImpl.class);
@@ -45,6 +48,7 @@ public class OcspClientImpl implements OcspClient {
4548
private final Duration ocspRequestTimeout;
4649

4750
public static OcspClient build(Duration ocspRequestTimeout) {
51+
requirePositiveDuration(ocspRequestTimeout, "ocspRequestTimeout");
4852
return new OcspClientImpl(
4953
HttpClient.newBuilder()
5054
.connectTimeout(ocspRequestTimeout)
@@ -91,8 +95,8 @@ public OCSPResp request(URI uri, OCSPReq ocspReq) throws IOException {
9195
}
9296

9397
public OcspClientImpl(HttpClient httpClient, Duration ocspRequestTimeout) {
94-
this.httpClient = httpClient;
95-
this.ocspRequestTimeout = ocspRequestTimeout;
98+
this.httpClient = requireNonNull(httpClient, "httpClient");
99+
this.ocspRequestTimeout = requirePositiveDuration(ocspRequestTimeout, "ocspRequestTimeout");
96100
}
97101

98102
}

src/main/java/eu/webeid/security/exceptions/OCSPCertificateException.java renamed to src/main/java/eu/webeid/ocsp/exceptions/OCSPCertificateException.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.security.exceptions;
23+
package eu.webeid.ocsp.exceptions;
24+
25+
import eu.webeid.security.exceptions.AuthTokenException;
2426

2527
public class OCSPCertificateException extends AuthTokenException {
2628

src/main/java/eu/webeid/security/exceptions/UserCertificateOCSPCheckFailedException.java renamed to src/main/java/eu/webeid/ocsp/exceptions/UserCertificateOCSPCheckFailedException.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,34 @@
2020
* SOFTWARE.
2121
*/
2222

23-
package eu.webeid.security.exceptions;
23+
package eu.webeid.ocsp.exceptions;
24+
25+
import eu.webeid.security.exceptions.AuthTokenException;
26+
27+
import java.net.URI;
2428

2529
/**
2630
* Thrown when user certificate revocation check with OCSP fails.
2731
*/
2832
public class UserCertificateOCSPCheckFailedException extends AuthTokenException {
29-
public UserCertificateOCSPCheckFailedException(Throwable cause) {
33+
34+
private final URI ocspResponderUri;
35+
36+
public UserCertificateOCSPCheckFailedException(Throwable cause, URI ocspResponderUri) {
3037
super("User certificate revocation check has failed", cause);
38+
this.ocspResponderUri = ocspResponderUri;
3139
}
3240

33-
public UserCertificateOCSPCheckFailedException(String message) {
41+
public UserCertificateOCSPCheckFailedException(String message, URI ocspResponderUri) {
3442
super("User certificate revocation check has failed: " + message);
43+
this.ocspResponderUri = ocspResponderUri;
44+
}
45+
46+
public UserCertificateOCSPCheckFailedException(String message) {
47+
this(message, null);
48+
}
49+
50+
public URI getOcspResponderUri() {
51+
return ocspResponderUri;
3552
}
3653
}

0 commit comments

Comments
 (0)