Skip to content
File

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

36.4 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 "crypto.h"
6 
7#include "impl.h"
8 
9#include <workerd/api/crypto/crc-impl.h>
10#include <workerd/api/crypto/endianness.h>
11#include <workerd/api/streams/standard.h>
12#include <workerd/api/util.h>
13#include <workerd/io/io-context.h>
14#include <workerd/jsg/jsg.h>
15#include <workerd/util/uuid.h>
16 
17#include <openssl/digest.h>
18#include <openssl/mem.h>
19#include <zlib.h>
20 
21#include <algorithm>
22#include <array>
23#include <limits>
24#include <set>
25#include <typeinfo>
26 
27namespace workerd::api {
28 
29kj::StringPtr CryptoKeyUsageSet::name() const {
30 if (*this == encrypt()) return "encrypt";
31 if (*this == decrypt()) return "decrypt";
32 if (*this == sign()) return "sign";
33 if (*this == verify()) return "verify";
34 if (*this == deriveKey()) return "deriveKey";
35 if (*this == deriveBits()) return "deriveBits";
36 if (*this == wrapKey()) return "wrapKey";
37 if (*this == unwrapKey()) return "unwrapKey";
38 KJ_FAIL_REQUIRE("CryptoKeyUsageSet does not contain exactly one key usage");
39}
40 
41CryptoKeyUsageSet CryptoKeyUsageSet::byName(kj::StringPtr name) {
42 for (auto& usage: singletons()) {
43 if (name == usage.name()) return usage;
44 }
45 return {};
46}
47 
48kj::ArrayPtr<const CryptoKeyUsageSet> CryptoKeyUsageSet::singletons() {
49 static const workerd::api::CryptoKeyUsageSet singletons[] = {
50 encrypt(), decrypt(), sign(), verify(), deriveKey(), deriveBits(), wrapKey(), unwrapKey()};
51 return singletons;
52}
53 
54CryptoKeyUsageSet CryptoKeyUsageSet::validate(kj::StringPtr normalizedName,
55 Context ctx,
56 kj::ArrayPtr<const kj::String> actual,
57 CryptoKeyUsageSet mask) {
58 const auto op = (ctx == Context::generate) ? "generate"
59 : (ctx == Context::importSecret) ? "import secret"
60 : (ctx == Context::importPublic) ? "import public"
61 : "import private";
62 CryptoKeyUsageSet usages;
63 for (const auto& usage: actual) {
64 CryptoKeyUsageSet match = byName(usage);
65 JSG_REQUIRE(match.isSingleton() && match <= mask, DOMSyntaxError, "Attempt to ", op, " ",
66 normalizedName, " key with invalid usage \"", usage, "\".");
67 usages |= match;
68 }
69 return usages;
70}
71 
72namespace {
73 
74// IMPLEMENTATION STRATEGY
75//
76// Each SubtleCrypto method is polymorphic, with different implementations selected based on the
77// `name` property of the Algorithm dictionary passed (or KeyAlgorithm dictionary of the CryptoKey
78// passed, in the case of subtle.exportKey()).
79//
80// This polymorphism is implemented in CryptoKey::Impl. All of the key-based crypto algorithm
81// operations (encrypt, decrypt, sign, verify, deriveBits, wrapKey, unwrapKey) are virtual functions
82// on CryptoKey::Impl -- SubtleCrypto forwards to CryptoKey which forwards to Impl.
83//
84// TODO(cleanup): We validate crypto algorithm/operation/key sanity in a preamble in the functions
85// defined in the SubtleCrypto interface. This is because this whole thing was originally
86// implemented differently and I haven't completed refactoring it. We should put this validation
87// somewhere in CryptoKey, perhaps implicitly in the default implementations of the
88// encrypt/decrypt/sign/verify/etc. functions.
89//
90// Note that SubtleCrypto.digest() is special. It is not a key-based operation and we only support
91// one hash family, SHA, so its implementation is non-virtual.
92//
93// NOTE(perf): The SubtleCrypto interface is asynchronous, but all of our implementations perform
94// the crypto synchronously before returning. In theory, we could be performing bulk crypto in a
95// separate thread, maybe improving performance. However, it's unclear what real use case would
96// benefit from this. It's also unclear that we would want a single request to be able to use
97// multiple cores -- certainly it would greatly complicate our implementation of request CPU
98// limits. So, we probably shouldn't implement true asynchronous crypto.
99//
100// Additionally, performing the crypto synchronously actually has a performance benefit: we can
101// safely avoid copying input BufferSources -- most of our functions can take
102// kj::ArrayPtr<const kj::byte>s, rather than kj::Array<kj::byte>s.
103 
104// =======================================================================================
105// Registered algorithms
106 
107static kj::Maybe<const CryptoAlgorithm&> lookupAlgorithm(kj::StringPtr name) {
108 static const std::set<CryptoAlgorithm> ALGORITHMS = {
109 {"AES-CTR"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},
110 {"AES-CBC"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},
111 {"AES-GCM"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},
112 {"AES-KW"_kj, &CryptoKey::Impl::importAes, &CryptoKey::Impl::generateAes},
113 {"HMAC"_kj, &CryptoKey::Impl::importHmac, &CryptoKey::Impl::generateHmac},
114 {"PBKDF2"_kj, &CryptoKey::Impl::importPbkdf2},
115 {"HKDF"_kj, &CryptoKey::Impl::importHkdf},
116 {"RSASSA-PKCS1-v1_5"_kj, &CryptoKey::Impl::importRsa, &CryptoKey::Impl::generateRsa},
117 {"RSA-PSS"_kj, &CryptoKey::Impl::importRsa, &CryptoKey::Impl::generateRsa},
118 {"RSA-OAEP"_kj, &CryptoKey::Impl::importRsa, &CryptoKey::Impl::generateRsa},
119 {"ECDSA"_kj, &CryptoKey::Impl::importEcdsa, &CryptoKey::Impl::generateEcdsa},
120 {"ECDH"_kj, &CryptoKey::Impl::importEcdh, &CryptoKey::Impl::generateEcdh},
121 {"NODE-ED25519"_kj, &CryptoKey::Impl::importEddsa, &CryptoKey::Impl::generateEddsa},
122 {"Ed25519"_kj, &CryptoKey::Impl::importEddsa, &CryptoKey::Impl::generateEddsa},
123 {"X25519"_kj, &CryptoKey::Impl::importEddsa, &CryptoKey::Impl::generateEddsa},
124 {"RSA-RAW"_kj, &CryptoKey::Impl::importRsaRaw},
125 };
126 
127 auto iter = ALGORITHMS.find(CryptoAlgorithm{name});
128 if (iter == ALGORITHMS.end()) {
129 // No such built-in algorithm, so fall back to checking if the Api has a custom
130 // algorithm registered.
131 return Worker::Api::current().getCryptoAlgorithm(name);
132 } else {
133 return *iter;
134 }
135}
136 
137// =======================================================================================
138// Helper functions
139 
140// Throws InvalidAccessError if the key is incompatible with the given normalized algorithm name,
141// or if it doesn't support the given usage.
142void validateOperation(const CryptoKey& key, kj::StringPtr requestedName, CryptoKeyUsageSet usage) {
143 // TODO(someday): Throw a NotSupportedError? The Web Crypto API spec says InvalidAccessError, but
144 // Web IDL says that's deprecated.
145 //
146 // TODO(cleanup): Make this function go away. Maybe this can be rolled into the default
147 // implementations of the CryptoKey::Impl::<crypto operation>() functions.
148 
149 JSG_REQUIRE(strcasecmp(requestedName.cStr(), key.getAlgorithmName().cStr()) == 0,
150 DOMInvalidAccessError, "Requested algorithm \"", requestedName,
151 "\" does not match this CryptoKey's algorithm \"", key.getAlgorithmName(), "\".");
152 JSG_REQUIRE(usage <= key.getUsageSet(), DOMInvalidAccessError, "Requested key usage \"",
153 usage.name(), "\" does not match any usage listed in this CryptoKey.");
154}
155 
156// Helper for `deriveKey()`. This private crypto operation is actually defined by the spec as
157// the "get key length" operation.
158kj::Maybe<uint32_t> getKeyLength(const SubtleCrypto::ImportKeyAlgorithm& derivedKeyAlgorithm) {
159 
160 kj::StringPtr algName = derivedKeyAlgorithm.name;
161 
162 // TODO(cleanup): This should be a method of CryptoKey::Impl so it can be abstracted. Currently
163 // we ad-hoc match various algorithms below, so the set of supported algorithms must be
164 // hard-coded.
165 static const std::set<kj::StringPtr, CiLess> registeredAlgorithms{
166 {"AES-CTR"},
167 {"AES-CBC"},
168 {"AES-GCM"},
169 {"AES-KW"},
170 {"HMAC"},
171 {"HKDF"},
172 {"PBKDF2"},
173 };
174 auto algIter = registeredAlgorithms.find(algName);
175 JSG_REQUIRE(algIter != registeredAlgorithms.end(), DOMNotSupportedError,
176 "Unrecognized derived key type \"", algName, "\" requested.");
177 
178 // We could implement getKeyLength() with the same map-of-strings-to-implementation-functions
179 // strategy as the rest of the crypto operations, but this function is so simple that it hardly
180 // seems worth the bother. The spec only identifies three cases: the AES family, HMAC, and the KDF
181 // algorithms.
182 if (algIter->startsWith("AES-")) {
183 int length = JSG_REQUIRE_NONNULL(
184 derivedKeyAlgorithm.length, TypeError, "Missing field \"length\" in \"derivedKeyParams\".");
185 switch (length) {
186 case 128:
187 [[fallthrough]];
188 case 192:
189 [[fallthrough]];
190 case 256:
191 break;
192 default:
193 JSG_FAIL_REQUIRE(DOMOperationError,
194 "Derived AES key must be 128, 192, or 256 bits in length but provided ", length, ".");
195 }
196 return length;
197 } else if (*algIter == "HMAC") {
198 KJ_IF_SOME(length, derivedKeyAlgorithm.length) {
199 // If the user requested a specific HMAC key length, honor it.
200 if (length > 0) {
201 return length;
202 }
203 JSG_FAIL_REQUIRE(TypeError, "HMAC key length must be a non-zero unsigned long integer.");
204 }
205 // Otherwise, assume the user wants the default HMAC key size.
206 auto digestAlg = getAlgorithmName(JSG_REQUIRE_NONNULL(
207 derivedKeyAlgorithm.hash, TypeError, "Missing field \"hash\" in \"derivedKeyParams\"."));
208 return EVP_MD_block_size(lookupDigestAlgorithm(digestAlg).second) * 8;
209 } else {
210 // HKDF or PBKDF2. I'm not not sure what it means to derive a HKDF/PBKDF2 key from a base key
211 // (are you deriving a password from a password?) but based on my reading of the spec, this code
212 // path will become meaningful once we support ECDH, which handles null-length deriveBits()
213 // operations. This is the entire reason getKeyLength() returns a Maybe<uint32_t> rather than a
214 // uint32_t (and also why we do not throw an OperationError here but rather later on in
215 // deriveBitsPbkdf2Impl()).
216 return kj::none;
217 }
218}
219 
220auto webCryptoOperationBegin(
221 const char* operation, kj::StringPtr algorithm, kj::Maybe<kj::StringPtr> context = kj::none) {
222 // This clears all OpenSSL errors & errno at the start & returns a deferred evaluation to make
223 // sure that, when the WebCrypto entrypoint completes, there are no errors hanging around.
224 // Context is used for adding contextual information (e.g. the algorithm name of the key being
225 // wrapped, the import/export format being processed etc).
226 ERR_clear_error();
227 ERR_clear_system_error();
228 
229 // Ok to capture pointers by value because this will only be used for the duration of the parent's
230 // scope which is passing in these arguments.
231 return kj::defer([=] {
232 if (ERR_peek_error() != 0) {
233 auto allErrors = KJ_MAP(e, consumeAllOpensslErrors()) {
234 KJ_SWITCH_ONEOF(e) {
235 KJ_CASE_ONEOF(friendly, kj::StringPtr) {
236 return kj::str(friendly);
237 }
238 KJ_CASE_ONEOF(raw, OpensslUntranslatedError) {
239 return kj::str(raw.library, "::", raw.reasonName);
240 }
241 }
242 
243 KJ_UNREACHABLE;
244 };
245 
246 kj::String stringifiedOperation;
247 KJ_IF_SOME(c, context) {
248 stringifiedOperation = kj::str(operation, "(", c, ")");
249 } else {
250 stringifiedOperation = kj::str(operation);
251 }
252 KJ_LOG(WARNING, "WebCrypto didn't handle all BoringSSL errors", stringifiedOperation,
253 algorithm, allErrors);
254 }
255 });
256}
257 
258auto webCryptoOperationBegin(
259 const char* operation, kj::StringPtr algorithm, const kj::String& context) {
260 return webCryptoOperationBegin(operation, algorithm, context.slice(0));
261}
262 
263template <typename T,
264 typename = kj::EnableIf<kj::isSameType<kj::String, decltype(kj::instance<T>().name)>()>>
265[[gnu::always_inline]] auto webCryptoOperationBegin(
266 const char* operation, const T& algorithm, kj::Maybe<kj::StringPtr> context = kj::none) {
267 return kj::defer([operation, algorithm = kj::str(algorithm.name), context] {
268 // We need a copy of the algorithm name as this defer runs after the EncryptAlgorithm struct
269 // is destroyed.
270 (void)webCryptoOperationBegin(operation, algorithm, context);
271 });
272}
273 
274} // namespace
275 
276// =======================================================================================
277// CryptoKey / SubtleCrypto implementations
278 
279CryptoKey::CryptoKey(kj::Own<Impl> impl): impl(kj::mv(impl)) {}
280CryptoKey::~CryptoKey() noexcept(false) {}
281kj::StringPtr CryptoKey::getAlgorithmName() const {
282 return impl->getAlgorithmName();
283}
284CryptoKey::AlgorithmVariant CryptoKey::getAlgorithm(jsg::Lock& js) const {
285 return impl->getAlgorithm(js);
286}
287kj::StringPtr CryptoKey::getType() const {
288 return impl->getType();
289}
290bool CryptoKey::getExtractable() const {
291 return impl->isExtractable();
292}
293kj::Array<kj::StringPtr> CryptoKey::getUsages() const {
294 return getUsageSet().map([](auto singleton) { return singleton.name(); });
295}
296CryptoKeyUsageSet CryptoKey::getUsageSet() const {
297 return impl->getUsages();
298}
299 
300bool CryptoKey::operator==(const CryptoKey& other) const {
301 // We check this first because we don't want any comparison to happen if
302 // either key is not extractable, even if they are the same object.
303 if (!getExtractable() || !other.getExtractable()) {
304 return false;
305 }
306 return this == &other || (getType() == other.getType() && impl->equals(*other.impl));
307}
308 
309CryptoKey::AsymmetricKeyDetails CryptoKey::getAsymmetricKeyDetails(jsg::Lock& js) const {
310 return impl->getAsymmetricKeyDetail(js);
311}
312 
313bool CryptoKey::verifyX509Public(const X509* cert) const {
314 if (this->getType() != "public"_kj) return false;
315 return impl->verifyX509Public(cert);
316}
317 
318bool CryptoKey::verifyX509Private(const X509* cert) const {
319 if (this->getType() != "private"_kj) return false;
320 return impl->verifyX509Private(cert);
321}
322 
323void CryptoKey::visitForGc(jsg::GcVisitor& visitor) {
324 if (impl.get() == nullptr) return;
325 impl->visitForGc(visitor);
326}
327 
328jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::encrypt(jsg::Lock& js,
329 kj::OneOf<kj::String, EncryptAlgorithm> algorithmParam,
330 const CryptoKey& key,
331 kj::Array<const kj::byte> plainText) {
332 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
333 
334 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
335 
336 return js.evalNow([&] {
337 validateOperation(key, algorithm.name, CryptoKeyUsageSet::encrypt());
338 return key.impl->encrypt(js, kj::mv(algorithm), plainText).addRef(js);
339 });
340}
341 
342jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::decrypt(jsg::Lock& js,
343 kj::OneOf<kj::String, EncryptAlgorithm> algorithmParam,
344 const CryptoKey& key,
345 kj::Array<const kj::byte> cipherText) {
346 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
347 
348 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
349 
350 return js.evalNow([&] {
351 validateOperation(key, algorithm.name, CryptoKeyUsageSet::decrypt());
352 return key.impl->decrypt(js, kj::mv(algorithm), cipherText).addRef(js);
353 });
354}
355 
356jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::sign(jsg::Lock& js,
357 kj::OneOf<kj::String, SignAlgorithm> algorithmParam,
358 const CryptoKey& key,
359 kj::Array<const kj::byte> data) {
360 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
361 
362 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
363 
364 return js.evalNow([&] {
365 validateOperation(key, algorithm.name, CryptoKeyUsageSet::sign());
366 return key.impl->sign(js, kj::mv(algorithm), data).addRef(js);
367 });
368}
369 
370jsg::Promise<bool> SubtleCrypto::verify(jsg::Lock& js,
371 kj::OneOf<kj::String, SignAlgorithm> algorithmParam,
372 const CryptoKey& key,
373 kj::Array<const kj::byte> signature,
374 kj::Array<const kj::byte> data) {
375 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
376 
377 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
378 
379 return js.evalNow([&] {
380 validateOperation(key, algorithm.name, CryptoKeyUsageSet::verify());
381 return key.impl->verify(js, kj::mv(algorithm), signature, data);
382 });
383}
384 
385jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::digest(jsg::Lock& js,
386 kj::OneOf<kj::String, HashAlgorithm> algorithmParam,
387 kj::Array<const kj::byte> data) {
388 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
389 
390 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
391 
392 return js.evalNow([&] {
393 auto type = lookupDigestAlgorithm(algorithm.name).second;
394 
395 auto digestCtx = kj::disposeWith<EVP_MD_CTX_free>(EVP_MD_CTX_new());
396 KJ_ASSERT(digestCtx.get() != nullptr);
397 
398 OSSLCALL(EVP_DigestInit_ex(digestCtx.get(), type, nullptr));
399 OSSLCALL(EVP_DigestUpdate(digestCtx.get(), data.begin(), data.size()));
400 
401 auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_CTX_size(digestCtx.get()));
402 uint messageDigestSize = 0;
403 OSSLCALL(EVP_DigestFinal_ex(digestCtx.get(), buf.asArrayPtr().begin(), &messageDigestSize));
404 
405 KJ_ASSERT(messageDigestSize == buf.size());
406 return buf.addRef(js);
407 });
408}
409 
410jsg::Promise<kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair>> SubtleCrypto::generateKey(jsg::Lock& js,
411 kj::OneOf<kj::String, GenerateKeyAlgorithm> algorithmParam,
412 bool extractable,
413 kj::Array<kj::String> keyUsages) {
414 
415 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
416 
417 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
418 
419 return js.evalNow([&] {
420 CryptoAlgorithm algoImpl = lookupAlgorithm(algorithm.name).orDefault({});
421 JSG_REQUIRE(algoImpl.generateFunc != nullptr, DOMNotSupportedError,
422 "Unrecognized key generation algorithm \"", algorithm.name, "\" requested.");
423 
424 auto cryptoKeyOrPair =
425 algoImpl.generateFunc(js, algoImpl.name, kj::mv(algorithm), extractable, keyUsages);
426 KJ_SWITCH_ONEOF(cryptoKeyOrPair) {
427 KJ_CASE_ONEOF(cryptoKey, jsg::Ref<CryptoKey>) {
428 if (keyUsages.size() == 0) {
429 auto type = cryptoKey->getType();
430 JSG_REQUIRE(type != "secret" && type != "private", DOMSyntaxError,
431 "Secret/private CryptoKeys must have at least one usage.");
432 }
433 }
434 KJ_CASE_ONEOF(keyPair, CryptoKeyPair) {
435 JSG_REQUIRE(keyPair.privateKey->getUsageSet().size() != 0, DOMSyntaxError,
436 "Attempt to generate asymmetric keys with no valid private key usages.");
437 }
438 }
439 return cryptoKeyOrPair;
440 });
441}
442 
443jsg::Promise<jsg::Ref<CryptoKey>> SubtleCrypto::deriveKey(jsg::Lock& js,
444 kj::OneOf<kj::String, DeriveKeyAlgorithm> algorithmParam,
445 const CryptoKey& baseKey,
446 kj::OneOf<kj::String, ImportKeyAlgorithm> derivedKeyAlgorithmParam,
447 bool extractable,
448 kj::Array<kj::String> keyUsages) {
449 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
450 auto derivedKeyAlgorithm = interpretAlgorithmParam(kj::mv(derivedKeyAlgorithmParam));
451 
452 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
453 
454 return js.evalNow([&] {
455 validateOperation(baseKey, algorithm.name, CryptoKeyUsageSet::deriveKey());
456 
457 auto length = getKeyLength(derivedKeyAlgorithm);
458 
459 auto secret = baseKey.impl->deriveBits(js, kj::mv(algorithm), length);
460 
461 // TODO(perf): For conformance, importKey() makes a copy of `secret`. In this case we really
462 // don't need to, but rather we ought to call the appropriate CryptoKey::Impl::import*()
463 // function directly.
464 return importKeySync(
465 js, "raw", secret.copy(), kj::mv(derivedKeyAlgorithm), extractable, kj::mv(keyUsages));
466 });
467}
468 
469jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::deriveBits(jsg::Lock& js,
470 kj::OneOf<kj::String, DeriveKeyAlgorithm> algorithmParam,
471 const CryptoKey& baseKey,
472 jsg::Optional<kj::Maybe<int>> lengthParam) {
473 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
474 
475 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
476 
477 kj::Maybe<uint32_t> length = kj::none;
478 KJ_IF_SOME(maybeLength, lengthParam) {
479 KJ_IF_SOME(l, maybeLength) {
480 JSG_REQUIRE(l >= 0, TypeError, "deriveBits length must be an unsigned long integer.");
481 length = static_cast<uint32_t>(l);
482 }
483 }
484 
485 return js.evalNow([&] {
486 validateOperation(baseKey, algorithm.name, CryptoKeyUsageSet::deriveBits());
487 return baseKey.impl->deriveBits(js, kj::mv(algorithm), length).addRef(js);
488 });
489}
490 
491jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> SubtleCrypto::wrapKey(jsg::Lock& js,
492 kj::String format,
493 const CryptoKey& key,
494 const CryptoKey& wrappingKey,
495 kj::OneOf<kj::String, EncryptAlgorithm> wrapAlgorithm,
496 const jsg::TypeHandler<JsonWebKey>& jwkHandler) {
497 auto checkErrorsOnFinish =
498 webCryptoOperationBegin(__func__, wrappingKey.getAlgorithmName(), key.getAlgorithmName());
499 
500 return js.evalNow([&] {
501 auto algorithm = interpretAlgorithmParam(kj::mv(wrapAlgorithm));
502 
503 validateOperation(wrappingKey, algorithm.name, CryptoKeyUsageSet::wrapKey());
504 
505 struct PaddingDetails {
506 kj::byte byteAlignment;
507 size_t minimumLength;
508 };
509 
510 JSG_REQUIRE(key.getExtractable(), DOMInvalidAccessError, "Attempt to export non-extractable ",
511 key.getAlgorithmName(), " key.");
512 
513 auto exportedKey = key.impl->exportKey(js, kj::mv(format));
514 
515 KJ_SWITCH_ONEOF(exportedKey) {
516 KJ_CASE_ONEOF(k, jsg::JsRef<jsg::JsArrayBuffer>) {
517 auto handle = k.getHandle(js);
518 return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), handle.asArrayPtr().asConst())
519 .addRef(js);
520 }
521 KJ_CASE_ONEOF(jwk, JsonWebKey) {
522 auto stringified = js.serializeJson(jwkHandler.wrap(js, kj::mv(jwk)));
523 return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), stringified.asBytes().asConst())
524 .addRef(js);
525 }
526 }
527 
528 KJ_UNREACHABLE;
529 });
530}
531 
532jsg::Promise<jsg::Ref<CryptoKey>> SubtleCrypto::unwrapKey(jsg::Lock& js,
533 kj::String format,
534 kj::Array<const kj::byte> wrappedKey,
535 const CryptoKey& unwrappingKey,
536 kj::OneOf<kj::String, EncryptAlgorithm> unwrapAlgorithm,
537 kj::OneOf<kj::String, ImportKeyAlgorithm> unwrappedKeyAlgorithm,
538 bool extractable,
539 kj::Array<kj::String> keyUsages,
540 const jsg::TypeHandler<JsonWebKey>& jwkHandler) {
541 auto operation = __func__;
542 return js.evalNow([&]() -> jsg::Ref<CryptoKey> {
543 auto normalizedAlgorithm = interpretAlgorithmParam(kj::mv(unwrapAlgorithm));
544 auto normalizedUnwrapAlgorithm = interpretAlgorithmParam(kj::mv(unwrappedKeyAlgorithm));
545 
546 // Need a copy of the algorithm name to live in this scope, because we later kj::mv() it out.
547 auto context = kj::str(normalizedUnwrapAlgorithm.name);
548 auto checkErrorsOnFinish =
549 webCryptoOperationBegin(operation, unwrappingKey.getAlgorithmName(), context);
550 
551 validateOperation(unwrappingKey, normalizedAlgorithm.name, CryptoKeyUsageSet::unwrapKey());
552 
553 auto bytes = unwrappingKey.impl->unwrapKey(js, kj::mv(normalizedAlgorithm), wrappedKey);
554 
555 ImportKeyData importData;
556 
557 if (format == "jwk") {
558 auto jwkDict = js.parseJson(bytes.asArrayPtr().asChars());
559 
560 importData = JSG_REQUIRE_NONNULL(jwkHandler.tryUnwrap(js, jwkDict.getHandle(js)),
561 DOMDataError, "Missing \"kty\" field or corrupt JSON unwrapping key?");
562 } else {
563 importData = bytes.copy();
564 }
565 
566 auto imported = importKeySync(js, format, kj::mv(importData), kj::mv(normalizedUnwrapAlgorithm),
567 extractable, keyUsages.asPtr());
568 
569 if (imported->getType() == "secret" || imported->getType() == "private") {
570 JSG_REQUIRE(imported->getUsageSet().size() != 0, DOMSyntaxError,
571 "Secret/private CryptoKeys must have at least one usage.");
572 }
573 
574 return imported;
575 });
576}
577 
578jsg::Promise<jsg::Ref<CryptoKey>> SubtleCrypto::importKey(jsg::Lock& js,
579 kj::String format,
580 ImportKeyData keyData,
581 kj::OneOf<kj::String, ImportKeyAlgorithm> algorithmParam,
582 bool extractable,
583 kj::Array<kj::String> keyUsages) {
584 auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam));
585 
586 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm, format.asPtr());
587 
588 return js.evalNow([&] {
589 return importKeySync(js, format, kj::mv(keyData), kj::mv(algorithm), extractable, keyUsages);
590 });
591}
592 
593jsg::Ref<CryptoKey> SubtleCrypto::importKeySync(jsg::Lock& js,
594 kj::StringPtr format,
595 ImportKeyData keyData,
596 ImportKeyAlgorithm algorithm,
597 bool extractable,
598 kj::ArrayPtr<const kj::String> keyUsages) {
599 if (format == "raw" || format == "pkcs8" || format == "spki") {
600 auto& key = JSG_REQUIRE_NONNULL(keyData.tryGet<kj::Array<kj::byte>>(), TypeError,
601 "Import data provided for \"raw\", \"pkcs8\", or \"spki\" import formats must be a buffer "
602 "source.");
603 
604 // Make a copy of the key import data.
605 keyData = kj::heapArray(key.asPtr());
606 } else if (format == "jwk") {
607 JSG_REQUIRE(keyData.is<JsonWebKey>(), TypeError,
608 "Import data provided for \"jwk\" import format must be a JsonWebKey.");
609 KJ_IF_SOME(ext, keyData.get<JsonWebKey>().ext) {
610 JSG_REQUIRE(ext || !extractable, DOMDataError, "JWK ext field for \"", algorithm.name,
611 "\" is set to false but "
612 "extractable is true");
613 }
614 } else {
615 // Not prescribed by the spec here, but we might as well bail out here by return. Otherwise,
616 // the import function implementations will eventually result in this error.
617 JSG_FAIL_REQUIRE(DOMNotSupportedError, "Unrecognized key import format \"", format, "\".");
618 }
619 
620 CryptoAlgorithm algoImpl = lookupAlgorithm(algorithm.name).orDefault({});
621 JSG_REQUIRE(algoImpl.importFunc != nullptr, DOMNotSupportedError,
622 "Unrecognized key import algorithm \"", algorithm.name, "\" requested.");
623 
624 // Note: we pass in the algorithm name (algoImpl.name) because we know it is uppercase, which
625 // the `name` member of the `algorithm` value itself is not required to be. The individual
626 // implementation functions don't necessarily know the name of the algorithm whose key they're
627 // importing (importKeyAesImpl handles AES-CTR, -CBC, and -GCM, for instance), so they should
628 // rely on this value to set the imported CryptoKey's name.
629 auto cryptoKey = js.alloc<CryptoKey>(algoImpl.importFunc(
630 js, algoImpl.name, format, kj::mv(keyData), kj::mv(algorithm), extractable, keyUsages));
631 
632 if (cryptoKey->getUsageSet().size() == 0) {
633 auto type = cryptoKey->getType();
634 JSG_REQUIRE(type != "secret" && type != "private", DOMSyntaxError,
635 "Secret/private CryptoKeys must have at least one usage.");
636 }
637 
638 return cryptoKey;
639}
640 
641jsg::Promise<SubtleCrypto::ExportKeyData> SubtleCrypto::exportKey(
642 jsg::Lock& js, kj::String format, const CryptoKey& key) {
643 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, key.getAlgorithmName());
644 
645 return js.evalNow([&] {
646 // TODO(someday): Throw a NotSupportedError? The Web Crypto API spec says InvalidAccessError,
647 // but Web IDL says that's deprecated.
648 JSG_REQUIRE(key.getExtractable(), DOMInvalidAccessError, "Attempt to export non-extractable ",
649 key.getAlgorithmName(), " key.");
650 
651 return key.impl->exportKey(js, format);
652 });
653}
654 
655bool SubtleCrypto::timingSafeEqual(jsg::JsBufferSource a, jsg::JsBufferSource b) {
656 JSG_REQUIRE(a.size() == b.size(), TypeError, "Input buffers must have the same byte length.");
657 
658 // The implementation here depends entirely on the characteristics of the CRYPTO_memcmp
659 // implementation. We do not perform any additional verification that the operation is
660 // actually timing safe other than checking the input types and lengths.
661 
662 return CRYPTO_memcmp(a.asArrayPtr().begin(), b.asArrayPtr().begin(), a.size()) == 0;
663}
664 
665// =======================================================================================
666// Crypto implementation
667 
668jsg::JsArrayBufferView Crypto::getRandomValues(jsg::JsArrayBufferView buffer) {
669 // NOTE: TypeMismatchError is deprecated (obviated by TypeError), but the spec and W3C tests still
670 // expect a TypeMismatchError here.
671 JSG_REQUIRE(buffer.isIntegerType(), DOMTypeMismatchError,
672 "ArrayBufferView argument to getRandomValues() must be an integer-typed view.");
673 JSG_REQUIRE(buffer.size() <= 0x10000, DOMQuotaExceededError,
674 "getRandomValues() only accepts buffers of size <= 64K but provided ", buffer.size(),
675 " bytes.");
676 IoContext::current().getEntropySource().generate(buffer.asArrayPtr());
677 return buffer;
678}
679 
680kj::String Crypto::randomUUID() {
681 return ::workerd::randomUUID(IoContext::current().getEntropySource());
682}
683 
684// =======================================================================================
685// Crypto Streams implementation
686 
687class CRC32DigestContext final: public DigestContext {
688 public:
689 CRC32DigestContext(): value(crc32(0, Z_NULL, 0)) {}
690 ~CRC32DigestContext() noexcept override = default;
691 
692 void write(kj::ArrayPtr<kj::byte> buffer) override {
693 value = crc32(value, buffer.begin(), buffer.size());
694 }
695 
696 jsg::JsArrayBuffer close(jsg::Lock& js) override {
697 auto beValue = htobe32(value);
698 static_assert(sizeof(value) == sizeof(beValue), "CRC32 digest is not 32 bits?");
699 kj::ArrayPtr<kj::byte> be(reinterpret_cast<kj::byte*>(&beValue), sizeof(beValue));
700 return jsg::JsArrayBuffer::create(js, be);
701 }
702 
703 private:
704 uint32_t value;
705};
706 
707class CRC32CDigestContext final: public DigestContext {
708 public:
709 CRC32CDigestContext(): value(crc32c(0, nullptr, 0)) {}
710 ~CRC32CDigestContext() noexcept override = default;
711 
712 void write(kj::ArrayPtr<kj::byte> buffer) override {
713 value = crc32c(value, buffer.begin(), buffer.size());
714 }
715 
716 jsg::JsArrayBuffer close(jsg::Lock& js) override {
717 auto beValue = htobe32(value);
718 static_assert(sizeof(value) == sizeof(beValue), "CRC32 digest is not 32 bits?");
719 kj::ArrayPtr<kj::byte> be(reinterpret_cast<kj::byte*>(&beValue), sizeof(beValue));
720 return jsg::JsArrayBuffer::create(js, be);
721 }
722 
723 private:
724 uint32_t value;
725};
726 
727class CRC64NVMEDigestContext final: public DigestContext {
728 public:
729 CRC64NVMEDigestContext(): value(crc64nvme(0, nullptr, 0)) {}
730 ~CRC64NVMEDigestContext() noexcept override = default;
731 
732 void write(kj::ArrayPtr<kj::byte> buffer) override {
733 value = crc64nvme(value, buffer.begin(), buffer.size());
734 }
735 
736 jsg::JsArrayBuffer close(jsg::Lock& js) override {
737 auto beValue = htobe64(value);
738 static_assert(sizeof(value) == sizeof(beValue), "CRC64 digest is not 64 bits?");
739 kj::ArrayPtr<kj::byte> be(reinterpret_cast<kj::byte*>(&beValue), sizeof(beValue));
740 return jsg::JsArrayBuffer::create(js, be);
741 }
742 
743 private:
744 uint64_t value;
745};
746 
747class OpenSSLDigestContext final: public DigestContext {
748 public:
749 OpenSSLDigestContext(kj::StringPtr algorithm): algorithm(kj::str(algorithm)) {
750 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
751 auto type = lookupDigestAlgorithm(algorithm).second;
752 auto opensslContext = kj::disposeWith<EVP_MD_CTX_free>(EVP_MD_CTX_new());
753 KJ_ASSERT(opensslContext.get() != nullptr);
754 OSSLCALL(EVP_DigestInit_ex(opensslContext.get(), type, nullptr));
755 context = kj::mv(opensslContext);
756 }
757 ~OpenSSLDigestContext() noexcept override = default;
758 
759 void write(kj::ArrayPtr<kj::byte> buffer) override {
760 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
761 OSSLCALL(EVP_DigestUpdate(context.get(), buffer.begin(), buffer.size()));
762 }
763 
764 jsg::JsArrayBuffer close(jsg::Lock& js) override {
765 auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm);
766 uint size = 0;
767 auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_CTX_size(context.get()));
768 OSSLCALL(EVP_DigestFinal_ex(context.get(), buf.asArrayPtr().begin(), &size));
769 KJ_ASSERT(size == buf.size());
770 return buf;
771 }
772 
773 private:
774 kj::String algorithm;
775 kj::Own<EVP_MD_CTX> context;
776};
777 
778DigestStream::DigestContextPtr DigestStream::initContext(SubtleCrypto::HashAlgorithm& algorithm) {
779 if (algorithm.name == "crc32") {
780 return kj::heap<CRC32DigestContext>();
781 } else if (algorithm.name == "crc32c") {
782 return kj::heap<CRC32CDigestContext>();
783 } else if (algorithm.name == "crc64nvme") {
784 return kj::heap<CRC64NVMEDigestContext>();
785 } else {
786 return kj::heap<OpenSSLDigestContext>(algorithm.name);
787 }
788}
789 
790DigestStream::DigestStream(kj::Own<WritableStreamController> controller,
791 SubtleCrypto::HashAlgorithm algorithm,
792 jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>>::Resolver resolver,
793 jsg::Promise<jsg::JsRef<jsg::JsArrayBuffer>> promise)
794 : WritableStream(kj::mv(controller)),
795 promise(kj::mv(promise)),
796 state(Ready(kj::mv(algorithm), kj::mv(resolver))) {}
797 
798void DigestStream::dispose(jsg::Lock& js) {
799 JSG_TRY(js) {
800 KJ_IF_SOME(ready, state.tryGet<Ready>()) {
801 auto reason = js.typeError("The DigestStream was disposed.");
802 ready.resolver.reject(js, reason);
803 state.init<StreamStates::Errored>(js.v8Ref<v8::Value>(reason));
804 }
805 }
806 JSG_CATCH(exception) {
807 js.throwException(kj::mv(exception));
808 }
809}
810 
811void DigestStream::visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
812 tracker.trackField("promise", promise);
813 KJ_IF_SOME(ready, state.tryGet<Ready>()) {
814 tracker.trackField("resolver", ready.resolver);
815 }
816}
817 
818void DigestStream::visitForGc(jsg::GcVisitor& visitor) {
819 visitor.visit(promise);
820 KJ_IF_SOME(ready, state.tryGet<Ready>()) {
821 visitor.visit(ready.resolver);
822 }
823}
824 
825kj::Maybe<StreamStates::Errored> DigestStream::write(jsg::Lock& js, kj::ArrayPtr<kj::byte> buffer) {
826 KJ_SWITCH_ONEOF(state) {
827 KJ_CASE_ONEOF(closed, StreamStates::Closed) {
828 return kj::none;
829 }
830 KJ_CASE_ONEOF(errored, StreamStates::Errored) {
831 return errored.addRef(js);
832 }
833 KJ_CASE_ONEOF(ready, Ready) {
834 ready.context->write(buffer);
835 return kj::none;
836 }
837 }
838 KJ_UNREACHABLE;
839}
840 
841kj::Maybe<StreamStates::Errored> DigestStream::close(jsg::Lock& js) {
842 KJ_SWITCH_ONEOF(state) {
843 KJ_CASE_ONEOF(closed, StreamStates::Closed) {
844 return kj::none;
845 }
846 KJ_CASE_ONEOF(errored, StreamStates::Errored) {
847 return errored.addRef(js);
848 }
849 KJ_CASE_ONEOF(ready, Ready) {
850 ready.resolver.resolve(js, ready.context->close(js).addRef(js));
851 state.init<StreamStates::Closed>();
852 return kj::none;
853 }
854 }
855 KJ_UNREACHABLE;
856}
857 
858void DigestStream::abort(jsg::Lock& js, jsg::JsValue reason) {
859 // If the state is already closed or errored, then this is a non-op
860 KJ_IF_SOME(ready, state.tryGet<Ready>()) {
861 ready.resolver.reject(js, reason);
862 state.init<StreamStates::Errored>(js.v8Ref<v8::Value>(reason));
863 }
864}
865 
866jsg::Ref<DigestStream> DigestStream::constructor(jsg::Lock& js, Algorithm algorithm) {
867 auto paf = js.newPromiseAndResolver<jsg::JsRef<jsg::JsArrayBuffer>>();
868 
869 auto stream = js.alloc<DigestStream>(newWritableStreamJsController(),
870 interpretAlgorithmParam(kj::mv(algorithm)), kj::mv(paf.resolver), kj::mv(paf.promise));
871 
872 // clang-format off
873 stream->getController().setup(js, UnderlyingSink{
874 .write = [&stream = *stream](jsg::Lock& js, v8::Local<v8::Value> chunk, auto c) mutable {
875 return js.tryCatch([&] {
876 // Make sure what we got can be interpreted as bytes...
877 if (chunk->IsArrayBuffer() || chunk->IsArrayBufferView()) {
878 jsg::JsBufferSource source(chunk);
879 if (source.size() == 0) return js.resolvedPromise();
880 
881 KJ_IF_SOME(error, stream.write(js, source.asArrayPtr())) {
882 return js.rejectedPromise<void>(kj::mv(error));
883 } else {
884 } // Here to silence a compiler warning
885 stream.bytesWritten += source.size();
886 return js.resolvedPromise();
887 } else if (chunk->IsString()) {
888 // If we receive a string, we'll convert that to UTF-8 bytes and digest that.
889 auto str = js.toString(chunk);
890 if (str.size() == 0) return js.resolvedPromise();
891 KJ_IF_SOME(error, stream.write(js, str.asBytes())) {
892 return js.rejectedPromise<void>(kj::mv(error));
893 }
894 stream.bytesWritten += str.size();
895 return js.resolvedPromise();
896 }
897 return js.rejectedPromise<void>(
898 js.typeError("DigestStream is a byte stream but received an object of "
899 "non-ArrayBuffer/ArrayBufferView/string type on its writable side."));
900 }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });
901 },
902 .abort = [&stream = *stream](jsg::Lock& js, auto reason) mutable {
903 return js.tryCatch([&] {
904 stream.abort(js, jsg::JsValue(reason));
905 return js.resolvedPromise();
906 }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });
907 },
908 .close = [&stream = *stream](jsg::Lock& js) mutable {
909 return js.tryCatch([&] {
910 // If sink.close returns a non kj::none value, that means the sink was errored
911 // and we return a rejected promise here. Otherwise, we return resolved.
912 KJ_IF_SOME(error, stream.close(js)) {
913 return js.rejectedPromise<void>(kj::mv(error));
914 } else {
915 } // Here to silence a compiler warning
916 return js.resolvedPromise();
917 }, [&](jsg::Value exception) { return js.rejectedPromise<void>(kj::mv(exception)); });
918 }
919 }, kj::none);
920 // clang-format on
921 
922 return kj::mv(stream);
923}
924 
925} // namespace workerd::api