@blamejs/pki 0.7.28 → 0.7.29
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/key.js +37 -27
- package/package.json +2 -2
- 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.7.29 — 2026-09-12
|
|
8
|
+
|
|
9
|
+
Every decision pki.key makes is taken with operations bound when the module loads.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- pki.key.import, pki.key.generate, pki.key.decrypt, pki.key.encrypt, pki.key.export, pki.key.publicFromPrivate and the key-pair correspondence check read Array.isArray, Math.floor, isFinite, String, String.prototype.toUpperCase and String.prototype.indexOf, Buffer.from, Object.assign, Object.create and Object.prototype.hasOwnProperty as bound at module load. Replacing any of them afterwards changes nothing the module decides: a non-array usages is still refused, a fractional or infinite maxIterations is still refused, an Ed25519 import still receives signing usages, and an unrestricted RSASSA-PSS pair still corresponds when an inherited saltLength is planted on Object.prototype. Behavior on an untouched runtime is unchanged.
|
|
14
|
+
|
|
7
15
|
## v0.7.28 — 2026-09-12
|
|
8
16
|
|
|
9
17
|
pki.cmp.verify names a MAC algorithm it does not compute as what it is, not as a failed signature.
|
package/lib/key.js
CHANGED
|
@@ -29,6 +29,16 @@ var webcrypto = require("./webcrypto");
|
|
|
29
29
|
var compositeKem = require("./composite-kem");
|
|
30
30
|
var frameworkError = require("./framework-error");
|
|
31
31
|
var guard = require("./guard-all");
|
|
32
|
+
var intrinsic = require("./guard-intrinsic");
|
|
33
|
+
var _assign = intrinsic.assign;
|
|
34
|
+
var _create = intrinsic.create;
|
|
35
|
+
var _isArray = intrinsic.isArray;
|
|
36
|
+
var _bufferFrom = intrinsic.bufferFrom;
|
|
37
|
+
var _floor = intrinsic.floor;
|
|
38
|
+
var _isFinite = intrinsic.isFinite;
|
|
39
|
+
var _String = intrinsic.String;
|
|
40
|
+
var _toUpperCase = intrinsic.toUpperCase;
|
|
41
|
+
var _strIndexOf = intrinsic.stringIndexOf;
|
|
32
42
|
|
|
33
43
|
var b = asn1.build;
|
|
34
44
|
var subtle = webcrypto.webcrypto.subtle;
|
|
@@ -37,16 +47,16 @@ var PemError = frameworkError.PemError;
|
|
|
37
47
|
function O(n) { return oid.byName(n); }
|
|
38
48
|
function _err(code, msg, cause) { return new KeyError(code, msg, cause); }
|
|
39
49
|
|
|
40
|
-
var CIPHER_NAME =
|
|
50
|
+
var CIPHER_NAME = _assign(_create(null), { "aes-128-cbc": "aes128-CBC", "aes-192-cbc": "aes192-CBC", "aes-256-cbc": "aes256-CBC" });
|
|
41
51
|
|
|
42
|
-
var INFER_ALG =
|
|
52
|
+
var INFER_ALG = _create(null);
|
|
43
53
|
["Ed25519", "Ed448", "X25519", "X448"].forEach(function (n) { INFER_ALG[O(n)] = { name: n }; });
|
|
44
54
|
[["id-ml-dsa-44", "ML-DSA-44"], ["id-ml-dsa-65", "ML-DSA-65"], ["id-ml-dsa-87", "ML-DSA-87"],
|
|
45
55
|
["id-ml-kem-512", "ML-KEM-512"], ["id-ml-kem-768", "ML-KEM-768"], ["id-ml-kem-1024", "ML-KEM-1024"]
|
|
46
56
|
].forEach(function (r) { INFER_ALG[O(r[0])] = { name: r[1] }; });
|
|
47
57
|
["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
|
|
48
58
|
"shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
|
|
49
|
-
].forEach(function (s) { INFER_ALG[O("id-slh-dsa-" + s)] = { name: ("SLH-DSA-" + s)
|
|
59
|
+
].forEach(function (s) { INFER_ALG[O("id-slh-dsa-" + s)] = { name: _toUpperCase("SLH-DSA-" + s) }; });
|
|
50
60
|
|
|
51
61
|
function _isCryptoKey(x) { return webcrypto.isCryptoKeyLike(x); }
|
|
52
62
|
function _algName(a) { return typeof a === "string" ? a : (a && a.name); }
|
|
@@ -89,7 +99,7 @@ async function _toPrivateKeyDer(input) {
|
|
|
89
99
|
* var enc = await pki.key.encrypt(der, "s3cr3t", { pem: true });
|
|
90
100
|
* var back = await pki.key.decrypt(enc, "s3cr3t");
|
|
91
101
|
*/
|
|
92
|
-
var _ENCRYPT_OPTS =
|
|
102
|
+
var _ENCRYPT_OPTS = _assign(_create(null), { cipher: 1, iterations: 1, pem: 1, prf: 1, salt: 1 });
|
|
93
103
|
|
|
94
104
|
async function encrypt(privateKey, password, opts) {
|
|
95
105
|
opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.encrypt options");
|
|
@@ -148,14 +158,14 @@ async function encrypt(privateKey, password, opts) {
|
|
|
148
158
|
* var enc = await pki.key.encrypt(await pki.key.export(pair.privateKey), "s3cr3t", { pem: true });
|
|
149
159
|
* var der = await pki.key.decrypt(enc, "s3cr3t");
|
|
150
160
|
*/
|
|
151
|
-
var _DECRYPT_OPTS =
|
|
161
|
+
var _DECRYPT_OPTS = _assign(_create(null), { maxIterations: 1, pem: 1 });
|
|
152
162
|
|
|
153
163
|
async function decrypt(encrypted, password, opts) {
|
|
154
164
|
opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.decrypt options");
|
|
155
165
|
guard.identifier.assertKnownKeys(opts, _DECRYPT_OPTS, _err, "key/bad-input",
|
|
156
166
|
"pki.key.decrypt has an unknown option (the PBKDF2 cap here is `maxIterations`; " +
|
|
157
167
|
"`iterations` is the encrypt-side count): ");
|
|
158
|
-
if (opts.maxIterations != null && (typeof opts.maxIterations !== "number" || !
|
|
168
|
+
if (opts.maxIterations != null && (typeof opts.maxIterations !== "number" || !_isFinite(opts.maxIterations) || opts.maxIterations < 1 || _floor(opts.maxIterations) !== opts.maxIterations)) {
|
|
159
169
|
throw _err("key/bad-input", "maxIterations must be a positive integer");
|
|
160
170
|
}
|
|
161
171
|
var input = pkix.coerceToDer(encrypted, { pemLabel: "ENCRYPTED PRIVATE KEY", PemError: PemError, ErrorClass: KeyError, prefix: "key" });
|
|
@@ -204,7 +214,7 @@ function _decryptPbes2(encAlg, ciphertext, password, opts) {
|
|
|
204
214
|
* var pair = await pki.key.generate("Ed25519");
|
|
205
215
|
* var spkiPem = await pki.key.export(pair.publicKey, { format: "pem" });
|
|
206
216
|
*/
|
|
207
|
-
var _EXPORT_OPTS =
|
|
217
|
+
var _EXPORT_OPTS = _assign(_create(null), { format: 1, label: 1 });
|
|
208
218
|
|
|
209
219
|
async function export_(key, opts) {
|
|
210
220
|
opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.export options");
|
|
@@ -247,7 +257,7 @@ async function export_(key, opts) {
|
|
|
247
257
|
* var spki = await pki.key.export(pair.publicKey);
|
|
248
258
|
* var pub = await pki.key.import(spki); // Ed25519 -- algorithm inferred
|
|
249
259
|
*/
|
|
250
|
-
var _IMPORT_OPTS =
|
|
260
|
+
var _IMPORT_OPTS = _assign(_create(null), { algorithm: 1, extractable: 1, password: 1, usages: 1 });
|
|
251
261
|
|
|
252
262
|
async function import_(input, opts) {
|
|
253
263
|
opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.import options");
|
|
@@ -260,7 +270,7 @@ async function import_(input, opts) {
|
|
|
260
270
|
}
|
|
261
271
|
var algorithm = opts.algorithm != null ? opts.algorithm : _inferAlgorithm(detected);
|
|
262
272
|
var isPublic = detected.format === "spki";
|
|
263
|
-
if (opts.usages != null && !
|
|
273
|
+
if (opts.usages != null && !_isArray(opts.usages)) throw _err("key/bad-input", "opts.usages must be an array of key-usage strings");
|
|
264
274
|
var usages = opts.usages || _importUsages(_algName(algorithm), isPublic);
|
|
265
275
|
var extractable = opts.extractable != null ? opts.extractable : false;
|
|
266
276
|
try {
|
|
@@ -291,14 +301,14 @@ async function import_(input, opts) {
|
|
|
291
301
|
* var pair = await pki.key.generate("Ed25519");
|
|
292
302
|
* var pkcs8 = await pki.key.export(pair.privateKey);
|
|
293
303
|
*/
|
|
294
|
-
var _GENERATE_OPTS =
|
|
304
|
+
var _GENERATE_OPTS = _assign(_create(null), { extractable: 1, usages: 1 });
|
|
295
305
|
|
|
296
306
|
async function generate(algorithm, opts) {
|
|
297
307
|
opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.generate options");
|
|
298
308
|
guard.identifier.assertKnownKeys(opts, _GENERATE_OPTS, _err, "key/bad-input",
|
|
299
309
|
"pki.key.generate has an unknown option (the WebCrypto spelling is `extractable`): ");
|
|
300
310
|
var extractable = opts.extractable != null ? opts.extractable : true;
|
|
301
|
-
if (opts.usages != null && !
|
|
311
|
+
if (opts.usages != null && !_isArray(opts.usages)) throw _err("key/bad-input", "opts.usages must be an array of key-usage strings");
|
|
302
312
|
var usages = opts.usages || _generateUsages(_algName(algorithm));
|
|
303
313
|
var pair;
|
|
304
314
|
try { pair = await subtle.generateKey(algorithm, extractable, usages); }
|
|
@@ -333,7 +343,7 @@ async function generate(algorithm, opts) {
|
|
|
333
343
|
* var pair = await pki.key.generate("Ed25519");
|
|
334
344
|
* var spki = await pki.key.publicFromPrivate(await pki.key.export(pair.privateKey));
|
|
335
345
|
*/
|
|
336
|
-
var _PUBLIC_FROM_PRIVATE_OPTS =
|
|
346
|
+
var _PUBLIC_FROM_PRIVATE_OPTS = _assign(_create(null), { pem: 1 });
|
|
337
347
|
|
|
338
348
|
/** @internal The composite ML-KEM OID a private key names, or null when it names anything else. Read
|
|
339
349
|
* from the AlgorithmIdentifier alone: a key that is not well formed enough to state one is left to the
|
|
@@ -399,17 +409,17 @@ async function publicFromPrivate(privateKey, opts) {
|
|
|
399
409
|
* correspondence that succeeds, and does not stop the key from being usable with the certificate.
|
|
400
410
|
* A caller needing structural consistency has a different question, and this is not it.
|
|
401
411
|
*/
|
|
402
|
-
var _PAIR_DIGEST =
|
|
403
|
-
var _PAIR_AGREE =
|
|
404
|
-
var _PAIR_KEM =
|
|
412
|
+
var _PAIR_DIGEST = _assign(_create(null), { rsa: "sha256", ec: "sha256" });
|
|
413
|
+
var _PAIR_AGREE = _assign(_create(null), { x25519: 1, x448: 1 });
|
|
414
|
+
var _PAIR_KEM = _assign(_create(null),
|
|
405
415
|
{ "ml-kem-512": 1, "ml-kem-768": 1, "ml-kem-1024": 1 });
|
|
406
416
|
/** @internal RFC 4055 sec. 1.2: ONE RSA key pair may be identified either by `rsaEncryption` or by
|
|
407
417
|
* `id-RSASSA-PSS`, and a delivered private key and the certificate that certifies it need not have
|
|
408
418
|
* chosen the same one. The key engine reports those two encodings as different types, so comparing
|
|
409
419
|
* types alone would call a usable pair no pair at all. They name one algorithm and are exercised
|
|
410
420
|
* together, under PSS, which is the padding the restricted encoding permits. */
|
|
411
|
-
var _PAIR_RSA =
|
|
412
|
-
var _PAIR_PROBE =
|
|
421
|
+
var _PAIR_RSA = _assign(_create(null), { rsa: 1, "rsa-pss": 1 });
|
|
422
|
+
var _PAIR_PROBE = _bufferFrom("pki.js key pair correspondence", "utf8");
|
|
413
423
|
|
|
414
424
|
function _sameKeyFamily(priv, pub) {
|
|
415
425
|
if (priv.asymmetricKeyType === pub.asymmetricKeyType) return true;
|
|
@@ -443,7 +453,7 @@ function _pairSignOptions(priv, pub) {
|
|
|
443
453
|
var salt = _pssSalt(privDetails.saltLength, pubDetails.saltLength);
|
|
444
454
|
/** @internal Null-prototype, because the engine READS this object by name. A plain object would
|
|
445
455
|
* let an inherited `saltLength` reach the signature as a restriction neither half stated. */
|
|
446
|
-
var options =
|
|
456
|
+
var options = _create(null);
|
|
447
457
|
options.padding = nodeCrypto.constants.RSA_PKCS1_PSS_PADDING;
|
|
448
458
|
if (salt != null) options.saltLength = salt;
|
|
449
459
|
return { digest: digest, options: options };
|
|
@@ -457,12 +467,12 @@ var _PSS_PINS = ["hashAlgorithm", "mgf1HashAlgorithm", "saltLength"];
|
|
|
457
467
|
* object would let an inherited `saltLength` or `hashAlgorithm` answer as a restriction the key never
|
|
458
468
|
* carried, and refuse a genuine pair. */
|
|
459
469
|
function _pssRestrictionOf(key, isPss) {
|
|
460
|
-
var out =
|
|
470
|
+
var out = _create(null);
|
|
461
471
|
var details = isPss ? key.asymmetricKeyDetails : null;
|
|
462
472
|
if (details == null) return out;
|
|
463
473
|
for (var i = 0; i < _PSS_PINS.length; i++) {
|
|
464
474
|
var name = _PSS_PINS[i];
|
|
465
|
-
if (
|
|
475
|
+
if (intrinsic.hasOwn(details, name) && details[name] != null) out[name] = details[name];
|
|
466
476
|
}
|
|
467
477
|
return out;
|
|
468
478
|
}
|
|
@@ -492,7 +502,7 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
492
502
|
var priv, pub;
|
|
493
503
|
try { priv = nodeCrypto.createPrivateKey({ key: privDer, format: "der", type: "pkcs8" }); }
|
|
494
504
|
catch (e) { throw _err("key/bad-input", "the private key could not be read", e); }
|
|
495
|
-
try { pub = nodeCrypto.createPublicKey({ key:
|
|
505
|
+
try { pub = nodeCrypto.createPublicKey({ key: _bufferFrom(spkiDer), format: "der", type: "spki" }); }
|
|
496
506
|
catch (e) { throw _err("key/bad-input", "the public key could not be read", e); }
|
|
497
507
|
if (!_sameKeyFamily(priv, pub)) return false;
|
|
498
508
|
var kind = guard.text.keyOf(priv.asymmetricKeyType);
|
|
@@ -521,8 +531,8 @@ async function correspondsTo(privateKey, spkiDer) {
|
|
|
521
531
|
if (how === null) {
|
|
522
532
|
throw _err("key/unsupported-algorithm", "the two halves pin incompatible RSASSA-PSS parameters, so no operation exercises them together and whether they are a pair cannot be decided");
|
|
523
533
|
}
|
|
524
|
-
var signKey = how.options ?
|
|
525
|
-
var verifyKey = how.options ?
|
|
534
|
+
var signKey = how.options ? _assign(_create(null), { key: priv }, how.options) : priv;
|
|
535
|
+
var verifyKey = how.options ? _assign(_create(null), { key: pub }, how.options) : pub;
|
|
526
536
|
return nodeCrypto.verify(how.digest, _PAIR_PROBE, verifyKey,
|
|
527
537
|
nodeCrypto.sign(how.digest, _PAIR_PROBE, signKey)) === true;
|
|
528
538
|
} catch (e) {
|
|
@@ -569,17 +579,17 @@ function _inferAlgorithm(detected) {
|
|
|
569
579
|
}
|
|
570
580
|
|
|
571
581
|
function _importUsages(name, isPublic) {
|
|
572
|
-
var n =
|
|
582
|
+
var n = _toUpperCase(_String(name == null ? "" : name));
|
|
573
583
|
if (n === "X25519" || n === "X448" || n === "ECDH") return isPublic ? [] : ["deriveBits", "deriveKey"];
|
|
574
|
-
if (n
|
|
584
|
+
if (_strIndexOf(n, "ML-KEM") === 0) return isPublic ? ["encapsulateBits"] : ["decapsulateBits"];
|
|
575
585
|
if (n === "RSA-OAEP") return isPublic ? ["encrypt"] : ["decrypt"];
|
|
576
586
|
return isPublic ? ["verify"] : ["sign"];
|
|
577
587
|
}
|
|
578
588
|
|
|
579
589
|
function _generateUsages(name) {
|
|
580
|
-
var n =
|
|
590
|
+
var n = _toUpperCase(_String(name == null ? "" : name));
|
|
581
591
|
if (n === "X25519" || n === "X448" || n === "ECDH") return ["deriveBits", "deriveKey"];
|
|
582
|
-
if (n
|
|
592
|
+
if (_strIndexOf(n, "ML-KEM") === 0) return ["encapsulateBits", "decapsulateBits"];
|
|
583
593
|
if (n === "RSA-OAEP") return ["encrypt", "decrypt"];
|
|
584
594
|
return ["sign", "verify"];
|
|
585
595
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/pki",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.29",
|
|
4
4
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "blamejs contributors",
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"c8": "12.0.0",
|
|
86
86
|
"esbuild": "0.28.2",
|
|
87
|
-
"eslint": "10.
|
|
87
|
+
"eslint": "10.10.0"
|
|
88
88
|
}
|
|
89
89
|
}
|
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:3c9a8588-ff6f-477c-92fd-33c03ea2c98b",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-12T05:32:15.338Z",
|
|
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.7.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.7.29",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.29",
|
|
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.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.7.29",
|
|
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.7.
|
|
57
|
+
"ref": "@blamejs/pki@0.7.29",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|