Skip to content

Commit 9f7fb95

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 9f7fb95

Some content is hidden

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

43 files changed

+678
-569
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: 66 additions & 53 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;
2630
import eu.webeid.security.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,19 +51,26 @@
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;
@@ -71,30 +80,33 @@ public final class SubjectCertificateNotRevokedValidator {
7180
Security.addProvider(new BouncyCastleProvider());
7281
}
7382

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;
83+
public DefaultOcspCertificateRevocationChecker(OcspClient ocspClient,
84+
OcspServiceProvider ocspServiceProvider,
85+
Duration allowedOcspResponseTimeSkew,
86+
Duration maxOcspResponseThisUpdateAge) {
87+
this.ocspClient = requireNonNull(ocspClient, "ocspClient");
88+
this.ocspServiceProvider = requireNonNull(ocspServiceProvider, "ocspServiceProvider");
89+
this.allowedOcspResponseTimeSkew = requirePositiveDuration(allowedOcspResponseTimeSkew, "allowedOcspResponseTimeSkew");
90+
this.maxOcspResponseThisUpdateAge = requirePositiveDuration(maxOcspResponseThisUpdateAge, "maxOcspResponseThisUpdateAge");
8491
}
8592

8693
/**
87-
* Validates that the user certificate from the authentication token is not revoked with OCSP.
94+
* Validates with OCSP that the user certificate from the authentication token is not revoked.
8895
*
8996
* @param subjectCertificate user certificate to be validated
9097
* @throws AuthTokenException when user certificate is revoked or revocation check fails.
9198
*/
92-
public void validateCertificateNotRevoked(X509Certificate subjectCertificate) throws AuthTokenException {
99+
@Override
100+
public List<RevocationInfo> validateCertificateNotRevoked(X509Certificate subjectCertificate, X509Certificate issuerCertificate) throws AuthTokenException {
101+
requireNonNull(subjectCertificate, "subjectCertificate");
102+
requireNonNull(issuerCertificate, "issuerCertificate");
103+
104+
URI ocspResponderUri = null;
93105
try {
94106
OcspService ocspService = ocspServiceProvider.getService(subjectCertificate);
107+
ocspResponderUri = requireNonNull(ocspService.getAccessLocation(), "ocspResponderUri");
95108

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

99111
final OCSPReq request = new OcspRequestBuilder()
100112
.withCertificateId(certificateId)
@@ -106,21 +118,27 @@ public void validateCertificateNotRevoked(X509Certificate subjectCertificate) th
106118
}
107119

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

114126
final BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
115127
if (basicResponse == null) {
116-
throw new UserCertificateOCSPCheckFailedException("Missing Basic OCSP Response");
128+
throw new UserCertificateOCSPCheckFailedException("Missing Basic OCSP Response", ocspResponderUri);
117129
}
130+
LOG.debug("OCSP response received successfully");
131+
118132
verifyOcspResponse(basicResponse, ocspService, certificateId);
119133
if (ocspService.doesSupportNonce()) {
120-
checkNonce(request, basicResponse);
134+
checkNonce(request, basicResponse, ocspResponderUri);
121135
}
136+
LOG.debug("OCSP response verified successfully");
137+
138+
return List.of(new RevocationInfo(ocspResponderUri, Map.of(RevocationInfo.KEY_OCSP_RESPONSE, response)));
139+
122140
} catch (OCSPException | CertificateException | OperatorCreationException | IOException e) {
123-
throw new UserCertificateOCSPCheckFailedException(e);
141+
throw new UserCertificateOCSPCheckFailedException(e, ocspResponderUri);
124142
}
125143
}
126144

@@ -137,11 +155,12 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
137155
// As we sent the request for only a single certificate, we expect only a single response.
138156
if (basicResponse.getResponses().length != 1) {
139157
throw new UserCertificateOCSPCheckFailedException("OCSP response must contain one response, "
140-
+ "received " + basicResponse.getResponses().length + " responses instead");
158+
+ "received " + basicResponse.getResponses().length + " responses instead", ocspService.getAccessLocation());
141159
}
142160
final SingleResp certStatusResponse = basicResponse.getResponses()[0];
143161
if (!requestCertificateId.equals(certStatusResponse.getCertID())) {
144-
throw new UserCertificateOCSPCheckFailedException("OCSP responded with certificate ID that differs from the requested ID");
162+
throw new UserCertificateOCSPCheckFailedException("OCSP responded with certificate ID that differs from the requested ID",
163+
ocspService.getAccessLocation());
145164
}
146165

147166
// 2. The signature on the response is valid.
@@ -151,11 +170,11 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
151170
// is standard practice.
152171
if (basicResponse.getCerts().length < 1) {
153172
throw new UserCertificateOCSPCheckFailedException("OCSP response must contain the responder certificate, "
154-
+ "but none was provided");
173+
+ "but none was provided", ocspService.getAccessLocation());
155174
}
156175
// The first certificate is the responder certificate, other certificates, if given, are the certificate's chain.
157176
final X509CertificateHolder responderCert = basicResponse.getCerts()[0];
158-
OcspResponseValidator.validateResponseSignature(basicResponse, responderCert);
177+
OcspResponseValidator.validateResponseSignature(basicResponse, responderCert, ocspService.getAccessLocation());
159178

160179
// 3. The identity of the signer matches the intended recipient of the
161180
// request.
@@ -174,23 +193,23 @@ private void verifyOcspResponse(BasicOCSPResp basicResponse, OcspService ocspSer
174193
// be available about the status of the certificate (nextUpdate) is
175194
// greater than the current time.
176195

177-
OcspResponseValidator.validateCertificateStatusUpdateTime(certStatusResponse, allowedOcspResponseTimeSkew, maxOcspResponseThisUpdateAge);
196+
OcspResponseValidator.validateCertificateStatusUpdateTime(certStatusResponse, allowedOcspResponseTimeSkew, maxOcspResponseThisUpdateAge, ocspService.getAccessLocation());
178197

179198
// Now we can accept the signed response as valid and validate the certificate status.
180-
OcspResponseValidator.validateSubjectCertificateStatus(certStatusResponse);
199+
OcspResponseValidator.validateSubjectCertificateStatus(certStatusResponse, ocspService.getAccessLocation());
181200
LOG.debug("OCSP check result is GOOD");
182201
}
183202

184-
private static void checkNonce(OCSPReq request, BasicOCSPResp response) throws UserCertificateOCSPCheckFailedException {
203+
private static void checkNonce(OCSPReq request, BasicOCSPResp response, URI ocspResponderUri) throws UserCertificateOCSPCheckFailedException {
185204
final Extension requestNonce = request.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
186205
final Extension responseNonce = response.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
187206
if (requestNonce == null || responseNonce == null) {
188207
throw new UserCertificateOCSPCheckFailedException("OCSP request or response nonce extension missing, " +
189-
"possible replay attack");
208+
"possible replay attack", ocspResponderUri);
190209
}
191210
if (!requestNonce.equals(responseNonce)) {
192211
throw new UserCertificateOCSPCheckFailedException("OCSP request and response nonces differ, " +
193-
"possible replay attack");
212+
"possible replay attack", ocspResponderUri);
194213
}
195214
}
196215

@@ -202,20 +221,14 @@ private static CertificateID getCertificateId(X509Certificate subjectCertificate
202221
}
203222

204223
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-
}
224+
return switch (status) {
225+
case OCSPResp.MALFORMED_REQUEST -> "malformed request";
226+
case OCSPResp.INTERNAL_ERROR -> "internal error";
227+
case OCSPResp.TRY_LATER -> "service unavailable";
228+
case OCSPResp.SIG_REQUIRED -> "request signature missing";
229+
case OCSPResp.UNAUTHORIZED -> "unauthorized";
230+
default -> "unknown";
231+
};
219232
}
220233

221234
}

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/validator/ocsp/DigestCalculatorImpl.java renamed to src/main/java/eu/webeid/ocsp/protocol/DigestCalculatorImpl.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.protocol;
2424

2525
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
2626
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;

src/main/java/eu/webeid/security/validator/ocsp/OcspRequestBuilder.java renamed to src/main/java/eu/webeid/ocsp/protocol/OcspRequestBuilder.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.protocol;
2424

2525
import org.bouncycastle.asn1.DEROctetString;
2626
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;

0 commit comments

Comments
 (0)