From d76d72e407aec457c35329a555c15e39ca74a0a1 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 25 Apr 2025 18:26:21 +0000 Subject: [PATCH 01/81] set endpoint to v2 Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index b4f348300..4473af1d3 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -112,7 +112,8 @@ def entries(self) -> RekorEntries: Returns a `RekorEntries` capable of accessing detailed information about individual log entries. """ - return RekorEntries(urljoin(self.url, "entries/"), session=self.session) + # NOTE: not "entries/" + return RekorEntries(urljoin(self.url, "entries"), session=self.session) class RekorEntries(_Endpoint): @@ -221,12 +222,13 @@ def post( class RekorClient: """The internal Rekor client""" + # NOTE: use "api/v2/" - def __init__(self, url: str) -> None: + def __init__(self, url: str, path: str = "api/v2/") -> None: """ Create a new `RekorClient` from the given URL. """ - self.url = urljoin(url, "api/v1/") + self.url = urljoin(url, path) self.session = requests.Session() self.session.headers.update( { From 1c9885c8d3e0f470fb9b24ecdc6fcc3dc5190d71 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 25 Apr 2025 22:05:08 +0000 Subject: [PATCH 02/81] test with local rekorv2 Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 18 +++++++++++++++- sigstore/sign.py | 9 +++++++- test_config_tiles.json | 33 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 test_config_tiles.json diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 4473af1d3..05ad1375c 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -155,7 +155,23 @@ def post( payload = proposed_entry.model_dump(mode="json", by_alias=True) _logger.debug(f"proposed: {json.dumps(payload)}") - + # layout from rekor-tiles/docs/openapi/rekor_service.swagger.json + payloadV2 = { + 'hashedRekordRequestV0_0_2': { + 'digest': payload["spec"]['data']['hash']['value'], + 'signature': { + 'content': payload["spec"]['signature']['content'], + 'verifier': { + 'public_key': { + 'rawBytes': payload["spec"]['signature']['publicKey']['content'] + }, + 'key_details': "PKIX_ECDSA_P384_SHA_384" + } + } + } + } + _logger.debug(f"proposed: {json.dumps(payloadV2)}") + payload = payloadV2 resp: requests.Response = self.session.post(self.url, json=payload) try: resp.raise_for_status() diff --git a/sigstore/sign.py b/sigstore/sign.py index 550fbf0e6..b1a7645ab 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -255,9 +255,16 @@ def sign_artifact( cert = self._signing_cert() + # Prepare inputs + # b64_cert = base64.b64encode( + # cert.public_bytes(encoding=serialization.Encoding.PEM) + # ) b64_cert = base64.b64encode( - cert.public_bytes(encoding=serialization.Encoding.PEM) + cert.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) ) # Sign artifact diff --git a/test_config_tiles.json b/test_config_tiles.json new file mode 100644 index 000000000..c081e99fc --- /dev/null +++ b/test_config_tiles.json @@ -0,0 +1,33 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [], + "certificateAuthorities": [], + "ctlogs": [ + { + "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=" + } + } + ] + }, + "signingConfig": { + "caUrl": "https://fulcio.sigstore.dev", + "tlogUrls": [ + "http://rpetgrave-1.c.googlers.com:3003" + ], + "tsaUrls": [ + "http://rpetgrave-1.c.googlers.com:3004/api/v1/timestamp" + ] + } +} \ No newline at end of file From e602ee98a536af88da7beaab0f2d1401cefa9c3a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 5 May 2025 21:25:03 +0000 Subject: [PATCH 03/81] seems to work, but no kind-version Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 6 ++++-- sigstore/models.py | 18 +++++++++--------- sigstore/sign.py | 5 ++++- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 05ad1375c..0a53fda66 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -165,7 +165,7 @@ def post( 'public_key': { 'rawBytes': payload["spec"]['signature']['publicKey']['content'] }, - 'key_details': "PKIX_ECDSA_P384_SHA_384" + 'key_details': "PKIX_ECDSA_P384_SHA_256" } } } @@ -180,7 +180,9 @@ def post( integrated_entry = resp.json() _logger.debug(f"integrated: {integrated_entry}") - return LogEntry._from_response(integrated_entry) + # return LogEntry._from_response(integrated_entry) + return LogEntry._from_dict_rekor(integrated_entry) + @property def retrieve(self) -> RekorEntriesRetrieve: diff --git a/sigstore/models.py b/sigstore/models.py index 674949cd7..3baa0c269 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -262,15 +262,15 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: ) # Fill in the appropriate kind - body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( - tlog_entry.canonicalized_body - ) - if not isinstance(body_entry, (Hashedrekord, Dsse)): - raise InvalidBundle("log entry is not of expected type") - - tlog_entry.kind_version = rekor_v1.KindVersion( - kind=body_entry.kind, version=body_entry.api_version - ) + # body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( + # tlog_entry.canonicalized_body + # ) + # if not isinstance(body_entry, (Hashedrekord, Dsse)): + # raise InvalidBundle("log entry is not of expected type") + + # tlog_entry.kind_version = rekor_v1.KindVersion( + # kind=body_entry.kind, version=body_entry.api_version + # ) return tlog_entry diff --git a/sigstore/sign.py b/sigstore/sign.py index b1a7645ab..b7fbad8e1 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -274,6 +274,8 @@ def sign_artifact( hashed_input.digest, ec.ECDSA(hashed_input._as_prehashed()) ) + b64_digest = base64.b64encode(hashed_input.digest).decode() + content = MessageSignature( message_digest=HashOutput( algorithm=hashed_input.algorithm, @@ -294,7 +296,8 @@ def sign_artifact( data=rekor_types.hashedrekord.Data( hash=rekor_types.hashedrekord.Hash( algorithm=hashed_input._as_hashedrekord_algorithm(), - value=hashed_input.digest.hex(), + # value=hashed_input.digest.decode(), + value=b64_digest ) ), ), From 79a6d315500cd6f4d0efa4d9b287d3f41e23d34b Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 6 May 2025 16:23:01 +0000 Subject: [PATCH 04/81] no trailing slash for post to /entries Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index b4f348300..943499e91 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -155,7 +155,7 @@ def post( payload = proposed_entry.model_dump(mode="json", by_alias=True) _logger.debug(f"proposed: {json.dumps(payload)}") - resp: requests.Response = self.session.post(self.url, json=payload) + resp: requests.Response = self.session.post(self.url.rstrip("/"), json=payload) try: resp.raise_for_status() except requests.HTTPError as http_error: From 2d658ba2514cd301b068914b1fbb901c86bff65d Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 6 May 2025 19:28:52 +0000 Subject: [PATCH 05/81] signing worksa Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 6 +++--- sigstore/models.py | 30 +++++++++++++++++++++--------- test_config_tiles.json | 18 +++++++++++++++++- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 0a53fda66..3fa7a3713 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -112,8 +112,7 @@ def entries(self) -> RekorEntries: Returns a `RekorEntries` capable of accessing detailed information about individual log entries. """ - # NOTE: not "entries/" - return RekorEntries(urljoin(self.url, "entries"), session=self.session) + return RekorEntries(urljoin(self.url, "entries/"), session=self.session) class RekorEntries(_Endpoint): @@ -172,7 +171,8 @@ def post( } _logger.debug(f"proposed: {json.dumps(payloadV2)}") payload = payloadV2 - resp: requests.Response = self.session.post(self.url, json=payload) + # NOTE: not "entries/" + resp: requests.Response = self.session.post(self.url.rstrip("/"), json=payload) try: resp.raise_for_status() except requests.HTTPError as http_error: diff --git a/sigstore/models.py b/sigstore/models.py index 3baa0c269..85e3835f3 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -261,16 +261,28 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: canonicalized_body=base64.b64decode(self.body), ) + # NOTE fix the canonicalized_body + import json + tlog_entry.canonicalized_body = bytes(json.dumps({ + # **json.loads(tlog_entry.canonicalized_body), + 'kind': 'hashedrekord', + 'api_version': '0.0.2', + 'spec': { + 'signature': json.loads(tlog_entry.canonicalized_body)['signature'], + 'data': json.loads(tlog_entry.canonicalized_body)['data'] + } + }), encoding='utf-8') + # Fill in the appropriate kind - # body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( - # tlog_entry.canonicalized_body - # ) - # if not isinstance(body_entry, (Hashedrekord, Dsse)): - # raise InvalidBundle("log entry is not of expected type") - - # tlog_entry.kind_version = rekor_v1.KindVersion( - # kind=body_entry.kind, version=body_entry.api_version - # ) + body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( + tlog_entry.canonicalized_body + ) + if not isinstance(body_entry, (Hashedrekord, Dsse)): + raise InvalidBundle("log entry is not of expected type") + + tlog_entry.kind_version = rekor_v1.KindVersion( + kind=body_entry.kind, version=body_entry.api_version + ) return tlog_entry diff --git a/test_config_tiles.json b/test_config_tiles.json index c081e99fc..818bc1b77 100644 --- a/test_config_tiles.json +++ b/test_config_tiles.json @@ -3,7 +3,6 @@ "trustedRoot": { "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", "tlogs": [], - "certificateAuthorities": [], "ctlogs": [ { "baseUrl": "https://ctfe.sigstore.dev/2022", @@ -19,6 +18,23 @@ "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" } } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGDCCAb2gAwIBAgIUGmDPyjHNmBovOIehfnpFqtErprMwCgYIKoZIzj0EAwIwaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMB4XDTI1MDUwNTAxNTYzNVoXDTM1MDUwNTAxNTYzNVowaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1/yazNuKwk5mM1806HIrOQly+RfphoKT+JfwIXq5hkXMoN4mRw3KnYVvlL4SJTHru4R+hMg9rs/Y8jJT/0wqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHIeBqzKwE0jtruNyuKAfmwVkURWMAoGCCqGSM49BAMCA0kAMEYCIQCgsaOL+r+h7xFTzoUi6jdzMjZrtmromhkJW8mugHbNXQIhAIg1tI2zcxkXnXknRxj5ewg+As/PrxMrpndcheb729ew" + } + ] + }, + "validFor": { + "start": "2025-05-05T01:56:35Z" + } + } ] }, "signingConfig": { From f2e64795a4313856431a4a1a51e7e6b5a92fb5b8 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 6 May 2025 23:00:04 +0000 Subject: [PATCH 06/81] signing, but no verify Signed-off-by: Ramon Petgrave --- sigstore/_internal/timestamp.py | 5 +- sigstore/verify/verifier.py | 1 + test_config_tiles.json | 48 ++++++++++++++++++- test_config_tiles_local_tsa.json | 74 +++++++++++++++++++++++++++++ test_config_tiles_sigstage_tsa.json | 71 +++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 test_config_tiles_local_tsa.json create mode 100644 test_config_tiles_sigstage_tsa.json diff --git a/sigstore/_internal/timestamp.py b/sigstore/_internal/timestamp.py index f279e9d47..4cad86003 100644 --- a/sigstore/_internal/timestamp.py +++ b/sigstore/_internal/timestamp.py @@ -27,6 +27,8 @@ decode_timestamp_response, ) +from rfc3161_client.base import HashAlgorithm + from sigstore._internal import USER_AGENT CLIENT_TIMEOUT: int = 5 @@ -93,7 +95,8 @@ def request_timestamp(self, signature: bytes) -> TimeStampResponse: # Build the timestamp request try: timestamp_request = ( - TimestampRequestBuilder().data(signature).nonce(nonce=True).build() + TimestampRequestBuilder().hash_algorithm(HashAlgorithm.SHA256).data( + signature).nonce(nonce=True).build() ) except ValueError as error: msg = f"invalid request: {error}" diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index b782f969c..a98d7c1c2 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -195,6 +195,7 @@ def _verify_timestamp_authority( # The Signer sends a hash of the signature as the messageImprint in a TimeStampReq # to the Timestamping Service signature_hash = sha256_digest(bundle.signature).digest + verified_timestamps = [ verified_timestamp for tsr in timestamp_responses diff --git a/test_config_tiles.json b/test_config_tiles.json index 818bc1b77..147274a91 100644 --- a/test_config_tiles.json +++ b/test_config_tiles.json @@ -35,6 +35,51 @@ "start": "2025-05-05T01:56:35Z" } } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "local", + "commonName": "Test TSA Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIBzDCCAXKgAwIBAgIUU/M3DoMFZcR5T7y29ZPA39Wj/ewwCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNTA1MDUwMTUzMzNaFw0zNDA1MDUwMTU2MzNaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT0s7X+aQdA81wkpwq1LKcqIefTPQSp8XKi+dC6oQDltHzbbMZ+o4NY7fGvQw3o0e0EOeor33CazjjHyJQBYEF3o2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFMqjQt5WWV5v+K5Yiu3OfE3ev8XyMB8GA1UdIwQYMBaAFHfGiMEdzXncE/BqBleICJMnwA0lMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0gAMEUCIEwZI1UdKcUIY5tcVjfqDuwyW2ITNIYcTeP+7KFbyv4bAiEAkeUqntzo/b8G5NB9YcuiqlAVjPe50m6/L8ryaWA9JV4=" + }, + { + "rawBytes": "MIIB0jCCAXigAwIBAgIUE2CFQQYNTCJpjohFNWJKMQEz/uEwCgYIKoZIzj0EAwIwKDEOMAwGA1UEChMFbG9jYWwxFjAUBgNVBAMTDVRlc3QgVFNBIFJvb3QwHhcNMjUwNTA1MDE1MTMzWhcNMzUwNTA1MDE1NjMzWjAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAWgRk3P+C+TGYKnNsWRipyKTtKhLmqDI4xl1AQ4LHk8JmtJ5NZnlnFMoZ5Dh9pYaDZe59seCfxkARKUHsUxEaN4MHYwDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHfGiMEdzXncE/BqBleICJMnwA0lMB8GA1UdIwQYMBaAFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0gAMEUCIGyZjfsEnIKv7xX9X2BR8qFzVFmyPjTvY9axP3Kk8xbxAiEA8V30nzomK6vQ1IAnDK+s/IPKrFoSXYTUfjgf1J5/tL8=" + }, + { + "rawBytes": "MIIBkjCCATmgAwIBAgITXXzm8g99fDSDWKjK2VQ9/HzYqzAKBggqhkjOPQQDAjAoMQ4wDAYDVQQKEwVsb2NhbDEWMBQGA1UEAxMNVGVzdCBUU0EgUm9vdDAeFw0yNTA1MDUwMTUxMzNaFw0zNTA1MDUwMTU2MzNaMCgxDjAMBgNVBAoTBWxvY2FsMRYwFAYDVQQDEw1UZXN0IFRTQSBSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC8KZqAMHSC5JQIqjcD5lwCr4XIq+daVSXJUq+uRwfJSEsdUrbSZsH3RScqJEVHUQPD+YW5gmKVlLl+RvV8GMT6NCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0cAMEQCIHLlhj2iI75fbPnHx8ocCOpCs/khsUrNIXL0foWqco+wAiBaW/38SleBAfofRZ7OErOinRQHvXvioWM6RqIXW50G1w==" + } + ] + }, + "validFor": { + "start": "2025-05-05T01:51:33Z", + "end": "2035-03-28T09:14:06Z" + } + }, + { + "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", + "end": "2035-03-28T09:14:06Z" + } + } ] }, "signingConfig": { @@ -43,7 +88,8 @@ "http://rpetgrave-1.c.googlers.com:3003" ], "tsaUrls": [ - "http://rpetgrave-1.c.googlers.com:3004/api/v1/timestamp" + "http://rpetgrave-1.c.googlers.com:3004/api/v1/timestamp", + "https://timestamp.sigstage.dev/api/v1/timestamp" ] } } \ No newline at end of file diff --git a/test_config_tiles_local_tsa.json b/test_config_tiles_local_tsa.json new file mode 100644 index 000000000..f4b57aa68 --- /dev/null +++ b/test_config_tiles_local_tsa.json @@ -0,0 +1,74 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [], + "ctlogs": [ + { + "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=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGDCCAb2gAwIBAgIUGmDPyjHNmBovOIehfnpFqtErprMwCgYIKoZIzj0EAwIwaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMB4XDTI1MDUwNTAxNTYzNVoXDTM1MDUwNTAxNTYzNVowaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1/yazNuKwk5mM1806HIrOQly+RfphoKT+JfwIXq5hkXMoN4mRw3KnYVvlL4SJTHru4R+hMg9rs/Y8jJT/0wqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHIeBqzKwE0jtruNyuKAfmwVkURWMAoGCCqGSM49BAMCA0kAMEYCIQCgsaOL+r+h7xFTzoUi6jdzMjZrtmromhkJW8mugHbNXQIhAIg1tI2zcxkXnXknRxj5ewg+As/PrxMrpndcheb729ew" + } + ] + }, + "validFor": { + "start": "2025-05-05T01:56:35Z" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "local", + "commonName": "Test TSA Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIBzDCCAXKgAwIBAgIUU/M3DoMFZcR5T7y29ZPA39Wj/ewwCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNTA1MDUwMTUzMzNaFw0zNDA1MDUwMTU2MzNaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT0s7X+aQdA81wkpwq1LKcqIefTPQSp8XKi+dC6oQDltHzbbMZ+o4NY7fGvQw3o0e0EOeor33CazjjHyJQBYEF3o2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFMqjQt5WWV5v+K5Yiu3OfE3ev8XyMB8GA1UdIwQYMBaAFHfGiMEdzXncE/BqBleICJMnwA0lMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0gAMEUCIEwZI1UdKcUIY5tcVjfqDuwyW2ITNIYcTeP+7KFbyv4bAiEAkeUqntzo/b8G5NB9YcuiqlAVjPe50m6/L8ryaWA9JV4=" + }, + { + "rawBytes": "MIIB0jCCAXigAwIBAgIUE2CFQQYNTCJpjohFNWJKMQEz/uEwCgYIKoZIzj0EAwIwKDEOMAwGA1UEChMFbG9jYWwxFjAUBgNVBAMTDVRlc3QgVFNBIFJvb3QwHhcNMjUwNTA1MDE1MTMzWhcNMzUwNTA1MDE1NjMzWjAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAWgRk3P+C+TGYKnNsWRipyKTtKhLmqDI4xl1AQ4LHk8JmtJ5NZnlnFMoZ5Dh9pYaDZe59seCfxkARKUHsUxEaN4MHYwDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHfGiMEdzXncE/BqBleICJMnwA0lMB8GA1UdIwQYMBaAFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0gAMEUCIGyZjfsEnIKv7xX9X2BR8qFzVFmyPjTvY9axP3Kk8xbxAiEA8V30nzomK6vQ1IAnDK+s/IPKrFoSXYTUfjgf1J5/tL8=" + }, + { + "rawBytes": "MIIBkjCCATmgAwIBAgITXXzm8g99fDSDWKjK2VQ9/HzYqzAKBggqhkjOPQQDAjAoMQ4wDAYDVQQKEwVsb2NhbDEWMBQGA1UEAxMNVGVzdCBUU0EgUm9vdDAeFw0yNTA1MDUwMTUxMzNaFw0zNTA1MDUwMTU2MzNaMCgxDjAMBgNVBAoTBWxvY2FsMRYwFAYDVQQDEw1UZXN0IFRTQSBSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC8KZqAMHSC5JQIqjcD5lwCr4XIq+daVSXJUq+uRwfJSEsdUrbSZsH3RScqJEVHUQPD+YW5gmKVlLl+RvV8GMT6NCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0cAMEQCIHLlhj2iI75fbPnHx8ocCOpCs/khsUrNIXL0foWqco+wAiBaW/38SleBAfofRZ7OErOinRQHvXvioWM6RqIXW50G1w==" + } + ] + }, + "validFor": { + "start": "2025-05-05T01:51:33Z", + "end": "2035-03-28T09:14:06Z" + } + } + ] + }, + "signingConfig": { + "caUrl": "https://fulcio.sigstore.dev", + "tlogUrls": [ + "http://rpetgrave-1.c.googlers.com:3003" + ], + "tsaUrls": [ + "http://rpetgrave-1.c.googlers.com:3004/api/v1/timestamp" + ] + } +} \ No newline at end of file diff --git a/test_config_tiles_sigstage_tsa.json b/test_config_tiles_sigstage_tsa.json new file mode 100644 index 000000000..f89e12c43 --- /dev/null +++ b/test_config_tiles_sigstage_tsa.json @@ -0,0 +1,71 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [], + "ctlogs": [ + { + "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=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGDCCAb2gAwIBAgIUGmDPyjHNmBovOIehfnpFqtErprMwCgYIKoZIzj0EAwIwaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMB4XDTI1MDUwNTAxNTYzNVoXDTM1MDUwNTAxNTYzNVowaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1/yazNuKwk5mM1806HIrOQly+RfphoKT+JfwIXq5hkXMoN4mRw3KnYVvlL4SJTHru4R+hMg9rs/Y8jJT/0wqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHIeBqzKwE0jtruNyuKAfmwVkURWMAoGCCqGSM49BAMCA0kAMEYCIQCgsaOL+r+h7xFTzoUi6jdzMjZrtmromhkJW8mugHbNXQIhAIg1tI2zcxkXnXknRxj5ewg+As/PrxMrpndcheb729ew" + } + ] + }, + "validFor": { + "start": "2025-05-05T01:56:35Z" + } + } + ], + "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", + "end": "2035-03-28T09:14:06Z" + } + } + ] + }, + "signingConfig": { + "caUrl": "https://fulcio.sigstore.dev", + "tlogUrls": [ + "http://rpetgrave-1.c.googlers.com:3003" + ], + "tsaUrls": [ + "https://timestamp.sigstage.dev/api/v1/timestamp" + ] + } +} \ No newline at end of file From e4470a9c793ac9c1189c2a0212f56d37935a6847 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 7 May 2025 18:28:11 +0000 Subject: [PATCH 07/81] cache the kindversion Signed-off-by: Ramon Petgrave --- sigstore/models.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index 85e3835f3..f21402e15 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -58,7 +58,7 @@ from sigstore_protobuf_specs.dev.sigstore.common.v1 import Rfc3161SignedTimestamp from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1 from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import ( - InclusionProof, + InclusionProof, KindVersion ) from sigstore import dsse @@ -172,6 +172,11 @@ class LogEntry: log entry. """ + kind_version: KindVersion + """ + The kind and version of the log entry. + """ + @classmethod def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: """ @@ -230,6 +235,7 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: tlog_entry.inclusion_promise.signed_entry_timestamp ).decode() ), + kind_version=tlog_entry.kind_version, ) def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @@ -258,32 +264,10 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: integrated_time=self.integrated_time, inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, + kind_version=self.kind_version, canonicalized_body=base64.b64decode(self.body), ) - # NOTE fix the canonicalized_body - import json - tlog_entry.canonicalized_body = bytes(json.dumps({ - # **json.loads(tlog_entry.canonicalized_body), - 'kind': 'hashedrekord', - 'api_version': '0.0.2', - 'spec': { - 'signature': json.loads(tlog_entry.canonicalized_body)['signature'], - 'data': json.loads(tlog_entry.canonicalized_body)['data'] - } - }), encoding='utf-8') - - # Fill in the appropriate kind - body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( - tlog_entry.canonicalized_body - ) - if not isinstance(body_entry, (Hashedrekord, Dsse)): - raise InvalidBundle("log entry is not of expected type") - - tlog_entry.kind_version = rekor_v1.KindVersion( - kind=body_entry.kind, version=body_entry.api_version - ) - return tlog_entry def encode_canonical(self) -> bytes: From f69efca16474c81deaeb3519c312d24d73fb6be7 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 7 May 2025 18:28:11 +0000 Subject: [PATCH 08/81] parse kind_version earlier Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 5 +++++ sigstore/models.py | 33 ++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab9362a33..d4ce59c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ All versions prior to 0.9.0 are untracked. ## [Unreleased] +### Added + +* Added `LogEntry.kind_version`, which is now parsed earlier upon receipt from the rekor API, + either from the root of the response, or from the reponse's inner base64-encoded JSON `body`. + ## [3.6.2] ### Fixed diff --git a/sigstore/models.py b/sigstore/models.py index 674949cd7..e01444de3 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -57,9 +57,7 @@ from sigstore_protobuf_specs.dev.sigstore.common import v1 as common_v1 from sigstore_protobuf_specs.dev.sigstore.common.v1 import Rfc3161SignedTimestamp from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1 -from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import ( - InclusionProof, -) +from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import InclusionProof, KindVersion from sigstore import dsse from sigstore._internal.merkle import verify_merkle_inclusion @@ -172,6 +170,11 @@ class LogEntry: log entry. """ + kind_version: KindVersion + """ + The kind and version of the log entry. + """ + @classmethod def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: """ @@ -182,8 +185,15 @@ def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: entries = list(dict_.items()) if len(entries) != 1: raise ValueError("Received multiple entries in response") - uuid, entry = entries[0] + + # Fill in the appropriate kind + body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( + base64.b64decode(entry["body"]) + ) + if not isinstance(body_entry, (Hashedrekord, Dsse)): + raise InvalidBundle("log entry is not of expected type") + return LogEntry( uuid=uuid, body=entry["body"], @@ -194,6 +204,8 @@ def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: entry["verification"]["inclusionProof"] ), inclusion_promise=entry["verification"]["signedEntryTimestamp"], + kind_version=KindVersion( + kind=body_entry.kind, version=body_entry.api_version) ) @classmethod @@ -230,6 +242,7 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: tlog_entry.inclusion_promise.signed_entry_timestamp ).decode() ), + kind_version=tlog_entry.kind_version, ) def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @@ -258,20 +271,10 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: integrated_time=self.integrated_time, inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, + kind_version=self.kind_version, canonicalized_body=base64.b64decode(self.body), ) - # Fill in the appropriate kind - body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( - tlog_entry.canonicalized_body - ) - if not isinstance(body_entry, (Hashedrekord, Dsse)): - raise InvalidBundle("log entry is not of expected type") - - tlog_entry.kind_version = rekor_v1.KindVersion( - kind=body_entry.kind, version=body_entry.api_version - ) - return tlog_entry def encode_canonical(self) -> bytes: From a6025a56056c1bcb953ea4c057ce7593472a1247 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 7 May 2025 19:12:22 +0000 Subject: [PATCH 09/81] Revert "no trailing slash for post to /entries" This reverts commit 79a6d315500cd6f4d0efa4d9b287d3f41e23d34b. Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 943499e91..b4f348300 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -155,7 +155,7 @@ def post( payload = proposed_entry.model_dump(mode="json", by_alias=True) _logger.debug(f"proposed: {json.dumps(payload)}") - resp: requests.Response = self.session.post(self.url.rstrip("/"), json=payload) + resp: requests.Response = self.session.post(self.url, json=payload) try: resp.raise_for_status() except requests.HTTPError as http_error: From b06b4b544537a53dc4568fc1d8c4f9e626836723 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 8 May 2025 12:30:40 +0000 Subject: [PATCH 10/81] lint Signed-off-by: Ramon Petgrave --- sigstore/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sigstore/models.py b/sigstore/models.py index e01444de3..83d075b47 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -205,7 +205,8 @@ def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: ), inclusion_promise=entry["verification"]["signedEntryTimestamp"], kind_version=KindVersion( - kind=body_entry.kind, version=body_entry.api_version) + kind=body_entry.kind, version=body_entry.api_version + ), ) @classmethod From 7777935fdbf2863e8c1b973662b91809a3383669 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave <32398091+ramonpetgrave64@users.noreply.github.com> Date: Thu, 8 May 2025 08:40:02 -0400 Subject: [PATCH 11/81] add PR link Signed-off-by: Ramon Petgrave <32398091+ramonpetgrave64@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ce59c92..4dff39b46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ All versions prior to 0.9.0 are untracked. * Added `LogEntry.kind_version`, which is now parsed earlier upon receipt from the rekor API, either from the root of the response, or from the reponse's inner base64-encoded JSON `body`. + [#1370](https://github.com/sigstore/sigstore-python/pull/1370) ## [3.6.2] From ac4f29d5b67a95a92a5672cc0eaf1209fc2a7f5b Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 8 May 2025 22:27:40 +0000 Subject: [PATCH 12/81] semi-working verification Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/checkpoint.py | 8 +++--- sigstore/_internal/rekor/client.py | 9 ++++--- sigstore/_internal/trust.py | 15 ++++++++--- sigstore/_utils.py | 15 +++++++---- sigstore/models.py | 10 +++---- sigstore/verify/verifier.py | 36 ++++++++++++++------------ 6 files changed, 55 insertions(+), 38 deletions(-) diff --git a/sigstore/_internal/rekor/checkpoint.py b/sigstore/_internal/rekor/checkpoint.py index c630d24fa..ce9e030eb 100644 --- a/sigstore/_internal/rekor/checkpoint.py +++ b/sigstore/_internal/rekor/checkpoint.py @@ -172,10 +172,10 @@ def verify(self, rekor_keyring: RekorKeyring, key_id: KeyID) -> None: note = str.encode(self.note) for sig in self.signatures: - if sig.sig_hash != key_id[:4]: - raise VerificationError( - "checkpoint: sig_hash hint does not match expected key_id" - ) + # if sig.sig_hash != key_id[:4]: + # raise VerificationError( + # "checkpoint: sig_hash hint does not match expected key_id" + # ) try: rekor_keyring.verify( diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 8250ad7d0..624085975 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -171,7 +171,8 @@ def post( _logger.debug(f"proposed: {json.dumps(payloadV2)}") payload = payloadV2 # NOTE: not "entries/" - resp: requests.Response = self.session.post(self.url.rstrip("/"), json=payload) + print(self.url) + resp: requests.Response = self.session.post(self.url, json=payload) try: resp.raise_for_status() except requests.HTTPError as http_error: @@ -237,13 +238,13 @@ def post( class RekorClient: """The internal Rekor client""" - # NOTE: use "api/v2/" - def __init__(self, url: str, path: str = "api/v2/") -> None: + def __init__(self, url: str) -> None: """ Create a new `RekorClient` from the given URL. """ - self.url = f"{url}/api/v1" + # NOTE: use "api/v2/" + self.url = f"{url}/api/v2" self.session = requests.Session() self.session.headers.update( { diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index a8e066a15..4bbb0bce4 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -23,12 +23,12 @@ from datetime import datetime, timezone from enum import Enum from pathlib import Path -from typing import ClassVar, NewType +from typing import ClassVar, NewType, Optional import cryptography.hazmat.primitives.asymmetric.padding as padding from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, rsa, ed25519 from cryptography.x509 import ( Certificate, load_der_x509_certificate, @@ -89,7 +89,7 @@ class Key: Represents a key in a `Keyring`. """ - hash_algorithm: hashes.HashAlgorithm + hash_algorithm: Optional[hashes.HashAlgorithm] key: PublicKey key_id: KeyID @@ -125,6 +125,10 @@ def __init__(self, public_key: _PublicKey) -> None: key = load_der_public_key( public_key.raw_bytes, types=(ec.EllipticCurvePublicKey,) ) + elif public_key.key_details == _PublicKeyDetails.PKIX_ED25519: + hash_algorithm = None + key = load_der_public_key( + public_key.raw_bytes, types=(ed25519.Ed25519PublicKey,)) else: raise VerificationError(f"unsupported key type: {public_key.key_details}") @@ -150,6 +154,11 @@ def verify(self, signature: bytes, data: bytes) -> None: data=data, signature_algorithm=ec.ECDSA(self.hash_algorithm), ) + elif isinstance(self.key, ed25519.Ed25519PublicKey): + self.key.verify( + signature=signature, + data=data, + ) else: # Unreachable without API misuse. raise VerificationError(f"keyring: unsupported key: {self.key}") diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 906c77213..77f3cd611 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -24,7 +24,7 @@ from typing import IO, NewType, Union from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, rsa, ed25519 from cryptography.x509 import ( Certificate, ExtensionNotFound, @@ -43,9 +43,11 @@ from importlib import resources -PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey] +PublicKey = Union[rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey] -PublicKeyTypes = Union[type[rsa.RSAPublicKey], type[ec.EllipticCurvePublicKey]] +PublicKeyTypes = Union[type[rsa.RSAPublicKey], + type[ec.EllipticCurvePublicKey], type[ed25519.Ed25519PublicKey]] HexStr = NewType("HexStr", str) """ @@ -64,7 +66,9 @@ def load_pem_public_key( key_pem: bytes, *, - types: tuple[PublicKeyTypes, ...] = (rsa.RSAPublicKey, ec.EllipticCurvePublicKey), + types: tuple[PublicKeyTypes, ...] = ( + rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey + ), ) -> PublicKey: """ A specialization of `cryptography`'s `serialization.load_pem_public_key` @@ -86,7 +90,8 @@ def load_pem_public_key( def load_der_public_key( key_der: bytes, *, - types: tuple[PublicKeyTypes, ...] = (rsa.RSAPublicKey, ec.EllipticCurvePublicKey), + types: tuple[PublicKeyTypes, ...] = ( + rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey), ) -> PublicKey: """ The `load_pem_public_key` specialization, but DER. diff --git a/sigstore/models.py b/sigstore/models.py index 7c95d9a02..3a44a9679 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -215,14 +215,14 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: if not inclusion_proof or not inclusion_proof.checkpoint.envelope: raise InvalidBundle("entry must contain inclusion proof, with checkpoint") + _logger.info(inclusion_proof.root_hash) parsed_inclusion_proof = LogInclusionProof( checkpoint=inclusion_proof.checkpoint.envelope, hashes=[h.hex() for h in inclusion_proof.hashes], log_index=inclusion_proof.log_index, - root_hash=inclusion_proof.root_hash.hex(), + root_hash=inclusion_proof.root_hash.decode(), tree_size=inclusion_proof.tree_size, ) - return LogEntry( uuid=None, body=B64Str(base64.b64encode(tlog_entry.canonicalized_body).decode()), @@ -252,7 +252,7 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, - root_hash=bytes.fromhex(self.inclusion_proof.root_hash), + root_hash=self.inclusion_proof.root_hash.encode(), tree_size=self.inclusion_proof.tree_size, hashes=[bytes.fromhex(hash_) for hash_ in self.inclusion_proof.hashes], checkpoint=rekor_v1.Checkpoint(envelope=self.inclusion_proof.checkpoint), @@ -484,9 +484,9 @@ def _verify(self) -> None: # We expect some old bundles to violate the rules around root # and intermediate CAs, so we issue warnings and not hard errors # in those cases. - leaf_cert, *chain_certs = ( + leaf_cert, *chain_certs = [ load_der_x509_certificate(cert.raw_bytes) for cert in certs - ) + ] if not cert_is_leaf(leaf_cert): raise InvalidBundle( "bundle contains an invalid leaf or non-leaf certificate in the leaf position" diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 8db37a657..fe4590317 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -376,16 +376,18 @@ def _verify_common_signing_cert( raise VerificationError(f"invalid log entry: {exc}") # (6): verify that log entry was integrated circa the signing certificate's - # validity period. - integrated_time = datetime.fromtimestamp(entry.integrated_time, tz=timezone.utc) - if not ( - bundle.signing_certificate.not_valid_before_utc - <= integrated_time - <= bundle.signing_certificate.not_valid_after_utc - ): - raise VerificationError( - "invalid signing cert: expired at time of Rekor entry" - ) + # validity period, if the inclusion promise is present. + if entry.integrated_time != 0: + integrated_time = datetime.fromtimestamp( + entry.integrated_time, tz=timezone.utc) + if not ( + bundle.signing_certificate.not_valid_before_utc + <= integrated_time + <= bundle.signing_certificate.not_valid_after_utc + ): + raise VerificationError( + "invalid signing cert: expired at time of Rekor entry" + ) def verify_dsse( self, bundle: Bundle, policy: VerificationPolicy @@ -512,10 +514,10 @@ def verify_artifact( 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" - ) + # 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" + # ) From 8e4c394137f11e7f7da8e76d379e4b5e940fc069 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 15:29:01 +0000 Subject: [PATCH 13/81] add support for ed25519 keys Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 4 ++++ sigstore/_internal/trust.py | 16 +++++++++++++--- sigstore/_utils.py | 22 +++++++++++++++++----- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64148f3f4..565a1ef54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ All versions prior to 0.9.0 are untracked. ### Fixed +### Added + +* Added support for ed25519 keys. + * Fixed the certificate calidity period check for Timestamp Authorities (TSA). Certificates need not have and end date, while still requiring a start date. [#1368](https://github.com/sigstore/sigstore-python/pull/1368) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 712eb0121..8ab855fb1 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -23,12 +23,12 @@ from datetime import datetime, timezone from enum import Enum from pathlib import Path -from typing import ClassVar, NewType +from typing import ClassVar, NewType, Optional import cryptography.hazmat.primitives.asymmetric.padding as padding from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa from cryptography.x509 import ( Certificate, load_der_x509_certificate, @@ -94,7 +94,7 @@ class Key: Represents a key in a `Keyring`. """ - hash_algorithm: hashes.HashAlgorithm + hash_algorithm: Optional[hashes.HashAlgorithm] key: PublicKey key_id: KeyID @@ -130,6 +130,11 @@ def __init__(self, public_key: _PublicKey) -> None: key = load_der_public_key( public_key.raw_bytes, types=(ec.EllipticCurvePublicKey,) ) + elif public_key.key_details == _PublicKeyDetails.PKIX_ED25519: + hash_algorithm = None + key = load_der_public_key( + public_key.raw_bytes, types=(ed25519.Ed25519PublicKey,) + ) else: raise VerificationError(f"unsupported key type: {public_key.key_details}") @@ -155,6 +160,11 @@ def verify(self, signature: bytes, data: bytes) -> None: data=data, signature_algorithm=ec.ECDSA(self.hash_algorithm), ) + elif isinstance(self.key, ed25519.Ed25519PublicKey): + self.key.verify( + signature=signature, + data=data, + ) else: # Unreachable without API misuse. raise VerificationError(f"keyring: unsupported key: {self.key}") diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 906c77213..26c1c6b92 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -24,7 +24,7 @@ from typing import IO, NewType, Union from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa from cryptography.x509 import ( Certificate, ExtensionNotFound, @@ -43,9 +43,13 @@ from importlib import resources -PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey] +PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey] -PublicKeyTypes = Union[type[rsa.RSAPublicKey], type[ec.EllipticCurvePublicKey]] +PublicKeyTypes = Union[ + type[rsa.RSAPublicKey], + type[ec.EllipticCurvePublicKey], + type[ed25519.Ed25519PublicKey], +] HexStr = NewType("HexStr", str) """ @@ -64,7 +68,11 @@ def load_pem_public_key( key_pem: bytes, *, - types: tuple[PublicKeyTypes, ...] = (rsa.RSAPublicKey, ec.EllipticCurvePublicKey), + types: tuple[PublicKeyTypes, ...] = ( + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ), ) -> PublicKey: """ A specialization of `cryptography`'s `serialization.load_pem_public_key` @@ -86,7 +94,11 @@ def load_pem_public_key( def load_der_public_key( key_der: bytes, *, - types: tuple[PublicKeyTypes, ...] = (rsa.RSAPublicKey, ec.EllipticCurvePublicKey), + types: tuple[PublicKeyTypes, ...] = ( + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ), ) -> PublicKey: """ The `load_pem_public_key` specialization, but DER. From ee73bb423ea9444c92bebcc201efd878d3482d51 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 16:39:26 +0000 Subject: [PATCH 14/81] add changelog self link Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ac75121..bc8ee2de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All versions prior to 0.9.0 are untracked. ### Added * Added support for ed25519 keys. + [#1377](https://github.com/sigstore/sigstore-python/pull/1377) ### Fixed From dad8ff69d8dbb6dc8f2a99a6556d8fe422475c75 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 16:49:12 +0000 Subject: [PATCH 15/81] lint Signed-off-by: Ramon Petgrave --- sigstore/_internal/trust.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 8ab855fb1..ae104e071 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -121,7 +121,7 @@ def __init__(self, public_key: _PublicKey) -> None: if not public_key.raw_bytes: raise VerificationError("public key is empty") - hash_algorithm: hashes.HashAlgorithm + hash_algorithm: Optional[hashes.HashAlgorithm] if public_key.key_details in self._RSA_SHA_256_DETAILS: hash_algorithm = hashes.SHA256() key = load_der_public_key(public_key.raw_bytes, types=(rsa.RSAPublicKey,)) @@ -146,7 +146,7 @@ def verify(self, signature: bytes, data: bytes) -> None: """ Verifies the given `data` against `signature` using the current key. """ - if isinstance(self.key, rsa.RSAPublicKey): + if isinstance(self.key, rsa.RSAPublicKey) and self.hash_algorithm is not None: self.key.verify( signature=signature, data=data, @@ -154,13 +154,19 @@ def verify(self, signature: bytes, data: bytes) -> None: padding=padding.PKCS1v15(), algorithm=self.hash_algorithm, ) - elif isinstance(self.key, ec.EllipticCurvePublicKey): + elif ( + isinstance(self.key, ec.EllipticCurvePublicKey) + and self.hash_algorithm is not None + ): self.key.verify( signature=signature, data=data, signature_algorithm=ec.ECDSA(self.hash_algorithm), ) - elif isinstance(self.key, ed25519.Ed25519PublicKey): + elif ( + isinstance(self.key, ed25519.Ed25519PublicKey) + and self.hash_algorithm is None + ): self.key.verify( signature=signature, data=data, From 95d6e116cd733a71d0881a25d1e80627053294a2 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 17:07:55 +0000 Subject: [PATCH 16/81] use latest tsa release, not latest tag Signed-off-by: Ramon Petgrave --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b97a13811..9c568d03b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: if: ${{ matrix.conf.os == 'ubuntu-latest' }} run: | # Fetch the latest sigstore/timestamp-authority build - SIGSTORE_TIMESTAMP_VERSION=$(gh api /repos/sigstore/timestamp-authority/tags --jq '.[0].name') + SIGSTORE_TIMESTAMP_VERSION=$(gh api /repos/sigstore/timestamp-authority/releases --jq '.[0].tag_name') wget https://github.com/sigstore/timestamp-authority/releases/download/${SIGSTORE_TIMESTAMP_VERSION}/timestamp-server-linux-amd64 -O /tmp/timestamp-server chmod +x /tmp/timestamp-server From da72b8ad6b3eb3e28b013399de11ad07becae236 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 17:11:10 +0000 Subject: [PATCH 17/81] add changelog for tsa test fix Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc8ee2de0..2f30a3ff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ All versions prior to 0.9.0 are untracked. * API: Make Rekor APIs compatible with Rekor v2 by removing trailing slashes from endpoints ([#1366](https://github.com/sigstore/sigstore-python/pull/1366)) +* CI: Timestamp Authority tests use latest release, not latest tag, of + [sigstore/timestamp-authority](https://github.com/sigstore/timestamp-authority) + [#1377](https://github.com/sigstore/sigstore-python/pull/1377) + ### Changed * `--trust-config` now requires a file with SigningConfig v0.2, and is able to fully From 146f85ef3e3c039951fca99f00ffd1cd9507d051 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 19:22:25 +0000 Subject: [PATCH 18/81] use all verified timestamps Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 5 +++++ q | 20 ++++++++++++++++++++ sigstore/verify/verifier.py | 23 ++++++++++++----------- test/unit/test_models.py | 5 +++-- 4 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 q diff --git a/CHANGELOG.md b/CHANGELOG.md index e77467913..1364e4bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ All versions prior to 0.9.0 are untracked. * API: Make Rekor APIs compatible with Rekor v2 by removing trailing slashes from endpoints ([#1366](https://github.com/sigstore/sigstore-python/pull/1366)) +* Timestamps: Verify the signature date against the validity period of both the + Timestamp Authority or the Transperency Service, if either of such timestamps + are present in the Bundle. We still require at lease one of such timestamps + [#PRLINK][PRLINK] + ### Changed * `--trust-config` now requires a file with SigningConfig v0.2, and is able to fully diff --git a/q b/q new file mode 100644 index 000000000..c02264732 --- /dev/null +++ b/q @@ -0,0 +1,20 @@ +diff --git a/test/unit/test_models.py b/test/unit/test_models.py +index 95f297f..088e86f 100644 +--- a/test/unit/test_models.py ++++ b/test/unit/test_models.py +@@ -30,12 +30,13 @@ from sigstore.models import ( +  +  + class TestLogEntry: +- def test_missing_inclusion_proof(self): ++ @pytest.mark.parametrize('integrated_time', [0, 1746819403]) ++ def test_missing_inclusion_proof(self, integrated_time: int): + with pytest.raises(ValueError, match=r"inclusion_proof"): + LogEntry( + uuid="fake", + body=b64encode(b"fake"), +- integrated_time=0, ++ integrated_time=integrated_time, + log_id="1234", + log_index=1, + inclusion_proof=None, diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 30e7e8e59..24efa665b 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -372,17 +372,18 @@ def _verify_common_signing_cert( except VerificationError as exc: raise VerificationError(f"invalid log entry: {exc}") - # (6): verify that log entry was integrated circa the signing certificate's - # validity period. - integrated_time = datetime.fromtimestamp(entry.integrated_time, tz=timezone.utc) - if not ( - bundle.signing_certificate.not_valid_before_utc - <= integrated_time - <= bundle.signing_certificate.not_valid_after_utc - ): - raise VerificationError( - "invalid signing cert: expired at time of Rekor entry" - ) + # (6): verify the log entry was signed circa circa the signing certificate's + # validity period, via the verified timestamps. In the case of timestamps from + # the transparency service, the integrated time will have been used. See `_establish_time()`. + for vts in verified_timestamps: + if not ( + bundle.signing_certificate.not_valid_before_utc + <= vts.time + <= bundle.signing_certificate.not_valid_after_utc + ): + raise VerificationError( + f"invalid signing cert: expired at time of signing, time via {vts}" + ) def verify_dsse( self, bundle: Bundle, policy: VerificationPolicy diff --git a/test/unit/test_models.py b/test/unit/test_models.py index 95f297f07..088e86f3f 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -30,12 +30,13 @@ class TestLogEntry: - def test_missing_inclusion_proof(self): + @pytest.mark.parametrize('integrated_time', [0, 1746819403]) + def test_missing_inclusion_proof(self, integrated_time: int): with pytest.raises(ValueError, match=r"inclusion_proof"): LogEntry( uuid="fake", body=b64encode(b"fake"), - integrated_time=0, + integrated_time=integrated_time, log_id="1234", log_index=1, inclusion_proof=None, From 830ea640e2f04ec781b243d93c80f1eb44dd53cc Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 20:16:02 +0000 Subject: [PATCH 19/81] additional test Signed-off-by: Ramon Petgrave --- test/unit/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test_models.py b/test/unit/test_models.py index 088e86f3f..f1d345e13 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -30,7 +30,7 @@ class TestLogEntry: - @pytest.mark.parametrize('integrated_time', [0, 1746819403]) + @pytest.mark.parametrize("integrated_time", [0, 1746819403]) def test_missing_inclusion_proof(self, integrated_time: int): with pytest.raises(ValueError, match=r"inclusion_proof"): LogEntry( From 17a97164942b3a7a1970df49023658a74ee7a94f Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 20:36:51 +0000 Subject: [PATCH 20/81] cleanup Signed-off-by: Ramon Petgrave --- q | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 q diff --git a/q b/q deleted file mode 100644 index c02264732..000000000 --- a/q +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/test/unit/test_models.py b/test/unit/test_models.py -index 95f297f..088e86f 100644 ---- a/test/unit/test_models.py -+++ b/test/unit/test_models.py -@@ -30,12 +30,13 @@ from sigstore.models import ( -  -  - class TestLogEntry: -- def test_missing_inclusion_proof(self): -+ @pytest.mark.parametrize('integrated_time', [0, 1746819403]) -+ def test_missing_inclusion_proof(self, integrated_time: int): - with pytest.raises(ValueError, match=r"inclusion_proof"): - LogEntry( - uuid="fake", - body=b64encode(b"fake"), -- integrated_time=0, -+ integrated_time=integrated_time, - log_id="1234", - log_index=1, - inclusion_proof=None, From 2e93051a73a27a7a22b3823cf68d9a6e7b31d41f Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 20:38:29 +0000 Subject: [PATCH 21/81] pr backlink Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1364e4bdc..e0430ed65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,8 @@ All versions prior to 0.9.0 are untracked. * Timestamps: Verify the signature date against the validity period of both the Timestamp Authority or the Transperency Service, if either of such timestamps - are present in the Bundle. We still require at lease one of such timestamps - [#PRLINK][PRLINK] + are present in the Bundle. We still require at lease one of such timestamps. + [#1381](https://github.com/sigstore/sigstore-python/pull/1381) ### Changed From f1058e8f1446c91fa8bb9fa957a05ce454169895 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 9 May 2025 23:25:04 +0000 Subject: [PATCH 22/81] attempt at root hash hex parse for v1 and v2 Signed-off-by: Ramon Petgrave --- sigstore/models.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index ad9e27e06..17a9a9e0e 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -216,12 +216,20 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: if not inclusion_proof or not inclusion_proof.checkpoint.envelope: raise InvalidBundle("entry must contain inclusion proof, with checkpoint") + # RekorV1 will hex-encode, but RekorV2 will not. + # See https://github.com/sigstore/rekor/blob/140f622a85d9eeb72677f6ab7de21744954eb4cc/pkg/api/entries.go#L446-L448. + try: + root_hash = inclusion_proof.root_hash.decode() + except UnicodeDecodeError as exc: + _logger.warning(exc) + root_hash = inclusion_proof.root_hash.hex() + _logger.info(inclusion_proof.root_hash) parsed_inclusion_proof = LogInclusionProof( checkpoint=inclusion_proof.checkpoint.envelope, hashes=[h.hex() for h in inclusion_proof.hashes], log_index=inclusion_proof.log_index, - root_hash=inclusion_proof.root_hash.decode(), + root_hash=root_hash, tree_size=inclusion_proof.tree_size, ) return LogEntry( @@ -245,11 +253,11 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ - inclusion_promise: rekor_v1.InclusionPromise | None = None - if self.inclusion_promise: - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) + # inclusion_promise: rekor_v1.InclusionPromise | None = None + # if self.inclusion_promise: + inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, @@ -259,6 +267,7 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: checkpoint=rekor_v1.Checkpoint(envelope=self.inclusion_proof.checkpoint), ) + _logger.info(inclusion_promise) tlog_entry = rekor_v1.TransparencyLogEntry( log_index=self.log_index, log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)), From 9fb64c362e908cbfcf8f8245b43e676344058dc9 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 17:35:06 +0000 Subject: [PATCH 23/81] Revert "cache the kindversion" This reverts commit e4470a9c793ac9c1189c2a0212f56d37935a6847. Signed-off-by: Ramon Petgrave --- sigstore/models.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index 17a9a9e0e..1a8ec95bc 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -59,7 +59,7 @@ from sigstore_protobuf_specs.dev.sigstore.common.v1 import Rfc3161SignedTimestamp from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1 from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import ( - InclusionProof, KindVersion + InclusionProof, ) from sigstore import dsse @@ -173,11 +173,6 @@ class LogEntry: log entry. """ - kind_version: KindVersion - """ - The kind and version of the log entry. - """ - @classmethod def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: """ @@ -244,7 +239,6 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: tlog_entry.inclusion_promise.signed_entry_timestamp ).decode() ), - kind_version=tlog_entry.kind_version, ) def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @@ -274,10 +268,32 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: integrated_time=self.integrated_time, inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, - kind_version=self.kind_version, canonicalized_body=base64.b64decode(self.body), ) + # NOTE fix the canonicalized_body + import json + tlog_entry.canonicalized_body = bytes(json.dumps({ + # **json.loads(tlog_entry.canonicalized_body), + 'kind': 'hashedrekord', + 'api_version': '0.0.2', + 'spec': { + 'signature': json.loads(tlog_entry.canonicalized_body)['signature'], + 'data': json.loads(tlog_entry.canonicalized_body)['data'] + } + }), encoding='utf-8') + + # Fill in the appropriate kind + body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( + tlog_entry.canonicalized_body + ) + if not isinstance(body_entry, (Hashedrekord, Dsse)): + raise InvalidBundle("log entry is not of expected type") + + tlog_entry.kind_version = rekor_v1.KindVersion( + kind=body_entry.kind, version=body_entry.api_version + ) + return tlog_entry def encode_canonical(self) -> bytes: From ceeb367e50e519b71eed2762e5e91dce1bfc5efc Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 18:15:01 +0000 Subject: [PATCH 24/81] Reapply "cache the kindversion" This reverts commit 9fb64c362e908cbfcf8f8245b43e676344058dc9. Signed-off-by: Ramon Petgrave --- sigstore/models.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index 1a8ec95bc..17a9a9e0e 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -59,7 +59,7 @@ from sigstore_protobuf_specs.dev.sigstore.common.v1 import Rfc3161SignedTimestamp from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1 from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import ( - InclusionProof, + InclusionProof, KindVersion ) from sigstore import dsse @@ -173,6 +173,11 @@ class LogEntry: log entry. """ + kind_version: KindVersion + """ + The kind and version of the log entry. + """ + @classmethod def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: """ @@ -239,6 +244,7 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: tlog_entry.inclusion_promise.signed_entry_timestamp ).decode() ), + kind_version=tlog_entry.kind_version, ) def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @@ -268,32 +274,10 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: integrated_time=self.integrated_time, inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, + kind_version=self.kind_version, canonicalized_body=base64.b64decode(self.body), ) - # NOTE fix the canonicalized_body - import json - tlog_entry.canonicalized_body = bytes(json.dumps({ - # **json.loads(tlog_entry.canonicalized_body), - 'kind': 'hashedrekord', - 'api_version': '0.0.2', - 'spec': { - 'signature': json.loads(tlog_entry.canonicalized_body)['signature'], - 'data': json.loads(tlog_entry.canonicalized_body)['data'] - } - }), encoding='utf-8') - - # Fill in the appropriate kind - body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( - tlog_entry.canonicalized_body - ) - if not isinstance(body_entry, (Hashedrekord, Dsse)): - raise InvalidBundle("log entry is not of expected type") - - tlog_entry.kind_version = rekor_v1.KindVersion( - kind=body_entry.kind, version=body_entry.api_version - ) - return tlog_entry def encode_canonical(self) -> bytes: From 6642f12a15f5eb21b1368ecefe02e8814d92c40a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 19:57:14 +0000 Subject: [PATCH 25/81] add test and data for a late timestamp Signed-off-by: Ramon Petgrave --- .../tsa/bundle.txt.late_timestamp.sigstore | 1 + test/unit/verify/test_verifier.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 test/assets/tsa/bundle.txt.late_timestamp.sigstore diff --git a/test/assets/tsa/bundle.txt.late_timestamp.sigstore b/test/assets/tsa/bundle.txt.late_timestamp.sigstore new file mode 100644 index 000000000..5b5e0f2fd --- /dev/null +++ b/test/assets/tsa/bundle.txt.late_timestamp.sigstore @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDBTCCAougAwIBAgIUIs3M2DgogCj3KotUVZg8Mok6IhMwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNTEyMTg0ODI2WhcNMjUwNTEyMTg1ODI2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEn2elp6N4BmBpOaQbpbiYY5EBXJq5+f0tPnffeJTbLVzPgUbpX4T5ZS7KDuQFQSPrljgIZAO3+ZmFSFFnwVrNv6OCAaowggGmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+j0g8S3mHrEo3eautm7T4RnwWwUwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlsXTr40AAAQDAEgwRgIhAN790LnuqDZwfqzyilH4qtk7zVvVZUQXB0Q0YfX9tNWXAiEAzH649BUx15UYsZUGihsBfNUQXov87UYzfYE2Zw2L174wCgYIKoZIzj0EAwMDaAAwZQIwCJ8+cVdfOc5SPoQnjY6rrIxIlYqLgtW65YrX8GzbRW4NpP37m6nxi6cjqtgwGFMeAjEAp4JgaETMFRgSBSSZLB7uhqr1fY97LPcHmAebKFpqFQERELMUmmqk5uHB2wgtvzB2"}, "tlogEntries": [{"logIndex": "42066373", "logId": {"keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1747075707", "inclusionPromise": {"signedEntryTimestamp": "MEUCIDxpagNcBytw52lZI3CwbTA6lfydnHlIGogI1Jfu13PKAiEAri9BAnNJDCZV3gEj9MuLEPw6jVbAiKfmrUmoaAIqSsc="}, "inclusionProof": {"logIndex": "10383961", "rootHash": "K6ZWztp1qbjIuzexDwMUOhf/+S+wqz4iQEDFTcEnNGQ=", "treeSize": "10383962", "hashes": ["HjQ6YJcBoxxKm4Uxs0zJCqC/LM/phnZMGiOiDLXo5wg=", "HSzuxscITh6g9k7vt64/9Z8zPwGwcQJv7NfnX92ULng=", "EEVPMqL5AIgHaYl2NbjmSTvn31oGEjhpTPbpgowrPM4=", "WbnH9wLRq4lD3Ju3FWOBZ+PEfvXT2c0Ugqy78gFgR0M=", "Kv0MBtfoWuGMfuJhPQiwSV7qUt+ALTQMx9BWYUrusb0=", "vld+lIPewmCjCp7W2cwUZD59sPgCK0rC0T2GpveXsmM=", "//dvSvxZzO+sVgkN0WfDdWVO4VGUsVGNT6bSmn5b/Qg=", "AzkFO8X9eKMNJxy+AuVjKe/2ObuNc4pGFzYucDuH87I=", "BVBT6KGWJPrAI4T7Zzt529+ZxU+G5UR0UMqPDcKUYzk=", "1CmXahercpSNPyH2ATDpK8S80Gim/GrKkm/8V5Ozue0=", "ZyAV6AeFLhv6n2Ya599XWHwy3HCr/y0+RF0P6Smg8IU=", "GoHRwlhYuJIYJdmRnHX5HWLr2ngxzHnAIIqBewovBi0=", "OdoqbUqBYHhj2W1RLM8APkQOnM2K9gzGm1KPFmwIIeQ="], "checkpoint": {"envelope": "rekor.sigstage.dev - 8202293616175992157\n10383962\nK6ZWztp1qbjIuzexDwMUOhf/+S+wqz4iQEDFTcEnNGQ=\n\n\u2014 rekor.sigstage.dev 0y8wozBFAiEA+12tjmkJ2CeZlW4baTsLtnVfdSeWNyW8ZFykmBcAn4QCIB6OZTD/bVgAsuq5FgSQZzwn0RPYl7+S1IFRYAoHIP5G\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4MDJkZDYwZmY4ODMzMzgwMmYyNTg1ZTczMDQzYmQyMWMzNDEyODVlMTk5MmZlNWIzMTc1NWUxY2FkZWFlMzBlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRmhsZU1oMjlRRW5QbVc3Mjhkd1h1ZkdGTVo0NG8zNkNseGVxRWVWaUxSdEFpQjIzUkRHenArbjF3aDVjVTF0cC9CampIc3RBQjdsWmY5S0tKbnpwM3ViV0E9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUkNWRU5EUVc5MVowRjNTVUpCWjBsVlNYTXpUVEpFWjI5blEyb3pTMjkwVlZaYVp6aE5iMnMyU1doTmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFZkMDVVUlhsTlZHY3dUMFJKTWxkb1kwNU5hbFYzVGxSRmVVMVVaekZQUkVreVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ1TW1Wc2NEWk9ORUp0UW5CUFlWRmljR0pwV1ZrMVJVSllTbkUxSzJZd2RGQnVabVlLWlVwVVlreFdlbEJuVldKd1dEUlVOVnBUTjB0RWRWRkdVVk5RY214cVowbGFRVTh6SzFwdFJsTkdSbTUzVm5KT2RqWlBRMEZoYjNkblowZHRUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlVyYWpCbkNqaFRNMjFJY2tWdk0yVmhkWFJ0TjFRMFVtNTNWM2RWZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFkUldVUldVakJTUVZGSUwwSkZPSGRVV1VaTVlWYzFlbHBYVGpGamJWVjBXVEo0ZG1SWFVqQmlNMEYwWXpKb2FHTnRWbXRNV0ZaNldsaEtRUXBaTW5oMlpGZFNNR0l6UVhSalNFcDJXa014TVdONU1XeFpXRTR3VEcxc2FHSlROVzVqTWxaNVpHMXNhbHBYUm1wWk1qa3hZbTVSZFZreU9YUk5RMnRIQ2tOcGMwZEJVVkZDWnpjNGQwRlJSVVZITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVGeVFtZHZja0puUlVVS1FWbFBMMDFCUlVsQ1FqQk5SekpvTUdSSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd4TWJVNTJZbFJEUW1sM1dVdExkMWxDUWtGSVZ3cGxVVWxGUVdkU09VSkljMEZsVVVJelFVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5ETnFlWFF2TkdWTFkyOUJka3RsTms5QlFVRkNDbXh6V0ZSeU5EQkJRVUZSUkVGRlozZFNaMGxvUVU0M09UQk1iblZ4UkZwM1puRjZlV2xzU0RSeGRHczNlbFoyVmxwVlVWaENNRkV3V1daWU9YUk9WMWdLUVdsRlFYcElOalE1UWxWNE1UVlZXWE5hVlVkcGFITkNaazVWVVZodmRqZzNWVmw2WmxsRk1scDNNa3d4TnpSM1EyZFpTVXR2V2tsNmFqQkZRWGROUkFwaFFVRjNXbEZKZDBOS09DdGpWbVJtVDJNMVUxQnZVVzVxV1RaeWNrbDRTV3haY1V4bmRGYzJOVmx5V0RoSGVtSlNWelJPY0ZBek4yMDJibmhwTm1OcUNuRjBaM2RIUmsxbFFXcEZRWEEwU21kaFJWUk5SbEpuVTBKVFUxcE1RamQxYUhGeU1XWlpPVGRNVUdOSWJVRmxZa3RHY0hGR1VVVlNSVXhOVlcxdGNXc0tOWFZJUWpKM1ozUjJla0l5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0="}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE6TADAgEAMIIE4AYJKoZIhvcNAQcCoIIE0TCCBM0CAQMxDTALBglghkgBZQMEAgEwgcIGCyqGSIb3DQEJEAEEoIGyBIGvMIGsAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgLenXcJBzdjua5V/WDyNWpTIysBnS9xKUPS0plFLqG0gCFQD3x9GVccz7Cvui6lxEdDQtb7L3uBgPMjAyNTA1MTIxOTAwMjdaMAMCAQECCHSCL66M6EByoDKkMDAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYaCCAhMwggIPMIIBlqADAgECAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkMB4XDTI1MDMyODA5MTQwNloXDTM1MDMyNjA4MTQwNlowLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATHW/kXcekP16Ae6SekEWVHPtAFEMm7hp5XO33MktFjSW+bHWUXtYEzZz0A3xkY9CyYOoeUk3ZH/v5HEuS+UvORzX0g7Hfy3uYYYRwHtqBQN0IX8rLdFMtIrRej/QCAdB2jajBoMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUqPxk9ijeLuY7c09UjFLE4ZzdU6UwHwYDVR0jBBgwFoAUOyBGWV61Mk1HMM5uY+5zdEfyBH0wFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDZwAwZAIwRK9VLoYa0Xff4nX1N/AQ1YleNG/iLT8dAXAtRKRfpN9XuDScbxWeo0cku8SkC06NAjBQPe7LBNeitA/UOBtXT2sX1h6f4ISqz+ISmJ4lY+y3bzRJI5nk1r53I9WT3/xIWToxggHbMIIB1wIBATBRMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAsGCWCGSAFlAwQCAaCB/DAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI1MDUxMjE5MDAyN1owLwYJKoZIhvcNAQkEMSIEIB+SgwjYmkSbLhZNvWnGj/KrNAOr+sqpO38OpoIYSOSZMIGOBgsqhkiG9w0BCRACLzF/MH0wezB5BCAG9P/gR/6zWZm3M7DXoyNQHPwY5MAzZqhF13U250snRDBVMD2kOzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAgRnMGUCMFMwn1mx1D3q+vKwf57UDA96286zoTJ+ITJG5IQVypKLqnKSEX8Gm7GIRDXR06PJPgIxANj1zJ+cVXxoYuH4H8yobeqVeztGLZNd+YqbkyuvTkcX46CTCH0e6imE+Z4yTCRiYw=="}]}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4="}, "signature": "MEQCIFhleMh29QEnPmW728dwXufGFMZ44o36ClxeqEeViLRtAiB23RDGzp+n1wh5cU1tp/BjjHstAB7lZf9KKJnzp3ubWA=="}} diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 5b0fbe401..292a4d514 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -301,6 +301,24 @@ def test_verifier_no_authorities(self, asset, null_policy): null_policy, ) + def test_late_timestamp(self, caplog, verifier, asset, null_policy): + ''' + Ensures that verifying the signing certificate fails because the timestamp + is outside the certificate's validity window. The sample bundle + "tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)` + into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor + but before the timestamp is requested. + ''' + with pytest.raises(VerificationError, match="not enough timestamps"): + verifier.verify_artifact( + asset("tsa/bundle.txt").read_bytes(), + Bundle.from_json( + asset("tsa/bundle.txt.late_timestamp.sigstore").read_bytes()), + null_policy, + ) + + assert caplog.records[0].message == "Error while verifying certificates: Unable to verify certificate" + def test_verifier_not_enough_timestamp( self, verifier, asset, null_policy, monkeypatch ): From 0375ed218fb4bbfde5545669ac6abad1f930564c Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 19:59:54 +0000 Subject: [PATCH 26/81] better documentation Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 6 +++--- sigstore/verify/verifier.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0430ed65..1792169c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,9 @@ All versions prior to 0.9.0 are untracked. * API: Make Rekor APIs compatible with Rekor v2 by removing trailing slashes from endpoints ([#1366](https://github.com/sigstore/sigstore-python/pull/1366)) -* Timestamps: Verify the signature date against the validity period of both the - Timestamp Authority or the Transperency Service, if either of such timestamps - are present in the Bundle. We still require at lease one of such timestamps. +* Verify: verify that all established times (timestamps or the log integration time) + are within the signing certificate validity period. At least one established time is + still required. [#1381](https://github.com/sigstore/sigstore-python/pull/1381) ### Changed diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 24efa665b..471b5e1f4 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -372,9 +372,8 @@ def _verify_common_signing_cert( except VerificationError as exc: raise VerificationError(f"invalid log entry: {exc}") - # (6): verify the log entry was signed circa circa the signing certificate's - # validity period, via the verified timestamps. In the case of timestamps from - # the transparency service, the integrated time will have been used. See `_establish_time()`. + # (6): verify our established times (timestamps or the log integration time) are + # within signing certificate validity period. for vts in verified_timestamps: if not ( bundle.signing_certificate.not_valid_before_utc From 6d9d7a81edef98be4799d3a287bb09a1aecdb763 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 20:00:49 +0000 Subject: [PATCH 27/81] lint Signed-off-by: Ramon Petgrave --- test/unit/verify/test_verifier.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 292a4d514..123bd00b5 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -302,22 +302,26 @@ def test_verifier_no_authorities(self, asset, null_policy): ) def test_late_timestamp(self, caplog, verifier, asset, null_policy): - ''' + """ Ensures that verifying the signing certificate fails because the timestamp is outside the certificate's validity window. The sample bundle "tsa/bundle.txt.late_timestamp.sigstore" was generated by adding `time.sleep(12*60)` into `sigstore.sign.Signer._finalize_sign()`, just after the entry is posted to Rekor but before the timestamp is requested. - ''' + """ with pytest.raises(VerificationError, match="not enough timestamps"): verifier.verify_artifact( asset("tsa/bundle.txt").read_bytes(), Bundle.from_json( - asset("tsa/bundle.txt.late_timestamp.sigstore").read_bytes()), + asset("tsa/bundle.txt.late_timestamp.sigstore").read_bytes() + ), null_policy, ) - assert caplog.records[0].message == "Error while verifying certificates: Unable to verify certificate" + assert ( + caplog.records[0].message + == "Error while verifying certificates: Unable to verify certificate" + ) def test_verifier_not_enough_timestamp( self, verifier, asset, null_policy, monkeypatch From d9ca17ea769d6cb6dd7efd7e7260ef46cda5df06 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 22:28:20 +0000 Subject: [PATCH 28/81] Revert "attempt at root hash hex parse for v1 and v2" This reverts commit f1058e8f1446c91fa8bb9fa957a05ce454169895. Signed-off-by: Ramon Petgrave --- sigstore/models.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index 17a9a9e0e..ad9e27e06 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -216,20 +216,12 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: if not inclusion_proof or not inclusion_proof.checkpoint.envelope: raise InvalidBundle("entry must contain inclusion proof, with checkpoint") - # RekorV1 will hex-encode, but RekorV2 will not. - # See https://github.com/sigstore/rekor/blob/140f622a85d9eeb72677f6ab7de21744954eb4cc/pkg/api/entries.go#L446-L448. - try: - root_hash = inclusion_proof.root_hash.decode() - except UnicodeDecodeError as exc: - _logger.warning(exc) - root_hash = inclusion_proof.root_hash.hex() - _logger.info(inclusion_proof.root_hash) parsed_inclusion_proof = LogInclusionProof( checkpoint=inclusion_proof.checkpoint.envelope, hashes=[h.hex() for h in inclusion_proof.hashes], log_index=inclusion_proof.log_index, - root_hash=root_hash, + root_hash=inclusion_proof.root_hash.decode(), tree_size=inclusion_proof.tree_size, ) return LogEntry( @@ -253,11 +245,11 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ - # inclusion_promise: rekor_v1.InclusionPromise | None = None - # if self.inclusion_promise: - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) + inclusion_promise: rekor_v1.InclusionPromise | None = None + if self.inclusion_promise: + inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, @@ -267,7 +259,6 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: checkpoint=rekor_v1.Checkpoint(envelope=self.inclusion_proof.checkpoint), ) - _logger.info(inclusion_promise) tlog_entry = rekor_v1.TransparencyLogEntry( log_index=self.log_index, log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)), From 1d9a669739e9ca4ba457795826e09d3c3249cb17 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 22:33:31 +0000 Subject: [PATCH 29/81] inclusion_promise typing Signed-off-by: Ramon Petgrave --- sigstore/models.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index ad9e27e06..539ee369d 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -216,12 +216,11 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: if not inclusion_proof or not inclusion_proof.checkpoint.envelope: raise InvalidBundle("entry must contain inclusion proof, with checkpoint") - _logger.info(inclusion_proof.root_hash) parsed_inclusion_proof = LogInclusionProof( checkpoint=inclusion_proof.checkpoint.envelope, hashes=[h.hex() for h in inclusion_proof.hashes], log_index=inclusion_proof.log_index, - root_hash=inclusion_proof.root_hash.decode(), + root_hash=inclusion_proof.root_hash.hex(), tree_size=inclusion_proof.tree_size, ) return LogEntry( @@ -245,15 +244,13 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ - inclusion_promise: rekor_v1.InclusionPromise | None = None - if self.inclusion_promise: - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) + inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, - root_hash=self.inclusion_proof.root_hash.encode(), + root_hash=bytes.fromhex(self.inclusion_proof.root_hash), tree_size=self.inclusion_proof.tree_size, hashes=[bytes.fromhex(hash_) for hash_ in self.inclusion_proof.hashes], checkpoint=rekor_v1.Checkpoint(envelope=self.inclusion_proof.checkpoint), From b7403d3aab81c1b75776080d2e515d5378adee47 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 22:44:44 +0000 Subject: [PATCH 30/81] post inclusion promise check Signed-off-by: Ramon Petgrave --- sigstore/models.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index 539ee369d..400085c52 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -244,10 +244,6 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) - inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, root_hash=bytes.fromhex(self.inclusion_proof.root_hash), @@ -260,11 +256,14 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: log_index=self.log_index, log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)), integrated_time=self.integrated_time, - inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, kind_version=self.kind_version, canonicalized_body=base64.b64decode(self.body), ) + if self.inclusion_promise: + tlog_entry.inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) return tlog_entry From 46b0087f616faa1a194242e9892ac8856c53bc2c Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 22:50:23 +0000 Subject: [PATCH 31/81] betterproto: avoid assigning a var of type Optional[InclusionPromise] to TransparencyLogEntry.inclusion_promise ``` File "/usr/local/google/home/rpetgrave/src/ssp/sigstore-python/sigstore/models.py", line 262, in _to_rekor tlog_entry = rekor_v1.TransparencyLogEntry( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/google/home/rpetgrave/src/ssp/.venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py", line 120, in __init__ s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s) pydantic_core._pydantic_core.ValidationError: 1 validation error for TransparencyLogEntry inclusion_promise Input should be a dictionary or an instance of InclusionPromise [type=dataclass_type, input_value=None, input_type=NoneType] ``` Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 2 ++ sigstore/models.py | 12 +++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e77467913..7b741e7c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ All versions prior to 0.9.0 are untracked. ### Fixed +* Avoid pydantic's instantiation issues with `TransparencyLogEntry` when `InclusionPromise` is not present. + * TSA: Changed the Timestamp Authority requests to explicitly use sha256 for message digests. [#1373](https://github.com/sigstore/sigstore-python/pull/1373) diff --git a/sigstore/models.py b/sigstore/models.py index 484ec4d60..4eb687f8e 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -239,12 +239,6 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ - inclusion_promise: rekor_v1.InclusionPromise | None = None - if self.inclusion_promise: - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) - inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, root_hash=bytes.fromhex(self.inclusion_proof.root_hash), @@ -257,10 +251,14 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: log_index=self.log_index, log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)), integrated_time=self.integrated_time, - inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, canonicalized_body=base64.b64decode(self.body), ) + if self.inclusion_promise: + inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) + tlog_entry.inclusion_promise = inclusion_promise # Fill in the appropriate kind body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( From c961b52009b6eb9776b948f0044c9bb46a209ed6 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 23:34:52 +0000 Subject: [PATCH 32/81] add test Signed-off-by: Ramon Petgrave --- sigstore/models.py | 14 +++-- test/assets/bundle.txt.ec_sig.sigstore | 60 +++++++++++++++++++++ test/assets/bundle.txt.ed25119_sig.sigstore | 1 + test/unit/conftest.py | 2 +- test/unit/test_models.py | 17 ++++++ 5 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 test/assets/bundle.txt.ec_sig.sigstore create mode 100644 test/assets/bundle.txt.ed25119_sig.sigstore diff --git a/sigstore/models.py b/sigstore/models.py index 4eb687f8e..ba245092c 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -219,6 +219,14 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: tree_size=inclusion_proof.tree_size, ) + inclusion_promise: Optional[B64Str] = None + if tlog_entry.inclusion_promise: + inclusion_promise = B64Str( + base64.b64encode( + tlog_entry.inclusion_promise.signed_entry_timestamp).decode() + ) + + return LogEntry( uuid=None, body=B64Str(base64.b64encode(tlog_entry.canonicalized_body).decode()), @@ -226,11 +234,7 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: log_id=tlog_entry.log_id.key_id.hex(), log_index=tlog_entry.log_index, inclusion_proof=parsed_inclusion_proof, - inclusion_promise=B64Str( - base64.b64encode( - tlog_entry.inclusion_promise.signed_entry_timestamp - ).decode() - ), + inclusion_promise=inclusion_promise, ) def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: diff --git a/test/assets/bundle.txt.ec_sig.sigstore b/test/assets/bundle.txt.ec_sig.sigstore new file mode 100644 index 000000000..38981e6c4 --- /dev/null +++ b/test/assets/bundle.txt.ec_sig.sigstore @@ -0,0 +1,60 @@ +{ + "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", + "verificationMaterial": { + "certificate": { + "rawBytes": "MIIDBTCCAougAwIBAgIUDHrJhCWCK2izQkrwdmDAUAvo7wwwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNTEyMjE0OTA2WhcNMjUwNTEyMjE1OTA2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1lXAecFnEQdQ7pV7IJZ4WrUkRiqOu6xxylT/uKQZ6KBoeeGWV7IKS3qyqNuvo1kp58QZyjf3pegGnr7NjBecJKOCAaowggGmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUzXnrLAOjQTu28nTJU9r4KWmq1bYwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlsZ5FowAAAQDAEgwRgIhAIruqc3Ze+CmmVcjQwceXa6kUBIC1yzMmau+FaKpRNCBAiEA4NCPhO226EI2wBGuDEJN2GzU6owyDNZ8zcPTweIW9xwwCgYIKoZIzj0EAwMDaAAwZQIxAOF3HAQutLxrw6032xGmCEQ6/DsRyc7xEpi8PPYkXWrcPgi3TBYk0Tve5Ee0s5ExxwIwO5NIwVT3bJYYerGVqKZ33baU0Zcqr2cOy+bqyIJ/hWsv4PI8y851Be1tRawQ69fo" + }, + "tlogEntries": [ + { + "logIndex": "42072024", + "logId": { + "keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY=" + }, + "kindVersion": { + "kind": "hashedrekord", + "version": "0.0.1" + }, + "integratedTime": "1747086546", + "inclusionPromise": { + "signedEntryTimestamp": "MEYCIQDb6doU+s78TofYvVMNFtYizrVUHd1nOwNb8NPtYKjmYAIhAKNuOHv3D6/yMuFTr32e3Cqn5u06GKucToL8STDVNexn" + }, + "inclusionProof": { + "logIndex": "10389612", + "rootHash": "bbsNKE9KZp+1xek3B1MvlibzHh4590E9scc+0jx98FE=", + "treeSize": "10389613", + "hashes": [ + "L8AoJyHC0vEBfjNNaBOUpkjNJVlGYHPzy8HaKDSODRg=", + "rCpL9WUSCfejPngtqr1D7hwXINkibaYs9TCIjFj6REA=", + "gjrAaC47wet08QIfKfdgZvcXMPT2cJyRo3zgAV7NgJU=", + "iq28HU8kGqSfL/W+a2JVTsr8hSnZyJjpUM9LxKp8ppg=", + "pj//jLKp4lWuUr1Ez6Gtxn1qIHupasn29h+eIpC9gfQ=", + "ilCTDd3VLkkwu96Md6bJXW5Ik9OqXMPgD/Fy9VDY7Ck=", + "BVBT6KGWJPrAI4T7Zzt529+ZxU+G5UR0UMqPDcKUYzk=", + "1CmXahercpSNPyH2ATDpK8S80Gim/GrKkm/8V5Ozue0=", + "ZyAV6AeFLhv6n2Ya599XWHwy3HCr/y0+RF0P6Smg8IU=", + "GoHRwlhYuJIYJdmRnHX5HWLr2ngxzHnAIIqBewovBi0=", + "OdoqbUqBYHhj2W1RLM8APkQOnM2K9gzGm1KPFmwIIeQ=" + ], + "checkpoint": { + "envelope": "rekor.sigstage.dev - 8202293616175992157\n10389613\nbbsNKE9KZp+1xek3B1MvlibzHh4590E9scc+0jx98FE=\n\n\u2014 rekor.sigstage.dev 0y8wozBEAiBd8UikHor4EsQSWuvZ4jH1z7O5yAPgbxut0qEN1oqACAIgSt4NvNaV3na15RQp3wjlwPoslDYFBPEP+A+RdbS/sRQ=\n" + } + }, + "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4MDJkZDYwZmY4ODMzMzgwMmYyNTg1ZTczMDQzYmQyMWMzNDEyODVlMTk5MmZlNWIzMTc1NWUxY2FkZWFlMzBlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJSHJFV0ZGNm1HRVRiZnFvRDh3TXNiR01CU0RIclM4eFV4WmZGSEVGcDF6T0FpRUE3QjFXRXZFNEYycFB3UlQ1MHN2UkNzL1hvdmltWitjdU1tK29nSHJwck1nPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUkNWRU5EUVc5MVowRjNTVUpCWjBsVlJFaHlTbWhEVjBOTE1tbDZVV3R5ZDJSdFJFRlZRWFp2TjNkM2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFZkMDVVUlhsTmFrVXdUMVJCTWxkb1kwNU5hbFYzVGxSRmVVMXFSVEZQVkVFeVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVV4YkZoQlpXTkdia1ZSWkZFM2NGWTNTVXBhTkZkeVZXdFNhWEZQZFRaNGVIbHNWQzhLZFV0UldqWkxRbTlsWlVkWFZqZEpTMU16Y1hseFRuVjJiekZyY0RVNFVWcDVhbVl6Y0dWblIyNXlOMDVxUW1WalNrdFBRMEZoYjNkblowZHRUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlY2V0c1eUNreEJUMnBSVkhVeU9HNVVTbFU1Y2pSTFYyMXhNV0paZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFkUldVUldVakJTUVZGSUwwSkZPSGRVV1VaTVlWYzFlbHBYVGpGamJWVjBXVEo0ZG1SWFVqQmlNMEYwWXpKb2FHTnRWbXRNV0ZaNldsaEtRUXBaTW5oMlpGZFNNR0l6UVhSalNFcDJXa014TVdONU1XeFpXRTR3VEcxc2FHSlROVzVqTWxaNVpHMXNhbHBYUm1wWk1qa3hZbTVSZFZreU9YUk5RMnRIQ2tOcGMwZEJVVkZDWnpjNGQwRlJSVVZITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVGeVFtZHZja0puUlVVS1FWbFBMMDFCUlVsQ1FqQk5SekpvTUdSSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd4TWJVNTJZbFJEUW1sM1dVdExkMWxDUWtGSVZ3cGxVVWxGUVdkU09VSkljMEZsVVVJelFVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5ETnFlWFF2TkdWTFkyOUJka3RsTms5QlFVRkNDbXh6V2pWR2IzZEJRVUZSUkVGRlozZFNaMGxvUVVseWRYRmpNMXBsSzBOdGJWWmphbEYzWTJWWVlUWnJWVUpKUXpGNWVrMXRZWFVyUm1GTGNGSk9RMElLUVdsRlFUUk9RMUJvVHpJeU5rVkpNbmRDUjNWRVJVcE9Na2Q2VlRadmQzbEVUbG80ZW1OUVZIZGxTVmM1ZUhkM1EyZFpTVXR2V2tsNmFqQkZRWGROUkFwaFFVRjNXbEZKZUVGUFJqTklRVkYxZEV4NGNuYzJNRE15ZUVkdFEwVlJOaTlFYzFKNVl6ZDRSWEJwT0ZCUVdXdFlWM0pqVUdkcE0xUkNXV3N3VkhabENqVkZaVEJ6TlVWNGVIZEpkMDgxVGtsM1ZsUXpZa3BaV1dWeVIxWnhTMW96TTJKaFZUQmFZM0Z5TW1OUGVTdGljWGxKU2k5b1YzTjJORkJKT0hrNE5URUtRbVV4ZEZKaGQxRTJPV1p2Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=" + } + ], + "timestampVerificationData": { + "rfc3161Timestamps": [ + { + "signedTimestamp": "MIIE6DADAgEAMIIE3wYJKoZIhvcNAQcCoIIE0DCCBMwCAQMxDTALBglghkgBZQMEAgEwgcIGCyqGSIb3DQEJEAEEoIGyBIGvMIGsAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgwYy4oaOlu9+0ir78A+UNZUwoNxqsdapT5si2GXVj32QCFG5dEFnm6TzNLIsY5ZDKQgO5rCpqGA8yMDI1MDUxMjIxNDkwN1owAwIBAQIJAJaPJ2RsSnggoDKkMDAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYaCCAhMwggIPMIIBlqADAgECAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkMB4XDTI1MDMyODA5MTQwNloXDTM1MDMyNjA4MTQwNlowLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATHW/kXcekP16Ae6SekEWVHPtAFEMm7hp5XO33MktFjSW+bHWUXtYEzZz0A3xkY9CyYOoeUk3ZH/v5HEuS+UvORzX0g7Hfy3uYYYRwHtqBQN0IX8rLdFMtIrRej/QCAdB2jajBoMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUqPxk9ijeLuY7c09UjFLE4ZzdU6UwHwYDVR0jBBgwFoAUOyBGWV61Mk1HMM5uY+5zdEfyBH0wFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDZwAwZAIwRK9VLoYa0Xff4nX1N/AQ1YleNG/iLT8dAXAtRKRfpN9XuDScbxWeo0cku8SkC06NAjBQPe7LBNeitA/UOBtXT2sX1h6f4ISqz+ISmJ4lY+y3bzRJI5nk1r53I9WT3/xIWToxggHaMIIB1gIBATBRMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAsGCWCGSAFlAwQCAaCB/DAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI1MDUxMjIxNDkwN1owLwYJKoZIhvcNAQkEMSIEIHp8LCphfuAEE4ZGsdzxBBGHt4PrblLYm67UiL2wyNaOMIGOBgsqhkiG9w0BCRACLzF/MH0wezB5BCAG9P/gR/6zWZm3M7DXoyNQHPwY5MAzZqhF13U250snRDBVMD2kOzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAgRmMGQCMDLfK3Z/hggfXLrSCWb+UNH0k0Cqn7tLPGBO8/crkhiiKldIdBqCTKWxJNBgtYS11wIwcQxraku4B3x09VTVbWy8e4DCSJPwr6Cve8u1qRNhK657rBUcWAifuytfI+OaAVO6" + } + ] + } + }, + "messageSignature": { + "messageDigest": { + "algorithm": "SHA2_256", + "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4=" + }, + "signature": "MEUCIHrEWFF6mGETbfqoD8wMsbGMBSDHrS8xUxZfFHEFp1zOAiEA7B1WEvE4F2pPwRT50svRCs/XovimZ+cuMm+ogHrprMg=" + } +} \ No newline at end of file diff --git a/test/assets/bundle.txt.ed25119_sig.sigstore b/test/assets/bundle.txt.ed25119_sig.sigstore new file mode 100644 index 000000000..ba34a738e --- /dev/null +++ b/test/assets/bundle.txt.ed25119_sig.sigstore @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDBjCCAougAwIBAgIUBmcKcm44996DqZ8AajzkgCTqTSYwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNTEyMjE0OTIzWhcNMjUwNTEyMjE1OTIzWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjUe/mTP/Ov9Vzd0EvI1kiN5YBM2vbEjSjV+85x/gmHnpAbBRLSxAz45pz3tv/tScyZYpaYO8LpO5jCKHi/pFr6OCAaowggGmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUN3tQsmRLo4QgJLIfLqnFozJERGQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlsZ5V2IAAAQDAEgwRgIhAMW1dIiyhdZlemxT9yAwj6Z6R9QAYPZnrfjFiG2LCUdNAiEAg7KBs3OBlXj7yUlcQNePeErCTYS7H4abEJzfWixTP1EwCgYIKoZIzj0EAwMDaQAwZgIxAJjuH61o/acPbkW7916t3/aO6lVGGjthESf3zL7MYRtL2WrkLC34p8r7EvMzZUpjJwIxANoZtIt4x/3LJIcNbYc/Y6GNY30YftaVnIJcMgkTiUfj6p01Y7xh+30pSGu+g8QaDw=="}, "tlogEntries": [{"logIndex": "11", "logId": {}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.2"}, "inclusionPromise": {}, "inclusionProof": {"logIndex": "11", "rootHash": "N2ZjNTQ3ZmFkODQwOTYyZTljZDM4ZWM2N2Y4NjdkMGY0Zjc4MjAzMTFkMmMyZjY2ZjYwNzY5MjE4ZTU4YWJkNw==", "treeSize": "12", "hashes": ["LFR8RRBHttds/zWI7G0SqCaQTKAY/LK+wqI1cwk+qZk=", "O8SpsKTjqBfWFr31XEgBjW0vga/o8tmxIwlqlku4PsE=", "N/6eReD9y/7siiZIbNJHXCSj9cnVo/Nb0jpUKanmb/0="], "checkpoint": {"envelope": "rekor-local\n12\nf8VH+thAli6c047Gf4Z9D094IDEdLC9m9gdpIY5Yq9c=\n\n\u2014 rekor-local 2AtEIIf92qDyUvoATNUZAPbhrjLgeHS1E0FE8dG/N8IZkrDz6qVtxw7rYxJ+iF+vLp9JKiJlqPxhIcg1DFXnzHJL1As=\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJoYXNoZWRSZWtvcmRWMF8wXzIiOnsiZGF0YSI6eyJhbGdvcml0aG0iOiJTSEEyXzI1NiIsImRpZ2VzdCI6ImdDM1dEL2lETTRBdkpZWG5NRU85SWNOQktGNFprdjViTVhWZUhLM3E0dzQ9In0sInNpZ25hdHVyZSI6eyJjb250ZW50IjoiTUVVQ0lFM0RCelhVVG0vSlFJZW5ndXkrZkNYN3h4L1lJV1lNRXBWY0tFcDRibjZaQWlFQThmV2wwektGUndtVkNGalVDWHNZZ0hXbURYb3Z2bC82SEhjL1cvMWEzSVk9IiwidmVyaWZpZXIiOnsia2V5RGV0YWlscyI6IlBLSVhfRUNEU0FfUDM4NF9TSEFfMjU2IiwicHVibGljS2V5Ijp7InJhd0J5dGVzIjoiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFalVlL21UUC9PdjlWemQwRXZJMWtpTjVZQk0ydmJFalNqVis4NXgvZ21IbnBBYkJSTFN4QXo0NXB6M3R2L3RTY3laWXBhWU84THBPNWpDS0hpL3BGcnc9PSJ9fX19fX0="}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE6TADAgEAMIIE4AYJKoZIhvcNAQcCoIIE0TCCBM0CAQMxDTALBglghkgBZQMEAgEwgcMGCyqGSIb3DQEJEAEEoIGzBIGwMIGtAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgy0XTMS1OieRcKYvekVJN32//loW7DgI9gMvDch2zz/4CFQCBTKTDxjPnL6T5UOUgztE9gnYX4BgPMjAyNTA1MTIyMTQ5MjVaMAMCAQECCQD60OumBC+I7qAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgggITMIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6MYIB2jCCAdYCAQEwUTA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzALBglghkgBZQMEAgGggfwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA1MTIyMTQ5MjVaMC8GCSqGSIb3DQEJBDEiBCBq2ndG4pDq7Rl1DG7+hkigOzXj1wE0igiEPhGWQKLekTCBjgYLKoZIhvcNAQkQAi8xfzB9MHsweQQgBvT/4Ef+s1mZtzOw16MjUBz8GOTAM2aoRdd1NudLJ0QwVTA9pDswOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwIEZjBkAjBBYnDvFA91/JDoz0q5YhOVpaUbQ5QS3opVZJVoluLEulIAI58OTFAk76JEf7HfD/oCMBOR++d9Wyg+gH5ttJuh7+WOoRtR6vxuJosEpF51SfvpHSam/bN90dc2Cl7z2hgpng=="}]}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4="}, "signature": "MEUCIE3DBzXUTm/JQIenguy+fCX7xx/YIWYMEpVcKEp4bn6ZAiEA8fWl0zKFRwmVCFjUCXsYgHWmDXovvl/6HHc/W/1a3IY="}} diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 4d1b2331c..e495557f4 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -113,7 +113,7 @@ def _signing_materials(name: str, client: RekorClient) -> tuple[Path, Bundle]: @pytest.fixture -def signing_bundle(asset): +def signing_bundle(asset) -> Callable[[str], tuple[Path, Bundle]]: def _signing_bundle(name: str) -> tuple[Path, Bundle]: file = asset(name) bundle_path = asset(f"{name}.sigstore") diff --git a/test/unit/test_models.py b/test/unit/test_models.py index 95f297f07..bcae73e61 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -42,6 +42,23 @@ def test_missing_inclusion_proof(self): inclusion_promise=None, ) + def test_missing_inclusion_promise_round_trip(self, signing_bundle): + ''' + Ensures that LogEntry._to_rekor() succeeds even without an inclusion_promise. + ''' + bundle: Bundle + _, bundle = signing_bundle("bundle.txt") + _dict = bundle.log_entry._to_rekor().to_dict() + print(_dict) + del _dict['inclusionPromise'] + entry = LogEntry._from_dict_rekor(_dict) + assert entry.inclusion_promise is None + assert entry._to_rekor() is not None + assert (LogEntry._from_dict_rekor( + entry._to_rekor().to_dict()) == entry) + assert False + + def test_logentry_roundtrip(self, signing_bundle): _, bundle = signing_bundle("bundle.txt") From 43dc46895414bdd6a053c6a93a9d389c776c2a47 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 23:36:35 +0000 Subject: [PATCH 33/81] Revert "betterproto: avoid assigning a var of type Optional[InclusionPromise] to TransparencyLogEntry.inclusion_promise" This reverts commit 46b0087f616faa1a194242e9892ac8856c53bc2c. Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 2 -- sigstore/models.py | 12 +++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b741e7c1..e77467913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,6 @@ All versions prior to 0.9.0 are untracked. ### Fixed -* Avoid pydantic's instantiation issues with `TransparencyLogEntry` when `InclusionPromise` is not present. - * TSA: Changed the Timestamp Authority requests to explicitly use sha256 for message digests. [#1373](https://github.com/sigstore/sigstore-python/pull/1373) diff --git a/sigstore/models.py b/sigstore/models.py index ba245092c..a936f8816 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -243,6 +243,12 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ + inclusion_promise: rekor_v1.InclusionPromise | None = None + if self.inclusion_promise: + inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) + inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, root_hash=bytes.fromhex(self.inclusion_proof.root_hash), @@ -255,14 +261,10 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: log_index=self.log_index, log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)), integrated_time=self.integrated_time, + inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, canonicalized_body=base64.b64decode(self.body), ) - if self.inclusion_promise: - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) - tlog_entry.inclusion_promise = inclusion_promise # Fill in the appropriate kind body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( From 274dc2398891cd8ebc165d3570b15c82ab225b5a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 23:39:07 +0000 Subject: [PATCH 34/81] cleanup Signed-off-by: Ramon Petgrave --- test/assets/bundle.txt.ec_sig.sigstore | 60 --------------------- test/assets/bundle.txt.ed25119_sig.sigstore | 1 - 2 files changed, 61 deletions(-) delete mode 100644 test/assets/bundle.txt.ec_sig.sigstore delete mode 100644 test/assets/bundle.txt.ed25119_sig.sigstore diff --git a/test/assets/bundle.txt.ec_sig.sigstore b/test/assets/bundle.txt.ec_sig.sigstore deleted file mode 100644 index 38981e6c4..000000000 --- a/test/assets/bundle.txt.ec_sig.sigstore +++ /dev/null @@ -1,60 +0,0 @@ -{ - "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", - "verificationMaterial": { - "certificate": { - "rawBytes": "MIIDBTCCAougAwIBAgIUDHrJhCWCK2izQkrwdmDAUAvo7wwwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNTEyMjE0OTA2WhcNMjUwNTEyMjE1OTA2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1lXAecFnEQdQ7pV7IJZ4WrUkRiqOu6xxylT/uKQZ6KBoeeGWV7IKS3qyqNuvo1kp58QZyjf3pegGnr7NjBecJKOCAaowggGmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUzXnrLAOjQTu28nTJU9r4KWmq1bYwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlsZ5FowAAAQDAEgwRgIhAIruqc3Ze+CmmVcjQwceXa6kUBIC1yzMmau+FaKpRNCBAiEA4NCPhO226EI2wBGuDEJN2GzU6owyDNZ8zcPTweIW9xwwCgYIKoZIzj0EAwMDaAAwZQIxAOF3HAQutLxrw6032xGmCEQ6/DsRyc7xEpi8PPYkXWrcPgi3TBYk0Tve5Ee0s5ExxwIwO5NIwVT3bJYYerGVqKZ33baU0Zcqr2cOy+bqyIJ/hWsv4PI8y851Be1tRawQ69fo" - }, - "tlogEntries": [ - { - "logIndex": "42072024", - "logId": { - "keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY=" - }, - "kindVersion": { - "kind": "hashedrekord", - "version": "0.0.1" - }, - "integratedTime": "1747086546", - "inclusionPromise": { - "signedEntryTimestamp": "MEYCIQDb6doU+s78TofYvVMNFtYizrVUHd1nOwNb8NPtYKjmYAIhAKNuOHv3D6/yMuFTr32e3Cqn5u06GKucToL8STDVNexn" - }, - "inclusionProof": { - "logIndex": "10389612", - "rootHash": "bbsNKE9KZp+1xek3B1MvlibzHh4590E9scc+0jx98FE=", - "treeSize": "10389613", - "hashes": [ - "L8AoJyHC0vEBfjNNaBOUpkjNJVlGYHPzy8HaKDSODRg=", - "rCpL9WUSCfejPngtqr1D7hwXINkibaYs9TCIjFj6REA=", - "gjrAaC47wet08QIfKfdgZvcXMPT2cJyRo3zgAV7NgJU=", - "iq28HU8kGqSfL/W+a2JVTsr8hSnZyJjpUM9LxKp8ppg=", - "pj//jLKp4lWuUr1Ez6Gtxn1qIHupasn29h+eIpC9gfQ=", - "ilCTDd3VLkkwu96Md6bJXW5Ik9OqXMPgD/Fy9VDY7Ck=", - "BVBT6KGWJPrAI4T7Zzt529+ZxU+G5UR0UMqPDcKUYzk=", - "1CmXahercpSNPyH2ATDpK8S80Gim/GrKkm/8V5Ozue0=", - "ZyAV6AeFLhv6n2Ya599XWHwy3HCr/y0+RF0P6Smg8IU=", - "GoHRwlhYuJIYJdmRnHX5HWLr2ngxzHnAIIqBewovBi0=", - "OdoqbUqBYHhj2W1RLM8APkQOnM2K9gzGm1KPFmwIIeQ=" - ], - "checkpoint": { - "envelope": "rekor.sigstage.dev - 8202293616175992157\n10389613\nbbsNKE9KZp+1xek3B1MvlibzHh4590E9scc+0jx98FE=\n\n\u2014 rekor.sigstage.dev 0y8wozBEAiBd8UikHor4EsQSWuvZ4jH1z7O5yAPgbxut0qEN1oqACAIgSt4NvNaV3na15RQp3wjlwPoslDYFBPEP+A+RdbS/sRQ=\n" - } - }, - "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4MDJkZDYwZmY4ODMzMzgwMmYyNTg1ZTczMDQzYmQyMWMzNDEyODVlMTk5MmZlNWIzMTc1NWUxY2FkZWFlMzBlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJSHJFV0ZGNm1HRVRiZnFvRDh3TXNiR01CU0RIclM4eFV4WmZGSEVGcDF6T0FpRUE3QjFXRXZFNEYycFB3UlQ1MHN2UkNzL1hvdmltWitjdU1tK29nSHJwck1nPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUkNWRU5EUVc5MVowRjNTVUpCWjBsVlJFaHlTbWhEVjBOTE1tbDZVV3R5ZDJSdFJFRlZRWFp2TjNkM2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFZkMDVVUlhsTmFrVXdUMVJCTWxkb1kwNU5hbFYzVGxSRmVVMXFSVEZQVkVFeVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVV4YkZoQlpXTkdia1ZSWkZFM2NGWTNTVXBhTkZkeVZXdFNhWEZQZFRaNGVIbHNWQzhLZFV0UldqWkxRbTlsWlVkWFZqZEpTMU16Y1hseFRuVjJiekZyY0RVNFVWcDVhbVl6Y0dWblIyNXlOMDVxUW1WalNrdFBRMEZoYjNkblowZHRUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlY2V0c1eUNreEJUMnBSVkhVeU9HNVVTbFU1Y2pSTFYyMXhNV0paZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFkUldVUldVakJTUVZGSUwwSkZPSGRVV1VaTVlWYzFlbHBYVGpGamJWVjBXVEo0ZG1SWFVqQmlNMEYwWXpKb2FHTnRWbXRNV0ZaNldsaEtRUXBaTW5oMlpGZFNNR0l6UVhSalNFcDJXa014TVdONU1XeFpXRTR3VEcxc2FHSlROVzVqTWxaNVpHMXNhbHBYUm1wWk1qa3hZbTVSZFZreU9YUk5RMnRIQ2tOcGMwZEJVVkZDWnpjNGQwRlJSVVZITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVGeVFtZHZja0puUlVVS1FWbFBMMDFCUlVsQ1FqQk5SekpvTUdSSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd4TWJVNTJZbFJEUW1sM1dVdExkMWxDUWtGSVZ3cGxVVWxGUVdkU09VSkljMEZsVVVJelFVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5ETnFlWFF2TkdWTFkyOUJka3RsTms5QlFVRkNDbXh6V2pWR2IzZEJRVUZSUkVGRlozZFNaMGxvUVVseWRYRmpNMXBsSzBOdGJWWmphbEYzWTJWWVlUWnJWVUpKUXpGNWVrMXRZWFVyUm1GTGNGSk9RMElLUVdsRlFUUk9RMUJvVHpJeU5rVkpNbmRDUjNWRVJVcE9Na2Q2VlRadmQzbEVUbG80ZW1OUVZIZGxTVmM1ZUhkM1EyZFpTVXR2V2tsNmFqQkZRWGROUkFwaFFVRjNXbEZKZUVGUFJqTklRVkYxZEV4NGNuYzJNRE15ZUVkdFEwVlJOaTlFYzFKNVl6ZDRSWEJwT0ZCUVdXdFlWM0pqVUdkcE0xUkNXV3N3VkhabENqVkZaVEJ6TlVWNGVIZEpkMDgxVGtsM1ZsUXpZa3BaV1dWeVIxWnhTMW96TTJKaFZUQmFZM0Z5TW1OUGVTdGljWGxKU2k5b1YzTjJORkJKT0hrNE5URUtRbVV4ZEZKaGQxRTJPV1p2Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=" - } - ], - "timestampVerificationData": { - "rfc3161Timestamps": [ - { - "signedTimestamp": "MIIE6DADAgEAMIIE3wYJKoZIhvcNAQcCoIIE0DCCBMwCAQMxDTALBglghkgBZQMEAgEwgcIGCyqGSIb3DQEJEAEEoIGyBIGvMIGsAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgwYy4oaOlu9+0ir78A+UNZUwoNxqsdapT5si2GXVj32QCFG5dEFnm6TzNLIsY5ZDKQgO5rCpqGA8yMDI1MDUxMjIxNDkwN1owAwIBAQIJAJaPJ2RsSnggoDKkMDAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYaCCAhMwggIPMIIBlqADAgECAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkMB4XDTI1MDMyODA5MTQwNloXDTM1MDMyNjA4MTQwNlowLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATHW/kXcekP16Ae6SekEWVHPtAFEMm7hp5XO33MktFjSW+bHWUXtYEzZz0A3xkY9CyYOoeUk3ZH/v5HEuS+UvORzX0g7Hfy3uYYYRwHtqBQN0IX8rLdFMtIrRej/QCAdB2jajBoMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUqPxk9ijeLuY7c09UjFLE4ZzdU6UwHwYDVR0jBBgwFoAUOyBGWV61Mk1HMM5uY+5zdEfyBH0wFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDZwAwZAIwRK9VLoYa0Xff4nX1N/AQ1YleNG/iLT8dAXAtRKRfpN9XuDScbxWeo0cku8SkC06NAjBQPe7LBNeitA/UOBtXT2sX1h6f4ISqz+ISmJ4lY+y3bzRJI5nk1r53I9WT3/xIWToxggHaMIIB1gIBATBRMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAsGCWCGSAFlAwQCAaCB/DAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI1MDUxMjIxNDkwN1owLwYJKoZIhvcNAQkEMSIEIHp8LCphfuAEE4ZGsdzxBBGHt4PrblLYm67UiL2wyNaOMIGOBgsqhkiG9w0BCRACLzF/MH0wezB5BCAG9P/gR/6zWZm3M7DXoyNQHPwY5MAzZqhF13U250snRDBVMD2kOzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzAKBggqhkjOPQQDAgRmMGQCMDLfK3Z/hggfXLrSCWb+UNH0k0Cqn7tLPGBO8/crkhiiKldIdBqCTKWxJNBgtYS11wIwcQxraku4B3x09VTVbWy8e4DCSJPwr6Cve8u1qRNhK657rBUcWAifuytfI+OaAVO6" - } - ] - } - }, - "messageSignature": { - "messageDigest": { - "algorithm": "SHA2_256", - "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4=" - }, - "signature": "MEUCIHrEWFF6mGETbfqoD8wMsbGMBSDHrS8xUxZfFHEFp1zOAiEA7B1WEvE4F2pPwRT50svRCs/XovimZ+cuMm+ogHrprMg=" - } -} \ No newline at end of file diff --git a/test/assets/bundle.txt.ed25119_sig.sigstore b/test/assets/bundle.txt.ed25119_sig.sigstore deleted file mode 100644 index ba34a738e..000000000 --- a/test/assets/bundle.txt.ed25119_sig.sigstore +++ /dev/null @@ -1 +0,0 @@ -{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDBjCCAougAwIBAgIUBmcKcm44996DqZ8AajzkgCTqTSYwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNTEyMjE0OTIzWhcNMjUwNTEyMjE1OTIzWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjUe/mTP/Ov9Vzd0EvI1kiN5YBM2vbEjSjV+85x/gmHnpAbBRLSxAz45pz3tv/tScyZYpaYO8LpO5jCKHi/pFr6OCAaowggGmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUN3tQsmRLo4QgJLIfLqnFozJERGQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABlsZ5V2IAAAQDAEgwRgIhAMW1dIiyhdZlemxT9yAwj6Z6R9QAYPZnrfjFiG2LCUdNAiEAg7KBs3OBlXj7yUlcQNePeErCTYS7H4abEJzfWixTP1EwCgYIKoZIzj0EAwMDaQAwZgIxAJjuH61o/acPbkW7916t3/aO6lVGGjthESf3zL7MYRtL2WrkLC34p8r7EvMzZUpjJwIxANoZtIt4x/3LJIcNbYc/Y6GNY30YftaVnIJcMgkTiUfj6p01Y7xh+30pSGu+g8QaDw=="}, "tlogEntries": [{"logIndex": "11", "logId": {}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.2"}, "inclusionPromise": {}, "inclusionProof": {"logIndex": "11", "rootHash": "N2ZjNTQ3ZmFkODQwOTYyZTljZDM4ZWM2N2Y4NjdkMGY0Zjc4MjAzMTFkMmMyZjY2ZjYwNzY5MjE4ZTU4YWJkNw==", "treeSize": "12", "hashes": ["LFR8RRBHttds/zWI7G0SqCaQTKAY/LK+wqI1cwk+qZk=", "O8SpsKTjqBfWFr31XEgBjW0vga/o8tmxIwlqlku4PsE=", "N/6eReD9y/7siiZIbNJHXCSj9cnVo/Nb0jpUKanmb/0="], "checkpoint": {"envelope": "rekor-local\n12\nf8VH+thAli6c047Gf4Z9D094IDEdLC9m9gdpIY5Yq9c=\n\n\u2014 rekor-local 2AtEIIf92qDyUvoATNUZAPbhrjLgeHS1E0FE8dG/N8IZkrDz6qVtxw7rYxJ+iF+vLp9JKiJlqPxhIcg1DFXnzHJL1As=\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJoYXNoZWRSZWtvcmRWMF8wXzIiOnsiZGF0YSI6eyJhbGdvcml0aG0iOiJTSEEyXzI1NiIsImRpZ2VzdCI6ImdDM1dEL2lETTRBdkpZWG5NRU85SWNOQktGNFprdjViTVhWZUhLM3E0dzQ9In0sInNpZ25hdHVyZSI6eyJjb250ZW50IjoiTUVVQ0lFM0RCelhVVG0vSlFJZW5ndXkrZkNYN3h4L1lJV1lNRXBWY0tFcDRibjZaQWlFQThmV2wwektGUndtVkNGalVDWHNZZ0hXbURYb3Z2bC82SEhjL1cvMWEzSVk9IiwidmVyaWZpZXIiOnsia2V5RGV0YWlscyI6IlBLSVhfRUNEU0FfUDM4NF9TSEFfMjU2IiwicHVibGljS2V5Ijp7InJhd0J5dGVzIjoiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFalVlL21UUC9PdjlWemQwRXZJMWtpTjVZQk0ydmJFalNqVis4NXgvZ21IbnBBYkJSTFN4QXo0NXB6M3R2L3RTY3laWXBhWU84THBPNWpDS0hpL3BGcnc9PSJ9fX19fX0="}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE6TADAgEAMIIE4AYJKoZIhvcNAQcCoIIE0TCCBM0CAQMxDTALBglghkgBZQMEAgEwgcMGCyqGSIb3DQEJEAEEoIGzBIGwMIGtAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgy0XTMS1OieRcKYvekVJN32//loW7DgI9gMvDch2zz/4CFQCBTKTDxjPnL6T5UOUgztE9gnYX4BgPMjAyNTA1MTIyMTQ5MjVaMAMCAQECCQD60OumBC+I7qAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgggITMIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6MYIB2jCCAdYCAQEwUTA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzALBglghkgBZQMEAgGggfwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA1MTIyMTQ5MjVaMC8GCSqGSIb3DQEJBDEiBCBq2ndG4pDq7Rl1DG7+hkigOzXj1wE0igiEPhGWQKLekTCBjgYLKoZIhvcNAQkQAi8xfzB9MHsweQQgBvT/4Ef+s1mZtzOw16MjUBz8GOTAM2aoRdd1NudLJ0QwVTA9pDswOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwIEZjBkAjBBYnDvFA91/JDoz0q5YhOVpaUbQ5QS3opVZJVoluLEulIAI58OTFAk76JEf7HfD/oCMBOR++d9Wyg+gH5ttJuh7+WOoRtR6vxuJosEpF51SfvpHSam/bN90dc2Cl7z2hgpng=="}]}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4="}, "signature": "MEUCIE3DBzXUTm/JQIenguy+fCX7xx/YIWYMEpVcKEp4bn6ZAiEA8fWl0zKFRwmVCFjUCXsYgHWmDXovvl/6HHc/W/1a3IY="}} From 3f58569bbeb253579878feb6e2a276328ef5f41f Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 23:40:08 +0000 Subject: [PATCH 35/81] Reapply "betterproto: avoid assigning a var of type Optional[InclusionPromise] to TransparencyLogEntry.inclusion_promise" This reverts commit 43dc46895414bdd6a053c6a93a9d389c776c2a47. Signed-off-by: Ramon Petgrave --- CHANGELOG.md | 2 ++ sigstore/models.py | 12 +++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e77467913..7b741e7c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ All versions prior to 0.9.0 are untracked. ### Fixed +* Avoid pydantic's instantiation issues with `TransparencyLogEntry` when `InclusionPromise` is not present. + * TSA: Changed the Timestamp Authority requests to explicitly use sha256 for message digests. [#1373](https://github.com/sigstore/sigstore-python/pull/1373) diff --git a/sigstore/models.py b/sigstore/models.py index a936f8816..ba245092c 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -243,12 +243,6 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @private """ - inclusion_promise: rekor_v1.InclusionPromise | None = None - if self.inclusion_promise: - inclusion_promise = rekor_v1.InclusionPromise( - signed_entry_timestamp=base64.b64decode(self.inclusion_promise) - ) - inclusion_proof = rekor_v1.InclusionProof( log_index=self.inclusion_proof.log_index, root_hash=bytes.fromhex(self.inclusion_proof.root_hash), @@ -261,10 +255,14 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: log_index=self.log_index, log_id=common_v1.LogId(key_id=bytes.fromhex(self.log_id)), integrated_time=self.integrated_time, - inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, canonicalized_body=base64.b64decode(self.body), ) + if self.inclusion_promise: + inclusion_promise = rekor_v1.InclusionPromise( + signed_entry_timestamp=base64.b64decode(self.inclusion_promise) + ) + tlog_entry.inclusion_promise = inclusion_promise # Fill in the appropriate kind body_entry: ProposedEntry = TypeAdapter(ProposedEntry).validate_json( From d61a0044b3b763485fec0410dbb635ae703c5790 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 23:41:38 +0000 Subject: [PATCH 36/81] cleanup Signed-off-by: Ramon Petgrave --- test/unit/test_models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unit/test_models.py b/test/unit/test_models.py index bcae73e61..aa0116ede 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -56,8 +56,6 @@ def test_missing_inclusion_promise_round_trip(self, signing_bundle): assert entry._to_rekor() is not None assert (LogEntry._from_dict_rekor( entry._to_rekor().to_dict()) == entry) - assert False - def test_logentry_roundtrip(self, signing_bundle): _, bundle = signing_bundle("bundle.txt") From 76f77013834bb25a6db36489d6de4ead668ad3a4 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 12 May 2025 23:45:56 +0000 Subject: [PATCH 37/81] format Signed-off-by: Ramon Petgrave --- sigstore/models.py | 4 ++-- test/unit/test_models.py | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index ba245092c..bbcb1cc7d 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -223,10 +223,10 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: if tlog_entry.inclusion_promise: inclusion_promise = B64Str( base64.b64encode( - tlog_entry.inclusion_promise.signed_entry_timestamp).decode() + tlog_entry.inclusion_promise.signed_entry_timestamp + ).decode() ) - return LogEntry( uuid=None, body=B64Str(base64.b64encode(tlog_entry.canonicalized_body).decode()), diff --git a/test/unit/test_models.py b/test/unit/test_models.py index aa0116ede..7a33eea5c 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -43,19 +43,18 @@ def test_missing_inclusion_proof(self): ) def test_missing_inclusion_promise_round_trip(self, signing_bundle): - ''' + """ Ensures that LogEntry._to_rekor() succeeds even without an inclusion_promise. - ''' + """ bundle: Bundle _, bundle = signing_bundle("bundle.txt") _dict = bundle.log_entry._to_rekor().to_dict() print(_dict) - del _dict['inclusionPromise'] + del _dict["inclusionPromise"] entry = LogEntry._from_dict_rekor(_dict) assert entry.inclusion_promise is None assert entry._to_rekor() is not None - assert (LogEntry._from_dict_rekor( - entry._to_rekor().to_dict()) == entry) + assert LogEntry._from_dict_rekor(entry._to_rekor().to_dict()) == entry def test_logentry_roundtrip(self, signing_bundle): _, bundle = signing_bundle("bundle.txt") From 1110d20f8c027f59778c652d9a22794630580c5a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 13 May 2025 18:34:56 +0000 Subject: [PATCH 38/81] add tests Signed-off-by: Ramon Petgrave --- .../trustedroot.v1.local_tlog_ed25519json | 114 ++++++++++++++++++ test/unit/internal/test_trust.py | 10 +- 2 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519json diff --git a/test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519json b/test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519json new file mode 100644 index 000000000..4e79be8f2 --- /dev/null +++ b/test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519json @@ -0,0 +1,114 @@ +{ + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "http://localhost:3003", + "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" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "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": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "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": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] +} diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 4042bf9c1..f2ac57beb 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -65,8 +65,9 @@ def test_good(self, asset): class TestTrustedRoot: - def test_good(self, asset): - path = asset("trusted_root/trustedroot.v1.json") + @pytest.mark.parametrize("file", ["trusted_root/trustedroot.v1.json", "trusted_root/trustedroot.v1.local_tlog_ed25519json"]) + def test_good(self, asset, file): + path = asset(file) root = TrustedRoot.from_file(path) assert ( @@ -76,6 +77,11 @@ def test_good(self, asset): assert len(root._inner.certificate_authorities) == 2 assert len(root._inner.ctlogs) == 2 assert len(root._inner.timestamp_authorities) == 1 + assert root.rekor_keyring(KeyringPurpose.VERIFY) is not None + assert root.ct_keyring(KeyringPurpose.VERIFY) is not None + assert root.get_fulcio_certs() is not None + assert root.get_timestamp_authorities() is not None + def test_bad_media_type(self, asset): path = asset("trusted_root/trustedroot.badtype.json") From ea6d65953dc0bf75e1bcb9498bc36309461d8f50 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 13 May 2025 18:37:23 +0000 Subject: [PATCH 39/81] dosctring for test Signed-off-by: Ramon Petgrave --- test/unit/internal/test_trust.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index f2ac57beb..5121c6405 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -67,6 +67,9 @@ def test_good(self, asset): class TestTrustedRoot: @pytest.mark.parametrize("file", ["trusted_root/trustedroot.v1.json", "trusted_root/trustedroot.v1.local_tlog_ed25519json"]) def test_good(self, asset, file): + """ + Ensures that the trusted_roots are well-formed and that the embedded keys are supported. + """ path = asset(file) root = TrustedRoot.from_file(path) From 676294aa8d108e7737e5a45fb39be9d2acfe4df3 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 13 May 2025 18:38:42 +0000 Subject: [PATCH 40/81] format Signed-off-by: Ramon Petgrave --- test/unit/internal/test_trust.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 5121c6405..6a570d95b 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -65,7 +65,13 @@ def test_good(self, asset): class TestTrustedRoot: - @pytest.mark.parametrize("file", ["trusted_root/trustedroot.v1.json", "trusted_root/trustedroot.v1.local_tlog_ed25519json"]) + @pytest.mark.parametrize( + "file", + [ + "trusted_root/trustedroot.v1.json", + "trusted_root/trustedroot.v1.local_tlog_ed25519json", + ], + ) def test_good(self, asset, file): """ Ensures that the trusted_roots are well-formed and that the embedded keys are supported. @@ -85,7 +91,6 @@ def test_good(self, asset, file): assert root.get_fulcio_certs() is not None assert root.get_timestamp_authorities() is not None - def test_bad_media_type(self, asset): path = asset("trusted_root/trustedroot.badtype.json") From ac6340607d25ffb2f7b3367bdb772b527869e2be Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 13 May 2025 18:41:48 +0000 Subject: [PATCH 41/81] fix filename Signed-off-by: Ramon Petgrave --- ...9json => trustedroot.v1.local_tlog_ed25519_rekor-tiles.json} | 0 test/unit/internal/test_trust.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename test/assets/trusted_root/{trustedroot.v1.local_tlog_ed25519json => trustedroot.v1.local_tlog_ed25519_rekor-tiles.json} (100%) diff --git a/test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519json b/test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519_rekor-tiles.json similarity index 100% rename from test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519json rename to test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519_rekor-tiles.json diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 6a570d95b..a2bf3c6a3 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -69,7 +69,7 @@ class TestTrustedRoot: "file", [ "trusted_root/trustedroot.v1.json", - "trusted_root/trustedroot.v1.local_tlog_ed25519json", + "trusted_root/trustedroot.v1.local_tlog_ed25519_rekor-tiles.json", ], ) def test_good(self, asset, file): From 8e5036b21c2b3ed8bf154e285d2c1e1323eac191 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Tue, 13 May 2025 20:48:10 +0000 Subject: [PATCH 42/81] undo sighash comment Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/checkpoint.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sigstore/_internal/rekor/checkpoint.py b/sigstore/_internal/rekor/checkpoint.py index ce9e030eb..c630d24fa 100644 --- a/sigstore/_internal/rekor/checkpoint.py +++ b/sigstore/_internal/rekor/checkpoint.py @@ -172,10 +172,10 @@ def verify(self, rekor_keyring: RekorKeyring, key_id: KeyID) -> None: note = str.encode(self.note) for sig in self.signatures: - # if sig.sig_hash != key_id[:4]: - # raise VerificationError( - # "checkpoint: sig_hash hint does not match expected key_id" - # ) + if sig.sig_hash != key_id[:4]: + raise VerificationError( + "checkpoint: sig_hash hint does not match expected key_id" + ) try: rekor_keyring.verify( From 7e3ef0a1599e49139bce26b3f2a3781d02ffe761 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 14 May 2025 14:04:42 +0000 Subject: [PATCH 43/81] also delete integrated_time Signed-off-by: Ramon Petgrave --- test/unit/test_models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/test_models.py b/test/unit/test_models.py index 7a33eea5c..c1930a1ac 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -42,15 +42,16 @@ def test_missing_inclusion_proof(self): inclusion_promise=None, ) - def test_missing_inclusion_promise_round_trip(self, signing_bundle): + def test_missing_inclusion_promise_and_integrated_time_round_trip(self, signing_bundle): """ - Ensures that LogEntry._to_rekor() succeeds even without an inclusion_promise. + Ensures that LogEntry._to_rekor() succeeds even without an inclusion_promise and integrated_time. """ bundle: Bundle _, bundle = signing_bundle("bundle.txt") _dict = bundle.log_entry._to_rekor().to_dict() print(_dict) del _dict["inclusionPromise"] + del _dict["integratedTime"] entry = LogEntry._from_dict_rekor(_dict) assert entry.inclusion_promise is None assert entry._to_rekor() is not None From 41a5a10cacaa566ee2a421c1ab032966e584f94f Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 14 May 2025 14:14:23 +0000 Subject: [PATCH 44/81] lint Signed-off-by: Ramon Petgrave --- test/unit/test_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/test_models.py b/test/unit/test_models.py index c1930a1ac..8786c2cfa 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -42,7 +42,9 @@ def test_missing_inclusion_proof(self): inclusion_promise=None, ) - def test_missing_inclusion_promise_and_integrated_time_round_trip(self, signing_bundle): + def test_missing_inclusion_promise_and_integrated_time_round_trip( + self, signing_bundle + ): """ Ensures that LogEntry._to_rekor() succeeds even without an inclusion_promise and integrated_time. """ From 457ba8cbe8b5895912a583beacbffd91e6f2614a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 14 May 2025 14:17:16 +0000 Subject: [PATCH 45/81] private .__kind_version Signed-off-by: Ramon Petgrave --- sigstore/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index b43264b21..add99c25b 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -171,7 +171,7 @@ class LogEntry: log entry. """ - kind_version: KindVersion + _kind_version: KindVersion """ The kind and version of the log entry. """ @@ -205,7 +205,7 @@ def _from_response(cls, dict_: dict[str, Any]) -> LogEntry: entry["verification"]["inclusionProof"] ), inclusion_promise=entry["verification"]["signedEntryTimestamp"], - kind_version=KindVersion( + _kind_version=KindVersion( kind=body_entry.kind, version=body_entry.api_version ), ) @@ -244,7 +244,7 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: tlog_entry.inclusion_promise.signed_entry_timestamp ).decode() ), - kind_version=tlog_entry.kind_version, + _kind_version=tlog_entry.kind_version, ) def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: @@ -273,7 +273,7 @@ def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: integrated_time=self.integrated_time, inclusion_promise=inclusion_promise, # type: ignore[arg-type] inclusion_proof=inclusion_proof, - kind_version=self.kind_version, + kind_version=self._kind_version, canonicalized_body=base64.b64decode(self.body), ) From f47ef161fa52b4dcd4c281fc6e5df8c866ad5bb0 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 14 May 2025 16:16:06 +0000 Subject: [PATCH 46/81] test with missing inclusion promise Signed-off-by: Ramon Petgrave --- test/unit/verify/test_verifier.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 123bd00b5..5ab336c97 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -24,7 +24,7 @@ from sigstore._internal.trust import CertificateAuthority from sigstore.dsse import StatementBuilder, Subject from sigstore.errors import VerificationError -from sigstore.models import Bundle +from sigstore.models import Bundle, LogEntry from sigstore.verify import policy from sigstore.verify.verifier import Verifier @@ -222,6 +222,35 @@ def test_verifier_no_validity_end(self, verifier, asset, null_policy): null_policy, ) + @pytest.mark.parametrize( + "fields_to_delete", + ( + [], + ["inclusionPromise"], + # integratedTime is required to verify the inclusionPromise. + pytest.param(["integratedTime"], marks=pytest.mark.xfail), + ["inclusionPromise", "integratedTime"], + ), + ) + def test_vierifier_verify_no_inclusion_promise_and_integrated_time( + self, verifier, asset, null_policy, fields_to_delete + ): + """ + Ensure that we can still verify a Bundle with a rfc3161 timestamp if the SET can't be verified or isn't present. + There is one exception: When inclusionPromise is present, but integratedTime is not, then we expect a failure + because the integratedTime is required to verify the inclusionPromise. + """ + bundle = Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()) + _dict = bundle.log_entry._to_rekor().to_dict() + for field in fields_to_delete: + del _dict[field] + bundle._log_entry = LogEntry._from_dict_rekor(_dict) + verifier.verify_artifact( + asset("tsa/bundle.txt").read_bytes(), + bundle, + null_policy, + ) + def test_verifier_without_timestamp( self, verifier, asset, null_policy, monkeypatch ): From 65e9a1a824354074b29ce3872aaab1c19638b96b Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Wed, 14 May 2025 20:29:03 +0000 Subject: [PATCH 47/81] add demo v2 types Signed-off-by: Ramon Petgrave --- sigstore/rekorv2.py | 502 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 502 insertions(+) create mode 100644 sigstore/rekorv2.py diff --git a/sigstore/rekorv2.py b/sigstore/rekorv2.py new file mode 100644 index 000000000..4e76bacd5 --- /dev/null +++ b/sigstore/rekorv2.py @@ -0,0 +1,502 @@ +# TODO: copypasted from https://github.com/trailofbits/sigstore-rekor-types/pull/168 + +# generated by datamodel-codegen: +# filename: rekor_service.swagger.json +# version: 0.30.1 + +from __future__ import annotations + +from enum import Enum +from typing import Any, List, Optional + +from pydantic import BaseModel, ConfigDict, Field, RootModel, StrictInt, StrictStr + + +class Model(RootModel[Any]): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Any + + +class IointotoSignature(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + sig: Optional[str] = Field( + default=None, + description="Signature itself. (In JSON, this is encoded as base64.)\nREQUIRED.", + ) + keyid: Optional[StrictStr] = Field( + default=None, + description="*Unauthenticated* hint identifying which public key was used.\nOPTIONAL.", + ) + + +class ProtobufAny(BaseModel): + """`Any` contains an arbitrary serialized protocol buffer message along with a + URL that describes the type of the serialized message. + + Protobuf library provides support to pack/unpack Any values in the form + of utility functions or additional generated methods of the Any type. + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + 'type.googleapis.com/full.type.name' as the type URL and the unpack + methods only use the fully qualified type name after the last '/' + in the type URL, for example "foo.bar.com/x/y.z" will yield type + name "y.z". + + JSON + ==== + The JSON representation of an `Any` value uses the regular + representation of the deserialized, embedded message, with an + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + representation, that representation will be embedded adding a field + `value` which holds the custom JSON in addition to the `@type` + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + """ + + model_config = ConfigDict( + populate_by_name=True, + ) + field_type: Optional[StrictStr] = Field( + default=None, + alias="@type", + description='A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none "/" character. The last segment of the URL\'s path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading "." is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics.', + ) + + +class Rekorv2PublicKey(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + raw_bytes: str = Field(..., alias="rawBytes", title="DER-encoded public key") + + +class RpcStatus(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + code: Optional[StrictInt] = None + message: Optional[StrictStr] = None + details: Optional[List[ProtobufAny]] = None + + +class V1Checkpoint(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + envelope: StrictStr + + +class V1InclusionPromise(BaseModel): + """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. + """ + + model_config = ConfigDict( + populate_by_name=True, + ) + signed_entry_timestamp: str = Field(..., alias="signedEntryTimestamp") + + +class V1InclusionProof(BaseModel): + """InclusionProof is the proof returned from the transparency log. Can + be used for offline or online verification against the log. + """ + + model_config = ConfigDict( + populate_by_name=True, + ) + log_index: StrictStr = Field( + ..., + alias="logIndex", + description="The index of the entry in the tree it was written to.", + ) + root_hash: str = Field( + ..., + alias="rootHash", + description="The hash digest stored at the root of the merkle tree at the time\nthe proof was generated.", + ) + tree_size: StrictStr = Field( + ..., + alias="treeSize", + description="The size of the merkle tree at the time the proof was generated.", + ) + hashes: List[str] = Field( + ..., + description="A list of hashes required to compute the inclusion proof, sorted\nin order from leaf to root.\nNote that leaf and root hashes are not included.\nThe root hash is available separately in this message, and the\nleaf hash should be calculated by the client.", + ) + checkpoint: V1Checkpoint = Field( + ..., + description="Signature of the tree head, as of the time of this proof was\ngenerated. See above info on 'Checkpoint' for more details.", + ) + + +class V1KindVersion(BaseModel): + """KindVersion contains the entry's kind and api version.""" + + model_config = ConfigDict( + populate_by_name=True, + ) + kind: StrictStr = Field( + ..., + title="Kind is the type of entry being stored in the log.\nSee here for a list: https://github.com/sigstore/rekor/tree/main/pkg/types", + ) + version: StrictStr = Field(..., description="The specific api version of the type.") + + +class V1LogId(BaseModel): + """LogId captures the identity of a transparency log.""" + + model_config = ConfigDict( + populate_by_name=True, + ) + key_id: str = Field( + ..., + alias="keyId", + description="The unique identity of the log, represented by its public key.", + ) + + +class V1PublicKeyDetails(str, 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). + + - PKCS1_RSA_PKCS1V5: RSA + + See RFC8017 + - PKCS1_RSA_PSS: See RFC8017 + - PKIX_RSA_PKCS1V15_2048_SHA256: RSA public key in PKIX format, PKCS#1v1.5 signature + - PKIX_RSA_PSS_2048_SHA256: RSA public key in PKIX format, RSASSA-PSS signature + + See RFC4055 + - PKIX_ECDSA_P256_HMAC_SHA_256: ECDSA + + See RFC6979 + - PKIX_ECDSA_P256_SHA_256: See NIST FIPS 186-4 + - PKIX_ED25519: Ed 25519 + + See RFC8032 + - LMS_SHA256: 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. + """ + + public_key_details_unspecified = "PUBLIC_KEY_DETAILS_UNSPECIFIED" + pkcs1_rsa_pkcs1_v5 = "PKCS1_RSA_PKCS1V5" + pkcs1_rsa_pss = "PKCS1_RSA_PSS" + pkix_rsa_pkcs1_v5 = "PKIX_RSA_PKCS1V5" + pkix_rsa_pss = "PKIX_RSA_PSS" + pkix_rsa_pkcs1_v15_2048_sha256 = "PKIX_RSA_PKCS1V15_2048_SHA256" + pkix_rsa_pkcs1_v15_3072_sha256 = "PKIX_RSA_PKCS1V15_3072_SHA256" + pkix_rsa_pkcs1_v15_4096_sha256 = "PKIX_RSA_PKCS1V15_4096_SHA256" + pkix_rsa_pss_2048_sha256 = "PKIX_RSA_PSS_2048_SHA256" + pkix_rsa_pss_3072_sha256 = "PKIX_RSA_PSS_3072_SHA256" + pkix_rsa_pss_4096_sha256 = "PKIX_RSA_PSS_4096_SHA256" + pkix_ecdsa_p256_hmac_sha_256 = "PKIX_ECDSA_P256_HMAC_SHA_256" + pkix_ecdsa_p256_sha_256 = "PKIX_ECDSA_P256_SHA_256" + pkix_ecdsa_p384_sha_384 = "PKIX_ECDSA_P384_SHA_384" + pkix_ecdsa_p521_sha_512 = "PKIX_ECDSA_P521_SHA_512" + pkix_ed25519 = "PKIX_ED25519" + pkix_ed25519_ph = "PKIX_ED25519_PH" + lms_sha256 = "LMS_SHA256" + lmots_sha256 = "LMOTS_SHA256" + + +class V1TransparencyLogEntry(BaseModel): + """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. + """ + + model_config = ConfigDict( + populate_by_name=True, + ) + log_index: StrictStr = Field( + ..., + alias="logIndex", + description="The global index of the entry, used when querying the log by index.", + ) + log_id: V1LogId = Field(..., alias="logId", description="The unique identifier of the log.") + kind_version: V1KindVersion = Field( + ..., + alias="kindVersion", + description="The kind (type) and version of the object associated with this\nentry. These values are required to construct the entry during\nverification.", + ) + integrated_time: StrictStr = Field( + ..., + alias="integratedTime", + description="The UNIX timestamp from the log when the entry was persisted.\nThe integration time MUST NOT be trusted if inclusion_promise\nis omitted.", + ) + inclusion_promise: Optional[V1InclusionPromise] = Field( + default=None, + alias="inclusionPromise", + description="The inclusion promise/signed entry timestamp from the log.\nRequired for v0.1 bundles, and MUST be verified.\nOptional for >= v0.2 bundles if another suitable source of\ntime is present (such as another source of signed time,\nor the current system time for long-lived certificates).\nMUST be verified if no other suitable source of time is present,\nand SHOULD be verified otherwise.", + ) + inclusion_proof: V1InclusionProof = Field( + ..., + alias="inclusionProof", + description="The inclusion proof can be used for offline or online verification\nthat the entry was appended to the log, and that the log has not been\naltered.", + ) + canonicalized_body: Optional[str] = Field( + default=None, + alias="canonicalizedBody", + description='Optional. The canonicalized transparency log entry, used to\nreconstruct the Signed Entry Timestamp (SET) during verification.\nThe contents of this field are the same as the `body` field in\na Rekor response, meaning that it does **not** include the "full"\ncanonicalized form (of log index, ID, etc.) which are\nexposed as separate fields. The verifier is responsible for\ncombining the `canonicalized_body`, `log_index`, `log_id`,\nand `integrated_time` into the payload that the SET\'s signature\nis generated over.\nThis field is intended to be used in cases where the SET cannot be\nproduced determinisitically (e.g. inconsistent JSON field ordering,\ndiffering whitespace, etc).\n\nIf set, clients MUST verify that the signature referenced in the\n`canonicalized_body` matches the signature provided in the\n`Bundle.content`.\nIf not set, clients are responsible for constructing an equivalent\npayload from other sources to verify the signature.', + ) + + +class V1X509Certificate(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + raw_bytes: str = Field(..., alias="rawBytes", description="DER-encoded X.509 certificate.") + + +class V2Verifier(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + public_key: Rekorv2PublicKey = Field( + ..., + alias="publicKey", + title="DER-encoded public key. Encoding method is specified by the key_details attribute", + ) + x509_certificate: V1X509Certificate = Field( + ..., + alias="x509Certificate", + title="DER-encoded certificate", + ) + key_details: V1PublicKeyDetails = Field( + ..., + alias="keyDetails", + title="Key encoding and signature algorithm to use for this key", + ) + + +class ApiHttpBody(BaseModel): + """Message that represents an arbitrary HTTP body. It should only be used for + payload formats that can't be represented as JSON, such as raw binary or + an HTML page. + + + This message can be used both in streaming and non-streaming API methods in + the request as well as the response. + + It can be used as a top-level request field, which is convenient if one + wants to extract parameters from either the URL or HTTP template into the + request fields and also want access to the raw HTTP body. + + Example: + message GetResourceRequest { + // A unique request id. + string request_id = 1; + + // The raw HTTP body is bound to this field. + google.api.HttpBody http_body = 2; + + } + + service ResourceService { + rpc GetResource(GetResourceRequest) + returns (google.api.HttpBody); + rpc UpdateResource(google.api.HttpBody) + returns (google.protobuf.Empty); + + } + + Example with streaming methods: + + service CaldavService { + rpc GetCalendar(stream google.api.HttpBody) + returns (stream google.api.HttpBody); + rpc UpdateCalendar(stream google.api.HttpBody) + returns (stream google.api.HttpBody); + + } + + Use of this type only changes how the request and response bodies are + handled, all other features will continue to work unchanged. + + """ + + model_config = ConfigDict( + populate_by_name=True, + ) + content_type: Optional[StrictStr] = Field( + default=None, + alias="contentType", + description="The HTTP Content-Type header value specifying the content type of the body.", + ) + data: Optional[str] = Field( + default=None, + description="The HTTP request/response body as raw binary.", + ) + extensions: Optional[List[ProtobufAny]] = Field( + default=None, + description="Application specific response metadata. Must be set in the first response\nfor streaming APIs.", + ) + + +class IntotoEnvelope(BaseModel): + """An authenticated message of arbitrary type.""" + + model_config = ConfigDict( + populate_by_name=True, + ) + payload: Optional[str] = Field( + default=None, + description="Message to be signed. (In JSON, this is encoded as base64.)\nREQUIRED.", + ) + payload_type: Optional[StrictStr] = Field( + default=None, + alias="payloadType", + description="String unambiguously identifying how to interpret payload.\nREQUIRED.", + ) + signatures: Optional[List[IointotoSignature]] = Field( + default=None, + description='Signature over:\n PAE(type, payload)\nWhere PAE is defined as:\nPAE(type, payload) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(payload) + SP + payload\n+ = concatenation\nSP = ASCII space [0x20]\n"DSSEv1" = ASCII [0x44, 0x53, 0x53, 0x45, 0x76, 0x31]\nLEN(s) = ASCII decimal encoding of the byte length of s, with no leading zeros\nREQUIRED (length >= 1).', + ) + + +class Rekorv2Signature(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + content: str + verifier: V2Verifier + + +class V2DSSERequestV002(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + envelope: IntotoEnvelope = Field(..., title="A DSSE envelope") + verifiers: List[V2Verifier] = Field( + ..., + title="All necessary verification material to verify all signatures embedded in the envelope", + ) + + +class V2HashedRekordRequestV002(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + digest: str = Field(..., title="The hashed data") + signature: Rekorv2Signature = Field( + ..., + title="A single signature over the hashed data with the verifier needed to validate it", + ) + + +class V2CreateEntryRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + hashed_rekord_request_v0_0_2: V2HashedRekordRequestV002 = Field( + ..., + alias="hashedRekordRequestV0_0_2", + ) + dsse_request_v0_0_2: V2DSSERequestV002 = Field(..., alias="dsseRequestV0_0_2") From a1def044bc3bc1036889f4e69980b6953819df4a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 15 May 2025 16:05:57 +0000 Subject: [PATCH 48/81] some types Signed-off-by: Ramon Petgrave --- sigstore/rekorv2.py | 38 +++++++++++++++++++++++ sigstore/verify/verifier.py | 51 +++++++++++++++++++++++-------- test/unit/verify/test_verifier.py | 10 +++--- 3 files changed, 83 insertions(+), 16 deletions(-) diff --git a/sigstore/rekorv2.py b/sigstore/rekorv2.py index 4e76bacd5..e74b7fa57 100644 --- a/sigstore/rekorv2.py +++ b/sigstore/rekorv2.py @@ -5,6 +5,7 @@ # version: 0.30.1 from __future__ import annotations +from sigstore_protobuf_specs.dev.sigstore.common import v1 from enum import Enum from typing import Any, List, Optional @@ -480,6 +481,8 @@ class V2DSSERequestV002(BaseModel): ) +# Custom Code + class V2HashedRekordRequestV002(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -500,3 +503,38 @@ class V2CreateEntryRequest(BaseModel): alias="hashedRekordRequestV0_0_2", ) dsse_request_v0_0_2: V2DSSERequestV002 = Field(..., alias="dsseRequestV0_0_2") + + +class Entry(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + kind: StrictStr = Field() + api_version: StrictStr = Field(..., alias="apiVersion") + spec: SpecRoot + + +class SpecRoot(RootModel): + root: HashedRekordLogEntryV002Root | DSSELogEntryV002Root + + +class HashedRekordLogEntryV002Root(BaseModel): + hashed_rekord_v0_0_2: HashedRekordLogEntryV002 = Field( + ..., + alias="hashedRekordV0_0_2", + ) + + +class HashedRekordLogEntryV002(BaseModel): + data: v1.HashOutput + signature: Rekorv2Signature + pass + + +class DSSELogEntryV002Root(BaseModel): + dsse_v0_0_2: DSSELogEntryV002 = Field( + ..., alias="dsseV0_0_2") + + +class DSSELogEntryV002(BaseModel): + pass diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index c61687953..defb858fe 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -19,6 +19,7 @@ from __future__ import annotations import base64 +import json import logging from datetime import datetime, timezone from typing import cast @@ -505,15 +506,41 @@ 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 expected_body != actual_body: - # raise VerificationError( - # "transparency log entry is inconsistent with other materials" - # ) + print((base64.b64decode(entry.body))) + if entry.kind_version.version == '0.0.2': + entry_body = json.loads(base64.b64decode(entry.body)) + from sigstore import rekorv2 + parsed_body = rekorv2.Entry.model_validate( + entry_body) + print(parsed_body) + # restructured_body = { + # **entry_body, + # "spec": { + # **entry_body["spec"]["hashedRekordV0_0_2"], + # "signature": { + # "content": entry_body["spec"]["hashedRekordV0_0_2"]["signature"]["content"], + # "verifier": + # } + # } + # } + # actual_body = rekor_types.Hashedrekord.model_validate( + # restructured_body + # ) + # from pprint import pprint + # pprint(restructured_body) + # pprint(actual_body) + # pass + 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/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 5ab336c97..85fb3eb64 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -14,6 +14,7 @@ import hashlib +import json import logging from datetime import datetime, timezone @@ -240,11 +241,12 @@ def test_vierifier_verify_no_inclusion_promise_and_integrated_time( There is one exception: When inclusionPromise is present, but integratedTime is not, then we expect a failure because the integratedTime is required to verify the inclusionPromise. """ - bundle = Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()) - _dict = bundle.log_entry._to_rekor().to_dict() + bundle_dict = json.loads(asset("tsa/bundle.txt.sigstore").read_bytes()) + (entry_dict,) = bundle_dict["verificationMaterial"]["tlogEntries"] for field in fields_to_delete: - del _dict[field] - bundle._log_entry = LogEntry._from_dict_rekor(_dict) + del entry_dict[field] + # Bundle.from_json() also validates the bundle's layout. + bundle = Bundle.from_json(json.dumps(bundle_dict)) verifier.verify_artifact( asset("tsa/bundle.txt").read_bytes(), bundle, From c0c44dba805593be07cfd37cb0b26a87edd84db7 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 15 May 2025 16:09:14 +0000 Subject: [PATCH 49/81] edit the fields before instantiating the bundle Signed-off-by: Ramon Petgrave --- test/unit/verify/test_verifier.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 5ab336c97..057b35e50 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -14,6 +14,7 @@ import hashlib +import json import logging from datetime import datetime, timezone @@ -24,7 +25,7 @@ from sigstore._internal.trust import CertificateAuthority from sigstore.dsse import StatementBuilder, Subject from sigstore.errors import VerificationError -from sigstore.models import Bundle, LogEntry +from sigstore.models import Bundle from sigstore.verify import policy from sigstore.verify.verifier import Verifier @@ -240,11 +241,12 @@ def test_vierifier_verify_no_inclusion_promise_and_integrated_time( There is one exception: When inclusionPromise is present, but integratedTime is not, then we expect a failure because the integratedTime is required to verify the inclusionPromise. """ - bundle = Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()) - _dict = bundle.log_entry._to_rekor().to_dict() + bundle_dict = json.loads(asset("tsa/bundle.txt.sigstore").read_bytes()) + (entry_dict,) = bundle_dict["verificationMaterial"]["tlogEntries"] for field in fields_to_delete: - del _dict[field] - bundle._log_entry = LogEntry._from_dict_rekor(_dict) + del entry_dict[field] + # Bundle.from_json() also validates the bundle's layout. + bundle = Bundle.from_json(json.dumps(bundle_dict)) verifier.verify_artifact( asset("tsa/bundle.txt").read_bytes(), bundle, From 618be8af75ba26dab7208470f11e7bd07915e40e Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 15 May 2025 19:21:34 +0000 Subject: [PATCH 50/81] gen from rekor-tiles Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor_tiles/__init__.py | 0 .../_internal/rekor_tiles/dev/__init__.py | 0 .../rekor_tiles/dev/sigstore/__init__.py | 0 .../dev/sigstore/common/__init__.py | 0 .../dev/sigstore/common/v1/__init__.py | 285 ++++++++++ .../dev/sigstore/rekor/__init__.py | 0 .../dev/sigstore/rekor/v1/__init__.py | 170 ++++++ .../dev/sigstore/rekor/v2/__init__.py | 328 +++++++++++ .../_internal/rekor_tiles/google/__init__.py | 0 .../rekor_tiles/google/api/__init__.py | 527 ++++++++++++++++++ sigstore/_internal/rekor_tiles/io/__init__.py | 0 .../rekor_tiles/io/intoto/__init__.py | 54 ++ 12 files changed, 1364 insertions(+) create mode 100644 sigstore/_internal/rekor_tiles/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/sigstore/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/sigstore/common/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/sigstore/rekor/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/google/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/google/api/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/io/__init__.py create mode 100644 sigstore/_internal/rekor_tiles/io/intoto/__init__.py 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..8703b78ab --- /dev/null +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -0,0 +1,285 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: sigstore_common.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from datetime import datetime +from typing import ( + List, + Optional, +) + +import betterproto + + +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 + + +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 + + +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. + """ + + +@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: str = betterproto.string_field(2, group="identity") + """ + A regular expression describing the expected value for + the SAN. + """ + + value: str = betterproto.string_field(3, group="identity") + """The exact value to match against.""" + + +@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) 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..890d3f799 --- /dev/null +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -0,0 +1,170 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: sigstore_rekor.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import List + +import betterproto + +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. + """ 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..d4fadacbb --- /dev/null +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -0,0 +1,328 @@ +# 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 dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Dict, + List, + Optional, +) + +import betterproto +import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf +import grpclib +from betterproto.grpc.grpclib_server import ServiceBase + +from .....google import api as ____google_api__ +from .....io import intoto as ____io_intoto__ +from ...common import v1 as __common_v1__ +from .. import v1 as _v1__ + + +if TYPE_CHECKING: + import grpclib.server + from betterproto.grpc.grpclib_client import MetadataLike + from grpclib.metadata import Deadline + + +@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: "PublicKey" = betterproto.message_field(1, group="verifier") + """ + DER-encoded public key. Encoding method is specified by the key_details attribute + """ + + x509_certificate: "__common_v1__.X509Certificate" = betterproto.message_field( + 2, group="verifier" + ) + """DER-encoded certificate""" + + key_details: "__common_v1__.PublicKeyDetails" = betterproto.enum_field(3) + """Key encoding and signature algorithm to use for this key""" + + +@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: "HashedRekordLogEntryV002" = betterproto.message_field( + 1, group="spec" + ) + dsse_v0_0_2: "DsseLogEntryV002" = betterproto.message_field(2, group="spec") + + +@dataclass(eq=False, repr=False) +class CreateEntryRequest(betterproto.Message): + """Create a new HashedRekord or DSSE""" + + hashed_rekord_request_v0_0_2: "HashedRekordRequestV002" = betterproto.message_field( + 1, group="spec" + ) + dsse_request_v0_0_2: "DsseRequestV002" = betterproto.message_field(2, group="spec") + + +@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 + """ + + +class RekorStub(betterproto.ServiceStub): + async def create_entry( + self, + create_entry_request: "CreateEntryRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "_v1__.TransparencyLogEntry": + return await self._unary_unary( + "/dev.sigstore.rekor.v2.Rekor/CreateEntry", + create_entry_request, + _v1__.TransparencyLogEntry, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def get_tile( + self, + tile_request: "TileRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "____google_api__.HttpBody": + return await self._unary_unary( + "/dev.sigstore.rekor.v2.Rekor/GetTile", + tile_request, + ____google_api__.HttpBody, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def get_entry_bundle( + self, + entry_bundle_request: "EntryBundleRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "____google_api__.HttpBody": + return await self._unary_unary( + "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle", + entry_bundle_request, + ____google_api__.HttpBody, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def get_checkpoint( + self, + betterproto_lib_google_protobuf_empty: "betterproto_lib_google_protobuf.Empty", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "____google_api__.HttpBody": + return await self._unary_unary( + "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint", + betterproto_lib_google_protobuf_empty, + ____google_api__.HttpBody, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + +class RekorBase(ServiceBase): + + async def create_entry( + self, create_entry_request: "CreateEntryRequest" + ) -> "_v1__.TransparencyLogEntry": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def get_tile( + self, tile_request: "TileRequest" + ) -> "____google_api__.HttpBody": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def get_entry_bundle( + self, entry_bundle_request: "EntryBundleRequest" + ) -> "____google_api__.HttpBody": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def get_checkpoint( + self, + betterproto_lib_google_protobuf_empty: "betterproto_lib_google_protobuf.Empty", + ) -> "____google_api__.HttpBody": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def __rpc_create_entry( + self, + stream: "grpclib.server.Stream[CreateEntryRequest, _v1__.TransparencyLogEntry]", + ) -> None: + request = await stream.recv_message() + response = await self.create_entry(request) + await stream.send_message(response) + + async def __rpc_get_tile( + self, stream: "grpclib.server.Stream[TileRequest, ____google_api__.HttpBody]" + ) -> None: + request = await stream.recv_message() + response = await self.get_tile(request) + await stream.send_message(response) + + async def __rpc_get_entry_bundle( + self, + stream: "grpclib.server.Stream[EntryBundleRequest, ____google_api__.HttpBody]", + ) -> None: + request = await stream.recv_message() + response = await self.get_entry_bundle(request) + await stream.send_message(response) + + async def __rpc_get_checkpoint( + self, + stream: "grpclib.server.Stream[betterproto_lib_google_protobuf.Empty, ____google_api__.HttpBody]", + ) -> None: + request = await stream.recv_message() + response = await self.get_checkpoint(request) + await stream.send_message(response) + + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + return { + "/dev.sigstore.rekor.v2.Rekor/CreateEntry": grpclib.const.Handler( + self.__rpc_create_entry, + grpclib.const.Cardinality.UNARY_UNARY, + CreateEntryRequest, + _v1__.TransparencyLogEntry, + ), + "/dev.sigstore.rekor.v2.Rekor/GetTile": grpclib.const.Handler( + self.__rpc_get_tile, + grpclib.const.Cardinality.UNARY_UNARY, + TileRequest, + ____google_api__.HttpBody, + ), + "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle": grpclib.const.Handler( + self.__rpc_get_entry_bundle, + grpclib.const.Cardinality.UNARY_UNARY, + EntryBundleRequest, + ____google_api__.HttpBody, + ), + "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint": grpclib.const.Handler( + self.__rpc_get_checkpoint, + grpclib.const.Cardinality.UNARY_UNARY, + betterproto_lib_google_protobuf.Empty, + ____google_api__.HttpBody, + ), + } diff --git a/sigstore/_internal/rekor_tiles/google/__init__.py b/sigstore/_internal/rekor_tiles/google/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sigstore/_internal/rekor_tiles/google/api/__init__.py b/sigstore/_internal/rekor_tiles/google/api/__init__.py new file mode 100644 index 000000000..b642b0ec5 --- /dev/null +++ b/sigstore/_internal/rekor_tiles/google/api/__init__.py @@ -0,0 +1,527 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: google/api/annotations.proto, google/api/field_behavior.proto, google/api/http.proto, google/api/httpbody.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import List + +import betterproto +import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf + + +class FieldBehavior(betterproto.Enum): + """ + An indicator of the behavior of a given field (for example, that a field + is required in requests, or given as output but ignored as input). + This **does not** change the behavior in protocol buffers itself; it only + denotes the behavior and may affect how API tooling handles the field. + + Note: This enum **may** receive new values in the future. + """ + + UNSPECIFIED = 0 + """Conventional default for enums. Do not use this.""" + + OPTIONAL = 1 + """ + Specifically denotes a field as optional. + While all fields in protocol buffers are optional, this may be specified + for emphasis if appropriate. + """ + + REQUIRED = 2 + """ + Denotes a field as required. + This indicates that the field **must** be provided as part of the request, + and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + """ + + OUTPUT_ONLY = 3 + """ + Denotes a field as output only. + This indicates that the field is provided in responses, but including the + field in a request does nothing (the server *must* ignore it and + *must not* throw an error as a result of the field's presence). + """ + + INPUT_ONLY = 4 + """ + Denotes a field as input only. + This indicates that the field is provided in requests, and the + corresponding field is not included in output. + """ + + IMMUTABLE = 5 + """ + Denotes a field as immutable. + This indicates that the field may be set once in a request to create a + resource, but may not be changed thereafter. + """ + + UNORDERED_LIST = 6 + """ + Denotes that a (repeated) field is an unordered list. + This indicates that the service may provide the elements of the list + in any arbitrary order, rather than the order the user originally + provided. Additionally, the list's order may or may not be stable. + """ + + NON_EMPTY_DEFAULT = 7 + """ + Denotes that this field returns a non-empty default value if not set. + This indicates that if the user provides the empty value in a request, + a non-empty value will be returned. The user will not be aware of what + non-empty value to expect. + """ + + IDENTIFIER = 8 + """ + Denotes that the field in a resource (a message annotated with + google.api.resource) is used in the resource name to uniquely identify the + resource. For AIP-compliant APIs, this should only be applied to the + `name` field on the resource. + + This behavior should not be applied to references to other resources within + the message. + + The identifier field of resources often have different field behavior + depending on the request it is embedded in (e.g. for Create methods name + is optional and unused, while for Update methods it is required). Instead + of method-specific annotations, only `IDENTIFIER` is required. + """ + + +@dataclass(eq=False, repr=False) +class Http(betterproto.Message): + """ + Defines the HTTP configuration for an API service. It contains a list of + [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + to one or more HTTP REST API methods. + """ + + rules: List["HttpRule"] = betterproto.message_field(1) + """ + A list of HTTP configuration rules that apply to individual API methods. + + **NOTE:** All service configuration rules follow "last one wins" order. + """ + + fully_decode_reserved_expansion: bool = betterproto.bool_field(2) + """ + When set to true, URL path parameters will be fully URI-decoded except in + cases of single segment matches in reserved expansion, where "%2F" will be + left encoded. + + The default behavior is to not decode RFC 6570 reserved characters in multi + segment matches. + """ + + +@dataclass(eq=False, repr=False) +class HttpRule(betterproto.Message): + """ + gRPC Transcoding + + gRPC Transcoding is a feature for mapping between a gRPC method and one or + more HTTP REST endpoints. It allows developers to build a single API service + that supports both gRPC APIs and REST APIs. Many systems, including [Google + APIs](https://github.com/googleapis/googleapis), + [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + and use it for large scale production services. + + `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + how different portions of the gRPC request message are mapped to the URL + path, URL query parameters, and HTTP request body. It also controls how the + gRPC response message is mapped to the HTTP response body. `HttpRule` is + typically specified as an `google.api.http` annotation on the gRPC method. + + Each mapping specifies a URL path template and an HTTP method. The path + template may refer to one or more fields in the gRPC request message, as long + as each field is a non-repeated field with a primitive (non-message) type. + The path template controls how fields of the request message are mapped to + the URL path. + + Example: + + service Messaging { + rpc GetMessage(GetMessageRequest) returns (Message) { + option (google.api.http) = { + get: "/v1/{name=messages/*}" + }; + } + } + message GetMessageRequest { + string name = 1; // Mapped to URL path. + } + message Message { + string text = 1; // The resource content. + } + + This enables an HTTP REST to gRPC mapping as below: + + - HTTP: `GET /v1/messages/123456` + - gRPC: `GetMessage(name: "messages/123456")` + + Any fields in the request message which are not bound by the path template + automatically become HTTP query parameters if there is no HTTP request body. + For example: + + service Messaging { + rpc GetMessage(GetMessageRequest) returns (Message) { + option (google.api.http) = { + get:"/v1/messages/{message_id}" + }; + } + } + message GetMessageRequest { + message SubMessage { + string subfield = 1; + } + string message_id = 1; // Mapped to URL path. + int64 revision = 2; // Mapped to URL query parameter `revision`. + SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + } + + This enables a HTTP JSON to RPC mapping as below: + + - HTTP: `GET /v1/messages/123456?revision=2&sub.subfield=foo` + - gRPC: `GetMessage(message_id: "123456" revision: 2 sub: + SubMessage(subfield: "foo"))` + + Note that fields which are mapped to URL query parameters must have a + primitive type or a repeated primitive type or a non-repeated message type. + In the case of a repeated type, the parameter can be repeated in the URL + as `...?param=A¶m=B`. In the case of a message type, each field of the + message is mapped to a separate parameter, such as + `...?foo.a=A&foo.b=B&foo.c=C`. + + For HTTP methods that allow a request body, the `body` field + specifies the mapping. Consider a REST update method on the + message resource collection: + + service Messaging { + rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + option (google.api.http) = { + patch: "/v1/messages/{message_id}" + body: "message" + }; + } + } + message UpdateMessageRequest { + string message_id = 1; // mapped to the URL + Message message = 2; // mapped to the body + } + + The following HTTP JSON to RPC mapping is enabled, where the + representation of the JSON in the request body is determined by + protos JSON encoding: + + - HTTP: `PATCH /v1/messages/123456 { "text": "Hi!" }` + - gRPC: `UpdateMessage(message_id: "123456" message { text: "Hi!" })` + + The special name `*` can be used in the body mapping to define that + every field not bound by the path template should be mapped to the + request body. This enables the following alternative definition of + the update method: + + service Messaging { + rpc UpdateMessage(Message) returns (Message) { + option (google.api.http) = { + patch: "/v1/messages/{message_id}" + body: "*" + }; + } + } + message Message { + string message_id = 1; + string text = 2; + } + + + The following HTTP JSON to RPC mapping is enabled: + + - HTTP: `PATCH /v1/messages/123456 { "text": "Hi!" }` + - gRPC: `UpdateMessage(message_id: "123456" text: "Hi!")` + + Note that when using `*` in the body mapping, it is not possible to + have HTTP parameters, as all fields not bound by the path end in + the body. This makes this option more rarely used in practice when + defining REST APIs. The common usage of `*` is in custom methods + which don't use the URL at all for transferring data. + + It is possible to define multiple HTTP methods for one RPC by using + the `additional_bindings` option. Example: + + service Messaging { + rpc GetMessage(GetMessageRequest) returns (Message) { + option (google.api.http) = { + get: "/v1/messages/{message_id}" + additional_bindings { + get: "/v1/users/{user_id}/messages/{message_id}" + } + }; + } + } + message GetMessageRequest { + string message_id = 1; + string user_id = 2; + } + + This enables the following two alternative HTTP JSON to RPC mappings: + + - HTTP: `GET /v1/messages/123456` + - gRPC: `GetMessage(message_id: "123456")` + + - HTTP: `GET /v1/users/me/messages/123456` + - gRPC: `GetMessage(user_id: "me" message_id: "123456")` + + Rules for HTTP mapping + + 1. Leaf request fields (recursive expansion nested messages in the request + message) are classified into three categories: + - Fields referred by the path template. They are passed via the URL path. + - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They + are passed via the HTTP + request body. + - All other fields are passed via the URL query parameters, and the + parameter name is the field path in the request message. A repeated + field can be represented as multiple query parameters under the same + name. + 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL + query parameter, all fields + are passed via URL path and HTTP request body. + 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP + request body, all + fields are passed via URL path and URL query parameters. + + Path template syntax + + Template = "/" Segments [ Verb ] ; + Segments = Segment { "/" Segment } ; + Segment = "*" | "**" | LITERAL | Variable ; + Variable = "{" FieldPath [ "=" Segments ] "}" ; + FieldPath = IDENT { "." IDENT } ; + Verb = ":" LITERAL ; + + The syntax `*` matches a single URL path segment. The syntax `**` matches + zero or more URL path segments, which must be the last part of the URL path + except the `Verb`. + + The syntax `Variable` matches part of the URL path as specified by its + template. A variable template must not contain other variables. If a variable + matches a single path segment, its template may be omitted, e.g. `{var}` + is equivalent to `{var=*}`. + + The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + contains any reserved character, such characters should be percent-encoded + before the matching. + + If a variable contains exactly one path segment, such as `"{var}"` or + `"{var=*}"`, when such a variable is expanded into a URL path on the client + side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + server side does the reverse decoding. Such variables show up in the + [Discovery + Document](https://developers.google.com/discovery/v1/reference/apis) as + `{var}`. + + If a variable contains multiple path segments, such as `"{var=foo/*}"` + or `"{var=**}"`, when such a variable is expanded into a URL path on the + client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + The server side does the reverse decoding, except "%2F" and "%2f" are left + unchanged. Such variables show up in the + [Discovery + Document](https://developers.google.com/discovery/v1/reference/apis) as + `{+var}`. + + Using gRPC API Service Configuration + + gRPC API Service Configuration (service config) is a configuration language + for configuring a gRPC service to become a user-facing product. The + service config is simply the YAML representation of the `google.api.Service` + proto message. + + As an alternative to annotating your proto file, you can configure gRPC + transcoding in your service config YAML files. You do this by specifying a + `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + effect as the proto annotation. This can be particularly useful if you + have a proto that is reused in multiple services. Note that any transcoding + specified in the service config will override any matching transcoding + configuration in the proto. + + The following example selects a gRPC method and applies an `HttpRule` to it: + + http: + rules: + - selector: example.v1.Messaging.GetMessage + get: /v1/messages/{message_id}/{sub.subfield} + + Special notes + + When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + proto to JSON conversion must follow the [proto3 + specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + + While the single segment variable follows the semantics of + [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + Expansion, the multi segment variable **does not** follow RFC 6570 Section + 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + does not expand special characters like `?` and `#`, which would lead + to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + for multi segment variables. + + The path variables **must not** refer to any repeated or mapped field, + because client libraries are not capable of handling such variable expansion. + + The path variables **must not** capture the leading "/" character. The reason + is that the most common use case "{var}" does not capture the leading "/" + character. For consistency, all path variables must share the same behavior. + + Repeated message fields must not be mapped to URL query parameters, because + no client library can support such complicated mapping. + + If an API needs to use a JSON array for request or response body, it can map + the request or response body to a repeated field. However, some gRPC + Transcoding implementations may not support this feature. + """ + + selector: str = betterproto.string_field(1) + """ + Selects a method to which this rule applies. + + Refer to [selector][google.api.DocumentationRule.selector] for syntax + details. + """ + + get: str = betterproto.string_field(2, group="pattern") + """ + Maps to HTTP GET. Used for listing and getting information about + resources. + """ + + put: str = betterproto.string_field(3, group="pattern") + """Maps to HTTP PUT. Used for replacing a resource.""" + + post: str = betterproto.string_field(4, group="pattern") + """ + Maps to HTTP POST. Used for creating a resource or performing an action. + """ + + delete: str = betterproto.string_field(5, group="pattern") + """Maps to HTTP DELETE. Used for deleting a resource.""" + + patch: str = betterproto.string_field(6, group="pattern") + """Maps to HTTP PATCH. Used for updating a resource.""" + + custom: "CustomHttpPattern" = betterproto.message_field(8, group="pattern") + """ + The custom pattern is used for specifying an HTTP method that is not + included in the `pattern` field, such as HEAD, or "*" to leave the + HTTP method unspecified for this rule. The wild-card rule is useful + for services that provide content to Web (HTML) clients. + """ + + body: str = betterproto.string_field(7) + """ + The name of the request field whose value is mapped to the HTTP request + body, or `*` for mapping all request fields not captured by the path + pattern to the HTTP body, or omitted for not having any HTTP request body. + + NOTE: the referred field must be present at the top-level of the request + message type. + """ + + response_body: str = betterproto.string_field(12) + """ + Optional. The name of the response field whose value is mapped to the HTTP + response body. When omitted, the entire response message will be used + as the HTTP response body. + + NOTE: The referred field must be present at the top-level of the response + message type. + """ + + additional_bindings: List["HttpRule"] = betterproto.message_field(11) + """ + Additional HTTP bindings for the selector. Nested bindings must + not contain an `additional_bindings` field themselves (that is, + the nesting may only be one level deep). + """ + + +@dataclass(eq=False, repr=False) +class CustomHttpPattern(betterproto.Message): + """A custom pattern is used for defining custom HTTP verb.""" + + kind: str = betterproto.string_field(1) + """The name of this custom HTTP verb.""" + + path: str = betterproto.string_field(2) + """The path matched by this custom verb.""" + + +@dataclass(eq=False, repr=False) +class HttpBody(betterproto.Message): + """ + Message that represents an arbitrary HTTP body. It should only be used for + payload formats that can't be represented as JSON, such as raw binary or + an HTML page. + + + This message can be used both in streaming and non-streaming API methods in + the request as well as the response. + + It can be used as a top-level request field, which is convenient if one + wants to extract parameters from either the URL or HTTP template into the + request fields and also want access to the raw HTTP body. + + Example: + + message GetResourceRequest { + // A unique request id. + string request_id = 1; + + // The raw HTTP body is bound to this field. + google.api.HttpBody http_body = 2; + + } + + service ResourceService { + rpc GetResource(GetResourceRequest) + returns (google.api.HttpBody); + rpc UpdateResource(google.api.HttpBody) + returns (google.protobuf.Empty); + + } + + Example with streaming methods: + + service CaldavService { + rpc GetCalendar(stream google.api.HttpBody) + returns (stream google.api.HttpBody); + rpc UpdateCalendar(stream google.api.HttpBody) + returns (stream google.api.HttpBody); + + } + + Use of this type only changes how the request and response bodies are + handled, all other features will continue to work unchanged. + """ + + content_type: str = betterproto.string_field(1) + """ + The HTTP Content-Type header value specifying the content type of the body. + """ + + data: bytes = betterproto.bytes_field(2) + """The HTTP request/response body as raw binary.""" + + extensions: List["betterproto_lib_google_protobuf.Any"] = betterproto.message_field( + 3 + ) + """ + Application specific response metadata. Must be set in the first response + for streaming APIs. + """ 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..eba60a6bb --- /dev/null +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -0,0 +1,54 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# sources: envelope.proto +# plugin: python-betterproto +# This file has been @generated + +from dataclasses import dataclass +from typing import List + +import betterproto + + +@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. + """ From 48217d4226f95f5cd9c4cbe41b3b0c0f5ccde47a Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 15 May 2025 20:36:13 +0000 Subject: [PATCH 51/81] pydantic types Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 40 +++++----- .../dev/sigstore/common/v1/__init__.py | 46 ++++++++++- .../dev/sigstore/rekor/v1/__init__.py | 14 +++- .../dev/sigstore/rekor/v2/__init__.py | 77 +++++++++++++------ .../rekor_tiles/google/api/__init__.py | 51 +++++++++--- .../rekor_tiles/io/intoto/__init__.py | 13 +++- sigstore/_internal/trust.py | 2 +- sigstore/sign.py | 22 ++++++ 8 files changed, 207 insertions(+), 58 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 624085975..cff2e414f 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -28,6 +28,7 @@ import requests from sigstore._internal import USER_AGENT +from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 from sigstore.models import LogEntry _logger = logging.getLogger(__name__) @@ -145,31 +146,34 @@ def get( def post( self, - proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse, + proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse | v2.CreateEntryRequest ) -> LogEntry: """ Submit a new entry for inclusion in the Rekor log. """ - payload = proposed_entry.model_dump(mode="json", by_alias=True) + if isinstance(proposed_entry, v2.CreateEntryRequest): + payload = proposed_entry.to_dict(include_default_values=True) + else: + payload = proposed_entry.model_dump(mode="json", by_alias=True) _logger.debug(f"proposed: {json.dumps(payload)}") # layout from rekor-tiles/docs/openapi/rekor_service.swagger.json - payloadV2 = { - 'hashedRekordRequestV0_0_2': { - 'digest': payload["spec"]['data']['hash']['value'], - 'signature': { - 'content': payload["spec"]['signature']['content'], - 'verifier': { - 'public_key': { - 'rawBytes': payload["spec"]['signature']['publicKey']['content'] - }, - 'key_details': "PKIX_ECDSA_P384_SHA_256" - } - } - } - } - _logger.debug(f"proposed: {json.dumps(payloadV2)}") - payload = payloadV2 + # payloadV2 = { + # 'hashedRekordRequestV0_0_2': { + # 'digest': payload["spec"]['data']['hash']['value'], + # 'signature': { + # 'content': payload["spec"]['signature']['content'], + # 'verifier': { + # 'public_key': { + # 'rawBytes': payload["spec"]['signature']['publicKey']['content'] + # }, + # 'key_details': "PKIX_ECDSA_P384_SHA_256" + # } + # } + # } + # } + # _logger.debug(f"proposed: {json.dumps(payloadV2)}") + # payload = payloadV2 # NOTE: not "entries/" print(self.url) resp: requests.Response = self.session.post(self.url, json=payload) diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py index 8703b78ab..6f44c3fa8 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -3,7 +3,14 @@ # plugin: python-betterproto # This file has been @generated -from dataclasses import dataclass +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, @@ -11,6 +18,8 @@ ) import betterproto +from pydantic import model_validator +from pydantic.dataclasses import rebuild_dataclass class HashAlgorithm(betterproto.Enum): @@ -31,6 +40,12 @@ class HashAlgorithm(betterproto.Enum): 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): """ @@ -105,6 +120,12 @@ class PublicKeyDetails(betterproto.Enum): 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 @@ -117,6 +138,12 @@ class SubjectAlternativeNameType(betterproto.Enum): 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): @@ -242,15 +269,19 @@ class X509Certificate(betterproto.Message): @dataclass(eq=False, repr=False) class SubjectAlternativeName(betterproto.Message): type: "SubjectAlternativeNameType" = betterproto.enum_field(1) - regexp: str = betterproto.string_field(2, group="identity") + regexp: Optional[str] = betterproto.string_field(2, optional=True, group="identity") """ A regular expression describing the expected value for the SAN. """ - value: str = betterproto.string_field(3, group="identity") + 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): @@ -283,3 +314,12 @@ class TimeRange(betterproto.Message): 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/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py index 890d3f799..07926e9e7 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -3,10 +3,18 @@ # plugin: python-betterproto # This file has been @generated -from dataclasses import dataclass +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__ @@ -168,3 +176,7 @@ class TransparencyLogEntry(betterproto.Message): 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 index d4fadacbb..fa05c40b6 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -3,18 +3,26 @@ # plugin: python-betterproto # This file has been @generated -from dataclasses import dataclass +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from dataclasses import dataclass +else: + from pydantic.dataclasses import dataclass + from typing import ( - TYPE_CHECKING, Dict, List, Optional, ) import betterproto -import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf +import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf import grpclib from betterproto.grpc.grpclib_server import ServiceBase +from pydantic import model_validator +from pydantic.dataclasses import rebuild_dataclass from .....google import api as ____google_api__ from .....io import intoto as ____io_intoto__ @@ -22,12 +30,6 @@ from .. import v1 as _v1__ -if TYPE_CHECKING: - import grpclib.server - from betterproto.grpc.grpclib_client import MetadataLike - from grpclib.metadata import Deadline - - @dataclass(eq=False, repr=False) class PublicKey(betterproto.Message): """PublicKey contains an encoded public key""" @@ -42,19 +44,25 @@ class Verifier(betterproto.Message): Either a public key or a X.509 cerificiate with an embedded public key """ - public_key: "PublicKey" = betterproto.message_field(1, group="verifier") + 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: "__common_v1__.X509Certificate" = betterproto.message_field( - 2, group="verifier" + 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): @@ -132,20 +140,32 @@ class Entry(betterproto.Message): class Spec(betterproto.Message): """Spec contains one of the Rekor entry types.""" - hashed_rekord_v0_0_2: "HashedRekordLogEntryV002" = betterproto.message_field( - 1, group="spec" + 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" ) - dsse_v0_0_2: "DsseLogEntryV002" = betterproto.message_field(2, 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: "HashedRekordRequestV002" = betterproto.message_field( - 1, group="spec" + 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" ) - dsse_request_v0_0_2: "DsseRequestV002" = betterproto.message_field(2, group="spec") + + @model_validator(mode="after") + def check_oneof(cls, values): + return cls._validate_field_groups(values) @dataclass(eq=False, repr=False) @@ -229,7 +249,7 @@ async def get_entry_bundle( async def get_checkpoint( self, - betterproto_lib_google_protobuf_empty: "betterproto_lib_google_protobuf.Empty", + betterproto_lib_pydantic_google_protobuf_empty: "betterproto_lib_pydantic_google_protobuf.Empty", *, timeout: Optional[float] = None, deadline: Optional["Deadline"] = None, @@ -237,7 +257,7 @@ async def get_checkpoint( ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint", - betterproto_lib_google_protobuf_empty, + betterproto_lib_pydantic_google_protobuf_empty, ____google_api__.HttpBody, timeout=timeout, deadline=deadline, @@ -264,7 +284,7 @@ async def get_entry_bundle( async def get_checkpoint( self, - betterproto_lib_google_protobuf_empty: "betterproto_lib_google_protobuf.Empty", + betterproto_lib_pydantic_google_protobuf_empty: "betterproto_lib_pydantic_google_protobuf.Empty", ) -> "____google_api__.HttpBody": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) @@ -293,7 +313,7 @@ async def __rpc_get_entry_bundle( async def __rpc_get_checkpoint( self, - stream: "grpclib.server.Stream[betterproto_lib_google_protobuf.Empty, ____google_api__.HttpBody]", + stream: "grpclib.server.Stream[betterproto_lib_pydantic_google_protobuf.Empty, ____google_api__.HttpBody]", ) -> None: request = await stream.recv_message() response = await self.get_checkpoint(request) @@ -322,7 +342,18 @@ def __mapping__(self) -> Dict[str, grpclib.const.Handler]: "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint": grpclib.const.Handler( self.__rpc_get_checkpoint, grpclib.const.Cardinality.UNARY_UNARY, - betterproto_lib_google_protobuf.Empty, + betterproto_lib_pydantic_google_protobuf.Empty, ____google_api__.HttpBody, ), } + + +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/google/api/__init__.py b/sigstore/_internal/rekor_tiles/google/api/__init__.py index b642b0ec5..75a1d1fa4 100644 --- a/sigstore/_internal/rekor_tiles/google/api/__init__.py +++ b/sigstore/_internal/rekor_tiles/google/api/__init__.py @@ -3,11 +3,23 @@ # plugin: python-betterproto # This file has been @generated -from dataclasses import dataclass -from typing import List +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 -import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf +import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf +from pydantic import model_validator +from pydantic.dataclasses import rebuild_dataclass class FieldBehavior(betterproto.Enum): @@ -91,6 +103,12 @@ class FieldBehavior(betterproto.Enum): of method-specific annotations, only `IDENTIFIER` is required. """ + @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 Http(betterproto.Message): @@ -395,27 +413,29 @@ class HttpRule(betterproto.Message): details. """ - get: str = betterproto.string_field(2, group="pattern") + get: Optional[str] = betterproto.string_field(2, optional=True, group="pattern") """ Maps to HTTP GET. Used for listing and getting information about resources. """ - put: str = betterproto.string_field(3, group="pattern") + put: Optional[str] = betterproto.string_field(3, optional=True, group="pattern") """Maps to HTTP PUT. Used for replacing a resource.""" - post: str = betterproto.string_field(4, group="pattern") + post: Optional[str] = betterproto.string_field(4, optional=True, group="pattern") """ Maps to HTTP POST. Used for creating a resource or performing an action. """ - delete: str = betterproto.string_field(5, group="pattern") + delete: Optional[str] = betterproto.string_field(5, optional=True, group="pattern") """Maps to HTTP DELETE. Used for deleting a resource.""" - patch: str = betterproto.string_field(6, group="pattern") + patch: Optional[str] = betterproto.string_field(6, optional=True, group="pattern") """Maps to HTTP PATCH. Used for updating a resource.""" - custom: "CustomHttpPattern" = betterproto.message_field(8, group="pattern") + custom: Optional["CustomHttpPattern"] = betterproto.message_field( + 8, optional=True, group="pattern" + ) """ The custom pattern is used for specifying an HTTP method that is not included in the `pattern` field, such as HEAD, or "*" to leave the @@ -450,6 +470,10 @@ class HttpRule(betterproto.Message): the nesting may only be one level deep). """ + @model_validator(mode="after") + def check_oneof(cls, values): + return cls._validate_field_groups(values) + @dataclass(eq=False, repr=False) class CustomHttpPattern(betterproto.Message): @@ -518,10 +542,15 @@ class HttpBody(betterproto.Message): data: bytes = betterproto.bytes_field(2) """The HTTP request/response body as raw binary.""" - extensions: List["betterproto_lib_google_protobuf.Any"] = betterproto.message_field( - 3 + extensions: List["betterproto_lib_pydantic_google_protobuf.Any"] = ( + betterproto.message_field(3) ) """ Application specific response metadata. Must be set in the first response for streaming APIs. """ + + +rebuild_dataclass(Http) # type: ignore +rebuild_dataclass(HttpRule) # type: ignore +rebuild_dataclass(HttpBody) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py index eba60a6bb..b3c482245 100644 --- a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -3,10 +3,18 @@ # plugin: python-betterproto # This file has been @generated -from dataclasses import dataclass +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) @@ -52,3 +60,6 @@ class Signature(betterproto.Message): *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 ae104e071..88c8a6ef8 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -357,7 +357,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/sign.py b/sigstore/sign.py index 0a6a136e6..0bce0e4ec 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -303,6 +303,28 @@ def sign_artifact( ), ) + from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 + from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 + proposed_entry = v2.CreateEntryRequest( + hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002( + digest=base64.b64encode(hashed_input.digest), + signature=v2.Signature( + content=base64.b64encode(artifact_signature), + verifier=v2.Verifier( + public_key=v2.PublicKey( + raw_bytes=base64.b64encode( + cert.public_key().public_bytes( + encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ), + ), + key_details=v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 + ), + ) + ) + ) + return self._finalize_sign(cert, content, proposed_entry) From 1b0c7d97d4ca3d378ae5e427d5a48a954018ddc3 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 03:09:43 +0000 Subject: [PATCH 52/81] branching on api_major_version Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 46 ++++++++----------- sigstore/sign.py | 74 +++++++++++++----------------- 2 files changed, 51 insertions(+), 69 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index cff2e414f..6ac198518 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -24,6 +24,8 @@ from dataclasses import dataclass from typing import Any, Optional +from betterproto import Casing +import betterproto import rekor_types import requests @@ -36,6 +38,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: @@ -151,31 +156,16 @@ def post( """ 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. if isinstance(proposed_entry, v2.CreateEntryRequest): - payload = proposed_entry.to_dict(include_default_values=True) + payload = proposed_entry.to_dict() + if "hashedRekordRequestV002" in payload: + payload["hashedRekordRequestV0_0_2"] = payload.pop( + "hashedRekordRequestV002") + if "dsseRequestV002" in payload: + payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002") else: payload = proposed_entry.model_dump(mode="json", by_alias=True) - _logger.debug(f"proposed: {json.dumps(payload)}") - # layout from rekor-tiles/docs/openapi/rekor_service.swagger.json - # payloadV2 = { - # 'hashedRekordRequestV0_0_2': { - # 'digest': payload["spec"]['data']['hash']['value'], - # 'signature': { - # 'content': payload["spec"]['signature']['content'], - # 'verifier': { - # 'public_key': { - # 'rawBytes': payload["spec"]['signature']['publicKey']['content'] - # }, - # 'key_details': "PKIX_ECDSA_P384_SHA_256" - # } - # } - # } - # } - # _logger.debug(f"proposed: {json.dumps(payloadV2)}") - # payload = payloadV2 - # NOTE: not "entries/" - print(self.url) resp: requests.Response = self.session.post(self.url, json=payload) try: resp.raise_for_status() @@ -184,8 +174,10 @@ def post( integrated_entry = resp.json() _logger.debug(f"integrated: {integrated_entry}") - # return LogEntry._from_response(integrated_entry) - return LogEntry._from_dict_rekor(integrated_entry) + if isinstance(proposed_entry, v2.CreateEntryRequest): + return LogEntry._from_dict_rekor(integrated_entry) + else: + return LogEntry._from_response(integrated_entry) @property @@ -243,12 +235,12 @@ def post( class RekorClient: """The internal Rekor client""" - def __init__(self, url: str) -> None: + def __init__(self, url: str, major_api_version: int = REKOR_V1_API_MAJOR_VERSION) -> None: """ Create a new `RekorClient` from the given URL. """ - # NOTE: use "api/v2/" - self.url = f"{url}/api/v2" + self.url = f"{url}/api/v{major_api_version}" + self.major_api_version = major_api_version self.session = requests.Session() self.session.headers.update( { diff --git a/sigstore/sign.py b/sigstore/sign.py index 0bce0e4ec..6824e79a1 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -61,13 +61,15 @@ ExpiredCertificate, FulcioClient, ) -from sigstore._internal.rekor.client import RekorClient +from sigstore._internal.rekor.client import RekorClient, REKOR_V2_API_MAJOR_VERSION, REKOR_V1_API_MAJOR_VERSION from sigstore._internal.sct import verify_sct from sigstore._internal.timestamp import TimestampAuthorityClient, TimestampError from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot from sigstore._utils import sha256_digest from sigstore.models import Bundle from sigstore.oidc import ExpiredIdentity, IdentityToken +from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 _logger = logging.getLogger(__name__) @@ -255,16 +257,9 @@ def sign_artifact( cert = self._signing_cert() - # Prepare inputs - # b64_cert = base64.b64encode( - # cert.public_bytes(encoding=serialization.Encoding.PEM) - # ) b64_cert = base64.b64encode( - cert.public_key().public_bytes( - encoding=serialization.Encoding.DER, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ) + cert.public_bytes(encoding=serialization.Encoding.PEM) ) # Sign artifact @@ -274,8 +269,6 @@ def sign_artifact( hashed_input.digest, ec.ECDSA(hashed_input._as_prehashed()) ) - b64_digest = base64.b64encode(hashed_input.digest).decode() - content = MessageSignature( message_digest=HashOutput( algorithm=hashed_input.algorithm, @@ -285,45 +278,41 @@ 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() + if self._signing_ctx._rekor.major_api_version == REKOR_V1_API_MAJOR_VERSION: + 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(), + ) ), ), - data=rekor_types.hashedrekord.Data( - hash=rekor_types.hashedrekord.Hash( - algorithm=hashed_input._as_hashedrekord_algorithm(), - # value=hashed_input.digest.decode(), - value=b64_digest - ) - ), - ), - ) - - from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 - from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 - proposed_entry = v2.CreateEntryRequest( - hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002( - digest=base64.b64encode(hashed_input.digest), - signature=v2.Signature( - content=base64.b64encode(artifact_signature), - verifier=v2.Verifier( - public_key=v2.PublicKey( - raw_bytes=base64.b64encode( - cert.public_key().public_bytes( + ) + else: + proposed_entry = v2.CreateEntryRequest( + hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002( + digest=hashed_input.digest, + signature=v2.Signature( + content=artifact_signature, + verifier=v2.Verifier( + public_key=v2.PublicKey( + raw_bytes=cert.public_key().public_bytes( encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ), + key_details=v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 ), - key_details=v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 - ), + ) ) ) - ) return self._finalize_sign(cert, content, proposed_entry) @@ -387,7 +376,8 @@ def _from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext: signing_config = trust_config.signing_config return cls( fulcio=FulcioClient(signing_config.get_fulcio_url()), - rekor=RekorClient(signing_config.get_tlog_urls()[0]), + rekor=RekorClient(signing_config.get_tlog_urls()[ + 0], signing_config._inner.rekor_tlog_urls[0].major_api_version), trusted_root=trust_config.trusted_root, tsa_clients=[ TimestampAuthorityClient(url) for url in signing_config.get_tsa_urls() From 5b79cc2d41be8e0c9f6765cc8ac437ec79384ebf Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 04:20:31 +0000 Subject: [PATCH 53/81] verification Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 1 + sigstore/models.py | 3 ++ sigstore/sign.py | 31 ++++++++++++++- sigstore/verify/verifier.py | 62 ++++++++++++++++-------------- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 6ac198518..9e24898e2 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -166,6 +166,7 @@ def post( payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002") else: payload = proposed_entry.model_dump(mode="json", by_alias=True) + _logger.debug(f"proposed: {json.dumps(payload)}") resp: requests.Response = self.session.post(self.url, json=payload) try: resp.raise_for_status() diff --git a/sigstore/models.py b/sigstore/models.py index 400085c52..ac1df4c6c 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -72,10 +72,13 @@ cert_is_root_ca, ) from sigstore.errors import Error, VerificationError +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 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 6824e79a1..440d0e5d1 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -48,8 +48,9 @@ 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.errors import Error from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( HashOutput, MessageSignature, @@ -74,6 +75,16 @@ _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 PKIX_ECDSA_P384_SHA_256. + ''' + if isinstance(key, ec.EllipticCurvePrivateKey) and isinstance(key.curve, ec.SECP256R1): + return v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 + else: + raise Exception("unsupported key type") + + class Signer: """ The primary API for signing operations. @@ -276,6 +287,22 @@ def sign_artifact( ), signature=artifact_signature, ) + 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(), + ) + ), + ), + ) # Create the proposed hashedrekord entry if self._signing_ctx._rekor.major_api_version == REKOR_V1_API_MAJOR_VERSION: @@ -308,7 +335,7 @@ def sign_artifact( format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ), - key_details=v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 + key_details=key_to_details(self._private_key) ), ) ) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index defb858fe..cd04738dc 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -29,6 +29,7 @@ from cryptography.hazmat.primitives.asymmetric import ec from cryptography.x509 import ExtendedKeyUsage, KeyUsage from cryptography.x509.oid import ExtendedKeyUsageOID +from cryptography.hazmat.primitives import serialization from OpenSSL.crypto import ( X509, X509Store, @@ -41,6 +42,7 @@ from rfc3161_client import VerificationError as Rfc3161VerificationError from sigstore import dsse +from sigstore import models from sigstore._internal.rekor import _hashedrekord_from_parts from sigstore._internal.rekor.client import RekorClient from sigstore._internal.sct import ( @@ -53,7 +55,8 @@ from sigstore.hashes import Hashed from sigstore.models import Bundle from sigstore.verify.policy import VerificationPolicy - +from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 +from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 _logger = logging.getLogger(__name__) # Limit the number of timestamps to prevent DoS @@ -64,7 +67,6 @@ # timestamps to consider a signature valid. VERIFY_TIMESTAMP_THRESHOLD: int = 1 - class Verifier: """ The primary API for verification operations. @@ -506,30 +508,32 @@ def verify_artifact( # the other bundle materials (and input being verified). entry = bundle.log_entry - print((base64.b64decode(entry.body))) if entry.kind_version.version == '0.0.2': - entry_body = json.loads(base64.b64decode(entry.body)) - from sigstore import rekorv2 - parsed_body = rekorv2.Entry.model_validate( - entry_body) - print(parsed_body) - # restructured_body = { - # **entry_body, - # "spec": { - # **entry_body["spec"]["hashedRekordV0_0_2"], - # "signature": { - # "content": entry_body["spec"]["hashedRekordV0_0_2"]["signature"]["content"], - # "verifier": - # } - # } - # } - # actual_body = rekor_types.Hashedrekord.model_validate( - # restructured_body - # ) - # from pprint import pprint - # pprint(restructured_body) - # pprint(actual_body) - # pass + actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) + 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, @@ -540,7 +544,7 @@ def verify_artifact( 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" - ) + if expected_body != actual_body: + raise VerificationError( + "transparency log entry is inconsistent with other materials" + ) From 72aad2bf04e411f640beb5374e344c36cb3e8d54 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 04:34:02 +0000 Subject: [PATCH 54/81] reformat Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 14 ++++---- .../dev/sigstore/common/v1/__init__.py | 1 - .../dev/sigstore/rekor/v1/__init__.py | 1 - .../dev/sigstore/rekor/v2/__init__.py | 10 +++--- .../rekor_tiles/google/api/__init__.py | 1 - .../rekor_tiles/io/intoto/__init__.py | 1 - sigstore/_internal/timestamp.py | 2 -- sigstore/models.py | 2 +- sigstore/rekorv2.py | 14 +++++--- sigstore/sign.py | 32 ++++++++++++------- sigstore/verify/verifier.py | 24 +++++++------- test/unit/verify/test_verifier.py | 2 +- 12 files changed, 55 insertions(+), 49 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 9e24898e2..a320d1314 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -24,8 +24,6 @@ from dataclasses import dataclass from typing import Any, Optional -from betterproto import Casing -import betterproto import rekor_types import requests @@ -151,7 +149,9 @@ def get( def post( self, - proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse | v2.CreateEntryRequest + proposed_entry: rekor_types.Hashedrekord + | rekor_types.Dsse + | v2.CreateEntryRequest, ) -> LogEntry: """ Submit a new entry for inclusion in the Rekor log. @@ -161,7 +161,8 @@ def post( payload = proposed_entry.to_dict() if "hashedRekordRequestV002" in payload: payload["hashedRekordRequestV0_0_2"] = payload.pop( - "hashedRekordRequestV002") + "hashedRekordRequestV002" + ) if "dsseRequestV002" in payload: payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002") else: @@ -180,7 +181,6 @@ def post( else: return LogEntry._from_response(integrated_entry) - @property def retrieve(self) -> RekorEntriesRetrieve: """ @@ -236,7 +236,9 @@ def post( class RekorClient: """The internal Rekor client""" - def __init__(self, url: str, major_api_version: int = REKOR_V1_API_MAJOR_VERSION) -> None: + def __init__( + self, url: str, major_api_version: int = REKOR_V1_API_MAJOR_VERSION + ) -> None: """ Create a new `RekorClient` from the given URL. """ diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py index 6f44c3fa8..2c448fe7c 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING - if TYPE_CHECKING: from dataclasses import dataclass else: diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py index 07926e9e7..ae5debd0c 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING - if TYPE_CHECKING: from dataclasses import dataclass else: diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py index fa05c40b6..2e24864ee 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING - if TYPE_CHECKING: from dataclasses import dataclass else: @@ -202,7 +201,7 @@ async def create_entry( *, timeout: Optional[float] = None, deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None + metadata: Optional["MetadataLike"] = None, ) -> "_v1__.TransparencyLogEntry": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/CreateEntry", @@ -219,7 +218,7 @@ async def get_tile( *, timeout: Optional[float] = None, deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None + metadata: Optional["MetadataLike"] = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetTile", @@ -236,7 +235,7 @@ async def get_entry_bundle( *, timeout: Optional[float] = None, deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None + metadata: Optional["MetadataLike"] = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle", @@ -253,7 +252,7 @@ async def get_checkpoint( *, timeout: Optional[float] = None, deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None + metadata: Optional["MetadataLike"] = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint", @@ -266,7 +265,6 @@ async def get_checkpoint( class RekorBase(ServiceBase): - async def create_entry( self, create_entry_request: "CreateEntryRequest" ) -> "_v1__.TransparencyLogEntry": diff --git a/sigstore/_internal/rekor_tiles/google/api/__init__.py b/sigstore/_internal/rekor_tiles/google/api/__init__.py index 75a1d1fa4..d60e85e96 100644 --- a/sigstore/_internal/rekor_tiles/google/api/__init__.py +++ b/sigstore/_internal/rekor_tiles/google/api/__init__.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING - if TYPE_CHECKING: from dataclasses import dataclass else: diff --git a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py index b3c482245..919957b55 100644 --- a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING - if TYPE_CHECKING: from dataclasses import dataclass else: diff --git a/sigstore/_internal/timestamp.py b/sigstore/_internal/timestamp.py index feb69ce33..fe210f4fc 100644 --- a/sigstore/_internal/timestamp.py +++ b/sigstore/_internal/timestamp.py @@ -28,8 +28,6 @@ ) from rfc3161_client.base import HashAlgorithm -from rfc3161_client.base import HashAlgorithm - from sigstore._internal import USER_AGENT CLIENT_TIMEOUT: int = 5 diff --git a/sigstore/models.py b/sigstore/models.py index 1a596805d..5468d43a5 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, @@ -70,7 +71,6 @@ cert_is_root_ca, ) from sigstore.errors import Error, VerificationError -from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 if typing.TYPE_CHECKING: from sigstore._internal.trust import RekorKeyring diff --git a/sigstore/rekorv2.py b/sigstore/rekorv2.py index e74b7fa57..0e4eac2d5 100644 --- a/sigstore/rekorv2.py +++ b/sigstore/rekorv2.py @@ -5,12 +5,12 @@ # version: 0.30.1 from __future__ import annotations -from sigstore_protobuf_specs.dev.sigstore.common import v1 from enum import Enum from typing import Any, List, Optional from pydantic import BaseModel, ConfigDict, Field, RootModel, StrictInt, StrictStr +from sigstore_protobuf_specs.dev.sigstore.common import v1 class Model(RootModel[Any]): @@ -321,7 +321,9 @@ class V1TransparencyLogEntry(BaseModel): alias="logIndex", description="The global index of the entry, used when querying the log by index.", ) - log_id: V1LogId = Field(..., alias="logId", description="The unique identifier of the log.") + log_id: V1LogId = Field( + ..., alias="logId", description="The unique identifier of the log." + ) kind_version: V1KindVersion = Field( ..., alias="kindVersion", @@ -353,7 +355,9 @@ class V1X509Certificate(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - raw_bytes: str = Field(..., alias="rawBytes", description="DER-encoded X.509 certificate.") + raw_bytes: str = Field( + ..., alias="rawBytes", description="DER-encoded X.509 certificate." + ) class V2Verifier(BaseModel): @@ -483,6 +487,7 @@ class V2DSSERequestV002(BaseModel): # Custom Code + class V2HashedRekordRequestV002(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -532,8 +537,7 @@ class HashedRekordLogEntryV002(BaseModel): class DSSELogEntryV002Root(BaseModel): - dsse_v0_0_2: DSSELogEntryV002 = Field( - ..., alias="dsseV0_0_2") + dsse_v0_0_2: DSSELogEntryV002 = Field(..., alias="dsseV0_0_2") class DSSELogEntryV002(BaseModel): diff --git a/sigstore/sign.py b/sigstore/sign.py index 440d0e5d1..4b9d963fc 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -50,7 +50,6 @@ from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa from cryptography.x509.oid import NameOID -from sigstore.errors import Error from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( HashOutput, MessageSignature, @@ -62,24 +61,31 @@ ExpiredCertificate, FulcioClient, ) -from sigstore._internal.rekor.client import RekorClient, REKOR_V2_API_MAJOR_VERSION, REKOR_V1_API_MAJOR_VERSION +from sigstore._internal.rekor.client import ( + REKOR_V1_API_MAJOR_VERSION, + 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 from sigstore._internal.timestamp import TimestampAuthorityClient, TimestampError from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot from sigstore._utils import sha256_digest from sigstore.models import Bundle from sigstore.oidc import ExpiredIdentity, IdentityToken -from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 -from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 _logger = logging.getLogger(__name__) -def key_to_details(key: ec.EllipticCurvePrivateKey | rsa.RSAPrivateKey) -> v1.PublicKeyDetails: - ''' +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 PKIX_ECDSA_P384_SHA_256. - ''' - if isinstance(key, ec.EllipticCurvePrivateKey) and isinstance(key.curve, ec.SECP256R1): + """ + if isinstance(key, ec.EllipticCurvePrivateKey) and isinstance( + key.curve, ec.SECP256R1 + ): return v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_256 else: raise Exception("unsupported key type") @@ -335,9 +341,9 @@ def sign_artifact( format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ), - key_details=key_to_details(self._private_key) + key_details=key_to_details(self._private_key), ), - ) + ), ) ) @@ -403,8 +409,10 @@ def _from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext: signing_config = trust_config.signing_config return cls( fulcio=FulcioClient(signing_config.get_fulcio_url()), - rekor=RekorClient(signing_config.get_tlog_urls()[ - 0], signing_config._inner.rekor_tlog_urls[0].major_api_version), + rekor=RekorClient( + signing_config.get_tlog_urls()[0], + signing_config._inner.rekor_tlog_urls[0].major_api_version, + ), 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 6c53f2141..6e5add543 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -19,17 +19,16 @@ from __future__ import annotations import base64 -import json import logging from datetime import datetime, timezone from typing import cast 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 -from cryptography.hazmat.primitives import serialization from OpenSSL.crypto import ( X509, X509Store, @@ -41,10 +40,11 @@ from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError -from sigstore import dsse -from sigstore import models +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, ) @@ -55,8 +55,7 @@ from sigstore.hashes import Hashed from sigstore.models import Bundle from sigstore.verify.policy import VerificationPolicy -from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 -from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 + _logger = logging.getLogger(__name__) # Limit the number of timestamps to prevent DoS @@ -67,6 +66,7 @@ # timestamps to consider a signature valid. VERIFY_TIMESTAMP_THRESHOLD: int = 1 + class Verifier: """ The primary API for verification operations. @@ -508,7 +508,7 @@ def verify_artifact( # the other bundle materials (and input being verified). entry = bundle.log_entry - if entry._kind_version.version == '0.0.2': + if entry._kind_version.version == "0.0.2": actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) expected_body = v2.Entry( kind=entry._kind_version.kind, @@ -517,7 +517,7 @@ def verify_artifact( 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 + digest=bundle._inner.message_signature.message_digest.digest, ), signature=v2.Signature( content=bundle._inner.message_signature.signature, @@ -528,11 +528,11 @@ def verify_artifact( format=serialization.PublicFormat.SubjectPublicKeyInfo, ) ), - key_details=models.DEFAULT_KEY_DETAILS - ) - ) + key_details=models.DEFAULT_KEY_DETAILS, + ), + ), ) - ) + ), ) else: expected_body = _hashedrekord_from_parts( diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 85fb3eb64..057b35e50 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -25,7 +25,7 @@ from sigstore._internal.trust import CertificateAuthority from sigstore.dsse import StatementBuilder, Subject from sigstore.errors import VerificationError -from sigstore.models import Bundle, LogEntry +from sigstore.models import Bundle from sigstore.verify import policy from sigstore.verify.verifier import Verifier From 7eaa09046be4f2da82701bf0a1f32c05e9d0b8ab Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 04:35:01 +0000 Subject: [PATCH 55/81] cleanup Signed-off-by: Ramon Petgrave --- test_config_tiles.json | 95 ----------------------------- test_config_tiles_local_tsa.json | 74 ---------------------- test_config_tiles_sigstage_tsa.json | 71 --------------------- 3 files changed, 240 deletions(-) delete mode 100644 test_config_tiles.json delete mode 100644 test_config_tiles_local_tsa.json delete mode 100644 test_config_tiles_sigstage_tsa.json diff --git a/test_config_tiles.json b/test_config_tiles.json deleted file mode 100644 index 147274a91..000000000 --- a/test_config_tiles.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", - "trustedRoot": { - "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", - "tlogs": [], - "ctlogs": [ - { - "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=" - } - } - ], - "certificateAuthorities": [ - { - "subject": { - "organization": "sigstore" - }, - "certChain": { - "certificates": [ - { - "rawBytes": "MIICGDCCAb2gAwIBAgIUGmDPyjHNmBovOIehfnpFqtErprMwCgYIKoZIzj0EAwIwaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMB4XDTI1MDUwNTAxNTYzNVoXDTM1MDUwNTAxNTYzNVowaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1/yazNuKwk5mM1806HIrOQly+RfphoKT+JfwIXq5hkXMoN4mRw3KnYVvlL4SJTHru4R+hMg9rs/Y8jJT/0wqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHIeBqzKwE0jtruNyuKAfmwVkURWMAoGCCqGSM49BAMCA0kAMEYCIQCgsaOL+r+h7xFTzoUi6jdzMjZrtmromhkJW8mugHbNXQIhAIg1tI2zcxkXnXknRxj5ewg+As/PrxMrpndcheb729ew" - } - ] - }, - "validFor": { - "start": "2025-05-05T01:56:35Z" - } - } - ], - "timestampAuthorities": [ - { - "subject": { - "organization": "local", - "commonName": "Test TSA Root" - }, - "certChain": { - "certificates": [ - { - "rawBytes": "MIIBzDCCAXKgAwIBAgIUU/M3DoMFZcR5T7y29ZPA39Wj/ewwCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNTA1MDUwMTUzMzNaFw0zNDA1MDUwMTU2MzNaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT0s7X+aQdA81wkpwq1LKcqIefTPQSp8XKi+dC6oQDltHzbbMZ+o4NY7fGvQw3o0e0EOeor33CazjjHyJQBYEF3o2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFMqjQt5WWV5v+K5Yiu3OfE3ev8XyMB8GA1UdIwQYMBaAFHfGiMEdzXncE/BqBleICJMnwA0lMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0gAMEUCIEwZI1UdKcUIY5tcVjfqDuwyW2ITNIYcTeP+7KFbyv4bAiEAkeUqntzo/b8G5NB9YcuiqlAVjPe50m6/L8ryaWA9JV4=" - }, - { - "rawBytes": "MIIB0jCCAXigAwIBAgIUE2CFQQYNTCJpjohFNWJKMQEz/uEwCgYIKoZIzj0EAwIwKDEOMAwGA1UEChMFbG9jYWwxFjAUBgNVBAMTDVRlc3QgVFNBIFJvb3QwHhcNMjUwNTA1MDE1MTMzWhcNMzUwNTA1MDE1NjMzWjAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAWgRk3P+C+TGYKnNsWRipyKTtKhLmqDI4xl1AQ4LHk8JmtJ5NZnlnFMoZ5Dh9pYaDZe59seCfxkARKUHsUxEaN4MHYwDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHfGiMEdzXncE/BqBleICJMnwA0lMB8GA1UdIwQYMBaAFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0gAMEUCIGyZjfsEnIKv7xX9X2BR8qFzVFmyPjTvY9axP3Kk8xbxAiEA8V30nzomK6vQ1IAnDK+s/IPKrFoSXYTUfjgf1J5/tL8=" - }, - { - "rawBytes": "MIIBkjCCATmgAwIBAgITXXzm8g99fDSDWKjK2VQ9/HzYqzAKBggqhkjOPQQDAjAoMQ4wDAYDVQQKEwVsb2NhbDEWMBQGA1UEAxMNVGVzdCBUU0EgUm9vdDAeFw0yNTA1MDUwMTUxMzNaFw0zNTA1MDUwMTU2MzNaMCgxDjAMBgNVBAoTBWxvY2FsMRYwFAYDVQQDEw1UZXN0IFRTQSBSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC8KZqAMHSC5JQIqjcD5lwCr4XIq+daVSXJUq+uRwfJSEsdUrbSZsH3RScqJEVHUQPD+YW5gmKVlLl+RvV8GMT6NCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0cAMEQCIHLlhj2iI75fbPnHx8ocCOpCs/khsUrNIXL0foWqco+wAiBaW/38SleBAfofRZ7OErOinRQHvXvioWM6RqIXW50G1w==" - } - ] - }, - "validFor": { - "start": "2025-05-05T01:51:33Z", - "end": "2035-03-28T09:14:06Z" - } - }, - { - "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", - "end": "2035-03-28T09:14:06Z" - } - } - ] - }, - "signingConfig": { - "caUrl": "https://fulcio.sigstore.dev", - "tlogUrls": [ - "http://rpetgrave-1.c.googlers.com:3003" - ], - "tsaUrls": [ - "http://rpetgrave-1.c.googlers.com:3004/api/v1/timestamp", - "https://timestamp.sigstage.dev/api/v1/timestamp" - ] - } -} \ No newline at end of file diff --git a/test_config_tiles_local_tsa.json b/test_config_tiles_local_tsa.json deleted file mode 100644 index f4b57aa68..000000000 --- a/test_config_tiles_local_tsa.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", - "trustedRoot": { - "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", - "tlogs": [], - "ctlogs": [ - { - "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=" - } - } - ], - "certificateAuthorities": [ - { - "subject": { - "organization": "sigstore" - }, - "certChain": { - "certificates": [ - { - "rawBytes": "MIICGDCCAb2gAwIBAgIUGmDPyjHNmBovOIehfnpFqtErprMwCgYIKoZIzj0EAwIwaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMB4XDTI1MDUwNTAxNTYzNVoXDTM1MDUwNTAxNTYzNVowaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1/yazNuKwk5mM1806HIrOQly+RfphoKT+JfwIXq5hkXMoN4mRw3KnYVvlL4SJTHru4R+hMg9rs/Y8jJT/0wqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHIeBqzKwE0jtruNyuKAfmwVkURWMAoGCCqGSM49BAMCA0kAMEYCIQCgsaOL+r+h7xFTzoUi6jdzMjZrtmromhkJW8mugHbNXQIhAIg1tI2zcxkXnXknRxj5ewg+As/PrxMrpndcheb729ew" - } - ] - }, - "validFor": { - "start": "2025-05-05T01:56:35Z" - } - } - ], - "timestampAuthorities": [ - { - "subject": { - "organization": "local", - "commonName": "Test TSA Root" - }, - "certChain": { - "certificates": [ - { - "rawBytes": "MIIBzDCCAXKgAwIBAgIUU/M3DoMFZcR5T7y29ZPA39Wj/ewwCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNTA1MDUwMTUzMzNaFw0zNDA1MDUwMTU2MzNaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT0s7X+aQdA81wkpwq1LKcqIefTPQSp8XKi+dC6oQDltHzbbMZ+o4NY7fGvQw3o0e0EOeor33CazjjHyJQBYEF3o2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFMqjQt5WWV5v+K5Yiu3OfE3ev8XyMB8GA1UdIwQYMBaAFHfGiMEdzXncE/BqBleICJMnwA0lMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0gAMEUCIEwZI1UdKcUIY5tcVjfqDuwyW2ITNIYcTeP+7KFbyv4bAiEAkeUqntzo/b8G5NB9YcuiqlAVjPe50m6/L8ryaWA9JV4=" - }, - { - "rawBytes": "MIIB0jCCAXigAwIBAgIUE2CFQQYNTCJpjohFNWJKMQEz/uEwCgYIKoZIzj0EAwIwKDEOMAwGA1UEChMFbG9jYWwxFjAUBgNVBAMTDVRlc3QgVFNBIFJvb3QwHhcNMjUwNTA1MDE1MTMzWhcNMzUwNTA1MDE1NjMzWjAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAWgRk3P+C+TGYKnNsWRipyKTtKhLmqDI4xl1AQ4LHk8JmtJ5NZnlnFMoZ5Dh9pYaDZe59seCfxkARKUHsUxEaN4MHYwDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHfGiMEdzXncE/BqBleICJMnwA0lMB8GA1UdIwQYMBaAFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0gAMEUCIGyZjfsEnIKv7xX9X2BR8qFzVFmyPjTvY9axP3Kk8xbxAiEA8V30nzomK6vQ1IAnDK+s/IPKrFoSXYTUfjgf1J5/tL8=" - }, - { - "rawBytes": "MIIBkjCCATmgAwIBAgITXXzm8g99fDSDWKjK2VQ9/HzYqzAKBggqhkjOPQQDAjAoMQ4wDAYDVQQKEwVsb2NhbDEWMBQGA1UEAxMNVGVzdCBUU0EgUm9vdDAeFw0yNTA1MDUwMTUxMzNaFw0zNTA1MDUwMTU2MzNaMCgxDjAMBgNVBAoTBWxvY2FsMRYwFAYDVQQDEw1UZXN0IFRTQSBSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC8KZqAMHSC5JQIqjcD5lwCr4XIq+daVSXJUq+uRwfJSEsdUrbSZsH3RScqJEVHUQPD+YW5gmKVlLl+RvV8GMT6NCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIPFlmXBxvMZviBL3dMXVuPXd5I3MAoGCCqGSM49BAMCA0cAMEQCIHLlhj2iI75fbPnHx8ocCOpCs/khsUrNIXL0foWqco+wAiBaW/38SleBAfofRZ7OErOinRQHvXvioWM6RqIXW50G1w==" - } - ] - }, - "validFor": { - "start": "2025-05-05T01:51:33Z", - "end": "2035-03-28T09:14:06Z" - } - } - ] - }, - "signingConfig": { - "caUrl": "https://fulcio.sigstore.dev", - "tlogUrls": [ - "http://rpetgrave-1.c.googlers.com:3003" - ], - "tsaUrls": [ - "http://rpetgrave-1.c.googlers.com:3004/api/v1/timestamp" - ] - } -} \ No newline at end of file diff --git a/test_config_tiles_sigstage_tsa.json b/test_config_tiles_sigstage_tsa.json deleted file mode 100644 index f89e12c43..000000000 --- a/test_config_tiles_sigstage_tsa.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", - "trustedRoot": { - "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", - "tlogs": [], - "ctlogs": [ - { - "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=" - } - } - ], - "certificateAuthorities": [ - { - "subject": { - "organization": "sigstore" - }, - "certChain": { - "certificates": [ - { - "rawBytes": "MIICGDCCAb2gAwIBAgIUGmDPyjHNmBovOIehfnpFqtErprMwCgYIKoZIzj0EAwIwaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMB4XDTI1MDUwNTAxNTYzNVoXDTM1MDUwNTAxNTYzNVowaDEMMAoGA1UEBhMDVVNBMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIS2lya2xhbmQxFTATBgNVBAkTDDc2NyA2dGggU3QgUzEOMAwGA1UEERMFOTgwMzMxETAPBgNVBAoTCHNpZ3N0b3JlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1/yazNuKwk5mM1806HIrOQly+RfphoKT+JfwIXq5hkXMoN4mRw3KnYVvlL4SJTHru4R+hMg9rs/Y8jJT/0wqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHIeBqzKwE0jtruNyuKAfmwVkURWMAoGCCqGSM49BAMCA0kAMEYCIQCgsaOL+r+h7xFTzoUi6jdzMjZrtmromhkJW8mugHbNXQIhAIg1tI2zcxkXnXknRxj5ewg+As/PrxMrpndcheb729ew" - } - ] - }, - "validFor": { - "start": "2025-05-05T01:56:35Z" - } - } - ], - "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", - "end": "2035-03-28T09:14:06Z" - } - } - ] - }, - "signingConfig": { - "caUrl": "https://fulcio.sigstore.dev", - "tlogUrls": [ - "http://rpetgrave-1.c.googlers.com:3003" - ], - "tsaUrls": [ - "https://timestamp.sigstage.dev/api/v1/timestamp" - ] - } -} \ No newline at end of file From 609c974c2849e62e87d95561917fda322203dab4 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 04:37:26 +0000 Subject: [PATCH 56/81] add trust_configs Signed-off-by: Ramon Petgrave --- trust_config.json | 137 +++++++++++++++++++++++++++++++++++++++++++ trust_config_v2.json | 137 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 trust_config.json create mode 100644 trust_config_v2.json 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" +} From 203b9c02f114c0f902cddbb635d358425c3612cd Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 04:55:20 +0000 Subject: [PATCH 57/81] merge Signed-off-by: Ramon Petgrave --- sigstore/models.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sigstore/models.py b/sigstore/models.py index 9155bb49d..e1f65dd52 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -251,11 +251,6 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: log_index=tlog_entry.log_index, inclusion_proof=parsed_inclusion_proof, inclusion_promise=inclusion_promise, - inclusion_promise=B64Str( - base64.b64encode( - tlog_entry.inclusion_promise.signed_entry_timestamp - ).decode() - ), _kind_version=tlog_entry.kind_version, ) From 687274972759ca843585490913db0116b4896e44 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 05:13:24 +0000 Subject: [PATCH 58/81] 3.10 style types Signed-off-by: Ramon Petgrave --- .../dev/sigstore/common/v1/__init__.py | 18 +++---- .../dev/sigstore/rekor/v1/__init__.py | 4 +- .../dev/sigstore/rekor/v2/__init__.py | 50 ++++++++----------- .../rekor_tiles/google/api/__init__.py | 23 ++++----- .../rekor_tiles/io/intoto/__init__.py | 4 +- 5 files changed, 40 insertions(+), 59 deletions(-) diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py index 2c448fe7c..8f3940eb6 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -11,10 +11,6 @@ from pydantic.dataclasses import dataclass from datetime import datetime -from typing import ( - List, - Optional, -) import betterproto from pydantic import model_validator @@ -204,7 +200,7 @@ class Rfc3161SignedTimestamp(betterproto.Message): @dataclass(eq=False, repr=False) class PublicKey(betterproto.Message): - raw_bytes: Optional[bytes] = betterproto.bytes_field(1, optional=True) + raw_bytes: "bytes | None" = betterproto.bytes_field(1, optional=True) """ DER-encoded public key, encoding method is specified by the key_details attribute. @@ -213,7 +209,7 @@ class PublicKey(betterproto.Message): 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) + valid_for: "TimeRange | None" = betterproto.message_field(3, optional=True) """Optional validity period for this key, *inclusive* of the endpoints.""" @@ -242,7 +238,7 @@ class PublicKeyIdentifier(betterproto.Message): class ObjectIdentifier(betterproto.Message): """An ASN.1 OBJECT IDENTIFIER""" - id: List[int] = betterproto.int32_field(1) + id: "list[int]" = betterproto.int32_field(1) @dataclass(eq=False, repr=False) @@ -268,13 +264,13 @@ class X509Certificate(betterproto.Message): @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") + regexp: "str | None" = 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") + value: "str | None" = betterproto.string_field(3, optional=True, group="identity") """The exact value to match against.""" @model_validator(mode="after") @@ -292,7 +288,7 @@ class X509CertificateChain(betterproto.Message): the purpose of chain building. """ - certificates: List["X509Certificate"] = betterproto.message_field(1) + certificates: "list[X509Certificate]" = betterproto.message_field(1) """ One or more DER-encoded certificates. @@ -312,7 +308,7 @@ class TimeRange(betterproto.Message): """ start: datetime = betterproto.message_field(1) - end: Optional[datetime] = betterproto.message_field(2, optional=True) + end: "datetime | None" = betterproto.message_field(2, optional=True) rebuild_dataclass(HashOutput) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py index ae5debd0c..4b73bbd8a 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -10,8 +10,6 @@ else: from pydantic.dataclasses import dataclass -from typing import List - import betterproto from pydantic.dataclasses import rebuild_dataclass @@ -69,7 +67,7 @@ class InclusionProof(betterproto.Message): 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) + hashes: "list[bytes]" = betterproto.bytes_field(4) """ A list of hashes required to compute the inclusion proof, sorted in order from leaf to root. diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py index 2e24864ee..4356ada86 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -10,12 +10,6 @@ else: from pydantic.dataclasses import dataclass -from typing import ( - Dict, - List, - Optional, -) - import betterproto import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf import grpclib @@ -43,14 +37,14 @@ 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( + public_key: "PublicKey | None" = 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"] = ( + x509_certificate: "__common_v1__.X509Certificate | None" = ( betterproto.message_field(2, optional=True, group="verifier") ) """DER-encoded certificate""" @@ -102,7 +96,7 @@ class DsseRequestV002(betterproto.Message): envelope: "____io_intoto__.Envelope" = betterproto.message_field(1) """A DSSE envelope""" - verifiers: List["Verifier"] = betterproto.message_field(2) + verifiers: "list[Verifier]" = betterproto.message_field(2) """ All necessary verification material to verify all signatures embedded in the envelope """ @@ -113,7 +107,7 @@ 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: "list[Signature]" = betterproto.message_field(2) """ Signatures and their associated verification material used to verify the payload """ @@ -139,10 +133,10 @@ class Entry(betterproto.Message): 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") + hashed_rekord_v0_0_2: "HashedRekordLogEntryV002 | None" = betterproto.message_field( + 1, optional=True, group="spec" ) - dsse_v0_0_2: Optional["DsseLogEntryV002"] = betterproto.message_field( + dsse_v0_0_2: "DsseLogEntryV002 | None" = betterproto.message_field( 2, optional=True, group="spec" ) @@ -155,10 +149,10 @@ def check_oneof(cls, values): class CreateEntryRequest(betterproto.Message): """Create a new HashedRekord or DSSE""" - hashed_rekord_request_v0_0_2: Optional["HashedRekordRequestV002"] = ( + hashed_rekord_request_v0_0_2: "HashedRekordRequestV002 | None" = ( betterproto.message_field(1, optional=True, group="spec") ) - dsse_request_v0_0_2: Optional["DsseRequestV002"] = betterproto.message_field( + dsse_request_v0_0_2: "DsseRequestV002 | None" = betterproto.message_field( 2, optional=True, group="spec" ) @@ -199,9 +193,9 @@ async def create_entry( self, create_entry_request: "CreateEntryRequest", *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, + timeout: "float | None" = None, + deadline: "Deadline | None" = None, + metadata: "MetadataLike | None" = None, ) -> "_v1__.TransparencyLogEntry": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/CreateEntry", @@ -216,9 +210,9 @@ async def get_tile( self, tile_request: "TileRequest", *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, + timeout: "float | None" = None, + deadline: "Deadline | None" = None, + metadata: "MetadataLike | None" = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetTile", @@ -233,9 +227,9 @@ async def get_entry_bundle( self, entry_bundle_request: "EntryBundleRequest", *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, + timeout: "float | None" = None, + deadline: "Deadline | None" = None, + metadata: "MetadataLike | None" = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle", @@ -250,9 +244,9 @@ async def get_checkpoint( self, betterproto_lib_pydantic_google_protobuf_empty: "betterproto_lib_pydantic_google_protobuf.Empty", *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, + timeout: "float | None" = None, + deadline: "Deadline | None" = None, + metadata: "MetadataLike | None" = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint", @@ -317,7 +311,7 @@ async def __rpc_get_checkpoint( response = await self.get_checkpoint(request) await stream.send_message(response) - def __mapping__(self) -> Dict[str, grpclib.const.Handler]: + def __mapping__(self) -> "dict[str, grpclib.const.Handler]": return { "/dev.sigstore.rekor.v2.Rekor/CreateEntry": grpclib.const.Handler( self.__rpc_create_entry, diff --git a/sigstore/_internal/rekor_tiles/google/api/__init__.py b/sigstore/_internal/rekor_tiles/google/api/__init__.py index d60e85e96..0844578a2 100644 --- a/sigstore/_internal/rekor_tiles/google/api/__init__.py +++ b/sigstore/_internal/rekor_tiles/google/api/__init__.py @@ -10,11 +10,6 @@ else: from pydantic.dataclasses import dataclass -from typing import ( - List, - Optional, -) - import betterproto import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf from pydantic import model_validator @@ -117,7 +112,7 @@ class Http(betterproto.Message): to one or more HTTP REST API methods. """ - rules: List["HttpRule"] = betterproto.message_field(1) + rules: "list[HttpRule]" = betterproto.message_field(1) """ A list of HTTP configuration rules that apply to individual API methods. @@ -412,27 +407,27 @@ class HttpRule(betterproto.Message): details. """ - get: Optional[str] = betterproto.string_field(2, optional=True, group="pattern") + get: "str | None" = betterproto.string_field(2, optional=True, group="pattern") """ Maps to HTTP GET. Used for listing and getting information about resources. """ - put: Optional[str] = betterproto.string_field(3, optional=True, group="pattern") + put: "str | None" = betterproto.string_field(3, optional=True, group="pattern") """Maps to HTTP PUT. Used for replacing a resource.""" - post: Optional[str] = betterproto.string_field(4, optional=True, group="pattern") + post: "str | None" = betterproto.string_field(4, optional=True, group="pattern") """ Maps to HTTP POST. Used for creating a resource or performing an action. """ - delete: Optional[str] = betterproto.string_field(5, optional=True, group="pattern") + delete: "str | None" = betterproto.string_field(5, optional=True, group="pattern") """Maps to HTTP DELETE. Used for deleting a resource.""" - patch: Optional[str] = betterproto.string_field(6, optional=True, group="pattern") + patch: "str | None" = betterproto.string_field(6, optional=True, group="pattern") """Maps to HTTP PATCH. Used for updating a resource.""" - custom: Optional["CustomHttpPattern"] = betterproto.message_field( + custom: "CustomHttpPattern | None" = betterproto.message_field( 8, optional=True, group="pattern" ) """ @@ -462,7 +457,7 @@ class HttpRule(betterproto.Message): message type. """ - additional_bindings: List["HttpRule"] = betterproto.message_field(11) + additional_bindings: "list[HttpRule]" = betterproto.message_field(11) """ Additional HTTP bindings for the selector. Nested bindings must not contain an `additional_bindings` field themselves (that is, @@ -541,7 +536,7 @@ class HttpBody(betterproto.Message): data: bytes = betterproto.bytes_field(2) """The HTTP request/response body as raw binary.""" - extensions: List["betterproto_lib_pydantic_google_protobuf.Any"] = ( + extensions: "list[betterproto_lib_pydantic_google_protobuf.Any]" = ( betterproto.message_field(3) ) """ diff --git a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py index 919957b55..9974f2b40 100644 --- a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -10,8 +10,6 @@ else: from pydantic.dataclasses import dataclass -from typing import List - import betterproto from pydantic.dataclasses import rebuild_dataclass @@ -32,7 +30,7 @@ class Envelope(betterproto.Message): REQUIRED. """ - signatures: List["Signature"] = betterproto.message_field(3) + signatures: "list[Signature]" = betterproto.message_field(3) """ Signature over: PAE(type, payload) From c3b32c99a57793e4682ff7b390ed7e08563c588c Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 05:19:35 +0000 Subject: [PATCH 59/81] reformat Signed-off-by: Ramon Petgrave --- sigstore/_cli.py | 3 +- sigstore/rekorv2.py | 544 ------------------------------- test/integration/cli/conftest.py | 2 +- test/unit/conftest.py | 3 +- 4 files changed, 3 insertions(+), 549 deletions(-) delete mode 100644 sigstore/rekorv2.py diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 480512e63..d7153a54c 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -22,7 +22,7 @@ import sys from dataclasses import dataclass from pathlib import Path -from typing import Any, NoReturn, Optional, TextIO, Union +from typing import Any, NoReturn, Optional, TextIO, TypeAlias, Union from cryptography.hazmat.primitives.serialization import Encoding from cryptography.x509 import load_pem_x509_certificate @@ -33,7 +33,6 @@ Bundle as RawBundle, ) from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm -from typing_extensions import TypeAlias from sigstore import __version__, dsse from sigstore._internal.fulcio.client import ExpiredCertificate diff --git a/sigstore/rekorv2.py b/sigstore/rekorv2.py deleted file mode 100644 index 0e4eac2d5..000000000 --- a/sigstore/rekorv2.py +++ /dev/null @@ -1,544 +0,0 @@ -# TODO: copypasted from https://github.com/trailofbits/sigstore-rekor-types/pull/168 - -# generated by datamodel-codegen: -# filename: rekor_service.swagger.json -# version: 0.30.1 - -from __future__ import annotations - -from enum import Enum -from typing import Any, List, Optional - -from pydantic import BaseModel, ConfigDict, Field, RootModel, StrictInt, StrictStr -from sigstore_protobuf_specs.dev.sigstore.common import v1 - - -class Model(RootModel[Any]): - model_config = ConfigDict( - populate_by_name=True, - ) - root: Any - - -class IointotoSignature(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - sig: Optional[str] = Field( - default=None, - description="Signature itself. (In JSON, this is encoded as base64.)\nREQUIRED.", - ) - keyid: Optional[StrictStr] = Field( - default=None, - description="*Unauthenticated* hint identifying which public key was used.\nOPTIONAL.", - ) - - -class ProtobufAny(BaseModel): - """`Any` contains an arbitrary serialized protocol buffer message along with a - URL that describes the type of the serialized message. - - Protobuf library provides support to pack/unpack Any values in the form - of utility functions or additional generated methods of the Any type. - - Example 1: Pack and unpack a message in C++. - - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } - - Example 2: Pack and unpack a message in Java. - - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } - // or ... - if (any.isSameTypeAs(Foo.getDefaultInstance())) { - foo = any.unpack(Foo.getDefaultInstance()); - } - - Example 3: Pack and unpack a message in Python. - - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... - - Example 4: Pack and unpack a message in Go - - foo := &pb.Foo{...} - any, err := anypb.New(foo) - if err != nil { - ... - } - ... - foo := &pb.Foo{} - if err := any.UnmarshalTo(foo); err != nil { - ... - } - - The pack methods provided by protobuf library will by default use - 'type.googleapis.com/full.type.name' as the type URL and the unpack - methods only use the fully qualified type name after the last '/' - in the type URL, for example "foo.bar.com/x/y.z" will yield type - name "y.z". - - JSON - ==== - The JSON representation of an `Any` value uses the regular - representation of the deserialized, embedded message, with an - additional field `@type` which contains the type URL. Example: - - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } - - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } - - If the embedded message type is well-known and has a custom JSON - representation, that representation will be embedded adding a field - `value` which holds the custom JSON in addition to the `@type` - field. Example (for message [google.protobuf.Duration][]): - - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - """ - - model_config = ConfigDict( - populate_by_name=True, - ) - field_type: Optional[StrictStr] = Field( - default=None, - alias="@type", - description='A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none "/" character. The last segment of the URL\'s path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading "." is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n URL, or have them precompiled into a binary to avoid any\n lookup. Therefore, binary compatibility needs to be preserved\n on changes to types. (Use versioned type names to manage\n breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics.', - ) - - -class Rekorv2PublicKey(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - raw_bytes: str = Field(..., alias="rawBytes", title="DER-encoded public key") - - -class RpcStatus(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - code: Optional[StrictInt] = None - message: Optional[StrictStr] = None - details: Optional[List[ProtobufAny]] = None - - -class V1Checkpoint(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - envelope: StrictStr - - -class V1InclusionPromise(BaseModel): - """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. - """ - - model_config = ConfigDict( - populate_by_name=True, - ) - signed_entry_timestamp: str = Field(..., alias="signedEntryTimestamp") - - -class V1InclusionProof(BaseModel): - """InclusionProof is the proof returned from the transparency log. Can - be used for offline or online verification against the log. - """ - - model_config = ConfigDict( - populate_by_name=True, - ) - log_index: StrictStr = Field( - ..., - alias="logIndex", - description="The index of the entry in the tree it was written to.", - ) - root_hash: str = Field( - ..., - alias="rootHash", - description="The hash digest stored at the root of the merkle tree at the time\nthe proof was generated.", - ) - tree_size: StrictStr = Field( - ..., - alias="treeSize", - description="The size of the merkle tree at the time the proof was generated.", - ) - hashes: List[str] = Field( - ..., - description="A list of hashes required to compute the inclusion proof, sorted\nin order from leaf to root.\nNote that leaf and root hashes are not included.\nThe root hash is available separately in this message, and the\nleaf hash should be calculated by the client.", - ) - checkpoint: V1Checkpoint = Field( - ..., - description="Signature of the tree head, as of the time of this proof was\ngenerated. See above info on 'Checkpoint' for more details.", - ) - - -class V1KindVersion(BaseModel): - """KindVersion contains the entry's kind and api version.""" - - model_config = ConfigDict( - populate_by_name=True, - ) - kind: StrictStr = Field( - ..., - title="Kind is the type of entry being stored in the log.\nSee here for a list: https://github.com/sigstore/rekor/tree/main/pkg/types", - ) - version: StrictStr = Field(..., description="The specific api version of the type.") - - -class V1LogId(BaseModel): - """LogId captures the identity of a transparency log.""" - - model_config = ConfigDict( - populate_by_name=True, - ) - key_id: str = Field( - ..., - alias="keyId", - description="The unique identity of the log, represented by its public key.", - ) - - -class V1PublicKeyDetails(str, 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). - - - PKCS1_RSA_PKCS1V5: RSA - - See RFC8017 - - PKCS1_RSA_PSS: See RFC8017 - - PKIX_RSA_PKCS1V15_2048_SHA256: RSA public key in PKIX format, PKCS#1v1.5 signature - - PKIX_RSA_PSS_2048_SHA256: RSA public key in PKIX format, RSASSA-PSS signature - - See RFC4055 - - PKIX_ECDSA_P256_HMAC_SHA_256: ECDSA - - See RFC6979 - - PKIX_ECDSA_P256_SHA_256: See NIST FIPS 186-4 - - PKIX_ED25519: Ed 25519 - - See RFC8032 - - LMS_SHA256: 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. - """ - - public_key_details_unspecified = "PUBLIC_KEY_DETAILS_UNSPECIFIED" - pkcs1_rsa_pkcs1_v5 = "PKCS1_RSA_PKCS1V5" - pkcs1_rsa_pss = "PKCS1_RSA_PSS" - pkix_rsa_pkcs1_v5 = "PKIX_RSA_PKCS1V5" - pkix_rsa_pss = "PKIX_RSA_PSS" - pkix_rsa_pkcs1_v15_2048_sha256 = "PKIX_RSA_PKCS1V15_2048_SHA256" - pkix_rsa_pkcs1_v15_3072_sha256 = "PKIX_RSA_PKCS1V15_3072_SHA256" - pkix_rsa_pkcs1_v15_4096_sha256 = "PKIX_RSA_PKCS1V15_4096_SHA256" - pkix_rsa_pss_2048_sha256 = "PKIX_RSA_PSS_2048_SHA256" - pkix_rsa_pss_3072_sha256 = "PKIX_RSA_PSS_3072_SHA256" - pkix_rsa_pss_4096_sha256 = "PKIX_RSA_PSS_4096_SHA256" - pkix_ecdsa_p256_hmac_sha_256 = "PKIX_ECDSA_P256_HMAC_SHA_256" - pkix_ecdsa_p256_sha_256 = "PKIX_ECDSA_P256_SHA_256" - pkix_ecdsa_p384_sha_384 = "PKIX_ECDSA_P384_SHA_384" - pkix_ecdsa_p521_sha_512 = "PKIX_ECDSA_P521_SHA_512" - pkix_ed25519 = "PKIX_ED25519" - pkix_ed25519_ph = "PKIX_ED25519_PH" - lms_sha256 = "LMS_SHA256" - lmots_sha256 = "LMOTS_SHA256" - - -class V1TransparencyLogEntry(BaseModel): - """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. - """ - - model_config = ConfigDict( - populate_by_name=True, - ) - log_index: StrictStr = Field( - ..., - alias="logIndex", - description="The global index of the entry, used when querying the log by index.", - ) - log_id: V1LogId = Field( - ..., alias="logId", description="The unique identifier of the log." - ) - kind_version: V1KindVersion = Field( - ..., - alias="kindVersion", - description="The kind (type) and version of the object associated with this\nentry. These values are required to construct the entry during\nverification.", - ) - integrated_time: StrictStr = Field( - ..., - alias="integratedTime", - description="The UNIX timestamp from the log when the entry was persisted.\nThe integration time MUST NOT be trusted if inclusion_promise\nis omitted.", - ) - inclusion_promise: Optional[V1InclusionPromise] = Field( - default=None, - alias="inclusionPromise", - description="The inclusion promise/signed entry timestamp from the log.\nRequired for v0.1 bundles, and MUST be verified.\nOptional for >= v0.2 bundles if another suitable source of\ntime is present (such as another source of signed time,\nor the current system time for long-lived certificates).\nMUST be verified if no other suitable source of time is present,\nand SHOULD be verified otherwise.", - ) - inclusion_proof: V1InclusionProof = Field( - ..., - alias="inclusionProof", - description="The inclusion proof can be used for offline or online verification\nthat the entry was appended to the log, and that the log has not been\naltered.", - ) - canonicalized_body: Optional[str] = Field( - default=None, - alias="canonicalizedBody", - description='Optional. The canonicalized transparency log entry, used to\nreconstruct the Signed Entry Timestamp (SET) during verification.\nThe contents of this field are the same as the `body` field in\na Rekor response, meaning that it does **not** include the "full"\ncanonicalized form (of log index, ID, etc.) which are\nexposed as separate fields. The verifier is responsible for\ncombining the `canonicalized_body`, `log_index`, `log_id`,\nand `integrated_time` into the payload that the SET\'s signature\nis generated over.\nThis field is intended to be used in cases where the SET cannot be\nproduced determinisitically (e.g. inconsistent JSON field ordering,\ndiffering whitespace, etc).\n\nIf set, clients MUST verify that the signature referenced in the\n`canonicalized_body` matches the signature provided in the\n`Bundle.content`.\nIf not set, clients are responsible for constructing an equivalent\npayload from other sources to verify the signature.', - ) - - -class V1X509Certificate(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - raw_bytes: str = Field( - ..., alias="rawBytes", description="DER-encoded X.509 certificate." - ) - - -class V2Verifier(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - public_key: Rekorv2PublicKey = Field( - ..., - alias="publicKey", - title="DER-encoded public key. Encoding method is specified by the key_details attribute", - ) - x509_certificate: V1X509Certificate = Field( - ..., - alias="x509Certificate", - title="DER-encoded certificate", - ) - key_details: V1PublicKeyDetails = Field( - ..., - alias="keyDetails", - title="Key encoding and signature algorithm to use for this key", - ) - - -class ApiHttpBody(BaseModel): - """Message that represents an arbitrary HTTP body. It should only be used for - payload formats that can't be represented as JSON, such as raw binary or - an HTML page. - - - This message can be used both in streaming and non-streaming API methods in - the request as well as the response. - - It can be used as a top-level request field, which is convenient if one - wants to extract parameters from either the URL or HTTP template into the - request fields and also want access to the raw HTTP body. - - Example: - message GetResourceRequest { - // A unique request id. - string request_id = 1; - - // The raw HTTP body is bound to this field. - google.api.HttpBody http_body = 2; - - } - - service ResourceService { - rpc GetResource(GetResourceRequest) - returns (google.api.HttpBody); - rpc UpdateResource(google.api.HttpBody) - returns (google.protobuf.Empty); - - } - - Example with streaming methods: - - service CaldavService { - rpc GetCalendar(stream google.api.HttpBody) - returns (stream google.api.HttpBody); - rpc UpdateCalendar(stream google.api.HttpBody) - returns (stream google.api.HttpBody); - - } - - Use of this type only changes how the request and response bodies are - handled, all other features will continue to work unchanged. - - """ - - model_config = ConfigDict( - populate_by_name=True, - ) - content_type: Optional[StrictStr] = Field( - default=None, - alias="contentType", - description="The HTTP Content-Type header value specifying the content type of the body.", - ) - data: Optional[str] = Field( - default=None, - description="The HTTP request/response body as raw binary.", - ) - extensions: Optional[List[ProtobufAny]] = Field( - default=None, - description="Application specific response metadata. Must be set in the first response\nfor streaming APIs.", - ) - - -class IntotoEnvelope(BaseModel): - """An authenticated message of arbitrary type.""" - - model_config = ConfigDict( - populate_by_name=True, - ) - payload: Optional[str] = Field( - default=None, - description="Message to be signed. (In JSON, this is encoded as base64.)\nREQUIRED.", - ) - payload_type: Optional[StrictStr] = Field( - default=None, - alias="payloadType", - description="String unambiguously identifying how to interpret payload.\nREQUIRED.", - ) - signatures: Optional[List[IointotoSignature]] = Field( - default=None, - description='Signature over:\n PAE(type, payload)\nWhere PAE is defined as:\nPAE(type, payload) = "DSSEv1" + SP + LEN(type) + SP + type + SP + LEN(payload) + SP + payload\n+ = concatenation\nSP = ASCII space [0x20]\n"DSSEv1" = ASCII [0x44, 0x53, 0x53, 0x45, 0x76, 0x31]\nLEN(s) = ASCII decimal encoding of the byte length of s, with no leading zeros\nREQUIRED (length >= 1).', - ) - - -class Rekorv2Signature(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - content: str - verifier: V2Verifier - - -class V2DSSERequestV002(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - envelope: IntotoEnvelope = Field(..., title="A DSSE envelope") - verifiers: List[V2Verifier] = Field( - ..., - title="All necessary verification material to verify all signatures embedded in the envelope", - ) - - -# Custom Code - - -class V2HashedRekordRequestV002(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - digest: str = Field(..., title="The hashed data") - signature: Rekorv2Signature = Field( - ..., - title="A single signature over the hashed data with the verifier needed to validate it", - ) - - -class V2CreateEntryRequest(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - hashed_rekord_request_v0_0_2: V2HashedRekordRequestV002 = Field( - ..., - alias="hashedRekordRequestV0_0_2", - ) - dsse_request_v0_0_2: V2DSSERequestV002 = Field(..., alias="dsseRequestV0_0_2") - - -class Entry(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - kind: StrictStr = Field() - api_version: StrictStr = Field(..., alias="apiVersion") - spec: SpecRoot - - -class SpecRoot(RootModel): - root: HashedRekordLogEntryV002Root | DSSELogEntryV002Root - - -class HashedRekordLogEntryV002Root(BaseModel): - hashed_rekord_v0_0_2: HashedRekordLogEntryV002 = Field( - ..., - alias="hashedRekordV0_0_2", - ) - - -class HashedRekordLogEntryV002(BaseModel): - data: v1.HashOutput - signature: Rekorv2Signature - pass - - -class DSSELogEntryV002Root(BaseModel): - dsse_v0_0_2: DSSELogEntryV002 = Field(..., alias="dsseV0_0_2") - - -class DSSELogEntryV002(BaseModel): - pass diff --git a/test/integration/cli/conftest.py b/test/integration/cli/conftest.py index abd2b9cc8..66ae2b79f 100644 --- a/test/integration/cli/conftest.py +++ b/test/integration/cli/conftest.py @@ -11,8 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from collections.abc import Callable from pathlib import Path -from typing import Callable import pytest diff --git a/test/unit/conftest.py b/test/unit/conftest.py index e495557f4..32a5596a0 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -18,10 +18,9 @@ import os import re from collections import defaultdict -from collections.abc import Iterator +from collections.abc import Callable, Iterator from io import BytesIO from pathlib import Path -from typing import Callable from urllib.parse import urlparse import jwt From b2fd3646ec90f96dd1eb435a07f1135bd4a663fd Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 06:05:49 +0000 Subject: [PATCH 60/81] py39 types Signed-off-by: Ramon Petgrave --- .../dev/sigstore/common/v1/__init__.py | 18 ++++--- .../dev/sigstore/rekor/v1/__init__.py | 4 +- .../dev/sigstore/rekor/v2/__init__.py | 50 +++++++++++-------- .../rekor_tiles/google/api/__init__.py | 23 +++++---- .../rekor_tiles/io/intoto/__init__.py | 4 +- 5 files changed, 59 insertions(+), 40 deletions(-) diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py index 8f3940eb6..2c448fe7c 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -11,6 +11,10 @@ from pydantic.dataclasses import dataclass from datetime import datetime +from typing import ( + List, + Optional, +) import betterproto from pydantic import model_validator @@ -200,7 +204,7 @@ class Rfc3161SignedTimestamp(betterproto.Message): @dataclass(eq=False, repr=False) class PublicKey(betterproto.Message): - raw_bytes: "bytes | None" = betterproto.bytes_field(1, optional=True) + raw_bytes: Optional[bytes] = betterproto.bytes_field(1, optional=True) """ DER-encoded public key, encoding method is specified by the key_details attribute. @@ -209,7 +213,7 @@ class PublicKey(betterproto.Message): key_details: "PublicKeyDetails" = betterproto.enum_field(2) """Key encoding and signature algorithm to use for this key.""" - valid_for: "TimeRange | None" = betterproto.message_field(3, optional=True) + valid_for: Optional["TimeRange"] = betterproto.message_field(3, optional=True) """Optional validity period for this key, *inclusive* of the endpoints.""" @@ -238,7 +242,7 @@ class PublicKeyIdentifier(betterproto.Message): class ObjectIdentifier(betterproto.Message): """An ASN.1 OBJECT IDENTIFIER""" - id: "list[int]" = betterproto.int32_field(1) + id: List[int] = betterproto.int32_field(1) @dataclass(eq=False, repr=False) @@ -264,13 +268,13 @@ class X509Certificate(betterproto.Message): @dataclass(eq=False, repr=False) class SubjectAlternativeName(betterproto.Message): type: "SubjectAlternativeNameType" = betterproto.enum_field(1) - regexp: "str | None" = betterproto.string_field(2, optional=True, group="identity") + regexp: Optional[str] = betterproto.string_field(2, optional=True, group="identity") """ A regular expression describing the expected value for the SAN. """ - value: "str | None" = betterproto.string_field(3, optional=True, group="identity") + value: Optional[str] = betterproto.string_field(3, optional=True, group="identity") """The exact value to match against.""" @model_validator(mode="after") @@ -288,7 +292,7 @@ class X509CertificateChain(betterproto.Message): the purpose of chain building. """ - certificates: "list[X509Certificate]" = betterproto.message_field(1) + certificates: List["X509Certificate"] = betterproto.message_field(1) """ One or more DER-encoded certificates. @@ -308,7 +312,7 @@ class TimeRange(betterproto.Message): """ start: datetime = betterproto.message_field(1) - end: "datetime | None" = betterproto.message_field(2, optional=True) + end: Optional[datetime] = betterproto.message_field(2, optional=True) rebuild_dataclass(HashOutput) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py index 4b73bbd8a..ae5debd0c 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -10,6 +10,8 @@ else: from pydantic.dataclasses import dataclass +from typing import List + import betterproto from pydantic.dataclasses import rebuild_dataclass @@ -67,7 +69,7 @@ class InclusionProof(betterproto.Message): 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) + hashes: List[bytes] = betterproto.bytes_field(4) """ A list of hashes required to compute the inclusion proof, sorted in order from leaf to root. diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py index 4356ada86..2e24864ee 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -10,6 +10,12 @@ else: from pydantic.dataclasses import dataclass +from typing import ( + Dict, + List, + Optional, +) + import betterproto import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf import grpclib @@ -37,14 +43,14 @@ class Verifier(betterproto.Message): Either a public key or a X.509 cerificiate with an embedded public key """ - public_key: "PublicKey | None" = betterproto.message_field( + 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: "__common_v1__.X509Certificate | None" = ( + x509_certificate: Optional["__common_v1__.X509Certificate"] = ( betterproto.message_field(2, optional=True, group="verifier") ) """DER-encoded certificate""" @@ -96,7 +102,7 @@ class DsseRequestV002(betterproto.Message): envelope: "____io_intoto__.Envelope" = betterproto.message_field(1) """A DSSE envelope""" - verifiers: "list[Verifier]" = betterproto.message_field(2) + verifiers: List["Verifier"] = betterproto.message_field(2) """ All necessary verification material to verify all signatures embedded in the envelope """ @@ -107,7 +113,7 @@ 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: List["Signature"] = betterproto.message_field(2) """ Signatures and their associated verification material used to verify the payload """ @@ -133,10 +139,10 @@ class Entry(betterproto.Message): class Spec(betterproto.Message): """Spec contains one of the Rekor entry types.""" - hashed_rekord_v0_0_2: "HashedRekordLogEntryV002 | None" = betterproto.message_field( - 1, optional=True, group="spec" + hashed_rekord_v0_0_2: Optional["HashedRekordLogEntryV002"] = ( + betterproto.message_field(1, optional=True, group="spec") ) - dsse_v0_0_2: "DsseLogEntryV002 | None" = betterproto.message_field( + dsse_v0_0_2: Optional["DsseLogEntryV002"] = betterproto.message_field( 2, optional=True, group="spec" ) @@ -149,10 +155,10 @@ def check_oneof(cls, values): class CreateEntryRequest(betterproto.Message): """Create a new HashedRekord or DSSE""" - hashed_rekord_request_v0_0_2: "HashedRekordRequestV002 | None" = ( + hashed_rekord_request_v0_0_2: Optional["HashedRekordRequestV002"] = ( betterproto.message_field(1, optional=True, group="spec") ) - dsse_request_v0_0_2: "DsseRequestV002 | None" = betterproto.message_field( + dsse_request_v0_0_2: Optional["DsseRequestV002"] = betterproto.message_field( 2, optional=True, group="spec" ) @@ -193,9 +199,9 @@ async def create_entry( self, create_entry_request: "CreateEntryRequest", *, - timeout: "float | None" = None, - deadline: "Deadline | None" = None, - metadata: "MetadataLike | None" = None, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None, ) -> "_v1__.TransparencyLogEntry": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/CreateEntry", @@ -210,9 +216,9 @@ async def get_tile( self, tile_request: "TileRequest", *, - timeout: "float | None" = None, - deadline: "Deadline | None" = None, - metadata: "MetadataLike | None" = None, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetTile", @@ -227,9 +233,9 @@ async def get_entry_bundle( self, entry_bundle_request: "EntryBundleRequest", *, - timeout: "float | None" = None, - deadline: "Deadline | None" = None, - metadata: "MetadataLike | None" = None, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle", @@ -244,9 +250,9 @@ async def get_checkpoint( self, betterproto_lib_pydantic_google_protobuf_empty: "betterproto_lib_pydantic_google_protobuf.Empty", *, - timeout: "float | None" = None, - deadline: "Deadline | None" = None, - metadata: "MetadataLike | None" = None, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None, ) -> "____google_api__.HttpBody": return await self._unary_unary( "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint", @@ -311,7 +317,7 @@ async def __rpc_get_checkpoint( response = await self.get_checkpoint(request) await stream.send_message(response) - def __mapping__(self) -> "dict[str, grpclib.const.Handler]": + def __mapping__(self) -> Dict[str, grpclib.const.Handler]: return { "/dev.sigstore.rekor.v2.Rekor/CreateEntry": grpclib.const.Handler( self.__rpc_create_entry, diff --git a/sigstore/_internal/rekor_tiles/google/api/__init__.py b/sigstore/_internal/rekor_tiles/google/api/__init__.py index 0844578a2..d60e85e96 100644 --- a/sigstore/_internal/rekor_tiles/google/api/__init__.py +++ b/sigstore/_internal/rekor_tiles/google/api/__init__.py @@ -10,6 +10,11 @@ else: from pydantic.dataclasses import dataclass +from typing import ( + List, + Optional, +) + import betterproto import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf from pydantic import model_validator @@ -112,7 +117,7 @@ class Http(betterproto.Message): to one or more HTTP REST API methods. """ - rules: "list[HttpRule]" = betterproto.message_field(1) + rules: List["HttpRule"] = betterproto.message_field(1) """ A list of HTTP configuration rules that apply to individual API methods. @@ -407,27 +412,27 @@ class HttpRule(betterproto.Message): details. """ - get: "str | None" = betterproto.string_field(2, optional=True, group="pattern") + get: Optional[str] = betterproto.string_field(2, optional=True, group="pattern") """ Maps to HTTP GET. Used for listing and getting information about resources. """ - put: "str | None" = betterproto.string_field(3, optional=True, group="pattern") + put: Optional[str] = betterproto.string_field(3, optional=True, group="pattern") """Maps to HTTP PUT. Used for replacing a resource.""" - post: "str | None" = betterproto.string_field(4, optional=True, group="pattern") + post: Optional[str] = betterproto.string_field(4, optional=True, group="pattern") """ Maps to HTTP POST. Used for creating a resource or performing an action. """ - delete: "str | None" = betterproto.string_field(5, optional=True, group="pattern") + delete: Optional[str] = betterproto.string_field(5, optional=True, group="pattern") """Maps to HTTP DELETE. Used for deleting a resource.""" - patch: "str | None" = betterproto.string_field(6, optional=True, group="pattern") + patch: Optional[str] = betterproto.string_field(6, optional=True, group="pattern") """Maps to HTTP PATCH. Used for updating a resource.""" - custom: "CustomHttpPattern | None" = betterproto.message_field( + custom: Optional["CustomHttpPattern"] = betterproto.message_field( 8, optional=True, group="pattern" ) """ @@ -457,7 +462,7 @@ class HttpRule(betterproto.Message): message type. """ - additional_bindings: "list[HttpRule]" = betterproto.message_field(11) + additional_bindings: List["HttpRule"] = betterproto.message_field(11) """ Additional HTTP bindings for the selector. Nested bindings must not contain an `additional_bindings` field themselves (that is, @@ -536,7 +541,7 @@ class HttpBody(betterproto.Message): data: bytes = betterproto.bytes_field(2) """The HTTP request/response body as raw binary.""" - extensions: "list[betterproto_lib_pydantic_google_protobuf.Any]" = ( + extensions: List["betterproto_lib_pydantic_google_protobuf.Any"] = ( betterproto.message_field(3) ) """ diff --git a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py index 9974f2b40..919957b55 100644 --- a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -10,6 +10,8 @@ else: from pydantic.dataclasses import dataclass +from typing import List + import betterproto from pydantic.dataclasses import rebuild_dataclass @@ -30,7 +32,7 @@ class Envelope(betterproto.Message): REQUIRED. """ - signatures: "list[Signature]" = betterproto.message_field(3) + signatures: List["Signature"] = betterproto.message_field(3) """ Signature over: PAE(type, payload) From ace0dfbe262d1abf14218a266c4dafbe8860c1ba Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 16:48:31 +0000 Subject: [PATCH 61/81] independent RekorV2Client Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 98 ++++++++++++++++++++++-------- sigstore/sign.py | 64 +++++++++++-------- 2 files changed, 112 insertions(+), 50 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index a320d1314..8ff76af9f 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -26,6 +26,7 @@ import rekor_types import requests +from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1 from sigstore._internal import USER_AGENT from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 @@ -148,25 +149,12 @@ def get( return LogEntry._from_response(resp.json()) def post( - self, - proposed_entry: rekor_types.Hashedrekord - | rekor_types.Dsse - | v2.CreateEntryRequest, + self, proposed_entry: rekor_types.Hashedrekord | rekor_types.Dsse ) -> 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. - if isinstance(proposed_entry, v2.CreateEntryRequest): - payload = proposed_entry.to_dict() - if "hashedRekordRequestV002" in payload: - payload["hashedRekordRequestV0_0_2"] = payload.pop( - "hashedRekordRequestV002" - ) - if "dsseRequestV002" in payload: - payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002") - else: - payload = proposed_entry.model_dump(mode="json", by_alias=True) + payload = proposed_entry.model_dump(mode="json", by_alias=True) _logger.debug(f"proposed: {json.dumps(payload)}") resp: requests.Response = self.session.post(self.url, json=payload) try: @@ -176,10 +164,7 @@ def post( integrated_entry = resp.json() _logger.debug(f"integrated: {integrated_entry}") - if isinstance(proposed_entry, v2.CreateEntryRequest): - return LogEntry._from_dict_rekor(integrated_entry) - else: - return LogEntry._from_response(integrated_entry) + return LogEntry._from_response(integrated_entry) @property def retrieve(self) -> RekorEntriesRetrieve: @@ -236,14 +221,11 @@ def post( class RekorClient: """The internal Rekor client""" - def __init__( - self, url: str, major_api_version: int = REKOR_V1_API_MAJOR_VERSION - ) -> None: + def __init__(self, url: str) -> None: """ Create a new `RekorClient` from the given URL. """ - self.url = f"{url}/api/v{major_api_version}" - self.major_api_version = major_api_version + self.url = f"{url}/api/v1" self.session = requests.Session() self.session.headers.update( { @@ -281,3 +263,71 @@ 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 + ) -> rekor_v1.TransparencyLogEntry: + """ + 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() + if "hashedRekordRequestV002" in payload: + payload["hashedRekordRequestV0_0_2"] = payload.pop( + "hashedRekordRequestV002" + ) + if "dsseRequestV002" in payload: + payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002") + _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 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/sign.py b/sigstore/sign.py index 4b9d963fc..1eefded32 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -64,6 +64,7 @@ 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 @@ -195,13 +196,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}") @@ -311,24 +318,7 @@ def sign_artifact( ) # Create the proposed hashedrekord entry - if self._signing_ctx._rekor.major_api_version == REKOR_V1_API_MAJOR_VERSION: - 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(), - ) - ), - ), - ) - else: + if isinstance(self._signing_ctx._rekor, RekorV2Client): proposed_entry = v2.CreateEntryRequest( hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002( digest=hashed_input.digest, @@ -346,7 +336,23 @@ def sign_artifact( ), ) ) - + else: + 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(), + ) + ), + ), + ) return self._finalize_sign(cert, content, proposed_entry) @@ -359,7 +365,7 @@ def __init__( self, *, fulcio: FulcioClient, - rekor: RekorClient, + rekor: RekorClient | RekorV2Client, trusted_root: TrustedRoot, tsa_clients: list[TimestampAuthorityClient] | None = None, ): @@ -407,12 +413,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], - signing_config._inner.rekor_tlog_urls[0].major_api_version, - ), + rekor=rekor_client, trusted_root=trust_config.trusted_root, tsa_clients=[ TimestampAuthorityClient(url) for url in signing_config.get_tsa_urls() From 7ad3185c39a5ba64b0493dece9999769a3049110 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 17:08:18 +0000 Subject: [PATCH 62/81] cleanup Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 6 ++---- sigstore/sign.py | 20 ++------------------ 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 8ff76af9f..7fb4fb7ee 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -26,7 +26,7 @@ import rekor_types import requests -from sigstore_protobuf_specs.dev.sigstore.rekor import v1 as rekor_v1 +from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import TransparencyLogEntry from sigstore._internal import USER_AGENT from sigstore._internal.rekor_tiles.dev.sigstore.rekor import v2 @@ -290,9 +290,7 @@ def __del__(self) -> None: """ self.session.close() - def create_entry( - self, request: v2.CreateEntryRequest - ) -> rekor_v1.TransparencyLogEntry: + def create_entry(self, request: v2.CreateEntryRequest) -> TransparencyLogEntry: """ Submit a new entry for inclusion in the Rekor log. """ diff --git a/sigstore/sign.py b/sigstore/sign.py index 1eefded32..3167eb344 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -82,14 +82,14 @@ 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 PKIX_ECDSA_P384_SHA_256. + 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("unsupported key type") + raise Exception(f"unsupported key type {key}") class Signer: @@ -300,22 +300,6 @@ def sign_artifact( ), signature=artifact_signature, ) - 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(), - ) - ), - ), - ) # Create the proposed hashedrekord entry if isinstance(self._signing_ctx._rekor, RekorV2Client): From 578758fa2ecf0f755acb2d6ca4e0052695bfb884 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 17:19:13 +0000 Subject: [PATCH 63/81] use HashOutput from protobuf specs Signed-off-by: Ramon Petgrave --- sigstore/verify/verifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 6e5add543..a6272fd5d 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -39,11 +39,13 @@ from pydantic import ValidationError from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError +from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( + HashOutput, +) 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, @@ -189,7 +191,6 @@ def _verify_timestamp_authority( # The Signer sends a hash of the signature as the messageImprint in a TimeStampReq # to the Timestamping Service signature_hash = sha256_digest(bundle.signature).digest - verified_timestamps = [ verified_timestamp for tsr in timestamp_responses @@ -515,7 +516,7 @@ def verify_artifact( api_version=entry._kind_version.version, spec=v2.Spec( hashed_rekord_v0_0_2=v2.HashedRekordLogEntryV002( - data=v1.HashOutput( + data=HashOutput( algorithm=bundle._inner.message_signature.message_digest.algorithm, digest=bundle._inner.message_signature.message_digest.digest, ), From 9a066e8753245cfe43de22c11780b2d99de012b7 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 17:21:42 +0000 Subject: [PATCH 64/81] back to imported HasHOutput Signed-off-by: Ramon Petgrave --- sigstore/verify/verifier.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index a6272fd5d..d515ca8da 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -39,13 +39,11 @@ from pydantic import ValidationError from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError -from sigstore_protobuf_specs.dev.sigstore.common.v1 import ( - HashOutput, -) 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, @@ -516,7 +514,7 @@ def verify_artifact( api_version=entry._kind_version.version, spec=v2.Spec( hashed_rekord_v0_0_2=v2.HashedRekordLogEntryV002( - data=HashOutput( + data=v1.HashOutput( algorithm=bundle._inner.message_signature.message_digest.algorithm, digest=bundle._inner.message_signature.message_digest.digest, ), From a0e6616b8b3b21cabb24561b31a583df1c7e712b Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 18:20:28 +0000 Subject: [PATCH 65/81] refactor the building of the hashedrekord preposed entries Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 59 ++++++++++++++++++++++++++++++ sigstore/sign.py | 40 +++++--------------- 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 7fb4fb7ee..66def3dd3 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,10 +27,14 @@ import rekor_types import requests +from cryptography.hazmat.primitives import serialization +from cryptography.x509 import Certificate from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import TransparencyLogEntry 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__) @@ -241,6 +246,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: """ @@ -314,6 +347,32 @@ def create_entry(self, request: v2.CreateEntryRequest) -> TransparencyLogEntry: _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: """ diff --git a/sigstore/sign.py b/sigstore/sign.py index 3167eb344..e973539a0 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -303,39 +303,17 @@ def sign_artifact( # Create the proposed hashedrekord entry if isinstance(self._signing_ctx._rekor, RekorV2Client): - proposed_entry = v2.CreateEntryRequest( - hashed_rekord_request_v0_0_2=v2.HashedRekordRequestV002( - digest=hashed_input.digest, - signature=v2.Signature( - content=artifact_signature, - verifier=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), - ), - ), - ) + 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 = 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(), - ) - ), - ), + proposed_entry = RekorClient._build_hashed_rekord_request( + hashed_input=hashed_input, + signature=artifact_signature, + certificate=cert, ) return self._finalize_sign(cert, content, proposed_entry) From 895ea51982a44a6d7e616fad809f1a8406644dca Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 18:27:59 +0000 Subject: [PATCH 66/81] specific kindversions when verifying Signed-off-by: Ramon Petgrave --- sigstore/verify/verifier.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index d515ca8da..ccd6832c3 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -39,6 +39,7 @@ from pydantic import ValidationError from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError +from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import KindVersion from sigstore import dsse, models from sigstore._internal.rekor import _hashedrekord_from_parts @@ -66,6 +67,11 @@ # timestamps to consider a signature valid. VERIFY_TIMESTAMP_THRESHOLD: int = 1 +REKOR_V2_BUNDLE_ENTRY_KIND_VERSIONS = [ + KindVersion(kind="hashedrekord", version="0.0.2"), + KindVersion(kind="dsse", version="0.0.2"), +] + class Verifier: """ @@ -507,7 +513,7 @@ def verify_artifact( # the other bundle materials (and input being verified). entry = bundle.log_entry - if entry._kind_version.version == "0.0.2": + if entry._kind_version in REKOR_V2_BUNDLE_ENTRY_KIND_VERSIONS: actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) expected_body = v2.Entry( kind=entry._kind_version.kind, From 2c0508cac7d460f894ddd826822596f256b3cfca Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 19:40:20 +0000 Subject: [PATCH 67/81] import from typing_extensions import TypeAlias Signed-off-by: Ramon Petgrave --- sigstore/_cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index d7153a54c..480512e63 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -22,7 +22,7 @@ import sys from dataclasses import dataclass from pathlib import Path -from typing import Any, NoReturn, Optional, TextIO, TypeAlias, Union +from typing import Any, NoReturn, Optional, TextIO, Union from cryptography.hazmat.primitives.serialization import Encoding from cryptography.x509 import load_pem_x509_certificate @@ -33,6 +33,7 @@ Bundle as RawBundle, ) from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm +from typing_extensions import TypeAlias from sigstore import __version__, dsse from sigstore._internal.fulcio.client import ExpiredCertificate From 686ccf827b26fdcfdc8c0af6f0c09f4762f430ca Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 19:47:52 +0000 Subject: [PATCH 68/81] smaller diff Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 2 ++ test/integration/cli/conftest.py | 2 +- test/unit/conftest.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 66def3dd3..afe1f974c 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -159,8 +159,10 @@ def post( """ Submit a new entry for inclusion in the Rekor log. """ + payload = proposed_entry.model_dump(mode="json", by_alias=True) _logger.debug(f"proposed: {json.dumps(payload)}") + resp: requests.Response = self.session.post(self.url, json=payload) try: resp.raise_for_status() diff --git a/test/integration/cli/conftest.py b/test/integration/cli/conftest.py index 66ae2b79f..abd2b9cc8 100644 --- a/test/integration/cli/conftest.py +++ b/test/integration/cli/conftest.py @@ -11,8 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from collections.abc import Callable from pathlib import Path +from typing import Callable import pytest diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 32a5596a0..e495557f4 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -18,9 +18,10 @@ import os import re from collections import defaultdict -from collections.abc import Callable, Iterator +from collections.abc import Iterator from io import BytesIO from pathlib import Path +from typing import Callable from urllib.parse import urlparse import jwt From b9a8baef53731c7bfd987ae23c69f9e8fdcd031d Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 19:58:17 +0000 Subject: [PATCH 69/81] no google bufs Signed-off-by: Ramon Petgrave --- .../dev/sigstore/rekor/v2/__init__.py | 158 ----- .../_internal/rekor_tiles/google/__init__.py | 0 .../rekor_tiles/google/api/__init__.py | 555 ------------------ 3 files changed, 713 deletions(-) delete mode 100644 sigstore/_internal/rekor_tiles/google/__init__.py delete mode 100644 sigstore/_internal/rekor_tiles/google/api/__init__.py diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py index 2e24864ee..c6a019197 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -11,22 +11,16 @@ from pydantic.dataclasses import dataclass from typing import ( - Dict, List, Optional, ) import betterproto -import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf -import grpclib -from betterproto.grpc.grpclib_server import ServiceBase from pydantic import model_validator from pydantic.dataclasses import rebuild_dataclass -from .....google import api as ____google_api__ from .....io import intoto as ____io_intoto__ from ...common import v1 as __common_v1__ -from .. import v1 as _v1__ @dataclass(eq=False, repr=False) @@ -194,158 +188,6 @@ class EntryBundleRequest(betterproto.Message): """ -class RekorStub(betterproto.ServiceStub): - async def create_entry( - self, - create_entry_request: "CreateEntryRequest", - *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, - ) -> "_v1__.TransparencyLogEntry": - return await self._unary_unary( - "/dev.sigstore.rekor.v2.Rekor/CreateEntry", - create_entry_request, - _v1__.TransparencyLogEntry, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ) - - async def get_tile( - self, - tile_request: "TileRequest", - *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, - ) -> "____google_api__.HttpBody": - return await self._unary_unary( - "/dev.sigstore.rekor.v2.Rekor/GetTile", - tile_request, - ____google_api__.HttpBody, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ) - - async def get_entry_bundle( - self, - entry_bundle_request: "EntryBundleRequest", - *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, - ) -> "____google_api__.HttpBody": - return await self._unary_unary( - "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle", - entry_bundle_request, - ____google_api__.HttpBody, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ) - - async def get_checkpoint( - self, - betterproto_lib_pydantic_google_protobuf_empty: "betterproto_lib_pydantic_google_protobuf.Empty", - *, - timeout: Optional[float] = None, - deadline: Optional["Deadline"] = None, - metadata: Optional["MetadataLike"] = None, - ) -> "____google_api__.HttpBody": - return await self._unary_unary( - "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint", - betterproto_lib_pydantic_google_protobuf_empty, - ____google_api__.HttpBody, - timeout=timeout, - deadline=deadline, - metadata=metadata, - ) - - -class RekorBase(ServiceBase): - async def create_entry( - self, create_entry_request: "CreateEntryRequest" - ) -> "_v1__.TransparencyLogEntry": - raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) - - async def get_tile( - self, tile_request: "TileRequest" - ) -> "____google_api__.HttpBody": - raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) - - async def get_entry_bundle( - self, entry_bundle_request: "EntryBundleRequest" - ) -> "____google_api__.HttpBody": - raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) - - async def get_checkpoint( - self, - betterproto_lib_pydantic_google_protobuf_empty: "betterproto_lib_pydantic_google_protobuf.Empty", - ) -> "____google_api__.HttpBody": - raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) - - async def __rpc_create_entry( - self, - stream: "grpclib.server.Stream[CreateEntryRequest, _v1__.TransparencyLogEntry]", - ) -> None: - request = await stream.recv_message() - response = await self.create_entry(request) - await stream.send_message(response) - - async def __rpc_get_tile( - self, stream: "grpclib.server.Stream[TileRequest, ____google_api__.HttpBody]" - ) -> None: - request = await stream.recv_message() - response = await self.get_tile(request) - await stream.send_message(response) - - async def __rpc_get_entry_bundle( - self, - stream: "grpclib.server.Stream[EntryBundleRequest, ____google_api__.HttpBody]", - ) -> None: - request = await stream.recv_message() - response = await self.get_entry_bundle(request) - await stream.send_message(response) - - async def __rpc_get_checkpoint( - self, - stream: "grpclib.server.Stream[betterproto_lib_pydantic_google_protobuf.Empty, ____google_api__.HttpBody]", - ) -> None: - request = await stream.recv_message() - response = await self.get_checkpoint(request) - await stream.send_message(response) - - def __mapping__(self) -> Dict[str, grpclib.const.Handler]: - return { - "/dev.sigstore.rekor.v2.Rekor/CreateEntry": grpclib.const.Handler( - self.__rpc_create_entry, - grpclib.const.Cardinality.UNARY_UNARY, - CreateEntryRequest, - _v1__.TransparencyLogEntry, - ), - "/dev.sigstore.rekor.v2.Rekor/GetTile": grpclib.const.Handler( - self.__rpc_get_tile, - grpclib.const.Cardinality.UNARY_UNARY, - TileRequest, - ____google_api__.HttpBody, - ), - "/dev.sigstore.rekor.v2.Rekor/GetEntryBundle": grpclib.const.Handler( - self.__rpc_get_entry_bundle, - grpclib.const.Cardinality.UNARY_UNARY, - EntryBundleRequest, - ____google_api__.HttpBody, - ), - "/dev.sigstore.rekor.v2.Rekor/GetCheckpoint": grpclib.const.Handler( - self.__rpc_get_checkpoint, - grpclib.const.Cardinality.UNARY_UNARY, - betterproto_lib_pydantic_google_protobuf.Empty, - ____google_api__.HttpBody, - ), - } - - rebuild_dataclass(Verifier) # type: ignore rebuild_dataclass(Signature) # type: ignore rebuild_dataclass(HashedRekordRequestV002) # type: ignore diff --git a/sigstore/_internal/rekor_tiles/google/__init__.py b/sigstore/_internal/rekor_tiles/google/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/sigstore/_internal/rekor_tiles/google/api/__init__.py b/sigstore/_internal/rekor_tiles/google/api/__init__.py deleted file mode 100644 index d60e85e96..000000000 --- a/sigstore/_internal/rekor_tiles/google/api/__init__.py +++ /dev/null @@ -1,555 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# sources: google/api/annotations.proto, google/api/field_behavior.proto, google/api/http.proto, google/api/httpbody.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 -import betterproto.lib.pydantic.google.protobuf as betterproto_lib_pydantic_google_protobuf -from pydantic import model_validator -from pydantic.dataclasses import rebuild_dataclass - - -class FieldBehavior(betterproto.Enum): - """ - An indicator of the behavior of a given field (for example, that a field - is required in requests, or given as output but ignored as input). - This **does not** change the behavior in protocol buffers itself; it only - denotes the behavior and may affect how API tooling handles the field. - - Note: This enum **may** receive new values in the future. - """ - - UNSPECIFIED = 0 - """Conventional default for enums. Do not use this.""" - - OPTIONAL = 1 - """ - Specifically denotes a field as optional. - While all fields in protocol buffers are optional, this may be specified - for emphasis if appropriate. - """ - - REQUIRED = 2 - """ - Denotes a field as required. - This indicates that the field **must** be provided as part of the request, - and failure to do so will cause an error (usually `INVALID_ARGUMENT`). - """ - - OUTPUT_ONLY = 3 - """ - Denotes a field as output only. - This indicates that the field is provided in responses, but including the - field in a request does nothing (the server *must* ignore it and - *must not* throw an error as a result of the field's presence). - """ - - INPUT_ONLY = 4 - """ - Denotes a field as input only. - This indicates that the field is provided in requests, and the - corresponding field is not included in output. - """ - - IMMUTABLE = 5 - """ - Denotes a field as immutable. - This indicates that the field may be set once in a request to create a - resource, but may not be changed thereafter. - """ - - UNORDERED_LIST = 6 - """ - Denotes that a (repeated) field is an unordered list. - This indicates that the service may provide the elements of the list - in any arbitrary order, rather than the order the user originally - provided. Additionally, the list's order may or may not be stable. - """ - - NON_EMPTY_DEFAULT = 7 - """ - Denotes that this field returns a non-empty default value if not set. - This indicates that if the user provides the empty value in a request, - a non-empty value will be returned. The user will not be aware of what - non-empty value to expect. - """ - - IDENTIFIER = 8 - """ - Denotes that the field in a resource (a message annotated with - google.api.resource) is used in the resource name to uniquely identify the - resource. For AIP-compliant APIs, this should only be applied to the - `name` field on the resource. - - This behavior should not be applied to references to other resources within - the message. - - The identifier field of resources often have different field behavior - depending on the request it is embedded in (e.g. for Create methods name - is optional and unused, while for Update methods it is required). Instead - of method-specific annotations, only `IDENTIFIER` is required. - """ - - @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 Http(betterproto.Message): - """ - Defines the HTTP configuration for an API service. It contains a list of - [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method - to one or more HTTP REST API methods. - """ - - rules: List["HttpRule"] = betterproto.message_field(1) - """ - A list of HTTP configuration rules that apply to individual API methods. - - **NOTE:** All service configuration rules follow "last one wins" order. - """ - - fully_decode_reserved_expansion: bool = betterproto.bool_field(2) - """ - When set to true, URL path parameters will be fully URI-decoded except in - cases of single segment matches in reserved expansion, where "%2F" will be - left encoded. - - The default behavior is to not decode RFC 6570 reserved characters in multi - segment matches. - """ - - -@dataclass(eq=False, repr=False) -class HttpRule(betterproto.Message): - """ - gRPC Transcoding - - gRPC Transcoding is a feature for mapping between a gRPC method and one or - more HTTP REST endpoints. It allows developers to build a single API service - that supports both gRPC APIs and REST APIs. Many systems, including [Google - APIs](https://github.com/googleapis/googleapis), - [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC - Gateway](https://github.com/grpc-ecosystem/grpc-gateway), - and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature - and use it for large scale production services. - - `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies - how different portions of the gRPC request message are mapped to the URL - path, URL query parameters, and HTTP request body. It also controls how the - gRPC response message is mapped to the HTTP response body. `HttpRule` is - typically specified as an `google.api.http` annotation on the gRPC method. - - Each mapping specifies a URL path template and an HTTP method. The path - template may refer to one or more fields in the gRPC request message, as long - as each field is a non-repeated field with a primitive (non-message) type. - The path template controls how fields of the request message are mapped to - the URL path. - - Example: - - service Messaging { - rpc GetMessage(GetMessageRequest) returns (Message) { - option (google.api.http) = { - get: "/v1/{name=messages/*}" - }; - } - } - message GetMessageRequest { - string name = 1; // Mapped to URL path. - } - message Message { - string text = 1; // The resource content. - } - - This enables an HTTP REST to gRPC mapping as below: - - - HTTP: `GET /v1/messages/123456` - - gRPC: `GetMessage(name: "messages/123456")` - - Any fields in the request message which are not bound by the path template - automatically become HTTP query parameters if there is no HTTP request body. - For example: - - service Messaging { - rpc GetMessage(GetMessageRequest) returns (Message) { - option (google.api.http) = { - get:"/v1/messages/{message_id}" - }; - } - } - message GetMessageRequest { - message SubMessage { - string subfield = 1; - } - string message_id = 1; // Mapped to URL path. - int64 revision = 2; // Mapped to URL query parameter `revision`. - SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. - } - - This enables a HTTP JSON to RPC mapping as below: - - - HTTP: `GET /v1/messages/123456?revision=2&sub.subfield=foo` - - gRPC: `GetMessage(message_id: "123456" revision: 2 sub: - SubMessage(subfield: "foo"))` - - Note that fields which are mapped to URL query parameters must have a - primitive type or a repeated primitive type or a non-repeated message type. - In the case of a repeated type, the parameter can be repeated in the URL - as `...?param=A¶m=B`. In the case of a message type, each field of the - message is mapped to a separate parameter, such as - `...?foo.a=A&foo.b=B&foo.c=C`. - - For HTTP methods that allow a request body, the `body` field - specifies the mapping. Consider a REST update method on the - message resource collection: - - service Messaging { - rpc UpdateMessage(UpdateMessageRequest) returns (Message) { - option (google.api.http) = { - patch: "/v1/messages/{message_id}" - body: "message" - }; - } - } - message UpdateMessageRequest { - string message_id = 1; // mapped to the URL - Message message = 2; // mapped to the body - } - - The following HTTP JSON to RPC mapping is enabled, where the - representation of the JSON in the request body is determined by - protos JSON encoding: - - - HTTP: `PATCH /v1/messages/123456 { "text": "Hi!" }` - - gRPC: `UpdateMessage(message_id: "123456" message { text: "Hi!" })` - - The special name `*` can be used in the body mapping to define that - every field not bound by the path template should be mapped to the - request body. This enables the following alternative definition of - the update method: - - service Messaging { - rpc UpdateMessage(Message) returns (Message) { - option (google.api.http) = { - patch: "/v1/messages/{message_id}" - body: "*" - }; - } - } - message Message { - string message_id = 1; - string text = 2; - } - - - The following HTTP JSON to RPC mapping is enabled: - - - HTTP: `PATCH /v1/messages/123456 { "text": "Hi!" }` - - gRPC: `UpdateMessage(message_id: "123456" text: "Hi!")` - - Note that when using `*` in the body mapping, it is not possible to - have HTTP parameters, as all fields not bound by the path end in - the body. This makes this option more rarely used in practice when - defining REST APIs. The common usage of `*` is in custom methods - which don't use the URL at all for transferring data. - - It is possible to define multiple HTTP methods for one RPC by using - the `additional_bindings` option. Example: - - service Messaging { - rpc GetMessage(GetMessageRequest) returns (Message) { - option (google.api.http) = { - get: "/v1/messages/{message_id}" - additional_bindings { - get: "/v1/users/{user_id}/messages/{message_id}" - } - }; - } - } - message GetMessageRequest { - string message_id = 1; - string user_id = 2; - } - - This enables the following two alternative HTTP JSON to RPC mappings: - - - HTTP: `GET /v1/messages/123456` - - gRPC: `GetMessage(message_id: "123456")` - - - HTTP: `GET /v1/users/me/messages/123456` - - gRPC: `GetMessage(user_id: "me" message_id: "123456")` - - Rules for HTTP mapping - - 1. Leaf request fields (recursive expansion nested messages in the request - message) are classified into three categories: - - Fields referred by the path template. They are passed via the URL path. - - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They - are passed via the HTTP - request body. - - All other fields are passed via the URL query parameters, and the - parameter name is the field path in the request message. A repeated - field can be represented as multiple query parameters under the same - name. - 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL - query parameter, all fields - are passed via URL path and HTTP request body. - 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP - request body, all - fields are passed via URL path and URL query parameters. - - Path template syntax - - Template = "/" Segments [ Verb ] ; - Segments = Segment { "/" Segment } ; - Segment = "*" | "**" | LITERAL | Variable ; - Variable = "{" FieldPath [ "=" Segments ] "}" ; - FieldPath = IDENT { "." IDENT } ; - Verb = ":" LITERAL ; - - The syntax `*` matches a single URL path segment. The syntax `**` matches - zero or more URL path segments, which must be the last part of the URL path - except the `Verb`. - - The syntax `Variable` matches part of the URL path as specified by its - template. A variable template must not contain other variables. If a variable - matches a single path segment, its template may be omitted, e.g. `{var}` - is equivalent to `{var=*}`. - - The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` - contains any reserved character, such characters should be percent-encoded - before the matching. - - If a variable contains exactly one path segment, such as `"{var}"` or - `"{var=*}"`, when such a variable is expanded into a URL path on the client - side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The - server side does the reverse decoding. Such variables show up in the - [Discovery - Document](https://developers.google.com/discovery/v1/reference/apis) as - `{var}`. - - If a variable contains multiple path segments, such as `"{var=foo/*}"` - or `"{var=**}"`, when such a variable is expanded into a URL path on the - client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. - The server side does the reverse decoding, except "%2F" and "%2f" are left - unchanged. Such variables show up in the - [Discovery - Document](https://developers.google.com/discovery/v1/reference/apis) as - `{+var}`. - - Using gRPC API Service Configuration - - gRPC API Service Configuration (service config) is a configuration language - for configuring a gRPC service to become a user-facing product. The - service config is simply the YAML representation of the `google.api.Service` - proto message. - - As an alternative to annotating your proto file, you can configure gRPC - transcoding in your service config YAML files. You do this by specifying a - `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same - effect as the proto annotation. This can be particularly useful if you - have a proto that is reused in multiple services. Note that any transcoding - specified in the service config will override any matching transcoding - configuration in the proto. - - The following example selects a gRPC method and applies an `HttpRule` to it: - - http: - rules: - - selector: example.v1.Messaging.GetMessage - get: /v1/messages/{message_id}/{sub.subfield} - - Special notes - - When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the - proto to JSON conversion must follow the [proto3 - specification](https://developers.google.com/protocol-buffers/docs/proto3#json). - - While the single segment variable follows the semantics of - [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String - Expansion, the multi segment variable **does not** follow RFC 6570 Section - 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion - does not expand special characters like `?` and `#`, which would lead - to invalid URLs. As the result, gRPC Transcoding uses a custom encoding - for multi segment variables. - - The path variables **must not** refer to any repeated or mapped field, - because client libraries are not capable of handling such variable expansion. - - The path variables **must not** capture the leading "/" character. The reason - is that the most common use case "{var}" does not capture the leading "/" - character. For consistency, all path variables must share the same behavior. - - Repeated message fields must not be mapped to URL query parameters, because - no client library can support such complicated mapping. - - If an API needs to use a JSON array for request or response body, it can map - the request or response body to a repeated field. However, some gRPC - Transcoding implementations may not support this feature. - """ - - selector: str = betterproto.string_field(1) - """ - Selects a method to which this rule applies. - - Refer to [selector][google.api.DocumentationRule.selector] for syntax - details. - """ - - get: Optional[str] = betterproto.string_field(2, optional=True, group="pattern") - """ - Maps to HTTP GET. Used for listing and getting information about - resources. - """ - - put: Optional[str] = betterproto.string_field(3, optional=True, group="pattern") - """Maps to HTTP PUT. Used for replacing a resource.""" - - post: Optional[str] = betterproto.string_field(4, optional=True, group="pattern") - """ - Maps to HTTP POST. Used for creating a resource or performing an action. - """ - - delete: Optional[str] = betterproto.string_field(5, optional=True, group="pattern") - """Maps to HTTP DELETE. Used for deleting a resource.""" - - patch: Optional[str] = betterproto.string_field(6, optional=True, group="pattern") - """Maps to HTTP PATCH. Used for updating a resource.""" - - custom: Optional["CustomHttpPattern"] = betterproto.message_field( - 8, optional=True, group="pattern" - ) - """ - The custom pattern is used for specifying an HTTP method that is not - included in the `pattern` field, such as HEAD, or "*" to leave the - HTTP method unspecified for this rule. The wild-card rule is useful - for services that provide content to Web (HTML) clients. - """ - - body: str = betterproto.string_field(7) - """ - The name of the request field whose value is mapped to the HTTP request - body, or `*` for mapping all request fields not captured by the path - pattern to the HTTP body, or omitted for not having any HTTP request body. - - NOTE: the referred field must be present at the top-level of the request - message type. - """ - - response_body: str = betterproto.string_field(12) - """ - Optional. The name of the response field whose value is mapped to the HTTP - response body. When omitted, the entire response message will be used - as the HTTP response body. - - NOTE: The referred field must be present at the top-level of the response - message type. - """ - - additional_bindings: List["HttpRule"] = betterproto.message_field(11) - """ - Additional HTTP bindings for the selector. Nested bindings must - not contain an `additional_bindings` field themselves (that is, - the nesting may only be one level deep). - """ - - @model_validator(mode="after") - def check_oneof(cls, values): - return cls._validate_field_groups(values) - - -@dataclass(eq=False, repr=False) -class CustomHttpPattern(betterproto.Message): - """A custom pattern is used for defining custom HTTP verb.""" - - kind: str = betterproto.string_field(1) - """The name of this custom HTTP verb.""" - - path: str = betterproto.string_field(2) - """The path matched by this custom verb.""" - - -@dataclass(eq=False, repr=False) -class HttpBody(betterproto.Message): - """ - Message that represents an arbitrary HTTP body. It should only be used for - payload formats that can't be represented as JSON, such as raw binary or - an HTML page. - - - This message can be used both in streaming and non-streaming API methods in - the request as well as the response. - - It can be used as a top-level request field, which is convenient if one - wants to extract parameters from either the URL or HTTP template into the - request fields and also want access to the raw HTTP body. - - Example: - - message GetResourceRequest { - // A unique request id. - string request_id = 1; - - // The raw HTTP body is bound to this field. - google.api.HttpBody http_body = 2; - - } - - service ResourceService { - rpc GetResource(GetResourceRequest) - returns (google.api.HttpBody); - rpc UpdateResource(google.api.HttpBody) - returns (google.protobuf.Empty); - - } - - Example with streaming methods: - - service CaldavService { - rpc GetCalendar(stream google.api.HttpBody) - returns (stream google.api.HttpBody); - rpc UpdateCalendar(stream google.api.HttpBody) - returns (stream google.api.HttpBody); - - } - - Use of this type only changes how the request and response bodies are - handled, all other features will continue to work unchanged. - """ - - content_type: str = betterproto.string_field(1) - """ - The HTTP Content-Type header value specifying the content type of the body. - """ - - data: bytes = betterproto.bytes_field(2) - """The HTTP request/response body as raw binary.""" - - extensions: List["betterproto_lib_pydantic_google_protobuf.Any"] = ( - betterproto.message_field(3) - ) - """ - Application specific response metadata. Must be set in the first response - for streaming APIs. - """ - - -rebuild_dataclass(Http) # type: ignore -rebuild_dataclass(HttpRule) # type: ignore -rebuild_dataclass(HttpBody) # type: ignore From e2724252fa94686c780538b387d7c8447634e433 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 20:00:18 +0000 Subject: [PATCH 70/81] unused var Signed-off-by: Ramon Petgrave --- sigstore/sign.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sigstore/sign.py b/sigstore/sign.py index e973539a0..a786a634b 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -281,11 +281,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_) From 2d50e4829f1d70cb4e7dc0333d26f5752ab7ef95 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 16 May 2025 20:00:30 +0000 Subject: [PATCH 71/81] list not List Signed-off-by: Ramon Petgrave --- .../_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py | 4 ++-- .../_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py | 2 +- .../_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py | 4 ++-- sigstore/_internal/rekor_tiles/io/intoto/__init__.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py index 2c448fe7c..a2e4d138e 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/common/v1/__init__.py @@ -242,7 +242,7 @@ class PublicKeyIdentifier(betterproto.Message): class ObjectIdentifier(betterproto.Message): """An ASN.1 OBJECT IDENTIFIER""" - id: List[int] = betterproto.int32_field(1) + id: list[int] = betterproto.int32_field(1) @dataclass(eq=False, repr=False) @@ -292,7 +292,7 @@ class X509CertificateChain(betterproto.Message): the purpose of chain building. """ - certificates: List["X509Certificate"] = betterproto.message_field(1) + certificates: list["X509Certificate"] = betterproto.message_field(1) """ One or more DER-encoded certificates. diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py index ae5debd0c..16e54761e 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v1/__init__.py @@ -69,7 +69,7 @@ class InclusionProof(betterproto.Message): 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) + hashes: list[bytes] = betterproto.bytes_field(4) """ A list of hashes required to compute the inclusion proof, sorted in order from leaf to root. diff --git a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py index c6a019197..9d267496c 100644 --- a/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py +++ b/sigstore/_internal/rekor_tiles/dev/sigstore/rekor/v2/__init__.py @@ -96,7 +96,7 @@ class DsseRequestV002(betterproto.Message): envelope: "____io_intoto__.Envelope" = betterproto.message_field(1) """A DSSE envelope""" - verifiers: List["Verifier"] = betterproto.message_field(2) + verifiers: list["Verifier"] = betterproto.message_field(2) """ All necessary verification material to verify all signatures embedded in the envelope """ @@ -107,7 +107,7 @@ 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: list["Signature"] = betterproto.message_field(2) """ Signatures and their associated verification material used to verify the payload """ diff --git a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py index 919957b55..da1edc772 100644 --- a/sigstore/_internal/rekor_tiles/io/intoto/__init__.py +++ b/sigstore/_internal/rekor_tiles/io/intoto/__init__.py @@ -32,7 +32,7 @@ class Envelope(betterproto.Message): REQUIRED. """ - signatures: List["Signature"] = betterproto.message_field(3) + signatures: list["Signature"] = betterproto.message_field(3) """ Signature over: PAE(type, payload) From cdd6de203a9c378f68d3b5aca5eb6ee7133e578d Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Sat, 17 May 2025 00:47:26 +0000 Subject: [PATCH 72/81] add cli test for v2, using local rekorv2 Signed-off-by: Ramon Petgrave --- .../trust_config/config.v1.rekorv2_local.json | 137 ++++++++++++++++++ test/integration/cli/test_sign.py | 44 +++++- 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 test/assets/integration/trust_config/config.v1.rekorv2_local.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/integration/cli/test_sign.py b/test/integration/cli/test_sign.py index 4d0953db7..92fff9d70 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,43 @@ def test_sign_success_default_output_bundle(capsys, sigstore, asset_integration) ) +@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): From f8a198eaa88bdbfab6efa715b75b03ffab2e0f64 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Sat, 17 May 2025 01:24:59 +0000 Subject: [PATCH 73/81] xfail and comment Signed-off-by: Ramon Petgrave --- test/integration/cli/test_sign.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/cli/test_sign.py b/test/integration/cli/test_sign.py index 92fff9d70..9fad71c6e 100644 --- a/test/integration/cli/test_sign.py +++ b/test/integration/cli/test_sign.py @@ -86,6 +86,9 @@ 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") From f057e8135546db3245137706dbe84efd1cf80b04 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 19 May 2025 19:13:19 +0000 Subject: [PATCH 74/81] test local env Signed-off-by: Ramon Petgrave --- test/assets/config.v1.rekorv2_local.json | 128 +++++++++++++++++++++++ test/integration/cli/test_sign.py | 20 ++-- test/unit/conftest.py | 30 +++++- test/unit/test_sign.py | 25 +++++ 4 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 test/assets/config.v1.rekorv2_local.json 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/integration/cli/test_sign.py b/test/integration/cli/test_sign.py index 9fad71c6e..05516b6cf 100644 --- a/test/integration/cli/test_sign.py +++ b/test/integration/cli/test_sign.py @@ -90,25 +90,21 @@ def test_sign_success_default_output_bundle(capsys, sigstore, asset_integration) # 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): +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") + 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 - ) - ) + 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() - )) + 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, diff --git a/test/unit/conftest.py b/test/unit/conftest.py index e495557f4..525243f75 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -18,7 +18,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 @@ -36,6 +36,7 @@ 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.oidc import _DEFAULT_AUDIENCE, IdentityToken @@ -218,3 +219,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 756748bc0..4d467bc38 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 class TestSigningContext: @@ -247,3 +248,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()) From 1f961438edad11d689bd5c99e91ebdd211bb0c09 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 19 May 2025 19:20:49 +0000 Subject: [PATCH 75/81] add workflow Signed-off-by: Ramon Petgrave --- .github/workflows/with-setup-sigstore-env.yml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/with-setup-sigstore-env.yml 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 From 95cff0ad1bb66f65970f1bc2a9743ad2db370b5b Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Mon, 19 May 2025 23:47:13 +0000 Subject: [PATCH 76/81] sign and verify dsse Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 3 +- sigstore/sign.py | 63 ++++++++++++----- sigstore/verify/verifier.py | 105 ++++++++++++++++++++--------- 3 files changed, 120 insertions(+), 51 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index afe1f974c..532e46a24 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -29,7 +29,6 @@ import requests from cryptography.hazmat.primitives import serialization from cryptography.x509 import Certificate -from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import TransparencyLogEntry from sigstore._internal import USER_AGENT from sigstore._internal.rekor_tiles.dev.sigstore.common import v1 @@ -325,7 +324,7 @@ def __del__(self) -> None: """ self.session.close() - def create_entry(self, request: v2.CreateEntryRequest) -> TransparencyLogEntry: + def create_entry(self, request: v2.CreateEntryRequest) -> LogEntry: """ Submit a new entry for inclusion in the Rekor log. """ diff --git a/sigstore/sign.py b/sigstore/sign.py index a786a634b..bfbd56ac7 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -68,6 +68,7 @@ ) 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 @@ -237,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( diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index ccd6832c3..ed54ad4dd 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -39,7 +39,6 @@ from pydantic import ValidationError from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError -from sigstore_protobuf_specs.dev.sigstore.rekor.v1 import KindVersion from sigstore import dsse, models from sigstore._internal.rekor import _hashedrekord_from_parts @@ -67,11 +66,6 @@ # timestamps to consider a signature valid. VERIFY_TIMESTAMP_THRESHOLD: int = 1 -REKOR_V2_BUNDLE_ENTRY_KIND_VERSIONS = [ - KindVersion(kind="hashedrekord", version="0.0.2"), - KindVersion(kind="dsse", version="0.0.2"), -] - class Verifier: """ @@ -440,34 +434,78 @@ 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)) + 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) @@ -513,7 +551,10 @@ def verify_artifact( # the other bundle materials (and input being verified). entry = bundle.log_entry - if entry._kind_version in REKOR_V2_BUNDLE_ENTRY_KIND_VERSIONS: + if ( + entry._kind_version.kind == "hashedrekord" + and entry._kind_version.version == "0.0.2" + ): actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) expected_body = v2.Entry( kind=entry._kind_version.kind, From e759b8a16b5626870480e24c67226fcf8d4cfe69 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 22 May 2025 21:41:33 +0000 Subject: [PATCH 77/81] alpha ytsu_config Signed-off-by: Ramon Petgrave --- .../tsa/trust_config.rekorv2_alpha.json | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 test/assets/tsa/trust_config.rekorv2_alpha.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..86ba2c2c4 --- /dev/null +++ b/test/assets/tsa/trust_config.rekorv2_alpha.json @@ -0,0 +1,148 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "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 From ac517e8612f7776ef53e987a368c01e3ea8a4155 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Thu, 22 May 2025 21:46:08 +0000 Subject: [PATCH 78/81] with v1 key Signed-off-by: Ramon Petgrave --- test/assets/tsa/trust_config.rekorv2_alpha.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/assets/tsa/trust_config.rekorv2_alpha.json b/test/assets/tsa/trust_config.rekorv2_alpha.json index 86ba2c2c4..354388df4 100644 --- a/test/assets/tsa/trust_config.rekorv2_alpha.json +++ b/test/assets/tsa/trust_config.rekorv2_alpha.json @@ -3,6 +3,20 @@ "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", From 435ddb063eb5f581ec044fbf1d04e85c2022bc06 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 23 May 2025 17:52:51 +0000 Subject: [PATCH 79/81] no V002 workaround Signed-off-by: Ramon Petgrave From cd52b93fc3d0917cfdc887696171291218337982 Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 23 May 2025 19:55:35 +0000 Subject: [PATCH 80/81] sign but cant parse entry Signed-off-by: Ramon Petgrave --- sigstore/_internal/rekor/client.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index 532e46a24..383aac4e5 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -330,12 +330,6 @@ def create_entry(self, request: v2.CreateEntryRequest) -> LogEntry: """ # There may be a bug in betterproto, where the V_0_0_2 is changed to V002. payload = request.to_dict() - if "hashedRekordRequestV002" in payload: - payload["hashedRekordRequestV0_0_2"] = payload.pop( - "hashedRekordRequestV002" - ) - if "dsseRequestV002" in payload: - payload["dsseRequestV0_0_2"] = payload.pop("dsseRequestV002") _logger.debug(f"request: {json.dumps(payload)}") resp = self.session.post(f"{self.url}/log/entries", json=payload) From 6258926115313d6c6205efff82c9dc55bc22d77d Mon Sep 17 00:00:00 2001 From: Ramon Petgrave Date: Fri, 23 May 2025 20:29:33 +0000 Subject: [PATCH 81/81] worarkound entry body parsing Signed-off-by: Ramon Petgrave --- sigstore/verify/verifier.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index b0d2ef4d3..266054a97 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -22,6 +22,7 @@ import logging from datetime import datetime, timezone from typing import cast +import json import rekor_types from cryptography.exceptions import InvalidSignature @@ -432,7 +433,19 @@ def verify_dsse( and entry._kind_version.version == "0.0.2" ): try: - entry_body = v2.Entry().from_json(base64.b64decode(entry.body)) + # 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}") @@ -546,7 +559,19 @@ def verify_artifact( entry._kind_version.kind == "hashedrekord" and entry._kind_version.version == "0.0.2" ): - actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) + _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,