@@ -650,6 +650,99 @@ def test_cross_signed_certificate_chain(cert_gen, session_manager):
650650 assert validator .validate_certificate_chains (chains )
651651
652652
653+ def test_starfield_incident (cert_gen , session_manager ):
654+ # leaf is signed by A, who is signed by CA (revoked) and B, who is also a trusted CA
655+ chain = cert_gen .create_cross_signed_chain ()
656+ validator = CRLValidator (
657+ session_manager ,
658+ cert_revocation_check_mode = CertRevocationCheckMode .ENABLED ,
659+ trusted_certificates = [cert_gen .ca_certificate , chain .rootB ],
660+ )
661+
662+ def mock_validate (cert , _ ):
663+ if cert == chain .rootA :
664+ return CRLValidationResult .REVOKED
665+ return CRLValidationResult .UNREVOKED
666+
667+ validator ._validate_certificate = mock_validate
668+
669+ assert (
670+ validator ._validate_single_chain ([chain .leafA , chain .BsignA , chain .rootA ])
671+ == CRLValidationResult .UNREVOKED
672+ )
673+
674+
675+ def test_validate_single_chain (cert_gen , session_manager ):
676+ chain = cert_gen .create_cross_signed_chain ()
677+ validator = CRLValidator (
678+ session_manager ,
679+ cert_revocation_check_mode = CertRevocationCheckMode .ENABLED ,
680+ trusted_certificates = [cert_gen .ca_certificate ],
681+ )
682+
683+ input_chain = [chain .leafA , chain .leafB , chain .rootA , chain .rootB ]
684+
685+ # case 1: at least one valid path
686+ def mock_validate_with_special_cert (revoked_cert , error_result ):
687+ validator ._validate_certificate_with_cache = lambda cert , _ : (
688+ error_result if cert == revoked_cert else CRLValidationResult .UNREVOKED
689+ )
690+
691+ for error_result in [CRLValidationResult .ERROR , CRLValidationResult .REVOKED ]:
692+ for revoked_cert in [chain .rootA , chain .rootB , chain .leafA , chain .leafB ]:
693+ mock_validate_with_special_cert (revoked_cert , error_result )
694+ assert (
695+ validator ._validate_single_chain (input_chain )
696+ == CRLValidationResult .UNREVOKED
697+ )
698+
699+ # case 2: all paths revoked
700+ def mock_validate (cert , _ ):
701+ if cert in [chain .rootA , chain .rootB ]:
702+ return CRLValidationResult .REVOKED
703+ return CRLValidationResult .UNREVOKED
704+
705+ validator ._validate_certificate_with_cache = mock_validate
706+ assert validator ._validate_single_chain (input_chain ) == CRLValidationResult .REVOKED
707+
708+ # case 3: revoked + error should result in revoked\
709+ def mock_validate (cert , _ ):
710+ if cert in [chain .rootA , chain .leafB ]:
711+ return CRLValidationResult .REVOKED
712+ return CRLValidationResult .ERROR
713+
714+ validator ._validate_certificate_with_cache = mock_validate
715+ assert validator ._validate_single_chain (input_chain ) == CRLValidationResult .REVOKED
716+
717+ # case 4: no path to trusted certificate
718+ def mock_validate (cert , _ ):
719+ return CRLValidationResult .UNREVOKED
720+
721+ validator ._validate_certificate_with_cache = mock_validate
722+ assert (
723+ validator ._validate_single_chain (
724+ [chain .leafA , chain .leafB , chain .AsignB , chain .BsignA ]
725+ )
726+ == CRLValidationResult .ERROR
727+ )
728+
729+ # case 5: only unrevoked path has an error
730+ def mock_validate (cert , _ ):
731+ if cert in [chain .rootA , chain .leafB ]:
732+ return CRLValidationResult .REVOKED
733+ if cert == chain .BsignA :
734+ return CRLValidationResult .ERROR
735+ return CRLValidationResult .UNREVOKED
736+
737+ validator ._validate_certificate_with_cache = mock_validate
738+ assert (
739+ validator ._validate_single_chain (
740+ [chain .leafA , chain .rootA , chain .leafB , chain .rootB , chain .BsignA ]
741+ )
742+ == CRLValidationResult .ERROR
743+ )
744+
745+
653746@responses .activate
654747def test_should_validate_non_revoked_certificate_successfully (
655748 cert_gen , crl_urls , session_manager
0 commit comments