synta 0.1.12

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# X.509 Path Validation

`synta-x509-verification` implements RFC 5280 §6 certificate path validation
and the CA/Browser Forum Baseline Requirements.  It is crypto-agnostic: all
signature verification is delegated to a caller-supplied `SignatureVerifier`.

## Dependency

With OpenSSL (default):

```toml
[dependencies]
synta-x509-verification = "0.1"
synta-certificate        = "0.1"
synta                    = "0.1"
```

With NSS instead of OpenSSL:

```toml
[dependencies]
synta-x509-verification = { version = "0.1", default-features = false, features = ["nss"] }
synta-certificate        = { version = "0.1", default-features = false, features = ["std", "derive", "nss"] }
synta                    = "0.1"
```

## Quick start: TLS server verification

The simplest approach uses `default_signature_verifier()`, which automatically selects
the NSS or OpenSSL backend based on which feature is active:

```rust,ignore
use std::time::{SystemTime, UNIX_EPOCH};
use synta::{Decoder, Encoding};
use synta_certificate::{Certificate, default_signature_verifier};
use synta_x509_verification::{
    ops::VerificationCertificate,
    policy::{PolicyDefinition, Subject},
    trust_store::Store,
    types::DNSName,
    verify, RevocationChecks,
};

fn parse<'a>(der: &'a [u8]) -> Certificate<'a> {
    Decoder::new(der, Encoding::Der).decode().unwrap()
}

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let root_der         = std::fs::read("root.der")?;
    let intermediate_der = std::fs::read("intermediate.der")?;
    let leaf_der         = std::fs::read("leaf.der")?;

    let root         = VerificationCertificate::new(parse(&root_der),         &root_der);
    let intermediate = VerificationCertificate::new(parse(&intermediate_der), &intermediate_der);
    let leaf         = VerificationCertificate::new(parse(&leaf_der),         &leaf_der);

    let store    = Store::new([root]);
    let hostname = DNSName::new("example.com").unwrap();
    let now      = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;

    // default_signature_verifier() selects NSS when the nss feature is active,
    // otherwise falls back to the OpenSSL verifier.
    let verifier = default_signature_verifier();

    let policy = PolicyDefinition::new_server(
        verifier,
        vec![Subject::Dns(hostname)],
        now,
    );

    let intermediates = [intermediate];
    let chain = verify(&leaf, &intermediates, &policy, &store, RevocationChecks::default())?;
    println!("Verified chain of {} certificates", chain.len());
    Ok(())
}
```

To pin to a specific backend type, use `OpensslSignatureVerifier` or
`NssSignatureVerifier` directly:

```rust,ignore
use synta_certificate::OpensslSignatureVerifier; // requires openssl feature
// or:
use synta_certificate::NssSignatureVerifier;     // requires nss feature

let policy = PolicyDefinition::new_server(OpensslSignatureVerifier, subjects, now);
```

## Validation flow

```mermaid
flowchart TD
    A([verify leaf · intermediates · policy · store · revocation])
    B["Build chain<br/>leaf → intermediates → trust anchor"]
    C["Verify each signature<br/>SignatureVerifier trait"]
    D{Signatures OK?}
    E["Apply policy<br/>EKU · validity · key size · profile"]
    F{Policy OK?}
    G["Name constraints<br/>dNSName · iPAddress · rfc822Name · directoryName"]
    H{Constraints OK?}
    I["Revocation<br/>CRL and/or OCSP — soft-fail on missing response"]
    J{Revoked?}
    K([Ok — chain])
    L([Err — ValidationError])

    A --> B --> C --> D
    D -->|yes| E
    D -->|no| L
    E --> F
    F -->|ok| G
    F -->|fail| L
    G --> H
    H -->|ok| I
    H -->|fail| L
    I --> J
    J -->|no| K
    J -->|yes| L

    classDef ok  fill:#16a34a,stroke:#14532d,color:#ffffff
    classDef err fill:#dc2626,stroke:#7f1d1d,color:#ffffff
    class K ok
    class L err
```

## Policy presets

| Constructor | EKU | Algorithm set |
|------------|-----|---------------|
| `PolicyDefinition::new_server(ops, subjects, time)` | serverAuth | CABF classical |
| `PolicyDefinition::new_client(ops, time)` | clientAuth | CABF classical |
| `PolicyDefinition::new_server_pq(ops, subjects, time)` | serverAuth | CABF + ML-DSA/ML-KEM |
| `PolicyDefinition::new_client_pq(ops, time)` | clientAuth | CABF + ML-DSA/ML-KEM |

## Validation profiles

`ValidationProfile` controls which compliance rules apply:

| Check | `WebPki` (default) | `Rfc5280` |
|---|---|---|
| NameConstraints criticality | enforced regardless of flag | MUST be critical |
| EKU in EE cert | MUST be present | absent = any purpose |
| CA cert in leaf position | rejected | permitted |

```rust,ignore
use synta_x509_verification::ValidationProfile;

let mut policy = PolicyDefinition::new_server(MyVerifier, subjects, now);
policy.profile = ValidationProfile::Rfc5280;
```

## Policy fields

All fields on `PolicyDefinition` are public:

```rust,ignore
use synta_certificate::OpensslSignatureVerifier;
use synta_x509_verification::policy::PolicyDefinition;

let now = 0i64; // Unix timestamp
let mut policy = PolicyDefinition::new_server(OpensslSignatureVerifier, vec![], now);
policy.max_chain_depth = 3;       // at most 3 intermediate CAs
policy.minimum_rsa_modulus = 3072; // require RSA-3072+
policy.extended_key_usage = None;  // skip EKU check
```

## Custom crypto backend

```rust,ignore
use synta_certificate::SignatureVerifier;

struct MyVerifier;

impl SignatureVerifier for MyVerifier {
    type Error = Box<dyn std::error::Error + Send + Sync>;

    fn verify_certificate_signature(
        &self,
        tbs_der: &[u8],
        sig_alg_der: &[u8],
        signature_bits: &[u8],
        issuer_spki_der: &[u8],
    ) -> Result<(), Self::Error> {
        // verify tbs_der using issuer_spki_der
        Ok(())
    }
}
```

## Revocation checking

```rust,ignore
use synta_x509_verification::{CrlStore, OcspStore, RevocationChecks, verify};

// No revocation
let chain = verify(&leaf, &intermediates, &policy, &store, RevocationChecks::default())?;

// CRL revocation
let mut crl_store = CrlStore::new();
crl_store.add_der(std::fs::read("issuing-ca.crl")?);
let chain = verify(&leaf, &intermediates, &policy, &store, RevocationChecks {
    crls: Some(&crl_store),
    ocsp: None,
})?;

// OCSP revocation
let mut ocsp_store = OcspStore::new();
ocsp_store.add_der(std::fs::read("leaf-ocsp.der")?);
let chain = verify(&leaf, &intermediates, &policy, &store, RevocationChecks {
    crls: None,
    ocsp: Some(&ocsp_store),
})?;
```

Revocation semantics: **soft-fail** when no matching CRL/OCSP response is found;
**hard-fail** only when a positively revoked entry is found.

## Name constraints

Four GeneralName types are evaluated:

| Type | Notes |
|------|-------|
| `dNSName` | Leading-dot optional; wildcards (`*.example.com`) supported |
| `iPAddress` | CIDR range; 8 bytes (IPv4) or 32 bytes (IPv6) |
| `rfc822Name` | Exact, OnDomain (`@domain`), or InDomain (`.domain`) |
| `directoryName` | DER byte prefix match |

Other alternatives (URI, OtherName) cause a validation error if they appear in a
NameConstraints extension.  A budget of 65 536 constraint checks per call
prevents denial-of-service.

## Post-quantum algorithm support

```rust,ignore
let policy = PolicyDefinition::new_server_pq(
    MyVerifier,
    vec![Subject::Dns(DNSName::new("example.com").unwrap())],
    now_unix,
);
```

Or extend an existing policy manually:

```rust,ignore
use synta_x509_verification::{
    WEBPKI_PERMITTED_SPKI_ALGORITHMS_WITH_PQ,
    WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS_WITH_PQ,
};

policy.permitted_spki_algorithms = WEBPKI_PERMITTED_SPKI_ALGORITHMS_WITH_PQ;
policy.permitted_signature_algorithms = WEBPKI_PERMITTED_SIGNATURE_ALGORITHMS_WITH_PQ;
```

ML-DSA and ML-KEM checks enforced automatically when a PQ algorithm appears in
the allowlist: parameter-absence, exact public-key-size validation, and KeyUsage
bit enforcement per RFC 9881 (ML-DSA) and RFC 9935 (ML-KEM).

## Error types

```rust,ignore
use synta_x509_verification::{ValidationError, ValidationErrorKind};

match err.kind {
    ValidationErrorKind::CandidatesExhausted(inner) => { /* all issuer paths exhausted */ }
    ValidationErrorKind::ExtensionError { oid, reason } => { /* extension policy failure */ }
    ValidationErrorKind::FatalError(msg) => { /* budget exceeded or hard limit */ }
    ValidationErrorKind::Other(msg) => { /* other validation failure */ }
}
```

## x509-limbo compliance

The crate is tested against the [x509-limbo](https://github.com/trailofbits/x509-limbo)
test suite with 9 747 tests passing.  Run locally:

```shell
bash synta-x509-verification/tests/limbo/fetch.sh
cargo test -p synta-x509-verification --test limbo -- --nocapture
```

## Known limitations

- OCSP CertID issuer hashes are not recomputed; matching uses `serialNumber` +
  signature verification.
- Name constraints: URI, OtherName, and other GeneralName alternatives are not
  evaluated.
- No PKIX policy mapping (Certificate Policies, Policy Mappings, etc.).
- Trust store uses byte-exact DER comparison rather than RFC 5280 DN matching
  algorithm.