@blamejs/pki 0.8.31 → 0.8.49
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 +401 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +1008 -58
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-decrypt.js
CHANGED
|
@@ -19,6 +19,9 @@ var compositeKem = require("./composite-kem");
|
|
|
19
19
|
var C = require("./constants");
|
|
20
20
|
var b = asn1.build;
|
|
21
21
|
var subtle = webcrypto.webcrypto.subtle;
|
|
22
|
+
/** @internal The verify operation, captured at load. It checks the AuthenticatedData MAC, and one
|
|
23
|
+
* answering true accepts content nothing authenticated while returning it as plaintext. */
|
|
24
|
+
var _subtleVerify = intrinsic.uncurry(subtle.verify);
|
|
22
25
|
var CmsError = frameworkError.CmsError;
|
|
23
26
|
var WRAP_KEK_LENGTHS = schemaCms.WRAP_KEK_LENGTHS;
|
|
24
27
|
var KEM_CT_LENGTHS = schemaCms.KEM_CT_LENGTHS;
|
|
@@ -61,6 +64,7 @@ async function decryptEnvelopedData(parsed, keyMaterial, opts, contentTypeName)
|
|
|
61
64
|
for (var ci = 0; ci < candidates.length; ci++) {
|
|
62
65
|
try {
|
|
63
66
|
_assertSupported(candidates[ci].ri, keyMaterial);
|
|
67
|
+
_assertAuthenticatedOrOptedIn(candidates[ci].ri, eci, opts);
|
|
64
68
|
var cek = await _acquireCek(candidates[ci].ri, keyMaterial, opts);
|
|
65
69
|
try {
|
|
66
70
|
var streamMode = opts.stream === true ? (candidates.length > 1 ? "eager" : "lazy") : null;
|
|
@@ -144,7 +148,10 @@ function _ridEq(rid, cert) {
|
|
|
144
148
|
try { return guard.name.dnEqual(cert.issuer.rdns, rid.issuer.rdns, _err, "cms/bad-input", "recipient issuer") && cert.serialNumber === rid.serialNumber; }
|
|
145
149
|
catch (_e) { return false; }
|
|
146
150
|
}
|
|
147
|
-
|
|
151
|
+
/** @internal Through the captured comparator: this answers WHICH recipient a certificate is, and the
|
|
152
|
+
* plaintext a caller receives is the one that recipient's key unwraps, so a replacement answering
|
|
153
|
+
* zero picks the recipient. */
|
|
154
|
+
if (rid.subjectKeyIdentifier) { var ski = _skiOf(cert); return !!ski && intrinsic.compare(ski, rid.subjectKeyIdentifier) === 0; }
|
|
148
155
|
return false;
|
|
149
156
|
}
|
|
150
157
|
function _skiOf(cert) {
|
|
@@ -157,7 +164,7 @@ function _skiOf(cert) {
|
|
|
157
164
|
function _kekIdMatches(ri, kekId) {
|
|
158
165
|
var id = ri.kekid && ri.kekid.keyIdentifier;
|
|
159
166
|
if (!id) return false;
|
|
160
|
-
return
|
|
167
|
+
return intrinsic.compare(id, guard.bytes.view(kekId, CmsError, "cms/bad-input", "kekId")) === 0;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
var _passThrough = intrinsic.assign(intrinsic.create(null), { "cms/unsupported-algorithm": 1, "cms/unsupported-recipient-type": 1, "cms/bad-input": 1, "cms/iteration-limit": 1, "cms/missing-key-derivation": 1, "cms/no-encrypted-content": 1, "cms/too-many-recipient-keys": 1 });
|
|
@@ -176,6 +183,35 @@ async function _acquireCek(ri, km, opts) {
|
|
|
176
183
|
throw _err("cms/unsupported-recipient-type", "unsupported recipient type " + ri.type);
|
|
177
184
|
}
|
|
178
185
|
|
|
186
|
+
/** @internal RSAES-PKCS1-v1_5 key transport over a content with no integrity tag is refused unless the
|
|
187
|
+
* caller opts in, and the refusal comes BEFORE the unwrap so no decision depends on whether the unwrap
|
|
188
|
+
* conformed. The combination cannot be made both correct and indistinguishable: the CBC padding check is
|
|
189
|
+
* the only integrity signal the content offers, so either a failed unwrap is accepted and the verb can
|
|
190
|
+
* return the plaintext of a substituted key, or it is refused and acceptance then tells an attacker that
|
|
191
|
+
* their chosen ciphertext decoded. MEASURED, 512 chosen ciphertexts per arm against a one-block
|
|
192
|
+
* AES-256-CBC content: 2 accepted where the unwrap conformed and 0 where it did not, which is the
|
|
193
|
+
* Bleichenbacher distinction at the padding check's own rate of about one in 256.
|
|
194
|
+
* THE OPT-IN EXISTS BECAUSE THIS IS WHAT OPENSSL PRODUCES BY DEFAULT. `openssl cms -encrypt` with no
|
|
195
|
+
* algorithm named emits exactly `rsaEncryption` with `aes-256-cbc`, decrypts it, and reports
|
|
196
|
+
* `ossl_cipher_unpadblock: bad decrypt` on a wrong key, so it leaks the same signal and names the step.
|
|
197
|
+
* Refusing outright would make those messages unreadable here, so the default is the safe one and the
|
|
198
|
+
* operator who must read them says so. An AEAD content needs no opt-in: the tag rejects a wrong key
|
|
199
|
+
* before any plaintext is released, which is why AES-GCM is the encrypt default. RSAES-OAEP needs none
|
|
200
|
+
* either, its own padding check being the integrity signal RFC 8017 sec. 7.1 gives it. */
|
|
201
|
+
function _assertAuthenticatedOrOptedIn(ri, eci, opts) {
|
|
202
|
+
if (opts.allowUnauthenticatedRsa15 === true) return;
|
|
203
|
+
if (ri.type !== "ktri") return;
|
|
204
|
+
if (ri.keyEncryptionAlgorithm.oid !== O("rsaEncryption")) return;
|
|
205
|
+
if (intrinsic.hasOwn(AEAD_ALGS, eci.contentEncryptionAlgorithm.oid)) return;
|
|
206
|
+
throw _err("cms/unauthenticated-rsa-v15",
|
|
207
|
+
"this message uses RSAES-PKCS1-v1_5 key transport over a content with no integrity tag (" +
|
|
208
|
+
(eci.contentEncryptionAlgorithm.name || eci.contentEncryptionAlgorithm.oid) + "), a combination whose " +
|
|
209
|
+
"only integrity check is the content padding: decrypting it either returns the plaintext of a " +
|
|
210
|
+
"substituted key or tells an attacker submitting chosen ciphertexts that theirs decoded. Pass " +
|
|
211
|
+
"allowUnauthenticatedRsa15 to read it anyway, which is what reading an `openssl cms -encrypt` default " +
|
|
212
|
+
"message needs, or re-encrypt to an AEAD content such as aes-256-gcm");
|
|
213
|
+
}
|
|
214
|
+
|
|
179
215
|
async function _ktriCek(ri, km) {
|
|
180
216
|
var kea = ri.keyEncryptionAlgorithm;
|
|
181
217
|
var k = _normKeyDer(km.key);
|
|
@@ -187,7 +223,7 @@ async function _ktriCek(ri, km) {
|
|
|
187
223
|
}
|
|
188
224
|
if (kea.oid === O("rsaEncryption")) {
|
|
189
225
|
var keyObj = nodeCrypto.createPrivateKey({ key: k.der, format: "der", type: "pkcs8" });
|
|
190
|
-
try { return nodeCrypto.privateDecrypt({ key: keyObj, padding:
|
|
226
|
+
try { return nodeCrypto.privateDecrypt({ key: keyObj, padding: guard.crypto.padding.RSA_PKCS1 }, ri.encryptedKey); }
|
|
191
227
|
catch (_e) {
|
|
192
228
|
return null;
|
|
193
229
|
}
|
|
@@ -349,6 +385,18 @@ function _assertContentCipherMode(eci, ct) {
|
|
|
349
385
|
throw _err("cms/unsupported-algorithm", "contentEncryptionAlgorithm " + oidStr + " is an authenticated encryption algorithm, which belongs in an authEnvelopedData rather than a " + ct + " (RFC 5083 sec. 2)");
|
|
350
386
|
}
|
|
351
387
|
|
|
388
|
+
/** @internal A candidate whose key unwrap did not produce a content key of the right length has FAILED,
|
|
389
|
+
* and the decrypt under a substitute runs only so that the failure costs the same work and the same time
|
|
390
|
+
* as a success: a recipient whose RSA v1.5 unwrap is implicitly rejected must not be distinguishable from
|
|
391
|
+
* one whose key simply does not match. Its result is therefore never a result. Without this the content
|
|
392
|
+
* decrypt decided it, and a random substitute key leaves a final CBC block that is valid PKCS#7 padding
|
|
393
|
+
* about one time in 256, so a known-bad candidate won that often and the verb returned WRONG PLAINTEXT
|
|
394
|
+
* for it while reporting that recipient as the one it used. An AEAD content algorithm rejected it on the
|
|
395
|
+
* tag already; this closes the non-AEAD modes, where padding is the only check the content offers. */
|
|
396
|
+
function _assertNotSubstituted(substitute) {
|
|
397
|
+
if (substitute !== null) throw _fail();
|
|
398
|
+
}
|
|
399
|
+
|
|
352
400
|
async function _openContent(parsed, eci, cek, ct, streamMode) {
|
|
353
401
|
var alg = eci.contentEncryptionAlgorithm;
|
|
354
402
|
var keyBits = intrinsic.hasOwn(CONTENT_KEYBITS, alg.oid) ? CONTENT_KEYBITS[alg.oid] : 0;
|
|
@@ -365,13 +413,32 @@ async function _openContent(parsed, eci, cek, ct, streamMode) {
|
|
|
365
413
|
if (ct === "authEnvelopedData") {
|
|
366
414
|
var aad = parsed.authAttrsBytes != null ? _explicitSetOf(parsed.authAttrsBytes) : Buffer.alloc(0);
|
|
367
415
|
var pt = _gcmOpen(cek, parsed.aead.nonce, eci.encryptedContent, parsed.mac, aad, keyBits, parsed.aead.icvLen);
|
|
416
|
+
_assertNotSubstituted(substitute);
|
|
368
417
|
return streamMode ? _bufferChunks(pt) : pt;
|
|
369
418
|
}
|
|
370
419
|
var iv = asn1.read.octetString(asn1.decode(alg.parameters));
|
|
371
420
|
if (iv.length !== 16) throw _fail();
|
|
372
|
-
if (streamMode === "lazy")
|
|
373
|
-
|
|
374
|
-
|
|
421
|
+
if (streamMode === "lazy") {
|
|
422
|
+
var lazy = _cbcStream(cek, iv, eci.encryptedContent, keyBits);
|
|
423
|
+
/** @internal A substituted candidate streams exactly as any other wrong key does and then fails,
|
|
424
|
+
* and all three of those words are load-bearing. On this path a key of the RIGHT length but the
|
|
425
|
+
* wrong value yields its garbage chunks and only then fails the padding check, so a substituted
|
|
426
|
+
* one that refused at the call, or that yielded nothing, would say which of the two happened, and
|
|
427
|
+
* whether the v1.5 padding was valid is exactly what the substitute exists to hide: the stage, the
|
|
428
|
+
* chunks and the code all have to match, not just the code. So the decrypt runs, its chunks are
|
|
429
|
+
* delivered, and the verdict is a refusal whether or not the padding happened to pass, which is
|
|
430
|
+
* what keeps a lucky substitute from becoming an answer. Chunks from a CBC stream are unverified
|
|
431
|
+
* until it completes either way, which is why the verdict carries `authenticated: false`. */
|
|
432
|
+
return substitute !== null ? _streamThenRefuse(lazy) : lazy;
|
|
433
|
+
}
|
|
434
|
+
if (streamMode === "eager") {
|
|
435
|
+
var eager = _bufferChunks(pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits, CmsError, "cms/decrypt-failed"));
|
|
436
|
+
_assertNotSubstituted(substitute);
|
|
437
|
+
return eager;
|
|
438
|
+
}
|
|
439
|
+
var buffered = pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits, CmsError, "cms/decrypt-failed");
|
|
440
|
+
_assertNotSubstituted(substitute);
|
|
441
|
+
return buffered;
|
|
375
442
|
} catch (e) {
|
|
376
443
|
if (e instanceof CmsError && e.code !== "cms/decrypt-failed") throw e;
|
|
377
444
|
throw _fail();
|
|
@@ -401,6 +468,34 @@ async function* _translateCbcStream(engineIter) {
|
|
|
401
468
|
try { for await (var chunk of engineIter) yield chunk; }
|
|
402
469
|
catch (_e) { throw _fail(); }
|
|
403
470
|
}
|
|
471
|
+
/** @internal Everything the inner stream yields, then a refusal, for a candidate the implicit rejection
|
|
472
|
+
* substituted for on the lazy path. The chunks and the stage are then the ones any other wrong key on
|
|
473
|
+
* that path produces, and the refusal does not wait on the padding check to decide, so a substitute
|
|
474
|
+
* whose final block happens to be valid padding is still refused. */
|
|
475
|
+
async function* _streamThenRefuse(inner) {
|
|
476
|
+
/** @internal One chunk of lookahead, so the chunk COUNTS match too and not merely the code and the
|
|
477
|
+
* stage. An ordinary wrong key's stream yields the padded content less its final block and then
|
|
478
|
+
* refuses, the final block never arriving because the padding check is what rejects it. A substitute
|
|
479
|
+
* whose final block happens to be valid padding, about one time in 256, instead COMPLETES and yields
|
|
480
|
+
* that last stripped chunk, which is a chunk no wrong key ever emits: forwarding it would say the
|
|
481
|
+
* substitute was lucky, and a one-in-256 signal is still a signal. So the last chunk is held back, and
|
|
482
|
+
* released only on the path where a wrong key would also have produced everything it produced. */
|
|
483
|
+
/** @internal The held chunk lives in a list of at most one, so releasing it is `yield*` and needs no
|
|
484
|
+
* test: an empty list yields nothing. The branch a test would need is reachable, the inner stream
|
|
485
|
+
* rejecting on its first read when the content length is not a block multiple, so the test would be
|
|
486
|
+
* load-bearing rather than defensive; expressing the absence as an empty list removes the question. */
|
|
487
|
+
var held = [];
|
|
488
|
+
try {
|
|
489
|
+
for await (var chunk of inner) {
|
|
490
|
+
yield* held;
|
|
491
|
+
held = [chunk];
|
|
492
|
+
}
|
|
493
|
+
} catch (_e) {
|
|
494
|
+
yield* held;
|
|
495
|
+
throw _fail();
|
|
496
|
+
}
|
|
497
|
+
throw _fail();
|
|
498
|
+
}
|
|
404
499
|
|
|
405
500
|
async function _decryptEncryptedData(parsed, km, opts) {
|
|
406
501
|
var eci = parsed.encryptedContentInfo;
|
|
@@ -607,7 +702,7 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
|
|
|
607
702
|
if (macKey == null || macKey.length < MAC_KEY_MIN_OCTETS) macSubstitute = nodeCrypto.randomBytes(MAC_KEY_MIN_OCTETS);
|
|
608
703
|
try {
|
|
609
704
|
var key = await subtle.importKey("raw", macSubstitute || macKey, { name: "HMAC", hash: hash }, false, ["verify"]);
|
|
610
|
-
if (!(await subtle
|
|
705
|
+
if (!(await _subtleVerify(subtle, { name: "HMAC" }, key, Buffer.from(parsed.mac), preimage))) throw _fail();
|
|
611
706
|
if (mdCheck) {
|
|
612
707
|
var actual = Buffer.from(await subtle.digest(mdCheck.hash, content));
|
|
613
708
|
if (!actual.equals(mdCheck.declared)) throw _fail();
|
package/lib/cms-encrypt.js
CHANGED
|
@@ -374,12 +374,28 @@ var _AUTHENTICATE_OPTS_BARE = intrinsic.assign(intrinsic.create(null), {
|
|
|
374
374
|
keyIdentifier: 1, oaepHash: 1, ukm: 1, pem: 1,
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
+
/** @internal A byte content is fixed first, ahead of the recipients and the options. Copying either of
|
|
378
|
+
* those reads the caller's own objects, so a property on one of them is caller code that runs before
|
|
379
|
+
* the content would otherwise have been read, and over a view it could replace what gets encrypted
|
|
380
|
+
* after this verb had accepted the content it was handed. The discriminator is a type test, which reads
|
|
381
|
+
* no property and so cannot be that accessor itself. A streamed content has no fixed form here: it is
|
|
382
|
+
* read through its iterator, which `_encryptImpl` probes only after the options have been copied, and
|
|
383
|
+
* is held to the same rule where it is consumed. */
|
|
377
384
|
function encrypt(content, recipients, opts) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
385
|
+
if (!guard.bytes.isByteSource(content)) {
|
|
386
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input",
|
|
387
|
+
[[recipients, "recipients"], [opts, "pki.cms.encrypt options"]],
|
|
388
|
+
function (recipientsCopy, optsCopy) { return _encryptImpl(content, recipientsCopy, optsCopy); });
|
|
389
|
+
}
|
|
390
|
+
/** @internal The argument list is written out rather than assembled from a shorter one. Built with
|
|
391
|
+
* `Array.prototype.concat`, the list a fixed call fixes was chosen by that method: a replacement
|
|
392
|
+
* could keep the caller's recipients and options and put OTHER bytes in the content slot, and this
|
|
393
|
+
* verb then encrypted those. A literal is not a dispatch, so there is nothing to replace. */
|
|
394
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input",
|
|
395
|
+
[[content, "content"], [recipients, "recipients"], [opts, "pki.cms.encrypt options"]],
|
|
396
|
+
function (contentCopy, recipientsCopy, optsCopy) {
|
|
397
|
+
return _encryptImpl(contentCopy, recipientsCopy, optsCopy);
|
|
398
|
+
});
|
|
383
399
|
}
|
|
384
400
|
|
|
385
401
|
async function _encryptImpl(content, recipients, opts) {
|
|
@@ -395,6 +411,9 @@ async function _encryptImpl(content, recipients, opts) {
|
|
|
395
411
|
: "");
|
|
396
412
|
});
|
|
397
413
|
var contentStream = guard.bytes.asyncStreamOf(content);
|
|
414
|
+
/** @internal A view is enough here: `encrypt` fixed a byte content at the door, ahead of the recipient
|
|
415
|
+
* and option copies, so these bytes are already this module's own and no caller accessor reaches
|
|
416
|
+
* them. */
|
|
398
417
|
var contentBytes = contentStream ? null : guard.bytes.view(content, CmsError, "cms/bad-input", "content");
|
|
399
418
|
var contentType = opts.contentType || "data";
|
|
400
419
|
var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
|
|
@@ -565,7 +584,32 @@ var MAC_ALGS = intrinsic.assign(intrinsic.create(null), {
|
|
|
565
584
|
var MAC_KEY_OCTETS = 32;
|
|
566
585
|
var SUPPORTED_DIGEST = intrinsic.assign(intrinsic.create(null), { sha256: 1, sha384: 1, sha512: 1 });
|
|
567
586
|
|
|
568
|
-
|
|
587
|
+
/** @internal The same door `encrypt` has, and for the same reason. This verb reads the caller's options
|
|
588
|
+
* before anything else, to decide whether authenticated attributes are built, so an accessor there is
|
|
589
|
+
* caller code that runs before the content would otherwise have been read; a getter on a recipient runs
|
|
590
|
+
* later, while the RecipientInfos are built. Over a view of the content either one could replace what
|
|
591
|
+
* the message authenticates and carries, after this verb had accepted the content it was handed, so a
|
|
592
|
+
* byte content is fixed first and the recipients and options are copies from there on. Closing one of
|
|
593
|
+
* those two routes and not the other leaves the rule holding for the argument that was looked at. The
|
|
594
|
+
* discriminator is a type test, which reads no property and so cannot be the accessor itself; a content
|
|
595
|
+
* that is not a byte source is passed through and refused where it is read, as before. */
|
|
596
|
+
function authenticate(content, recipients, opts) {
|
|
597
|
+
if (!guard.bytes.isByteSource(content)) {
|
|
598
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input",
|
|
599
|
+
[[recipients, "recipients"], [opts, "pki.cms.authenticate options"]],
|
|
600
|
+
function (recipientsCopy, optsCopy) { return _authenticateImpl(content, recipientsCopy, optsCopy); });
|
|
601
|
+
}
|
|
602
|
+
/** @internal Written out for the reason `encrypt` above gives: a list assembled by a live
|
|
603
|
+
* `Array.prototype.concat` is a list that method chooses, and here it decides which bytes the MAC
|
|
604
|
+
* is computed over. */
|
|
605
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input",
|
|
606
|
+
[[content, "content"], [recipients, "recipients"], [opts, "pki.cms.authenticate options"]],
|
|
607
|
+
function (contentCopy, recipientsCopy, optsCopy) {
|
|
608
|
+
return _authenticateImpl(contentCopy, recipientsCopy, optsCopy);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function _authenticateImpl(content, recipients, opts) {
|
|
569
613
|
opts = opts || {};
|
|
570
614
|
var withAttrs = opts.authenticatedAttributes !== false;
|
|
571
615
|
guard.identifier.assertKnownKeys(opts, withAttrs ? _AUTHENTICATE_OPTS_ATTRS : _AUTHENTICATE_OPTS_BARE,
|
|
@@ -575,6 +619,9 @@ async function authenticate(content, recipients, opts) {
|
|
|
575
619
|
? " with authenticatedAttributes: false; the MAC then covers the content octets directly and no attribute set is built"
|
|
576
620
|
: "");
|
|
577
621
|
});
|
|
622
|
+
/** @internal A view is enough here: `authenticate` fixed a byte content at the door, ahead of the
|
|
623
|
+
* recipient and option copies, so these bytes are already this module's own and no caller accessor
|
|
624
|
+
* reaches them. */
|
|
578
625
|
var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
|
|
579
626
|
var macName = opts.macAlgorithm || "hmac-sha256";
|
|
580
627
|
var mac = MAC_ALGS[guard.text.keyOf(macName)];
|
|
@@ -595,7 +642,12 @@ async function authenticate(content, recipients, opts) {
|
|
|
595
642
|
if (withAttrs) {
|
|
596
643
|
var digestName = opts.digestAlgorithm || mac.node;
|
|
597
644
|
if (!SUPPORTED_DIGEST[guard.text.keyOf(digestName)]) throw _err("cms/bad-input", "unsupported digestAlgorithm " + guard.text.showValue(digestName) + " (sha256/384/512)");
|
|
598
|
-
|
|
645
|
+
/** @internal Through the captured operations: `update` and `digest` are ordinary writable
|
|
646
|
+
* properties of the hash prototype, so a replacement decided what this digest was OVER, and the
|
|
647
|
+
* MAC below covers the attribute set carrying it. Measured, a replaced `update` put the digest
|
|
648
|
+
* of other bytes into the messageDigest attribute and the message no longer verified against
|
|
649
|
+
* the content it carries. */
|
|
650
|
+
var mdDigest = guard.crypto.digest(digestName, contentBytes);
|
|
599
651
|
var pairs = [
|
|
600
652
|
b.sequence([b.oid(O("contentType")), b.setOf([b.oid(O(contentType))])]),
|
|
601
653
|
b.sequence([b.oid(O("messageDigest")), b.setOf([b.octetString(mdDigest)])]),
|
package/lib/cms-sign.js
CHANGED
|
@@ -15,6 +15,14 @@ var webcrypto = require("./webcrypto");
|
|
|
15
15
|
var signScheme = require("./sign-scheme");
|
|
16
16
|
var guard = require("./guard-all");
|
|
17
17
|
var intrinsic = require("./guard-intrinsic");
|
|
18
|
+
/** @internal `resolve` and `all` build their promise from the RECEIVER, so reading the global binding
|
|
19
|
+
* at the call hands the construction to whatever that binding holds. Each signer's digest and
|
|
20
|
+
* signature are awaited through one of these, and a replacement that settles without awaiting emits a
|
|
21
|
+
* SignedData whose SignerInfos were never completed. The constructor and both operations are taken at
|
|
22
|
+
* load. */
|
|
23
|
+
var _Promise = intrinsic.Promise;
|
|
24
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
25
|
+
var _promiseAll = intrinsic.uncurry(intrinsic.promiseAll);
|
|
18
26
|
var pkiBuild = require("./pki-build");
|
|
19
27
|
var cms = require("./schema-cms");
|
|
20
28
|
|
|
@@ -72,10 +80,16 @@ function _buildSid(cert, useSki) {
|
|
|
72
80
|
return { sid: sid, version: useSki ? 3 : 1 };
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
/** @internal The signed side keeps its blanket refusal of a repeated type, and the citation is what
|
|
84
|
+
* changes. RFC 5652 clause 5.3 does not state that rule: what it states is per type, "The
|
|
85
|
+
* SignedAttributes in a signerInfo MUST include only one instance of the message-digest attribute".
|
|
86
|
+
* Refusing every repeat is this builder's posture, stricter than the RFC and in the safe direction for
|
|
87
|
+
* something being emitted, so it stands; naming a clause that does not say it did not. The unsigned
|
|
88
|
+
* side had the same misattribution and could not keep the rule, a repeat being ordinary there. */
|
|
75
89
|
function _buildSignedAttrs(pairs) {
|
|
76
90
|
var seenTypes = intrinsic.create(null);
|
|
77
91
|
var attrs = intrinsic.map(pairs, function (p) {
|
|
78
|
-
if (intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "
|
|
92
|
+
if (intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "this builder emits one instance of each signed attribute type, and " + p.type + " is repeated; RFC 5652 sec. 11.2 requires it of the message-digest attribute and this build asks it of all of them");
|
|
79
93
|
seenTypes[p.type] = 1;
|
|
80
94
|
return b.sequence([b.oid(p.type), b.set(p.values)]);
|
|
81
95
|
});
|
|
@@ -105,11 +119,17 @@ function _buildUnsignedAttrs(list) {
|
|
|
105
119
|
if (!intrinsic.isArray(list)) throw _err("cms/bad-input", "opts.unsignedAttributes must be an array of { type, values }");
|
|
106
120
|
if (!list.length) return null;
|
|
107
121
|
var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
|
|
108
|
-
|
|
122
|
+
/** @internal A TYPE MAY REPEAT HERE, and the RFC is the authority for that rather than against it.
|
|
123
|
+
* `UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute` imposes no per-type uniqueness; clause 5.3
|
|
124
|
+
* says of the field only that it "is a collection of attributes that are not signed. The field is
|
|
125
|
+
* optional"; and the uniqueness rules the RFC does state are per type, "The SignedAttributes in a
|
|
126
|
+
* signerInfo MUST include only one instance of the message-digest attribute", while clause 11.4's
|
|
127
|
+
* countersignature type "specifies one or more signatures". A blanket refusal citing clause 5.3
|
|
128
|
+
* therefore denied a conforming message, and the shape it denied is the ordinary one: a signature
|
|
129
|
+
* re-timestamped by several authorities carries one timeStampToken attribute per authority. The
|
|
130
|
+
* per-type rules below are kept, and they are the ones with a clause behind them. */
|
|
109
131
|
intrinsic.forEach(pairs, function (p) {
|
|
110
132
|
if (intrinsic.hasOwn(UNSIGNED_FORBIDDEN, p.type)) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
|
|
111
|
-
if (intrinsic.hasOwn(seen, p.type)) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
112
|
-
seen[p.type] = 1;
|
|
113
133
|
});
|
|
114
134
|
var setOf = b.set(intrinsic.map(pairs, function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
|
|
115
135
|
var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA1;
|
|
@@ -157,7 +177,7 @@ async function _finishSignerInfo(rc, md, content, eContentType, opts) {
|
|
|
157
177
|
|
|
158
178
|
function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
159
179
|
var rc = _resolveSignerContext(signer, opts);
|
|
160
|
-
var mdP = opts.signedAttributes === false ?
|
|
180
|
+
var mdP = opts.signedAttributes === false ? _promiseResolve(_Promise, null) : _digest(rc.scheme.digest, content);
|
|
161
181
|
return mdP.then(function (md) { return _finishSignerInfo(rc, md, content, eContentType, opts); });
|
|
162
182
|
}
|
|
163
183
|
|
|
@@ -323,7 +343,7 @@ function _sign(content, signers, opts) {
|
|
|
323
343
|
}
|
|
324
344
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertEncodable(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
325
345
|
|
|
326
|
-
return
|
|
346
|
+
return _promiseAll(_Promise, list.map(function (s) { return _buildSignerInfo(s, contentBuf, eContentType, opts); }))
|
|
327
347
|
.then(function (built) { return _assembleSignedData(built, eContentType, opts, contentBuf); });
|
|
328
348
|
}
|
|
329
349
|
|
|
@@ -435,7 +455,7 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
|
|
|
435
455
|
var scheme = signScheme.resolveSignScheme(cert, so, opts.signedAttributes === false, _signE);
|
|
436
456
|
var sidv = _buildSid(cert, opts.sid === "ski");
|
|
437
457
|
var soKey = so.key;
|
|
438
|
-
return
|
|
458
|
+
return _promiseResolve(_Promise, undefined).then(function () {
|
|
439
459
|
if (opts.signedAttributes === false) return null;
|
|
440
460
|
return _digest(scheme.digest, targetSigOctets).then(function (md) {
|
|
441
461
|
var pairs = [{ type: O("messageDigest"), values: [b.octetString(md)] }];
|
|
@@ -551,7 +571,7 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
551
571
|
var preimage = _targetPreimage(siSet.children[t], opts);
|
|
552
572
|
list.forEach(function (cs) { jobs.push({ t: t, p: _buildCountersignature(preimage, cs, opts) }); });
|
|
553
573
|
});
|
|
554
|
-
return
|
|
574
|
+
return _promiseAll(_Promise, jobs.map(function (j) { return j.p; })).then(function (built) {
|
|
555
575
|
var byTarget = intrinsic.create(null), certDers = [], csDigestAlgs = [];
|
|
556
576
|
intrinsic.forEach(built, function (res, i) {
|
|
557
577
|
var slot = byTarget[jobs[i].t] || (byTarget[jobs[i].t] = []);
|
|
@@ -573,7 +593,9 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
573
593
|
var existing = [];
|
|
574
594
|
if (certsNode) certsNode.children.forEach(function (c) { existing.push(c.bytes); });
|
|
575
595
|
if (opts.certificates !== false) certDers.forEach(function (d) { existing.push(d); });
|
|
576
|
-
|
|
596
|
+
/** @internal The DER SET ordering through the captured copy, sort AND comparator: read off its own
|
|
597
|
+
* home, any of the three decides which certificate lands where in the set this message carries. */
|
|
598
|
+
var allCerts = guard.list.sortedCopy(_dedupe(existing), intrinsic.compare);
|
|
577
599
|
|
|
578
600
|
/** @internal RFC 5652 sec. 5.1 has this collection list "the message digest algorithms employed
|
|
579
601
|
* by all of the signers", and sec. 11.4 makes a countersignature a SignerInfo, so sec. 5.3's
|
package/lib/cms-verify.js
CHANGED
|
@@ -49,9 +49,14 @@ var intrinsic = require("./guard-intrinsic");
|
|
|
49
49
|
var frameworkError = require("./framework-error");
|
|
50
50
|
var pkix = require("./schema-pkix");
|
|
51
51
|
|
|
52
|
+
/** @internal The verify operation, captured at load. The object was held and its METHOD fetched per
|
|
53
|
+
* call, which leaves it replaceable: one answering true accepts a SignerInfo no signer signed, which
|
|
54
|
+
* is the whole verdict this module reports. `path-validate` and `ct` hold it the same way. */
|
|
55
|
+
var _subtleVerify = intrinsic.uncurry(subtle.verify);
|
|
56
|
+
|
|
52
57
|
var CmsError = frameworkError.CmsError;
|
|
53
58
|
function _err(code, message, cause) { return new CmsError(code, message, cause); }
|
|
54
|
-
var _Promise = Promise, _promiseResolveRaw =
|
|
59
|
+
var _Promise = intrinsic.Promise, _promiseResolveRaw = intrinsic.promiseResolve;
|
|
55
60
|
/**
|
|
56
61
|
* @internal Every value resolved here is a signer row, and resolving a promise reads `then` off the
|
|
57
62
|
* value. Building the row through the verdict guard ends that lookup on the row itself, so an
|
|
@@ -214,26 +219,26 @@ function _resolvePss(paramsBytes) {
|
|
|
214
219
|
async function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOid, pssSalt) {
|
|
215
220
|
if (scheme.kind === "rsa") {
|
|
216
221
|
var kRsa = await subtle.importKey("spki", spki, { name: "RSASSA-PKCS1-v1_5", hash: hashName }, false, ["verify"]);
|
|
217
|
-
return subtle
|
|
222
|
+
return _subtleVerify(subtle, { name: "RSASSA-PKCS1-v1_5" }, kRsa, sigBytes, signedBytes);
|
|
218
223
|
}
|
|
219
224
|
if (scheme.kind === "rsapss") {
|
|
220
225
|
var kPss = await subtle.importKey("spki", spki, { name: "RSA-PSS", hash: hashName }, false, ["verify"]);
|
|
221
|
-
return subtle
|
|
226
|
+
return _subtleVerify(subtle, { name: "RSA-PSS", saltLength: pssSalt }, kPss, sigBytes, signedBytes);
|
|
222
227
|
}
|
|
223
228
|
if (scheme.kind === "ec") {
|
|
224
229
|
var ec = EC_CURVE[curveOid];
|
|
225
230
|
if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
|
|
226
231
|
var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
|
|
227
232
|
var kEc = await subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"]);
|
|
228
|
-
return subtle
|
|
233
|
+
return _subtleVerify(subtle, { name: "ECDSA", hash: hashName }, kEc, raw, signedBytes);
|
|
229
234
|
}
|
|
230
235
|
if (scheme.kind === "mldsa" || scheme.kind === "slhdsa") {
|
|
231
236
|
var kPq = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
|
|
232
|
-
return subtle
|
|
237
|
+
return _subtleVerify(subtle, { name: scheme.name }, kPq, sigBytes, signedBytes);
|
|
233
238
|
}
|
|
234
239
|
_requireValidEdPoint(spki, scheme.name);
|
|
235
240
|
var kEd = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
|
|
236
|
-
return subtle
|
|
241
|
+
return _subtleVerify(subtle, { name: scheme.name }, kEd, sigBytes, signedBytes);
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
function _requireValidEdPoint(spkiBytes, name) {
|
|
@@ -562,7 +567,11 @@ async function _verify(input, opts) {
|
|
|
562
567
|
content = _toBuf(_snapshotIfBytes(opts.content, "opts.content"), "opts.content");
|
|
563
568
|
}
|
|
564
569
|
} else if (opts.content != null) {
|
|
565
|
-
|
|
570
|
+
/** @internal The comparison goes through the captured operation. Read off `Buffer`, it decides
|
|
571
|
+
* whether the content a caller supplied is the content this message carries, so a replacement
|
|
572
|
+
* answering zero would have this verb accept a detached content that disagrees with the embedded
|
|
573
|
+
* one and then report a verdict about whichever of the two it went on to digest. */
|
|
574
|
+
if (intrinsic.compare(_toBuf(opts.content, "opts.content"), Buffer.from(content)) !== 0) {
|
|
566
575
|
throw _err("cms/content-conflict", "opts.content disagrees with this SignedData's own encapsulated content (a detached signature must omit eContent)");
|
|
567
576
|
}
|
|
568
577
|
}
|
|
@@ -631,21 +640,15 @@ function _snapshotTrust(opts) {
|
|
|
631
640
|
};
|
|
632
641
|
}
|
|
633
642
|
|
|
634
|
-
|
|
643
|
+
/** @internal A signature over content is authorized by digitalSignature, contentCommitment or
|
|
644
|
+
* nonRepudiation alike; the shared helper carries the rest of the rule. */
|
|
645
|
+
var _permitsSigning = pkix.keyUsagePermits(pkix.makeNS("cms", CmsError, oid),
|
|
646
|
+
["digitalSignature", "contentCommitment", "nonRepudiation"]);
|
|
635
647
|
|
|
636
648
|
function _keyUsagePermitsSigning(parsedCerts, der) {
|
|
637
649
|
var entry = intrinsic.filter(parsedCerts, function (c) { return intrinsic.bufferEquals(c.der, der); })[0];
|
|
638
650
|
if (!entry) return true;
|
|
639
|
-
|
|
640
|
-
var exts = entry.cert.extensions || [];
|
|
641
|
-
for (var i = 0; i < exts.length; i++) {
|
|
642
|
-
if (exts[i].oid !== kuOid) continue;
|
|
643
|
-
var ku;
|
|
644
|
-
try { ku = _CERT_EXT_DECODERS[kuOid](exts[i].value); }
|
|
645
|
-
catch (_e) { return false; }
|
|
646
|
-
return ku.digitalSignature === true || ku.contentCommitment === true || ku.nonRepudiation === true;
|
|
647
|
-
}
|
|
648
|
-
return true;
|
|
651
|
+
return _permitsSigning(entry.cert);
|
|
649
652
|
}
|
|
650
653
|
|
|
651
654
|
function _allTrusted(signers) {
|
|
@@ -951,6 +954,7 @@ var authenticate = cmsEncrypt.authenticate;
|
|
|
951
954
|
* @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
|
|
952
955
|
* @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
|
|
953
956
|
* @opts stream Return `content` as an async iterable of plaintext `Buffer` chunks instead of a single `Buffer`.
|
|
957
|
+
* @opts allowUnauthenticatedRsa15 Read a message that uses RSAES-PKCS1-v1_5 key transport over a content with no integrity tag, such as AES-CBC. Refused by default with `cms/unauthenticated-rsa-v15`: the content padding is that combination's only integrity check, so decrypting it either returns the plaintext of a substituted key or tells an attacker submitting chosen ciphertexts that theirs decoded (RFC 8017 sec. 7.2). `openssl cms -encrypt` emits exactly this combination when no algorithm is named, so reading one of its messages needs this option. An AEAD content or an RSAES-OAEP recipient needs none.
|
|
954
958
|
* @example
|
|
955
959
|
* var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
|
|
956
960
|
* var pair = await pki.key.generate(rsa);
|
package/lib/composite-kem.js
CHANGED
|
@@ -26,6 +26,18 @@ var webcrypto = require("./webcrypto");
|
|
|
26
26
|
var nodeCrypto = require("node:crypto");
|
|
27
27
|
var frameworkError = require("./framework-error");
|
|
28
28
|
var guard = require("./guard-all");
|
|
29
|
+
var intrinsic = require("./guard-intrinsic");
|
|
30
|
+
/** @internal `resolve`, `reject` and `allSettled` build their promise from the RECEIVER, so reading the
|
|
31
|
+
* global binding at the call hands the construction to whatever that binding holds. Two things here
|
|
32
|
+
* turn on that. Each component refusal is returned as a rejected promise, so a replacement whose
|
|
33
|
+
* `reject` resolves turns a `kem/bad-key` or `kem/decapsulation-failed` refusal into a pass. And the
|
|
34
|
+
* pair of component results is settled through `allSettled` before either shared secret is used or
|
|
35
|
+
* wiped, so a replacement reporting both fulfilled with values of its own both skips the wipe and
|
|
36
|
+
* hands the combiner material it chose, which is the hybrid's whole premise. Taken at load. */
|
|
37
|
+
var _Promise = intrinsic.Promise;
|
|
38
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
39
|
+
var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
40
|
+
var _promiseAllSettled = intrinsic.uncurry(intrinsic.promiseAllSettled);
|
|
29
41
|
var pkix = require("./schema-pkix");
|
|
30
42
|
var schemaPkcs8 = require("./schema-pkcs8");
|
|
31
43
|
var subtle = webcrypto.webcrypto.subtle;
|
|
@@ -125,16 +137,16 @@ function _tradEncaps(d, tradPK) {
|
|
|
125
137
|
try {
|
|
126
138
|
asn1.decode(tradPK);
|
|
127
139
|
pub = nodeCrypto.createPublicKey({ key: tradPK, format: "der", type: "pkcs1" });
|
|
128
|
-
} catch (e) { return
|
|
129
|
-
if (pub.asymmetricKeyDetails.modulusLength !== trad.bits) return
|
|
140
|
+
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component public key is not a valid RSAPublicKey", e)); }
|
|
141
|
+
if (pub.asymmetricKeyDetails.modulusLength !== trad.bits) return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component modulus is not " + trad.bits + " bits (draft sec. 6)"));
|
|
130
142
|
var ss = Buffer.from(nodeCrypto.randomBytes(RSA_SS));
|
|
131
143
|
var ct;
|
|
132
|
-
try { ct = Buffer.from(nodeCrypto.publicEncrypt({ key: pub, padding:
|
|
144
|
+
try { ct = Buffer.from(nodeCrypto.publicEncrypt({ key: pub, padding: guard.crypto.padding.RSA_PKCS1_OAEP, oaepHash: "sha256" }, ss)); }
|
|
133
145
|
catch (e) {
|
|
134
146
|
guard.secret.zeroize(ss, KemError, "kem/bad-input", "the RSA-OAEP component secret");
|
|
135
|
-
return
|
|
147
|
+
return _promiseReject(_Promise, _err("kem/bad-key", "the RSA-OAEP component encryption failed", e));
|
|
136
148
|
}
|
|
137
|
-
return
|
|
149
|
+
return _promiseResolve(_Promise, { ss: ss, ct: ct });
|
|
138
150
|
}
|
|
139
151
|
var dh, dhCt;
|
|
140
152
|
try {
|
|
@@ -142,8 +154,8 @@ function _tradEncaps(d, tradPK) {
|
|
|
142
154
|
var eph = trad.kind === "ec" ? nodeCrypto.generateKeyPairSync("ec", { namedCurve: trad.node }) : nodeCrypto.generateKeyPairSync(trad.curve.toLowerCase());
|
|
143
155
|
dh = Buffer.from(nodeCrypto.diffieHellman({ privateKey: eph.privateKey, publicKey: recip }));
|
|
144
156
|
dhCt = _rawPubOf(eph.publicKey);
|
|
145
|
-
} catch (e) { return
|
|
146
|
-
return
|
|
157
|
+
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the " + trad.curve + " public key is invalid", e)); }
|
|
158
|
+
return _promiseResolve(_Promise, { ss: dh, ct: dhCt });
|
|
147
159
|
}
|
|
148
160
|
|
|
149
161
|
function _tradDecaps(d, tradSK, tradCT) {
|
|
@@ -153,16 +165,16 @@ function _tradDecaps(d, tradSK, tradCT) {
|
|
|
153
165
|
try {
|
|
154
166
|
asn1.decode(tradSK);
|
|
155
167
|
priv = nodeCrypto.createPrivateKey({ key: tradSK, format: "der", type: "pkcs1" });
|
|
156
|
-
} catch (e) { return
|
|
157
|
-
if (priv.asymmetricKeyDetails.modulusLength !== trad.bits) return
|
|
168
|
+
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component private key is not a valid RSAPrivateKey", e)); }
|
|
169
|
+
if (priv.asymmetricKeyDetails.modulusLength !== trad.bits) return _promiseReject(_Promise, _err("kem/bad-key", "the RSA component modulus is not " + trad.bits + " bits (draft sec. 6)"));
|
|
158
170
|
var ss;
|
|
159
|
-
try { ss = Buffer.from(nodeCrypto.privateDecrypt({ key: priv, padding:
|
|
160
|
-
catch (_e) { return
|
|
171
|
+
try { ss = Buffer.from(nodeCrypto.privateDecrypt({ key: priv, padding: guard.crypto.padding.RSA_PKCS1_OAEP, oaepHash: "sha256" }, tradCT)); }
|
|
172
|
+
catch (_e) { return _promiseReject(_Promise, _err("kem/decapsulation-failed", "the RSA-OAEP component decryption failed")); }
|
|
161
173
|
if (ss.length !== RSA_SS) {
|
|
162
174
|
guard.secret.zeroize(ss, KemError, "kem/bad-input", "the RSA-OAEP component secret");
|
|
163
|
-
return
|
|
175
|
+
return _promiseReject(_Promise, _err("kem/decapsulation-failed", "the RSA-OAEP component decryption failed"));
|
|
164
176
|
}
|
|
165
|
-
return
|
|
177
|
+
return _promiseResolve(_Promise, { ss: ss, tradPK: Buffer.from(nodeCrypto.createPublicKey(priv).export({ format: "der", type: "pkcs1" })) });
|
|
166
178
|
}
|
|
167
179
|
var priv2, eph;
|
|
168
180
|
try {
|
|
@@ -173,10 +185,10 @@ function _tradDecaps(d, tradSK, tradCT) {
|
|
|
173
185
|
priv2 = _okpPrivFromRaw(d, tradSK);
|
|
174
186
|
}
|
|
175
187
|
eph = trad.kind === "ec" ? _ecPubFromPoint(d, tradCT) : _okpPubFromRaw(d, tradCT);
|
|
176
|
-
} catch (e) { return
|
|
188
|
+
} catch (e) { return _promiseReject(_Promise, _err("kem/bad-key", "the " + trad.curve + " key material is invalid", e)); }
|
|
177
189
|
var dh;
|
|
178
190
|
try { dh = Buffer.from(nodeCrypto.diffieHellman({ privateKey: priv2, publicKey: eph })); }
|
|
179
|
-
catch (e) { return
|
|
191
|
+
catch (e) { return _promiseReject(_Promise, _err("kem/decapsulation-failed", "the " + trad.curve + " Diffie-Hellman failed", e)); }
|
|
180
192
|
/** @internal The same canonical point the derivation states, read from the one function that spells
|
|
181
193
|
* it. The combiner hashes these bytes, so a stored spelling here and a generated one there would
|
|
182
194
|
* reach different secrets from the two sides of one key. */
|
|
@@ -188,9 +200,9 @@ function _tradDecaps(d, tradSK, tradCT) {
|
|
|
188
200
|
* secret is wiped here: the caller's settle-or-wipe covers a value it was handed, and a rejected
|
|
189
201
|
* promise hands it nothing. */
|
|
190
202
|
guard.secret.zeroize(dh, KemError, "kem/bad-input", "the " + trad.curve + " component secret");
|
|
191
|
-
return
|
|
203
|
+
return _promiseReject(_Promise, e);
|
|
192
204
|
}
|
|
193
|
-
return
|
|
205
|
+
return _promiseResolve(_Promise, { ss: dh, tradPK: tradPK });
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
function _combine(d, mlkemSS, tradSS, tradCT, tradPK) {
|
|
@@ -203,7 +215,7 @@ function _combine(d, mlkemSS, tradSS, tradCT, tradPK) {
|
|
|
203
215
|
}
|
|
204
216
|
|
|
205
217
|
async function _settleOrWipe(pA, pB, secA, secB) {
|
|
206
|
-
var s = await
|
|
218
|
+
var s = await _promiseAllSettled(_Promise, [pA, pB]);
|
|
207
219
|
if (s[0].status === "rejected" || s[1].status === "rejected") {
|
|
208
220
|
if (s[0].status === "fulfilled") guard.secret.zeroize(secA(s[0].value), KemError, "kem/bad-input", "an ML-KEM shared secret");
|
|
209
221
|
if (s[1].status === "fulfilled") guard.secret.zeroize(secB(s[1].value), KemError, "kem/bad-input", "a traditional shared secret");
|