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)
@dataclass(init=False)
class Key:
 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.

Key(public_key: sigstore_protobuf_specs.dev.sigstore.common.v1.PublicKey)
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.

hash_algorithm: cryptography.hazmat.primitives.hashes.HashAlgorithm
key: Union[cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey]
def verify(self, signature: bytes, data: bytes) -> None:
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.

class Keyring:
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.

Keyring( public_keys: List[sigstore_protobuf_specs.dev.sigstore.common.v1.PublicKey] = [])
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.

def verify( self, *, key_id: sigstore._utils.KeyID, signature: bytes, data: bytes) -> None:
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.

RekorKeyring = RekorKeyring
CTKeyring = CTKeyring
class KeyringPurpose(builtins.str, enum.Enum):
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

SIGN = <KeyringPurpose.SIGN: 'sign'>
VERIFY = <KeyringPurpose.VERIFY: 'verify'>
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
class TrustedRoot:
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.

TrustedRoot(inner: sigstore_protobuf_specs.dev.sigstore.trustroot.v1.TrustedRoot)
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

@classmethod
def from_file(cls, path: str) -> TrustedRoot:
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

@classmethod
def from_tuf( cls, url: str, offline: bool = False) -> TrustedRoot:
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.

@classmethod
def production(cls, offline: bool = False) -> TrustedRoot:
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.

@classmethod
def staging(cls, offline: bool = False) -> TrustedRoot:
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.

def rekor_keyring( self, purpose: KeyringPurpose) -> RekorKeyring:
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.

def ct_keyring( self, purpose: KeyringPurpose) -> CTKeyring:
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.

def get_fulcio_certs(self) -> list[cryptography.x509.base.Certificate]:
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.

class TrustedRoot.TrustedRootType(builtins.str, enum.Enum):
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.

TRUSTED_ROOT_0_1 = <TrustedRootType.TRUSTED_ROOT_0_1: 'application/vnd.dev.sigstore.trustedroot+json;version=0.1'>
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
class ClientTrustConfig:
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.

ClientTrustConfig( inner: sigstore_protobuf_specs.dev.sigstore.trustroot.v1.ClientTrustConfig)
390    def __init__(self, inner: _ClientTrustConfig) -> None:
391        """
392        @api private
393        """
394        self._inner = inner
395        self._verify()

@api private

@classmethod
def from_json(cls, raw: str) -> ClientTrustConfig:
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.

trusted_root: TrustedRoot
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.

class ClientTrustConfig.ClientTrustConfigType(builtins.str, enum.Enum):
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.

CONFIG_0_1 = <ClientTrustConfigType.CONFIG_0_1: 'application/vnd.dev.sigstore.clienttrustconfig.v0.1+json'>
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