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 any_files
35from utils import create_fuzz_files
36
37import model_signing
38
39
40def TestOneInput(data: bytes) -> None:
41 """Fuzzer on OSS-Fuzz: sign with a random key that parses as private."""
42 fdp = atheris.FuzzedDataProvider(data)
43
44 # Generate a random (possibly invalid) private key blob.
45 key_size = fdp.ConsumeIntInRange(0, 8 * 1024)
46 key_bytes = fdp.ConsumeBytes(key_size)
47 try:
48 serialization.load_pem_private_key(
49 key_bytes, password=None, unsafe_skip_rsa_key_validation=True
50 )
51 except ValueError:
52 return
53
54 # Separate dirs: model tree vs. signature destination.
55 with (
56 tempfile.TemporaryDirectory(prefix="mt_sign_only_") as tmpdir,
57 tempfile.TemporaryDirectory(prefix="mt_sig_fuzz_") as sigdir,
58 ):
59 root = Path(tmpdir)
60
61 # Create 0..30 files with fuzzed paths and contents.
62 create_fuzz_files(root, fdp)
63
64 # Early return if there are NO files.
65 if not any_files(root):
66 return
67
68 # Persist the parsed private key.
69 key_path = os.path.join(tmpdir, "fuzz.priv")
70 with open(key_path, "wb") as f:
71 f.write(key_bytes)
72
73 # Sign the directory root; signature goes elsewhere.
74 model_path = str(root)
75 sig_path = os.path.join(sigdir, "model.sig")
76
77 scfg = model_signing.signing.Config()
78 signer = scfg.use_elliptic_key_signer(private_key=key_path)
79 _ = signer.sign(model_path, sig_path)
80
81
82def main() -> None:
83 atheris.instrument_all()
84 atheris.Setup(sys.argv, TestOneInput)
85 atheris.Fuzz()
86
87
88if __name__ == "__main__":
89 main()