tsp-ltv 0.5.0

Shared timestamping (RFC 3161) and long-term validation (OCSP, CRL, trust stores) infrastructure for AdES signatures
Documentation

tsp-ltv

Shared timestamping (RFC 3161) and long-term validation infrastructure for Advanced Electronic Signature (AdES) formats. Provides OCSP, CRL, trust stores, and certificate chain building used by underskrift (PAdES/CAdES), bergshamra (XAdES), and jades (JAdES).

Version 0.4 requires Rust 1.88 and delegates all cryptographic operations and TLS provider configuration to kryptering 0.6.0 from crates.io.

Features

  • RFC 3161 timestamping — TSA client and pool with failover, TimeStampReq/Resp ASN.1 building and parsing, nonce-based replay protection
  • OCSP client (RFC 6960) — request building, response parsing and validation, AIA extension extraction, responder signature verification
  • CRL client (RFC 5280) — CRL fetching from distribution points, in-memory caching with configurable grace period, revocation status checking
  • Certificate chain building — local pool-based chain construction from unordered intermediates (subject/issuer name matching with signature verification at each link), AIA-based intermediate discovery, configurable recursion depth
  • Trust stores — load PEM files/directories, DER, PKCS#7 bundles; typed store sets for signature, timestamp, and SVT validation; chain verification with time validity checking
  • X.509 extension validation — BasicConstraints, KeyUsage, ExtendedKeyUsage, role-based validation (end entity, intermediate CA, CRL signer, OCSP responder)
  • Revocation checking — concurrent OCSP + CRL with priority-based result merging, configurable timeouts and policies
  • Signature verification — RSA PKCS#1 v1.5 (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, MD5), RSA-PSS, ECDSA (P-256, P-384, P-521 with SHA-1 through SHA-512), Ed25519 for certificate, OCSP, and CRL signature validation
  • Algorithm coverage — SHA-1, SHA-224, SHA-256/384/512, SHA3-256/384/512, MD5 digest algorithms with OID mappings

Design

This crate is format-agnostic — it does not know about PDF, XML, or JSON. Each AdES crate builds its own format-specific embedding (DSS dictionaries for PAdES, XAdES qualifying properties, JAdES etsiU headers) on top of these shared clients. Consumer crates typically re-export tsp-ltv modules as thin facades (e.g. pub use tsp_ltv::trust::*;).

tsp-ltv (this crate)
   ├── tsp     — RFC 3161 TSA client + ASN.1 parsing
   ├── ltv     — OCSP, CRL, chain building, revocation
   ├── trust   — trust stores, chain building from cert pools, chain validation
   ├── crypto  — digest algorithms, signature verification, OID constants
   └── der_utils — ASN.1/DER parsing utilities

Used by:
   ├── underskrift  (PAdES/CAdES — adds DSS dictionary embedding)
   ├── bergshamra   (XAdES — adds qualifying property embedding)
   └── jades        (JAdES — adds etsiU header construction)

Quick start

Add to your Cargo.toml:

[dependencies]
tsp-ltv = "0.4"

Defaults are tsp, ltv, blocking, rustcrypto, tls-ring, and legacy-algorithms. To use only a subset:

[dependencies]
tsp-ltv = { version = "0.4", default-features = false, features = ["tsp", "rustcrypto", "tls-ring"] }

For crates that only need trust store management and certificate verification (no network operations):

[dependencies]
tsp-ltv = { version = "0.4", default-features = false, features = ["rustcrypto"] }

This gives you access to the trust and crypto modules without pulling in OCSP, CRL, or TSP client dependencies.

Request a timestamp

use tsp_ltv::tsp::{TsaClient, TsaClientPool};

let client = TsaClient::new("http://timestamp.digicert.com")?;
let hash = vec![0u8; 32]; // SHA-256 hash of signature value
let token = client.timestamp(&hash).await?;

// Multiple TSAs with automatic failover
let pool = TsaClientPool::from_urls(&[
    "http://timestamp.digicert.com",
    "http://timestamp.globalsign.com/tsa/r6advanced1",
])?;
let token = pool.timestamp(&hash).await?;

Check certificate revocation

use tsp_ltv::ltv::{OcspClient, CrlClient, RevocationConfig, check_certificate_revocation};

let ocsp = OcspClient::new()?;
let crl = CrlClient::new()?;
let config = RevocationConfig::default();

let status = check_certificate_revocation(
    &cert, &issuer, &config, &crl, &ocsp, &[],
).await?;

Load trust anchors

use tsp_ltv::trust::{TrustStore, TrustStoreSet};

let sig_store = TrustStore::from_pem_file("ca-certs.pem")?;
let tsa_store = TrustStore::from_pem_directory("/etc/ssl/certs")?;

let stores = TrustStoreSet::new()
    .with_sig_store(sig_store)
    .with_tsa_store(tsa_store);

Build and verify a certificate chain

use tsp_ltv::trust::{TrustStore, build_chain_from_pool, trust_anchor_subjects};

// Load trust anchors
let trust_store = TrustStore::from_pem_file("ca-certs.pem")?;

// Build an ordered chain from a leaf and unordered intermediates
let anchor_subjects = trust_anchor_subjects(&trust_store);
let chain = build_chain_from_pool(&leaf_cert, &intermediates, &anchor_subjects, None)?;

// Verify signatures and time validity
trust_store.verify_chain(&chain, None)?;

Feature flags

Flag Default Description
tsp yes RFC 3161 timestamping (requires network)
ltv yes OCSP/CRL/chain building (implies tsp)
blocking yes Synchronous API wrappers via tokio::runtime::Runtime::block_on
rustcrypto yes RustCrypto document provider (exactly one document provider is required)
aws-lc no AWS-LC document provider
tls-ring yes ring TLS provider for network-enabled builds
tls-aws-lc no AWS-LC TLS provider
fips no Select AWS-LC and require explicit, attested provider initialization

With default-features = false, the trust, crypto, error, and der_utils modules are always available, but one document provider still has to be selected. Exactly one TLS provider is also required whenever tsp (and therefore ltv) enables networking. Document crypto and TLS are independent; there is no provider fallback. In FIPS builds, AWS-LC is the document provider and, when networking is enabled, the only TLS provider. --all-features is intentionally invalid.

Network clients are built fallibly with net::hardened_http_client() and carry an AttestedHttpClient; raw reqwest::Client injection is unavailable in FIPS builds. Non-FIPS callers that deliberately accept the risk must use the explicitly named unverified_http_client escape hatch.

In FIPS builds, call initialize_backend() before digesting, validation, or HTTPS client creation. Enabling the feature does not itself certify the application or deployment.

License

BSD-2-Clause