diff --git a/src/workerd/api/crypto/aes-test.c++ b/src/workerd/api/crypto/aes-test.c++ index a868ca866c2..ad3436ecc9b 100644 --- a/src/workerd/api/crypto/aes-test.c++ +++ b/src/workerd/api/crypto/aes-test.c++ @@ -29,9 +29,6 @@ KJ_TEST("AES-KW key wrap") { // ASAN/valgrind than using our conformance tests with test-runner. jsg::test::Evaluator e(v8System); e.getIsolate().runInLockScope([&](CryptoIsolate::Lock& isolateLock) { - auto isolate = isolateLock.v8Isolate; - auto& js = jsg::Lock::from(isolate); - auto rawWrappingKeys = std::array, 3>({ kj::heapArray({0xe6, 0x95, 0xea, 0xe3, 0xa8, 0xc0, 0x30, 0xf1, 0x76, 0xe3, 0x0e, 0x8e, 0x36, 0xf8, 0xf4, 0x31}), @@ -51,32 +48,35 @@ KJ_TEST("AES-KW key wrap") { }; bool extractable = false; - return CryptoKey::Impl::importAes(js, "AES-KW", "raw", kj::mv(rawKey), kj::mv(algorithm), - extractable, {kj::str("wrapKey"), kj::str("unwrapKey")}); + return CryptoKey::Impl::importAes(isolateLock, "AES-KW", "raw", kj::mv(rawKey), + kj::mv(algorithm), extractable, {kj::str("wrapKey"), kj::str("unwrapKey")}); }; auto keyMaterial = kj::heapArray( {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}); - for (const auto& aesKey: aesKeys) { - SubtleCrypto::EncryptAlgorithm params; - params.name = kj::str("AES-KW"); + JSG_WITHIN_CONTEXT_SCOPE(isolateLock, + isolateLock.newContext().getHandle(isolateLock), [&](jsg::Lock& js) { + for (const auto& aesKey: aesKeys) { + SubtleCrypto::EncryptAlgorithm params; + params.name = kj::str("AES-KW"); - auto wrapped = aesKey->wrapKey(kj::mv(params), keyMaterial.asPtr()); + auto wrapped = aesKey->wrapKey(js, kj::mv(params), keyMaterial.asPtr()); - params = {}; - params.name = kj::str("AES-KW"); + params = {}; + params.name = kj::str("AES-KW"); - auto unwrapped = aesKey->unwrapKey(kj::mv(params), wrapped); + auto unwrapped = aesKey->unwrapKey(js, kj::mv(params), wrapped); - KJ_EXPECT(unwrapped == keyMaterial); + KJ_EXPECT(unwrapped.asArrayPtr() == keyMaterial); - // Corruption of wrapped key material should throw. - params = {}; - params.name = kj::str("AES-KW"); - wrapped[5] += 1; - KJ_EXPECT_THROW_MESSAGE("[24 == -1]", aesKey->unwrapKey(kj::mv(params), wrapped)); - } + // Corruption of wrapped key material should throw. + params = {}; + params.name = kj::str("AES-KW"); + wrapped.asArrayPtr()[5] += 1; + KJ_EXPECT_THROW_MESSAGE("[24 == -1]", aesKey->unwrapKey(js, kj::mv(params), wrapped)); + } + }); }); } @@ -136,14 +136,15 @@ KJ_TEST("AES-CTR key wrap") { return subtle.wrapKey(js, kj::str("raw"), *toWrap, *wrappingKey, getEnc(), *jwkHandler); }) .then(js, - [&](jsg::Lock&, kj::Array wrapped) { - return subtle.unwrapKey(js, kj::str("raw"), kj::mv(wrapped), *wrappingKey, getEnc(), + [&](jsg::Lock&, jsg::BufferSource wrapped) { + auto data = kj::heapArray(wrapped.asArrayPtr()); + return subtle.unwrapKey(js, kj::str("raw"), kj::mv(data), *wrappingKey, getEnc(), getImportKeyAlg(), true, kj::arr(kj::str("encrypt")), *jwkHandler); }) .then(js, [&](jsg::Lock& js, jsg::Ref unwrapped) { return subtle.exportKey(js, kj::str("raw"), *unwrapped); }).then(js, [&](jsg::Lock&, api::SubtleCrypto::ExportKeyData roundTrippedKeyMaterial) { - KJ_ASSERT(roundTrippedKeyMaterial.get>() == KEY_DATA); + KJ_ASSERT(roundTrippedKeyMaterial.get() == KEY_DATA); completed = true; }); diff --git a/src/workerd/api/crypto/aes.c++ b/src/workerd/api/crypto/aes.c++ index 5ecf2ca6d1b..1e21c082f35 100644 --- a/src/workerd/api/crypto/aes.c++ +++ b/src/workerd/api/crypto/aes.c++ @@ -133,6 +133,11 @@ protected: CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0; } + bool equals(const jsg::BufferSource& other) const override final { + return keyData.size() == other.size() && + CRYPTO_memcmp(keyData.begin(), other.asArrayPtr().begin(), keyData.size()) == 0; + } + kj::StringPtr jsgGetMemoryName() const override { return "AesKeyBase"_kjc; } @@ -149,7 +154,7 @@ private: return keyAlgorithm; } - SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override final { + SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final { JSG_REQUIRE(format == "raw" || format == "jwk", DOMNotSupportedError, getAlgorithmName(), " key only supports exporting \"raw\" & \"jwk\", not \"", format, "\"."); @@ -184,7 +189,10 @@ private: return jwk; } - return kj::heapArray(keyData.asPtr()); + // Every export should be a separate copy. + auto backing = jsg::BackingStore::alloc(js, keyData.size()); + backing.asArrayPtr().copyFrom(keyData); + return jsg::BufferSource(js, kj::mv(backing)); } protected: @@ -201,7 +209,8 @@ public: : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} private: - kj::Array encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource encrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const override { kj::ArrayPtr iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\"."); @@ -242,31 +251,32 @@ private: // a stream cipher in that it does not add padding and can process partial blocks, meaning that // we know the exact ciphertext size in advance. auto tagByteSize = tagLength / 8; - auto cipherText = kj::heapArray(plainText.size() + tagByteSize); + auto cipherText = jsg::BackingStore::alloc(js, plainText.size() + tagByteSize); // Perform the actual encryption. int cipherSize = 0; - OSSLCALL(EVP_EncryptUpdate( - cipherCtx.get(), cipherText.begin(), &cipherSize, plainText.begin(), plainText.size())); + OSSLCALL(EVP_EncryptUpdate(cipherCtx.get(), cipherText.asArrayPtr().begin(), &cipherSize, + plainText.begin(), plainText.size())); KJ_ASSERT(cipherSize == plainText.size(), "EVP_EncryptUpdate should encrypt all at once"); int finalCipherSize = 0; - OSSLCALL( - EVP_EncryptFinal_ex(cipherCtx.get(), cipherText.begin() + cipherSize, &finalCipherSize)); + OSSLCALL(EVP_EncryptFinal_ex( + cipherCtx.get(), cipherText.asArrayPtr().begin() + cipherSize, &finalCipherSize)); KJ_ASSERT(finalCipherSize == 0, "EVP_EncryptFinal_ex should not output any data"); // Concatenate the tag onto the cipher text. KJ_ASSERT(cipherSize + tagByteSize == cipherText.size(), "imminent buffer overrun"); - OSSLCALL(EVP_CIPHER_CTX_ctrl( - cipherCtx.get(), EVP_CTRL_GCM_GET_TAG, tagByteSize, cipherText.begin() + cipherSize)); + OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_GET_TAG, tagByteSize, + cipherText.asArrayPtr().begin() + cipherSize)); cipherSize += tagByteSize; KJ_ASSERT(cipherSize == cipherText.size(), "buffer overrun"); - return cipherText; + return jsg::BufferSource(js, kj::mv(cipherText)); } - kj::Array decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource decrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const override { kj::ArrayPtr iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\"."); @@ -303,10 +313,10 @@ private: auto actualCipherText = cipherText.first(cipherText.size() - tagLength / 8); auto tagText = cipherText.slice(actualCipherText.size(), cipherText.size()); - auto plainText = kj::heapArray(actualCipherText.size()); + auto plainText = jsg::BackingStore::alloc(js, actualCipherText.size()); // Perform the actual decryption. - OSSLCALL(EVP_DecryptUpdate(cipherCtx.get(), plainText.begin(), &plainSize, + OSSLCALL(EVP_DecryptUpdate(cipherCtx.get(), plainText.asArrayPtr().begin(), &plainSize, actualCipherText.begin(), actualCipherText.size())); KJ_ASSERT(plainSize == plainText.size()); @@ -322,10 +332,10 @@ private: const_cast(tagText.begin()))); plainSize += decryptFinalHelper(getAlgorithmName(), actualCipherText.size(), plainSize, - cipherCtx.get(), plainText.begin() + plainSize); + cipherCtx.get(), plainText.asArrayPtr().begin() + plainSize); KJ_ASSERT(plainSize == plainText.size()); - return plainText; + return jsg::BufferSource(js, kj::mv(plainText)); } }; @@ -338,7 +348,8 @@ public: : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} private: - kj::Array encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource encrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const override { kj::ArrayPtr iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\"."); @@ -355,7 +366,7 @@ private: auto blockSize = EVP_CIPHER_CTX_block_size(cipherCtx.get()); size_t paddingSize = blockSize - (plainText.size() % blockSize); - auto cipherText = kj::heapArray(plainText.size() + paddingSize); + auto cipherText = jsg::BackingStore::alloc(js, plainText.size() + paddingSize); // Perform the actual encryption. // @@ -363,21 +374,22 @@ private: // takes care of it for us by default in EVP_EncryptFinal_ex(). int cipherSize = 0; - OSSLCALL(EVP_EncryptUpdate( - cipherCtx.get(), cipherText.begin(), &cipherSize, plainText.begin(), plainText.size())); + OSSLCALL(EVP_EncryptUpdate(cipherCtx.get(), cipherText.asArrayPtr().begin(), &cipherSize, + plainText.begin(), plainText.size())); KJ_ASSERT(cipherSize <= cipherText.size(), "buffer overrun"); KJ_ASSERT(cipherSize + blockSize <= cipherText.size(), "imminent buffer overrun"); int finalCipherSize = 0; - OSSLCALL( - EVP_EncryptFinal_ex(cipherCtx.get(), cipherText.begin() + cipherSize, &finalCipherSize)); + OSSLCALL(EVP_EncryptFinal_ex( + cipherCtx.get(), cipherText.asArrayPtr().begin() + cipherSize, &finalCipherSize)); cipherSize += finalCipherSize; KJ_ASSERT(cipherSize == cipherText.size(), "buffer overrun"); - return cipherText; + return jsg::BufferSource(js, kj::mv(cipherText)); } - kj::Array decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource decrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const override { kj::ArrayPtr iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\"."); @@ -408,7 +420,9 @@ private: KJ_ASSERT(plainSize <= plainText.size()); // TODO(perf): Avoid this copy, see comment in the encrypt implementation functions. - return kj::heapArray(plainText.begin(), plainSize); + auto backing = jsg::BackingStore::alloc(js, plainSize); + backing.asArrayPtr().copyFrom(plainText.first(plainSize)); + return jsg::BufferSource(js, kj::mv(backing)); } }; @@ -422,14 +436,16 @@ public: CryptoKeyUsageSet usages) : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} - kj::Array encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource encrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const override { - return encryptOrDecrypt(kj::mv(algorithm), plainText); + return encryptOrDecrypt(js, kj::mv(algorithm), plainText); } - kj::Array decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource decrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const override { - return encryptOrDecrypt(kj::mv(algorithm), cipherText); + return encryptOrDecrypt(js, kj::mv(algorithm), cipherText); } protected: @@ -448,8 +464,9 @@ protected: KJ_FAIL_ASSERT("CryptoKey has invalid data length"); } - kj::Array encryptOrDecrypt( - SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data) const { + jsg::BufferSource encryptOrDecrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, + kj::ArrayPtr data) const { auto& counter = JSG_REQUIRE_NONNULL( algorithm.counter, TypeError, "Missing \"counter\" member in \"algorithm\"."); JSG_REQUIRE(counter.size() == expectedCounterByteSize, DOMOperationError, @@ -472,9 +489,8 @@ protected: const auto& cipher = lookupAesType(keyData.size()); - kj::Vector result; // The output of AES-CTR is the same size as the input. - result.resize(data.size()); + auto result = jsg::BackingStore::alloc(js, data.size()); auto numCounterValues = newBignum(); JSG_REQUIRE(BN_lshift(numCounterValues.get(), BN_value_one(), counterBitLength), @@ -505,15 +521,15 @@ protected: if (BN_cmp(numBlocksUntilReset.get(), numOutputBlocks.get()) >= 0) { // If the counter doesn't need any wrapping, can evaluate this as a single call. - process(&cipher, data, counter, result.asPtr()); - return result.releaseAsArray(); + process(&cipher, data, counter, result.asArrayPtr()); + return jsg::BufferSource(js, kj::mv(result)); } // Need this to be done in 2 parts using the current counter block and then resetting the // counter portion of the block back to zero. auto inputSizePart1 = BN_get_word(numBlocksUntilReset.get()) * AES_BLOCK_SIZE; - process(&cipher, data.first(inputSizePart1), counter, result.asPtr()); + process(&cipher, data.first(inputSizePart1), counter, result.asArrayPtr()); // Zero the counter bits of the block. Chromium creates a copy but we own our buffer. { @@ -528,9 +544,9 @@ protected: } process(&cipher, data.slice(inputSizePart1, data.size()), counter, - result.slice(inputSizePart1, result.size())); + result.asArrayPtr().slice(inputSizePart1, result.size())); - return result.releaseAsArray(); + return jsg::BufferSource(js, kj::mv(result)); } private: @@ -620,7 +636,8 @@ public: CryptoKeyUsageSet usages) : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} - kj::Array wrapKey(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource wrapKey(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr unwrappedKey) const override { // Resources used to implement this: // https://www.ietf.org/rfc/rfc3394.txt @@ -636,8 +653,7 @@ public: "equal to 16 and less than or equal to ", SIZE_MAX - 8); - kj::Vector wrapped(unwrappedKey.size() + 8); - wrapped.resize(unwrappedKey.size() + 8); + auto wrapped = jsg::BackingStore::alloc(js, unwrappedKey.size() + 8); // Wrapping adds 8 bytes of overhead for storing the IV which we check on decryption. AES_KEY aesKey; @@ -646,14 +662,15 @@ public: internalDescribeOpensslErrors()); JSG_REQUIRE(wrapped.size() == - AES_wrap_key( - &aesKey, nullptr, wrapped.begin(), unwrappedKey.begin(), unwrappedKey.size()), + AES_wrap_key(&aesKey, nullptr, wrapped.asArrayPtr().begin(), unwrappedKey.begin(), + unwrappedKey.size()), DOMOperationError, getAlgorithmName(), " key wrapping failed", tryDescribeOpensslErrors()); - return wrapped.releaseAsArray(); + return jsg::BufferSource(js, kj::mv(wrapped)); } - kj::Array unwrapKey(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource unwrapKey(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr wrappedKey) const override { // Resources used to implement this: // https://www.ietf.org/rfc/rfc3394.txt @@ -667,9 +684,7 @@ public: "Provided a wrapped key to unwrap this is ", wrappedKey.size() * 8, " bits that is less than the minimal length of 192 bits."); - kj::Vector unwrapped(wrappedKey.size() - 8); - // Key wrap adds 8 bytes of overhead because it mixes in the IV. - unwrapped.resize(wrappedKey.size() - 8); + auto unwrapped = jsg::BackingStore::alloc(js, wrappedKey.size() - 8); AES_KEY aesKey; JSG_REQUIRE(0 == AES_set_decrypt_key(keyData.begin(), keyData.size() * 8, &aesKey), @@ -679,12 +694,12 @@ public: // null for the IV value here will tell OpenSSL to validate using the default IV from RFC3394. // https://github.com/openssl/openssl/blob/13a574d8bb2523181f8150de49bc041c9841f59d/crypto/modes/wrap128.c JSG_REQUIRE(unwrapped.size() == - AES_unwrap_key( - &aesKey, nullptr, unwrapped.begin(), wrappedKey.begin(), wrappedKey.size()), + AES_unwrap_key(&aesKey, nullptr, unwrapped.asArrayPtr().begin(), wrappedKey.begin(), + wrappedKey.size()), DOMOperationError, getAlgorithmName(), " key unwrapping failed", tryDescribeOpensslErrors()); - return unwrapped.releaseAsArray(); + return jsg::BufferSource(js, kj::mv(unwrapped)); } }; diff --git a/src/workerd/api/crypto/crypto.c++ b/src/workerd/api/crypto/crypto.c++ index e6ebd0ff690..04c4eacf8c8 100644 --- a/src/workerd/api/crypto/crypto.c++ +++ b/src/workerd/api/crypto/crypto.c++ @@ -317,7 +317,7 @@ bool CryptoKey::verifyX509Private(const X509* cert) const { return impl->verifyX509Private(cert); } -jsg::Promise> SubtleCrypto::encrypt(jsg::Lock& js, +jsg::Promise SubtleCrypto::encrypt(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& key, kj::Array plainText) { @@ -327,11 +327,11 @@ jsg::Promise> SubtleCrypto::encrypt(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::encrypt()); - return key.impl->encrypt(kj::mv(algorithm), plainText); + return key.impl->encrypt(js, kj::mv(algorithm), plainText); }); } -jsg::Promise> SubtleCrypto::decrypt(jsg::Lock& js, +jsg::Promise SubtleCrypto::decrypt(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& key, kj::Array cipherText) { @@ -341,11 +341,11 @@ jsg::Promise> SubtleCrypto::decrypt(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::decrypt()); - return key.impl->decrypt(kj::mv(algorithm), cipherText); + return key.impl->decrypt(js, kj::mv(algorithm), cipherText); }); } -jsg::Promise> SubtleCrypto::sign(jsg::Lock& js, +jsg::Promise SubtleCrypto::sign(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& key, kj::Array data) { @@ -355,7 +355,7 @@ jsg::Promise> SubtleCrypto::sign(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::sign()); - return key.impl->sign(kj::mv(algorithm), data); + return key.impl->sign(js, kj::mv(algorithm), data); }); } @@ -370,11 +370,11 @@ jsg::Promise SubtleCrypto::verify(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::verify()); - return key.impl->verify(kj::mv(algorithm), signature, data); + return key.impl->verify(js, kj::mv(algorithm), signature, data); }); } -jsg::Promise> SubtleCrypto::digest(jsg::Lock& js, +jsg::Promise SubtleCrypto::digest(jsg::Lock& js, kj::OneOf algorithmParam, kj::Array data) { auto algorithm = interpretAlgorithmParam(kj::mv(algorithmParam)); @@ -389,12 +389,15 @@ jsg::Promise> SubtleCrypto::digest(jsg::Lock& js, OSSLCALL(EVP_DigestInit_ex(digestCtx.get(), type, nullptr)); OSSLCALL(EVP_DigestUpdate(digestCtx.get(), data.begin(), data.size())); - auto messageDigest = kj::heapArray(EVP_MD_CTX_size(digestCtx.get())); + + auto messageDigest = + jsg::BackingStore::alloc(js, EVP_MD_CTX_size(digestCtx.get())); uint messageDigestSize = 0; - OSSLCALL(EVP_DigestFinal_ex(digestCtx.get(), messageDigest.begin(), &messageDigestSize)); + OSSLCALL(EVP_DigestFinal_ex( + digestCtx.get(), messageDigest.asArrayPtr().begin(), &messageDigestSize)); KJ_ASSERT(messageDigestSize == messageDigest.size()); - return kj::mv(messageDigest); + return jsg::BufferSource(js, kj::mv(messageDigest)); }); } @@ -452,12 +455,13 @@ jsg::Promise> SubtleCrypto::deriveKey(jsg::Lock& js, // TODO(perf): For conformance, importKey() makes a copy of `secret`. In this case we really // don't need to, but rather we ought to call the appropriate CryptoKey::Impl::import*() // function directly. + auto data = kj::heapArray(secret); return importKeySync( - js, "raw", kj::mv(secret), kj::mv(derivedKeyAlgorithm), extractable, kj::mv(keyUsages)); + js, "raw", kj::mv(data), kj::mv(derivedKeyAlgorithm), extractable, kj::mv(keyUsages)); }); } -jsg::Promise> SubtleCrypto::deriveBits(jsg::Lock& js, +jsg::Promise SubtleCrypto::deriveBits(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& baseKey, jsg::Optional> lengthParam) { @@ -479,7 +483,7 @@ jsg::Promise> SubtleCrypto::deriveBits(jsg::Lock& js, }); } -jsg::Promise> SubtleCrypto::wrapKey(jsg::Lock& js, +jsg::Promise SubtleCrypto::wrapKey(jsg::Lock& js, kj::String format, const CryptoKey& key, const CryptoKey& wrappingKey, @@ -501,15 +505,15 @@ jsg::Promise> SubtleCrypto::wrapKey(jsg::Lock& js, JSG_REQUIRE(key.getExtractable(), DOMInvalidAccessError, "Attempt to export non-extractable ", key.getAlgorithmName(), " key."); - auto exportedKey = key.impl->exportKey(kj::mv(format)); + auto exportedKey = key.impl->exportKey(js, kj::mv(format)); KJ_SWITCH_ONEOF(exportedKey) { - KJ_CASE_ONEOF(k, kj::Array) { - return wrappingKey.impl->wrapKey(kj::mv(algorithm), k.asPtr().asConst()); + KJ_CASE_ONEOF(k, jsg::BufferSource) { + return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), k.asArrayPtr().asConst()); } KJ_CASE_ONEOF(jwk, JsonWebKey) { auto stringified = js.serializeJson(jwkHandler.wrap(js, kj::mv(jwk))); - return wrappingKey.impl->wrapKey(kj::mv(algorithm), stringified.asBytes().asConst()); + return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), stringified.asBytes().asConst()); } } @@ -538,18 +542,17 @@ jsg::Promise> SubtleCrypto::unwrapKey(jsg::Lock& js, validateOperation(unwrappingKey, normalizedAlgorithm.name, CryptoKeyUsageSet::unwrapKey()); - kj::Array bytes = - unwrappingKey.impl->unwrapKey(kj::mv(normalizedAlgorithm), wrappedKey); + auto bytes = unwrappingKey.impl->unwrapKey(js, kj::mv(normalizedAlgorithm), wrappedKey); ImportKeyData importData; if (format == "jwk") { - auto jwkDict = js.parseJson(bytes.asChars()); + auto jwkDict = js.parseJson(bytes.asArrayPtr().asChars()); importData = JSG_REQUIRE_NONNULL(jwkHandler.tryUnwrap(js, jwkDict.getHandle(js)), DOMDataError, "Missing \"kty\" field or corrupt JSON unwrapping key?"); } else { - importData = kj::mv(bytes); + importData = kj::heapArray(bytes); } auto imported = importKeySync(js, format, kj::mv(importData), kj::mv(normalizedUnwrapAlgorithm), @@ -637,7 +640,7 @@ jsg::Promise SubtleCrypto::exportKey( JSG_REQUIRE(key.getExtractable(), DOMInvalidAccessError, "Attempt to export non-extractable ", key.getAlgorithmName(), " key."); - return key.impl->exportKey(format); + return key.impl->exportKey(js, format); }); } diff --git a/src/workerd/api/crypto/crypto.h b/src/workerd/api/crypto/crypto.h index 4bbd2b46d93..b205f861af3 100644 --- a/src/workerd/api/crypto/crypto.h +++ b/src/workerd/api/crypto/crypto.h @@ -207,19 +207,28 @@ class CryptoKey: public jsg::Object { KJ_SWITCH_ONEOF(publicExponent) { KJ_CASE_ONEOF(array, BigInteger) { if (fixPublicExp) { - auto expCopy = kj::heapArray(array.asPtr()); - jsg::BackingStore expBack = jsg::BackingStore::from(kj::mv(expCopy)); + // alloc will, by default create a Uint8Array + auto expBack = jsg::BackingStore::alloc(js, array.size()); + expBack.asArrayPtr().copyFrom(array); return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; } else { - return {name, modulusLength, kj::heapArray(array.asPtr()), hash}; + auto expBack = jsg::BackingStore::alloc(js, array.size()); + expBack.asArrayPtr().copyFrom(array); + return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; } } KJ_CASE_ONEOF(source, jsg::BufferSource) { // Should only happen if the flag is enabled and an algorithm field is cloned twice. - KJ_ASSERT(fixPublicExp == true); - auto expCopy = kj::heapArray(source.asArrayPtr()); - jsg::BackingStore expBack = jsg::BackingStore::from(kj::mv(expCopy)); - return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; + if (fixPublicExp) { + // alloc will, by default create a Uint8Array + auto expBack = jsg::BackingStore::alloc(js, source.size()); + expBack.asArrayPtr().copyFrom(source); + return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; + } else { + auto expBack = jsg::BackingStore::alloc(js, source.size()); + expBack.asArrayPtr().copyFrom(source); + return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; + } } } KJ_UNREACHABLE; @@ -526,18 +535,18 @@ class SubtleCrypto: public jsg::Object { }; using ImportKeyData = kj::OneOf, JsonWebKey>; - using ExportKeyData = kj::OneOf, JsonWebKey>; + using ExportKeyData = kj::OneOf; - jsg::Promise> encrypt(jsg::Lock& js, + jsg::Promise encrypt(jsg::Lock& js, kj::OneOf algorithm, const CryptoKey& key, kj::Array plainText); - jsg::Promise> decrypt(jsg::Lock& js, + jsg::Promise decrypt(jsg::Lock& js, kj::OneOf algorithm, const CryptoKey& key, kj::Array cipherText); - jsg::Promise> sign(jsg::Lock& js, + jsg::Promise sign(jsg::Lock& js, kj::OneOf algorithm, const CryptoKey& key, kj::Array data); @@ -547,7 +556,7 @@ class SubtleCrypto: public jsg::Object { kj::Array signature, kj::Array data); - jsg::Promise> digest(jsg::Lock& js, + jsg::Promise digest(jsg::Lock& js, kj::OneOf algorithm, kj::Array data); @@ -562,7 +571,7 @@ class SubtleCrypto: public jsg::Object { kj::OneOf derivedKeyAlgorithm, bool extractable, kj::Array keyUsages); - jsg::Promise> deriveBits(jsg::Lock& js, + jsg::Promise deriveBits(jsg::Lock& js, kj::OneOf algorithm, const CryptoKey& baseKey, // The operation needs to be able to take both undefined and null @@ -589,7 +598,7 @@ class SubtleCrypto: public jsg::Object { jsg::Promise exportKey(jsg::Lock& js, kj::String format, const CryptoKey& key); - jsg::Promise> wrapKey(jsg::Lock& js, + jsg::Promise wrapKey(jsg::Lock& js, kj::String format, const CryptoKey& key, const CryptoKey& wrappingKey, @@ -622,6 +631,34 @@ class SubtleCrypto: public jsg::Object { JSG_METHOD(wrapKey); JSG_METHOD(unwrapKey); JSG_METHOD(timingSafeEqual); + + JSG_TS_OVERRIDE({ + wrapKey(format: string, + key: CryptoKey, + wrappingKey: CryptoKey, + wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm) + : Promise; + deriveBits(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, + baseKey : CryptoKey, + length? : number | null) + : Promise; + digest(algorithm: string | SubtleCryptoHashAlgorithm, + data: ArrayBuffer | ArrayBufferView) + : Promise; + sign(algorithm: string | SubtleCryptoSignAlgorithm, + key: CryptoKey, + data: ArrayBuffer | ArrayBufferView) + : Promise; + decrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, + key: CryptoKey, + cipherText: ArrayBuffer | ArrayBufferView) + : Promise; + encrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, + key: CryptoKey, + plainText: ArrayBuffer | ArrayBufferView) + : Promise; + exportKey(format: string, key: CryptoKey) : Promise; + }); } }; diff --git a/src/workerd/api/crypto/digest.c++ b/src/workerd/api/crypto/digest.c++ index f27347e91c4..9a5bc5a6dc0 100644 --- a/src/workerd/api/crypto/digest.c++ +++ b/src/workerd/api/crypto/digest.c++ @@ -38,35 +38,38 @@ public: } private: - kj::Array sign( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override { - return computeHmac(kj::mv(algorithm), data); + jsg::BufferSource sign(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const override { + return computeHmac(js, kj::mv(algorithm), data); } - bool verify(SubtleCrypto::SignAlgorithm&& algorithm, + bool verify(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr signature, kj::ArrayPtr data) const override { - auto messageDigest = computeHmac(kj::mv(algorithm), data); + auto messageDigest = computeHmac(js, kj::mv(algorithm), data); return messageDigest.size() == signature.size() && - CRYPTO_memcmp(messageDigest.begin(), signature.begin(), signature.size()) == 0; + CRYPTO_memcmp(messageDigest.asArrayPtr().begin(), signature.begin(), signature.size()) == 0; } - kj::Array computeHmac( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const { + jsg::BufferSource computeHmac(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const { // For HMAC, the hash is specified when creating the key, not at call time. auto type = lookupDigestAlgorithm(keyAlgorithm.hash.name).second; - auto messageDigest = kj::heapArray(EVP_MD_size(type)); + auto messageDigest = jsg::BackingStore::alloc(js, EVP_MD_size(type)); uint messageDigestSize = 0; auto ptr = HMAC(type, keyData.begin(), keyData.size(), data.begin(), data.size(), - messageDigest.begin(), &messageDigestSize); + messageDigest.asArrayPtr().begin(), &messageDigestSize); JSG_REQUIRE(ptr != nullptr, DOMOperationError, "HMAC computation failed."); KJ_ASSERT(messageDigestSize == messageDigest.size()); - return kj::mv(messageDigest); + return jsg::BufferSource(js, kj::mv(messageDigest)); } - SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override { + SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override { JSG_REQUIRE(format == "raw" || format == "jwk", DOMNotSupportedError, "Unimplemented key export format \"", format, "\"."); @@ -95,7 +98,9 @@ private: return jwk; } - return kj::heapArray(keyData.asPtr()); + auto backing = jsg::BackingStore::alloc(js, keyData.size()); + backing.asArrayPtr().copyFrom(keyData); + return jsg::BufferSource(js, kj::mv(backing)); } kj::StringPtr getAlgorithmName() const override { @@ -130,7 +135,8 @@ void zeroOutTrailingKeyBits(kj::Array& keyDataArray, int keyBitLength) } } -kj::Own initHmacContext(kj::StringPtr algorithm, HmacContext::KeyData& key) { +kj::Own initHmacContext( + jsg::Lock& js, kj::StringPtr algorithm, HmacContext::KeyData& key) { static constexpr auto handle = [](kj::StringPtr algorithm, kj::ArrayPtr key) { ClearErrorOnReturn clearErrorOnReturn; JSG_REQUIRE(key.size() <= INT_MAX, RangeError, "key is too long"); @@ -150,10 +156,10 @@ kj::Own initHmacContext(kj::StringPtr algorithm, HmacContext::KeyData& } KJ_CASE_ONEOF(key2, CryptoKey::Impl*) { // We already checked that the key is a secret key, so the following should succeed. - SubtleCrypto::ExportKeyData keyData = key2->exportKey("raw"_kj); + SubtleCrypto::ExportKeyData keyData = key2->exportKey(js, "raw"_kj); KJ_SWITCH_ONEOF(keyData) { - KJ_CASE_ONEOF(key_data, kj::Array) { + KJ_CASE_ONEOF(key_data, jsg::BufferSource) { return handle(algorithm, key_data); } KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) { @@ -166,8 +172,8 @@ kj::Own initHmacContext(kj::StringPtr algorithm, HmacContext::KeyData& } } // namespace -HmacContext::HmacContext(kj::StringPtr algorithm, KeyData key) - : state(initHmacContext(algorithm, key)) {} +HmacContext::HmacContext(jsg::Lock& js, kj::StringPtr algorithm, KeyData key) + : state(initHmacContext(js, algorithm, key)) {} void HmacContext::update(kj::ArrayPtr data) { KJ_SWITCH_ONEOF(state) { diff --git a/src/workerd/api/crypto/digest.h b/src/workerd/api/crypto/digest.h index 5724dfeed5b..7ea94ab876e 100644 --- a/src/workerd/api/crypto/digest.h +++ b/src/workerd/api/crypto/digest.h @@ -11,7 +11,7 @@ class HmacContext final { public: using KeyData = kj::OneOf, CryptoKey::Impl*>; - HmacContext(kj::StringPtr algorithm, KeyData key); + HmacContext(jsg::Lock& js, kj::StringPtr algorithm, KeyData key); HmacContext(HmacContext&&) = default; HmacContext& operator=(HmacContext&&) = default; KJ_DISALLOW_COPY(HmacContext); diff --git a/src/workerd/api/crypto/ec.c++ b/src/workerd/api/crypto/ec.c++ index 2613f31c610..03a6be38f95 100644 --- a/src/workerd/api/crypto/ec.c++ +++ b/src/workerd/api/crypto/ec.c++ @@ -84,7 +84,7 @@ SubtleCrypto::JsonWebKey Ec::toJwk(KeyType keyType, kj::StringPtr curveName) con return jwk; } -kj::Array Ec::getRawPublicKey() const { +jsg::BufferSource Ec::getRawPublicKey(jsg::Lock& js) const { JSG_REQUIRE_NONNULL(group, InternalDOMOperationError, "No elliptic curve group in this key", tryDescribeOpensslErrors()); auto publicKey = getPublicKey(); @@ -108,7 +108,10 @@ kj::Array Ec::getRawPublicKey() const { JSG_REQUIRE(1 == CBB_finish(&cbb, &raw, &raw_len), InternalDOMOperationError, "Failed to finish CBB", internalDescribeOpensslErrors()); - return kj::Array(raw, raw_len, SslArrayDisposer::INSTANCE); + auto backing = jsg::BackingStore::alloc(js, raw_len); + auto src = kj::arrayPtr(raw, raw_len); + backing.asArrayPtr().copyFrom(src); + return jsg::BufferSource(js, kj::mv(backing)); } CryptoKey::AsymmetricKeyDetails Ec::getAsymmetricKeyDetail() const { @@ -168,7 +171,7 @@ public: "algorithms for historical reasons.)")); } - kj::Array deriveBits(jsg::Lock& js, + jsg::BufferSource deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe resultBitLength) const override final { JSG_REQUIRE(keyAlgorithm.name == "ECDH", DOMNotSupportedError, @@ -271,10 +274,13 @@ public: sharedSecret.back() &= mask; - return sharedSecret.releaseAsArray(); + auto backing = jsg::BackingStore::alloc(js, sharedSecret.size()); + backing.asArrayPtr().copyFrom(sharedSecret); + return jsg::BufferSource(js, kj::mv(backing)); } - kj::Array signatureSslToWebCrypto(kj::Array signature) const override { + jsg::BufferSource signatureSslToWebCrypto( + jsg::Lock& js, kj::Array signature) const override { // An EC signature is two big integers "r" and "s". WebCrypto wants us to just concatenate both // integers, using a constant size of each that depends on the curve size. OpenSSL wants to // encode them in some ASN.1 wrapper with variable-width sizes. Ugh. @@ -319,23 +325,23 @@ public: KJ_ASSERT(s.size() <= rsSize); // Construct WebCrypto format. - auto out = kj::heapArray(rsSize * 2); - out.asPtr().fill(0); + auto out = jsg::BackingStore::alloc(js, rsSize * 2); // We're dealing with big-endian, so we have to align the copy to the right. This is exactly // why big-endian is the wrong edian. - memcpy(out.begin() + rsSize - r.size(), r.begin(), r.size()); - memcpy(out.end() - s.size(), s.begin(), s.size()); - return out; + memcpy(out.asArrayPtr().begin() + rsSize - r.size(), r.begin(), r.size()); + memcpy(out.asArrayPtr().end() - s.size(), s.begin(), s.size()); + return jsg::BufferSource(js, kj::mv(out)); } - kj::Array signatureWebCryptoToSsl( - kj::ArrayPtr signature) const override { + jsg::BufferSource signatureWebCryptoToSsl( + jsg::Lock& js, kj::ArrayPtr signature) const override { requireSigningAbility(); if (signature.size() != rsSize * 2) { // The signature is the wrong size. Return an empty signature, which will be judged invalid. - return nullptr; + auto backing = jsg::BackingStore::alloc(js, 0); + return jsg::BufferSource(js, kj::mv(backing)); } auto r = signature.first(rsSize); @@ -351,9 +357,9 @@ public: size_t bodySize = 4 + padR + padS + r.size() + s.size(); size_t resultSize = 2 + bodySize + (bodySize >= 128); - auto result = kj::heapArray(resultSize); + auto result = jsg::BackingStore::alloc(js, resultSize); - kj::byte* pos = result.begin(); + kj::byte* pos = result.asArrayPtr().begin(); *pos++ = 0x30; if (bodySize < 128) { *pos++ = bodySize; @@ -374,9 +380,9 @@ public: memcpy(pos, s.begin(), s.size()); pos += s.size(); - KJ_ASSERT(pos == result.end()); + KJ_ASSERT(pos == result.asArrayPtr().end()); - return result; + return jsg::BufferSource(js, kj::mv(result)); } static kj::OneOf, CryptoKeyPair> generateElliptic( @@ -404,12 +410,12 @@ private: return ec.toJwk(getTypeEnum(), kj::str(keyAlgorithm.namedCurve)); } - kj::Array exportRaw() const override final { + jsg::BufferSource exportRaw(jsg::Lock& js) const override final { JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, "Raw export of elliptic curve keys is only allowed for public keys."); return JSG_REQUIRE_NONNULL(Ec::tryGetEc(getEvpPkey()), InternalDOMOperationError, "No elliptic curve data backing key", tryDescribeOpensslErrors()) - .getRawPublicKey(); + .getRawPublicKey(js); } CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail() const override { @@ -825,8 +831,9 @@ public: KJ_UNIMPLEMENTED(); } - kj::Array sign( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override { + jsg::BufferSource sign(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const override { JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError, "Asymmetric signing requires a private key."); @@ -836,7 +843,7 @@ public: // inconsistent with the broader WebCrypto standard. Filed an issue with the standard for // clarification: https://github.com/tQsW/webcrypto-curve25519/issues/7 - auto signature = kj::heapArray(ED25519_SIGNATURE_LEN); + auto signature = jsg::BackingStore::alloc(js, ED25519_SIGNATURE_LEN); size_t signatureLength = signature.size(); // NOTE: Even though there's a ED25519_sign/ED25519_verify methods, they don't actually seem to @@ -848,18 +855,19 @@ public: InternalDOMOperationError, "Failed to initialize Ed25519 signing digest", internalDescribeOpensslErrors()); JSG_REQUIRE(1 == - EVP_DigestSign( - digestCtx.get(), signature.begin(), &signatureLength, data.begin(), data.size()), + EVP_DigestSign(digestCtx.get(), signature.asArrayPtr().begin(), &signatureLength, + data.begin(), data.size()), InternalDOMOperationError, "Failed to sign with Ed25119 key", internalDescribeOpensslErrors()); JSG_REQUIRE(signatureLength == signature.size(), InternalDOMOperationError, "Unexpected change in size signing Ed25519", signatureLength); - return signature; + return jsg::BufferSource(js, kj::mv(signature)); } - bool verify(SubtleCrypto::SignAlgorithm&& algorithm, + bool verify(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr signature, kj::ArrayPtr data) const override { ClearErrorOnReturn clearErrorOnReturn; @@ -887,7 +895,7 @@ public: return !!result; } - kj::Array deriveBits(jsg::Lock& js, + jsg::BufferSource deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe resultBitLength) const override final { JSG_REQUIRE(getAlgorithmName() == "X25519", DOMNotSupportedError, @@ -957,7 +965,10 @@ public: KJ_DASSERT(numBitsToMaskOff < 8, numBitsToMaskOff); uint8_t mask = ~((1 << numBitsToMaskOff) - 1); sharedSecret.back() &= mask; - return sharedSecret.releaseAsArray(); + + auto backing = jsg::BackingStore::alloc(js, sharedSecret.size()); + backing.asArrayPtr().copyFrom(sharedSecret); + return jsg::BufferSource(js, kj::mv(backing)); } CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail() const override { @@ -1017,22 +1028,22 @@ private: return jwk; } - kj::Array exportRaw() const override final { + jsg::BufferSource exportRaw(jsg::Lock& js) const override final { JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, "Raw export of ", getAlgorithmName(), " keys is only allowed for public keys."); - kj::Vector raw(ED25519_PUBLIC_KEY_LEN); - raw.resize(ED25519_PUBLIC_KEY_LEN); + auto raw = jsg::BackingStore::alloc(js, ED25519_PUBLIC_KEY_LEN); size_t exportedLength = raw.size(); - JSG_REQUIRE(1 == EVP_PKEY_get_raw_public_key(getEvpPkey(), raw.begin(), &exportedLength), + JSG_REQUIRE( + 1 == EVP_PKEY_get_raw_public_key(getEvpPkey(), raw.asArrayPtr().begin(), &exportedLength), InternalDOMOperationError, "Failed to retrieve public key", internalDescribeOpensslErrors()); JSG_REQUIRE(exportedLength == raw.size(), InternalDOMOperationError, "Unexpected change in size", raw.size(), exportedLength); - return raw.releaseAsArray(); + return jsg::BufferSource(js, kj::mv(raw)); } }; diff --git a/src/workerd/api/crypto/ec.h b/src/workerd/api/crypto/ec.h index e7b6e1cd025..20c0185d118 100644 --- a/src/workerd/api/crypto/ec.h +++ b/src/workerd/api/crypto/ec.h @@ -36,7 +36,7 @@ class Ec final { SubtleCrypto::JsonWebKey toJwk( KeyType keyType, kj::StringPtr curveName) const KJ_WARN_UNUSED_RESULT; - kj::Array getRawPublicKey() const KJ_WARN_UNUSED_RESULT; + jsg::BufferSource getRawPublicKey(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT; CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail() const; diff --git a/src/workerd/api/crypto/hkdf.c++ b/src/workerd/api/crypto/hkdf.c++ index 3a0d3922ba4..9242ac87764 100644 --- a/src/workerd/api/crypto/hkdf.c++ +++ b/src/workerd/api/crypto/hkdf.c++ @@ -33,7 +33,7 @@ public: } private: - kj::Array deriveBits(jsg::Lock& js, + jsg::BufferSource deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe maybeLength) const override { kj::StringPtr hashName = api::getAlgorithmName( @@ -54,7 +54,7 @@ private: auto derivedLengthBytes = length / 8; - return JSG_REQUIRE_NONNULL(hkdf(derivedLengthBytes, hashType, keyData, salt, info), + return JSG_REQUIRE_NONNULL(hkdf(js, derivedLengthBytes, hashType, keyData, salt, info), DOMOperationError, "HKDF deriveBits failed."); } @@ -80,17 +80,18 @@ private: } // namespace -kj::Maybe> hkdf(size_t length, +kj::Maybe hkdf(jsg::Lock& js, + size_t length, const EVP_MD* digest, kj::ArrayPtr key, kj::ArrayPtr salt, kj::ArrayPtr info) { - auto buf = kj::heapArray(length); - if (HKDF(buf.begin(), length, digest, key.begin(), key.size(), salt.begin(), salt.size(), - info.begin(), info.size()) != 1) { + auto buf = jsg::BackingStore::alloc(js, length); + if (HKDF(buf.asArrayPtr().begin(), length, digest, key.begin(), key.size(), salt.begin(), + salt.size(), info.begin(), info.size()) != 1) { return kj::none; } - return kj::mv(buf); + return jsg::BufferSource(js, kj::mv(buf)); } kj::Own CryptoKey::Impl::importHkdf(jsg::Lock& js, diff --git a/src/workerd/api/crypto/impl.c++ b/src/workerd/api/crypto/impl.c++ index ec408bd43ec..8c33aaacce4 100644 --- a/src/workerd/api/crypto/impl.c++ +++ b/src/workerd/api/crypto/impl.c++ @@ -218,6 +218,10 @@ bool CryptoKey::Impl::equals(const kj::Array& other) const { KJ_FAIL_REQUIRE("Unable to compare raw key material for this key"); } +bool CryptoKey::Impl::equals(const jsg::BufferSource& other) const { + KJ_FAIL_REQUIRE("Unable to compare raw key material for this key"); +} + kj::Own CryptoKey::Impl::from(kj::Own key) { switch (EVP_PKEY_id(key.get())) { case EVP_PKEY_RSA: @@ -275,6 +279,12 @@ kj::Maybe> bignumToArrayPadded(const BIGNUM& n, size_t padde return kj::mv(result); } +kj::Maybe bignumToArray(jsg::Lock& js, const BIGNUM& n) { + auto backing = jsg::BackingStore::alloc(js, BN_num_bytes(&n)); + if (BN_bn2bin(&n, backing.asArrayPtr().begin()) != backing.size()) return kj::none; + return jsg::BufferSource(js, kj::mv(backing)); +} + kj::Maybe bignumToArrayPadded(jsg::Lock& js, const BIGNUM& n) { auto result = jsg::BackingStore::alloc(js, BN_num_bytes(&n)); if (BN_bn2binpad(&n, result.asArrayPtr().begin(), result.size()) != result.size()) { diff --git a/src/workerd/api/crypto/impl.h b/src/workerd/api/crypto/impl.h index 54af5728567..159722b035d 100644 --- a/src/workerd/api/crypto/impl.h +++ b/src/workerd/api/crypto/impl.h @@ -145,30 +145,34 @@ class CryptoKey::Impl { return usages; } - virtual kj::Array encrypt( - SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const { + virtual jsg::BufferSource encrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, + kj::ArrayPtr plainText) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The encrypt operation is not implemented for \"", getAlgorithmName(), "\"."); } - virtual kj::Array decrypt( - SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const { + virtual jsg::BufferSource decrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, + kj::ArrayPtr cipherText) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The decrypt operation is not implemented for \"", getAlgorithmName(), "\"."); } - virtual kj::Array sign( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const { + virtual jsg::BufferSource sign(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The sign operation is not implemented for \"", getAlgorithmName(), "\"."); } - virtual bool verify(SubtleCrypto::SignAlgorithm&& algorithm, + virtual bool verify(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr signature, kj::ArrayPtr data) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The verify operation is not implemented for \"", getAlgorithmName(), "\"."); } - virtual kj::Array deriveBits(jsg::Lock& js, + virtual jsg::BufferSource deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe length) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, @@ -176,21 +180,23 @@ class CryptoKey::Impl { "\"."); } - virtual kj::Array wrapKey( - SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr unwrappedKey) const { + virtual jsg::BufferSource wrapKey(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, + kj::ArrayPtr unwrappedKey) const { // For many algorithms, wrapKey() is the same as encrypt(), so as a convenience the default // implementation just forwards to it. - return encrypt(kj::mv(algorithm), unwrappedKey); + return encrypt(js, kj::mv(algorithm), unwrappedKey); } - virtual kj::Array unwrapKey( - SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr wrappedKey) const { + virtual jsg::BufferSource unwrapKey(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, + kj::ArrayPtr wrappedKey) const { // For many algorithms, unwrapKey() is the same as decrypt(), so as a convenience the default // implementation just forwards to it. - return decrypt(kj::mv(algorithm), wrappedKey); + return decrypt(js, kj::mv(algorithm), wrappedKey); } - virtual SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const { + virtual SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "Unrecognized or unsupported export of \"", getAlgorithmName(), "\" requested."); } @@ -203,7 +209,8 @@ class CryptoKey::Impl { // cipher and passphrase. // Rather than modify the existing exportKey API, we add this new variant to support the // Node.js implementation without risking breaking the Web Crypto impl. - virtual kj::Array exportKeyExt(kj::StringPtr format, + virtual jsg::BufferSource exportKeyExt(jsg::Lock& js, + kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher = kj::none, jsg::Optional> passphrase = kj::none) const { @@ -228,6 +235,7 @@ class CryptoKey::Impl { virtual bool equals(const Impl& other) const = 0; virtual bool equals(const kj::Array& other) const; + virtual bool equals(const jsg::BufferSource& other) const; virtual kj::StringPtr jsgGetMemoryName() const { return "CryptoKey::Impl"; @@ -325,6 +333,7 @@ BIGNUM* toBignumUnowned(kj::ArrayPtr data); kj::Maybe> bignumToArray(const BIGNUM& bignum); kj::Maybe> bignumToArrayPadded(const BIGNUM& bignum); kj::Maybe> bignumToArrayPadded(const BIGNUM& bignum, size_t paddedLength); +kj::Maybe bignumToArray(jsg::Lock& js, const BIGNUM& bignum); kj::Maybe bignumToArrayPadded(jsg::Lock& js, const BIGNUM& bignum); kj::Maybe bignumToArrayPadded( jsg::Lock& js, const BIGNUM& bignum, size_t paddedLength); diff --git a/src/workerd/api/crypto/kdf.h b/src/workerd/api/crypto/kdf.h index 1488b77bf71..675f76ebb37 100644 --- a/src/workerd/api/crypto/kdf.h +++ b/src/workerd/api/crypto/kdf.h @@ -3,6 +3,8 @@ // https://opensource.org/licenses/Apache-2.0 #pragma once +#include + #include #include @@ -12,21 +14,24 @@ typedef struct env_md_st EVP_MD; namespace workerd::api { // Perform HKDF key derivation. -kj::Maybe> hkdf(size_t length, +kj::Maybe hkdf(jsg::Lock& js, + size_t length, const EVP_MD* digest, kj::ArrayPtr key, kj::ArrayPtr salt, kj::ArrayPtr info); // Perform PBKDF2 key derivation. -kj::Maybe> pbkdf2(size_t length, +kj::Maybe pbkdf2(jsg::Lock& js, + size_t length, size_t iterations, const EVP_MD* digest, kj::ArrayPtr password, kj::ArrayPtr salt); // Perform Scrypt key derivation. -kj::Maybe> scrypt(size_t length, +kj::Maybe scrypt(jsg::Lock& js, + size_t length, uint32_t N, uint32_t r, uint32_t p, diff --git a/src/workerd/api/crypto/keys.c++ b/src/workerd/api/crypto/keys.c++ index d313275adf6..d3d009260f3 100644 --- a/src/workerd/api/crypto/keys.c++ +++ b/src/workerd/api/crypto/keys.c++ @@ -30,17 +30,22 @@ AsymmetricKeyCryptoKeyImpl::AsymmetricKeyCryptoKeyImpl(AsymmetricKeyData&& key, KJ_DASSERT(keyType != KeyType::SECRET); } -kj::Array AsymmetricKeyCryptoKeyImpl::signatureSslToWebCrypto( - kj::Array signature) const { - return kj::mv(signature); +jsg::BufferSource AsymmetricKeyCryptoKeyImpl::signatureSslToWebCrypto( + jsg::Lock& js, kj::Array signature) const { + auto backing = jsg::BackingStore::alloc(js, signature.size()); + backing.asArrayPtr().copyFrom(signature); + return jsg::BufferSource(js, kj::mv(backing)); } -kj::Array AsymmetricKeyCryptoKeyImpl::signatureWebCryptoToSsl( - kj::ArrayPtr signature) const { - return {signature.begin(), signature.size(), kj::NullArrayDisposer::instance}; +jsg::BufferSource AsymmetricKeyCryptoKeyImpl::signatureWebCryptoToSsl( + jsg::Lock& js, kj::ArrayPtr signature) const { + auto backing = jsg::BackingStore::alloc(js, signature.size()); + backing.asArrayPtr().copyFrom(signature); + return jsg::BufferSource(js, kj::mv(backing)); } -SubtleCrypto::ExportKeyData AsymmetricKeyCryptoKeyImpl::exportKey(kj::StringPtr format) const { +SubtleCrypto::ExportKeyData AsymmetricKeyCryptoKeyImpl::exportKey( + jsg::Lock& js, kj::StringPtr format) const { // EVP_marshal_{public,private}_key() functions are BoringSSL // extensions which export asymmetric keys in DER format. // DER is the binary format which *should* work to export any EVP_PKEY. @@ -71,16 +76,20 @@ SubtleCrypto::ExportKeyData AsymmetricKeyCryptoKeyImpl::exportKey(kj::StringPtr jwk.key_ops = getUsages().map([](auto usage) { return kj::str(usage.name()); }); return jwk; } else if (format == "raw"_kj) { - return exportRaw(); + return exportRaw(js); } else { JSG_FAIL_REQUIRE(DOMInvalidAccessError, "Cannot export \"", getAlgorithmName(), "\" in \"", format, "\" format."); } - return kj::heapArray(der, derLen); + auto backing = jsg::BackingStore::alloc(js, derLen); + auto src = kj::arrayPtr(der, derLen); + backing.asArrayPtr().copyFrom(src); + return jsg::BufferSource(js, kj::mv(backing)); } -kj::Array AsymmetricKeyCryptoKeyImpl::exportKeyExt(kj::StringPtr format, +jsg::BufferSource AsymmetricKeyCryptoKeyImpl::exportKeyExt(jsg::Lock& js, + kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher, jsg::Optional> passphrase) const { @@ -114,9 +123,10 @@ kj::Array AsymmetricKeyCryptoKeyImpl::exportKeyExt(kj::StringPtr forma const auto fromBio = [&](kj::StringPtr format) { BUF_MEM* bptr; BIO_get_mem_ptr(bio.get(), &bptr); - auto result = kj::heapArray(bptr->length); - memcpy(result.begin(), bptr->data, bptr->length); - return kj::mv(result); + auto result = jsg::BackingStore::alloc(js, bptr->length); + auto src = kj::arrayPtr(bptr->data, bptr->length); + result.asArrayPtr().copyFrom(src.asBytes()); + return jsg::BufferSource(js, kj::mv(result)); }; if (getType() == "public"_kj) { @@ -207,8 +217,9 @@ kj::Array AsymmetricKeyCryptoKeyImpl::exportKeyExt(kj::StringPtr forma JSG_FAIL_REQUIRE(TypeError, "Failed to encode private key"); } -kj::Array AsymmetricKeyCryptoKeyImpl::sign( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const { +jsg::BufferSource AsymmetricKeyCryptoKeyImpl::sign(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const { JSG_REQUIRE(keyType == KeyType::PRIVATE, DOMInvalidAccessError, "Asymmetric signing requires a private key."); @@ -258,10 +269,11 @@ kj::Array AsymmetricKeyCryptoKeyImpl::sign( signature = kj::heapArray(signature.first(signatureSize)); } - return signatureSslToWebCrypto(kj::mv(signature)); + return signatureSslToWebCrypto(js, kj::mv(signature)); } -bool AsymmetricKeyCryptoKeyImpl::verify(SubtleCrypto::SignAlgorithm&& algorithm, +bool AsymmetricKeyCryptoKeyImpl::verify(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr signature, kj::ArrayPtr data) const { ClearErrorOnReturn clearErrorOnReturn; @@ -269,7 +281,7 @@ bool AsymmetricKeyCryptoKeyImpl::verify(SubtleCrypto::SignAlgorithm&& algorithm, JSG_REQUIRE(keyType == KeyType::PUBLIC, DOMInvalidAccessError, "Asymmetric verification requires a public key."); - auto sslSignature = signatureWebCryptoToSsl(signature); + auto sslSignature = signatureWebCryptoToSsl(js, signature); auto type = lookupDigestAlgorithm(chooseHash(algorithm.hash)).second; @@ -281,7 +293,8 @@ bool AsymmetricKeyCryptoKeyImpl::verify(SubtleCrypto::SignAlgorithm&& algorithm, OSSLCALL(EVP_DigestVerifyUpdate(digestCtx.get(), data.begin(), data.size())); // EVP_DigestVerifyFinal() returns 1 on success, 0 on invalid signature, and any other value // indicates "a more serious error". - auto result = EVP_DigestVerifyFinal(digestCtx.get(), sslSignature.begin(), sslSignature.size()); + auto result = EVP_DigestVerifyFinal( + digestCtx.get(), sslSignature.asArrayPtr().begin(), sslSignature.size()); JSG_REQUIRE(result == 0 || result == 1, InternalDOMOperationError, "Unexpected return code from digest verify", getAlgorithmName()); return !!result; diff --git a/src/workerd/api/crypto/keys.h b/src/workerd/api/crypto/keys.h index 56617376056..d9fb20d5068 100644 --- a/src/workerd/api/crypto/keys.h +++ b/src/workerd/api/crypto/keys.h @@ -63,11 +63,12 @@ class AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl { const kj::Maybe>& callTimeHash) const = 0; // Convert OpenSSL-format signature to WebCrypto-format signature, if different. - virtual kj::Array signatureSslToWebCrypto(kj::Array signature) const; + virtual jsg::BufferSource signatureSslToWebCrypto( + jsg::Lock& js, kj::Array signature) const; // Convert WebCrypto-format signature to OpenSSL-format signature, if different. - virtual kj::Array signatureWebCryptoToSsl( - kj::ArrayPtr signature) const; + virtual jsg::BufferSource signatureWebCryptoToSsl( + jsg::Lock& js, kj::ArrayPtr signature) const; // Add salt to digest context in order to generate or verify salted signature. // Currently only used for RSA-PSS sign and verify operations. @@ -77,17 +78,20 @@ class AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl { // --------------------------------------------------------------------------- // Implementation of CryptoKey - SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override final; + SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final; - virtual kj::Array exportKeyExt(kj::StringPtr format, + virtual jsg::BufferSource exportKeyExt(jsg::Lock& js, + kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher = kj::none, jsg::Optional> passphrase = kj::none) const override final; - kj::Array sign( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override; + jsg::BufferSource sign(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const override; - bool verify(SubtleCrypto::SignAlgorithm&& algorithm, + bool verify(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr signature, kj::ArrayPtr data) const override; @@ -115,7 +119,7 @@ class AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl { private: virtual SubtleCrypto::JsonWebKey exportJwk() const = 0; - virtual kj::Array exportRaw() const = 0; + virtual jsg::BufferSource exportRaw(jsg::Lock& js) const = 0; mutable kj::Own keyData; // mutable because OpenSSL wants non-const pointers even when the object won't be modified... diff --git a/src/workerd/api/crypto/pbkdf2.c++ b/src/workerd/api/crypto/pbkdf2.c++ index 15dba477c9c..e661a3fa5ae 100644 --- a/src/workerd/api/crypto/pbkdf2.c++ +++ b/src/workerd/api/crypto/pbkdf2.c++ @@ -34,7 +34,7 @@ public: } private: - kj::Array deriveBits(jsg::Lock& js, + jsg::BufferSource deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe maybeLength) const override { kj::StringPtr hashName = api::getAlgorithmName( @@ -63,7 +63,7 @@ private: // wisest. checkPbkdfLimits(js, iterations); - return JSG_REQUIRE_NONNULL(pbkdf2(length / 8, iterations, hashType, keyData, salt), Error, + return JSG_REQUIRE_NONNULL(pbkdf2(js, length / 8, iterations, hashType, keyData, salt), Error, "PBKDF2 deriveBits failed."); } @@ -101,17 +101,18 @@ private: } // namespace -kj::Maybe> pbkdf2(size_t length, +kj::Maybe pbkdf2(jsg::Lock& js, + size_t length, size_t iterations, const EVP_MD* digest, kj::ArrayPtr password, kj::ArrayPtr salt) { - auto buf = kj::heapArray(length); + auto buf = jsg::BackingStore::alloc(js, length); if (PKCS5_PBKDF2_HMAC(password.asChars().begin(), password.size(), salt.begin(), salt.size(), - iterations, digest, length, buf.begin()) != 1) { + iterations, digest, length, buf.asArrayPtr().begin()) != 1) { return kj::none; } - return kj::mv(buf); + return jsg::BufferSource(js, kj::mv(buf)); } kj::Own CryptoKey::Impl::importPbkdf2(jsg::Lock& js, diff --git a/src/workerd/api/crypto/rsa.c++ b/src/workerd/api/crypto/rsa.c++ index 80b2c8d256b..ee8e00711db 100644 --- a/src/workerd/api/crypto/rsa.c++ +++ b/src/workerd/api/crypto/rsa.c++ @@ -36,27 +36,33 @@ kj::Maybe fromBignum(kj::ArrayPtr value) { return asUnsigned; } -kj::Array bioToArray(BIO* bio) { +jsg::BufferSource bioToArray(jsg::Lock& js, BIO* bio) { BUF_MEM* bptr; BIO_get_mem_ptr(bio, &bptr); - auto buf = kj::heapArray(bptr->length); + auto buf = jsg::BackingStore::alloc(js, bptr->length); auto aptr = kj::arrayPtr(bptr->data, bptr->length); - buf.asPtr().copyFrom(aptr); - return buf.releaseAsBytes(); + buf.asArrayPtr().copyFrom(aptr.asBytes()); + return jsg::BufferSource(js, kj::mv(buf)); } -kj::Maybe> simdutfBase64UrlDecode(kj::StringPtr input) { +kj::Maybe simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input) { auto size = simdutf::maximal_binary_length_from_base64(input.begin(), input.size()); + ; auto buf = kj::heapArray(size); auto result = simdutf::base64_to_binary( input.begin(), input.size(), buf.asChars().begin(), simdutf::base64_url); if (result.error != simdutf::SUCCESS) return kj::none; KJ_ASSERT(result.count <= size); - return buf.first(result.count).attach(kj::mv(buf)); + + auto backing = jsg::BackingStore::alloc(js, result.count); + auto src = kj::arrayPtr(buf.begin(), result.count); + backing.asArrayPtr().copyFrom(src); + return jsg::BufferSource(js, kj::mv(backing)); } -kj::Array simdutfBase64UrlDecodeChecked(kj::StringPtr input, kj::StringPtr error) { - return JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(input), Error, error); +jsg::BufferSource simdutfBase64UrlDecodeChecked( + jsg::Lock& js, kj::StringPtr input, kj::StringPtr error) { + return JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(js, input), Error, error); } } // namespace @@ -80,8 +86,8 @@ size_t Rsa::getModulusSize() const { return RSA_size(rsa); } -kj::Array Rsa::getPublicExponent() { - return KJ_REQUIRE_NONNULL(bignumToArray(*e)); +jsg::BufferSource Rsa::getPublicExponent(jsg::Lock& js) { + return KJ_REQUIRE_NONNULL(bignumToArray(js, *e)); } CryptoKey::AsymmetricKeyDetails Rsa::getAsymmetricKeyDetail() const { @@ -138,7 +144,7 @@ CryptoKey::AsymmetricKeyDetails Rsa::getAsymmetricKeyDetail() const { return kj::mv(details); } -kj::Array Rsa::sign(const kj::ArrayPtr data) const { +jsg::BufferSource Rsa::sign(jsg::Lock& js, const kj::ArrayPtr data) const { size_t size = getModulusSize(); // RSA encryption/decryption requires the key value to be strictly larger than the value to be @@ -164,17 +170,15 @@ kj::Array Rsa::sign(const kj::ArrayPtr data) const { OSSLCALL(RSA_decrypt(rsa, &signatureSize, signature.begin(), signature.size(), data.begin(), data.size(), RSA_NO_PADDING)); KJ_ASSERT(signatureSize <= signature.size()); - if (signatureSize < signature.size()) { - // We did not fill the entire buffer, let's make sure we zero - // out the rest of it so we don't leak any uninitialized data. - signature.slice(signatureSize).fill(0); - return signature.first(signatureSize).attach(kj::mv(signature)); - } - return kj::mv(signature); + auto backing = jsg::BackingStore::alloc(js, signatureSize); + auto src = kj::arrayPtr(signature.begin(), signatureSize); + backing.asArrayPtr().copyFrom(src); + return jsg::BufferSource(js, kj::mv(backing)); } -kj::Array Rsa::cipher(EVP_PKEY_CTX* ctx, +jsg::BufferSource Rsa::cipher(jsg::Lock& js, + EVP_PKEY_CTX* ctx, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data, EncryptDecryptFunction encryptDecrypt, @@ -225,7 +229,9 @@ kj::Array Rsa::cipher(EVP_PKEY_CTX* ctx, 1 == err, DOMOperationError, "RSA-OAEP failed encrypt/decrypt", tryDescribeOpensslErrors()); result.resize(maxResultLength); - return result.releaseAsArray(); + auto backing = jsg::BackingStore::alloc(js, result.size()); + backing.asArrayPtr().copyFrom(result); + return jsg::BufferSource(js, kj::mv(backing)); } SubtleCrypto::JsonWebKey Rsa::toJwk( @@ -256,7 +262,8 @@ SubtleCrypto::JsonWebKey Rsa::toJwk( return jwk; } -kj::Maybe Rsa::fromJwk(KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) { +kj::Maybe Rsa::fromJwk( + jsg::Lock& js, KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) { ClearErrorOnReturn clearErrorOnReturn; if (jwk.kty != "RSA"_kj) return kj::none; @@ -271,8 +278,8 @@ kj::Maybe Rsa::fromJwk(KeyType keyType, const SubtleCrypto::J static constexpr auto kInvalidBase64Error = "Invalid RSA key in JSON Web Key; invalid base64."_kj; - auto nDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(n, kInvalidBase64Error)); - auto eDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(e, kInvalidBase64Error)); + auto nDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, n, kInvalidBase64Error)); + auto eDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, e, kInvalidBase64Error)); JSG_REQUIRE(RSA_set0_key(rsa.get(), nDecoded, eDecoded, nullptr) == 1, Error, "Invalid RSA key in JSON Web Key; failed to set key parameters"); @@ -296,12 +303,12 @@ kj::Maybe Rsa::fromJwk(KeyType keyType, const SubtleCrypto::J "Invalid RSA key in JSON Web Key; missing or invalid " "First CRT Coefficient parameter (\"qi\")."); auto dDecoded = - toBignumUnowned(simdutfBase64UrlDecodeChecked(d, "Invalid RSA key in JSON Web Key"_kj)); - auto pDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(p, kInvalidBase64Error)); - auto qDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(q, kInvalidBase64Error)); - auto dpDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(dp, kInvalidBase64Error)); - auto dqDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(dq, kInvalidBase64Error)); - auto qiDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(qi, kInvalidBase64Error)); + toBignumUnowned(simdutfBase64UrlDecodeChecked(js, d, "Invalid RSA key in JSON Web Key"_kj)); + auto pDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, p, kInvalidBase64Error)); + auto qDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, q, kInvalidBase64Error)); + auto dpDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, dp, kInvalidBase64Error)); + auto dqDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, dq, kInvalidBase64Error)); + auto qiDecoded = toBignumUnowned(simdutfBase64UrlDecodeChecked(js, qi, kInvalidBase64Error)); JSG_REQUIRE(RSA_set0_key(rsa.get(), nullptr, nullptr, dDecoded) == 1, Error, "Invalid RSA key in JSON Web Key; failed to set private exponent"); @@ -320,7 +327,7 @@ kj::Maybe Rsa::fromJwk(KeyType keyType, const SubtleCrypto::J } kj::String Rsa::toPem( - KeyEncoding encoding, KeyType keyType, kj::Maybe options) const { + jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe options) const { ClearErrorOnReturn clearErrorOnReturn; auto bio = OSSL_BIO_MEM(); switch (keyType) { @@ -375,11 +382,11 @@ kj::String Rsa::toPem( default: KJ_UNREACHABLE; } - return kj::String(bioToArray(bio.get()).releaseAsChars()); + return kj::str(bioToArray(js, bio.get()).asArrayPtr().asChars()); } -kj::Array Rsa::toDer( - KeyEncoding encoding, KeyType keyType, kj::Maybe options) const { +jsg::BufferSource Rsa::toDer( + jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe options) const { ClearErrorOnReturn clearErrorOnReturn; auto bio = OSSL_BIO_MEM(); switch (keyType) { @@ -436,7 +443,7 @@ kj::Array Rsa::toDer( default: KJ_UNREACHABLE; } - return bioToArray(bio.get()); + return bioToArray(js, bio.get()); } void Rsa::validateRsaParams( @@ -515,7 +522,7 @@ private: return rsa.toJwk(getTypeEnum(), jwkHashAlgorithmName()); } - kj::Array exportRaw() const override final { + jsg::BufferSource exportRaw(jsg::Lock& js) const override final { JSG_FAIL_REQUIRE( DOMInvalidAccessError, "Cannot export \"", getAlgorithmName(), "\" in \"raw\" format."); } @@ -619,24 +626,27 @@ public: "The sign and verify operations are not implemented for \"", keyAlgorithm.name, "\"."); } - kj::Array encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource encrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const override { JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, "Encryption/key wrapping only works with public keys, not \"", getType(), "\"."); return commonEncryptDecrypt( - kj::mv(algorithm), plainText, EVP_PKEY_encrypt_init, EVP_PKEY_encrypt); + js, kj::mv(algorithm), plainText, EVP_PKEY_encrypt_init, EVP_PKEY_encrypt); } - kj::Array decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource decrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const override { JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError, "Decryption/key unwrapping only works with private keys, not \"", getType(), "\"."); return commonEncryptDecrypt( - kj::mv(algorithm), cipherText, EVP_PKEY_decrypt_init, EVP_PKEY_decrypt); + js, kj::mv(algorithm), cipherText, EVP_PKEY_decrypt_init, EVP_PKEY_decrypt); } private: - kj::Array commonEncryptDecrypt(SubtleCrypto::EncryptAlgorithm&& algorithm, + jsg::BufferSource commonEncryptDecrypt(jsg::Lock& js, + SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data, InitFunction init, EncryptDecryptFunction encryptDecrypt) const { @@ -646,7 +656,7 @@ private: JSG_REQUIRE(1 == init(ctx.get()), DOMOperationError, "RSA-OAEP failed to initialize", tryDescribeOpensslErrors()); return KJ_ASSERT_NONNULL(Rsa::tryGetRsa(pkey)) - .cipher(ctx, kj::mv(algorithm), data, encryptDecrypt, digest); + .cipher(js, ctx, kj::mv(algorithm), data, encryptDecrypt, digest); } kj::String jwkHashAlgorithmName() const override { @@ -666,13 +676,15 @@ public: AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable) : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {} - kj::Array sign( - SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override { + jsg::BufferSource sign(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, + kj::ArrayPtr data) const override { auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(getEvpPkey()), DOMDataError, "Missing RSA key"); - return rsa.sign(data); + return rsa.sign(js, data); } - bool verify(SubtleCrypto::SignAlgorithm&& algorithm, + bool verify(jsg::Lock& js, + SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr signature, kj::ArrayPtr data) const override { KJ_UNIMPLEMENTED("RawRsa Verification currently unsupported"); @@ -952,7 +964,7 @@ kj::Own CryptoKey::Impl::importRsa(jsg::Lock& js, // interface to extract the hash from the ASN.1. Oh well... size_t modulusLength = rsa.getModulusBits(); - auto publicExponent = rsa.getPublicExponent(); + auto publicExponent = rsa.getPublicExponent(js); // Validate modulus and exponent, reject imported RSA keys that may be unsafe. Rsa::validateRsaParams(js, modulusLength, publicExponent, true); diff --git a/src/workerd/api/crypto/rsa.h b/src/workerd/api/crypto/rsa.h index bc965dc808b..befd160b654 100644 --- a/src/workerd/api/crypto/rsa.h +++ b/src/workerd/api/crypto/rsa.h @@ -28,14 +28,15 @@ class Rsa final { return d; } - kj::Array getPublicExponent() KJ_WARN_UNUSED_RESULT; + jsg::BufferSource getPublicExponent(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail() const KJ_WARN_UNUSED_RESULT; - kj::Array sign(const kj::ArrayPtr data) const KJ_WARN_UNUSED_RESULT; + jsg::BufferSource sign( + jsg::Lock& js, const kj::ArrayPtr data) const KJ_WARN_UNUSED_RESULT; static kj::Maybe fromJwk( - KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) KJ_WARN_UNUSED_RESULT; + jsg::Lock& js, KeyType keyType, const SubtleCrypto::JsonWebKey& jwk) KJ_WARN_UNUSED_RESULT; SubtleCrypto::JsonWebKey toJwk( KeyType keytype, kj::Maybe maybeHashAlgorithm) const KJ_WARN_UNUSED_RESULT; @@ -45,16 +46,19 @@ class Rsa final { kj::ArrayPtr passphrase; }; - kj::String toPem(KeyEncoding encoding, + kj::String toPem(jsg::Lock& js, + KeyEncoding encoding, KeyType keyType, kj::Maybe options = kj::none) const KJ_WARN_UNUSED_RESULT; - kj::Array toDer(KeyEncoding encoding, + jsg::BufferSource toDer(jsg::Lock& js, + KeyEncoding encoding, KeyType keyType, kj::Maybe options = kj::none) const KJ_WARN_UNUSED_RESULT; using EncryptDecryptFunction = decltype(EVP_PKEY_encrypt); - kj::Array cipher(EVP_PKEY_CTX* ctx, + jsg::BufferSource cipher(jsg::Lock& js, + EVP_PKEY_CTX* ctx, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data, EncryptDecryptFunction encryptDecrypt, diff --git a/src/workerd/api/crypto/scrypt.c++ b/src/workerd/api/crypto/scrypt.c++ index aefc813ad29..32f83aeeef5 100644 --- a/src/workerd/api/crypto/scrypt.c++ +++ b/src/workerd/api/crypto/scrypt.c++ @@ -10,7 +10,8 @@ namespace workerd::api { -kj::Maybe> scrypt(size_t length, +kj::Maybe scrypt(jsg::Lock& js, + size_t length, uint32_t N, uint32_t r, uint32_t p, @@ -18,9 +19,9 @@ kj::Maybe> scrypt(size_t length, kj::ArrayPtr pass, kj::ArrayPtr salt) { ClearErrorOnReturn clearErrorOnReturn; - auto buf = kj::heapArray(length); + auto buf = jsg::BackingStore::alloc(js, length); if (!EVP_PBE_scrypt(pass.asChars().begin(), pass.size(), salt.begin(), salt.size(), N, r, p, - maxmem, buf.begin(), length)) { + maxmem, buf.asArrayPtr().begin(), length)) { // This does not currently handle the errors in exactly the same way as // the Node.js implementation but that's probably ok? We can update the // error thrown to match Node.js more closely later if necessary. There @@ -30,7 +31,7 @@ kj::Maybe> scrypt(size_t length, } return kj::none; } - return kj::mv(buf); + return jsg::BufferSource(js, kj::mv(buf)); } } // namespace workerd::api diff --git a/src/workerd/api/node/crypto-keys.c++ b/src/workerd/api/node/crypto-keys.c++ index 4c9f558b22a..88ae0a8c330 100644 --- a/src/workerd/api/node/crypto-keys.c++ +++ b/src/workerd/api/node/crypto-keys.c++ @@ -40,7 +40,7 @@ public: CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0; } - SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override final { + SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final { JSG_REQUIRE(format == "raw" || format == "jwk", DOMNotSupportedError, getAlgorithmName(), " key only supports exporting \"raw\" & \"jwk\", not \"", format, "\"."); @@ -52,7 +52,9 @@ public: return jwk; } - return kj::heapArray(keyData.asPtr()); + auto backing = jsg::BackingStore::alloc(js, keyData.size()); + backing.asArrayPtr().copyFrom(keyData); + return jsg::BufferSource(js, kj::mv(backing)); } kj::StringPtr jsgGetMemoryName() const override { @@ -70,7 +72,7 @@ private: }; } // namespace -kj::OneOf, SubtleCrypto::JsonWebKey> CryptoImpl::exportKey( +kj::OneOf CryptoImpl::exportKey( jsg::Lock& js, jsg::Ref key, jsg::Optional options) { JSG_REQUIRE(key->getExtractable(), TypeError, "Unable to export non-extractable key"); auto& opts = JSG_REQUIRE_NONNULL(options, TypeError, "Options must be an object"); @@ -78,7 +80,7 @@ kj::OneOf, SubtleCrypto::JsonWebKey> CryptoImpl: kj::StringPtr format = JSG_REQUIRE_NONNULL(opts.format, TypeError, "Missing format option"); if (format == "jwk"_kj) { // When format is jwk, all other options are ignored. - return key->impl->exportKey(format); + return key->impl->exportKey(js, format); } if (key->getType() == "secret"_kj) { @@ -86,14 +88,15 @@ kj::OneOf, SubtleCrypto::JsonWebKey> CryptoImpl: // one of either "buffer" or "jwk". The "buffer" option correlates to the "raw" // format in Web Crypto. The "jwk" option is handled above. JSG_REQUIRE(format == "buffer"_kj, TypeError, "Invalid format for secret key export: ", format); - return key->impl->exportKey("raw"_kj); + return key->impl->exportKey(js, "raw"_kj); } kj::StringPtr type = JSG_REQUIRE_NONNULL(opts.type, TypeError, "Missing type option"); - auto data = key->impl->exportKeyExt(format, type, kj::mv(opts.cipher), kj::mv(opts.passphrase)); + auto data = + key->impl->exportKeyExt(js, format, type, kj::mv(opts.cipher), kj::mv(opts.passphrase)); if (format == "pem"_kj) { // TODO(perf): As a later performance optimization, change this so that it doesn't copy. - return kj::str(data.asChars()); + return kj::str(data.asArrayPtr().asChars()); } return kj::mv(data); } diff --git a/src/workerd/api/node/crypto.c++ b/src/workerd/api/node/crypto.c++ index 1d342afe71a..2b7682dffc9 100644 --- a/src/workerd/api/node/crypto.c++ +++ b/src/workerd/api/node/crypto.c++ @@ -12,7 +12,8 @@ namespace workerd::api::node { -kj::Array CryptoImpl::getHkdf(kj::String hash, +jsg::BufferSource CryptoImpl::getHkdf(jsg::Lock& js, + kj::String hash, kj::Array key, kj::Array salt, kj::Array info, @@ -43,10 +44,10 @@ kj::Array CryptoImpl::getHkdf(kj::String hash, JSG_REQUIRE( length <= EVP_MD_size(digest) * kMaxDigestMultiplier, RangeError, "Invalid Hkdf key length"); - return JSG_REQUIRE_NONNULL(hkdf(length, digest, key, salt, info), Error, "Hkdf failed"); + return JSG_REQUIRE_NONNULL(hkdf(js, length, digest, key, salt, info), Error, "Hkdf failed"); } -kj::Array CryptoImpl::getPbkdf(jsg::Lock& js, +jsg::BufferSource CryptoImpl::getPbkdf(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t num_iterations, @@ -70,10 +71,10 @@ kj::Array CryptoImpl::getPbkdf(jsg::Lock& js, // Both pass and salt may be zero length here. return JSG_REQUIRE_NONNULL( - pbkdf2(keylen, num_iterations, digest, password, salt), Error, "Pbkdf2 failed"); + pbkdf2(js, keylen, num_iterations, digest, password, salt), Error, "Pbkdf2 failed"); } -kj::Array CryptoImpl::getScrypt(jsg::Lock& js, +jsg::BufferSource CryptoImpl::getScrypt(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t N, @@ -86,7 +87,7 @@ kj::Array CryptoImpl::getScrypt(jsg::Lock& js, JSG_REQUIRE(salt.size() <= INT32_MAX, RangeError, "Scrypt failed: salt is too large"); return JSG_REQUIRE_NONNULL( - scrypt(keylen, N, r, p, maxmem, password, salt), Error, "Scrypt failed"); + scrypt(js, keylen, N, r, p, maxmem, password, salt), Error, "Scrypt failed"); } bool CryptoImpl::verifySpkac(kj::Array input) { @@ -119,13 +120,13 @@ bool CryptoImpl::checkPrimeSync(kj::Array bufferView, uint32_t num_che // ====================================================================================== jsg::Ref CryptoImpl::HmacHandle::constructor( - kj::String algorithm, kj::OneOf, jsg::Ref> key) { + jsg::Lock& js, kj::String algorithm, kj::OneOf, jsg::Ref> key) { KJ_SWITCH_ONEOF(key) { KJ_CASE_ONEOF(key_data, kj::Array) { - return jsg::alloc(HmacContext(algorithm, key_data.asPtr())); + return jsg::alloc(HmacContext(js, algorithm, key_data.asPtr())); } KJ_CASE_ONEOF(key, jsg::Ref) { - return jsg::alloc(HmacContext(algorithm, key->impl.get())); + return jsg::alloc(HmacContext(js, algorithm, key->impl.get())); } } KJ_UNREACHABLE; @@ -146,12 +147,12 @@ jsg::BufferSource CryptoImpl::HmacHandle::oneshot(jsg::Lock& js, kj::Array data) { KJ_SWITCH_ONEOF(key) { KJ_CASE_ONEOF(key_data, kj::Array) { - HmacContext ctx(algorithm, key_data.asPtr()); + HmacContext ctx(js, algorithm, key_data.asPtr()); ctx.update(data); return ctx.digest(js); } KJ_CASE_ONEOF(key, jsg::Ref) { - HmacContext ctx(algorithm, key->impl.get()); + HmacContext ctx(js, algorithm, key->impl.get()); ctx.update(data); return ctx.digest(js); } diff --git a/src/workerd/api/node/crypto.h b/src/workerd/api/node/crypto.h index 941a43e20fe..4561eaef664 100644 --- a/src/workerd/api/node/crypto.h +++ b/src/workerd/api/node/crypto.h @@ -92,7 +92,7 @@ class CryptoImpl final: public jsg::Object { HmacHandle(HmacContext ctx): ctx(kj::mv(ctx)) {}; - static jsg::Ref constructor(kj::String algorithm, KeyParam key); + static jsg::Ref constructor(jsg::Lock& js, kj::String algorithm, KeyParam key); // Efficiently implement one-shot hmac that avoids multiple calls // across the C++/JS boundary. @@ -115,14 +115,15 @@ class CryptoImpl final: public jsg::Object { }; // Hkdf - kj::Array getHkdf(kj::String hash, + jsg::BufferSource getHkdf(jsg::Lock& js, + kj::String hash, kj::Array key, kj::Array salt, kj::Array info, uint32_t length); // Pbkdf2 - kj::Array getPbkdf(jsg::Lock& js, + jsg::BufferSource getPbkdf(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t num_iterations, @@ -130,7 +131,7 @@ class CryptoImpl final: public jsg::Object { kj::String name); // Scrypt - kj::Array getScrypt(jsg::Lock& js, + jsg::BufferSource getScrypt(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t N, @@ -196,7 +197,7 @@ class CryptoImpl final: public jsg::Object { CryptoImpl() = default; CryptoImpl(jsg::Lock&, const jsg::Url&) {} - kj::OneOf, SubtleCrypto::JsonWebKey> exportKey( + kj::OneOf exportKey( jsg::Lock& js, jsg::Ref key, jsg::Optional options); bool equals(jsg::Lock& js, jsg::Ref key, jsg::Ref otherKey);