@blamejs/pki 0.6.37 → 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 +42 -0
- package/lib/cmc-verify.js +10 -4
- package/lib/cmp-session.js +8 -9
- package/lib/cms-verify.js +39 -29
- package/lib/composite-sig.js +1 -1
- package/lib/crmf-sign.js +6 -3
- package/lib/guard-all.js +2 -0
- package/lib/guard-verdict.js +41 -0
- package/lib/ocsp-verify.js +20 -6
- package/lib/ocsp.js +21 -12
- package/lib/path-validate.js +317 -31
- package/lib/smime.js +2 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,48 @@ 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
|
+
|
|
32
|
+
## v0.6.38 — 2026-09-05
|
|
33
|
+
|
|
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.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- A trust anchor passed to pki.path.validate or pki.path.build may carry nameConstraints: { permitted, excluded }, each an array of { tag, base } subtrees. They seed the RFC 5280 section 6.1.1(h)(i) initial permitted and excluded state, so a root a program trusts only for a namespace enforces that namespace even though its certificate states no nameConstraints extension. The anchor's subtrees intersect with an intermediate's own extension and with opts.initialPermittedSubtrees; excluded subtrees are a union.
|
|
39
|
+
- The pki.path.validate verdict reports anchorConstraints.nameConstraintsApplied, so a stored verdict distinguishes an anchor that named a namespace from one that did not, alongside the distrustAfterApplied and purposeTrustApplied it already carries.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- pki.path.validate and pki.path.build copy a directoryName subtree base and snapshot an iPAddress one before validating, and read opts.initialPermittedSubtrees and opts.initialExcludedSubtrees once rather than again for each trust anchor or candidate path tried. A caller mutating its own subtree list, entry or base while the path is still verifying signatures no longer changes the namespace the validation enforces; the subtrees that bind are the ones read at entry.
|
|
44
|
+
- A directoryName subtree base whose rdns is an accessor rather than a data property is refused. Its value was read once to check its shape and again to copy it, so an accessor could answer with a restrictive name the first time and an empty one the second, and an empty directoryName constraint matches every name.
|
|
45
|
+
- A subtree list that is a Proxy, that is sparse, or whose entries are accessors is refused, on a trust anchor and on opts.initialPermittedSubtrees and opts.initialExcludedSubtrees alike. Such a list answers its own length and index reads, so it could hold a restrictive subtree while reporting none and leave the namespace unenforced. A directoryName subtree is built from property descriptors alone, so nothing the caller supplies runs while the namespace is read: an accessor, own or inherited, is refused rather than invoked. It is a relative-name sequence of attribute lists and nothing deeper, so an array nested past that, including one that refers to itself, is a typed refusal, as is a hole, an entry the array does not own, an attribute that is not an object, or one lacking its own type and value. Each of those refusals is a configuration error reported wherever the anchor sits among several, rather than a name mismatch a different anchor could mask.
|
|
46
|
+
- A parsed certificate passed as a trust anchor is refused if it carries nameConstraints, the way one carrying purposes or distrustAfter already is. A certificate anchor cannot hold that metadata, so accepting it would have dropped the namespace and validated a leaf the anchor was meant to exclude.
|
|
47
|
+
- An anchor whose nameConstraints names a field other than permitted or excluded is refused rather than read as unconstrained, and its subtrees are checked while the anchor is normalized, so a malformed one is refused wherever that anchor sits among several rather than only once it is reached.
|
|
48
|
+
|
|
7
49
|
## v0.6.37 — 2026-09-05
|
|
8
50
|
|
|
9
51
|
A natively signed C509 certificate is refused when it writes an extension in the generic OID form that the registry can carry as an identifier.
|
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
|
/**
|
package/lib/cmp-session.js
CHANGED
|
@@ -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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
543
|
-
verdict.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
}
|
package/lib/composite-sig.js
CHANGED
|
@@ -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) {
|
|
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
|
|
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 });
|
package/lib/ocsp-verify.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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 = [];
|
package/lib/path-validate.js
CHANGED
|
@@ -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;
|
|
@@ -553,19 +553,61 @@ function isSubtreeBaseValid(tag, base) {
|
|
|
553
553
|
|
|
554
554
|
function checkedSubtreeSeeds(list, optName) {
|
|
555
555
|
if (list === undefined || list === null) return [];
|
|
556
|
-
if (!intrinsic.isArray(list)) throw E("path/bad-input", "validate: opts." + optName + " must be
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
556
|
+
if (!intrinsic.isArray(list) || intrinsic.types.isProxy(list)) throw E("path/bad-input", "validate: opts." + optName + " must be a plain array of { tag, base } subtree entries");
|
|
557
|
+
for (var li = 0; li < list.length; li++) {
|
|
558
|
+
var ld = intrinsic.getOwnPropertyDescriptor(list, li);
|
|
559
|
+
if (ld === undefined) throw E("path/bad-input", "validate: opts." + optName + " must not be a sparse array; index " + li + " is a hole");
|
|
560
|
+
if (!intrinsic.hasOwn(ld, "value")) throw E("path/bad-input", "validate: opts." + optName + " entry " + li + " must be a data property, not an accessor");
|
|
561
|
+
}
|
|
562
|
+
/** @internal Built with a plain loop into a fresh array: Array.prototype.map consults the caller
|
|
563
|
+
* array's constructor and Symbol.species, which is more caller code running mid-copy. */
|
|
564
|
+
var seedsOut = [];
|
|
565
|
+
for (var si = 0; si < list.length; si++) {
|
|
566
|
+
intrinsic.push(seedsOut, (function (st) {
|
|
567
|
+
var shapeMsg = "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }";
|
|
568
|
+
if (!st || typeof st !== "object" || intrinsic.types.isProxy(st)) throw E("path/bad-input", shapeMsg);
|
|
569
|
+
/** @internal Read tag and base once each, from their own data properties: an accessor could
|
|
570
|
+
* answer with a restrictive value to a check and a permissive one to the copy. */
|
|
571
|
+
var tagD = intrinsic.getOwnPropertyDescriptor(st, "tag");
|
|
572
|
+
var baseD = intrinsic.getOwnPropertyDescriptor(st, "base");
|
|
573
|
+
if (tagD !== undefined && !intrinsic.hasOwn(tagD, "value")) throw E("path/bad-input", "validate: opts." + optName + " entry tag must be a data property, not an accessor");
|
|
574
|
+
if (baseD !== undefined && !intrinsic.hasOwn(baseD, "value")) throw E("path/bad-input", "validate: opts." + optName + " entry base must be a data property, not an accessor");
|
|
575
|
+
var tag = tagD === undefined ? undefined : tagD.value;
|
|
576
|
+
var base = baseD === undefined ? undefined : baseD.value;
|
|
577
|
+
if (!Number.isInteger(tag) || tag < 0 || tag > 8) throw E("path/bad-input", shapeMsg);
|
|
578
|
+
if (tag === 7 && guard.bytes.isByteSource(base)) {
|
|
579
|
+
base = guard.bytes.snapshotSource(base, PathError, "path/bad-input", "an iPAddress subtree base");
|
|
560
580
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
581
|
+
if (tag === 4 && base !== null && typeof base === "object") {
|
|
582
|
+
var seedRdns = _capturedRdns(base, "validate: opts." + optName);
|
|
583
|
+
if (seedRdns !== undefined) {
|
|
584
|
+
var dnCopy = {};
|
|
585
|
+
intrinsic.defineProperty(dnCopy, "rdns", { value: seedRdns, enumerable: true, configurable: true, writable: true });
|
|
586
|
+
base = dnCopy;
|
|
587
|
+
}
|
|
566
588
|
}
|
|
567
|
-
|
|
568
|
-
|
|
589
|
+
if (!isSubtreeBaseValid(tag, base)) throw E("path/bad-input", shapeMsg);
|
|
590
|
+
return { tag: tag, base: base };
|
|
591
|
+
})(intrinsic.getOwnPropertyDescriptor(list, si).value));
|
|
592
|
+
}
|
|
593
|
+
return seedsOut;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* @internal RFC 5280 sec. 6.1.1(h)(i): the anchor's own permitted/excluded subtrees, the way a root
|
|
598
|
+
* program restricts a root to a namespace its certificate does not state. Validated through the same
|
|
599
|
+
* entry-point checker the caller's seeds use, so a mis-shaped subtree is refused rather than dropped.
|
|
600
|
+
*/
|
|
601
|
+
function _anchorSubtreeSeeds(anchor) {
|
|
602
|
+
var nc = anchor.nameConstraints;
|
|
603
|
+
if (nc === undefined || nc === null) return { permitted: [], excluded: [] };
|
|
604
|
+
if (typeof nc !== "object" || intrinsic.isArray(nc)) {
|
|
605
|
+
throw E("path/bad-input", "validate: a trustAnchor nameConstraints must be a { permitted, excluded } object of { tag, base } subtree entries");
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
permitted: checkedSubtreeSeeds(nc.permitted, "trustAnchors[].nameConstraints.permitted"),
|
|
609
|
+
excluded: checkedSubtreeSeeds(nc.excluded, "trustAnchors[].nameConstraints.excluded"),
|
|
610
|
+
};
|
|
569
611
|
}
|
|
570
612
|
|
|
571
613
|
function initialize(certs, params, seeds, anchor) {
|
|
@@ -574,7 +616,7 @@ function initialize(certs, params, seeds, anchor) {
|
|
|
574
616
|
validPolicyTree: rootNode(),
|
|
575
617
|
policyNodeCount: 1,
|
|
576
618
|
maxPolicyNodes: params.maxPolicyNodes !== undefined ? params.maxPolicyNodes : constants.LIMITS.PATH_MAX_POLICY_NODES,
|
|
577
|
-
permittedGenerations: seeds.
|
|
619
|
+
permittedGenerations: intrinsic.filter(seeds.permittedGenerations, function (g) { return g.length > 0; }),
|
|
578
620
|
excludedSubtrees: seeds.excluded,
|
|
579
621
|
explicitPolicy: params.initialExplicitPolicy ? 0 : n + 1,
|
|
580
622
|
inhibitAnyPolicy: params.initialAnyPolicyInhibit ? 0 : n + 1,
|
|
@@ -867,10 +909,21 @@ function validateCriticalExtensionStructure(cert) {
|
|
|
867
909
|
*
|
|
868
910
|
* `anchorConstraints` reports what the anchor's own trust metadata decided:
|
|
869
911
|
* the `checkedPurpose` it was judged under, and whether the `distrustAfter`
|
|
870
|
-
* date
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
912
|
+
* date, the `purposes` delegator map and the `nameConstraints` namespace each
|
|
913
|
+
* applied. The purpose-keyed metadata is keyed BY key purpose, so an anchor
|
|
914
|
+
* carrying it while `opts.checkPurpose` is absent is a configuration fault
|
|
915
|
+
* (`path/bad-input`). A silently inert constraint would let a root distrusted
|
|
916
|
+
* years ago quietly validate a current leaf.
|
|
917
|
+
*
|
|
918
|
+
* An anchor may also carry `nameConstraints`, a `{ permitted, excluded }` pair
|
|
919
|
+
* of `{ tag, base }` subtrees naming the namespace that root is trusted for.
|
|
920
|
+
* A root program restricts a root this way when the restriction appears in no
|
|
921
|
+
* `nameConstraints` extension the root certificate carries. The subtrees become
|
|
922
|
+
* the RFC 5280 sec. 6.1.1(h)(i) initial permitted and excluded state, so an
|
|
923
|
+
* intermediate's own `nameConstraints` extension intersects with them and a leaf
|
|
924
|
+
* must satisfy both. They are a separate generation from
|
|
925
|
+
* `opts.initialPermittedSubtrees`, so those two intersect as well. A mis-shaped
|
|
926
|
+
* subtree is refused with `path/bad-input` rather than left inert.
|
|
874
927
|
*
|
|
875
928
|
* @example
|
|
876
929
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -938,17 +991,35 @@ async function validate(path, opts) {
|
|
|
938
991
|
var certs = path.map(function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
|
|
939
992
|
var n = certs.length;
|
|
940
993
|
if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
|
|
994
|
+
/** @internal Captured before any anchor is normalized: an anchor field may be an accessor, and
|
|
995
|
+
* that getter runs caller code which could otherwise clear these lists first. */
|
|
996
|
+
var callerPermitted = checkedSubtreeSeeds(opts.initialPermittedSubtrees, "initialPermittedSubtrees");
|
|
997
|
+
var callerExcluded = checkedSubtreeSeeds(opts.initialExcludedSubtrees, "initialExcludedSubtrees");
|
|
941
998
|
var rawAnchors = opts.trustAnchors;
|
|
942
999
|
var anchorList = intrinsic.isArray(rawAnchors) ? rawAnchors : [rawAnchors];
|
|
943
1000
|
if (rawAnchors == null || anchorList.length === 0) throw E("path/bad-input", "validate: trustAnchors is required (a single anchor or a non-empty array of anchors)");
|
|
1001
|
+
/** @internal Read the caller's list once here, whatever its length, so neither branch below reads
|
|
1002
|
+
* one of its indices twice. */
|
|
1003
|
+
if (intrinsic.isArray(rawAnchors)) anchorList = _ownAnchorEntries(rawAnchors, "validate");
|
|
944
1004
|
if (anchorList.length > 1) {
|
|
1005
|
+
var anchorEntries = anchorList;
|
|
1006
|
+
var preNc = _precaptureAnchorNameConstraints(anchorEntries, "validate");
|
|
945
1007
|
var tuples = [];
|
|
946
|
-
for (var ti = 0; ti <
|
|
1008
|
+
for (var ti = 0; ti < anchorEntries.length; ti++) {
|
|
1009
|
+
tuples[ti] = toAnchor(anchorEntries[ti], "validate");
|
|
1010
|
+
if (preNc[ti] !== undefined) intrinsic.defineProperty(tuples[ti], "nameConstraints", { value: preNc[ti], enumerable: true, configurable: true, writable: true });
|
|
1011
|
+
}
|
|
1012
|
+
/** @internal Each anchor is a separate attempt and the later ones run after an await, so the
|
|
1013
|
+
* caller's own subtree lists are read once here rather than again per attempt. */
|
|
1014
|
+
var permRawOnce = opts.initialPermittedSubtrees;
|
|
1015
|
+
var exclRawOnce = opts.initialExcludedSubtrees;
|
|
947
1016
|
var singleOpts = function (a) {
|
|
948
1017
|
var keys = intrinsic.ownKeys(opts);
|
|
949
1018
|
var o = {};
|
|
950
1019
|
for (var ki = 0; ki < keys.length; ki++) { o[keys[ki]] = opts[keys[ki]]; }
|
|
951
1020
|
o.trustAnchors = a;
|
|
1021
|
+
if (permRawOnce != null) o.initialPermittedSubtrees = callerPermitted;
|
|
1022
|
+
if (exclRawOnce != null) o.initialExcludedSubtrees = callerExcluded;
|
|
952
1023
|
return o;
|
|
953
1024
|
};
|
|
954
1025
|
var lastMatched = null, anyMatched = false;
|
|
@@ -965,10 +1036,12 @@ async function validate(path, opts) {
|
|
|
965
1036
|
guard.time.assertValid(opts.time, E, "path/bad-input", "validate: opts.time (the always-on validity-window check date)");
|
|
966
1037
|
guard.limits.cap(opts.maxPolicyNodes, "validate: opts.maxPolicyNodes", undefined, { E: E, code: "path/bad-input", min: 1 });
|
|
967
1038
|
_assertUserInitialPolicySet(opts.userInitialPolicySet, "validate");
|
|
1039
|
+
var anchorNc = _anchorSubtreeSeeds(anchor);
|
|
968
1040
|
var seeds = {
|
|
969
|
-
|
|
970
|
-
excluded:
|
|
1041
|
+
permittedGenerations: [callerPermitted, anchorNc.permitted],
|
|
1042
|
+
excluded: intrinsic.concat(callerExcluded, anchorNc.excluded),
|
|
971
1043
|
};
|
|
1044
|
+
var anchorNameConstraintsApplied = anchorNc.permitted.length > 0 || anchorNc.excluded.length > 0;
|
|
972
1045
|
var purposeOpts = resolvePurposeOpts(opts);
|
|
973
1046
|
var requiredEku = purposeOpts.requiredEku;
|
|
974
1047
|
var checkPurpose = purposeOpts.checkPurpose;
|
|
@@ -1158,6 +1231,7 @@ async function validate(path, opts) {
|
|
|
1158
1231
|
checkedPurpose: checkPurpose || null,
|
|
1159
1232
|
distrustAfterApplied: anchorDistrustApplied,
|
|
1160
1233
|
purposeTrustApplied: anchorPurposeApplied,
|
|
1234
|
+
nameConstraintsApplied: anchorNameConstraintsApplied,
|
|
1161
1235
|
},
|
|
1162
1236
|
path: certs,
|
|
1163
1237
|
results: state.results,
|
|
@@ -1696,7 +1770,7 @@ function ocspChecker(responses) {
|
|
|
1696
1770
|
|
|
1697
1771
|
/**
|
|
1698
1772
|
* @primitive pki.path.verifyOcspResponse
|
|
1699
|
-
* @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 }>
|
|
1700
1774
|
* @since 0.2.22
|
|
1701
1775
|
* @status stable
|
|
1702
1776
|
* @spec RFC 6960
|
|
@@ -1752,14 +1826,16 @@ function ocspChecker(responses) {
|
|
|
1752
1826
|
* var v = await pki.path.verifyOcspResponse(der, cert, issuerCert, new Date());
|
|
1753
1827
|
* v.status; // "good" | "revoked" | "unknown"
|
|
1754
1828
|
*/
|
|
1755
|
-
function verifyOcspResponse(response, cert, issuerCert, time, opts) {
|
|
1829
|
+
async function verifyOcspResponse(response, cert, issuerCert, time, opts) {
|
|
1756
1830
|
var parsedResponse, subject, issuer;
|
|
1757
1831
|
try {
|
|
1758
1832
|
parsedResponse = _ocspFromBytes(response);
|
|
1759
1833
|
subject = coerceCert(cert, "the certificate");
|
|
1760
1834
|
issuer = coerceCert(issuerCert, "the issuer certificate");
|
|
1761
1835
|
} catch (e) { return Promise.reject(e); }
|
|
1762
|
-
|
|
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" });
|
|
1763
1839
|
}
|
|
1764
1840
|
|
|
1765
1841
|
var _OCSP_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
|
|
@@ -1778,7 +1854,7 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
|
1778
1854
|
if (!guard.time.isDate(time) || isNaN(guard.time.instantOf(time))) {
|
|
1779
1855
|
return Promise.reject(E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
|
|
1780
1856
|
}
|
|
1781
|
-
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 }); }
|
|
1782
1858
|
var boundName;
|
|
1783
1859
|
try { boundName = dnEqual(cert.issuer.rdns, issuerCert.subject.rdns); }
|
|
1784
1860
|
catch (e) { return Promise.reject(e); }
|
|
@@ -1795,9 +1871,9 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
|
1795
1871
|
try { issuerKeyBits = ocspVerify.ocspKeyValue(issuerCert.subjectPublicKeyInfo.bytes); }
|
|
1796
1872
|
catch (_e) { return unbound("the issuer public key could not be read to recompute the OCSP CertID"); }
|
|
1797
1873
|
return ocspCore.evaluateResponse(parsedResponse, cert, issuerCtx, issuerKeyBits, issuerNameCandidates, time, opts.historicalMode === true).then(function (v) {
|
|
1798
|
-
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 };
|
|
1799
|
-
if (v.sawGood) return { status: "good", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: "good" };
|
|
1800
|
-
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 });
|
|
1801
1877
|
});
|
|
1802
1878
|
});
|
|
1803
1879
|
}
|
|
@@ -1903,6 +1979,198 @@ function _captureOwnData(entry, key) {
|
|
|
1903
1979
|
return (d && intrinsic.hasOwn(d, "value")) ? d.value : undefined;
|
|
1904
1980
|
}
|
|
1905
1981
|
|
|
1982
|
+
/**
|
|
1983
|
+
* @internal Copy the anchor's subtree lists getter-free, so a caller mutating the entry afterwards
|
|
1984
|
+
* cannot widen the namespace the validation already resolved.
|
|
1985
|
+
*/
|
|
1986
|
+
/**
|
|
1987
|
+
* @internal Read a directoryName base's `rdns` once, from its own data property. Reading it twice,
|
|
1988
|
+
* to test its shape and then to copy it, lets an accessor answer differently each time and hand the
|
|
1989
|
+
* validation an empty DN, which matches every name.
|
|
1990
|
+
*/
|
|
1991
|
+
var _ATV_KEYS = ["type", "value", "name"];
|
|
1992
|
+
/**
|
|
1993
|
+
* @internal Build the directoryName's own copy from property descriptors alone, so no caller code
|
|
1994
|
+
* runs while the namespace is read: a getter, own or inherited, could clear another part of the
|
|
1995
|
+
* namespace before it is captured. A directoryName is a relative-name sequence of attribute lists
|
|
1996
|
+
* and nothing deeper, so exactly those levels are walked and a self-referencing array is refused
|
|
1997
|
+
* rather than followed.
|
|
1998
|
+
*/
|
|
1999
|
+
function _copyDnRdns(rdns, who) {
|
|
2000
|
+
_assertPlainDnRdns(rdns, who);
|
|
2001
|
+
var out = [];
|
|
2002
|
+
for (var i = 0; i < rdns.length; i++) {
|
|
2003
|
+
var rd = intrinsic.getOwnPropertyDescriptor(rdns, i);
|
|
2004
|
+
if (rd === undefined) throw E("path/bad-input", who + " index " + i + " is a hole or is inherited; every entry must be its own");
|
|
2005
|
+
var rdn = rd.value;
|
|
2006
|
+
if (!intrinsic.isArray(rdn)) throw E("path/bad-input", who + "[" + i + "] must be an array of attributes");
|
|
2007
|
+
var rdnOut = [];
|
|
2008
|
+
for (var j = 0; j < rdn.length; j++) {
|
|
2009
|
+
var ad = intrinsic.getOwnPropertyDescriptor(rdn, j);
|
|
2010
|
+
if (ad === undefined) throw E("path/bad-input", who + "[" + i + "] index " + j + " is a hole or is inherited; every entry must be its own");
|
|
2011
|
+
var atv = ad.value;
|
|
2012
|
+
if (atv === null || typeof atv !== "object") throw E("path/bad-input", who + "[" + i + "][" + j + "] must be an attribute object carrying a type and a value");
|
|
2013
|
+
var atvOut = {};
|
|
2014
|
+
for (var k = 0; k < _ATV_KEYS.length; k++) {
|
|
2015
|
+
var fd = intrinsic.getOwnPropertyDescriptor(atv, _ATV_KEYS[k]);
|
|
2016
|
+
if (fd === undefined) continue;
|
|
2017
|
+
intrinsic.defineProperty(atvOut, _ATV_KEYS[k], { value: fd.value, enumerable: true, configurable: true, writable: true });
|
|
2018
|
+
}
|
|
2019
|
+
if (!intrinsic.hasOwn(atvOut, "type") || !intrinsic.hasOwn(atvOut, "value")) {
|
|
2020
|
+
throw E("path/bad-input", who + "[" + i + "][" + j + "] must carry its type and value as own data properties");
|
|
2021
|
+
}
|
|
2022
|
+
intrinsic.push(rdnOut, atvOut);
|
|
2023
|
+
}
|
|
2024
|
+
intrinsic.push(out, rdnOut);
|
|
2025
|
+
}
|
|
2026
|
+
return out;
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* @internal The shape check the copy above relies on: a Proxy answers its own reads and an accessor
|
|
2030
|
+
* runs caller code, so either one at any level of the directoryName is refused before it is copied.
|
|
2031
|
+
*/
|
|
2032
|
+
function _assertPlainDnRdns(rdns, who) {
|
|
2033
|
+
if (intrinsic.types.isProxy(rdns)) throw E("path/bad-input", who + " must be a plain array, not a Proxy");
|
|
2034
|
+
for (var i = 0; i < rdns.length; i++) {
|
|
2035
|
+
var rd = intrinsic.getOwnPropertyDescriptor(rdns, i);
|
|
2036
|
+
if (rd === undefined) continue;
|
|
2037
|
+
if (!intrinsic.hasOwn(rd, "value")) throw E("path/bad-input", who + " entry " + i + " must be a data property, not an accessor");
|
|
2038
|
+
var rdn = rd.value;
|
|
2039
|
+
if (!intrinsic.isArray(rdn)) continue;
|
|
2040
|
+
if (intrinsic.types.isProxy(rdn)) throw E("path/bad-input", who + "[" + i + "] must be a plain array, not a Proxy");
|
|
2041
|
+
for (var j = 0; j < rdn.length; j++) {
|
|
2042
|
+
var ad = intrinsic.getOwnPropertyDescriptor(rdn, j);
|
|
2043
|
+
if (ad === undefined) continue;
|
|
2044
|
+
if (!intrinsic.hasOwn(ad, "value")) throw E("path/bad-input", who + "[" + i + "] entry " + j + " must be a data property, not an accessor");
|
|
2045
|
+
var atv = ad.value;
|
|
2046
|
+
if (atv === null || typeof atv !== "object") continue;
|
|
2047
|
+
if (intrinsic.types.isProxy(atv)) throw E("path/bad-input", who + "[" + i + "][" + j + "] must not be a Proxy");
|
|
2048
|
+
if (intrinsic.isArray(atv)) throw E("path/bad-input", who + "[" + i + "][" + j + "] must be an attribute object, not an array");
|
|
2049
|
+
for (var k = 0; k < _ATV_KEYS.length; k++) {
|
|
2050
|
+
var fd = intrinsic.getOwnPropertyDescriptor(atv, _ATV_KEYS[k]);
|
|
2051
|
+
if (fd !== undefined && !intrinsic.hasOwn(fd, "value")) {
|
|
2052
|
+
throw E("path/bad-input", who + "[" + i + "][" + j + "]." + _ATV_KEYS[k] + " must be a data property, not an accessor");
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function _capturedRdns(base, who) {
|
|
2059
|
+
if (intrinsic.types.isProxy(base)) throw E("path/bad-input", who + ": a trustAnchor nameConstraints directoryName base must not be a Proxy");
|
|
2060
|
+
/** @internal An inherited Proxy answers `in` through its has trap, which is caller code running
|
|
2061
|
+
* inside what is meant to be a getter-free read. */
|
|
2062
|
+
var baseProto = intrinsic.getPrototypeOf(base);
|
|
2063
|
+
while (baseProto !== null) {
|
|
2064
|
+
if (intrinsic.types.isProxy(baseProto)) throw E("path/bad-input", who + ": a trustAnchor nameConstraints directoryName base must not inherit from a Proxy");
|
|
2065
|
+
baseProto = intrinsic.getPrototypeOf(baseProto);
|
|
2066
|
+
}
|
|
2067
|
+
var d = intrinsic.getOwnPropertyDescriptor(base, "rdns");
|
|
2068
|
+
if (d === undefined) {
|
|
2069
|
+
if ("rdns" in base) throw E("path/bad-input", who + ": a trustAnchor nameConstraints directoryName rdns must be an own data property, not inherited");
|
|
2070
|
+
return undefined;
|
|
2071
|
+
}
|
|
2072
|
+
if (!intrinsic.hasOwn(d, "value")) throw E("path/bad-input", who + ": a trustAnchor nameConstraints directoryName rdns must be a data property, not an accessor");
|
|
2073
|
+
if (!intrinsic.isArray(d.value)) return undefined;
|
|
2074
|
+
return _copyDnRdns(d.value, who + ": a trustAnchor nameConstraints directoryName rdns");
|
|
2075
|
+
}
|
|
2076
|
+
var _NC_SUBTREE_KEYS = ["tag", "base"];
|
|
2077
|
+
var _NC_LIST_KEYS = ["permitted", "excluded"];
|
|
2078
|
+
function _copyAnchorSubtreeList(list, key, who) {
|
|
2079
|
+
if (list === undefined || list === null) return undefined;
|
|
2080
|
+
if (!intrinsic.isArray(list) || intrinsic.types.isProxy(list)) throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " must be a plain array of { tag, base } subtree entries");
|
|
2081
|
+
var copied = [];
|
|
2082
|
+
var len = list.length;
|
|
2083
|
+
for (var i = 0; i < len; i++) {
|
|
2084
|
+
var entryD = intrinsic.getOwnPropertyDescriptor(list, i);
|
|
2085
|
+
if (entryD === undefined) throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " must not be a sparse array; index " + i + " is a hole");
|
|
2086
|
+
if (!intrinsic.hasOwn(entryD, "value")) throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " entry " + i + " must be a data property, not an accessor");
|
|
2087
|
+
var st = entryD.value;
|
|
2088
|
+
if (st === null || typeof st !== "object" || intrinsic.types.isProxy(st)) {
|
|
2089
|
+
throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " entry must be a plain { tag, base } object");
|
|
2090
|
+
}
|
|
2091
|
+
var out = {};
|
|
2092
|
+
for (var k = 0; k < _NC_SUBTREE_KEYS.length; k++) {
|
|
2093
|
+
var field = _NC_SUBTREE_KEYS[k];
|
|
2094
|
+
var fd = intrinsic.getOwnPropertyDescriptor(st, field);
|
|
2095
|
+
if (fd === undefined) continue;
|
|
2096
|
+
if (!intrinsic.hasOwn(fd, "value")) throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " " + field + " must be a data property, not an accessor");
|
|
2097
|
+
var fv = fd.value;
|
|
2098
|
+
if (field === "base" && fv !== null && typeof fv === "object") {
|
|
2099
|
+
var rdnsOwn = _capturedRdns(fv, who);
|
|
2100
|
+
if (rdnsOwn !== undefined) {
|
|
2101
|
+
var dnBase = {};
|
|
2102
|
+
intrinsic.defineProperty(dnBase, "rdns", { value: rdnsOwn, enumerable: true, configurable: true, writable: true });
|
|
2103
|
+
fv = dnBase;
|
|
2104
|
+
} else if (guard.bytes.isByteSource(fv)) {
|
|
2105
|
+
fv = guard.bytes.snapshotSource(fv, PathError, "path/bad-input", who + ": a trustAnchor nameConstraints iPAddress subtree base");
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
intrinsic.defineProperty(out, field, { value: fv, enumerable: true, configurable: true, writable: true });
|
|
2109
|
+
}
|
|
2110
|
+
intrinsic.push(copied, out);
|
|
2111
|
+
}
|
|
2112
|
+
return copied;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* @internal Anchors are normalized one at a time, and each may expose fields through getters that
|
|
2116
|
+
* run caller code. Capture every anchor's namespace first, so an earlier anchor's getter cannot
|
|
2117
|
+
* clear a later anchor's restriction before that anchor is reached.
|
|
2118
|
+
*/
|
|
2119
|
+
/**
|
|
2120
|
+
* @internal Read a caller's anchor list once, from its own indices, into a fresh array. The list is
|
|
2121
|
+
* walked twice, to capture namespaces and then to normalize, and an accessor index could answer
|
|
2122
|
+
* differently each time.
|
|
2123
|
+
*/
|
|
2124
|
+
function _ownAnchorEntries(list, who) {
|
|
2125
|
+
if (intrinsic.types.isProxy(list)) throw E("path/bad-input", who + ": trustAnchors must be a plain array, not a Proxy");
|
|
2126
|
+
var out = [];
|
|
2127
|
+
for (var i = 0; i < list.length; i++) {
|
|
2128
|
+
var d = intrinsic.getOwnPropertyDescriptor(list, i);
|
|
2129
|
+
if (d === undefined) throw E("path/bad-input", who + ": trustAnchors index " + i + " is a hole or is inherited; every entry must be its own");
|
|
2130
|
+
if (!intrinsic.hasOwn(d, "value")) throw E("path/bad-input", who + ": trustAnchors entry " + i + " must be a data property, not an accessor");
|
|
2131
|
+
intrinsic.push(out, d.value);
|
|
2132
|
+
}
|
|
2133
|
+
return out;
|
|
2134
|
+
}
|
|
2135
|
+
function _precaptureAnchorNameConstraints(list, who) {
|
|
2136
|
+
var out = [];
|
|
2137
|
+
for (var i = 0; i < list.length; i++) {
|
|
2138
|
+
var entry = list[i];
|
|
2139
|
+
var captured;
|
|
2140
|
+
if (entry && typeof entry === "object" && !intrinsic.isBuffer(entry) && !guard.parsed.isRecorded(entry) && !intrinsic.types.isProxy(entry)) {
|
|
2141
|
+
var raw = _captureConstraintMap(entry, "nameConstraints", E, "path/bad-input", who);
|
|
2142
|
+
captured = raw == null ? undefined : _copyAnchorNameConstraints(raw, who);
|
|
2143
|
+
}
|
|
2144
|
+
intrinsic.push(out, captured);
|
|
2145
|
+
}
|
|
2146
|
+
return out;
|
|
2147
|
+
}
|
|
2148
|
+
function _copyAnchorNameConstraints(nc, who) {
|
|
2149
|
+
if (nc === null || typeof nc !== "object" || intrinsic.isArray(nc)) {
|
|
2150
|
+
throw E("path/bad-input", who + ": a trustAnchor nameConstraints must be a { permitted, excluded } object of { tag, base } subtree entries");
|
|
2151
|
+
}
|
|
2152
|
+
guard.identifier.assertPlainRecord(nc, E, "path/bad-input", who + ": a trustAnchor nameConstraints");
|
|
2153
|
+
guard.identifier.assertKnownKeys(nc, { permitted: 1, excluded: 1 }, E, "path/bad-input",
|
|
2154
|
+
who + ": a trustAnchor nameConstraints names its subtree lists `permitted` and `excluded`. The unknown key was: ");
|
|
2155
|
+
var out = {};
|
|
2156
|
+
for (var i = 0; i < _NC_LIST_KEYS.length; i++) {
|
|
2157
|
+
var key = _NC_LIST_KEYS[i];
|
|
2158
|
+
var d = intrinsic.getOwnPropertyDescriptor(nc, key);
|
|
2159
|
+
if (d === undefined) {
|
|
2160
|
+
if (key in nc) throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " must be an own data property, not inherited");
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
if (!intrinsic.hasOwn(d, "value")) throw E("path/bad-input", who + ": a trustAnchor nameConstraints." + key + " must be a data property, not an accessor");
|
|
2164
|
+
var copiedList = _copyAnchorSubtreeList(d.value, key, who);
|
|
2165
|
+
if (copiedList === undefined) continue;
|
|
2166
|
+
/** @internal Validated while the anchor is normalized, so a malformed subtree is refused
|
|
2167
|
+
* wherever the anchor sits in the list rather than only once that anchor is reached. */
|
|
2168
|
+
var checkedList = checkedSubtreeSeeds(copiedList, "trustAnchors[].nameConstraints." + key);
|
|
2169
|
+
intrinsic.defineProperty(out, key, { value: checkedList, enumerable: true, configurable: true, writable: true });
|
|
2170
|
+
}
|
|
2171
|
+
return out;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
1906
2174
|
function _copyAtv(atv) {
|
|
1907
2175
|
var out = {};
|
|
1908
2176
|
var t = atv.type;
|
|
@@ -1939,7 +2207,7 @@ function _copyRdnsDeep(rdns) {
|
|
|
1939
2207
|
function toAnchor(entry, verb) {
|
|
1940
2208
|
var who = verb || "build";
|
|
1941
2209
|
var isTuple = false, alg = null, pubRaw, nameRaw = null, pk = null, purposesSnap, distrustSnap;
|
|
1942
|
-
var subjectDerRaw, labelRaw, mozRaw;
|
|
2210
|
+
var subjectDerRaw, labelRaw, mozRaw, ncSnap;
|
|
1943
2211
|
if (intrinsic.types.isProxy(entry)) {
|
|
1944
2212
|
throw E("path/bad-input", who + ": a trustAnchor must not be a Proxy");
|
|
1945
2213
|
}
|
|
@@ -1956,6 +2224,8 @@ function toAnchor(entry, verb) {
|
|
|
1956
2224
|
purposesSnap = _normalizeConstraintField(pRaw, E, "path/bad-input", who);
|
|
1957
2225
|
var dRaw = _captureConstraintMap(entry, "distrustAfter", E, "path/bad-input", who);
|
|
1958
2226
|
distrustSnap = _normalizeConstraintField(dRaw, E, "path/bad-input", who);
|
|
2227
|
+
var ncRaw = _captureConstraintMap(entry, "nameConstraints", E, "path/bad-input", who);
|
|
2228
|
+
ncSnap = ncRaw == null ? undefined : _copyAnchorNameConstraints(ncRaw, who);
|
|
1959
2229
|
subjectDerRaw = _captureOwnData(entry, "subjectDer");
|
|
1960
2230
|
labelRaw = _captureOwnData(entry, "label");
|
|
1961
2231
|
mozRaw = _captureOwnData(entry, "mozillaCaPolicy");
|
|
@@ -2023,6 +2293,7 @@ function toAnchor(entry, verb) {
|
|
|
2023
2293
|
intrinsic.defineProperty(flat, "name", { value: nameOut, enumerable: true, configurable: true, writable: true });
|
|
2024
2294
|
if (purposesSnap !== undefined) intrinsic.defineProperty(flat, "purposes", { value: purposesSnap, enumerable: true, configurable: true, writable: true });
|
|
2025
2295
|
if (distrustSnap !== undefined) intrinsic.defineProperty(flat, "distrustAfter", { value: distrustSnap, enumerable: true, configurable: true, writable: true });
|
|
2296
|
+
if (ncSnap !== undefined) intrinsic.defineProperty(flat, "nameConstraints", { value: ncSnap, enumerable: true, configurable: true, writable: true });
|
|
2026
2297
|
if (subjectDerRaw !== undefined) intrinsic.defineProperty(flat, "subjectDer", { value: subjectDerRaw, enumerable: true, configurable: true, writable: true });
|
|
2027
2298
|
if (labelRaw !== undefined) intrinsic.defineProperty(flat, "label", { value: labelRaw, enumerable: true, configurable: true, writable: true });
|
|
2028
2299
|
if (mozRaw !== undefined) intrinsic.defineProperty(flat, "mozillaCaPolicy", { value: mozRaw, enumerable: true, configurable: true, writable: true });
|
|
@@ -2031,8 +2302,8 @@ function toAnchor(entry, verb) {
|
|
|
2031
2302
|
intrinsic.defineProperty(flat, "parameters", { value: spkiAlg.parameters, enumerable: true, configurable: true, writable: true });
|
|
2032
2303
|
return flat;
|
|
2033
2304
|
}
|
|
2034
|
-
if (entry && typeof entry === "object" && (("purposes" in entry) || ("distrustAfter" in entry))) {
|
|
2035
|
-
throw E("path/bad-input", who + ": a trustAnchor certificate must not carry purposes / distrustAfter -- put
|
|
2305
|
+
if (entry && typeof entry === "object" && (("purposes" in entry) || ("distrustAfter" in entry) || ("nameConstraints" in entry))) {
|
|
2306
|
+
throw E("path/bad-input", who + ": a trustAnchor certificate must not carry purposes / distrustAfter / nameConstraints -- put anchor constraints on a { name, publicKey, algorithm, purposes, distrustAfter, nameConstraints } tuple (or a pki.trust anchor), not on a parsed certificate");
|
|
2036
2307
|
}
|
|
2037
2308
|
var cert;
|
|
2038
2309
|
try { cert = coerceCert(entry); }
|
|
@@ -2349,7 +2620,20 @@ async function build(leaf, opts) {
|
|
|
2349
2620
|
for (var pdk = 0; pdk < pool.length; pdk++) intrinsic.setAdd(poolDerKeys, certDerKey(pool[pdk]));
|
|
2350
2621
|
|
|
2351
2622
|
if (!intrinsic.isArray(opts.trustAnchors) || opts.trustAnchors.length === 0) throw E("path/bad-input", "build: opts.trustAnchors must be a non-empty array of anchor tuples or root certificates");
|
|
2352
|
-
|
|
2623
|
+
/** @internal Captured before any anchor is normalized: an anchor field may be an accessor whose
|
|
2624
|
+
* getter runs caller code, and every candidate is validated in its own pass after an await. */
|
|
2625
|
+
var buildPermRaw = opts.initialPermittedSubtrees;
|
|
2626
|
+
var buildExclRaw = opts.initialExcludedSubtrees;
|
|
2627
|
+
var buildPermSeeds = checkedSubtreeSeeds(buildPermRaw, "initialPermittedSubtrees");
|
|
2628
|
+
var buildExclSeeds = checkedSubtreeSeeds(buildExclRaw, "initialExcludedSubtrees");
|
|
2629
|
+
var buildEntries = _ownAnchorEntries(opts.trustAnchors, "build");
|
|
2630
|
+
var buildPreNc = _precaptureAnchorNameConstraints(buildEntries, "build");
|
|
2631
|
+
var anchors = [];
|
|
2632
|
+
for (var bai = 0; bai < buildEntries.length; bai++) {
|
|
2633
|
+
var bAnchor = toAnchor(buildEntries[bai], "build");
|
|
2634
|
+
if (buildPreNc[bai] !== undefined) intrinsic.defineProperty(bAnchor, "nameConstraints", { value: buildPreNc[bai], enumerable: true, configurable: true, writable: true });
|
|
2635
|
+
intrinsic.push(anchors, bAnchor);
|
|
2636
|
+
}
|
|
2353
2637
|
|
|
2354
2638
|
guard.time.assertValid(opts.time, E, "path/bad-input", "build: opts.time (the validity-window check date, forwarded to validate)");
|
|
2355
2639
|
var effectiveMaxCerts = guard.limits.cap(opts.maxPathCerts, "build: opts.maxPathCerts", constants.LIMITS.PATH_MAX_CERTS, { E: E, code: "path/bad-input", min: 1 });
|
|
@@ -2396,6 +2680,8 @@ async function build(leaf, opts) {
|
|
|
2396
2680
|
if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
|
|
2397
2681
|
if (k in opts) forwarded[k] = opts[k];
|
|
2398
2682
|
});
|
|
2683
|
+
if (buildPermRaw != null) forwarded.initialPermittedSubtrees = buildPermSeeds;
|
|
2684
|
+
if (buildExclRaw != null) forwarded.initialExcludedSubtrees = buildExclSeeds;
|
|
2399
2685
|
function validateOpts(anchor) {
|
|
2400
2686
|
var vo = Object.create(null);
|
|
2401
2687
|
Object.keys(forwarded).forEach(function (f) { vo[f] = forwarded[f]; });
|
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
|
|
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
|
|
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
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:
|
|
5
|
+
"serialNumber": "urn:uuid:b1c86284-d109-4852-8e85-dc5992b74178",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.6.39",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.6.
|
|
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.
|
|
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.
|
|
57
|
+
"ref": "@blamejs/pki@0.6.39",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|