3636import io .github .resilience4j .circuitbreaker .CircuitBreakerConfig ;
3737import io .github .resilience4j .retry .RetryConfig ;
3838import org .bouncycastle .cert .ocsp .BasicOCSPResp ;
39+ import org .bouncycastle .cert .ocsp .CertificateStatus ;
3940import org .bouncycastle .cert .ocsp .OCSPResp ;
4041import org .bouncycastle .cert .ocsp .RevokedStatus ;
4142import 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}
0 commit comments