Skip to content

Commit ef4ae35

Browse files
committed
AUT-2623 Fix and improve tests
1 parent de5b0b2 commit ef4ae35

File tree

3 files changed

+160
-9
lines changed

3 files changed

+160
-9
lines changed

src/test/java/eu/webeid/ocsp/protocol/OcspResponseValidatorTest.java

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

2525
import eu.webeid.ocsp.OcspCertificateRevocationChecker;
2626
import eu.webeid.ocsp.exceptions.UserCertificateOCSPCheckFailedException;
27+
import eu.webeid.ocsp.exceptions.UserCertificateRevokedException;
28+
import eu.webeid.ocsp.exceptions.UserCertificateUnknownException;
29+
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
30+
import org.bouncycastle.cert.ocsp.OCSPResp;
2731
import org.bouncycastle.cert.ocsp.SingleResp;
2832
import org.junit.jupiter.api.Test;
2933

@@ -33,7 +37,9 @@
3337
import java.time.temporal.ChronoUnit;
3438
import java.util.Date;
3539

40+
import static eu.webeid.ocsp.OcspCertificateRevocationCheckerTest.getOcspResponseBytesFromResources;
3641
import static eu.webeid.ocsp.protocol.OcspResponseValidator.validateCertificateStatusUpdateTime;
42+
import static eu.webeid.ocsp.protocol.OcspResponseValidator.validateSubjectCertificateStatus;
3743
import static org.assertj.core.api.Assertions.assertThatCode;
3844
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3945
import static org.mockito.Mockito.mock;
@@ -118,8 +124,32 @@ void whenNextUpdateHalfHourBeforeNow_thenThrows() {
118124
+ " (OCSP responder: https://example.org)");
119125
}
120126

127+
@Test
128+
void whenRejectUnknownOcspResponseStatusIsFalse_ThenUnknownStatusThrowsUserCertificateRevokedException() throws Exception {
129+
SingleResp unknownCertStatus = getUnknownCertStatusResponse();
130+
assertThatExceptionOfType(UserCertificateRevokedException.class)
131+
.isThrownBy(() ->
132+
validateSubjectCertificateStatus(unknownCertStatus, OCSP_URL, false))
133+
.withMessage("User certificate has been revoked: Unknown status (OCSP responder: https://example.org)");
134+
}
135+
136+
@Test
137+
void whenRejectUnknownOcspResponseStatusIsTrue_ThenUnknownStatusThrowsUserCertificateUnknownException() throws Exception {
138+
SingleResp unknownCertStatus = getUnknownCertStatusResponse();
139+
assertThatExceptionOfType(UserCertificateUnknownException.class)
140+
.isThrownBy(() ->
141+
validateSubjectCertificateStatus(unknownCertStatus, OCSP_URL, true))
142+
.withMessage("User certificate status is unknown: Unknown status (OCSP responder: https://example.org)");
143+
}
144+
121145
private static Date getThisUpdateWithinAgeLimit(Instant now) {
122146
return Date.from(now.minus(THIS_UPDATE_AGE.minusSeconds(1)));
123147
}
124148

149+
private static SingleResp getUnknownCertStatusResponse() throws Exception {
150+
final OCSPResp ocspRespUnknown = new OCSPResp(getOcspResponseBytesFromResources("ocsp_response_unknown.der"));
151+
final BasicOCSPResp basicResponse = (BasicOCSPResp) ocspRespUnknown.getResponseObject();
152+
return basicResponse.getResponses()[0];
153+
}
154+
125155
}

src/test/java/eu/webeid/resilientocsp/ResilientOcspCertificateRevocationCheckerTest.java

Lines changed: 128 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
3737
import io.github.resilience4j.retry.RetryConfig;
3838
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
39+
import org.bouncycastle.cert.ocsp.CertificateStatus;
3940
import org.bouncycastle.cert.ocsp.OCSPResp;
4041
import org.bouncycastle.cert.ocsp.RevokedStatus;
4142
import org.bouncycastle.cert.ocsp.SingleResp;
@@ -72,13 +73,16 @@ public class ResilientOcspCertificateRevocationCheckerTest {
7273

7374
private X509Certificate estEid2018Cert;
7475
private X509Certificate testEsteid2018CA;
76+
7577
private OCSPResp ocspRespGood;
78+
private OCSPResp ocspRespRevoked;
7679

7780
@BeforeEach
7881
void setUp() throws Exception {
7982
estEid2018Cert = getJaakKristjanEsteid2018Cert();
8083
testEsteid2018CA = getTestEsteid2018CA();
8184
ocspRespGood = new OCSPResp(getOcspResponseBytesFromResources("ocsp_response.der"));
85+
ocspRespRevoked = new OCSPResp(getOcspResponseBytesFromResources("ocsp_response_revoked.der"));
8286
}
8387

8488
@Test
@@ -133,8 +137,6 @@ void whenMultipleValidationCalls_thenPreviousResultsAreNotModified() throws Exce
133137

134138
@Test
135139
void whenFirstFallbackReturnsRevoked_thenRevocationPropagatesWithoutSecondFallback() throws Exception {
136-
OCSPResp ocspRespRevoked = new OCSPResp(getOcspResponseBytesFromResources("ocsp_response_revoked.der"));
137-
138140
OcspClient ocspClient = mock(OcspClient.class);
139141
when(ocspClient.request(eq(PRIMARY_URI), any()))
140142
.thenThrow(new OCSPClientException("Primary OCSP service unavailable"));
@@ -152,6 +154,25 @@ void whenFirstFallbackReturnsRevoked_thenRevocationPropagatesWithoutSecondFallba
152154
verify(ocspClient, never()).request(eq(SECOND_FALLBACK_URI), any());
153155
}
154156

157+
@Test
158+
void whenMaxAttemptsIsOneAndAllCallsFail_thenRevocationInfoListShouldHaveThreeElements() throws Exception {
159+
OcspClient ocspClient = mock(OcspClient.class);
160+
when(ocspClient.request(eq(PRIMARY_URI), any()))
161+
.thenThrow(new OCSPClientException());
162+
when(ocspClient.request(eq(FALLBACK_URI), any()))
163+
.thenThrow(new OCSPClientException());
164+
when(ocspClient.request(eq(SECOND_FALLBACK_URI), any()))
165+
.thenThrow(new OCSPClientException());
166+
167+
RetryConfig retryConfig = RetryConfig.custom()
168+
.maxAttempts(1)
169+
.build();
170+
171+
ResilientOcspCertificateRevocationChecker checker = buildChecker(ocspClient, retryConfig, false);
172+
ResilientUserCertificateOCSPCheckFailedException ex = assertThrows(ResilientUserCertificateOCSPCheckFailedException.class, () -> checker.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA));
173+
assertThat(ex.getValidationInfo().revocationInfoList().size()).isEqualTo(3);
174+
}
175+
155176
@Test
156177
void whenMaxAttemptsIsTwoAndAllCallsFail_thenRevocationInfoListShouldHaveFourElements() throws Exception {
157178
OcspClient ocspClient = mock(OcspClient.class);
@@ -179,6 +200,10 @@ void whenMaxAttemptsIsTwoAndFirstCallFails_thenTwoCallsToPrimaryShouldBeRecorded
179200
when(ocspClient.request(eq(PRIMARY_URI), any()))
180201
.thenThrow(new OCSPClientException("Primary OCSP service unavailable (call1)"))
181202
.thenReturn(ocspRespGood);
203+
when(ocspClient.request(eq(FALLBACK_URI), any()))
204+
.thenReturn(ocspRespRevoked);
205+
when(ocspClient.request(eq(SECOND_FALLBACK_URI), any()))
206+
.thenReturn(ocspRespRevoked);
182207

183208
RetryConfig retryConfig = RetryConfig.custom()
184209
.maxAttempts(2)
@@ -206,36 +231,124 @@ void whenFirstCallSucceeds_thenRevocationInfoListShouldHaveOneElementAndItShould
206231
OcspClient ocspClient = mock(OcspClient.class);
207232
when(ocspClient.request(eq(PRIMARY_URI), any()))
208233
.thenReturn(ocspRespGood);
234+
when(ocspClient.request(eq(FALLBACK_URI), any()))
235+
.thenReturn(ocspRespRevoked);
236+
when(ocspClient.request(eq(SECOND_FALLBACK_URI), any()))
237+
.thenReturn(ocspRespRevoked);
209238

210239
ResilientOcspCertificateRevocationChecker checker = buildChecker(ocspClient, null, false);
211240

212241
List<RevocationInfo> revocationInfoList = checker.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA);
213242
assertThat(revocationInfoList.size()).isEqualTo(1);
214243
Map<String, Object> responseAttributes = revocationInfoList.get(0).ocspResponseAttributes();
215244
OCSPResp ocspResp = (OCSPResp) responseAttributes.get("OCSP_RESPONSE");
216-
final BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
217-
final SingleResp certStatusResponse = basicResponse.getResponses()[0];
218-
assertThat(certStatusResponse.getCertStatus()).isEqualTo(org.bouncycastle.cert.ocsp.CertificateStatus.GOOD);
245+
CertificateStatus status = getCertificateStatus(ocspResp);
246+
assertThat(status).isEqualTo(org.bouncycastle.cert.ocsp.CertificateStatus.GOOD);
219247
}
220248

221249
@Test
222250
@Disabled("Primary supplier has allowThisUpdateInPast disabled and that is checked before revocation, " +
223251
"which results in ResilientUserCertificateOCSPCheckFailedException")
224252
void whenFirstCallResultsInRevoked_thenRevocationInfoListShouldHaveOneElementAndItShouldHaveRevokedStatus() throws Exception {
225253
OcspClient ocspClient = mock(OcspClient.class);
226-
OCSPResp ocspRespRevoked = new OCSPResp(getOcspResponseBytesFromResources("ocsp_response_revoked.der"));
227254
when(ocspClient.request(eq(PRIMARY_URI), any()))
228255
.thenReturn(ocspRespRevoked);
256+
when(ocspClient.request(eq(FALLBACK_URI), any()))
257+
.thenReturn(ocspRespGood);
258+
when(ocspClient.request(eq(SECOND_FALLBACK_URI), any()))
259+
.thenReturn(ocspRespGood);
229260

230261
ResilientOcspCertificateRevocationChecker checker = buildChecker(ocspClient, null, false);
231262
ResilientUserCertificateRevokedException ex = assertThrows(ResilientUserCertificateRevokedException.class, () -> checker.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA));
232263
List<RevocationInfo> revocationInfoList = ex.getValidationInfo().revocationInfoList();
233264
assertThat(revocationInfoList.size()).isEqualTo(1);
234265
Map<String, Object> responseAttributes = ex.getValidationInfo().revocationInfoList().get(0).ocspResponseAttributes();
235266
OCSPResp ocspResp = (OCSPResp) responseAttributes.get("OCSP_RESPONSE");
236-
final BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
237-
final SingleResp certStatusResponse = basicResponse.getResponses()[0];
238-
assertThat(certStatusResponse.getCertStatus()).isInstanceOf(RevokedStatus.class);
267+
CertificateStatus status = getCertificateStatus(ocspResp);
268+
assertThat(status).isInstanceOf(RevokedStatus.class);
269+
}
270+
271+
@Test
272+
void whenOneFallbackIsConfiguredAndPrimaryFails_thenRevocationInfoListShouldHaveTwoElements() throws Exception {
273+
OcspClient ocspClient = mock(OcspClient.class);
274+
when(ocspClient.request(eq(PRIMARY_URI), any()))
275+
.thenThrow(new OCSPClientException());
276+
when(ocspClient.request(eq(FALLBACK_URI), any()))
277+
.thenThrow(new OCSPClientException());
278+
279+
FallbackOcspService fallbackService = mock(FallbackOcspService.class);
280+
when(fallbackService.getAccessLocation()).thenReturn(FALLBACK_URI);
281+
when(fallbackService.doesSupportNonce()).thenReturn(false);
282+
when(fallbackService.getNextFallback()).thenReturn(null);
283+
284+
OcspService primaryService = mock(OcspService.class);
285+
when(primaryService.getAccessLocation()).thenReturn(PRIMARY_URI);
286+
when(primaryService.doesSupportNonce()).thenReturn(false);
287+
when(primaryService.getFallbackService()).thenReturn(fallbackService);
288+
289+
OcspServiceProvider ocspServiceProvider = mock(OcspServiceProvider.class);
290+
when(ocspServiceProvider.getService(any())).thenReturn(primaryService);
291+
292+
ResilientOcspCertificateRevocationChecker checker = new ResilientOcspCertificateRevocationChecker(
293+
ocspClient,
294+
ocspServiceProvider,
295+
CircuitBreakerConfig.ofDefaults(),
296+
null,
297+
OcspCertificateRevocationChecker.DEFAULT_TIME_SKEW,
298+
OcspCertificateRevocationChecker.DEFAULT_THIS_UPDATE_AGE,
299+
false
300+
);
301+
302+
ResilientUserCertificateOCSPCheckFailedException ex = assertThrows(ResilientUserCertificateOCSPCheckFailedException.class, () -> checker.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA));
303+
List<RevocationInfo> revocationInfoList = ex.getValidationInfo().revocationInfoList();
304+
assertThat(revocationInfoList.size()).isEqualTo(2);
305+
}
306+
307+
@Test
308+
void whenNoFallbacksAreConfigured_thenRevocationInfoListShouldHaveOneElement() throws Exception {
309+
OcspClient ocspClient = mock(OcspClient.class);
310+
when(ocspClient.request(eq(PRIMARY_URI), any()))
311+
.thenThrow(new OCSPClientException());
312+
when(ocspClient.request(eq(FALLBACK_URI), any()))
313+
.thenThrow(new OCSPClientException());
314+
315+
OcspService primaryService = mock(OcspService.class);
316+
when(primaryService.getAccessLocation()).thenReturn(PRIMARY_URI);
317+
when(primaryService.doesSupportNonce()).thenReturn(false);
318+
when(primaryService.getFallbackService()).thenReturn(null);
319+
320+
OcspServiceProvider ocspServiceProvider = mock(OcspServiceProvider.class);
321+
when(ocspServiceProvider.getService(any())).thenReturn(primaryService);
322+
323+
ResilientOcspCertificateRevocationChecker checker = new ResilientOcspCertificateRevocationChecker(
324+
ocspClient,
325+
ocspServiceProvider,
326+
CircuitBreakerConfig.ofDefaults(),
327+
null,
328+
OcspCertificateRevocationChecker.DEFAULT_TIME_SKEW,
329+
OcspCertificateRevocationChecker.DEFAULT_THIS_UPDATE_AGE,
330+
false
331+
);
332+
333+
ResilientUserCertificateOCSPCheckFailedException ex = assertThrows(ResilientUserCertificateOCSPCheckFailedException.class, () -> checker.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA));
334+
List<RevocationInfo> revocationInfoList = ex.getValidationInfo().revocationInfoList();
335+
assertThat(revocationInfoList.size()).isEqualTo(1);
336+
}
337+
338+
@Test
339+
void whenOcspResponseStatusIsUnauthorized_thenThrows() throws Exception {
340+
OCSPResp ocspRespStatusUnauthorized = new OCSPResp(getOcspResponseBytesFromResources("ocsp_response_unauthorized.der"));
341+
342+
OcspClient ocspClient = mock(OcspClient.class);
343+
when(ocspClient.request(eq(PRIMARY_URI), any()))
344+
.thenReturn(ocspRespStatusUnauthorized);
345+
346+
ResilientOcspCertificateRevocationChecker checker = buildChecker(ocspClient, null, false);
347+
ResilientUserCertificateOCSPCheckFailedException ex = assertThrows(ResilientUserCertificateOCSPCheckFailedException.class, () -> checker.validateCertificateNotRevoked(estEid2018Cert, testEsteid2018CA));
348+
349+
Map<String, Object> responseAttributes = ex.getValidationInfo().revocationInfoList().get(0).ocspResponseAttributes();
350+
ResilientUserCertificateOCSPCheckFailedException firstException = (ResilientUserCertificateOCSPCheckFailedException) responseAttributes.get(RevocationInfo.KEY_OCSP_ERROR);
351+
assertThat(firstException.getMessage()).isEqualTo("Response status: unauthorized");
239352
}
240353

241354
private ResilientOcspCertificateRevocationChecker buildChecker(OcspClient ocspClient, RetryConfig retryConfig, boolean rejectUnknownOcspResponseStatus) throws Exception {
@@ -266,4 +379,10 @@ private ResilientOcspCertificateRevocationChecker buildChecker(OcspClient ocspCl
266379
rejectUnknownOcspResponseStatus
267380
);
268381
}
382+
383+
private CertificateStatus getCertificateStatus(OCSPResp ocspResp) throws Exception {
384+
final BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
385+
final SingleResp certStatusResponse = basicResponse.getResponses()[0];
386+
return certStatusResponse.getCertStatus();
387+
}
269388
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
0
2+


0 commit comments

Comments
 (0)