@blamejs/pki 0.5.26 → 0.5.28

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,31 @@ 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.5.28 — 2026-08-23
8
+
9
+ Importing the toolkit is now silent, and a key's WebCrypto algorithm can no longer change under a signature once the key has been created.
10
+
11
+ ### Changed
12
+
13
+ - `require("@blamejs/pki")` no longer prints Node experimental-feature warnings on load. The engine's typed-array scan reads each global's property descriptor and uses only a data value, so it never touches a host's lazy accessor (Node's `localStorage` and `WASI` emit an `ExperimentalWarning` the moment they are read). Recognition of the concrete typed-array kinds -- the reason for the scan -- is unchanged, since those are ordinary data properties.
14
+
15
+ ### Security
16
+
17
+ - A `CryptoKey`'s `algorithm` is immutable once the key is created: the property is non-writable so it cannot be replaced, and its value is frozen so its fields cannot be changed. This engine reads `key.algorithm.hash` at sign time, and a mutable algorithm let the hash checked against a JWS `alg` (an `RS256` header signs under SHA-256) be rewritten between that check and the signature -- by swapping the whole object or a field of it, including from a microtask during the signing await -- producing a JWS whose signature does not match the algorithm its header advertises. The frozen value is a copy, so a caller's own `importKey` parameters object is left untouched. A key adopted from another WebCrypto implementation is re-imported from its own algorithm; the keys this engine mints, which is what the enrollment builders sign with, carry the immutable one.
18
+
19
+ ## v0.5.27 — 2026-08-22
20
+
21
+ A mistyped option passed to pki.trust.anchor or the pki.acme.client constructor is now refused, naming it, instead of being read as absent and silently defaulted.
22
+
23
+ ### Changed
24
+
25
+ - `pki.trust.anchor(entry, opts)` rejects an option it does not recognize, naming it, instead of ignoring it and applying the default. The one option it reads is `purpose`.
26
+ - The `pki.acme.client(directoryUrl, opts)` constructor rejects an unrecognized option the same way. The options it reads are `accountKey`, `accountJwk`, `alg`, `transport`, `tls`, `timeout`, `maxResponseBytes`, `maxRedirects`, `maxNonceRetries`, `maxPolls`, `maxTotalWait`, `sleep`, and `clock`; each method the client returns validates its own per-call options separately.
27
+
28
+ ### Security
29
+
30
+ - An unrecognized option is a fail-closed error rather than a silent default. The prior behavior meant a security-relevant option -- a shorter redirect budget, a specific trust purpose -- that was misspelled took no effect and raised no error, so a caller could believe a stricter setting was in force when the default was.
31
+
7
32
  ## v0.5.26 — 2026-08-22
8
33
 
9
34
  Revoking a certificate and asking a certification authority what it offers now go through the same verified CMP transaction as enrollment.
package/lib/acme.js CHANGED
@@ -1947,8 +1947,17 @@ function _parseLinkAlternates(headers, base) {
1947
1947
  * var acct = await acme.newAccount({ termsOfServiceAgreed: true });
1948
1948
  * var ord = await acme.newOrder({ identifiers: [{ type: "dns", value: "example.org" }] });
1949
1949
  */
1950
+ // The options pki.acme.client reads at construction. A near-miss spelling (a plural, a sibling verb's
1951
+ // word, a dropped letter) must be refused with a message naming it, not read as undefined and silently
1952
+ // defaulted -- a mistyped `maxRedirects` or `transport` would otherwise change the client's behavior with
1953
+ // no error. The per-request options each returned method takes are checked in those methods, not here.
1954
+ var _CLIENT_OPTS = {
1955
+ accountKey: 1, accountJwk: 1, alg: 1, transport: 1, tls: 1, timeout: 1, maxResponseBytes: 1,
1956
+ maxRedirects: 1, maxNonceRetries: 1, maxPolls: 1, maxTotalWait: 1, sleep: 1, clock: 1,
1957
+ };
1950
1958
  function client(directoryUrl, opts) {
1951
- opts = opts || {};
1959
+ opts = guard.identifier.optionsObject(opts, E, "acme/bad-input", "pki.acme.client options");
1960
+ guard.identifier.assertKnownKeys(opts, _CLIENT_OPTS, E, "acme/bad-input", "unknown pki.acme.client option ");
1952
1961
  if (!opts.accountKey) throw E("acme/bad-input", "client requires opts.accountKey (the account private key)");
1953
1962
  if (!_isObject(opts.accountJwk)) throw E("acme/bad-input", "client requires opts.accountJwk (the account public JWK)");
1954
1963
  if (!_isString(opts.alg)) throw E("acme/bad-input", "client requires opts.alg (the account-key JWS alg)");
@@ -433,13 +433,17 @@ var _INTRINSIC_HOLDERS = (function () {
433
433
  // built over `%TypedArray%.prototype`, and a bag inheriting `BYTES_PER_ELEMENT` from such an
434
434
  // object then read as an intrinsic member rather than as the unknown option it was.
435
435
  //
436
- // Reading a global can run a getter, and a deprecated one can throw; a fault here would break
437
- // this module's load over a name it does not need.
436
+ // Read the DESCRIPTOR, never the value through the runtime. `globalThis[n]` invokes a getter, and a
437
+ // host may define one that warns on read -- Node exposes `localStorage` and `WASI` as lazy accessors
438
+ // that emit an ExperimentalWarning the moment they are touched, so a plain read here printed that
439
+ // warning on every import -- or one that throws. A typed-array constructor is a plain data property, so
440
+ // taking `d.value` finds every concrete kind without ever invoking an accessor, which also means such
441
+ // an accessor can neither warn nor fault this load over a name it does not need.
438
442
  _forEach(_getOwnPropertyNames(globalThis), function (n) {
439
- var C;
440
- try { C = globalThis[n]; }
441
- catch (_e) { /* allow:swallow-unverified this runs once at load over whatever globals the host defines, and no global in a supported runtime throws on read, so no test drives it; it is here so an embedder's throwing accessor cannot stop this module loading over a name it does not need */ return; }
442
- if (typeof C !== "function" || typeof C.BYTES_PER_ELEMENT !== "number") return;
443
+ var d = _getOwnPropertyDescriptor(globalThis, n);
444
+ if (!d || typeof d.value !== "function") return;
445
+ var C = d.value;
446
+ if (typeof C.BYTES_PER_ELEMENT !== "number") return;
443
447
  if (!C.prototype || _getPrototypeOf(C.prototype) !== _TYPED_ARRAY_PROTO) return;
444
448
  _mapSet(m, C.prototype, _CONCRETE_TYPED_MEMBERS);
445
449
  });
package/lib/trust.js CHANGED
@@ -629,6 +629,9 @@ function _csvDate(cell, label) {
629
629
  if (!m) throw E("trust/bad-csv", label + " must be a Y-M-D date, got " + JSON.stringify(s));
630
630
  var pad = function (x) { return x.length === 1 ? "0" + x : x; };
631
631
  var payload = Buffer.from(m[1] + pad(m[2]) + pad(m[3]) + "235959Z", "latin1");
632
+ // _strictTime tags every rejection (a rolled-over month/day among them) with the code passed here,
633
+ // so the re-throw carries it unchanged; the wrap normalizes any other failure to the same typed code
634
+ // so a bad row is always trust/bad-csv, never an untyped exception escaping parseCcadbCsv.
632
635
  try { return _strictTime(payload, "trust/bad-csv", label); }
633
636
  catch (e) {
634
637
  if (e && e.code === "trust/bad-csv") throw e;
@@ -774,11 +777,16 @@ function parseCcadbCsv(text) {
774
777
  * await pki.path.validate([pki.schema.x509.parse(der)],
775
778
  * { time: new Date("2026-06-01T00:00:00Z"), trustAnchor: anchor, checkPurpose: "serverAuth" });
776
779
  */
780
+ // `purpose` is the only option, and it gates the authorization itself, so a near-miss spelling must not
781
+ // pass silently: a caller who wrote the plural `purposes` (or carried a sibling's word) would otherwise get
782
+ // the anchor with NO purpose restriction rather than an error naming the option they meant.
783
+ var _ANCHOR_OPTS = { purpose: 1 };
777
784
  function anchor(entry, opts) {
778
785
  if (!entry || typeof entry !== "object") {
779
786
  throw E("trust/bad-input", "anchor expects a trust-store entry ({ name, publicKey, algorithm, ... })");
780
787
  }
781
- opts = opts || {};
788
+ opts = guard.identifier.optionsObject(opts, E, "trust/bad-input", "pki.trust.anchor options");
789
+ guard.identifier.assertKnownKeys(opts, _ANCHOR_OPTS, E, "trust/bad-input", "unknown pki.trust.anchor option ");
782
790
  // Where this entry came from a store, everything the anchor asserts is read from the record the
783
791
  // store minted, not from the entry -- the entry is a plain object the caller holds and can write
784
792
  // to. `purposes` is the authorization itself, so an entry whose serverAuth bit was flipped after
package/lib/webcrypto.js CHANGED
@@ -254,7 +254,20 @@ var SECRET_KEY_NAMES = { "AES-GCM": true, "AES-CBC": true, "AES-CTR": true, "AES
254
254
  function CryptoKey(type, extractable, algorithm, usages, handle) {
255
255
  this.type = type;
256
256
  this.extractable = !!extractable;
257
- this.algorithm = algorithm;
257
+ // The algorithm is immutable both ways. The PROPERTY is defined non-writable and non-configurable so
258
+ // it cannot be REPLACED wholesale (`key.algorithm = { hash: "SHA-512" }`), and its value is a frozen
259
+ // COPY -- a caller's importKey params object is left untouched -- so its fields, the nested `hash` an
260
+ // RSA/HMAC signature reads among them, cannot be MUTATED. This engine reads key.algorithm.hash live
261
+ // when it signs, and a W3C CryptoKey's [[algorithm]] is immutable for exactly this reason: neither
262
+ // swapping the object nor a field of it (e.g. from a microtask during a signing await) can make the
263
+ // hash the signature is computed under differ from the one checked against a header algorithm.
264
+ var frozenAlg = algorithm;
265
+ if (frozenAlg && typeof frozenAlg === "object") {
266
+ frozenAlg = _assign({}, algorithm);
267
+ if (frozenAlg.hash && typeof frozenAlg.hash === "object") frozenAlg.hash = intrinsic.freeze(_assign({}, frozenAlg.hash));
268
+ frozenAlg = intrinsic.freeze(frozenAlg);
269
+ }
270
+ _defineProperty(this, "algorithm", { value: frozenAlg, enumerable: true });
258
271
  // The copy is what the key will be asked about for the rest of its life, so the method that makes
259
272
  // it is the captured one: a replaced `slice` hands back a list the caller still controls. The
260
273
  // list-ness is checked rather than left to the copy to discover -- `Array.prototype.slice` on a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.26",
3
+ "version": "0.5.28",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:af65ad7a-ef35-4bc4-8aef-24a43cb8ece0",
5
+ "serialNumber": "urn:uuid:c0564356-6da6-47da-a75d-a05ee2125d32",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-23T00:05:35.940Z",
8
+ "timestamp": "2026-08-23T03:34:40.100Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/pki@0.5.26",
22
+ "bom-ref": "@blamejs/pki@0.5.28",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.26",
25
+ "version": "0.5.28",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
29
- "purl": "pkg:npm/%40blamejs/pki@0.5.26",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.28",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/pki@0.5.26",
57
+ "ref": "@blamejs/pki@0.5.28",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]