/*
RFC-0715: TSA adapter for RFC 3161 timestamp queries — encodes TimeStampReq via pkijs/asn1js, sends HTTP POST, returns DER-encoded TimeStampResp.
- Does not verify the timestamp token — that is nachweis.verify-signature.
- Does not implement retry logic — caller handles transient failures.
- RFC-0715: initial TSA adapter with FreeTSA.org implementation.
- RFC-0715 review fix: use byteHash from @warpgogol/fingerprint (DNA-53). Replace FreeTsaAdapter class + createCustomTsaAdapter with HttpTsaAdapter class.
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
import { byteHash } from "@warpgogol/werkstatt-engine/fingerprint";
export interface TsaAdapter {
readonly name: string;
readonly url: string;
timestamp(message: Uint8Array): Promise;
}
export class HttpTsaAdapter implements TsaAdapter {
readonly name: string;
readonly url: string;
constructor(name: string, url: string) {
this.name = name;
this.url = url;
}
async timestamp(message: Uint8Array): Promise {
const reqBytes = await encodeTimestampReq(message);
const response = await fetch(this.url, {
method: "POST",
headers: { "Content-Type": "application/timestamp-query" },
body: Buffer.from(reqBytes),
});
if (!response.ok) {
throw new Error(
`[${this.name}] TSA returned HTTP ${response.status}: ${response.statusText}`,
);
}
const respBuffer = await response.arrayBuffer();
return new Uint8Array(respBuffer);
}
}
export const FreeTsaAdapter = new HttpTsaAdapter("FreeTSA", "https://freetsa.org/tsr");
export async function encodeTimestampReq(message: Uint8Array): Promise {
const { default: pkijs } = await import("pkijs");
const { default: asn1js } = await import("asn1js");
const hashHex = byteHash(message).replace("sha256:", "");
const hashBytes = Buffer.from(hashHex, "hex");
const hashBuffer = hashBytes.buffer.slice(
hashBytes.byteOffset,
hashBytes.byteOffset + hashBytes.byteLength,
);
const messageImprint = new pkijs.MessageImprint({
hashAlgorithm: new pkijs.AlgorithmIdentifier({
algorithmId: "2.16.840.1.101.3.4.2.1",
}),
hashedMessage: new asn1js.OctetString({ valueHex: hashBuffer }),
});
const nonceBytes = new Uint8Array(10);
crypto.getRandomValues(nonceBytes);
const tspReq = new pkijs.TimeStampReq({
version: 1,
messageImprint,
reqPolicy: "1.2.3.4.5.6",
certReq: true,
nonce: new asn1js.Integer({ valueHex: nonceBytes.buffer }),
});
const reqSchema = tspReq.toSchema();
const reqBer = reqSchema.toBER(false);
return new Uint8Array(reqBer);
}