RFC 3161 Time-Stamp Protocol — anchor an audit chain head against an external Time-Stamping Authority (TSA).
The library doesn't trust the operator's clock for "this entry was
inserted at time T" — Pkcs11ex.Audit.Entry.inserted_at is whatever
the operator says. RFC 3161 fixes that: send the chain head's
content_hash to a third-party TSA, get back a TimeStampToken (TST,
a CMS SignedData) that binds the hash to a TSA-attested time. Store
the TST as an audit entry. Auditors verify the TST against the TSA's
certificate chain to bound when the chain reached that state.
Request structure (RFC 3161 §2.4.1)
TimeStampReq ::= SEQUENCE {
version INTEGER { v1(1) },
messageImprint MessageImprint,
reqPolicy TSAPolicyId OPTIONAL,
nonce INTEGER OPTIONAL,
certReq BOOLEAN DEFAULT FALSE,
extensions [0] IMPLICIT Extensions OPTIONAL
}
MessageImprint ::= SEQUENCE {
hashAlgorithm AlgorithmIdentifier,
hashedMessage OCTET STRING
}This module emits SHA-256 only and includes a 64-bit random nonce.
What this module does NOT do
- Parse the response. The TST is stored opaquely as the entry's payload. Verification (TST signature against TSA cert chain) is the auditor's job and an entirely separate workflow.
- Verify the TSA's certificate chain.
- Pick a TSA. Apps configure their TSA URL.
Network
Uses OTP :httpc (requires :inets started; added to extra_applications
in mix.exs).
Summary
Functions
Build an RFC 3161 TimeStampReq DER over hash_bytes (which must be
exactly 32 bytes — SHA-256 output).
Extracts the TimeStampToken (a ContentInfo per RFC 3161 §2.4.2)
from a TimeStampResp body returned by fetch_token/3.
POST a TimeStampReq DER to a TSA over HTTP and return the response bytes.
Types
@type request() :: %{der: binary(), nonce: non_neg_integer(), hash: binary()}
Output of build_request/2 — the encoded request and the random nonce we used.
Functions
Build an RFC 3161 TimeStampReq DER over hash_bytes (which must be
exactly 32 bytes — SHA-256 output).
Returns {:ok, request} where request is a map with :der (the
bytes to POST), :nonce (random integer included in the request), and
:hash (echoed back for the audit entry).
@spec extract_token(binary()) :: {:ok, binary()} | {:error, {:tsa_status, non_neg_integer()} | :missing_time_stamp_token | {:malformed_tsa_response, term()}}
Extracts the TimeStampToken (a ContentInfo per RFC 3161 §2.4.2)
from a TimeStampResp body returned by fetch_token/3.
RFC 3161 §2.4.2 grammar:
TimeStampResp ::= SEQUENCE {
status PKIStatusInfo,
timeStampToken TimeStampToken OPTIONAL
}The TST is OPTIONAL — present only when PKIStatus is granted (0)
or grantedWithMods (1). This function refuses to extract on any
other status. Returns the TST DER bytes verbatim — they are a CMS
ContentInfo (id-signedData) ready to embed as the value of a
signature-time-stamp-token unsigned attribute (PAdES B-T) or a
<xades:EncapsulatedTimeStamp> (XAdES B-T).
POST a TimeStampReq DER to a TSA over HTTP and return the response bytes.
Content type is application/timestamp-query; response Content-Type
is expected to be application/timestamp-reply. The library doesn't
parse the response — apps and auditors verify the TST against the
TSA's certificate chain themselves.