RFC 3161 timestamps (TSP): request, sign and verify
TimeStampResp is what a client receives from a TSA: a PKIStatusInfo plus, on success, a TimeStampToken; parse decodes it and enforces the status-to-token coupling (a granted response carries a token, a rejection does not). A TimeStampToken is itself a CMS SignedData whose encapsulated content is a TSTInfo, so parseToken composes the CMS parser, asserts the id-ct-TSTInfo content type and the single-signer rule, and decodes the inner TSTInfo. parseTstInfo decodes a bare TSTInfo payload directly.
The parser surfaces everything a verifier needs and interprets nothing it cannot: messageImprint.hashAlgorithm and the raw hashedMessage, the genTime (with sub-second precision), the serialNumber and nonce (lossless, as BigInt + hex), and the policy. The PRESENCE of the ESS SigningCertificate(V2) attribute is asserted at parse (a structural token property); the imprint-to-request and nonce-to-request round-trips, the ESS hash-vs-certificate binding, the timestamping EKU, and the signature are verification-layer concerns above parse altitude. DER-only, fail-closed.
pki.schema.tsp.parseRequest
pki.schema.tsp.parseRequest(input, caps?) -> timeStampReq
Parse a DER Buffer or PEM string into a TimeStampReq (RFC 3161 sec. 2.4.1): { version, messageImprint, reqPolicy, reqPolicyName, nonce, nonceHex, certReq, extensions }. version MUST be 1; certReq is BOOLEAN DEFAULT FALSE (an explicit FALSE is non-DER and rejected tsp/bad-request); nonce is lossless (BigInt + hex); messageImprint.hashedMessage is the raw digest. A malformed structure throws a typed TspError.
Options
- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
above it, or an option outside this set, is refused. A parse that exceeds one is
refused with the `/too-large` code of this format.
Example
async function example() {
var der = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ certReq: true });
var req = pki.schema.tsp.parseRequest(der);
req.certReq; // -> boolean
req.messageImprint.hashedMessage; // -> Buffer (the raw digest)
}
example();
References
- spec RFC 3161
pki.schema.tsp.parseTstInfo
pki.schema.tsp.parseTstInfo(input, caps?) -> tstInfo
Parse a bare TSTInfo payload (a DER Buffer, the structure a timestamp token encapsulates) into { version, policy, messageImprint, serialNumber, genTime, accuracy, ordering, nonce, tsa, extensions }. messageImprint.hashedMessage is the raw digest; serialNumber / nonce are lossless (BigInt + hex); genTime is a Date with sub-second precision. A malformed structure throws a typed TspError (tsp/*); a leaf-level codec fault surfaces as asn1/*.
Options
- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
above it, or an option outside this set, is refused. A parse that exceeds one is
refused with the `/too-large` code of this format.
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var key = await pki.key.export(pair.privateKey);
var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: key });
var token = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
var der = pki.schema.tsp.parseToken(token).eContent; // the raw TSTInfo the TSA signed
var tst = pki.schema.tsp.parseTstInfo(der);
tst.genTime; // -> Date
tst.messageImprint.hashedMessage; // -> Buffer (the raw digest)
}
example();
References
- spec RFC 3161
pki.schema.tsp.parse
pki.schema.tsp.parse(input, caps?) -> timeStampResp
Parse a DER Buffer or a PEM string into a TimeStampResp: { status, statusString, failInfo, timeStampToken }. The status-to-token coupling is enforced: a granted response (status 0/1) carries timeStampToken (surfaced raw for parseToken), any other status does not. failInfo decodes the PKIFailureInfo named bits. A malformed structure throws a typed TspError.
Options
- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
above it, or an option outside this set, is refused. A parse that exceeds one is
refused with the `/too-large` code of this format.
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var key = await pki.key.export(pair.privateKey);
var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: key });
var token = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
var der = pki.tsp.response(token, {});
var res = pki.schema.tsp.parse(der);
res.status; // -> 0 (granted)
res.timeStampToken.tstInfo.genTime; // -> Date (a granted token is decoded)
}
example();
References
- spec RFC 3161
pki.schema.tsp.parseToken
pki.schema.tsp.parseToken(input, caps?) -> tstInfo
Parse a TimeStampToken (a DER Buffer or PEM): a CMS SignedData whose encapsulated content is a TSTInfo. Composes pki.schema.cms.parse, asserts the id-ct-TSTInfo content type (tsp/wrong-econtent-type), that the content is attached (tsp/detached-token), the single-signer rule (tsp/multi-signer, RFC 3161 sec. 2.4.2), and that the signerInfo carries a SigningCertificate or SigningCertificateV2 signed attribute (tsp/missing-signing-certificate, RFC 3161 sec. 2.4.2 / RFC 5816; the hash-vs-certificate binding stays a verification-layer concern), then decodes the inner TSTInfo. Returns { tstInfo, eContent, signerInfo, certificates }: the decoded payload, the raw eContent bytes a verifier hashes for the CMS message-digest, and the CMS signer material.
Options
- `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
defaults to the matching `pki.C.LIMITS` figure and may only be set lower; a value
above it, or an option outside this set, is refused. A parse that exceeds one is
refused with the `/too-large` code of this format.
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var key = await pki.key.export(pair.privateKey);
var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: key });
var tokenDer = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
var token = pki.schema.tsp.parseToken(tokenDer);
token.tstInfo.genTime; // -> Date
token.signerInfo.sid; // -> the TSA signer identifier
}
example();
References
pki.schema.tsp.pemDecode
pki.schema.tsp.pemDecode(text, label?) -> Buffer
Extract the DER bytes from a PEM block (RFC 3161 defines no standard label, so the a block of any label is accepted unless label is given). The text holds one object: a file of several is refused with pem/multiple-blocks and read with pki.schema.pem.decodeBundle. Throws PemError on a missing envelope or a non-base64 body.
Example
async function example() {
var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) }, {});
var pemText = pki.schema.tsp.pemEncode(req, "TSP REQUEST");
var der = pki.schema.tsp.pemDecode(pemText);
}
example();
References
pki.schema.tsp.pemEncode
pki.schema.tsp.pemEncode(der, label) -> string
Wrap DER bytes in a PEM envelope. RFC 3161 defines no standard PEM label, so label is REQUIRED: the operator names the envelope explicitly (mirroring pemDecode, which accepts any label). Omitting it throws pem/bad-label.
Example
async function example() {
var der = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) }, {});
var pem = pki.schema.tsp.pemEncode(der, "TSP RESPONSE");
}
example();
References
pki.tsp.sign
pki.tsp.sign(messageImprint, tsa, opts) -> Promise<Buffer|string>
Create an RFC 3161 TimeStampToken over messageImprint ({ hashAlgorithm, hashedMessage }: the hash of the data being timestamped, computed by the requester). tsa is the timestamp authority's { cert, key } (as pki.cms.sign takes them). The token is a CMS SignedData whose content is a TSTInfo carrying the imprint, the TSA policy, a serial number, and genTime; the RFC 3161 sec. 2.4.2 signing-certificate attribute binding the token to the TSA certificate is attached automatically. The TSA certificate is held to sec. 2.3 before anything is signed: one critical extendedKeyUsage naming id-kp-timeStamping alone, and a keyUsage, when present, that allows signing; a certificate that fails is refused with the code pki.tsp.verify would answer (tsp/bad-eku, tsp/eku-not-critical, tsp/eku-not-exclusive, tsp/bad-key-usage).
Options
sid / pem Passed through to `pki.cms.sign` (signer identifier, PEM output).
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
var signerCertDer = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
// RFC 3161 sec. 2.3: a TSA certificate's extendedKeyUsage MUST be critical
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: signerKeyPkcs8 });
var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
var imprint = { hashAlgorithm: "sha256", hashedMessage: sha256Digest };
var token = await pki.tsp.sign(imprint, { cert: signerCertDer, key: signerKeyPkcs8 }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
(await pki.cms.verify(token)).valid; // true
}
example();
References
- spec RFC 3161
pki.tsp.request
pki.tsp.request(messageImprint, opts) -> Buffer|string
Build an RFC 3161 TimeStampReq (sec. 2.4.1) over messageImprint ({ hashAlgorithm, hashedMessage }, the same shape pki.tsp.sign takes). version is 1; certReq is BOOLEAN DEFAULT FALSE, so it is emitted only when explicitly true. Returns DER (or PEM when opts.pem).
Options
pem Return a PEM "TIMESTAMP REQUEST" string instead of DER (boolean).
Example
async function example() {
var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: sha256Digest }, { nonce: 0x0102030405060708n, certReq: true });
}
example();
References
- spec RFC 3161
pki.tsp.parseRequest
pki.tsp.parseRequest(input) -> timeStampReq
Parse a TimeStampReq (DER Buffer or PEM), the pki.schema.tsp.parseRequest decoder on the pki.tsp namespace. Returns { version, messageImprint, reqPolicy, reqPolicyName, nonce, nonceHex, certReq, extensions }; a malformed structure throws a typed TspError.
Example
async function example() {
var der = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ certReq: true });
var req = pki.tsp.parseRequest(der);
req.certReq; // -> boolean
}
example();
References
- spec RFC 3161
pki.tsp.response
pki.tsp.response(token, opts) -> Buffer|string
Build an RFC 3161 TimeStampResp (sec. 2.4.2): a PKIStatusInfo and, on success, the timeStampToken. Pass a token (the CMS ContentInfo pki.tsp.sign produces) with the default granted status, or build a rejection with response(null, { status, failInfo, statusString }). The status-to-token coupling is enforced (granted 0/1 carries a token, any other status must not), mirroring the parse-side gate. Returns DER (or PEM when opts.pem).
Options
pem Return a PEM "TIMESTAMP RESPONSE" string instead of DER (boolean).
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var key = await pki.key.export(pair.privateKey);
var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: key });
var token = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
var resp = pki.tsp.response(token, {}); // granted
var rej = pki.tsp.response(null, { status: 2, failInfo: ["badAlg"] }); // rejection
}
example();
References
- spec RFC 3161
pki.tsp.parseResponse
pki.tsp.parseResponse(input) -> timeStampResp
Parse a TimeStampResp (DER Buffer or PEM), the shipped pki.schema.tsp.parse decoder on the pki.tsp namespace. Returns { status, statusString, failInfo, timeStampToken } with the status-to-token coupling enforced; a granted response's token is decoded via parseToken.
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var key = await pki.key.export(pair.privateKey);
var cert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: key });
var token = await pki.tsp.sign({ hashAlgorithm: "sha256", hashedMessage: Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello"))) },
{ cert: cert, key: key }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
var der = pki.tsp.response(token, {});
var resp = pki.tsp.parseResponse(der);
resp.timeStampToken.tstInfo.genTime; // -> Date (on a granted response)
}
example();
References
- spec RFC 3161
pki.tsp.verify
pki.tsp.verify(token, data, opts) -> Promise<result>
Verify an RFC 3161 TimeStampToken against the data it should cover. token is the token DER / PEM, never a parsed object: every checked field is read from the CMS-verified eContent, so a mutated parsed structure cannot desynchronize the checks from the signed bytes. data is the original bytes (hashed under the token's messageImprint algorithm) or a precomputed { hashAlgorithm, hashedMessage }. Returns { valid, trusted, revocationChecked, anchorConstraints, genTime, accuracy, serialNumber, serialNumberHex, policy, policyName, nonce, tsa, tstInfo, signer, code?, reason? }. valid is true only when the CMS signature, the imprint match, the eContentType, the ESSCertID(V2) binding, the RFC 3161 sec. 2.3 critical single-timeStamping extendedKeyUsage rule, the requested nonce (when supplied), and, when trustAnchors is supplied, the full out-of-path TSA-cert path validation all pass. A conformance / trust failure of a well-formed token is a { valid:false, code } verdict; malformed or config input throws a typed TspError.
trusted is the second claim and is kept apart from the first. valid says the token's signature and structural bindings hold; trusted says the timestamp authority chained to an anchor this caller named. Without trustAnchors there is nothing to chain to and trusted is false: every branch, refusing and accepting alike, carries the field with a definite value. A timestamp is archived precisely to be re-read years later, and one boolean cannot answer both questions then.
revocationChecked is the third claim, for the same reason. Revocation runs only when a revocationChecker is supplied. Without a field naming which happened, a trusted token whose TSA was never checked against a CRL or an OCSP responder reads identically to one established un-revoked. It is false whenever no path ran at all. anchorConstraints carries whatever the anchor itself constrained, from pki.path.validate.
Options
revocationChecker Passed through to `pki.path.validate`.
Example
async function example() {
var pair = await pki.key.generate("Ed25519");
var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
var signerCertDer = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: await pki.key.export(pair.publicKey),
notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
{ key: signerKeyPkcs8 });
var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
var imprint = { hashAlgorithm: "sha256", hashedMessage: sha256Digest };
var token = await pki.tsp.sign(imprint, { cert: signerCertDer, key: signerKeyPkcs8 }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
var res = await pki.tsp.verify(token, Buffer.from("hello"), {});
res.valid; // boolean; pass opts.trustAnchors to also chain the TSA cert to a root
res.genTime; // Date, read from the verified eContent
}
example();