cert_utils (@techteamer/cert-utils)

A small, library-only npm package that parses X.509 certificates (P12/PKCS#12 containers and PEM) and validates them (expiry, CA chain, OCSP revocation). It shells out to the openssl CLI for the actual cryptographic work and wraps the output in three classes: CertService, CertInfo, CertValidationError. The README states it “implements the CertService, Certinfo and CertvalidationError classes for the timestamps” (cert_utils/README.md:4).

This is a leaf dependency, not a service.

There is no HTTP server, no message queue consumer, no daemon, no bin entrypoint, and no Dockerfile in this repo. It is consumed by other FaceKom packages/services via require('@techteamer/cert-utils') / ESM import. The package only ships the build/ directory (cert_utils/package.json:17-19). Within FaceKom it is the building block for certificate handling in timestamp/signing flows (README framing: “for the timestamps”, cert_utils/README.md:4); the consumers themselves live in other repos.

1. Purpose & role in FaceKom

AspectDetailSource
Package name@techteamer/cert-utilspackage.json:2
Version1.1.3package.json:3
Description”This package contains a CertUtils which can parsing certs”package.json:4
LicenseMIT, © 2024 TechTeamerLICENSE:1-3
Public surfaceExports only CertService (which exposes nested CertType, and returns CertInfo / throws CertValidationError)src/index.ts:1-3

Public API (verified against README + CertService.ts):

  • parseCert(certBuf, password, certType='P12')Promise<CertInfo> — dispatches to P12 or PEM parser (CertService.ts:70-83).
  • parseCertFromFile(certPath, password) → reads a P12 file path via openssl, with legacy-algorithm fallback (CertService.ts:86-130).
  • parsePemCert(pemCert) → parse a PEM cert buffer/string (CertService.ts:167-180).
  • verifyCert(pemCert, pemCaCert=null, verifyOcsp=false) → validity + optional CA chain + optional OCSP; throws CertValidationError (CertService.ts:396-409).
  • getPemCertBody(pemCert) → returns the first cert’s DER body as a Buffer from a PEM/chain (CertService.ts:507-561).
  • Static/instance getter CertType{ P12: 'P12', PEM: 'PEM' } (CertService.ts:44-53).

See also architecture-overview, vuer_oss.

2. Tech stack & runtime

ItemValueSource
LanguageTypeScript (compiled/bundled to CJS + ESM + .d.ts)package.json:7-16, rollup.config.js
Module typeESM ("type": "module")package.json:37
Required Node>=20.19.0 (engines)package.json:24-26
Package managerYarn classic (1.22.x)yarn.lock present (134,503 bytes, ~131 KiB); maintenance logs show yarn v1.22.22yarn.lock, maintenance/2026-05-04.md:42
Build toolRollup + rollup-plugin-esbuild (esbuild target es2022) + rollup-plugin-dtsrollup.config.js:1-39
TS configmodule: esnext, target: es2022, moduleResolution: bundler, strict: true, noEmit: true (rollup/esbuild emits, not tsc)tsconfig.json:2-28
LintESLint flat config (eslint.config.js) — standard-ish rules: no semicolons, single quotes, 2-space indent, no-console: erroreslint.config.js
Test runnerVitest 4.x with v8 coveragepackage.json:53,64, test/vitest.config.js
External runtime requirementthe openssl CLI binary must be on PATH — every parse/verify path spawns openssl via child_processCertService.ts:100,146,171,451

Runtime dependencies (package.json:42-46):

  • @techteamer/ocsp ^1.0.1 — TechTeamer’s own OCSP client, used for revocation checks (CertService.ts:8,482). This is the only first-party/internal dependency.
  • iconv-lite ^0.7.2 — re-encodes escaped special characters in cert subject/issuer fields (CertService.ts:1,377).
  • tmp ^0.2.1 — temp files for CA-chain verification (util/TempFileService.ts:1).

3. Build & run

No start, no server, no Docker. This is a library.

There is no main-as-daemon, no bin field, no Dockerfile (verified: find for Dockerfile* and bin/ dirs returned nothing). You build it and other packages import it.

Entrypoints declared in package.json:

  • main: build/certUtils.cjs (CommonJS) — package.json:7
  • module: build/certUtils.mjs (ESM) — package.json:8
  • types: build/certUtils.d.tspackage.json:13
  • exports["."]: import→.mjs, require→.cjs, types→.d.ts, default→.mjspackage.json:9-16

build/ is git-ignored and absent from this clone.

.gitignore:33 ignores build, and the package.json only ships build to npm. So in a fresh checkout the entrypoints do not exist until you run yarn build. The source of truth is src/index.tssrc/certUtils/CertService.ts.

Every package.json “scripts” entry (verbatim, package.json:27-36)

ScriptCommandWhat it does
vitestvitest run --config test/vitest.config.jsRuns the full Vitest suite once (unit + feature), using the dedicated config (coverage on).
buildrollup -cBundles src/index.ts into build/certUtils.{cjs,mjs,d.ts} via rollup.config.js.
vitest:unityarn vitest unit/Runs only tests under unit/ (i.e. test/tests/unit/).
vitest:featureyarn vitest feature/Runs only tests under feature/ (i.e. test/tests/feature/).
linteslint . && echo 'npm run lint: OK'Lints the repo with the flat ESLint config.
lint:fixeslint . --fixLints and auto-fixes.
tsctscType-checks (noEmit: true, so this is a type check only, no output).
testnpm run lint --silent && echo 'npm test: OK'The test script is lint-only — it does NOT run Vitest. (CI runs yarn vitest separately.)

yarn test ≠ running the test suite.

test only runs eslint (package.json:35). To actually execute unit/feature tests you must run yarn vitest (or the :unit/:feature variants). CI runs both yarn test (lint) and yarn vitest (tests) as separate steps — see CI below.

Typical workflow

yarn install --frozen-lockfile   # as CI does
yarn tsc                          # type check
yarn test                         # lint (NOT tests)
yarn vitest                       # actual test suite
yarn build                        # produce build/certUtils.{cjs,mjs,d.ts}

(Sequence mirrors .github/workflows/yarn-vitest.yaml:15-18.)

4. Top-level structure

Verified via find (cert_utils/ root):

PathRoleSource
src/TypeScript source (3 dirs, 6 files, 754 LOC total)listing + wc -l
src/index.tsPublic entry — re-exports CertService onlysrc/index.ts
src/certUtils/The three core classes: CertService.ts, CertInfo.ts, CertValidationError.tslisting
src/util/Helpers: TempFileService.ts, child_process_promise.tslisting
test/Vitest config + tests (tests/unit/, tests/feature/)listing
maintenance/Dated dependency-bump audit logs (yarn outdated) — 2026-04-07.md, 2026-05-04.mdfiles read
security/Dated yarn audit logs — 2026-04-07.md, 2026-05-04.mdfiles read
.github/workflows/Single CI workflow yarn-vitest.yamlfile read
package.json / yarn.lockManifest + lockfileread
rollup.config.jsBuild configread
tsconfig.jsonTS compiler configread
eslint.config.jsESLint flat configread
.editorconfig / .gitignoreEditor + ignore rules.gitignore read
README.md / LICENSEDocs + MIT licenseread

No node_modules, dist, build, or vendored trees are present in the clone (all git-ignored).

5. Helper scripts under first-party bin/

None. There is no bin/ directory in this repo and no bin field in package.json (verified by find and reading package.json). The closest “helper” code is in src/util/ (documented in §6).

eslint.config.js:12 ignores a bin/mc path and *.sh, suggesting this config is shared/templated across TechTeamer repos that do have a bin/mc (MinIO client) — but no such file or any shell script exists in cert_utils (find for *.sh and bin/ returned nothing).

6. Key modules / source files

src/certUtils/CertService.ts (564 LOC) — the core

The single class behind the whole package. Key behaviors verified:

  • P12 parsing (_parsePkcs12Cert, CertService.ts:140-160): pipes the P12 buffer through openssl pkcs12 -nodes -passin env:PASSWORD | openssl x509 -noout -subject -issuer -enddate -startdate, passing the password via the PASSWORD env var (not argv). Wrong password → CertValidationError(INCORRECT_PASSWORD); other failures → INVALID.
  • P12-from-file (parseCertFromFile, CertService.ts:86-130): runs openssl pkcs12 -in <path> -password env:PASSWORD -nodes [-legacy] | openssl x509 -text. If openssl errors mention legacy ciphers (RC2-*-CBC / 3DES-*-CBC), it retries with -legacy (CertService.ts:114-118) — this is the OpenSSL 3 legacy-provider workaround.
  • PEM parsing (parsePemCert, CertService.ts:167-180): openssl x509 -noout -subject -issuer -enddate -startdate -serial over stdin.
  • openssl invocation: two helpers — _execOpenSSLCLI (uses the promisified util/child_process_promise.exec, CertService.ts:215-220) and _execOpenSSLCLIWithBuffer (raw node:child_process.exec, writes the cert to stdin, CertService.ts:229-240).
  • Output parsing (_parseOpenSSLCertOutput, CertService.ts:247-296): line-based switch mapping subject/issuer/notBefore/notAfter/version/serial/signatureAlgorithm into a CertInfo.
  • Subject/issuer parsing (_parseSubjectLine, CertService.ts:338-385): regex-splits K = V, K = V pairs, handles quoted values, commas inside values, and re-decodes escaped hex sequences (e.g. \C3\83) using iconv-lite, auto-switching latin1utf8 when the input looks like UTF-8 (CertService.ts:362-379). _addNamedAttributes (CertService.ts:303-328) expands short keys (CN, O, OU, C, ST, L, STREET, EMAILADDRESS, UID, SN, GN, T, I, GQ) into long names like commonName, organizationName.
  • CA-chain verify (_verifyCertCA, CertService.ts:438-465): writes cert + CA to temp files (via TempFileService) and runs openssl verify -verbose -CAfile <ca> <cert>; success is the literal string <certPath>: OK. Always cleans up temp files in finally.
  • OCSP (_verifyOcsp, CertService.ts:475-498): calls ocsp.check({ cert, issuer }, cb) from @techteamer/ocsp; non-good status → CertValidationError(REVOKED).
  • PEM body extraction (getPemCertBody, CertService.ts:507-561): manual PEM line-walker — skips bag attributes/headers, validates BEGIN/END markers, requires type CERTIFICATE, returns base64-decoded body. Throws MALFORMED on bad markers.
  • Default encoding is 'latin1' (constructor, CertService.ts:38) — matches README default config.

Security-relevant: shell command construction.

parseCertFromFile interpolates the file path directly into the openssl shell command string (CertService.ts:100), and _verifyCertCA interpolates temp-file paths (CertService.ts:451). Passwords are passed safely via env vars (PASSWORD), but caller-supplied certPath is not shell-escaped. Treat certPath as trusted input.

src/certUtils/CertInfo.ts (96 LOC)

Data holder for a parsed cert. Fields: decrypted, version, subject, issuer, notBefore, notAfter, serial, signatureAlgorithm (CertInfo.ts:17-36). Computed getters:

  • isExpired = notAfter < now (CertInfo.ts:51-53), isEffective = notBefore < now (CertInfo.ts:55-57), isValid = effective && !expired (CertInfo.ts:59-61).
  • validator getter returns { ok, info: { isEffective, isExpired }, message } — matches the README example (CertInfo.ts:63-69, README :118-127).

src/certUtils/CertValidationError.ts (32 LOC)

Error subclass with a reason field. Reason enum: INVALID, MALFORMED, EXPIRED, REVOKED, NOT_ACTIVE_YET, INCORRECT_PASSWORD (CertValidationError.ts:10-19). Default message: Failed to validate certificate, reason: <reason> (CertValidationError.ts:25).

src/util/TempFileService.ts (30 LOC)

Wraps the tmp package; createTempFile(options, content) creates a temp file, writes the buffer, returns { tempPath, fd, cleanupCallback }. Calls tmp.setGracefulCleanup() at module load (TempFileService.ts:5).

src/util/child_process_promise.ts (29 LOC)

Promisified child_process.exec returning stdout; optional logger streams stdout→logger.info, stderr→logger.error (child_process_promise.ts:3-29). Used by _execOpenSSLCLI.

7. Configuration

There is no config file, no env-var schema, no AJV/getconfig inside this package — it is a stateless library. Configuration is purely constructor arguments:

  • new CertService(encoding = 'latin1') — the only config knob. README documents the intended host-app pattern config.get('certService.encoding', 'latin1') and default config block { "certService": { "encoding": 'latin1' } } (README.md:30-38), but that config.get lives in the consuming app, not here.
  • Runtime env vars used internally are ephemeral per-exec: PASSWORD is injected into the openssl child process env for P12 decryption (CertService.ts:101-103,147-149). It is not read from the ambient environment.
  • .gitignore:17 references config/local.json and .gitignore:9-13 ignore *.key, *.pem, *.crt, etc. — again templated; no config/ dir exists in the repo.

8. Tests

ItemDetailSource
FrameworkVitest 4.x, v8 coverage (lcov)test/vitest.config.js:7-12, package.json:53
Locationtest/tests/unit/cert-service.test.ts (717 LOC), test/tests/feature/cert-service.test.ts (137 LOC)listing
Configtest/vitest.config.js — 30 s timeout, excludes node_modules/web/yarn-offline-cache, coverage→test/coverage/vitesttest/vitest.config.js
Run allyarn vitestpackage.json:28
Run subsetyarn vitest:unit / yarn vitest:featurepackage.json:30-31

Tests require openssl and generate real certs.

The unit suite’s beforeAll shells out to openssl genrsa, openssl req, openssl x509, openssl pkcs12 to create ./test/certs/* fixtures at runtime (test/tests/unit/cert-service.test.ts:22-47). Running tests without openssl on PATH will fail. The test/certs/ dir is generated, not committed.

Coverage of behaviors: CertValidationError semantics, parseCert dispatch (P12/PEM/default/invalid), P12 & PEM parse success/failure, verifyCert orchestration, validity (expired / not-active), CA verification (valid/invalid/wrong-CA), OCSP (good/revoked/error), getPemCertBody (valid + 5 malformed cases), and _parseSubjectLine encoding/quote/comma edge cases. Feature tests assert full CertInfo.body shape against canned openssl output (feature/cert-service.test.ts:12-135).

CI

.github/workflows/yarn-vitest.yaml: on PRs to master, Ubuntu, Node 20, yarn install --frozen-lockfileyarn tscyarn test (lint) → yarn vitest (tests). openssl is provided by the ubuntu-latest image.

CI Node 20 vs engines >=20.19.0 vs @types/node ^25.

CI pins node-version: '20' (yarn-vitest.yaml:13) while engines requires >=20.19.0 (package.json:25) and devDeps pull @types/node ^25.9.1 (package.json:49). The maintenance logs (maintenance/2026-05-04.md) show several deps deliberately held back (“Update scheduled”), so manifest version ranges and the lockfile/CI may diverge in practice.

9. Dependencies on other FaceKom services

None at the network/infra level. This library makes no RabbitMQ, HTTP-API, DB, or Redis calls of its own (verified: no such clients imported anywhere in src/). Its only external interactions are:

  1. Spawning the local openssl CLI (subprocess) for all parse/verify operations.
  2. OCSP revocation requests, performed by the @techteamer/ocsp dependency when verifyCert(..., verifyOcsp=true) is called — the OCSP responder URL comes from the certificate’s AIA extension (handled inside @techteamer/ocsp, not configured here) (CertService.ts:482).

The only first-party FaceKom/TechTeamer code dependency is @techteamer/ocsp (package.json:43). cert_utils itself is a dependency consumed by other FaceKom repos (e.g. timestamp/signing services per README framing); those consumers are out of scope for this doc.

10. Verified gotchas (summary)

openssl CLI is a hard runtime requirement. Every public method ultimately spawns openssl. No pure-JS fallback exists.

yarn test runs only ESLint, not the test suite. Use yarn vitest.

build/ (the published entrypoints) is git-ignored and absent from source checkouts — run yarn build first.

OpenSSL 3 legacy-cipher P12s only work via parseCertFromFile's -legacy retry path (CertService.ts:114-118); the buffer-based _parsePkcs12Cert has no legacy fallback and will throw INVALID on RC2/3DES-encrypted containers.

certPath in parseCertFromFile is interpolated unescaped into a shell command — caller must ensure it is trusted.

_addNamedAttributes has a latent bug: it guards on attributeMap[key] (raw case) but looks up attributeMap[keyUppercase] (CertService.ts:323-325); lowercase keys won't get expanded. Not asserted by tests (tests pass uppercase keys like C, ST).

Unverified / gaps

  • yarn.lock (134,503 bytes, ~131 KiB) was confirmed present but not read line-by-line; exact transitive pinned versions are not enumerated here (manifest ranges are reported instead).
  • build/ artifacts are absent (git-ignored), so the compiled .cjs/.mjs/.d.ts were not inspected — claims about runtime behavior come from src/.
  • .editorconfig present but not read (cosmetic editor settings only).
  • Consumer services: the specific FaceKom repos that import @techteamer/cert-utils were not traced from within this repo (out of scope; would require cross-repo search). README only states the “for the timestamps” intent (README.md:4).
  • No Git-LFS / model-weight pointer files exist in this repo (verified: no .gitattributes, no binary blobs) — N/A for cert_utils.
  • Git history is a single squashed commit 58437be chore: [fkqa-332] bump packages (#14) with no tags (shallow/source-only clone), so historical evolution could not be traced.

Sources

Files read in full (read-only):

  • cert_utils/README.md
  • cert_utils/package.json
  • cert_utils/LICENSE
  • cert_utils/rollup.config.js
  • cert_utils/tsconfig.json
  • cert_utils/eslint.config.js
  • cert_utils/.gitignore
  • cert_utils/src/index.ts
  • cert_utils/src/certUtils/CertService.ts
  • cert_utils/src/certUtils/CertInfo.ts
  • cert_utils/src/certUtils/CertValidationError.ts
  • cert_utils/src/util/TempFileService.ts
  • cert_utils/src/util/child_process_promise.ts
  • cert_utils/test/vitest.config.js
  • cert_utils/test/tests/unit/cert-service.test.ts
  • cert_utils/test/tests/feature/cert-service.test.ts
  • cert_utils/.github/workflows/yarn-vitest.yaml
  • cert_utils/maintenance/2026-04-07.md, cert_utils/maintenance/2026-05-04.md
  • cert_utils/security/2026-04-07.md, cert_utils/security/2026-05-04.md

Commands run (read-only): find (file inventory, Dockerfile/bin/LFS checks), wc -l (LOC), git -C … branch/log/for-each-ref (branch master, single commit 58437be, no tags).