Skip to content
File

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

33.2 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#include "util.h"
6 
7#include <workerd/api/crypto/impl.h>
8#include <workerd/api/crypto/jwk.h>
9#include <workerd/api/crypto/keys.h>
10 
11#include <ncrypto.h>
12#include <openssl/crypto.h>
13 
14#include <map>
15 
16// TODO(soon): This implements most of node:crypto key import, export, and
17// generation with a number of notable exceptions.
18//
19// 1. While it is possible to import DSA keys, it is currently not possible
20// to generate a new DSA key pair. This is due entirely to limitations
21// currently in boringssl+fips that we use in production.
22// 2. It is currently not possible to generate or import diffie-hellman
23// keys or use the stateless diffie-hellman API. The older DH apis are
24// still functional, but the stateless DH and DH keys currently rely on
25// the EVP DH APIs that are not implementing by boringssl+fips. An
26// alternative approach is possible but requires a bit more effort.
27// 3.
28namespace workerd::api::node {
29 
30namespace {
31// An algorithm-independent secret key. Used as the underlying
32// implementation of Node.js SecretKey objects. Unlike Web Crypto,
33// a Node.js secret key is not algorithm specific. For instance, a
34// single secret key can be used for both AES and HMAC, where as
35// Web Crypto requires a separate key for each algorithm.
36class SecretKey final: public CryptoKey::Impl {
37 public:
38 explicit SecretKey(kj::Array<kj::byte> keyData)
39 : Impl(true, CryptoKeyUsageSet::privateKeyMask() | CryptoKeyUsageSet::publicKeyMask()),
40 keyData(kj::mv(keyData)) {}
41 ~SecretKey() noexcept(false) {
42 OPENSSL_cleanse(keyData.begin(), keyData.size());
43 }
44 
45 kj::StringPtr getAlgorithmName() const override {
46 return "secret"_kj;
47 }
48 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
49 return CryptoKey::ArbitraryKeyAlgorithm{
50 .name = getAlgorithmName(),
51 .length = keyData.size(),
52 };
53 }
54 
55 bool equals(const CryptoKey::Impl& other) const override final {
56 if (this == &other) return true;
57 if (other.getType() != "secret"_kj) return false;
58 KJ_IF_SOME(o, kj::dynamicDowncastIfAvailable<const SecretKey>(other)) {
59 return equalsImpl(o.rawKeyData());
60 }
61 return false;
62 }
63 
64 bool equalsImpl(kj::ArrayPtr<const kj::byte> other) const {
65 return keyData.size() == other.size() &&
66 CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;
67 }
68 
69 bool equals(const kj::Array<kj::byte>& other) const override final {
70 return equalsImpl(other.asPtr());
71 }
72 
73 SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final {
74 JSG_REQUIRE(format == "raw" || format == "jwk", DOMNotSupportedError, getAlgorithmName(),
75 " key only supports exporting \"raw\" & \"jwk\", not \"", format, "\".");
76 
77 if (format == "jwk") {
78 SubtleCrypto::JsonWebKey jwk;
79 jwk.kty = kj::str("oct");
80 jwk.k = fastEncodeBase64Url(keyData.asPtr());
81 jwk.ext = true;
82 return jwk;
83 }
84 
85 return jsg::JsArrayBuffer::create(js, keyData.asPtr()).addRef(js);
86 }
87 
88 kj::StringPtr jsgGetMemoryName() const override {
89 return "SecretKey";
90 }
91 size_t jsgGetMemorySelfSize() const override {
92 return sizeof(SecretKey);
93 }
94 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {
95 tracker.trackFieldWithSize("keyData", keyData.size());
96 }
97 
98 void visitForGc(jsg::GcVisitor& visitor) override {}
99 
100 const kj::ArrayPtr<const kj::byte> rawKeyData() const {
101 return keyData.asPtr();
102 }
103 
104 private:
105 kj::Array<kj::byte> keyData;
106};
107 
108CryptoKey::AsymmetricKeyDetails getRsaKeyDetails(jsg::Lock& js, const ncrypto::EVPKeyPointer& key) {
109 ncrypto::Rsa rsa = key;
110 
111 // BoringSSL does not currently support the id-RSASSA-PSS key encoding and
112 // does not support getting the PSS param details using RSA_get0_pss_params.
113 // Therefore there's nothing else to do here currently.
114 // TODO(later): If/When BoringSSL supports getting the pss params, we will
115 // need to update this.
116 KJ_ASSERT(!rsa.getPssParams().has_value());
117 
118 auto pubExp = JSG_REQUIRE_NONNULL(
119 bignumToArrayPadded(js, *rsa.getPublicKey().e), Error, "Failed to extract public exponent");
120 auto ab = jsg::JsArrayBuffer::create(js, pubExp.asArrayPtr());
121 return CryptoKey::AsymmetricKeyDetails{
122 .modulusLength = key.bits(),
123 .publicExponent = ab.addRef(js),
124 };
125}
126 
127CryptoKey::AsymmetricKeyDetails getDsaKeyDetails(const ncrypto::EVPKeyPointer& key) {
128 ncrypto::Dsa dsa = key;
129 
130 return CryptoKey::AsymmetricKeyDetails{
131 .modulusLength = static_cast<uint32_t>(dsa.getModulusLength()),
132 .divisorLength = static_cast<uint32_t>(dsa.getDivisorLength()),
133 };
134}
135 
136CryptoKey::AsymmetricKeyDetails getEcKeyDetails(const ncrypto::EVPKeyPointer& key) {
137 ncrypto::Ec ec = key;
138 
139 return CryptoKey::AsymmetricKeyDetails{
140 .namedCurve = kj::str(OBJ_nid2sn(EC_GROUP_get_curve_name(ec.getGroup()))),
141 };
142}
143 
144kj::Maybe<ncrypto::EVPKeyPointer::PKFormatType> trySelectKeyFormat(kj::StringPtr format) {
145 if (format == "pem"_kj) return ncrypto::EVPKeyPointer::PKFormatType::PEM;
146 if (format == "der"_kj) return ncrypto::EVPKeyPointer::PKFormatType::DER;
147 if (format == "jwk"_kj) return ncrypto::EVPKeyPointer::PKFormatType::JWK;
148 return kj::none;
149}
150 
151kj::Maybe<ncrypto::EVPKeyPointer::PKEncodingType> trySelectKeyEncoding(kj::StringPtr enc) {
152 if (enc == "pkcs1"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::PKCS1;
153 if (enc == "pkcs8"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::PKCS8;
154 if (enc == "sec1"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::SEC1;
155 if (enc == "spki"_kj) return ncrypto::EVPKeyPointer::PKEncodingType::SPKI;
156 return kj::none;
157}
158 
159class AsymmetricKey final: public CryptoKey::Impl {
160 public:
161 static kj::Own<AsymmetricKey> NewPrivate(ncrypto::EVPKeyPointer&& key) {
162 return kj::heap<AsymmetricKey>(kj::mv(key), true);
163 }
164 
165 static kj::Own<AsymmetricKey> NewPublic(ncrypto::EVPKeyPointer&& key) {
166 return kj::heap<AsymmetricKey>(kj::mv(key), false);
167 }
168 
169 AsymmetricKey(ncrypto::EVPKeyPointer&& key, bool isPrivate)
170 : CryptoKey::Impl(true, CryptoKeyUsageSet::privateKeyMask()),
171 key(kj::mv(key)),
172 isPrivate(isPrivate) {}
173 
174 kj::StringPtr getAlgorithmName() const override {
175 if (!key) return nullptr;
176 switch (key.id()) {
177 case EVP_PKEY_RSA:
178 return "rsa"_kj;
179 case EVP_PKEY_RSA2:
180 return "rsa"_kj;
181 case EVP_PKEY_RSA_PSS:
182 return "rsa"_kj;
183 case EVP_PKEY_EC:
184 return "ec"_kj;
185 case EVP_PKEY_ED25519:
186 return "ed25519"_kj;
187 case EVP_PKEY_ED448:
188 return "ed448"_kj;
189 case EVP_PKEY_X25519:
190 return "x25519"_kj;
191 case EVP_PKEY_DSA:
192 return "dsa"_kj;
193 case EVP_PKEY_DH:
194 return "dh"_kj;
195#ifndef NCRYPTO_NO_KDF_H
196 case EVP_PKEY_HKDF:
197 return "hkdf"_kj;
198#endif
199 default:
200 return nullptr;
201 }
202 KJ_UNREACHABLE;
203 }
204 
205 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
206 CryptoKey::ArbitraryKeyAlgorithm alg;
207 if (key) [[likely]] {
208 switch (key.id()) {
209 case EVP_PKEY_RSA:
210 alg.name = "RSASSA-PKCS1-v1_5"_kj;
211 break;
212 case EVP_PKEY_RSA2:
213 alg.name = "RSASSA-PKCS1-v1_5"_kj;
214 break;
215 case EVP_PKEY_RSA_PSS:
216 alg.name = "RSA-PSS"_kj;
217 break;
218 case EVP_PKEY_EC:
219 alg.name = "ECDSA"_kj;
220 break;
221 case EVP_PKEY_ED25519:
222 alg.name = "Ed25519"_kj;
223 break;
224 case EVP_PKEY_ED448:
225 alg.name = "Ed448"_kj;
226 break;
227 case EVP_PKEY_X25519:
228 alg.name = "X25519"_kj;
229 break;
230 case EVP_PKEY_DSA:
231 alg.name = "NODE-DSA"_kj;
232 break;
233 case EVP_PKEY_DH:
234 alg.name = "NODE-DH"_kj;
235 break;
236#ifndef NCRYPTO_NO_KDF_H
237 case EVP_PKEY_HKDF:
238 alg.name = "NODE-HKDF"_kj;
239 break;
240#endif
241 }
242 }
243 return alg;
244 }
245 
246 CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {
247 if (!key) [[unlikely]]
248 return {};
249 
250 if (key.isRsaVariant()) {
251 return getRsaKeyDetails(js, key);
252 }
253 
254 if (key.id() == EVP_PKEY_DSA) {
255 return getDsaKeyDetails(key);
256 }
257 
258 if (key.id() == EVP_PKEY_EC) {
259 return getEcKeyDetails(key);
260 }
261 
262 return {};
263 }
264 
265 jsg::JsUint8Array exportKeyExt(jsg::Lock& js,
266 kj::StringPtr format,
267 kj::StringPtr type,
268 jsg::Optional<kj::String> cipher = kj::none,
269 jsg::Optional<kj::Array<kj::byte>> passphrase = kj::none) const override {
270 if (!key) {
271 return jsg::JsUint8Array::create(js, 0);
272 }
273 
274 auto formatType = JSG_REQUIRE_NONNULL(trySelectKeyFormat(format), Error, "Invalid key format");
275 auto encType = JSG_REQUIRE_NONNULL(trySelectKeyEncoding(type), Error, "Invalid key encoding");
276 
277 if (!key.isRsaVariant()) {
278 JSG_REQUIRE(encType != ncrypto::EVPKeyPointer::PKEncodingType::PKCS1, Error,
279 "PKCS1 can only be used for RSA keys");
280 }
281 
282 if (encType == ncrypto::EVPKeyPointer::PKEncodingType::SEC1) {
283 JSG_REQUIRE(key.id() == EVP_PKEY_EC, Error, "SEC1 can only be used for EC keys");
284 }
285 
286 // This branch should never be taken for JWK
287 KJ_ASSERT(formatType != ncrypto::EVPKeyPointer::PKFormatType::JWK);
288 
289 auto maybeBio = ([&] {
290 if (isPrivate) {
291 ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config(false, formatType, encType);
292 
293 KJ_IF_SOME(ciph, cipher) {
294 config.cipher = ncrypto::getCipherByName(ciph.cStr());
295 JSG_REQUIRE(config.cipher != nullptr, Error, "Unknown cipher: ", ciph);
296 }
297 
298 KJ_IF_SOME(pass, passphrase) {
299 auto dp = ncrypto::DataPointer::Alloc(pass.size());
300 kj::ArrayPtr<kj::byte> ptr(dp.get<kj::byte>(), dp.size());
301 ptr.copyFrom(pass.asPtr());
302 config.passphrase = kj::mv(dp);
303 }
304 
305 return key.writePrivateKey(config);
306 }
307 return key.writePublicKey(
308 ncrypto::EVPKeyPointer::PublicKeyEncodingConfig(false, formatType, encType));
309 })();
310 if (maybeBio.has_value) {
311 BUF_MEM* mem = maybeBio.value;
312 kj::ArrayPtr<kj::byte> source(reinterpret_cast<kj::byte*>(mem->data), mem->length);
313 if (source.size() > 0) {
314 return jsg::JsUint8Array::create(js, source);
315 } else {
316 return jsg::JsUint8Array::create(js, 0);
317 }
318 }
319 
320 JSG_FAIL_REQUIRE(Error, "Failed to export key");
321 }
322 
323 SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final {
324 if (format == "jwk") {
325 auto res = toJwk(key, isPrivate ? KeyType::PRIVATE : KeyType::PUBLIC);
326 JSG_REQUIRE(res.kty != "INVALID"_kj, Error, "Key type is invalid for JWK export");
327 return kj::mv(res);
328 }
329 
330 // exportKeyExt returns JsUint8Array. We need to wrap it in a JsRef<JsArrayBuffer>
331 // since ExportKeyData is OneOf<JsRef<JsArrayBuffer>, JsonWebKey>.
332 return jsg::JsArrayBuffer::create(js, exportKeyExt(js, format, "pkcs8"_kj).asArrayPtr())
333 .addRef(js);
334 }
335 
336 bool equals(const CryptoKey::Impl& other) const override final {
337 KJ_IF_SOME(o, kj::dynamicDowncastIfAvailable<const AsymmetricKey>(other)) {
338 return EVP_PKEY_cmp(key.get(), o.key.get());
339 }
340 // TODO(later): Currently, this only compares keys using the ncrypto::EVPKeyPointer.
341 // If the "other" impl happens to be from the web crypto impl that does not use
342 // this AsymmetricKey impl then the comparison will be false. We can support both
343 // cases but for now, skip it.
344 return false;
345 }
346 
347 kj::StringPtr getType() const override {
348 return isPrivate ? "private"_kj : "public"_kj;
349 }
350 
351 kj::Own<AsymmetricKey> cloneAsPublicKey() {
352 if (!key) return kj::Own<AsymmetricKey>();
353 auto cloned = key.clone();
354 if (!cloned) return kj::Own<AsymmetricKey>();
355 return NewPublic(kj::mv(cloned));
356 }
357 
358 operator const ncrypto::EVPKeyPointer&() const {
359 return key;
360 }
361 
362 private:
363 ncrypto::EVPKeyPointer key;
364 bool isPrivate;
365};
366 
367int getCurveFromName(kj::StringPtr name) {
368 int nid = EC_curve_nist2nid(name.begin());
369 if (nid == NID_undef) nid = OBJ_sn2nid(name.begin());
370 return nid;
371}
372} // namespace
373 
374kj::OneOf<kj::String, jsg::JsArrayBuffer, SubtleCrypto::JsonWebKey> CryptoImpl::exportKey(
375 jsg::Lock& js, jsg::Ref<CryptoKey> key, jsg::Optional<KeyExportOptions> options) {
376 JSG_REQUIRE(key->getExtractable(), TypeError, "Unable to export non-extractable key");
377 auto& opts = JSG_REQUIRE_NONNULL(options, TypeError, "Options must be an object");
378 
379 kj::StringPtr format = JSG_REQUIRE_NONNULL(opts.format, TypeError, "Missing format option");
380 
381 auto convertExportKeyData = [&](SubtleCrypto::ExportKeyData&& exportData)
382 -> kj::OneOf<kj::String, jsg::JsArrayBuffer, SubtleCrypto::JsonWebKey> {
383 KJ_SWITCH_ONEOF(exportData) {
384 KJ_CASE_ONEOF(buf, jsg::JsRef<jsg::JsArrayBuffer>) {
385 return buf.getHandle(js);
386 }
387 KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {
388 return kj::mv(jwk);
389 }
390 }
391 KJ_UNREACHABLE;
392 };
393 
394 if (format == "jwk"_kj) {
395 // When format is jwk, all other options are ignored.
396 return convertExportKeyData(key->impl->exportKey(js, format));
397 }
398 
399 if (key->getType() == "secret"_kj) {
400 // For secret keys, we only pay attention to the format option, which will be
401 // one of either "buffer" or "jwk". The "buffer" option correlates to the "raw"
402 // format in Web Crypto. The "jwk" option is handled above.
403 JSG_REQUIRE(format == "buffer"_kj, TypeError, "Invalid format for secret key export: ", format);
404 return convertExportKeyData(key->impl->exportKey(js, "raw"_kj));
405 }
406 
407 kj::StringPtr type = JSG_REQUIRE_NONNULL(opts.type, TypeError, "Missing type option");
408 auto data =
409 key->impl->exportKeyExt(js, format, type, kj::mv(opts.cipher), kj::mv(opts.passphrase));
410 if (format == "pem"_kj) {
411 // TODO(perf): As a later performance optimization, change this so that it doesn't copy.
412 return kj::str(data.asArrayPtr().asChars());
413 }
414 // exportKeyExt returns JsUint8Array; copy to ArrayBuffer for the Node.js TS layer.
415 return jsg::JsArrayBuffer::create(js, data.asArrayPtr());
416}
417 
418bool CryptoImpl::equals(jsg::Lock& js, jsg::Ref<CryptoKey> key, jsg::Ref<CryptoKey> otherKey) {
419 return *key == *otherKey;
420}
421 
422CryptoKey::AsymmetricKeyDetails CryptoImpl::getAsymmetricKeyDetail(
423 jsg::Lock& js, jsg::Ref<CryptoKey> key) {
424 JSG_REQUIRE(key->getType() != "secret"_kj, Error, "Secret keys do not have asymmetric details");
425 return key->getAsymmetricKeyDetails(js);
426}
427 
428kj::StringPtr CryptoImpl::getAsymmetricKeyType(jsg::Lock& js, jsg::Ref<CryptoKey> key) {
429 static const std::map<kj::StringPtr, kj::StringPtr> mapping{
430 {"RSASSA-PKCS1-v1_5", "rsa"},
431 {"RSA-PSS", "rsa"},
432 {"RSA-OAEP", "rsa"},
433 {"ECDSA", "ec"},
434 {"Ed25519", "ed25519"},
435 {"NODE-ED25519", "ed25519"},
436 {"ECDH", "ecdh"},
437 {"X25519", "x25519"},
438 };
439 JSG_REQUIRE(
440 key->getType() != "secret"_kj, TypeError, "Secret key does not have an asymmetric type");
441 auto name = key->getAlgorithmName();
442 auto found = mapping.find(name);
443 return found != mapping.end() ? found->second : name;
444}
445 
446jsg::Ref<CryptoKey> CryptoImpl::createSecretKey(jsg::Lock& js, jsg::JsBufferSource keyData) {
447 // The keyData we receive here should be an exclusive copy of the key data.
448 // It will have been copied on the JS side before being passed to this function.
449 // We copy the raw bytes into a kj::Array for persistent storage.
450 return js.alloc<CryptoKey>(kj::heap<SecretKey>(keyData.copy()));
451}
452 
453namespace {
454std::optional<ncrypto::EVPKeyPointer> tryParsingPrivate(jsg::Lock& js,
455 const CryptoImpl::CreateAsymmetricKeyOptions& options,
456 kj::ArrayPtr<const kj::byte> buffer) {
457 // As a private key the format can be either 'pem' or 'der',
458 // while type can be one of `pkcs1`, `pkcs8`, or `sec1`.
459 // The type is only required when format is 'der'.
460 
461 auto format =
462 trySelectKeyFormat(options.format).orDefault(ncrypto::EVPKeyPointer::PKFormatType::PEM);
463 
464 auto enc = ncrypto::EVPKeyPointer::PKEncodingType::PKCS8;
465 KJ_IF_SOME(type, options.type) {
466 enc = trySelectKeyEncoding(type).orDefault(enc);
467 }
468 
469 ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config(false, format, enc);
470 
471 KJ_IF_SOME(passphrase, options.passphrase) {
472 // TODO(later): Avoid using DataPointer for passphrase... so we
473 // can avoid the copy...
474 auto passphrasePtr = passphrase.getHandle(js).asArrayPtr();
475 auto dp = ncrypto::DataPointer::Alloc(passphrasePtr.size());
476 kj::ArrayPtr<kj::byte> ptr(dp.get<kj::byte>(), dp.size());
477 ptr.copyFrom(passphrasePtr);
478 config.passphrase = kj::mv(dp);
479 }
480 
481 auto result = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ToNcryptoBuffer(buffer));
482 
483 if (result.has_value) return kj::mv(result.value);
484 return std::nullopt;
485}
486} // namespace
487 
488jsg::Ref<CryptoKey> CryptoImpl::createPrivateKey(
489 jsg::Lock& js, CreateAsymmetricKeyOptions options) {
490 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
491 
492 // Unlike with Web Crypto, where the CryptoKey being created is always
493 // algorithm specific, here we will create a generic private key impl
494 // that can be used for multiple kinds of operations.
495 
496 KJ_SWITCH_ONEOF(options.key) {
497 KJ_CASE_ONEOF(bs, jsg::JsRef<jsg::JsBufferSource>) {
498 JSG_REQUIRE(options.format == "pem"_kj || options.format == "der"_kj, TypeError,
499 "Invalid format for private key creation");
500 
501 auto bufferPtr = bs.getHandle(js).asArrayPtr();
502 if (auto maybePrivate = tryParsingPrivate(js, options, bufferPtr)) {
503 return js.alloc<CryptoKey>(AsymmetricKey::NewPrivate(kj::mv(maybePrivate.value())));
504 }
505 
506 JSG_FAIL_REQUIRE(Error, "Failed to parse private key");
507 }
508 KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {
509 JSG_REQUIRE(options.format == "jwk"_kj, TypeError, "Invalid format for JWK key creation");
510 
511 if (auto key = fromJwk(jwk, KeyType::PRIVATE)) {
512 return js.alloc<CryptoKey>(AsymmetricKey::NewPrivate(kj::mv(key)));
513 }
514 
515 JSG_FAIL_REQUIRE(Error, "JWK private key import is not implemented for this key type");
516 }
517 KJ_CASE_ONEOF(key, jsg::Ref<api::CryptoKey>) {
518 // This path shouldn't be reachable.
519 JSG_FAIL_REQUIRE(TypeError, "Invalid key data");
520 }
521 }
522 
523 KJ_UNREACHABLE;
524}
525 
526jsg::Ref<CryptoKey> CryptoImpl::createPublicKey(jsg::Lock& js, CreateAsymmetricKeyOptions options) {
527 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
528 
529 KJ_SWITCH_ONEOF(options.key) {
530 KJ_CASE_ONEOF(bs, jsg::JsRef<jsg::JsBufferSource>) {
531 JSG_REQUIRE(options.format == "pem"_kj || options.format == "der"_kj, TypeError,
532 "Invalid format for public key creation");
533 
534 auto bufferPtr = bs.getHandle(js).asArrayPtr();
535 
536 // As a public key the format can be either 'pem' or 'der',
537 // while type can be one of either `pkcs1` or `spki`
538 
539 {
540 // It is necessary to pop the error on return before we attempt
541 // to try parsing as a private key if the public key parsing fails.
542 ncrypto::MarkPopErrorOnReturn markPopErrorOnReturn;
543 
544 auto format =
545 trySelectKeyFormat(options.format).orDefault(ncrypto::EVPKeyPointer::PKFormatType::PEM);
546 
547 auto enc = ncrypto::EVPKeyPointer::PKEncodingType::PKCS1;
548 KJ_IF_SOME(type, options.type) {
549 enc = trySelectKeyEncoding(type).orDefault(enc);
550 }
551 
552 ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config(true, format, enc);
553 
554 auto result =
555 ncrypto::EVPKeyPointer::TryParsePublicKey(config, ToNcryptoBuffer(bufferPtr.asConst()));
556 
557 if (result.has_value) {
558 return js.alloc<CryptoKey>(AsymmetricKey::NewPublic(kj::mv(result.value)));
559 }
560 }
561 
562 // Otherwise, let's try parsing as a private key...
563 if (auto maybePrivate = tryParsingPrivate(js, options, bufferPtr)) {
564 return js.alloc<CryptoKey>(AsymmetricKey::NewPublic(kj::mv(maybePrivate.value())));
565 }
566 
567 JSG_FAIL_REQUIRE(Error, "Failed to parse public key");
568 }
569 KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) {
570 JSG_REQUIRE(options.format == "jwk"_kj, TypeError, "Invalid format for JWK key creation");
571 
572 if (auto key = fromJwk(jwk, KeyType::PUBLIC)) {
573 return js.alloc<CryptoKey>(AsymmetricKey::NewPublic(kj::mv(key)));
574 }
575 
576 JSG_FAIL_REQUIRE(Error, "JWK public key import is not implemented for this key type");
577 }
578 KJ_CASE_ONEOF(key, jsg::Ref<api::CryptoKey>) {
579 JSG_REQUIRE(key->getType() == "private"_kj, TypeError,
580 "Cannot create public key from secret or public key");
581 
582 // TODO(later): For now, this only works with crypto keys that are created using
583 // AsymmetricKey above. Web crypto private keys won't work here.
584 KJ_IF_SOME(impl, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*key->impl.get())) {
585 return js.alloc<CryptoKey>(impl.cloneAsPublicKey());
586 }
587 
588 JSG_FAIL_REQUIRE(Error, "Failed to derive public key from private key");
589 }
590 }
591 
592 KJ_UNREACHABLE;
593}
594 
595CryptoKeyPair CryptoImpl::generateRsaKeyPair(jsg::Lock& js, RsaKeyPairOptions options) {
596 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
597 
598 auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(
599 options.type == "rsa-pss" ? EVP_PKEY_RSA_PSS : EVP_PKEY_RSA);
600 
601 JSG_REQUIRE(ctx, Error, "Failed to create keygen context");
602 JSG_REQUIRE(ctx.initForKeygen(), Error, "Failed to initialize keygen context");
603 JSG_REQUIRE(ctx.setRsaKeygenBits(options.modulusLength), Error, "Failed to set modulus length");
604 
605 if (options.publicExponent != ncrypto::EVPKeyCtxPointer::kDefaultRsaExponent) {
606 auto bn = ncrypto::BignumPointer::New();
607 JSG_REQUIRE(bn, Error, "Failed to initialize public exponent");
608 JSG_REQUIRE(bn.setWord(options.publicExponent) && ctx.setRsaKeygenPubExp(kj::mv(bn)), Error,
609 "Failed to set public exponent");
610 }
611 
612 // TODO(later): BoringSSL does not support generating RSA-PSS this
613 // way... later see if there's an alternative approach.
614 // if (options.type == "rsa-pss") {
615 
616 // KJ_IF_SOME(hash, options.hashAlgorithm) {
617 // std::string_view hashName(hash.begin(), hash.size());
618 // auto nid = ncrypto::getDigestByName(hashName);
619 // JSG_REQUIRE(nid != nullptr, Error, "Unsupported hash algorithm");
620 // JSG_REQUIRE(ctx.setRsaPssKeygenMd(nid), Error, "Failed to set hash algorithm");
621 // }
622 
623 // KJ_IF_SOME(hash, options.mgf1HashAlgorithm) {
624 // std::string_view mgf1hashName(hash.begin(), hash.size());
625 // auto mgf1_nid = ncrypto::getDigestByName(mgf1hashName);
626 // if (mgf1_nid == nullptr) {
627 // KJ_IF_SOME(hash, options.hashAlgorithm) {
628 // std::string_view hashName(hash.begin(), hash.size());
629 // mgf1_nid = ncrypto::getDigestByName(hashName);
630 // }
631 // }
632 // if (mgf1_nid != nullptr) {
633 // JSG_REQUIRE(ctx.setRsaPssKeygenMgf1Md(mgf1_nid), Error,
634 // "Failed to set MGF1 hash algorithm");
635 // }
636 // }
637 
638 // KJ_IF_SOME(len, options.saltLength) {
639 // JSG_REQUIRE(ctx.setRsaPssSaltlen(len), Error, "Failed to set salt length");
640 // }
641 // }
642 
643 // Generate the key
644 EVP_PKEY* pkey = nullptr;
645 JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, "Failed to generate key");
646 
647 auto generated = ncrypto::EVPKeyPointer(pkey);
648 
649 auto publicKey = AsymmetricKey::NewPublic(generated.clone());
650 JSG_REQUIRE(publicKey, Error, "Failed to create public key");
651 auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));
652 JSG_REQUIRE(privateKey, Error, "Failed to create private key");
653 
654 return CryptoKeyPair{
655 .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),
656 .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),
657 };
658}
659 
660CryptoKeyPair CryptoImpl::generateDsaKeyPair(jsg::Lock& js, DsaKeyPairOptions options) {
661 // TODO(later): BoringSSL does not implement DSA key generation using
662 // EVP_PKEY_keygen. We would need to implement this using the DSA-specific
663 // APIs which get a bit complicated when it comes to using a user-provided
664 // modulus length and divisor length. For now, leave this un-implemented.
665 
666 // auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(EVP_PKEY_DSA);
667 
668 // JSG_REQUIRE(ctx, Error, "Failed to create keygen context");
669 // JSG_REQUIRE(ctx.initForKeygen(), Error, "Failed to initialize keygen context");
670 
671 // uint32_t bits = options.modulusLength;
672 // std::optional<uint32_t> q_bits = std::nullopt;
673 // KJ_IF_SOME(d, options.divisorLength) {
674 // q_bits = d;
675 // }
676 
677 // JSG_REQUIRE(ctx.setDsaParameters(bits, q_bits), Error, "Failed to set DSA parameters");
678 
679 // // Generate the key
680 // EVP_PKEY* pkey = nullptr;
681 // JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, "Failed to generate key");
682 
683 // auto generated = ncrypto::EVPKeyPointer(pkey);
684 
685 // auto publicKey = AsymmetricKey::NewPublic(generated.clone());
686 // JSG_REQUIRE(publicKey, Error, "Failed to create public key");
687 // auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));
688 // JSG_REQUIRE(privateKey, Error, "Failed to create private key");
689 
690 // return CryptoKeyPair {
691 // .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),
692 // .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),
693 // };
694 
695 JSG_FAIL_REQUIRE(Error, "Not yet implemented");
696}
697 
698CryptoKeyPair CryptoImpl::generateEcKeyPair(jsg::Lock& js, EcKeyPairOptions options) {
699 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
700 
701 auto nid = getCurveFromName(options.namedCurve);
702 JSG_REQUIRE(nid != NID_undef, Error, "Invalid or unsupported curve");
703 
704 auto paramEncoding =
705 options.paramEncoding == "named"_kj ? OPENSSL_EC_NAMED_CURVE : OPENSSL_EC_EXPLICIT_CURVE;
706 
707 auto ecPrivateKey = ncrypto::ECKeyPointer::NewByCurveName(nid);
708 JSG_REQUIRE(ecPrivateKey, Error, "Failed to initialize key");
709 JSG_REQUIRE(ecPrivateKey.generate(), Error, "Failed to generate private key");
710 
711 EC_KEY_set_enc_flags(ecPrivateKey, paramEncoding);
712 
713 auto ecPublicKey = ncrypto::ECKeyPointer::NewByCurveName(nid);
714 JSG_REQUIRE(EC_KEY_set_public_key(ecPublicKey, EC_KEY_get0_public_key(ecPrivateKey)), Error,
715 "Failed to derive public key");
716 
717 auto privateKey = ncrypto::EVPKeyPointer::New();
718 JSG_REQUIRE(privateKey.assign(ecPrivateKey), Error, "Failed to assign private key");
719 
720 auto publicKey = ncrypto::EVPKeyPointer::New();
721 JSG_REQUIRE(publicKey.assign(ecPublicKey), Error, "Failed to assign public key");
722 
723 ecPrivateKey.release();
724 ecPublicKey.release();
725 
726 auto pubKey = AsymmetricKey::NewPublic(kj::mv(publicKey));
727 JSG_REQUIRE(pubKey, Error, "Failed to create public key");
728 auto pvtKey = AsymmetricKey::NewPrivate(kj::mv(privateKey));
729 JSG_REQUIRE(pvtKey, Error, "Failed to create private key");
730 
731 return CryptoKeyPair{
732 .publicKey = js.alloc<CryptoKey>(kj::mv(pubKey)),
733 .privateKey = js.alloc<CryptoKey>(kj::mv(pvtKey)),
734 };
735}
736 
737CryptoKeyPair CryptoImpl::generateEdKeyPair(jsg::Lock& js, EdKeyPairOptions options) {
738 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
739 
740 auto nid = ([&] {
741 if (options.type == "ed25519") {
742 return EVP_PKEY_ED25519;
743 }
744 if (options.type == "x25519") {
745 return EVP_PKEY_X25519;
746 }
747 return NID_undef;
748 })();
749 JSG_REQUIRE(nid != NID_undef, Error, "Invalid or unsupported curve");
750 
751 auto ctx = ncrypto::EVPKeyCtxPointer::NewFromID(nid);
752 JSG_REQUIRE(ctx, Error, "Failed to create keygen context");
753 JSG_REQUIRE(ctx.initForKeygen(), Error, "Failed to initialize keygen");
754 
755 // Generate the key
756 EVP_PKEY* pkey = nullptr;
757 JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, "Failed to generate key");
758 
759 auto generated = ncrypto::EVPKeyPointer(pkey);
760 
761 auto publicKey = AsymmetricKey::NewPublic(generated.clone());
762 JSG_REQUIRE(publicKey, Error, "Failed to create public key");
763 auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));
764 JSG_REQUIRE(privateKey, Error, "Failed to create private key");
765 
766 return CryptoKeyPair{
767 .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),
768 .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),
769 };
770}
771 
772CryptoKeyPair CryptoImpl::generateDhKeyPair(jsg::Lock& js, DhKeyPairOptions options) {
773 
774 // TODO(soon): Older versions of boringssl+fips do not support EVP with
775 // DH key pairs that are required to make the following work. A compile
776 // flag is used to disable the mechanism in ncrypto, causing the calls
777 // to `ncrypto::EVPKeyPointer::NewDH to return an empty EVPKeyPointer.
778 // While the ideal situation would be for us to adopt a newer version
779 // of boringssl+fips that *does* support EVP+DH, we can possibly work
780 // around the issue by implementing an alternative that uses the older
781 // DH_* specific APIs like the rest of our DH implementation does.
782 
783 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
784 
785 static constexpr uint32_t kStandardizedGenerator = 2;
786 
787 ncrypto::EVPKeyPointer key_params;
788 auto generator = options.generator.orDefault(kStandardizedGenerator);
789 
790 KJ_SWITCH_ONEOF(options.primeOrGroup) {
791 KJ_CASE_ONEOF(group, kj::String) {
792 std::string_view group_name(group.begin(), group.size());
793 auto found = ncrypto::DHPointer::FindGroup(group_name);
794 JSG_REQUIRE(found, Error, "Invalid or unsupported group");
795 
796 auto bn_g = ncrypto::BignumPointer::New();
797 JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, "Failed to set generator");
798 
799 auto dh = ncrypto::DHPointer::New(kj::mv(found), kj::mv(bn_g));
800 JSG_REQUIRE(dh, Error, "Failed to create DH key");
801 
802 key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh));
803 }
804 KJ_CASE_ONEOF(prime, jsg::JsRef<jsg::JsBufferSource>) {
805 auto primePtr = prime.getHandle(js).asArrayPtr();
806 ncrypto::BignumPointer bn(primePtr.begin(), primePtr.size());
807 
808 auto bn_g = ncrypto::BignumPointer::New();
809 JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, "Failed to set generator");
810 
811 auto dh = ncrypto::DHPointer::New(kj::mv(bn), kj::mv(bn_g));
812 JSG_REQUIRE(dh, Error, "Failed to create DH key");
813 
814 key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh));
815 }
816 KJ_CASE_ONEOF(length, uint32_t) {
817 // TODO(later): BoringSSL appears to not implement DH key generation
818 // from a prime length the same way Node.js does. For now, defer this
819 // and come back to implement later.
820 JSG_FAIL_REQUIRE(Error, "Generating DH keys from a prime length is not yet implemented");
821 }
822 }
823 
824 JSG_REQUIRE(key_params, Error, "Failed to create keygen context");
825 auto ctx = key_params.newCtx();
826 JSG_REQUIRE(ctx, Error, "Failed to create keygen context");
827 JSG_REQUIRE(ctx.initForKeygen(), Error, "Failed to initialize keygen context");
828 
829 // Generate the key
830 EVP_PKEY* pkey = nullptr;
831 JSG_REQUIRE(EVP_PKEY_keygen(ctx.get(), &pkey), Error, "Failed to generate key");
832 
833 auto generated = ncrypto::EVPKeyPointer(pkey);
834 
835 auto publicKey = AsymmetricKey::NewPublic(generated.clone());
836 JSG_REQUIRE(publicKey, Error, "Failed to create public key");
837 auto privateKey = AsymmetricKey::NewPrivate(kj::mv(generated));
838 JSG_REQUIRE(privateKey, Error, "Failed to create private key");
839 
840 return CryptoKeyPair{
841 .publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),
842 .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey)),
843 };
844}
845 
846jsg::JsUint8Array CryptoImpl::statelessDH(
847 jsg::Lock& js, jsg::Ref<CryptoKey> privateKey, jsg::Ref<CryptoKey> publicKey) {
848 auto privateKeyAlg = privateKey->getAlgorithmName();
849 auto publicKeyAlg = publicKey->getAlgorithmName();
850 KJ_ASSERT(privateKeyAlg == "dh"_kj || privateKeyAlg == "ec"_kj || privateKeyAlg == "x25519"_kj,
851 "Invalid private key algorithm");
852 KJ_ASSERT(publicKeyAlg == "dh"_kj || publicKeyAlg == "ec"_kj || publicKeyAlg == "x25519"_kj,
853 "Invalid public key algorithm");
854 KJ_ASSERT(privateKeyAlg == publicKeyAlg, "Mismatched public and private key types");
855 KJ_IF_SOME(pubKey, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*publicKey->impl)) {
856 KJ_IF_SOME(pvtKey, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*privateKey->impl)) {
857 auto data = ncrypto::DHPointer::stateless(pubKey, pvtKey);
858 JSG_REQUIRE(data, Error, "Failed to derive shared diffie-hellman secret");
859 kj::ArrayPtr<const kj::byte> ptr(static_cast<const kj::byte*>(data.get()), data.size());
860 return jsg::JsUint8Array::create(js, ptr);
861 }
862 }
863 JSG_FAIL_REQUIRE(Error, "Unsupported keys for stateless diffie-hellman");
864}
865 
866kj::Maybe<ncrypto::EVPKeyPointer> CryptoImpl::tryGetKey(jsg::Ref<CryptoKey>& key) {
867 // AsymmetricKeyCryptoKeyImpl doesn't provide a reference like AsymmetricKey,
868 // so this function must return a value type since we create the EVPKeyPointer
869 // in here
870 KJ_IF_SOME(asymKey, kj::dynamicDowncastIfAvailable<AsymmetricKey>(*key->impl)) {
871 const ncrypto::EVPKeyPointer& evp = asymKey;
872 // Internally just incrementing the ref count and returning a new pointer, no
873 // copied key data so impact should be minimal.
874 return evp.clone();
875 }
876 // Also handle keys created via the Web Crypto API (crypto.subtle), which use a
877 // different CryptoKey::Impl subclass.
878 KJ_IF_SOME(webCryptoKey, kj::dynamicDowncastIfAvailable<AsymmetricKeyCryptoKeyImpl>(*key->impl)) {
879 EVP_PKEY* raw = webCryptoKey.getEvpPkey();
880 // Mimicking the internal implementation of EVPKeyPointer::clone() since getEvpPkey()
881 // returns a raw pointer
882 if (raw != nullptr) {
883 if (!EVP_PKEY_up_ref(raw)) {
884 return kj::none;
885 }
886 return ncrypto::EVPKeyPointer(raw);
887 }
888 }
889 return kj::none;
890}
891 
892kj::Maybe<kj::ArrayPtr<const kj::byte>> CryptoImpl::tryGetSecretKeyData(jsg::Ref<CryptoKey>& key) {
893 KJ_IF_SOME(secret, kj::dynamicDowncastIfAvailable<SecretKey>(*key->impl)) {
894 return secret.rawKeyData();
895 }
896 return kj::none;
897}
898 
899} // namespace workerd::api::node