Skip to content

Commit f430317

Browse files
committed
refactor: concentrate keys on trusted root
1 parent f793ef0 commit f430317

File tree

9 files changed

+116
-75
lines changed

9 files changed

+116
-75
lines changed

sigstore/_cli.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@
3838
from sigstore._internal.rekor.client import (
3939
DEFAULT_REKOR_URL,
4040
RekorClient,
41-
RekorKeyring,
4241
)
43-
from sigstore._internal.trustroot import TrustedRoot
42+
from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot
4443
from sigstore._utils import PEMCert, cert_der_to_pem, sha256_digest
4544
from sigstore.errors import Error
4645
from sigstore.oidc import (
@@ -656,13 +655,9 @@ def _sign(args: argparse.Namespace) -> None:
656655
ctfe_keys = [args.ctfe_pem.read()]
657656
else:
658657
ctfe_keys = trusted_root.get_ctfe_keys()
659-
if args.rekor_root_pubkey is not None:
660-
rekor_keys = [args.rekor_root_pubkey.read()]
661-
else:
662-
rekor_keys = trusted_root.get_rekor_keys()
663658

664659
ct_keyring = CTKeyring(Keyring(ctfe_keys))
665-
rekor_keyring = RekorKeyring(Keyring(rekor_keys))
660+
rekor_keyring = trusted_root.rekor_keyring(KeyringPurpose.SIGN)
666661

667662
signing_ctx = SigningContext(
668663
fulcio=FulcioClient(args.fulcio_url),
@@ -814,12 +809,12 @@ def _collect_verification_state(
814809
args,
815810
f"Missing verification materials for {(file)}: {', '.join(missing)}",
816811
)
817-
812+
purpose = KeyringPurpose.VERIFY
818813
if args.staging:
819814
logger.debug("verify: staging instances requested")
820-
verifier = Verifier.staging()
815+
verifier = Verifier.staging(purpose)
821816
elif args.rekor_url == DEFAULT_REKOR_URL:
822-
verifier = Verifier.production()
817+
verifier = Verifier.production(purpose)
823818
else:
824819
if not args.certificate_chain:
825820
_die(args, "Custom Rekor URL used without specifying --certificate-chain")
@@ -831,17 +826,13 @@ def _collect_verification_state(
831826
except ValueError as error:
832827
_die(args, f"Invalid certificate chain: {error}")
833828

834-
if args.rekor_root_pubkey is not None:
835-
rekor_keys = [args.rekor_root_pubkey.read()]
836-
else:
837-
trusted_root = TrustedRoot.production()
838-
rekor_keys = trusted_root.get_rekor_keys()
839-
ct_keys = trusted_root.get_ctfe_keys()
829+
trusted_root = TrustedRoot.production()
830+
ct_keys = trusted_root.get_ctfe_keys()
840831

841832
verifier = Verifier(
842833
rekor=RekorClient(
843834
url=args.rekor_url,
844-
rekor_keyring=RekorKeyring(Keyring(rekor_keys)),
835+
rekor_keyring=trusted_root.rekor_keyring(KeyringPurpose.VERIFY),
845836
ct_keyring=CTKeyring(Keyring(ct_keys)),
846837
),
847838
fulcio_certificate_chain=certificate_chain,

sigstore/_internal/rekor/client.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
import logging
2323
from abc import ABC
2424
from dataclasses import dataclass
25-
from typing import Any, Dict, NewType, Optional
25+
from typing import Any, Dict, Optional
2626
from urllib.parse import urljoin
2727

2828
import rekor_types
2929
import requests
3030

3131
from sigstore._internal.ctfe import CTKeyring
3232
from sigstore._internal.keyring import Keyring
33-
from sigstore._internal.trustroot import TrustedRoot
33+
from sigstore._internal.trustroot import KeyringPurpose, RekorKeyring, TrustedRoot
3434
from sigstore.transparency import LogEntry
3535

3636
logger = logging.getLogger(__name__)
@@ -39,9 +39,6 @@
3939
STAGING_REKOR_URL = "https://rekor.sigstage.dev"
4040

4141

42-
RekorKeyring = NewType("RekorKeyring", Keyring)
43-
44-
4542
@dataclass(frozen=True)
4643
class RekorLogInfo:
4744
"""
@@ -249,34 +246,36 @@ def __del__(self) -> None:
249246
self.session.close()
250247

251248
@classmethod
252-
def production(cls, trust_root: TrustedRoot) -> RekorClient:
249+
def production(
250+
cls, trust_root: TrustedRoot, purpose: KeyringPurpose
251+
) -> RekorClient:
253252
"""
254253
Returns a `RekorClient` populated with the default Rekor production instance.
255254
256255
trust_root must be a `TrustedRoot` for the production TUF repository.
257256
"""
258-
rekor_keys = trust_root.get_rekor_keys()
257+
rekor_keyring = trust_root.rekor_keyring(purpose)
259258
ctfe_keys = trust_root.get_ctfe_keys()
260259

261260
return cls(
262261
DEFAULT_REKOR_URL,
263-
RekorKeyring(Keyring(rekor_keys)),
262+
rekor_keyring,
264263
CTKeyring(Keyring(ctfe_keys)),
265264
)
266265

267266
@classmethod
268-
def staging(cls, trust_root: TrustedRoot) -> RekorClient:
267+
def staging(cls, trust_root: TrustedRoot, purpose: KeyringPurpose) -> RekorClient:
269268
"""
270269
Returns a `RekorClient` populated with the default Rekor staging instance.
271270
272271
trust_root must be a `TrustedRoot` for the staging TUF repository.
273272
"""
274-
rekor_keys = trust_root.get_rekor_keys()
273+
rekor_keyring = trust_root.rekor_keyring(purpose)
275274
ctfe_keys = trust_root.get_ctfe_keys()
276275

277276
return cls(
278277
STAGING_REKOR_URL,
279-
RekorKeyring(Keyring(rekor_keys)),
278+
rekor_keyring,
280279
CTKeyring(Keyring(ctfe_keys)),
281280
)
282281

sigstore/_internal/trustroot.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
from __future__ import annotations
2020

2121
from datetime import datetime, timezone
22+
from enum import Enum
2223
from pathlib import Path
23-
from typing import Iterable
24+
from typing import Iterable, NewType
2425

2526
from cryptography.x509 import Certificate, load_der_x509_certificate
2627
from sigstore_protobuf_specs.dev.sigstore.common.v1 import TimeRange
@@ -32,9 +33,12 @@
3233
TrustedRoot as _TrustedRoot,
3334
)
3435

36+
from sigstore._internal.keyring import Keyring
3537
from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater
3638
from sigstore.errors import MetadataError
3739

40+
RekorKeyring = NewType("RekorKeyring", Keyring)
41+
3842

3943
def _is_timerange_valid(period: TimeRange | None, *, allow_expired: bool) -> bool:
4044
"""
@@ -58,6 +62,19 @@ def _is_timerange_valid(period: TimeRange | None, *, allow_expired: bool) -> boo
5862
return allow_expired or (period.end is None or now <= period.end)
5963

6064

65+
class KeyringPurpose(str, Enum):
66+
"""
67+
Keyring purpose typing
68+
"""
69+
70+
SIGN = "Signing"
71+
VERIFY = "Verifying"
72+
73+
def __str__(self) -> str:
74+
"""Returns the purpose string value."""
75+
return self.value
76+
77+
6178
class TrustedRoot(_TrustedRoot):
6279
"""Complete set of trusted entities for a Sigstore client"""
6380

@@ -96,10 +113,14 @@ def staging(cls, offline: bool = False) -> "TrustedRoot":
96113
return cls.from_tuf(STAGING_TUF_URL, offline)
97114

98115
@staticmethod
99-
def _get_tlog_keys(tlogs: list[TransparencyLogInstance]) -> Iterable[bytes]:
116+
def _get_tlog_keys(
117+
tlogs: list[TransparencyLogInstance], allow_expired: bool
118+
) -> Iterable[bytes]:
100119
"""Return public key contents given transparency log instances."""
101120
for key in tlogs:
102-
if not _is_timerange_valid(key.public_key.valid_for, allow_expired=True):
121+
if not _is_timerange_valid(
122+
key.public_key.valid_for, allow_expired=allow_expired
123+
):
103124
continue
104125
key_bytes = key.public_key.raw_bytes
105126
if key_bytes:
@@ -117,16 +138,29 @@ def _get_ca_keys(
117138
for cert in ca.cert_chain.certificates:
118139
yield cert.raw_bytes
119140

141+
def rekor_keyring(self, purpose: KeyringPurpose) -> RekorKeyring:
142+
"""Return public key contents given certificate authorities."""
143+
144+
if purpose is KeyringPurpose.VERIFY:
145+
allow_expired = True
146+
elif purpose is KeyringPurpose.SIGN:
147+
allow_expired = False
148+
else:
149+
# TODO(javan): Create a specific exception
150+
raise Exception()
151+
152+
return RekorKeyring(Keyring(self._get_rekor_keys(allow_expired)))
153+
120154
def get_ctfe_keys(self) -> list[bytes]:
121155
"""Return the CTFE public keys contents."""
122-
ctfes: list[bytes] = list(self._get_tlog_keys(self.ctlogs))
156+
ctfes: list[bytes] = list(self._get_tlog_keys(self.ctlogs, True))
123157
if not ctfes:
124158
raise MetadataError("CTFE keys not found in trusted root")
125159
return ctfes
126160

127-
def get_rekor_keys(self) -> list[bytes]:
161+
def _get_rekor_keys(self, allow_expired: bool) -> list[bytes]:
128162
"""Return the rekor public key content."""
129-
keys: list[bytes] = list(self._get_tlog_keys(self.tlogs))
163+
keys: list[bytes] = list(self._get_tlog_keys(self.tlogs, allow_expired))
130164

131165
if len(keys) != 1:
132166
raise MetadataError("Did not find one Rekor key in trusted root")

sigstore/sign.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
)
7979
from sigstore._internal.rekor.client import RekorClient
8080
from sigstore._internal.sct import verify_sct
81-
from sigstore._internal.trustroot import TrustedRoot
81+
from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot
8282
from sigstore._utils import BundleType, PEMCert, sha256_digest
8383
from sigstore.oidc import ExpiredIdentity, IdentityToken
8484
from sigstore.transparency import LogEntry
@@ -303,7 +303,7 @@ def production(cls) -> SigningContext:
303303
Return a `SigningContext` instance configured against Sigstore's production-level services.
304304
"""
305305
trust_root = TrustedRoot.production()
306-
rekor = RekorClient.production(trust_root)
306+
rekor = RekorClient.production(trust_root, purpose=KeyringPurpose.SIGN)
307307
return cls(
308308
fulcio=FulcioClient.production(),
309309
rekor=rekor,
@@ -315,7 +315,7 @@ def staging(cls) -> SigningContext:
315315
Return a `SignerContext` instance configured against Sigstore's staging-level services.
316316
"""
317317
trust_root = TrustedRoot.staging()
318-
rekor = RekorClient.staging(trust_root)
318+
rekor = RekorClient.staging(trust_root, purpose=KeyringPurpose.SIGN)
319319
return cls(
320320
fulcio=FulcioClient.staging(),
321321
rekor=rekor,

sigstore/verify/verifier.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
verify_sct,
5555
)
5656
from sigstore._internal.set import InvalidSETError, verify_set
57-
from sigstore._internal.trustroot import TrustedRoot
57+
from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot
5858
from sigstore._utils import B64Str, HexStr, sha256_digest
5959
from sigstore.hashes import Hashed
6060
from sigstore.verify.models import InvalidRekorEntry as InvalidRekorEntryError
@@ -129,24 +129,24 @@ def __init__(
129129
]
130130

131131
@classmethod
132-
def production(cls) -> Verifier:
132+
def production(cls, purpose: KeyringPurpose) -> Verifier:
133133
"""
134134
Return a `Verifier` instance configured against Sigstore's production-level services.
135135
"""
136136
trust_root = TrustedRoot.production()
137137
return cls(
138-
rekor=RekorClient.production(trust_root),
138+
rekor=RekorClient.production(trust_root, purpose),
139139
fulcio_certificate_chain=trust_root.get_fulcio_certs(),
140140
)
141141

142142
@classmethod
143-
def staging(cls) -> Verifier:
143+
def staging(cls, purpose: KeyringPurpose) -> Verifier:
144144
"""
145145
Return a `Verifier` instance configured against Sigstore's staging-level services.
146146
"""
147147
trust_root = TrustedRoot.staging()
148148
return cls(
149-
rekor=RekorClient.staging(trust_root),
149+
rekor=RekorClient.staging(trust_root, purpose),
150150
fulcio_certificate_chain=trust_root.get_fulcio_certs(),
151151
)
152152

0 commit comments

Comments
 (0)