@blamejs/pki 0.6.38 → 0.6.39

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.6.39 — 2026-09-05
8
+
9
+ A verdict field is created with the verdict rather than written onto it afterward, so a value left on Object.prototype can no longer answer a question the verification never asked.
10
+
11
+ ### Added
12
+
13
+ - The pki.ocsp.verify and pki.path.verifyOcspResponse verdicts carry valid, true only when status is good. Every failure these verbs detect already reports status unknown, so the boolean covers exactly the checks they run.
14
+ - The pki.cmc.verify verdict carries valid, true only for the issued outcome. Every other outcome, pending included, reads as false, with outcome, failInfo and pendToken unchanged.
15
+
16
+ ### Changed
17
+
18
+ - The pki.ocsp.verify and pki.path.verifyOcspResponse verdicts report thisUpdate and nextUpdate as null, not undefined, when no response covered the certificate. Every path now reports both, so a reader gets the same shape whatever the verdict. Code testing them with === undefined should test == null.
19
+ - The container release workflow takes docker/setup-qemu-action v4.3.0.
20
+
21
+ ### Fixed
22
+
23
+ - A verdict field is now created with the verdict rather than assigned onto it afterward, so a value left on Object.prototype cannot answer a read the verification never wrote. Three verdicts could report an affirmative that had not been established: an OCSP response whose signature does not verify, or that no authorized responder signs, contributed a good status through pki.ocsp.verify, pki.path.verifyOcspResponse and pki.path.ocspChecker, so pki.path.validate accepted a certificate whose revocation status was never determined; pki.cms.verify reported a signer as trusted when it had been given no trust anchors to chain to; and pki.crmf.verifyPop reported verified for a request carrying no proof of possession at all. pki.cmc.verify, pki.smime.verify and the pki.cmp session results carried the same construction and are fixed with them.
24
+ - A verdict also ends the prototype lookup for then, so an accessor installed there cannot rewrite it between the verification and the caller.
25
+ - A pki.cmp session transcript no longer reports a protection result on a request leg. A request records no verdict, and the snapshot read the field without asking whether the entry carried one, so a value left on Object.prototype was copied in and read back as a result the session never reached.
26
+ - The tables that decide how a signature is verified answer from their own entries alone. pki.cms.verify looked its signature scheme up in a table that inherits from Object.prototype, so a message could name an algorithm the toolkit does not implement and have the verification proceed under a descriptor left there. The dispatch tables in pki.cms.verify, pki.path.validate and the composite-signature resolver are fixed.
27
+
28
+ ### Internal
29
+
30
+ - The test covering the implicit-rejection substitute content key chooses a ciphertext whose outcome it can predict. OpenSSL answers an RSA PKCS#1 v1.5 decode fault with a synthetic key whose length is drawn from the private key and the ciphertext, and a draw equal to the content-key length took the substitute out of the path, so the test failed about once in every few hundred runs.
31
+
7
32
  ## v0.6.38 — 2026-09-05
8
33
 
9
34
  A trust anchor can name the namespace its root is trusted for, and pki.path.validate enforces it as the initial name-constraint state.
package/lib/cmc-verify.js CHANGED
@@ -89,12 +89,13 @@ function _assertBound(body, sent) {
89
89
  if (Buffer.isBuffer(v) || v instanceof Uint8Array) return v.length > 0; // allow:byte-source-narrow -- sent.senderNonce / dataReturn are already normalized to a Buffer by _snapshotSent; this is a length test on a settled value, not an input door
90
90
  return true;
91
91
  }
92
- var bound = {
92
+ var bound = guard.verdict.of({
93
93
  transactionId: _carries(sent.transactionId),
94
94
  senderNonce: _carries(sent.senderNonce),
95
95
  dataReturn: _carries(sent.dataReturn),
96
96
  bodyPartIDs: Array.isArray(sent.bodyPartIDs),
97
- };
97
+ boundToRequest: false,
98
+ });
98
99
  if (sent.transactionId != null) {
99
100
  var txControl = _findControl(body.controls, OID_TRANSACTION_ID, "Transaction Identifier");
100
101
  if (!txControl) {
@@ -197,6 +198,10 @@ function _governingStatus(statuses, outcome) {
197
198
  * @defends cmc-response-replay (CWE-294)
198
199
  * @related pki.schema.cmc.parse, pki.path.validate, pki.cms.verify
199
200
  *
201
+ * The verdict carries `valid`, the toolkit-wide alias the other verify verbs use, true only for the
202
+ * `issued` outcome. Every other outcome, including `pending`, is `valid: false`, and which one it
203
+ * was stays in `outcome` alongside `failInfo` and `pendToken`.
204
+ *
200
205
  * Interpret a Full PKI Response into one terminal verdict. `response` is the DER or a PEM `CMS`
201
206
  * block; an already-parsed message is interpreted too, but only under `allowUnverified: true`,
202
207
  * because both carriers authenticate over BYTES and a parsed object the caller still owns pins
@@ -419,7 +424,8 @@ function _shape(body, sent, signatureVerified, bound) {
419
424
  var confirm = _findControl(body.controls, OID_CONFIRM_CERT_ACCEPTANCE, "Confirm Certificate Acceptance");
420
425
  var serverNonce = _findControl(body.controls, OID_SENDER_NONCE, "Sender Nonce");
421
426
 
422
- return {
427
+ return guard.verdict.of({
428
+ valid: outcome === "issued",
423
429
  outcome: outcome,
424
430
  statuses: body.statuses,
425
431
  failInfo: governing ? governing.failInfoName : null,
@@ -448,7 +454,7 @@ function _shape(body, sent, signatureVerified, bound) {
448
454
  controls: body.controls,
449
455
  unhandled: body.unhandled,
450
456
  cms: body.cms,
451
- };
457
+ });
452
458
  }
453
459
 
454
460
  /**
@@ -590,24 +590,23 @@ function session(opts) {
590
590
 
591
591
  function _recordTranscript(entry) {
592
592
  var len = Buffer.isBuffer(entry.bytes) ? entry.bytes.length : 0;
593
- if (transcriptBytes + len > transcriptCap) {
594
- entry.byteLength = len; entry.bytes = null; entry.truncated = true;
595
- } else {
596
- transcriptBytes += len;
597
- }
598
- transcript.push(entry);
593
+ var over = transcriptBytes + len > transcriptCap;
594
+ if (!over) transcriptBytes += len;
595
+ transcript.push(over
596
+ ? guard.verdict.of(entry, { byteLength: len, bytes: null, truncated: true })
597
+ : guard.verdict.of(entry));
599
598
  }
600
599
 
601
600
  function _transcriptSnapshot() {
602
601
  return transcript.map(function (e) {
603
- var c = Object.assign({}, e);
602
+ var c = guard.verdict.of(e);
604
603
  if (Buffer.isBuffer(c.bytes)) c.bytes = Buffer.from(c.bytes);
605
- if (c.verdict) c.verdict = Object.assign({}, c.verdict);
604
+ if (guard.verdict.carries(c, "verdict") && c.verdict) c.verdict = guard.verdict.of(c.verdict);
606
605
  return c;
607
606
  });
608
607
  }
609
608
  function _terminal(outcome, extra) {
610
- return Object.assign({
609
+ return guard.verdict.of({
611
610
  outcome: outcome, certificate: null, chain: [], status: null, trusted: true,
612
611
  confirmed: false, implicitConfirm: false, transactionID: Buffer.from(transactionID),
613
612
  polls: 0, transcript: _transcriptSnapshot(),
package/lib/cms-verify.js CHANGED
@@ -51,22 +51,29 @@ var pkix = require("./schema-pkix");
51
51
  var CmsError = frameworkError.CmsError;
52
52
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
53
53
  var _Promise = Promise, _promiseResolveRaw = Promise.resolve;
54
- function _pResolve(v) { return intrinsic.apply(_promiseResolveRaw, _Promise, [v]); }
54
+ /**
55
+ * @internal Every value resolved here is a signer row, and resolving a promise reads `then` off the
56
+ * value. Building the row through the verdict guard ends that lookup on the row itself, so an
57
+ * accessor installed while the signature check is pending cannot rewrite the `ok` the message
58
+ * verdict is derived from before it is read.
59
+ */
60
+ function _pResolve(v) { return intrinsic.apply(_promiseResolveRaw, _Promise, [_row(v)]); }
61
+ function _row(o) { return guard.verdict.of(o); }
55
62
 
56
63
  var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
57
64
  var OID_CONTENT_TYPE = oid.byName("contentType");
58
65
  var OID_COUNTERSIGNATURE = oid.byName("countersignature");
59
66
 
60
- var DIGEST_HASH = {
67
+ var DIGEST_HASH = intrinsic.assign(intrinsic.create(null), {
61
68
  sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512",
62
69
  shake128: "SHAKE128", shake256: "SHAKE256",
63
- };
64
- var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
70
+ });
71
+ var SIG_HASH = intrinsic.assign(intrinsic.create(null), { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" });
65
72
  function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
66
73
  async function _computeDigest(name, content) {
67
74
  return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
68
75
  }
69
- var SIG_SCHEME = {
76
+ var SIG_SCHEME = intrinsic.assign(intrinsic.create(null), {
70
77
  rsaEncryption: { kind: "rsa", params: "null" },
71
78
  rsassaPss: { kind: "rsapss" },
72
79
  ecPublicKey: { kind: "ec", params: "absent" },
@@ -83,13 +90,13 @@ var SIG_SCHEME = {
83
90
  ecdsaWithSHA256: { kind: "ec", hash: "SHA-256", params: "absent" },
84
91
  ecdsaWithSHA384: { kind: "ec", hash: "SHA-384", params: "absent" },
85
92
  ecdsaWithSHA512: { kind: "ec", hash: "SHA-512", params: "absent" },
86
- };
93
+ });
87
94
  ["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
88
95
  "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
89
96
  ].forEach(function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + s.toUpperCase(), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
90
97
  function _isDerNull(p) { return Buffer.isBuffer(p) && p.length === 2 && p[0] === 0x05 && p[1] === 0x00; }
91
98
  function _algParamsOk(shape, p) { return shape === "null" ? _isDerNull(p) : (p === null || p === undefined); }
92
- var EC_CURVE = {};
99
+ var EC_CURVE = intrinsic.create(null);
93
100
  EC_CURVE[oid.byName("prime256v1")] = { curve: "P-256", coordLen: 32 };
94
101
  EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
95
102
  EC_CURVE[oid.byName("secp521r1")] = { curve: "P-521", coordLen: 66 };
@@ -122,13 +129,13 @@ async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes,
122
129
  }
123
130
  try {
124
131
  var ok = await _verifySignature(scheme, sigHash, sigBytes, c.cert.subjectPublicKeyInfo.bytes, signedBytes, _certCurveOid(c.cert), pssSalt);
125
- if (ok === true) return { ok: true, sid: sid, cert: c.der };
132
+ if (ok === true) return _row({ ok: true, sid: sid, cert: c.der });
126
133
  } catch (e) {
127
134
  lastErr = (e instanceof CmsError) ? e : _err("cms/verify-error", "the SignerInfo signature could not be evaluated", e);
128
135
  }
129
136
  }
130
- return lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
131
- : { ok: false, sid: sid, cert: candidates[0].der };
137
+ return _row(lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
138
+ : { ok: false, sid: sid, cert: candidates[0].der });
132
139
  }
133
140
 
134
141
  function _certSki(cert) {
@@ -262,7 +269,7 @@ async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, dige
262
269
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
263
270
 
264
271
  var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
265
- if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
272
+ if (signedBytes && signedBytes.mismatch) return _row({ ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message });
266
273
  return _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, si.sid, signers, pss ? pss.saltLength : 0, scheme.sameKeyOid ? oid.byName(si.signatureAlgorithm.name) : null);
267
274
  }
268
275
 
@@ -347,7 +354,7 @@ async function _verifyComposite(si, comp, content, eContentType, parsedCerts, cs
347
354
  if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
348
355
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
349
356
  var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
350
- if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
357
+ if (signedBytes && signedBytes.mismatch) return _row({ ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message });
351
358
  return _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si.sid, signers, si.signatureAlgorithm.oid);
352
359
  }
353
360
 
@@ -360,11 +367,11 @@ async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si
360
367
  continue;
361
368
  }
362
369
  var r = await compositeSig.compositeVerify(c.cert.subjectPublicKeyInfo.bytes, sigBytes, signedBytes, comp, CmsError, "cms/unsupported-algorithm", "cms/bad-signature");
363
- if (r.ok === true) return { ok: true, sid: sid, cert: c.der };
370
+ if (r.ok === true) return _row({ ok: true, sid: sid, cert: c.der });
364
371
  if (r.code) lastErr = (r.error instanceof CmsError) ? r.error : _err(r.code, r.error && r.error.message ? r.error.message : "the composite signature could not be evaluated");
365
372
  }
366
- return lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
367
- : { ok: false, sid: sid, cert: candidates[0].der };
373
+ return _row(lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
374
+ : { ok: false, sid: sid, cert: candidates[0].der });
368
375
  }
369
376
 
370
377
  function _surfaceUnsignedAttrs(si) {
@@ -394,14 +401,13 @@ async function _verifyCountersignatures(si, parsedCerts) {
394
401
  async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
395
402
  var csSi;
396
403
  try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
397
- catch (e) { return { ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }; }
404
+ catch (e) { return _row({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
398
405
  var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig);
399
406
  var nested = await _verifyCountersignatures(csSi, parsedCerts);
400
- var node = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
401
- if (verdict.code) node.code = verdict.code;
402
- node.unsignedAttrs = _surfaceUnsignedAttrs(csSi);
403
- node.countersignatures = nested;
404
- return node;
407
+ var head = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
408
+ return guard.verdict.of(
409
+ verdict.code ? guard.verdict.of(head, { code: verdict.code }) : head,
410
+ { unsignedAttrs: _surfaceUnsignedAttrs(csSi), countersignatures: nested });
405
411
  }
406
412
 
407
413
  /**
@@ -539,19 +545,23 @@ async function _verify(input, opts) {
539
545
  for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
540
546
  var siOne = parsed.signerInfos[_sp];
541
547
  var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName);
542
- verdict.countersignatures = await _verifyCountersignatures(siOne, parsedCerts);
543
- verdict.unsignedAttrs = _surfaceUnsignedAttrs(siOne);
544
- verdict.signedAttributesPresent = !!siOne.signedAttrsBytes;
545
- verdict.signedAttributes = _surfaceSignedAttrs(siOne);
546
- verdict.digestAlgorithm = siOne.digestAlgorithm.name;
547
- intrinsic.push(signers, verdict);
548
+ var counters = await _verifyCountersignatures(siOne, parsedCerts);
549
+ intrinsic.push(signers, guard.verdict.of(verdict, {
550
+ countersignatures: counters,
551
+ unsignedAttrs: _surfaceUnsignedAttrs(siOne),
552
+ signedAttributesPresent: !!siOne.signedAttrsBytes,
553
+ signedAttributes: _surfaceSignedAttrs(siOne),
554
+ digestAlgorithm: siOne.digestAlgorithm.name,
555
+ trusted: false,
556
+ }));
548
557
  }
549
- var res = {
558
+ var res = guard.verdict.of({
550
559
  valid: signers.length > 0 && guard.list.allMatch(signers, function (s) { return s.ok === true; }),
551
560
  eContentType: parsed.encapContentInfo.eContentType,
552
561
  eContent: parsed.encapContentInfo.eContent != null ? parsed.encapContentInfo.eContent : null,
553
562
  signers: signers,
554
- };
563
+ trusted: false,
564
+ });
555
565
  await _applyTrust(res, parsedCerts, trustCfg);
556
566
  return res;
557
567
  }
@@ -24,7 +24,7 @@ var MLDSA_COMPONENT = {
24
24
  };
25
25
  var EC_CURVE_OID = { "P-256": "prime256v1", "P-384": "secp384r1", "P-521": "secp521r1" };
26
26
  var PH_CMS = { "SHA-256": "sha256", "SHA-512": "sha512", "SHAKE256": "shake256" };
27
- var COMPOSITE_ALGS = {};
27
+ var COMPOSITE_ALGS = Object.create(null);
28
28
  function _comp(name, mldsa, ph, label, trad) {
29
29
  var sz = MLDSA_COMPONENT[mldsa];
30
30
  COMPOSITE_ALGS[oid.byName(name)] = {
package/lib/crmf-sign.js CHANGED
@@ -478,7 +478,10 @@ function _verifyOne(msg) {
478
478
  subjectBound: false,
479
479
  publicKey: (t && t.publicKey && t.publicKey.bytes) || null,
480
480
  };
481
- function settle(extra) { var v = Object.assign({}, base, extra); v.valid = v.verified === true; return Promise.resolve(v); }
481
+ function settle(extra) {
482
+ var v = guard.verdict.of(base, extra);
483
+ return Promise.resolve(guard.verdict.of(v, { valid: v.verified === true }));
484
+ }
482
485
 
483
486
  var popo = msg.popo;
484
487
  if (popo == null) {
@@ -499,7 +502,7 @@ function _verifyOne(msg) {
499
502
  var spki = pin ? pin.publicKey : base.publicKey;
500
503
  var bound = !pin;
501
504
  return crmfVerify.verifyPopSignature(popo, spki, preimage).then(function (ok) {
502
- return Object.assign({}, base, {
505
+ return guard.verdict.of(base, {
503
506
  valid: ok === true, verified: ok === true, method: "signature", cryptographicallyVerified: ok === true,
504
507
  subject: bound ? base.subject : null,
505
508
  subjectBound: bound,
@@ -558,7 +561,7 @@ function _verifyPop(messages) {
558
561
  var parsed = _coerceMessages(messages);
559
562
  return Promise.all(parsed.messages.map(_verifyOne)).then(function (out) {
560
563
  var allVerified = out.length > 0 && out.every(function (m) { return m.verified === true; });
561
- return { valid: allVerified, verified: allVerified, messages: out };
564
+ return guard.verdict.of({ valid: allVerified, verified: allVerified, messages: out });
562
565
  });
563
566
  }
564
567
 
package/lib/guard-all.js CHANGED
@@ -20,6 +20,7 @@ var header = require("./guard-header");
20
20
  var compress = require("./guard-compress");
21
21
  var secret = require("./guard-secret");
22
22
  var parsed = require("./guard-parsed");
23
+ var verdict = require("./guard-verdict");
23
24
  var async_ = require("./guard-async");
24
25
 
25
26
  module.exports = _freeze({
@@ -39,5 +40,6 @@ module.exports = _freeze({
39
40
  compress: compress,
40
41
  secret: secret,
41
42
  parsed: parsed,
43
+ verdict: verdict,
42
44
  async: async_,
43
45
  });
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ // @internal
5
+
6
+ var _freeze = require("./guard-intrinsic").freeze;
7
+ var _keys = require("./guard-intrinsic").keys;
8
+ var _defineProperty = require("./guard-intrinsic").defineProperty;
9
+ var _getOwnPropertyDescriptor = require("./guard-intrinsic").getOwnPropertyDescriptor;
10
+ var _hasOwn = require("./guard-intrinsic").hasOwn;
11
+ var _Object = require("./guard-intrinsic").Object;
12
+
13
+ function _copyOwn(out, src) {
14
+ var keys = _keys(src);
15
+ for (var i = 0; i < keys.length; i++) {
16
+ var d = _getOwnPropertyDescriptor(src, keys[i]);
17
+ if (!d) continue;
18
+ var value = _hasOwn(d, "value") ? d.value : src[keys[i]];
19
+ _defineProperty(out, keys[i], { value: value, enumerable: true, configurable: true, writable: true });
20
+ }
21
+ }
22
+
23
+ // @enforced-by behavioral -- the defect is the ABSENCE of this call at a construction site, not a
24
+ function of(src, extras) {
25
+ var out = {};
26
+ _copyOwn(out, src);
27
+ if (extras != null) _copyOwn(out, extras);
28
+ /** @internal Resolving a promise reads `then` off the value, so an inherited accessor would run
29
+ * with the verdict as its receiver and could rewrite a field or hand the caller a different
30
+ * object. An own `then` ends that lookup, and is non-enumerable so keys, JSON, a spread and a
31
+ * deep-equality comparison do not see it. */
32
+ _defineProperty(out, "then", { value: undefined, enumerable: false, configurable: true, writable: true });
33
+ return out;
34
+ }
35
+
36
+ // @enforced-by behavioral -- Object.hasOwn and hasOwnProperty are replaceable at any time, and a
37
+ function carries(obj, key) {
38
+ return obj != null && _hasOwn(_Object(obj), key);
39
+ }
40
+
41
+ module.exports = _freeze({ of: of, carries: carries });
@@ -121,19 +121,33 @@ function makeOcspVerify(deps) {
121
121
 
122
122
  function isOctetAligned(bitString) { return !!bitString && bitString.unusedBits === 0; }
123
123
 
124
+ /**
125
+ * @internal Every field the callers decide on is present on every return, so a field an early
126
+ * return would have left absent is read from the response that was evaluated rather than from
127
+ * whatever a polluted Object.prototype answers: a rejected response would otherwise be able to
128
+ * report `sawGood`, and a certificate with no authoritative response would read as good.
129
+ */
130
+ function _evaluation(extras) {
131
+ return guard.verdict.of({
132
+ applicable: false, matched: false, responderAuthorized: false, signatureValid: false,
133
+ revoked: null, sawGood: false, sawUnknownStatus: false,
134
+ thisUpdate: null, nextUpdate: null, reason: "",
135
+ }, extras);
136
+ }
137
+
124
138
  async function evaluateResponse(resp, cert, issuer, issuerKeyBits, issuerNameCandidates, time, historical) {
125
- if (resp.responseStatus.code !== 0) return { applicable: false, matched: false, reason: "non-successful OCSP responseStatus (" + resp.responseStatus.code + ")" };
139
+ if (resp.responseStatus.code !== 0) return _evaluation({ reason: "non-successful OCSP responseStatus (" + resp.responseStatus.code + ")" });
126
140
  var br = resp.basicResponse;
127
- if (!br) return { applicable: false, matched: false, reason: "successful OCSP response carries no BasicOCSPResponse" };
141
+ if (!br) return _evaluation({ reason: "successful OCSP response carries no BasicOCSPResponse" });
128
142
  var signerSpki = await ocspAuthorizeResponder(br, cert, issuer, issuerKeyBits, time);
129
- if (!signerSpki) return { applicable: true, matched: false, responderAuthorized: false, reason: "no authorized OCSP responder signs this response (RFC 6960 sec. 4.2.2.2)" };
143
+ if (!signerSpki) return _evaluation({ applicable: true, reason: "no authorized OCSP responder signs this response (RFC 6960 sec. 4.2.2.2)" });
130
144
  if (!(await verifyWithSpki(br.signatureAlgorithm, br.signature, signerSpki, br.tbsResponseDataBytes))) {
131
- return { applicable: true, matched: false, responderAuthorized: true, signatureValid: false, reason: "the OCSP response signature does not verify over tbsResponseData" };
145
+ return _evaluation({ applicable: true, responderAuthorized: true, reason: "the OCSP response signature does not verify over tbsResponseData" });
132
146
  }
133
147
  if (ocspHasCriticalExtension(br.responseExtensions)) {
134
- return { applicable: true, matched: false, responderAuthorized: true, signatureValid: true, reason: "the OCSP response carries an unrecognized critical extension" };
148
+ return _evaluation({ applicable: true, responderAuthorized: true, signatureValid: true, reason: "the OCSP response carries an unrecognized critical extension" });
135
149
  }
136
- var out = { applicable: true, matched: false, responderAuthorized: true, signatureValid: true, revoked: null, sawGood: false, sawUnknownStatus: false, reason: "no current SingleResponse names this certificate" };
150
+ var out = _evaluation({ applicable: true, responderAuthorized: true, signatureValid: true, reason: "no current SingleResponse names this certificate" });
137
151
  for (var s = 0; s < br.responses.length; s++) {
138
152
  var sr = br.responses[s];
139
153
  if (!(await ocspCertIdMatches(sr.certID, cert, issuerNameCandidates, issuerKeyBits))) continue;
package/lib/ocsp.js CHANGED
@@ -374,7 +374,7 @@ function buildErrorResponse(status) {
374
374
 
375
375
  /**
376
376
  * @primitive pki.ocsp.verify
377
- * @signature pki.ocsp.verify(response, opts) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, revocationTime?, nonceMatched?, reason }>
377
+ * @signature pki.ocsp.verify(response, opts) -> Promise<{ valid, status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, revocationTime?, nonceMatched?, reason }>
378
378
  * @since 0.2.22
379
379
  * @status stable
380
380
  * @spec RFC 6960, RFC 9654, RFC 5019
@@ -389,6 +389,11 @@ function buildErrorResponse(status) {
389
389
  * unauthorized, stale, or CertID-mismatched response is a `"unknown"` verdict (never a silent
390
390
  * accept); a malformed response's parse fault surfaces as the parser's `ocsp/*` / `asn1/*`.
391
391
  *
392
+ * `valid` is the toolkit-wide verdict alias the other verify verbs carry, and it is true only when
393
+ * `status` is `"good"`. Because every failure this verb detects already collapses to `"unknown"`,
394
+ * `valid` states exactly what `status === "good"` does and hides no unrun check. A revoked
395
+ * certificate is `valid: false`; which of the two it was stays in `status`.
396
+ *
392
397
  * The request-nonce check is reported, and downgrades `good` alone. Every verdict carries
393
398
  * `nonceMatched` (true / false / null when the client sent no nonce). An unmatched nonce turns a
394
399
  * `good` into `"unknown"`, because a response that is not an answer to this request cannot be relied
@@ -437,16 +442,20 @@ function verify(response, opts) {
437
442
  time = opts.time == null ? new Date() : _asDate(opts.time);
438
443
  } catch (e) { return Promise.reject(e); }
439
444
  return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time, { historicalMode: opts.historicalMode === true }).then(function (verdict) {
440
- if (opts.requestNonce == null) return Object.assign({}, verdict, { nonceMatched: null });
441
- var respNonce = _responseNonce(parsed);
442
- var reqNonce = guard.bytes.isByteSource(opts.requestNonce)
443
- ? guard.bytes.snapshotSource(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
444
- var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
445
- var out = Object.assign({}, verdict, { nonceMatched: matched });
446
- if (!matched && verdict.status === "good") {
447
- return Object.assign(out, { status: "unknown", reason: "the OCSP response nonce does not echo the request nonce (RFC 9654)" });
445
+ var out;
446
+ if (opts.requestNonce == null) {
447
+ out = guard.verdict.of(verdict, { nonceMatched: null });
448
+ } else {
449
+ var respNonce = _responseNonce(parsed);
450
+ var reqNonce = guard.bytes.isByteSource(opts.requestNonce)
451
+ ? guard.bytes.snapshotSource(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
452
+ var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
453
+ out = guard.verdict.of(verdict, { nonceMatched: matched });
454
+ if (!matched && out.status === "good") {
455
+ out = guard.verdict.of(out, { status: "unknown", reason: "the OCSP response nonce does not echo the request nonce (RFC 9654)" });
456
+ }
448
457
  }
449
- return out;
458
+ return guard.verdict.of(out, { valid: out.status === "good" });
450
459
  });
451
460
  }
452
461
  function _responseNonce(parsed) {
@@ -462,7 +471,7 @@ function _requestFromBytes(request) {
462
471
  return ocspSchema.parseRequest(request);
463
472
  }
464
473
  function _reqVerdict(parsed, extra) {
465
- return {
474
+ return guard.verdict.of({
466
475
  valid: extra.signed === true && extra.signatureValid === true,
467
476
  signed: extra.signed, signatureValid: extra.signatureValid,
468
477
  signerCert: extra.signerCert != null ? extra.signerCert : null,
@@ -472,7 +481,7 @@ function _reqVerdict(parsed, extra) {
472
481
  requestorName: parsed.requestorName, requestList: parsed.requestList,
473
482
  requestExtensions: parsed.requestExtensions, version: parsed.version,
474
483
  reason: extra.reason != null ? extra.reason : null,
475
- };
484
+ });
476
485
  }
477
486
  async function _findRequestSigners(candidates, strict, parsed, sig, sigBits) {
478
487
  var parsedList = [];
@@ -108,7 +108,7 @@ var PROCESSED_EXTENSIONS = {};
108
108
  Object.freeze(PROCESSED_EXTENSIONS);
109
109
 
110
110
 
111
- var SIG_ALGS = {};
111
+ var SIG_ALGS = intrinsic.create(null);
112
112
  function _sig(name, verify, imp, params, ecdsa, sameKeyOid) {
113
113
  var entry = { verify: verify, imp: imp, params: params };
114
114
  if (ecdsa) entry.ecdsa = true;
@@ -1770,7 +1770,7 @@ function ocspChecker(responses) {
1770
1770
 
1771
1771
  /**
1772
1772
  * @primitive pki.path.verifyOcspResponse
1773
- * @signature pki.path.verifyOcspResponse(response, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, revocationTime?, reason }>
1773
+ * @signature pki.path.verifyOcspResponse(response, cert, issuerCert, time, opts?) -> Promise<{ valid, status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, revocationTime?, reason }>
1774
1774
  * @since 0.2.22
1775
1775
  * @status stable
1776
1776
  * @spec RFC 6960
@@ -1826,14 +1826,16 @@ function ocspChecker(responses) {
1826
1826
  * var v = await pki.path.verifyOcspResponse(der, cert, issuerCert, new Date());
1827
1827
  * v.status; // "good" | "revoked" | "unknown"
1828
1828
  */
1829
- function verifyOcspResponse(response, cert, issuerCert, time, opts) {
1829
+ async function verifyOcspResponse(response, cert, issuerCert, time, opts) {
1830
1830
  var parsedResponse, subject, issuer;
1831
1831
  try {
1832
1832
  parsedResponse = _ocspFromBytes(response);
1833
1833
  subject = coerceCert(cert, "the certificate");
1834
1834
  issuer = coerceCert(issuerCert, "the issuer certificate");
1835
1835
  } catch (e) { return Promise.reject(e); }
1836
- return _verifyOcspParsed(parsedResponse, subject, issuer, time, opts);
1836
+ var v = await _verifyOcspParsed(parsedResponse, subject, issuer, time, opts);
1837
+ var out = guard.verdict.of(v);
1838
+ return guard.verdict.of(out, { valid: out.status === "good" });
1837
1839
  }
1838
1840
 
1839
1841
  var _OCSP_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
@@ -1852,7 +1854,7 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
1852
1854
  if (!guard.time.isDate(time) || isNaN(guard.time.instantOf(time))) {
1853
1855
  return Promise.reject(E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
1854
1856
  }
1855
- function unbound(reason) { return { status: "unknown", responderAuthorized: false, signatureValid: false, matched: false, reason: reason }; }
1857
+ function unbound(reason) { return guard.verdict.of({ status: "unknown", responderAuthorized: false, signatureValid: false, matched: false, thisUpdate: null, nextUpdate: null, reason: reason }); }
1856
1858
  var boundName;
1857
1859
  try { boundName = dnEqual(cert.issuer.rdns, issuerCert.subject.rdns); }
1858
1860
  catch (e) { return Promise.reject(e); }
@@ -1869,9 +1871,9 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
1869
1871
  try { issuerKeyBits = ocspVerify.ocspKeyValue(issuerCert.subjectPublicKeyInfo.bytes); }
1870
1872
  catch (_e) { return unbound("the issuer public key could not be read to recompute the OCSP CertID"); }
1871
1873
  return ocspCore.evaluateResponse(parsedResponse, cert, issuerCtx, issuerKeyBits, issuerNameCandidates, time, opts.historicalMode === true).then(function (v) {
1872
- if (v.revoked) return { status: "revoked", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason };
1873
- if (v.sawGood) return { status: "good", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: "good" };
1874
- return { status: "unknown", responderAuthorized: v.responderAuthorized === true, signatureValid: v.signatureValid === true, matched: v.matched === true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: v.reason };
1874
+ if (v.revoked) return guard.verdict.of({ status: "revoked", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason });
1875
+ if (v.sawGood) return guard.verdict.of({ status: "good", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: "good" });
1876
+ return guard.verdict.of({ status: "unknown", responderAuthorized: v.responderAuthorized === true, signatureValid: v.signatureValid === true, matched: v.matched === true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: v.reason });
1875
1877
  });
1876
1878
  });
1877
1879
  }
package/lib/smime.js CHANGED
@@ -630,7 +630,7 @@ async function verify(message, opts) {
630
630
  var inner;
631
631
  try { inner = _toBuf(schemaCms.parse(p7m).encapContentInfo.eContent); }
632
632
  catch (e) { throw _err("smime/bad-mime", "the pkcs7-mime SignedData has no encapsulated content", e); }
633
- return Object.assign({ valid: res.valid, trusted: res.trusted, signers: res.signers, form: "pkcs7-mime", content: inner, micalg: null, sender: _senderSurface(res.signers, ent, opts.expectedSender) }, _hpSurface(inner, ent, "clear", res.valid, opts.legacyHeaderProtection === true));
633
+ return guard.verdict.of({ valid: res.valid, trusted: res.trusted, signers: res.signers, form: "pkcs7-mime", content: inner, micalg: null, sender: _senderSurface(res.signers, ent, opts.expectedSender) }, _hpSurface(inner, ent, "clear", res.valid, opts.legacyHeaderProtection === true));
634
634
  }
635
635
  if (ct.type === "multipart/signed") {
636
636
  if (ct.params.protocol && !_isPkcs7(ct.params.protocol, "signature")) throw _err("smime/bad-multipart", "multipart/signed protocol must be application/pkcs7-signature");
@@ -651,7 +651,7 @@ async function verify(message, opts) {
651
651
  if (opts.strictMicalg && micalg && _micalgSet(micalg) !== (_micalgOf(p7s) || "")) {
652
652
  throw _err("smime/micalg-mismatch", "the multipart/signed micalg " + JSON.stringify(micalg) + " disagrees with the SignerInfo digests");
653
653
  }
654
- return Object.assign({ valid: res2.valid, trusted: res2.trusted, signers: res2.signers, form: "multipart/signed", content: parts[0], micalg: micalg, sender: _senderSurface(res2.signers, ent, opts.expectedSender) }, _hpSurface(parts[0], ent, "clear", res2.valid, opts.legacyHeaderProtection === true));
654
+ return guard.verdict.of({ valid: res2.valid, trusted: res2.trusted, signers: res2.signers, form: "multipart/signed", content: parts[0], micalg: micalg, sender: _senderSurface(res2.signers, ent, opts.expectedSender) }, _hpSurface(parts[0], ent, "clear", res2.valid, opts.legacyHeaderProtection === true));
655
655
  }
656
656
  throw _err("smime/unsupported-type", "not a signed S/MIME message (Content-Type " + JSON.stringify(ct.type) + ")");
657
657
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.6.38",
3
+ "version": "0.6.39",
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:adf5e979-6804-475f-a0d0-8aef04be0317",
5
+ "serialNumber": "urn:uuid:b1c86284-d109-4852-8e85-dc5992b74178",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-09-05T07:57:13.454Z",
8
+ "timestamp": "2026-09-05T11:25:26.514Z",
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.6.38",
22
+ "bom-ref": "@blamejs/pki@0.6.39",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.6.38",
25
+ "version": "0.6.39",
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.6.38",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.6.39",
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.6.38",
57
+ "ref": "@blamejs/pki@0.6.39",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]