Skip to content
File

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

44.8 KB
1#include "rsa.h"
2 
3#include "impl.h"
4#include "keys.h"
5#include "util.h"
6 
7#include <openssl/bn.h>
8#include <openssl/crypto.h>
9#include <openssl/evp.h>
10#include <openssl/pem.h>
11 
12#include <kj/array.h>
13#include <kj/common.h>
14 
15#include <map>
16 
17namespace workerd::api {
18 
19namespace {
20template <typename T>
21kj::Maybe<T> fromBignum(kj::ArrayPtr<kj::byte> value) {
22 static_assert(std::is_unsigned_v<T>, "This can only be invoked when the return type is unsigned");
23 
24 T asUnsigned = 0;
25 for (size_t i = 0; i < value.size(); ++i) {
26 size_t bitShift = value.size() - i - 1;
27 if (bitShift >= sizeof(T) && value[i]) {
28 // Too large for desired type.
29 return kj::none;
30 }
31 
32 asUnsigned |= value[i] << 8 * bitShift;
33 }
34 
35 return asUnsigned;
36}
37 
38jsg::JsArrayBuffer bioToArray(jsg::Lock& js, BIO* bio) {
39 BUF_MEM* bptr;
40 BIO_get_mem_ptr(bio, &bptr);
41 return jsg::JsArrayBuffer::create(js, kj::asBytes(bptr->data, bptr->length));
42}
43} // namespace
44 
45kj::Maybe<Rsa> Rsa::tryGetRsa(const EVP_PKEY* key) {
46 int type = EVP_PKEY_id(key);
47 if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return kj::none;
48 auto rsa = EVP_PKEY_get0_RSA(key);
49 if (rsa == nullptr) return kj::none;
50 return Rsa(rsa);
51}
52 
53Rsa::Rsa(RSA* rsa): rsa(rsa) {
54 RSA_get0_key(rsa, &n, &e, &d);
55}
56 
57size_t Rsa::getModulusBits() const {
58 return getModulusSize() * 8;
59}
60 
61size_t Rsa::getModulusSize() const {
62 return RSA_size(rsa);
63}
64 
65jsg::JsUint8Array Rsa::getPublicExponent(jsg::Lock& js) {
66 return KJ_REQUIRE_NONNULL(bignumToArray(js, *e));
67}
68 
69CryptoKey::AsymmetricKeyDetails Rsa::getAsymmetricKeyDetail(jsg::Lock& js) const {
70 CryptoKey::AsymmetricKeyDetails details;
71 
72 details.modulusLength = BN_num_bits(n);
73 auto pubExp =
74 JSG_REQUIRE_NONNULL(bignumToArrayPadded(js, *e), Error, "Failed to extract public exponent");
75 auto ab = jsg::JsArrayBuffer::create(js, pubExp.asArrayPtr());
76 details.publicExponent = ab.addRef(js);
77 
78 // TODO(soon): Does BoringSSL not support retrieving RSA_PSS params?
79 // if (type == EVP_PKEY_RSA_PSS) {
80 // // Due to the way ASN.1 encoding works, default values are omitted when
81 // // encoding the data structure. However, there are also RSA-PSS keys for
82 // // which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params
83 // // sequence will be missing entirely and RSA_get0_pss_params will return
84 // // nullptr. If parameters are present but all parameters are set to their
85 // // default values, an empty sequence will be stored in the ASN.1 structure.
86 // // In that case, RSA_get0_pss_params does not return nullptr but all fields
87 // // of the returned RSA_PSS_PARAMS will be set to nullptr.
88 
89 // const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa);
90 // if (params != nullptr) {
91 // int hash_nid = NID_sha1;
92 // int mgf_nid = NID_mgf1;
93 // int mgf1_hash_nid = NID_sha1;
94 // int64_t salt_length = 20;
95 
96 // if (params->hashAlgorithm != nullptr) {
97 // hash_nid = OBJ_obj2nid(params->hashAlgorithm->algorithm);
98 // }
99 // details.hashAlgorithm = kj::str(OBJ_nid2ln(hash_nid));
100 
101 // if (params->maskGenAlgorithm != nullptr) {
102 // mgf_nid = OBJ_obj2nid(params->maskGenAlgorithm->algorithm);
103 // if (mgf_nid == NID_mgf1) {
104 // mgf1_hash_nid = OBJ_obj2nid(params->maskHash->algorithm);
105 // }
106 // }
107 
108 // // If, for some reason, the MGF is not MGF1, then the MGF1 hash function
109 // // is intentionally not added to the object.
110 // if (mgf_nid == NID_mgf1) {
111 // details.mgf1HashAlgorithm = kj::str(OBJ_nid2ln(mgf1_hash_nid));
112 // }
113 
114 // if (params->saltLength != nullptr) {
115 // JSG_REQUIRE(ASN1_INTEGER_get_int64(&salt_length, params->saltLength) == 1,
116 // Error, "Unable to get salt length from RSA-PSS parameters");
117 // }
118 // details.saltLength = static_cast<double>(salt_length);
119 // }
120 // }
121 
122 return kj::mv(details);
123}
124 
125jsg::JsArrayBuffer Rsa::sign(jsg::Lock& js, const kj::ArrayPtr<const kj::byte> data) const {
126 size_t size = getModulusSize();
127 
128 // RSA encryption/decryption requires the key value to be strictly larger than the value to be
129 // signed. Ideally we would enforce this by checking that the key size is larger than the input
130 // size – having both the same size makes it highly likely that some values are higher than the
131 // key value – but there are scripts and test cases that depend on signing data with keys of
132 // the same size.
133 JSG_REQUIRE(data.size() <= size, DOMDataError, "Blind Signing requires presigned data (",
134 data.size(),
135 " bytes) to be smaller than "
136 "the key (",
137 size, " bytes).");
138 if (data.size() == size) {
139 auto dataVal = JSG_REQUIRE_NONNULL(toBignum(data), InternalDOMOperationError,
140 "Error converting presigned data", internalDescribeOpensslErrors());
141 JSG_REQUIRE(BN_ucmp(dataVal, getN()) < 0, DOMDataError,
142 "Blind Signing requires presigned data value to be strictly smaller than RSA key"
143 "modulus, consider using a larger key size.");
144 }
145 
146 KJ_STACK_ARRAY(kj::byte, signature, size, 256, 256);
147 size_t signatureSize = 0;
148 OSSLCALL(RSA_decrypt(rsa, &signatureSize, signature.begin(), signature.size(), data.begin(),
149 data.size(), RSA_NO_PADDING));
150 KJ_ASSERT(signatureSize <= signature.size());
151 
152 return jsg::JsArrayBuffer::create(js, signature.first(signatureSize));
153}
154 
155jsg::JsArrayBuffer Rsa::cipher(jsg::Lock& js,
156 EVP_PKEY_CTX* ctx,
157 SubtleCrypto::EncryptAlgorithm&& algorithm,
158 kj::ArrayPtr<const kj::byte> data,
159 EncryptDecryptFunction encryptDecrypt,
160 const EVP_MD* digest) const {
161 
162 JSG_REQUIRE(1 == EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING),
163 InternalDOMOperationError, "Error doing RSA OAEP encrypt/decrypt (", "padding", ")",
164 internalDescribeOpensslErrors());
165 JSG_REQUIRE(1 == EVP_PKEY_CTX_set_rsa_oaep_md(ctx, digest), InternalDOMOperationError,
166 "Error doing RSA OAEP encrypt/decrypt (", "message digest", ")",
167 internalDescribeOpensslErrors());
168 JSG_REQUIRE(1 == EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, digest), InternalDOMOperationError,
169 "Error doing RSA OAEP encrypt/decrypt (", "MGF1 digest", ")",
170 internalDescribeOpensslErrors());
171 
172 KJ_IF_SOME(lRef, algorithm.label) {
173 auto l = lRef.getHandle(js);
174 auto labelCopy = reinterpret_cast<uint8_t*>(OPENSSL_malloc(l.size()));
175 KJ_DEFER(OPENSSL_free(labelCopy));
176 // If setting the label fails we need to remember to destroy the buffer. In practice it can't
177 // actually happen since we set RSA_PKCS1_OAEP_PADDING above & that appears to be the only way
178 // this API call can fail.
179 
180 JSG_REQUIRE(labelCopy != nullptr, DOMOperationError,
181 "Failed to allocate space for RSA-OAEP label copy", tryDescribeOpensslErrors());
182 kj::arrayPtr(labelCopy, l.size()).copyFrom(l.asArrayPtr());
183 
184 // EVP_PKEY_CTX_set0_rsa_oaep_label below takes ownership of the buffer passed in (must have
185 // been OPENSSL_malloc-allocated).
186 JSG_REQUIRE(1 == EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, labelCopy, l.size()), DOMOperationError,
187 "Failed to set RSA-OAEP label", tryDescribeOpensslErrors());
188 
189 // Ownership has now been transferred. The chromium WebCrypto code technically has a potential
190 // memory leak here in that they check the error for EVP_PKEY_CTX_set0_rsa_oaep_label after
191 // releasing. It's not actually possible though because the padding mode is set unconditionally
192 // to RSA_PKCS1_OAEP_PADDING which seems to be the only way setting the label will fail.
193 labelCopy = nullptr;
194 }
195 
196 size_t maxResultLength = 0;
197 // First compute an upper bound on the amount of space we need to store the encrypted/decrypted
198 // result. Then we actually apply the encryption & finally resize to the actual correct length.
199 JSG_REQUIRE(1 == encryptDecrypt(ctx, nullptr, &maxResultLength, data.begin(), data.size()),
200 DOMOperationError, "Failed to compute length of RSA-OAEP result", tryDescribeOpensslErrors());
201 
202 kj::Vector<kj::byte> result(maxResultLength);
203 auto err = encryptDecrypt(ctx, result.begin(), &maxResultLength, data.begin(), data.size());
204 JSG_REQUIRE(
205 1 == err, DOMOperationError, "RSA-OAEP failed encrypt/decrypt", tryDescribeOpensslErrors());
206 result.resize(maxResultLength);
207 
208 return jsg::JsArrayBuffer::create(js, result.asPtr());
209}
210 
211SubtleCrypto::JsonWebKey Rsa::toJwk(
212 KeyType keyType, kj::Maybe<kj::String> maybeHashAlgorithm) const {
213 SubtleCrypto::JsonWebKey jwk;
214 jwk.kty = kj::str("RSA");
215 KJ_IF_SOME(name, maybeHashAlgorithm) {
216 jwk.alg = kj::mv(name);
217 }
218 
219 jwk.n = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(n))));
220 jwk.e = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(e))));
221 
222 if (keyType == KeyType::PRIVATE) {
223 jwk.d = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(d))));
224 jwk.p =
225 fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_p(rsa)))));
226 jwk.q =
227 fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_q(rsa)))));
228 jwk.dp = fastEncodeBase64Url(
229 KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_dmp1(rsa)))));
230 jwk.dq = fastEncodeBase64Url(
231 KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_dmq1(rsa)))));
232 jwk.qi = fastEncodeBase64Url(
233 KJ_REQUIRE_NONNULL(bignumToArray(KJ_REQUIRE_NONNULL(RSA_get0_iqmp(rsa)))));
234 }
235 
236 return jwk;
237}
238 
239kj::Maybe<AsymmetricKeyData> Rsa::fromJwk(
240 jsg::Lock& js, KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) {
241 ClearErrorOnReturn clearErrorOnReturn;
242 
243 if (jwk.kty != "RSA"_kj) return kj::none;
244 auto n = JSG_REQUIRE_NONNULL(jwk.n.map([](auto& str) { return str.asPtr(); }), Error,
245 "Invalid RSA key in JSON Web Key; missing or invalid "
246 "Modulus parameter (\"n\").");
247 auto e = JSG_REQUIRE_NONNULL(jwk.e.map([](auto& str) { return str.asPtr(); }), Error,
248 "Invalid RSA key in JSON Web Key; missing or invalid "
249 "Exponent parameter (\"e\").");
250 
251 auto rsa = OSSL_NEW(RSA);
252 
253 static constexpr auto kInvalidBase64Error = "Invalid RSA key in JSON Web Key; invalid base64."_kj;
254 
255 auto nBuf = simdutfBase64UrlDecodeChecked(js, n, kInvalidBase64Error);
256 auto nDecoded = toBignumOwned(nBuf.asArrayPtr());
257 auto eBuf = simdutfBase64UrlDecodeChecked(js, e, kInvalidBase64Error);
258 auto eDecoded = toBignumOwned(eBuf.asArrayPtr());
259 JSG_REQUIRE(RSA_set0_key(rsa.get(), nDecoded.get(), eDecoded.get(), nullptr) == 1, Error,
260 "Invalid RSA key in JSON Web Key; failed to set key parameters");
261 nDecoded.release();
262 eDecoded.release();
263 
264 if (keyType == KeyType::PRIVATE) {
265 auto d = JSG_REQUIRE_NONNULL(jwk.d.map([](auto& str) { return str.asPtr(); }), Error,
266 "Invalid RSA key in JSON Web Key; missing or invalid "
267 "Private Exponent parameter (\"d\").");
268 auto p = JSG_REQUIRE_NONNULL(jwk.p.map([](auto& str) { return str.asPtr(); }), Error,
269 "Invalid RSA key in JSON Web Key; missing or invalid "
270 "First Prime Factor parameter (\"p\").");
271 auto q = JSG_REQUIRE_NONNULL(jwk.q.map([](auto& str) { return str.asPtr(); }), Error,
272 "Invalid RSA key in JSON Web Key; missing or invalid "
273 "Second Prime Factor parameter (\"q\").");
274 auto dp = JSG_REQUIRE_NONNULL(jwk.dp.map([](auto& str) { return str.asPtr(); }), Error,
275 "Invalid RSA key in JSON Web Key; missing or invalid "
276 "First Factor CRT Exponent parameter (\"dp\").");
277 auto dq = JSG_REQUIRE_NONNULL(jwk.dq.map([](auto& str) { return str.asPtr(); }), Error,
278 "Invalid RSA key in JSON Web Key; missing or invalid "
279 "Second Factor CRT Exponent parameter (\"dq\").");
280 auto qi = JSG_REQUIRE_NONNULL(jwk.qi.map([](auto& str) { return str.asPtr(); }), Error,
281 "Invalid RSA key in JSON Web Key; missing or invalid "
282 "First CRT Coefficient parameter (\"qi\").");
283 auto dBuf = simdutfBase64UrlDecodeChecked(js, d, "Invalid RSA key in JSON Web Key"_kj);
284 auto dDecoded = toBignumOwned(dBuf.asArrayPtr());
285 auto pBuf = simdutfBase64UrlDecodeChecked(js, p, kInvalidBase64Error);
286 auto pDecoded = toBignumOwned(pBuf.asArrayPtr());
287 auto qBuf = simdutfBase64UrlDecodeChecked(js, q, kInvalidBase64Error);
288 auto qDecoded = toBignumOwned(qBuf.asArrayPtr());
289 auto dpBuf = simdutfBase64UrlDecodeChecked(js, dp, kInvalidBase64Error);
290 auto dpDecoded = toBignumOwned(dpBuf.asArrayPtr());
291 auto dqBuf = simdutfBase64UrlDecodeChecked(js, dq, kInvalidBase64Error);
292 auto dqDecoded = toBignumOwned(dqBuf.asArrayPtr());
293 auto qiBuf = simdutfBase64UrlDecodeChecked(js, qi, kInvalidBase64Error);
294 auto qiDecoded = toBignumOwned(qiBuf.asArrayPtr());
295 
296 // .release() transfers BIGNUM ownership to the RSA key. UniqueBignum ensures
297 // cleanup if any earlier allocation or decode throws.
298 JSG_REQUIRE(RSA_set0_key(rsa.get(), nullptr, nullptr, dDecoded.get()) == 1, Error,
299 "Invalid RSA key in JSON Web Key; failed to set private exponent");
300 dDecoded.release();
301 JSG_REQUIRE(RSA_set0_factors(rsa.get(), pDecoded.get(), qDecoded.get()) == 1, Error,
302 "Invalid RSA key in JSON Web Key; failed to set prime factors");
303 pDecoded.release();
304 qDecoded.release();
305 JSG_REQUIRE(
306 RSA_set0_crt_params(rsa.get(), dpDecoded.get(), dqDecoded.get(), qiDecoded.get()) == 1,
307 Error, "Invalid RSA key in JSON Web Key; failed to set CRT parameters");
308 dpDecoded.release();
309 dqDecoded.release();
310 qiDecoded.release();
311 }
312 
313 auto evpPkey = OSSL_NEW(EVP_PKEY);
314 OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa.get()));
315 
316 auto usages = keyType == KeyType::PRIVATE ? CryptoKeyUsageSet::privateKeyMask()
317 : CryptoKeyUsageSet::publicKeyMask();
318 return AsymmetricKeyData{kj::mv(evpPkey), keyType, usages};
319}
320 
321kj::String Rsa::toPem(
322 jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe<CipherOptions> options) const {
323 ClearErrorOnReturn clearErrorOnReturn;
324 auto bio = OSSL_BIO_MEM();
325 switch (keyType) {
326 case KeyType::PUBLIC: {
327 switch (encoding) {
328 case KeyEncoding::PKCS1: {
329 JSG_REQUIRE(PEM_write_bio_RSAPublicKey(bio.get(), rsa) == 1, Error,
330 "Failed to write RSA public key to PEM", tryDescribeOpensslErrors());
331 break;
332 }
333 case workerd::api::KeyEncoding::SPKI: {
334 JSG_REQUIRE(PEM_write_bio_RSA_PUBKEY(bio.get(), rsa) == 1, Error,
335 "Failed to write RSA public key to PEM", tryDescribeOpensslErrors());
336 break;
337 }
338 default: {
339 JSG_FAIL_REQUIRE(Error, "Unsupported RSA public key encoding: ", encoding);
340 }
341 }
342 break;
343 }
344 case KeyType::PRIVATE: {
345 kj::byte* passphrase = nullptr;
346 size_t passLen = 0;
347 const EVP_CIPHER* cipher = nullptr;
348 KJ_IF_SOME(opts, options) {
349 passphrase = const_cast<kj::byte*>(opts.passphrase.begin());
350 passLen = opts.passphrase.size();
351 cipher = opts.cipher;
352 }
353 switch (encoding) {
354 case KeyEncoding::PKCS1: {
355 JSG_REQUIRE(PEM_write_bio_RSAPrivateKey(
356 bio.get(), rsa, cipher, passphrase, passLen, nullptr, nullptr) == 1,
357 Error, "Failed to write RSA private key to PEM", tryDescribeOpensslErrors());
358 break;
359 }
360 case KeyEncoding::PKCS8: {
361 auto evpPkey = OSSL_NEW(EVP_PKEY);
362 OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa));
363 JSG_REQUIRE(PEM_write_bio_PKCS8PrivateKey(bio.get(), evpPkey.get(), cipher,
364 reinterpret_cast<char*>(passphrase), passLen, nullptr, nullptr) == 1,
365 Error, "Failed to write RSA private key to PKCS8 PEM", tryDescribeOpensslErrors());
366 break;
367 }
368 default: {
369 JSG_FAIL_REQUIRE(Error, "Unsupported RSA private key encoding: ", encoding);
370 }
371 }
372 break;
373 }
374 default:
375 KJ_UNREACHABLE;
376 }
377 return kj::str(bioToArray(js, bio.get()).asArrayPtr().asChars());
378}
379 
380jsg::JsArrayBuffer Rsa::toDer(
381 jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe<CipherOptions> options) const {
382 ClearErrorOnReturn clearErrorOnReturn;
383 auto bio = OSSL_BIO_MEM();
384 switch (keyType) {
385 case KeyType::PUBLIC: {
386 switch (encoding) {
387 case KeyEncoding::PKCS1: {
388 JSG_REQUIRE(i2d_RSAPublicKey_bio(bio.get(), rsa) == 1, Error,
389 "Failed to write RSA public key to DER", tryDescribeOpensslErrors());
390 break;
391 }
392 case workerd::api::KeyEncoding::SPKI: {
393 auto evpPkey = OSSL_NEW(EVP_PKEY);
394 OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa));
395 JSG_REQUIRE(i2d_PUBKEY_bio(bio.get(), evpPkey.get()) == 1, Error,
396 "Failed to write RSA public key to SPKI", tryDescribeOpensslErrors());
397 break;
398 }
399 default: {
400 JSG_FAIL_REQUIRE(Error, "Unsupported RSA public key encoding: ", encoding);
401 }
402 }
403 break;
404 }
405 case KeyType::PRIVATE: {
406 kj::byte* passphrase = nullptr;
407 size_t passLen = 0;
408 const EVP_CIPHER* cipher = nullptr;
409 KJ_IF_SOME(opts, options) {
410 passphrase = const_cast<kj::byte*>(opts.passphrase.begin());
411 passLen = opts.passphrase.size();
412 cipher = opts.cipher;
413 }
414 switch (encoding) {
415 case KeyEncoding::PKCS1: {
416 // Does not permit encryption
417 JSG_REQUIRE(i2d_RSAPrivateKey_bio(bio.get(), rsa), Error,
418 "Failed to write RSA private key to PEM", tryDescribeOpensslErrors());
419 break;
420 }
421 case KeyEncoding::PKCS8: {
422 auto evpPkey = OSSL_NEW(EVP_PKEY);
423 OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa));
424 JSG_REQUIRE(i2d_PKCS8PrivateKey_bio(bio.get(), evpPkey.get(), cipher,
425 reinterpret_cast<char*>(passphrase), passLen, nullptr, nullptr) == 1,
426 Error, "Failed to write RSA private key to PKCS8 PEM", tryDescribeOpensslErrors());
427 break;
428 }
429 default: {
430 JSG_FAIL_REQUIRE(Error, "Unsupported RSA private key encoding: ", encoding);
431 }
432 }
433 break;
434 }
435 default:
436 KJ_UNREACHABLE;
437 }
438 return bioToArray(js, bio.get());
439}
440 
441void Rsa::validateRsaParams(
442 jsg::Lock& js, size_t modulusLength, kj::ArrayPtr<kj::byte> publicExponent, bool isImport) {
443 KJ_ASSERT(modulusLength <= ~uint16_t(0));
444 // Use Chromium's limits for RSA keygen to avoid infinite loops:
445 // * Key sizes a multiple of 8 bits.
446 // * Key sizes must be in [256, 16k] bits.
447 auto strictCrypto = FeatureFlags::get(js).getStrictCrypto();
448 JSG_REQUIRE(!(strictCrypto || !isImport) ||
449 (modulusLength % 8 == 0 && modulusLength >= 256 && modulusLength <= 16384),
450 DOMOperationError,
451 "The modulus length must be a multiple of 8 and "
452 "between 256 and 16k, but ",
453 modulusLength, " was requested.");
454 
455 // Now check the public exponent for allow-listed values.
456 // First see if we can convert the public exponent to an unsigned number. Unfortunately OpenSSL
457 // doesn't have convenient APIs to do this (since these are bignums) so we have to do it by hand.
458 // Since the problematic BIGNUMs are within the range of an unsigned int (& technically an
459 // unsigned short) we can treat an out-of-range issue as valid input.
460 KJ_IF_SOME(v, fromBignum<unsigned>(publicExponent)) {
461 if (!isImport) {
462 JSG_REQUIRE(v == 3 || v == 65537, DOMOperationError,
463 "The \"publicExponent\" must be either 3 or 65537, but got ", v, ".");
464 } else if (strictCrypto) {
465 // While we have long required the exponent to be 3 or 65537 when generating keys, handle
466 // imported keys more permissively and allow additional exponents that are considered safe
467 // and commonly used.
468 JSG_REQUIRE(v == 3 || v == 17 || v == 37 || v == 65537, DOMOperationError,
469 "Imported RSA key has invalid publicExponent ", v, ".");
470 }
471 } else {
472 JSG_FAIL_REQUIRE(DOMOperationError,
473 "The \"publicExponent\" must be either 3 or 65537, but "
474 "got a number larger than 2^32.");
475 }
476}
477 
478bool Rsa::isRSAPrivateKey(kj::ArrayPtr<const kj::byte> keyData) {
479 KJ_IF_SOME(rem, tryGetAsn1Sequence(keyData)) {
480 return rem.size() >= 3 && rem[0] == 2 && rem[1] == 1 && !(rem[2] & 0xfe);
481 }
482 return false;
483}
484 
485// ======================================================================================
486// Web Crypto Impl: RSASSA-PKCS1-V1_5, RSA-PSS, RSA-OEAP, RSA-RAW
487 
488namespace {
489class RsaBase: public AsymmetricKeyCryptoKeyImpl {
490 public:
491 explicit RsaBase(
492 AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)
493 : AsymmetricKeyCryptoKeyImpl(kj::mv(keyData), extractable),
494 keyAlgorithm(kj::mv(keyAlgorithm)) {}
495 
496 kj::StringPtr jsgGetMemoryName() const override {
497 return "AsymmetricKey";
498 }
499 size_t jsgGetMemorySelfSize() const override {
500 return sizeof(AsymmetricKeyCryptoKeyImpl);
501 }
502 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {
503 AsymmetricKeyCryptoKeyImpl::jsgGetMemoryInfo(tracker);
504 tracker.trackField("keyAlgorithm", keyAlgorithm);
505 }
506 
507 protected:
508 CryptoKey::RsaKeyAlgorithm keyAlgorithm;
509 
510 private:
511 SubtleCrypto::JsonWebKey exportJwk() const override final {
512 auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(getEvpPkey()), DOMDataError,
513 "No RSA data backing key", tryDescribeOpensslErrors());
514 return rsa.toJwk(getTypeEnum(), jwkHashAlgorithmName());
515 }
516 
517 jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final {
518 JSG_FAIL_REQUIRE(
519 DOMInvalidAccessError, "Cannot export \"", getAlgorithmName(), "\" in \"raw\" format.");
520 }
521 
522 CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override {
523 return KJ_ASSERT_NONNULL(Rsa::tryGetRsa(getEvpPkey())).getAsymmetricKeyDetail(js);
524 }
525 
526 virtual kj::String jwkHashAlgorithmName() const = 0;
527};
528 
529class RsassaPkcs1V15Key final: public RsaBase {
530 public:
531 explicit RsassaPkcs1V15Key(
532 AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)
533 : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}
534 
535 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
536 return keyAlgorithm.clone(js);
537 }
538 kj::StringPtr getAlgorithmName() const override {
539 return "RSASSA-PKCS1-v1_5";
540 }
541 
542 kj::StringPtr chooseHash(
543 const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)
544 const override {
545 // RSASSA-PKCS1-v1_5 attaches the hash to the key, ignoring whatever is specified at call time.
546 return KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;
547 }
548 
549 private:
550 kj::String jwkHashAlgorithmName() const override {
551 const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;
552 JSG_REQUIRE(hashName.startsWith("SHA"), DOMNotSupportedError,
553 "JWK export not supported for hash algorithm \"", hashName, "\".");
554 return kj::str("RS", hashName.slice(4, hashName.size()));
555 }
556};
557 
558class RsaPssKey final: public RsaBase {
559 public:
560 explicit RsaPssKey(
561 AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)
562 : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}
563 
564 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
565 return keyAlgorithm.clone(js);
566 }
567 kj::StringPtr getAlgorithmName() const override {
568 return keyAlgorithm.name;
569 }
570 
571 kj::StringPtr chooseHash(
572 const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)
573 const override {
574 // RSA-PSS attaches the hash to the key, ignoring whatever is specified at call time.
575 return KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;
576 }
577 
578 void addSalt(EVP_PKEY_CTX* pctx, const SubtleCrypto::SignAlgorithm& algorithm) const override {
579 auto salt = JSG_REQUIRE_NONNULL(algorithm.saltLength, TypeError,
580 "Failed to provide salt for RSA-PSS key operation which requires a salt");
581 JSG_REQUIRE(salt >= 0, DOMDataError, "SaltLength for RSA-PSS must be non-negative (provided ",
582 salt, ").");
583 OSSLCALL(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
584 OSSLCALL(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, salt));
585 }
586 
587 private:
588 kj::String jwkHashAlgorithmName() const override {
589 const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;
590 JSG_REQUIRE(hashName.startsWith("SHA"), DOMNotSupportedError,
591 "JWK export not supported for hash algorithm \"", hashName, "\".");
592 return kj::str("PS", hashName.slice(4, hashName.size()));
593 }
594};
595 
596class RsaOaepKey final: public RsaBase {
597 using InitFunction = decltype(EVP_PKEY_encrypt_init);
598 using EncryptDecryptFunction = decltype(EVP_PKEY_encrypt);
599 
600 public:
601 explicit RsaOaepKey(
602 AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)
603 : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}
604 
605 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
606 return keyAlgorithm.clone(js);
607 }
608 kj::StringPtr getAlgorithmName() const override {
609 return keyAlgorithm.name;
610 }
611 
612 kj::StringPtr chooseHash(
613 const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)
614 const override {
615 // RSA-OAEP is for encryption/decryption, not signing, but this method is called by the
616 // parent class when performing sign() or verify().
617 JSG_FAIL_REQUIRE(DOMNotSupportedError,
618 "The sign and verify operations are not implemented for \"", keyAlgorithm.name, "\".");
619 }
620 
621 jsg::JsArrayBuffer encrypt(jsg::Lock& js,
622 SubtleCrypto::EncryptAlgorithm&& algorithm,
623 kj::ArrayPtr<const kj::byte> plainText) const override {
624 JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError,
625 "Encryption/key wrapping only works with public keys, not \"", getType(), "\".");
626 return commonEncryptDecrypt(
627 js, kj::mv(algorithm), plainText, EVP_PKEY_encrypt_init, EVP_PKEY_encrypt);
628 }
629 
630 jsg::JsArrayBuffer decrypt(jsg::Lock& js,
631 SubtleCrypto::EncryptAlgorithm&& algorithm,
632 kj::ArrayPtr<const kj::byte> cipherText) const override {
633 JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError,
634 "Decryption/key unwrapping only works with private keys, not \"", getType(), "\".");
635 return commonEncryptDecrypt(
636 js, kj::mv(algorithm), cipherText, EVP_PKEY_decrypt_init, EVP_PKEY_decrypt);
637 }
638 
639 private:
640 jsg::JsArrayBuffer commonEncryptDecrypt(jsg::Lock& js,
641 SubtleCrypto::EncryptAlgorithm&& algorithm,
642 kj::ArrayPtr<const kj::byte> data,
643 InitFunction init,
644 EncryptDecryptFunction encryptDecrypt) const {
645 auto pkey = getEvpPkey();
646 auto digest = lookupDigestAlgorithm(KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name).second;
647 auto ctx = OSSL_NEW(EVP_PKEY_CTX, pkey, nullptr);
648 JSG_REQUIRE(1 == init(ctx.get()), DOMOperationError, "RSA-OAEP failed to initialize",
649 tryDescribeOpensslErrors());
650 return KJ_ASSERT_NONNULL(Rsa::tryGetRsa(pkey))
651 .cipher(js, ctx, kj::mv(algorithm), data, encryptDecrypt, digest);
652 }
653 
654 kj::String jwkHashAlgorithmName() const override {
655 const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;
656 JSG_REQUIRE(hashName.startsWith("SHA"), DOMNotSupportedError,
657 "JWK export not supported for hash algorithm \"", hashName, "\".");
658 if (hashName == "SHA-1") {
659 return kj::str("RSA-OAEP");
660 }
661 return kj::str("RSA-OAEP-", hashName.slice(4, hashName.size()));
662 }
663};
664 
665class RsaRawKey final: public RsaBase {
666 public:
667 explicit RsaRawKey(
668 AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable)
669 : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {}
670 
671 jsg::JsArrayBuffer sign(jsg::Lock& js,
672 SubtleCrypto::SignAlgorithm&& algorithm,
673 kj::ArrayPtr<const kj::byte> data) const override {
674 auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(getEvpPkey()), DOMDataError, "Missing RSA key");
675 return rsa.sign(js, data);
676 }
677 
678 bool verify(jsg::Lock& js,
679 SubtleCrypto::SignAlgorithm&& algorithm,
680 kj::ArrayPtr<const kj::byte> signature,
681 kj::ArrayPtr<const kj::byte> data) const override {
682 KJ_UNIMPLEMENTED("RawRsa Verification currently unsupported");
683 }
684 
685 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
686 return keyAlgorithm.clone(js);
687 }
688 
689 kj::StringPtr getAlgorithmName() const override {
690 return keyAlgorithm.name;
691 }
692 
693 kj::StringPtr chooseHash(
694 const kj::Maybe<kj::OneOf<kj::String, SubtleCrypto::HashAlgorithm>>& callTimeHash)
695 const override {
696 KJ_UNIMPLEMENTED("this should not be called since we overrode sign() and verify()");
697 }
698 
699 private:
700 kj::String jwkHashAlgorithmName() const override {
701 const auto& hashName = KJ_REQUIRE_NONNULL(keyAlgorithm.hash).name;
702 JSG_REQUIRE(hashName.startsWith("SHA"), DOMNotSupportedError,
703 "JWK export not supported for hash algorithm \"", hashName, "\".");
704 return kj::str("RS", hashName.slice(4, hashName.size()));
705 }
706};
707 
708CryptoKeyPair generateRsaPair(jsg::Lock& js,
709 kj::StringPtr normalizedName,
710 kj::Own<EVP_PKEY> privateEvpPKey,
711 kj::Own<EVP_PKEY> publicEvpPKey,
712 CryptoKey::RsaKeyAlgorithm&& keyAlgorithm,
713 bool privateKeyExtractable,
714 CryptoKeyUsageSet usages) {
715 auto privateKeyAlgorithm = keyAlgorithm.clone(js);
716 
717 AsymmetricKeyData publicKeyData{
718 .evpPkey = kj::mv(publicEvpPKey),
719 .keyType = KeyType::PUBLIC,
720 .usages = usages & CryptoKeyUsageSet::publicKeyMask(),
721 };
722 AsymmetricKeyData privateKeyData{
723 .evpPkey = kj::mv(privateEvpPKey),
724 .keyType = KeyType::PRIVATE,
725 .usages = usages & CryptoKeyUsageSet::privateKeyMask(),
726 };
727 
728 static constexpr auto createPair = [](jsg::Lock& js, kj::Own<CryptoKey::Impl> publicKey,
729 kj::Own<CryptoKey::Impl> privateKey) {
730 return CryptoKeyPair{.publicKey = js.alloc<CryptoKey>(kj::mv(publicKey)),
731 .privateKey = js.alloc<CryptoKey>(kj::mv(privateKey))};
732 };
733 
734 if (normalizedName == "RSASSA-PKCS1-v1_5") {
735 return createPair(js,
736 kj::heap<RsassaPkcs1V15Key>(kj::mv(publicKeyData), kj::mv(keyAlgorithm), true),
737 kj::heap<RsassaPkcs1V15Key>(
738 kj::mv(privateKeyData), kj::mv(privateKeyAlgorithm), privateKeyExtractable));
739 } else if (normalizedName == "RSA-PSS") {
740 return createPair(js, kj::heap<RsaPssKey>(kj::mv(publicKeyData), kj::mv(keyAlgorithm), true),
741 kj::heap<RsaPssKey>(
742 kj::mv(privateKeyData), kj::mv(privateKeyAlgorithm), privateKeyExtractable));
743 } else if (normalizedName == "RSA-OAEP") {
744 return createPair(js, kj::heap<RsaOaepKey>(kj::mv(publicKeyData), kj::mv(keyAlgorithm), true),
745 kj::heap<RsaOaepKey>(
746 kj::mv(privateKeyData), kj::mv(privateKeyAlgorithm), privateKeyExtractable));
747 }
748 JSG_FAIL_REQUIRE(DOMNotSupportedError, "Unimplemented RSA generation \"", normalizedName, "\".");
749}
750 
751kj::Own<EVP_PKEY> rsaJwkReader(SubtleCrypto::JsonWebKey&& keyDataJwk) {
752 auto rsaKey = OSSL_NEW(RSA);
753 
754 auto modulus = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.n), DOMDataError,
755 "Invalid RSA key in JSON Web Key; missing or invalid Modulus "
756 "parameter (\"n\").");
757 auto publicExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.e), DOMDataError,
758 "Invalid RSA key in JSON Web Key; missing or invalid "
759 "Exponent parameter (\"e\").");
760 
761 auto nBignum = toBignumOwned(modulus);
762 auto eBignum = toBignumOwned(publicExponent);
763 OSSLCALL(RSA_set0_key(rsaKey.get(), nBignum.get(), eBignum.get(), nullptr));
764 nBignum.release();
765 eBignum.release();
766 
767 if (keyDataJwk.d != kj::none) {
768 // This is a private key.
769 
770 auto privateExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.d), DOMDataError,
771 "Invalid RSA key in JSON Web Key; missing or invalid "
772 "Private Exponent parameter (\"d\").");
773 
774 auto dBignum = toBignumOwned(privateExponent);
775 OSSLCALL(RSA_set0_key(rsaKey.get(), nullptr, nullptr, dBignum.get()));
776 dBignum.release();
777 
778 auto presence = (keyDataJwk.p != kj::none) + (keyDataJwk.q != kj::none) +
779 (keyDataJwk.dp != kj::none) + (keyDataJwk.dq != kj::none) + (keyDataJwk.qi != kj::none);
780 
781 if (presence == 5) {
782 auto firstPrimeFactor = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.p), DOMDataError,
783 "Invalid RSA key in JSON Web Key; invalid First Prime "
784 "Factor parameter (\"p\").");
785 auto secondPrimeFactor = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.q), DOMDataError,
786 "Invalid RSA key in JSON Web Key; invalid Second Prime "
787 "Factor parameter (\"q\").");
788 auto firstFactorCrtExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.dp), DOMDataError,
789 "Invalid RSA key in JSON Web Key; invalid First Factor "
790 "CRT Exponent parameter (\"dp\").");
791 auto secondFactorCrtExponent = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.dq), DOMDataError,
792 "Invalid RSA key in JSON Web Key; invalid Second Factor "
793 "CRT Exponent parameter (\"dq\").");
794 auto firstCrtCoefficient = UNWRAP_JWK_BIGNUM(kj::mv(keyDataJwk.qi), DOMDataError,
795 "Invalid RSA key in JSON Web Key; invalid First CRT "
796 "Coefficient parameter (\"qi\").");
797 
798 auto pBn = toBignumOwned(firstPrimeFactor);
799 auto qBn = toBignumOwned(secondPrimeFactor);
800 auto dpBn = toBignumOwned(firstFactorCrtExponent);
801 auto dqBn = toBignumOwned(secondFactorCrtExponent);
802 auto qiBn = toBignumOwned(firstCrtCoefficient);
803 
804 OSSLCALL(RSA_set0_factors(rsaKey.get(), pBn.get(), qBn.get()));
805 pBn.release();
806 qBn.release();
807 OSSLCALL(RSA_set0_crt_params(rsaKey.get(), dpBn.get(), dqBn.get(), qiBn.get()));
808 dpBn.release();
809 dqBn.release();
810 qiBn.release();
811 } else {
812 JSG_REQUIRE(presence == 0, DOMDataError,
813 "Invalid RSA private key in JSON Web Key; if one Prime "
814 "Factor or CRT Exponent/Coefficient parameter is present, then they must all be "
815 "present (\"p\", \"q\", \"dp\", \"dq\", \"qi\").");
816 }
817 }
818 
819 auto evpPkey = OSSL_NEW(EVP_PKEY);
820 OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsaKey.get()));
821 return evpPkey;
822}
823} // namespace
824 
825kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair> CryptoKey::Impl::generateRsa(jsg::Lock& js,
826 kj::StringPtr normalizedName,
827 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
828 bool extractable,
829 kj::ArrayPtr<const kj::String> keyUsages) {
830 
831 KJ_ASSERT(normalizedName == "RSASSA-PKCS1-v1_5" || normalizedName == "RSA-PSS" ||
832 normalizedName == "RSA-OAEP",
833 "generateRsa called on non-RSA cryptoKey", normalizedName);
834 
835 auto publicExponent = JSG_REQUIRE_NONNULL(kj::mv(algorithm.publicExponent), TypeError,
836 "Missing field \"publicExponent\" in \"algorithm\".")
837 .getHandle(js);
838 kj::StringPtr hash = api::getAlgorithmName(
839 JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\"."));
840 int modulusLength = JSG_REQUIRE_NONNULL(
841 algorithm.modulusLength, TypeError, "Missing field \"modulusLength\" in \"algorithm\".");
842 JSG_REQUIRE(modulusLength > 0, DOMOperationError,
843 "modulusLength must be greater than zero "
844 "(requested ",
845 modulusLength, ").");
846 auto [normalizedHashName, hashEvpMd] = lookupDigestAlgorithm(hash);
847 
848 CryptoKeyUsageSet validUsages = (normalizedName == "RSA-OAEP")
849 ? (CryptoKeyUsageSet::encrypt() | CryptoKeyUsageSet::decrypt() |
850 CryptoKeyUsageSet::wrapKey() | CryptoKeyUsageSet::unwrapKey())
851 : (CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());
852 auto usages = CryptoKeyUsageSet::validate(
853 normalizedName, CryptoKeyUsageSet::Context::generate, keyUsages, validUsages);
854 
855 Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr());
856 // BoringSSL silently uses (modulusLength & ~127) for the key size, i.e. it rounds down to the
857 // closest multiple of 128 bits. This can easily cause confusion when non-standard key sizes are
858 // requested.
859 // The `modulusLength` field of the resulting CryptoKey will be incorrect when the compat flag
860 // is disabled and the key size is rounded down, but since it is not currently used this is
861 // acceptable.
862 JSG_REQUIRE(!(FeatureFlags::get(js).getStrictCrypto() && (modulusLength & 127)),
863 DOMOperationError, "Can't generate key: RSA key size is required to be a multiple of 128");
864 
865 auto bnExponent = JSG_REQUIRE_NONNULL(toBignum(publicExponent.asArrayPtr()),
866 InternalDOMOperationError, "Error setting up RSA keygen.");
867 
868 auto rsaPrivateKey = OSSL_NEW(RSA);
869 OSSLCALL(RSA_generate_key_ex(rsaPrivateKey, modulusLength, bnExponent.get(), nullptr));
870 auto privateEvpPKey = OSSL_NEW(EVP_PKEY);
871 OSSLCALL(EVP_PKEY_set1_RSA(privateEvpPKey.get(), rsaPrivateKey.get()));
872 kj::Own<RSA> rsaPublicKey = OSSLCALL_OWN(RSA, RSAPublicKey_dup(rsaPrivateKey.get()),
873 InternalDOMOperationError, "Error finalizing RSA keygen", internalDescribeOpensslErrors());
874 auto publicEvpPKey = OSSL_NEW(EVP_PKEY);
875 OSSLCALL(EVP_PKEY_set1_RSA(publicEvpPKey.get(), rsaPublicKey));
876 
877 // Create a JsUint8Array copy of the public exponent for the key algorithm struct.
878 auto expCopy = jsg::JsUint8Array::create(js, publicExponent.asArrayPtr());
879 auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = normalizedName,
880 .modulusLength = static_cast<uint16_t>(modulusLength),
881 .publicExponent = jsg::JsBufferSource(expCopy).addRef(js),
882 .hash = KeyAlgorithm{normalizedHashName}};
883 
884 return generateRsaPair(js, normalizedName, kj::mv(privateEvpPKey), kj::mv(publicEvpPKey),
885 kj::mv(keyAlgorithm), extractable, usages);
886}
887 
888kj::Own<CryptoKey::Impl> CryptoKey::Impl::importRsa(jsg::Lock& js,
889 kj::StringPtr normalizedName,
890 kj::StringPtr format,
891 SubtleCrypto::ImportKeyData keyData,
892 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
893 bool extractable,
894 kj::ArrayPtr<const kj::String> keyUsages) {
895 kj::StringPtr hash = api::getAlgorithmName(
896 JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\"."));
897 
898 CryptoKeyUsageSet allowedUsages = (normalizedName == "RSA-OAEP")
899 ? (CryptoKeyUsageSet::encrypt() | CryptoKeyUsageSet::decrypt() |
900 CryptoKeyUsageSet::wrapKey() | CryptoKeyUsageSet::unwrapKey())
901 : (CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify());
902 
903 auto [normalizedHashName, hashEvpMd] = lookupDigestAlgorithm(hash);
904 
905 auto importedKey = importAsymmetricForWebCrypto(js, kj::mv(format), kj::mv(keyData),
906 normalizedName, extractable, keyUsages,
907 // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984
908 [hashEvpMd = hashEvpMd, &algorithm](
909 SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {
910 JSG_REQUIRE(keyDataJwk.kty == "RSA", DOMDataError,
911 "RSASSA-PKCS1-v1_5 \"jwk\" key import requires a JSON Web Key with Key Type parameter "
912 "\"kty\" (\"",
913 keyDataJwk.kty, "\") equal to \"RSA\".");
914 
915 KJ_IF_SOME(alg, keyDataJwk.alg) {
916 // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via
917 // importKey().
918 static const std::map<kj::StringPtr, const EVP_MD*> knownRsaAlgorithms{
919 {"RS1", EVP_sha1()},
920 {"RS256", EVP_sha256()},
921 {"RS384", EVP_sha384()},
922 {"RS512", EVP_sha512()},
923 {"PS1", EVP_sha1()},
924 {"PS256", EVP_sha256()},
925 {"PS384", EVP_sha384()},
926 {"PS512", EVP_sha512()},
927 {"RSA-OAEP", EVP_sha1()},
928 {"RSA-OAEP-256", EVP_sha256()},
929 {"RSA-OAEP-384", EVP_sha384()},
930 {"RSA-OAEP-512", EVP_sha512()},
931 };
932 const auto tryFindAlgorithm = [&](kj::StringPtr alg) -> kj::Maybe<const EVP_MD*> {
933 if (algorithm.name == "RSASSA-PKCS1-v1_5" || algorithm.name == "RSA-PSS" ||
934 algorithm.name == "RSA-OAEP") {
935 auto ret = knownRsaAlgorithms.find(alg);
936 if (ret != knownRsaAlgorithms.end()) {
937 return ret->second;
938 }
939 return kj::none;
940 } else {
941 JSG_FAIL_REQUIRE(
942 DOMNotSupportedError, "Unrecognized RSA variant \"", algorithm.name, "\".");
943 }
944 };
945 auto jwkHash = JSG_REQUIRE_NONNULL(tryFindAlgorithm(alg), DOMNotSupportedError,
946 "Unrecognized or unimplemented algorithm \"", alg,
947 "\" listed in JSON Web Key Algorithm "
948 "parameter.");
949 
950 JSG_REQUIRE(jwkHash == hashEvpMd, DOMDataError,
951 "JSON Web Key Algorithm parameter \"alg\" (\"", alg,
952 "\") does not match requested hash "
953 "algorithm \"",
954 alg, "\".");
955 }
956 
957 return rsaJwkReader(kj::mv(keyDataJwk));
958 },
959 allowedUsages);
960 
961 // get0 avoids adding a refcount...
962 auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(importedKey.evpPkey.get()), DOMDataError,
963 "Input was not an RSA key", tryDescribeOpensslErrors());
964 
965 // TODO(conform): We're supposed to check if PKCS8/SPKI input specified a hash and, if so,
966 // compare it against the hash requested in `algorithm`. But, I can't find the OpenSSL
967 // interface to extract the hash from the ASN.1. Oh well...
968 
969 size_t modulusLength = rsa.getModulusBits();
970 auto publicExponent = rsa.getPublicExponent(js);
971 
972 // Validate modulus and exponent, reject imported RSA keys that may be unsafe.
973 Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr(), true);
974 
975 auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = normalizedName,
976 .modulusLength = static_cast<uint16_t>(modulusLength),
977 .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js),
978 .hash = KeyAlgorithm{normalizedHashName}};
979 if (normalizedName == "RSASSA-PKCS1-v1_5") {
980 return kj::heap<RsassaPkcs1V15Key>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);
981 } else if (normalizedName == "RSA-PSS") {
982 return kj::heap<RsaPssKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);
983 } else if (normalizedName == "RSA-OAEP") {
984 return kj::heap<RsaOaepKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);
985 } else {
986 JSG_FAIL_REQUIRE(DOMNotSupportedError, "Unrecognized RSA variant \"", normalizedName, "\".");
987 }
988}
989 
990kj::Own<CryptoKey::Impl> CryptoKey::Impl::importRsaRaw(jsg::Lock& js,
991 kj::StringPtr normalizedName,
992 kj::StringPtr format,
993 SubtleCrypto::ImportKeyData keyData,
994 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
995 bool extractable,
996 kj::ArrayPtr<const kj::String> keyUsages) {
997 // Note that in this context raw refers to the RSA-RAW algorithm, not to keys represented by raw
998 // data. Importing raw keys is currently not supported for this algorithm.
999 CryptoKeyUsageSet allowedUsages = CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify();
1000 auto importedKey = importAsymmetricForWebCrypto(js, kj::mv(format), kj::mv(keyData),
1001 normalizedName, extractable, keyUsages,
1002 // Verbose lambda capture needed because: https://bugs.llvm.org/show_bug.cgi?id=35984
1003 [](SubtleCrypto::JsonWebKey keyDataJwk) -> kj::Own<EVP_PKEY> {
1004 JSG_REQUIRE(keyDataJwk.kty == "RSA", DOMDataError,
1005 "RSA-RAW \"jwk\" key import requires a JSON Web Key with Key Type parameter "
1006 "\"kty\" (\"",
1007 keyDataJwk.kty, "\") equal to \"RSA\".");
1008 
1009 KJ_IF_SOME(alg, keyDataJwk.alg) {
1010 // If this JWK specifies an algorithm, make sure it jives with the hash we were passed via
1011 // importKey().
1012 static const std::map<kj::StringPtr, const EVP_MD*> rsaAlgorithms{
1013 {"RS1", EVP_sha1()},
1014 {"RS256", EVP_sha256()},
1015 {"RS384", EVP_sha384()},
1016 {"RS512", EVP_sha512()},
1017 };
1018 auto jwkHash = rsaAlgorithms.find(alg);
1019 JSG_REQUIRE(jwkHash != rsaAlgorithms.end(), DOMNotSupportedError,
1020 "Unrecognized or unimplemented algorithm \"", alg,
1021 "\" listed in JSON Web Key Algorithm parameter.");
1022 }
1023 return rsaJwkReader(kj::mv(keyDataJwk));
1024 }, allowedUsages);
1025 
1026 JSG_REQUIRE(importedKey.keyType == KeyType::PRIVATE, DOMDataError,
1027 "RSA-RAW only supports private keys but requested \"", toStringPtr(importedKey.keyType),
1028 "\".");
1029 
1030 // get0 avoids adding a refcount...
1031 auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(importedKey.evpPkey.get()), DOMDataError,
1032 "Input was not an RSA key", tryDescribeOpensslErrors());
1033 
1034 size_t modulusLength = rsa.getModulusBits();
1035 auto publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE()));
1036 
1037 // Validate modulus and exponent, reject imported RSA keys that may be unsafe.
1038 Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr(), true);
1039 
1040 auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = "RSA-RAW"_kj,
1041 .modulusLength = static_cast<uint16_t>(modulusLength),
1042 .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js)};
1043 
1044 return kj::heap<RsaRawKey>(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable);
1045}
1046 
1047kj::Own<CryptoKey::Impl> fromRsaKey(jsg::Lock& js, kj::Own<EVP_PKEY> key) {
1048 auto rsa =
1049 JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(key.get()), DOMDataError, "Input was not an RSA key");
1050 
1051 auto publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE()));
1052 return kj::heap<RsassaPkcs1V15Key>(AsymmetricKeyData{.evpPkey = kj::mv(key),
1053 .keyType = KeyType::PUBLIC,
1054 .usages = CryptoKeyUsageSet::decrypt() |
1055 CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify()},
1056 CryptoKey::RsaKeyAlgorithm{
1057 .name = "RSA"_kj,
1058 .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js),
1059 },
1060 true);
1061}
1062 
1063} // namespace workerd::api