@blamejs/pki 0.1.22 → 0.1.23

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 CHANGED
@@ -4,6 +4,41 @@ 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.1.23 — 2026-07-10
8
+
9
+ CMS grows authenticated content: RFC 5652 AuthenticatedData, RFC 5083 AuthEnvelopedData, and RFC 9629 KEM recipients (ML-KEM ready) — plus a toolkit-wide hardening pass.
10
+
11
+ ### Added
12
+
13
+ - pki.schema.cms.parse decodes id-ct-authData (RFC 5652 section 9 AuthenticatedData): { version, originatorInfo, recipientInfos, macAlgorithm, digestAlgorithm, encapContentInfo, authAttrs, authAttrsBytes, mac, unauthAttrs }. The section 9.1 version rule is computed from originatorInfo contents (recipient kinds never influence it); digestAlgorithm and authAttrs are enforced as a biconditional; authAttrs are required for a non-id-data content type and must carry content-type (matching the eContentType) and message-digest; authAttrsBytes is the raw on-wire [2] TLV for the section 9.2 MAC re-tag.
14
+ - pki.schema.cms.parse decodes id-ct-authEnvelopedData (RFC 5083): { version, originatorInfo, recipientInfos, encryptedContentInfo, aead, authAttrs, authAttrsBytes, mac, unauthAttrs }. A recognized AES-GCM/CCM content-encryption algorithm gets its RFC 5084 parameters validated (present, nonce bounds — CCM 7..13 octets, ICV length from the allowed set and equal to the mac length, DEFAULT-omitted per X.690 11.5) and surfaced as aead: { kind, nonce, icvLen }; an unrecognized algorithm surfaces raw parameters with aead null.
15
+ - KEMRecipientInfo (RFC 9629) parsed under the OtherRecipientInfo id-ori-kem arm: { version, rid, ridType, kem, kemct, kdf, kekLength, ukm, wrap, encryptedKey } as kemri alongside the raw oriValue. version must be 0; kekLength must be 1..65535 and match a recognized AES key-wrap's KEK size; a recognized ML-KEM kem pins the exact FIPS 203 ciphertext length. An unrecognized oriType still surfaces raw (the ORI extension point); a recognized one is validated by content, never accepted on the type OID alone.
16
+ - The parameters-absent registry (pki.oid.paramsMustBeAbsent) gains ML-KEM-512/768/1024 (RFC 9936) and the three HKDF identifiers (RFC 8619), enforced once in the shared AlgorithmIdentifier schema so certificates, CMS, and every other format inherit the rule.
17
+ - RFC 5652 section 11 attribute placement rules, enforced everywhere attribute sets are parsed: content-type / message-digest / signing-time must not appear in unsigned, unauthenticated, or unprotected attribute sets; countersignature only in unsigned attributes. signing-time values are validated as single-valued Time; every countersignature value is validated as a SignerInfo whose signedAttrs carry message-digest and no content-type (RFC 5652 section 11.4), recursively.
18
+ - Every pki.schema.cms.parse result carries contentType (the dotted OID) and contentTypeName, naming which of the five content types was dispatched — no more duck-typing the result shape.
19
+ - pemEncode lands on every remaining format: pki.schema.ocsp.pemEncode (default label OCSP RESPONSE), pki.schema.attrcert.pemEncode (ATTRIBUTE CERTIFICATE), pki.schema.crl.pemEncode (X509 CRL), and label-required pki.schema.tsp.pemEncode / pki.schema.crmf.pemEncode (no standard PEM label exists for those formats, so the operator names the envelope explicitly).
20
+ - New fuzz targets with seed corpora: crl-parse, csr-parse, pkcs8-parse, and schema-all-parse (the orchestrator front door), plus authenticated-content and KEM-recipient seeds for cms-parse; all wired into the per-PR and nightly fuzz matrices.
21
+ - pki.oid.register / registerFamily validate X.660 encodability at registration: root arc 0..2, second arc 0..39 under roots 0 and 1, at least two arcs, and no leading-zero components — a typo fails at config time instead of minting an unmatchable registry key.
22
+
23
+ ### Changed
24
+
25
+ - SignedData and OriginatorInfo certificates/crls buckets validate the closed CertificateChoices / RevocationInfoChoice tag sets (RFC 5652 sections 10.2.1-10.2.2): elements are still surfaced raw, but a tag outside the CHOICE — or a primitive encoding — is rejected instead of silently feeding the version computation.
26
+ - Signed and authenticated attribute sets must be DER encoded even when the enclosing structure is BER (RFC 5652 sections 5.3/9.1, RFC 5083 section 2.1) — an indefinite-length attribute set reaching the PKCS#12 public-key-integrity path is now rejected instead of surfacing re-tag bytes a verifier would hash incorrectly.
27
+ - Validity, TBSCertificate, and AttributeTypeAndValue assert their SEQUENCE tag (RFC 5280 section 4.1): a SET-tagged body no longer parses through pki.schema.x509.parse while the format orchestrator rejects the same bytes.
28
+ - issuerUniqueID / subjectUniqueID are decoded as the [n] IMPLICIT BIT STRING RFC 5280 section 4.1.2.8 defines — an EXPLICIT-wrapped or malformed unique identifier is rejected.
29
+ - The IssuingDistributionPoint decoder enforces the DER field grammar (strictly ascending tags, each at most once, DER BOOLEAN values, no encoded DEFAULT FALSE); a CRL whose IDP violates it has unknown scope and is unusable for revocation decisions.
30
+ - An empty embedded SEQUENCE is rejected wherever a Certificate / CertificateList is expected (OCSP certs lists, CMP, CRMF); CRMF encryptedKey proof-of-possession and PKCS#12 encrypted safes reject a zero-length ciphertext the same way as a missing one.
31
+ - RecipientEncryptedKey surfaces ridType (rKeyId | issuerAndSerialNumber) so a key-agreement consumer no longer duck-types the recipient-matching form.
32
+ - The pki.schema.cms.parse reference documentation describes all five decoded content types and their return shapes.
33
+
34
+ ### Fixed
35
+
36
+ - Crypto engine: deriveKey implements the W3C get-key-length rules (AES lengths limited to 128/192/256, HMAC defaulting to the hash block size); deriveBits rejects over-length, zero, and non-multiple-of-8 requests against the actual shared-secret size; unwrapKey surfaces a typed error instead of a bare JSON.parse SyntaxError; an RSA publicExponent is bounds-checked before numeric narrowing.
37
+ - Path validation: an unreadable basicConstraints or issuer key-usage extension now fails the check that consulted it instead of being silently swallowed; name-constraint inputs are validated at the entry point; the policy-tree node budget is a documented constant (LIMITS.PATH_MAX_POLICY_NODES).
38
+ - ASN.1 codec: the constructed encodings of GeneralString, NumericString, and ObjectDescriptor are rejected in DER mode; the byte caps passed to asn1.decode are validated as finite non-negative integers.
39
+ - Publish pipeline: the npm tarball and SBOM are re-hashed against the attested SLSA subjects and verified with slsa-verifier before anything is signed, released, or published; eslint is lockfile-pinned and runs isolated from the pack workspace; gitleaks is version+checksum pinned; the OpenSSF Scorecard threshold gate fails closed when a score cannot be extracted.
40
+ - Every behavioral fix above ships with a conformance vector driving the public parse/validate surface on the malformed input.
41
+
7
42
  ## v0.1.22 — 2026-07-09
8
43
 
9
44
  An RFC 5035 / RFC 8551 S/MIME ESS signed-attribute decoder joins the schema family.
package/README.md CHANGED
@@ -11,10 +11,20 @@ in-house, fail-closed DER codec and a post-quantum-first algorithm registry.
11
11
  No npm runtime dependencies. No TypeScript. No Web Crypto ceiling.
12
12
 
13
13
  [![npm version](https://img.shields.io/npm/v/@blamejs/pki.svg?label=%40blamejs%2Fpki&color=2563eb)](https://www.npmjs.com/package/@blamejs/pki)
14
+ [![npm downloads](https://img.shields.io/npm/dm/@blamejs/pki.svg?color=2563eb)](https://www.npmjs.com/package/@blamejs/pki)
14
15
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
15
16
  [![node](https://img.shields.io/node/v/@blamejs/pki.svg)](https://nodejs.org)
17
+
18
+ [![CI](https://github.com/blamejs/pki/actions/workflows/ci.yml/badge.svg)](https://github.com/blamejs/pki/actions/workflows/ci.yml)
19
+ [![CodeQL](https://github.com/blamejs/pki/actions/workflows/codeql.yml/badge.svg)](https://github.com/blamejs/pki/actions/workflows/codeql.yml)
20
+ [![Fuzzing](https://github.com/blamejs/pki/actions/workflows/cflite_batch.yml/badge.svg)](https://github.com/blamejs/pki/actions/workflows/cflite_batch.yml)
21
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/blamejs/pki/badge)](https://scorecard.dev/viewer/?uri=github.com/blamejs/pki)
22
+ [![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev/spec/v1.0/levels#build-l3)
23
+
16
24
  [![Zero runtime deps](https://img.shields.io/badge/runtime%20deps-0-2ea043)](#security-posture)
17
25
  [![PQC-first](https://img.shields.io/badge/crypto-PQC--first-2563eb)](#security-posture)
26
+ [![No TypeScript](https://img.shields.io/badge/TypeScript-not%20required-2ea043)](#why-this-toolkit)
27
+ [![strict DER](https://img.shields.io/badge/DER-strict%20%2F%20fail--closed-2ea043)](#security-posture)
18
28
 
19
29
  [pkijs.com](https://pkijs.com) · [Roadmap](ROADMAP.md) · [Security](SECURITY.md) · [Changelog](CHANGELOG.md)
20
30
 
@@ -192,14 +202,14 @@ is callable today; nothing below is a stub.
192
202
  | `pki.webcrypto` | A W3C WebCrypto (`SubtleCrypto`) engine over `node:crypto` — `sign`/`verify`/`encrypt`/`decrypt`/`deriveBits`/`digest`/`generateKey`/`importKey`/`exportKey` across RSA, ECDSA, ECDH, Ed25519/Ed448, AES, HMAC, HKDF, PBKDF2, SHA — **and** post-quantum ML-DSA-44/65/87 and SLH-DSA signatures, plus ML-KEM-512/768/1024 key generation (KEM encapsulation on the roadmap). Zero-dependency, OpenSSL-interoperable |
193
203
  | `pki.schema` | The schema family — `parse` detects which PKI format DER / PEM encodes and routes to the right parser, `all` enumerates the registered formats, and the engine + per-format members are grouped here |
194
204
  | `pki.schema.x509` | Parse DER / PEM certificates into structured, validated fields — `parse`, `pemDecode`, `pemEncode` |
195
- | `pki.schema.crl` | Parse DER / PEM X.509 CRLs per RFC 5280 §5 — revoked serials with real-`Date` revocation times, named + partly-decoded extensions, fail-closed — `parse`, `pemDecode` |
205
+ | `pki.schema.crl` | Parse DER / PEM X.509 CRLs per RFC 5280 §5 — revoked serials with real-`Date` revocation times, named + partly-decoded extensions, fail-closed — `parse`, `pemDecode`, `pemEncode` |
196
206
  | `pki.schema.csr` | Parse DER / PEM PKCS#10 certification requests per RFC 2986 — subject DN, public key, requested attributes, signature, fail-closed — `parse`, `pemDecode`, `pemEncode` |
197
207
  | `pki.schema.pkcs8` | Parse DER / PEM PKCS#8 private keys per RFC 5208 / 5958 — algorithm, raw key bytes, attributes, optional public key, fail-closed; encrypted keys recognized (not decrypted) — `parse`, `parseEncrypted`, `pemDecode`, `pemEncode` |
198
- | `pki.schema.cms` | Parse DER / PEM CMS per RFC 5652 — SignedData (§5, signer infos + raw signed-attribute bytes for external verification), EnvelopedData (§6, all five RecipientInfo kinds incl. RFC 5753 key-agreement, with the wrapped keys / ciphertext surfaced raw), and EncryptedData (§8); certificates / CRLs kept raw, fail-closed; remaining content types recognized (not decoded) — `parse`, `pemDecode`, `pemEncode` |
199
- | `pki.schema.ocsp` | Parse DER / PEM OCSP requests and responses per RFC 6960 — per-certificate status (good / revoked / unknown), responder identity, raw tbs bytes for external verification, certificates kept raw, fail-closed; non-basic response types recognized (not decoded) — `parseRequest`, `parseResponse`, `pemDecode` |
200
- | `pki.schema.tsp` | Parse DER / PEM RFC 3161 timestamp responses and tokens — the TSTInfo payload (imprint, genTime with sub-second precision, serial, nonce, accuracy), the status-to-token coupling, and the token wrapper composed over CMS with the single-signer rule, fail-closed — `parse`, `parseTstInfo`, `parseToken`, `pemDecode` |
201
- | `pki.schema.attrcert` | Parse DER / PEM RFC 5755 attribute certificates — the holder and issuer identities (validated GeneralNames), the validity window (real `Date`s), the privilege attributes (role / group / clearance) and extensions, with the raw signed region for a verifier; the obsolete v1 form is recognized and deferred, fail-closed — `parse`, `pemDecode` |
202
- | `pki.schema.crmf` | Parse DER / PEM RFC 4211 certificate request messages (CertReqMessages — the CMP / EST enrollment body) — the requested-certificate template (subject, public key, validity, extensions), proof-of-possession, and registration controls, with the raw `CertRequest` region a POP verifier hashes; names dual-accepted (IMPLICIT and EXPLICIT), fail-closed — `parse`, `pemDecode` |
208
+ | `pki.schema.cms` | Parse DER / PEM CMS per RFC 5652 / 5083 / 9629 — SignedData (§5, signer infos + raw signed-attribute bytes for external verification), EnvelopedData (§6, all five RecipientInfo kinds incl. RFC 5753 key-agreement and RFC 9629 KEM recipients with ML-KEM validation), EncryptedData (§8), AuthenticatedData (§9, MAC surface + raw `authAttrsBytes`), and AuthEnvelopedData (RFC 5083, with RFC 5084 AES-GCM/CCM parameter validation); §11 attribute placement enforced, countersignatures validated recursively, certificates / CRLs validated against the closed CHOICE sets and kept raw, every result tagged `contentTypeName`, fail-closed — `parse`, `pemDecode`, `pemEncode` |
209
+ | `pki.schema.ocsp` | Parse DER / PEM OCSP requests and responses per RFC 6960 — per-certificate status (good / revoked / unknown), responder identity, raw tbs bytes for external verification, certificates kept raw, fail-closed; non-basic response types recognized (not decoded) — `parseRequest`, `parseResponse`, `pemDecode`, `pemEncode` |
210
+ | `pki.schema.tsp` | Parse DER / PEM RFC 3161 timestamp responses and tokens — the TSTInfo payload (imprint, genTime with sub-second precision, serial, nonce, accuracy), the status-to-token coupling, and the token wrapper composed over CMS with the single-signer rule, fail-closed — `parse`, `parseTstInfo`, `parseToken`, `pemDecode`, `pemEncode` |
211
+ | `pki.schema.attrcert` | Parse DER / PEM RFC 5755 attribute certificates — the holder and issuer identities (validated GeneralNames), the validity window (real `Date`s), the privilege attributes (role / group / clearance) and extensions, with the raw signed region for a verifier; the obsolete v1 form is recognized and deferred, fail-closed — `parse`, `pemDecode`, `pemEncode` |
212
+ | `pki.schema.crmf` | Parse DER / PEM RFC 4211 certificate request messages (CertReqMessages — the CMP / EST enrollment body) — the requested-certificate template (subject, public key, validity, extensions), proof-of-possession, and registration controls, with the raw `CertRequest` region a POP verifier hashes; names dual-accepted (IMPLICIT and EXPLICIT), fail-closed — `parse`, `pemDecode`, `pemEncode` |
203
213
  | `pki.schema.pkcs12` | Parse DER / BER / PEM RFC 7292 PKCS#12 (PFX) stores — key bags via the PKCS#8 parser, shrouded keys (algorithm surfaced, ciphertext opaque), cert / CRL / secret bags raw and byte-exact, encrypted and enveloped safes structurally via CMS, `friendlyName` / `localKeyId` decoded, and the exact MAC byte range (`macedBytes`) plus RFC 9579 PBMAC1 recognition for external verification; BER accepted exactly where §4.1 requires it, fail-closed — `parse`, `pemDecode`, `pemEncode` |
204
214
  | `pki.schema.cmp` | Parse DER / PEM RFC 9810 Certificate Management Protocol messages (PKIMessage) — the header (version, sender / recipient incl. the anonymous NULL-DN, nonces, transaction id, general info), the 27-arm body (certificate requests via the CRMF parser, an encrypted certificate's EnvelopedData via CMS, response / revocation / confirmation / error / support / polling arms structural, the rest raw), and the exact `headerBytes` / `bodyBytes` slices an external verifier reconstructs the protected part from; the CMP-before-OCSP dispatch order is enforced, fail-closed — `parse`, `pemDecode`, `pemEncode` |
205
215
  | `pki.schema.smime` | Decode S/MIME ESS signed-attribute values (RFC 5035 / RFC 8551) — `parseSigningCertificate` / `parseSigningCertificateV2` bind a signature to its signing certificate (cert hash, hash algorithm, issuer `GeneralNames` + serial), `parseSmimeCapabilities` decodes the ordered capability list, and `decodeAttribute` OID-dispatches a CMS attribute (enforcing the single-value rule, recognize-and-defer for unknown types). A companion decoder for CMS signed attributes, not an auto-routed format, fail-closed — `parseSigningCertificate`, `parseSigningCertificateV2`, `parseSmimeCapabilities`, `decodeAttribute` |
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
4
  /**
5
- * @blamejs/pki public API entry point.
5
+ * @blamejs/pki -- public API entry point.
6
6
  *
7
7
  * A pure-JavaScript PKI toolkit that owns its stack. Zero npm runtime
8
8
  * dependencies; strict, fail-closed parsing; post-quantum-first crypto.
@@ -11,14 +11,18 @@
11
11
  *
12
12
  * Core: C / constants (scale helpers + version), errors
13
13
  * (PkiError taxonomy), asn1 (strict DER codec), oid
14
- * (OID name registry), webcrypto (W3C SubtleCrypto
15
- * engine over node:crypto PQC-first, classical-capable)
16
- * Schema: schema (the structure-schema engine + per-format parsers:
17
- * schema.engine, schema.x509, schema.parse detect-and-route)
14
+ * (OID <-> name registry), webcrypto (W3C SubtleCrypto
15
+ * engine over node:crypto -- PQC-first, classical-capable,
16
+ * ML-DSA / ML-KEM / SLH-DSA alongside the classical set)
17
+ * Schema: schema (the structure-schema engine + the registered
18
+ * format parsers -- X.509, CRL, CSR, PKCS#8, PKCS#12, CMS,
19
+ * OCSP, timestamps, CRMF, CMP, attribute certificates --
20
+ * with detect-and-route schema.parse; schema.all() lists
21
+ * the registered set)
22
+ * Validation: path (RFC 5280 certification-path validation), ct
23
+ * (RFC 6962 Certificate Transparency SCT decoding)
18
24
  *
19
- * The surface grows per ROADMAP.md CMS, OCSP, CRL, CSR, PKCS#8/#12,
20
- * timestamping, path validation, and the post-quantum algorithm set are
21
- * targeted additions that ride the same core.
25
+ * ROADMAP.md tracks what remains ahead of the shipped surface.
22
26
  *
23
27
  * See LICENSE (Apache-2.0) and NOTICE for vendored attribution.
24
28
  */
@@ -42,12 +46,12 @@ module.exports = {
42
46
  asn1: asn1,
43
47
  oid: oid,
44
48
  // `schema` is the family: the L2 structure-schema engine (schema.engine) and
45
- // the per-format parsers (schema.x509, ) with detect-and-route schema.parse.
49
+ // the per-format parsers (schema.x509, ...) with detect-and-route schema.parse.
46
50
  schema: schema,
47
- // `path` is RFC 5280 §6 certification-path validation pki.path.validate
48
- // runs the §6.1 state machine over an already-parsed path + a trust anchor.
51
+ // `path` is RFC 5280 sec. 6 certification-path validation -- pki.path.validate
52
+ // runs the sec. 6.1 state machine over an already-parsed path + a trust anchor.
49
53
  path: path,
50
- // `ct` is RFC 6962 Certificate Transparency pki.ct.parseSctList decodes the
54
+ // `ct` is RFC 6962 Certificate Transparency -- pki.ct.parseSctList decodes the
51
55
  // SCT-list extension a certificate / OCSP response carries; the signature is
52
56
  // surfaced raw for external verification (pki.ct.reconstructSignedData).
53
57
  ct: ct,