Skip to content

feat: expose ClientTrustConfig as a public class #1496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ All versions prior to 0.9.0 are untracked.
* SigningConfig now has methods that return actual clients (like `RekorClient`) instead of
just URLs. The returned clients are also filtered according to SigningConfig contents.
[#1407](https://github.com/sigstore/sigstore-python/pull/1407)
* The ClientTrustConfig class has been moved from the private _internal package to a public
module (sigstore.models). This change formally adds the class to the project's public API,
making it available for use in other projects. [#1496](https://github.com/sigstore/sigstore-python/pull/1496)
* `--trust-config` now requires a file with SigningConfig v0.2, and is able to fully
configure the used Sigstore instance [#1358]/(https://github.com/sigstore/sigstore-python/pull/1358)
* By default (when `--trust-config` is not used) the whole trust configuration now
Expand Down
3 changes: 1 addition & 2 deletions sigstore/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from sigstore._internal.fulcio.client import ExpiredCertificate
from sigstore._internal.rekor import _hashedrekord_from_parts
from sigstore._internal.rekor.client import RekorClient
from sigstore._internal.trust import ClientTrustConfig
from sigstore._utils import sha256_digest
from sigstore.dsse import StatementBuilder, Subject
from sigstore.dsse._predicate import (
Expand All @@ -48,7 +47,7 @@
)
from sigstore.errors import Error, VerificationError
from sigstore.hashes import Hashed
from sigstore.models import Bundle, InvalidBundle
from sigstore.models import Bundle, ClientTrustConfig, InvalidBundle
from sigstore.oidc import (
ExpiredIdentity,
IdentityToken,
Expand Down
107 changes: 4 additions & 103 deletions sigstore/_internal/trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,14 @@

from sigstore._internal.fulcio.client import FulcioClient
from sigstore._internal.rekor import RekorLogSubmitter
from sigstore._internal.rekor.client import RekorClient
from sigstore._internal.rekor.client_v2 import RekorV2Client
from sigstore._internal.timestamp import TimestampAuthorityClient
from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater
from sigstore._utils import (
KeyID,
PublicKey,
key_id,
load_der_public_key,
)
from sigstore.errors import Error, MetadataError, TUFError, VerificationError
from sigstore.errors import Error, MetadataError, VerificationError

# Versions supported by this client
REKOR_VERSIONS = [1, 2]
Expand Down Expand Up @@ -407,6 +404,9 @@ def get_tlogs(self) -> list[RekorLogSubmitter]:
"""
Returns the rekor transparency log clients to sign with.
"""
from sigstore._internal.rekor.client import RekorClient
from sigstore._internal.rekor.client_v2 import RekorV2Client

result: list[RekorLogSubmitter] = []
for tlog in self._tlogs:
if tlog.major_api_version == 1:
Expand Down Expand Up @@ -547,102 +547,3 @@ def get_timestamp_authorities(self) -> list[CertificateAuthority]:
for cert_chain in self._inner.timestamp_authorities
]
return certificate_authorities


class ClientTrustConfig:
"""
Represents a Sigstore client's trust configuration, including a root of trust.
"""

class ClientTrustConfigType(str, Enum):
"""
Known Sigstore client trust config media types.
"""

CONFIG_0_1 = "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json"

def __str__(self) -> str:
"""Returns the variant's string value."""
return self.value

@classmethod
def from_json(cls, raw: str) -> ClientTrustConfig:
"""
Deserialize the given client trust config.
"""
inner = trustroot_v1.ClientTrustConfig.from_json(raw)
return cls(inner)

@classmethod
def production(
cls,
offline: bool = False,
) -> ClientTrustConfig:
"""Create new trust config from Sigstore production TUF repository.

If `offline`, will use data in local TUF cache. Otherwise will
update the data from remote TUF repository.
"""
return cls.from_tuf(DEFAULT_TUF_URL, offline)

@classmethod
def staging(
cls,
offline: bool = False,
) -> ClientTrustConfig:
"""Create new trust config from Sigstore staging TUF repository.

If `offline`, will use data in local TUF cache. Otherwise will
update the data from remote TUF repository.
"""
return cls.from_tuf(STAGING_TUF_URL, offline)

@classmethod
def from_tuf(
cls,
url: str,
offline: bool = False,
) -> ClientTrustConfig:
"""Create a new trust config from a TUF repository.

If `offline`, will use data in local TUF cache. Otherwise will
update the trust config from remote TUF repository.
"""
updater = TrustUpdater(url, offline)

tr_path = updater.get_trusted_root_path()
inner_tr = trustroot_v1.TrustedRoot.from_json(Path(tr_path).read_bytes())

try:
sc_path = updater.get_signing_config_path()
inner_sc = trustroot_v1.SigningConfig.from_json(Path(sc_path).read_bytes())
except TUFError as e:
raise e

return cls(
trustroot_v1.ClientTrustConfig(
media_type=ClientTrustConfig.ClientTrustConfigType.CONFIG_0_1.value,
trusted_root=inner_tr,
signing_config=inner_sc,
)
)

def __init__(self, inner: trustroot_v1.ClientTrustConfig) -> None:
"""
@api private
"""
self._inner = inner

@property
def trusted_root(self) -> TrustedRoot:
"""
Return the interior root of trust, as a `TrustedRoot`.
"""
return TrustedRoot(self._inner.trusted_root)

@property
def signing_config(self) -> SigningConfig:
"""
Return the interior root of trust, as a `SigningConfig`.
"""
return SigningConfig(self._inner.signing_config)
106 changes: 106 additions & 0 deletions sigstore/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@
if typing.TYPE_CHECKING:
from sigstore._internal.trust import RekorKeyring

from pathlib import Path

from sigstore_models.trustroot import v1 as trustroot_v1

from sigstore._internal.trust import SigningConfig, TrustedRoot
from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater
from sigstore.errors import TUFError

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -593,3 +600,102 @@ def _from_parts(
)

return cls(inner)


class ClientTrustConfig:
"""
Represents a Sigstore client's trust configuration, including a root of trust.
"""

class ClientTrustConfigType(str, Enum):
"""
Known Sigstore client trust config media types.
"""

CONFIG_0_1 = "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json"

def __str__(self) -> str:
"""Returns the variant's string value."""
return self.value

@classmethod
def from_json(cls, raw: str) -> ClientTrustConfig:
"""
Deserialize the given client trust config.
"""
inner = trustroot_v1.ClientTrustConfig.from_json(raw)
return cls(inner)

@classmethod
def production(
cls,
offline: bool = False,
) -> ClientTrustConfig:
"""Create new trust config from Sigstore production TUF repository.

If `offline`, will use data in local TUF cache. Otherwise will
update the data from remote TUF repository.
"""
return cls.from_tuf(DEFAULT_TUF_URL, offline)

@classmethod
def staging(
cls,
offline: bool = False,
) -> ClientTrustConfig:
"""Create new trust config from Sigstore staging TUF repository.

If `offline`, will use data in local TUF cache. Otherwise will
update the data from remote TUF repository.
"""
return cls.from_tuf(STAGING_TUF_URL, offline)

@classmethod
def from_tuf(
cls,
url: str,
offline: bool = False,
) -> ClientTrustConfig:
"""Create a new trust config from a TUF repository.

If `offline`, will use data in local TUF cache. Otherwise will
update the trust config from remote TUF repository.
"""
updater = TrustUpdater(url, offline)

tr_path = updater.get_trusted_root_path()
inner_tr = trustroot_v1.TrustedRoot.from_json(Path(tr_path).read_bytes())

try:
sc_path = updater.get_signing_config_path()
inner_sc = trustroot_v1.SigningConfig.from_json(Path(sc_path).read_bytes())
except TUFError as e:
raise e

return cls(
trustroot_v1.ClientTrustConfig(
media_type=ClientTrustConfig.ClientTrustConfigType.CONFIG_0_1.value,
trusted_root=inner_tr,
signing_config=inner_sc,
)
)

def __init__(self, inner: trustroot_v1.ClientTrustConfig) -> None:
"""
@api private
"""
self._inner = inner

@property
def trusted_root(self) -> TrustedRoot:
"""
Return the interior root of trust, as a `TrustedRoot`.
"""
return TrustedRoot(self._inner.trusted_root)

@property
def signing_config(self) -> SigningConfig:
"""
Return the interior root of trust, as a `SigningConfig`.
"""
return SigningConfig(self._inner.signing_config)
4 changes: 2 additions & 2 deletions sigstore/sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@
from sigstore._internal.rekor import EntryRequestBody, RekorLogSubmitter
from sigstore._internal.sct import verify_sct
from sigstore._internal.timestamp import TimestampAuthorityClient, TimestampError
from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot
from sigstore._internal.trust import KeyringPurpose, TrustedRoot
from sigstore._utils import sha256_digest
from sigstore.models import Bundle
from sigstore.models import Bundle, ClientTrustConfig
from sigstore.oidc import ExpiredIdentity, IdentityToken

_logger = logging.getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions sigstore/verify/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@
verify_sct,
)
from sigstore._internal.timestamp import TimestampSource, TimestampVerificationResult
from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot
from sigstore._internal.trust import KeyringPurpose, TrustedRoot
from sigstore._utils import base64_encode_pem_cert, sha256_digest
from sigstore.errors import VerificationError
from sigstore.hashes import Hashed
from sigstore.models import Bundle
from sigstore.models import Bundle, ClientTrustConfig
from sigstore.verify.policy import VerificationPolicy

_logger = logging.getLogger(__name__)
Expand Down
3 changes: 1 addition & 2 deletions test/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@
from sigstore._internal import tuf
from sigstore._internal.rekor import _hashedrekord_from_parts
from sigstore._internal.rekor.client import RekorClient
from sigstore._internal.trust import ClientTrustConfig
from sigstore._utils import sha256_digest
from sigstore.models import Bundle
from sigstore.models import Bundle, ClientTrustConfig
from sigstore.oidc import IdentityToken
from sigstore.sign import SigningContext
from sigstore.verify.verifier import Verifier
Expand Down
2 changes: 1 addition & 1 deletion test/unit/internal/test_trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
from sigstore._internal.timestamp import TimestampAuthorityClient
from sigstore._internal.trust import (
CertificateAuthority,
ClientTrustConfig,
KeyringPurpose,
SigningConfig,
TrustedRoot,
_is_timerange_valid,
)
from sigstore._utils import load_pem_public_key
from sigstore.errors import Error
from sigstore.models import ClientTrustConfig

# Test data for TestSigningcconfig
_service_v1_op1 = Service(url="url1", major_api_version=1, operator="op1")
Expand Down
2 changes: 1 addition & 1 deletion test/unit/test_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

import sigstore.oidc
from sigstore._internal.timestamp import TimestampAuthorityClient
from sigstore._internal.trust import ClientTrustConfig
from sigstore.dsse import StatementBuilder, Subject
from sigstore.errors import VerificationError
from sigstore.hashes import Hashed
from sigstore.models import ClientTrustConfig
from sigstore.sign import SigningContext
from sigstore.verify.policy import UnsafeNoOp

Expand Down
Loading