diff --git a/sigstore/sign.py b/sigstore/sign.py index 2c0fe17c7..2036e4807 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -151,36 +151,35 @@ def _signing_cert( the returned certificate is present in Fulcio's CT log. """ - # Our CSR cannot possibly succeed if our underlying identity token - # is expired. - if not self._identity_token.in_validity_period(): - raise ExpiredIdentity - - # If it exists, verify if the current certificate is expired + # If a cached certificate exists, use it until it expires. if self.__cached_signing_certificate: not_valid_after = self.__cached_signing_certificate.not_valid_after_utc if datetime.now(timezone.utc) > not_valid_after: raise ExpiredCertificate return self.__cached_signing_certificate - else: - _logger.debug("Retrieving signed certificate...") + # Our CSR cannot possibly succeed if our underlying identity token + # is expired. + if not self._identity_token.in_validity_period(): + raise ExpiredIdentity + + _logger.debug("Retrieving signed certificate...") - certificate_request = self._build_csr() + certificate_request = self._build_csr() - certificate_response = self._signing_ctx._fulcio.signing_cert.post( - certificate_request, self._identity_token - ) + certificate_response = self._signing_ctx._fulcio.signing_cert.post( + certificate_request, self._identity_token + ) - verify_sct( - certificate_response.cert, - certificate_response.chain, - self._signing_ctx._trusted_root.ct_keyring(KeyringPurpose.SIGN), - ) + verify_sct( + certificate_response.cert, + certificate_response.chain, + self._signing_ctx._trusted_root.ct_keyring(KeyringPurpose.SIGN), + ) - _logger.debug("Successfully verified SCT...") + _logger.debug("Successfully verified SCT...") - return certificate_response.cert + return certificate_response.cert def _finalize_sign( self, diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index d488d756f..49e389bbf 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -72,6 +72,22 @@ def test_build_csr_non_ascii_identity_produces_valid_csr(): assert len(reparsed.subject) == 0 +@pytest.mark.staging +@pytest.mark.ambient_oidc +def test_signer_uses_valid_cached_certificate_with_expired_identity(staging): + ctx_cls, _, identity = staging + ctx: SigningContext = ctx_cls() + assert identity is not None + + # Constructing a cached signer obtains its certificate while the token is + # valid. Expire the local token afterward, then perform a real signing + # operation: it must reuse the still-valid cached certificate. + with ctx.signer(identity) as signer: + identity._exp = 0 + assert not signer._identity_token.in_validity_period() + signer.sign_artifact(secrets.token_bytes(32)) + + # only check the log contents for production: staging is already on # rekor v2 and we don't currently support log lookups on rekor v2. # This test can likely be removed once prod also uses rekor v2