timestamp_service

One-line role

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

Related: architecture-overview · customization-architecture · sibling repo docs under vuer_oss.


1. Purpose & role in FaceKom

  • 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/.


2. Tech stack & runtime

AspectValueSource
LanguageTypeScript ~5.5package/timestamp_service/package.json:59
Module systemESM ("type": "module")package/timestamp_service/package.json:37
Node version>=22.0.0 (engines); CI uses Node 22package/.../package.json:23-25, .github/workflows/yarn-vitest.yaml:13
Package managerYarn (classic, v1 workspaces); yarn.lock at root, cache: 'yarn' in CIpackage.json:5-9, yarn.lock, CI:11
BundlerRollup (@rollup/plugin-typescript) → emits .cjs + .mjs + .d.tsrollup.config.js, package/.../package.json:8-15
Test runnerVitest v2 + nock (HTTP mock)package/.../package.json:55,60
Lint / formatESLint 8 (@typescript-eslint, eslint-config-standard, prettier) + Prettier 3package/.../package.json:48-57, .eslintrc.json
External runtime depopenssl CLI must be on $PATHTrustedTimestampCommand.ts (every command shells openssl ts ...)

Runtime dependencies (package/timestamp_service/package.json:38-44):

  • @techteamer/cert-utils ^1.1.3 — parses the X.509 cert extracted from the timestamp token (TechTeamer internal package).
  • node-fetch ^3 (+ @types/node-fetch ^3) — HTTP client for the TSA request.
  • proxy-agent ^6.5.0 — outbound HTTP(S) proxy support.
  • 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 three package.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)

ScriptCommandWhat it does
test:cjscd ./app/timestamp_cjs/ && yarn startRuns the CommonJS smoke-test app (node app/createTimestamp.test.js).
test:mjscd ./app/timestamp_mjs/ && yarn startRuns the ESM smoke-test app.
testyarn test:cjs && yarn test:mjsRuns both example apps in sequence.

Library package/timestamp_service/package.json scripts (:26-36)

ScriptCommandWhat it does
vitestvitest run --config test/vitest.config.tsRuns the Vitest suite once (no watch).
buildrollup -cBundles src/index.tsbuild/timestamp_config.cjs, .mjs, .d.ts (rollup.config.js).
vitest:unityarn vitest unit/Runs only tests under a unit/ path. No unit/ dir exists in this clone — see Gaps.
vitest:featureyarn vitest feature/Runs the feature tests (test/tests/feature/).
linteslint ./src ./test && echo 'npm run lint: OK'Lints source + tests.
lint:fixeslint . --fixAuto-fixes lint issues.
formatprettier -w .Formats the package in place.
testnpm run lint --silent && echo 'npm test: OK'Only lints (does NOT run vitest).
releaserm -rf ./build && yarn build && npm publish --access publicClean build + publish to npm public registry.

Example-app scripts (app/timestamp_cjs/package.json:6-8, app/timestamp_mjs/package.json:7-9)

  • 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 WireMock 3.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

PathRoleVerified
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.read
app/timestamp_mjs/ESM example/smoke app (identical logic, import syntax).read
mock_servers/Local TSA mock: docker-compose.yaml (mitmproxy + WireMock), mappings/signo.json (stub for POST /tsa), __files/tsa_response.json (canned cert+signature body).read
docs/Markdown docs: proxy.md (proxy config), log-history.md (multi-provider failover log format).read
maintenance/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.lockWorkspaces root + lockfile.read
LICENSEMIT (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.

trustedTimestamp/TrustedTimestampRequest.ts — provider orchestration & failover

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.

trustedTimestamp/TimestampRequest.ts — HTTP request builder & auth strategies

class TimestampRequest (:25). Default request is POST with Content-Type: application/timestamp-query (:26-31). authStrategy (:90-108) switches between:

  • _getTimestampRequestBasic (:113-128) — adds Authorization: Basic base64(user:pass), sends tsQuery as body.
  • _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-agent ProxyAgent honoring proxy.url and allowUnauthorized.

trustedTimestamp/TrustedTimestampCommand.ts — openssl wrappers

Free functions, each shelling openssl via the promisified exec:

  • getTsQuery(digest, digestFormat)openssl ts -query -digest <d> -no_nonce -<fmt> -cert (:3-10).
  • getTsVerify(digest, tempPath, isToken, certsLocation)openssl ts -verify ... -CApath <certsLocation> (:12-26).
  • getTsReply / generateTsReplyopenssl ts -reply ... to read or extract the token (:28-42).
  • extractCertFromTokenopenssl pkcs7 -inform der -in <tst> -print_certs (:44-48).
  • checkSslPathwhich openssl (:50-60).

trustedTimestamp/TrustedTimestampInfo.ts — openssl text → structured object

class TimestampInfo (:34) parses openssl ts -reply -text output with regexes (parseOpensslOutput / parseOpensslOutputShort) into version, policyOID, hashAlgorithm, hash, serialNumber, timeStamp/timeStampDate, accuracy (computed to ms), ordering, nonce, issuer, and a parsed tsa DirName object ({C,L,O,OU,CN}). The header comment (:12-33) documents the exact openssl output it expects.

trustedTimestamp/TrustedTimestampCheck.ts — digest validation

supportedDigestForamts whitelist (:1-14, note the typo’d identifier) incl. sha1/sha256/sha512/md5/.... checkDigestFormat (:16-18), checkDigest (:20-22, hex regex), normalizeDigestFormat (:24-28, strips leading/all -).

trustedTimestamp/error/create-timestamp-token.error.ts

class CreateTimestampTokenError extends Error (:8-16) carrying context: { logHistory?, providerName? } so callers can inspect failover history (see docs/log-history.md).

util/ helpers

  • TempFileService.ts — wraps tmp (with setGracefulCleanup()); createTempFile(options, content?) returns {tempPath, fd, cleanupCallback}.
  • child_process_promise.ts — promisified exec (supports encoding:'buffer', optional streaming logger) and execFile (+ ExecFileError). Used by all openssl calls.
  • regexParser.ts — generic parseRegex(text, regex, groups, revive) + create factory; powers TimestampInfo.

patch/node-fetch.ts

Lazy ESM shim: fetch does await import('node-fetch') and calls its default export (:3-6) — so the dual CJS/ESM build can use ESM-only node-fetch@3.

Types (trustedTimestamp/types/)

  • timestamp-config.type.tsTimestampConfig { certsLocation, providers[] }.
  • timestamp-provider.type.tsTimestampProvider { name, url, auth?, priority?, body?, proxy? }; url is string | { getTokenUrl, getTimestampUrl }; proxy { url, allowUnauthorized? }.
  • timestamp-token.type.tsTimestamp { digest, hashAlgorithm, dataSize, tsr:Buffer, isToken, certExpiry, verified }; CreatedTimestampToken { timestamp, providerName, logHistory }.
  • timestamp-log.type.tsTimestampLog { info{name,date,url,response,error}, errorTrace }.
  • timestamp-request.type.tsTimestampRequestAuthTypes = 'oauth'|'basic'|'noAuth', request options, auth result.

7. Configuration

No config files, no env vars, no AJV/getconfig schema in this repo. Config is passed in by the consumer as the TimestampConfig constructor argument.

  • Shape (types/timestamp-config.type.ts): { certsLocation: string, providers: TimestampProvider[] }.
  • Validation is imperative in _init() (TrustedTimestampService.ts:48-64): missing certsLocation → throws 'trustedTimestamp config "certsLocation" missing!'; missing/empty providers → throws 'trustedTimestamp config "providers" missing or empty!'. (Both asserted in test/.../timestamp-service.test.ts:84-109.)
  • Provider fields (README.md:54-66, types file): required name, url; optional auth{user,pass}, priority (higher = earlier), body (oauth form params), proxy{url, allowUnauthorized?}.
  • Default/example config blocks: README.md:38-103; proxy variant in docs/proxy.md:12-32 (adds proxy.url + allowUnauthorized: true).
  • certsLocation feeds openssl ts -verify -CApath <certsLocation> — it must be an OpenSSL CA cert directory (README default /etc/ssl/certs/).
  • .gitignore deliberately excludes cert material: *.pub *.key *.der *.pem *.crt and /logs/* (.gitignore:8-13).

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.
  • How to run:
    • Library unit/feature tests: yarn workspace @techteamer/timestamp vitest (or inside the package: yarn vitest / yarn vitest:feature).
    • Lint-only “test”: yarn workspace @techteamer/timestamp test.
    • 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-fetch POST to the provider url (or oauth getTokenUrlgetTimestampUrl). 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).
  • TechTeamer internal npm dep @techteamer/cert-utils (cert parsing) — yarn.lock resolves @techteamer/cert-utils@^1.1.3.

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.

Sources

Files read in full unless noted:

  • README.md, package.json, .gitignore, .prettierrc.json, .editorconfig, LICENSE (header)
  • package/timestamp_service/package.json, rollup.config.js, tsconfig.json, .eslintrc.json, .eslintignore
  • package/timestamp_service/src/index.ts
  • package/timestamp_service/src/trustedTimestamp/TrustedTimestampService.ts
  • package/timestamp_service/src/trustedTimestamp/TrustedTimestampRequest.ts
  • package/timestamp_service/src/trustedTimestamp/TimestampRequest.ts
  • package/timestamp_service/src/trustedTimestamp/TrustedTimestampCommand.ts
  • package/timestamp_service/src/trustedTimestamp/TrustedTimestampInfo.ts
  • package/timestamp_service/src/trustedTimestamp/TrustedTimestampCheck.ts
  • package/timestamp_service/src/trustedTimestamp/error/create-timestamp-token.error.ts
  • package/timestamp_service/src/util/{TempFileService,child_process_promise,regexParser}.ts
  • package/timestamp_service/src/patch/node-fetch.ts
  • package/timestamp_service/src/trustedTimestamp/types/{timestamp-config,timestamp-provider,timestamp-log,timestamp-request,timestamp-token}.type.ts
  • package/timestamp_service/test/vitest.config.ts, test/tests/feature/timestamp-service.test.ts, test/tests/mocks/TrustedTimestampCommand.mock.ts
  • app/timestamp_cjs/{package.json,app/createTimestamp.test.js}, app/timestamp_mjs/{package.json,app/createTimestamp.test.js}
  • mock_servers/docker-compose.yaml, mock_servers/mappings/signo.json, mock_servers/__files/tsa_response.json
  • docs/proxy.md, docs/log-history.md
  • maintenance/2026-04-07.md (full), maintenance/2026-05-04.md (full); security/2026-04-07.md, security/2026-05-04.md (sampled)
  • .github/workflows/yarn-vitest.yaml
  • git -C ... log/ls-tree/branch; find for bin/Dockerfile/.sh/LFS; git lfs ls-files (empty)