Skip to content

Commit f53369c

Browse files
committed
feat(OCSP): add AuthTokenValidatorBuilder.withOcspClient() for overriding the OCSP client. Fixes #31
1 parent ad8798d commit f53369c

File tree

6 files changed

+99
-14
lines changed

6 files changed

+99
-14
lines changed

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
package eu.webeid.security.validator;
2424

2525
import eu.webeid.security.exceptions.JceException;
26+
import eu.webeid.security.validator.ocsp.OcspClient;
27+
import eu.webeid.security.validator.ocsp.OcspClientImpl;
2628
import eu.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
2729
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
2830
import org.slf4j.Logger;
@@ -42,6 +44,7 @@ public class AuthTokenValidatorBuilder {
4244
private static final Logger LOG = LoggerFactory.getLogger(AuthTokenValidatorBuilder.class);
4345

4446
private final AuthTokenValidationConfiguration configuration = new AuthTokenValidationConfiguration();
47+
private OcspClient ocspClient;
4548

4649
/**
4750
* Sets the expected site origin, i.e. the domain that the application is running on.
@@ -152,6 +155,19 @@ public AuthTokenValidatorBuilder withDesignatedOcspServiceConfiguration(Designat
152155
return this;
153156
}
154157

158+
/**
159+
* Uses the provided OCSP client instance during user certificate revocation check with OCSP.
160+
* The provided client instance must be thread-safe.
161+
*
162+
* @param ocspClient OCSP client instance
163+
* @return the builder instance for method chaining
164+
*/
165+
public AuthTokenValidatorBuilder withOcspClient(OcspClient ocspClient) {
166+
this.ocspClient = ocspClient;
167+
LOG.debug("Using the OCSP client provided by API consumer");
168+
return this;
169+
}
170+
155171
/**
156172
* Validates the configuration and builds the {@link AuthTokenValidator} object with it.
157173
* The returned {@link AuthTokenValidator} object is immutable/thread-safe.
@@ -163,7 +179,10 @@ public AuthTokenValidatorBuilder withDesignatedOcspServiceConfiguration(Designat
163179
*/
164180
public AuthTokenValidator build() throws NullPointerException, IllegalArgumentException, JceException {
165181
configuration.validate();
166-
return new AuthTokenValidatorImpl(configuration);
182+
if (ocspClient == null) {
183+
ocspClient = OcspClientImpl.build(configuration.getOcspRequestTimeout());
184+
}
185+
return new AuthTokenValidatorImpl(configuration, ocspClient);
167186
}
168187

169188
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import eu.webeid.security.validator.certvalidators.SubjectCertificateTrustedValidator;
3737
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
3838
import eu.webeid.security.validator.ocsp.OcspClient;
39-
import eu.webeid.security.validator.ocsp.OcspClientImpl;
4039
import eu.webeid.security.validator.ocsp.OcspServiceProvider;
4140
import eu.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
4241
import org.slf4j.Logger;
@@ -73,8 +72,9 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
7372

7473
/**
7574
* @param configuration configuration parameters for the token validator
75+
* @param ocspClient client for communicating with the OCSP service
7676
*/
77-
AuthTokenValidatorImpl(AuthTokenValidationConfiguration configuration) throws JceException {
77+
AuthTokenValidatorImpl(AuthTokenValidationConfiguration configuration, OcspClient ocspClient) throws JceException {
7878
// Copy the configuration object to make AuthTokenValidatorImpl immutable and thread-safe.
7979
this.configuration = configuration.copy();
8080

@@ -89,7 +89,8 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
8989
);
9090

9191
if (configuration.isUserCertificateRevocationCheckWithOcspEnabled()) {
92-
ocspClient = OcspClientImpl.build(configuration.getOcspRequestTimeout());
92+
// The OCSP client may be provided by the API consumer.
93+
this.ocspClient = ocspClient;
9394
ocspServiceProvider = new OcspServiceProvider(
9495
configuration.getDesignatedOcspServiceConfiguration(),
9596
new AiaOcspServiceConfiguration(configuration.getNonceDisabledOcspUrls(),

src/test/java/eu/webeid/security/testutil/AuthTokenValidators.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import eu.webeid.security.exceptions.OCSPCertificateException;
2828
import eu.webeid.security.validator.AuthTokenValidator;
2929
import eu.webeid.security.validator.AuthTokenValidatorBuilder;
30+
import eu.webeid.security.validator.ocsp.OcspClient;
3031
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
3132

3233
import java.io.IOException;
@@ -59,6 +60,12 @@ public static AuthTokenValidator getAuthTokenValidator(String url, X509Certifica
5960
.build();
6061
}
6162

63+
public static AuthTokenValidator getAuthTokenValidatorWithOverriddenOcspClient(OcspClient ocspClient) throws CertificateException, JceException, IOException {
64+
return getAuthTokenValidatorBuilder(TOKEN_ORIGIN_URL, getCACertificates())
65+
.withOcspClient(ocspClient)
66+
.build();
67+
}
68+
6269
public static AuthTokenValidator getAuthTokenValidatorWithOcspCheck() throws CertificateException, JceException, IOException {
6370
return getAuthTokenValidatorBuilder(TOKEN_ORIGIN_URL, getCACertificates())
6471
.build();

src/test/java/eu/webeid/security/validator/AuthTokenCertificateTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ void whenCertificateFieldIsEmpty_thenParsingFails() throws AuthTokenException {
9191
}
9292

9393
@Test
94-
void whenCertificateFieldIsArray_thenParsingFails() throws AuthTokenException {
94+
void whenCertificateFieldIsArray_thenParsingFails() {
9595
assertThatThrownBy(() -> replaceTokenField(AUTH_TOKEN, "\"X5C\"", "[1,2,3,4]"))
9696
.isInstanceOf(AuthTokenParseException.class)
9797
.hasMessage("Error parsing Web eID authentication token")
98-
.getCause()
98+
.cause()
9999
.isInstanceOf(MismatchedInputException.class)
100100
.hasMessageStartingWith("Cannot deserialize value of type `java.lang.String` from Array value");
101101
}
@@ -106,7 +106,7 @@ void whenCertificateFieldIsNumber_thenParsingFails() throws AuthTokenException {
106106
assertThatThrownBy(() -> validator
107107
.validate(token, VALID_CHALLENGE_NONCE))
108108
.isInstanceOf(CertificateDecodingException.class)
109-
.getCause()
109+
.cause()
110110
.isInstanceOf(CertificateException.class)
111111
.hasMessage("Could not parse certificate: java.io.IOException: Empty input");
112112
}
@@ -117,7 +117,7 @@ void whenCertificateFieldIsNotBase64_thenParsingFails() throws AuthTokenExceptio
117117
assertThatThrownBy(() -> validator
118118
.validate(token, VALID_CHALLENGE_NONCE))
119119
.isInstanceOf(CertificateDecodingException.class)
120-
.getCause()
120+
.cause()
121121
.isInstanceOf(IllegalArgumentException.class)
122122
.hasMessage("Illegal base64 character 20");
123123
}
@@ -128,7 +128,7 @@ void whenCertificateFieldIsNotCertificate_thenParsingFails() throws AuthTokenExc
128128
assertThatThrownBy(() -> validator
129129
.validate(token, VALID_CHALLENGE_NONCE))
130130
.isInstanceOf(CertificateDecodingException.class)
131-
.getCause()
131+
.cause()
132132
.isInstanceOf(CertificateException.class)
133133
.hasMessage("Could not parse certificate: java.io.IOException: Empty input");
134134
}

src/test/java/eu/webeid/security/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ void whenOcspUrlIsInvalid_thenThrows() throws Exception {
102102
assertThatCode(() ->
103103
validator.validateCertificateNotRevoked(estEid2018Cert))
104104
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
105-
.getCause()
105+
.cause()
106106
.isInstanceOf(IOException.class)
107107
.hasMessageMatching("invalid.invalid: (Name or service not known|"
108108
+ "Temporary failure in name resolution)");
@@ -115,7 +115,7 @@ void whenOcspRequestFails_thenThrows() throws Exception {
115115
assertThatCode(() ->
116116
validator.validateCertificateNotRevoked(estEid2018Cert))
117117
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
118-
.getCause()
118+
.cause()
119119
.isInstanceOf(IOException.class)
120120
.hasMessageStartingWith("OCSP request was not successful, response: Response{");
121121
}
@@ -129,7 +129,7 @@ void whenOcspRequestHasInvalidBody_thenThrows() throws Exception {
129129
assertThatCode(() ->
130130
validator.validateCertificateNotRevoked(estEid2018Cert))
131131
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
132-
.getCause()
132+
.cause()
133133
.isInstanceOf(IOException.class)
134134
.hasMessage("DEF length 110 object truncated by 105");
135135
}
@@ -179,7 +179,7 @@ void whenOcspResponseHasInvalidResponderCert_thenThrows() throws Exception {
179179
assertThatCode(() ->
180180
validator.validateCertificateNotRevoked(estEid2018Cert))
181181
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
182-
.getCause()
182+
.cause()
183183
.isInstanceOf(OCSPException.class)
184184
.hasMessage("exception processing sig: java.lang.IllegalArgumentException: invalid info structure in RSA public key");
185185
}
@@ -193,7 +193,7 @@ void whenOcspResponseHasInvalidTag_thenThrows() throws Exception {
193193
assertThatCode(() ->
194194
validator.validateCertificateNotRevoked(estEid2018Cert))
195195
.isInstanceOf(UserCertificateOCSPCheckFailedException.class)
196-
.getCause()
196+
.cause()
197197
.isInstanceOf(OCSPException.class)
198198
.hasMessage("problem decoding object: java.io.IOException: unknown tag 23 encountered");
199199
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2022 Estonian Information System Authority
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
package eu.webeid.security.validator.ocsp;
24+
25+
import eu.webeid.security.exceptions.JceException;
26+
import eu.webeid.security.testutil.AbstractTestWithValidator;
27+
import eu.webeid.security.testutil.AuthTokenValidators;
28+
import eu.webeid.security.validator.AuthTokenValidator;
29+
import org.bouncycastle.cert.ocsp.OCSPReq;
30+
import org.bouncycastle.cert.ocsp.OCSPResp;
31+
import org.junit.jupiter.api.Test;
32+
33+
import java.io.IOException;
34+
import java.net.URI;
35+
import java.security.cert.CertificateException;
36+
37+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
38+
39+
class OcspClientOverrideTest extends AbstractTestWithValidator {
40+
41+
@Test
42+
void whenOcspClientIsOverridden_thenItIsUsed() throws JceException, CertificateException, IOException {
43+
final AuthTokenValidator validator = AuthTokenValidators.getAuthTokenValidatorWithOverriddenOcspClient(new OcpClientThatThrows());
44+
assertThatThrownBy(() -> validator.validate(validAuthToken, VALID_CHALLENGE_NONCE))
45+
.cause()
46+
.isInstanceOf(OcpClientThatThrowsException.class);
47+
}
48+
49+
private static class OcpClientThatThrows implements OcspClient {
50+
@Override
51+
public OCSPResp request(URI url, OCSPReq request) throws IOException {
52+
throw new OcpClientThatThrowsException();
53+
}
54+
}
55+
56+
private static class OcpClientThatThrowsException extends IOException {
57+
}
58+
}

0 commit comments

Comments
 (0)