@blamejs/pki 0.5.5 → 0.5.7
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/MIGRATING.md +63 -0
- package/lib/acme.js +47 -19
- package/lib/attrcert-sign.js +5 -1
- package/lib/cmc-build.js +8 -13
- package/lib/cmc-verify.js +2 -2
- package/lib/cmp-build.js +7 -2
- package/lib/cmp-session.js +4 -2
- package/lib/cmp-verify.js +1 -1
- package/lib/cms-compress.js +1 -2
- package/lib/cms-decrypt.js +2 -4
- package/lib/cms-encrypt.js +1 -2
- package/lib/cms-sign.js +42 -4
- package/lib/cms-verify.js +68 -17
- package/lib/crl-sign.js +9 -3
- package/lib/crmf-sign.js +5 -1
- package/lib/csr-sign.js +5 -1
- package/lib/est.js +5 -4
- package/lib/guard-all.js +2 -0
- package/lib/guard-async.js +37 -0
- package/lib/guard-bytes.js +368 -5
- package/lib/guard-parsed.js +117 -9
- package/lib/ocsp.js +24 -6
- package/lib/pbes2.js +11 -1
- package/lib/pkcs12-build.js +90 -27
- package/lib/pki-build.js +2 -3
- package/lib/schema-cms.js +150 -2
- package/lib/sign-scheme.js +7 -4
- package/lib/sigstore.js +10 -0
- package/lib/tsp-sign.js +116 -19
- package/lib/validator-tpm.js +1 -1
- package/lib/webauthn-mds.js +1 -1
- package/lib/webauthn.js +1 -1
- package/lib/x509-sign.js +11 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-verify.js
CHANGED
|
@@ -119,8 +119,7 @@ EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
|
|
|
119
119
|
EC_CURVE[oid.byName("secp521r1")] = { curve: "P-521", coordLen: 66 };
|
|
120
120
|
|
|
121
121
|
function _toBuf(v, what) {
|
|
122
|
-
if (Buffer.isBuffer(v)) return v;
|
|
123
|
-
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
122
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
|
|
124
123
|
throw _err("cms/bad-input", what + " must be a Buffer");
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -420,7 +419,24 @@ function _computeCountersigBytes(si, csTarget) {
|
|
|
420
419
|
// content-type / message-digest attribute disagrees.
|
|
421
420
|
function _computeSignedBytes(si, content, eContentType) {
|
|
422
421
|
return Promise.resolve().then(function () {
|
|
423
|
-
if (!si.signedAttrsBytes)
|
|
422
|
+
if (!si.signedAttrsBytes) {
|
|
423
|
+
// No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). That is
|
|
424
|
+
// also exactly what a stripped-attributes forgery looks like, because a CMS signature does not
|
|
425
|
+
// commit to whether attributes were present -- so a signature made over a SignedAttributes
|
|
426
|
+
// block re-presented as one made over content verifies, and with no attributes there is no
|
|
427
|
+
// message-digest or content-type attribute left to disagree. Refuse the shape: content that
|
|
428
|
+
// parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
|
|
429
|
+
// the verifier's place to guess which one it is holding.
|
|
430
|
+
if (cms.looksLikeSignedAttributes(content)) {
|
|
431
|
+
return { mismatch: {
|
|
432
|
+
code: "cms/ambiguous-content",
|
|
433
|
+
message: "the content of a SignerInfo with no signed attributes is itself an encoded " +
|
|
434
|
+
"SignedAttributes block, which is indistinguishable from a signature over attributes " +
|
|
435
|
+
"re-presented as one over content (RFC 5652 sec. 5.4); sign such content WITH signed attributes",
|
|
436
|
+
} };
|
|
437
|
+
}
|
|
438
|
+
return content;
|
|
439
|
+
}
|
|
424
440
|
// With signed attributes: decode them from the EXACT bytes the signature covers -- the
|
|
425
441
|
// SignedAttributes SET OF, the on-wire [0] IMPLICIT tag replaced by a universal SET OF
|
|
426
442
|
// (RFC 5652 sec. 5.4) -- so the content-type / message-digest checks bind the same bytes
|
|
@@ -538,7 +554,10 @@ function _surfaceUnsignedAttrs(si) {
|
|
|
538
554
|
// Verify every countersignature attached to `si` (RFC 5652 sec. 11.4): each id-countersignature
|
|
539
555
|
// value is a SignerInfo over `si`'s signature octets. Returns per-countersignature verdicts; a
|
|
540
556
|
// countersignature's OWN countersignatures verify over ITS signature octets (recursive). A
|
|
541
|
-
//
|
|
557
|
+
// countersignature that FAILS TO VERIFY is surfaced ok:false -- never silently dropped, and never
|
|
558
|
+
// allowed to change the primary verdict. A countersignature value that is not a well-formed
|
|
559
|
+
// SignerInfo does not reach here at all: the decoder validates every id-countersignature value by
|
|
560
|
+
// content rather than on the attribute type, so such a message is refused whole. The recursion
|
|
542
561
|
// terminates because it only walks the FINITE parsed structure: each nested countersignature value
|
|
543
562
|
// is a sub-encoding of its parent, and the strict decoder already bounds total nesting by
|
|
544
563
|
// C.LIMITS.DER_MAX_DEPTH at parse (CWE-834/770), so a hostile deep chain fails closed before verify.
|
|
@@ -571,7 +590,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
571
590
|
|
|
572
591
|
/**
|
|
573
592
|
* @primitive pki.cms.verify
|
|
574
|
-
* @signature pki.cms.verify(input, opts?) -> Promise<{ valid, trusted, signers }>
|
|
593
|
+
* @signature pki.cms.verify(input, opts?) -> Promise<{ valid, trusted, eContentType, signers }>
|
|
575
594
|
* @since 0.2.14
|
|
576
595
|
* @status stable
|
|
577
596
|
* @spec RFC 5652
|
|
@@ -581,10 +600,17 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
581
600
|
* @related pki.schema.cms.parse, pki.path.validate
|
|
582
601
|
*
|
|
583
602
|
* Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
|
|
584
|
-
* `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, signers }`
|
|
585
|
-
* `signers[i]` is `{ ok, sid, cert, trusted }` (`cert` the
|
|
586
|
-
* a `code` on a structural failure; `valid` is true when
|
|
587
|
-
* every signer verified.
|
|
603
|
+
* `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, signers }`
|
|
604
|
+
* where each `signers[i]` is `{ ok, sid, cert, trusted, signedAttributesPresent }` (`cert` the
|
|
605
|
+
* matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
|
|
606
|
+
* there is at least one signer and every signer verified.
|
|
607
|
+
*
|
|
608
|
+
* `eContentType` and `signedAttributesPresent` are there for a caller whose profile is stricter
|
|
609
|
+
* than RFC 5652's. Signing WITH attributes and signing the content directly are different claims --
|
|
610
|
+
* attributes bind a content type and a signing time alongside the digest, content-only binds
|
|
611
|
+
* nothing but the bytes -- and one message may carry a signer of each. A profile that requires
|
|
612
|
+
* attributes (RFC 8551 S/MIME does) or a particular content type can enforce it from the verdict
|
|
613
|
+
* rather than parsing the message a second time.
|
|
588
614
|
*
|
|
589
615
|
* `valid` and `trusted` are DIFFERENT claims and neither implies the other. A SignedData carries
|
|
590
616
|
* its own certificates, so `valid` establishes that the message is internally consistent -- the
|
|
@@ -650,7 +676,14 @@ function _snapshotIfBytes(input, label) {
|
|
|
650
676
|
// capability cannot arrive with its option silently ignored at this boundary.
|
|
651
677
|
var _VERIFY_OPTS = { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1 };
|
|
652
678
|
|
|
679
|
+
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async). The checks below stay
|
|
680
|
+
// synchronous -- they read a caller's mutable options and bytes, and resolving those before any turn
|
|
681
|
+
// passes is what stops a value being swapped between the check and the use.
|
|
653
682
|
function verify(input, opts) {
|
|
683
|
+
return guard.async.deferred(function () { return _verify(input, opts); });
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function _verify(input, opts) {
|
|
654
687
|
opts = opts || {};
|
|
655
688
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.verify options must be an object");
|
|
656
689
|
// An unrecognized option is refused, not swallowed. This is what kept the missing trust seam
|
|
@@ -663,13 +696,17 @@ function verify(input, opts) {
|
|
|
663
696
|
// the parse surfaced -- the signed content above all -- stays a view into the
|
|
664
697
|
// caller's memory across that await, and a buffer rewritten in the gap would leave
|
|
665
698
|
// the result describing one message while the signature was checked over another.
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
699
|
+
// Re-derived from the bytes the parser read, never trusted as the object it arrives as. A
|
|
700
|
+
// SignedData's meaning is a signature over a byte range, but a parsed one presents that range
|
|
701
|
+
// (`signedAttrsBytes`, or the encapsulated `eContent` when there are no signed attributes), the
|
|
702
|
+
// signature, the algorithms and the certificates as SEPARATE properties. Keep a genuine signer's
|
|
703
|
+
// signature and signed attributes and put different content beside them and every part of this
|
|
704
|
+
// check passes for a message that signer never signed -- the forgery this verb's own block claims
|
|
705
|
+
// to defend (CWE-347). A duck-type test on `signerInfos` cannot see that, because every field is
|
|
706
|
+
// individually well-formed; only re-deriving them all from one byte string can.
|
|
707
|
+
var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
|
|
708
|
+
return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
|
|
709
|
+
}, _err, "cms/bad-input", "the SignedData");
|
|
673
710
|
if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
|
|
674
711
|
var content = parsed.encapContentInfo.eContent;
|
|
675
712
|
if (content == null) {
|
|
@@ -707,11 +744,25 @@ function verify(input, opts) {
|
|
|
707
744
|
return _verifyCountersignatures(si, parsedCerts).then(function (countersignatures) {
|
|
708
745
|
verdict.countersignatures = countersignatures;
|
|
709
746
|
verdict.unsignedAttrs = _surfaceUnsignedAttrs(si);
|
|
747
|
+
// Whether THIS signer signed attributes or signed the content directly. The two are
|
|
748
|
+
// different claims -- attributes bind a content type and a signing time alongside the
|
|
749
|
+
// digest, and content-only binds nothing but the bytes -- and RFC 5652 lets a message carry
|
|
750
|
+
// one signer of each. A caller whose profile requires attributes (RFC 8551 S/MIME does) can
|
|
751
|
+
// only enforce it if the verdict says which they got.
|
|
752
|
+
verdict.signedAttributesPresent = !!si.signedAttrsBytes;
|
|
710
753
|
return verdict;
|
|
711
754
|
});
|
|
712
755
|
});
|
|
713
756
|
})).then(function (signers) {
|
|
714
|
-
|
|
757
|
+
// The content type travels with the verdict. An operator applying a policy of their own -- "I
|
|
758
|
+
// only accept id-data", or a profile that names its own type -- otherwise had to parse the
|
|
759
|
+
// message a second time to learn it, and a check that needs a second parse is a check most
|
|
760
|
+
// callers will not write.
|
|
761
|
+
var res = {
|
|
762
|
+
valid: signers.length > 0 && signers.every(function (s) { return s.ok === true; }),
|
|
763
|
+
eContentType: parsed.encapContentInfo.eContentType,
|
|
764
|
+
signers: signers,
|
|
765
|
+
};
|
|
715
766
|
return _applyTrust(res, parsedCerts, trustCfg).then(function () { return res; });
|
|
716
767
|
});
|
|
717
768
|
}
|
package/lib/crl-sign.js
CHANGED
|
@@ -144,7 +144,7 @@ function _resolveReason(reason, isDelta) {
|
|
|
144
144
|
// The AKI keyIdentifier from the issuer: an explicit Buffer, or true -> the issuer cert's subjectKeyIdentifier,
|
|
145
145
|
// else the SHA-1 of the issuer SPKI (RFC 5280 sec. 5.2.1 key-identifier method).
|
|
146
146
|
function _akiKeyId(val, ctx) {
|
|
147
|
-
if (Buffer.isBuffer(val)) return val;
|
|
147
|
+
if (Buffer.isBuffer(val)) return guard.bytes.snapshot(val, CrlError, "crl/bad-input", "the authorityKeyIdentifier keyIdentifier");
|
|
148
148
|
if (val === true) {
|
|
149
149
|
if (ctx.issuerCert) {
|
|
150
150
|
var ski = (ctx.issuerCert.extensions || []).filter(function (e) { return e.oid === OID_SKI; })[0];
|
|
@@ -507,7 +507,13 @@ function _sign(spec, issuer, opts) {
|
|
|
507
507
|
* }, { cert: signerCertDer, key: signerKeyPkcs8 });
|
|
508
508
|
* pki.schema.crl.parse(der).revokedCertificates[0].serialNumberHex; // "1234"
|
|
509
509
|
*/
|
|
510
|
-
function sign(spec, issuer, opts) {
|
|
510
|
+
function sign(spec, issuer, opts) {
|
|
511
|
+
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
512
|
+
// on the same call in x509-sign.
|
|
513
|
+
return guard.bytes.fixedCall(CrlError, "crl/bad-input", [
|
|
514
|
+
[spec, "the CRL spec"], [issuer, "the issuer"], [opts, "pki.crl.sign options"],
|
|
515
|
+
], _sign);
|
|
516
|
+
}
|
|
511
517
|
|
|
512
518
|
// A CRL these verbs answer from is re-derived from the bytes its parser read. Completeness -- every
|
|
513
519
|
// field present with the right type -- is not enough for a verdict: the signature covers a byte
|
|
@@ -598,7 +604,7 @@ function _issuerMaySign(parsed, cert) {
|
|
|
598
604
|
* { cert: signerCertDer, key: signerKeyPkcs8 });
|
|
599
605
|
* var ok = await pki.crl.verify(crlDer, { publicKey: signerSpki }); // true / false
|
|
600
606
|
*/
|
|
601
|
-
function verify(crl, issuer) { return
|
|
607
|
+
function verify(crl, issuer) { return guard.async.deferred(function () { return _verify(crl, issuer); }); }
|
|
602
608
|
function _verify(crl, issuer) {
|
|
603
609
|
var parsed = _coerceCrl(crl);
|
|
604
610
|
var resolved = _resolveIssuer(issuer);
|
package/lib/crmf-sign.js
CHANGED
|
@@ -238,7 +238,11 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
238
238
|
* pki.schema.crmf.parse(msg).messages[0].certReq.certTemplate.subject.dn; // "CN=device-42"
|
|
239
239
|
*/
|
|
240
240
|
function build(spec, key, opts) {
|
|
241
|
-
|
|
241
|
+
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
242
|
+
// on the same call in x509-sign.
|
|
243
|
+
return guard.bytes.fixedCall(CrmfError, "crmf/bad-input", [
|
|
244
|
+
[spec, "the certificate-request-message spec"], [key, "the signing key"], [opts, "pki.crmf.build options"],
|
|
245
|
+
], _build);
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
function _buildCertReqMsg(spec, key, opts) {
|
package/lib/csr-sign.js
CHANGED
|
@@ -101,7 +101,11 @@ function _challengePassword(pw) {
|
|
|
101
101
|
* pki.schema.csr.parse(req).subject.dn; // "CN=req.example.com"
|
|
102
102
|
*/
|
|
103
103
|
function sign(spec, key, opts) {
|
|
104
|
-
|
|
104
|
+
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
105
|
+
// on the same call in x509-sign.
|
|
106
|
+
return guard.bytes.fixedCall(CsrError, "csr/bad-input", [
|
|
107
|
+
[spec, "the certification-request spec"], [key, "the signing key"], [opts, "pki.csr.sign options"],
|
|
108
|
+
], _sign);
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function _sign(spec, key, opts) {
|
package/lib/est.js
CHANGED
|
@@ -826,7 +826,7 @@ var MAX_TIMEOUT = constants.TIME.seconds(600);
|
|
|
826
826
|
// The DER of a caller-supplied CSR: a DER Buffer as-is, or a PEM "CERTIFICATE REQUEST"
|
|
827
827
|
// decoded. Any other input is a config-time est/bad-input.
|
|
828
828
|
function _csrDer(input) {
|
|
829
|
-
if (Buffer.isBuffer(input)) return input;
|
|
829
|
+
if (Buffer.isBuffer(input)) return guard.bytes.snapshot(input, EstError, "est/bad-input", "a CSR");
|
|
830
830
|
if (typeof input === "string") return csr.pemDecode(input);
|
|
831
831
|
throw E("est/bad-input", "a CSR must be a DER Buffer or a PEM CERTIFICATE REQUEST string");
|
|
832
832
|
}
|
|
@@ -1428,7 +1428,7 @@ function _cmcSent(opts, der) {
|
|
|
1428
1428
|
// window this exists to close, open for exactly the inputs that came in by the wider door.
|
|
1429
1429
|
// A DataView is copied over its OWN window, not the whole backing buffer it happens to sit in.
|
|
1430
1430
|
function _copyBytes(v) {
|
|
1431
|
-
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return
|
|
1431
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, EstError, "est/bad-input", "a byte field of the request");
|
|
1432
1432
|
if (ArrayBuffer.isView(v)) return Buffer.from(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));
|
|
1433
1433
|
if (v instanceof ArrayBuffer) return Buffer.from(new Uint8Array(v));
|
|
1434
1434
|
return v;
|
|
@@ -1591,8 +1591,9 @@ function _shallowCopy(o) {
|
|
|
1591
1591
|
function _cmcRequestDer(request) {
|
|
1592
1592
|
// COPIED, not aliased: these bytes are parsed now (for the requested keys) and
|
|
1593
1593
|
// transmitted later, so sharing the caller's buffer would let the two disagree.
|
|
1594
|
-
if (Buffer.isBuffer(request)
|
|
1595
|
-
|
|
1594
|
+
if (Buffer.isBuffer(request) || request instanceof Uint8Array) {
|
|
1595
|
+
return guard.bytes.snapshot(request, EstError, "est/bad-input", "the Full PKI Request");
|
|
1596
|
+
}
|
|
1596
1597
|
if (typeof request === "string") return cms.pemDecode(request);
|
|
1597
1598
|
throw E("est/bad-input", "pki.est.fullcmc requires the Full PKI Request as DER bytes or a PEM CMS block");
|
|
1598
1599
|
}
|
package/lib/guard-all.js
CHANGED
|
@@ -65,6 +65,7 @@ var header = require("./guard-header");
|
|
|
65
65
|
var compress = require("./guard-compress");
|
|
66
66
|
var secret = require("./guard-secret");
|
|
67
67
|
var parsed = require("./guard-parsed");
|
|
68
|
+
var async_ = require("./guard-async");
|
|
68
69
|
|
|
69
70
|
module.exports = {
|
|
70
71
|
bytes: bytes,
|
|
@@ -81,4 +82,5 @@ module.exports = {
|
|
|
81
82
|
compress: compress,
|
|
82
83
|
secret: secret,
|
|
83
84
|
parsed: parsed,
|
|
85
|
+
async: async_,
|
|
84
86
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// @internal -- no operator-facing namespace. The documented surface is every verb whose
|
|
6
|
+
// @signature says `-> Promise<...>`; this is how each of them refuses.
|
|
7
|
+
//
|
|
8
|
+
// guard-async -- a verb documented as Promise-returning refuses by REJECTING, never by throwing.
|
|
9
|
+
//
|
|
10
|
+
// The failure is invisible at the call site, which is what makes it worth a choke point. An operator
|
|
11
|
+
// reads `-> Promise<...>` in the reference and writes the documented shape:
|
|
12
|
+
//
|
|
13
|
+
// pki.acme.newOrder(opts).catch(handleIt);
|
|
14
|
+
//
|
|
15
|
+
// A validation that runs BEFORE the promise is created throws straight past that `.catch`, so a
|
|
16
|
+
// misspelled option or a malformed input becomes an uncaught exception in code that already handles
|
|
17
|
+
// errors -- and nothing in the shape of the call tells the caller which verbs do that. Eleven verbs
|
|
18
|
+
// across five modules had it (pki.cms.verify / sign / countersign, pki.ocsp.sign, pki.tsp.sign and
|
|
19
|
+
// six pki.acme verbs), each having grown the same way: a cheap synchronous check added at the top of
|
|
20
|
+
// a function that returns a promise further down.
|
|
21
|
+
//
|
|
22
|
+
// What this does NOT change is WHEN the work happens. The body still runs synchronously, because
|
|
23
|
+
// several of these verbs must resolve a caller's mutable options object before any turn passes --
|
|
24
|
+
// reading a key, a nonce, or a request's bytes a turn later is a different value than the one that
|
|
25
|
+
// was checked. Only the exit changes: a fault leaves as a rejection instead of a throw.
|
|
26
|
+
//
|
|
27
|
+
// @enforced-by behavioral -- the rule has no rename-proof code shape (it is the ABSENCE of a wrapper
|
|
28
|
+
// around a synchronous prefix, which no lexical pattern can see). The guard is the derived test
|
|
29
|
+
// test/layer-0-primitives/promise-contract.test.js, which reads every `-> Promise<` @signature out
|
|
30
|
+
// of lib/ and calls each verb to prove the refusal arrives as a rejection -- so a new verb is in
|
|
31
|
+
// scope the day it is documented, and no list here can go stale.
|
|
32
|
+
function deferred(body) {
|
|
33
|
+
try { return Promise.resolve(body()); }
|
|
34
|
+
catch (e) { return Promise.reject(e); }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { deferred: deferred };
|