@blamejs/pki 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/README.md +49 -27
- package/index.js +10 -0
- package/lib/framework-error.js +13 -0
- package/lib/hpke.js +487 -0
- package/lib/oid.js +14 -0
- package/lib/sigstore.js +613 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,25 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## v0.2.
|
|
7
|
+
## v0.2.3 — 2026-07-12
|
|
8
|
+
|
|
9
|
+
Offline Sigstore bundle verification joins the toolkit as pki.sigstore.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- pki.sigstore.verifyBundle(bundle, opts) verifies a Sigstore bundle offline against caller-supplied trust material (opts.fulcioRoots, the Fulcio CA certificates; opts.rekorKeys, the Rekor log public keys, each with an optional validFor window honored against the log time so a rotated-out key is not used; optional opts.identity policy and opts.time). It returns { verified: true, payload, statement, subjects, predicateType, predicate, identity, integratedTime } on success -- payload being the raw verified envelope bytes, never a re-serialization -- and throws a typed sigstore/* error on any leg's failure. The transparency-log entry is bound to both the bundle signature and its leaf certificate, and only the v0.1-v0.3 bundle versions this release verifies are accepted (a newer version is recognized and deferred). pki.sigstore.parseBundle(input) decodes and structurally validates a bundle (object, JSON string, or Buffer) fail-closed. pki.sigstore.pae(payloadType, payloadBytes) builds the DSSE Pre-Authentication Encoding a signature covers. DSSE / Sigstore bundle v0.3 / RFC 9162 / SLSA provenance v1.
|
|
14
|
+
- The OID registry gains the Fulcio (Sigstore) certificate-extension arc 1.3.6.1.4.1.57264.1.* (the OIDC issuer, build-signer and source-repository identity claims), honoring the raw-string-vs-DER-UTF8String encoding split by member. The error taxonomy gains SigstoreError (sigstore/*): a malformed or oversize bundle (sigstore/bad-bundle), an unknown media type (sigstore/bad-bundle-version), an unsupported content arm (sigstore/unsupported-content), a DSSE signature that does not verify under the Fulcio leaf key (sigstore/dsse-verify-failed), an inclusion proof that does not reconstruct the tree root (sigstore/inclusion-proof-mismatch) or is malformed (sigstore/bad-inclusion-proof), a tree root not attested by the Rekor key (sigstore/unsigned-root), a log time not attested by the Rekor SET that signs it (sigstore/unattested-time, so an attacker cannot backdate the ephemeral Fulcio certificate into validity), a Fulcio chain that does not terminate at a caller-supplied trust anchor (sigstore/chain-incomplete) or fails validation as of the log time (sigstore/chain-invalid), an undecodable certificate identity (sigstore/bad-certificate), a malformed transparency-log entry (sigstore/bad-tlog-entry), a log entry that does not bind this signature (sigstore/entry-mismatch), an identity that fails the caller policy (sigstore/identity-mismatch), a payload that is not the expected in-toto statement (sigstore/bad-statement), and a predicateType that does not match a caller-pinned one (sigstore/predicate-mismatch). Fulcio CA anchors and Rekor keys honor their trusted-root validity windows so a rotated-out key or CA is not used, and every anchor sharing a subject DN is tried.
|
|
15
|
+
|
|
16
|
+
## v0.2.2 — 2026-07-11
|
|
17
|
+
|
|
18
|
+
Hybrid Public Key Encryption (RFC 9180) joins the toolkit as pki.hpke.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- pki.hpke.setupS(suiteIds, recipientPublicKey, opts) / pki.hpke.setupR(suiteIds, enc, recipientPrivateKey, opts) establish a sender / recipient HPKE context (RFC 9180 sec. 5.1); the returned context exposes seal(aad, pt) / open(aad, ct) with the sequence-counter nonce and a message-limit guard, and export(exporterContext, L) for the secret-export interface. pki.hpke.seal / pki.hpke.open are the single-shot wrappers (sec. 6). pki.hpke.suites carries the RFC 9180 sec. 7 KEM / KDF / AEAD / MODE code points. Keys are node KeyObjects or serialized bytes; the offered suites are DHKEM P-256, P-521, X25519, and X448, HKDF-SHA256 / HKDF-SHA512, the three AEADs plus export-only, and all four modes. RFC 9180.
|
|
23
|
+
- The error taxonomy gains HpkeError (hpke/*): a malformed, low-order, or otherwise invalid encapsulated or KEM key (hpke/bad-key, so a Diffie-Hellman that fails during derivation surfaces as a typed error, never a raw fault), an unknown or unsupported ciphersuite code point (hpke/unknown-suite, never a silent default), an unsupported mode (hpke/unknown-mode, so an out-of-registry mode is rejected rather than key-scheduled), an authenticated mode invoked without the sender's key (hpke/auth-key-required), inconsistent PSK inputs (hpke/inconsistent-psk), an AEAD tag that does not verify (hpke/open-failed, returning no plaintext), a sequence-number overflow (hpke/message-limit, before any nonce reuse), a seal/open against an export-only suite (hpke/export-only), and a wrong-direction context call (hpke/wrong-role, so a recipient context cannot seal nor a sender context open -- they share a key and base nonce).
|
|
24
|
+
|
|
25
|
+
## v0.2.1 — 2026-07-11
|
|
8
26
|
|
|
9
27
|
Stateful hash-based signature verification (HSS/LMS) joins the toolkit as pki.shbs.
|
|
10
28
|
|
package/README.md
CHANGED
|
@@ -225,6 +225,8 @@ is callable today; nothing below is a stub.
|
|
|
225
225
|
| `pki.merkle` | RFC 6962 / RFC 9162 Merkle-tree proof verification — `leafHash` / `nodeHash` / `emptyRootHash` build the domain-separated (0x00 leaf / 0x01 node) SHA-256 tree hashes; `verifyInclusion` folds an audit proof back to a root and `verifyConsistency` reconstructs both the old and new root (the append-only guarantee), each constant-time-compared to a trusted checkpoint root. Fail-closed on bad geometry, sync hashing, transport-free — `leafHash`, `nodeHash`, `emptyRootHash`, `verifyInclusion`, `verifyConsistency` |
|
|
226
226
|
| `pki.trust` | Mozilla / CCADB trust-store ingestion — `parseCertdata` reads the NSS `certdata.txt` object stream and `parseCcadbCsv` the CCADB CSV export into one identical constraint-carrying anchor shape: the per-purpose trust bits (only `CKT_NSS_TRUSTED_DELEGATOR` grants) and the per-purpose distrust-after dates the bare root list omits. Certificate and trust objects pair by byte-exact issuer + serial (never adjacency) and are cross-checked against the parsed DER, so metadata can never attach to the wrong root; `anchor()` hands an entry to `pki.path.validate({ trustAnchor, checkPurpose })`. Offline, fail-closed, bounded — `parseCertdata`, `parseCcadbCsv`, `anchor` |
|
|
227
227
|
| `pki.shbs` | Stateful hash-based signature **verification** — HSS/LMS (RFC 8554), carried in X.509 by RFC 9802 and CMS by RFC 9708, profiled by NIST SP 800-208 (CNSA 2.0 firmware signing). `verify` checks an HSS signature (every level must pass) and `verifyLms` a single-tree LMS, over the raw public-key / signature blobs the parsers already surface. Pure public-input SHA-256 / SHAKE256 hashing, a data-driven typecode registry, bounds-before-slice reads; a malformed blob throws a typed `ShbsError`, a well-formed-but-wrong signature returns `false`. **Verify only by design** — stateful signing needs atomic one-time-key state that belongs in an HSM — `verify`, `verifyLms` |
|
|
228
|
+
| `pki.hpke` | Hybrid Public Key Encryption (RFC 9180) — the encrypt-to-a-public-key primitive behind TLS ECH, MLS, and OHTTP. `setupS`/`setupR` establish a sender/recipient context (KEM encapsulation + HKDF key schedule); the context's `seal`/`open` AEAD-encrypt with a sequence-counter nonce and `export` derives further secrets; `seal`/`open` are single-shot wrappers. DHKEM (P-256, P-521, X25519, X448) × HKDF-SHA256/SHA512 × AES-GCM/ChaCha20Poly1305/export-only × all four modes, proven against the RFC 9180 Appendix A vectors. DHKEM(P-384) and HKDF-SHA384 are RFC-registered but Appendix A ships no vector for them, so they fail closed until an authoritative KAT exists. Pure composition over `node:crypto`; ML-KEM / X-Wing are a registry data-row extension pending stable drafts — `suites`, `setupS`, `setupR`, `seal`, `open` |
|
|
229
|
+
| `pki.sigstore` | Offline verifier for a Sigstore bundle — the exact artifact `npm publish --provenance` produces and the registry serves. `verifyBundle` composes five fail-closed legs against caller-pinned trust (Fulcio CA roots + Rekor log keys, never trusted from the bundle): the DSSE signature over its PAE preimage under the Fulcio leaf key; the ephemeral Fulcio certificate chain, validated as of the Rekor log time; the RFC 9162 inclusion proof folded to a Rekor-signed tree root; the log entry bound to this exact signature; and the in-toto SLSA subject digest the caller confirms against the published artifact. Zero runtime deps — reuses the X.509 parser, RFC 5280 path validator, and Merkle verifier; the net-new codecs are the DSSE PAE byte-builder and a fail-closed JSON reader. `pae`, `parseBundle`, `verifyBundle` |
|
|
228
230
|
| `pki.C` / `pki.constants` | Version-stable constants — functional scale helpers (`C.TIME.*`, `C.BYTES.*`), codec `LIMITS`, `version` |
|
|
229
231
|
| `pki.errors` | The `PkiError` taxonomy — `defineClass` plus `ConstantsError` / `Asn1Error` / `OidError` / `PemError` / `CertificateError` / `CrlError` / `CsrError` / `Pkcs8Error` / `CmsError` / `OcspError` / `TspError` / `AttrCertError` / `CrmfError` / `Pkcs12Error` / `CmpError` / `PathError` / `CtError` / `JoseError` / `AcmeError`, each carrying a stable `code` in `domain/reason` form |
|
|
230
232
|
| `pki` CLI | `pki version`, `pki oid <dotted\|name>`, `pki parse <cert>` |
|
|
@@ -260,34 +262,42 @@ reintroduce the bug class it prevents. Adding a format is a schema declaration
|
|
|
260
262
|
plus a documentation comment block, not new parse logic.
|
|
261
263
|
|
|
262
264
|
```
|
|
263
|
-
┌─ Detect + route
|
|
264
|
-
│
|
|
265
|
-
|
|
265
|
+
┌─ Detect + route ─────────────────────────────────────────────────────────┐
|
|
266
|
+
│ pki.schema.parse — inspect the DER root, route to the matching sibling │
|
|
267
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
266
268
|
│
|
|
267
|
-
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ Format
|
|
268
|
-
│ x509 │ │ crl │ │ csr │ │ pkcs8 │ parsers
|
|
269
|
-
└────────┘ └────────┘ └────────┘ └────────┘ (siblings)
|
|
270
269
|
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
|
|
271
|
-
│
|
|
270
|
+
│ x509 │ │ crl │ │ csr │ │ pkcs8 │
|
|
272
271
|
└────────┘ └────────┘ └────────┘ └────────┘
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
│
|
|
278
|
-
|
|
279
|
-
|
|
272
|
+
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
|
|
273
|
+
│ cms │ │ ocsp │ │ tsp │ │ crmf │
|
|
274
|
+
└────────┘ └────────┘ └────────┘ └────────┘
|
|
275
|
+
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
|
|
276
|
+
│ pkcs12 │ │ cmp │ │ smime │ │attrcert│
|
|
277
|
+
└────────┘ └────────┘ └────────┘ └────────┘
|
|
278
|
+
│ routed DER format parsers (siblings)
|
|
279
|
+
┌─ Protocols · trust · supply chain (reached by explicit call) ───────────┐
|
|
280
|
+
│ pki.path RFC 5280 · pki.trust anchors · pki.ct SCTs · pki.hpke RFC 9180 │
|
|
281
|
+
│ pki.shbs HSS/LMS · pki.merkle RFC 9162 · pki.sigstore npm provenance │
|
|
282
|
+
│ pki.jose · pki.acme · pki.est — compose the layers below directly. │
|
|
283
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
284
|
+
│ every module composes ↓
|
|
285
|
+
┌─ Shared structure ───────────────────────────────────────────────────────┐
|
|
286
|
+
│ pki.schema.engine — walk + combinators (positional reads, tag order, │
|
|
287
|
+
│ SET-OF uniqueness, typed errors) · the PKIX sub-schemas · and the │
|
|
288
|
+
│ guard family (guard-*) — one fail-closed choke point per CVE class. │
|
|
289
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
280
290
|
│ built on ↓
|
|
281
|
-
┌─ Foundation
|
|
282
|
-
│
|
|
283
|
-
│
|
|
284
|
-
│
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
┌─ Crypto
|
|
288
|
-
│
|
|
289
|
-
│
|
|
290
|
-
|
|
291
|
+
┌─ Foundation ─────────────────────────────────────────────────────────────┐
|
|
292
|
+
│ pki.asn1 — strict bounded DER codec · pki.cbor — deterministic CBOR · │
|
|
293
|
+
│ pki.oid — two-way PQC-seeded registry · pki.errors — PkiError taxonomy │
|
|
294
|
+
│ · pki.C — version-stable LIMITS + scale constants. │
|
|
295
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
296
|
+
|
|
297
|
+
┌─ Crypto ─────────────────────────────────────────────────────────────────┐
|
|
298
|
+
│ pki.webcrypto ─▶ node:crypto — a W3C SubtleCrypto engine: the │
|
|
299
|
+
│ classical set + post-quantum ML-DSA / SLH-DSA + ML-KEM key generation. │
|
|
300
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
291
301
|
```
|
|
292
302
|
|
|
293
303
|
**Foundation.** The strict, bounded DER codec (`pki.asn1`), the two-way OID
|
|
@@ -301,12 +311,24 @@ bounded version reader, and the single coerce → decode → walk parse-entry th
|
|
|
301
311
|
every format's `parse` is bound to. Because input coercion, the PEM size cap, and
|
|
302
312
|
the DER-decode wrapping live here once, a format cannot diverge on a guard.
|
|
303
313
|
|
|
304
|
-
**Format parsers.** `x509`, `crl`, `csr`, `pkcs8`, `cms`, `ocsp`, `tsp`,
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
**Format parsers.** `x509`, `crl`, `csr`, `pkcs8`, `cms`, `ocsp`, `tsp`, `attrcert`,
|
|
315
|
+
`crmf`, `pkcs12`, `cmp`, `smime`, and `csrattrs` are siblings: each is a schema
|
|
316
|
+
declaration composed from the shared pieces, emitting its own typed
|
|
317
|
+
`domain/reason` error codes. `pki.schema.parse` inspects a decoded root and
|
|
307
318
|
detect-and-routes to the matching sibling; the detectors are mutually exclusive by
|
|
308
319
|
construction, so routing is unambiguous regardless of registration order.
|
|
309
320
|
|
|
321
|
+
**Protocols, trust, and supply chain.** Above the format parsers sit the domain
|
|
322
|
+
modules reached by explicit call rather than DER routing: `pki.path` (RFC 5280
|
|
323
|
+
path validation), `pki.trust` (trust anchors), `pki.ct` (Certificate Transparency
|
|
324
|
+
SCTs), `pki.hpke` (RFC 9180), `pki.shbs` (HSS/LMS stateful hash signatures),
|
|
325
|
+
`pki.merkle` (RFC 9162 transparency proofs), `pki.sigstore` (offline npm-provenance
|
|
326
|
+
verification), and the `jose` / `acme` / `est` enrollment surfaces. Each composes
|
|
327
|
+
the shared structure, foundation, and crypto layers directly. Alongside the schema
|
|
328
|
+
engine, the fail-closed **guard family** (`guard-*`) centralizes each CVE-class
|
|
329
|
+
defense — detached-buffer re-view, resource caps, constant-time compares,
|
|
330
|
+
canonical-DN comparison — as one choke point a format cannot re-inline.
|
|
331
|
+
|
|
310
332
|
**Crypto.** `pki.webcrypto` is a W3C `SubtleCrypto` engine over `node:crypto`,
|
|
311
333
|
carrying the classical suite plus post-quantum ML-DSA / SLH-DSA signatures and
|
|
312
334
|
ML-KEM key generation. Sign/verify resolves algorithms through the same OID
|
package/index.js
CHANGED
|
@@ -38,6 +38,8 @@ var path = require("./lib/path-validate");
|
|
|
38
38
|
var ct = require("./lib/ct");
|
|
39
39
|
var merkle = require("./lib/merkle");
|
|
40
40
|
var shbs = require("./lib/shbs");
|
|
41
|
+
var hpke = require("./lib/hpke");
|
|
42
|
+
var sigstore = require("./lib/sigstore");
|
|
41
43
|
var est = require("./lib/est");
|
|
42
44
|
var jose = require("./lib/jose");
|
|
43
45
|
var acme = require("./lib/acme");
|
|
@@ -79,6 +81,14 @@ module.exports = {
|
|
|
79
81
|
// VERIFY ONLY by design: stateful signing requires atomic one-time-key index
|
|
80
82
|
// state that belongs in an HSM, so this module never mints a signature.
|
|
81
83
|
shbs: shbs,
|
|
84
|
+
// `hpke` is RFC 9180 Hybrid Public Key Encryption -- the KEM + HKDF key
|
|
85
|
+
// schedule + AEAD context construction behind TLS ECH / MLS / OHTTP. Pure
|
|
86
|
+
// composition over node:crypto; the classical DHKEM suites, all four modes.
|
|
87
|
+
hpke: hpke,
|
|
88
|
+
// `sigstore` verifies a Sigstore bundle (the npm --provenance artifact): a
|
|
89
|
+
// keyless Fulcio signature over a DSSE-wrapped in-toto SLSA attestation with a
|
|
90
|
+
// Rekor inclusion proof -- offline, zero-dep, against caller-pinned trust.
|
|
91
|
+
sigstore: sigstore,
|
|
82
92
|
// `est` is RFC 7030 / 8951 / 9908 Enrollment over Secure Transport -- the
|
|
83
93
|
// transport-agnostic client codecs (base64 transfer, multipart splitter),
|
|
84
94
|
// certs-only + serverkeygen validators over CMS, the enroll-attribute builders,
|
package/lib/framework-error.js
CHANGED
|
@@ -282,6 +282,17 @@ var AcmeError = defineClass("AcmeError", { withCause: true });
|
|
|
282
282
|
// asn1/* error) as `.cause`.
|
|
283
283
|
var TrustError = defineClass("TrustError", { withCause: true });
|
|
284
284
|
var ShbsError = defineClass("ShbsError", { withCause: true });
|
|
285
|
+
var HpkeError = defineClass("HpkeError", { withCause: true });
|
|
286
|
+
// SigstoreError -- a Sigstore bundle (npm --provenance artifact) that is not a
|
|
287
|
+
// well-formed / verifiable keyless signature: a malformed or oversize JSON
|
|
288
|
+
// bundle, an unknown media type or content arm, a DSSE envelope missing a
|
|
289
|
+
// required field or whose signature does not verify under the Fulcio leaf key, a
|
|
290
|
+
// Rekor inclusion proof that does not reconstruct the signed tree root, an
|
|
291
|
+
// unsigned/attacker-supplied root, an entry that does not bind this signature, a
|
|
292
|
+
// Fulcio identity that fails the caller policy, or a payload that is not the
|
|
293
|
+
// expected in-toto Statement. Carries the underlying leaf fault (an x509/*,
|
|
294
|
+
// merkle/*, or asn1/* error) as `.cause`.
|
|
295
|
+
var SigstoreError = defineClass("SigstoreError", { withCause: true });
|
|
285
296
|
|
|
286
297
|
module.exports = {
|
|
287
298
|
PkiError: PkiError,
|
|
@@ -306,6 +317,8 @@ module.exports = {
|
|
|
306
317
|
PathError: PathError,
|
|
307
318
|
CtError: CtError,
|
|
308
319
|
ShbsError: ShbsError,
|
|
320
|
+
HpkeError: HpkeError,
|
|
321
|
+
SigstoreError: SigstoreError,
|
|
309
322
|
MerkleError: MerkleError,
|
|
310
323
|
SmimeError: SmimeError,
|
|
311
324
|
CsrattrsError: CsrattrsError,
|