Skip to content

Commit ff30b09

Browse files
Refactor signing API into SigningContext and Signer
Signed-off-by: Maya Costantini <[email protected]>
1 parent 3a0f6b7 commit ff30b09

File tree

5 files changed

+196
-95
lines changed

5 files changed

+196
-95
lines changed

sigstore/_cli.py

+55-37
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@
2828

2929
from sigstore import __version__
3030
from sigstore._internal.ctfe import CTKeyring
31-
from sigstore._internal.fulcio.client import DEFAULT_FULCIO_URL, FulcioClient
31+
from sigstore._internal.fulcio.client import (
32+
DEFAULT_FULCIO_URL,
33+
ExpiredCertificate,
34+
FulcioClient,
35+
)
3236
from sigstore._internal.keyring import Keyring
37+
from sigstore._internal.oidc import ExpiredIdentity
3338
from sigstore._internal.rekor.client import (
3439
DEFAULT_REKOR_URL,
3540
RekorClient,
@@ -44,7 +49,7 @@
4449
Issuer,
4550
detect_credential,
4651
)
47-
from sigstore.sign import Signer
52+
from sigstore.sign import SigningContext
4853
from sigstore.transparency import LogEntry
4954
from sigstore.verify import (
5055
CertificateVerificationFailure,
@@ -330,10 +335,11 @@ def _parser() -> argparse.ArgumentParser:
330335
help="Overwrite preexisting signature and certificate outputs, if present",
331336
)
332337
output_options.add_argument(
333-
"--single-cert",
338+
"--no-cache",
339+
dest="no_cache",
334340
action="store_true",
335-
default=_boolify_env("SIGSTORE_SINGLE_CERT"),
336-
help="Use a single signing certificate and key to sign multiple artifacts",
341+
default=_boolify_env("SIGSTORE_NO_CACHE"),
342+
help="Generate a new signing certificate and private key for each artifact signed",
337343
)
338344

339345
instance_options = sign.add_argument_group("Sigstore instance options")
@@ -617,13 +623,13 @@ def _sign(args: argparse.Namespace) -> None:
617623
"bundle": bundle,
618624
}
619625

620-
# Select the signer to use.
626+
# Select the signing context to use.
621627
if args.staging:
622628
logger.debug("sign: staging instances requested")
623-
signer = Signer.staging(single_certificate=args.single_cert)
629+
signing_ctx = SigningContext.staging()
624630
args.oidc_issuer = STAGING_OAUTH_ISSUER_URL
625631
elif args.fulcio_url == DEFAULT_FULCIO_URL and args.rekor_url == DEFAULT_REKOR_URL:
626-
signer = Signer.production(single_certificate=args.single_cert)
632+
signing_ctx = SigningContext.production()
627633
else:
628634
# Assume "production" keys if none are given as arguments
629635
updater = TrustUpdater.production()
@@ -639,10 +645,9 @@ def _sign(args: argparse.Namespace) -> None:
639645
ct_keyring = CTKeyring(Keyring(ctfe_keys))
640646
rekor_keyring = RekorKeyring(Keyring(rekor_keys))
641647

642-
signer = Signer(
648+
signing_ctx = SigningContext(
643649
fulcio=FulcioClient(args.fulcio_url),
644650
rekor=RekorClient(args.rekor_url, rekor_keyring, ct_keyring),
645-
single_certificate=args.single_cert,
646651
)
647652

648653
# The order of precedence is as follows:
@@ -655,38 +660,51 @@ def _sign(args: argparse.Namespace) -> None:
655660
if not args.identity_token:
656661
args._parser.error("No identity token supplied or detected!")
657662

658-
for file, outputs in output_map.items():
659-
logger.debug(f"signing for {file.name}")
660-
with file.open(mode="rb", buffering=0) as io:
661-
result = signer.sign(
662-
input_=io,
663-
identity_token=args.identity_token,
663+
cache = not args.no_cache
664+
with signing_ctx.with_signer(
665+
identity_token=args.identity_token, cache=cache
666+
) as signer:
667+
for file, outputs in output_map.items():
668+
logger.debug(f"signing for {file.name}")
669+
with file.open(mode="rb", buffering=0) as io:
670+
try:
671+
result = signer.sign(
672+
input_=io, rekor=signing_ctx._rekor, fulcio=signing_ctx._fulcio
673+
)
674+
except ExpiredIdentity as exp_identity:
675+
print("Signature failed: identity token has expired")
676+
raise exp_identity
677+
678+
except ExpiredCertificate as exp_certificate:
679+
print("Signature failed: Fulcio signing certificate has expired")
680+
raise exp_certificate
681+
682+
print("Using ephemeral certificate:")
683+
print(result.cert_pem)
684+
685+
print(
686+
f"Transparency log entry created at index: {result.log_entry.log_index}"
664687
)
665688

666-
print("Using ephemeral certificate:")
667-
print(result.cert_pem)
668-
669-
print(f"Transparency log entry created at index: {result.log_entry.log_index}")
670-
671-
sig_output: TextIO
672-
if outputs["sig"] is not None:
673-
sig_output = outputs["sig"].open("w")
674-
else:
675-
sig_output = sys.stdout
689+
sig_output: TextIO
690+
if outputs["sig"] is not None:
691+
sig_output = outputs["sig"].open("w")
692+
else:
693+
sig_output = sys.stdout
676694

677-
print(result.b64_signature, file=sig_output)
678-
if outputs["sig"] is not None:
679-
print(f"Signature written to {outputs['sig']}")
695+
print(result.b64_signature, file=sig_output)
696+
if outputs["sig"] is not None:
697+
print(f"Signature written to {outputs['sig']}")
680698

681-
if outputs["cert"] is not None:
682-
with outputs["cert"].open(mode="w") as io:
683-
print(result.cert_pem, file=io)
684-
print(f"Certificate written to {outputs['cert']}")
699+
if outputs["cert"] is not None:
700+
with outputs["cert"].open(mode="w") as io:
701+
print(result.cert_pem, file=io)
702+
print(f"Certificate written to {outputs['cert']}")
685703

686-
if outputs["bundle"] is not None:
687-
with outputs["bundle"].open(mode="w") as io:
688-
print(result._to_bundle().to_json(), file=io)
689-
print(f"Sigstore bundle written to {outputs['bundle']}")
704+
if outputs["bundle"] is not None:
705+
with outputs["bundle"].open(mode="w") as io:
706+
print(result._to_bundle().to_json(), file=io)
707+
print(f"Sigstore bundle written to {outputs['bundle']}")
690708

691709

692710
def _collect_verification_state(

sigstore/_internal/fulcio/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919

2020
from .client import (
2121
DetachedFulcioSCT,
22+
ExpiredCertificate,
2223
FulcioCertificateSigningResponse,
2324
FulcioClient,
2425
)
2526

2627
__all__ = [
2728
"DetachedFulcioSCT",
29+
"ExpiredCertificate",
2830
"FulcioCertificateSigningResponse",
2931
"FulcioClient",
3032
]

sigstore/_internal/fulcio/client.py

+4
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ def signature(self) -> bytes:
163163
SignedCertificateTimestamp.register(DetachedFulcioSCT)
164164

165165

166+
class ExpiredCertificate(Exception):
167+
"""An error raised when the Certificate is expired."""
168+
169+
166170
@dataclass(frozen=True)
167171
class FulcioCertificateSigningResponse:
168172
"""Certificate response"""

sigstore/_internal/oidc/__init__.py

+16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
OIDC functionality for sigstore-python.
1717
"""
1818

19+
from datetime import datetime, timezone
20+
1921
import jwt
2022
from id import IdentityError
2123

@@ -29,6 +31,10 @@
2931
DEFAULT_AUDIENCE = "sigstore"
3032

3133

34+
class ExpiredIdentity(Exception):
35+
"""An error raised when an identity token is expired."""
36+
37+
3238
class Identity:
3339
"""
3440
A wrapper for an OIDC "identity", as extracted from an OIDC token.
@@ -40,6 +46,7 @@ def __init__(self, identity_token: str) -> None:
4046
"""
4147
identity_jwt = jwt.decode(identity_token, options={"verify_signature": False})
4248

49+
self.exp_timestamp = identity_jwt.get("exp")
4350
self.issuer = identity_jwt.get("iss")
4451
if self.issuer is None:
4552
raise IdentityError("Identity token missing the required `iss` claim")
@@ -69,3 +76,12 @@ def __init__(self, identity_token: str) -> None:
6976
self.proof = str(identity_jwt["sub"])
7077
except KeyError:
7178
raise IdentityError("Identity token missing `sub` claim")
79+
80+
def is_expired(self) -> bool:
81+
"""Verify if the identity token for this `Identity` is expired."""
82+
if self.exp_timestamp:
83+
now: float = datetime.now(timezone.utc).timestamp()
84+
token_timestamp: float = self.exp_timestamp
85+
return now > token_timestamp
86+
else:
87+
raise ValueError("Identity token does not have an expiration timestamp")

0 commit comments

Comments
 (0)