@blamejs/pki 0.6.53 → 0.6.54
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 +8 -0
- package/lib/cmc-build.js +41 -12
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.54 — 2026-09-07
|
|
8
|
+
|
|
9
|
+
A holder of a composite ML-KEM key can enroll for it, proving possession by opening the challenge addressed to it.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- pki.cmc.build completes an unsigned enrollment for a composite ML-KEM key (RFC 5272 App. C.1). The rule is unchanged: the challenge must be addressed to the key the request asks for, and a challenge opened by any other key licenses nothing. What changed is how the two are compared. A key the runtime engine cannot read has no key object to compare, so the composite SubjectPublicKeyInfo answers instead, which is sound because that encoding is canonical: fixed component widths under a single OID, with no compressed-point spelling for two forms of one key to differ over. That ambiguity is the reason the classical comparison uses key objects, and it does not arise here. Every other key type is compared exactly as before.
|
|
14
|
+
|
|
7
15
|
## v0.6.53 — 2026-09-07
|
|
8
16
|
|
|
9
17
|
A composite ML-KEM private key states its public half, so a holder can name the recipient it is.
|
package/lib/cmc-build.js
CHANGED
|
@@ -17,6 +17,7 @@ var oid = require("./oid");
|
|
|
17
17
|
var cmsSign = require("./cms-sign");
|
|
18
18
|
var cmsEncrypt = require("./cms-encrypt");
|
|
19
19
|
var cmsDecrypt = require("./cms-decrypt");
|
|
20
|
+
var compositeKem = require("./composite-kem");
|
|
20
21
|
var key = require("./key");
|
|
21
22
|
var _createPublicKey = nodeCrypto.createPublicKey;
|
|
22
23
|
var guard = require("./guard-all");
|
|
@@ -518,19 +519,40 @@ function _requestIdentity(parsed) {
|
|
|
518
519
|
|
|
519
520
|
/** @internal Whether the key that opened the challenge is the key the request asks to have certified.
|
|
520
521
|
* Only a tcr is answered this way: a crm request carries its own proof-of-possession field. */
|
|
521
|
-
function _provesRequestKey(
|
|
522
|
-
if (!
|
|
523
|
-
var
|
|
522
|
+
function _provesRequestKey(opening, outgoing) {
|
|
523
|
+
if (!opening || !outgoing || !outgoing.certificationRequestBytes) return false;
|
|
524
|
+
var requestedSpki;
|
|
525
|
+
try {
|
|
526
|
+
requestedSpki = csr.parse(outgoing.certificationRequestBytes).subjectPublicKeyInfo.bytes;
|
|
527
|
+
} catch (_e) { return false; } // allow:swallow-unverified a request this runtime cannot read proves nothing here
|
|
524
528
|
/** @internal Compared as keys rather than as encodings: an elliptic-curve point written in compressed
|
|
525
529
|
* form and the same point written uncompressed are different bytes and the same key, and it is the key
|
|
526
530
|
* the section is about. */
|
|
531
|
+
if (opening.key !== null) {
|
|
532
|
+
var requested;
|
|
533
|
+
try {
|
|
534
|
+
requested = _createPublicKey({ key: requestedSpki, format: "der", type: "spki" });
|
|
535
|
+
} catch (_e) { return false; } // allow:swallow-unverified a key this runtime cannot read proves nothing here
|
|
536
|
+
return intrinsic.keyEquals(requested, opening.key);
|
|
537
|
+
}
|
|
538
|
+
/** @internal A key the runtime engine cannot read has no KeyObject to compare, and a composite
|
|
539
|
+
* ML-KEM key is one. Its encoding answers instead, which is sound because that encoding is
|
|
540
|
+
* canonical: fixed component widths under a single OID, with no compressed-point spelling for two
|
|
541
|
+
* forms of one key to differ over, which is the ambiguity the comparison above exists to absorb. */
|
|
542
|
+
if (!_isCompositeSpki(requestedSpki)) return false;
|
|
543
|
+
return intrinsic.bufferEquals(requestedSpki, opening.spki);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** @internal Whether a SubjectPublicKeyInfo names a composite ML-KEM algorithm, read from its
|
|
547
|
+
* AlgorithmIdentifier alone. Anything this cannot read is not one. */
|
|
548
|
+
function _isCompositeSpki(spki) {
|
|
527
549
|
try {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
550
|
+
var node = asn1.decode(spki);
|
|
551
|
+
if (!node.children || node.children.length !== 2) return false;
|
|
552
|
+
var alg = node.children[0];
|
|
553
|
+
if (!alg.children || alg.children.length < 1) return false;
|
|
554
|
+
return compositeKem.isCompositeKem(asn1.read.oid(alg.children[0]));
|
|
555
|
+
} catch (_e) { return false; } // allow:swallow-unverified an unreadable algorithm is not a composite one
|
|
534
556
|
}
|
|
535
557
|
|
|
536
558
|
/** @internal The public half of the key that OPENED the challenge. The recipient kind comes from the
|
|
@@ -539,11 +561,18 @@ function _provesRequestKey(openingKey, outgoing) {
|
|
|
539
561
|
* yields null, and the request is then refused rather than accepted on an unproven key. */
|
|
540
562
|
async function _openingPublicKey(recipient, recipientType) {
|
|
541
563
|
if (!ASYMMETRIC_RECIPIENT[recipientType]) return null;
|
|
564
|
+
var spki;
|
|
542
565
|
try {
|
|
543
|
-
|
|
544
|
-
key: await key.publicFromPrivate(recipient.key), format: "der", type: "spki",
|
|
545
|
-
});
|
|
566
|
+
spki = await key.publicFromPrivate(recipient.key);
|
|
546
567
|
} catch (_e) { return null; } // allow:swallow-unverified a key whose public half cannot be derived proves nothing here
|
|
568
|
+
/** @internal Both spellings of the answer: the KeyObject the comparison prefers, and the encoding a
|
|
569
|
+
* key the runtime engine cannot read has instead of one. A null `key` is not a failure; it says the
|
|
570
|
+
* engine does not know this algorithm, and the comparison below decides what that permits. */
|
|
571
|
+
var asKey;
|
|
572
|
+
try {
|
|
573
|
+
asKey = _createPublicKey({ key: spki, format: "der", type: "spki" });
|
|
574
|
+
} catch (_e) { asKey = null; } // allow:swallow-unverified a composite key the runtime cannot read is compared by its canonical bytes
|
|
575
|
+
return { spki: spki, key: asKey };
|
|
547
576
|
}
|
|
548
577
|
|
|
549
578
|
async function _recoverProofValue(decoded, recipient, witnessHash) {
|
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:3fcb367d-0079-41f4-bd25-0141050006d7",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-07T18:01:28.536Z",
|
|
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.54",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.54",
|
|
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.54",
|
|
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.54",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|