File
Blob: src/workerd/api/crypto/crypto.c++
| 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 | |
| 27 | namespace workerd::api { |
| 28 | |
| 29 | kj::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 | |
| 41 | CryptoKeyUsageSet CryptoKeyUsageSet::byName(kj::StringPtr name) { |
| 42 | for (auto& usage: singletons()) { |
| 43 | if (name == usage.name()) return usage; |
| 44 | } |
| 45 | return {}; |
| 46 | } |
| 47 | |
| 48 | kj::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 | |
| 54 | CryptoKeyUsageSet 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 | |
| 72 | namespace { |
| 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 | |
| 107 | static 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. |
| 142 | void 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. |
| 158 | kj::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 | |
| 220 | auto 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 | |
| 258 | auto webCryptoOperationBegin( |
| 259 | const char* operation, kj::StringPtr algorithm, const kj::String& context) { |
| 260 | return webCryptoOperationBegin(operation, algorithm, context.slice(0)); |
| 261 | } |
| 262 | |
| 263 | template <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 | |
| 279 | CryptoKey::CryptoKey(kj::Own<Impl> impl): impl(kj::mv(impl)) {} |
| 280 | CryptoKey::~CryptoKey() noexcept(false) {} |
| 281 | kj::StringPtr CryptoKey::getAlgorithmName() const { |
| 282 | return impl->getAlgorithmName(); |
| 283 | } |
| 284 | CryptoKey::AlgorithmVariant CryptoKey::getAlgorithm(jsg::Lock& js) const { |
| 285 | return impl->getAlgorithm(js); |
| 286 | } |
| 287 | kj::StringPtr CryptoKey::getType() const { |
| 288 | return impl->getType(); |
| 289 | } |
| 290 | bool CryptoKey::getExtractable() const { |
| 291 | return impl->isExtractable(); |
| 292 | } |
| 293 | kj::Array<kj::StringPtr> CryptoKey::getUsages() const { |
| 294 | return getUsageSet().map([](auto singleton) { return singleton.name(); }); |
| 295 | } |
| 296 | CryptoKeyUsageSet CryptoKey::getUsageSet() const { |
| 297 | return impl->getUsages(); |
| 298 | } |
| 299 | |
| 300 | bool 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 | |
| 309 | CryptoKey::AsymmetricKeyDetails CryptoKey::getAsymmetricKeyDetails(jsg::Lock& js) const { |
| 310 | return impl->getAsymmetricKeyDetail(js); |
| 311 | } |
| 312 | |
| 313 | bool CryptoKey::verifyX509Public(const X509* cert) const { |
| 314 | if (this->getType() != "public"_kj) return false; |
| 315 | return impl->verifyX509Public(cert); |
| 316 | } |
| 317 | |
| 318 | bool CryptoKey::verifyX509Private(const X509* cert) const { |
| 319 | if (this->getType() != "private"_kj) return false; |
| 320 | return impl->verifyX509Private(cert); |
| 321 | } |
| 322 | |
| 323 | void CryptoKey::visitForGc(jsg::GcVisitor& visitor) { |
| 324 | if (impl.get() == nullptr) return; |
| 325 | impl->visitForGc(visitor); |
| 326 | } |
| 327 | |
| 328 | jsg::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 | |
| 342 | jsg::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 | |
| 356 | jsg::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 | |
| 370 | jsg::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 | |
| 385 | jsg::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 | |
| 410 | jsg::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 | |
| 443 | jsg::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 | |
| 469 | jsg::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 | |
| 491 | jsg::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 | |
| 532 | jsg::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 | |
| 578 | jsg::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 | |
| 593 | jsg::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 | |
| 641 | jsg::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 | |
| 655 | bool 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 | |
| 668 | jsg::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 | |
| 680 | kj::String Crypto::randomUUID() { |
| 681 | return ::workerd::randomUUID(IoContext::current().getEntropySource()); |
| 682 | } |
| 683 | |
| 684 | // ======================================================================================= |
| 685 | // Crypto Streams implementation |
| 686 | |
| 687 | class 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 | |
| 707 | class 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 | |
| 727 | class 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 | |
| 747 | class 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 | |
| 778 | DigestStream::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 | |
| 790 | DigestStream::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 | |
| 798 | void 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 | |
| 811 | void 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 | |
| 818 | void 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 | |
| 825 | kj::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 | |
| 841 | kj::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 | |
| 858 | void 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 | |
| 866 | jsg::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 |