@blamejs/pki 0.1.14 → 0.1.16

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,7 +4,39 @@ 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.14 — 2026-07-06
7
+ ## v0.1.16 — 2026-07-06
8
+
9
+ Certification path validation joins the toolkit — RFC 5280 section 6, as a pure re-entrant algorithm.
10
+
11
+ ### Added
12
+
13
+ - pki.path.validate(path, opts) — RFC 5280 section 6 certification-path validation. It validates an ordered array of pki.schema.x509.parse certificates (or DER/PEM it parses) against a trust anchor, running the section 6.1 state machine: section 6.1.3(a)(1) signature chaining, the always-on section 6.1.3(a)(2) validity window with the check date an explicit input, section 6.1.3(a)(4) name chaining, section 6.1.3(b,c)/6.1.4(g) name constraints (directoryName, dNSName, rfc822Name including an emailAddress carried in the subject DN, uniformResourceIdentifier, and iPAddress with the address-and-mask subtree form), section 6.1.4(k) basic constraints as the single authoritative CA gate, section 6.1.4(l,m) path length, section 6.1.4(n) keyUsage keyCertSign, and the section 6.1.3(d)/6.1.4(a,b,i,j)/6.1.5 certificate-policy tree with its explicit-policy, policy-mapping, and inhibit-any-policy counters. It returns { valid, path, results, workingPublicKey, workingPublicKeyAlgorithm, workingPublicKeyParameters, validPolicyTree } where results[i].checks carries a stable path/* reason code per check. Validation is pure and re-entrant. An unrecognized critical extension, an undetermined revocation status, or a structural fault fails the path with a typed code.
14
+ - pki.path.crlChecker(crls) — a CRL-backed revocation checker for the validate revocationChecker option, composing pki.schema.crl.parse. For each certificate it consults every CRL issued by the certificate's issuer (so a clean CRL cannot shadow a revoking one), verifies each CRL signature over its tbsBytes, honors the issuing-distribution-point scope and reason coverage, checks thisUpdate/nextUpdate currency, and requires the CRL signer to assert keyUsage cRLSign; a certificate listed in any authoritative in-scope CRL is revoked, and an issuer with no authoritative in-scope CRL yields an undetermined status, which the validator fails closed unless softFail is set. An OCSP checker satisfies the same interface.
15
+ - pki.schema.pkix gains the ns-parameterized RFC 5280 section 4.2.1 extension-value decoders (pkix.certExtensionDecoders) — basicConstraints, keyUsage, nameConstraints, certificatePolicies, policyMappings, policyConstraints, inhibitAnyPolicy, subjectAltName / issuerAltName, extKeyUsage, and authorityKeyIdentifier / subjectKeyIdentifier — each turning a raw extension value into a validated structure or a typed error, fail-closed. The shared GeneralName validator gains a decoded-value mode (surfacing the IA5 text, the IP octets, or the directoryName as a structured name alongside the raw bytes) and an address-and-mask subtree-base mode for name-constraint bases; both are opt-in, so the existing callers are byte-identical.
16
+ - The OID registry gains the RFC 5280 policy and wildcard extension identifiers used by path validation: policyMappings, policyConstraints, and inhibitAnyPolicy, plus the anyPolicy and anyExtendedKeyUsage special-OID leaves.
17
+ - The error taxonomy gains PathError, carrying the per-check reason in its stable path/* code.
18
+
19
+ ## v0.1.15 — 2026-07-06
20
+
21
+ CMS EnvelopedData and EncryptedData join the parser, and every documentation example is now executed as a test.
22
+
23
+ ### Added
24
+
25
+ - pki.schema.cms.parse now decodes CMS EnvelopedData (RFC 5652 §6) and EncryptedData (§8). An EnvelopedData returns { version, originatorInfo, recipientInfos, encryptedContentInfo, unprotectedAttrs } with all five RecipientInfo kinds decoded — KeyTransRecipientInfo (§6.2.1, with the issuerAndSerialNumber/subjectKeyIdentifier version coupling enforced), KeyAgreeRecipientInfo (§6.2.2 + RFC 5753 §3.1), KEKRecipientInfo (§6.2.3), PasswordRecipientInfo (§6.2.4), and OtherRecipientInfo (§6.2.5). An EncryptedData returns { version, encryptedContentInfo, unprotectedAttrs }. The wrapped keys, the ciphertext, and all AlgorithmIdentifier parameters are surfaced raw — every recipient carries the keyEncryptionAlgorithm its encryptedKey must be unwrapped with, and a kekid / rKeyId OtherKeyAttribute is surfaced as raw DER — decryption and key-unwrap are a separate layer. The CMSVersion is recomputed and enforced per structure and per recipient, recipientInfos is required non-empty, and the encryptedContent [0] IMPLICIT OCTET STRING is read as the ciphertext directly.
26
+ - The schema engine gains an implicitTag option on pki.schema.engine.seq() and on pki.schema.pkix.algorithmIdentifier(ns, { implicitTag }) — a [tag] IMPLICIT SEQUENCE / AlgorithmIdentifier reader (used by the PasswordRecipientInfo keyDerivationAlgorithm [0]). A call with no option is byte-identical to before.
27
+ - Every @example in the documentation comment blocks is now executed end-to-end as a test (test/layer-0-primitives/doc-examples.test.js, in the smoke gate), not merely parse-checked: an example must run to completion or throw a typed PkiError, and every documented @primitive path must resolve to a real export — so a documented example can no longer drift from the shipped API. A new @originated comment tag records a callable's original availability version when its documented path is later corrected, enforced alongside the @since version gate.
28
+
29
+ ### Changed
30
+
31
+ - The W3C WebCrypto constructor classes (CryptoKey, Crypto, SubtleCrypto, WebCryptoError) are now reachable under pki.webcrypto (e.g. pki.webcrypto.CryptoKey) alongside the ready Crypto instance, matching their documented path; the previously-separate pki.WebCrypto holder is removed.
32
+ - Repository tooling now installs npm packages exclusively through integrity-verified lockfiles: the fuzz build installs the jazzer.js engine via npm ci against the committed fuzz/package-lock.json, and the vendoring script resolves a package to an integrity-pinned lockfile in an isolated staging workspace — no install script runs and the repo's own node_modules is never touched — before bundling. Tooling child processes that need a shell (the Windows npm shim) now receive one explicitly-quoted command string instead of an unescaped argument array.
33
+
34
+ ### Fixed
35
+
36
+ - Two documented API paths that did not resolve at runtime are corrected: pki.webcrypto.CryptoKey (previously reachable only via pki.WebCrypto.CryptoKey) and pki.asn1.read.oid (its comment block labeled the path pki.asn1.readOid, which never existed). Both are now reachable at the documented path.
37
+ - A documentation example for pki.webcrypto.subtle.exportKey referenced an undefined variable; it now generates the key pair it exports.
38
+
39
+ ## v0.1.14 — 2026-07-05
8
40
 
9
41
  An RFC 5755 attribute-certificate parser joins the pki.schema family.
10
42
 
package/README.md CHANGED
@@ -195,13 +195,14 @@ is callable today; nothing below is a stub.
195
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` |
196
196
  | `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
197
  | `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 SignedData per RFC 5652 — encapsulated content, signer infos, raw signed-attribute bytes for external verification, certificates / CRLs kept raw, fail-closed; non-SignedData content types recognized (not decoded) — `parse`, `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
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
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
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
202
  | `pki.schema.engine` | The declarative ASN.1 structure-schema engine every format parser composes — `walk` plus the schema combinators |
203
+ | `pki.path` | RFC 5280 §6 certification-path validation — `validate` runs the §6.1 state machine (signature chaining, validity windows, name chaining, basic constraints and path length, key usage, name constraints, the certificate-policy tree) over an ordered path and a trust anchor, returning a structured verdict with per-check reason codes; `crlChecker` supplies CRL-based revocation. Pure and re-entrant, fail-closed — `validate`, `crlChecker` |
203
204
  | `pki.C` / `pki.constants` | Version-stable constants — functional scale helpers (`C.TIME.*`, `C.BYTES.*`), codec `LIMITS`, `version` |
204
- | `pki.errors` | The `PkiError` taxonomy — `defineClass` plus `ConstantsError` / `Asn1Error` / `OidError` / `PemError` / `CertificateError` / `CrlError` / `CsrError` / `Pkcs8Error` / `CmsError` / `OcspError` / `TspError`, each carrying a stable `code` in `domain/reason` form |
205
+ | `pki.errors` | The `PkiError` taxonomy — `defineClass` plus `ConstantsError` / `Asn1Error` / `OidError` / `PemError` / `CertificateError` / `CrlError` / `CsrError` / `Pkcs8Error` / `CmsError` / `OcspError` / `TspError` / `AttrCertError` / `PathError`, each carrying a stable `code` in `domain/reason` form |
205
206
  | `pki` CLI | `pki version`, `pki oid <dotted\|name>`, `pki parse <cert>` |
206
207
 
207
208
  ### CLI
package/index.js CHANGED
@@ -29,6 +29,7 @@ var asn1 = require("./lib/asn1-der");
29
29
  var oid = require("./lib/oid");
30
30
  var webcrypto = require("./lib/webcrypto");
31
31
  var schema = require("./lib/schema-all");
32
+ var path = require("./lib/path-validate");
32
33
 
33
34
  module.exports = {
34
35
  version: constants.version,
@@ -42,13 +43,20 @@ module.exports = {
42
43
  // `schema` is the family: the L2 structure-schema engine (schema.engine) and
43
44
  // the per-format parsers (schema.x509, …) with detect-and-route schema.parse.
44
45
  schema: schema,
45
- // A ready W3C Crypto instance (globalThis.crypto shape) + the classes
46
- // for constructing more. PQC-first, classical-capable, zero-dep.
47
- webcrypto: webcrypto.webcrypto,
48
- WebCrypto: {
49
- Crypto: webcrypto.Crypto,
50
- SubtleCrypto: webcrypto.SubtleCrypto,
51
- CryptoKey: webcrypto.CryptoKey,
52
- WebCryptoError: webcrypto.WebCryptoError,
53
- },
46
+ // `path` is RFC 5280 §6 certification-path validation pki.path.validate
47
+ // runs the §6.1 state machine over an already-parsed path + a trust anchor.
48
+ path: path,
49
+ // A ready W3C Crypto instance (globalThis.crypto shape) with the classes for
50
+ // constructing more attached under the same namespace (pki.webcrypto.CryptoKey,
51
+ // .SubtleCrypto, .Crypto, .WebCryptoError). PQC-first, classical-capable, zero-dep.
52
+ webcrypto: _webcryptoNamespace(),
54
53
  };
54
+
55
+ function _webcryptoNamespace() {
56
+ var wc = webcrypto.webcrypto; // the ready Crypto instance
57
+ wc.Crypto = webcrypto.Crypto;
58
+ wc.SubtleCrypto = webcrypto.SubtleCrypto;
59
+ wc.CryptoKey = webcrypto.CryptoKey;
60
+ wc.WebCryptoError = webcrypto.WebCryptoError;
61
+ return wc;
62
+ }
package/lib/asn1-der.js CHANGED
@@ -408,9 +408,10 @@ function readNullImplicit(node, tag) {
408
408
  }
409
409
 
410
410
  /**
411
- * @primitive pki.asn1.readOid
411
+ * @primitive pki.asn1.read.oid
412
412
  * @signature pki.asn1.read.oid(node) -> "1.2.840.113549.1.1.11"
413
- * @since 0.1.0
413
+ * @since 0.1.15
414
+ * @originated 0.1.0
414
415
  * @status stable
415
416
  * @spec X.690 §8.19
416
417
  * @related pki.oid.name
package/lib/constants.js CHANGED
@@ -117,6 +117,10 @@ var LIMITS = {
117
117
  PEM_MAX_BYTES: BYTES.mib(16),
118
118
  DER_MAX_INTEGER_BYTES: BYTES.kib(16),
119
119
  OID_MAX_SUBIDENTIFIER_BYTES: 32,
120
+ // Certification-path length ceiling: bounds the per-cert asymmetric verify
121
+ // work on an untrusted certificate bundle (a real chain is well under this;
122
+ // the operator may override via opts.maxPathCerts).
123
+ PATH_MAX_CERTS: 100,
120
124
  };
121
125
 
122
126
  // Single-sourced from the package manifest so the reported version can
@@ -155,6 +155,13 @@ var TspError = defineClass("TspError", { withCause: true });
155
155
  // underlying leaf fault as `.cause`.
156
156
  var AttrCertError = defineClass("AttrCertError", { withCause: true });
157
157
 
158
+ // PathError — a certification path that fails RFC 5280 §6 validation, or a
159
+ // malformed input handed to the validator (an extension value that does not
160
+ // decode, an empty path, an unsupported signature algorithm). Carries the
161
+ // per-check reason in `.code` (`path/*`) and the underlying leaf fault as
162
+ // `.cause`.
163
+ var PathError = defineClass("PathError", { withCause: true });
164
+
158
165
  module.exports = {
159
166
  PkiError: PkiError,
160
167
  defineClass: defineClass,
@@ -171,4 +178,5 @@ module.exports = {
171
178
  OcspError: OcspError,
172
179
  TspError: TspError,
173
180
  AttrCertError: AttrCertError,
181
+ PathError: PathError,
174
182
  };
package/lib/oid.js CHANGED
@@ -61,8 +61,12 @@ var FAMILIES = {
61
61
  cRLNumber: 20, reasonCode: 21, invalidityDate: 24, deltaCRLIndicator: 27,
62
62
  issuingDistributionPoint: 28, certificateIssuer: 29,
63
63
  nameConstraints: 30, cRLDistributionPoints: 31,
64
- certificatePolicies: 32, authorityKeyIdentifier: 35, extKeyUsage: 37,
65
- freshestCRL: 46,
64
+ certificatePolicies: 32, policyMappings: 33, authorityKeyIdentifier: 35,
65
+ policyConstraints: 36, extKeyUsage: 37, freshestCRL: 46,
66
+ inhibitAnyPolicy: 54,
67
+ // The two special-OID leaves under extension arcs (RFC 5280 §4.2.1.4 /
68
+ // §4.2.1.12): the certificate-policies wildcard and the EKU wildcard.
69
+ anyPolicy: [32, 0], anyExtendedKeyUsage: [37, 0],
66
70
  // RFC 5755 attribute-certificate extensions (§4.3.2, §4.3.6).
67
71
  targetInformation: 55, noRevAvail: 56 } },
68
72
 
@@ -106,7 +110,8 @@ var FAMILIES = {
106
110
 
107
111
  // PKCS#1 RSA public-key + RSASSA signature algorithms.
108
112
  rsa: { base: [1, 2, 840, 113549, 1, 1], of: {
109
- rsaEncryption: 1, rsassaPss: 10, sha256WithRSAEncryption: 11,
113
+ rsaEncryption: 1, rsaesOaep: 7, mgf1: 8, rsassaPss: 10,
114
+ sha256WithRSAEncryption: 11,
110
115
  sha384WithRSAEncryption: 12, sha512WithRSAEncryption: 13 } },
111
116
 
112
117
  // PKCS#7 / CMS content types (RFC 5652 §4, RFC 2315). id-signedData is the one