sigstore._internal.trust
Client trust configuration and trust root management for sigstore-python.
1# Copyright 2023 The Sigstore Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16Client trust configuration and trust root management for sigstore-python. 17""" 18 19from __future__ import annotations 20 21from dataclasses import dataclass 22from datetime import datetime, timezone 23from enum import Enum 24from pathlib import Path 25from typing import ClassVar, Iterable, List, NewType 26 27import cryptography.hazmat.primitives.asymmetric.padding as padding 28from cryptography.exceptions import InvalidSignature 29from cryptography.hazmat.primitives import hashes 30from cryptography.hazmat.primitives.asymmetric import ec, rsa 31from cryptography.x509 import ( 32 Certificate, 33 load_der_x509_certificate, 34) 35from sigstore_protobuf_specs.dev.sigstore.common.v1 import PublicKey as _PublicKey 36from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( 37 PublicKeyDetails as _PublicKeyDetails, 38) 39from sigstore_protobuf_specs.dev.sigstore.common.v1 import TimeRange 40from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import ( 41 CertificateAuthority, 42 TransparencyLogInstance, 43) 44from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import ( 45 ClientTrustConfig as _ClientTrustConfig, 46) 47from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import ( 48 TrustedRoot as _TrustedRoot, 49) 50 51from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater 52from sigstore._utils import ( 53 KeyID, 54 PublicKey, 55 key_id, 56 load_der_public_key, 57) 58from sigstore.errors import Error, MetadataError, VerificationError 59 60 61def _is_timerange_valid(period: TimeRange | None, *, allow_expired: bool) -> bool: 62 """ 63 Given a `period`, checks that the the current time is not before `start`. If 64 `allow_expired` is `False`, also checks that the current time is not after 65 `end`. 66 """ 67 now = datetime.now(timezone.utc) 68 69 # If there was no validity period specified, the key is always valid. 70 if not period: 71 return True 72 73 # Active: if the current time is before the starting period, we are not yet 74 # valid. 75 if now < period.start: 76 return False 77 78 # If we want Expired keys, the key is valid at this point. Otherwise, check 79 # that we are within range. 80 return allow_expired or (period.end is None or now <= period.end) 81 82 83@dataclass(init=False) 84class Key: 85 """ 86 Represents a key in a `Keyring`. 87 """ 88 89 hash_algorithm: hashes.HashAlgorithm 90 key: PublicKey 91 key_id: KeyID 92 93 _RSA_SHA_256_DETAILS: ClassVar[set[_PublicKeyDetails]] = { 94 _PublicKeyDetails.PKCS1_RSA_PKCS1V5, 95 _PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256, 96 _PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256, 97 _PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256, 98 } 99 100 _EC_DETAILS_TO_HASH: ClassVar[dict[_PublicKeyDetails, hashes.HashAlgorithm]] = { 101 _PublicKeyDetails.PKIX_ECDSA_P256_SHA_256: hashes.SHA256(), 102 _PublicKeyDetails.PKIX_ECDSA_P384_SHA_384: hashes.SHA384(), 103 _PublicKeyDetails.PKIX_ECDSA_P521_SHA_512: hashes.SHA512(), 104 } 105 106 def __init__(self, public_key: _PublicKey) -> None: 107 """ 108 Construct a key from the given Sigstore PublicKey message. 109 """ 110 111 # NOTE: `raw_bytes` is marked as `optional` in the `PublicKey` message, 112 # for unclear reasons. 113 if not public_key.raw_bytes: 114 raise VerificationError("public key is empty") 115 116 hash_algorithm: hashes.HashAlgorithm 117 if public_key.key_details in self._RSA_SHA_256_DETAILS: 118 hash_algorithm = hashes.SHA256() 119 key = load_der_public_key(public_key.raw_bytes, types=(rsa.RSAPublicKey,)) 120 elif public_key.key_details in self._EC_DETAILS_TO_HASH: 121 hash_algorithm = self._EC_DETAILS_TO_HASH[public_key.key_details] 122 key = load_der_public_key( 123 public_key.raw_bytes, types=(ec.EllipticCurvePublicKey,) 124 ) 125 else: 126 raise VerificationError(f"unsupported key type: {public_key.key_details}") 127 128 self.hash_algorithm = hash_algorithm 129 self.key = key 130 self.key_id = key_id(key) 131 132 def verify(self, signature: bytes, data: bytes) -> None: 133 """ 134 Verifies the given `data` against `signature` using the current key. 135 """ 136 if isinstance(self.key, rsa.RSAPublicKey): 137 self.key.verify( 138 signature=signature, 139 data=data, 140 # TODO: Parametrize this as well, for PSS. 141 padding=padding.PKCS1v15(), 142 algorithm=self.hash_algorithm, 143 ) 144 elif isinstance(self.key, ec.EllipticCurvePublicKey): 145 self.key.verify( 146 signature=signature, 147 data=data, 148 signature_algorithm=ec.ECDSA(self.hash_algorithm), 149 ) 150 else: 151 # Unreachable without API misuse. 152 raise VerificationError(f"keyring: unsupported key: {self.key}") 153 154 155class Keyring: 156 """ 157 Represents a set of keys, each of which is a potentially valid verifier. 158 """ 159 160 def __init__(self, public_keys: List[_PublicKey] = []): 161 """ 162 Create a new `Keyring`, with `keys` as the initial set of verifying keys. 163 """ 164 self._keyring: dict[KeyID, Key] = {} 165 166 for public_key in public_keys: 167 key = Key(public_key) 168 self._keyring[key.key_id] = key 169 170 def verify(self, *, key_id: KeyID, signature: bytes, data: bytes) -> None: 171 """ 172 Verify that `signature` is a valid signature for `data`, using the 173 key identified by `key_id`. 174 175 `key_id` is an unauthenticated hint; if no key matches the given key ID, 176 all keys in the keyring are tried. 177 178 Raises if the signature is invalid, i.e. is not valid for any of the 179 keys in the keyring. 180 """ 181 182 key = self._keyring.get(key_id) 183 if key is not None: 184 candidates = [key] 185 else: 186 candidates = list(self._keyring.values()) 187 188 # Try to verify each candidate key. In the happy case, this will 189 # be exactly one candidate. 190 valid = False 191 for candidate in candidates: 192 try: 193 candidate.verify(signature, data) 194 valid = True 195 break 196 except InvalidSignature: 197 pass 198 199 if not valid: 200 raise VerificationError("keyring: invalid signature") 201 202 203RekorKeyring = NewType("RekorKeyring", Keyring) 204CTKeyring = NewType("CTKeyring", Keyring) 205 206 207class KeyringPurpose(str, Enum): 208 """ 209 Keyring purpose typing 210 """ 211 212 SIGN = "sign" 213 VERIFY = "verify" 214 215 def __str__(self) -> str: 216 """Returns the purpose string value.""" 217 return self.value 218 219 220class TrustedRoot: 221 """ 222 The cryptographic root(s) of trust for a Sigstore instance. 223 """ 224 225 class TrustedRootType(str, Enum): 226 """ 227 Known Sigstore trusted root media types. 228 """ 229 230 TRUSTED_ROOT_0_1 = "application/vnd.dev.sigstore.trustedroot+json;version=0.1" 231 232 def __str__(self) -> str: 233 """Returns the variant's string value.""" 234 return self.value 235 236 def __init__(self, inner: _TrustedRoot): 237 """ 238 Construct a new `TrustedRoot`. 239 240 @api private 241 """ 242 self._inner = inner 243 self._verify() 244 245 def _verify(self) -> None: 246 """ 247 Performs various feats of heroism to ensure that the trusted root 248 is well-formed. 249 """ 250 251 # The trusted root must have a recognized media type. 252 try: 253 TrustedRoot.TrustedRootType(self._inner.media_type) 254 except ValueError: 255 raise Error(f"unsupported trusted root format: {self._inner.media_type}") 256 257 @classmethod 258 def from_file( 259 cls, 260 path: str, 261 ) -> TrustedRoot: 262 """Create a new trust root from file""" 263 inner = _TrustedRoot().from_json(Path(path).read_bytes()) 264 return cls(inner) 265 266 @classmethod 267 def from_tuf( 268 cls, 269 url: str, 270 offline: bool = False, 271 ) -> TrustedRoot: 272 """Create a new trust root from a TUF repository. 273 274 If `offline`, will use trust root in local TUF cache. Otherwise will 275 update the trust root from remote TUF repository. 276 """ 277 path = TrustUpdater(url, offline).get_trusted_root_path() 278 return cls.from_file(path) 279 280 @classmethod 281 def production( 282 cls, 283 offline: bool = False, 284 ) -> TrustedRoot: 285 """Create new trust root from Sigstore production TUF repository. 286 287 If `offline`, will use trust root in local TUF cache. Otherwise will 288 update the trust root from remote TUF repository. 289 """ 290 return cls.from_tuf(DEFAULT_TUF_URL, offline) 291 292 @classmethod 293 def staging( 294 cls, 295 offline: bool = False, 296 ) -> TrustedRoot: 297 """Create new trust root from Sigstore staging TUF repository. 298 299 If `offline`, will use trust root in local TUF cache. Otherwise will 300 update the trust root from remote TUF repository. 301 """ 302 return cls.from_tuf(STAGING_TUF_URL, offline) 303 304 def _get_tlog_keys( 305 self, tlogs: list[TransparencyLogInstance], purpose: KeyringPurpose 306 ) -> Iterable[_PublicKey]: 307 """ 308 Yields an iterator of public keys for transparency log instances that 309 are suitable for `purpose`. 310 """ 311 allow_expired = purpose is KeyringPurpose.VERIFY 312 for tlog in tlogs: 313 if not _is_timerange_valid( 314 tlog.public_key.valid_for, allow_expired=allow_expired 315 ): 316 continue 317 318 yield tlog.public_key 319 320 @staticmethod 321 def _get_ca_keys( 322 cas: list[CertificateAuthority], *, allow_expired: bool 323 ) -> Iterable[bytes]: 324 """Return public key contents given certificate authorities.""" 325 326 for ca in cas: 327 if not _is_timerange_valid(ca.valid_for, allow_expired=allow_expired): 328 continue 329 for cert in ca.cert_chain.certificates: 330 yield cert.raw_bytes 331 332 def rekor_keyring(self, purpose: KeyringPurpose) -> RekorKeyring: 333 """Return keyring with keys for Rekor.""" 334 335 keys: list[_PublicKey] = list(self._get_tlog_keys(self._inner.tlogs, purpose)) 336 if len(keys) != 1: 337 raise MetadataError("Did not find one Rekor key in trusted root") 338 return RekorKeyring(Keyring(keys)) 339 340 def ct_keyring(self, purpose: KeyringPurpose) -> CTKeyring: 341 """Return keyring with key for CTFE.""" 342 ctfes: list[_PublicKey] = list(self._get_tlog_keys(self._inner.ctlogs, purpose)) 343 if not ctfes: 344 raise MetadataError("CTFE keys not found in trusted root") 345 return CTKeyring(Keyring(ctfes)) 346 347 def get_fulcio_certs(self) -> list[Certificate]: 348 """Return the Fulcio certificates.""" 349 350 certs: list[Certificate] 351 352 # Return expired certificates too: they are expired now but may have 353 # been active when the certificate was used to sign. 354 certs = [ 355 load_der_x509_certificate(c) 356 for c in self._get_ca_keys( 357 self._inner.certificate_authorities, allow_expired=True 358 ) 359 ] 360 if not certs: 361 raise MetadataError("Fulcio certificates not found in trusted root") 362 return certs 363 364 365class ClientTrustConfig: 366 """ 367 Represents a Sigstore client's trust configuration, including a root of trust. 368 """ 369 370 class ClientTrustConfigType(str, Enum): 371 """ 372 Known Sigstore client trust config media types. 373 """ 374 375 CONFIG_0_1 = "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" 376 377 def __str__(self) -> str: 378 """Returns the variant's string value.""" 379 return self.value 380 381 @classmethod 382 def from_json(cls, raw: str) -> ClientTrustConfig: 383 """ 384 Deserialize the given client trust config. 385 """ 386 inner = _ClientTrustConfig().from_json(raw) 387 return cls(inner) 388 389 def __init__(self, inner: _ClientTrustConfig) -> None: 390 """ 391 @api private 392 """ 393 self._inner = inner 394 self._verify() 395 396 def _verify(self) -> None: 397 """ 398 Performs various feats of heroism to ensure that the client trust config 399 is well-formed. 400 """ 401 402 # The client trust config must have a recognized media type. 403 try: 404 ClientTrustConfig.ClientTrustConfigType(self._inner.media_type) 405 except ValueError: 406 raise Error( 407 f"unsupported client trust config format: {self._inner.media_type}" 408 ) 409 410 @property 411 def trusted_root(self) -> TrustedRoot: 412 """ 413 Return the interior root of trust, as a `TrustedRoot`. 414 """ 415 return TrustedRoot(self._inner.trusted_root)
84@dataclass(init=False) 85class Key: 86 """ 87 Represents a key in a `Keyring`. 88 """ 89 90 hash_algorithm: hashes.HashAlgorithm 91 key: PublicKey 92 key_id: KeyID 93 94 _RSA_SHA_256_DETAILS: ClassVar[set[_PublicKeyDetails]] = { 95 _PublicKeyDetails.PKCS1_RSA_PKCS1V5, 96 _PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256, 97 _PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256, 98 _PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256, 99 } 100 101 _EC_DETAILS_TO_HASH: ClassVar[dict[_PublicKeyDetails, hashes.HashAlgorithm]] = { 102 _PublicKeyDetails.PKIX_ECDSA_P256_SHA_256: hashes.SHA256(), 103 _PublicKeyDetails.PKIX_ECDSA_P384_SHA_384: hashes.SHA384(), 104 _PublicKeyDetails.PKIX_ECDSA_P521_SHA_512: hashes.SHA512(), 105 } 106 107 def __init__(self, public_key: _PublicKey) -> None: 108 """ 109 Construct a key from the given Sigstore PublicKey message. 110 """ 111 112 # NOTE: `raw_bytes` is marked as `optional` in the `PublicKey` message, 113 # for unclear reasons. 114 if not public_key.raw_bytes: 115 raise VerificationError("public key is empty") 116 117 hash_algorithm: hashes.HashAlgorithm 118 if public_key.key_details in self._RSA_SHA_256_DETAILS: 119 hash_algorithm = hashes.SHA256() 120 key = load_der_public_key(public_key.raw_bytes, types=(rsa.RSAPublicKey,)) 121 elif public_key.key_details in self._EC_DETAILS_TO_HASH: 122 hash_algorithm = self._EC_DETAILS_TO_HASH[public_key.key_details] 123 key = load_der_public_key( 124 public_key.raw_bytes, types=(ec.EllipticCurvePublicKey,) 125 ) 126 else: 127 raise VerificationError(f"unsupported key type: {public_key.key_details}") 128 129 self.hash_algorithm = hash_algorithm 130 self.key = key 131 self.key_id = key_id(key) 132 133 def verify(self, signature: bytes, data: bytes) -> None: 134 """ 135 Verifies the given `data` against `signature` using the current key. 136 """ 137 if isinstance(self.key, rsa.RSAPublicKey): 138 self.key.verify( 139 signature=signature, 140 data=data, 141 # TODO: Parametrize this as well, for PSS. 142 padding=padding.PKCS1v15(), 143 algorithm=self.hash_algorithm, 144 ) 145 elif isinstance(self.key, ec.EllipticCurvePublicKey): 146 self.key.verify( 147 signature=signature, 148 data=data, 149 signature_algorithm=ec.ECDSA(self.hash_algorithm), 150 ) 151 else: 152 # Unreachable without API misuse. 153 raise VerificationError(f"keyring: unsupported key: {self.key}")
Represents a key in a Keyring
.
107 def __init__(self, public_key: _PublicKey) -> None: 108 """ 109 Construct a key from the given Sigstore PublicKey message. 110 """ 111 112 # NOTE: `raw_bytes` is marked as `optional` in the `PublicKey` message, 113 # for unclear reasons. 114 if not public_key.raw_bytes: 115 raise VerificationError("public key is empty") 116 117 hash_algorithm: hashes.HashAlgorithm 118 if public_key.key_details in self._RSA_SHA_256_DETAILS: 119 hash_algorithm = hashes.SHA256() 120 key = load_der_public_key(public_key.raw_bytes, types=(rsa.RSAPublicKey,)) 121 elif public_key.key_details in self._EC_DETAILS_TO_HASH: 122 hash_algorithm = self._EC_DETAILS_TO_HASH[public_key.key_details] 123 key = load_der_public_key( 124 public_key.raw_bytes, types=(ec.EllipticCurvePublicKey,) 125 ) 126 else: 127 raise VerificationError(f"unsupported key type: {public_key.key_details}") 128 129 self.hash_algorithm = hash_algorithm 130 self.key = key 131 self.key_id = key_id(key)
Construct a key from the given Sigstore PublicKey message.
133 def verify(self, signature: bytes, data: bytes) -> None: 134 """ 135 Verifies the given `data` against `signature` using the current key. 136 """ 137 if isinstance(self.key, rsa.RSAPublicKey): 138 self.key.verify( 139 signature=signature, 140 data=data, 141 # TODO: Parametrize this as well, for PSS. 142 padding=padding.PKCS1v15(), 143 algorithm=self.hash_algorithm, 144 ) 145 elif isinstance(self.key, ec.EllipticCurvePublicKey): 146 self.key.verify( 147 signature=signature, 148 data=data, 149 signature_algorithm=ec.ECDSA(self.hash_algorithm), 150 ) 151 else: 152 # Unreachable without API misuse. 153 raise VerificationError(f"keyring: unsupported key: {self.key}")
Verifies the given data
against signature
using the current key.
156class Keyring: 157 """ 158 Represents a set of keys, each of which is a potentially valid verifier. 159 """ 160 161 def __init__(self, public_keys: List[_PublicKey] = []): 162 """ 163 Create a new `Keyring`, with `keys` as the initial set of verifying keys. 164 """ 165 self._keyring: dict[KeyID, Key] = {} 166 167 for public_key in public_keys: 168 key = Key(public_key) 169 self._keyring[key.key_id] = key 170 171 def verify(self, *, key_id: KeyID, signature: bytes, data: bytes) -> None: 172 """ 173 Verify that `signature` is a valid signature for `data`, using the 174 key identified by `key_id`. 175 176 `key_id` is an unauthenticated hint; if no key matches the given key ID, 177 all keys in the keyring are tried. 178 179 Raises if the signature is invalid, i.e. is not valid for any of the 180 keys in the keyring. 181 """ 182 183 key = self._keyring.get(key_id) 184 if key is not None: 185 candidates = [key] 186 else: 187 candidates = list(self._keyring.values()) 188 189 # Try to verify each candidate key. In the happy case, this will 190 # be exactly one candidate. 191 valid = False 192 for candidate in candidates: 193 try: 194 candidate.verify(signature, data) 195 valid = True 196 break 197 except InvalidSignature: 198 pass 199 200 if not valid: 201 raise VerificationError("keyring: invalid signature")
Represents a set of keys, each of which is a potentially valid verifier.
161 def __init__(self, public_keys: List[_PublicKey] = []): 162 """ 163 Create a new `Keyring`, with `keys` as the initial set of verifying keys. 164 """ 165 self._keyring: dict[KeyID, Key] = {} 166 167 for public_key in public_keys: 168 key = Key(public_key) 169 self._keyring[key.key_id] = key
Create a new Keyring
, with keys
as the initial set of verifying keys.
171 def verify(self, *, key_id: KeyID, signature: bytes, data: bytes) -> None: 172 """ 173 Verify that `signature` is a valid signature for `data`, using the 174 key identified by `key_id`. 175 176 `key_id` is an unauthenticated hint; if no key matches the given key ID, 177 all keys in the keyring are tried. 178 179 Raises if the signature is invalid, i.e. is not valid for any of the 180 keys in the keyring. 181 """ 182 183 key = self._keyring.get(key_id) 184 if key is not None: 185 candidates = [key] 186 else: 187 candidates = list(self._keyring.values()) 188 189 # Try to verify each candidate key. In the happy case, this will 190 # be exactly one candidate. 191 valid = False 192 for candidate in candidates: 193 try: 194 candidate.verify(signature, data) 195 valid = True 196 break 197 except InvalidSignature: 198 pass 199 200 if not valid: 201 raise VerificationError("keyring: invalid signature")
Verify that signature
is a valid signature for data
, using the
key identified by key_id
.
key_id
is an unauthenticated hint; if no key matches the given key ID,
all keys in the keyring are tried.
Raises if the signature is invalid, i.e. is not valid for any of the keys in the keyring.
208class KeyringPurpose(str, Enum): 209 """ 210 Keyring purpose typing 211 """ 212 213 SIGN = "sign" 214 VERIFY = "verify" 215 216 def __str__(self) -> str: 217 """Returns the purpose string value.""" 218 return self.value
Keyring purpose typing
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
221class TrustedRoot: 222 """ 223 The cryptographic root(s) of trust for a Sigstore instance. 224 """ 225 226 class TrustedRootType(str, Enum): 227 """ 228 Known Sigstore trusted root media types. 229 """ 230 231 TRUSTED_ROOT_0_1 = "application/vnd.dev.sigstore.trustedroot+json;version=0.1" 232 233 def __str__(self) -> str: 234 """Returns the variant's string value.""" 235 return self.value 236 237 def __init__(self, inner: _TrustedRoot): 238 """ 239 Construct a new `TrustedRoot`. 240 241 @api private 242 """ 243 self._inner = inner 244 self._verify() 245 246 def _verify(self) -> None: 247 """ 248 Performs various feats of heroism to ensure that the trusted root 249 is well-formed. 250 """ 251 252 # The trusted root must have a recognized media type. 253 try: 254 TrustedRoot.TrustedRootType(self._inner.media_type) 255 except ValueError: 256 raise Error(f"unsupported trusted root format: {self._inner.media_type}") 257 258 @classmethod 259 def from_file( 260 cls, 261 path: str, 262 ) -> TrustedRoot: 263 """Create a new trust root from file""" 264 inner = _TrustedRoot().from_json(Path(path).read_bytes()) 265 return cls(inner) 266 267 @classmethod 268 def from_tuf( 269 cls, 270 url: str, 271 offline: bool = False, 272 ) -> TrustedRoot: 273 """Create a new trust root from a TUF repository. 274 275 If `offline`, will use trust root in local TUF cache. Otherwise will 276 update the trust root from remote TUF repository. 277 """ 278 path = TrustUpdater(url, offline).get_trusted_root_path() 279 return cls.from_file(path) 280 281 @classmethod 282 def production( 283 cls, 284 offline: bool = False, 285 ) -> TrustedRoot: 286 """Create new trust root from Sigstore production TUF repository. 287 288 If `offline`, will use trust root in local TUF cache. Otherwise will 289 update the trust root from remote TUF repository. 290 """ 291 return cls.from_tuf(DEFAULT_TUF_URL, offline) 292 293 @classmethod 294 def staging( 295 cls, 296 offline: bool = False, 297 ) -> TrustedRoot: 298 """Create new trust root from Sigstore staging TUF repository. 299 300 If `offline`, will use trust root in local TUF cache. Otherwise will 301 update the trust root from remote TUF repository. 302 """ 303 return cls.from_tuf(STAGING_TUF_URL, offline) 304 305 def _get_tlog_keys( 306 self, tlogs: list[TransparencyLogInstance], purpose: KeyringPurpose 307 ) -> Iterable[_PublicKey]: 308 """ 309 Yields an iterator of public keys for transparency log instances that 310 are suitable for `purpose`. 311 """ 312 allow_expired = purpose is KeyringPurpose.VERIFY 313 for tlog in tlogs: 314 if not _is_timerange_valid( 315 tlog.public_key.valid_for, allow_expired=allow_expired 316 ): 317 continue 318 319 yield tlog.public_key 320 321 @staticmethod 322 def _get_ca_keys( 323 cas: list[CertificateAuthority], *, allow_expired: bool 324 ) -> Iterable[bytes]: 325 """Return public key contents given certificate authorities.""" 326 327 for ca in cas: 328 if not _is_timerange_valid(ca.valid_for, allow_expired=allow_expired): 329 continue 330 for cert in ca.cert_chain.certificates: 331 yield cert.raw_bytes 332 333 def rekor_keyring(self, purpose: KeyringPurpose) -> RekorKeyring: 334 """Return keyring with keys for Rekor.""" 335 336 keys: list[_PublicKey] = list(self._get_tlog_keys(self._inner.tlogs, purpose)) 337 if len(keys) != 1: 338 raise MetadataError("Did not find one Rekor key in trusted root") 339 return RekorKeyring(Keyring(keys)) 340 341 def ct_keyring(self, purpose: KeyringPurpose) -> CTKeyring: 342 """Return keyring with key for CTFE.""" 343 ctfes: list[_PublicKey] = list(self._get_tlog_keys(self._inner.ctlogs, purpose)) 344 if not ctfes: 345 raise MetadataError("CTFE keys not found in trusted root") 346 return CTKeyring(Keyring(ctfes)) 347 348 def get_fulcio_certs(self) -> list[Certificate]: 349 """Return the Fulcio certificates.""" 350 351 certs: list[Certificate] 352 353 # Return expired certificates too: they are expired now but may have 354 # been active when the certificate was used to sign. 355 certs = [ 356 load_der_x509_certificate(c) 357 for c in self._get_ca_keys( 358 self._inner.certificate_authorities, allow_expired=True 359 ) 360 ] 361 if not certs: 362 raise MetadataError("Fulcio certificates not found in trusted root") 363 return certs
The cryptographic root(s) of trust for a Sigstore instance.
237 def __init__(self, inner: _TrustedRoot): 238 """ 239 Construct a new `TrustedRoot`. 240 241 @api private 242 """ 243 self._inner = inner 244 self._verify()
Construct a new TrustedRoot
.
@api private
258 @classmethod 259 def from_file( 260 cls, 261 path: str, 262 ) -> TrustedRoot: 263 """Create a new trust root from file""" 264 inner = _TrustedRoot().from_json(Path(path).read_bytes()) 265 return cls(inner)
Create a new trust root from file
267 @classmethod 268 def from_tuf( 269 cls, 270 url: str, 271 offline: bool = False, 272 ) -> TrustedRoot: 273 """Create a new trust root from a TUF repository. 274 275 If `offline`, will use trust root in local TUF cache. Otherwise will 276 update the trust root from remote TUF repository. 277 """ 278 path = TrustUpdater(url, offline).get_trusted_root_path() 279 return cls.from_file(path)
Create a new trust root from a TUF repository.
If offline
, will use trust root in local TUF cache. Otherwise will
update the trust root from remote TUF repository.
281 @classmethod 282 def production( 283 cls, 284 offline: bool = False, 285 ) -> TrustedRoot: 286 """Create new trust root from Sigstore production TUF repository. 287 288 If `offline`, will use trust root in local TUF cache. Otherwise will 289 update the trust root from remote TUF repository. 290 """ 291 return cls.from_tuf(DEFAULT_TUF_URL, offline)
Create new trust root from Sigstore production TUF repository.
If offline
, will use trust root in local TUF cache. Otherwise will
update the trust root from remote TUF repository.
293 @classmethod 294 def staging( 295 cls, 296 offline: bool = False, 297 ) -> TrustedRoot: 298 """Create new trust root from Sigstore staging TUF repository. 299 300 If `offline`, will use trust root in local TUF cache. Otherwise will 301 update the trust root from remote TUF repository. 302 """ 303 return cls.from_tuf(STAGING_TUF_URL, offline)
Create new trust root from Sigstore staging TUF repository.
If offline
, will use trust root in local TUF cache. Otherwise will
update the trust root from remote TUF repository.
333 def rekor_keyring(self, purpose: KeyringPurpose) -> RekorKeyring: 334 """Return keyring with keys for Rekor.""" 335 336 keys: list[_PublicKey] = list(self._get_tlog_keys(self._inner.tlogs, purpose)) 337 if len(keys) != 1: 338 raise MetadataError("Did not find one Rekor key in trusted root") 339 return RekorKeyring(Keyring(keys))
Return keyring with keys for Rekor.
341 def ct_keyring(self, purpose: KeyringPurpose) -> CTKeyring: 342 """Return keyring with key for CTFE.""" 343 ctfes: list[_PublicKey] = list(self._get_tlog_keys(self._inner.ctlogs, purpose)) 344 if not ctfes: 345 raise MetadataError("CTFE keys not found in trusted root") 346 return CTKeyring(Keyring(ctfes))
Return keyring with key for CTFE.
348 def get_fulcio_certs(self) -> list[Certificate]: 349 """Return the Fulcio certificates.""" 350 351 certs: list[Certificate] 352 353 # Return expired certificates too: they are expired now but may have 354 # been active when the certificate was used to sign. 355 certs = [ 356 load_der_x509_certificate(c) 357 for c in self._get_ca_keys( 358 self._inner.certificate_authorities, allow_expired=True 359 ) 360 ] 361 if not certs: 362 raise MetadataError("Fulcio certificates not found in trusted root") 363 return certs
Return the Fulcio certificates.
226 class TrustedRootType(str, Enum): 227 """ 228 Known Sigstore trusted root media types. 229 """ 230 231 TRUSTED_ROOT_0_1 = "application/vnd.dev.sigstore.trustedroot+json;version=0.1" 232 233 def __str__(self) -> str: 234 """Returns the variant's string value.""" 235 return self.value
Known Sigstore trusted root media types.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
366class ClientTrustConfig: 367 """ 368 Represents a Sigstore client's trust configuration, including a root of trust. 369 """ 370 371 class ClientTrustConfigType(str, Enum): 372 """ 373 Known Sigstore client trust config media types. 374 """ 375 376 CONFIG_0_1 = "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" 377 378 def __str__(self) -> str: 379 """Returns the variant's string value.""" 380 return self.value 381 382 @classmethod 383 def from_json(cls, raw: str) -> ClientTrustConfig: 384 """ 385 Deserialize the given client trust config. 386 """ 387 inner = _ClientTrustConfig().from_json(raw) 388 return cls(inner) 389 390 def __init__(self, inner: _ClientTrustConfig) -> None: 391 """ 392 @api private 393 """ 394 self._inner = inner 395 self._verify() 396 397 def _verify(self) -> None: 398 """ 399 Performs various feats of heroism to ensure that the client trust config 400 is well-formed. 401 """ 402 403 # The client trust config must have a recognized media type. 404 try: 405 ClientTrustConfig.ClientTrustConfigType(self._inner.media_type) 406 except ValueError: 407 raise Error( 408 f"unsupported client trust config format: {self._inner.media_type}" 409 ) 410 411 @property 412 def trusted_root(self) -> TrustedRoot: 413 """ 414 Return the interior root of trust, as a `TrustedRoot`. 415 """ 416 return TrustedRoot(self._inner.trusted_root)
Represents a Sigstore client's trust configuration, including a root of trust.
390 def __init__(self, inner: _ClientTrustConfig) -> None: 391 """ 392 @api private 393 """ 394 self._inner = inner 395 self._verify()
@api private
382 @classmethod 383 def from_json(cls, raw: str) -> ClientTrustConfig: 384 """ 385 Deserialize the given client trust config. 386 """ 387 inner = _ClientTrustConfig().from_json(raw) 388 return cls(inner)
Deserialize the given client trust config.
411 @property 412 def trusted_root(self) -> TrustedRoot: 413 """ 414 Return the interior root of trust, as a `TrustedRoot`. 415 """ 416 return TrustedRoot(self._inner.trusted_root)
Return the interior root of trust, as a TrustedRoot
.
371 class ClientTrustConfigType(str, Enum): 372 """ 373 Known Sigstore client trust config media types. 374 """ 375 376 CONFIG_0_1 = "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" 377 378 def __str__(self) -> str: 379 """Returns the variant's string value.""" 380 return self.value
Known Sigstore client trust config media types.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans