x509-validator 0.3.0

X.509 certificate chain validation with RFC 5280 and server-identity policies.
Documentation
<p align="center">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/namecare/x509-validator/master/.local/logo-dark.png">
  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/namecare/x509-validator/master/.local/logo-light.png">
  <img width="33%" alt="X509-validator" src="https://raw.githubusercontent.com/namecare/x509-validator/master/.local/logo-light.png">
</picture>
</p>

# X509Validator

[![Build+test](https://github.com/namecare/x509-validator/actions/workflows/build_test.yml/badge.svg?branch=master)](https://github.com/namecare/x509-validator/actions/workflows/build_test.yml?query=branch%3Amaster)
[![Documentation](https://docs.rs/x509-validator/badge.svg)](https://docs.rs/x509-validator/)
[![Crates.io](https://img.shields.io/crates/v/x509-validator.svg)](https://crates.io/crates/x509-validator)
[![Coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnamecare%2Fx509-validator%2Fmaster%2F.local%2Fcoverage.json)](https://github.com/namecare/x509-validator/actions/workflows/build_test.yml?query=branch%3Amaster)

X.509 certificate chain validator.

## Overview

This library validates an X.509 certificate chain against a set of root certificates and a policy. This is an essential building block for a wide range
of PKI applications. It ships with a default verifier and a number of built-in verifier policies.

> This library is heavily inspired by, and follows the design of, the [verifier from the swift-certificates library][ref]. Some pragmatic ideas and project structure have been taken from [rustls]https://github.com/rustls/rustls/tree/main.

## Requirements

- Rust 1.88 or newer, edition 2024.

## Installation

Add the dependency and pick a crypto backend:

```toml
x509-validator = { version = "0.3.0", features = ["aws_lc"] }
```

| Feature | Backend | Notes                   |
|---|---|-------------------------|
| `aws_lc` | [aws-lc-rs]https://github.com/aws/aws-lc-rs | Fastest.                |
| `ring` | [ring]https://github.com/briansmith/ring | Close to aws_lc.        |
| `rust_crypto` | [RustCrypto]https://github.com/RustCrypto | Pure Rust. Slowest.     |

There is no default backend: without one of these features enabled, the crate compiles
but verifies nothing. You can also provide your own by implementing
`SignatureVerifier` — see the [`custom_crypto_backend`][custom-backend] example.

## Example code

```rust
use x509_validator::rfc5280::RFC5280Policy;
use x509_validator::store::CertificateStore;
use x509_validator::Validator;
use x509_validator::rfc5280::Timestamp;
use x509_validator::{Certificate, FromDer};

fn example(root_der: &[u8], intermediate_der: &[u8], leaf_der: &[u8], now: Timestamp) {
    let parse = |der| Certificate::from_der(der).expect("parse").1;
    let (root, intermediate, leaf) = (parse(root_der), parse(intermediate_der), parse(leaf_der));

    // Roots are trusted a priori. Intermediates are only available to build
    // through — each still has to be signed by something leading back to a root.
    let roots = CertificateStore::from_iter([root]);
    let intermediates = CertificateStore::from_iter([intermediate]);

    let policy = RFC5280Policy::new(now);
    let validator = Validator::with_policy(roots, policy);

    match validator.validate_with_diagnostics(&leaf, &intermediates, &mut |_| {}) {
        Ok(chain) => {
            // Leaf first, root last.
            for cert in chain.iter() {
                println!("{}", cert.tbs_certificate.subject);
            }
        }
        Err(reasons) => {
            // Empty means no candidate chain reached a root, so the policy was
            // never asked.
            for reason in reasons {
                println!("rejected: {reason}");
            }
        }
    }
}
```

>The closure is the diagnostic channel: chain building reports every issuer it
> considers and every candidate it discards through it. If you don't need any of
> that, call `validate(&leaf, &intermediates)` instead.

More are in [examples]:

| Example | Shows |
|---|---|
| `webpki` | What a TLS client checks: platform trust store, serverAuth, hostname |
| `apple_x5c` | Validating the `x5c` chain from an App Store JWS |
| `client_certificate` | The mutual-TLS server side |
| `pinned_root` | Trusting one private CA instead of the public web PKI |
| `diagnostics` | Reading the diagnostic callback to find out *why* a chain failed |
| `custom_crypto_backend` | Implementing `SignatureVerifier` over OpenSSL |

```sh
cargo run -p x509-validator-examples --example webpki
```

## Approach

Parsing is done by [x509-parser]. 

Crypto is swappable via the feature flags above, or you can supply your own
`SignatureVerifier`.

Policy is where the actual rules live. A `ValidationPolicy` receives each candidate chain and
accepts or rejects it. The built-in ones:

| Policy | Checks |
|---|---|
| `RFC5280Policy` | Validity period, version, basic constraints, name constraints |
| `EkuPolicy` | Extended key usage: serverAuth, clientAuth, or any purpose OID you name |
| `ServerIdentityPolicy` | Hostname or IP against the subject alternative names, RFC 6125 style |

Policies compose with the `policy!` macro, so a TLS client's checks read as
one list:

```rust
use x509_validator::rfc5280::{EkuPolicy, RFC5280Policy, Timestamp};
use x509_validator::{ServerIdentityPolicy, policy};

fn tls_client_policy(now: Timestamp, hostname: &str) -> impl x509_validator::ValidationPolicy {
    policy! {
        RFC5280Policy::new(now);
        EkuPolicy::server_auth();
        ServerIdentityPolicy::new(Some(hostname), None)
    }
}
```

## Benchmarks

Two crates, in [x509-validator-bench]:

- [`measure`][bench-measure] — Regression benchmarks.
- [`compare`][bench-compare] — Compare backends, parsers, other verifiers, and
  the Swift original across four groups ([index][bench-results]).

## Fuzzing

Four [cargo-fuzz] targets live in [fuzz], covering parsing, chain validation,
server identity matching and name constraints. They run on every pull request
and nightly; see the [fuzzing README][fuzz] to run them locally.

## Contributing

Thanks for your help improving the project! We are so happy to have you! We have a [contributing guide][contribute] to help you get involved in the X509Validator project, and everyone taking part is expected to follow our [Code of Conduct][coc].

## License

X509Validator is distributed under the following two licenses:

- Apache License version 2.0.
- MIT license.

These are included as LICENSE-APACHE and LICENSE-MIT respectively.  
You may use this software under the terms of any of these licenses, at your option.

[ref]: https://github.com/apple/swift-certificates/tree/main/Sources/X509/Verifier
[x509-parser]: https://github.com/rusticata/x509-parser
[examples]: https://github.com/namecare/x509-validator/tree/master/examples
[custom-backend]: https://github.com/namecare/x509-validator/blob/master/examples/custom_crypto_backend.rs
[x509-validator-bench]: https://github.com/namecare/x509-validator/tree/master/x509-validator-bench
[bench-measure]: https://github.com/namecare/x509-validator/blob/master/x509-validator-bench/measure/README.md
[bench-compare]: https://github.com/namecare/x509-validator/blob/master/x509-validator-bench/compare/README.md
[fuzz]: https://github.com/namecare/x509-validator/tree/master/fuzz
[cargo-fuzz]: https://github.com/rust-fuzz/cargo-fuzz
[bench-results]: https://github.com/namecare/x509-validator/blob/master/x509-validator-bench/compare/README.md#groups
[coc]: https://github.com/namecare/x509-validator/blob/master/CODE_OF_CONDUCT.md
[contribute]: https://github.com/namecare/x509-validator/blob/master/CONTRIBUTING.md