Skip to content

Commit 9402a98

Browse files
authored
Add sigstore fuzzer for signing and verifying (#535)
* Add sigstore fuzzer for signing and verifying Signed-off-by: Adam Korczynski <[email protected]> * fix hatch linting Signed-off-by: Adam Korczynski <[email protected]> * fix type errors Signed-off-by: Adam Korczynski <[email protected]> --------- Signed-off-by: Adam Korczynski <[email protected]>
1 parent eda9fc4 commit 9402a98

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"{"
2+
"}"
3+
"["
4+
"]"
5+
":"
6+
","
7+
"\"mediaType\": \"application/vnd.dev.sigstore.trustedroot+json;version=0.1\","
8+
"\"tlogs\": ["
9+
"\"baseUrl\": \"https://rekor.sigstore.dev\","
10+
"\"hashAlgorithm": "SHA2_256\","
11+
"\"publicKey\": {"
12+
"\"rawBytes\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==\","
13+
"\"keyDetails\": \"PKIX_ECDSA_P256_SHA_256\","
14+
"\"validFor\": {"
15+
"\"start\": \"2021-01-12T11:53:27.000Z\""
16+
"\"logId\": {"
17+
"\"keyId\": \"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=\""
18+
"\"certificateAuthorities\": ["
19+
"\"rawBytes\": \"MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==\""
20+
"\"subject\": {"
21+
"\"organization\": \"sigstore.dev\","
22+
"\"commonName\": \"sigstore\""
23+
"\"uri\": \"https://fulcio.sigstore.dev\","
24+
"\"rawBytes\": \"MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=\""
25+
"\"rawBytes\": \"MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ\""
26+
"\"ctlogs\": ["
27+
"\"baseUrl\": \"https://ctfe.sigstore.dev/test\","
28+
"\"hashAlgorithm\": \"SHA2_256\","
29+
"\"publicKey\": {"
30+
"\"rawBytes\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==\","
31+
"\"end\": \"2022-10-31T23:59:59.999Z\""
32+
"\"logId\": {"
33+
"\"keyId\": \"CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=\""
34+
"\"baseUrl\": \"https://ctfe.sigstore.dev/2022\","
35+
"\"hashAlgorithm\": \"SHA2_256\","
36+
"\"publicKey\": {"
37+
"\"rawBytes\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==\","
38+
"\"keyDetails\": \"PKIX_ECDSA_P256_SHA_256\","
39+
"\"logId\": {"
40+
"\"keyId\": \"3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=\""
41+
"\"timestampAuthorities\": ["
42+
"\"subject\": {"
43+
"\"organization\": \"GitHub, Inc.\","
44+
"\"commonName\": \"Internal Services Root\""
45+
"\"certChain\": {"
46+
"\"certificates\": ["
47+
"\"rawBytes\": \"MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe\""
48+
"\"rawBytes\": \"MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==\""
49+
"\"rawBytes\": \"MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD\""
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Copyright 2025 The Sigstore Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import importlib
16+
import os
17+
from pathlib import Path
18+
import sys
19+
import tempfile
20+
21+
# type: ignore
22+
import atheris
23+
from sigstore.models import TrustedRoot
24+
from utils import any_files
25+
from utils import create_fuzz_files
26+
27+
from model_signing import signing
28+
from model_signing import verifying
29+
30+
31+
def _patch_sigstore_get_dirs(metadata_dir: Path, artifacts_dir: Path) -> None:
32+
"""Overwrite sigstore._internal.tuf._get_dirs(url: str).
33+
34+
This allows us to return directories that the fuzzer controls.
35+
"""
36+
tuf_mod = importlib.import_module("sigstore._internal.tuf")
37+
38+
def _stub_get_dirs(url: str):
39+
return metadata_dir, artifacts_dir
40+
41+
tuf_mod._get_dirs = _stub_get_dirs
42+
43+
44+
def _patch_trust_updater_offline_default_true() -> None:
45+
"""Make TrustUpdater.__init__ offline by default.
46+
47+
This avoids network calls at runtime which is important
48+
for when the fuzzer runs on OSS-Fuzz.
49+
"""
50+
tuf_mod = importlib.import_module("sigstore._internal.tuf")
51+
trust_updater = tuf_mod.TrustUpdater
52+
_orig_init = trust_updater.__init__
53+
54+
def _patched_init(self, url: str, offline: bool = True) -> None:
55+
_orig_init(self, url, offline=True)
56+
57+
trust_updater.__init__ = _patched_init
58+
59+
60+
def TestOneInput(data: bytes) -> None:
61+
fdp = atheris.FuzzedDataProvider(data)
62+
63+
# When the fuzzer creates a signer further down,
64+
# Sigstore will use a trusted root that the fuzzer
65+
# has created. It is possible for the fuzzer to create
66+
# an invalid trusted root, so it creates and tests it
67+
# here - very early in the whole iteration - to return
68+
# if it is invalid. If it is valid, it will use it laeter.
69+
root_sz = fdp.ConsumeIntInRange(0, 16 * 1024) # up to 16KB
70+
trusted_root_bytes = fdp.ConsumeBytes(root_sz)
71+
72+
tmp_tr_path: Path
73+
with tempfile.NamedTemporaryFile(
74+
prefix="trusted_root_", suffix=".json", delete=False
75+
) as tmp_tr:
76+
tmp_tr_path = Path(tmp_tr.name)
77+
tmp_tr.write(trusted_root_bytes)
78+
79+
try:
80+
# Early validation to catch bad JSON
81+
TrustedRoot.from_file(str(tmp_tr_path))
82+
except Exception:
83+
# Bad or unsupported JSON: return and retry
84+
os.unlink(tmp_tr_path)
85+
return
86+
87+
# Temp dirs for sigstore TUF (metadata/artifacts) + model + signature
88+
with (
89+
tempfile.TemporaryDirectory(prefix="tuf-metadata-") as md_tmp,
90+
tempfile.TemporaryDirectory(prefix="tuf-artifacts-") as art_tmp,
91+
tempfile.TemporaryDirectory(prefix="mt_file_fuzz_") as tmpdir,
92+
tempfile.TemporaryDirectory(prefix="mt_sig_fuzz_") as sigdir,
93+
):
94+
# Create the model root
95+
root = Path(tmpdir)
96+
97+
# 1) Populate model dir with randomizd files and exit early if empty
98+
create_fuzz_files(root, fdp)
99+
if not any_files(root):
100+
return
101+
102+
metadata_dir = Path(md_tmp)
103+
artifacts_dir = Path(art_tmp)
104+
105+
# 2) Create the hooks into sigstore python
106+
_patch_sigstore_get_dirs(metadata_dir, artifacts_dir)
107+
_patch_trust_updater_offline_default_true()
108+
109+
# 3) Write the (already validated) trusted_root.json into artifacts dir
110+
trusted_root_path = artifacts_dir / "trusted_root.json"
111+
trusted_root_path.write_bytes(trusted_root_bytes)
112+
113+
# 4) Fuzz/write signing_config.v0.2.json
114+
signing_config_path = artifacts_dir / "signing_config.v0.2.json"
115+
cfg_sz = fdp.ConsumeIntInRange(0, 16 * 1024) # up to 16KB
116+
signing_config_path.write_bytes(fdp.ConsumeBytes(cfg_sz))
117+
118+
# 5) Prepare signature path
119+
signature_path = Path(sigdir) / "model.signature"
120+
121+
# 6) Sign
122+
expected_identity = (
123+
fdp.ConsumeBytes(32).decode("utf-8", errors="ignore")
124+
or "default-identity"
125+
)
126+
expected_oidc_issuer = (
127+
fdp.ConsumeBytes(32).decode("utf-8", errors="ignore")
128+
or "https://example.com/"
129+
)
130+
sigstore_oidc_beacon_token = (
131+
fdp.ConsumeBytes(64).decode("utf-8", errors="ignore") or "token"
132+
)
133+
134+
sc = signing.Config()
135+
sc.use_sigstore_signer(
136+
use_staging=True, identity_token=sigstore_oidc_beacon_token
137+
)
138+
sc.sign(root, signature_path)
139+
140+
if not signature_path.exists():
141+
return
142+
143+
# 7) Verify
144+
vc = verifying.Config()
145+
vc.use_sigstore_verifier(
146+
identity=expected_identity,
147+
oidc_issuer=expected_oidc_issuer,
148+
use_staging=True,
149+
)
150+
vc.verify(root, signature_path)
151+
152+
153+
def main():
154+
atheris.instrument_all()
155+
atheris.Setup(sys.argv, TestOneInput)
156+
atheris.Fuzz()
157+
158+
159+
if __name__ == "__main__":
160+
main()

0 commit comments

Comments
 (0)