Skip to content
File

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

50.8 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 
5#include "ec.h"
6 
7#include "impl.h"
8#include "keys.h"
9 
10#include <workerd/api/util.h>
11#include <workerd/io/features.h>
12 
13#include <openssl/bn.h>
14#include <openssl/crypto.h>
15#include <openssl/curve25519.h>
16#include <openssl/ec_key.h>
17#include <openssl/x509.h>
18 
19#include <kj/function.h>
20 
21#include <map>
22#include <type_traits>
23 
24namespace workerd::api {
25 
26Ec::Ec(EC_KEY* key): key(key), x(OSSL_NEW(BIGNUM)), y(OSSL_NEW(BIGNUM)) {
27 KJ_ASSERT(key != nullptr);
28 group = EC_KEY_get0_group(key);
29 JSG_REQUIRE(
30 1 == EC_POINT_get_affine_coordinates(group, getPublicKey(), x.get(), y.get(), nullptr),
31 InternalDOMOperationError, "Error getting affine coordinates for export",
32 internalDescribeOpensslErrors());
33}
34 
35int Ec::getCurveName() const {
36 return EC_GROUP_get_curve_name(group);
37}
38 
39uint32_t Ec::getDegree() const {
40 return EC_GROUP_get_degree(getGroup());
41}
42 
43const EC_POINT* Ec::getPublicKey() const {
44 return EC_KEY_get0_public_key(key);
45}
46 
47const BIGNUM* Ec::getPrivateKey() const {
48 return EC_KEY_get0_private_key(key);
49}
50 
51SubtleCrypto::JsonWebKey Ec::toJwk(KeyType keyType, kj::StringPtr curveName) const {
52 JSG_REQUIRE(group != nullptr, DOMOperationError, "No elliptic curve group in this key",
53 tryDescribeOpensslErrors());
54 JSG_REQUIRE(getPublicKey() != nullptr, DOMOperationError,
55 "No public elliptic curve key data in this key", tryDescribeOpensslErrors());
56 
57 auto groupDegreeInBytes = integerCeilDivision(getDegree(), 8u);
58 // EC_GROUP_get_degree returns number of bits. We need this because x, y, & d need to match the
59 // group degree according to JWK.
60 
61 SubtleCrypto::JsonWebKey jwk;
62 jwk.kty = kj::str("EC");
63 jwk.crv = kj::str(curveName);
64 
65 static constexpr auto handleBn = [](const BIGNUM& bn, size_t size) {
66 return JSG_REQUIRE_NONNULL(bignumToArrayPadded(bn, size), InternalDOMOperationError,
67 "Error converting EC affine co-ordinates to padded array", internalDescribeOpensslErrors());
68 };
69 
70 auto xa = handleBn(*x, groupDegreeInBytes);
71 auto ya = handleBn(*y, groupDegreeInBytes);
72 
73 jwk.x = fastEncodeBase64Url(xa);
74 jwk.y = fastEncodeBase64Url(ya);
75 
76 if (keyType == KeyType::PRIVATE) {
77 const auto privateKey = getPrivateKey();
78 JSG_REQUIRE(privateKey != nullptr, InternalDOMOperationError,
79 "Error getting private key material for JSON Web Key export",
80 internalDescribeOpensslErrors());
81 auto pk = handleBn(*privateKey, groupDegreeInBytes);
82 jwk.d = fastEncodeBase64Url(pk);
83 }
84 return jwk;
85}
86 
87jsg::JsArrayBuffer Ec::getRawPublicKey(jsg::Lock& js) const {
88 JSG_REQUIRE_NONNULL(group, InternalDOMOperationError, "No elliptic curve group in this key",
89 tryDescribeOpensslErrors());
90 auto publicKey = getPublicKey();
91 JSG_REQUIRE(publicKey != nullptr, InternalDOMOperationError,
92 "No public elliptic curve key data in this key", tryDescribeOpensslErrors());
93 
94 // Serialize the public key as an uncompressed point in X9.62 form.
95 uint8_t* raw;
96 // The caller takes ownership of the buffer and, unless the buffer was fixed with CBB_init_fixed,
97 // must call OPENSSL_free when done.
98 // https://commondatastorage.googleapis.com/chromium-boringssl-docs/bytestring.h.html#CBB_finish
99 KJ_DEFER(if (raw != nullptr) { OPENSSL_free(raw); });
100 size_t raw_len;
101 CBB cbb;
102 
103 JSG_REQUIRE(1 == CBB_init(&cbb, 0), InternalDOMOperationError, "Failed to init CBB",
104 internalDescribeOpensslErrors());
105 KJ_DEFER(CBB_cleanup(&cbb));
106 
107 JSG_REQUIRE(
108 1 == EC_POINT_point2cbb(&cbb, group, publicKey, POINT_CONVERSION_UNCOMPRESSED, nullptr),
109 InternalDOMOperationError, "Failed to convert to serialize EC key",
110 internalDescribeOpensslErrors());
111 
112 JSG_REQUIRE(1 == CBB_finish(&cbb, &raw, &raw_len), InternalDOMOperationError,
113 "Failed to finish CBB", internalDescribeOpensslErrors());
114 
115 return jsg::JsArrayBuffer::create(js, kj::arrayPtr(raw, raw_len));
116}
117 
118CryptoKey::AsymmetricKeyDetails Ec::getAsymmetricKeyDetail(jsg::Lock& js) const {
119 // Adapted from Node.js' GetEcKeyDetail
120 return CryptoKey::AsymmetricKeyDetails{
121 .namedCurve = kj::str(OBJ_nid2sn(EC_GROUP_get_curve_name(group)))};
122}
123 
124kj::Maybe<Ec> Ec::tryGetEc(const EVP_PKEY* key) {
125 int type = EVP_PKEY_id(key);
126 if (type != EVP_PKEY_EC) return kj::none;
127 auto ec = EVP_PKEY_get0_EC_KEY(key);
128 if (ec == nullptr) return kj::none;
129 return Ec(ec);
130}
131 
132// =====================================================================================
133// ECDSA & ECDH
134 
135namespace {
136 
137class EllipticKey final: public AsymmetricKeyCryptoKeyImpl {
138 public:
139 explicit EllipticKey(AsymmetricKeyData keyData,
140 CryptoKey::EllipticKeyAlgorithm keyAlgorithm,
141 uint rsSize,
142 bool extractable)
143 : AsymmetricKeyCryptoKeyImpl(kj::mv(keyData), extractable),
144 keyAlgorithm(kj::mv(keyAlgorithm)),
145 rsSize(rsSize) {}
146 
147 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
148 return keyAlgorithm;
149 }
150 kj::StringPtr getAlgorithmName() const override {
151 return keyAlgorithm.name;
152 }
153 
154 void requireSigningAbility() const {
155 // This assert is internal to our WebCrypto implementation because we share the AsymmetricKey
156 // implementation between ECDH & ECDSA (the former only supports deriveBits/deriveKey, not
157 // signing which is the usage for this function).
158 JSG_REQUIRE(keyAlgorithm.name == "ECDSA", DOMNotSupportedError,
159 "The sign and verify operations are not implemented for \"", keyAlgorithm.name, "\".");
160 }
161 
162 kj::StringPtr chooseHash(
163 const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)
164 const override {
165 requireSigningAbility();
166 
167 // ECDSA infamously expects the hash to be specified at call time.
168 // See: https://github.com/w3c/webcrypto/issues/111
169 return api::getAlgorithmName(JSG_REQUIRE_NONNULL(callTimeHash, TypeError,
170 "Missing \"hash\" in AlgorithmIdentifier. (ECDSA requires that the hash algorithm be "
171 "specified at call time rather than on the key. This differs from other WebCrypto "
172 "algorithms for historical reasons.)"));
173 }
174 
175 jsg::JsArrayBuffer deriveBits(jsg::Lock& js,
176 SubtleCrypto::DeriveKeyAlgorithm&& algorithm,
177 kj::Maybe<uint32_t> resultBitLength) const override final {
178 JSG_REQUIRE(keyAlgorithm.name == "ECDH", DOMNotSupportedError,
179 ""
180 "The deriveBits operation is not implemented for \"",
181 keyAlgorithm.name, "\".");
182 
183 JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,
184 ""
185 "The deriveBits operation is only valid for a private key, not \"",
186 getType(), "\".");
187 
188 auto& publicKey = JSG_REQUIRE_NONNULL(
189 algorithm.$public, TypeError, "Missing field \"public\" in \"derivedKeyParams\".");
190 
191 JSG_REQUIRE(publicKey->getType() == "public"_kj, DOMInvalidAccessError,
192 ""
193 "The provided key has type \"",
194 publicKey->getType(), "\", not \"public\"");
195 
196 JSG_REQUIRE(getAlgorithm(js).which() == publicKey->getAlgorithm(js).which(),
197 DOMInvalidAccessError, "Base ", getAlgorithmName(),
198 " private key cannot be used to derive"
199 " a key from a peer ",
200 publicKey->getAlgorithmName(), " public key");
201 
202 JSG_REQUIRE(getAlgorithmName() == publicKey->getAlgorithmName(), DOMInvalidAccessError,
203 "Private key for derivation is using \"", getAlgorithmName(),
204 "\" while public key is using \"", publicKey->getAlgorithmName(), "\".");
205 
206 auto publicCurve =
207 publicKey->getAlgorithm(js).get<CryptoKey::EllipticKeyAlgorithm>().namedCurve;
208 JSG_REQUIRE(keyAlgorithm.namedCurve == publicCurve, DOMInvalidAccessError,
209 "Private key for derivation is using curve \"", keyAlgorithm.namedCurve,
210 "\" while public key is using \"", publicCurve, "\".");
211 
212 // The check above for the algorithm `which` equality ensures that the impl can be downcast to
213 // EllipticKey (assuming we don't accidentally create a class that doesn't inherit this one that
214 // for some reason returns an EllipticKey).
215 auto& publicKeyImpl = kj::downcast<EllipticKey>(*publicKey->impl);
216 
217 // Adapted from https://wiki.openssl.org/index.php/Elliptic_Curve_Diffie_Hellman:
218 auto privateEcKey = JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), InternalDOMOperationError,
219 "No elliptic curve data backing key", tryDescribeOpensslErrors());
220 auto publicEcKey =
221 JSG_REQUIRE_NONNULL(Ec::tryGetEc(publicKeyImpl.getEvpPkey()), InternalDOMOperationError,
222 "No elliptic curve data backing key", tryDescribeOpensslErrors());
223 JSG_REQUIRE(publicEcKey.getPublicKey() != nullptr, DOMOperationError,
224 "No public elliptic curve key data in this key", tryDescribeOpensslErrors());
225 auto fieldSize = privateEcKey.getDegree();
226 
227 // Assuming that `fieldSize` will always be a sane value since it's related to the keys we
228 // construct in C++ (i.e. not untrusted user input).
229 
230 kj::Vector<kj::byte> sharedSecret;
231 sharedSecret.resize(
232 integerCeilDivision<std::make_unsigned_t<decltype(fieldSize)>>(fieldSize, 8u));
233 auto written = ECDH_compute_key(sharedSecret.begin(), sharedSecret.capacity(),
234 publicEcKey.getPublicKey(), privateEcKey.getKey(), nullptr);
235 JSG_REQUIRE(written > 0, DOMOperationError, "Failed to generate shared ECDH secret",
236 tryDescribeOpensslErrors());
237 
238 sharedSecret.resize(written);
239 
240 auto outputBitLength = resultBitLength.orDefault(sharedSecret.size() * 8);
241 JSG_REQUIRE(outputBitLength <= sharedSecret.size() * 8, DOMOperationError,
242 "Derived key length (", outputBitLength, " bits) is too long (should be at most ",
243 sharedSecret.size() * 8, " bits).");
244 
245 // Round up since outputBitLength may not be a perfect multiple of 8.
246 // However, the last byte may now have bits that have leaked which we handle below.
247 auto resultByteLength = integerCeilDivision(outputBitLength, 8u);
248 sharedSecret.truncate(resultByteLength);
249 
250 // We have to remember to mask off the bits that weren't requested (if a non multiple of 8 was
251 // passed in). NOTE: The conformance tests DO NOT appear to test for this. This is my reading of
252 // the spec, combining:
253 // * ECDH: Return an octet string containing the first length bits of secret.
254 // * octet string: b is the octet string obtained by first appending zero or more bits of
255 // value zero to b such that the length of the resulting bit string is minimal
256 // and an integer multiple of 8.
257 auto numBitsToMaskOff = resultByteLength * 8 - outputBitLength;
258 KJ_DASSERT(numBitsToMaskOff < 8, numBitsToMaskOff);
259 
260 // The mask should have `numBitsToMaskOff` bits set to 0 from least significant to most.
261 // 0 = 1 1 1 1 1 1 1 1 (0xFF)
262 // 1 = 1 1 1 1 1 1 1 0 (0xFE)
263 // 2 = 1 1 1 1 1 1 0 0 (0xFD)
264 // 3 = 1 1 1 1 1 0 0 0 (0xFC)
265 // Let's rewrite this to have the lower bits set to 1 since that's typically the easier form to
266 // generate with bit twiddling.
267 // 0 = 0 0 0 0 0 0 0 0 (0)
268 // 1 = 0 0 0 0 0 0 0 1 (1)
269 // 2 = 0 0 0 0 0 0 1 1 (3)
270 // 3 = 0 0 0 0 0 1 1 1 (7)
271 // The pattern seems pretty clearly ~(2^n - 1) where n is the number of bits to mask off. Let's
272 // check the last one though (8 is not a possible boundary condition).
273 // (2^7 - 1) = 0x7f => ~0x7f = 0x80 (when truncated to a byte)
274 if (numBitsToMaskOff) {
275 uint8_t mask = ~((1 << numBitsToMaskOff) - 1);
276 sharedSecret.back() &= mask;
277 }
278 
279 return jsg::JsArrayBuffer::create(js, sharedSecret.asPtr());
280 }
281 
282 jsg::JsArrayBuffer signatureSslToWebCrypto(
283 jsg::Lock& js, kj::ArrayPtr<kj::byte> signature) const override {
284 // An EC signature is two big integers "r" and "s". WebCrypto wants us to just concatenate both
285 // integers, using a constant size of each that depends on the curve size. OpenSSL wants to
286 // encode them in some ASN.1 wrapper with variable-width sizes. Ugh.
287 
288 requireSigningAbility();
289 
290 // Manually decode ASN.1 BER.
291 KJ_ASSERT(signature.size() >= 6);
292 KJ_ASSERT(signature[0] == 0x30);
293 kj::ArrayPtr<const kj::byte> rest;
294 if (signature[1] < 128) {
295 KJ_ASSERT(signature[1] == signature.size() - 2);
296 rest = signature.slice(2, signature.size());
297 } else {
298 // Size of message did not fit in 7 bits, so the first byte encodes the size-of-size, but it
299 // will always fit in 8 bits so the size-of-size will always be 1 (plus 128 because top bit
300 // is set).
301 KJ_ASSERT(signature[1] == 129);
302 KJ_ASSERT(signature[2] == signature.size() - 3);
303 rest = signature.slice(3, signature.size());
304 }
305 
306 KJ_ASSERT(rest.size() >= 2);
307 KJ_ASSERT(rest[0] == 0x02);
308 size_t rSize = rest[1];
309 KJ_ASSERT(rest.size() >= 2 + rSize);
310 auto r = rest.slice(2, 2 + rSize);
311 
312 rest = rest.slice(2 + rSize, rest.size());
313 
314 KJ_ASSERT(rest.size() >= 2);
315 KJ_ASSERT(rest[0] == 0x02);
316 size_t sSize = rest[1];
317 KJ_ASSERT(rest.size() == 2 + sSize);
318 auto s = rest.slice(2, 2 + sSize);
319 
320 // If the top bit is set, BER encoding will add an extra 0-byte prefix to disambiguate from a
321 // negative number. Uggghhh.
322 while (r.size() > rsSize && r[0] == 0) r = r.slice(1, r.size());
323 while (s.size() > rsSize && s[0] == 0) s = s.slice(1, s.size());
324 KJ_ASSERT(r.size() <= rsSize);
325 KJ_ASSERT(s.size() <= rsSize);
326 
327 // Construct WebCrypto format.
328 auto out = jsg::JsArrayBuffer::create(js, rsSize * 2);
329 auto outPtr = out.asArrayPtr();
330 
331 // We're dealing with big-endian, so we have to align the copy to the right. This is exactly
332 // why big-endian is the wrong endian.
333 outPtr.slice(rsSize - r.size(), rsSize).copyFrom(r);
334 outPtr.slice(rsSize * 2 - s.size(), rsSize * 2).copyFrom(s);
335 return out;
336 }
337 
338 jsg::JsArrayBuffer signatureWebCryptoToSsl(
339 jsg::Lock& js, kj::ArrayPtr<const kj::byte> signature) const override {
340 requireSigningAbility();
341 
342 if (signature.size() != rsSize * 2) {
343 // The signature is the wrong size. Return an empty signature, which will be judged invalid.
344 return jsg::JsArrayBuffer::create(js, 0);
345 }
346 
347 auto r = signature.first(rsSize);
348 auto s = signature.slice(rsSize, signature.size());
349 
350 // Trim leading zeros.
351 while (r.size() > 1 && r[0] == 0) r = r.slice(1, r.size());
352 while (s.size() > 1 && s[0] == 0) s = s.slice(1, s.size());
353 
354 // If the most significant bit is set, we have to add a zero, ugh.
355 bool padR = r[0] >= 128;
356 bool padS = s[0] >= 128;
357 
358 size_t bodySize = 4 + padR + padS + r.size() + s.size();
359 size_t resultSize = 2 + bodySize + (bodySize >= 128);
360 auto result = jsg::JsArrayBuffer::create(js, resultSize);
361 
362 kj::byte* pos = result.asArrayPtr().begin();
363 *pos++ = 0x30;
364 if (bodySize < 128) {
365 *pos++ = bodySize;
366 } else {
367 *pos++ = 129;
368 *pos++ = bodySize;
369 }
370 
371 *pos++ = 0x02;
372 *pos++ = r.size() + padR;
373 if (padR) *pos++ = 0;
374 memcpy(pos, r.begin(), r.size());
375 pos += r.size();
376 
377 *pos++ = 0x02;
378 *pos++ = s.size() + padS;
379 if (padS) *pos++ = 0;
380 memcpy(pos, s.begin(), s.size());
381 pos += s.size();
382 
383 KJ_ASSERT(pos == result.asArrayPtr().end());
384 
385 return result;
386 }
387 
388 static kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> generateElliptic(jsg::Lock& js,
389 kj::StringPtr normalizedName,
390 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
391 bool extractable,
392 CryptoKeyUsageSet privateKeyUsages,
393 CryptoKeyUsageSet publicKeyUsages);
394 
395 kj::StringPtr jsgGetMemoryName() const override {
396 return "EllipticKey";
397 }
398 size_t jsgGetMemorySelfSize() const override {
399 return sizeof(EllipticKey);
400 }
401 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {
402 AsymmetricKeyCryptoKeyImpl::jsgGetMemoryInfo(tracker);
403 tracker.trackField("keyAlgorithm", keyAlgorithm);
404 }
405 
406 private:
407 SubtleCrypto::JsonWebKey exportJwk() const override final {
408 auto ec = JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), DOMOperationError,
409 "No elliptic curve data backing key", tryDescribeOpensslErrors());
410 return ec.toJwk(getTypeEnum(), kj::str(keyAlgorithm.namedCurve));
411 }
412 
413 jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final {
414 JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError,
415 "Raw export of elliptic curve keys is only allowed for public keys.");
416 return JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), InternalDOMOperationError,
417 "No elliptic curve data backing key", tryDescribeOpensslErrors())
418 .getRawPublicKey(js);
419 }
420 
421 CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {
422 // Adapted from Node.js' GetEcKeyDetail
423 return KJ_ASSERT_NONNULL(Ec::tryGetEc(getEvpPkey())).getAsymmetricKeyDetail(js);
424 }
425 
426 CryptoKey::EllipticKeyAlgorithm keyAlgorithm;
427 uint rsSize;
428};
429 
430struct EllipticCurveInfo {
431 kj::StringPtr normalizedName;
432 int opensslCurveId;
433 uint rsSize; // size of "r" and "s" in the signature
434};
435 
436EllipticCurveInfo lookupEllipticCurve(kj::StringPtr curveName) {
437 static const std::map<kj::StringPtr, EllipticCurveInfo, CiLess> registeredCurves{
438 {"P-256", {"P-256", NID_X9_62_prime256v1, 32}},
439 {"P-384", {"P-384", NID_secp384r1, 48}},
440 {"P-521", {"P-521", NID_secp521r1, 66}},
441 };
442 
443 auto iter = registeredCurves.find(curveName);
444 JSG_REQUIRE(iter != registeredCurves.end(), DOMNotSupportedError,
445 "Unrecognized or unimplemented EC curve \"", curveName, "\" requested.");
446 return iter->second;
447}
448 
449kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> EllipticKey::generateElliptic(jsg::Lock& js,
450 kj::StringPtr normalizedName,
451 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
452 bool extractable,
453 CryptoKeyUsageSet privateKeyUsages,
454 CryptoKeyUsageSet publicKeyUsages) {
455 kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
456 algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");
457 
458 auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);
459 
460 auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{
461 normalizedName,
462 normalizedNamedCurve,
463 };
464 
465 // Used OpenBSD man pages starting with https://man.openbsd.org/ECDSA_SIG_new.3 for functions and
466 // CryptoKey::Impl::generateRsa as a template.
467 // https://stackoverflow.com/questions/18155559/how-does-one-access-the-raw-ecdh-public-key-private-key-and-params-inside-opens
468 // for the reference on how to deserialize the public/private key.
469 
470 auto ecPrivateKey =
471 OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), InternalDOMOperationError,
472 "Error generating EC \"", namedCurve, "\" key", internalDescribeOpensslErrors());
473 OSSLCALL(EC_KEY_generate_key(ecPrivateKey));
474 
475 auto privateEvpPKey = OSSL_NEW(EVP_PKEY);
476 OSSLCALL(EVP_PKEY_set1_EC_KEY(privateEvpPKey.get(), ecPrivateKey.get()));
477 
478 auto ecPublicKey =
479 OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), InternalDOMOperationError,
480 "Error generating EC \"", namedCurve, "\" key", internalDescribeOpensslErrors());
481 OSSLCALL(EC_KEY_set_public_key(ecPublicKey, EC_KEY_get0_public_key(ecPrivateKey)));
482 auto publicEvpPKey = OSSL_NEW(EVP_PKEY);
483 OSSLCALL(EVP_PKEY_set1_EC_KEY(publicEvpPKey.get(), ecPublicKey.get()));
484 
485 AsymmetricKeyData privateKeyData{
486 .evpPkey = kj::mv(privateEvpPKey),
487 .keyType = KeyType::PRIVATE,
488 .usages = privateKeyUsages,
489 };
490 AsymmetricKeyData publicKeyData{
491 .evpPkey = kj::mv(publicEvpPKey),
492 .keyType = KeyType::PUBLIC,
493 .usages = publicKeyUsages,
494 };
495 
496 auto privateKey = js.alloc<CryptoKey>(
497 kj::heap<EllipticKey>(kj::mv(privateKeyData), keyAlgorithm, rsSize, extractable));
498 auto publicKey =
499 js.alloc<CryptoKey>(kj::heap<EllipticKey>(kj::mv(publicKeyData), keyAlgorithm, rsSize, true));
500 
501 return CryptoKeyPair{.publicKey = kj::mv(publicKey), .privateKey = kj::mv(privateKey)};
502}
503 
504AsymmetricKeyData importEllipticRaw(SubtleCrypto::ImportKeyData keyData,
505 int curveId,
506 kj::StringPtr normalizedName,
507 kj::ArrayPtr<const kj::String> keyUsages,
508 CryptoKeyUsageSet allowedUsages) {
509 // Import an elliptic key represented by raw data, only public keys are supported.
510 JSG_REQUIRE(keyData.is<kj::Array<kj::byte>>(), DOMDataError,
511 "Expected raw EC key but instead got a Json Web Key.");
512 
513 const auto& raw = keyData.get<kj::Array<kj::byte>>();
514 
515 auto usages = CryptoKeyUsageSet::validate(
516 normalizedName, CryptoKeyUsageSet::Context::importPublic, keyUsages, allowedUsages);
517 
518 if (curveId == NID_ED25519 || curveId == NID_X25519) {
519 auto evpId = curveId == NID_X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519;
520 auto curveName = curveId == NID_X25519 ? "X25519" : "Ed25519";
521 
522 JSG_REQUIRE(raw.size() == 32, DOMDataError, curveName,
523 " raw keys must be exactly 32-bytes "
524 "(provided ",
525 raw.size(), ").");
526 
527 return {
528 OSSLCALL_OWN(EVP_PKEY, EVP_PKEY_new_raw_public_key(evpId, nullptr, raw.begin(), raw.size()),
529 InternalDOMOperationError, "Failed to import raw public EDDSA", raw.size(),
530 internalDescribeOpensslErrors()),
531 KeyType::PUBLIC, usages};
532 }
533 
534 auto ecKey = OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), DOMOperationError,
535 "Error importing EC key", tryDescribeOpensslErrors());
536 auto ecGroup = EC_KEY_get0_group(ecKey.get());
537 
538 auto point = OSSL_NEW(EC_POINT, ecGroup);
539 JSG_REQUIRE(1 == EC_POINT_oct2point(ecGroup, point.get(), raw.begin(), raw.size(), nullptr),
540 DOMDataError, "Failed to import raw EC key data", tryDescribeOpensslErrors());
541 JSG_REQUIRE(1 == EC_KEY_set_public_key(ecKey.get(), point.get()), InternalDOMOperationError,
542 "Failed to set EC raw public key", internalDescribeOpensslErrors());
543 JSG_REQUIRE(1 == EC_KEY_check_key(ecKey.get()), DOMDataError, "Invalid raw EC key provided",
544 tryDescribeOpensslErrors());
545 
546 auto evpPkey = OSSL_NEW(EVP_PKEY);
547 OSSLCALL(EVP_PKEY_set1_EC_KEY(evpPkey.get(), ecKey.get()));
548 
549 return AsymmetricKeyData{kj::mv(evpPkey), KeyType::PUBLIC, usages};
550}
551 
552kj::Own<EVP_PKEY> ellipticJwkReader(
553 int curveId, SubtleCrypto::JsonWebKey&& keyDataJwk, kj::StringPtr normalizedName) {
554 if (curveId == NID_ED25519 || curveId == NID_X25519) {
555 auto evpId = curveId == NID_X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519;
556 auto curveName = curveId == NID_X25519 ? "X25519" : "Ed25519";
557 
558 JSG_REQUIRE(keyDataJwk.kty == "OKP", DOMDataError, curveName,
559 " \"jwk\" key imports requires a JSON Web Key with Key Type parameter "
560 "\"kty\" (\"",
561 keyDataJwk.kty, "\") equal to \"OKP\".");
562 auto& crv = JSG_REQUIRE_NONNULL(
563 keyDataJwk.crv, DOMDataError, "Missing field \"crv\" for ", curveName, " key.");
564 JSG_REQUIRE(crv == curveName, DOMNotSupportedError, "Only ", curveName, " is supported but \"",
565 crv, "\" was requested.");
566 KJ_IF_SOME(alg, keyDataJwk.alg) {
567 // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via
568 // importKey().
569 if (curveId == NID_ED25519) {
570 JSG_REQUIRE(alg == "EdDSA", DOMDataError, "JSON Web Key Algorithm parameter \"alg\" (\"",
571 alg,
572 "\") does not match requested "
573 "Ed25519 curve.");
574 }
575 }
576 
577 auto x = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.x), DOMDataError, "Invalid ", crv,
578 " key in JSON WebKey; missing or invalid public key component (\"x\").");
579 JSG_REQUIRE(x.size() == 32, DOMDataError, "Invalid length ", x.size(), " for public key");
580 
581 if (keyDataJwk.d == kj::none) {
582 // This is a public key.
583 return OSSLCALL_OWN(EVP_PKEY,
584 EVP_PKEY_new_raw_public_key(evpId, nullptr, x.begin(), x.size()),
585 InternalDOMOperationError, "Failed to construct ", crv, " public key",
586 internalDescribeOpensslErrors());
587 }
588 
589 // This is a private key. The Section 2 of the RFC says...
590 // > The parameter "x" MUST be present and contain the public key encoded using the base64url
591 // > [RFC4648] encoding.
592 // https://tools.ietf.org/html/draft-ietf-jose-cfrg-curves-06
593 // ... but there's nothing really to do beside enforce that it's set? The NodeJS implementation
594 // seems to throw it away when a private key is provided.
595 
596 auto d = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.d), DOMDataError, "Invalid ", curveName,
597 " key in JSON Web Key; missing or invalid private key component (\"d\").");
598 JSG_REQUIRE(d.size() == 32, DOMDataError, "Invalid length ", d.size(), " for private key");
599 
600 return OSSLCALL_OWN(EVP_PKEY, EVP_PKEY_new_raw_private_key(evpId, nullptr, d.begin(), d.size()),
601 InternalDOMOperationError, "Failed to construct ", crv, " private key",
602 internalDescribeOpensslErrors());
603 }
604 
605 JSG_REQUIRE(keyDataJwk.kty == "EC", DOMDataError,
606 "Elliptic curve \"jwk\" key import requires a JSON Web Key with Key Type parameter "
607 "\"kty\" (\"",
608 keyDataJwk.kty, "\") equal to \"EC\".");
609 
610 if (normalizedName == "ECDSA") {
611 KJ_IF_SOME(alg, keyDataJwk.alg) {
612 // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via
613 // importKey().
614 static const std::map<kj::StringPtr, int> ecdsaAlgorithms{
615 {"ES256", NID_X9_62_prime256v1},
616 {"ES384", NID_secp384r1},
617 {"ES512", NID_secp521r1},
618 };
619 
620 auto iter = ecdsaAlgorithms.find(alg);
621 JSG_REQUIRE(iter != ecdsaAlgorithms.end(), DOMNotSupportedError,
622 "Unrecognized or unimplemented algorithm \"", alg,
623 "\" listed in JSON Web Key Algorithm parameter.");
624 
625 JSG_REQUIRE(iter->second == curveId, DOMDataError,
626 "JSON Web Key Algorithm parameter \"alg\" (\"", alg,
627 "\") does not match requested curve.");
628 }
629 }
630 
631 auto ecKey = OSSLCALL_OWN(EC_KEY, EC_KEY_new_by_curve_name(curveId), DOMOperationError,
632 "Error importing EC key", tryDescribeOpensslErrors());
633 
634 auto x = UNWRAP_JWK_BIGNUM(
635 kj::mv(keyDataJwk.x), DOMDataError, "Invalid EC key in JSON Web Key; missing \"x\".");
636 auto y = UNWRAP_JWK_BIGNUM(
637 kj::mv(keyDataJwk.y), DOMDataError, "Invalid EC key in JSON Web Key; missing \"y\".");
638 
639 auto group = EC_KEY_get0_group(ecKey);
640 
641 auto bigX = JSG_REQUIRE_NONNULL(toBignum(x), InternalDOMOperationError, "Error importing EC key",
642 internalDescribeOpensslErrors());
643 auto bigY = JSG_REQUIRE_NONNULL(toBignum(y), InternalDOMOperationError, "Error importing EC key",
644 internalDescribeOpensslErrors());
645 
646 auto point = OSSL_NEW(EC_POINT, group);
647 JSG_REQUIRE(1 == EC_POINT_set_affine_coordinates_GFp(group, point, bigX, bigY, nullptr),
648 DOMOperationError, "Invalid EC key; public key coordinates \"x\" and \"y\" are invalid",
649 tryDescribeOpensslErrors());
650 JSG_REQUIRE(1 == EC_KEY_set_public_key(ecKey, point), DOMOperationError,
651 "Invalid EC key; public key coordinates \"x\" and \"y\" are invalid",
652 tryDescribeOpensslErrors());
653 
654 if (keyDataJwk.d != kj::none) {
655 // This is a private key.
656 
657 auto d = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.d), DOMDataError,
658 "Invalid EC key in JSON Web Key; missing or invalid private key component (\"d\").");
659 
660 auto bigD = JSG_REQUIRE_NONNULL(toBignum(d), InternalDOMOperationError,
661 "Error importing EC key", internalDescribeOpensslErrors());
662 
663 JSG_REQUIRE(1 == EC_KEY_set_private_key(ecKey, bigD), DOMOperationError,
664 "Invalid EC key; "
665 "private key component \"d\" is invalid",
666 tryDescribeOpensslErrors());
667 }
668 
669 JSG_REQUIRE(1 == EC_KEY_check_key(ecKey.get()), DOMDataError, "Invalid EC key in JSON Web Key",
670 tryDescribeOpensslErrors());
671 
672 auto evpPkey = OSSL_NEW(EVP_PKEY);
673 JSG_REQUIRE(1 == EVP_PKEY_set1_EC_KEY(evpPkey.get(), ecKey.get()), DOMOperationError,
674 "Error importing EC key", tryDescribeOpensslErrors());
675 return evpPkey;
676}
677} // namespace
678 
679kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateEcdsa(jsg::Lock& js,
680 kj::StringPtr normalizedName,
681 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
682 bool extractable,
683 kj::ArrayPtr<const kj::String> keyUsages) {
684 auto usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate,
685 keyUsages, CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());
686 auto privateKeyUsages = usages & CryptoKeyUsageSet::privateKeyMask();
687 auto publicKeyUsages = usages & CryptoKeyUsageSet::publicKeyMask();
688 
689 return EllipticKey::generateElliptic(
690 js, normalizedName, kj::mv(algorithm), extractable, privateKeyUsages, publicKeyUsages);
691}
692 
693kj::Own<CryptoKey::Impl> CryptoKey::Impl::importEcdsa(jsg::Lock& js,
694 kj::StringPtr normalizedName,
695 kj::StringPtr format,
696 SubtleCrypto::ImportKeyData keyData,
697 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
698 bool extractable,
699 kj::ArrayPtr<const kj::String> keyUsages) {
700 kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
701 algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");
702 
703 auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);
704 
705 auto importedKey = [&, curveId = curveId] {
706 if (format != "raw") {
707 return importAsymmetricForWebCrypto(js, format, kj::mv(keyData), normalizedName, extractable,
708 keyUsages,
709 // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984
710 [curveId = curveId, normalizedName = kj::str(normalizedName)](
711 SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {
712 return ellipticJwkReader(curveId, kj::mv(keyDataJwk), normalizedName);
713 },
714 CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());
715 } else {
716 return importEllipticRaw(
717 kj::mv(keyData), curveId, normalizedName, keyUsages, CryptoKeyUsageSet::verify());
718 }
719 }();
720 
721 // get0 avoids adding a refcount...
722 auto ecKey = JSG_REQUIRE_NONNULL(Ec::tryGetEc(importedKey.evpPkey.get()), DOMDataError,
723 "Input was not an EC key", tryDescribeOpensslErrors());
724 
725 // Verify namedCurve matches what was specified in the key data.
726 JSG_REQUIRE(ecKey.getGroup() != nullptr && ecKey.getCurveName() == curveId, DOMDataError,
727 "\"algorithm.namedCurve\" \"", namedCurve,
728 "\" does not match the curve specified by the "
729 "input key data",
730 tryDescribeOpensslErrors());
731 
732 auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{
733 normalizedName,
734 normalizedNamedCurve,
735 };
736 
737 return kj::heap<EllipticKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), rsSize, extractable);
738}
739 
740kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateEcdh(jsg::Lock& js,
741 kj::StringPtr normalizedName,
742 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
743 bool extractable,
744 kj::ArrayPtr<const kj::String> keyUsages) {
745 auto usages = CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate,
746 keyUsages, CryptoKeyUsageSet::derivationKeyMask());
747 return EllipticKey::generateElliptic(
748 js, normalizedName, kj::mv(algorithm), extractable, usages, {});
749}
750 
751kj::Own<CryptoKey::Impl> CryptoKey::Impl::importEcdh(jsg::Lock& js,
752 kj::StringPtr normalizedName,
753 kj::StringPtr format,
754 SubtleCrypto::ImportKeyData keyData,
755 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
756 bool extractable,
757 kj::ArrayPtr<const kj::String> keyUsages) {
758 kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
759 algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");
760 
761 auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(namedCurve);
762 
763 auto importedKey = [&, curveId = curveId] {
764 auto strictCrypto = FeatureFlags::get(js).getStrictCrypto();
765 auto usageSet = strictCrypto ? CryptoKeyUsageSet() : CryptoKeyUsageSet::derivationKeyMask();
766 
767 if (format != "raw") {
768 return importAsymmetricForWebCrypto(js, format, kj::mv(keyData), normalizedName, extractable,
769 keyUsages,
770 // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984
771 [curveId = curveId, normalizedName = kj::str(normalizedName)](
772 SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {
773 return ellipticJwkReader(curveId, kj::mv(keyDataJwk), normalizedName);
774 },
775 CryptoKeyUsageSet::derivationKeyMask());
776 } else {
777 // The usage set is required to be empty for public ECDH keys, including raw keys.
778 return importEllipticRaw(kj::mv(keyData), curveId, normalizedName, keyUsages, usageSet);
779 }
780 }();
781 
782 auto ecKey = JSG_REQUIRE_NONNULL(Ec::tryGetEc(importedKey.evpPkey.get()), DOMDataError,
783 "Input was not an EC public key nor a DH key", tryDescribeOpensslErrors());
784 
785 // We ignore id-ecDH because BoringSSL doesn't implement this.
786 // https://bugs.chromium.org/p/chromium/issues/detail?id=532728
787 // https://bugs.chromium.org/p/chromium/issues/detail?id=389400
788 
789 // Verify namedCurve matches what was specified in the key data.
790 JSG_REQUIRE(ecKey.getGroup() != nullptr && ecKey.getCurveName() == curveId, DOMDataError,
791 "\"algorithm.namedCurve\" \"", namedCurve,
792 "\", does not match the curve "
793 "specified by the input key data",
794 tryDescribeOpensslErrors());
795 
796 auto keyAlgorithm = CryptoKey::EllipticKeyAlgorithm{
797 normalizedName,
798 normalizedNamedCurve,
799 };
800 
801 return kj::heap<EllipticKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), rsSize, extractable);
802}
803 
804// =====================================================================================
805// EDDSA & EDDH
806 
807namespace {
808 
809// Abstract base class for EDDSA and EDDH. The legacy NODE-ED25519 identifier for EDDSA has a
810// namedCurve field whereas the algorithms in the Secure Curves spec do not. We handle this by
811// keeping track of the algorithm identifier and returning an algorithm struct based on that.
812class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl {
813 public:
814 explicit EdDsaKey(AsymmetricKeyData keyData, kj::StringPtr keyAlgorithm, bool extractable)
815 : AsymmetricKeyCryptoKeyImpl(kj::mv(keyData), extractable),
816 keyAlgorithm(kj::mv(keyAlgorithm)) {}
817 
818 static kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> generateKey(jsg::Lock& js,
819 kj::StringPtr normalizedName,
820 int nid,
821 CryptoKeyUsageSet privateKeyUsages,
822 CryptoKeyUsageSet publicKeyUsages,
823 bool extractablePrivateKey);
824 
825 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
826 // For legacy node-based keys with NODE-ED25519, algorithm contains a namedCurve field.
827 if (keyAlgorithm == "NODE-ED25519") {
828 return CryptoKey::EllipticKeyAlgorithm{
829 keyAlgorithm,
830 keyAlgorithm,
831 };
832 } else {
833 return CryptoKey::KeyAlgorithm{keyAlgorithm};
834 }
835 }
836 
837 kj::StringPtr getAlgorithmName() const override {
838 return keyAlgorithm;
839 }
840 
841 kj::StringPtr chooseHash(
842 const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)
843 const override {
844 KJ_UNIMPLEMENTED();
845 }
846 
847 jsg::JsArrayBuffer sign(jsg::Lock& js,
848 SubtleCrypto::SignAlgorithm&& algorithm,
849 kj::ArrayPtr<const kj::byte> data) const override {
850 JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,
851 "Asymmetric signing requires a private key.");
852 
853 JSG_REQUIRE(getAlgorithmName() == "Ed25519" || getAlgorithmName() == "NODE-ED25519",
854 DOMOperationError, "Not implemented for algorithm \"", getAlgorithmName(), "\".");
855 // Why NODE-ED25519? NodeJS uses NODE-ED25519/NODE-448 as algorithm names but that feels
856 // inconsistent with the broader WebCrypto standard. Filed an issue with the standard for
857 // clarification: https://github.com/tQsW/webcrypto-curve25519/issues/7
858 
859 auto signature = jsg::JsArrayBuffer::create(js, ED25519_SIGNATURE_LEN);
860 size_t signatureLength = signature.size();
861 
862 // NOTE: Even though there's a ED25519_sign/ED25519_verify methods, they don't actually seem to
863 // work or are intended for some other use-case. I tried adding the verify immediately after
864 // signing here & the verification failed.
865 auto digestCtx = OSSL_NEW(EVP_MD_CTX);
866 
867 JSG_REQUIRE(1 == EVP_DigestSignInit(digestCtx.get(), nullptr, nullptr, nullptr, getEvpPkey()),
868 DOMOperationError, "Failed to initialize Ed25519 signing digest",
869 tryDescribeOpensslErrors());
870 JSG_REQUIRE(1 ==
871 EVP_DigestSign(digestCtx.get(), signature.asArrayPtr().begin(), &signatureLength,
872 data.begin(), data.size()),
873 DOMOperationError, "Failed to sign with Ed25119 key", tryDescribeOpensslErrors());
874 
875 JSG_REQUIRE(signatureLength == signature.size(), InternalDOMOperationError,
876 "Unexpected change in size signing Ed25519", signatureLength);
877 
878 return signature;
879 }
880 
881 bool verify(jsg::Lock& js,
882 SubtleCrypto::SignAlgorithm&& algorithm,
883 kj::ArrayPtr<const kj::byte> signature,
884 kj::ArrayPtr<const kj::byte> data) const override {
885 ClearErrorOnReturn clearErrorOnReturn;
886 
887 JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError,
888 "Asymmetric verification requires a public key.");
889 
890 JSG_REQUIRE(getAlgorithmName() == "Ed25519" || getAlgorithmName() == "NODE-ED25519",
891 DOMOperationError, "Not implemented for this algorithm", getAlgorithmName());
892 
893 JSG_REQUIRE(signature.size() == ED25519_SIGNATURE_LEN, DOMOperationError, "Invalid ",
894 getAlgorithmName(), " signature length ", signature.size());
895 
896 auto digestCtx = OSSL_NEW(EVP_MD_CTX);
897 JSG_REQUIRE(1 == EVP_DigestSignInit(digestCtx.get(), nullptr, nullptr, nullptr, getEvpPkey()),
898 DOMOperationError, "Failed to initialize Ed25519 verification digest",
899 tryDescribeOpensslErrors());
900 
901 auto result = EVP_DigestVerify(
902 digestCtx.get(), signature.begin(), signature.size(), data.begin(), data.size());
903 
904 JSG_REQUIRE(result == 0 || result == 1, InternalDOMOperationError, "Unexpected return code",
905 result, internalDescribeOpensslErrors());
906 
907 return !!result;
908 }
909 
910 jsg::JsArrayBuffer deriveBits(jsg::Lock& js,
911 SubtleCrypto::DeriveKeyAlgorithm&& algorithm,
912 kj::Maybe<uint32_t> resultBitLength) const override final {
913 JSG_REQUIRE(getAlgorithmName() == "X25519", DOMNotSupportedError,
914 ""
915 "The deriveBits operation is not implemented for \"",
916 getAlgorithmName(), "\".");
917 
918 JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,
919 ""
920 "The deriveBits operation is only valid for a private key, not \"",
921 getType(), "\".");
922 
923 auto& publicKey = JSG_REQUIRE_NONNULL(
924 algorithm.$public, TypeError, "Missing field \"public\" in \"derivedKeyParams\".");
925 
926 JSG_REQUIRE(publicKey->getType() == "public"_kj, DOMInvalidAccessError,
927 ""
928 "The provided key has type \"",
929 publicKey->getType(), "\", not \"public\"");
930 
931 JSG_REQUIRE(getAlgorithm(js).which() == publicKey->getAlgorithm(js).which(),
932 DOMInvalidAccessError, "Base ", getAlgorithmName(),
933 " private key cannot be used to derive"
934 " a key from a peer ",
935 publicKey->getAlgorithmName(), " public key");
936 
937 JSG_REQUIRE(getAlgorithmName() == publicKey->getAlgorithmName(), DOMInvalidAccessError,
938 "Private key for derivation is using \"", getAlgorithmName(),
939 "\" while public key is using \"", publicKey->getAlgorithmName(), "\".");
940 
941 auto outputBitLength = resultBitLength.orDefault(X25519_SHARED_KEY_LEN * 8);
942 JSG_REQUIRE(outputBitLength <= X25519_SHARED_KEY_LEN * 8, DOMOperationError,
943 "Derived key length (", outputBitLength, " bits) is too long (should be at most ",
944 X25519_SHARED_KEY_LEN * 8, " bits).");
945 
946 // The check above for the algorithm `which` equality ensures that the impl can be downcast to
947 // EdDsaKey (assuming we don't accidentally create a class that doesn't inherit this one that
948 // for some reason returns an EdDsaKey).
949 auto& publicKeyImpl = kj::downcast<EdDsaKey>(*publicKey->impl);
950 
951 // EDDH code derived from https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_derive.html
952 auto ctx = OSSL_NEW(EVP_PKEY_CTX, getEvpPkey(), nullptr);
953 JSG_REQUIRE(1 == EVP_PKEY_derive_init(ctx), InternalDOMOperationError,
954 "Failed to init EDDH key derivation", internalDescribeOpensslErrors());
955 JSG_REQUIRE(1 == EVP_PKEY_derive_set_peer(ctx, publicKeyImpl.getEvpPkey()),
956 InternalDOMOperationError, "Failed to set EDDH peer", internalDescribeOpensslErrors());
957 
958 kj::Vector<kj::byte> sharedSecret;
959 sharedSecret.resize(X25519_SHARED_KEY_LEN);
960 size_t skeylen = X25519_SHARED_KEY_LEN;
961 JSG_REQUIRE(1 == EVP_PKEY_derive(ctx, sharedSecret.begin(), &skeylen), DOMOperationError,
962 "Failed to derive EDDH key", internalDescribeOpensslErrors());
963 KJ_ASSERT(skeylen == X25519_SHARED_KEY_LEN);
964 
965 // Check for all-zero value as mandated by spec
966 kj::byte isNonZeroSecret = 0;
967 for (kj::byte b: sharedSecret) {
968 isNonZeroSecret |= b;
969 }
970 JSG_REQUIRE(isNonZeroSecret, DOMOperationError,
971 "Detected small order secure curve points, aborting EDDH derivation");
972 
973 // mask off bits like in ECDH's deriveBits()
974 auto resultByteLength = integerCeilDivision(outputBitLength, 8u);
975 sharedSecret.truncate(resultByteLength);
976 auto numBitsToMaskOff = resultByteLength * 8 - outputBitLength;
977 KJ_DASSERT(numBitsToMaskOff < 8, numBitsToMaskOff);
978 
979 if (numBitsToMaskOff) {
980 uint8_t mask = ~((1 << numBitsToMaskOff) - 1);
981 sharedSecret.back() &= mask;
982 }
983 
984 return jsg::JsArrayBuffer::create(js, sharedSecret.asPtr());
985 }
986 
987 CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {
988 // Node.js implementation for EdDsa keys currently does not provide any detail
989 return CryptoKey::AsymmetricKeyDetails{};
990 }
991 
992 kj::StringPtr jsgGetMemoryName() const override {
993 return "EdDsaKey";
994 }
995 size_t jsgGetMemorySelfSize() const override {
996 return sizeof(EdDsaKey);
997 }
998 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {
999 AsymmetricKeyCryptoKeyImpl::jsgGetMemoryInfo(tracker);
1000 }
1001 
1002 private:
1003 kj::StringPtr keyAlgorithm;
1004 
1005 SubtleCrypto::JsonWebKey exportJwk() const override final {
1006 KJ_ASSERT(getAlgorithmName() == "X25519"_kj || getAlgorithmName() == "Ed25519"_kj ||
1007 getAlgorithmName() == "NODE-ED25519"_kj);
1008 
1009 uint8_t rawPublicKey[ED25519_PUBLIC_KEY_LEN]{};
1010 size_t publicKeyLen = sizeof(rawPublicKey);
1011 JSG_REQUIRE(1 == EVP_PKEY_get_raw_public_key(getEvpPkey(), rawPublicKey, &publicKeyLen),
1012 InternalDOMOperationError, "Failed to retrieve public key",
1013 internalDescribeOpensslErrors());
1014 
1015 KJ_ASSERT(publicKeyLen == 32, publicKeyLen);
1016 
1017 SubtleCrypto::JsonWebKey jwk;
1018 jwk.kty = kj::str("OKP");
1019 jwk.crv = kj::str(getAlgorithmName() == "X25519"_kj ? "X25519"_kj : "Ed25519"_kj);
1020 jwk.x = fastEncodeBase64Url(kj::arrayPtr(rawPublicKey, publicKeyLen));
1021 if (getAlgorithmName() == "Ed25519"_kj) {
1022 jwk.alg = kj::str("EdDSA");
1023 }
1024 
1025 if (getTypeEnum() == KeyType::PRIVATE) {
1026 // Deliberately use ED25519_PUBLIC_KEY_LEN here.
1027 // BoringSSL defines ED25519_PRIVATE_KEY_LEN as 64B since it stores the private key together
1028 // with public key data in some functions, but in the EVP interface only the 32B private key
1029 // itself is returned.
1030 uint8_t rawPrivateKey[ED25519_PUBLIC_KEY_LEN]{};
1031 size_t privateKeyLen = ED25519_PUBLIC_KEY_LEN;
1032 JSG_REQUIRE(1 == EVP_PKEY_get_raw_private_key(getEvpPkey(), rawPrivateKey, &privateKeyLen),
1033 InternalDOMOperationError, "Failed to retrieve private key",
1034 internalDescribeOpensslErrors());
1035 
1036 KJ_ASSERT(privateKeyLen == 32, privateKeyLen);
1037 
1038 jwk.d = fastEncodeBase64Url(kj::arrayPtr(rawPrivateKey, privateKeyLen));
1039 OPENSSL_cleanse(rawPrivateKey, sizeof(rawPrivateKey));
1040 }
1041 
1042 return jwk;
1043 }
1044 
1045 jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final {
1046 JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, "Raw export of ",
1047 getAlgorithmName(), " keys is only allowed for public keys.");
1048 
1049 auto raw = jsg::JsArrayBuffer::create(js, ED25519_PUBLIC_KEY_LEN);
1050 size_t exportedLength = raw.size();
1051 
1052 JSG_REQUIRE(
1053 1 == EVP_PKEY_get_raw_public_key(getEvpPkey(), raw.asArrayPtr().begin(), &exportedLength),
1054 InternalDOMOperationError, "Failed to retrieve public key",
1055 internalDescribeOpensslErrors());
1056 
1057 JSG_REQUIRE(exportedLength == raw.size(), InternalDOMOperationError,
1058 "Unexpected change in size", raw.size(), exportedLength);
1059 
1060 return raw;
1061 }
1062};
1063 
1064template <size_t keySize, void (*KeypairInit)(uint8_t[keySize], uint8_t[keySize * 2])>
1065CryptoKeyPair generateKeyImpl(jsg::Lock& js,
1066 kj::StringPtr normalizedName,
1067 int nid,
1068 CryptoKeyUsageSet privateKeyUsages,
1069 CryptoKeyUsageSet publicKeyUsages,
1070 bool extractablePrivateKey,
1071 kj::StringPtr curveName) {
1072 uint8_t rawPublicKey[keySize] = {0};
1073 uint8_t rawPrivateKey[keySize * 2] = {0};
1074 KeypairInit(rawPublicKey, rawPrivateKey);
1075 KJ_DEFER(OPENSSL_cleanse(rawPrivateKey, sizeof(rawPrivateKey)));
1076 
1077 // The private key technically also contains the public key. Why does the keypair function bother
1078 // writing out the public key to a separate buffer?
1079 
1080 auto privateEvpPKey = OSSLCALL_OWN(EVP_PKEY,
1081 EVP_PKEY_new_raw_private_key(nid, nullptr, rawPrivateKey, keySize), InternalDOMOperationError,
1082 "Error constructing ", curveName, " private key", internalDescribeOpensslErrors());
1083 
1084 auto publicEvpPKey = OSSLCALL_OWN(EVP_PKEY,
1085 EVP_PKEY_new_raw_public_key(nid, nullptr, rawPublicKey, keySize), InternalDOMOperationError,
1086 "Internal error construct ", curveName, "public key", internalDescribeOpensslErrors());
1087 
1088 AsymmetricKeyData privateKeyData{
1089 .evpPkey = kj::mv(privateEvpPKey),
1090 .keyType = KeyType::PRIVATE,
1091 .usages = privateKeyUsages,
1092 };
1093 AsymmetricKeyData publicKeyData{
1094 .evpPkey = kj::mv(publicEvpPKey),
1095 .keyType = KeyType::PUBLIC,
1096 .usages = publicKeyUsages,
1097 };
1098 
1099 auto privateKey = js.alloc<CryptoKey>(
1100 kj::heap<EdDsaKey>(kj::mv(privateKeyData), normalizedName, extractablePrivateKey));
1101 auto publicKey =
1102 js.alloc<CryptoKey>(kj::heap<EdDsaKey>(kj::mv(publicKeyData), normalizedName, true));
1103 
1104 return CryptoKeyPair{.publicKey = kj::mv(publicKey), .privateKey = kj::mv(privateKey)};
1105}
1106 
1107kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> EdDsaKey::generateKey(jsg::Lock& js,
1108 kj::StringPtr normalizedName,
1109 int nid,
1110 CryptoKeyUsageSet privateKeyUsages,
1111 CryptoKeyUsageSet publicKeyUsages,
1112 bool extractablePrivateKey) {
1113 switch (nid) {
1114 // BoringSSL doesn't support ED448/X448.
1115 case NID_ED25519:
1116 return generateKeyImpl<ED25519_PUBLIC_KEY_LEN, ED25519_keypair>(js, normalizedName, nid,
1117 privateKeyUsages, publicKeyUsages, extractablePrivateKey, "Ed25519"_kj);
1118 case NID_X25519:
1119 return generateKeyImpl<X25519_PUBLIC_VALUE_LEN, X25519_keypair>(js, normalizedName, nid,
1120 privateKeyUsages, publicKeyUsages, extractablePrivateKey, "X25519"_kj);
1121 }
1122 
1123 KJ_FAIL_REQUIRE("ED ", normalizedName, " unimplemented", nid);
1124}
1125 
1126} // namespace
1127 
1128kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateEddsa(jsg::Lock& js,
1129 kj::StringPtr normalizedName,
1130 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
1131 bool extractable,
1132 kj::ArrayPtr<const kj::String> keyUsages) {
1133 auto usages =
1134 CryptoKeyUsageSet::validate(normalizedName, CryptoKeyUsageSet::Context::generate, keyUsages,
1135 normalizedName == "X25519" ? CryptoKeyUsageSet::derivationKeyMask()
1136 : CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());
1137 auto privateKeyUsages = usages & CryptoKeyUsageSet::privateKeyMask();
1138 auto publicKeyUsages = usages & CryptoKeyUsageSet::publicKeyMask();
1139 
1140 if (normalizedName == "NODE-ED25519") {
1141 kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
1142 algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");
1143 JSG_REQUIRE(namedCurve == "NODE-ED25519", DOMNotSupportedError, "EDDSA curve \"", namedCurve,
1144 "\" isn't supported.");
1145 }
1146 
1147 return EdDsaKey::generateKey(js, normalizedName,
1148 normalizedName == "X25519" ? NID_X25519 : NID_ED25519, privateKeyUsages, publicKeyUsages,
1149 extractable);
1150}
1151 
1152kj::Own<CryptoKey::Impl> CryptoKey::Impl::importEddsa(jsg::Lock& js,
1153 kj::StringPtr normalizedName,
1154 kj::StringPtr format,
1155 SubtleCrypto::ImportKeyData keyData,
1156 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
1157 bool extractable,
1158 kj::ArrayPtr<const kj::String> keyUsages) {
1159 
1160 // BoringSSL doesn't support ED448.
1161 if (normalizedName == "NODE-ED25519") {
1162 // TODO: I prefer this style (declaring variables within the scope where they are needed) –
1163 // does KJ style want this to be done differently?
1164 kj::StringPtr namedCurve = JSG_REQUIRE_NONNULL(
1165 algorithm.namedCurve, TypeError, "Missing field \"namedCurve\" in \"algorithm\".");
1166 JSG_REQUIRE(namedCurve == "NODE-ED25519", DOMNotSupportedError, "EDDSA curve \"", namedCurve,
1167 "\" isn't supported.");
1168 }
1169 
1170 auto importedKey = [&] {
1171 auto nid = normalizedName == "X25519" ? NID_X25519 : NID_ED25519;
1172 if (format != "raw") {
1173 return importAsymmetricForWebCrypto(js, format, kj::mv(keyData), normalizedName, extractable,
1174 keyUsages,
1175 [nid, normalizedName = kj::str(normalizedName)](
1176 SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {
1177 return ellipticJwkReader(nid, kj::mv(keyDataJwk), normalizedName);
1178 },
1179 normalizedName == "X25519" ? CryptoKeyUsageSet::derivationKeyMask()
1180 : CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());
1181 } else {
1182 return importEllipticRaw(kj::mv(keyData), nid, normalizedName, keyUsages,
1183 normalizedName == "X25519" ? CryptoKeyUsageSet() : CryptoKeyUsageSet::verify());
1184 }
1185 }();
1186 
1187 // In X25519 we ignore the id-X25519 identifier, as with id-ecDH above.
1188 return kj::heap<EdDsaKey>(kj::mv(importedKey), normalizedName, extractable);
1189}
1190 
1191kj::Own<CryptoKey::Impl> fromEcKey(kj::Own<EVP_PKEY> key) {
1192 auto nid = EVP_PKEY_id(key.get());
1193 if (nid == NID_X25519 || nid == NID_ED25519) {
1194 return fromEd25519Key(kj::mv(key));
1195 }
1196 
1197 // EVP_PKEY_id() returns the key type NID (e.g. EVP_PKEY_EC / "id-ecPublicKey"), not the curve
1198 // NID. We must extract the actual named curve from the EC key's group instead.
1199 auto ec = EVP_PKEY_get0_EC_KEY(key.get());
1200 KJ_ASSERT(ec != nullptr, "Expected an EC key");
1201 auto group = EC_KEY_get0_group(ec);
1202 KJ_ASSERT(group != nullptr, "EC key has no group");
1203 auto curveNid = EC_GROUP_get_curve_name(group);
1204 
1205 // Prefer NIST names ("P-256", "P-384", "P-521") since that is what lookupEllipticCurve()
1206 // recognizes, falling back to the short OID name for other curves.
1207 auto curveName = EC_curve_nid2nist(curveNid);
1208 if (curveName == nullptr) {
1209 curveName = OBJ_nid2sn(curveNid);
1210 }
1211 if (curveName == nullptr) {
1212 curveName = "unknown";
1213 }
1214 
1215 auto [normalizedNamedCurve, curveId, rsSize] = lookupEllipticCurve(curveName);
1216 
1217 return kj::heap<EllipticKey>(
1218 AsymmetricKeyData{
1219 .evpPkey = kj::mv(key),
1220 .keyType = KeyType::PUBLIC,
1221 .usages = CryptoKeyUsageSet::verify(),
1222 },
1223 CryptoKey::EllipticKeyAlgorithm{.name = "ECDSA"_kj, .namedCurve = normalizedNamedCurve},
1224 rsSize, true);
1225}
1226 
1227kj::Own<CryptoKey::Impl> fromEd25519Key(kj::Own<EVP_PKEY> key) {
1228 return kj::heap<EdDsaKey>(
1229 AsymmetricKeyData{
1230 .evpPkey = kj::mv(key),
1231 .keyType = KeyType::PUBLIC,
1232 .usages = CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify(),
1233 },
1234 "Ed25519"_kj, true);
1235}
1236} // namespace workerd::api