diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ece2349a..dc3cb76bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,18 @@ All versions prior to 0.9.0 are untracked. ## [Unreleased] +### Changed + +* A cached copy of the trust bundle is now included with the distribution. + ([#611](https://github.com/sigstore/sigstore-python/pull/611)) + ## [1.1.2] ### Fixed * Updated the `staging-root.json` for recent changes to the Sigstore staging instance ([#602](https://github.com/sigstore/sigstore-python/pull/602)) + * Switched TUF requests to their CDN endpoints, rather than direct GCS access ([#609](https://github.com/sigstore/sigstore-python/pull/609)) @@ -27,6 +33,7 @@ All versions prior to 0.9.0 are untracked. generate staging and production OIDC tokens, which are used to test the `sigstore.sign` module. All signing tests need to be completed before token expiry, which is currently 60 seconds after issuance. + * Network-related errors from the `sigstore._internal.tuf` module now have better diagnostics. @@ -34,11 +41,14 @@ All versions prior to 0.9.0 are untracked. * Replaced ambient credential detection logic with the `id` package ([#535](https://github.com/sigstore/sigstore-python/pull/535)) + * Revamped error diagnostics reporting. All errors with diagnostics now implement `sigstore.errors.Error`. + * Trust root materials are now retrieved from a single trust bundle, if it is available via TUF ([#542](https://github.com/sigstore/sigstore-python/pull/542)) + * Improved diagnostics around Signed Certificate Timestamp verification failures. ([#555](https://github.com/sigstore/sigstore-python/pull/555)) diff --git a/sigstore/_internal/tuf.py b/sigstore/_internal/tuf.py index 9a64b2908..d53a8723e 100644 --- a/sigstore/_internal/tuf.py +++ b/sigstore/_internal/tuf.py @@ -42,7 +42,7 @@ from tuf.ngclient import RequestsFetcher, Updater from sigstore._utils import read_embedded -from sigstore.errors import MetadataError, TUFError +from sigstore.errors import MetadataError, RootError, TUFError logger = logging.getLogger(__name__) @@ -121,24 +121,37 @@ def __init__(self, url: str) -> None: self._repo_url = url self._metadata_dir, self._targets_dir = _get_dirs(url) + rsrc_prefix: str + if self._repo_url == DEFAULT_TUF_URL: + rsrc_prefix = "prod" + elif self._repo_url == STAGING_TUF_URL: + rsrc_prefix = "staging" + else: + raise RootError + # Initialize metadata dir + self._metadata_dir.mkdir(parents=True, exist_ok=True) tuf_root = self._metadata_dir / "root.json" + if not tuf_root.exists(): - if self._repo_url == DEFAULT_TUF_URL: - fname = "root.json" - elif self._repo_url == STAGING_TUF_URL: - fname = "staging-root.json" - else: - raise Exception(f"TUF root not found in {tuf_root}") - - self._metadata_dir.mkdir(parents=True, exist_ok=True) - root_json = read_embedded(fname) - with tuf_root.open("wb") as io: - io.write(root_json) + try: + root_json = read_embedded("root.json", rsrc_prefix) + except FileNotFoundError as e: + raise RootError from e + + tuf_root.write_bytes(root_json) # Initialize targets cache dir - # NOTE: Could prime the cache here with any embedded certs/keys self._targets_dir.mkdir(parents=True, exist_ok=True) + trusted_root_target = self._targets_dir / "trusted_root.json" + + if not trusted_root_target.exists(): + try: + trusted_root_json = read_embedded("trusted_root.json", rsrc_prefix) + except FileNotFoundError as e: + raise RootError from e + + trusted_root_target.write_bytes(trusted_root_json) logger.debug(f"TUF metadata: {self._metadata_dir}") logger.debug(f"TUF targets cache: {self._targets_dir}") diff --git a/sigstore/_store/root.json b/sigstore/_store/prod/root.json similarity index 100% rename from sigstore/_store/root.json rename to sigstore/_store/prod/root.json diff --git a/sigstore/_store/prod/trusted_root.json b/sigstore/_store/prod/trusted_root.json new file mode 100644 index 000000000..bb4e6fcd8 --- /dev/null +++ b/sigstore/_store/prod/trusted_root.json @@ -0,0 +1,91 @@ +{ + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "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": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + }, + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + } + ] + }, + "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": [] +} diff --git a/sigstore/_store/staging-root.json b/sigstore/_store/staging/root.json similarity index 100% rename from sigstore/_store/staging-root.json rename to sigstore/_store/staging/root.json diff --git a/sigstore/_store/staging/trusted_root.json b/sigstore/_store/staging/trusted_root.json new file mode 100644 index 000000000..6a1c1f5a4 --- /dev/null +++ b/sigstore/_store/staging/trusted_root.json @@ -0,0 +1,86 @@ +{ + "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=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstage.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB9jCCAXugAwIBAgITDdEJvluliE0AzYaIE4jTMdnFTzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDMyNTE2NTA0NloXDTMyMDMyMjE2NTA0NVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMo9BUNk9QIYisYysC24+2OytoV72YiLonYcqR3yeVnYziPt7Xv++CYE8yoCTiwedUECCWKOcvQKRCJZb9ht4Hzy+VvBx36hK+C6sECCSR0x6pPSiz+cTk1f788ZjBlUZaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP9CMrpofas6cK/cDNQa4j6Hj2ZlMB8GA1UdIwQYMBaAFP9CMrpofas6cK/cDNQa4j6Hj2ZlMAoGCCqGSM49BAMDA2kAMGYCMQD+kojuzMwztNay9Ibzjuk//ZL5m6T2OCsm45l1lY004pcb984L926BowodoirFMcMCMQDIJtFHhP/1D3a+M3dAGomOb6O4CmTry3TTPbPsAFnv22YA0Y+P21NVoxKDjdu0tkw=" + }, + { + "rawBytes": "MIICGTCCAaCgAwIBAgITJta/okfgHvjabGm1BOzuhrwA1TAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDQxNDIxMzg0MFoXDTMyMDMyMjE2NTA0NVowNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASosAySWJQ/tK5r8T5aHqavk0oI+BKQbnLLdmOMRXHQF/4Hx9KtNfpcdjH9hNKQSBxSlLFFN3tvFCco0qFBzWYwZtsYsBe1l91qYn/9VHFTaEVwYQWIJEEvrs0fvPuAqjajezB5MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRxhjCmFHxib/n31vQFGn9f/+tvrDAfBgNVHSMEGDAWgBT/QjK6aH2rOnCv3AzUGuI+h49mZTAKBggqhkjOPQQDAwNnADBkAjAM1lbKkcqQlE/UspMTbWNo1y2TaJ44tx3l/FJFceTSdDZ+0W1OHHeU4twie/lq8XgCMHQxgEv26xNNiAGyPXbkYgrDPvbOqp0UeWX4mJnLSrBr3aN/KX1SBrKQu220FmVL0Q==" + } + ] + }, + "validFor": { + "start": "2022-03-25T16:50:46.000Z" + } + } + ], + "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:00.000Z" + } + }, + "logId": { + "keyId": "s9AOb93xWxr+a4ztxJnxxJCX7VZ0V3IF4jTu/OoL84A=" + } + }, + { + "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:00.000Z" + } + }, + "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:00.000Z" + } + }, + "logId": { + "keyId": "KzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshno=" + } + } + ], + "timestampAuthorities": [] +} diff --git a/sigstore/_utils.py b/sigstore/_utils.py index eec450cb2..a2148bb62 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -166,9 +166,9 @@ def sha256_streaming(io: IO[bytes]) -> bytes: return sha256.digest() -def read_embedded(name: str) -> bytes: +def read_embedded(name: str, prefix: str) -> bytes: """ Read a resource embedded in this distribution of sigstore-python, returning its contents as bytes. """ - return resources.files("sigstore._store").joinpath(name).read_bytes() # type: ignore + return resources.files("sigstore._store").joinpath(prefix, name).read_bytes() # type: ignore diff --git a/sigstore/errors.py b/sigstore/errors.py index 6d8c4cfc2..5a044bc91 100644 --- a/sigstore/errors.py +++ b/sigstore/errors.py @@ -64,7 +64,8 @@ def diagnostics(self) -> str: ) return ( - """A network issue occurred. + """\ + A network issue occurred. Check your internet connection and try again. """ @@ -92,7 +93,8 @@ def diagnostics(self) -> str: "Please report this issue at .", ) - return f"""{self.message}. + return f"""\ + {self.message}. {details} """ @@ -104,3 +106,14 @@ class MetadataError(Error): def diagnostics(self) -> str: """Returns diagnostics for the error.""" return f"""{str(self)}.""" + + +class RootError(Error): + """Raised when TUF cannot establish its root of trust.""" + + def diagnostics(self) -> str: + """Returns diagnostics for the error.""" + return """\ + Unable to establish root of trust. + + This error may occur when the resources embedded in this distribution of sigstore-python are out of date.""" diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 0c05e8ba7..c60d6dfb5 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -192,11 +192,12 @@ def mock_staging_tuf(monkeypatch, tuf_dirs): class MockFetcher(FetcherInterface): def _fetch(self, url: str) -> Iterator[bytes]: filepath = _TUF_ASSETS / urlparse(url).path.lstrip("/") + filename = filepath.name if filepath.is_file(): - success[filepath] += 1 + success[filename] += 1 return BytesIO(filepath.read_bytes()) - failure[filepath] += 1 + failure[filename] += 1 raise DownloadHTTPError("File not found", 404) monkeypatch.setattr(tuf, "_get_fetcher", lambda: MockFetcher()) diff --git a/test/unit/internal/test_tuf.py b/test/unit/internal/test_tuf.py index 110f6fdc1..f5fd0f76d 100644 --- a/test/unit/internal/test_tuf.py +++ b/test/unit/internal/test_tuf.py @@ -23,15 +23,10 @@ from sigstore._internal.tuf import TrustUpdater, _is_timerange_valid from sigstore._utils import load_der_public_key, load_pem_public_key +from sigstore.errors import RootError def test_updater_staging_caches_and_requests(mock_staging_tuf, tuf_dirs): - def consistent_targets_match(consistent_targets, targets): - for t in consistent_targets: - if os.path.basename(t) not in targets: - return False - return True - # start with empty target cache, empty local metadata dir data_dir, cache_dir = tuf_dirs @@ -51,49 +46,40 @@ def consistent_targets_match(consistent_targets, targets): assert sorted(os.listdir(data_dir)) == expected # Expect requests of top-level metadata, and the ctfe targets expected_requests = { - "ctfe.pub": 1, - "ctfe_2022.pub": 1, - "ctfe_2022_2.pub": 1, "2.root.json": 1, "2.snapshot.json": 1, "2.targets.json": 1, - "2.timestamp.json": 1, "timestamp.json": 1, - "6494317303d0e04509a30b239bf8290057164fba67072b6f89ddf1032273a78b.trusted_root.json": 1, + # trusted_root.json should not be requested, as it is cached locally } expected_fail_reqs = {"3.root.json": 1} - assert consistent_targets_match(reqs, expected_requests) - # Expect 404 from the next root version - assert consistent_targets_match(fail_reqs, expected_fail_reqs) - updater.get_rekor_keys() - # Expect request of the rekor key but nothing else - expected_requests["rekor.pub"] = 1 - assert consistent_targets_match(reqs, expected_requests) - assert consistent_targets_match(fail_reqs, expected_fail_reqs) + assert reqs == expected_requests + # Expect 404 from the next root version + assert fail_reqs == expected_fail_reqs updater.get_rekor_keys() - # Expect no requests - assert consistent_targets_match(reqs, expected_requests) - assert consistent_targets_match(fail_reqs, expected_fail_reqs) + # Expect no requests, as the `get_ctfe_keys` should have populated the bundled trust root + assert reqs == expected_requests + assert fail_reqs == expected_fail_reqs # New Updater instance, same cache dirs updater = TrustUpdater.staging() # Expect no requests happened - assert consistent_targets_match(reqs, expected_requests) - assert consistent_targets_match(fail_reqs, expected_fail_reqs) + assert reqs == expected_requests + assert fail_reqs == expected_fail_reqs updater.get_ctfe_keys() # Expect new timestamp and root requests expected_requests["timestamp.json"] += 1 expected_fail_reqs["3.root.json"] += 1 - assert consistent_targets_match(reqs, expected_requests) - assert consistent_targets_match(fail_reqs, expected_fail_reqs) + assert reqs == expected_requests + assert fail_reqs == expected_fail_reqs updater.get_rekor_keys() # Expect no requests - assert consistent_targets_match(reqs, expected_requests) - assert consistent_targets_match(fail_reqs, expected_fail_reqs) + assert reqs == expected_requests + assert fail_reqs == expected_fail_reqs def test_is_timerange_valid(): @@ -183,7 +169,7 @@ def _pem_keys(keys): def test_updater_instance_error(): - with pytest.raises(Exception, match="TUF root not found in"): + with pytest.raises(RootError): TrustUpdater("foo.bar") diff --git a/test/unit/test_store.py b/test/unit/test_store.py index 6f6ac1ae0..7e546f9bd 100644 --- a/test/unit/test_store.py +++ b/test/unit/test_store.py @@ -14,14 +14,18 @@ import json +import pytest + from sigstore._utils import read_embedded -def test_store_reads_root_json(): - root_json = read_embedded("root.json") +@pytest.mark.parametrize("env", ["prod", "staging"]) +def test_store_reads_root_json(env): + root_json = read_embedded("root.json", env) assert json.loads(root_json) -def test_store_reads_staging_root_json(): - root_json = read_embedded("staging-root.json") - assert json.loads(root_json) +@pytest.mark.parametrize("env", ["prod", "staging"]) +def test_store_reads_targets_json(env): + trusted_root_json = read_embedded("trusted_root.json", env) + assert json.loads(trusted_root_json)