Skip to content

Commit

Permalink
benches: initial CRL parsing/searching benchmarks.
Browse files Browse the repository at this point in the history
This commit adds two kinds of CRL benchmark tests:

1. Parsing benchmarks, testing how long it takes to use
   `CertRevocationList::from_der`.
2. Searching benchmarks, testing how long it takes to search an already
   parsed CRL for a serial number that doesn't appear in the CRL.

In both cases we add benchmarks that use three sizes of CRLs:

1. a small CRL (2,000 serials, ~72kb)
2. a medium CRL (600,000 serials, ~22mb)
3. a large CRL (1,500,000 serials, ~50mb)

Since large test files aren't suitable for inclusion in git, these CRLs
are generated on first benchmark run using `rcgen`. The upstream `rcgen`
project doesn't yet have CRL generation support, so a Cargo patch is
used to pin the `rcgen` dependency to a branch of a fork that adds the
required capability.

Initial results from my laptop:
```
running 6 tests
test bench_parse_crl_large   ... bench:  38,383,165 ns/iter (+/- 5,642,343)
test bench_parse_crl_medium  ... bench:  15,027,127 ns/iter (+/- 318,397)
test bench_parse_crl_small   ... bench:      49,869 ns/iter (+/- 2,452)
test bench_search_crl_large  ... bench:  38,301,688 ns/iter (+/- 904,333)
test bench_search_crl_medium ... bench:  15,252,508 ns/iter (+/- 479,950)
test bench_search_crl_small  ... bench:      50,118 ns/iter (+/- 1,738)

test result: ok. 0 passed; 0 failed; 0 ignored; 6 measured
```

Laptop specs:
* rustc 1.69.0
* NixOS 22.11
* 12th Gen Intel(R) Core(TM) i7-1280P
* 32GB DDR4-3200
* WD_BLACK SN850 NVMe
  • Loading branch information
cpu committed Jun 22, 2023
1 parent 5105ccc commit 5a6fc94
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ target/
# IntelliJ junk
*.iml
.idea

# Benchmark data
benches/*.der
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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' }
153 changes: 153 additions & 0 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -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<Mutex<Certificate>> = 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<Path> + Copy, revoked_count: usize) -> Vec<u8> {
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<u8> {
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);

0 comments on commit 5a6fc94

Please sign in to comment.