@blamejs/pki 0.2.6 → 0.2.8
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 +23 -1
- package/README.md +1 -1
- package/lib/path-validate.js +1 -1
- package/lib/webcrypto.js +100 -20
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,29 @@ 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.2.
|
|
7
|
+
## v0.2.8 — 2026-07-13
|
|
8
|
+
|
|
9
|
+
pki.webcrypto rejects an AES key of invalid length at import instead of deferring the failure to first use.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- pki.webcrypto.subtle.importKey now rejects a raw or JWK AES key whose length is not 128, 192, or 256 bits as a webcrypto/data DataError at import, rather than returning a CryptoKey that only fails at first use. Covers AES-GCM, AES-CBC, AES-CTR, and AES-KW; HMAC, HKDF, and PBKDF2 keys are unaffected.
|
|
14
|
+
- The README capability table and the documentation site render the certificate-inspection entry correctly: an inline code span containing pipe characters no longer breaks the surrounding table into misaligned columns.
|
|
15
|
+
|
|
16
|
+
## v0.2.7 — 2026-07-13
|
|
17
|
+
|
|
18
|
+
pki.webcrypto rejects an imported key whose type disagrees with its algorithm, and reports every cipher fault as a typed error.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- The pkijs.com documentation site is regenerated with content-hashed CSS/JS under a strict Content-Security-Policy, an in-memory search endpoint, a browsable reference of every error class and code, symbol autocomplete, and concept guides. Documentation only -- the published package is unchanged.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- pki.webcrypto.subtle.importKey now validates that an imported asymmetric key's actual type matches the requested algorithm (an RSA key imported under an Ed25519, ECDSA, or RSA-PSS name is rejected as webcrypto/data), closing an algorithm-confusion path where a mislabeled CryptoKey could later be used under the wrong signature scheme. The EC import path already derived and checked the curve; this extends the same key-is-authority rule to RSA and the Edwards/Montgomery curves.
|
|
27
|
+
- pki.webcrypto AES cipher faults now fail closed with a typed webcrypto/operation error instead of a raw Node exception: a decrypt of a tampered AES-GCM ciphertext (failed authentication tag), bad AES-CBC padding, a non-8-byte-multiple AES-KW wrap/unwrap length, and a malformed cipher parameter all surface as a WebCryptoError, so a caller catching pki.errors.PkiError sees a typed verdict rather than a bare Node error crossing the API boundary.
|
|
28
|
+
|
|
29
|
+
## v0.2.6 — 2026-07-12
|
|
8
30
|
|
|
9
31
|
WebAuthn attestation verification covers Ed448 and the RFC 9864 fully-specified COSE algorithms, and hardens credential-key conformance.
|
|
10
32
|
|
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ is callable today; nothing below is a stub.
|
|
|
227
227
|
| `pki.shbs` | Stateful hash-based signature **verification** — HSS/LMS (RFC 8554), carried in X.509 by RFC 9802 and CMS by RFC 9708, profiled by NIST SP 800-208 (CNSA 2.0 firmware signing). `verify` checks an HSS signature (every level must pass) and `verifyLms` a single-tree LMS, over the raw public-key / signature blobs the parsers already surface. Pure public-input SHA-256 / SHAKE256 hashing, a data-driven typecode registry, bounds-before-slice reads; a malformed blob throws a typed `ShbsError`, a well-formed-but-wrong signature returns `false`. **Verify only by design** — stateful signing needs atomic one-time-key state that belongs in an HSM — `verify`, `verifyLms` |
|
|
228
228
|
| `pki.hpke` | Hybrid Public Key Encryption (RFC 9180) — the encrypt-to-a-public-key primitive behind TLS ECH, MLS, and OHTTP. `setupS`/`setupR` establish a sender/recipient context (KEM encapsulation + HKDF key schedule); the context's `seal`/`open` AEAD-encrypt with a sequence-counter nonce and `export` derives further secrets; `seal`/`open` are single-shot wrappers. DHKEM (P-256, P-521, X25519, X448) × HKDF-SHA256/SHA512 × AES-GCM/ChaCha20Poly1305/export-only × all four modes, proven against the RFC 9180 Appendix A vectors. DHKEM(P-384) and HKDF-SHA384 are RFC-registered but Appendix A ships no vector for them, so they fail closed until an authoritative KAT exists. Pure composition over `node:crypto`; ML-KEM / X-Wing are a registry data-row extension pending stable drafts — `suites`, `setupS`, `setupR`, `seal`, `open` |
|
|
229
229
|
| `pki.sigstore` | Offline verifier for a Sigstore bundle — the exact artifact `npm publish --provenance` produces and the registry serves. `verifyBundle` composes five fail-closed legs against caller-pinned trust (Fulcio CA roots + Rekor log keys, never trusted from the bundle): the DSSE signature over its PAE preimage under the Fulcio leaf key; the ephemeral Fulcio certificate chain, validated as of the Rekor log time; the RFC 9162 inclusion proof folded to a Rekor-signed tree root; the log entry bound to this exact signature; and the in-toto SLSA subject digest the caller confirms against the published artifact. Zero runtime deps — reuses the X.509 parser, RFC 5280 path validator, and Merkle verifier; the net-new codecs are the DSSE PAE byte-builder and a fail-closed JSON reader. `pae`, `parseBundle`, `verifyBundle` |
|
|
230
|
-
| `pki.inspect` | Human-readable certificate inspection — the pure-JS equivalent of `openssl x509 -text`. `certificate(pem
|
|
230
|
+
| `pki.inspect` | Human-readable certificate inspection — the pure-JS equivalent of `openssl x509 -text`. `certificate(pem \| der \| parsed)` renders a familiar OpenSSL-style report: version, serial, signature algorithm, issuer/subject distinguished names, validity, public-key details (curve or modulus size + the raw point/modulus), every decoded extension with its critical flag, and the signature. Built over the strict parser and the two-way OID registry, so it names extension and algorithm OIDs an OpenSSL build shows only as raw bytes. No OpenSSL dependency; the format is stable and OpenSSL-familiar rather than pinned to one OpenSSL version; a malformed extension falls back to a hex dump rather than throwing — `certificate` |
|
|
231
231
|
| `pki.webauthn` | WebAuthn / passkey attestation verification — offline trust evaluation of a W3C WebAuthn (Level 3) attestation. `parseAttestationObject(bytes)` decodes the CBOR attestation object + authenticatorData + COSE credential key over the strict `pki.cbor` codec; `verify(attestationObject, clientDataHash, opts)` checks the attestation-statement signature and each format's structural bindings for **packed / tpm / android-key / apple / fido-u2f / none** — the x5c leaf key, the apple nonce, the tpm `certInfo` Name/`extraData` over the `pubArea`, the android `KeyDescription`, the fido-u2f `verificationData` — binding the credential public key to each attestation (via the signed authenticatorData for packed/fido-u2f, or a cert/`pubArea`-key equality check for android-key/apple/tpm) and enforcing each leaf's certificate requirements. The credential-key check covers the full WebAuthn COSE algorithm set — ES256/384/512, RS256/384/512, PS256, EdDSA (Ed25519), and the RFC 9864 fully-specified identifiers **ESP256/384/512, Ed25519, and Ed448** — validating the public-key point on its curve, rejecting the compressed EC point form, and enforcing a minimally-encoded DER ECDSA signature. A verifier, not a ceremony client; fail-closed with typed `webauthn/*` errors. Chaining the returned trust path to a pinned root (and aaguid→root via FIDO MDS) is the caller's step through `pki.path.validate` — `parseAttestationObject`, `verify` |
|
|
232
232
|
| `pki.C` / `pki.constants` | Version-stable constants — functional scale helpers (`C.TIME.*`, `C.BYTES.*`), codec `LIMITS`, `version` |
|
|
233
233
|
| `pki.errors` | The `PkiError` taxonomy — `defineClass` plus `ConstantsError` / `Asn1Error` / `OidError` / `PemError` / `CertificateError` / `CrlError` / `CsrError` / `Pkcs8Error` / `CmsError` / `OcspError` / `TspError` / `AttrCertError` / `CrmfError` / `Pkcs12Error` / `CmpError` / `PathError` / `CtError` / `JoseError` / `AcmeError` / `WebauthnError`, each carrying a stable `code` in `domain/reason` form |
|
package/lib/path-validate.js
CHANGED
|
@@ -1463,7 +1463,7 @@ function crlIssuerNamesIssuer(cRLIssuer, issuerRdns) {
|
|
|
1463
1463
|
// dnEqual throws only on a control-byte / malformed cRLIssuer DN; returning false excludes
|
|
1464
1464
|
// that DP from the correspondence (a malformed indirect-CRL issuer name never corresponds).
|
|
1465
1465
|
try { if (dnEqual(n.value.rdns, issuerRdns)) return true; }
|
|
1466
|
-
catch (_e) { return false; }
|
|
1466
|
+
catch (_e) { return false; }
|
|
1467
1467
|
}
|
|
1468
1468
|
return false;
|
|
1469
1469
|
}
|
package/lib/webcrypto.js
CHANGED
|
@@ -353,6 +353,16 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
|
|
|
353
353
|
throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + JSON.stringify(name));
|
|
354
354
|
};
|
|
355
355
|
|
|
356
|
+
// Run a node:crypto AES cipher sequence, converting any raw node fault -- a bad IV length,
|
|
357
|
+
// a failed GCM authentication tag, bad CBC padding -- into a typed OperationError. A
|
|
358
|
+
// decrypt of tampered ciphertext (or a malformed cipher parameter) is a typed webcrypto
|
|
359
|
+
// verdict, never a raw Error crossing the public API (input prep that already throws a
|
|
360
|
+
// typed error runs OUTSIDE this, so a typed fault is never double-wrapped).
|
|
361
|
+
function _runCipher(fn, who) {
|
|
362
|
+
try { return fn(); }
|
|
363
|
+
catch (e) { throw new WebCryptoError("webcrypto/operation", who + ": AES cipher operation failed (bad key / iv / tag / padding)", e); }
|
|
364
|
+
}
|
|
365
|
+
|
|
356
366
|
SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
|
|
357
367
|
var alg = _normalizeAlg(algorithm, "encrypt");
|
|
358
368
|
_requireUsage(key, "encrypt");
|
|
@@ -369,19 +379,28 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
|
|
|
369
379
|
}
|
|
370
380
|
if (name === "AES-GCM") {
|
|
371
381
|
var iv = _toBuf(alg.iv, "AES-GCM iv");
|
|
372
|
-
var
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
382
|
+
var aad = alg.additionalData ? _toBuf(alg.additionalData, "AES-GCM aad") : null;
|
|
383
|
+
return _toArrayBuffer(_runCipher(function () {
|
|
384
|
+
var cipher = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-gcm", _secretBytes(key), iv, { authTagLength: (alg.tagLength || 128) / 8 });
|
|
385
|
+
if (aad) cipher.setAAD(aad);
|
|
386
|
+
var ct = Buffer.concat([cipher.update(buf), cipher.final()]);
|
|
387
|
+
return Buffer.concat([ct, cipher.getAuthTag()]);
|
|
388
|
+
}, "encrypt"));
|
|
376
389
|
}
|
|
377
390
|
if (name === "AES-CBC") {
|
|
378
|
-
var
|
|
379
|
-
return _toArrayBuffer(
|
|
391
|
+
var cbcIv = _toBuf(alg.iv, "AES-CBC iv");
|
|
392
|
+
return _toArrayBuffer(_runCipher(function () {
|
|
393
|
+
var c2 = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-cbc", _secretBytes(key), cbcIv);
|
|
394
|
+
return Buffer.concat([c2.update(buf), c2.final()]);
|
|
395
|
+
}, "encrypt"));
|
|
380
396
|
}
|
|
381
397
|
if (name === "AES-CTR") {
|
|
382
398
|
_requireCtrLength128(alg);
|
|
383
|
-
var
|
|
384
|
-
return _toArrayBuffer(
|
|
399
|
+
var ctrCounter = _toBuf(alg.counter, "AES-CTR counter");
|
|
400
|
+
return _toArrayBuffer(_runCipher(function () {
|
|
401
|
+
var c3 = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-ctr", _secretBytes(key), ctrCounter);
|
|
402
|
+
return Buffer.concat([c3.update(buf), c3.final()]);
|
|
403
|
+
}, "encrypt"));
|
|
385
404
|
}
|
|
386
405
|
throw new WebCryptoError("webcrypto/not-supported", "encrypt: unsupported algorithm " + JSON.stringify(name));
|
|
387
406
|
};
|
|
@@ -403,21 +422,31 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
|
|
|
403
422
|
if (name === "AES-GCM") {
|
|
404
423
|
var tagLen = (alg.tagLength || 128) / 8;
|
|
405
424
|
var iv = _toBuf(alg.iv, "AES-GCM iv");
|
|
425
|
+
if (buf.length < tagLen) throw new WebCryptoError("webcrypto/operation", "decrypt: AES-GCM ciphertext is shorter than its authentication tag");
|
|
406
426
|
var ct = buf.subarray(0, buf.length - tagLen);
|
|
407
427
|
var tag = buf.subarray(buf.length - tagLen);
|
|
408
|
-
var
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
428
|
+
var gcmAad = alg.additionalData ? _toBuf(alg.additionalData, "AES-GCM aad") : null;
|
|
429
|
+
return _toArrayBuffer(_runCipher(function () {
|
|
430
|
+
var d = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-gcm", _secretBytes(key), iv, { authTagLength: tagLen });
|
|
431
|
+
if (gcmAad) d.setAAD(gcmAad);
|
|
432
|
+
d.setAuthTag(tag);
|
|
433
|
+
return Buffer.concat([d.update(ct), d.final()]);
|
|
434
|
+
}, "decrypt"));
|
|
412
435
|
}
|
|
413
436
|
if (name === "AES-CBC") {
|
|
414
|
-
var
|
|
415
|
-
return _toArrayBuffer(
|
|
437
|
+
var cbcIv2 = _toBuf(alg.iv, "AES-CBC iv");
|
|
438
|
+
return _toArrayBuffer(_runCipher(function () {
|
|
439
|
+
var d2 = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-cbc", _secretBytes(key), cbcIv2);
|
|
440
|
+
return Buffer.concat([d2.update(buf), d2.final()]);
|
|
441
|
+
}, "decrypt"));
|
|
416
442
|
}
|
|
417
443
|
if (name === "AES-CTR") {
|
|
418
444
|
_requireCtrLength128(alg);
|
|
419
|
-
var
|
|
420
|
-
return _toArrayBuffer(
|
|
445
|
+
var ctrCounter2 = _toBuf(alg.counter, "AES-CTR counter");
|
|
446
|
+
return _toArrayBuffer(_runCipher(function () {
|
|
447
|
+
var d3 = nodeCrypto.createDecipheriv("aes-" + key.algorithm.length + "-ctr", _secretBytes(key), ctrCounter2);
|
|
448
|
+
return Buffer.concat([d3.update(buf), d3.final()]);
|
|
449
|
+
}, "decrypt"));
|
|
421
450
|
}
|
|
422
451
|
throw new WebCryptoError("webcrypto/not-supported", "decrypt: unsupported algorithm " + JSON.stringify(name));
|
|
423
452
|
};
|
|
@@ -526,8 +555,16 @@ SubtleCrypto.prototype.wrapKey = async function wrapKey(format, key, wrappingKey
|
|
|
526
555
|
if (alg.name !== "AES-KW" && !ENCRYPT_DECRYPT_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "wrapKey: unsupported algorithm " + JSON.stringify(alg.name));
|
|
527
556
|
_requireAlgMatch(alg, wrappingKey, "wrapKey");
|
|
528
557
|
if (alg.name === "AES-KW") {
|
|
529
|
-
|
|
530
|
-
|
|
558
|
+
// AES-KW wraps 64-bit blocks (RFC 3394): the serialized key MUST be a multiple of 8
|
|
559
|
+
// bytes and at least 16. A "jwk" serialization (arbitrary-length JSON) or any other
|
|
560
|
+
// non-conforming length is an OperationError -- a typed verdict, never a raw node fault.
|
|
561
|
+
if (bytes.length < 16 || bytes.length % 8 !== 0) {
|
|
562
|
+
throw new WebCryptoError("webcrypto/operation", "wrapKey: AES-KW requires the serialized key be a multiple of 8 bytes (>= 16); got " + bytes.length + " -- format " + JSON.stringify(format) + " is not AES-KW-wrappable");
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
var c = nodeCrypto.createCipheriv("aes" + wrappingKey.algorithm.length + "-wrap", _secretBytes(wrappingKey), Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
|
|
566
|
+
return _toArrayBuffer(Buffer.concat([c.update(bytes), c.final()]));
|
|
567
|
+
} catch (e) { throw new WebCryptoError("webcrypto/operation", "wrapKey: AES-KW key wrap failed", e); }
|
|
531
568
|
}
|
|
532
569
|
// Delegate to a content-encryption algorithm (RSA-OAEP / AES-GCM).
|
|
533
570
|
var wrapKeyClone = _cloneWithUsage(wrappingKey, "encrypt");
|
|
@@ -546,8 +583,17 @@ SubtleCrypto.prototype.unwrapKey = async function unwrapKey(format, wrappedKey,
|
|
|
546
583
|
_requireAlgMatch(alg, unwrappingKey, "unwrapKey");
|
|
547
584
|
var bytes;
|
|
548
585
|
if (alg.name === "AES-KW") {
|
|
549
|
-
var
|
|
550
|
-
|
|
586
|
+
var wrapped = _toBuf(wrappedKey, "unwrapKey");
|
|
587
|
+
// The wrapped input is the plaintext plus one 64-bit integrity block: a multiple of 8,
|
|
588
|
+
// at least 24. A wrong length, or a failed integrity check inside final(), is an
|
|
589
|
+
// OperationError -- a typed verdict, never a raw node cipher fault.
|
|
590
|
+
if (wrapped.length < 24 || wrapped.length % 8 !== 0) {
|
|
591
|
+
throw new WebCryptoError("webcrypto/operation", "unwrapKey: AES-KW wrapped key must be a multiple of 8 bytes (>= 24); got " + wrapped.length);
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
var d = nodeCrypto.createDecipheriv("aes" + unwrappingKey.algorithm.length + "-wrap", _secretBytes(unwrappingKey), Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
|
|
595
|
+
bytes = Buffer.concat([d.update(wrapped), d.final()]);
|
|
596
|
+
} catch (e) { throw new WebCryptoError("webcrypto/operation", "unwrapKey: AES-KW key unwrap failed (integrity or length)", e); }
|
|
551
597
|
} else {
|
|
552
598
|
var unwrapKeyClone = _cloneWithUsage(unwrappingKey, "decrypt");
|
|
553
599
|
bytes = Buffer.from(await this.decrypt(unwrapAlgorithm, unwrapKeyClone, wrappedKey));
|
|
@@ -575,6 +621,17 @@ function _cloneWithUsage(key, usage) {
|
|
|
575
621
|
return k;
|
|
576
622
|
}
|
|
577
623
|
|
|
624
|
+
// A raw or JWK-oct AES key MUST be 128/192/256 bits: W3C WebCrypto importKey rejects a
|
|
625
|
+
// non-conforming length as a DataError AT IMPORT, never a CryptoKey that only faults at
|
|
626
|
+
// first use (where node would build an "aes-160-gcm" cipher that fails only then). HMAC /
|
|
627
|
+
// HKDF / PBKDF2 keys carry no length restriction, so the check is scoped to the AES names.
|
|
628
|
+
function _assertAesImportLen(name, byteLen) {
|
|
629
|
+
if ((name === "AES-GCM" || name === "AES-CBC" || name === "AES-CTR" || name === "AES-KW") &&
|
|
630
|
+
byteLen !== 16 && byteLen !== 24 && byteLen !== 32) {
|
|
631
|
+
throw new WebCryptoError("webcrypto/data", name + ": an imported AES key must be 128, 192, or 256 bits");
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
578
635
|
SubtleCrypto.prototype.importKey = async function importKey(format, keyData, algorithm, extractable, keyUsages) {
|
|
579
636
|
var alg = _normalizeAlg(algorithm, "importKey");
|
|
580
637
|
var usages = keyUsages || [];
|
|
@@ -584,6 +641,7 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
|
|
|
584
641
|
// Symmetric raw material, or a raw public key for EC/OKP.
|
|
585
642
|
if (name === "AES-GCM" || name === "AES-CBC" || name === "AES-CTR" || name === "AES-KW" || name === "HMAC" || name === "HKDF" || name === "PBKDF2") {
|
|
586
643
|
var raw = _toBuf(keyData, "importKey raw");
|
|
644
|
+
_assertAesImportLen(name, raw.length);
|
|
587
645
|
var secret = nodeCrypto.createSecretKey(raw);
|
|
588
646
|
var symAlg = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash), length: raw.length * 8 } : { name: name, length: raw.length * 8 };
|
|
589
647
|
return new CryptoKey("secret", extractable, symAlg, usages, secret);
|
|
@@ -604,6 +662,7 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
|
|
|
604
662
|
var jwk = keyData;
|
|
605
663
|
if (jwk.kty === "oct") {
|
|
606
664
|
var kbuf = _b64urlToBuf(jwk.k, "JWK oct key material");
|
|
665
|
+
_assertAesImportLen(name, kbuf.length);
|
|
607
666
|
var s2 = nodeCrypto.createSecretKey(kbuf);
|
|
608
667
|
var a2 = (name === "HMAC") ? { name: name, hash: _hashObj(alg.hash), length: kbuf.length * 8 } : { name: name, length: kbuf.length * 8 };
|
|
609
668
|
return new CryptoKey("secret", extractable, a2, usages, s2);
|
|
@@ -633,7 +692,28 @@ function _importRawPublic(name, alg, raw, extractable, usages) {
|
|
|
633
692
|
throw new WebCryptoError("webcrypto/not-supported", "importKey raw: unsupported public-key algorithm " + JSON.stringify(name));
|
|
634
693
|
}
|
|
635
694
|
|
|
695
|
+
// The node asymmetricKeyType(s) each imported asymmetric algorithm requires. The key TYPE
|
|
696
|
+
// is a property of the key material, not the caller's claim: an RSA key imported under
|
|
697
|
+
// {name:"Ed25519"} must be a DataError, not a CryptoKey mislabeled Ed25519 that then signs
|
|
698
|
+
// under the wrong scheme (algorithm confusion). EC is additionally curve-validated below.
|
|
699
|
+
var IMPORT_KEY_TYPE = {
|
|
700
|
+
"RSASSA-PKCS1-V1_5": { rsa: 1 }, "RSA-PSS": { rsa: 1, "rsa-pss": 1 }, "RSA-OAEP": { rsa: 1 },
|
|
701
|
+
"ECDSA": { ec: 1 }, "ECDH": { ec: 1 },
|
|
702
|
+
"ED25519": { ed25519: 1 }, "ED448": { ed448: 1 },
|
|
703
|
+
"X25519": { x25519: 1 }, "X448": { x448: 1 },
|
|
704
|
+
};
|
|
705
|
+
// The FIPS 203/204/205 PQC families import the same key-is-authority rule: each WebCrypto
|
|
706
|
+
// algorithm name maps to exactly its node asymmetricKeyType, so an RSA (or any) key imported
|
|
707
|
+
// under an ML-DSA / ML-KEM / SLH-DSA name is a DataError, not a mislabeled PQC CryptoKey.
|
|
708
|
+
[ML_DSA_NODE, ML_KEM_NODE, SLH_DSA_NODE].forEach(function (m) {
|
|
709
|
+
Object.keys(m).forEach(function (webName) { var t = {}; t[m[webName]] = 1; IMPORT_KEY_TYPE[webName] = t; });
|
|
710
|
+
});
|
|
636
711
|
function _algFromImport(name, alg, keyObject) {
|
|
712
|
+
var wantType = IMPORT_KEY_TYPE[name];
|
|
713
|
+
if (wantType && keyObject && keyObject.asymmetricKeyType && !wantType[keyObject.asymmetricKeyType]) {
|
|
714
|
+
throw new WebCryptoError("webcrypto/data", name + ": the imported key is a " + keyObject.asymmetricKeyType +
|
|
715
|
+
" key, which is not compatible with algorithm " + name);
|
|
716
|
+
}
|
|
637
717
|
if (name === "ECDSA" || name === "ECDH") {
|
|
638
718
|
// W3C WebCrypto EC import -- the curve is a property of the KEY, not the
|
|
639
719
|
// caller's claim. Derive it from the imported key material; reject an
|
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:fb3ea0b4-6f7b-459d-b24d-19dcdee7f0a8",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-13T08:58:47.895Z",
|
|
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.2.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.2.8",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.2.
|
|
25
|
+
"version": "0.2.8",
|
|
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.2.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.2.8",
|
|
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.2.
|
|
57
|
+
"ref": "@blamejs/pki@0.2.8",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|