@blamejs/pki 0.8.60 → 0.8.61
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 +24 -0
- package/lib/cmc-build.js +14 -9
- package/lib/cms-decrypt.js +15 -15
- package/lib/cms-digest.js +2 -3
- package/lib/cms-encrypt.js +15 -16
- package/lib/composite-kem.js +28 -29
- package/lib/crmf-sign.js +11 -17
- package/lib/ct.js +11 -9
- package/lib/guard-crypto.js +506 -14
- package/lib/guard-secret.js +3 -2
- package/lib/hpke.js +24 -24
- package/lib/http-digest.js +5 -7
- package/lib/key.js +41 -29
- package/lib/ocsp.js +1 -2
- package/lib/pbes2.js +6 -7
- package/lib/pkcs12-build.js +5 -6
- package/lib/pki-build.js +3 -3
- package/lib/scep.js +7 -8
- package/lib/schema-tsp.js +3 -3
- package/lib/shbs.js +3 -6
- package/lib/sign-scheme.js +2 -2
- package/lib/sigstore.js +9 -9
- package/lib/smime.js +1 -2
- package/lib/tlog.js +3 -2
- package/lib/tuf.js +4 -3
- package/lib/validator-cose.js +2 -2
- package/lib/webauthn-mds.js +3 -7
- package/lib/webauthn.js +4 -11
- package/lib/webcrypto.js +61 -46
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ 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.8.61 — 2026-10-07
|
|
8
|
+
|
|
9
|
+
Take every cryptographic operation, and every question about which key a key is, through an operation captured when the toolkit loads, so a replacement installed afterwards cannot decide a secret, a tag, a key type or a verdict.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- The internal crypto guard gained the operations the library needed to stop reading `node:crypto` at call time: randomness, the password-based and extract-and-expand derivations, the RSA transforms, the elliptic-curve point derivation and conversion, the AEAD transform methods, the hash handle for a caller that cannot hold its whole input, and the four key-identity accessors. The digest primitive takes input and output encodings. None of this is on the public surface.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `pki.webcrypto.subtle.exportKey` and `pki.key.export` read a key's type at most twice per call, once to admit the key and once for every decision that follows. Nothing in the public surface changed shape, and no error code was added or removed.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- An imported key is bound to the algorithm the caller named it under. `pki.webcrypto.subtle.importKey` settles that by asking the key which algorithm family it belongs to, and that answer came from an accessor on a node prototype, read at the moment of the check. An accessor answering `rsa` for an Ed25519 key therefore made the import accept it as RSA-PSS, and every later operation ran under an algorithm the key does not belong to. The accessor is captured when the engine loads. The same read also decided a curve, a modulus length and whether a key pair corresponds, across `pki.key`, `pki.tuf`, `pki.tlog`, `pki.sigstore`, `pki.cmc`, `pki.crmf`, `pki.ct` and the signature-scheme check that matches a signer to a certificate.
|
|
22
|
+
- A plain object carrying an `asymmetricKeyType` field is no longer accepted as a key. The sites that asked whether a caller had handed them a key object tested for that one field, which any object can carry, so a forged object passed the test and was then used as a key. The question is asked of the object's prototype now, and the answer settles both whether it is a key and which kind.
|
|
23
|
+
- A shared secret is derived through the agreement the toolkit captured, not one replaced afterwards. `crypto.diffieHellman` returns the traditional half of a hybrid secret outright, so a replacement supplies that half: driving `pki.hpke.decap` with one returning a fixed buffer derived a secret from the value the replacement chose. The post-quantum half, through `encapsulate` and `decapsulate`, had the same shape, as did the key generation that decides the ephemeral key an encapsulation is made under. `pki.hpke`, `pki.kem`, `pki.key`, `pki.cms` and `pki.webcrypto` all take these through operations captured at load.
|
|
24
|
+
- A content-encryption key, a MAC key, an IV, a nonce and a salt are drawn through the randomness the toolkit captured. Twenty-seven sites read it off the module handle when they needed it, so a replacement returning a value it already knows handed it every message those protect, and nothing downstream can tell a chosen key from a random one.
|
|
25
|
+
- An AEAD message is authenticated against the tag it carries. The tag is set and read through methods on the cipher object, which were reached through that object: a replacement decides which tag a ciphertext is checked against, so the verdict stops being a function of the message. The additional authenticated data, the padding mode, and the bytes a transform produces are reached the same way and are now taken through operations captured at load. The streaming paths in `pki.webcrypto` already did this; the one-shot encrypt and decrypt did not.
|
|
26
|
+
- A key-transport ciphertext is produced, and the secret inside one recovered, through the RSA transforms captured at load. A password-derived key-encryption key, an HKDF expansion, and the elliptic-curve point a scalar generates are the same shape: each returns a value rather than a claim about one, so a replacement returning a fixed value makes every password open the message or puts a point of its own choosing where a public key was expected.
|
|
27
|
+
- `pki.key.export` emits a PEM label that agrees with the bytes in the block. It asked the key its type once to choose the label and again to choose the encoding, and on a `CryptoKey` from node's own WebCrypto that is an accessor, so an accessor answering differently across those reads picked the two separately and a public key came back inside a `PRIVATE KEY` block. The type is read once and every decision in an export comes from that one answer.
|
|
28
|
+
- A digest is computed through the hash operation the toolkit captured, across every path that takes one: the certificate and CRL digests, the CMS message digests and countersignature imprints, the transparency-log identifiers, the PKCS#12 key derivation, the hash-based signature trees, the WebAuthn attestation comparisons and the HTTP Digest credential. `pki.scep`'s check that a named digest is available goes through the same operation, so the name it accepts is the name it would use.
|
|
29
|
+
- The RFC 7616 HTTP Digest credential is computed over the octets of the latin1 encoding the specification names, through one operation rather than a copy of the hash construction kept in the module. Three modules held their own copy of what the digest primitive already does; all three route through it now, and the input and output encodings are named at the call rather than defaulted.
|
|
30
|
+
|
|
7
31
|
## v0.8.60 — 2026-10-07
|
|
8
32
|
|
|
9
33
|
Build every list a parse or an encode reads from by defining its indexes, so an accessor inherited from the array prototype cannot take an element as it is added.
|
package/lib/cmc-build.js
CHANGED
|
@@ -470,8 +470,8 @@ function identityProofV2(secret, reqSequenceBytes, identity) {
|
|
|
470
470
|
_ownSecret(Buffer.from(identity, "utf8"), owned),
|
|
471
471
|
]), owned);
|
|
472
472
|
}
|
|
473
|
-
key =
|
|
474
|
-
var witness =
|
|
473
|
+
key = guard.crypto.digest("sha256", material);
|
|
474
|
+
var witness = guard.crypto.hmac("sha256", key, reqSequenceBytes);
|
|
475
475
|
return b.sequence([algId(OID_SHA256), algId(OID_HMAC_SHA256), b.octetString(witness)]);
|
|
476
476
|
} finally {
|
|
477
477
|
if (key) guard.secret.zeroize(key, CmcError, "cmc/bad-input", "the Identity Proof MAC key");
|
|
@@ -541,7 +541,7 @@ function _provesRequestKey(opening, outgoing) {
|
|
|
541
541
|
/** @internal Keys of different types are never the same key, and comparing them through the
|
|
542
542
|
* runtime's equality leaves a fault queued in the crypto library that surfaces on the next
|
|
543
543
|
* unrelated key import in the process, so the type decides that case first. */
|
|
544
|
-
if (requested
|
|
544
|
+
if (guard.crypto.keyType(requested) !== guard.crypto.keyType(opening.key)) return false;
|
|
545
545
|
return intrinsic.keyEquals(requested, opening.key);
|
|
546
546
|
}
|
|
547
547
|
/** @internal A key the runtime engine cannot read has no KeyObject to compare, and a composite
|
|
@@ -607,7 +607,7 @@ async function _recoverProofValue(decoded, recipient, witnessHash) {
|
|
|
607
607
|
continue; // allow:swallow-unverified this recipient is not addressed to the supplied key; the witness decides
|
|
608
608
|
}
|
|
609
609
|
var candidate = opened.content;
|
|
610
|
-
var computed =
|
|
610
|
+
var computed = guard.crypto.digest(witnessHash, candidate);
|
|
611
611
|
var matches = computed.length === decoded.witness.length &&
|
|
612
612
|
guard.crypto.constantTimeEqual(computed, decoded.witness);
|
|
613
613
|
guard.secret.zeroize(computed, CmcError, "cmc/bad-input", "the computed witness");
|
|
@@ -674,7 +674,7 @@ async function answerPopChallenge(popChallenge, ids) {
|
|
|
674
674
|
* in the challenge at all: neither side re-encodes it. */
|
|
675
675
|
macKey = _bufferFrom(proofValue.length > POP_MAC_KEY_MAX_BYTES
|
|
676
676
|
? _subarray(proofValue, 0, POP_MAC_KEY_MAX_BYTES) : proofValue);
|
|
677
|
-
var thePOP =
|
|
677
|
+
var thePOP = guard.crypto.hmac(macHash, macKey, decoded.requestBytes);
|
|
678
678
|
return { algBytes: decoded.thePOPAlgID.bytes, thePOP: thePOP, ids: ids,
|
|
679
679
|
recipientSpki: await _openingPublicKey(recipient, recovered.recipientType),
|
|
680
680
|
challengedRequest: _requestIdentity(decoded.request) };
|
|
@@ -688,13 +688,18 @@ async function answerPopChallenge(popChallenge, ids) {
|
|
|
688
688
|
|
|
689
689
|
function popLinkWitnessV2(secret, R) {
|
|
690
690
|
_assertSecret(secret, "a POP Link Witness V2 control");
|
|
691
|
-
|
|
691
|
+
/** @internal The secret's octets, held so the finally can wipe them. Handing the string and an
|
|
692
|
+
* encoding to a digest instead leaves the copy inside the provider, where nothing clears it. */
|
|
693
|
+
var material = _bufferFrom(secret, "utf8");
|
|
694
|
+
var key = null;
|
|
692
695
|
try {
|
|
696
|
+
key = guard.crypto.digest("sha256", material);
|
|
693
697
|
/** @internal Inside the try, so a MAC that faults on its message still wipes the key above it. */
|
|
694
|
-
var witness =
|
|
698
|
+
var witness = guard.crypto.hmac("sha256", key, R);
|
|
695
699
|
return b.sequence([algId(OID_SHA256), algId(OID_HMAC_SHA256), b.octetString(witness)]);
|
|
696
700
|
} finally {
|
|
697
|
-
guard.secret.zeroize(
|
|
701
|
+
guard.secret.zeroize(material, CmcError, "cmc/bad-input", "the POP Link secret");
|
|
702
|
+
if (key !== null) guard.secret.zeroize(key, CmcError, "cmc/bad-input", "the POP Link MAC key");
|
|
698
703
|
}
|
|
699
704
|
}
|
|
700
705
|
|
|
@@ -935,7 +940,7 @@ async function _build(spec, signer, opts) {
|
|
|
935
940
|
if (typeof rBytes !== "number" || !Number.isInteger(rBytes) || rBytes < 1) {
|
|
936
941
|
throw E("cmc/bad-input", "popLinkRandomBytes must be a positive integer");
|
|
937
942
|
}
|
|
938
|
-
var R =
|
|
943
|
+
var R = guard.crypto.randomBytes(rBytes);
|
|
939
944
|
_push(controls,encodeControl(OID_POP_LINK_RANDOM, [b.octetString(R)], ids));
|
|
940
945
|
_push(controls,encodeControl(O("id-cmc-popLinkWitnessV2"),
|
|
941
946
|
[popLinkWitnessV2(spec.popLink.secret, R)], ids));
|
package/lib/cms-decrypt.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// @internal
|
|
5
5
|
// @primitive pki.cms.decrypt block live in cms-verify.js, which re-exports this function.
|
|
6
6
|
|
|
7
|
-
var nodeCrypto = require("crypto");
|
|
8
7
|
var asn1 = require("./asn1-der");
|
|
9
8
|
var oid = require("./oid");
|
|
10
9
|
var x509 = require("./schema-x509");
|
|
@@ -235,8 +234,8 @@ async function _ktriCek(ri, km) {
|
|
|
235
234
|
return Buffer.from(await subtle.decrypt({ name: "RSA-OAEP" }, pub, ri.encryptedKey));
|
|
236
235
|
}
|
|
237
236
|
if (kea.oid === O("rsaEncryption")) {
|
|
238
|
-
var keyObj =
|
|
239
|
-
try { return
|
|
237
|
+
var keyObj = guard.crypto.privateKey({ key: k.der, format: "der", type: "pkcs8" });
|
|
238
|
+
try { return guard.crypto.privateDecrypt({ key: keyObj, padding: guard.crypto.padding.RSA_PKCS1 }, ri.encryptedKey); }
|
|
240
239
|
catch (_e) {
|
|
241
240
|
return null;
|
|
242
241
|
}
|
|
@@ -339,7 +338,7 @@ async function _pwriCek(ri, km, opts) {
|
|
|
339
338
|
var iv = asn1.read.octetString(inner.children[1]);
|
|
340
339
|
var pw = pbes2.passwordBytesOwned(km.password, _err, "cms");
|
|
341
340
|
var kek;
|
|
342
|
-
try { kek =
|
|
341
|
+
try { kek = guard.crypto.pbkdf2(pw.bytes, pb.salt, pb.iterations, innerBits / 8, pb.prfNode); }
|
|
343
342
|
finally { if (pw.owned) guard.secret.zeroize(pw.bytes, CmsError, "cms/bad-input", "the password encoding"); }
|
|
344
343
|
try {
|
|
345
344
|
return _pwriUnwrap(kek, ri.encryptedKey, iv, innerBits);
|
|
@@ -421,7 +420,7 @@ async function _openContent(parsed, eci, cek, ct, streamMode) {
|
|
|
421
420
|
}
|
|
422
421
|
if (eci.encryptedContent == null) throw _err("cms/no-encrypted-content", "the message has no encryptedContent (detached; supply it out of band)");
|
|
423
422
|
var substitute = null;
|
|
424
|
-
if (cek == null || cek.length !== keyBits / 8) { substitute =
|
|
423
|
+
if (cek == null || cek.length !== keyBits / 8) { substitute = guard.crypto.randomBytes(keyBits / 8); cek = substitute; }
|
|
425
424
|
try {
|
|
426
425
|
if (ct === "authEnvelopedData") {
|
|
427
426
|
var aad = parsed.authAttrsBytes != null ? _explicitSetOf(parsed.authAttrsBytes) : Buffer.alloc(0);
|
|
@@ -461,9 +460,9 @@ async function _openContent(parsed, eci, cek, ct, streamMode) {
|
|
|
461
460
|
}
|
|
462
461
|
function _gcmOpen(cek, nonce, ct, tag, aad, keyBits, icvLen) {
|
|
463
462
|
if (!tag || tag.length !== icvLen) throw _fail();
|
|
464
|
-
var d =
|
|
465
|
-
|
|
466
|
-
if (aad && aad.length)
|
|
463
|
+
var d = guard.crypto.decipher("aes-" + keyBits + "-gcm", cek, nonce, { authTagLength: icvLen });
|
|
464
|
+
guard.crypto.decipherSetAuthTag(d, tag);
|
|
465
|
+
if (aad && aad.length) guard.crypto.transformSetAAD(d, aad);
|
|
467
466
|
return guard.secret.cipherFinish(d, ct, CmsError, "cms/decrypt-failed", "the recovered content");
|
|
468
467
|
}
|
|
469
468
|
|
|
@@ -543,7 +542,7 @@ async function _decryptPbes2(parsed, eci, km, opts) {
|
|
|
543
542
|
if (!keyBits) throw _err("cms/unsupported-algorithm", "unsupported PBES2 content cipher " + encOid);
|
|
544
543
|
var pwE = pbes2.passwordBytesOwned(km.password, _err, "cms");
|
|
545
544
|
var key;
|
|
546
|
-
try { key =
|
|
545
|
+
try { key = guard.crypto.pbkdf2(pwE.bytes, pb.salt, pb.iterations, keyBits / 8, pb.prfNode); }
|
|
547
546
|
finally { if (pwE.owned) guard.secret.zeroize(pwE.bytes, CmsError, "cms/bad-input", "the password encoding"); }
|
|
548
547
|
try { return _originFields({ content: opts.stream === true ? _cbcStream(key, iv, eci.encryptedContent, keyBits) : pbes2.cbcDecrypt(key, iv, eci.encryptedContent, keyBits, CmsError, "cms/decrypt-failed"), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "password", recipientIndex: -1, contentEncryptionAlgorithm: oid.name(encOid) || encOid, authenticated: false }, null, null); }
|
|
549
548
|
catch (_e) { throw _fail(); }
|
|
@@ -588,13 +587,13 @@ function _pwriUnwrap(kek, wrapped, iv, keyBits) {
|
|
|
588
587
|
var n = wrapped.length;
|
|
589
588
|
var lastDec = null, pass1 = null, body = null;
|
|
590
589
|
try {
|
|
591
|
-
var ecb =
|
|
590
|
+
var ecb = guard.crypto.decipher("aes-" + keyBits + "-ecb", kek, Buffer.alloc(0)); guard.crypto.transformNoPadding(ecb);
|
|
592
591
|
lastDec = guard.secret.cipherFinish(ecb, wrapped.subarray(n - blk), CmsError, "cms/decrypt-failed", "the recovered CBC last block");
|
|
593
592
|
var iv2 = Buffer.alloc(blk);
|
|
594
593
|
for (var i = 0; i < blk; i++) iv2[i] = lastDec[i] ^ wrapped[n - 2 * blk + i];
|
|
595
|
-
var d1 =
|
|
594
|
+
var d1 = guard.crypto.decipher(alg, kek, iv2); guard.crypto.transformNoPadding(d1);
|
|
596
595
|
pass1 = guard.secret.cipherFinish(d1, wrapped, CmsError, "cms/decrypt-failed", "the PWRI first-pass plaintext");
|
|
597
|
-
var d2 =
|
|
596
|
+
var d2 = guard.crypto.decipher(alg, kek, iv); guard.crypto.transformNoPadding(d2);
|
|
598
597
|
body = guard.secret.cipherFinish(d2, pass1, CmsError, "cms/decrypt-failed", "the PWRI plaintext block");
|
|
599
598
|
var count = body[0];
|
|
600
599
|
if (count < 1 || count + 4 > body.length) throw _fail();
|
|
@@ -670,8 +669,9 @@ function _ecCurveFromCert(cert) {
|
|
|
670
669
|
function _ecCurveFromPrivateKey(keyDer) {
|
|
671
670
|
var spki;
|
|
672
671
|
try {
|
|
673
|
-
spki =
|
|
674
|
-
.
|
|
672
|
+
spki = guard.crypto.exportKey(
|
|
673
|
+
guard.crypto.publicKey({ key: keyDer, format: "der", type: "pkcs8" }),
|
|
674
|
+
{ format: "der", type: "spki" });
|
|
675
675
|
} catch (_e) { return null; } // allow:swallow-unverified a key this runtime cannot read names no curve
|
|
676
676
|
var alg = asn1.decode(spki).children[0];
|
|
677
677
|
if (asn1.read.oid(alg.children[0]) !== O("ecPublicKey") || alg.children.length < 2) return null;
|
|
@@ -756,7 +756,7 @@ async function _verifyAuthenticatedData(parsed, km, opts, requireProtection) {
|
|
|
756
756
|
_assertSupported(candidates[ci].ri, km);
|
|
757
757
|
var macKey = await _acquireCek(candidates[ci].ri, km, opts);
|
|
758
758
|
var macSubstitute = null;
|
|
759
|
-
if (macKey == null || macKey.length < MAC_KEY_MIN_OCTETS) macSubstitute =
|
|
759
|
+
if (macKey == null || macKey.length < MAC_KEY_MIN_OCTETS) macSubstitute = guard.crypto.randomBytes(MAC_KEY_MIN_OCTETS);
|
|
760
760
|
try {
|
|
761
761
|
var key = await subtle.importKey("raw", macSubstitute || macKey, { name: "HMAC", hash: hash }, false, ["verify"]);
|
|
762
762
|
if (!(await _subtleVerify(subtle, { name: "HMAC" }, key, Buffer.from(parsed.mac), preimage))) throw _fail();
|
package/lib/cms-digest.js
CHANGED
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
* attached or detached, constant-time comparison, fail-closed.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
var nodeCrypto = require("node:crypto");
|
|
30
29
|
var oid = require("./oid");
|
|
31
30
|
var schemaCms = require("./schema-cms");
|
|
32
31
|
var frameworkError = require("./framework-error");
|
|
@@ -101,7 +100,7 @@ async function _digest(content, opts) {
|
|
|
101
100
|
var ctOid = O(ctName);
|
|
102
101
|
if (!ctOid) throw _err("cms/bad-input", "opts.contentType is not a known OID name: " + guard.text.showValue(ctName));
|
|
103
102
|
|
|
104
|
-
var value =
|
|
103
|
+
var value = guard.crypto.digest(NODE_DIGEST[alg], raw);
|
|
105
104
|
var eciChildren = opts.detached === true
|
|
106
105
|
? [b.oid(ctOid)]
|
|
107
106
|
: [b.oid(ctOid), b.explicit(0, b.octetString(raw))];
|
|
@@ -176,7 +175,7 @@ async function _verifyDigest(input, opts) {
|
|
|
176
175
|
var covered = embedded != null
|
|
177
176
|
? guard.bytes.snapshotSource(embedded, CmsError, "cms/bad-input", "the encapsulated content")
|
|
178
177
|
: supplied;
|
|
179
|
-
var recomputed =
|
|
178
|
+
var recomputed = guard.crypto.digest(NODE_DIGEST[alg], covered);
|
|
180
179
|
var matched = guard.crypto.constantTimeEqual(recomputed, parsed.digest);
|
|
181
180
|
return guard.verdict.of({
|
|
182
181
|
valid: matched,
|
package/lib/cms-encrypt.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// @internal
|
|
5
5
|
// @primitive pki.cms.encrypt block live in cms-verify.js, which re-exports this function (the
|
|
6
6
|
|
|
7
|
-
var nodeCrypto = require("crypto");
|
|
8
7
|
var asn1 = require("./asn1-der");
|
|
9
8
|
var pkiBuild = require("./pki-build");
|
|
10
9
|
var oid = require("./oid");
|
|
@@ -203,7 +202,7 @@ async function _buildKekri(cek, desc) {
|
|
|
203
202
|
|
|
204
203
|
async function _buildPwri(cek, desc) {
|
|
205
204
|
var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
|
|
206
|
-
var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") :
|
|
205
|
+
var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : guard.crypto.randomBytes(16);
|
|
207
206
|
var prf = desc.prf || "hmacWithSHA256";
|
|
208
207
|
_prfHash(prf);
|
|
209
208
|
var innerKeyBytes = 32;
|
|
@@ -213,7 +212,7 @@ async function _buildPwri(cek, desc) {
|
|
|
213
212
|
var kek = Buffer.from(await subtle.deriveBits({ name: "PBKDF2", hash: _prfHash(prf), salt: salt, iterations: iterations }, kekKey, innerKeyBytes * 8));
|
|
214
213
|
if (pwOwn.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
|
|
215
214
|
try {
|
|
216
|
-
var iv =
|
|
215
|
+
var iv = guard.crypto.randomBytes(16);
|
|
217
216
|
var encryptedKey = _pwriWrapIv(kek, cek, iv);
|
|
218
217
|
var kdfParams = pbes2.pbkdf2ParamsSeq(salt, iterations, prf);
|
|
219
218
|
var kdfAlg = b.contextConstructed(0, Buffer.concat([b.oid(O("pbkdf2")), kdfParams]));
|
|
@@ -288,17 +287,17 @@ function _pwriFormat(cek) {
|
|
|
288
287
|
var blk = 16;
|
|
289
288
|
var padLen = body.length % blk === 0 ? 0 : blk - (body.length % blk);
|
|
290
289
|
if (body.length + padLen < 2 * blk) padLen += (2 * blk - (body.length + padLen));
|
|
291
|
-
var wrapped = Buffer.concat([body,
|
|
290
|
+
var wrapped = Buffer.concat([body, guard.crypto.randomBytes(padLen)]);
|
|
292
291
|
guard.secret.zeroizeAll([check, body], CmsError, "cms/bad-input", "a PWRI formatting intermediate");
|
|
293
292
|
return wrapped;
|
|
294
293
|
}
|
|
295
294
|
function _pwriWrapIv(kek, cek, iv) {
|
|
296
295
|
var wk = _pwriFormat(cek);
|
|
297
296
|
try {
|
|
298
|
-
var c1 =
|
|
297
|
+
var c1 = guard.crypto.cipher("aes-256-cbc", kek, iv); guard.crypto.transformNoPadding(c1);
|
|
299
298
|
var pass1 = guard.secret.cipherFinish(c1, wk, CmsError, "cms/bad-input", "the PWRI first-pass ciphertext");
|
|
300
299
|
var iv2 = pass1.subarray(pass1.length - 16);
|
|
301
|
-
var c2 =
|
|
300
|
+
var c2 = guard.crypto.cipher("aes-256-cbc", kek, iv2); guard.crypto.transformNoPadding(c2);
|
|
302
301
|
return guard.secret.cipherFinish(c2, pass1, CmsError, "cms/bad-input", "the PWRI wrapped key");
|
|
303
302
|
} finally {
|
|
304
303
|
guard.secret.zeroize(wk, CmsError, "cms/bad-input", "the PWRI plaintext block");
|
|
@@ -444,7 +443,7 @@ async function _encryptImpl(content, recipients, opts) {
|
|
|
444
443
|
* them. */
|
|
445
444
|
var contentBytes = contentStream ? null : guard.bytes.view(content, CmsError, "cms/bad-input", "content");
|
|
446
445
|
var contentType = opts.contentType || "data";
|
|
447
|
-
var cek =
|
|
446
|
+
var cek = guard.crypto.randomBytes(ca.keyBits / 8);
|
|
448
447
|
|
|
449
448
|
try {
|
|
450
449
|
if (!Array.isArray(recipients)) return await _encryptedData(contentBytes, contentStream, recipients, ca, contentType, opts, cek);
|
|
@@ -477,7 +476,7 @@ async function _encStream(spec, stream) {
|
|
|
477
476
|
}
|
|
478
477
|
|
|
479
478
|
async function _envelopedData(contentBytes, contentStream, cek, ca, contentType, riNodes, recips) {
|
|
480
|
-
var iv =
|
|
479
|
+
var iv = guard.crypto.randomBytes(16);
|
|
481
480
|
var enc = contentStream
|
|
482
481
|
? (await _encStream({ algNode: "aes-" + ca.keyBits + "-cbc", key: cek, iv: iv }, contentStream)).ciphertext
|
|
483
482
|
: pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
|
|
@@ -522,7 +521,7 @@ var AUTH_ENVELOPED_FORBIDDEN = intrinsic.assign(intrinsic.create(null), {
|
|
|
522
521
|
});
|
|
523
522
|
|
|
524
523
|
async function _authEnvelopedData(contentBytes, contentStream, cek, ca, contentType, opts, riNodes, recips) {
|
|
525
|
-
var nonce =
|
|
524
|
+
var nonce = guard.crypto.randomBytes(12);
|
|
526
525
|
var authAttrsDer = null, aad = Buffer.alloc(0);
|
|
527
526
|
if (contentType !== "data" && !(opts.authAttrs && opts.authAttrs.length)) {
|
|
528
527
|
throw _err("cms/bad-input", "AuthEnvelopedData with a non-data contentType requires authAttrs (RFC 5083 sec. 2.1)");
|
|
@@ -561,7 +560,7 @@ async function _encryptedData(contentBytes, contentStream, desc, ca, contentType
|
|
|
561
560
|
" EncryptedData descriptor; that field is not read for this arm";
|
|
562
561
|
});
|
|
563
562
|
}
|
|
564
|
-
var iv =
|
|
563
|
+
var iv = guard.crypto.randomBytes(16);
|
|
565
564
|
var contentAlgNode, encKey;
|
|
566
565
|
if (desc && desc.cek != null) {
|
|
567
566
|
encKey = guard.bytes.view(desc.cek, CmsError, "cms/bad-input", "cek");
|
|
@@ -584,9 +583,9 @@ async function _encryptedDataPbes2(contentBytes, contentStream, desc, ca, conten
|
|
|
584
583
|
var pwOwn2 = pbes2.passwordBytesOwned(desc.password, _err, "cms");
|
|
585
584
|
var password = pwOwn2.bytes;
|
|
586
585
|
var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
|
|
587
|
-
var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") :
|
|
586
|
+
var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : guard.crypto.randomBytes(16);
|
|
588
587
|
var prf = desc.prf || "hmacWithSHA256";
|
|
589
|
-
var key =
|
|
588
|
+
var key = guard.crypto.pbkdf2(password, salt, iterations, ca.keyBits / 8, pbes2.prfNodeByName(prf, _err, "cms"));
|
|
590
589
|
if (pwOwn2.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
|
|
591
590
|
try {
|
|
592
591
|
var enc = contentStream
|
|
@@ -602,10 +601,10 @@ async function _encryptedDataPbes2(contentBytes, contentStream, desc, ca, conten
|
|
|
602
601
|
}
|
|
603
602
|
|
|
604
603
|
function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
|
|
605
|
-
var c =
|
|
606
|
-
if (aad && aad.length)
|
|
604
|
+
var c = guard.crypto.cipher("aes-" + keyBits + "-gcm", key, nonce, { authTagLength: tagLen });
|
|
605
|
+
if (aad && aad.length) guard.crypto.transformSetAAD(c, aad);
|
|
607
606
|
var ct = guard.secret.cipherFinish(c, plaintext, CmsError, "cms/bad-input", "a content-encryption intermediate");
|
|
608
|
-
return { ct: ct, tag:
|
|
607
|
+
return { ct: ct, tag: guard.crypto.cipherAuthTag(c) };
|
|
609
608
|
}
|
|
610
609
|
|
|
611
610
|
function _tlvHeaderLen(der) {
|
|
@@ -682,7 +681,7 @@ async function _authenticateImpl(content, recipients, opts) {
|
|
|
682
681
|
var contentType = opts.contentType || "data";
|
|
683
682
|
if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
|
|
684
683
|
|
|
685
|
-
var macKey =
|
|
684
|
+
var macKey = guard.crypto.randomBytes(MAC_KEY_OCTETS);
|
|
686
685
|
try {
|
|
687
686
|
var recips = [];
|
|
688
687
|
var consumedAuth = {};
|
package/lib/composite-kem.js
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
var asn1 = require("./asn1-der");
|
|
24
24
|
var oid = require("./oid");
|
|
25
25
|
var webcrypto = require("./webcrypto");
|
|
26
|
-
var nodeCrypto = require("node:crypto");
|
|
27
26
|
var frameworkError = require("./framework-error");
|
|
28
27
|
var guard = require("./guard-all");
|
|
29
28
|
var intrinsic = require("./guard-intrinsic");
|
|
@@ -115,11 +114,11 @@ function _mlkemDecaps(d, seed, mlkemCT) {
|
|
|
115
114
|
function _ecPubFromPoint(d, point) {
|
|
116
115
|
if (point.length < 1 || point[0] !== 0x04) throw _err("kem/bad-key", "the EC component point is not in uncompressed form");
|
|
117
116
|
var spki = _b.sequence([_b.sequence([_b.oid(oid.byName("ecPublicKey")), _b.oid(oid.byName(d.trad.curve))]), _b.bitString(point, 0)]);
|
|
118
|
-
return
|
|
117
|
+
return guard.crypto.publicKey({ key: spki, format: "der", type: "spki" });
|
|
119
118
|
}
|
|
120
119
|
function _okpPubFromRaw(d, raw) {
|
|
121
120
|
var spki = _b.sequence([_b.sequence([_b.oid(oid.byName(d.trad.curve))]), _b.bitString(raw, 0)]);
|
|
122
|
-
return
|
|
121
|
+
return guard.crypto.publicKey({ key: spki, format: "der", type: "spki" });
|
|
123
122
|
}
|
|
124
123
|
/** @internal The encoding is this helper's own allocation and holds the traditional private scalar, so
|
|
125
124
|
* it is cleared here. A caller's own cleanup cannot reach a buffer it never sees, and both the
|
|
@@ -127,13 +126,13 @@ function _okpPubFromRaw(d, raw) {
|
|
|
127
126
|
function _okpPrivFromRaw(d, raw) {
|
|
128
127
|
var p8 = _b.sequence([_b.integer(0n), _b.sequence([_b.oid(oid.byName(d.trad.curve))]), _b.octetString(_b.octetString(raw))]);
|
|
129
128
|
try {
|
|
130
|
-
return
|
|
129
|
+
return guard.crypto.privateKey({ key: p8, format: "der", type: "pkcs8" });
|
|
131
130
|
} finally {
|
|
132
131
|
guard.secret.zeroize(p8, KemError, "kem/bad-input", "the " + d.trad.curve + " component key encoding");
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
134
|
function _rawPubOf(keyObject) {
|
|
136
|
-
var spki =
|
|
135
|
+
var spki = guard.crypto.exportKey(keyObject, { format: "der", type: "spki" });
|
|
137
136
|
return Buffer.from(asn1.read.bitString(asn1.decode(spki).children[1]).bytes);
|
|
138
137
|
}
|
|
139
138
|
|
|
@@ -143,12 +142,12 @@ function _tradEncaps(d, tradPK) {
|
|
|
143
142
|
var pub;
|
|
144
143
|
try {
|
|
145
144
|
asn1.decode(tradPK);
|
|
146
|
-
pub =
|
|
145
|
+
pub = guard.crypto.publicKey({ key: tradPK, format: "der", type: "pkcs1" });
|
|
147
146
|
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component public key is not a valid RSAPublicKey", e)); }
|
|
148
|
-
if (pub.
|
|
149
|
-
var ss = Buffer.from(
|
|
147
|
+
if (guard.crypto.keyDetails(pub).modulusLength !== trad.bits) return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component modulus is not " + trad.bits + " bits (draft sec. 6)"));
|
|
148
|
+
var ss = Buffer.from(guard.crypto.randomBytes(RSA_SS));
|
|
150
149
|
var ct;
|
|
151
|
-
try { ct = Buffer.from(
|
|
150
|
+
try { ct = Buffer.from(guard.crypto.publicEncrypt({ key: pub, padding: guard.crypto.padding.RSA_PKCS1_OAEP, oaepHash: "sha256" }, ss)); }
|
|
152
151
|
catch (e) {
|
|
153
152
|
guard.secret.zeroize(ss, KemError, "kem/bad-input", "the RSA-OAEP component secret");
|
|
154
153
|
return _promiseReject(_Promise, _err("kem/bad-key", "the RSA-OAEP component encryption failed", e));
|
|
@@ -158,8 +157,8 @@ function _tradEncaps(d, tradPK) {
|
|
|
158
157
|
var dh, dhCt;
|
|
159
158
|
try {
|
|
160
159
|
var recip = trad.kind === "ec" ? _ecPubFromPoint(d, tradPK) : _okpPubFromRaw(d, tradPK);
|
|
161
|
-
var eph = trad.kind === "ec" ?
|
|
162
|
-
dh = Buffer.from(
|
|
160
|
+
var eph = trad.kind === "ec" ? guard.crypto.generateKeyPair("ec", { namedCurve: trad.node }) : guard.crypto.generateKeyPair(trad.curve.toLowerCase());
|
|
161
|
+
dh = Buffer.from(guard.crypto.diffieHellman({ privateKey: eph.privateKey, publicKey: recip }));
|
|
163
162
|
dhCt = _rawPubOf(eph.publicKey);
|
|
164
163
|
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the " + trad.curve + " public key is invalid", e)); }
|
|
165
164
|
return _promiseResolve(_Promise, { ss: dh, ct: dhCt });
|
|
@@ -171,37 +170,38 @@ function _tradDecaps(d, tradSK, tradCT) {
|
|
|
171
170
|
var priv;
|
|
172
171
|
try {
|
|
173
172
|
asn1.decode(tradSK);
|
|
174
|
-
priv =
|
|
173
|
+
priv = guard.crypto.privateKey({ key: tradSK, format: "der", type: "pkcs1" });
|
|
175
174
|
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component private key is not a valid RSAPrivateKey", e)); }
|
|
176
|
-
if (priv.
|
|
175
|
+
if (guard.crypto.keyDetails(priv).modulusLength !== trad.bits) return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component modulus is not " + trad.bits + " bits (draft sec. 6)"));
|
|
177
176
|
var ss;
|
|
178
|
-
try { ss = Buffer.from(
|
|
177
|
+
try { ss = Buffer.from(guard.crypto.privateDecrypt({ key: priv, padding: guard.crypto.padding.RSA_PKCS1_OAEP, oaepHash: "sha256" }, tradCT)); }
|
|
179
178
|
catch (_e) { return _promiseReject(_Promise, _err("kem/decapsulation-failed", "the RSA-OAEP component decryption failed")); }
|
|
180
179
|
if (ss.length !== RSA_SS) {
|
|
181
180
|
guard.secret.zeroize(ss, KemError, "kem/bad-input", "the RSA-OAEP component secret");
|
|
182
181
|
return _promiseReject(_Promise, _err("kem/decapsulation-failed", "the RSA-OAEP component decryption failed"));
|
|
183
182
|
}
|
|
184
|
-
return _promiseResolve(_Promise, { ss: ss,
|
|
183
|
+
return _promiseResolve(_Promise, { ss: ss,
|
|
184
|
+
tradPK: Buffer.from(guard.crypto.exportKey(guard.crypto.publicKey(priv), { format: "der", type: "pkcs1" })) });
|
|
185
185
|
}
|
|
186
186
|
var priv2, eph;
|
|
187
187
|
try {
|
|
188
188
|
if (trad.kind === "ec") {
|
|
189
189
|
asn1.decode(tradSK);
|
|
190
|
-
priv2 =
|
|
190
|
+
priv2 = guard.crypto.privateKey({ key: tradSK, format: "der", type: "sec1" });
|
|
191
191
|
} else {
|
|
192
192
|
priv2 = _okpPrivFromRaw(d, tradSK);
|
|
193
193
|
}
|
|
194
194
|
eph = trad.kind === "ec" ? _ecPubFromPoint(d, tradCT) : _okpPubFromRaw(d, tradCT);
|
|
195
195
|
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the " + trad.curve + " key material is invalid", e)); }
|
|
196
196
|
var dh;
|
|
197
|
-
try { dh = Buffer.from(
|
|
197
|
+
try { dh = Buffer.from(guard.crypto.diffieHellman({ privateKey: priv2, publicKey: eph })); }
|
|
198
198
|
catch (e) { return _promiseReject(_Promise, _err("kem/decapsulation-failed", "the " + trad.curve + " Diffie-Hellman failed", e)); }
|
|
199
199
|
/** @internal The same canonical point the derivation states, read from the one function that spells
|
|
200
200
|
* it. The combiner hashes these bytes, so a stored spelling here and a generated one there would
|
|
201
201
|
* reach different secrets from the two sides of one key. */
|
|
202
202
|
var tradPK;
|
|
203
203
|
try {
|
|
204
|
-
tradPK = trad.kind === "ec" ? _ecPointOf(d, tradSK) : _rawPubOf(
|
|
204
|
+
tradPK = trad.kind === "ec" ? _ecPointOf(d, tradSK) : _rawPubOf(guard.crypto.publicKey(priv2));
|
|
205
205
|
} catch (e) {
|
|
206
206
|
/** @internal The agreement already produced a secret, and this rejection happens after it, so the
|
|
207
207
|
* secret is wiped here: the caller's settle-or-wipe covers a value it was handed, and a rejected
|
|
@@ -371,9 +371,7 @@ function _ecPointOf(d, tradSK) {
|
|
|
371
371
|
var scalar = _ecScalarOf(d, tradSK);
|
|
372
372
|
var point;
|
|
373
373
|
try {
|
|
374
|
-
|
|
375
|
-
ecdh.setPrivateKey(scalar);
|
|
376
|
-
point = ecdh.getPublicKey(null, "uncompressed");
|
|
374
|
+
point = guard.crypto.ecPointFromScalar(d.trad.node, scalar);
|
|
377
375
|
} catch (e) {
|
|
378
376
|
if (e instanceof KemError) throw e;
|
|
379
377
|
throw _err("kem/bad-key", "the EC component scalar does not generate a point on " + d.trad.node, e);
|
|
@@ -418,18 +416,18 @@ function _tradPubOf(d, tradSK) {
|
|
|
418
416
|
var rp;
|
|
419
417
|
try {
|
|
420
418
|
asn1.decode(tradSK);
|
|
421
|
-
rp =
|
|
419
|
+
rp = guard.crypto.privateKey({ key: tradSK, format: "der", type: "pkcs1" });
|
|
422
420
|
} catch (e) { throw _err("kem/bad-key", "the RSA component private key is not a valid RSAPrivateKey", e); }
|
|
423
|
-
if (rp.
|
|
421
|
+
if (guard.crypto.keyDetails(rp).modulusLength !== trad.bits) {
|
|
424
422
|
throw _err("kem/bad-key", "the RSA component modulus is not " + trad.bits + " bits (draft sec. 6)");
|
|
425
423
|
}
|
|
426
|
-
return Buffer.from(
|
|
424
|
+
return Buffer.from(guard.crypto.exportKey(guard.crypto.publicKey(rp), { format: "der", type: "pkcs1" }));
|
|
427
425
|
}
|
|
428
426
|
var priv;
|
|
429
427
|
try {
|
|
430
428
|
if (trad.kind === "ec") {
|
|
431
429
|
asn1.decode(tradSK);
|
|
432
|
-
priv =
|
|
430
|
+
priv = guard.crypto.privateKey({ key: tradSK, format: "der", type: "sec1" });
|
|
433
431
|
} else {
|
|
434
432
|
priv = _okpPrivFromRaw(d, tradSK);
|
|
435
433
|
}
|
|
@@ -439,7 +437,8 @@ function _tradPubOf(d, tradSK) {
|
|
|
439
437
|
* public key nothing can use, so the two are held to each other here. Decapsulation reaches the same
|
|
440
438
|
* conclusion by failing the agreement; a derivation performs no operation that would notice. */
|
|
441
439
|
if (trad.kind === "ec") {
|
|
442
|
-
var
|
|
440
|
+
var privDetails = guard.crypto.keyDetails(priv);
|
|
441
|
+
var named = privDetails && privDetails.namedCurve;
|
|
443
442
|
if (named !== trad.node) {
|
|
444
443
|
throw _err("kem/bad-key", "the EC component is on " + guard.text.showValue(named) + ", and " + d.name + " names " + trad.node);
|
|
445
444
|
}
|
|
@@ -450,7 +449,7 @@ function _tradPubOf(d, tradSK) {
|
|
|
450
449
|
* point stored beside it cannot stand in for one it does not generate. */
|
|
451
450
|
return _ecPointOf(d, tradSK);
|
|
452
451
|
}
|
|
453
|
-
return _rawPubOf(
|
|
452
|
+
return _rawPubOf(guard.crypto.publicKey(priv));
|
|
454
453
|
}
|
|
455
454
|
|
|
456
455
|
/** @internal The composite `SubjectPublicKeyInfo` for a composite ML-KEM private key. The runtime key
|
|
@@ -488,8 +487,8 @@ async function _publicFromPrivate(privateKey) {
|
|
|
488
487
|
* copy of the seed and is registered as one. */
|
|
489
488
|
var seedPkcs8 = _mlkemSeedPkcs8(d, seed);
|
|
490
489
|
guard.list.append(secrets, seedPkcs8);
|
|
491
|
-
var mlPriv =
|
|
492
|
-
ek = _rawPubOf(
|
|
490
|
+
var mlPriv = guard.crypto.privateKey({ key: seedPkcs8, format: "der", type: "pkcs8" });
|
|
491
|
+
ek = _rawPubOf(guard.crypto.publicKey(mlPriv));
|
|
493
492
|
} catch (e) {
|
|
494
493
|
if (e instanceof KemError) throw e;
|
|
495
494
|
throw _err("kem/bad-key", "the ML-KEM component private key is invalid", e);
|
package/lib/crmf-sign.js
CHANGED
|
@@ -17,12 +17,6 @@
|
|
|
17
17
|
* @card Build a CRMF CertReqMessages with a signature proof of possession.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
var nodeCrypto = require("node:crypto");
|
|
21
|
-
/** @internal Bound at module load, like every other capture here: the proof below decides what it
|
|
22
|
-
* agrees and what it hashes, so a replacement swapped in afterwards must not be able to steer it. */
|
|
23
|
-
var _diffieHellman = nodeCrypto.diffieHellman;
|
|
24
|
-
var _createPublicKey = nodeCrypto.createPublicKey;
|
|
25
|
-
var _createPrivateKey = nodeCrypto.createPrivateKey;
|
|
26
20
|
var asn1 = require("./asn1-der");
|
|
27
21
|
var oid = require("./oid");
|
|
28
22
|
var x509 = require("./schema-x509");
|
|
@@ -345,7 +339,7 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
345
339
|
_assertAgreementExponent(pkcs3, "the authority certificate's key");
|
|
346
340
|
}
|
|
347
341
|
}
|
|
348
|
-
try { caPub =
|
|
342
|
+
try { caPub = guard.crypto.publicKey({ key: caSpkiBytes, format: "der", type: "spki" }); }
|
|
349
343
|
catch (e) { throw _err("crmf/bad-popo", "the authority certificate does not carry a key this runtime can agree with", e); }
|
|
350
344
|
/** @internal The snapshot of the caller's key is this function's own copy of private material, so it
|
|
351
345
|
* is registered for the wipe below rather than left to the argument boundary, which clears what it
|
|
@@ -357,7 +351,7 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
357
351
|
/** @internal Inside the cleanup scope, because the import can throw AFTER the snapshot exists: a
|
|
358
352
|
* catch that returned from out here would leave that copy of private material behind. */
|
|
359
353
|
try {
|
|
360
|
-
if (
|
|
354
|
+
if (guard.crypto.keyTypeOf(pop.key) != null) {
|
|
361
355
|
eePriv = pop.key;
|
|
362
356
|
} else if (pop.key && typeof pop.key === "object" && pop.key.type === "private" &&
|
|
363
357
|
typeof pop.key.export === "function") {
|
|
@@ -366,10 +360,10 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
366
360
|
* applies here: supporting the encoding for a caller holding bytes and refusing the caller
|
|
367
361
|
* holding the key those bytes import to would be the same key admitted one way. The export
|
|
368
362
|
* is this function's own copy of private material and is cleared with the rest. */
|
|
369
|
-
keySnapshot = pop.key
|
|
363
|
+
keySnapshot = guard.crypto.exportKey(pop.key, { format: "der", type: "pkcs8" });
|
|
370
364
|
translated = _x942PrivToPkcs3(keySnapshot);
|
|
371
365
|
eePriv = translated !== null
|
|
372
|
-
?
|
|
366
|
+
? guard.crypto.privateKey({ key: translated, format: "der", type: "pkcs8" })
|
|
373
367
|
: pop.key;
|
|
374
368
|
} else if (guard.bytes.isByteSource(pop.key)) {
|
|
375
369
|
keySnapshot = guard.bytes.snapshotSource(pop.key, CrmfError, "crmf/bad-popo", "the requester's private key");
|
|
@@ -377,7 +371,7 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
377
371
|
* copy of the private value, and an import that throws after it exists must not leave it
|
|
378
372
|
* behind. The wipe below clears whatever was made, on every way out. */
|
|
379
373
|
translated = _x942PrivToPkcs3(keySnapshot);
|
|
380
|
-
eePriv =
|
|
374
|
+
eePriv = guard.crypto.privateKey({ key: translated || keySnapshot, format: "der", type: "pkcs8" });
|
|
381
375
|
} else {
|
|
382
376
|
/** @internal A PEM key reaches the same conversion: it is the same key in the same encoding
|
|
383
377
|
* with an armor around it, so admitting one form and not the other would support X9.42 for a
|
|
@@ -386,9 +380,9 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
386
380
|
if (pemDer !== null) {
|
|
387
381
|
keySnapshot = pemDer;
|
|
388
382
|
translated = _x942PrivToPkcs3(keySnapshot);
|
|
389
|
-
eePriv =
|
|
383
|
+
eePriv = guard.crypto.privateKey({ key: translated || keySnapshot, format: "der", type: "pkcs8" });
|
|
390
384
|
} else {
|
|
391
|
-
eePriv =
|
|
385
|
+
eePriv = guard.crypto.privateKey(pop.key);
|
|
392
386
|
}
|
|
393
387
|
}
|
|
394
388
|
} catch (e) {
|
|
@@ -402,7 +396,7 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
402
396
|
* something this request never asked to have certified. The encryptedKey arm holds its enclosed
|
|
403
397
|
* key to the same rule. */
|
|
404
398
|
var eeSpki;
|
|
405
|
-
try { eeSpki =
|
|
399
|
+
try { eeSpki = guard.crypto.exportKey(guard.crypto.publicKey(eePriv), { format: "der", type: "spki" }); }
|
|
406
400
|
catch (e) { throw _err("crmf/bad-popo", "the public half of pop.key could not be derived to check it against the requested key", e); }
|
|
407
401
|
/** @internal Compared as keys, not as bytes. A PKCS#3 DHParameter carries an optional
|
|
408
402
|
* privateValueLength, so one finite-field key has more than one valid SubjectPublicKeyInfo and a
|
|
@@ -449,7 +443,7 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
449
443
|
* with parameters g and p, agreeing `g^xy mod p`. An elliptic-curve or montgomery pair agrees a
|
|
450
444
|
* secret through the same call and would be emitted under the same OID, which names an operation
|
|
451
445
|
* the recipient would not be performing. */
|
|
452
|
-
if (eePriv
|
|
446
|
+
if (guard.crypto.keyType(eePriv) !== "dh" || guard.crypto.keyType(caPub) !== "dh") {
|
|
453
447
|
/** @internal A key this runtime imported but did not classify reports no asymmetricKeyType, which
|
|
454
448
|
* is what an X9.42 dhpublicnumber SubjectPublicKeyInfo does here where the PKCS#3 dhKeyAgreement
|
|
455
449
|
* form is classified. The agreement refuses such a key on its own, so naming the unrecognized
|
|
@@ -459,7 +453,7 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
459
453
|
}
|
|
460
454
|
/** @internal The agreement's own Buffer, not a copy of it: wrapping it would leave the original
|
|
461
455
|
* holding the secret with nothing to clear it. */
|
|
462
|
-
try { zz =
|
|
456
|
+
try { zz = guard.crypto.diffieHellman({ privateKey: eePriv, publicKey: caPub }); }
|
|
463
457
|
catch (e) { throw _err("crmf/bad-popo", "the requester's key and the authority's key did not agree a secret; this proof needs a key on the authority's own parameters (RFC 4211 sec. 4.3)", e); }
|
|
464
458
|
/** @internal K = SHA1(LeadingInfo | ZZ | TrailingInfo) (RFC 2875 sec. 3, step 3c), under the
|
|
465
459
|
* digest the proof's own algorithm identifier names. */
|
|
@@ -762,7 +756,7 @@ function _x942ToPkcs3(spkiBytes, what) {
|
|
|
762
756
|
/** @internal How a key reads in the refusal above. A key the runtime imported without classifying
|
|
763
757
|
* carries no asymmetricKeyType, and reporting that as a type name says nothing an operator can act on. */
|
|
764
758
|
function _keyTypeLabel(what, key) {
|
|
765
|
-
var type = key
|
|
759
|
+
var type = guard.crypto.keyTypeOf(key);
|
|
766
760
|
if (typeof type !== "string" || type === "") {
|
|
767
761
|
return what + " carries an algorithm this runtime does not recognize as a key it can agree with";
|
|
768
762
|
}
|