@blamejs/pki 0.9.1 → 0.9.4
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 +37 -0
- package/MIGRATING.md +57 -0
- package/README.md +5 -5
- package/lib/alt-sig.js +3 -3
- package/lib/attrcert-sign.js +5 -5
- package/lib/cmc-verify.js +13 -9
- package/lib/cmp-build.js +2 -2
- package/lib/cmp-verify.js +1 -1
- package/lib/cms-sign.js +21 -0
- package/lib/cms-verify.js +5 -3
- package/lib/crl-sign.js +1 -1
- package/lib/crmf-sign.js +1 -1
- package/lib/csr-sign.js +4 -2
- package/lib/est.js +4 -4
- package/lib/guard-identifier.js +110 -0
- package/lib/guard-name.js +91 -4
- package/lib/inspect.js +4 -4
- package/lib/lint.js +55 -8
- package/lib/ocsp-verify.js +1 -1
- package/lib/pki-build.js +14 -0
- package/lib/possession.js +20 -14
- package/lib/related-cert.js +72 -11
- package/lib/schema-attrcert.js +5 -5
- package/lib/schema-c509.js +42 -6
- package/lib/schema-cmp.js +3 -3
- package/lib/schema-pkix.js +62 -3
- package/lib/schema-trustanchor.js +4 -4
- package/lib/sigstore.js +1 -1
- package/lib/smime.js +18 -2
- package/lib/trust.js +1 -1
- package/lib/trustanchor-build.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/related-cert.js
CHANGED
|
@@ -99,6 +99,33 @@ function _certIdParts(der) {
|
|
|
99
99
|
var DIGEST_LENGTHS = pkix.digestLengths();
|
|
100
100
|
var SIG_OID_DIGEST = pkix.signatureDigestNames(oid);
|
|
101
101
|
|
|
102
|
+
/** @internal Whether the related certificate's key may make this one signature. RFC 5280 sec. 4.2.1.3
|
|
103
|
+
* confines a key to the purposes a present keyUsage asserts, so a certificate issued only to sign
|
|
104
|
+
* certificates or CRLs does not authorize a proof of possession over a certification request.
|
|
105
|
+
*
|
|
106
|
+
* The set is NOT `pki.possession`'s. RFC 9763 sec. 3.1 states that "if the related certificate is a
|
|
107
|
+
* key establishment certificate (e.g., using RSA key transport or Elliptic Curve Cryptography (ECC)
|
|
108
|
+
* key agreement), the private key is used to sign one time for proof of possession (POP)", so
|
|
109
|
+
* keyEncipherment and keyAgreement authorize this signature where they authorize no other, and
|
|
110
|
+
* holding the proof to possession's signing-only set would refuse a request the document admits.
|
|
111
|
+
* What is left to refuse is a certificate confined to keyCertSign, cRLSign, or the two modifier bits
|
|
112
|
+
* that carry no meaning without keyAgreement. An absent keyUsage constrains nothing.
|
|
113
|
+
*
|
|
114
|
+
* Which establishment bit counts comes from the SUBJECT KEY, because the sentence admits a key
|
|
115
|
+
* establishment CERTIFICATE and a bit the key's own family cannot perform does not make one. The
|
|
116
|
+
* union of both bits admitted a certificate that is not an establishment certificate under any
|
|
117
|
+
* reading: measured, an EC certificate asserting only keyEncipherment, which RFC 8813 sec. 3 makes a
|
|
118
|
+
* MUST NOT for id-ecPublicKey, and an RSA certificate asserting only keyAgreement, which RFC 3279
|
|
119
|
+
* sec. 2.3.1 does not list, both reported signerMaySign and a valid proof. A family the shared table
|
|
120
|
+
* places in neither column admits neither bit, so a signature usage is what authorizes it. */
|
|
121
|
+
var SIGNATURE_USAGE_BITS = ["digitalSignature", "nonRepudiation"];
|
|
122
|
+
var _estabBit = pkix.keyEstablishmentBit(NS);
|
|
123
|
+
var _maySign = pkix.keyUsagePermits(NS, function (parsedCert) {
|
|
124
|
+
var spki = parsedCert && parsedCert.subjectPublicKeyInfo;
|
|
125
|
+
var bit = _estabBit(spki && spki.algorithm ? spki.algorithm.oid : null);
|
|
126
|
+
return bit === null ? SIGNATURE_USAGE_BITS : [bit, "digitalSignature", "nonRepudiation"];
|
|
127
|
+
});
|
|
128
|
+
|
|
102
129
|
/** @internal The digests this module will bind a certificate identity to. sha1 is registered and
|
|
103
130
|
* decodable, and is absent here: a chosen-prefix collision on a certificate is a demonstrated
|
|
104
131
|
* attack, so a value naming it is refused before anything is computed. A caller reading a hashValue a
|
|
@@ -392,7 +419,7 @@ function _parseCert(certDer, what) {
|
|
|
392
419
|
* @example
|
|
393
420
|
* var kp = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
|
|
394
421
|
* var spki = await pki.key.export(kp.publicKey, { format: "der" });
|
|
395
|
-
* var related = await pki.x509.sign({ subject: "
|
|
422
|
+
* var related = await pki.x509.sign({ subject: "Held", subjectPublicKey: spki, serialNumber: 42n,
|
|
396
423
|
* notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z") }, { key: kp.privateKey });
|
|
397
424
|
* pki.relatedCert.certificateHash(related).hashAlgorithm; // -> "sha256", from this certificate's signature OID
|
|
398
425
|
* pki.relatedCert.certificateHash(related, "sha512").hashValue.length; // -> 64
|
|
@@ -455,9 +482,9 @@ function digestValue(hashAlgorithm, hashValue) {
|
|
|
455
482
|
* @example
|
|
456
483
|
* var kp = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
|
|
457
484
|
* var spki = await pki.key.export(kp.publicKey, { format: "der" });
|
|
458
|
-
* var related = await pki.x509.sign({ subject: "
|
|
485
|
+
* var related = await pki.x509.sign({ subject: "Held", subjectPublicKey: spki, serialNumber: 42n,
|
|
459
486
|
* notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z") }, { key: kp.privateKey });
|
|
460
|
-
* var leaf = await pki.x509.sign({ subject: "
|
|
487
|
+
* var leaf = await pki.x509.sign({ subject: "New Key", subjectPublicKey: spki, serialNumber: 43n,
|
|
461
488
|
* notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z"),
|
|
462
489
|
* extensions: { relatedCertificate: { relatedCertificate: related } } }, { key: kp.privateKey });
|
|
463
490
|
* var ext = pki.schema.x509.parse(leaf).extensions
|
|
@@ -563,7 +590,7 @@ function _namedAlgorithm(value) {
|
|
|
563
590
|
|
|
564
591
|
/**
|
|
565
592
|
* @primitive pki.relatedCert.verifyRequest
|
|
566
|
-
* @signature pki.relatedCert.verifyRequest(requesterCertificate, certificate, opts?) -> Promise<
|
|
593
|
+
* @signature pki.relatedCert.verifyRequest(requesterCertificate, certificate, opts?) -> Promise<verdict>
|
|
567
594
|
* @since 0.8.42
|
|
568
595
|
* @status stable
|
|
569
596
|
* @spec RFC 9763 sec. 3.1
|
|
@@ -572,9 +599,18 @@ function _namedAlgorithm(value) {
|
|
|
572
599
|
*
|
|
573
600
|
* Verify the proof of possession in a `relatedCertRequest` CSR attribute. `requesterCertificate` is
|
|
574
601
|
* the value `pki.schema.csr.parse` carries on that attribute, and `certificate` is the DER of the
|
|
575
|
-
* certificate its `certID` names.
|
|
576
|
-
*
|
|
577
|
-
* `
|
|
602
|
+
* certificate its `certID` names.
|
|
603
|
+
*
|
|
604
|
+
* Returns `{ valid, verified, signerMaySign, signatureAlgorithm }`, plus `code` and `reason` when it
|
|
605
|
+
* did not pass. `verified` is the signature over the bytes
|
|
606
|
+
* `pki.relatedCert.requestSignedData` describes, checked under that certificate's subject key.
|
|
607
|
+
* `signerMaySign` is whether the certificate's keyUsage authorizes that signature at all, and
|
|
608
|
+
* `valid` is both together. They are separate because a key the certificate never authorized for
|
|
609
|
+
* signing and a signature that does not verify are different facts, and a caller acting on one as
|
|
610
|
+
* if it were the other acts on the wrong one. RFC 5280 sec. 4.2.1.3 confines a key to the purposes a
|
|
611
|
+
* present keyUsage asserts; a certificate carrying none is unconstrained. A key-establishment
|
|
612
|
+
* certificate still passes, because RFC 9763 sec. 3.1 has such a key "sign one time for proof of
|
|
613
|
+
* possession", so keyEncipherment and keyAgreement authorize this signature and no other.
|
|
578
614
|
*
|
|
579
615
|
* The certificate handed in must be the one `certID` names: an issuer and serial that do not match
|
|
580
616
|
* throw `relatedcert/cert-mismatch`, since verifying a proof against a certificate the request did
|
|
@@ -614,7 +650,7 @@ function _namedAlgorithm(value) {
|
|
|
614
650
|
* @example
|
|
615
651
|
* var kp = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
|
|
616
652
|
* var spki = await pki.key.export(kp.publicKey, { format: "der" });
|
|
617
|
-
* var held = await pki.x509.sign({ subject: "
|
|
653
|
+
* var held = await pki.x509.sign({ subject: "Held", subjectPublicKey: spki, serialNumber: 42n,
|
|
618
654
|
* notBefore: new Date("2027-01-01T00:00:00Z"), notAfter: new Date("2028-01-01T00:00:00Z") }, { key: kp.privateKey });
|
|
619
655
|
* var parsed = pki.schema.x509.parse(held);
|
|
620
656
|
* var certID = { issuer: parsed.issuer.bytes, serialNumber: parsed.serialNumber };
|
|
@@ -627,7 +663,7 @@ function _namedAlgorithm(value) {
|
|
|
627
663
|
* pki.asn1.build.integer(BigInt("0x" + raw.subarray(0, half).toString("hex"))),
|
|
628
664
|
* pki.asn1.build.integer(BigInt("0x" + raw.subarray(half).toString("hex"))),
|
|
629
665
|
* ]);
|
|
630
|
-
* var csr = await pki.csr.sign({ subject: "
|
|
666
|
+
* var csr = await pki.csr.sign({ subject: "New Key", subjectPublicKey: spki,
|
|
631
667
|
* relatedCertRequest: { certID: certID, requestTime: 1800000000,
|
|
632
668
|
* locationInfo: ["https://certs.example/held.cer"], signature: proof } }, { key: kp.privateKey });
|
|
633
669
|
* var attr = pki.schema.csr.parse(csr).attributes
|
|
@@ -635,7 +671,7 @@ function _namedAlgorithm(value) {
|
|
|
635
671
|
* // The window is the certification authority's policy, so it is named: five minutes here, judged
|
|
636
672
|
* // against the instant the request was made, which is what a CA reading a recorded request does.
|
|
637
673
|
* await pki.relatedCert.verifyRequest(attr.relatedCertRequest, held,
|
|
638
|
-
* { maxAge: 300, at: new Date(1800000060 * 1000) }); // -> true
|
|
674
|
+
* { maxAge: 300, at: new Date(1800000060 * 1000) }); // -> { valid: true, verified: true, ... }
|
|
639
675
|
*/
|
|
640
676
|
function verifyRequest(requesterCertificate, certificate, opts) {
|
|
641
677
|
/** @internal Every refusal reaches the caller as a rejection, so a verb that returns a promise never
|
|
@@ -680,6 +716,8 @@ function _verifyRequest(requesterCertificate, certificate, opts) {
|
|
|
680
716
|
"the certificate given is not the one certID names (RFC 9763 sec. 3.2)");
|
|
681
717
|
}
|
|
682
718
|
|
|
719
|
+
var signerMaySign = _maySign(c.parsed);
|
|
720
|
+
|
|
683
721
|
var sigAlg = o.signatureAlgorithm !== undefined && o.signatureAlgorithm !== null
|
|
684
722
|
? _namedAlgorithm(o.signatureAlgorithm)
|
|
685
723
|
: _proofAlgorithm(c.parsed, o.digestAlgorithm);
|
|
@@ -694,7 +732,30 @@ function _verifyRequest(requesterCertificate, certificate, opts) {
|
|
|
694
732
|
}
|
|
695
733
|
if (sig.bytes.length === 0) throw _err("relatedcert/bad-input", "the proof signature must not be empty");
|
|
696
734
|
|
|
697
|
-
|
|
735
|
+
/** @internal The two facts are reported apart. A bare boolean could not say which check failed, so
|
|
736
|
+
* a key the certificate never authorized for signing and a signature that simply does not verify
|
|
737
|
+
* read the same, and a caller acting on one as if it were the other acts on the wrong fact. */
|
|
738
|
+
return intrinsic.chain(_seam.verify(sigAlg, sig, c.parsed.subjectPublicKeyInfo.bytes, preimage), function (verified) {
|
|
739
|
+
var ok = verified === true && signerMaySign;
|
|
740
|
+
/** @internal The verdict carries what this verb DETERMINED, not an echo of what it was handed.
|
|
741
|
+
* The caller already holds `certID` and `requestTime` on the request it passed in, and copying
|
|
742
|
+
* the decoded issuer onto the result would invite a byte comparison against a name this verb
|
|
743
|
+
* judged equal under the RFC 5280 sec. 7.1 folding: two names that compare equal here are
|
|
744
|
+
* routinely different bytes, and there is no public comparison a caller could use instead.
|
|
745
|
+
* `signatureAlgorithm` is kept because it is derived rather than supplied, so without it a
|
|
746
|
+
* caller cannot tell which digest the proof was actually checked under. */
|
|
747
|
+
return guard.verdict.of({
|
|
748
|
+
valid: ok,
|
|
749
|
+
verified: verified === true,
|
|
750
|
+
signerMaySign: signerMaySign,
|
|
751
|
+
signatureAlgorithm: sigAlg,
|
|
752
|
+
}, ok ? null : {
|
|
753
|
+
code: !signerMaySign ? "relatedcert/signer-may-not-sign" : "relatedcert/bad-signature",
|
|
754
|
+
reason: !signerMaySign
|
|
755
|
+
? "the related certificate's keyUsage does not authorize a proof-of-possession signature (RFC 5280 sec. 4.2.1.3)"
|
|
756
|
+
: "the proof signature does not verify under the key of the certificate certID names",
|
|
757
|
+
});
|
|
758
|
+
});
|
|
698
759
|
}
|
|
699
760
|
|
|
700
761
|
module.exports = {
|
package/lib/schema-attrcert.js
CHANGED
|
@@ -293,7 +293,7 @@ var ATTRIBUTE_CERTIFICATE = pkix.signedEnvelope(NS, ACINFO, {
|
|
|
293
293
|
* var aaCert = await pki.x509.sign({ subject: "Attribute Authority", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
294
294
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
295
295
|
* var der = await pki.attrcert.sign(
|
|
296
|
-
* { holder: { entityName: { directoryName: "
|
|
296
|
+
* { holder: { entityName: { directoryName: "Alice" } },
|
|
297
297
|
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
298
298
|
* attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
|
|
299
299
|
* { cert: aaCert, key: key });
|
|
@@ -350,7 +350,7 @@ function parseV1(input, callerOpts) {
|
|
|
350
350
|
* var aaCert = await pki.x509.sign({ subject: "Attribute Authority", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
351
351
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
352
352
|
* var pemText = await pki.attrcert.sign(
|
|
353
|
-
* { holder: { entityName: { directoryName: "
|
|
353
|
+
* { holder: { entityName: { directoryName: "Alice" } },
|
|
354
354
|
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
355
355
|
* attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
|
|
356
356
|
* { cert: aaCert, key: key }, { pem: true });
|
|
@@ -375,7 +375,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label === null ? n
|
|
|
375
375
|
* var aaCert = await pki.x509.sign({ subject: "Attribute Authority", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
376
376
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
377
377
|
* var der = await pki.attrcert.sign(
|
|
378
|
-
* { holder: { entityName: { directoryName: "
|
|
378
|
+
* { holder: { entityName: { directoryName: "Alice" } },
|
|
379
379
|
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
380
380
|
* attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
|
|
381
381
|
* { cert: aaCert, key: key });
|
|
@@ -700,7 +700,7 @@ var DOOR = pkix.extensionDoor({
|
|
|
700
700
|
* var key = await pki.key.export(pair.privateKey);
|
|
701
701
|
* var aaCert = await pki.x509.sign({ subject: "Example AA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
702
702
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
703
|
-
* var acDer = await pki.attrcert.sign({ holder: { entityName: [{ directoryName: "
|
|
703
|
+
* var acDer = await pki.attrcert.sign({ holder: { entityName: [{ directoryName: "Holder" }] },
|
|
704
704
|
* attributes: { role: { roleName: "https://example.test/role/admin" } }, serialNumber: 1n,
|
|
705
705
|
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
706
706
|
* extensions: { noRevAvail: true } }, { cert: aaCert, key: key });
|
|
@@ -729,7 +729,7 @@ var decodeExtensions = DOOR.decodeExtensions;
|
|
|
729
729
|
* var key = await pki.key.export(pair.privateKey);
|
|
730
730
|
* var aaCert = await pki.x509.sign({ subject: "Example AA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
731
731
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
732
|
-
* var acDer = await pki.attrcert.sign({ holder: { entityName: [{ directoryName: "
|
|
732
|
+
* var acDer = await pki.attrcert.sign({ holder: { entityName: [{ directoryName: "Holder" }] },
|
|
733
733
|
* attributes: { role: { roleName: "https://example.test/role/admin" } }, serialNumber: 1n,
|
|
734
734
|
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
735
735
|
* extensions: { noRevAvail: true } }, { cert: aaCert, key: key });
|
package/lib/schema-c509.js
CHANGED
|
@@ -1590,6 +1590,12 @@ function _reconstructDer(r, sigNode) {
|
|
|
1590
1590
|
* reached by an explicit call and is not auto-routed by pki.schema.parse. The type-2 signedData and the
|
|
1591
1591
|
* raw signature are surfaced as-is (a native verifier hashes them without re-serialization).
|
|
1592
1592
|
*
|
|
1593
|
+
* A type-3 ECDSA signature is a fixed-width `r || s` that names no curve, so the same pair padded to a
|
|
1594
|
+
* wider accepted width reconstructs byte-identical DER: 64, 96 and 132 are three spellings of one
|
|
1595
|
+
* certificate. `signatureValueMinimalWidth` reports the narrowest width that represents the value, and
|
|
1596
|
+
* does not fail the parse. A reader that deduplicates, caches or blocklists by encoded form compares it
|
|
1597
|
+
* against `signatureValue.length`, and re-narrows to it for a key that is the same for every spelling.
|
|
1598
|
+
*
|
|
1593
1599
|
* @example
|
|
1594
1600
|
* // the RFC 7925 profiled certificate from draft-ietf-cose-cbor-encoded-cert Appendix A.1 (type 3)
|
|
1595
1601
|
* var bytes = Buffer.from(
|
|
@@ -1703,7 +1709,10 @@ function _finishParse(f, root, nested, fields) {
|
|
|
1703
1709
|
result._fieldBytes = root.bytes.subarray(1, root.bytes.length - f[10].bytes.length);
|
|
1704
1710
|
if (type === 2) result.signedData = result._fieldBytes;
|
|
1705
1711
|
|
|
1706
|
-
if (type === 3)
|
|
1712
|
+
if (type === 3) {
|
|
1713
|
+
result.reconstructedDer = _reconstructDer(result, f[10]);
|
|
1714
|
+
result.signatureValueMinimalWidth = _minimalSigWidth(signatureValue);
|
|
1715
|
+
}
|
|
1707
1716
|
return result;
|
|
1708
1717
|
}
|
|
1709
1718
|
|
|
@@ -1960,11 +1969,37 @@ function _keyUsageBitsFromDer(extnValue, ctx) {
|
|
|
1960
1969
|
var _NO_EXPIRY = C.TIME.seconds(Number(NO_EXPIRY_SECONDS));
|
|
1961
1970
|
var NODE_TO_WEBCRYPTO = Object.assign(Object.create(null), { "prime256v1": "P-256", "secp384r1": "P-384", "secp521r1": "P-521" });
|
|
1962
1971
|
var WEBCRYPTO_FIELD_BYTES = Object.assign(Object.create(null), { "P-256": 32, "P-384": 48, "P-521": 66 });
|
|
1972
|
+
/** @internal The accepted fixed-width `r || s` lengths, derived from the field sizes above rather
|
|
1973
|
+
* than written a fourth time. */
|
|
1974
|
+
var EC_SIG_WIDTHS = Object.keys(WEBCRYPTO_FIELD_BYTES).map(function (n) { return WEBCRYPTO_FIELD_BYTES[n] * 2; });
|
|
1963
1975
|
var SIG_ALG_TO_CURVE = Object.assign(Object.create(null), { "ecdsaWithSHA256": "P-256", "ecdsaWithSHA384": "P-384", "ecdsaWithSHA512": "P-521" });
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1976
|
+
/** @internal The significant byte count of a big-endian magnitude in `buf[from, to)`: the leading
|
|
1977
|
+
* zero bytes do not change the value they encode. Taken as a range so the fixed-width halves of an
|
|
1978
|
+
* r||s signature and a decoded INTEGER's content are measured by the one scan. */
|
|
1979
|
+
function _magBytes(buf, from, to) {
|
|
1980
|
+
var i = from;
|
|
1981
|
+
while (i < to - 1 && buf[i] === 0x00) i++;
|
|
1982
|
+
return to - i;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/** @internal The narrowest accepted r||s width that encodes the same (r, s) as `sig`. A fixed-width
|
|
1986
|
+
* r||s carries no curve, so padding each half of a 64-byte signature to 32 more bytes reconstructs
|
|
1987
|
+
* byte-identical DER: 64, 96 and 132 are three spellings of one certificate, which makes a C509 byte
|
|
1988
|
+
* string something other than a unique identifier for what it encodes. Neither the width nor the
|
|
1989
|
+
* issuer's curve is recoverable from the bytes, and `parse` takes no option to be told, so this is
|
|
1990
|
+
* reported and the caller judges. The widths come from the field-size table rather than a fourth
|
|
1991
|
+
* copy of 32/48/66. */
|
|
1992
|
+
function _minimalSigWidth(sig) {
|
|
1993
|
+
var half = sig.length / 2;
|
|
1994
|
+
var need = _magBytes(sig, 0, half);
|
|
1995
|
+
var other = _magBytes(sig, half, sig.length);
|
|
1996
|
+
if (other > need) need = other;
|
|
1997
|
+
var narrowest = sig.length;
|
|
1998
|
+
for (var i = 0; i < EC_SIG_WIDTHS.length; i++) {
|
|
1999
|
+
var width = EC_SIG_WIDTHS[i];
|
|
2000
|
+
if (width >= need * 2 && width <= sig.length && width < narrowest) narrowest = width;
|
|
2001
|
+
}
|
|
2002
|
+
return narrowest;
|
|
1968
2003
|
}
|
|
1969
2004
|
function _sigMagWidth(derSig) {
|
|
1970
2005
|
var n;
|
|
@@ -1975,7 +2010,8 @@ function _sigMagWidth(derSig) {
|
|
|
1975
2010
|
var ch = n.children[i];
|
|
1976
2011
|
if (ch.tagNumber !== asn1.TAGS.INTEGER || ch.tagClass !== "universal" || ch.constructed) throw _err("c509/bad-signature", "the ECDSA issuer signature r and s must be universal INTEGERs");
|
|
1977
2012
|
}
|
|
1978
|
-
|
|
2013
|
+
var r0 = n.children[0].content, s0 = n.children[1].content;
|
|
2014
|
+
return Math.max(_magBytes(r0, 0, r0.length), _magBytes(s0, 0, s0.length));
|
|
1979
2015
|
}
|
|
1980
2016
|
var CURVE_ORDER = ["P-256", "P-384", "P-521"];
|
|
1981
2017
|
function _minCurveForMag(mag) {
|
package/lib/schema-cmp.js
CHANGED
|
@@ -803,7 +803,7 @@ var readName = _valueParser("Name", pkix.name(NS));
|
|
|
803
803
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
804
804
|
* var csrDer = await pki.csr.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey) }, { key: key });
|
|
805
805
|
* var der = await pki.cmp.build(
|
|
806
|
-
* { header: { sender: { directoryName: "
|
|
806
|
+
* { header: { sender: { directoryName: "client" }, recipient: { directoryName: "CA" } },
|
|
807
807
|
* body: { p10cr: csrDer } }, { key: key, cert: cert });
|
|
808
808
|
* var m = pki.schema.cmp.parse(der);
|
|
809
809
|
* m.body.arm; // "ir", "ip", "error", ...
|
|
@@ -837,7 +837,7 @@ var parse = pkix.makeParser({
|
|
|
837
837
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
838
838
|
* var csrDer = await pki.csr.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey) }, { key: key });
|
|
839
839
|
* var msg = await pki.cmp.build(
|
|
840
|
-
* { header: { sender: { directoryName: "
|
|
840
|
+
* { header: { sender: { directoryName: "client" }, recipient: { directoryName: "CA" } },
|
|
841
841
|
* body: { p10cr: csrDer } }, { key: key, cert: cert });
|
|
842
842
|
* var pemText = pki.schema.cmp.pemEncode(msg);
|
|
843
843
|
* var der = pki.schema.cmp.pemDecode(pemText);
|
|
@@ -861,7 +861,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "CMP", Pe
|
|
|
861
861
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
862
862
|
* var csrDer = await pki.csr.sign({ subject: "client", subjectPublicKey: await pki.key.export(pair.publicKey) }, { key: key });
|
|
863
863
|
* var der = await pki.cmp.build(
|
|
864
|
-
* { header: { sender: { directoryName: "
|
|
864
|
+
* { header: { sender: { directoryName: "client" }, recipient: { directoryName: "CA" } },
|
|
865
865
|
* body: { p10cr: csrDer } }, { key: key, cert: cert });
|
|
866
866
|
* var pem = pki.schema.cmp.pemEncode(der);
|
|
867
867
|
*/
|
package/lib/schema-pkix.js
CHANGED
|
@@ -1811,6 +1811,12 @@ function _acceptExtensionRecord(ext, E, code, key) {
|
|
|
1811
1811
|
function keyUsagePermits(ns, allowed) {
|
|
1812
1812
|
var kuOid = ns.oid.byName("keyUsage");
|
|
1813
1813
|
var decoders = certExtensionDecoders(ns).byOid;
|
|
1814
|
+
/** @internal `allowed` may be a FUNCTION of the certificate, for a caller whose permitted set
|
|
1815
|
+
* depends on the subject key: an establishment bit authorizes nothing where the key's own family
|
|
1816
|
+
* cannot perform that establishment mechanism, and the certificate is the only place that says
|
|
1817
|
+
* which family it is. Every name the set names must be one the KeyUsage decoder defines, since
|
|
1818
|
+
* the record it answers from inherits nothing. */
|
|
1819
|
+
var perCert = typeof allowed === "function";
|
|
1814
1820
|
return function (parsedCert) {
|
|
1815
1821
|
var exts = (parsedCert && parsedCert.extensions) || [];
|
|
1816
1822
|
for (var i = 0; i < exts.length; i++) {
|
|
@@ -1818,13 +1824,60 @@ function keyUsagePermits(ns, allowed) {
|
|
|
1818
1824
|
var ku;
|
|
1819
1825
|
try { ku = decoders[kuOid](exts[i].value); }
|
|
1820
1826
|
catch (e) { void e; return false; }
|
|
1821
|
-
|
|
1827
|
+
var names = perCert ? allowed(parsedCert) : allowed;
|
|
1828
|
+
for (var a = 0; a < names.length; a++) if (ku[names[a]] === true) return true;
|
|
1822
1829
|
return false;
|
|
1823
1830
|
}
|
|
1824
1831
|
return true;
|
|
1825
1832
|
};
|
|
1826
1833
|
}
|
|
1827
1834
|
|
|
1835
|
+
/** @internal Which key-establishment bit a subject key's own family can carry, as one table the
|
|
1836
|
+
* callers that read an establishment usage share. The governing documents state it per family:
|
|
1837
|
+
* RFC 3279 sec. 2.3.1 lists keyEncipherment for an RSA key and does not list keyAgreement; RFC 5480
|
|
1838
|
+
* sec. 3 lists keyAgreement for id-ecPublicKey, id-ecDH and id-ecMQV, and RFC 8813 sec. 3 makes
|
|
1839
|
+
* keyEncipherment and dataEncipherment a MUST NOT for all three; RFC 8410 sec. 5 makes keyAgreement
|
|
1840
|
+
* a MUST for id-X25519 and id-X448; and RFC 9935 sec. 5 has an ML-KEM certificate assert
|
|
1841
|
+
* keyEncipherment as its only usage.
|
|
1842
|
+
*
|
|
1843
|
+
* A family in NEITHER column answers null, which is the refusing direction: an establishment bit
|
|
1844
|
+
* then authorizes nothing, and a signature bit is what the certificate has to assert. */
|
|
1845
|
+
var KEY_TRANSPORT_KEY_NAMES = ["rsaEncryption", "rsaesOaep"];
|
|
1846
|
+
/** @internal The three FIPS 203 sets and the composite KEM arms, matched by the prefix the registry
|
|
1847
|
+
* files them under rather than listed one at a time, so a set the registry gains later belongs to
|
|
1848
|
+
* this rule without a second edit. */
|
|
1849
|
+
var KEY_TRANSPORT_KEY_PREFIXES = ["id-ml-kem-", "id-MLKEM"];
|
|
1850
|
+
var KEY_AGREEMENT_KEY_NAMES = ["ecPublicKey", "X25519", "X448", "dhpublicnumber"];
|
|
1851
|
+
|
|
1852
|
+
var _estabBitByNs = new _WeakMapI();
|
|
1853
|
+
|
|
1854
|
+
function keyEstablishmentBit(ns) {
|
|
1855
|
+
var table = _weakGet(_estabBitByNs, ns.oid);
|
|
1856
|
+
if (table === undefined) {
|
|
1857
|
+
table = _assign(_create(null), {});
|
|
1858
|
+
var put = function (bit) {
|
|
1859
|
+
return function (n) {
|
|
1860
|
+
var dotted = ns.oid.byName(n);
|
|
1861
|
+
if (dotted !== undefined && dotted !== null) table[dotted] = bit;
|
|
1862
|
+
};
|
|
1863
|
+
};
|
|
1864
|
+
_forEach(KEY_TRANSPORT_KEY_NAMES, put("keyEncipherment"));
|
|
1865
|
+
_forEach(KEY_AGREEMENT_KEY_NAMES, put("keyAgreement"));
|
|
1866
|
+
var registry = ns.oid.all();
|
|
1867
|
+
_forEach(intrinsic.keys(registry), function (dotted) {
|
|
1868
|
+
var n = registry[dotted];
|
|
1869
|
+
if (typeof n !== "string") return;
|
|
1870
|
+
_forEach(KEY_TRANSPORT_KEY_PREFIXES, function (p) {
|
|
1871
|
+
if (_strIndexOf(n, p) === 0) table[dotted] = "keyEncipherment";
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
_weakSet(_estabBitByNs, ns.oid, table);
|
|
1875
|
+
}
|
|
1876
|
+
return function (dotted) {
|
|
1877
|
+
return typeof dotted === "string" && _hasOwn(table, dotted) ? table[dotted] : null;
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1828
1881
|
/** @internal The subject-key algorithms that can only ever SIGN, as one table the linter and the verifier
|
|
1829
1882
|
* share. RFC 9883 sec. 6 states "The privateKeyPossessionStatement attribute MUST NOT be used to obtain a
|
|
1830
1883
|
* signature certificate", and sec. 4 states the same requirement as a property of the request: "the
|
|
@@ -1912,8 +1965,13 @@ function certExtensionDecoders(ns) {
|
|
|
1912
1965
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
1913
1966
|
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
|
|
1914
1967
|
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
|
|
1915
|
-
|
|
1916
|
-
|
|
1968
|
+
/** @internal The record inherits nothing. The exemption this carried rested on its keys being
|
|
1969
|
+
* the fixed KU_BITS names, and `keyUsagePermits` is called with a name that is NOT one of them:
|
|
1970
|
+
* `contentCommitment` is the X.509 spelling of the nonRepudiation bit and the BUILDER accepts
|
|
1971
|
+
* it, so an authorization gate asked for a name this decoder never defines and whatever
|
|
1972
|
+
* answered for it decided. Measured: a certificate confined to cRLSign went from maySign=false
|
|
1973
|
+
* to maySign=true under one assignment to `Object.prototype.contentCommitment`. */
|
|
1974
|
+
var out = _create(null);
|
|
1917
1975
|
_forEach(KU_BITS, function (nm, bit) {
|
|
1918
1976
|
var byte = bit >> 3, mask = 0x80 >> (bit & 7);
|
|
1919
1977
|
out[nm] = byte < bs.bytes.length && (bs.bytes[byte] & mask) !== 0;
|
|
@@ -2725,6 +2783,7 @@ module.exports = intrinsic.freeze({
|
|
|
2725
2783
|
crlEntryFixedCriticality: crlEntryFixedCriticality,
|
|
2726
2784
|
certExtensionDecoders: certExtensionDecoders,
|
|
2727
2785
|
keyUsagePermits: keyUsagePermits,
|
|
2786
|
+
keyEstablishmentBit: keyEstablishmentBit,
|
|
2728
2787
|
signatureOnlyKeyOid: signatureOnlyKeyOid,
|
|
2729
2788
|
scopedCertDecoder: scopedCertDecoder,
|
|
2730
2789
|
crlExtensionDecoders: crlExtensionDecoders,
|
|
@@ -359,7 +359,7 @@ var TRUST_ANCHOR_LIST = schema.seqOf(TRUST_ANCHOR_CHOICE, {
|
|
|
359
359
|
* var pair = await pki.key.generate("Ed25519");
|
|
360
360
|
* var spki = await pki.key.export(pair.publicKey);
|
|
361
361
|
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
362
|
-
* pubKey: spki, keyId: Buffer.alloc(20, 1), certPath: { taName: "
|
|
362
|
+
* pubKey: spki, keyId: Buffer.alloc(20, 1), certPath: { taName: "Example Root" } } }] });
|
|
363
363
|
* pki.schema.trustanchor.parse(der).anchors[0].kind; // -> "taInfo"
|
|
364
364
|
*/
|
|
365
365
|
var parse = pkix.makeParser({
|
|
@@ -385,7 +385,7 @@ var parse = pkix.makeParser({
|
|
|
385
385
|
* var pair = await pki.key.generate("Ed25519");
|
|
386
386
|
* var spki = await pki.key.export(pair.publicKey);
|
|
387
387
|
* var list = pki.schema.trustanchor.parse(pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
388
|
-
* pubKey: spki, keyId: Buffer.alloc(20, 1), certPath: { taName: "
|
|
388
|
+
* pubKey: spki, keyId: Buffer.alloc(20, 1), certPath: { taName: "Example Root" } } }] }));
|
|
389
389
|
* list.anchors[0].taInfo.certPath.taName.dn; // -> "CN=Example Root"
|
|
390
390
|
*/
|
|
391
391
|
var parseInfo = pkix.makeParser({
|
|
@@ -407,7 +407,7 @@ var parseInfo = pkix.makeParser({
|
|
|
407
407
|
* var pair = await pki.key.generate("Ed25519");
|
|
408
408
|
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
409
409
|
* pubKey: await pki.key.export(pair.publicKey), keyId: Buffer.alloc(20, 1),
|
|
410
|
-
* certPath: { taName: "
|
|
410
|
+
* certPath: { taName: "Example Root" } } }] });
|
|
411
411
|
* pki.schema.trustanchor.pemDecode(pki.schema.trustanchor.pemEncode(der)).equals(der); // -> true
|
|
412
412
|
*/
|
|
413
413
|
function pemDecode(text, label) { return pkix.pemDecode(text, label || "TRUST ANCHOR LIST", PemError); }
|
|
@@ -426,7 +426,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "TRUST AN
|
|
|
426
426
|
* var pair = await pki.key.generate("Ed25519");
|
|
427
427
|
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
428
428
|
* pubKey: await pki.key.export(pair.publicKey), keyId: Buffer.alloc(20, 1),
|
|
429
|
-
* certPath: { taName: "
|
|
429
|
+
* certPath: { taName: "Example Root" } } }] });
|
|
430
430
|
* pki.schema.trustanchor.pemEncode(der).indexOf("-----BEGIN TRUST ANCHOR LIST-----"); // -> 0
|
|
431
431
|
*/
|
|
432
432
|
function pemEncode(der, label) { return pkix.pemEncode(der, label || "TRUST ANCHOR LIST", PemError); }
|
package/lib/sigstore.js
CHANGED
|
@@ -1383,7 +1383,7 @@ async function _verifyEmbeddedScts(leaf, leafDer, issuer, logs, timeMs, atMs) {
|
|
|
1383
1383
|
/** @internal A receipt the verifier refuses structurally counted as verified would be the whole leg
|
|
1384
1384
|
* lost, so a fault is an unverified receipt and the next one is still tried. */
|
|
1385
1385
|
function _sctVerifies(entry, sct, spki) {
|
|
1386
|
-
return _ctVerifySct(entry, sct, spki)
|
|
1386
|
+
return intrinsic.chain(_ctVerifySct(entry, sct, spki), function (ok) { return ok === true; }, function () { return false; });
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
1389
|
/** @internal A receipt's own instant, in milliseconds. The parser's `timestampMs` is this same value
|
package/lib/smime.js
CHANGED
|
@@ -457,8 +457,24 @@ function _readDescriptor(s, position) {
|
|
|
457
457
|
var names = Object.getOwnPropertyNames(proto);
|
|
458
458
|
for (var n = 0; n < names.length; n++) {
|
|
459
459
|
var name = names[n];
|
|
460
|
-
if (
|
|
461
|
-
|
|
460
|
+
if (seen[name] === 1) continue;
|
|
461
|
+
/** @internal The three names this walk declines: a prototype level's `constructor`, which is
|
|
462
|
+
* the class rather than a field; a prototype level's method, likewise; and a callable
|
|
463
|
+
* descriptor's own function machinery, which `guard.identifier` knows the names of and which
|
|
464
|
+
* copying turned a legitimate callable into a descriptor `pki.cms.sign`'s own unknown-field
|
|
465
|
+
* door then refused.
|
|
466
|
+
*
|
|
467
|
+
* A declined name is marked SEEN, because the value `s[name]` reads is the one the NEAREST
|
|
468
|
+
* level holds and a deeper property of that name is shadowed and unreadable. Measured before
|
|
469
|
+
* the marking: a derived class's own `length` was declined and then read off the base class
|
|
470
|
+
* as a field; a generator's own `prototype` was declined and then its generator-prototype
|
|
471
|
+
* OBJECT was walked, which read as a signer nested too deeply to copy; and a prototype-level
|
|
472
|
+
* method was declined and then a data property of the same name one level up was copied,
|
|
473
|
+
* which set the field to the method shadowing it and reached the digest resolver as
|
|
474
|
+
* `unsupported RSA digest algorithm undefined`. */
|
|
475
|
+
if ((name === "constructor" && proto !== s) ||
|
|
476
|
+
(proto !== s && _isMethod(proto, name)) ||
|
|
477
|
+
(proto === s && guard.identifier.isOwnMachineryName(s, name))) { seen[name] = 1; continue; }
|
|
462
478
|
seen[name] = 1;
|
|
463
479
|
if (++fields > DESCRIPTOR_MAX_FIELDS) throw _err("smime/bad-input", "signer " + position + " carries more than " + DESCRIPTOR_MAX_FIELDS + " fields");
|
|
464
480
|
copy[name] = s[name];
|
package/lib/trust.js
CHANGED
|
@@ -1156,7 +1156,7 @@ function _subtreeOf(st) { return { tag: st.base.tagNumber, base: st.base.value }
|
|
|
1156
1156
|
* var pair = await pki.key.generate("Ed25519");
|
|
1157
1157
|
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
1158
1158
|
* pubKey: await pki.key.export(pair.publicKey), keyId: Buffer.alloc(20, 1),
|
|
1159
|
-
* certPath: { taName: "
|
|
1159
|
+
* certPath: { taName: "Example Root" } } }] });
|
|
1160
1160
|
* pki.trust.parseTrustAnchorList(der).anchors.length; // -> 1
|
|
1161
1161
|
*/
|
|
1162
1162
|
function parseTrustAnchorList(input, opts) {
|
package/lib/trustanchor-build.js
CHANGED
|
@@ -249,7 +249,7 @@ function _anchorDer(spec, i) {
|
|
|
249
249
|
* pubKey: await pki.key.export(pair.publicKey),
|
|
250
250
|
* keyId: Buffer.alloc(20, 1),
|
|
251
251
|
* taTitle: "Example Root CA",
|
|
252
|
-
* certPath: { taName: "
|
|
252
|
+
* certPath: { taName: "Example Root", pathLenConstraint: 2 } } }] });
|
|
253
253
|
* pki.schema.trustanchor.parse(der).anchors[0].taInfo.certPath.pathLenConstraint; // -> 2
|
|
254
254
|
*/
|
|
255
255
|
function build(spec, opts) {
|
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:9f1767af-18a9-4a06-85fd-6273c947d9b9",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-12T04:46:50.282Z",
|
|
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.9.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.9.4",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.9.
|
|
25
|
+
"version": "0.9.4",
|
|
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.9.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.9.4",
|
|
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.9.
|
|
57
|
+
"ref": "@blamejs/pki@0.9.4",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|