A small TypeScript library (npm package @techteamer/timestamp) that creates and verifies RFC 3161 trusted timestamps by shelling out to the openssl ts CLI and POSTing the timestamp query to one or more external TSA (Time Stamping Authority) providers. It is a library, not a long-running service — there is no HTTP server, no message queue consumer, and no bin/ in this repo. It is consumed by other FaceKom services that require/import it.
Repo root: /Users/levander/coding/facekom-v2-clones/timestamp_service · base branch master · last commit 7544f92 “chore: [fkqa-327]: maintenance/master (#19)” (Balázs Horváth, 2026-05-08).
README.md:1-3 — “Trustedtimestamp service implements the generate and verification of timestamps.”
The publishable package is @techteamer/timestamp v2.0.2 (package/timestamp_service/package.json:2-3), described as “This package contains a TimestampService, which can create and verify timestamps”.
Public API surface (README.md:105-112, re-exported from src/index.ts:1-3): getTimestampInfo, createTimestampToken, verifyToken, verifyTsr, testService.
Intended consumption pattern (README.md:24-34): a host service constructs the service from app config —
new TrustedTimestampServiceLib('normal', config.get('trustedTimestamp'), config.get('certService.encoding', 'latin1'))
The config.get('trustedTimestamp') / certService.encoding keys imply the consumer is a getconfig-style FaceKom app, but that consumer lives in another repo — see “Dependencies on other FaceKom services” below.
This repo is a monorepo wrapping ONE library + smoke-test harnesses
The top-level package.json is a private Yarn workspaces root (package.json:1-15) whose only job is to build/run the library and two example apps. The real code is entirely under package/timestamp_service/.
tmp^0.2.1 — temp files (openssl reads/writes from disk).
resolutions: pins strip-ansi to 6.0.1 (package/.../package.json:62-64).
Why openssl + temp files
The library does no ASN.1 / RFC 3161 parsing in JS. It builds a query with openssl ts -query, sends it to the TSA over HTTP, then writes the response to a temp .tsr file and runs openssl ts -verify / -reply / pkcs7 against it (src/trustedTimestamp/TrustedTimestampCommand.ts). All openssl invocations are child_process.exec of interpolated command strings (see Gotchas).
3. Build & run
There are threepackage.json files with scripts (plus the root). No main/bin executable — the library entrypoint is src/index.ts (dev) / build/timestamp_config.{cjs,mjs} (published).
Root package.json scripts (package.json:10-14)
Script
Command
What it does
test:cjs
cd ./app/timestamp_cjs/ && yarn start
Runs the CommonJS smoke-test app (node app/createTimestamp.test.js).
Both define start: node app/createTimestamp.test.js. Each depends on the library via file:../../package/timestamp_service (workspace symlink).
Entrypoints
Dev / source-of-truth: package/timestamp_service/src/index.ts (re-exports TrustedTimestampService + CreateTimestampTokenError).
Published: build/timestamp_config.mjs (import) / build/timestamp_config.cjs (require) / build/src/index.d.ts (types) — package/.../package.json:8-15. files: ["build"] so only build/ ships.
Smoke apps: app/timestamp_cjs/app/createTimestamp.test.js (CJS require) and app/timestamp_mjs/app/createTimestamp.test.js (ESM import). Both build one provider pointing at http://wiremock:8080/tsa through proxy http://localhost:8080, call createTimestampToken('ca44...e68','sha256',20), and log the result.
Dockerfiles
No application Dockerfile anywhere in the repo (verified by find -iname Dockerfile*).
The only container config is mock_servers/docker-compose.yaml — spins up mitmproxy (mitmweb, ports 8080/8081) and WireMock3.6.0 (host 8888→container 8080) for local TSA mocking. Not a deployment artifact.
CI
.github/workflows/yarn-vitest.yaml — on PRs to master: checkout, setup Node 22 (yarn cache), yarn install --frozen-lockfile, then yarn workspace @techteamer/timestamp test (lint) and yarn workspace @techteamer/timestamp vitest (full suite).
4. Top-level structure
Path
Role
Verified
package/timestamp_service/
The library (@techteamer/timestamp): all production source, tests, build config.
listed + read
app/timestamp_cjs/
CommonJS example/smoke app consuming the library via workspace link.
Dated yarn outdated dependency-audit logs (2026-04-07.md, 2026-05-04.md). Process notes, not code.
read
security/
Dated dependency security-audit logs (2026-04-07.md, 2026-05-04.md). Process notes.
sampled
.github/workflows/
CI (yarn-vitest.yaml).
read
package.json, yarn.lock
Workspaces root + lockfile.
read
LICENSE
MIT (LICENSE:1).
read
No bin/ directory exists
.eslintignore (package/.../.eslintignore) lists bin/mc, *.tar.gz, *.sh — these are leftover lines from a shared template (likely copied from a deployment repo). find ... -type d -name bin and find ... -name '*.sh' -o -name mc return nothing outside node_modules. There are zero first-party helper scripts to document (requirement #5 → N/A).
5. Helper scripts under first-party bin/
None. Verified: no bin/ dir, no .sh, no mc binary, no *.tar.gz outside node_modules (find over the whole tree). The .eslintignore references are dead template entries (see callout above).
6. Key modules (all under package/timestamp_service/src/)
Read in full; summarized below.
trustedTimestamp/TrustedTimestampService.ts — the public class
class TrustedTimestampService (:29). Constructor args (:37-41): timestampInfoType: 'normal'|'short' (default 'normal'), config: TimestampConfig, encoding (default 'latin1'). _init() (:48-64) validates config.certsLocation and non-empty config.providers (throws otherwise), then instantiates TempFileService, CertService, and TrustedTimestampRequest.
Methods:
getTimestampInfo(tsr, isToken=false) (:69-117) — writes the tsr to a temp file, runs openssl ts -reply ... -text to parse it into a TimestampInfo, extracts the signing cert via openssl pkcs7 + CertService.parseCert, attaches cert info when type is 'normal'. On error returns a TimestampInfo carrying the error message (does not throw).
createTimestampToken(digest, hashAlgorithm, dataSize) (:125-166) — normalizes/validates the digest format & digest, builds the query (getTsQuery), asks TrustedTimestampRequest.getTimestamp for a tsr from the first working provider, then verifies it and returns { timestamp, providerName, logHistory }. Throws CreateTimestampTokenError (with {providerName, logHistory} context) when no provider succeeds.
verifyToken(timestampToken, digest, dataSize) (:171-186) — guards that dataSize and digest match the stored token, then delegates to verifyTsr.
verifyTsr(digest, tsr, isToken=false) (:191-219) — writes tsr to temp file, runs openssl ts -verify, returns /Verification: OK/i.test(stdout).
testService() (:224-226) — which openssl health check.
class TrustedTimestampRequest (:20). Sorts providers by priority (higher-priority-first via toSorted, providers without priority appended) in _sortedProviders (:66-81). getTimestamp(tsQuery) (:35-61) iterates providers in order, stopping at the first that returns a tsr, accumulating a logHistory entry per attempt. _getTimeStampToken (:86-135) builds the request, fetches it, treats non-200 as failure (TSA response unsatisfactory: <status>), and returns the token as a Buffer. _getTimestampRequest (:140-166) picks the auth strategy: oauth (url has getTokenUrl), basic (string url + auth.user/pass), or noAuth.
class TimestampRequest (:25). Default request is POST with Content-Type: application/timestamp-query (:26-31). authStrategy (:90-108) switches between:
_getTimestampRequestOauth (:133-176) — first POSTs to getTokenUrl (Basic-auth + x-www-form-urlencoded body from provider.body) to obtain access_token, then sends the timestamp request to getTimestampUrl with Authorization: Bearer <token>, streaming the tsQuery from a temp file with an explicit Content-length.
_getTimestampRequestNoAuth (:181-187).
setProxy (:59-64) wires a proxy-agentProxyAgent honoring proxy.url and allowUnauthorized.
class CreateTimestampTokenError extends Error (:8-16) carrying context: { logHistory?, providerName? } so callers can inspect failover history (see docs/log-history.md).
child_process_promise.ts — promisified exec (supports encoding:'buffer', optional streaming logger) and execFile (+ ExecFileError). Used by all openssl calls.
Example apps reference a cert/ dir that is not committed
Both smoke apps compute certsLocation as path.join(__dirname,'..','..','cert') (app/timestamp_cjs/app/createTimestamp.test.js:5, mjs :4). No cert/ directory exists in the clone (it is gitignored). To run the apps end-to-end you must provide that CA directory yourself.
8. Tests
Framework: Vitest v2 with nock for HTTP mocking; @techteamer/cert-utils and the openssl command module are vi.mock-ed.
Location: package/timestamp_service/test/tests/feature/timestamp-service.test.ts (the only spec; the mock lives at test/tests/mocks/TrustedTimestampCommand.mock.ts).
Config: test/vitest.config.ts — coverage lcovonly via v8 → test/coverage/vitest, 30 000 ms timeout, excludes node_modules/**, web/**, yarn-offline-cache/**.
What it covers: config validation (ok / missing providers / missing certsLocation), createTimestampToken for basic-auth & oauth, provider failover (“wrong provider use next provider”), the all-providers-fail path asserting CreateTimestampTokenError + context.providerName/logHistory, and getTimestampInfo / verifyToken / verifyTsr / testService happy paths.
The mock (TrustedTimestampCommand.mock.ts) stubs every openssl wrapper: getTsVerify always returns 'Verification: ok', extractCertFromToken returns a dummy PEM, etc. — so tests never call real openssl.
Example smoke apps (need real openssl + a TSA, e.g. WireMock + cert/): root yarn test (runs both apps).
yarn vitest:unit has no matching tests in this clone
The unit/ glob (package/.../package.json:29) finds nothing — there is no test/tests/unit/ directory. Only feature/ tests exist.
9. Dependencies on other FaceKom services
This is a library; it has no RabbitMQ consumer/producer, no DB, no Redis, and no HTTP server in this repo (verified by full-tree read — none of those clients are imported).
Evidenced outbound integrations (all per-provider, from config supplied by the host app):
External TSA over HTTP(S) — node-fetchPOST to the provider url (or oauth getTokenUrl→getTimestampUrl). Example providers in README/tests: Microsec e-Szigno https://bteszt.e-szigno.hu/tsa, InfoCert https://*.infocert.digital/... (README.md:84-101).
Outbound HTTP proxy — optional via proxy-agent when provider.proxy.url is set (TimestampRequest.ts:59-64; docs/proxy.md).
openssl binary — hard runtime dependency on the host (not a network service).
The actual FaceKom consumer (the getconfig-based service that calls new TrustedTimestampServiceLib(...) per README.md:24-34) is in a different repo and is not present here. Cross-reference architecture-overview for who calls this library.
10. Verified gotchas
Shell-injected openssl commands
Every openssl call is child_process.exec of an interpolated string (TrustedTimestampCommand.ts). digest/digestFormat are validated against a hex regex and a format whitelist (TrustedTimestampCheck.ts) before use in getTsQuery, but temp-file paths and certsLocation are interpolated unquoted/quoted inconsistently (e.g. -CApath ${certsLocation} unquoted at :21). Treat certsLocation and any caller-supplied path as trusted/controlled input.
Verification string matching is loose
verifyTsr returns success on /Verification: OK/i.test(stdout) (TrustedTimestampService.ts:205). The test mock returns 'Verification: ok' (lowercase) — the i flag makes it pass. Any change to openssl’s wording silently breaks verification.
engines.node >= 22 is real
The code uses Array.prototype.toSorted (TrustedTimestampRequest.ts:78), an ES2023/Node-20+ API. Running on older Node will throw at provider-sort time.
Dual-build via lazy node-fetch import
node-fetch@3 is ESM-only; the package ships both CJS and ESM. patch/node-fetch.ts defers the import so the CJS bundle still works (require of an ESM-only dep would otherwise fail).
package script "test" ≠ tests
yarn ... test only runs ESLint (package/.../package.json:34). To actually run the spec you must call the vitest script. CI runs both separately.
Provider priority is "higher number first"
Counter-intuitive: _sortedProviders puts providers with a priority first, ascending by value — but README/tests use large numbers (e.g. priority: 999) to mean “try first”. Read TrustedTimestampRequest.ts:66-81 carefully before tuning provider order.
maintenance/ & security/ are audit logs, not architecture
The dated markdown files are captured yarn outdated / dependency-audit output documenting scheduled upgrades (e.g. @techteamer/cert-utils 1.1.3→2.0.0 pending). Useful for upgrade history only.
Unverified / gaps
Git history: only a single commit (7544f92) is visible via git log in this clone (shallow/squashed); PR refs (#19, fkqa-327, fkqa-327) appear in the message but full history was not available, so evolution claims are limited to what files state.
security/2026-04-07.md / security/2026-05-04.md: confirmed to exist and to be dependency-audit logs of the same form as maintenance/* (read maintenance/* in full; security/* sampled/structurally surveyed, not line-by-line — they mirror the outdated-package tables).
yarn.lock (114 KB): surveyed only to confirm @techteamer/cert-utils@^1.1.3 resolution; not read line-by-line (vendored lock data).
The consuming FaceKom service (the getconfig app from README.md:24-34) is not in this repo; its queue/DB/HTTP wiring is out of scope here.
No Git-LFS / binaries: no .gitattributes, git lfs ls-files empty, no model weights or binaries — nothing to record as LFS pointers.
build/ is gitignored and absent (must run yarn build to produce published artifacts); not inspected.