Pkcs11ex.Audit.Anchor.RFC3161 (pkcs11ex_audit v0.1.0)

Copy Markdown View Source

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

Types

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).

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

request()

@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_request(hash_bytes, opts \\ [])

@spec build_request(
  binary(),
  keyword()
) :: {:ok, request()} | {:error, term()}

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).

extract_token(arg1)

@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).

fetch_token(tsa_url, request_der, opts \\ [])

@spec fetch_token(String.t(), binary(), keyword()) ::
  {:ok, binary()} | {:error, term()}

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.