Mudrit
Guides

Signature Timestamps

Attach an RFC-3161 signature timestamp over http or https with pure-Rust TLS, add auth, or plug your own TSA client

Part of mudrit-pdfsign

An RFC-3161 timestamp binds your signature to a moment in time attested by a trusted Time-Stamp Authority (TSA), independent of the signer's own clock. It proves the signature existed before a given instant — the foundation of every PAdES profile above B-B and of long-term validation. Attach one with the builder's .timestamp(...).

use mudrit_pdfsign::prelude::*;

let cfg = SignConfig::builder()
    .place("F", [350, 60, 560, 160])?
    .timestamp(Timestamp::url("http://timestamp.comodoca.com"))
    .build();

let signed = sign_pdf(&pdf, &signer, &cfg)?;

The built-in TSA

Timestamp::url(...) uses the bundled UrlTimestamper, which speaks to both http:// and https:// authorities. TLS is pure-Rust (rustls) — no OpenSSL, no system libraries — so an https TSA works out of the box on every platform.

.timestamp(Timestamp::url("http://timestamp.comodoca.com"))       // plain http
.timestamp(Timestamp::url("https://tsa.company.com/tsr"))         // https via rustls

If you don't set a TSA but a profile requires one, the SDK falls back to DEFAULT_TSA_URL (http://timestamp.comodoca.com) — for example PAdES B-T auto-uses it when no explicit source is given.

Authenticated ("protected") TSAs

A protected TSA is configured by chaining auth onto Timestamp::url(...). All three combine with http or https:

MethodHeader sent
.basic_auth("user", "pass")Authorization: Basic …
.bearer("token")Authorization: Bearer …
.header("X-Api-Key", "…")any custom request header (call it repeatedly for several)
.timestamp(Timestamp::url("https://tsa.company.com/tsr").basic_auth("user", "pass")) // https + Basic
.timestamp(Timestamp::url("https://tsa.company.com/tsr").bearer("my-token"))         // https + Bearer
.timestamp(Timestamp::url("https://tsa.company.com/tsr").header("X-Api-Key", "…"))   // https + header

Credentials are never printed — UrlTimestamper's Debug shows only the URL and whether auth is set, so a config logged for diagnostics can't leak a token or password.

The Timestamp options

VariantMeaning
Timestamp::None (default)No timestamp. The signature is valid but carries no trusted time.
Timestamp::url(url)The built-in TSA (http/https + optional .basic_auth / .bearer / .header).
Timestamp::custom(t)Your own Timestamper — mutual-TLS, a proxy, or any non-standard flow.

Auto-sized placeholders make one extra fetch

With SigSize::Auto (the default) the SDK measures the real TSA token size to reserve exactly the right /Contents space — one extra timestamp fetch, but no extra key or PIN operation. Use SigSize::Fixed(n) if you want a single round-trip.

Custom TSA client

For anything the built-in doesn't cover — a mutual-TLS client certificate, an outbound proxy, or a vendor-specific handshake — implement the Timestamper trait with your own HTTP client and pass it via Timestamp::custom.

The contract is a single method. The SDK builds the RFC-3161 TimeStampReq (DER, content-type application/timestamp-query) and hands it to you; you POST it however you like and return the raw TimeStampResp (DER) exactly as the TSA replied. The SDK then extracts the token and verifies it commits to this request — the token's messageImprint must match, and any nonce it carries must echo the one the SDK sent — so a stale or mismatched response is rejected, never embedded.

use mudrit_pdfsign::prelude::*;

struct MyTsa { /* client, client-cert, proxy, … */ }

impl Timestamper for MyTsa {
    fn timestamp(&self, request: &[u8]) -> Result<Vec<u8>> {
        // POST `request` (a DER TimeStampReq) with your own client — reqwest/ureq, mTLS, proxy …
        // and return the raw response body (a DER TimeStampResp).
        todo!()
    }
}

let cfg = SignConfig::builder()
    .place("F", [350, 60, 560, 160])?
    .timestamp(Timestamp::custom(MyTsa { /* … */ }))
    .build();

You only handle transport. Request construction, nonce generation, token extraction, and the imprint/nonce checks stay inside the SDK — your Timestamper is just the network hop, so a broken or malicious TSA response can't slip a bad timestamp into the document.

Next

On this page