diff --git a/.gitignore b/.gitignore index 767fb5e3..e75e92f8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ target/ # IntelliJ junk *.iml .idea + +# Benchmark data +benches/*.der diff --git a/Cargo.toml b/Cargo.toml index 49d93ca4..754b5306 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ name = "rustls-webpki" readme = "README.md" repository = "https://github.com/rustls/webpki" version = "0.100.1" +autobenches = false # TODO(@cpu): Necessary? include = [ "Cargo.toml", @@ -84,6 +85,9 @@ untrusted = "0.7.1" [dev-dependencies] base64 = "0.13" +bencher = "0.1.5" +once_cell = "1.17.2" # 1.18.0+ requires MSRV of 1.60+ +rcgen = { version = "0.11.1", default-features = false } [profile.bench] opt-level = 3 @@ -100,3 +104,11 @@ rpath = false lto = true debug-assertions = false codegen-units = 1 + +[[bench]] +name = "benchmarks" +path = "benches/benchmark.rs" +harness = false + +[patch.crates-io] +rcgen = { git = 'https://github.com/cpu/rcgen.git', branch = 'cpu-109-crl-support' } diff --git a/benches/benchmark.rs b/benches/benchmark.rs new file mode 100644 index 00000000..f76062c7 --- /dev/null +++ b/benches/benchmark.rs @@ -0,0 +1,153 @@ +use bencher::{benchmark_group, benchmark_main, Bencher}; +use once_cell::sync::Lazy; +use rcgen::{ + date_time_ymd, BasicConstraints, Certificate, CertificateParams, CertificateRevocationList, + CertificateRevocationListParams, IsCa, KeyIdMethod, KeyUsagePurpose, RevocationReason, + RevokedCertParams, SerialNumber, PKCS_ECDSA_P256_SHA256, +}; + +use webpki::CertRevocationList; + +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; +use std::path::Path; +use std::sync::Mutex; + +/// Lazy initialized CRL issuer to be used when generating CRL data. Includes +/// `KeyUsagePurpose::CrlSign` key usage bit. +static CRL_ISSUER: Lazy> = Lazy::new(|| { + let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]); + issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + issuer_params.key_usages = vec![ + KeyUsagePurpose::KeyCertSign, + KeyUsagePurpose::DigitalSignature, + KeyUsagePurpose::CrlSign, + ]; + Mutex::new(Certificate::from_params(issuer_params).unwrap()) +}); + +/// Number of revoked certificates to include in the small benchmark CRL. Produces a CRL roughly +/// ~72kb in size when serialized to disk. +const SMALL_CRL_CERT_COUNT: usize = 2_000; +/// Number of revoked certificates to include in the medium benchmark CRL. Produces a CRL roughly +/// ~22mb in size when serialized to disk. +const MEDIUM_CRL_CERT_COUNT: usize = 600_000; +/// Number of revoked certificates to include in the large benchmark CRL. Produces a CRL roughly +/// ~50mb in size when serialized to disk. +const LARGE_CRL_CERT_COUNT: usize = 1_500_000; + +/// A fake serial number to use in the search tests. In order to provoke a full scan of the CRL +/// contents this serial should **not** appear in the revoked certificates. +const FAKE_SERIAL: &[u8] = &[0xC0, 0xFF, 0xEE]; + +/// Try to load a DER bytes from `crl_path`. If that file path does not exist, generate a CRL +/// with `revoked_count` revoked certificates, write the DER encoding to `crl_path` and return the +/// newly created DER bytes. +fn load_or_generate(crl_path: impl AsRef + Copy, revoked_count: usize) -> Vec { + match File::open(crl_path) { + Ok(mut crl_file) => { + let mut crl_der = Vec::new(); + crl_file.read_to_end(&mut crl_der).unwrap(); + return crl_der; + } + Err(e) => match e.kind() { + ErrorKind::NotFound => match File::create(crl_path) { + Err(e) => panic!("unexpected err creating CRL file: {:?}", e), + Ok(mut crl_file) => { + let new_crl = generate_crl(revoked_count); + crl_file.write_all(&new_crl).unwrap(); + return new_crl; + } + }, + e => { + panic!("unexpected err opening CRL file: {:?}", e); + } + }, + } +} + +/// Create a new benchmark CRL with `revoked_count` revoked certificates. +fn generate_crl(revoked_count: usize) -> Vec { + let mut revoked_certs = Vec::with_capacity(revoked_count); + (0..revoked_count).for_each(|_| { + revoked_certs.push(RevokedCertParams { + // TODO(@cpu): Randomize parameters. + serial_number: SerialNumber::from(9999), + revocation_time: date_time_ymd(2024, 06, 17), + reason_code: Some(RevocationReason::KeyCompromise), + invalidity_date: None, + }); + }); + + let crl = CertificateRevocationListParams { + this_update: date_time_ymd(2023, 06, 17), + next_update: date_time_ymd(2024, 06, 17), + crl_number: SerialNumber::from(1234), + alg: &PKCS_ECDSA_P256_SHA256, + key_identifier_method: KeyIdMethod::Sha256, + revoked_certs, + }; + let crl = CertificateRevocationList::from_params(crl).unwrap(); + let issuer = CRL_ISSUER.lock().unwrap(); + crl.serialize_der_with_signer(&issuer).unwrap() +} + +/// Benchmark parsing a small CRL file. +fn bench_parse_crl_small(c: &mut Bencher) { + let crl_bytes = load_or_generate("./benches/small.crl.der", SMALL_CRL_CERT_COUNT); + + c.iter(|| CertRevocationList::from_der(&crl_bytes).unwrap()); +} + +/// Benchmark parsing a medium CRL file. +fn bench_parse_crl_medium(c: &mut Bencher) { + let crl_bytes = load_or_generate("./benches/medium.crl.der", MEDIUM_CRL_CERT_COUNT); + + c.iter(|| CertRevocationList::from_der(&crl_bytes).unwrap()); +} + +/// Benchmark parsing a large CRL file. +fn bench_parse_crl_large(c: &mut Bencher) { + let crl_bytes = load_or_generate("./benches/large.crl.der", LARGE_CRL_CERT_COUNT); + + c.iter(|| CertRevocationList::from_der(&crl_bytes).unwrap()); +} + +/// Benchmark searching a small CRL file for a serial that does not appear. Doesn't include the +/// time it takes to parse the CRL in the benchmark task. +fn bench_search_crl_small(c: &mut Bencher) { + let crl_bytes = load_or_generate("./benches/small.crl.der", SMALL_CRL_CERT_COUNT); + let crl = CertRevocationList::from_der(&crl_bytes).unwrap(); + + c.iter(|| crl.find_serial(FAKE_SERIAL)) +} + +/// Benchmark searching a medium CRL file for a serial that does not appear. Doesn't include the +/// time it takes to parse the CRL in the benchmark task. +fn bench_search_crl_medium(c: &mut Bencher) { + let crl_bytes = load_or_generate("./benches/medium.crl.der", MEDIUM_CRL_CERT_COUNT); + let crl = CertRevocationList::from_der(&crl_bytes).unwrap(); + + c.iter(|| crl.find_serial(FAKE_SERIAL)) +} + +/// Benchmark searching a large CRL file for a serial that does not appear. Doesn't include the +/// time it takes to parse the CRL in the benchmark task. +fn bench_search_crl_large(c: &mut Bencher) { + let crl_bytes = load_or_generate("./benches/large.crl.der", LARGE_CRL_CERT_COUNT); + let crl = CertRevocationList::from_der(&crl_bytes).unwrap(); + + c.iter(|| crl.find_serial(FAKE_SERIAL)) +} + +benchmark_group!( + crl_benches, + bench_parse_crl_small, + bench_parse_crl_medium, + bench_parse_crl_large, + bench_search_crl_small, + bench_search_crl_medium, + bench_search_crl_large, +); + +benchmark_main!(crl_benches);