diff --git a/.github/workflows/with-setup-sigstore-env.yml b/.github/workflows/with-setup-sigstore-env.yml new file mode 100644 index 000000000..2fd6debbe --- /dev/null +++ b/.github/workflows/with-setup-sigstore-env.yml @@ -0,0 +1,25 @@ +name: test-with-setup-sigstore-env + +on: + push: + branches: + - main + - series/* + pull_request: + schedule: + - cron: '0 12 * * *' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: sigstore/scaffolding/actions/setup-sigstore-env + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: ${{ matrix.conf.py }} + allow-prereleases: true + cache: "pip" + cache-dependency-path: pyproject.toml + - env: + TEST_SETUP_SIGSTORE_ENV: true + run: pytest -k test_sign_prehashed_rekorv2 \ No newline at end of file diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 80801579d..383aac4e5 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -18,6 +18,7 @@ from __future__ import annotations +import base64 import json import logging from abc import ABC @@ -26,8 +27,13 @@ import rekor_types import requests +from cryptography.hazmat.primitives import serialization +from cryptography.x509 import Certificate from sigstore._internal import USER_AGENT +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 +from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 +from sigstore.hashes import Hashed from sigstore.models import LogEntry _logger = logging.getLogger(__name__) @@ -35,6 +41,9 @@ DEFAULT_REKOR_URL = "https://rekor.sigstore.dev" STAGING_REKOR_URL = "https://rekor.sigstage.dev" +REKOR_V1_API_MAJOR_VERSION = 1 +REKOR_V2_API_MAJOR_VERSION = 2 + @dataclass(frozen=True) class RekorLogInfo: @@ -144,8 +153,7 @@ def get( return LogEntry._from_response(resp.json()) def post( - self, - proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse, + self, proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse ) -> LogEntry: """ Submit a new entry for inclusion in the Rekor log. @@ -239,6 +247,34 @@ def __del__(self) -> None: """ self.session.close() + @classmethod + def _build_hashed_rekord_request( + cls, + hashed_input: Hashed, + signature: bytes, + certificate: Certificate, + ) -> rekor_types.Hashedrekordkord: + return rekor_types.Hashedrekord( + spec=rekor_types.hashedrekord.HashedrekordV001Schema( + signature=rekor_types.hashedrekord.Signature( + content=base64.b64encode(signature).decode(), + public_key=rekor_types.hashedrekord.PublicKey( + content=base64.b64encode( + certificate.public_bytes( + encoding=serialization.Encoding.PEM + ) + ).decode() + ), + ), + data=rekor_types.hashedrekord.Data( + hash=rekor_types.hashedrekord.Hash( + algorithm=hashed_input._as_hashedrekord_algorithm(), + value=hashed_input.digest.hex(), + ) + ), + ), + ) + @classmethod def production(cls) -> RekorClient: """ @@ -261,3 +297,89 @@ def log(self) -> RekorLog: Returns a `RekorLog` adapter for making requests to a Rekor log. """ return RekorLog(f"{self.url}/log", session=self.session) + + +class RekorV2Client: + """The internal Rekor client for the v2 API""" + + # TODO: implement get_tile, get_entry_bundle, get_checkpoint. + + def __init__(self, base_url: str) -> None: + """ + Create a new `RekorV2Client` from the given URL. + """ + self.url = f"{base_url}/api/v2" + self.session = requests.Session() + self.session.headers.update( + { + "Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": USER_AGENT, + } + ) + + def __del__(self) -> None: + """ + Terminates the underlying network session. + """ + self.session.close() + + def create_entry(self, request: v2.CreateEntryRequest) -> LogEntry: + """ + Submit a new entry for inclusion in the Rekor log. + """ + # There may be a bug in betterproto, where the V_0_0_2 is changed to V002. + payload = request.to_dict() + _logger.debug(f"request: {json.dumps(payload)}") + resp = self.session.post(f"{self.url}/log/entries", json=payload) + + try: + resp.raise_for_status() + except requests.HTTPError as http_error: + raise RekorClientError(http_error) + + integrated_entry = resp.json() + _logger.debug(f"integrated: {integrated_entry}") + return LogEntry._from_dict_rekor(integrated_entry) + + @classmethod + def _build_create_entry_request( + cls, + hashed_input: Hashed, + signature: bytes, + certificate: Certificate, + key_details: v1.PublicKeyDetails, + ) -> v2.CreateEntryRequest: + return v2.CreateEntryRequest( + hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002( + digest=hashed_input.digest, + signature=v2.Signature( + content=signature, + verifier=v2.Verifier( + public_key=v2.PublicKey( + raw_bytes=certificate.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ), + key_details=key_details, + ), + ), + ) + ) + + @classmethod + def production(cls) -> RekorClient: + """ + Returns a `RekorClient` populated with the default Rekor production instance. + """ + return cls( + DEFAULT_REKOR_URL, + ) + + @classmethod + def staging(cls) -> RekorClient: + """ + Returns a `RekorClient` populated with the default Rekor staging instance. + """ + return cls(STAGING_REKOR_URL) diff --git a/sigstore/_internal/rekor_tiles/__init__.py b/sigstore/_internal/rekor_tiles/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/dev/__init__.py b/sigstore/_internal/rekor_tiles/dev/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py new file mode 100644 index 000000000..a2e4d138e --- /dev/null +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -0,0 +1,324 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: sigstore_common.proto +# plugin: python-betterproto +# This file has been @generated + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from dataclasses import dataclass +else: + from pydantic.dataclasses import dataclass + +from datetime import datetime +from typing import ( + List, + Optional, +) + +import betterproto +from pydantic import model_validator +from pydantic.dataclasses import rebuild_dataclass + + +class HashAlgorithm(betterproto.Enum): + """ + Only a subset of the secure hash standard algorithms are supported. + See for more + details. + UNSPECIFIED SHOULD not be used, primary reason for inclusion is to force + any proto JSON serialization to emit the used hash algorithm, as default + option is to *omit* the default value of an enum (which is the first + value, represented by '0'. + """ + + UNSPECIFIED = 0 + SHA2_256 = 1 + SHA2_384 = 2 + SHA2_512 = 3 + SHA3_256 = 4 + SHA3_384 = 5 + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type, _handler): + from pydantic_core import core_schema + + return core_schema.int_schema(ge=0) + + +class PublicKeyDetails(betterproto.Enum): + """ + Details of a specific public key, capturing the the key encoding method, + and signature algorithm. + + PublicKeyDetails captures the public key/hash algorithm combinations + recommended in the Sigstore ecosystem. + + This is modelled as a linear set as we want to provide a small number of + opinionated options instead of allowing every possible permutation. + + Any changes to this enum MUST be reflected in the algorithm registry. + See: docs/algorithm-registry.md + + To avoid the possibility of contradicting formats such as PKCS1 with + ED25519 the valid permutations are listed as a linear set instead of a + cartesian set (i.e one combined variable instead of two, one for encoding + and one for the signature algorithm). + """ + + UNSPECIFIED = 0 + PKCS1_RSA_PKCS1V5 = 1 + """RSA""" + + PKCS1_RSA_PSS = 2 + PKIX_RSA_PKCS1V5 = 3 + PKIX_RSA_PSS = 4 + PKIX_RSA_PKCS1V15_2048_SHA256 = 9 + """RSA public key in PKIX format, PKCS#1v1.5 signature""" + + PKIX_RSA_PKCS1V15_3072_SHA256 = 10 + PKIX_RSA_PKCS1V15_4096_SHA256 = 11 + PKIX_RSA_PSS_2048_SHA256 = 16 + """RSA public key in PKIX format, RSASSA-PSS signature""" + + PKIX_RSA_PSS_3072_SHA256 = 17 + PKIX_RSA_PSS_4096_SHA256 = 18 + PKIX_ECDSA_P256_HMAC_SHA_256 = 6 + """ECDSA""" + + PKIX_ECDSA_P256_SHA_256 = 5 + PKIX_ECDSA_P384_SHA_384 = 12 + PKIX_ECDSA_P521_SHA_512 = 13 + PKIX_ED25519 = 7 + """Ed 25519""" + + PKIX_ED25519_PH = 8 + PKIX_ECDSA_P384_SHA_256 = 19 + """ + These algorithms are deprecated and should not be used, but they + were/are being used by most Sigstore clients implementations. + """ + + PKIX_ECDSA_P521_SHA_256 = 20 + LMS_SHA256 = 14 + """ + LMS and LM-OTS + + These keys and signatures may be used by private Sigstore + deployments, but are not currently supported by the public + good instance. + + USER WARNING: LMS and LM-OTS are both stateful signature schemes. + Using them correctly requires discretion and careful consideration + to ensure that individual secret keys are not used more than once. + In addition, LM-OTS is a single-use scheme, meaning that it + MUST NOT be used for more than one signature per LM-OTS key. + If you cannot maintain these invariants, you MUST NOT use these + schemes. + """ + + LMOTS_SHA256 = 15 + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type, _handler): + from pydantic_core import core_schema + + return core_schema.int_schema(ge=0) + + +class SubjectAlternativeNameType(betterproto.Enum): + UNSPECIFIED = 0 + EMAIL = 1 + URI = 2 + OTHER_NAME = 3 + """ + OID 1.3.6.1.4.1.57264.1.7 + See https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726417--othername-san + for more details. + """ + + @classmethod + def __get_pydantic_core_schema__(cls, _source_type, _handler): + from pydantic_core import core_schema + + return core_schema.int_schema(ge=0) + + +@dataclass(eq=False, repr=False) +class HashOutput(betterproto.Message): + """ + HashOutput captures a digest of a 'message' (generic octet sequence) + and the corresponding hash algorithm used. + """ + + algorithm: "HashAlgorithm" = betterproto.enum_field(1) + digest: bytes = betterproto.bytes_field(2) + """ + This is the raw octets of the message digest as computed by + the hash algorithm. + """ + + +@dataclass(eq=False, repr=False) +class MessageSignature(betterproto.Message): + """MessageSignature stores the computed signature over a message.""" + + message_digest: "HashOutput" = betterproto.message_field(1) + """ + Message digest can be used to identify the artifact. + Clients MUST NOT attempt to use this digest to verify the associated + signature; it is intended solely for identification. + """ + + signature: bytes = betterproto.bytes_field(2) + """ + The raw bytes as returned from the signature algorithm. + The signature algorithm (and so the format of the signature bytes) + are determined by the contents of the 'verification_material', + either a key-pair or a certificate. If using a certificate, the + certificate contains the required information on the signature + algorithm. + When using a key pair, the algorithm MUST be part of the public + key, which MUST be communicated out-of-band. + """ + + +@dataclass(eq=False, repr=False) +class LogId(betterproto.Message): + """LogId captures the identity of a transparency log.""" + + key_id: bytes = betterproto.bytes_field(1) + """The unique identity of the log, represented by its public key.""" + + +@dataclass(eq=False, repr=False) +class Rfc3161SignedTimestamp(betterproto.Message): + """This message holds a RFC 3161 timestamp.""" + + signed_timestamp: bytes = betterproto.bytes_field(1) + """ + Signed timestamp is the DER encoded TimeStampResponse. + See https://www.rfc-editor.org/rfc/rfc3161.html#section-2.4.2 + """ + + +@dataclass(eq=False, repr=False) +class PublicKey(betterproto.Message): + raw_bytes: Optional[bytes] = betterproto.bytes_field(1, optional=True) + """ + DER-encoded public key, encoding method is specified by the + key_details attribute. + """ + + key_details: "PublicKeyDetails" = betterproto.enum_field(2) + """Key encoding and signature algorithm to use for this key.""" + + valid_for: Optional["TimeRange"] = betterproto.message_field(3, optional=True) + """Optional validity period for this key, *inclusive* of the endpoints.""" + + +@dataclass(eq=False, repr=False) +class PublicKeyIdentifier(betterproto.Message): + """ + PublicKeyIdentifier can be used to identify an (out of band) delivered + key, to verify a signature. + """ + + hint: str = betterproto.string_field(1) + """ + Optional unauthenticated hint on which key to use. + The format of the hint must be agreed upon out of band by the + signer and the verifiers, and so is not subject to this + specification. + Example use-case is to specify the public key to use, from a + trusted key-ring. + Implementors are RECOMMENDED to derive the value from the public + key as described in RFC 6962. + See: + """ + + +@dataclass(eq=False, repr=False) +class ObjectIdentifier(betterproto.Message): + """An ASN.1 OBJECT IDENTIFIER""" + + id: list[int] = betterproto.int32_field(1) + + +@dataclass(eq=False, repr=False) +class ObjectIdentifierValuePair(betterproto.Message): + """An OID and the corresponding (byte) value.""" + + oid: "ObjectIdentifier" = betterproto.message_field(1) + value: bytes = betterproto.bytes_field(2) + + +@dataclass(eq=False, repr=False) +class DistinguishedName(betterproto.Message): + organization: str = betterproto.string_field(1) + common_name: str = betterproto.string_field(2) + + +@dataclass(eq=False, repr=False) +class X509Certificate(betterproto.Message): + raw_bytes: bytes = betterproto.bytes_field(1) + """DER-encoded X.509 certificate.""" + + +@dataclass(eq=False, repr=False) +class SubjectAlternativeName(betterproto.Message): + type: "SubjectAlternativeNameType" = betterproto.enum_field(1) + regexp: Optional[str] = betterproto.string_field(2, optional=True, group="identity") + """ + A regular expression describing the expected value for + the SAN. + """ + + value: Optional[str] = betterproto.string_field(3, optional=True, group="identity") + """The exact value to match against.""" + + @model_validator(mode="after") + def check_oneof(cls, values): + return cls._validate_field_groups(values) + + +@dataclass(eq=False, repr=False) +class X509CertificateChain(betterproto.Message): + """ + A collection of X.509 certificates. + + This "chain" can be used in multiple contexts, such as providing a root CA + certificate within a TUF root of trust or multiple untrusted certificates for + the purpose of chain building. + """ + + certificates: list["X509Certificate"] = betterproto.message_field(1) + """ + One or more DER-encoded certificates. + + In some contexts (such as `VerificationMaterial.x509_certificate_chain`), this sequence + has an imposed order. Unless explicitly specified, there is otherwise no + guaranteed order. + """ + + +@dataclass(eq=False, repr=False) +class TimeRange(betterproto.Message): + """ + The time range is closed and includes both the start and end times, + (i.e., [start, end]). + End is optional to be able to capture a period that has started but + has no known end. + """ + + start: datetime = betterproto.message_field(1) + end: Optional[datetime] = betterproto.message_field(2, optional=True) + + +rebuild_dataclass(HashOutput) # type: ignore +rebuild_dataclass(MessageSignature) # type: ignore +rebuild_dataclass(PublicKey) # type: ignore +rebuild_dataclass(ObjectIdentifierValuePair) # type: ignore +rebuild_dataclass(SubjectAlternativeName) # type: ignore +rebuild_dataclass(X509CertificateChain) # type: ignore +rebuild_dataclass(TimeRange) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py new file mode 100644 index 000000000..16e54761e --- /dev/null +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -0,0 +1,181 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: sigstore_rekor.proto +# plugin: python-betterproto +# This file has been @generated + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from dataclasses import dataclass +else: + from pydantic.dataclasses import dataclass + +from typing import List + +import betterproto +from pydantic.dataclasses import rebuild_dataclass + +from ...common import v1 as __common_v1__ + + +@dataclass(eq=False, repr=False) +class KindVersion(betterproto.Message): + """KindVersion contains the entry's kind and api version.""" + + kind: str = betterproto.string_field(1) + """ + Kind is the type of entry being stored in the log. + See here for a list: https://github.com/sigstore/rekor/tree/main/pkg/types + """ + + version: str = betterproto.string_field(2) + """The specific api version of the type.""" + + +@dataclass(eq=False, repr=False) +class Checkpoint(betterproto.Message): + """ + The checkpoint MUST contain an origin string as a unique log identifier, + the tree size, and the root hash. It MAY also be followed by optional data, + and clients MUST NOT assume optional data. The checkpoint MUST also contain + a signature over the root hash (tree head). The checkpoint MAY contain additional + signatures, but the first SHOULD be the signature from the log. Checkpoint contents + are concatenated with newlines into a single string. + The checkpoint format is described in + https://github.com/transparency-dev/formats/blob/main/log/README.md + and https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md. + An example implementation can be found in https://github.com/sigstore/rekor/blob/main/pkg/util/signed_note.go + """ + + envelope: str = betterproto.string_field(1) + + +@dataclass(eq=False, repr=False) +class InclusionProof(betterproto.Message): + """ + InclusionProof is the proof returned from the transparency log. Can + be used for offline or online verification against the log. + """ + + log_index: int = betterproto.int64_field(1) + """The index of the entry in the tree it was written to.""" + + root_hash: bytes = betterproto.bytes_field(2) + """ + The hash digest stored at the root of the merkle tree at the time + the proof was generated. + """ + + tree_size: int = betterproto.int64_field(3) + """The size of the merkle tree at the time the proof was generated.""" + + hashes: list[bytes] = betterproto.bytes_field(4) + """ + A list of hashes required to compute the inclusion proof, sorted + in order from leaf to root. + Note that leaf and root hashes are not included. + The root hash is available separately in this message, and the + leaf hash should be calculated by the client. + """ + + checkpoint: "Checkpoint" = betterproto.message_field(5) + """ + Signature of the tree head, as of the time of this proof was + generated. See above info on 'Checkpoint' for more details. + """ + + +@dataclass(eq=False, repr=False) +class InclusionPromise(betterproto.Message): + """ + The inclusion promise is calculated by Rekor. It's calculated as a + signature over a canonical JSON serialization of the persisted entry, the + log ID, log index and the integration timestamp. + See https://github.com/sigstore/rekor/blob/a6e58f72b6b18cc06cefe61808efd562b9726330/pkg/api/entries.go#L54 + The format of the signature depends on the transparency log's public key. + If the signature algorithm requires a hash function and/or a signature + scheme (e.g. RSA) those has to be retrieved out-of-band from the log's + operators, together with the public key. + This is used to verify the integration timestamp's value and that the log + has promised to include the entry. + """ + + signed_entry_timestamp: bytes = betterproto.bytes_field(1) + + +@dataclass(eq=False, repr=False) +class TransparencyLogEntry(betterproto.Message): + """ + TransparencyLogEntry captures all the details required from Rekor to + reconstruct an entry, given that the payload is provided via other means. + This type can easily be created from the existing response from Rekor. + Future iterations could rely on Rekor returning the minimal set of + attributes (excluding the payload) that are required for verifying the + inclusion promise. The inclusion promise (called SignedEntryTimestamp in + the response from Rekor) is similar to a Signed Certificate Timestamp + as described here https://www.rfc-editor.org/rfc/rfc6962.html#section-3.2. + """ + + log_index: int = betterproto.int64_field(1) + """The global index of the entry, used when querying the log by index.""" + + log_id: "__common_v1__.LogId" = betterproto.message_field(2) + """The unique identifier of the log.""" + + kind_version: "KindVersion" = betterproto.message_field(3) + """ + The kind (type) and version of the object associated with this + entry. These values are required to construct the entry during + verification. + """ + + integrated_time: int = betterproto.int64_field(4) + """ + The UNIX timestamp from the log when the entry was persisted. + The integration time MUST NOT be trusted if inclusion_promise + is omitted. + """ + + inclusion_promise: "InclusionPromise" = betterproto.message_field(5) + """ + The inclusion promise/signed entry timestamp from the log. + Required for v0.1 bundles, and MUST be verified. + Optional for >= v0.2 bundles if another suitable source of + time is present (such as another source of signed time, + or the current system time for long-lived certificates). + MUST be verified if no other suitable source of time is present, + and SHOULD be verified otherwise. + """ + + inclusion_proof: "InclusionProof" = betterproto.message_field(6) + """ + The inclusion proof can be used for offline or online verification + that the entry was appended to the log, and that the log has not been + altered. + """ + + canonicalized_body: bytes = betterproto.bytes_field(7) + """ + Optional. The canonicalized transparency log entry, used to + reconstruct the Signed Entry Timestamp (SET) during verification. + The contents of this field are the same as the `body` field in + a Rekor response, meaning that it does **not** include the "full" + canonicalized form (of log index, ID, etc.) which are + exposed as separate fields. The verifier is responsible for + combining the `canonicalized_body`, `log_index`, `log_id`, + and `integrated_time` into the payload that the SET's signature + is generated over. + This field is intended to be used in cases where the SET cannot be + produced determinisitically (e.g. inconsistent JSON field ordering, + differing whitespace, etc). + + If set, clients MUST verify that the signature referenced in the + `canonicalized_body` matches the signature provided in the + `Bundle.content`. + If not set, clients are responsible for constructing an equivalent + payload from other sources to verify the signature. + """ + + +rebuild_dataclass(InclusionProof) # type: ignore +rebuild_dataclass(TransparencyLogEntry) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py new file mode 100644 index 000000000..9d267496c --- /dev/null +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -0,0 +1,199 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: dsse.proto, entry.proto, hashedrekord.proto, rekor_service.proto, verifier.proto +# plugin: python-betterproto +# This file has been @generated + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from dataclasses import dataclass +else: + from pydantic.dataclasses import dataclass + +from typing import ( + List, + Optional, +) + +import betterproto +from pydantic import model_validator +from pydantic.dataclasses import rebuild_dataclass + +from .....io import intoto as ____io_intoto__ +from ...common import v1 as __common_v1__ + + +@dataclass(eq=False, repr=False) +class PublicKey(betterproto.Message): + """PublicKey contains an encoded public key""" + + raw_bytes: bytes = betterproto.bytes_field(1) + """DER-encoded public key""" + + +@dataclass(eq=False, repr=False) +class Verifier(betterproto.Message): + """ + Either a public key or a X.509 cerificiate with an embedded public key + """ + + public_key: Optional["PublicKey"] = betterproto.message_field( + 1, optional=True, group="verifier" + ) + """ + DER-encoded public key. Encoding method is specified by the key_details attribute + """ + + x509_certificate: Optional["__common_v1__.X509Certificate"] = ( + betterproto.message_field(2, optional=True, group="verifier") + ) + """DER-encoded certificate""" + + key_details: "__common_v1__.PublicKeyDetails" = betterproto.enum_field(3) + """Key encoding and signature algorithm to use for this key""" + + @model_validator(mode="after") + def check_oneof(cls, values): + return cls._validate_field_groups(values) + + +@dataclass(eq=False, repr=False) +class Signature(betterproto.Message): + """A signature and an associated verifier""" + + content: bytes = betterproto.bytes_field(1) + verifier: "Verifier" = betterproto.message_field(2) + + +@dataclass(eq=False, repr=False) +class HashedRekordRequestV002(betterproto.Message): + """A request to add a hashedrekord to the log""" + + digest: bytes = betterproto.bytes_field(1) + """The hashed data""" + + signature: "Signature" = betterproto.message_field(2) + """ + A single signature over the hashed data with the verifier needed to validate it + """ + + +@dataclass(eq=False, repr=False) +class HashedRekordLogEntryV002(betterproto.Message): + data: "__common_v1__.HashOutput" = betterproto.message_field(1) + """The hashed data""" + + signature: "Signature" = betterproto.message_field(2) + """ + A single signature over the hashed data with the verifier needed to validate it + """ + + +@dataclass(eq=False, repr=False) +class DsseRequestV002(betterproto.Message): + """A request to add a DSSE entry to the log""" + + envelope: "____io_intoto__.Envelope" = betterproto.message_field(1) + """A DSSE envelope""" + + verifiers: list["Verifier"] = betterproto.message_field(2) + """ + All necessary verification material to verify all signatures embedded in the envelope + """ + + +@dataclass(eq=False, repr=False) +class DsseLogEntryV002(betterproto.Message): + payload_hash: "__common_v1__.HashOutput" = betterproto.message_field(1) + """The hash of the DSSE payload""" + + signatures: list["Signature"] = betterproto.message_field(2) + """ + Signatures and their associated verification material used to verify the payload + """ + + +@dataclass(eq=False, repr=False) +class Entry(betterproto.Message): + """ + Entry is the message that is canonicalized and uploaded to the log. + This format is meant to be compliant with Rekor v1 entries in that + the `apiVersion` and `kind` can be parsed before parsing the spec. + Clients are expected to understand and handle the differences in the + contents of `spec` between Rekor v1 (a polymorphic OpenAPI defintion) + and Rekor v2 (a typed proto defintion). + """ + + kind: str = betterproto.string_field(1) + api_version: str = betterproto.string_field(2) + spec: "Spec" = betterproto.message_field(3) + + +@dataclass(eq=False, repr=False) +class Spec(betterproto.Message): + """Spec contains one of the Rekor entry types.""" + + hashed_rekord_v0_0_2: Optional["HashedRekordLogEntryV002"] = ( + betterproto.message_field(1, optional=True, group="spec") + ) + dsse_v0_0_2: Optional["DsseLogEntryV002"] = betterproto.message_field( + 2, optional=True, group="spec" + ) + + @model_validator(mode="after") + def check_oneof(cls, values): + return cls._validate_field_groups(values) + + +@dataclass(eq=False, repr=False) +class CreateEntryRequest(betterproto.Message): + """Create a new HashedRekord or DSSE""" + + hashed_rekord_request_v0_0_2: Optional["HashedRekordRequestV002"] = ( + betterproto.message_field(1, optional=True, group="spec") + ) + dsse_request_v0_0_2: Optional["DsseRequestV002"] = betterproto.message_field( + 2, optional=True, group="spec" + ) + + @model_validator(mode="after") + def check_oneof(cls, values): + return cls._validate_field_groups(values) + + +@dataclass(eq=False, repr=False) +class TileRequest(betterproto.Message): + """ + Request for a full or partial tile (see https://github.com/C2SP/C2SP/blob/main/tlog-tiles.md#merkle-tree) + """ + + l: int = betterproto.uint32_field(1) + n: str = betterproto.string_field(2) + """ + N must be either an index encoded as zero-padded 3-digit path elements, e.g. "x123/x456/789", + and may end with ".p/", where "" is a uint8 + """ + + +@dataclass(eq=False, repr=False) +class EntryBundleRequest(betterproto.Message): + """ + Request for a full or partial entry bundle (see https://github.com/C2SP/C2SP/blob/main/tlog-tiles.md#log-entries) + """ + + n: str = betterproto.string_field(1) + """ + N must be either an index encoded as zero-padded 3-digit path elements, e.g. "x123/x456/789", + and may end with ".p/", where "" is a uint8 + """ + + +rebuild_dataclass(Verifier) # type: ignore +rebuild_dataclass(Signature) # type: ignore +rebuild_dataclass(HashedRekordRequestV002) # type: ignore +rebuild_dataclass(HashedRekordLogEntryV002) # type: ignore +rebuild_dataclass(DsseRequestV002) # type: ignore +rebuild_dataclass(DsseLogEntryV002) # type: ignore +rebuild_dataclass(Entry) # type: ignore +rebuild_dataclass(Spec) # type: ignore +rebuild_dataclass(CreateEntryRequest) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/io/__init__.py b/sigstore/_internal/rekor_tiles/io/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py new file mode 100644 index 000000000..da1edc772 --- /dev/null +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -0,0 +1,64 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: envelope.proto +# plugin: python-betterproto +# This file has been @generated + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from dataclasses import dataclass +else: + from pydantic.dataclasses import dataclass + +from typing import List + +import betterproto +from pydantic.dataclasses import rebuild_dataclass + + +@dataclass(eq=False, repr=False) +class Envelope(betterproto.Message): + """An authenticated message of arbitrary type.""" + + payload: bytes = betterproto.bytes_field(1) + """ + Message to be signed. (In JSON, this is encoded as base64.) + REQUIRED. + """ + + payload_type: str = betterproto.string_field(2) + """ + String unambiguously identifying how to interpret payload. + REQUIRED. + """ + + signatures: list["Signature"] = betterproto.message_field(3) + """ + Signature over: + PAE(type, payload) + Where PAE is defined as: + PAE(type, payload) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(payload) + SP + payload + + = concatenation + SP = ASCII space [0x20] + "DSSEv1" = ASCII [0x44, 0x53, 0x53, 0x45, 0x76, 0x31] + LEN(s) = ASCII decimal encoding of the byte length of s, with no leading zeros + REQUIRED (length >= 1). + """ + + +@dataclass(eq=False, repr=False) +class Signature(betterproto.Message): + sig: bytes = betterproto.bytes_field(1) + """ + Signature itself. (In JSON, this is encoded as base64.) + REQUIRED. + """ + + keyid: str = betterproto.string_field(2) + """ + *Unauthenticated* hint identifying which public key was used. + OPTIONAL. + """ + + +rebuild_dataclass(Envelope) # type: ignore diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index f77181ef0..36aef3ddf 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -358,7 +358,7 @@ def from_file( @staticmethod def _get_valid_service_url(services: list[Service]) -> str | None: for service in services: - if service.major_api_version != 1: + if service.major_api_version not in [1, 2]: continue if not _is_timerange_valid(service.valid_for, allow_expired=False): diff --git a/sigstore/models.py b/sigstore/models.py index e4ff5462e..a2ccffac0 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -63,6 +63,7 @@ from sigstore import dsse from sigstore._internal.merkle import verify_merkle_inclusion from sigstore._internal.rekor.checkpoint import verify_checkpoint +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 from sigstore._utils import ( B64Str, KeyID, @@ -74,6 +75,8 @@ if typing.TYPE_CHECKING: from sigstore._internal.trust import RekorKeyring +DEFAULT_KEY_DETAILS = v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 + _logger = logging.getLogger(__name__) diff --git a/sigstore/sign.py b/sigstore/sign.py index 643cc7960..89d2afa8e 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -48,7 +48,7 @@ import cryptography.x509 as x509 import rekor_types from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.x509.oid import NameOID from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( HashOutput, @@ -61,7 +61,14 @@ ExpiredCertificate, FulcioClient, ) -from sigstore._internal.rekor.client import RekorClient +from sigstore._internal.rekor.client import ( + REKOR_V1_API_MAJOR_VERSION, + RekorClient, + RekorV2Client, +) +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 +from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 +from sigstore._internal.rekor_tiles.io import intoto from sigstore._internal.sct import verify_sct from sigstore._internal.timestamp import TimestampAuthorityClient, TimestampError from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot @@ -72,6 +79,20 @@ _logger = logging.getLogger(__name__) +def key_to_details( + key: ec.EllipticCurvePrivateKey | rsa.RSAPrivateKey, +) -> v1.PublicKeyDetails: + """ + Converts a key to a PublicKeyDetails. Although, the key type is currently hardcoded to a single type. + """ + if isinstance(key, ec.EllipticCurvePrivateKey) and isinstance( + key.curve, ec.SECP256R1 + ): + return v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 + else: + raise Exception(f"unsupported key type {key}") + + class Signer: """ The primary API for signing operations. @@ -176,13 +197,19 @@ def _finalize_sign( self, cert: x509.Certificate, content: MessageSignature | dsse.Envelope, - proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse, + proposed_entry: rekor_types.Hashedrekord + | rekor_types.Dsse + | v2.CreateEntryRequest, ) -> Bundle: """ Perform the common "finalizing" steps in a Sigstore signing flow. """ # Submit the proposed entry to the transparency log - entry = self._signing_ctx._rekor.log.entries.post(proposed_entry) + client = self._signing_ctx._rekor + if isinstance(client, RekorV2Client): + entry = client.create_entry(proposed_entry) + else: + entry = self._signing_ctx._rekor.log.entries.post(proposed_entry) _logger.debug(f"Transparency log entry created with index: {entry.log_index}") @@ -211,28 +238,56 @@ def sign_dsse( """ cert = self._signing_cert() - # Prepare inputs - b64_cert = base64.b64encode( - cert.public_bytes(encoding=serialization.Encoding.PEM) - ) - # Sign the statement, producing a DSSE envelope content = dsse._sign(self._private_key, input_) - # Create the proposed DSSE log entry - proposed_entry = rekor_types.Dsse( - spec=rekor_types.dsse.DsseSchema( - # NOTE: mypy can't see that this kwarg is correct due to two interacting - # behaviors/bugs (one pydantic, one datamodel-codegen): - # See: - # See: - proposed_content=rekor_types.dsse.ProposedContent( # type: ignore[call-arg] - envelope=content.to_json(), - verifiers=[b64_cert.decode()], + # Prepare inputs + if isinstance(self._signing_ctx._rekor, RekorV2Client): + proposed_entry = v2.CreateEntryRequest( + dsse_request_v0_0_2=v2.DsseRequestV002( + # TOODO: fix when the types from intoto no longer come from different import paths. + envelope=intoto.Envelope( + payload=content._inner.payload, + payload_type=content._inner.payload_type, + signatures=[ + intoto.Signature( + keyid=signature.keyid, + sig=signature.sig, + ) + for signature in content._inner.signatures + ], + ), + verifiers=[ + v2.Verifier( + public_key=v2.PublicKey( + raw_bytes=cert.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ), + key_details=key_to_details(self._private_key), + ) + ], ), - ), - ) + ) + else: + b64_cert = base64.b64encode( + cert.public_bytes(encoding=serialization.Encoding.PEM) + ) + # Create the proposed DSSE log entry + proposed_entry = rekor_types.Dsse( + spec=rekor_types.dsse.DsseSchema( + # NOTE: mypy can't see that this kwarg is correct due to two interacting + # behaviors/bugs (one pydantic, one datamodel-codegen): + # See: + # See: + proposed_content=rekor_types.dsse.ProposedContent( # type: ignore[call-arg] + envelope=content.to_json(), + verifiers=[b64_cert.decode()], + ), + ), + ) return self._finalize_sign(cert, content, proposed_entry) def sign_artifact( @@ -255,11 +310,6 @@ def sign_artifact( cert = self._signing_cert() - # Prepare inputs - b64_cert = base64.b64encode( - cert.public_bytes(encoding=serialization.Encoding.PEM) - ) - # Sign artifact hashed_input = sha256_digest(input_) @@ -276,23 +326,19 @@ def sign_artifact( ) # Create the proposed hashedrekord entry - proposed_entry = rekor_types.Hashedrekord( - spec=rekor_types.hashedrekord.HashedrekordV001Schema( - signature=rekor_types.hashedrekord.Signature( - content=base64.b64encode(artifact_signature).decode(), - public_key=rekor_types.hashedrekord.PublicKey( - content=b64_cert.decode() - ), - ), - data=rekor_types.hashedrekord.Data( - hash=rekor_types.hashedrekord.Hash( - algorithm=hashed_input._as_hashedrekord_algorithm(), - value=hashed_input.digest.hex(), - ) - ), - ), - ) - + if isinstance(self._signing_ctx._rekor, RekorV2Client): + proposed_entry = RekorV2Client._build_create_entry_request( + hashed_input=hashed_input, + signature=artifact_signature, + certificate=cert, + key_details=key_to_details(self._private_key), + ) + else: + proposed_entry = RekorClient._build_hashed_rekord_request( + hashed_input=hashed_input, + signature=artifact_signature, + certificate=cert, + ) return self._finalize_sign(cert, content, proposed_entry) @@ -305,7 +351,7 @@ def __init__( self, *, fulcio: FulcioClient, - rekor: RekorClient, + rekor: RekorClient | RekorV2Client, trusted_root: TrustedRoot, tsa_clients: list[TimestampAuthorityClient] | None = None, ): @@ -331,9 +377,18 @@ def from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext: @api private """ signing_config = trust_config.signing_config + if ( + signing_config._inner.rekor_tlog_urls[0].major_api_version + == REKOR_V1_API_MAJOR_VERSION + ): + rekor_client = RekorClient( + signing_config.get_tlog_urls()[0], + ) + else: + rekor_client = RekorV2Client(signing_config.get_tlog_urls()[0]) return cls( fulcio=FulcioClient(signing_config.get_fulcio_url()), - rekor=RekorClient(signing_config.get_tlog_urls()[0]), + rekor=rekor_client, trusted_root=trust_config.trusted_root, tsa_clients=[ TimestampAuthorityClient(url) for url in signing_config.get_tsa_urls() diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index e05a3d7c7..266054a97 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -22,9 +22,11 @@ import logging from datetime import datetime, timezone from typing import cast +import json import rekor_types from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.x509 import ExtendedKeyUsage, KeyUsage from cryptography.x509.oid import ExtendedKeyUsageOID @@ -39,9 +41,11 @@ from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError -from sigstore import dsse +from sigstore import dsse, models from sigstore._internal.rekor import _hashedrekord_from_parts from sigstore._internal.rekor.client import RekorClient +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 +from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 from sigstore._internal.sct import ( verify_sct, ) @@ -422,34 +426,90 @@ def verify_dsse( # Instead, we manually pick apart the entry body below and verify # the parts we can (namely the payload hash and signature list). entry = bundle.log_entry - try: - entry_body = rekor_types.Dsse.model_validate_json( - base64.b64decode(entry.body) - ) - except ValidationError as exc: - raise VerificationError(f"invalid DSSE log entry: {exc}") - payload_hash = sha256_digest(envelope._inner.payload).digest.hex() + # TODO: make this branching cleaner if ( - entry_body.spec.root.payload_hash.algorithm # type: ignore[union-attr] - != rekor_types.dsse.Algorithm.SHA256 + entry._kind_version.kind == "dsse" + and entry._kind_version.version == "0.0.2" ): - raise VerificationError("expected SHA256 payload hash in DSSE log entry") - if payload_hash != entry_body.spec.root.payload_hash.value: # type: ignore[union-attr] - raise VerificationError("log entry payload hash does not match bundle") - - # NOTE: Like `dsse._verify`: multiple signatures would be frivolous here, - # but we handle them just in case the signer has somehow produced multiple - # signatures for their envelope with the same signing key. - signatures = [ - rekor_types.dsse.Signature( - signature=base64.b64encode(signature.sig).decode(), - verifier=base64_encode_pem_cert(bundle.signing_certificate), - ) - for signature in envelope._inner.signatures - ] - if signatures != entry_body.spec.root.signatures: - raise VerificationError("log entry signatures do not match bundle") + try: + # entry_body = v2.Entry().from_json(base64.b64decode(entry.body)) + # A potential issue with the returned body not parsing properly. + # perhaps the json_name in the protos. + _dict = json.loads(base64.b64decode(entry.body)) + actual_body = v2.Entry( + kind=_dict['kind'], + api_version=_dict['apiVersion'], + spec=v2.Spec( + hashed_rekord_v0_0_2=v2.HashedRekordLogEntryV002.from_dict( + _dict['spec']['dsseV002'] + ) + ) + ) + except ValidationError as exc: + raise VerificationError(f"invalid DSSE log entry: {exc}") + + payload_hash = sha256_digest(envelope._inner.payload).digest + if ( + entry_body.spec.dsse_v0_0_2.payload_hash.algorithm + != v1.HashAlgorithm.SHA2_256 + ): + raise VerificationError( + "expected SHA256 payload hash in DSSE log entry" + ) + if entry_body.spec.dsse_v0_0_2.payload_hash.digest != payload_hash: + raise VerificationError("log entry payload hash does not match bundle") + + signatures = [ + v2.Signature( + content=signature.sig, + verifier=v2.Verifier( + public_key=v2.PublicKey( + raw_bytes=bundle.signing_certificate.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ), + key_details=models.DEFAULT_KEY_DETAILS, + ), + ) + for signature in envelope._inner.signatures + ] + if signatures != entry_body.spec.dsse_v0_0_2.signatures: + raise VerificationError("log entry signatures do not match bundle") + else: + try: + entry_body = rekor_types.Dsse.model_validate_json( + base64.b64decode(entry.body) + ) + except ValidationError as exc: + raise VerificationError(f"invalid DSSE log entry: {exc}") + + payload_hash = sha256_digest(envelope._inner.payload).digest.hex() + if ( + # type: ignore[union-attr] + entry_body.spec.root.payload_hash.algorithm + != rekor_types.dsse.Algorithm.SHA256 + ): + raise VerificationError( + "expected SHA256 payload hash in DSSE log entry" + ) + # type: ignore[union-attr] + if payload_hash != entry_body.spec.root.payload_hash.value: + raise VerificationError("log entry payload hash does not match bundle") + + # NOTE: Like `dsse._verify`: multiple signatures would be frivolous here, + # but we handle them just in case the signer has somehow produced multiple + # signatures for their envelope with the same signing key. + signatures = [ + rekor_types.dsse.Signature( + signature=base64.b64encode(signature.sig).decode(), + verifier=base64_encode_pem_cert(bundle.signing_certificate), + ) + for signature in envelope._inner.signatures + ] + if signatures != entry_body.spec.root.signatures: + raise VerificationError("log entry signatures do not match bundle") return (envelope._inner.payload_type, envelope._inner.payload) @@ -495,14 +555,57 @@ def verify_artifact( # the other bundle materials (and input being verified). entry = bundle.log_entry - expected_body = _hashedrekord_from_parts( - bundle.signing_certificate, - bundle._inner.message_signature.signature, # type: ignore[union-attr] - hashed_input, - ) - actual_body = rekor_types.Hashedrekord.model_validate_json( - base64.b64decode(entry.body) - ) + if ( + entry._kind_version.kind == "hashedrekord" + and entry._kind_version.version == "0.0.2" + ): + _dict = json.loads(base64.b64decode(entry.body)) + print(_dict) + # A potential issue with the returned body not parsing properly. + # perhaps the json_name in the protos. + actual_body = v2.Entry( + kind=_dict['kind'], + api_version=_dict['apiVersion'], + spec=v2.Spec( + hashed_rekord_v0_0_2=v2.HashedRekordLogEntryV002.from_dict( + _dict['spec']['hashedRekordV002'] + ) + ) + ) + expected_body = v2.Entry( + kind=entry._kind_version.kind, + api_version=entry._kind_version.version, + spec=v2.Spec( + hashed_rekord_v0_0_2=v2.HashedRekordLogEntryV002( + data=v1.HashOutput( + algorithm=bundle._inner.message_signature.message_digest.algorithm, + digest=bundle._inner.message_signature.message_digest.digest, + ), + signature=v2.Signature( + content=bundle._inner.message_signature.signature, + verifier=v2.Verifier( + public_key=v2.PublicKey( + raw_bytes=bundle.signing_certificate.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ), + key_details=models.DEFAULT_KEY_DETAILS, + ), + ), + ) + ), + ) + else: + expected_body = _hashedrekord_from_parts( + bundle.signing_certificate, + # type: ignore[union-attr] + bundle._inner.message_signature.signature, + hashed_input, + ) + actual_body = rekor_types.Hashedrekord.model_validate_json( + base64.b64decode(entry.body) + ) if expected_body != actual_body: raise VerificationError( "transparency log entry is inconsistent with other materials" diff --git a/test/assets/config.v1.rekorv2_local.json b/test/assets/config.v1.rekorv2_local.json new file mode 100644 index 000000000..d18e09d16 --- /dev/null +++ b/test/assets/config.v1.rekorv2_local.json @@ -0,0 +1,128 @@ +{ + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MCowBQYDK2VwAyEAREvJyNZGjX6B3DAIuD3BTg9rIwV00GY8Xg5FU+IFDUQ=", + "keyDetails": "PKIX_ED25519", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "tAlACZWkUrif9Z9sOIrpk1ak1I8loRNufk79N6l1SNg=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2021-10-07T13:56:59Z" + } + } + ], + "ctlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbbQiLx6GKy6ivhc11wJGbQjc2VX/mnuk5d670MTXR3p+LIAcxd5MhqIHpLmyYJ5mDKLEoZ/pC0nPuje3JueBcA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "ekJiz/ZpG+UEn5w/GaIr6+awI+RKfkpt/V9Teu7va1k=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore-tsa-selfsigned" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUCPExEFKiQh0dP4sp5ltmSYSSkFUwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATt0tIDWyo4ARfL9BaSo0W5bJQEbKJTU/u7llvdjSI5aTkOAJa8tixn2+LEfPG4dMFdsMPtsIuU1qn2OqFiuMk6vHv/c+az25RQVY1oo50iMb0jIL3N4FgwhPFpZnCbQPOjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAKBggqhkjOPQQDAwNpADBmAjEA2MI1VXgbf3dUOSc95hSRypBKOab18eh2xzQtxUsHvWeY+1iFgyMluUuNR6taoSmFAjEA31m2czguZhKYX+4JSKu5pRYhBTXAd8KKQ3xdPRX/qCaLvT2qJAEQ1YQM3EJRrtI7" + } + ] + }, + "validFor": { + "start": "2025-03-28T09:14:06Z" + } + } + ] + }, + "signingConfig": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstore.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2023-04-14T21:38:40Z" + } + } + ], + "rekorTlogUrls": [ + { + "url": "http://localhost:3003", + "majorApiVersion": 2, + "validFor": { + "start": "2021-01-12T11:53:27Z" + } + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstage.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + }, + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" +} diff --git a/test/assets/integration/trust_config/config.v1.rekorv2_local.json b/test/assets/integration/trust_config/config.v1.rekorv2_local.json new file mode 100644 index 000000000..eee239e5e --- /dev/null +++ b/test/assets/integration/trust_config/config.v1.rekorv2_local.json @@ -0,0 +1,137 @@ +{ + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MCowBQYDK2VwAyEAREvJyNZGjX6B3DAIuD3BTg9rIwV00GY8Xg5FU+IFDUQ=", + "keyDetails": "PKIX_ED25519", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "tAlACZWkUrif9Z9sOIrpk1ak1I8loRNufk79N6l1SNg=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2021-10-07T13:56:59Z" + } + } + ], + "ctlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbbQiLx6GKy6ivhc11wJGbQjc2VX/mnuk5d670MTXR3p+LIAcxd5MhqIHpLmyYJ5mDKLEoZ/pC0nPuje3JueBcA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "ekJiz/ZpG+UEn5w/GaIr6+awI+RKfkpt/V9Teu7va1k=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore-tsa-selfsigned" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUCPExEFKiQh0dP4sp5ltmSYSSkFUwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATt0tIDWyo4ARfL9BaSo0W5bJQEbKJTU/u7llvdjSI5aTkOAJa8tixn2+LEfPG4dMFdsMPtsIuU1qn2OqFiuMk6vHv/c+az25RQVY1oo50iMb0jIL3N4FgwhPFpZnCbQPOjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAKBggqhkjOPQQDAwNpADBmAjEA2MI1VXgbf3dUOSc95hSRypBKOab18eh2xzQtxUsHvWeY+1iFgyMluUuNR6taoSmFAjEA31m2czguZhKYX+4JSKu5pRYhBTXAd8KKQ3xdPRX/qCaLvT2qJAEQ1YQM3EJRrtI7" + } + ] + }, + "validFor": { + "start": "2025-03-28T09:14:06Z" + } + } + ] + }, + "signingConfig": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstore.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2023-04-14T21:38:40Z" + } + } + ], + "oidcUrls": [ + { + "url": "https://accounts.google.com", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-16T00:00:00Z" + } + } + ], + "rekorTlogUrls": [ + { + "url": "http://localhost:3003", + "majorApiVersion": 2, + "validFor": { + "start": "2021-01-12T11:53:27Z" + } + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstage.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + }, + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" +} diff --git a/test/assets/tsa/trust_config.rekorv2_alpha.json b/test/assets/tsa/trust_config.rekorv2_alpha.json new file mode 100644 index 000000000..354388df4 --- /dev/null +++ b/test/assets/tsa/trust_config.rekorv2_alpha.json @@ -0,0 +1,162 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstage.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDODRU688UYGuy54mNUlaEBiQdTE9nYLr0lg6RXowI/QV/RE1azBn4Eg5/2uTOMbhB1/gfcHzijzFi9Tk+g1Prg==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY=" + } + }, + { + "baseUrl": "https://log2025-alpha1.rekor.sigstage.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MCowBQYDK2VwAyEAPn+AREHoBaZ7wgS1zBqpxmLSGnyhxXj4lFxSdWVB8o8=", + "keyDetails": "PKIX_ED25519", + "validFor": { + "start": "2025-04-16T00:00:00Z" + } + }, + "logId": { + "keyId": "RycrnT/11WQ15JtgBXeYVLlFYMtbAka7+JnxUQaOX5E=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstage.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGTCCAaCgAwIBAgITJta/okfgHvjabGm1BOzuhrwA1TAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDQxNDIxMzg0MFoXDTMyMDMyMjE2NTA0NVowNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASosAySWJQ/tK5r8T5aHqavk0oI+BKQbnLLdmOMRXHQF/4Hx9KtNfpcdjH9hNKQSBxSlLFFN3tvFCco0qFBzWYwZtsYsBe1l91qYn/9VHFTaEVwYQWIJEEvrs0fvPuAqjajezB5MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRxhjCmFHxib/n31vQFGn9f/+tvrDAfBgNVHSMEGDAWgBT/QjK6aH2rOnCv3AzUGuI+h49mZTAKBggqhkjOPQQDAwNnADBkAjAM1lbKkcqQlE/UspMTbWNo1y2TaJ44tx3l/FJFceTSdDZ+0W1OHHeU4twie/lq8XgCMHQxgEv26xNNiAGyPXbkYgrDPvbOqp0UeWX4mJnLSrBr3aN/KX1SBrKQu220FmVL0Q==" + }, + { + "rawBytes": "MIIB9jCCAXugAwIBAgITDdEJvluliE0AzYaIE4jTMdnFTzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDMyNTE2NTA0NloXDTMyMDMyMjE2NTA0NVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMo9BUNk9QIYisYysC24+2OytoV72YiLonYcqR3yeVnYziPt7Xv++CYE8yoCTiwedUECCWKOcvQKRCJZb9ht4Hzy+VvBx36hK+C6sECCSR0x6pPSiz+cTk1f788ZjBlUZaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP9CMrpofas6cK/cDNQa4j6Hj2ZlMB8GA1UdIwQYMBaAFP9CMrpofas6cK/cDNQa4j6Hj2ZlMAoGCCqGSM49BAMDA2kAMGYCMQD+kojuzMwztNay9Ibzjuk//ZL5m6T2OCsm45l1lY004pcb984L926BowodoirFMcMCMQDIJtFHhP/1D3a+M3dAGomOb6O4CmTry3TTPbPsAFnv22YA0Y+P21NVoxKDjdu0tkw=" + } + ] + }, + "validFor": { + "start": "2022-04-14T21:38:40Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstage.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MIICCgKCAgEA27A2MPQXm0I0v7/Ly5BIauDjRZF5Jor9vU+QheoE2UIIsZHcyYq3slHzSSHy2lLj1ZD2d91CtJ492ZXqnBmsr4TwZ9jQ05tW2mGIRI8u2DqN8LpuNYZGz/f9SZrjhQQmUttqWmtu3UoLfKz6NbNXUnoo+NhZFcFRLXJ8VporVhuiAmL7zqT53cXR3yQfFPCUDeGnRksnlhVIAJc3AHZZSHQJ8DEXMhh35TVv2nYhTI3rID7GwjXXw4ocz7RGDD37ky6p39Tl5NB71gT1eSqhZhGHEYHIPXraEBd5+3w9qIuLWlp5Ej/K6Mu4ELioXKCUimCbwy+Cs8UhHFlqcyg4AysOHJwIadXIa8LsY51jnVSGrGOEBZevopmQPNPtyfFY3dmXSS+6Z3RD2Gd6oDnNGJzpSyEk410Ag5uvNDfYzJLCWX9tU8lIxNwdFYmIwpd89HijyRyoGnoJ3entd63cvKfuuix5r+GHyKp1Xm1L5j5AWM6P+z0xigwkiXnt+adexAl1J9wdDxv/pUFEESRF4DG8DFGVtbdH6aR1A5/vD4krO4tC1QYUSeyL5Mvsw8WRqIFHcXtgybtxylljvNcGMV1KXQC8UFDmpGZVDSHx6v3e/BHMrZ7gjoCCfVMZ/cFcQi0W2AIHPYEMH/C95J2r4XbHMRdYXpovpOoT5Ca78gsCAwEAAQ==", + "keyDetails": "PKCS1_RSA_PKCS1V5", + "validFor": { + "start": "2021-03-14T00:00:00Z", + "end": "2022-07-31T00:00:00Z" + } + }, + "logId": { + "keyId": "G3wUKk6ZK6ffHh/FdCRUE2wVekyzHEEIpSG4savnv0w=" + } + }, + { + "baseUrl": "https://ctfe.sigstage.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh99xuRi6slBFd8VUJoK/rLigy4bYeSYWO/fE6Br7r0D8NpMI94+A63LR/WvLxpUUGBpY8IJA3iU2telag5CRpA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-07-01T00:00:00Z", + "end": "2022-07-31T00:00:00Z" + } + }, + "logId": { + "keyId": "++JKOMQt7SJ3ynUHnCfnDhcKP8/58J4TueMqXuk3HmA=" + } + }, + { + "baseUrl": "https://ctfe.sigstage.dev/2022-2", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8gEDKNme8AnXuPBgHjrtXdS6miHqc24CRblNEOFpiJRngeq8Ko73Y+K18yRYVf1DXD4AVLwvKyzdNdl5n0jUSQ==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-07-01T00:00:00Z" + } + }, + "logId": { + "keyId": "KzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshno=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore-tsa-selfsigned" + }, + "uri": "https://timestamp.sigstage.dev/api/v1/timestamp", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUCPExEFKiQh0dP4sp5ltmSYSSkFUwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATt0tIDWyo4ARfL9BaSo0W5bJQEbKJTU/u7llvdjSI5aTkOAJa8tixn2+LEfPG4dMFdsMPtsIuU1qn2OqFiuMk6vHv/c+az25RQVY1oo50iMb0jIL3N4FgwhPFpZnCbQPOjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAKBggqhkjOPQQDAwNpADBmAjEA2MI1VXgbf3dUOSc95hSRypBKOab18eh2xzQtxUsHvWeY+1iFgyMluUuNR6taoSmFAjEA31m2czguZhKYX+4JSKu5pRYhBTXAd8KKQ3xdPRX/qCaLvT2qJAEQ1YQM3EJRrtI7" + } + ] + }, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ] + }, + "signing_config": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstage.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2022-04-14T21:38:40.000Z" + } + } + ], + "rekorTlogUrls": [ + { + "url": "https://log2025-alpha1.rekor.sigstage.dev", + "majorApiVersion": 2, + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstage.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2024-11-07T14:59:40.000Z" + } + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + } +} \ No newline at end of file diff --git a/test/integration/cli/test_sign.py b/test/integration/cli/test_sign.py index 4d0953db7..05516b6cf 100644 --- a/test/integration/cli/test_sign.py +++ b/test/integration/cli/test_sign.py @@ -16,6 +16,7 @@ import pytest +from sigstore._internal.trust import ClientTrustConfig from sigstore.models import Bundle from sigstore.verify import Verifier from sigstore.verify.policy import UnsafeNoOp @@ -29,8 +30,12 @@ def get_cli_params( bundle_path: Optional[Path] = None, signature_path: Optional[Path] = None, certificate_path: Optional[Path] = None, + trust_config_path: Optional[Path] = None, ) -> list[str]: - cli_params = ["--staging", "sign"] + if trust_config_path is not None: + cli_params = ["--trust-config", str(trust_config_path), "sign"] + else: + cli_params = ["--staging", "sign"] if output_directory is not None: cli_params.extend(["--output-directory", str(output_directory)]) if bundle_path is not None: @@ -81,6 +86,42 @@ def test_sign_success_default_output_bundle(capsys, sigstore, asset_integration) ) +# expected to fail untilwe can make a proper trust_config for rekorv2 staging. +# and perhaps also pending https://github.com/sigstore/sigstore-python/pull/1363. +@pytest.mark.xfail +@pytest.mark.ambient_oidc +def test_sign_success_default_output_bundle_with_trust_config( + capsys, sigstore, asset_integration +): + artifact = asset_integration("a.txt") + expected_output_bundle = artifact.with_name("a.txt.sigstore.json") + + trust_config = asset_integration("trust_config/config.v1.rekorv2_local.json") + + assert not expected_output_bundle.exists() + sigstore(*get_cli_params(artifact_paths=[artifact], trust_config_path=trust_config)) + + assert expected_output_bundle.exists() + verifier = Verifier._from_trust_config( + ClientTrustConfig.from_json(trust_config.read_text()) + ) + with ( + open(expected_output_bundle, "r") as bundle_file, + open(artifact, "rb") as input_file, + ): + bundle = Bundle.from_json(bundle_file.read()) + verifier.verify_artifact( + input_=input_file.read(), bundle=bundle, policy=UnsafeNoOp() + ) + + expected_output_bundle.unlink() + + captures = capsys.readouterr() + assert captures.out.endswith( + f"Sigstore bundle written to {expected_output_bundle}\n" + ) + + @pytest.mark.staging @pytest.mark.ambient_oidc def test_sign_success_custom_outputs(capsys, sigstore, asset_integration, tmp_path): diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 768625cb9..4987eff2c 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -19,7 +19,7 @@ import os import re from collections import defaultdict -from collections.abc import Iterator +from collections.abc import Iterable, Iterator from io import BytesIO from pathlib import Path from typing import Callable @@ -247,3 +247,30 @@ def _dummy_jwt(claims: dict): def tsa_url(): """Return the URL of the TSA""" return os.getenv("TEST_SIGSTORE_TIMESTAMP_AUTHORITY_URL") + + +def has_setup_sigstore_env() -> bool: + """ + Checks whether the TEST_SETUP_SIGSTORE_ENV variable is set to true, + This means we are using the sigstore/scaffolding/actions/setup-sigstore-env + that has the sigstore services in containers available for us to use. + """ + return bool(os.getenv("TEST_SETUP_SIGSTORE_ENV", False)) + + +def get_trust_config_filenames() -> Iterable[str]: + # yield "config.v1.rekorv2_staging.json" + if has_setup_sigstore_env(): + # prepare the file and return its path + yield "config.v1.rekorv2_local.json" + + +@pytest.fixture(params=[*get_trust_config_filenames()]) +def v2_trust_config(request, asset) -> ClientTrustConfig: + """ + Yields v2 trust_configs: one from an enbedded file that talks with staging, + and another that talks with the local containers, if available from `has_setup_sigstore_env()`. + """ + path = asset(request.param) + print(path) + return ClientTrustConfig.from_json(path.read_text()) diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index 244cfc8e7..eae301fff 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -27,6 +27,7 @@ from sigstore.hashes import Hashed from sigstore.sign import SigningContext from sigstore.verify.policy import UnsafeNoOp +from sigstore.verify.verifier import Verifier @pytest.mark.parametrize("env", ["staging", "production"]) @@ -238,3 +239,27 @@ def test_with_timestamp_error(self, sig_ctx, identity, hashed, caplog): assert ( bundle.verification_material.timestamp_verification_data.rfc3161_timestamps ) + + +@pytest.mark.staging +@pytest.mark.ambient_oidc +def test_sign_prehashed_rekorv2(v2_trust_config, staging) -> None: + _, _, identity = staging + trust_config = v2_trust_config + sign_ctx = SigningContext._from_trust_config(trust_config) + + input_ = secrets.token_bytes(32) + hashed = Hashed( + digest=hashlib.sha256(input_).digest(), algorithm=HashAlgorithm.SHA2_256 + ) + + with sign_ctx.signer(identity) as signer: + bundle = signer.sign_artifact(hashed) + assert bundle._inner.message_signature.message_digest.algorithm == hashed.algorithm + assert bundle._inner.message_signature.message_digest.digest == hashed.digest + + verifier = Verifier._from_trust_config(trust_config) + # verifying against the original input works + verifier.verify_artifact(input_, bundle=bundle, policy=UnsafeNoOp()) + # verifying against the prehash also works + verifier.verify_artifact(hashed, bundle=bundle, policy=UnsafeNoOp()) diff --git a/trust_config.json b/trust_config.json new file mode 100644 index 000000000..186ddc96a --- /dev/null +++ b/trust_config.json @@ -0,0 +1,137 @@ +{ + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDODRU688UYGuy54mNUlaEBiQdTE9nYLr0lg6RXowI/QV/RE1azBn4Eg5/2uTOMbhB1/gfcHzijzFi9Tk+g1Prg==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2021-10-07T13:56:59Z" + } + } + ], + "ctlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbbQiLx6GKy6ivhc11wJGbQjc2VX/mnuk5d670MTXR3p+LIAcxd5MhqIHpLmyYJ5mDKLEoZ/pC0nPuje3JueBcA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "ekJiz/ZpG+UEn5w/GaIr6+awI+RKfkpt/V9Teu7va1k=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore-tsa-selfsigned" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUCPExEFKiQh0dP4sp5ltmSYSSkFUwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATt0tIDWyo4ARfL9BaSo0W5bJQEbKJTU/u7llvdjSI5aTkOAJa8tixn2+LEfPG4dMFdsMPtsIuU1qn2OqFiuMk6vHv/c+az25RQVY1oo50iMb0jIL3N4FgwhPFpZnCbQPOjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAKBggqhkjOPQQDAwNpADBmAjEA2MI1VXgbf3dUOSc95hSRypBKOab18eh2xzQtxUsHvWeY+1iFgyMluUuNR6taoSmFAjEA31m2czguZhKYX+4JSKu5pRYhBTXAd8KKQ3xdPRX/qCaLvT2qJAEQ1YQM3EJRrtI7" + } + ] + }, + "validFor": { + "start": "2025-03-28T09:14:06Z" + } + } + ] + }, + "signingConfig": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstore.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2023-04-14T21:38:40Z" + } + } + ], + "oidcUrls": [ + { + "url": "https://accounts.google.com", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-16T00:00:00Z" + } + } + ], + "rekorTlogUrls": [ + { + "url": "https://rekor.sigstage.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2021-01-12T11:53:27Z" + } + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstage.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + }, + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" +} \ No newline at end of file diff --git a/trust_config_v2.json b/trust_config_v2.json new file mode 100644 index 000000000..eee239e5e --- /dev/null +++ b/trust_config_v2.json @@ -0,0 +1,137 @@ +{ + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MCowBQYDK2VwAyEAREvJyNZGjX6B3DAIuD3BTg9rIwV00GY8Xg5FU+IFDUQ=", + "keyDetails": "PKIX_ED25519", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "tAlACZWkUrif9Z9sOIrpk1ak1I8loRNufk79N6l1SNg=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2021-10-07T13:56:59Z" + } + } + ], + "ctlogs": [ + { + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbbQiLx6GKy6ivhc11wJGbQjc2VX/mnuk5d670MTXR3p+LIAcxd5MhqIHpLmyYJ5mDKLEoZ/pC0nPuje3JueBcA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "1970-01-01T00:00:00Z" + } + }, + "logId": { + "keyId": "ekJiz/ZpG+UEn5w/GaIr6+awI+RKfkpt/V9Teu7va1k=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore-tsa-selfsigned" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUCPExEFKiQh0dP4sp5ltmSYSSkFUwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATt0tIDWyo4ARfL9BaSo0W5bJQEbKJTU/u7llvdjSI5aTkOAJa8tixn2+LEfPG4dMFdsMPtsIuU1qn2OqFiuMk6vHv/c+az25RQVY1oo50iMb0jIL3N4FgwhPFpZnCbQPOjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAKBggqhkjOPQQDAwNpADBmAjEA2MI1VXgbf3dUOSc95hSRypBKOab18eh2xzQtxUsHvWeY+1iFgyMluUuNR6taoSmFAjEA31m2czguZhKYX+4JSKu5pRYhBTXAd8KKQ3xdPRX/qCaLvT2qJAEQ1YQM3EJRrtI7" + } + ] + }, + "validFor": { + "start": "2025-03-28T09:14:06Z" + } + } + ] + }, + "signingConfig": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstore.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2023-04-14T21:38:40Z" + } + } + ], + "oidcUrls": [ + { + "url": "https://accounts.google.com", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-16T00:00:00Z" + } + } + ], + "rekorTlogUrls": [ + { + "url": "http://localhost:3003", + "majorApiVersion": 2, + "validFor": { + "start": "2021-01-12T11:53:27Z" + } + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstage.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + }, + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" +}