Skip to content
File

Blob: src/workerd/api/node/crypto.c++

59.5 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4#include "crypto.h"
5 
6#include <workerd/api/crypto/digest.h>
7#include <workerd/api/crypto/impl.h>
8#include <workerd/api/crypto/kdf.h>
9#include <workerd/api/crypto/prime.h>
10#include <workerd/api/crypto/spkac.h>
11#include <workerd/jsg/jsg.h>
12 
13#include <ncrypto.h>
14 
15#include <string_view>
16 
17using namespace std::string_view_literals;
18 
19namespace workerd::api::node {
20 
21// ======================================================================================
22#pragma region KDF
23 
24jsg::JsArrayBuffer CryptoImpl::getHkdf(jsg::Lock& js,
25 kj::String hash,
26 kj::Array<const kj::byte> key,
27 kj::Array<const kj::byte> salt,
28 kj::Array<const kj::byte> info,
29 uint32_t length) {
30 // The Node.js version of the HKDF is a bit different from the Web Crypto API
31 // version. For one, the length here specifies the number of bytes, whereas
32 // in Web Crypto the length is expressed in the number of bits. Second, the
33 // Node.js implementation allows for a broader range of possible digest
34 // algorithms whereas the Web Crypto API only allows for a few specific ones.
35 // Third, the Node.js implementation enforces max size limits on the key,
36 // salt, and info parameters. Fourth, the Web Crypto API relies on the key
37 // being a CryptoKey object, whereas the Node.js implementation here takes a
38 // raw byte array.
39 auto digest = ncrypto::getDigestByName(hash.begin());
40 
41 JSG_REQUIRE_NONNULL(digest, TypeError, "Invalid Hkdf digest: ", hash);
42 JSG_REQUIRE(info.size() <= INT32_MAX, RangeError, "Hkdf failed: info is too large");
43 JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, "Hkdf failed: salt is too large");
44 JSG_REQUIRE(key.size() <= INT32_MAX, RangeError, "Hkdf failed: key is too large");
45 JSG_REQUIRE(ncrypto::checkHkdfLength(digest, length), RangeError, "Invalid Hkdf key length");
46 
47 return JSG_REQUIRE_NONNULL(api::hkdf(js, length, digest, key, salt, info), Error, "Hkdf failed");
48}
49 
50jsg::JsArrayBuffer CryptoImpl::getPbkdf(jsg::Lock& js,
51 kj::Array<const kj::byte> password,
52 kj::Array<const kj::byte> salt,
53 uint32_t num_iterations,
54 uint32_t keylen,
55 kj::String name) {
56 // The Node.js version of the PBKDF2 is a bit different from the Web Crypto API.
57 // For one, the Node.js implementation allows for a broader range of possible
58 // digest algorithms whereas the Web Crypto API only allows for a few specific ones.
59 // Second, the Node.js implementation enforces max size limits on the password and
60 // salt parameters.
61 auto digest = ncrypto::getDigestByName(name.begin());
62 
63 JSG_REQUIRE_NONNULL(
64 digest, TypeError, "Invalid Pbkdf2 digest: ", name, internalDescribeOpensslErrors());
65 JSG_REQUIRE(password.size() <= INT32_MAX, RangeError, "Pbkdf2 failed: password is too large");
66 JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, "Pbkdf2 failed: salt is too large");
67 // Note: The user could DoS us by selecting a very high iteration count. As with the Web Crypto
68 // API, intentionally limit the maximum iteration count.
69 checkPbkdfLimits(js, num_iterations);
70 
71 return JSG_REQUIRE_NONNULL(
72 api::pbkdf2(js, keylen, num_iterations, digest, password, salt), Error, "Pbkdf2 failed");
73}
74 
75jsg::JsArrayBuffer CryptoImpl::getScrypt(jsg::Lock& js,
76 kj::Array<const kj::byte> password,
77 kj::Array<const kj::byte> salt,
78 uint32_t N,
79 uint32_t r,
80 uint32_t p,
81 uint32_t maxmem,
82 uint32_t keylen) {
83 JSG_REQUIRE(password.size() <= INT32_MAX, RangeError, "Scrypt failed: password is too large");
84 JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, "Scrypt failed: salt is too large");
85 
86 return JSG_REQUIRE_NONNULL(
87 api::scrypt(js, keylen, N, r, p, maxmem, password, salt), Error, "Scrypt failed");
88}
89#pragma endregion // KDF
90 
91// ======================================================================================
92#pragma region SPKAC
93 
94bool CryptoImpl::verifySpkac(kj::Array<const kj::byte> input) {
95 return workerd::api::verifySpkac(input);
96}
97 
98kj::Maybe<jsg::JsUint8Array> CryptoImpl::exportPublicKey(
99 jsg::Lock& js, kj::Array<const kj::byte> input) {
100 return workerd::api::exportPublicKey(js, input);
101}
102 
103kj::Maybe<jsg::JsUint8Array> CryptoImpl::exportChallenge(
104 jsg::Lock& js, kj::Array<const kj::byte> input) {
105 return workerd::api::exportChallenge(js, input);
106}
107#pragma endregion // SPKAC
108 
109// ======================================================================================
110#pragma region Primes
111 
112jsg::JsArrayBuffer CryptoImpl::randomPrime(jsg::Lock& js,
113 uint32_t size,
114 bool safe,
115 jsg::Optional<kj::Array<kj::byte>> add_buf,
116 jsg::Optional<kj::Array<kj::byte>> rem_buf) {
117 return workerd::api::randomPrime(js, size, safe,
118 add_buf.map([](kj::Array<kj::byte>& buf) { return buf.asPtr(); }),
119 rem_buf.map([](kj::Array<kj::byte>& buf) { return buf.asPtr(); }));
120}
121 
122bool CryptoImpl::checkPrimeSync(kj::Array<kj::byte> bufferView, uint32_t num_checks) {
123 return workerd::api::checkPrime(bufferView.asPtr(), num_checks);
124}
125#pragma endregion // Primes
126 
127// ======================================================================================
128#pragma region Hmac
129jsg::Ref<CryptoImpl::HmacHandle> CryptoImpl::HmacHandle::constructor(
130 jsg::Lock& js, kj::String algorithm, kj::OneOf<kj::Array<kj::byte>, jsg::Ref<CryptoKey>> key) {
131 KJ_SWITCH_ONEOF(key) {
132 KJ_CASE_ONEOF(key_data, kj::Array<kj::byte>) {
133 return js.alloc<HmacHandle>(HmacContext(js, algorithm, key_data.asPtr()));
134 }
135 KJ_CASE_ONEOF(key, jsg::Ref<CryptoKey>) {
136 return js.alloc<HmacHandle>(HmacContext(js, algorithm, key->impl.get()));
137 }
138 }
139 KJ_UNREACHABLE;
140}
141 
142int CryptoImpl::HmacHandle::update(kj::Array<kj::byte> data) {
143 ctx.update(data);
144 return 1; // This just always returns 1 no matter what.
145}
146 
147jsg::JsUint8Array CryptoImpl::HmacHandle::digest(jsg::Lock& js) {
148 return ctx.digest(js);
149}
150 
151jsg::JsUint8Array CryptoImpl::HmacHandle::oneshot(jsg::Lock& js,
152 kj::String algorithm,
153 CryptoImpl::HmacHandle::KeyParam key,
154 kj::Array<kj::byte> data) {
155 KJ_SWITCH_ONEOF(key) {
156 KJ_CASE_ONEOF(key_data, kj::Array<kj::byte>) {
157 HmacContext ctx(js, algorithm, key_data.asPtr());
158 ctx.update(data);
159 return ctx.digest(js);
160 }
161 KJ_CASE_ONEOF(key, jsg::Ref<CryptoKey>) {
162 HmacContext ctx(js, algorithm, key->impl.get());
163 ctx.update(data);
164 return ctx.digest(js);
165 }
166 }
167 KJ_UNREACHABLE;
168}
169 
170void CryptoImpl::HmacHandle::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
171 tracker.trackFieldWithSize("digest", ctx.size());
172}
173#pragma endregion // Hmac
174 
175// ======================================================================================
176#pragma region Hash
177jsg::Ref<CryptoImpl::HashHandle> CryptoImpl::HashHandle::constructor(
178 jsg::Lock& js, kj::String algorithm, kj::Maybe<uint32_t> xofLen) {
179 return js.alloc<HashHandle>(HashContext(algorithm, xofLen));
180}
181 
182int CryptoImpl::HashHandle::update(kj::Array<kj::byte> data) {
183 ctx.update(data);
184 return 1;
185}
186 
187jsg::JsUint8Array CryptoImpl::HashHandle::digest(jsg::Lock& js) {
188 return ctx.digest(js);
189}
190 
191jsg::Ref<CryptoImpl::HashHandle> CryptoImpl::HashHandle::copy(
192 jsg::Lock& js, kj::Maybe<uint32_t> xofLen) {
193 return js.alloc<HashHandle>(ctx.clone(js, kj::mv(xofLen)));
194}
195 
196void CryptoImpl::HashHandle::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
197 tracker.trackFieldWithSize("digest", ctx.size());
198}
199 
200jsg::JsUint8Array CryptoImpl::HashHandle::oneshot(
201 jsg::Lock& js, kj::String algorithm, kj::Array<kj::byte> data, kj::Maybe<uint32_t> xofLen) {
202 HashContext ctx(algorithm, xofLen);
203 ctx.update(data);
204 return ctx.digest(js);
205}
206#pragma endregion Hash
207 
208// ======================================================================================
209#pragma region DiffieHellman
210 
211jsg::Ref<CryptoImpl::DiffieHellmanHandle> CryptoImpl::DiffieHellmanGroupHandle(
212 jsg::Lock& js, kj::String name) {
213 return js.alloc<DiffieHellmanHandle>(DiffieHellman(name));
214}
215 
216jsg::Ref<CryptoImpl::DiffieHellmanHandle> CryptoImpl::DiffieHellmanHandle::constructor(
217 jsg::Lock& js,
218 kj::OneOf<kj::Array<kj::byte>, int> sizeOrKey,
219 kj::OneOf<kj::Array<kj::byte>, int> generator) {
220 return js.alloc<DiffieHellmanHandle>(DiffieHellman(sizeOrKey, generator));
221}
222 
223CryptoImpl::DiffieHellmanHandle::DiffieHellmanHandle(DiffieHellman dh): dh(kj::mv(dh)) {
224 verifyError = JSG_REQUIRE_NONNULL(this->dh.check(), Error, "DiffieHellman init failed");
225};
226 
227void CryptoImpl::DiffieHellmanHandle::setPrivateKey(kj::Array<kj::byte> key) {
228 dh.setPrivateKey(key);
229}
230 
231void CryptoImpl::DiffieHellmanHandle::setPublicKey(kj::Array<kj::byte> key) {
232 dh.setPublicKey(key);
233}
234 
235jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPublicKey(jsg::Lock& js) {
236 return dh.getPublicKey(js);
237}
238 
239jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPrivateKey(jsg::Lock& js) {
240 return dh.getPrivateKey(js);
241}
242 
243jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getGenerator(jsg::Lock& js) {
244 return dh.getGenerator(js);
245}
246 
247jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPrime(jsg::Lock& js) {
248 return dh.getPrime(js);
249}
250 
251jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::computeSecret(
252 jsg::Lock& js, kj::Array<kj::byte> key) {
253 return dh.computeSecret(js, key);
254}
255 
256jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::generateKeys(jsg::Lock& js) {
257 return dh.generateKeys(js);
258}
259 
260int CryptoImpl::DiffieHellmanHandle::getVerifyError() {
261 return verifyError;
262}
263#pragma endregion // DiffieHellman
264 
265// ======================================================================================
266#pragma region SignVerify
267 
268namespace {
269jsg::JsUint8Array signFinal(jsg::Lock& js,
270 ncrypto::EVPMDCtxPointer&& mdctx,
271 const ncrypto::EVPKeyPointer& pkey,
272 int padding,
273 jsg::Optional<int> pss_salt_len) {
274 
275 // The version of BoringSSL we use does not support DSA keys with EVP
276 // When signing/verification. This may change in the future.
277 JSG_REQUIRE(pkey.id() != EVP_PKEY_DSA, Error, "Signing with DSA keys is not currently supported");
278 
279 auto data = mdctx.digestFinal(mdctx.getExpectedSize());
280 JSG_REQUIRE(data, Error, "Failed to generate digest");
281 
282 auto sig = jsg::JsUint8Array::create(js, pkey.size());
283 ncrypto::Buffer<kj::byte> sig_buf{
284 .data = sig.asArrayPtr().begin(),
285 .len = sig.size(),
286 };
287 
288 ncrypto::EVPKeyCtxPointer pkctx = pkey.newCtx();
289 JSG_REQUIRE(pkctx.initForSign(), Error, "Failed to initialize signing context");
290 
291 if (pkey.isRsaVariant()) {
292 std::optional<int> maybeSaltLen = std::nullopt;
293 KJ_IF_SOME(len, pss_salt_len) {
294 maybeSaltLen = len;
295 }
296 JSG_REQUIRE(ncrypto::EVPKeyCtxPointer::setRsaPadding(pkctx.get(), padding, maybeSaltLen), Error,
297 "Failed to set RSA parameters for signature");
298 }
299 
300 JSG_REQUIRE(pkctx.setSignatureMd(mdctx), Error, "Failed to set signature digest");
301 JSG_REQUIRE(pkctx.signInto(data, &sig_buf), Error, "Failed to generate signature");
302 
303 if (sig_buf.len < sig.size()) {
304 return sig.slice(js, sig_buf.len);
305 }
306 
307 return sig;
308}
309 
310bool verifyFinal(jsg::Lock& js,
311 ncrypto::EVPMDCtxPointer&& mdctx,
312 const ncrypto::EVPKeyPointer& pkey,
313 jsg::JsBufferSource& signature,
314 int padding,
315 jsg::Optional<int> pss_salt_len) {
316 
317 // The version of BoringSSL we use does not support DSA keys with EVP
318 // When signing/verification. This may change in the future.
319 JSG_REQUIRE(
320 pkey.id() != EVP_PKEY_DSA, Error, "Verifying with DSA keys is not currently supported");
321 
322 auto data = mdctx.digestFinal(mdctx.getExpectedSize());
323 JSG_REQUIRE(data, Error, "Failed to finalize signature verification");
324 
325 ncrypto::EVPKeyCtxPointer pkctx = pkey.newCtx();
326 JSG_REQUIRE(pkctx, Error, "Failed to initialize key for verification");
327 
328 const int init_ret = pkctx.initForVerify();
329 JSG_REQUIRE(init_ret != -2, Error, "Failed to initialize key for verification");
330 
331 if (pkey.isRsaVariant()) {
332 std::optional<int> maybeSaltLen = std::nullopt;
333 KJ_IF_SOME(len, pss_salt_len) {
334 maybeSaltLen = len;
335 }
336 JSG_REQUIRE(ncrypto::EVPKeyCtxPointer::setRsaPadding(pkctx.get(), padding, maybeSaltLen), Error,
337 "Failed to set RSA parameters for signature");
338 }
339 
340 JSG_REQUIRE(pkctx.setSignatureMd(mdctx), Error,
341 "Failed to set digest context for signature verification");
342 
343 ncrypto::Buffer<const kj::byte> sig{
344 .data = signature.asArrayPtr().begin(),
345 .len = signature.size(),
346 };
347 
348 return pkctx.verify(sig, data);
349}
350 
351jsg::JsUint8Array convertSignatureToP1363(
352 jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::JsUint8Array&& signature) {
353 auto maybeRs = pkey.getBytesOfRS();
354 if (!maybeRs.has_value()) return kj::mv(signature);
355 unsigned int n = maybeRs.value();
356 
357 auto ret = jsg::JsUint8Array::create(js, 2 * n);
358 
359 ncrypto::Buffer<const unsigned char> sig_buffer{
360 .data = signature.asArrayPtr().begin(),
361 .len = signature.size(),
362 };
363 
364 if (!ncrypto::extractP1363(sig_buffer, ret.asArrayPtr().begin(), n)) {
365 return kj::mv(signature);
366 }
367 
368 return ret;
369}
370 
371jsg::JsUint8Array convertSignatureToDER(
372 jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::JsUint8Array&& signature) {
373 auto maybeRs = pkey.getBytesOfRS();
374 if (!maybeRs.has_value()) return kj::mv(signature);
375 unsigned int n = maybeRs.value();
376 
377 if (signature.size() != 2 * n) {
378 return jsg::JsUint8Array::create(js, 0);
379 }
380 
381 const kj::byte* sig_data = signature.asArrayPtr().begin();
382 
383 auto asn1_sig = ncrypto::ECDSASigPointer::New();
384 JSG_REQUIRE(asn1_sig, Error, "Internal error generating signature");
385 ncrypto::BignumPointer r(sig_data, n);
386 JSG_REQUIRE(r, Error, "Internal error generating signature");
387 ncrypto::BignumPointer s(sig_data + n, n);
388 JSG_REQUIRE(s, Error, "Internal error generating signature");
389 JSG_REQUIRE(asn1_sig.setParams(kj::mv(r), kj::mv(s)), Error,
390 "Internal error setting signature parameters");
391 
392 auto buf = asn1_sig.encode();
393 if (buf.len <= 0) [[unlikely]] {
394 return jsg::JsUint8Array::create(js, 0);
395 }
396 
397 return jsg::JsUint8Array::create(js, kj::ArrayPtr<kj::byte>(buf.data, buf.len));
398}
399 
400const EVP_MD* maybeGetDigest(jsg::Optional<kj::String>& maybeAlgorithm) {
401 KJ_IF_SOME(alg, maybeAlgorithm) {
402 auto md = ncrypto::getDigestByName(alg.cStr());
403 JSG_REQUIRE(md != nullptr, Error, kj::str("Unknown digest: ", alg));
404 return md;
405 }
406 return nullptr;
407}
408} // namespace
409 
410CryptoImpl::SignHandle::SignHandle(ncrypto::EVPMDCtxPointer ctx)
411 : ctx(ncrypto::EVPMDCtxPointer(ctx.release())) {}
412 
413jsg::Ref<CryptoImpl::SignHandle> CryptoImpl::SignHandle::constructor(
414 jsg::Lock& js, kj::String algorithm) {
415 ncrypto::ClearErrorOnReturn clear_error_on_return;
416 auto md = ncrypto::getDigestByName(algorithm.cStr());
417 JSG_REQUIRE(md != nullptr, Error, kj::str("Unknown digest: ", algorithm));
418 
419 auto mdctx = ncrypto::EVPMDCtxPointer::New();
420 JSG_REQUIRE(mdctx, Error, "Failed to create signing context");
421 JSG_REQUIRE(mdctx.digestInit(md), Error, "Failed to initialize signing context");
422 return js.alloc<SignHandle>(kj::mv(mdctx));
423}
424 
425void CryptoImpl::SignHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {
426 ncrypto::ClearErrorOnReturn clear_error_on_return;
427 JSG_REQUIRE(ctx, Error, "Signing context has already been finalized");
428 auto ptr = data.asArrayPtr();
429 ncrypto::Buffer<const void> buf{
430 .data = ptr.begin(),
431 .len = ptr.size(),
432 };
433 JSG_REQUIRE(ctx.digestUpdate(buf), Error, "Failed to update signing context");
434}
435 
436jsg::JsUint8Array CryptoImpl::SignHandle::sign(jsg::Lock& js,
437 jsg::Ref<CryptoKey> key,
438 jsg::Optional<int> rsaPadding,
439 jsg::Optional<int> pssSaltLength,
440 jsg::Optional<int> dsaSigEnc) {
441 ncrypto::ClearErrorOnReturn clear_error_on_return;
442 JSG_REQUIRE(ctx, Error, "Signing context has already been finalized");
443 
444 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "Invalid key for sign operation");
445 JSG_REQUIRE(pkey.validateDsaParameters(), Error, "Invalid DSA parameters");
446 
447 // There's a bug in ncrypto that doesn't clear the EVPMDCtxPointer when
448 // moved with kj::mv so instead we release and wrap again.
449 auto sig = signFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey,
450 rsaPadding.orDefault(pkey.getDefaultSignPadding()), pssSaltLength);
451 
452 KJ_IF_SOME(enc, dsaSigEnc) {
453 static constexpr unsigned kP1363 = 1;
454 JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding");
455 if (enc == kP1363) {
456 sig = convertSignatureToP1363(js, pkey, kj::mv(sig));
457 }
458 }
459 
460 return sig;
461}
462 
463CryptoImpl::VerifyHandle::VerifyHandle(ncrypto::EVPMDCtxPointer ctx)
464 : ctx(ncrypto::EVPMDCtxPointer(ctx.release())) {}
465 
466jsg::Ref<CryptoImpl::VerifyHandle> CryptoImpl::VerifyHandle::constructor(
467 jsg::Lock& js, kj::String algorithm) {
468 ncrypto::ClearErrorOnReturn clear_error_on_return;
469 auto md = ncrypto::getDigestByName(algorithm.cStr());
470 JSG_REQUIRE(md != nullptr, Error, kj::str("Unknown digest: ", algorithm));
471 
472 auto mdctx = ncrypto::EVPMDCtxPointer::New();
473 JSG_REQUIRE(mdctx, Error, "Failed to create verification context");
474 JSG_REQUIRE(mdctx.digestInit(md), Error, "Failed to initialize verification context");
475 
476 return js.alloc<VerifyHandle>(kj::mv(mdctx));
477}
478 
479void CryptoImpl::VerifyHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {
480 ncrypto::ClearErrorOnReturn clear_error_on_return;
481 JSG_REQUIRE(ctx, Error, "Verification context has already been finalized");
482 auto ptr = data.asArrayPtr();
483 ncrypto::Buffer<const void> buf{
484 .data = ptr.begin(),
485 .len = ptr.size(),
486 };
487 JSG_REQUIRE(ctx.digestUpdate(buf), Error, "Failed to update verification context");
488}
489 
490bool CryptoImpl::VerifyHandle::verify(jsg::Lock& js,
491 jsg::Ref<CryptoKey> key,
492 jsg::JsBufferSource signature,
493 jsg::Optional<int> rsaPadding,
494 jsg::Optional<int> maybeSaltLen,
495 jsg::Optional<int> dsaSigEnc) {
496 ncrypto::ClearErrorOnReturn clear_error_on_return;
497 
498 JSG_REQUIRE(ctx, Error, "Verification context has already been finalized");
499 
500 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "Invalid key for verify operation");
501 
502 JSG_REQUIRE(!pkey.isOneShotVariant(), Error, "Unsupported operation for this key");
503 
504 auto sigCopy = jsg::JsUint8Array::create(js, signature.asArrayPtr());
505 
506 KJ_IF_SOME(enc, dsaSigEnc) {
507 static constexpr unsigned kP1363 = 1;
508 JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding");
509 if (enc == kP1363) {
510 sigCopy = convertSignatureToDER(js, pkey, kj::mv(sigCopy));
511 }
512 }
513 
514 auto sigSource = jsg::JsBufferSource(sigCopy);
515 
516 return verifyFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey, sigSource,
517 rsaPadding.orDefault(pkey.getDefaultSignPadding()), maybeSaltLen);
518}
519 
520jsg::JsUint8Array CryptoImpl::signOneShot(jsg::Lock& js,
521 jsg::Ref<CryptoKey> key,
522 jsg::Optional<kj::String> algorithm,
523 jsg::JsBufferSource data,
524 jsg::Optional<int> rsaPadding,
525 jsg::Optional<int> pssSaltLength,
526 jsg::Optional<int> dsaSigEnc) {
527 ncrypto::ClearErrorOnReturn clear_error_on_return;
528 
529 auto mdctx = ncrypto::EVPMDCtxPointer::New();
530 JSG_REQUIRE(mdctx, Error, "Failed to create signing context");
531 
532 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "Invalid key for sign operation");
533 
534 // The version of BoringSSL we use does not support DSA keys with EVP
535 // When signing/verification. This may change in the future.
536 JSG_REQUIRE(pkey.id() != EVP_PKEY_DSA, Error, "Signing with DSA keys is not currently supported");
537 // TODO(later): When DSA keys are supported, uncomment to validate DSA params.
538 // JSG_REQUIRE(pkey.validateDsaParameters(), Error, "Invalid DSA parameters");
539 
540 auto md = maybeGetDigest(algorithm);
541 
542 JSG_REQUIRE(mdctx.signInit(pkey, md).has_value(), Error, "Failed to initialize signing context");
543 
544 ncrypto::Buffer<const kj::byte> buf{
545 .data = data.asArrayPtr().begin(),
546 .len = data.size(),
547 };
548 
549 // For one-shot-only key types (e.g. Ed25519, Ed448), we must use
550 // EVP_DigestSign via signOneShot(). For other key types (e.g. ECDSA),
551 // we use sign() which calls EVP_DigestSignUpdate + EVP_DigestSignFinal
552 // and correctly resizes the output buffer to the actual signature length.
553 // This matters for ECDSA where DER-encoded signatures can be shorter
554 // than the maximum estimated size.
555 ncrypto::DataPointer sig = pkey.isOneShotVariant() ? mdctx.signOneShot(buf) : mdctx.sign(buf);
556 auto sigBuf =
557 jsg::JsUint8Array::create(js, kj::ArrayPtr<const kj::byte>(sig.get<kj::byte>(), sig.size()));
558 
559 KJ_IF_SOME(enc, dsaSigEnc) {
560 static constexpr unsigned kP1363 = 1;
561 JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding");
562 if (enc == kP1363) {
563 sigBuf = convertSignatureToP1363(js, pkey, kj::mv(sigBuf));
564 }
565 }
566 
567 return sigBuf;
568}
569 
570bool CryptoImpl::verifyOneShot(jsg::Lock& js,
571 jsg::Ref<CryptoKey> key,
572 jsg::Optional<kj::String> algorithm,
573 jsg::JsBufferSource data,
574 jsg::JsBufferSource signature,
575 jsg::Optional<int> rsaPadding,
576 jsg::Optional<int> pssSaltLength,
577 jsg::Optional<int> dsaSigEnc) {
578 ncrypto::ClearErrorOnReturn clear_error_on_return;
579 
580 auto mdctx = ncrypto::EVPMDCtxPointer::New();
581 JSG_REQUIRE(mdctx, Error, "Failed to create verification context");
582 
583 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "Invalid key for verification operation");
584 
585 // The version of BoringSSL we use does not support DSA keys with EVP
586 // When signing/verification. This may change in the future.
587 JSG_REQUIRE(
588 pkey.id() != EVP_PKEY_DSA, Error, "Verifying with DSA keys is not currently supported");
589 // TODO(later): When DSA keys are supported, uncomment to validate DSA params.
590 // JSG_REQUIRE(pkey.validateDsaParameters(), Error, "Invalid DSA parameters");
591 
592 auto md = maybeGetDigest(algorithm);
593 
594 JSG_REQUIRE(
595 mdctx.verifyInit(pkey, md).has_value(), Error, "Failed to initialize verification context");
596 
597 auto sigCopy = jsg::JsUint8Array::create(js, signature.asArrayPtr());
598 
599 KJ_IF_SOME(enc, dsaSigEnc) {
600 static constexpr unsigned kP1363 = 1;
601 JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding");
602 if (enc == kP1363) {
603 sigCopy = convertSignatureToDER(js, pkey, kj::mv(sigCopy));
604 }
605 }
606 
607 ncrypto::Buffer<const kj::byte> buf{
608 .data = data.asArrayPtr().begin(),
609 .len = data.size(),
610 };
611 
612 ncrypto::Buffer<const kj::byte> sig{
613 .data = sigCopy.asArrayPtr().begin(),
614 .len = sigCopy.size(),
615 };
616 
617 return mdctx.verify(buf, sig);
618}
619 
620#pragma endregion // SignVerify
621 
622// ======================================================================================
623#pragma region Cipher/Decipher
624 
625namespace {
626constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
627 
628CryptoImpl::CipherHandle::AuthenticatedInfo initAuthenticated(ncrypto::CipherCtxPointer& ctx,
629 bool encrypt,
630 kj::StringPtr cipher_type,
631 int iv_len,
632 unsigned int auth_tag_len) {
633 ncrypto::MarkPopErrorOnReturn mark_pop_error_on_return;
634 
635 JSG_REQUIRE(ctx.setIvLength(iv_len), Error, "Invalid initialization vector");
636 
637 CryptoImpl::CipherHandle::AuthenticatedInfo info;
638 info.auth_tag_len = auth_tag_len;
639 
640 const int mode = ctx.getMode();
641 if (mode == EVP_CIPH_GCM_MODE) {
642 if (info.auth_tag_len != kNoAuthTagLength) {
643 JSG_REQUIRE(ncrypto::Cipher::IsValidGCMTagLength(auth_tag_len), Error,
644 "Invalid authentication tag length");
645 }
646 } else {
647 if (auth_tag_len == kNoAuthTagLength) {
648 // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag
649 // length defaults to 16 bytes when encrypting. Unlike GCM, the
650 // authentication tag length also defaults to 16 bytes when decrypting,
651 // whereas GCM would accept any valid authentication tag length.
652 if (ctx.getNid() == NID_chacha20_poly1305) {
653 info.auth_tag_len = 16;
654 } else {
655 JSG_FAIL_REQUIRE(
656 Error, kj::str("The auth tag length is required for cipher ", cipher_type));
657 }
658 }
659 
660 if (mode == EVP_CIPH_CCM_MODE && !encrypt && FIPS_mode()) {
661 JSG_FAIL_REQUIRE(Error, "CCM encryption not supported in FIPS mode");
662 }
663 
664 JSG_REQUIRE(
665 ctx.setAeadTagLength(info.auth_tag_len), Error, "Invalid authentication tag length");
666 
667 if (mode == EVP_CIPH_CCM_MODE) {
668 // See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38c.pdf
669 // A.1 Length Requirements
670 
671 JSG_REQUIRE(iv_len >= 7 && iv_len <= 13, Error, "Invalid authentication tag length");
672 if (iv_len == 12) info.max_message_size = 16777215;
673 if (iv_len == 13) info.max_message_size = 65535;
674 }
675 }
676 
677 return info;
678}
679 
680bool isAuthenticatedMode(const ncrypto::CipherCtxPointer& ctx) {
681 return ncrypto::Cipher::FromCtx(ctx).isSupportedAuthenticatedMode();
682}
683 
684bool passAuthTagToOpenSSL(ncrypto::CipherCtxPointer& ctx, kj::ArrayPtr<const kj::byte> authTag) {
685 ncrypto::Buffer<const char> buffer{
686 .data = reinterpret_cast<const char*>(authTag.begin()),
687 .len = authTag.size(),
688 };
689 return ctx.setAeadTag(buffer);
690}
691} // namespace
692 
693CryptoImpl::CipherHandle::CipherHandle(CipherMode mode,
694 ncrypto::CipherCtxPointer ctx,
695 jsg::Ref<CryptoKey> key,
696 kj::Array<kj::byte> iv,
697 kj::Maybe<AuthenticatedInfo> maybeAuthInfo)
698 : mode(mode),
699 ctx(kj::mv(ctx)),
700 key(kj::mv(key)),
701 iv(kj::mv(iv)),
702 maybeAuthInfo(kj::mv(maybeAuthInfo)) {}
703 
704jsg::Ref<CryptoImpl::CipherHandle> CryptoImpl::CipherHandle::construct(jsg::Lock& js,
705 CipherMode mode,
706 kj::StringPtr algorithm,
707 ncrypto::Cipher cipher,
708 jsg::Ref<CryptoKey> key,
709 jsg::JsBufferSource iv,
710 jsg::Optional<uint32_t> maybeAuthTagLength) {
711 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
712 
713 JSG_REQUIRE(key->getType() == "secret"_kj, TypeError, "Invalid key type for cipher");
714 
715 auto keyData =
716 JSG_REQUIRE_NONNULL(tryGetSecretKeyData(key), Error, "Failed to get raw secret key data");
717 
718 int expectedIvLength = cipher.getIvLength();
719 
720 if ((expectedIvLength && !iv.size()) ||
721 (!cipher.isSupportedAuthenticatedMode() && iv.size() &&
722 static_cast<int>(iv.size()) != expectedIvLength)) {
723 JSG_FAIL_REQUIRE(Error, "Invalid initialization vector");
724 }
725 
726 if (cipher.getNid() == NID_chacha20_poly1305) {
727 JSG_REQUIRE(iv.size(), Error, "ChaCha20-Poly1305 requires an initialization vector");
728 JSG_REQUIRE(iv.size() <= 12, Error, "Invalid initialization vector");
729 }
730 
731 auto ctx = ncrypto::CipherCtxPointer::New();
732 JSG_REQUIRE(ctx, Error, "Failed to create cipher/decipher context");
733 
734 if (cipher.getMode() == EVP_CIPH_WRAP_MODE) {
735 ctx.setAllowWrap();
736 }
737 
738 bool encrypt = mode == CipherMode::CIPHER;
739 
740 JSG_REQUIRE(ctx.init(cipher, encrypt), Error, "Failed to initialize cipher/decipher context");
741 
742 kj::Maybe<CryptoImpl::CipherHandle::AuthenticatedInfo> maybeAuthInfo = kj::none;
743 if (cipher.isSupportedAuthenticatedMode()) {
744 maybeAuthInfo = initAuthenticated(
745 ctx, encrypt, algorithm, iv.size(), maybeAuthTagLength.orDefault(kNoAuthTagLength));
746 }
747 
748 JSG_REQUIRE(ctx.setKeyLength(keyData.size()), Error, "Invalid key length");
749 
750 JSG_REQUIRE(ctx.init(ncrypto::Cipher(), encrypt, keyData.begin(), iv.asArrayPtr().begin()), Error,
751 "Failed to initialize cipher/cipher context");
752 
753 // Copy the IV into C++-owned memory so that later modifications to the JS buffer
754 // cannot affect the cipher. This matches Node.js, which copies the IV into OpenSSL
755 // at init time.
756 auto ivCopy = kj::heapArray<kj::byte>(iv.asArrayPtr());
757 return js.alloc<CipherHandle>(
758 mode, kj::mv(ctx), kj::mv(key), kj::mv(ivCopy), kj::mv(maybeAuthInfo));
759}
760 
761jsg::JsUint8Array CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {
762 
763 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
764 JSG_REQUIRE(data.size() <= INT_MAX, Error, "Data too large");
765 
766 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
767 
768 const int ctxMode = ctx.getMode();
769 
770 if (ctxMode == EVP_CIPH_CCM_MODE) {
771 auto max = KJ_ASSERT_NONNULL(maybeAuthInfo).max_message_size;
772 JSG_REQUIRE(data.size() <= max, Error, "Invalid message length");
773 }
774 
775 if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) {
776 authTagPassed = true;
777 auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided");
778 JSG_REQUIRE(passAuthTagToOpenSSL(ctx, tagRef.asPtr()), Error, "Failed to set auth tag");
779 }
780 
781 const int block_size = ctx.getBlockSize();
782 KJ_ASSERT(block_size > 0);
783 JSG_REQUIRE(data.size() + block_size <= INT_MAX, Error, "Data too large");
784 int buf_len = data.size() + block_size;
785 
786 ncrypto::Buffer<const unsigned char> buffer = {
787 .data = data.asArrayPtr().begin(),
788 .len = data.size(),
789 };
790 if (mode == CipherMode::CIPHER && ctxMode == EVP_CIPH_WRAP_MODE &&
791 !ctx.update(buffer, nullptr, &buf_len)) {
792 JSG_FAIL_REQUIRE(Error, "Failed to process data");
793 }
794 
795 auto buf = jsg::JsUint8Array::create(js, buf_len);
796 buffer.data = data.asArrayPtr().begin();
797 buffer.len = data.size();
798 bool r = ctx.update(buffer, buf.asArrayPtr().begin(), &buf_len);
799 
800 if (buf_len != buf.size()) {
801 JSG_REQUIRE(buf_len < buf.size(), Error, "Invalid buffer length");
802 auto newBuf = jsg::JsUint8Array::create(js, buf_len);
803 if (buf_len > 0) {
804 newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(buf_len));
805 }
806 buf = kj::mv(newBuf);
807 }
808 
809 // When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is
810 // invalid. In that case, remember the error and throw in final().
811 if (!r && mode == CipherMode::DECIPHER && ctxMode == EVP_CIPH_CCM_MODE) {
812 pendingAuthFailed = true;
813 }
814 
815 return buf;
816}
817 
818jsg::JsUint8Array CryptoImpl::CipherHandle::final(jsg::Lock& js) {
819 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
820 
821 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
822 
823 int ctxMode = ctx.getMode();
824 
825 auto buf = jsg::JsUint8Array::create(js, ctx.getBlockSize());
826 
827 if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) {
828 authTagPassed = true;
829 auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided");
830 JSG_REQUIRE(passAuthTagToOpenSSL(ctx, tagRef.asPtr()), Error, "Failed to set auth tag");
831 }
832 
833 if (ctx.getNid() == NID_chacha20_poly1305 && mode == CipherMode::DECIPHER) {
834 JSG_REQUIRE(authTagPassed, Error, "An auth tag is required");
835 }
836 
837 // In CCM mode, final() only checks whether authentication failed in update().
838 // EVP_CipherFinal_ex must not be called and will fail.
839 bool ok;
840 if (mode == CipherMode::DECIPHER && ctxMode == EVP_CIPH_CCM_MODE) {
841 ok = !pendingAuthFailed;
842 buf = jsg::JsUint8Array::create(js, 0);
843 } else {
844 int out_len = buf.size();
845 ok = ctx.update({}, buf.asArrayPtr().begin(), &out_len, true);
846 
847 if (out_len != buf.size()) {
848 JSG_REQUIRE(out_len < buf.size(), Error, "Invalid buffer length");
849 auto newBuf = jsg::JsUint8Array::create(js, out_len);
850 if (out_len > 0) {
851 newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(out_len));
852 }
853 buf = kj::mv(newBuf);
854 }
855 
856 if (ok && mode == CipherMode::CIPHER && isAuthenticatedMode(ctx)) {
857 auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, "Missing required auth info");
858 // In GCM mode, the authentication tag length can be specified in advance,
859 // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
860 // always be given by the user.
861 if (info.auth_tag_len == kNoAuthTagLength) {
862 info.auth_tag_len = 16;
863 }
864 auto tag = kj::heapArray<kj::byte>(info.auth_tag_len);
865 ok = ctx.getAeadTag(info.auth_tag_len, tag.begin());
866 maybeAuthTag = kj::mv(tag);
867 }
868 }
869 
870 JSG_REQUIRE(ok, Error, "Authentication failed");
871 
872 ctx.reset();
873 return buf;
874}
875 
876void CryptoImpl::CipherHandle::setAAD(
877 jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional<uint32_t> maybePlaintextLength) {
878 
879 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
880 JSG_REQUIRE(isAuthenticatedMode(ctx), Error, "Cipher does not support authenticated mode");
881 
882 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
883 
884 int outlen;
885 const int ctxMode = ctx.getMode();
886 
887 // When in CCM mode, we need to set the authentication tag and the plaintext
888 // length in advance.
889 if (ctxMode == EVP_CIPH_CCM_MODE) {
890 auto plaintextLength = JSG_REQUIRE_NONNULL(
891 maybePlaintextLength, Error, "options.plaintextLength is required for CCM mode with AAD");
892 
893 auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, "Required auth info is not available");
894 
895 JSG_REQUIRE(plaintextLength <= info.max_message_size, Error, "Data too large");
896 
897 if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) {
898 authTagPassed = true;
899 auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided");
900 JSG_REQUIRE(passAuthTagToOpenSSL(ctx, tagRef.asPtr()), Error, "Failed to set auth tag");
901 }
902 
903 ncrypto::Buffer<const unsigned char> buffer{
904 .data = nullptr,
905 .len = plaintextLength,
906 };
907 // Specify the plaintext length.
908 JSG_REQUIRE(ctx.update(buffer, nullptr, &outlen), Error, "Failed to set plaintext length");
909 }
910 
911 ncrypto::Buffer<const unsigned char> buffer{
912 .data = aad.asArrayPtr().begin(),
913 .len = aad.size(),
914 };
915 JSG_REQUIRE(ctx.update(buffer, nullptr, &outlen), Error, "Failed to set AAD");
916}
917 
918void CryptoImpl::CipherHandle::setAutoPadding(jsg::Lock& js, bool autoPadding) {
919 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
920 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
921 JSG_REQUIRE(ctx.setPadding(autoPadding), Error, "Failed to set autopadding");
922}
923 
924void CryptoImpl::CipherHandle::setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag) {
925 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
926 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
927 JSG_REQUIRE(isAuthenticatedMode(ctx), Error, "Cipher does not support authenticated mode");
928 JSG_REQUIRE(
929 mode == CipherMode::DECIPHER, Error, "Setting auth tag only support in decipher mode");
930 JSG_REQUIRE(maybeAuthTag == kj::none, Error, "Auth tag is already set");
931 JSG_REQUIRE(authTag.size() <= INT_MAX, Error, "Auth tag is too big");
932 
933 int ctxMode = ctx.getMode();
934 bool is_valid = false;
935 
936 auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, "Required auth info is not available");
937 
938 if (ctxMode == EVP_CIPH_GCM_MODE) {
939 // Restrict GCM tag lengths according to NIST 800-38d, page 9.
940 is_valid = (info.auth_tag_len == kNoAuthTagLength || info.auth_tag_len == authTag.size()) &&
941 ncrypto::Cipher::IsValidGCMTagLength(authTag.size());
942 } else {
943 is_valid = info.auth_tag_len == authTag.size();
944 }
945 
946 JSG_REQUIRE(is_valid, Error, "Invalid authentication tag length");
947 
948 info.auth_tag_len = authTag.size();
949 
950 // Copy the auth tag so that later modifications to the JS buffer cannot affect the cipher.
951 maybeAuthTag = kj::heapArray<kj::byte>(authTag.asArrayPtr());
952}
953 
954jsg::JsUint8Array CryptoImpl::CipherHandle::getAuthTag(jsg::Lock& js) {
955 JSG_REQUIRE(!ctx, Error, "Auth tag is only available once cipher context has been finalized");
956 JSG_REQUIRE(mode == CipherMode::CIPHER, Error, "Getting the auth tag is only support for cipher");
957 
958 KJ_IF_SOME(ref, maybeAuthTag) {
959 auto result = jsg::JsUint8Array::create(js, ref.asPtr());
960 maybeAuthTag = kj::none;
961 return result;
962 }
963 
964 return jsg::JsUint8Array::create(js, 0);
965}
966 
967namespace {
968CryptoImpl::AeadHandle::AuthenticatedInfo initAuthenticated(ncrypto::Aead& aead,
969 ncrypto::AeadCtxPointer& ctx,
970 bool encrypt,
971 kj::StringPtr cipher_type,
972 int iv_len,
973 unsigned int auth_tag_len) {
974 ncrypto::MarkPopErrorOnReturn mark_pop_error_on_return;
975 
976 CryptoImpl::AeadHandle::AuthenticatedInfo info;
977 info.auth_tag_len = auth_tag_len;
978 
979 const int mode = aead.getMode();
980 if (mode == EVP_CIPH_GCM_MODE) {
981 if (info.auth_tag_len != kNoAuthTagLength) {
982 JSG_REQUIRE(ncrypto::Cipher::IsValidGCMTagLength(auth_tag_len), Error,
983 "Invalid authentication tag length");
984 }
985 } else {
986 if (auth_tag_len == kNoAuthTagLength) {
987 // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag
988 // length defaults to 16 bytes when encrypting. Unlike GCM, the
989 // authentication tag length also defaults to 16 bytes when decrypting,
990 // whereas GCM would accept any valid authentication tag length.
991 if (aead.getName() == "chacha20-poly1305"sv) {
992 info.auth_tag_len = 16;
993 } else {
994 JSG_FAIL_REQUIRE(
995 Error, kj::str("The auth tag length is required for cipher ", cipher_type));
996 }
997 }
998 
999 if (mode == EVP_CIPH_CCM_MODE && !encrypt && FIPS_mode()) {
1000 JSG_FAIL_REQUIRE(Error, "CCM encryption not supported in FIPS mode");
1001 }
1002 
1003 if (mode == EVP_CIPH_CCM_MODE) {
1004 // See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38c.pdf
1005 // A.1 Length Requirements
1006 JSG_REQUIRE(iv_len >= 7 && iv_len <= 13, Error, "Invalid authentication tag length");
1007 if (iv_len == 12) info.max_message_size = 16777215;
1008 if (iv_len == 13) info.max_message_size = 65535;
1009 }
1010 }
1011 
1012 return info;
1013}
1014} // namespace
1015 
1016CryptoImpl::AeadHandle::AeadHandle(CipherMode mode,
1017 ncrypto::Aead aead,
1018 ncrypto::AeadCtxPointer ctx,
1019 jsg::Ref<CryptoKey> key,
1020 kj::Array<kj::byte> iv,
1021 kj::Maybe<AuthenticatedInfo> maybeAuthInfo)
1022 : mode(mode),
1023 aead(aead),
1024 ctx(kj::mv(ctx)),
1025 key(kj::mv(key)),
1026 iv(kj::mv(iv)),
1027 maybeAuthInfo(kj::mv(maybeAuthInfo)) {}
1028 
1029jsg::Ref<CryptoImpl::AeadHandle> CryptoImpl::AeadHandle::construct(jsg::Lock& js,
1030 CipherMode mode,
1031 kj::StringPtr algorithm,
1032 ncrypto::Aead aead,
1033 jsg::Ref<CryptoKey> key,
1034 jsg::JsBufferSource iv,
1035 jsg::Optional<uint32_t> maybeAuthTagLength) {
1036 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
1037 
1038 JSG_REQUIRE(key->getType() == "secret"_kj, TypeError, "Invalid key type for cipher");
1039 
1040 auto keyData =
1041 JSG_REQUIRE_NONNULL(tryGetSecretKeyData(key), Error, "Failed to get raw secret key data");
1042 
1043 int expectedIvLength = aead.getNonceLength();
1044 JSG_REQUIRE(iv.size() == expectedIvLength, Error, "Invalid initialization vector");
1045 
1046 if (aead.getName() == "chacha20-poly1305"sv) {
1047 JSG_REQUIRE(iv.size(), Error, "ChaCha20-Poly1305 requires an initialization vector");
1048 JSG_REQUIRE(iv.size() <= 12, Error, "Invalid initialization vector");
1049 }
1050 
1051 bool encrypt = mode == CipherMode::CIPHER;
1052 
1053 // Note: kNoAuthTagLength is -1, and is used within the implementation of the node:crypto API,
1054 // while EVP_AEAD_DEFAULT_TAG_LENGTH is 0 and is used when communicating with BoringSSL
1055 
1056 auto ctx = ncrypto::AeadCtxPointer::New(aead, encrypt, keyData.begin(), keyData.size(),
1057 maybeAuthTagLength.orDefault(EVP_AEAD_DEFAULT_TAG_LENGTH));
1058 JSG_REQUIRE(ctx, Error, "Failed to initialize AEAD cipher/decipher context");
1059 
1060 kj::Maybe<CryptoImpl::AeadHandle::AuthenticatedInfo> maybeAuthInfo = kj::none;
1061 maybeAuthInfo = initAuthenticated(
1062 aead, ctx, encrypt, algorithm, iv.size(), maybeAuthTagLength.orDefault(kNoAuthTagLength));
1063 
1064 // Copy the IV into C++-owned memory so that later modifications to the JS buffer
1065 // cannot affect the cipher. The EVP_AEAD API requires the IV at encrypt/decrypt time
1066 // (not init time), so we store it. This matches Node.js behavior where the IV is
1067 // consumed at creation time.
1068 auto ivCopy = kj::heapArray<kj::byte>(iv.asArrayPtr());
1069 return js.alloc<AeadHandle>(
1070 mode, aead, kj::mv(ctx), kj::mv(key), kj::mv(ivCopy), kj::mv(maybeAuthInfo));
1071}
1072 
1073jsg::JsUint8Array CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::JsBufferSource data) {
1074 JSG_REQUIRE(!updated, Error, "update() can only be invoked once on an AEAD");
1075 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
1076 JSG_REQUIRE(data.size() <= INT_MAX, Error, "Data too large");
1077 
1078 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
1079 
1080 const int aeadMode = aead.getMode();
1081 
1082 if (aeadMode == EVP_CIPH_CCM_MODE) {
1083 auto max = KJ_ASSERT_NONNULL(maybeAuthInfo).max_message_size;
1084 JSG_REQUIRE(data.size() <= max, Error, "Invalid message length");
1085 }
1086 
1087 const int block_size = aead.getBlockSize();
1088 KJ_ASSERT(block_size > 0);
1089 JSG_REQUIRE(data.size() + block_size <= INT_MAX, Error, "Data too large");
1090 
1091 ncrypto::Buffer<const unsigned char> buffer = {
1092 .data = data.asArrayPtr().begin(),
1093 .len = data.size(),
1094 };
1095 
1096 auto buf = jsg::JsUint8Array::create(js, data.size());
1097 
1098 ncrypto::Buffer<unsigned char> outBuf = {.data = buf.asArrayPtr().begin(), .len = data.size()};
1099 ncrypto::Buffer<const unsigned char> ivBuf = {.data = iv.begin(), .len = iv.size()};
1100 ncrypto::Buffer<const unsigned char> aadBuf;
1101 
1102 KJ_IF_SOME(aadRef, maybeAad) {
1103 aadBuf = {.data = aadRef.begin(), .len = aadRef.size()};
1104 }
1105 
1106 bool r;
1107 if (mode == CipherMode::CIPHER) {
1108 auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, "Missing required auth info");
1109 // In GCM mode, the authentication tag length can be specified in advance,
1110 // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
1111 // always be given by the user.
1112 
1113 if (info.auth_tag_len == kNoAuthTagLength) {
1114 info.auth_tag_len = 16;
1115 }
1116 
1117 auto tag = kj::heapArray<kj::byte>(info.auth_tag_len);
1118 
1119 ncrypto::Buffer<unsigned char> tagBuf = {.data = tag.begin(), .len = info.auth_tag_len};
1120 
1121 r = ctx.encrypt(buffer, outBuf, tagBuf, ivBuf, aadBuf);
1122 maybeAuthTag = kj::mv(tag);
1123 } else {
1124 auto& tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided");
1125 
1126 ncrypto::Buffer<const unsigned char> tagBuf = {.data = tag.begin(), .len = tag.size()};
1127 
1128 r = ctx.decrypt(buffer, outBuf, tagBuf, ivBuf, aadBuf);
1129 ERR_print_errors_fp(stderr);
1130 }
1131 
1132 JSG_REQUIRE(r, Error, "Authentication failed");
1133 // EVP_AEAD operations always return an output of the same size as the input
1134 KJ_REQUIRE(outBuf.len == buf.size(), "Invalid output length for AEAD operation");
1135 updated = true;
1136 return buf;
1137}
1138 
1139jsg::JsUint8Array CryptoImpl::AeadHandle::final(jsg::Lock& js) {
1140 // There is no finalization operation in the EVP_AEAD API.
1141 // Just return an empty value and clean up.
1142 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
1143 
1144 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
1145 ctx.reset();
1146 return jsg::JsUint8Array::create(js, 0);
1147}
1148 
1149void CryptoImpl::AeadHandle::setAAD(
1150 jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional<uint32_t> maybePlaintextLength) {
1151 // In EVP_AEAD, the AAD is handled at the same time as the update.
1152 // Just save the value until update() is called.
1153 
1154 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
1155 
1156 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
1157 
1158 const int aeadMode = aead.getMode();
1159 
1160 // When in CCM mode, we need to set the authentication tag and the plaintext
1161 // length in advance.
1162 if (aeadMode == EVP_CIPH_CCM_MODE) {
1163 auto plaintextLength = JSG_REQUIRE_NONNULL(
1164 maybePlaintextLength, Error, "options.plaintextLength is required for CCM mode with AAD");
1165 
1166 auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, "Required auth info is not available");
1167 
1168 JSG_REQUIRE(plaintextLength <= info.max_message_size, Error, "Data too large");
1169 
1170 if (mode == CipherMode::DECIPHER) {
1171 JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided");
1172 }
1173 }
1174 
1175 // Copy the AAD data so that later modifications to the JS buffer cannot affect the cipher.
1176 maybeAad = kj::heapArray<kj::byte>(aad.asArrayPtr());
1177}
1178 
1179void CryptoImpl::AeadHandle::setAutoPadding(jsg::Lock&, bool) {
1180 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
1181 JSG_FAIL_REQUIRE(Error, "Setting autopadding is not supported on AEADs");
1182}
1183 
1184void CryptoImpl::AeadHandle::setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag) {
1185 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
1186 JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized");
1187 JSG_REQUIRE(
1188 mode == CipherMode::DECIPHER, Error, "Setting auth tag only support in decipher mode");
1189 JSG_REQUIRE(maybeAuthTag == kj::none, Error, "Auth tag is already set");
1190 JSG_REQUIRE(authTag.size() <= INT_MAX, Error, "Auth tag is too big");
1191 
1192 int aeadMode = aead.getMode();
1193 bool is_valid = false;
1194 
1195 auto& info = JSG_REQUIRE_NONNULL(maybeAuthInfo, Error, "Required auth info is not available");
1196 
1197 if (aeadMode == EVP_CIPH_GCM_MODE) {
1198 // Restrict GCM tag lengths according to NIST 800-38d, page 9.
1199 is_valid = (info.auth_tag_len == kNoAuthTagLength || info.auth_tag_len == authTag.size()) &&
1200 ncrypto::Cipher::IsValidGCMTagLength(authTag.size());
1201 } else {
1202 is_valid = info.auth_tag_len == authTag.size();
1203 }
1204 
1205 JSG_REQUIRE(is_valid, Error, "Invalid authentication tag length");
1206 
1207 info.auth_tag_len = authTag.size();
1208 
1209 // Copy the auth tag so that later modifications to the JS buffer cannot affect the cipher.
1210 maybeAuthTag = kj::heapArray<kj::byte>(authTag.asArrayPtr());
1211}
1212 
1213jsg::JsUint8Array CryptoImpl::AeadHandle::getAuthTag(jsg::Lock& js) {
1214 JSG_REQUIRE(!ctx, Error, "Auth tag is only available once cipher context has been finalized");
1215 JSG_REQUIRE(mode == CipherMode::CIPHER, Error, "Getting the auth tag is only support for cipher");
1216 
1217 KJ_IF_SOME(ref, maybeAuthTag) {
1218 auto result = jsg::JsUint8Array::create(js, ref.asPtr());
1219 maybeAuthTag = kj::none;
1220 return result;
1221 }
1222 
1223 return jsg::JsUint8Array::create(js, 0);
1224}
1225 
1226kj::OneOf<jsg::Ref<CryptoImpl::CipherHandle>, jsg::Ref<CryptoImpl::AeadHandle>> CryptoImpl::
1227 newHandle(jsg::Lock& js,
1228 kj::uint mode,
1229 kj::String algorithm,
1230 jsg::Ref<CryptoKey> key,
1231 jsg::JsBufferSource iv,
1232 jsg::Optional<uint32_t> maybeAuthTagLength) {
1233 CipherMode cipherMode = static_cast<CipherMode>(mode);
1234 
1235 if (auto cipher = ncrypto::Cipher::FromName(algorithm.cStr())) {
1236 return CipherHandle::construct(
1237 js, cipherMode, algorithm, cipher, kj::mv(key), kj::mv(iv), kj::mv(maybeAuthTagLength));
1238 } else if (auto aead =
1239 ncrypto::Aead::FromName(std::string_view(algorithm.begin(), algorithm.size()))) {
1240 return AeadHandle::construct(
1241 js, cipherMode, algorithm, aead, kj::mv(key), kj::mv(iv), kj::mv(maybeAuthTagLength));
1242 }
1243 
1244 JSG_FAIL_REQUIRE(Error, kj::str("Unknown or unsupported cipher: ", algorithm));
1245}
1246 
1247namespace {
1248 
1249// TODO(soon): For some reason the ncrypto implementation of these is not
1250// working for us but they do work in Node.js. Will need to figure out why.
1251// For now, it's easy enough to implement ourselves here.
1252using EVP_PKEY_cipher_t = int(
1253 EVP_PKEY_CTX* ctx, unsigned char* out, size_t* outlen, const unsigned char* in, size_t inlen);
1254 
1255template <EVP_PKEY_cipher_t cipher>
1256jsg::JsUint8Array Cipher(jsg::Lock& js,
1257 ncrypto::EVPKeyCtxPointer&& ctx,
1258 jsg::JsBufferSource& buffer,
1259 const CryptoImpl::PublicPrivateCipherOptions& options) {
1260 
1261 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
1262 
1263 const EVP_MD* digest = nullptr;
1264 if (options.oaepHash.size() > 0) {
1265 digest = ncrypto::getDigestByName(options.oaepHash.cStr());
1266 JSG_REQUIRE(digest != nullptr, Error, "Unsupported hash digest");
1267 }
1268 
1269 JSG_REQUIRE(
1270 EVP_PKEY_CTX_set_rsa_padding(ctx.get(), options.padding), Error, "Failed to set the padding");
1271 
1272 if (digest != nullptr && options.padding == RSA_PKCS1_OAEP_PADDING) {
1273 JSG_REQUIRE(
1274 EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), digest) == 1, Error, "Failed to set the digest");
1275 JSG_REQUIRE(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), digest) == 1, Error,
1276 "Failed to set the mgf1 digest");
1277 }
1278 
1279 KJ_IF_SOME(labelRef, options.oaepLabel) {
1280 auto label = labelRef.getHandle(js);
1281 // The ctx takes ownership of the data buffer so we have to copy.
1282 auto data = ncrypto::DataPointer::Alloc(label.size());
1283 kj::ArrayPtr<kj::byte> dataPtr(data.get<kj::byte>(), data.size());
1284 dataPtr.copyFrom(label.asArrayPtr());
1285 auto released = data.release();
1286 JSG_REQUIRE(EVP_PKEY_CTX_set0_rsa_oaep_label(
1287 ctx.get(), static_cast<uint8_t*>(released.data), released.len) == 1,
1288 Error, "Failed to set the OAEP label");
1289 }
1290 
1291 size_t len;
1292 JSG_REQUIRE(cipher(ctx.get(), nullptr, &len, buffer.asArrayPtr().begin(), buffer.size()) == 1,
1293 Error, "Failed to determine output size");
1294 
1295 if (len == 0) {
1296 return jsg::JsUint8Array::create(js, 0);
1297 }
1298 
1299 auto buf = jsg::JsUint8Array::create(js, len);
1300 JSG_REQUIRE(cipher(ctx.get(), buf.asArrayPtr().begin(), &len, buffer.asArrayPtr().begin(),
1301 buffer.size()) == 1,
1302 Error, "Failed to cipher/decipher");
1303 
1304 if (len < buf.size()) {
1305 auto newBuf = jsg::JsUint8Array::create(js, len);
1306 newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(len));
1307 buf = kj::mv(newBuf);
1308 }
1309 
1310 return buf;
1311}
1312} // namespace
1313 
1314jsg::JsUint8Array CryptoImpl::publicEncrypt(jsg::Lock& js,
1315 jsg::Ref<CryptoKey> key,
1316 jsg::JsBufferSource buffer,
1317 CryptoImpl::PublicPrivateCipherOptions options) {
1318 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided");
1319 JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys");
1320 auto ctx = pkey.newCtx();
1321 JSG_REQUIRE(ctx.initForEncrypt(), Error, "Failed to init for encryption");
1322 return Cipher<EVP_PKEY_encrypt>(js, kj::mv(ctx), buffer, options);
1323}
1324 
1325jsg::JsUint8Array CryptoImpl::privateDecrypt(jsg::Lock& js,
1326 jsg::Ref<CryptoKey> key,
1327 jsg::JsBufferSource buffer,
1328 CryptoImpl::PublicPrivateCipherOptions options) {
1329 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided");
1330 JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys");
1331 auto ctx = pkey.newCtx();
1332 JSG_REQUIRE(ctx.initForDecrypt(), Error, "Failed to init for decryption");
1333 return Cipher<EVP_PKEY_decrypt>(js, kj::mv(ctx), buffer, options);
1334}
1335 
1336jsg::JsUint8Array CryptoImpl::publicDecrypt(jsg::Lock& js,
1337 jsg::Ref<CryptoKey> key,
1338 jsg::JsBufferSource buffer,
1339 CryptoImpl::PublicPrivateCipherOptions options) {
1340 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided");
1341 JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys");
1342 auto ctx = pkey.newCtx();
1343 JSG_REQUIRE(EVP_PKEY_verify_recover_init(ctx.get()) == 1, Error, "Failed to init for decryption");
1344 return Cipher<EVP_PKEY_verify_recover>(js, kj::mv(ctx), buffer,
1345 {
1346 .padding = options.padding,
1347 .oaepHash = kj::String(),
1348 });
1349}
1350jsg::JsUint8Array CryptoImpl::privateEncrypt(jsg::Lock& js,
1351 jsg::Ref<CryptoKey> key,
1352 jsg::JsBufferSource buffer,
1353 CryptoImpl::PublicPrivateCipherOptions options) {
1354 auto pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided");
1355 JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys");
1356 auto ctx = pkey.newCtx();
1357 JSG_REQUIRE(EVP_PKEY_sign_init(ctx.get()) == 1, Error, "Failed to init for encryption");
1358 return Cipher<EVP_PKEY_sign>(js, kj::mv(ctx), buffer,
1359 {
1360 .padding = options.padding,
1361 .oaepHash = kj::String(),
1362 });
1363}
1364 
1365namespace {
1366ncrypto::Cipher getCipher(kj::OneOf<kj::String, int>& nameOrNid) {
1367 KJ_SWITCH_ONEOF(nameOrNid) {
1368 KJ_CASE_ONEOF(nid, int) {
1369 return ncrypto::Cipher::FromNid(nid);
1370 }
1371 KJ_CASE_ONEOF(name, kj::String) {
1372 return ncrypto::Cipher::FromName(name.cStr());
1373 }
1374 }
1375 return {};
1376}
1377} // namespace
1378 
1379jsg::Optional<CryptoImpl::CipherInfo> CryptoImpl::getCipherInfo(
1380 kj::OneOf<kj::String, int> nameOrNid, CryptoImpl::GetCipherInfoOptions options) {
1381 
1382 if (auto cipher = getCipher(nameOrNid)) {
1383 
1384 int keyLength = cipher.getKeyLength();
1385 int ivLength = cipher.getIvLength();
1386 
1387 if (options.ivLength != kj::none || options.keyLength != kj::none) {
1388 auto ctx = ncrypto::CipherCtxPointer::New();
1389 if (!ctx.init(cipher, true)) return kj::none;
1390 KJ_IF_SOME(len, options.keyLength) {
1391 if (!ctx.setKeyLength(len)) return kj::none;
1392 keyLength = len;
1393 }
1394 KJ_IF_SOME(len, options.ivLength) {
1395 // For CCM modes, the IV may be between 7 and 13 bytes.
1396 // For GCM and OCB modes, we'll check by attempting to
1397 // set the value. For everything else, just check that
1398 // check_len == iv_length.
1399 switch (cipher.getMode()) {
1400 case EVP_CIPH_CCM_MODE: {
1401 if (len < 7 || len > 13) return kj::none;
1402 break;
1403 }
1404 case EVP_CIPH_GCM_MODE: {
1405 if (!ctx.setIvLength(len)) return kj::none;
1406 break;
1407 }
1408 case EVP_CIPH_OCB_MODE: {
1409 if (!ctx.setIvLength(len)) return kj::none;
1410 break;
1411 }
1412 default:
1413 if (len != ivLength) return kj::none;
1414 break;
1415 }
1416 ivLength = len;
1417 }
1418 }
1419 
1420 auto nameCstr = cipher.getName();
1421 auto modeView = cipher.getModeLabel();
1422 kj::String name = kj::heapString(nameCstr);
1423 kj::String mode = kj::str(kj::heapArray<char>(modeView.data(), modeView.size()));
1424 
1425 return CipherInfo{
1426 .name = kj::mv(name),
1427 .nid = cipher.getNid(),
1428 .blockSize = cipher.getBlockSize(),
1429 .ivLength = ivLength,
1430 .keyLength = keyLength,
1431 .mode = kj::mv(mode),
1432 };
1433 }
1434 
1435 // If the cipher can't be found it might be an AEAD, which is handled using a different BoringSSL
1436 // interface
1437 KJ_SWITCH_ONEOF(nameOrNid) {
1438 KJ_CASE_ONEOF(nid, int) {
1439 // Can't safely find an AEAD by nid in boringssl
1440 return kj::none;
1441 }
1442 
1443 KJ_CASE_ONEOF(name, kj::String) {
1444 if (auto aead = ncrypto::Aead::FromName(std::string_view(name.begin(), name.size()))) {
1445 auto modeView = aead.getModeLabel();
1446 // The copy is strictly necessary
1447 kj::String mode = kj::str(kj::heapArray<char>(modeView.data(), modeView.size()));
1448 
1449 return CipherInfo{.name = kj::mv(name),
1450 .nid = aead.getNid(),
1451 .blockSize = aead.getBlockSize(),
1452 .ivLength = aead.getNonceLength(),
1453 .keyLength = aead.getKeyLength(),
1454 .mode = kj::mv(mode)};
1455 }
1456 }
1457 }
1458 
1459 return kj::none;
1460}
1461 
1462kj::ArrayPtr<kj::StringPtr> CryptoImpl::getCiphers() {
1463 // Cipher names are stored as string literals either within boringssl or ncrypto, so we can
1464 // safely return pointers to them.
1465 static kj::Array<kj::StringPtr> allCiphers = []() {
1466 kj::Vector<kj::StringPtr> allCiphers;
1467 ncrypto::Cipher::ForEach([&](const auto& name) { allCiphers.add(kj::StringPtr(name)); });
1468 
1469 ncrypto::Aead::ForEach(
1470 [&](const auto& name) { allCiphers.add(kj::StringPtr(name.data(), name.size())); });
1471 return allCiphers.releaseAsArray();
1472 }();
1473 
1474 return allCiphers;
1475}
1476 
1477#pragma endregion // Cipher/Decipher
1478 
1479// =============================================================================
1480#pragma region ECDH
1481 
1482namespace {
1483ncrypto::ECPointPointer bufferToPoint(const EC_GROUP* group, jsg::JsBufferSource& buf) {
1484 JSG_REQUIRE(buf.size() <= INT32_MAX, Error, "buffer is too big");
1485 
1486 auto pub = ncrypto::ECPointPointer::New(group);
1487 JSG_REQUIRE(pub, Error, "Failed to allocate EC_POINT for a public key");
1488 
1489 ncrypto::Buffer<const unsigned char> buffer{
1490 .data = buf.asArrayPtr().begin(),
1491 .len = buf.size(),
1492 };
1493 
1494 JSG_REQUIRE(pub.setFromBuffer(buffer, group), Error, "Failed to set point");
1495 return pub;
1496}
1497 
1498point_conversion_form_t getFormat(kj::StringPtr format) {
1499 if (format == "compressed"_kj) return POINT_CONVERSION_COMPRESSED;
1500 if (format == "uncompressed"_kj) return POINT_CONVERSION_UNCOMPRESSED;
1501 if (format == "hybrid"_kj) return POINT_CONVERSION_HYBRID;
1502 JSG_FAIL_REQUIRE(Error, "Invalid ECDH public key format");
1503}
1504 
1505jsg::JsUint8Array ecPointToBuffer(
1506 jsg::Lock& js, const EC_GROUP* group, const EC_POINT* point, point_conversion_form_t form) {
1507 size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr);
1508 JSG_REQUIRE(len != 0, Error, "Failed to get public key length");
1509 
1510 auto buf = jsg::JsUint8Array::create(js, len);
1511 
1512 len = EC_POINT_point2oct(group, point, form, buf.asArrayPtr().begin(), buf.size(), nullptr);
1513 JSG_REQUIRE(len != 0, Error, "Failed to get public key");
1514 
1515 return buf;
1516}
1517 
1518bool isKeyValidForCurve(const EC_GROUP* group, const ncrypto::BignumPointer& private_key) {
1519 // Private keys must be in the range [1, n-1].
1520 // Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf
1521 if (private_key < ncrypto::BignumPointer::One()) {
1522 return false;
1523 }
1524 auto order = ncrypto::BignumPointer::New();
1525 JSG_REQUIRE(order, Error, "Internal failure when checking ECDH key");
1526 return EC_GROUP_get_order(group, order.get(), nullptr) && private_key < order;
1527}
1528} // namespace
1529 
1530CryptoImpl::ECDHHandle::ECDHHandle(ncrypto::ECKeyPointer key)
1531 : key_(kj::mv(key)),
1532 group_(key_.getGroup()) {}
1533 
1534jsg::Ref<CryptoImpl::ECDHHandle> CryptoImpl::ECDHHandle::constructor(
1535 jsg::Lock& js, kj::String curveName) {
1536 
1537 int nid = OBJ_sn2nid(curveName.begin());
1538 JSG_REQUIRE(nid != NID_undef, Error, "Invalid curve");
1539 
1540 auto key = ncrypto::ECKeyPointer::NewByCurveName(nid);
1541 JSG_REQUIRE(key, Error, "Failed to create key using named curve");
1542 
1543 return js.alloc<CryptoImpl::ECDHHandle>(kj::mv(key));
1544}
1545 
1546jsg::JsUint8Array CryptoImpl::ECDHHandle::computeSecret(
1547 jsg::Lock& js, jsg::JsBufferSource otherPublicKey) {
1548 
1549 ncrypto::ClearErrorOnReturn clear_error_on_return;
1550 
1551 JSG_REQUIRE(key_.checkKey(), Error, "Invalid keypair");
1552 
1553 auto pub = bufferToPoint(group_, otherPublicKey);
1554 JSG_REQUIRE(pub, Error, "Invalid to set ECDH public key");
1555 
1556 int field_size = EC_GROUP_get_degree(group_);
1557 size_t out_len = (field_size + 7) / 8;
1558 
1559 auto buf = jsg::JsUint8Array::create(js, out_len);
1560 
1561 JSG_REQUIRE(ECDH_compute_key(buf.asArrayPtr().begin(), out_len, pub, key_.get(), nullptr), Error,
1562 "Failed to compute ECDH key");
1563 
1564 return buf;
1565}
1566 
1567void CryptoImpl::ECDHHandle::generateKeys() {
1568 ncrypto::ClearErrorOnReturn clear_error_on_return;
1569 JSG_REQUIRE(key_.generate(), Error, "Failed to generate keys");
1570}
1571 
1572jsg::JsUint8Array CryptoImpl::ECDHHandle::getPrivateKey(jsg::Lock& js) {
1573 auto b = key_.getPrivateKey();
1574 JSG_REQUIRE(b != nullptr, Error, "Failed to get ECDH private key");
1575 auto buf = jsg::JsUint8Array::create(js, ncrypto::BignumPointer::GetByteCount(b));
1576 JSG_REQUIRE(buf.size() ==
1577 ncrypto::BignumPointer::EncodePaddedInto(b, buf.asArrayPtr().begin(), buf.size()),
1578 Error, "Failed to encode the private key");
1579 return buf;
1580}
1581 
1582jsg::JsUint8Array CryptoImpl::ECDHHandle::getPublicKey(jsg::Lock& js, kj::String format) {
1583 const auto group = key_.getGroup();
1584 const auto pub = key_.getPublicKey();
1585 JSG_REQUIRE(pub != nullptr, Error, "Failed to get ECDH public key");
1586 point_conversion_form_t form = getFormat(format);
1587 return ecPointToBuffer(js, group, pub, form);
1588}
1589 
1590void CryptoImpl::ECDHHandle::setPrivateKey(jsg::Lock& js, jsg::JsBufferSource key) {
1591 
1592 JSG_REQUIRE(key.size() <= INT32_MAX, Error, "key is too big");
1593 
1594 ncrypto::BignumPointer priv(key.asArrayPtr().begin(), key.size());
1595 JSG_REQUIRE(priv, Error, "Failed to convert buffer to BN");
1596 
1597 JSG_REQUIRE(
1598 isKeyValidForCurve(group_, priv), Error, "Private key is not valid for specified curve.");
1599 
1600 auto new_key = key_.clone();
1601 JSG_REQUIRE(new_key, Error, "Internal error when setting private key");
1602 
1603 bool result = new_key.setPrivateKey(priv);
1604 priv.reset();
1605 
1606 JSG_REQUIRE(result, Error, "Failed to convert BN to a private key");
1607 
1608 ncrypto::ClearErrorOnReturn clear_error_on_return;
1609 
1610 auto priv_key = new_key.getPrivateKey();
1611 JSG_REQUIRE(priv_key, Error, "Failed to get ECDH private key");
1612 
1613 auto pub = ncrypto::ECPointPointer::New(group_);
1614 JSG_REQUIRE(pub, Error, "Internal error when initializing new EC point");
1615 
1616 JSG_REQUIRE(pub.mul(group_, priv_key), Error, "Failed to generate ECDH public key");
1617 
1618 JSG_REQUIRE(new_key.setPublicKey(pub), Error, "Failed to set generated public key");
1619 
1620 key_ = std::move(new_key);
1621 group_ = key_.getGroup();
1622}
1623 
1624jsg::JsUint8Array CryptoImpl::ECDHHandle::convertKey(
1625 jsg::Lock& js, jsg::JsBufferSource key, kj::String curveName, kj::String format) {
1626 ncrypto::ClearErrorOnReturn clear_error_on_return;
1627 
1628 JSG_REQUIRE(key.size() <= INT32_MAX, Error, "key is too big");
1629 if (key.size() == 0) {
1630 return jsg::JsUint8Array::create(js, 0);
1631 }
1632 
1633 int nid = OBJ_sn2nid(curveName.begin());
1634 JSG_REQUIRE(nid != NID_undef, Error, "Invalid curve");
1635 
1636 auto group = ncrypto::ECGroupPointer::NewByCurveName(nid);
1637 
1638 auto pub = bufferToPoint(group, key);
1639 JSG_REQUIRE(pub, Error, "Failed to convert buffer to EC_POINT");
1640 
1641 point_conversion_form_t form = getFormat(format);
1642 
1643 return ecPointToBuffer(js, group, pub, form);
1644}
1645 
1646#pragma endregion // ECDH
1647 
1648} // namespace workerd::api::node