@vauban-org/agent-sdk-verify
v0.10.2
Published
RFC 3161 TSA timestamping adapters for @vauban-org/agent-sdk (audit-ready)
Maintainers
Readme
@vauban-org/agent-sdk-verify
RFC 3161 Time-Stamping Authority (TSA) adapters for @vauban-org/agent-sdk.
Status — pre-1.0. All
0.x.yreleases may contain breaking changes between minor versions. Pin tightly.
This package implements the TimestampPort interface from @vauban-org/agent-sdk against the RFC 3161 wire protocol, so that audit traces produced by Vauban agents can carry a verifiable third-party timestamp signed by a public TSA (FreeTSA, DigiCert, GlobalSign, …).
Why a separate package?
The core SDK keeps the TimestampPort interface and a NullTimestampPort only — no ASN.1, no DER parsing, no PKCS#7. All wire-format machinery lives here so it can evolve independently and stay opt-in for SDK consumers that do not need third-party timestamping.
Install
pnpm add @vauban-org/agent-sdk @vauban-org/agent-sdk-verifyAdapters
| Adapter | Endpoint | Cost | eIDAS |
| ------------------- | --------------------------------------------------------- | --------------------- | ------ |
| FreeTSAAdapter | https://freetsa.org/tsr | Free | recognized (non-qualified) |
| DigicertAdapter | http://timestamp.digicert.com (override possible) | Free / commercial | non-qualified |
| GlobalSignAdapter | https://timestamp.globalsign.com/tsa/r6advanced1 | Commercial | qualified |
You can also instantiate RFC3161Adapter directly for any other RFC 3161 endpoint:
import { RFC3161Adapter } from "@vauban-org/agent-sdk-verify";
const tsa = new RFC3161Adapter({
url: "https://your-tsa.example/tsr",
tsaName: "your-tsa.example",
timeoutMs: 10_000,
});Usage with FallbackAdapter
import { FallbackAdapter } from "@vauban-org/agent-sdk";
import { FreeTSAAdapter, DigicertAdapter } from "@vauban-org/agent-sdk-verify";
const timestamp = new FallbackAdapter([
new FreeTSAAdapter(),
new DigicertAdapter(),
]);
// Inject `timestamp` wherever the SDK accepts a `TimestampPort`.Verification scope (Sprint A — 0.10.x)
adapter.verify(receipt, rootHash) is offline-only. It checks:
- The signature parses as an RFC 3161
TimeStampToken(PKCS#7 / CMSSignedData). - The
messageImprintalgorithm OID is SHA-256. - The
messageImprintdigest equalsrootHash. receipt.timestampmatchesTSTInfo.genTime.genTimeis not in the future (configurable clock-skew tolerance).- If a certificate chain is present: the leaf certificate's validity window covers
genTime, and its signature algorithm OID is in a known set (RSA / ECDSA over SHA-2).
It deliberately does not check (Sprint B):
- CRL / OCSP / AIA revocation.
- Chain-of-trust to a known root CA.
- Cryptographic verification of the SignedData signature itself.
E2E test
The integration test against the live freetsa.org endpoint is gated behind an env var:
RUN_E2E=1 pnpm --filter @vauban-org/agent-sdk-verify test:e2eCI runs unit tests only by default to stay deterministic.
Implementation notes
TimeStampReq(outbound) is encoded by hand (~50 lines of ASN.1/DER) — pulling in a full ASN.1 codec just to emit a 3-field SEQUENCE would be net-negative.TimeStampResp(inbound) is parsed via@peculiar/asn1-cms,@peculiar/asn1-tsp, and@peculiar/asn1-x509— the outerPKIStatusInfoSEQUENCE is walked manually to keep the dependency surface minimal and to avoid runtime breakage when the peculiar packages bump majors.verify()reusesparseTimeStampRespby wrapping the bareTimeStampTokenstored inreceipt.signatureinside a syntheticTimeStampRespwithstatus=granted(0).
License
MIT — see LICENSE in the repo root.
