pax_global_header00006660000000000000000000000064145157400010014507gustar00rootroot0000000000000052 comment=ee5aab1dff6b76aad650e69b0c2d4f999c24d2ee webpki-v-0.101.7/000077500000000000000000000000001451574000100134015ustar00rootroot00000000000000webpki-v-0.101.7/.flake8000066400000000000000000000002061451574000100145520ustar00rootroot00000000000000[flake8] exclude = .git, __pycache__, src, third-party, target, .cargo, tests/venv max-line-length = 120 webpki-v-0.101.7/.gitattributes000066400000000000000000000002111451574000100162660ustar00rootroot00000000000000* text=auto !eol *.der binary *.sln eol=crlf *.vcxproj eol=crlf *.vcxproj.filters eol=crlf *.props eol=crlf *.bat eol=crlf *.rc eol=crlf webpki-v-0.101.7/.github/000077500000000000000000000000001451574000100147415ustar00rootroot00000000000000webpki-v-0.101.7/.github/dependabot.yml000066400000000000000000000003471451574000100175750ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: github-actions directory: "/" schedule: interval: weekly webpki-v-0.101.7/.github/workflows/000077500000000000000000000000001451574000100167765ustar00rootroot00000000000000webpki-v-0.101.7/.github/workflows/ci.yml000066400000000000000000000145111451574000100201160ustar00rootroot00000000000000name: ci permissions: contents: read on: push: pull_request: merge_group: schedule: - cron: '0 18 * * *' jobs: rustfmt: name: Format runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check formatting run: cargo fmt --all -- --check clippy: name: Clippy runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy --all-features --all-targets deny: name: Cargo Deny runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable - name: Install cargo deny uses: taiki-e/install-action@cargo-deny - run: cargo deny check # Verify that documentation builds. rustdoc: name: Check for documentation errors runs-on: ubuntu-20.04 strategy: matrix: rust_channel: - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_channel }} - name: cargo doc (all features) run: cargo doc --all-features env: RUSTDOCFLAGS: ${{ matrix.rust_channel == 'nightly' && '-Dwarnings --cfg=docsrs' || '-Dwarnings' }} package: name: Cargo Package runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable - run: cargo package test: name: Build+test runs-on: ${{ matrix.host_os }} strategy: matrix: features: - # Default - --features=alloc - --all-features - --no-default-features mode: - # debug - --release rust_channel: - stable - nightly - beta exclude: - features: # Default - features: --features=alloc - features: --no-default-features - features: --all-features mode: --release - features: --all-features mode: # debug rust_channel: nightly - features: --all-features mode: # debug rust_channel: beta include: - features: # Default mode: # debug rust_channel: stable host_os: ubuntu-20.04 - features: --features=alloc mode: # debug rust_channel: stable host_os: ubuntu-20.04 - features: --no-default-features mode: # debug rust_channel: stable host_os: ubuntu-20.04 - features: --all-features mode: --release rust_channel: stable host_os: ubuntu-20.04 - features: --all-features mode: # debug rust_channel: nightly host_os: ubuntu-20.04 - features: --all-features mode: # debug rust_channel: beta host_os: ubuntu-20.04 - features: --all-features mode: # debug rust_channel: stable host_os: macos-latest - features: --all-features mode: # debug rust_channel: stable host_os: windows-latest - features: --all-features mode: # debug rust_channel: stable host_os: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust ${{ matrix.rust_channel }} toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust_channel }} - name: cargo test (${{ matrix.mode }}, ${{ matrix.features }}) run: cargo test -vv ${{ matrix.features }} ${{ matrix.mode }} env: RUSTFLAGS: "-D warnings" msrv: name: MSRV runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install toolchain uses: dtolnay/rust-toolchain@master with: toolchain: "1.61" - run: cargo check --lib --all-features cross: name: Check cross compilation targets runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable - name: Install cross uses: taiki-e/install-action@cross - run: cross build --target i686-unknown-linux-gnu semver: name: Check semver compatibility runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 coverage: name: Measure coverage runs-on: ubuntu-20.04 steps: - name: Checkout sources uses: actions/checkout@v3 with: persist-credentials: false - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Install rust toolchain uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools - name: Measure coverage run: cargo llvm-cov --all-features --lcov --output-path ./lcov.info - name: Report to codecov.io uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_UPLOAD_TOKEN }} files: ./lcov.info fail_ci_if_error: true verbose: true webpki-v-0.101.7/.github/workflows/testgen.yml000066400000000000000000000032111451574000100211670ustar00rootroot00000000000000name: Python Testgen permissions: contents: read on: pull_request: push: merge_group: schedule: - cron: '0 18 * * *' env: PYTHON_VERSION: "3.11" jobs: linting: runs-on: ubuntu-latest name: Lint steps: - name: Check out source repository uses: actions/checkout@v3 - name: Set up Python environment uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' # caching pip dependencies - name: Install Python requirements. run: pip install -r requirements.txt - name: flake8 Lint uses: py-actions/flake8@v2 - name: mypy Typecheck run: mypy ./tests - name: Black Format uses: psf/black@stable with: src: "./tests" testgen: runs-on: ubuntu-latest name: Generate Tests steps: - name: Check out source repository uses: actions/checkout@v3 - name: Set up Rust toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Set up Python environment uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' # caching pip dependencies - name: Install Python requirements. run: pip install -r requirements.txt - name: Generate test files working-directory: ./tests # Generate but don't run the test suite - we already do that in the # other CI tasks that run `cargo test`. run: python3 generate.py --no-test - name: Enforce no diff run: git diff --exit-code webpki-v-0.101.7/.gitignore000066400000000000000000000001411451574000100153650ustar00rootroot00000000000000*~ # Cargo Junk Cargo.lock target/ # IntelliJ junk *.iml .idea # Benchmark data benches/*.der webpki-v-0.101.7/Cargo.toml000066400000000000000000000045151451574000100153360ustar00rootroot00000000000000# Copyright 2015 Brian Smith. # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. [package] categories = ["cryptography", "no-std"] description = "Web PKI X.509 Certificate Verification." edition = "2021" rust-version = "1.61" license = "ISC" name = "rustls-webpki" readme = "README.md" repository = "https://github.com/rustls/webpki" version = "0.101.7" include = [ "Cargo.toml", "/LICENSE", "README.md", "src/calendar.rs", "src/cert.rs", "src/crl.rs", "src/der.rs", "src/end_entity.rs", "src/error.rs", "src/subject_name/dns_name.rs", "src/subject_name/ip_address.rs", "src/subject_name/mod.rs", "src/subject_name/name.rs", "src/subject_name/verify.rs", "src/name/verify.rs", "src/name/name.rs", "src/signed_data.rs", "src/time.rs", "src/trust_anchor.rs", "src/x509.rs", "src/verify_cert.rs", "src/lib.rs", "src/data/**/*", "tests/**", ] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [lib] name = "webpki" [features] default = ["std"] alloc = ["ring/alloc"] std = ["alloc"] [dependencies] ring = { version = "0.17", default-features = false } untrusted = "0.9" [dev-dependencies] base64 = "0.21" bencher = "0.1.5" once_cell = "1.17.2" rcgen = { version = "0.11.3", default-features = false } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" [profile.bench] opt-level = 3 debug = false rpath = false lto = true debug-assertions = false codegen-units = 1 [profile.release] opt-level = 3 debug = false rpath = false lto = true debug-assertions = false codegen-units = 1 [[bench]] name = "benchmarks" path = "benches/benchmark.rs" harness = false webpki-v-0.101.7/LICENSE000066400000000000000000000016241451574000100144110ustar00rootroot00000000000000Except as otherwise noted, this project is licensed under the following (ISC-style) terms: Copyright 2015 Brian Smith. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The files under third-party/chromium are licensed as described in third-party/chromium/LICENSE. webpki-v-0.101.7/README.md000066400000000000000000000056151451574000100146670ustar00rootroot00000000000000[![Build Status](https://github.com/rustls/webpki/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/rustls/webpki/actions/workflows/ci.yml?query=branch%3Amain) [![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/webpki/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/webpki/) [![Documentation](https://docs.rs/rustls-webpki/badge.svg)](https://docs.rs/rustls-webpki/) [![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/MCSB76RU96) webpki is a library that validates Web PKI (TLS/SSL) certificates. It's used by [Rustls](https://github.com/rustls/rustls) to handle certificate-related tasks required for implementing TLS clients and servers. webpki is written in [Rust](https://www.rust-lang.org/) and uses [*ring*](https://github.com/briansmith/ring) for cryptographic operations and low-level parsing. This is a fork of the [original webpki project](https://github.com/briansmith/webpki) which adds a number of features required by the rustls project. This fork is released as the `rustls-webpki` crate, with versions starting 0.100.0 so as to not confusingly overlap with `webpki` versions. Features =============== * Representing trust anchors - webpki requires the caller to bootstrap trust by explicitly specifying a set of trust anchors using the `TrustAnchor` type. * Parsing certificates - webpki can convert from the raw encoded form of a certificate into something that can be used for making trust decisions. * Path building - webpki can determine if a certificate for an end entity like a website or client identity was issued by a trust anchor, or a series of intermediate certificates the trust anchor has endorsed. * Name/usage validation - webpki can determine if a certificate is valid for a given DNS name or IP address by considering the allowed usage of the certificate and additional constraints. Limitations =============== webpki offers a minimal feature set tailored to the needs of Rustls. Notably it does not offer: * Certificate or keypair generation * Access to arbitrary certificate extensions * Parsing/representation of certificate subjects, or human-friendly display of these fields For these tasks you may prefer using webpki in combination with libraries like [x509-parser](https://github.com/rusticata/x509-parser) and [rcgen](https://github.com/est31/rcgen). Changelog ========= Release history can be found [on GitHub](https://github.com/rustls/webpki/releases). Demo ==== See https://github.com/rustls/rustls#example-code for an example of using webpki. License ======= See [LICENSE](LICENSE). This project happily accepts pull requests without any formal copyright/contributor license agreement. Bug Reporting ============= Please refer to the [SECURITY](SECURITY.md) policy for security issues. All other bugs should be reported as [GitHub issues](https://github.com/rustls/webpki/issues/new). webpki-v-0.101.7/SECURITY.md000066400000000000000000000014031451574000100151700ustar00rootroot00000000000000# Security Policy ## Supported Versions Security fixes will be backported only to the webpki versions for which the original semver-compatible release was published less than 2 years ago. For example, as of 2023-06-13 the latest release is 0.100.1 * 0.100.0 was released in March of 2023 * 0.17.0 was released in August of 2017 Therefore 0.100.x will be updated, while 0.17.x will not be. ## Reporting a Vulnerability Please report security bugs by email to rustls-security@googlegroups.com. We'll then: - Prepare a fix and regression tests. - Backport the fix and make a patch release for most recent release. - Submit an advisory to [rustsec/advisory-db](https://github.com/RustSec/advisory-db). - Refer to the advisory on the main README.md and release notes. webpki-v-0.101.7/benches/000077500000000000000000000000001451574000100150105ustar00rootroot00000000000000webpki-v-0.101.7/benches/benchmark.rs000066400000000000000000000223431451574000100173140ustar00rootroot00000000000000use 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 std::fs::File; use std::hint::black_box; use std::io::{ErrorKind, Read, Write}; use std::path::Path; use std::sync::Mutex; use webpki::{BorrowedCertRevocationList, CertRevocationList}; /// 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(); 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(); 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(|i| { revoked_certs.push(RevokedCertParams { serial_number: SerialNumber::from((i + 1) as u64), revocation_time: date_time_ymd(2024, 6, 17), reason_code: Some(RevocationReason::KeyCompromise), invalidity_date: None, }); }); let crl = CertificateRevocationListParams { this_update: date_time_ymd(2023, 6, 17), next_update: date_time_ymd(2024, 6, 17), crl_number: SerialNumber::from(1234), alg: &PKCS_ECDSA_P256_SHA256, key_identifier_method: KeyIdMethod::Sha256, issuing_distribution_point: None, 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 into a borrowed representation. fn bench_parse_borrowed_crl_small(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/small.crl.der", SMALL_CRL_CERT_COUNT); c.iter(|| BorrowedCertRevocationList::from_der(&crl_bytes).unwrap()); } /// Benchmark parsing a small CRL file into an owned representation. fn bench_parse_owned_crl_small(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/small.crl.der", SMALL_CRL_CERT_COUNT); c.iter(|| { BorrowedCertRevocationList::from_der(&crl_bytes) .unwrap() .to_owned() .unwrap() }); } /// Benchmark parsing a medium CRL file into a borrowed representation.. fn bench_parse_borrowed_crl_medium(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/medium.crl.der", MEDIUM_CRL_CERT_COUNT); c.iter(|| BorrowedCertRevocationList::from_der(&crl_bytes).unwrap()); } /// Benchmark parsing a medium CRL file into an owned representation.. fn bench_parse_owned_crl_medium(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/medium.crl.der", MEDIUM_CRL_CERT_COUNT); c.iter(|| { BorrowedCertRevocationList::from_der(&crl_bytes) .unwrap() .to_owned() .unwrap() }); } /// Benchmark parsing a large CRL file into a borrowed representation.. fn bench_parse_borrowed_crl_large(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/large.crl.der", LARGE_CRL_CERT_COUNT); c.iter(|| BorrowedCertRevocationList::from_der(&crl_bytes).unwrap()); } /// Benchmark parsing a large CRL file into an owned representation.. fn bench_parse_owned_crl_large(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/large.crl.der", LARGE_CRL_CERT_COUNT); c.iter(|| { BorrowedCertRevocationList::from_der(&crl_bytes) .unwrap() .to_owned() .unwrap() }); } /// Benchmark searching a small CRL file in borrowed representation 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_borrowed_crl_small(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/small.crl.der", SMALL_CRL_CERT_COUNT); let crl = BorrowedCertRevocationList::from_der(&crl_bytes).unwrap(); c.iter(|| black_box(assert!(matches!(crl.find_serial(FAKE_SERIAL), Ok(None))))); } /// Benchmark searching a small CRL file in owned representation 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_owned_crl_small(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/small.crl.der", SMALL_CRL_CERT_COUNT); let crl = BorrowedCertRevocationList::from_der(&crl_bytes) .unwrap() .to_owned() .unwrap(); c.iter(|| black_box(assert!(matches!(crl.find_serial(FAKE_SERIAL), Ok(None))))); } /// Benchmark searching a medium CRL file in borrowed representation 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_borrowed_crl_medium(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/medium.crl.der", MEDIUM_CRL_CERT_COUNT); let crl = BorrowedCertRevocationList::from_der(&crl_bytes).unwrap(); c.iter(|| black_box(assert!(matches!(crl.find_serial(FAKE_SERIAL), Ok(None))))); } /// Benchmark searching a medium CRL file in owned representation 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_owned_crl_medium(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/medium.crl.der", MEDIUM_CRL_CERT_COUNT); let crl = BorrowedCertRevocationList::from_der(&crl_bytes) .unwrap() .to_owned() .unwrap(); c.iter(|| black_box(assert!(matches!(crl.find_serial(FAKE_SERIAL), Ok(None))))); } /// Benchmark searching a large CRL file in borrowed representation 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_borrowed_crl_large(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/large.crl.der", LARGE_CRL_CERT_COUNT); let crl = BorrowedCertRevocationList::from_der(&crl_bytes).unwrap(); c.iter(|| black_box(assert!(matches!(crl.find_serial(FAKE_SERIAL), Ok(None))))); } /// Benchmark searching a large CRL file in owned representation 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_owned_crl_large(c: &mut Bencher) { let crl_bytes = load_or_generate("./benches/large.crl.der", LARGE_CRL_CERT_COUNT); let crl = BorrowedCertRevocationList::from_der(&crl_bytes) .unwrap() .to_owned() .unwrap(); c.iter(|| black_box(assert!(matches!(crl.find_serial(FAKE_SERIAL), Ok(None))))); } benchmark_group!( crl_benches, bench_parse_borrowed_crl_small, bench_parse_owned_crl_small, bench_parse_borrowed_crl_medium, bench_parse_owned_crl_medium, bench_parse_borrowed_crl_large, bench_parse_owned_crl_large, bench_search_borrowed_crl_small, bench_search_owned_crl_small, bench_search_borrowed_crl_medium, bench_search_owned_crl_medium, bench_search_borrowed_crl_large, bench_search_owned_crl_large, ); benchmark_main!(crl_benches); webpki-v-0.101.7/deny.toml000066400000000000000000000017111451574000100152350ustar00rootroot00000000000000[advisories] unmaintained = "deny" yanked = "deny" notice = "deny" [licenses] allow = [ "Apache-2.0", "ISC", "LicenseRef-ring", "LicenseRef-webpki", "MIT", "Unicode-DFS-2016", ] confidence-threshold = 1.0 [[licenses.clarify]] name = "ring" expression = "LicenseRef-ring" license-files = [ { path = "LICENSE", hash = 0xbd0eed23 }, ] # XXX: Figure out how to deal with the Google-source test data # https://github.com/briansmith/webpki/issues/148. [[licenses.clarify]] name = "rustls-webpki" expression = "LicenseRef-webpki" license-files = [ { path = "LICENSE", hash = 0x001c7e6c }, ] [bans] # We don't maintain a fixed Cargo.lock so enforcing # `multiple-versions = "deny"` is impractical. multiple-versions = "allow" wildcards = "deny" [sources] unknown-registry = "deny" unknown-git = "deny" # Dev dependency only. # TODO(@cpu): Remove once rcgen cuts a release w/ crl support. allow-git = [ "https://github.com/est31/rcgen" ] webpki-v-0.101.7/mypy.ini000066400000000000000000000000251451574000100150750ustar00rootroot00000000000000[mypy] strict = True webpki-v-0.101.7/requirements.txt000066400000000000000000000001731451574000100166660ustar00rootroot00000000000000cffi==1.15.1 cryptography==41.0.2 mypy==1.3.0 mypy-extensions==1.0.0 pycparser==2.21 tomli==2.0.1 typing_extensions==4.5.0 webpki-v-0.101.7/rustfmt.toml000066400000000000000000000001571451574000100160050ustar00rootroot00000000000000edition = "2018" max_width = 100 newline_style = "Unix" reorder_imports = true use_field_init_shorthand = true webpki-v-0.101.7/src/000077500000000000000000000000001451574000100141705ustar00rootroot00000000000000webpki-v-0.101.7/src/calendar.rs000066400000000000000000000160231451574000100163110ustar00rootroot00000000000000// Copyright 2015-2016 Brian Smith. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use super::{time::Time, Error}; pub(crate) fn time_from_ymdhms_utc( year: u64, month: u64, day_of_month: u64, hours: u64, minutes: u64, seconds: u64, ) -> Result { let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?; const JAN: u64 = 31; let feb = days_in_feb(year); const MAR: u64 = 31; const APR: u64 = 30; const MAY: u64 = 31; const JUN: u64 = 30; const JUL: u64 = 31; const AUG: u64 = 31; const SEP: u64 = 30; const OCT: u64 = 31; const NOV: u64 = 30; let days_before_month_in_year = match month { 1 => 0, 2 => JAN, 3 => JAN + feb, 4 => JAN + feb + MAR, 5 => JAN + feb + MAR + APR, 6 => JAN + feb + MAR + APR + MAY, 7 => JAN + feb + MAR + APR + MAY + JUN, 8 => JAN + feb + MAR + APR + MAY + JUN + JUL, 9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG, 10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP, 11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT, 12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV, _ => unreachable!(), // `read_two_digits` already bounds-checked it. }; let days_before = days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1; let seconds_since_unix_epoch = (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds; Ok(Time::from_seconds_since_unix_epoch( seconds_since_unix_epoch, )) } fn days_before_year_since_unix_epoch(year: u64) -> Result { // We don't support dates before January 1, 1970 because that is the // Unix epoch. It is likely that other software won't deal well with // certificates that have dates before the epoch. if year < UNIX_EPOCH_YEAR { return Err(Error::BadDerTime); } let days_before_year_ad = days_before_year_ad(year); debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD); Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD) } const UNIX_EPOCH_YEAR: u64 = 1970; fn days_before_year_ad(year: u64) -> u64 { ((year - 1) * 365) + ((year - 1) / 4) // leap years are every 4 years, - ((year - 1) / 100) // except years divisible by 100, + ((year - 1) / 400) // except years divisible by 400. } pub(crate) fn days_in_month(year: u64, month: u64) -> u64 { match month { 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, 4 | 6 | 9 | 11 => 30, 2 => days_in_feb(year), _ => unreachable!(), // `read_two_digits` already bounds-checked it. } } fn days_in_feb(year: u64) -> u64 { if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) { 29 } else { 28 } } #[allow(clippy::unreadable_literal)] // TODO: Make this clear. const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 719162; #[cfg(test)] mod tests { #[test] fn test_days_before_unix_epoch() { use super::{days_before_year_ad, DAYS_BEFORE_UNIX_EPOCH_AD, UNIX_EPOCH_YEAR}; assert_eq!( DAYS_BEFORE_UNIX_EPOCH_AD, days_before_year_ad(UNIX_EPOCH_YEAR) ); } #[test] fn test_days_before_year_since_unix_epoch() { use super::{days_before_year_since_unix_epoch, Error, UNIX_EPOCH_YEAR}; assert_eq!(Ok(0), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR)); assert_eq!( Ok(365), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR + 1) ); assert_eq!( Err(Error::BadDerTime), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR - 1) ); } #[test] fn test_days_in_month() { use super::days_in_month; assert_eq!(days_in_month(2017, 1), 31); assert_eq!(days_in_month(2017, 2), 28); assert_eq!(days_in_month(2017, 3), 31); assert_eq!(days_in_month(2017, 4), 30); assert_eq!(days_in_month(2017, 5), 31); assert_eq!(days_in_month(2017, 6), 30); assert_eq!(days_in_month(2017, 7), 31); assert_eq!(days_in_month(2017, 8), 31); assert_eq!(days_in_month(2017, 9), 30); assert_eq!(days_in_month(2017, 10), 31); assert_eq!(days_in_month(2017, 11), 30); assert_eq!(days_in_month(2017, 12), 31); // leap cases assert_eq!(days_in_month(2000, 2), 29); assert_eq!(days_in_month(2004, 2), 29); assert_eq!(days_in_month(2016, 2), 29); assert_eq!(days_in_month(2100, 2), 28); } #[allow(clippy::unreadable_literal)] // TODO: Make this clear. #[test] fn test_time_from_ymdhms_utc() { use super::{time_from_ymdhms_utc, Error, Time, UNIX_EPOCH_YEAR}; // 1969-12-31 00:00:00 assert_eq!( Err(Error::BadDerTime), time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 1, 1, 0, 0, 0) ); // 1969-12-31 23:59:59 assert_eq!( Err(Error::BadDerTime), time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 12, 31, 23, 59, 59) ); // 1970-01-01 00:00:00 assert_eq!( Time::from_seconds_since_unix_epoch(0), time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 0).unwrap() ); // 1970-01-01 00:00:01 assert_eq!( Time::from_seconds_since_unix_epoch(1), time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 1).unwrap() ); // 1971-01-01 00:00:00 assert_eq!( Time::from_seconds_since_unix_epoch(365 * 86400), time_from_ymdhms_utc(UNIX_EPOCH_YEAR + 1, 1, 1, 0, 0, 0).unwrap() ); // year boundary assert_eq!( Time::from_seconds_since_unix_epoch(1483228799), time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap() ); assert_eq!( Time::from_seconds_since_unix_epoch(1483228800), time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap() ); // not a leap year assert_eq!( Time::from_seconds_since_unix_epoch(1492449162), time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap() ); // leap year, post-feb assert_eq!( Time::from_seconds_since_unix_epoch(1460913162), time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap() ); } } webpki-v-0.101.7/src/cert.rs000066400000000000000000000227221451574000100155000ustar00rootroot00000000000000// Copyright 2015 Brian Smith. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use crate::der::Tag; use crate::signed_data::SignedData; use crate::x509::{remember_extension, set_extension_once, Extension}; use crate::{der, public_values_eq, Error}; /// An enumeration indicating whether a [`Cert`] is a leaf end-entity cert, or a linked /// list node from the CA `Cert` to a child `Cert` it issued. pub enum EndEntityOrCa<'a> { /// The [`Cert`] is a leaf end-entity certificate. EndEntity, /// The [`Cert`] is an issuer certificate, and issued the referenced child `Cert`. Ca(&'a Cert<'a>), } /// A parsed X509 certificate. pub struct Cert<'a> { pub(crate) ee_or_ca: EndEntityOrCa<'a>, pub(crate) serial: untrusted::Input<'a>, pub(crate) signed_data: SignedData<'a>, pub(crate) issuer: untrusted::Input<'a>, pub(crate) validity: untrusted::Input<'a>, pub(crate) subject: untrusted::Input<'a>, pub(crate) spki: der::Value<'a>, pub(crate) basic_constraints: Option>, // key usage (KU) extension (if any). When validating certificate revocation lists (CRLs) this // field will be consulted to determine if the cert is allowed to sign CRLs. For cert validation // this field is ignored (for more detail see in `verify_cert.rs` and // `check_issuer_independent_properties`). pub(crate) key_usage: Option>, pub(crate) eku: Option>, pub(crate) name_constraints: Option>, pub(crate) subject_alt_name: Option>, } impl<'a> Cert<'a> { pub(crate) fn from_der( cert_der: untrusted::Input<'a>, ee_or_ca: EndEntityOrCa<'a>, ) -> Result { let (tbs, signed_data) = cert_der.read_all(Error::BadDer, |cert_der| { der::nested(cert_der, der::Tag::Sequence, Error::BadDer, |der| { // limited to SEQUENCEs of size 2^16 or less. SignedData::from_der(der, der::TWO_BYTE_DER_SIZE) }) })?; tbs.read_all(Error::BadDer, |tbs| { version3(tbs)?; let serial = lenient_certificate_serial_number(tbs)?; let signature = der::expect_tag_and_get_value(tbs, der::Tag::Sequence)?; // TODO: In mozilla::pkix, the comparison is done based on the // normalized value (ignoring whether or not there is an optional NULL // parameter for RSA-based algorithms), so this may be too strict. if !public_values_eq(signature, signed_data.algorithm) { return Err(Error::SignatureAlgorithmMismatch); } let issuer = der::expect_tag_and_get_value(tbs, der::Tag::Sequence)?; let validity = der::expect_tag_and_get_value(tbs, der::Tag::Sequence)?; let subject = der::expect_tag_and_get_value(tbs, der::Tag::Sequence)?; let spki = der::expect_tag(tbs, der::Tag::Sequence)?; // In theory there could be fields [1] issuerUniqueID and [2] // subjectUniqueID, but in practice there never are, and to keep the // code small and simple we don't accept any certificates that do // contain them. let mut cert = Cert { ee_or_ca, signed_data, serial, issuer, validity, subject, spki, basic_constraints: None, key_usage: None, eku: None, name_constraints: None, subject_alt_name: None, }; if !tbs.at_end() { der::nested( tbs, der::Tag::ContextSpecificConstructed3, Error::MalformedExtensions, |tagged| { der::nested_of_mut( tagged, der::Tag::Sequence, der::Tag::Sequence, Error::BadDer, |extension| { remember_cert_extension(&mut cert, &Extension::parse(extension)?) }, ) }, )?; } Ok(cert) }) } /// Raw DER encoded certificate serial number. pub fn serial(&self) -> &[u8] { self.serial.as_slice_less_safe() } /// Raw DER encoded certificate issuer. pub fn issuer(&self) -> &[u8] { self.issuer.as_slice_less_safe() } /// Raw DER encoded certificate subject. pub fn subject(&self) -> &[u8] { self.subject.as_slice_less_safe() } /// Returns an indication of whether the certificate is an end-entity (leaf) certificate, /// or a certificate authority. pub fn end_entity_or_ca(&self) -> &EndEntityOrCa { &self.ee_or_ca } } // mozilla::pkix supports v1, v2, v3, and v4, including both the implicit // (correct) and explicit (incorrect) encoding of v1. We allow only v3. fn version3(input: &mut untrusted::Reader) -> Result<(), Error> { der::nested( input, der::Tag::ContextSpecificConstructed0, Error::UnsupportedCertVersion, |input| { let version = der::small_nonnegative_integer(input)?; if version != 2 { // v3 return Err(Error::UnsupportedCertVersion); } Ok(()) }, ) } pub(crate) fn lenient_certificate_serial_number<'a>( input: &mut untrusted::Reader<'a>, ) -> Result, Error> { // https://tools.ietf.org/html/rfc5280#section-4.1.2.2: // * Conforming CAs MUST NOT use serialNumber values longer than 20 octets." // * "The serial number MUST be a positive integer [...]" // // However, we don't enforce these constraints, as there are widely-deployed trust anchors // and many X.509 implementations in common use that violate these constraints. This is called // out by the same section of RFC 5280 as cited above: // Note: Non-conforming CAs may issue certificates with serial numbers // that are negative or zero. Certificate users SHOULD be prepared to // gracefully handle such certificates. der::expect_tag_and_get_value(input, Tag::Integer) } fn remember_cert_extension<'a>( cert: &mut Cert<'a>, extension: &Extension<'a>, ) -> Result<(), Error> { // We don't do anything with certificate policies so we can safely ignore // all policy-related stuff. We assume that the policy-related extensions // are not marked critical. remember_extension(extension, |id| { let out = match id { // id-ce-keyUsage 2.5.29.15. 15 => &mut cert.key_usage, // id-ce-subjectAltName 2.5.29.17 17 => &mut cert.subject_alt_name, // id-ce-basicConstraints 2.5.29.19 19 => &mut cert.basic_constraints, // id-ce-nameConstraints 2.5.29.30 30 => &mut cert.name_constraints, // id-ce-extKeyUsage 2.5.29.37 37 => &mut cert.eku, // Unsupported extension _ => return extension.unsupported(), }; set_extension_once(out, || { extension.value.read_all(Error::BadDer, |value| match id { // Unlike the other extensions we remember KU is a BitString and not a Sequence. We // read the raw bytes here and parse at the time of use. 15 => Ok(value.read_bytes_to_end()), // All other remembered certificate extensions are wrapped in a Sequence. _ => der::expect_tag_and_get_value(value, Tag::Sequence), }) }) }) } #[cfg(test)] mod tests { use crate::cert::{Cert, EndEntityOrCa}; #[test] // Note: cert::parse_cert is crate-local visibility, and EndEntityCert doesn't expose the // inner Cert, or the serial number. As a result we test that the raw serial value // is read correctly here instead of in tests/integration.rs. fn test_serial_read() { let ee = include_bytes!("../tests/misc/serial_neg_ee.der"); let cert = Cert::from_der(untrusted::Input::from(ee), EndEntityOrCa::EndEntity) .expect("failed to parse certificate"); assert_eq!(cert.serial.as_slice_less_safe(), &[255, 33, 82, 65, 17]); let ee = include_bytes!("../tests/misc/serial_large_positive.der"); let cert = Cert::from_der(untrusted::Input::from(ee), EndEntityOrCa::EndEntity) .expect("failed to parse certificate"); assert_eq!( cert.serial.as_slice_less_safe(), &[ 0, 230, 9, 254, 122, 234, 0, 104, 140, 224, 36, 180, 237, 32, 27, 31, 239, 82, 180, 68, 209 ] ) } } webpki-v-0.101.7/src/crl.rs000066400000000000000000000655371451574000100153360ustar00rootroot00000000000000// Copyright 2023 Daniel McCarney. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use crate::cert::lenient_certificate_serial_number; use crate::der::Tag; use crate::signed_data::{self, SignedData}; use crate::verify_cert::Budget; use crate::x509::{remember_extension, set_extension_once, Extension}; use crate::{der, public_values_eq, Error, SignatureAlgorithm, Time}; #[cfg(feature = "alloc")] use std::collections::HashMap; use private::Sealed; /// Operations over a RFC 5280[^1] profile Certificate Revocation List (CRL) required /// for revocation checking. Implemented by [`OwnedCertRevocationList`] and /// [`BorrowedCertRevocationList`]. /// /// [^1]: pub trait CertRevocationList: Sealed { /// Return the DER encoded issuer of the CRL. fn issuer(&self) -> &[u8]; /// Try to find a revoked certificate in the CRL by DER encoded serial number. This /// may yield an error if the CRL has malformed revoked certificates. fn find_serial(&self, serial: &[u8]) -> Result, Error>; /// Verify the CRL signature using the issuer's subject public key information (SPKI) /// and a list of supported signature algorithms. fn verify_signature( &self, supported_sig_algs: &[&SignatureAlgorithm], issuer_spki: &[u8], ) -> Result<(), Error>; } /// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL). /// /// [^1]: #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[allow(dead_code)] // we parse some fields we don't expose now, but may choose to expose in the future. #[derive(Debug, Clone)] pub struct OwnedCertRevocationList { /// A map of the revoked certificates contained in then CRL, keyed by the DER encoding /// of the revoked cert's serial number. revoked_certs: HashMap, OwnedRevokedCert>, issuer: Vec, signed_data: signed_data::OwnedSignedData, } #[cfg(feature = "alloc")] impl Sealed for OwnedCertRevocationList {} #[cfg(feature = "alloc")] impl CertRevocationList for OwnedCertRevocationList { fn issuer(&self) -> &[u8] { &self.issuer } fn find_serial(&self, serial: &[u8]) -> Result, Error> { // note: this is infallible for the owned representation because we process all // revoked certificates at the time of construction to build the `revoked_certs` map, // returning any encountered errors at that time. Ok(self .revoked_certs .get(serial) .map(|owned_revoked_cert| owned_revoked_cert.borrow())) } fn verify_signature( &self, supported_sig_algs: &[&SignatureAlgorithm], issuer_spki: &[u8], ) -> Result<(), Error> { signed_data::verify_signed_data( supported_sig_algs, untrusted::Input::from(issuer_spki), &self.signed_data.borrow(), &mut Budget::default(), ) } } /// Borrowed representation of a RFC 5280[^1] profile Certificate Revocation List (CRL). /// /// [^1]: #[derive(Debug)] pub struct BorrowedCertRevocationList<'a> { /// A `SignedData` structure that can be passed to `verify_signed_data`. signed_data: SignedData<'a>, /// Identifies the entity that has signed and issued this /// CRL. issuer: untrusted::Input<'a>, /// List of certificates revoked by the issuer in this CRL. revoked_certs: untrusted::Input<'a>, } impl<'a> BorrowedCertRevocationList<'a> { /// Try to parse the given bytes as a RFC 5280[^1] profile Certificate Revocation List (CRL). /// /// Webpki does not support: /// * CRL versions other than version 2. /// * CRLs missing the next update field. /// * CRLs missing certificate revocation list extensions. /// * Delta CRLs. /// * CRLs larger than (2^32)-1 bytes in size. /// /// [^1]: pub fn from_der(crl_der: &'a [u8]) -> Result { // Try to parse the CRL. let reader = untrusted::Input::from(crl_der); let (tbs_cert_list, signed_data) = reader.read_all(Error::BadDer, |crl_der| { der::nested_limited( crl_der, Tag::Sequence, Error::BadDer, |signed_der| SignedData::from_der(signed_der, der::MAX_DER_SIZE), der::MAX_DER_SIZE, ) })?; let crl = tbs_cert_list.read_all(Error::BadDer, |tbs_cert_list| { // RFC 5280 §5.1.2.1: // This optional field describes the version of the encoded CRL. When // extensions are used, as required by this profile, this field MUST be // present and MUST specify version 2 (the integer value is 1). // RFC 5280 §5.2: // Conforming CRL issuers are REQUIRED to include the authority key // identifier (Section 5.2.1) and the CRL number (Section 5.2.3) // extensions in all CRLs issued. // As a result of the above we parse this as a required section, not OPTIONAL. // NOTE: Encoded value of version 2 is 1. if der::small_nonnegative_integer(tbs_cert_list)? != 1 { return Err(Error::UnsupportedCrlVersion); } // RFC 5280 §5.1.2.2: // This field MUST contain the same algorithm identifier as the // signatureAlgorithm field in the sequence CertificateList let signature = der::expect_tag_and_get_value(tbs_cert_list, Tag::Sequence)?; if !public_values_eq(signature, signed_data.algorithm) { return Err(Error::SignatureAlgorithmMismatch); } // RFC 5280 §5.1.2.3: // The issuer field MUST contain a non-empty X.500 distinguished name (DN). let issuer = der::expect_tag_and_get_value(tbs_cert_list, Tag::Sequence)?; // RFC 5280 §5.1.2.4: // This field indicates the issue date of this CRL. thisUpdate may be // encoded as UTCTime or GeneralizedTime. // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on // whether the date is post 2050. der::time_choice(tbs_cert_list)?; // While OPTIONAL in the ASN.1 module, RFC 5280 §5.1.2.5 says: // Conforming CRL issuers MUST include the nextUpdate field in all CRLs. // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on // whether the date is post 2050. der::time_choice(tbs_cert_list)?; // RFC 5280 §5.1.2.6: // When there are no revoked certificates, the revoked certificates list // MUST be absent // TODO(@cpu): Do we care to support empty CRLs if we don't support delta CRLs? let revoked_certs = if tbs_cert_list.peek(Tag::Sequence.into()) { der::expect_tag_and_get_value_limited( tbs_cert_list, Tag::Sequence, der::MAX_DER_SIZE, )? } else { untrusted::Input::from(&[]) }; let mut crl = BorrowedCertRevocationList { signed_data, issuer, revoked_certs, }; // RFC 5280 §5.1.2.7: // This field may only appear if the version is 2 (Section 5.1.2.1). If // present, this field is a sequence of one or more CRL extensions. // RFC 5280 §5.2: // Conforming CRL issuers are REQUIRED to include the authority key // identifier (Section 5.2.1) and the CRL number (Section 5.2.3) // extensions in all CRLs issued. // As a result of the above we parse this as a required section, not OPTIONAL. der::nested( tbs_cert_list, Tag::ContextSpecificConstructed0, Error::MalformedExtensions, |tagged| { der::nested_of_mut( tagged, Tag::Sequence, Tag::Sequence, Error::BadDer, |extension| { // RFC 5280 §5.2: // If a CRL contains a critical extension // that the application cannot process, then the application MUST NOT // use that CRL to determine the status of certificates. However, // applications may ignore unrecognized non-critical extensions. crl.remember_extension(&Extension::parse(extension)?) }, ) }, )?; Ok(crl) })?; Ok(crl) } /// Convert the CRL to an [`OwnedCertRevocationList`]. This may error if any of the revoked /// certificates in the CRL are malformed or contain unsupported features. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn to_owned(&self) -> Result { // Parse and collect the CRL's revoked cert entries, ensuring there are no errors. With // the full set in-hand, create a lookup map by serial number for fast revocation checking. let revoked_certs = self .into_iter() .collect::, _>>()? .iter() .map(|revoked_cert| (revoked_cert.serial_number.to_vec(), revoked_cert.to_owned())) .collect::>(); Ok(OwnedCertRevocationList { signed_data: self.signed_data.to_owned(), issuer: self.issuer.as_slice_less_safe().to_vec(), revoked_certs, }) } fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> { remember_extension(extension, |id| { match id { // id-ce-cRLNumber 2.5.29.20 - RFC 5280 §5.2.3 20 => { // RFC 5280 §5.2.3: // CRL verifiers MUST be able to handle CRLNumber values // up to 20 octets. Conforming CRL issuers MUST NOT use CRLNumber // values longer than 20 octets. // extension.value.read_all(Error::InvalidCrlNumber, |der| { let crl_number = ring::io::der::positive_integer(der) .map_err(|_| Error::InvalidCrlNumber)? .big_endian_without_leading_zero(); if crl_number.len() <= 20 { Ok(crl_number) } else { Err(Error::InvalidCrlNumber) } })?; // We enforce the cRLNumber is sensible, but don't retain the value for use. Ok(()) } // id-ce-deltaCRLIndicator 2.5.29.27 - RFC 5280 §5.2.4 // We explicitly do not support delta CRLs. 27 => Err(Error::UnsupportedDeltaCrl), // id-ce-issuingDistributionPoint 2.5.29.28 - RFC 5280 §5.2.4 // Although the extension is critical, conforming implementations are not // required to support this extension. However, implementations that do not // support this extension MUST either treat the status of any certificate not listed // on this CRL as unknown or locate another CRL that does not contain any // unrecognized critical extensions. // TODO(@cpu): We may want to parse this enough to be able to error on indirectCRL // bool == true, or to enforce validation based on onlyContainsUserCerts, // onlyContainsCACerts, and onlySomeReasons. For now we use the carve-out where // we'll treat it as understood without parsing and consider certificates not found // in the list as unknown. 28 => Ok(()), // id-ce-authorityKeyIdentifier 2.5.29.35 - RFC 5280 §5.2.1, §4.2.1.1 // We recognize the extension but don't retain its value for use. 35 => Ok(()), // Unsupported extension _ => extension.unsupported(), } }) } } impl Sealed for BorrowedCertRevocationList<'_> {} impl CertRevocationList for BorrowedCertRevocationList<'_> { fn issuer(&self) -> &[u8] { self.issuer.as_slice_less_safe() } fn find_serial(&self, serial: &[u8]) -> Result, Error> { for revoked_cert_result in self { match revoked_cert_result { Err(e) => return Err(e), Ok(revoked_cert) => { if revoked_cert.serial_number.eq(serial) { return Ok(Some(revoked_cert)); } } } } Ok(None) } fn verify_signature( &self, supported_sig_algs: &[&SignatureAlgorithm], issuer_spki: &[u8], ) -> Result<(), Error> { signed_data::verify_signed_data( supported_sig_algs, untrusted::Input::from(issuer_spki), &self.signed_data, &mut Budget::default(), ) } } impl<'a> IntoIterator for &'a BorrowedCertRevocationList<'a> { type Item = Result, Error>; type IntoIter = RevokedCerts<'a>; fn into_iter(self) -> Self::IntoIter { RevokedCerts { reader: untrusted::Reader::new(self.revoked_certs), } } } #[derive(Debug)] pub struct RevokedCerts<'a> { reader: untrusted::Reader<'a>, } impl<'a> Iterator for RevokedCerts<'a> { type Item = Result, Error>; fn next(&mut self) -> Option { (!self.reader.at_end()).then(|| BorrowedRevokedCert::from_der(&mut self.reader)) } } /// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL) revoked /// certificate entry. /// /// Only available when the "alloc" feature is enabled. /// /// [^1]: #[cfg(feature = "alloc")] #[derive(Clone, Debug)] pub struct OwnedRevokedCert { /// Serial number of the revoked certificate. pub serial_number: Vec, /// The date at which the CA processed the revocation. pub revocation_date: Time, /// Identifies the reason for the certificate revocation. When absent, the revocation reason /// is assumed to be RevocationReason::Unspecified. For consistency with other extensions /// and to ensure only one revocation reason extension may be present we maintain this field /// as optional instead of defaulting to unspecified. pub reason_code: Option, /// Provides the date on which it is known or suspected that the private key was compromised or /// that the certificate otherwise became invalid. This date may be earlier than the revocation /// date which is the date at which the CA processed the revocation. pub invalidity_date: Option