diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 00000000..e14b95f9 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,53 @@ +name: Fuzz + +permissions: + contents: read + +on: + push: + branches: ["main", "ci/*"] + pull_request: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + fuzz: + name: Fuzz Testing + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Install cargo-fuzz + run: cargo install cargo-fuzz + + - name: Build seed corpus for certificate fuzzers + run: | + mkdir -p fuzz/seed-certs + find tests -name "*.der" ! -path "*/crls/*" ! -name "*.crl.der" -exec cp {} fuzz/seed-certs/ \; + + - name: Fuzz CRL parsing + working-directory: fuzz + run: | + mkdir -p corpus/crl + cargo fuzz run crl corpus/crl ../tests/crls -- -max_total_time=60 + + - name: Fuzz certificate parsing + working-directory: fuzz + run: | + mkdir -p corpus/cert + cargo fuzz run cert corpus/cert seed-certs -- -max_total_time=60 + + - name: Fuzz trust anchor extraction + working-directory: fuzz + run: | + mkdir -p corpus/anchor + cargo fuzz run anchor corpus/anchor seed-certs -- -max_total_time=60 diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..947d747b --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +artifacts +corpus +seed-certs diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 00000000..9f013013 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,136 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.104.0-alpha.7" +dependencies = [ + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "webpki-fuzz" +version = "0.0.1" +dependencies = [ + "libfuzzer-sys", + "rustls-pki-types", + "rustls-webpki", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..f8a4c60f --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "webpki-fuzz" +version = "0.0.1" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +pki-types = { package = "rustls-pki-types", version = "1" } +webpki = { package = "rustls-webpki", path = "..", default-features = false, features = ["alloc"] } + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "crl" +path = "fuzzers/crl.rs" + +[[bin]] +name = "cert" +path = "fuzzers/cert.rs" + +[[bin]] +name = "anchor" +path = "fuzzers/anchor.rs" diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..a4f23081 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,35 @@ +# Fuzz Testing + +Webpki supports fuzz testing using [cargo-fuzz]. See the [cargo-fuzz setup] +instructions for requirements (requires nightly Rust). + +## Fuzz Targets + +- `crl` - Fuzz `BorrowedCertRevocationList::from_der()` +- `cert` - Fuzz `EndEntityCert::try_from()` +- `anchor` - Fuzz `anchor_from_trusted_cert()` + +## Running Locally + +```shell +$ cargo fuzz list +anchor +cert +crl + +# Fuzz CRL parsing (uses tests/crls as seed corpus) +$ mkdir -p corpus/crl +$ cargo fuzz run crl corpus/crl ../tests/crls -- -max_total_time=120 + +# Fuzz certificate parsing (build seed corpus first) +$ mkdir -p corpus/cert seed-certs +$ find ../tests -name "*.der" ! -path "*/crls/*" ! -name "*.crl.der" -exec cp {} seed-certs/ \; +$ cargo fuzz run cert corpus/cert seed-certs -- -max_total_time=120 + +# Fuzz trust anchor extraction (reuses seed-certs) +$ mkdir -p corpus/anchor +$ cargo fuzz run anchor corpus/anchor seed-certs -- -max_total_time=120 +``` + +[cargo-fuzz]: https://github.com/rust-fuzz/cargo-fuzz +[cargo-fuzz setup]: https://rust-fuzz.github.io/book/cargo-fuzz/setup.html diff --git a/fuzz/fuzzers/anchor.rs b/fuzz/fuzzers/anchor.rs new file mode 100644 index 00000000..218a496e --- /dev/null +++ b/fuzz/fuzzers/anchor.rs @@ -0,0 +1,12 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; +extern crate webpki; + +use pki_types::CertificateDer; +use webpki::anchor_from_trusted_cert; + +fuzz_target!(|data: &[u8]| { + let _ = anchor_from_trusted_cert(&CertificateDer::from(data)); +}); diff --git a/fuzz/fuzzers/cert.rs b/fuzz/fuzzers/cert.rs new file mode 100644 index 00000000..5839aa3b --- /dev/null +++ b/fuzz/fuzzers/cert.rs @@ -0,0 +1,12 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; +extern crate webpki; + +use pki_types::CertificateDer; +use webpki::EndEntityCert; + +fuzz_target!(|data: &[u8]| { + let _ = EndEntityCert::try_from(&CertificateDer::from(data)); +}); diff --git a/fuzz/fuzzers/crl.rs b/fuzz/fuzzers/crl.rs new file mode 100644 index 00000000..5ab19aa4 --- /dev/null +++ b/fuzz/fuzzers/crl.rs @@ -0,0 +1,11 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; +extern crate webpki; + +use webpki::BorrowedCertRevocationList; + +fuzz_target!(|data: &[u8]| { + let _ = BorrowedCertRevocationList::from_der(data); +});