1###### Coverage stub
2import atexit
3import coverage
4cov = coverage.coverage(data_file='.coverage', cover_pylib=True)
5cov.start()
6# Register an exist handler that will print coverage
7def exit_handler():
8 cov.stop()
9 cov.save()
10atexit.register(exit_handler)
11####### End of coverage stub
12# Copyright 2025 The Sigstore Authors
13#
14# Licensed under the Apache License, Version 2.0 (the "License");
15# you may not use this file except in compliance with the License.
16# You may obtain a copy of the License at
17#
18# http://www.apache.org/licenses/LICENSE-2.0
19#
20# Unless required by applicable law or agreed to in writing, software
21# distributed under the License is distributed on an "AS IS" BASIS,
22# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23# See the License for the specific language governing permissions and
24# limitations under the License.
25
26import os
27from pathlib import Path
28import sys
29import tempfile
30
31# type: ignore
32import atheris
33from cryptography.hazmat.primitives import serialization
34from utils import _build_hashing_config_from_fdp
35from utils import any_files
36from utils import create_fuzz_files
37
38import model_signing
39
40
41def TestOneInput(data: bytes) -> None:
42 """Fuzzer on OSS-Fuzz: sign with a random key that parses as private."""
43 fdp = atheris.FuzzedDataProvider(data)
44
45 # Generate a random (possibly invalid) private key blob.
46 key_size = fdp.ConsumeIntInRange(0, 8 * 1024)
47 key_bytes = fdp.ConsumeBytes(key_size)
48 try:
49 serialization.load_pem_private_key(
50 key_bytes, password=None, unsafe_skip_rsa_key_validation=True
51 )
52 except ValueError:
53 return
54
55 # Separate dirs: model tree vs. signature destination.
56 with (
57 tempfile.TemporaryDirectory(prefix="mt_sign_only_") as tmpdir,
58 tempfile.TemporaryDirectory(prefix="mt_sig_fuzz_") as sigdir,
59 ):
60 root = Path(tmpdir)
61
62 # Create 0..30 files with fuzzed paths and contents.
63 create_fuzz_files(root, fdp)
64
65 # Early return if there are NO files.
66 if not any_files(root):
67 return
68
69 # Persist the parsed private key.
70 key_path = os.path.join(tmpdir, "fuzz.priv")
71 with open(key_path, "wb") as f:
72 f.write(key_bytes)
73
74 # Sign the directory root; signature goes elsewhere.
75 model_path = str(root)
76 sig_path = os.path.join(sigdir, "model.sig")
77
78 hcfg = _build_hashing_config_from_fdp(fdp)
79
80 scfg = model_signing.signing.Config()
81 scfg.set_hashing_config(hcfg)
82 signer = scfg.use_elliptic_key_signer(private_key=key_path)
83 _ = signer.sign(model_path, sig_path)
84
85
86def main() -> None:
87 atheris.instrument_all()
88 atheris.Setup(sys.argv, TestOneInput)
89 atheris.Fuzz()
90
91
92if __name__ == "__main__":
93 main()