diff --git a/src/workerd/api/crypto/aes-test.c++ b/src/workerd/api/crypto/aes-test.c++ index 8a8cc5c7f87..cd7ee28e5d8 100644 --- a/src/workerd/api/crypto/aes-test.c++ +++ b/src/workerd/api/crypto/aes-test.c++ @@ -66,7 +66,7 @@ KJ_TEST("AES-KW key wrap") { params = {}; params.name = kj::str("AES-KW"); - auto unwrapped = aesKey->unwrapKey(js, kj::mv(params), wrapped); + auto unwrapped = aesKey->unwrapKey(js, kj::mv(params), wrapped.asArrayPtr().asConst()); KJ_EXPECT(unwrapped.asArrayPtr() == keyMaterial); @@ -74,7 +74,8 @@ KJ_TEST("AES-KW key wrap") { params = {}; params.name = kj::str("AES-KW"); wrapped.asArrayPtr()[5] += 1; - KJ_EXPECT_THROW_MESSAGE("[24 == -1]", aesKey->unwrapKey(js, kj::mv(params), wrapped)); + KJ_EXPECT_THROW_MESSAGE( + "[24 == -1]", aesKey->unwrapKey(js, kj::mv(params), wrapped.asArrayPtr().asConst())); } }); }); @@ -109,13 +110,11 @@ KJ_TEST("AES-CTR key wrap") { static constexpr auto getEnc = [](jsg::Lock& js) { static constexpr auto kRaw = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10"_kjb; - auto backing = jsg::BackingStore::alloc(js, 16); - auto buffersource = jsg::BufferSource(js, kj::mv(backing)); - buffersource.asArrayPtr().copyFrom(kRaw); + auto counter = jsg::JsUint8Array::create(js, kRaw); return SubtleCrypto::EncryptAlgorithm{ .name = kj::str("AES-CTR"), - .counter = kj::mv(buffersource), + .counter = jsg::JsBufferSource(counter).addRef(js), .length = 5, }; }; @@ -142,15 +141,16 @@ KJ_TEST("AES-CTR key wrap") { return subtle.wrapKey(js, kj::str("raw"), *toWrap, *wrappingKey, getEnc(js), *jwkHandler); }) .then(js, - [&](jsg::Lock&, jsg::BufferSource wrapped) { - auto data = kj::heapArray(wrapped.asArrayPtr()); + [&](jsg::Lock& js, jsg::JsRef wrapped) { + auto data = wrapped.getHandle(js).copy(); return subtle.unwrapKey(js, kj::str("raw"), kj::mv(data), *wrappingKey, getEnc(js), 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); + }).then(js, [&](jsg::Lock& js, api::SubtleCrypto::ExportKeyData roundTrippedKeyMaterial) { + auto& buf = roundTrippedKeyMaterial.get>(); + KJ_ASSERT(buf.getHandle(js).asArrayPtr() == KEY_DATA); completed = true; }); diff --git a/src/workerd/api/crypto/aes.c++ b/src/workerd/api/crypto/aes.c++ index bd46f0a65e4..200ba57f6d6 100644 --- a/src/workerd/api/crypto/aes.c++ +++ b/src/workerd/api/crypto/aes.c++ @@ -6,6 +6,7 @@ #include "util.h" #include +#include #include #include @@ -133,11 +134,6 @@ class AesKeyBase: public CryptoKey::Impl { 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; } @@ -190,9 +186,7 @@ class AesKeyBase: public CryptoKey::Impl { } // 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)); + return jsg::JsArrayBuffer::create(js, keyData).addRef(js); } protected: @@ -209,16 +203,17 @@ class AesGcmKey final: public AesKeyBase { : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} private: - jsg::BufferSource encrypt(jsg::Lock& js, + jsg::JsArrayBuffer 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\"."); + auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".") + .getHandle(js); JSG_REQUIRE(iv.size() != 0, DOMOperationError, "AES-GCM IV must not be empty."); kj::ArrayPtr empty = nullptr; auto additionalData = ([&] { - KJ_IF_SOME(source, algorithm.additionalData) { + KJ_IF_SOME(sourceRef, algorithm.additionalData) { + auto source = sourceRef.getHandle(js); return source.asArrayPtr(); } else { return empty; @@ -243,7 +238,8 @@ class AesGcmKey final: public AesKeyBase { // and initialization vector because we may need to override the default IV length. OSSLCALL(EVP_EncryptInit_ex(cipherCtx.get(), type, nullptr, nullptr, nullptr)); OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)); - OSSLCALL(EVP_EncryptInit_ex(cipherCtx.get(), nullptr, nullptr, keyData.begin(), iv.begin())); + OSSLCALL(EVP_EncryptInit_ex( + cipherCtx.get(), nullptr, nullptr, keyData.begin(), iv.asArrayPtr().begin())); if (additionalData.size() > 0) { // Run the engine with the additional data, which will presumably be transmitted alongside the @@ -258,7 +254,7 @@ class AesGcmKey final: public AesKeyBase { // 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 = jsg::BackingStore::alloc(js, plainText.size() + tagByteSize); + auto cipherText = jsg::JsArrayBuffer::create(js, plainText.size() + tagByteSize); // Perform the actual encryption. @@ -279,14 +275,14 @@ class AesGcmKey final: public AesKeyBase { cipherSize += tagByteSize; KJ_ASSERT(cipherSize == cipherText.size(), "buffer overrun"); - return jsg::BufferSource(js, kj::mv(cipherText)); + return cipherText; } - jsg::BufferSource decrypt(jsg::Lock& js, + jsg::JsArrayBuffer 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\"."); + auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".") + .getHandle(js); JSG_REQUIRE(iv.size() != 0, DOMOperationError, "AES-GCM IV must not be empty."); int tagLength = algorithm.tagLength.orDefault(128); @@ -300,7 +296,8 @@ class AesGcmKey final: public AesKeyBase { kj::ArrayPtr empty = nullptr; auto additionalData = ([&] { - KJ_IF_SOME(source, algorithm.additionalData) { + KJ_IF_SOME(sourceRef, algorithm.additionalData) { + auto source = sourceRef.getHandle(js); return source.asArrayPtr(); } return empty; @@ -313,7 +310,8 @@ class AesGcmKey final: public AesKeyBase { OSSLCALL(EVP_DecryptInit_ex(cipherCtx.get(), type, nullptr, nullptr, nullptr)); OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)); - OSSLCALL(EVP_DecryptInit_ex(cipherCtx.get(), nullptr, nullptr, keyData.begin(), iv.begin())); + OSSLCALL(EVP_DecryptInit_ex( + cipherCtx.get(), nullptr, nullptr, keyData.begin(), iv.asArrayPtr().begin())); int plainSize = 0; @@ -326,7 +324,7 @@ class AesGcmKey final: public AesKeyBase { auto actualCipherText = cipherText.first(cipherText.size() - tagLength / 8); auto tagText = cipherText.slice(actualCipherText.size(), cipherText.size()); - auto plainText = jsg::BackingStore::alloc(js, actualCipherText.size()); + auto plainText = jsg::JsArrayBuffer::create(js, actualCipherText.size()); // Perform the actual decryption. OSSLCALL(EVP_DecryptUpdate(cipherCtx.get(), plainText.asArrayPtr().begin(), &plainSize, @@ -348,7 +346,7 @@ class AesGcmKey final: public AesKeyBase { cipherCtx.get(), plainText.asArrayPtr().begin() + plainSize); KJ_ASSERT(plainSize == plainText.size()); - return jsg::BufferSource(js, kj::mv(plainText)); + return plainText; } }; @@ -361,11 +359,11 @@ class AesCbcKey final: public AesKeyBase { : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} private: - jsg::BufferSource encrypt(jsg::Lock& js, + jsg::JsArrayBuffer 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\"."); + auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".") + .getHandle(js); JSG_REQUIRE(iv.size() == 16, DOMOperationError, "AES-CBC IV must be 16 bytes long (provided ", iv.size(), " bytes)."); @@ -375,11 +373,12 @@ class AesCbcKey final: public AesKeyBase { auto type = lookupAesCbcType(keyData.size() * 8); // Set up the cipher context with the initialization vector. - OSSLCALL(EVP_EncryptInit_ex(cipherCtx.get(), type, nullptr, keyData.begin(), iv.begin())); + OSSLCALL(EVP_EncryptInit_ex( + cipherCtx.get(), type, nullptr, keyData.begin(), iv.asArrayPtr().begin())); auto blockSize = EVP_CIPHER_CTX_block_size(cipherCtx.get()); size_t paddingSize = blockSize - (plainText.size() % blockSize); - auto cipherText = jsg::BackingStore::alloc(js, plainText.size() + paddingSize); + auto cipherText = jsg::JsArrayBuffer::create(js, plainText.size() + paddingSize); // Perform the actual encryption. // @@ -398,14 +397,14 @@ class AesCbcKey final: public AesKeyBase { cipherSize += finalCipherSize; KJ_ASSERT(cipherSize == cipherText.size(), "buffer overrun"); - return jsg::BufferSource(js, kj::mv(cipherText)); + return cipherText; } - jsg::BufferSource decrypt(jsg::Lock& js, + jsg::JsArrayBuffer 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\"."); + auto iv = JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".") + .getHandle(js); JSG_REQUIRE(iv.size() == 16, DOMOperationError, "AES-CBC IV must be 16 bytes long (provided ", iv.size(), ")."); @@ -416,7 +415,8 @@ class AesCbcKey final: public AesKeyBase { auto type = lookupAesCbcType(keyData.size() * 8); // Set up the cipher context with the initialization vector. - OSSLCALL(EVP_DecryptInit_ex(cipherCtx.get(), type, nullptr, keyData.begin(), iv.begin())); + OSSLCALL(EVP_DecryptInit_ex( + cipherCtx.get(), type, nullptr, keyData.begin(), iv.asArrayPtr().begin())); int plainSize = 0; auto blockSize = EVP_CIPHER_CTX_block_size(cipherCtx.get()); @@ -435,9 +435,7 @@ class AesCbcKey final: public AesKeyBase { // Copy is necessary to support v8:Sandbox where all ArrayBuffers have to be // allocated from within the sandbox. - auto backing = jsg::BackingStore::alloc(js, plainSize); - backing.asArrayPtr().copyFrom(plainText.first(plainSize)); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, plainText.first(plainSize)); } }; @@ -451,13 +449,13 @@ class AesCtrKey final: public AesKeyBase { CryptoKeyUsageSet usages) : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} - jsg::BufferSource encrypt(jsg::Lock& js, + jsg::JsArrayBuffer encrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const override { return encryptOrDecrypt(js, kj::mv(algorithm), plainText); } - jsg::BufferSource decrypt(jsg::Lock& js, + jsg::JsArrayBuffer decrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const override { return encryptOrDecrypt(js, kj::mv(algorithm), cipherText); @@ -479,11 +477,12 @@ class AesCtrKey final: public AesKeyBase { KJ_FAIL_ASSERT("CryptoKey has invalid data length"); } - jsg::BufferSource encryptOrDecrypt(jsg::Lock& js, + jsg::JsArrayBuffer encryptOrDecrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data) const { - auto& counter = JSG_REQUIRE_NONNULL( - algorithm.counter, TypeError, "Missing \"counter\" member in \"algorithm\"."); + auto counter = JSG_REQUIRE_NONNULL( + algorithm.counter, TypeError, "Missing \"counter\" member in \"algorithm\".") + .getHandle(js); JSG_REQUIRE(counter.size() == expectedCounterByteSize, DOMOperationError, "Counter must have length of 16 bytes (provided ", counter.size(), ")."); @@ -505,7 +504,7 @@ class AesCtrKey final: public AesKeyBase { const auto& cipher = lookupAesType(keyData.size()); // The output of AES-CTR is the same size as the input. - auto result = jsg::BackingStore::alloc(js, data.size()); + auto result = jsg::JsArrayBuffer::create(js, data.size()); auto numCounterValues = newBignum(); JSG_REQUIRE(BN_lshift(numCounterValues.get(), BN_value_one(), counterBitLength), @@ -536,18 +535,18 @@ class AesCtrKey final: public AesKeyBase { 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.asArrayPtr()); - return jsg::BufferSource(js, kj::mv(result)); + process(&cipher, data, counter.asArrayPtr(), result.asArrayPtr()); + return 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.asArrayPtr()); + process(&cipher, data.first(inputSizePart1), counter.asArrayPtr(), result.asArrayPtr()); // Zero the counter bits of the block in a copy of the input counter. - kj::Array zeroed_counter = kj::heapArray(counter.asArrayPtr()); + kj::Array zeroed_counter = counter.copy(); { KJ_DASSERT(counterBitLength / 8 <= expectedCounterByteSize); @@ -562,7 +561,7 @@ class AesCtrKey final: public AesKeyBase { process(&cipher, data.slice(inputSizePart1, data.size()), zeroed_counter, result.asArrayPtr().slice(inputSizePart1, result.size())); - return jsg::BufferSource(js, kj::mv(result)); + return result; } private: @@ -625,7 +624,7 @@ class AesCtrKey final: public AesKeyBase { InternalDOMOperationError, "Error doing ", getAlgorithmName(), " encrypt/decrypt", internalDescribeOpensslErrors()); - KJ_DASSERT(outputLength >= 0 && outputLength <= output.size(), outputLength, output.size()); + KJ_ASSERT(outputLength >= 0 && outputLength <= output.size(), outputLength, output.size()); int finalOutputChunkLength = 0; auto finalizationBuffer = output.slice(outputLength, output.size()).asBytes().begin(); @@ -634,7 +633,7 @@ class AesCtrKey final: public AesKeyBase { InternalDOMOperationError, "Error doing ", getAlgorithmName(), " encrypt/decrypt", internalDescribeOpensslErrors()); - KJ_DASSERT(finalOutputChunkLength >= 0 && finalOutputChunkLength <= output.size(), + KJ_ASSERT(finalOutputChunkLength >= 0 && finalOutputChunkLength <= output.size(), finalOutputChunkLength, output.size()); JSG_REQUIRE(static_cast(outputLength) + static_cast(finalOutputChunkLength) == @@ -651,7 +650,7 @@ class AesKwKey final: public AesKeyBase { CryptoKeyUsageSet usages) : AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {} - jsg::BufferSource wrapKey(jsg::Lock& js, + jsg::JsArrayBuffer wrapKey(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr unwrappedKey) const override { // Resources used to implement this: @@ -668,7 +667,7 @@ class AesKwKey final: public AesKeyBase { "equal to 16 and less than or equal to ", SIZE_MAX - 8); - auto wrapped = jsg::BackingStore::alloc(js, unwrappedKey.size() + 8); + auto wrapped = jsg::JsArrayBuffer::create(js, unwrappedKey.size() + 8); // Wrapping adds 8 bytes of overhead for storing the IV which we check on decryption. AES_KEY aesKey; @@ -681,10 +680,10 @@ class AesKwKey final: public AesKeyBase { unwrappedKey.size()), DOMOperationError, getAlgorithmName(), " key wrapping failed", tryDescribeOpensslErrors()); - return jsg::BufferSource(js, kj::mv(wrapped)); + return wrapped; } - jsg::BufferSource unwrapKey(jsg::Lock& js, + jsg::JsArrayBuffer unwrapKey(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr wrappedKey) const override { // Resources used to implement this: @@ -699,7 +698,7 @@ class AesKwKey final: public AesKeyBase { "Provided a wrapped key to unwrap this is ", wrappedKey.size() * 8, " bits that is less than the minimal length of 192 bits."); - auto unwrapped = jsg::BackingStore::alloc(js, wrappedKey.size() - 8); + auto unwrapped = jsg::JsArrayBuffer::create(js, wrappedKey.size() - 8); AES_KEY aesKey; JSG_REQUIRE(0 == AES_set_decrypt_key(keyData.begin(), keyData.size() * 8, &aesKey), @@ -714,7 +713,7 @@ class AesKwKey final: public AesKeyBase { DOMOperationError, getAlgorithmName(), " key unwrapping failed", tryDescribeOpensslErrors()); - return jsg::BufferSource(js, kj::mv(unwrapped)); + return unwrapped; } }; diff --git a/src/workerd/api/crypto/crypto.c++ b/src/workerd/api/crypto/crypto.c++ index cd0db80ffe8..6cf3456554f 100644 --- a/src/workerd/api/crypto/crypto.c++ +++ b/src/workerd/api/crypto/crypto.c++ @@ -325,7 +325,7 @@ void CryptoKey::visitForGc(jsg::GcVisitor& visitor) { impl->visitForGc(visitor); } -jsg::Promise SubtleCrypto::encrypt(jsg::Lock& js, +jsg::Promise> SubtleCrypto::encrypt(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& key, kj::Array plainText) { @@ -335,11 +335,11 @@ jsg::Promise SubtleCrypto::encrypt(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::encrypt()); - return key.impl->encrypt(js, kj::mv(algorithm), plainText); + return key.impl->encrypt(js, kj::mv(algorithm), plainText).addRef(js); }); } -jsg::Promise SubtleCrypto::decrypt(jsg::Lock& js, +jsg::Promise> SubtleCrypto::decrypt(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& key, kj::Array cipherText) { @@ -349,11 +349,11 @@ jsg::Promise SubtleCrypto::decrypt(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::decrypt()); - return key.impl->decrypt(js, kj::mv(algorithm), cipherText); + return key.impl->decrypt(js, kj::mv(algorithm), cipherText).addRef(js); }); } -jsg::Promise SubtleCrypto::sign(jsg::Lock& js, +jsg::Promise> SubtleCrypto::sign(jsg::Lock& js, kj::OneOf algorithmParam, const CryptoKey& key, kj::Array data) { @@ -363,7 +363,7 @@ jsg::Promise SubtleCrypto::sign(jsg::Lock& js, return js.evalNow([&] { validateOperation(key, algorithm.name, CryptoKeyUsageSet::sign()); - return key.impl->sign(js, kj::mv(algorithm), data); + return key.impl->sign(js, kj::mv(algorithm), data).addRef(js); }); } @@ -382,7 +382,7 @@ jsg::Promise SubtleCrypto::verify(jsg::Lock& js, }); } -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)); @@ -398,14 +398,12 @@ 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 = - jsg::BackingStore::alloc(js, EVP_MD_CTX_size(digestCtx.get())); + auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_CTX_size(digestCtx.get())); uint messageDigestSize = 0; - OSSLCALL(EVP_DigestFinal_ex( - digestCtx.get(), messageDigest.asArrayPtr().begin(), &messageDigestSize)); + OSSLCALL(EVP_DigestFinal_ex(digestCtx.get(), buf.asArrayPtr().begin(), &messageDigestSize)); - KJ_ASSERT(messageDigestSize == messageDigest.size()); - return jsg::BufferSource(js, kj::mv(messageDigest)); + KJ_ASSERT(messageDigestSize == buf.size()); + return buf.addRef(js); }); } @@ -463,13 +461,12 @@ 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(data), kj::mv(derivedKeyAlgorithm), extractable, kj::mv(keyUsages)); + js, "raw", secret.copy(), 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) { @@ -487,11 +484,11 @@ jsg::Promise SubtleCrypto::deriveBits(jsg::Lock& js, return js.evalNow([&] { validateOperation(baseKey, algorithm.name, CryptoKeyUsageSet::deriveBits()); - return baseKey.impl->deriveBits(js, kj::mv(algorithm), length); + return baseKey.impl->deriveBits(js, kj::mv(algorithm), length).addRef(js); }); } -jsg::Promise SubtleCrypto::wrapKey(jsg::Lock& js, +jsg::Promise> SubtleCrypto::wrapKey(jsg::Lock& js, kj::String format, const CryptoKey& key, const CryptoKey& wrappingKey, @@ -516,12 +513,15 @@ jsg::Promise SubtleCrypto::wrapKey(jsg::Lock& js, auto exportedKey = key.impl->exportKey(js, kj::mv(format)); KJ_SWITCH_ONEOF(exportedKey) { - KJ_CASE_ONEOF(k, jsg::BufferSource) { - return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), k.asArrayPtr().asConst()); + KJ_CASE_ONEOF(k, jsg::JsRef) { + auto handle = k.getHandle(js); + return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), handle.asArrayPtr().asConst()) + .addRef(js); } KJ_CASE_ONEOF(jwk, JsonWebKey) { auto stringified = js.serializeJson(jwkHandler.wrap(js, kj::mv(jwk))); - return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), stringified.asBytes().asConst()); + return wrappingKey.impl->wrapKey(js, kj::mv(algorithm), stringified.asBytes().asConst()) + .addRef(js); } } @@ -560,7 +560,7 @@ jsg::Promise> SubtleCrypto::unwrapKey(jsg::Lock& js, importData = JSG_REQUIRE_NONNULL(jwkHandler.tryUnwrap(js, jwkDict.getHandle(js)), DOMDataError, "Missing \"kty\" field or corrupt JSON unwrapping key?"); } else { - importData = kj::heapArray(bytes); + importData = bytes.copy(); } auto imported = importKeySync(js, format, kj::mv(importData), kj::mv(normalizedUnwrapAlgorithm), @@ -652,7 +652,7 @@ jsg::Promise SubtleCrypto::exportKey( }); } -bool SubtleCrypto::timingSafeEqual(jsg::BufferSource a, jsg::BufferSource b) { +bool SubtleCrypto::timingSafeEqual(jsg::JsBufferSource a, jsg::JsBufferSource b) { JSG_REQUIRE(a.size() == b.size(), TypeError, "Input buffers must have the same byte length."); // The implementation here depends entirely on the characteristics of the CRYPTO_memcmp @@ -665,7 +665,7 @@ bool SubtleCrypto::timingSafeEqual(jsg::BufferSource a, jsg::BufferSource b) { // ======================================================================================= // Crypto implementation -jsg::BufferSource Crypto::getRandomValues(jsg::BufferSource buffer) { +jsg::JsArrayBufferView Crypto::getRandomValues(jsg::JsArrayBufferView buffer) { // NOTE: TypeMismatchError is deprecated (obviated by TypeError), but the spec and W3C tests still // expect a TypeMismatchError here. JSG_REQUIRE(buffer.isIntegerType(), DOMTypeMismatchError, @@ -674,7 +674,7 @@ jsg::BufferSource Crypto::getRandomValues(jsg::BufferSource buffer) { "getRandomValues() only accepts buffers of size <= 64K but provided ", buffer.size(), " bytes."); IoContext::current().getEntropySource().generate(buffer.asArrayPtr()); - return kj::mv(buffer); + return buffer; } kj::String Crypto::randomUUID() { @@ -693,14 +693,11 @@ class CRC32DigestContext final: public DigestContext { value = crc32(value, buffer.begin(), buffer.size()); } - jsg::BufferSource close(jsg::Lock& js) override { + jsg::JsArrayBuffer close(jsg::Lock& js) override { auto beValue = htobe32(value); static_assert(sizeof(value) == sizeof(beValue), "CRC32 digest is not 32 bits?"); - auto backing = jsg::BackingStore::alloc(js, sizeof(beValue)); - jsg::BufferSource source(js, kj::mv(backing)); kj::ArrayPtr be(reinterpret_cast(&beValue), sizeof(beValue)); - source.asArrayPtr().copyFrom(be); - return kj::mv(source); + return jsg::JsArrayBuffer::create(js, be); } private: @@ -716,14 +713,11 @@ class CRC32CDigestContext final: public DigestContext { value = crc32c(value, buffer.begin(), buffer.size()); } - jsg::BufferSource close(jsg::Lock& js) override { + jsg::JsArrayBuffer close(jsg::Lock& js) override { auto beValue = htobe32(value); static_assert(sizeof(value) == sizeof(beValue), "CRC32 digest is not 32 bits?"); - auto backing = jsg::BackingStore::alloc(js, sizeof(beValue)); - jsg::BufferSource source(js, kj::mv(backing)); kj::ArrayPtr be(reinterpret_cast(&beValue), sizeof(beValue)); - source.asArrayPtr().copyFrom(be); - return kj::mv(source); + return jsg::JsArrayBuffer::create(js, be); } private: @@ -739,14 +733,11 @@ class CRC64NVMEDigestContext final: public DigestContext { value = crc64nvme(value, buffer.begin(), buffer.size()); } - jsg::BufferSource close(jsg::Lock& js) override { + jsg::JsArrayBuffer close(jsg::Lock& js) override { auto beValue = htobe64(value); static_assert(sizeof(value) == sizeof(beValue), "CRC64 digest is not 64 bits?"); - auto backing = jsg::BackingStore::alloc(js, sizeof(beValue)); - jsg::BufferSource source(js, kj::mv(backing)); kj::ArrayPtr be(reinterpret_cast(&beValue), sizeof(beValue)); - source.asArrayPtr().copyFrom(be); - return kj::mv(source); + return jsg::JsArrayBuffer::create(js, be); } private: @@ -770,14 +761,13 @@ class OpenSSLDigestContext final: public DigestContext { OSSLCALL(EVP_DigestUpdate(context.get(), buffer.begin(), buffer.size())); } - jsg::BufferSource close(jsg::Lock& js) override { + jsg::JsArrayBuffer close(jsg::Lock& js) override { auto checkErrorsOnFinish = webCryptoOperationBegin(__func__, algorithm); uint size = 0; - auto backing = jsg::BackingStore::alloc(js, EVP_MD_CTX_size(context.get())); - jsg::BufferSource source(js, kj::mv(backing)); - OSSLCALL(EVP_DigestFinal_ex(context.get(), source.asArrayPtr().begin(), &size)); - KJ_ASSERT(size, source.size()); - return kj::mv(source); + auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_CTX_size(context.get())); + OSSLCALL(EVP_DigestFinal_ex(context.get(), buf.asArrayPtr().begin(), &size)); + KJ_ASSERT(size == buf.size()); + return buf; } private: @@ -799,8 +789,8 @@ DigestStream::DigestContextPtr DigestStream::initContext(SubtleCrypto::HashAlgor DigestStream::DigestStream(kj::Own controller, SubtleCrypto::HashAlgorithm algorithm, - jsg::Promise::Resolver resolver, - jsg::Promise promise) + jsg::Promise>::Resolver resolver, + jsg::Promise> promise) : WritableStream(kj::mv(controller)), promise(kj::mv(promise)), state(Ready(kj::mv(algorithm), kj::mv(resolver))) {} @@ -857,7 +847,7 @@ kj::Maybe DigestStream::close(jsg::Lock& js) { return errored.addRef(js); } KJ_CASE_ONEOF(ready, Ready) { - ready.resolver.resolve(js, ready.context->close(js)); + ready.resolver.resolve(js, ready.context->close(js).addRef(js)); state.init(); return kj::none; } @@ -874,7 +864,7 @@ void DigestStream::abort(jsg::Lock& js, jsg::JsValue reason) { } jsg::Ref DigestStream::constructor(jsg::Lock& js, Algorithm algorithm) { - auto paf = js.newPromiseAndResolver(); + auto paf = js.newPromiseAndResolver>(); auto stream = js.alloc(newWritableStreamJsController(), interpretAlgorithmParam(kj::mv(algorithm)), kj::mv(paf.resolver), kj::mv(paf.promise)); @@ -884,9 +874,8 @@ jsg::Ref DigestStream::constructor(jsg::Lock& js, Algorithm algori .write = [&stream = *stream](jsg::Lock& js, v8::Local chunk, auto c) mutable { return js.tryCatch([&] { // Make sure what we got can be interpreted as bytes... - std::shared_ptr backing; if (chunk->IsArrayBuffer() || chunk->IsArrayBufferView()) { - jsg::BufferSource source(js, chunk); + jsg::JsBufferSource source(chunk); if (source.size() == 0) return js.resolvedPromise(); KJ_IF_SOME(error, stream.write(js, source.asArrayPtr())) { diff --git a/src/workerd/api/crypto/crypto.h b/src/workerd/api/crypto/crypto.h index 3560f142833..ad2fff86e83 100644 --- a/src/workerd/api/crypto/crypto.h +++ b/src/workerd/api/crypto/crypto.h @@ -8,6 +8,7 @@ #include #include #include +#include #include // for EVP_MD_CTX, X509 @@ -195,24 +196,21 @@ class CryptoKey: public jsg::Object { uint16_t modulusLength; // The RSA public exponent (in unsigned big-endian form) - jsg::BufferSource publicExponent; + jsg::JsRef publicExponent; // The hash algorithm that is used with this key. jsg::Optional hash; RsaKeyAlgorithm clone(jsg::Lock& js) const { - auto fixPublicExp = FeatureFlags::get(js).getCryptoPreservePublicExponent(); - + auto pe = publicExponent.getHandle(js); + auto data = pe.asArrayPtr(); // Should only happen if the flag is enabled and an algorithm field is cloned twice. - if (fixPublicExp) { - // alloc will, by default create a Uint8Array. - auto expBack = jsg::BackingStore::alloc(js, publicExponent.size()); - expBack.asArrayPtr().copyFrom(publicExponent.asArrayPtr()); - return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; + if (FeatureFlags::get(js).getCryptoPreservePublicExponent()) { + auto exp = jsg::JsUint8Array::create(js, data); + return {name, modulusLength, jsg::JsBufferSource(exp).addRef(js), hash}; } else { - auto expBack = jsg::BackingStore::alloc(js, publicExponent.size()); - expBack.asArrayPtr().copyFrom(publicExponent.asArrayPtr()); - return {name, modulusLength, jsg::BufferSource(js, kj::mv(expBack)), hash}; + auto exp = jsg::JsArrayBuffer::create(js, data); + return {name, modulusLength, jsg::JsBufferSource(exp).addRef(js), hash}; } } @@ -252,7 +250,7 @@ class CryptoKey: public jsg::Object { // by CryptoKey::Impl to provide the actual implementation. struct AsymmetricKeyDetails { jsg::Optional modulusLength; - jsg::Optional publicExponent; + jsg::Optional> publicExponent; // TODO(later): BoringSSL does not currently support getting the RSA-PSS // details for an RSA key. Once it does, we can update our impl and add // these fields. @@ -359,10 +357,10 @@ class SubtleCrypto: public jsg::Object { kj::String name; // For AES: The initialization vector use. May be up to 2^64-1 bytes long. - jsg::Optional iv; + jsg::Optional> iv; // The additional authentication data to include. - jsg::Optional additionalData; + jsg::Optional> additionalData; // The desired length of the authentication tag. May be 0 - 128. // Note: the spec specifies this as a Web IDL byte (== signed char in C++), not an int, but JS @@ -371,7 +369,7 @@ class SubtleCrypto: public jsg::Object { // The initial value of the counter block for AES-CTR. // https://www.w3.org/TR/WebCryptoAPI/#aes-ctr-params - jsg::Optional counter; + jsg::Optional> counter; // The length, in bits, of the rightmost part of the counter block that is incremented. // See above why we use int instead of int8_t. @@ -379,7 +377,7 @@ class SubtleCrypto: public jsg::Object { jsg::Optional length; // The optional label/application data to associate with the message (for RSA-OAEP) - jsg::Optional label; + jsg::Optional> label; JSG_STRUCT(name, iv, additionalData, tagLength, counter, length, label); }; @@ -417,7 +415,7 @@ class SubtleCrypto: public jsg::Object { jsg::Optional modulusLength; // For RSA algorithms - jsg::Optional publicExponent; + jsg::Optional> publicExponent; // For AES algorithms or when name == "HMAC": The length in bits of the key. jsg::Optional length; @@ -459,7 +457,7 @@ class SubtleCrypto: public jsg::Object { kj::String name; // PBKDF2 parameters - jsg::Optional salt; + jsg::Optional> salt; jsg::Optional iterations; jsg::Optional> hash; @@ -470,7 +468,7 @@ class SubtleCrypto: public jsg::Object { // Bit string that corresponds to the context and application specific context for the derived // keying material - jsg::Optional info; + jsg::Optional> info; JSG_STRUCT(name, salt, iterations, hash, $public, info); }; @@ -522,18 +520,18 @@ class SubtleCrypto: public jsg::Object { }; using ImportKeyData = kj::OneOf, JsonWebKey>; - using ExportKeyData = kj::OneOf; + using ExportKeyData = kj::OneOf, JsonWebKey>; - 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); @@ -543,7 +541,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); @@ -558,7 +556,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 @@ -585,7 +583,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, @@ -602,7 +600,7 @@ class SubtleCrypto: public jsg::Object { const jsg::TypeHandler& jwkHandler); // This is a non-standard extension based off Node.js' implementation of crypto.timingSafeEqual. - bool timingSafeEqual(jsg::BufferSource a, jsg::BufferSource b); + bool timingSafeEqual(jsg::JsBufferSource a, jsg::JsBufferSource b); JSG_RESOURCE_TYPE(SubtleCrypto) { JSG_METHOD(encrypt); @@ -657,7 +655,7 @@ class DigestContext { public: virtual ~DigestContext() noexcept = default; virtual void write(kj::ArrayPtr buffer) = 0; - virtual jsg::BufferSource close(jsg::Lock& js) = 0; + virtual jsg::JsArrayBuffer close(jsg::Lock& js) = 0; }; class DigestStream: public WritableStream { @@ -667,13 +665,12 @@ class DigestStream: public WritableStream { explicit DigestStream(kj::Own controller, SubtleCrypto::HashAlgorithm algorithm, - jsg::Promise::Resolver resolver, - jsg::Promise promise); + jsg::Promise>::Resolver resolver, + jsg::Promise> promise); static jsg::Ref constructor(jsg::Lock& js, Algorithm algorithm); - // The BufferSource returned will always be an ArrayBuffer here. - jsg::MemoizedIdentity>& getDigest() { + jsg::MemoizedIdentity>>& getDigest() { return promise; } void dispose(jsg::Lock& js); @@ -703,14 +700,15 @@ class DigestStream: public WritableStream { struct Ready { SubtleCrypto::HashAlgorithm algorithm; - jsg::Promise::Resolver resolver; + jsg::Promise>::Resolver resolver; DigestContextPtr context; - Ready(SubtleCrypto::HashAlgorithm algorithm, jsg::Promise::Resolver resolver) + Ready(SubtleCrypto::HashAlgorithm algorithm, + jsg::Promise>::Resolver resolver) : algorithm(kj::mv(algorithm)), resolver(kj::mv(resolver)), context(initContext(this->algorithm)) {} }; - jsg::MemoizedIdentity> promise; + jsg::MemoizedIdentity>> promise; kj::OneOf state; uint64_t bytesWritten = 0; @@ -730,7 +728,7 @@ class Crypto: public jsg::Object { public: Crypto(jsg::Lock& js): subtle(js.alloc()) {} - jsg::BufferSource getRandomValues(jsg::BufferSource buffer); + jsg::JsArrayBufferView getRandomValues(jsg::JsArrayBufferView buffer); kj::String randomUUID(); diff --git a/src/workerd/api/crypto/dh.c++ b/src/workerd/api/crypto/dh.c++ index f8112126b1d..2aca01b6a3c 100644 --- a/src/workerd/api/crypto/dh.c++ +++ b/src/workerd/api/crypto/dh.c++ @@ -207,42 +207,58 @@ kj::Maybe DiffieHellman::check() { } void DiffieHellman::setPrivateKey(kj::ArrayPtr key) { - OSSLCALL(DH_set0_key(dh, nullptr, toBignumUnowned(key))); + auto bn = toBignumOwned(key); + OSSLCALL(DH_set0_key(dh, nullptr, bn.get())); + bn.release(); } void DiffieHellman::setPublicKey(kj::ArrayPtr key) { - OSSLCALL(DH_set0_key(dh, toBignumUnowned(key), nullptr)); + auto bn = toBignumOwned(key); + + int checkResult; + JSG_REQUIRE(DH_check_pub_key(dh, bn.get(), &checkResult), Error, + "DiffieHellman setPublicKey() failed: could not validate public key"); + if (checkResult) { + JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError, + "DiffieHellman setPublicKey() failed: key is too small"); + JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError, + "DiffieHellman setPublicKey() failed: key is too large"); + JSG_FAIL_REQUIRE(Error, "DiffieHellman setPublicKey() failed: invalid public key"); + } + + OSSLCALL(DH_set0_key(dh, bn.get(), nullptr)); + bn.release(); } -jsg::BufferSource DiffieHellman::getPublicKey(jsg::Lock& js) { +jsg::JsUint8Array DiffieHellman::getPublicKey(jsg::Lock& js) { const BIGNUM* pub_key = DH_get0_pub_key(dh); JSG_REQUIRE(pub_key != nullptr, Error, "No public key"); return JSG_REQUIRE_NONNULL( bignumToArrayPadded(js, *pub_key), Error, "Error while retrieving DiffieHellman public key"); } -jsg::BufferSource DiffieHellman::getPrivateKey(jsg::Lock& js) { +jsg::JsUint8Array DiffieHellman::getPrivateKey(jsg::Lock& js) { const BIGNUM* priv_key = DH_get0_priv_key(dh); JSG_REQUIRE(priv_key != nullptr, Error, "No private key"); return JSG_REQUIRE_NONNULL(bignumToArrayPadded(js, *priv_key), Error, "Error while retrieving DiffieHellman private key"); } -jsg::BufferSource DiffieHellman::getGenerator(jsg::Lock& js) { +jsg::JsUint8Array DiffieHellman::getGenerator(jsg::Lock& js) { const BIGNUM* g = DH_get0_g(dh); JSG_REQUIRE(g != nullptr, Error, "No DiffieHellman generator"); return JSG_REQUIRE_NONNULL( bignumToArrayPadded(js, *g), Error, "Error while retrieving DiffieHellman generator"); } -jsg::BufferSource DiffieHellman::getPrime(jsg::Lock& js) { +jsg::JsUint8Array DiffieHellman::getPrime(jsg::Lock& js) { const BIGNUM* p = DH_get0_p(dh); JSG_REQUIRE(p != nullptr, Error, "No DiffieHellman prime"); return JSG_REQUIRE_NONNULL( bignumToArrayPadded(js, *p), Error, "Error while retrieving DiffieHellman prime"); } -jsg::BufferSource DiffieHellman::computeSecret(jsg::Lock& js, kj::ArrayPtr key) { +jsg::JsUint8Array DiffieHellman::computeSecret(jsg::Lock& js, kj::ArrayPtr key) { JSG_REQUIRE(key.size() <= INT32_MAX, RangeError, "DiffieHellman computeSecret() failed: key is too large"); JSG_REQUIRE(key.size() > 0, Error, "DiffieHellman computeSecret() failed: invalid key"); @@ -251,30 +267,31 @@ jsg::BufferSource DiffieHellman::computeSecret(jsg::Lock& js, kj::ArrayPtr(js, prime_size); - - int size = DH_compute_key(prime_enc.asArrayPtr().begin(), k.get(), dh); - if (size == -1) { - // various error checking - int checkResult; - int checked = DH_check_pub_key(dh, k, &checkResult); - - if (checked && checkResult) { - JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError, - "DiffieHellman computeSecret() failed: Supplied key is too small"); - JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError, - "DiffieHellman computeSecret() failed: Supplied key is too large"); - } - JSG_FAIL_REQUIRE(Error, "Invalid Key"); + // Validate the peer's public key before computing the shared secret. + int checkResult; + JSG_REQUIRE(DH_check_pub_key(dh, k, &checkResult), Error, + "DiffieHellman computeSecret() failed: could not validate peer public key"); + if (checkResult) { + JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError, + "DiffieHellman computeSecret() failed: Supplied key is too small"); + JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError, + "DiffieHellman computeSecret() failed: Supplied key is too large"); + JSG_FAIL_REQUIRE(Error, "DiffieHellman computeSecret() failed: invalid peer public key"); } + size_t prime_size = DH_size(dh); + auto buf = jsg::JsUint8Array::create(js, prime_size); + + int size = DH_compute_key(buf.asArrayPtr().begin(), k.get(), dh); + JSG_REQUIRE( + size != -1, Error, "DiffieHellman computeSecret() failed: error computing shared secret"); + KJ_ASSERT(size >= 0); - zeroPadDiffieHellmanSecret(size, prime_enc.asArrayPtr().begin(), prime_size); - return jsg::BufferSource(js, kj::mv(prime_enc)); + zeroPadDiffieHellmanSecret(size, buf.asArrayPtr().begin(), prime_size); + return buf; } -jsg::BufferSource DiffieHellman::generateKeys(jsg::Lock& js) { +jsg::JsUint8Array DiffieHellman::generateKeys(jsg::Lock& js) { ClearErrorOnReturn clear_error_on_return; OSSLCALL(DH_generate_key(dh)); const BIGNUM* pub_key = DH_get0_pub_key(dh); diff --git a/src/workerd/api/crypto/dh.h b/src/workerd/api/crypto/dh.h index 1fb1de0ce45..07a91d5985f 100644 --- a/src/workerd/api/crypto/dh.h +++ b/src/workerd/api/crypto/dh.h @@ -20,12 +20,12 @@ class DiffieHellman final { void setPrivateKey(kj::ArrayPtr key); void setPublicKey(kj::ArrayPtr key); - jsg::BufferSource getPublicKey(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; - jsg::BufferSource getPrivateKey(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; - jsg::BufferSource getGenerator(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; - jsg::BufferSource getPrime(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; - jsg::BufferSource computeSecret(jsg::Lock& js, kj::ArrayPtr key) KJ_WARN_UNUSED_RESULT; - jsg::BufferSource generateKeys(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array getPublicKey(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array getPrivateKey(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array getGenerator(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array getPrime(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array computeSecret(jsg::Lock& js, kj::ArrayPtr key) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array generateKeys(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; kj::Maybe check() KJ_WARN_UNUSED_RESULT; diff --git a/src/workerd/api/crypto/digest.c++ b/src/workerd/api/crypto/digest.c++ index 9c2d719df6f..79e85e54907 100644 --- a/src/workerd/api/crypto/digest.c++ +++ b/src/workerd/api/crypto/digest.c++ @@ -38,7 +38,7 @@ class HmacKey final: public CryptoKey::Impl { } private: - jsg::BufferSource sign(jsg::Lock& js, + jsg::JsArrayBuffer sign(jsg::Lock& js, SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override { return computeHmac(js, kj::mv(algorithm), data); @@ -53,20 +53,20 @@ class HmacKey final: public CryptoKey::Impl { CRYPTO_memcmp(messageDigest.asArrayPtr().begin(), signature.begin(), signature.size()) == 0; } - jsg::BufferSource computeHmac(jsg::Lock& js, + jsg::JsArrayBuffer 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 = jsg::BackingStore::alloc(js, EVP_MD_size(type)); + auto buf = jsg::JsArrayBuffer::create(js, EVP_MD_size(type)); uint messageDigestSize = 0; auto ptr = HMAC(type, keyData.begin(), keyData.size(), data.begin(), data.size(), - messageDigest.asArrayPtr().begin(), &messageDigestSize); + buf.asArrayPtr().begin(), &messageDigestSize); JSG_REQUIRE(ptr != nullptr, DOMOperationError, "HMAC computation failed."); - KJ_ASSERT(messageDigestSize == messageDigest.size()); - return jsg::BufferSource(js, kj::mv(messageDigest)); + KJ_ASSERT(messageDigestSize == buf.size()); + return buf; } SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override { @@ -98,9 +98,7 @@ class HmacKey final: public CryptoKey::Impl { return jwk; } - auto backing = jsg::BackingStore::alloc(js, keyData.size()); - backing.asArrayPtr().copyFrom(keyData); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, keyData).addRef(js); } kj::StringPtr getAlgorithmName() const override { @@ -159,8 +157,9 @@ kj::Own initHmacContext( SubtleCrypto::ExportKeyData keyData = key2->exportKey(js, "raw"_kj); KJ_SWITCH_ONEOF(keyData) { - KJ_CASE_ONEOF(key_data, jsg::BufferSource) { - return handle(algorithm, key_data); + KJ_CASE_ONEOF(key_data, jsg::JsRef) { + auto buf = key_data.getHandle(js); + return handle(algorithm, buf.asArrayPtr()); } KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) { KJ_UNREACHABLE; @@ -181,32 +180,31 @@ void HmacContext::update(kj::ArrayPtr data) { JSG_REQUIRE(data.size() <= INT_MAX, RangeError, "data is too long"); KJ_ASSERT(HMAC_Update(ctx.get(), data.begin(), data.size()) == 1); } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { + KJ_CASE_ONEOF(digest, jsg::JsRef) { JSG_FAIL_REQUIRE(DOMOperationError, "HMAC context has already been finalized."); } } } -jsg::BufferSource HmacContext::digest(jsg::Lock& js) { +jsg::JsUint8Array HmacContext::digest(jsg::Lock& js) { KJ_SWITCH_ONEOF(state) { KJ_CASE_ONEOF(ctx, kj::Own) { auto theCtx = kj::mv(ctx); unsigned len; - auto digest = jsg::BackingStore::alloc(js, HMAC_size(theCtx.get())); - JSG_REQUIRE(HMAC_Final(theCtx.get(), digest.asArrayPtr().begin(), &len), Error, + auto buf = jsg::JsUint8Array::create(js, HMAC_size(theCtx.get())); + JSG_REQUIRE(HMAC_Final(theCtx.get(), buf.asArrayPtr().begin(), &len), Error, "Failed to finalize HMAC"); - KJ_ASSERT(len == digest.size()); - auto ret = jsg::BufferSource(js, kj::mv(digest)); - state = ret.clone(js); - return kj::mv(ret); + KJ_ASSERT(len == buf.size()); + state = buf.addRef(js); + return buf; } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { - return digest.clone(js); + KJ_CASE_ONEOF(digest, jsg::JsRef) { + auto cached = digest.getHandle(js); + return jsg::JsUint8Array::create(js, cached.asArrayPtr()); } KJ_UNREACHABLE; } - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } size_t HmacContext::size() const { @@ -214,8 +212,10 @@ size_t HmacContext::size() const { KJ_CASE_ONEOF(ctx, kj::Own) { return 0; } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { - return digest.size(); + KJ_CASE_ONEOF(digest, jsg::JsRef) { + // JsRef doesn't expose size() without a lock. Return 0 for memory tracking; + // the JsRef itself is tracked separately by the GC visitor. + return 0; } } KJ_UNREACHABLE; @@ -343,8 +343,8 @@ void checkXofLen(EVP_MD_CTX* ctx, kj::Maybe& maybeXof) { } } // namespace -HashContext::HashContext( - kj::OneOf, jsg::BufferSource> state, kj::Maybe maybeXof) +HashContext::HashContext(kj::OneOf, jsg::JsRef> state, + kj::Maybe maybeXof) : state(kj::mv(state)), maybeXof(kj::mv(maybeXof)) { checkXofLen(this->state.get>().get(), this->maybeXof); @@ -359,52 +359,49 @@ void HashContext::update(kj::ArrayPtr data) { JSG_REQUIRE(data.size() <= INT_MAX, RangeError, "data is too long"); OSSLCALL(EVP_DigestUpdate(ctx.get(), data.begin(), data.size())); } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { + KJ_CASE_ONEOF(digest, jsg::JsRef) { JSG_FAIL_REQUIRE(DOMOperationError, "Hash context has already been finalized."); } } } -jsg::BufferSource HashContext::digest(jsg::Lock& js) { +jsg::JsUint8Array HashContext::digest(jsg::Lock& js) { KJ_SWITCH_ONEOF(state) { KJ_CASE_ONEOF(ctx, kj::Own) { auto theCtx = kj::mv(ctx); uint32_t len = EVP_MD_size(EVP_MD_CTX_md(theCtx.get())); KJ_IF_SOME(xof, maybeXof) { if (xof == len) { - auto digest = jsg::BackingStore::alloc(js, len); - JSG_REQUIRE(EVP_DigestFinal_ex(theCtx.get(), digest.asArrayPtr().begin(), &len) == 1, - Error, "Failed to compute hash digest"); - KJ_ASSERT(len == digest.size()); - auto ret = jsg::BufferSource(js, kj::mv(digest)); - state = ret.clone(js); - return kj::mv(ret); + auto buf = jsg::JsUint8Array::create(js, len); + JSG_REQUIRE(EVP_DigestFinal_ex(theCtx.get(), buf.asArrayPtr().begin(), &len) == 1, Error, + "Failed to compute hash digest"); + KJ_ASSERT(len == buf.size()); + state = buf.addRef(js); + return buf; } - auto digest = jsg::BackingStore::alloc(js, xof); - JSG_REQUIRE(EVP_DigestFinalXOF(theCtx.get(), digest.asArrayPtr().begin(), xof) == 1, Error, + auto buf = jsg::JsUint8Array::create(js, xof); + JSG_REQUIRE(EVP_DigestFinalXOF(theCtx.get(), buf.asArrayPtr().begin(), xof) == 1, Error, "Failed to compute XOF hash digest"); - auto ret = jsg::BufferSource(js, kj::mv(digest)); - state = ret.clone(js); - return kj::mv(ret); + state = buf.addRef(js); + return buf; } - auto digest = jsg::BackingStore::alloc(js, len); - JSG_REQUIRE(EVP_DigestFinal_ex(theCtx.get(), digest.asArrayPtr().begin(), &len) == 1, Error, + auto buf = jsg::JsUint8Array::create(js, len); + JSG_REQUIRE(EVP_DigestFinal_ex(theCtx.get(), buf.asArrayPtr().begin(), &len) == 1, Error, "Failed to compute hash digest"); - KJ_ASSERT(len == digest.size()); - auto ret = jsg::BufferSource(js, kj::mv(digest)); - state = ret.clone(js); - return kj::mv(ret); + KJ_ASSERT(len == buf.size()); + state = buf.addRef(js); + return buf; } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { - return digest.clone(js); + KJ_CASE_ONEOF(digest, jsg::JsRef) { + auto cached = digest.getHandle(js); + return jsg::JsUint8Array::create(js, cached.asArrayPtr()); } KJ_UNREACHABLE } - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } HashContext HashContext::clone(jsg::Lock& js, kj::Maybe xofLen) { @@ -414,7 +411,7 @@ HashContext HashContext::clone(jsg::Lock& js, kj::Maybe xofLen) { OSSLCALL(EVP_MD_CTX_copy_ex(newCtx, ctx.get())); return HashContext(kj::mv(newCtx), kj::mv(xofLen)); } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { + KJ_CASE_ONEOF(digest, jsg::JsRef) { JSG_FAIL_REQUIRE(DOMOperationError, "Hash context has already been finalized."); } } @@ -426,8 +423,10 @@ size_t HashContext::size() const { KJ_CASE_ONEOF(ctx, kj::Own) { return 0; } - KJ_CASE_ONEOF(digest, jsg::BufferSource) { - return digest.size(); + KJ_CASE_ONEOF(digest, jsg::JsRef) { + // JsRef doesn't expose size() without a lock. Return 0 for memory tracking; + // the JsRef itself is tracked separately by the GC visitor. + return 0; } } KJ_UNREACHABLE; diff --git a/src/workerd/api/crypto/digest.h b/src/workerd/api/crypto/digest.h index 323e1d7cc85..590ca8131ff 100644 --- a/src/workerd/api/crypto/digest.h +++ b/src/workerd/api/crypto/digest.h @@ -17,14 +17,14 @@ class HmacContext final { KJ_DISALLOW_COPY(HmacContext); void update(kj::ArrayPtr data); - jsg::BufferSource digest(jsg::Lock& js); + jsg::JsUint8Array digest(jsg::Lock& js); size_t size() const; private: // Will be kj::Own while the HMAC data is being updated, - // and kj::Array after the digest() has been called. - kj::OneOf, jsg::BufferSource> state; + // and jsg::JsRef after the digest() has been called. + kj::OneOf, jsg::JsRef> state; }; class HashContext final { @@ -35,17 +35,18 @@ class HashContext final { KJ_DISALLOW_COPY(HashContext); void update(kj::ArrayPtr data); - jsg::BufferSource digest(jsg::Lock& js); + jsg::JsUint8Array digest(jsg::Lock& js); HashContext clone(jsg::Lock& js, kj::Maybe xofLen); size_t size() const; private: - HashContext(kj::OneOf, jsg::BufferSource>, kj::Maybe maybeXof); + HashContext( + kj::OneOf, jsg::JsRef>, kj::Maybe maybeXof); // Will be kj::Own while the hash data is being updated, - // and jsg::BufferSource after the digest() has been called. - kj::OneOf, jsg::BufferSource> state; + // and jsg::JsRef after the digest() has been called. + kj::OneOf, jsg::JsRef> state; kj::Maybe maybeXof; }; } // namespace workerd::api diff --git a/src/workerd/api/crypto/ec.c++ b/src/workerd/api/crypto/ec.c++ index 0d3ed57af47..a81a337bafc 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; } -jsg::BufferSource Ec::getRawPublicKey(jsg::Lock& js) const { +jsg::JsArrayBuffer Ec::getRawPublicKey(jsg::Lock& js) const { JSG_REQUIRE_NONNULL(group, InternalDOMOperationError, "No elliptic curve group in this key", tryDescribeOpensslErrors()); auto publicKey = getPublicKey(); @@ -112,10 +112,7 @@ jsg::BufferSource Ec::getRawPublicKey(jsg::Lock& js) const { JSG_REQUIRE(1 == CBB_finish(&cbb, &raw, &raw_len), InternalDOMOperationError, "Failed to finish CBB", internalDescribeOpensslErrors()); - 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)); + return jsg::JsArrayBuffer::create(js, kj::arrayPtr(raw, raw_len)); } CryptoKey::AsymmetricKeyDetails Ec::getAsymmetricKeyDetail(jsg::Lock& js) const { @@ -175,7 +172,7 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl { "algorithms for historical reasons.)")); } - jsg::BufferSource deriveBits(jsg::Lock& js, + jsg::JsArrayBuffer deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe resultBitLength) const override final { JSG_REQUIRE(keyAlgorithm.name == "ECDH", DOMNotSupportedError, @@ -279,12 +276,10 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl { sharedSecret.back() &= mask; } - auto backing = jsg::BackingStore::alloc(js, sharedSecret.size()); - backing.asArrayPtr().copyFrom(sharedSecret); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, sharedSecret.asPtr()); } - jsg::BufferSource signatureSslToWebCrypto( + jsg::JsArrayBuffer signatureSslToWebCrypto( jsg::Lock& js, kj::ArrayPtr 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 @@ -330,23 +325,23 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl { KJ_ASSERT(s.size() <= rsSize); // Construct WebCrypto format. - auto out = jsg::BackingStore::alloc(js, rsSize * 2); + auto out = jsg::JsArrayBuffer::create(js, rsSize * 2); + auto outPtr = out.asArrayPtr(); // 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 endian. - 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)); + outPtr.slice(rsSize - r.size(), rsSize).copyFrom(r); + outPtr.slice(rsSize * 2 - s.size(), rsSize * 2).copyFrom(s); + return out; } - jsg::BufferSource signatureWebCryptoToSsl( + jsg::JsArrayBuffer 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. - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, 0); } auto r = signature.first(rsSize); @@ -362,7 +357,7 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl { size_t bodySize = 4 + padR + padS + r.size() + s.size(); size_t resultSize = 2 + bodySize + (bodySize >= 128); - auto result = jsg::BackingStore::alloc(js, resultSize); + auto result = jsg::JsArrayBuffer::create(js, resultSize); kj::byte* pos = result.asArrayPtr().begin(); *pos++ = 0x30; @@ -387,7 +382,7 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl { KJ_ASSERT(pos == result.asArrayPtr().end()); - return jsg::BufferSource(js, kj::mv(result)); + return result; } static kj::OneOf, CryptoKeyPair> generateElliptic(jsg::Lock& js, @@ -415,7 +410,7 @@ class EllipticKey final: public AsymmetricKeyCryptoKeyImpl { return ec.toJwk(getTypeEnum(), kj::str(keyAlgorithm.namedCurve)); } - jsg::BufferSource exportRaw(jsg::Lock& js) const override final { + jsg::JsArrayBuffer 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, @@ -671,6 +666,9 @@ kj::Own ellipticJwkReader( tryDescribeOpensslErrors()); } + JSG_REQUIRE(1 == EC_KEY_check_key(ecKey.get()), DOMDataError, "Invalid EC key in JSON Web Key", + tryDescribeOpensslErrors()); + auto evpPkey = OSSL_NEW(EVP_PKEY); JSG_REQUIRE(1 == EVP_PKEY_set1_EC_KEY(evpPkey.get(), ecKey.get()), DOMOperationError, "Error importing EC key", tryDescribeOpensslErrors()); @@ -846,7 +844,7 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { KJ_UNIMPLEMENTED(); } - jsg::BufferSource sign(jsg::Lock& js, + jsg::JsArrayBuffer sign(jsg::Lock& js, SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override { JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError, @@ -858,7 +856,7 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { // inconsistent with the broader WebCrypto standard. Filed an issue with the standard for // clarification: https://github.com/tQsW/webcrypto-curve25519/issues/7 - auto signature = jsg::BackingStore::alloc(js, ED25519_SIGNATURE_LEN); + auto signature = jsg::JsArrayBuffer::create(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 @@ -877,7 +875,7 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { JSG_REQUIRE(signatureLength == signature.size(), InternalDOMOperationError, "Unexpected change in size signing Ed25519", signatureLength); - return jsg::BufferSource(js, kj::mv(signature)); + return signature; } bool verify(jsg::Lock& js, @@ -909,7 +907,7 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { return !!result; } - jsg::BufferSource deriveBits(jsg::Lock& js, + jsg::JsArrayBuffer deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe resultBitLength) const override final { JSG_REQUIRE(getAlgorithmName() == "X25519", DOMNotSupportedError, @@ -983,9 +981,7 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { sharedSecret.back() &= mask; } - auto backing = jsg::BackingStore::alloc(js, sharedSecret.size()); - backing.asArrayPtr().copyFrom(sharedSecret); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, sharedSecret.asPtr()); } CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const override { @@ -1040,16 +1036,17 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { KJ_ASSERT(privateKeyLen == 32, privateKeyLen); jwk.d = fastEncodeBase64Url(kj::arrayPtr(rawPrivateKey, privateKeyLen)); + OPENSSL_cleanse(rawPrivateKey, sizeof(rawPrivateKey)); } return jwk; } - jsg::BufferSource exportRaw(jsg::Lock& js) const override final { + jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final { JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, "Raw export of ", getAlgorithmName(), " keys is only allowed for public keys."); - auto raw = jsg::BackingStore::alloc(js, ED25519_PUBLIC_KEY_LEN); + auto raw = jsg::JsArrayBuffer::create(js, ED25519_PUBLIC_KEY_LEN); size_t exportedLength = raw.size(); JSG_REQUIRE( @@ -1060,7 +1057,7 @@ class EdDsaKey final: public AsymmetricKeyCryptoKeyImpl { JSG_REQUIRE(exportedLength == raw.size(), InternalDOMOperationError, "Unexpected change in size", raw.size(), exportedLength); - return jsg::BufferSource(js, kj::mv(raw)); + return raw; } }; @@ -1075,6 +1072,7 @@ CryptoKeyPair generateKeyImpl(jsg::Lock& js, uint8_t rawPublicKey[keySize] = {0}; uint8_t rawPrivateKey[keySize * 2] = {0}; KeypairInit(rawPublicKey, rawPrivateKey); + KJ_DEFER(OPENSSL_cleanse(rawPrivateKey, sizeof(rawPrivateKey))); // The private key technically also contains the public key. Why does the keypair function bother // writing out the public key to a separate buffer? diff --git a/src/workerd/api/crypto/ec.h b/src/workerd/api/crypto/ec.h index 4097781a45f..d69c1fa646e 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; - jsg::BufferSource getRawPublicKey(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT; + jsg::JsArrayBuffer getRawPublicKey(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT; CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const; diff --git a/src/workerd/api/crypto/hkdf.c++ b/src/workerd/api/crypto/hkdf.c++ index 75869f653a7..1e169c070a5 100644 --- a/src/workerd/api/crypto/hkdf.c++ +++ b/src/workerd/api/crypto/hkdf.c++ @@ -5,6 +5,8 @@ #include "impl.h" #include "kdf.h" +#include + #include namespace workerd::api { @@ -34,19 +36,21 @@ class HkdfKey final: public CryptoKey::Impl { } private: - jsg::BufferSource deriveBits(jsg::Lock& js, + jsg::JsArrayBuffer deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe maybeLength) const override { kj::StringPtr hashName = api::getAlgorithmName( JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\".")); const EVP_MD* hashType = lookupDigestAlgorithm(hashName).second; - const auto& salt = + auto saltHandle = JSG_REQUIRE_NONNULL(algorithm.salt, TypeError, "Missing field \"salt\" in \"algorithm\".") - .asArrayPtr(); - const auto& info = + .getHandle(js); + const auto& salt = saltHandle.asArrayPtr(); + auto infoHandle = JSG_REQUIRE_NONNULL(algorithm.info, TypeError, "Missing field \"info\" in \"algorithm\".") - .asArrayPtr(); + .getHandle(js); + const auto& info = infoHandle.asArrayPtr(); uint32_t length = JSG_REQUIRE_NONNULL( maybeLength, DOMOperationError, "HKDF cannot derive a key with null length."); @@ -81,7 +85,7 @@ class HkdfKey final: public CryptoKey::Impl { }; } // namespace -kj::Maybe hkdf(jsg::Lock& js, +kj::Maybe hkdf(jsg::Lock& js, size_t length, const EVP_MD* digest, kj::ArrayPtr key, @@ -90,11 +94,11 @@ kj::Maybe hkdf(jsg::Lock& js, // Because we want to be using the v8 sandbox, we need to allocate the result // buffer in the v8 isolate heap then generate the HKDF result into that. ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto backing = jsg::BackingStore::alloc(js, length); - auto buf = ToNcryptoBuffer(backing.asArrayPtr()); + auto buf = jsg::JsArrayBuffer::create(js, length); + auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr()); if (ncrypto::hkdfInfo(digest, ToNcryptoBuffer(key), ToNcryptoBuffer(info), ToNcryptoBuffer(salt), - length, &buf)) { - return jsg::BufferSource(js, kj::mv(backing)); + length, &ncBuf)) { + return kj::mv(buf); } return kj::none; diff --git a/src/workerd/api/crypto/impl.c++ b/src/workerd/api/crypto/impl.c++ index 8f4841c805d..a33015c523c 100644 --- a/src/workerd/api/crypto/impl.c++ +++ b/src/workerd/api/crypto/impl.c++ @@ -7,6 +7,7 @@ #include "simdutf.h" #include +#include #include #include @@ -226,10 +227,6 @@ 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(jsg::Lock& js, kj::Own key) { switch (EVP_PKEY_id(key.get())) { case EVP_PKEY_RSA: @@ -264,7 +261,13 @@ kj::Maybe> toBignum(kj::ArrayPtr data) { } BIGNUM* toBignumUnowned(kj::ArrayPtr data) { - return BN_bin2bn(data.begin(), data.size(), nullptr); + auto result = BN_bin2bn(data.begin(), data.size(), nullptr); + JSG_REQUIRE(result != nullptr, DOMOperationError, "Error importing BIGNUM"); + return result; +} + +UniqueBignum toBignumOwned(kj::ArrayPtr data) { + return UniqueBignum(toBignumUnowned(data), &BN_clear_free); } kj::Maybe> bignumToArray(const BIGNUM& n) { @@ -287,27 +290,28 @@ 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 bignumToArray(jsg::Lock& js, const BIGNUM& n) { + auto buf = jsg::JsUint8Array::create(js, BN_num_bytes(&n)); + if (BN_bn2bin(&n, buf.asArrayPtr().begin()) != buf.asArrayPtr().size()) return kj::none; + return buf; } -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()) { +kj::Maybe bignumToArrayPadded(jsg::Lock& js, const BIGNUM& n) { + auto buf = jsg::JsUint8Array::create(js, BN_num_bytes(&n)); + if (BN_bn2binpad(&n, buf.asArrayPtr().begin(), buf.asArrayPtr().size()) != + buf.asArrayPtr().size()) { return kj::none; } - return jsg::BufferSource(js, kj::mv(result)); + return buf; } -kj::Maybe bignumToArrayPadded( +kj::Maybe bignumToArrayPadded( jsg::Lock& js, const BIGNUM& n, size_t paddedLength) { - auto result = jsg::BackingStore::alloc(js, paddedLength); - if (BN_bn2bin_padded(result.asArrayPtr().begin(), paddedLength, &n) == 0) { + auto buf = jsg::JsUint8Array::create(js, paddedLength); + if (BN_bn2bin_padded(buf.asArrayPtr().begin(), paddedLength, &n) == 0) { return kj::none; } - return jsg::BufferSource(js, kj::mv(result)); + return buf; } kj::Own newBignum() { @@ -370,7 +374,7 @@ kj::Maybe> simdutfBase64UrlDecode(kj::StringPtr input) { return buf.slice(0, result.count).attach(kj::mv(buf)); } -kj::Maybe simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input) { +kj::Maybe simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input) { auto size = simdutf::maximal_binary_length_from_base64(input.begin(), input.size()); KJ_STACK_ARRAY(kj::byte, buf, size, 1024, 4096); auto result = simdutf::base64_to_binary( @@ -378,12 +382,10 @@ kj::Maybe simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr if (result.error != simdutf::SUCCESS) return kj::none; KJ_ASSERT(result.count <= size); - auto backing = jsg::BackingStore::alloc(js, result.count); - backing.asArrayPtr().copyFrom(buf.first(result.count)); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, buf.first(result.count)); } -jsg::BufferSource simdutfBase64UrlDecodeChecked( +jsg::JsUint8Array simdutfBase64UrlDecodeChecked( jsg::Lock& js, kj::StringPtr input, kj::StringPtr error) { return JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(js, input), Error, error); } diff --git a/src/workerd/api/crypto/impl.h b/src/workerd/api/crypto/impl.h index 4df31b5ce77..b9477be2b20 100644 --- a/src/workerd/api/crypto/impl.h +++ b/src/workerd/api/crypto/impl.h @@ -10,6 +10,7 @@ #include "crypto.h" #include +#include #include #include @@ -146,20 +147,20 @@ class CryptoKey::Impl { return usages; } - virtual jsg::BufferSource encrypt(jsg::Lock& js, + virtual jsg::JsArrayBuffer encrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The encrypt operation is not implemented for \"", getAlgorithmName(), "\"."); } - virtual jsg::BufferSource decrypt(jsg::Lock& js, + virtual jsg::JsArrayBuffer decrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The decrypt operation is not implemented for \"", getAlgorithmName(), "\"."); } - virtual jsg::BufferSource sign(jsg::Lock& js, + virtual jsg::JsArrayBuffer sign(jsg::Lock& js, SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, "The sign operation is not implemented for \"", @@ -173,7 +174,7 @@ class CryptoKey::Impl { getAlgorithmName(), "\"."); } - virtual jsg::BufferSource deriveBits(jsg::Lock& js, + virtual jsg::JsArrayBuffer deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe length) const { JSG_FAIL_REQUIRE(DOMNotSupportedError, @@ -181,7 +182,7 @@ class CryptoKey::Impl { "\"."); } - virtual jsg::BufferSource wrapKey(jsg::Lock& js, + virtual jsg::JsArrayBuffer 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 @@ -189,7 +190,7 @@ class CryptoKey::Impl { return encrypt(js, kj::mv(algorithm), unwrappedKey); } - virtual jsg::BufferSource unwrapKey(jsg::Lock& js, + virtual jsg::JsArrayBuffer 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 @@ -210,7 +211,7 @@ 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 jsg::BufferSource exportKeyExt(jsg::Lock& js, + virtual jsg::JsUint8Array exportKeyExt(jsg::Lock& js, kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher = kj::none, @@ -236,7 +237,6 @@ 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"; @@ -335,12 +335,15 @@ const SslDisposer SslDisposer::INSTANCE; using UniqueBignum = std::unique_ptr; kj::Maybe> toBignum(kj::ArrayPtr data); BIGNUM* toBignumUnowned(kj::ArrayPtr data); +// Like toBignumUnowned but returns a UniqueBignum for RAII. Use .release() to transfer +// ownership to RSA_set0_key etc. +UniqueBignum toBignumOwned(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( +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); kj::Own newBignum(); @@ -443,8 +446,8 @@ ncrypto::Buffer ToNcryptoBuffer(kj::ArrayPtr array) { } kj::Maybe> simdutfBase64UrlDecode(kj::StringPtr input); -kj::Maybe simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input); -jsg::BufferSource simdutfBase64UrlDecodeChecked( +kj::Maybe simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input); +jsg::JsUint8Array simdutfBase64UrlDecodeChecked( jsg::Lock& js, kj::StringPtr input, kj::StringPtr error); } // namespace workerd::api diff --git a/src/workerd/api/crypto/kdf.h b/src/workerd/api/crypto/kdf.h index b8a03e1f88e..0bc14ab708b 100644 --- a/src/workerd/api/crypto/kdf.h +++ b/src/workerd/api/crypto/kdf.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include @@ -14,7 +15,7 @@ using EVP_MD = struct env_md_st; namespace workerd::api { // Perform HKDF key derivation. -kj::Maybe hkdf(jsg::Lock& js, +kj::Maybe hkdf(jsg::Lock& js, size_t length, const EVP_MD* digest, kj::ArrayPtr key, @@ -22,7 +23,7 @@ kj::Maybe hkdf(jsg::Lock& js, kj::ArrayPtr info); // Perform PBKDF2 key derivation. -kj::Maybe pbkdf2(jsg::Lock& js, +kj::Maybe pbkdf2(jsg::Lock& js, size_t length, size_t iterations, const EVP_MD* digest, @@ -30,7 +31,7 @@ kj::Maybe pbkdf2(jsg::Lock& js, kj::ArrayPtr salt); // Perform Scrypt key derivation. -kj::Maybe scrypt(jsg::Lock& js, +kj::Maybe scrypt(jsg::Lock& js, size_t length, uint32_t N, uint32_t r, diff --git a/src/workerd/api/crypto/keys.c++ b/src/workerd/api/crypto/keys.c++ index 715f560dd4e..03a20891e6d 100644 --- a/src/workerd/api/crypto/keys.c++ +++ b/src/workerd/api/crypto/keys.c++ @@ -30,18 +30,14 @@ AsymmetricKeyCryptoKeyImpl::AsymmetricKeyCryptoKeyImpl(AsymmetricKeyData&& key, KJ_DASSERT(keyType != KeyType::SECRET); } -jsg::BufferSource AsymmetricKeyCryptoKeyImpl::signatureSslToWebCrypto( +jsg::JsArrayBuffer AsymmetricKeyCryptoKeyImpl::signatureSslToWebCrypto( 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)); + return jsg::JsArrayBuffer::create(js, signature); } -jsg::BufferSource AsymmetricKeyCryptoKeyImpl::signatureWebCryptoToSsl( +jsg::JsArrayBuffer 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)); + return jsg::JsArrayBuffer::create(js, signature); } SubtleCrypto::ExportKeyData AsymmetricKeyCryptoKeyImpl::exportKey( @@ -79,19 +75,16 @@ SubtleCrypto::ExportKeyData AsymmetricKeyCryptoKeyImpl::exportKey( jwk.key_ops = getUsages().map([](auto usage) { return kj::str(usage.name()); }); return jwk; } else if (format == "raw"_kj) { - return exportRaw(js); + return exportRaw(js).addRef(js); } else { JSG_FAIL_REQUIRE(DOMInvalidAccessError, "Cannot export \"", getAlgorithmName(), "\" in \"", format, "\" format."); } - auto backing = jsg::BackingStore::alloc(js, derLen); - auto src = kj::arrayPtr(der, derLen); - backing.asArrayPtr().copyFrom(src); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, kj::arrayPtr(der, derLen)).addRef(js); } -jsg::BufferSource AsymmetricKeyCryptoKeyImpl::exportKeyExt(jsg::Lock& js, +jsg::JsUint8Array AsymmetricKeyCryptoKeyImpl::exportKeyExt(jsg::Lock& js, kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher, @@ -126,10 +119,8 @@ jsg::BufferSource AsymmetricKeyCryptoKeyImpl::exportKeyExt(jsg::Lock& js, const auto fromBio = [&](kj::StringPtr format) { BUF_MEM* bptr; BIO_get_mem_ptr(bio.get(), &bptr); - 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)); + return jsg::JsUint8Array::create(js, src.asBytes()); }; if (getType() == "public"_kj) { @@ -220,7 +211,7 @@ jsg::BufferSource AsymmetricKeyCryptoKeyImpl::exportKeyExt(jsg::Lock& js, JSG_FAIL_REQUIRE(TypeError, "Failed to encode private key"); } -jsg::BufferSource AsymmetricKeyCryptoKeyImpl::sign(jsg::Lock& js, +jsg::JsArrayBuffer AsymmetricKeyCryptoKeyImpl::sign(jsg::Lock& js, SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const { JSG_REQUIRE(keyType == KeyType::PRIVATE, DOMInvalidAccessError, diff --git a/src/workerd/api/crypto/keys.h b/src/workerd/api/crypto/keys.h index 0467562c6a7..e2a04211ba1 100644 --- a/src/workerd/api/crypto/keys.h +++ b/src/workerd/api/crypto/keys.h @@ -63,11 +63,11 @@ class AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl { const kj::Maybe>& callTimeHash) const = 0; // Convert OpenSSL-format signature to WebCrypto-format signature, if different. - virtual jsg::BufferSource signatureSslToWebCrypto( + virtual jsg::JsArrayBuffer signatureSslToWebCrypto( jsg::Lock& js, kj::ArrayPtr signature) const; // Convert WebCrypto-format signature to OpenSSL-format signature, if different. - virtual jsg::BufferSource signatureWebCryptoToSsl( + virtual jsg::JsArrayBuffer signatureWebCryptoToSsl( jsg::Lock& js, kj::ArrayPtr signature) const; // Add salt to digest context in order to generate or verify salted signature. @@ -80,13 +80,13 @@ class AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl { SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final; - virtual jsg::BufferSource exportKeyExt(jsg::Lock& js, + virtual jsg::JsUint8Array exportKeyExt(jsg::Lock& js, kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher = kj::none, jsg::Optional> passphrase = kj::none) const override final; - jsg::BufferSource sign(jsg::Lock& js, + jsg::JsArrayBuffer sign(jsg::Lock& js, SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override; @@ -119,7 +119,7 @@ class AsymmetricKeyCryptoKeyImpl: public CryptoKey::Impl { private: virtual SubtleCrypto::JsonWebKey exportJwk() const = 0; - virtual jsg::BufferSource exportRaw(jsg::Lock& js) const = 0; + virtual jsg::JsArrayBuffer 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 35678299f07..845696e552f 100644 --- a/src/workerd/api/crypto/pbkdf2.c++ +++ b/src/workerd/api/crypto/pbkdf2.c++ @@ -34,15 +34,16 @@ class Pbkdf2Key final: public CryptoKey::Impl { } private: - jsg::BufferSource deriveBits(jsg::Lock& js, + jsg::JsArrayBuffer deriveBits(jsg::Lock& js, SubtleCrypto::DeriveKeyAlgorithm&& algorithm, kj::Maybe maybeLength) const override { kj::StringPtr hashName = api::getAlgorithmName( JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\".")); auto hashType = lookupDigestAlgorithm(hashName).second; - kj::ArrayPtr salt = + auto saltHandle = JSG_REQUIRE_NONNULL(algorithm.salt, TypeError, "Missing field \"salt\" in \"algorithm\".") - .asArrayPtr(); + .getHandle(js); + kj::ArrayPtr salt = saltHandle.asArrayPtr(); int iterations = JSG_REQUIRE_NONNULL( algorithm.iterations, TypeError, "Missing field \"iterations\" in \"algorithm\"."); @@ -101,18 +102,18 @@ class Pbkdf2Key final: public CryptoKey::Impl { }; } // namespace -kj::Maybe pbkdf2(jsg::Lock& js, +kj::Maybe pbkdf2(jsg::Lock& js, size_t length, size_t iterations, const EVP_MD* digest, kj::ArrayPtr password, kj::ArrayPtr salt) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto backing = jsg::BackingStore::alloc(js, length); - auto buf = ToNcryptoBuffer(backing.asArrayPtr()); + auto buf = jsg::JsArrayBuffer::create(js, length); + auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr()); if (ncrypto::pbkdf2Into(digest, ToNcryptoBuffer(password.asChars()), ToNcryptoBuffer(salt), - iterations, length, &buf)) { - return jsg::BufferSource(js, kj::mv(backing)); + iterations, length, &ncBuf)) { + return kj::mv(buf); } return kj::none; } diff --git a/src/workerd/api/crypto/prime.c++ b/src/workerd/api/crypto/prime.c++ index c5da55a4f6f..37c31a0057f 100644 --- a/src/workerd/api/crypto/prime.c++ +++ b/src/workerd/api/crypto/prime.c++ @@ -8,7 +8,7 @@ namespace workerd::api { -jsg::BufferSource randomPrime(jsg::Lock& js, +jsg::JsArrayBuffer randomPrime(jsg::Lock& js, uint32_t size, bool safe, kj::Maybe> add_buf, @@ -78,8 +78,9 @@ jsg::BufferSource randomPrime(jsg::Lock& js, .add = kj::mv(add), .rem = kj::mv(rem), })) { - return JSG_REQUIRE_NONNULL( + auto buf = JSG_REQUIRE_NONNULL( bignumToArrayPadded(js, *prime.get()), Error, "Error while generating prime"); + return jsg::JsArrayBuffer::create(js, buf.asArrayPtr()); } JSG_FAIL_REQUIRE(Error, "Error while generating prime"); diff --git a/src/workerd/api/crypto/prime.h b/src/workerd/api/crypto/prime.h index 319616cce45..cc16d7b6e7d 100644 --- a/src/workerd/api/crypto/prime.h +++ b/src/workerd/api/crypto/prime.h @@ -6,13 +6,13 @@ namespace workerd::jsg { class Lock; -class BufferSource; +class JsArrayBuffer; } // namespace workerd::jsg namespace workerd::api { // Generate a random prime number -jsg::BufferSource randomPrime(jsg::Lock& js, +jsg::JsArrayBuffer randomPrime(jsg::Lock& js, uint32_t size, bool safe, kj::Maybe> add_buf, diff --git a/src/workerd/api/crypto/rsa.c++ b/src/workerd/api/crypto/rsa.c++ index f3ff39b2942..d6d77c91cc9 100644 --- a/src/workerd/api/crypto/rsa.c++ +++ b/src/workerd/api/crypto/rsa.c++ @@ -35,13 +35,10 @@ kj::Maybe fromBignum(kj::ArrayPtr value) { return asUnsigned; } -jsg::BufferSource bioToArray(jsg::Lock& js, BIO* bio) { +jsg::JsArrayBuffer bioToArray(jsg::Lock& js, BIO* bio) { BUF_MEM* bptr; BIO_get_mem_ptr(bio, &bptr); - auto buf = jsg::BackingStore::alloc(js, bptr->length); - auto aptr = kj::arrayPtr(bptr->data, bptr->length); - buf.asArrayPtr().copyFrom(aptr.asBytes()); - return jsg::BufferSource(js, kj::mv(buf)); + return jsg::JsArrayBuffer::create(js, kj::asBytes(bptr->data, bptr->length)); } } // namespace @@ -65,7 +62,7 @@ size_t Rsa::getModulusSize() const { return RSA_size(rsa); } -jsg::BufferSource Rsa::getPublicExponent(jsg::Lock& js) { +jsg::JsUint8Array Rsa::getPublicExponent(jsg::Lock& js) { return KJ_REQUIRE_NONNULL(bignumToArray(js, *e)); } @@ -73,8 +70,10 @@ CryptoKey::AsymmetricKeyDetails Rsa::getAsymmetricKeyDetail(jsg::Lock& js) const CryptoKey::AsymmetricKeyDetails details; details.modulusLength = BN_num_bits(n); - details.publicExponent = + auto pubExp = JSG_REQUIRE_NONNULL(bignumToArrayPadded(js, *e), Error, "Failed to extract public exponent"); + auto ab = jsg::JsArrayBuffer::create(js, pubExp.asArrayPtr()); + details.publicExponent = ab.addRef(js); // TODO(soon): Does BoringSSL not support retrieving RSA_PSS params? // if (type == EVP_PKEY_RSA_PSS) { @@ -123,7 +122,7 @@ CryptoKey::AsymmetricKeyDetails Rsa::getAsymmetricKeyDetail(jsg::Lock& js) const return kj::mv(details); } -jsg::BufferSource Rsa::sign(jsg::Lock& js, const kj::ArrayPtr data) const { +jsg::JsArrayBuffer 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 @@ -150,12 +149,10 @@ jsg::BufferSource Rsa::sign(jsg::Lock& js, const kj::ArrayPtr da data.size(), RSA_NO_PADDING)); KJ_ASSERT(signatureSize <= signature.size()); - auto backing = jsg::BackingStore::alloc(js, signatureSize); - backing.asArrayPtr().copyFrom(signature.first(signatureSize)); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, signature.first(signatureSize)); } -jsg::BufferSource Rsa::cipher(jsg::Lock& js, +jsg::JsArrayBuffer Rsa::cipher(jsg::Lock& js, EVP_PKEY_CTX* ctx, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data, @@ -172,7 +169,8 @@ jsg::BufferSource Rsa::cipher(jsg::Lock& js, "Error doing RSA OAEP encrypt/decrypt (", "MGF1 digest", ")", internalDescribeOpensslErrors()); - KJ_IF_SOME(l, algorithm.label) { + KJ_IF_SOME(lRef, algorithm.label) { + auto l = lRef.getHandle(js); auto labelCopy = reinterpret_cast(OPENSSL_malloc(l.size())); KJ_DEFER(OPENSSL_free(labelCopy)); // If setting the label fails we need to remember to destroy the buffer. In practice it can't @@ -207,9 +205,7 @@ jsg::BufferSource Rsa::cipher(jsg::Lock& js, 1 == err, DOMOperationError, "RSA-OAEP failed encrypt/decrypt", tryDescribeOpensslErrors()); result.resize(maxResultLength); - auto backing = jsg::BackingStore::alloc(js, result.size()); - backing.asArrayPtr().copyFrom(result); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, result.asPtr()); } SubtleCrypto::JsonWebKey Rsa::toJwk( @@ -256,10 +252,14 @@ kj::Maybe Rsa::fromJwk( static constexpr auto kInvalidBase64Error = "Invalid RSA key in JSON Web Key; invalid base64."_kj; - 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, + auto nBuf = simdutfBase64UrlDecodeChecked(js, n, kInvalidBase64Error); + auto nDecoded = toBignumOwned(nBuf.asArrayPtr()); + auto eBuf = simdutfBase64UrlDecodeChecked(js, e, kInvalidBase64Error); + auto eDecoded = toBignumOwned(eBuf.asArrayPtr()); + JSG_REQUIRE(RSA_set0_key(rsa.get(), nDecoded.get(), eDecoded.get(), nullptr) == 1, Error, "Invalid RSA key in JSON Web Key; failed to set key parameters"); + nDecoded.release(); + eDecoded.release(); if (keyType == KeyType::PRIVATE) { auto d = JSG_REQUIRE_NONNULL(jwk.d.map([](auto& str) { return str.asPtr(); }), Error, @@ -280,24 +280,38 @@ kj::Maybe Rsa::fromJwk( auto qi = JSG_REQUIRE_NONNULL(jwk.qi.map([](auto& str) { return str.asPtr(); }), Error, "Invalid RSA key in JSON Web Key; missing or invalid " "First CRT Coefficient parameter (\"qi\")."); - auto dDecoded = - 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, + auto dBuf = simdutfBase64UrlDecodeChecked(js, d, "Invalid RSA key in JSON Web Key"_kj); + auto dDecoded = toBignumOwned(dBuf.asArrayPtr()); + auto pBuf = simdutfBase64UrlDecodeChecked(js, p, kInvalidBase64Error); + auto pDecoded = toBignumOwned(pBuf.asArrayPtr()); + auto qBuf = simdutfBase64UrlDecodeChecked(js, q, kInvalidBase64Error); + auto qDecoded = toBignumOwned(qBuf.asArrayPtr()); + auto dpBuf = simdutfBase64UrlDecodeChecked(js, dp, kInvalidBase64Error); + auto dpDecoded = toBignumOwned(dpBuf.asArrayPtr()); + auto dqBuf = simdutfBase64UrlDecodeChecked(js, dq, kInvalidBase64Error); + auto dqDecoded = toBignumOwned(dqBuf.asArrayPtr()); + auto qiBuf = simdutfBase64UrlDecodeChecked(js, qi, kInvalidBase64Error); + auto qiDecoded = toBignumOwned(qiBuf.asArrayPtr()); + + // .release() transfers BIGNUM ownership to the RSA key. UniqueBignum ensures + // cleanup if any earlier allocation or decode throws. + JSG_REQUIRE(RSA_set0_key(rsa.get(), nullptr, nullptr, dDecoded.get()) == 1, Error, "Invalid RSA key in JSON Web Key; failed to set private exponent"); - JSG_REQUIRE(RSA_set0_factors(rsa.get(), pDecoded, qDecoded) == 1, Error, + dDecoded.release(); + JSG_REQUIRE(RSA_set0_factors(rsa.get(), pDecoded.get(), qDecoded.get()) == 1, Error, "Invalid RSA key in JSON Web Key; failed to set prime factors"); - JSG_REQUIRE(RSA_set0_crt_params(rsa.get(), dpDecoded, dqDecoded, qiDecoded) == 1, Error, - "Invalid RSA key in JSON Web Key; failed to set CRT parameters"); + pDecoded.release(); + qDecoded.release(); + JSG_REQUIRE( + RSA_set0_crt_params(rsa.get(), dpDecoded.get(), dqDecoded.get(), qiDecoded.get()) == 1, + Error, "Invalid RSA key in JSON Web Key; failed to set CRT parameters"); + dpDecoded.release(); + dqDecoded.release(); + qiDecoded.release(); } auto evpPkey = OSSL_NEW(EVP_PKEY); - KJ_ASSERT(EVP_PKEY_set1_RSA(evpPkey.get(), rsa.get()) == 1); + OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa.get())); auto usages = keyType == KeyType::PRIVATE ? CryptoKeyUsageSet::privateKeyMask() : CryptoKeyUsageSet::publicKeyMask(); @@ -345,7 +359,7 @@ kj::String Rsa::toPem( } case KeyEncoding::PKCS8: { auto evpPkey = OSSL_NEW(EVP_PKEY); - EVP_PKEY_set1_RSA(evpPkey.get(), rsa); + OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa)); JSG_REQUIRE(PEM_write_bio_PKCS8PrivateKey(bio.get(), evpPkey.get(), cipher, reinterpret_cast(passphrase), passLen, nullptr, nullptr) == 1, Error, "Failed to write RSA private key to PKCS8 PEM", tryDescribeOpensslErrors()); @@ -363,7 +377,7 @@ kj::String Rsa::toPem( return kj::str(bioToArray(js, bio.get()).asArrayPtr().asChars()); } -jsg::BufferSource Rsa::toDer( +jsg::JsArrayBuffer Rsa::toDer( jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe options) const { ClearErrorOnReturn clearErrorOnReturn; auto bio = OSSL_BIO_MEM(); @@ -377,7 +391,7 @@ jsg::BufferSource Rsa::toDer( } case workerd::api::KeyEncoding::SPKI: { auto evpPkey = OSSL_NEW(EVP_PKEY); - EVP_PKEY_set1_RSA(evpPkey.get(), rsa); + OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa)); JSG_REQUIRE(i2d_PUBKEY_bio(bio.get(), evpPkey.get()) == 1, Error, "Failed to write RSA public key to SPKI", tryDescribeOpensslErrors()); break; @@ -406,7 +420,7 @@ jsg::BufferSource Rsa::toDer( } case KeyEncoding::PKCS8: { auto evpPkey = OSSL_NEW(EVP_PKEY); - EVP_PKEY_set1_RSA(evpPkey.get(), rsa); + OSSLCALL(EVP_PKEY_set1_RSA(evpPkey.get(), rsa)); JSG_REQUIRE(i2d_PKCS8PrivateKey_bio(bio.get(), evpPkey.get(), cipher, reinterpret_cast(passphrase), passLen, nullptr, nullptr) == 1, Error, "Failed to write RSA private key to PKCS8 PEM", tryDescribeOpensslErrors()); @@ -500,7 +514,7 @@ class RsaBase: public AsymmetricKeyCryptoKeyImpl { return rsa.toJwk(getTypeEnum(), jwkHashAlgorithmName()); } - jsg::BufferSource exportRaw(jsg::Lock& js) const override final { + jsg::JsArrayBuffer exportRaw(jsg::Lock& js) const override final { JSG_FAIL_REQUIRE( DOMInvalidAccessError, "Cannot export \"", getAlgorithmName(), "\" in \"raw\" format."); } @@ -604,7 +618,7 @@ class RsaOaepKey final: public RsaBase { "The sign and verify operations are not implemented for \"", keyAlgorithm.name, "\"."); } - jsg::BufferSource encrypt(jsg::Lock& js, + jsg::JsArrayBuffer encrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr plainText) const override { JSG_REQUIRE(getTypeEnum() == KeyType::PUBLIC, DOMInvalidAccessError, @@ -613,7 +627,7 @@ class RsaOaepKey final: public RsaBase { js, kj::mv(algorithm), plainText, EVP_PKEY_encrypt_init, EVP_PKEY_encrypt); } - jsg::BufferSource decrypt(jsg::Lock& js, + jsg::JsArrayBuffer decrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr cipherText) const override { JSG_REQUIRE(getTypeEnum() == KeyType::PRIVATE, DOMInvalidAccessError, @@ -623,7 +637,7 @@ class RsaOaepKey final: public RsaBase { } private: - jsg::BufferSource commonEncryptDecrypt(jsg::Lock& js, + jsg::JsArrayBuffer commonEncryptDecrypt(jsg::Lock& js, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data, InitFunction init, @@ -654,7 +668,7 @@ class RsaRawKey final: public RsaBase { AsymmetricKeyData keyData, CryptoKey::RsaKeyAlgorithm keyAlgorithm, bool extractable) : RsaBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable) {} - jsg::BufferSource sign(jsg::Lock& js, + jsg::JsArrayBuffer sign(jsg::Lock& js, SubtleCrypto::SignAlgorithm&& algorithm, kj::ArrayPtr data) const override { auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(getEvpPkey()), DOMDataError, "Missing RSA key"); @@ -744,10 +758,11 @@ kj::Own rsaJwkReader(SubtleCrypto::JsonWebKey&& keyDataJwk) { "Invalid RSA key in JSON Web Key; missing or invalid " "Exponent parameter (\"e\")."); - // RSA_set0_*() transfers BIGNUM ownership to the RSA key, so we don't need to worry about - // calling BN_free(). - OSSLCALL(RSA_set0_key( - rsaKey.get(), toBignumUnowned(modulus), toBignumUnowned(publicExponent), nullptr)); + auto nBignum = toBignumOwned(modulus); + auto eBignum = toBignumOwned(publicExponent); + OSSLCALL(RSA_set0_key(rsaKey.get(), nBignum.get(), eBignum.get(), nullptr)); + nBignum.release(); + eBignum.release(); if (keyDataJwk.d != kj::none) { // This is a private key. @@ -756,7 +771,9 @@ kj::Own rsaJwkReader(SubtleCrypto::JsonWebKey&& keyDataJwk) { "Invalid RSA key in JSON Web Key; missing or invalid " "Private Exponent parameter (\"d\")."); - OSSLCALL(RSA_set0_key(rsaKey.get(), nullptr, nullptr, toBignumUnowned(privateExponent))); + auto dBignum = toBignumOwned(privateExponent); + OSSLCALL(RSA_set0_key(rsaKey.get(), nullptr, nullptr, dBignum.get())); + dBignum.release(); auto presence = (keyDataJwk.p != kj::none) + (keyDataJwk.q != kj::none) + (keyDataJwk.dp != kj::none) + (keyDataJwk.dq != kj::none) + (keyDataJwk.qi != kj::none); @@ -778,10 +795,19 @@ kj::Own rsaJwkReader(SubtleCrypto::JsonWebKey&& keyDataJwk) { "Invalid RSA key in JSON Web Key; invalid First CRT " "Coefficient parameter (\"qi\")."); - OSSLCALL(RSA_set0_factors( - rsaKey.get(), toBignumUnowned(firstPrimeFactor), toBignumUnowned(secondPrimeFactor))); - OSSLCALL(RSA_set0_crt_params(rsaKey.get(), toBignumUnowned(firstFactorCrtExponent), - toBignumUnowned(secondFactorCrtExponent), toBignumUnowned(firstCrtCoefficient))); + auto pBn = toBignumOwned(firstPrimeFactor); + auto qBn = toBignumOwned(secondPrimeFactor); + auto dpBn = toBignumOwned(firstFactorCrtExponent); + auto dqBn = toBignumOwned(secondFactorCrtExponent); + auto qiBn = toBignumOwned(firstCrtCoefficient); + + OSSLCALL(RSA_set0_factors(rsaKey.get(), pBn.get(), qBn.get())); + pBn.release(); + qBn.release(); + OSSLCALL(RSA_set0_crt_params(rsaKey.get(), dpBn.get(), dqBn.get(), qiBn.get())); + dpBn.release(); + dqBn.release(); + qiBn.release(); } else { JSG_REQUIRE(presence == 0, DOMDataError, "Invalid RSA private key in JSON Web Key; if one Prime " @@ -807,7 +833,8 @@ kj::OneOf, CryptoKeyPair> CryptoKey::Impl::generateRsa(jsg:: "generateRsa called on non-RSA cryptoKey", normalizedName); auto publicExponent = JSG_REQUIRE_NONNULL(kj::mv(algorithm.publicExponent), TypeError, - "Missing field \"publicExponent\" in \"algorithm\"."); + "Missing field \"publicExponent\" in \"algorithm\".") + .getHandle(js); kj::StringPtr hash = api::getAlgorithmName( JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\".")); int modulusLength = JSG_REQUIRE_NONNULL( @@ -847,9 +874,11 @@ kj::OneOf, CryptoKeyPair> CryptoKey::Impl::generateRsa(jsg:: auto publicEvpPKey = OSSL_NEW(EVP_PKEY); OSSLCALL(EVP_PKEY_set1_RSA(publicEvpPKey.get(), rsaPublicKey)); + // Create a JsUint8Array copy of the public exponent for the key algorithm struct. + auto expCopy = jsg::JsUint8Array::create(js, publicExponent.asArrayPtr()); auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = normalizedName, .modulusLength = static_cast(modulusLength), - .publicExponent = kj::mv(publicExponent), + .publicExponent = jsg::JsBufferSource(expCopy).addRef(js), .hash = KeyAlgorithm{normalizedHashName}}; return generateRsaPair(js, normalizedName, kj::mv(privateEvpPKey), kj::mv(publicEvpPKey), @@ -941,11 +970,11 @@ kj::Own CryptoKey::Impl::importRsa(jsg::Lock& js, auto publicExponent = rsa.getPublicExponent(js); // Validate modulus and exponent, reject imported RSA keys that may be unsafe. - Rsa::validateRsaParams(js, modulusLength, publicExponent, true); + Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr(), true); auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = normalizedName, .modulusLength = static_cast(modulusLength), - .publicExponent = kj::mv(publicExponent), + .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js), .hash = KeyAlgorithm{normalizedHashName}}; if (normalizedName == "RSASSA-PKCS1-v1_5") { return kj::heap(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable); @@ -1006,11 +1035,11 @@ kj::Own CryptoKey::Impl::importRsaRaw(jsg::Lock& js, auto publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE())); // Validate modulus and exponent, reject imported RSA keys that may be unsafe. - Rsa::validateRsaParams(js, modulusLength, publicExponent, true); + Rsa::validateRsaParams(js, modulusLength, publicExponent.asArrayPtr(), true); auto keyAlgorithm = CryptoKey::RsaKeyAlgorithm{.name = "RSA-RAW"_kj, .modulusLength = static_cast(modulusLength), - .publicExponent = kj::mv(publicExponent)}; + .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js)}; return kj::heap(kj::mv(importedKey), kj::mv(keyAlgorithm), extractable); } @@ -1019,13 +1048,14 @@ kj::Own fromRsaKey(jsg::Lock& js, kj::Own key) { auto rsa = JSG_REQUIRE_NONNULL(Rsa::tryGetRsa(key.get()), DOMDataError, "Input was not an RSA key"); + auto publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE())); return kj::heap(AsymmetricKeyData{.evpPkey = kj::mv(key), .keyType = KeyType::PUBLIC, .usages = CryptoKeyUsageSet::decrypt() | CryptoKeyUsageSet::sign() | CryptoKeyUsageSet::verify()}, CryptoKey::RsaKeyAlgorithm{ .name = "RSA"_kj, - .publicExponent = KJ_REQUIRE_NONNULL(bignumToArray(js, *rsa.getE())), + .publicExponent = jsg::JsBufferSource(publicExponent).addRef(js), }, true); } diff --git a/src/workerd/api/crypto/rsa.h b/src/workerd/api/crypto/rsa.h index d394473a476..ac26eff4d42 100644 --- a/src/workerd/api/crypto/rsa.h +++ b/src/workerd/api/crypto/rsa.h @@ -3,6 +3,8 @@ #include "crypto.h" #include "keys.h" +#include + #include #include @@ -28,11 +30,11 @@ class Rsa final { return d; } - jsg::BufferSource getPublicExponent(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; + jsg::JsUint8Array getPublicExponent(jsg::Lock& js) KJ_WARN_UNUSED_RESULT; CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const KJ_WARN_UNUSED_RESULT; - jsg::BufferSource sign( + jsg::JsArrayBuffer sign( jsg::Lock& js, kj::ArrayPtr data) const KJ_WARN_UNUSED_RESULT; static kj::Maybe fromJwk( @@ -51,13 +53,13 @@ class Rsa final { KeyType keyType, kj::Maybe options = kj::none) const KJ_WARN_UNUSED_RESULT; - jsg::BufferSource toDer(jsg::Lock& js, + jsg::JsArrayBuffer toDer(jsg::Lock& js, KeyEncoding encoding, KeyType keyType, kj::Maybe options = kj::none) const KJ_WARN_UNUSED_RESULT; using EncryptDecryptFunction = decltype(EVP_PKEY_encrypt); - jsg::BufferSource cipher(jsg::Lock& js, + jsg::JsArrayBuffer cipher(jsg::Lock& js, EVP_PKEY_CTX* ctx, SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr data, diff --git a/src/workerd/api/crypto/scrypt.c++ b/src/workerd/api/crypto/scrypt.c++ index 8e10f6b4fc5..27d716b9d66 100644 --- a/src/workerd/api/crypto/scrypt.c++ +++ b/src/workerd/api/crypto/scrypt.c++ @@ -9,7 +9,7 @@ namespace workerd::api { -kj::Maybe scrypt(jsg::Lock& js, +kj::Maybe scrypt(jsg::Lock& js, size_t length, uint32_t N, uint32_t r, @@ -18,11 +18,11 @@ kj::Maybe scrypt(jsg::Lock& js, kj::ArrayPtr pass, kj::ArrayPtr salt) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto backing = jsg::BackingStore::alloc(js, length); - auto buf = ToNcryptoBuffer(backing.asArrayPtr()); - if (ncrypto::scryptInto( - ToNcryptoBuffer(pass.asChars()), ToNcryptoBuffer(salt), N, r, p, maxmem, length, &buf)) { - return jsg::BufferSource(js, kj::mv(backing)); + auto buf = jsg::JsArrayBuffer::create(js, length); + auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr()); + if (ncrypto::scryptInto(ToNcryptoBuffer(pass.asChars()), ToNcryptoBuffer(salt), N, r, p, maxmem, + length, &ncBuf)) { + return kj::mv(buf); } return kj::none; } diff --git a/src/workerd/api/crypto/spkac.c++ b/src/workerd/api/crypto/spkac.c++ index a2b8437b2ac..efde7d635d4 100644 --- a/src/workerd/api/crypto/spkac.c++ +++ b/src/workerd/api/crypto/spkac.c++ @@ -38,7 +38,7 @@ bool verifySpkac(kj::ArrayPtr input) { return ncrypto::VerifySpkac(ToNcryptoBuffer(input.asChars())); } -kj::Maybe exportPublicKey(jsg::Lock& js, kj::ArrayPtr input) { +kj::Maybe exportPublicKey(jsg::Lock& js, kj::ArrayPtr input) { // Works around a bug in ncrypto... auto pos = std::string_view(input.asChars().begin(), input.size()).find_last_not_of(" \n\r\t"); if (pos == std::string_view::npos) { @@ -47,15 +47,15 @@ kj::Maybe exportPublicKey(jsg::Lock& js, kj::ArrayPtrlength); + auto buf = jsg::JsUint8Array::create(js, bptr->length); auto aptr = kj::arrayPtr(bptr->data, bptr->length); buf.asArrayPtr().copyFrom(aptr); - return jsg::BufferSource(js, kj::mv(buf)); + return buf; } return kj::none; } -kj::Maybe exportChallenge(jsg::Lock& js, kj::ArrayPtr input) { +kj::Maybe exportChallenge(jsg::Lock& js, kj::ArrayPtr input) { // Works around a bug in ncrypto... auto pos = std::string_view(input.asChars().begin(), input.size()).find_last_not_of(" \n\r\t"); @@ -64,10 +64,8 @@ kj::Maybe exportChallenge(jsg::Lock& js, kj::ArrayPtr(), dp.size()); - dest.asArrayPtr().copyFrom(src); - return jsg::BufferSource(js, kj::mv(dest)); + return jsg::JsUint8Array::create(js, src); } return kj::none; } diff --git a/src/workerd/api/crypto/spkac.h b/src/workerd/api/crypto/spkac.h index 8dc79e44206..e932f9b6800 100644 --- a/src/workerd/api/crypto/spkac.h +++ b/src/workerd/api/crypto/spkac.h @@ -8,8 +8,8 @@ namespace workerd::api { bool verifySpkac(kj::ArrayPtr input); -kj::Maybe exportPublicKey(jsg::Lock& js, kj::ArrayPtr input); +kj::Maybe exportPublicKey(jsg::Lock& js, kj::ArrayPtr input); -kj::Maybe exportChallenge(jsg::Lock& js, kj::ArrayPtr input); +kj::Maybe exportChallenge(jsg::Lock& js, kj::ArrayPtr input); } // namespace workerd::api diff --git a/src/workerd/api/crypto/x509.c++ b/src/workerd/api/crypto/x509.c++ index 28d7b827251..dd613d39c80 100644 --- a/src/workerd/api/crypto/x509.c++ +++ b/src/workerd/api/crypto/x509.c++ @@ -384,15 +384,15 @@ kj::String getExponentString(BIO* bio, const BIGNUM* e) { return toString(bio); } -jsg::BufferSource getRsaPubKey(jsg::Lock& js, RSA* rsa) { +jsg::JsUint8Array getRsaPubKey(jsg::Lock& js, RSA* rsa) { int size = i2d_RSA_PUBKEY(rsa, nullptr); KJ_ASSERT(size >= 0); - auto buf = jsg::BackingStore::alloc(js, size); + auto buf = jsg::JsUint8Array::create(js, size); auto data = buf.asArrayPtr().begin(); KJ_ASSERT(i2d_RSA_PUBKEY(rsa, &data) >= 0); - return jsg::BufferSource(js, kj::mv(buf)); + return buf; } kj::Maybe getECGroupBits(const EC_GROUP* group) { @@ -404,21 +404,21 @@ kj::Maybe getECGroupBits(const EC_GROUP* group) { return bits; } -kj::Maybe eCPointToBuffer( +kj::Maybe eCPointToBuffer( jsg::Lock& js, const EC_GROUP* group, const EC_POINT* point, point_conversion_form_t form) { size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); if (len == 0) { return kj::none; } - auto buffer = jsg::BackingStore::alloc(js, len); + auto buffer = jsg::JsUint8Array::create(js, len); len = EC_POINT_point2oct(group, point, form, buffer.asArrayPtr().begin(), buffer.size(), nullptr); if (len == 0) { return kj::none; } - return jsg::BufferSource(js, kj::mv(buffer)); + return buffer; } template @@ -430,7 +430,7 @@ kj::Maybe getCurveName(const int nid) { return kj::str(name); } -kj::Maybe getECPubKey(jsg::Lock& js, const EC_GROUP* group, EC_KEY* ec) { +kj::Maybe getECPubKey(jsg::Lock& js, const EC_GROUP* group, EC_KEY* ec) { const EC_POINT* pubkey = EC_KEY_get0_public_key(ec); if (pubkey == nullptr) return kj::none; @@ -650,13 +650,13 @@ kj::Maybe> X509Certificate::getSerialNumber() { return kj::none; } -jsg::BufferSource X509Certificate::getRaw(jsg::Lock& js) { +jsg::JsUint8Array X509Certificate::getRaw(jsg::Lock& js) { ClearErrorOnReturn clearErrorOnReturn; int size = i2d_X509(cert_.get(), nullptr); - auto buf = jsg::BackingStore::alloc(js, size); + auto buf = jsg::JsUint8Array::create(js, size); auto data = buf.asArrayPtr().begin(); KJ_REQUIRE(i2d_X509(cert_.get(), &data) >= 0); - return jsg::BufferSource(js, kj::mv(buf)); + return buf; } kj::Maybe> X509Certificate::getPublicKey(jsg::Lock& js) { @@ -791,7 +791,7 @@ jsg::JsObject X509Certificate::toLegacyObject(jsg::Lock& js) { obj.set(js, "modulus", js.str(getModulusString(bio.get(), RSA_get0_n(rsa)))); obj.set(js, "bits", js.num(RSA_bits(rsa))); obj.set(js, "exponent", js.str(getExponentString(bio.get(), RSA_get0_e(rsa)))); - obj.set(js, "pubkey", jsg::JsValue(getRsaPubKey(js, rsa).getHandle(js))); + obj.set(js, "pubkey", getRsaPubKey(js, rsa)); break; } case EVP_PKEY_EC: { @@ -803,7 +803,7 @@ jsg::JsObject X509Certificate::toLegacyObject(jsg::Lock& js) { obj.set(js, "bits", js.num(bits)); } KJ_IF_SOME(pubkey, getECPubKey(js, group, ec)) { - obj.set(js, "pubkey", jsg::JsValue(pubkey.getHandle(js))); + obj.set(js, "pubkey", pubkey); } const int nid = EC_GROUP_get_curve_name(group); @@ -848,7 +848,7 @@ jsg::JsObject X509Certificate::toLegacyObject(jsg::Lock& js) { KJ_IF_SOME(serialNumber, getSerialNumber()) { obj.set(js, "serialNumber", js.str(serialNumber)); } - obj.set(js, "raw", jsg::JsValue(getRaw(js).getHandle(js))); + obj.set(js, "raw", getRaw(js)); return obj; } diff --git a/src/workerd/api/crypto/x509.h b/src/workerd/api/crypto/x509.h index 3156360f628..7dc1cc59ada 100644 --- a/src/workerd/api/crypto/x509.h +++ b/src/workerd/api/crypto/x509.h @@ -23,7 +23,7 @@ class X509Certificate: public jsg::Object { kj::Maybe getValidTo(); kj::Maybe> getKeyUsage(); kj::Maybe> getSerialNumber(); - jsg::BufferSource getRaw(jsg::Lock& js); + jsg::JsUint8Array getRaw(jsg::Lock& js); kj::Maybe> getPublicKey(jsg::Lock& js); kj::Maybe getPem(); kj::Maybe getFingerprint(); diff --git a/src/workerd/api/node/crypto-keys.c++ b/src/workerd/api/node/crypto-keys.c++ index a16d3a99c4c..3c743eecbe0 100644 --- a/src/workerd/api/node/crypto-keys.c++ +++ b/src/workerd/api/node/crypto-keys.c++ @@ -34,11 +34,11 @@ namespace { // Web Crypto requires a separate key for each algorithm. class SecretKey final: public CryptoKey::Impl { public: - explicit SecretKey(jsg::BufferSource keyData) + explicit SecretKey(kj::Array keyData) : Impl(true, CryptoKeyUsageSet::privateKeyMask() | CryptoKeyUsageSet::publicKeyMask()), keyData(kj::mv(keyData)) {} ~SecretKey() noexcept(false) { - keyData.setToZero(); + OPENSSL_cleanse(keyData.begin(), keyData.size()); } kj::StringPtr getAlgorithmName() const override { @@ -52,22 +52,23 @@ class SecretKey final: public CryptoKey::Impl { } bool equals(const CryptoKey::Impl& other) const override final { - return this == &other || (other.getType() == "secret"_kj && other.equals(keyData)); + if (this == &other) return true; + if (other.getType() != "secret"_kj) return false; + KJ_IF_SOME(o, kj::dynamicDowncastIfAvailable(other)) { + return equalsImpl(o.rawKeyData()); + } + return false; } bool equalsImpl(kj::ArrayPtr other) const { return keyData.size() == other.size() && - CRYPTO_memcmp(keyData.asArrayPtr().begin(), other.begin(), keyData.size()) == 0; + CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0; } bool equals(const kj::Array& other) const override final { return equalsImpl(other.asPtr()); } - bool equals(const jsg::BufferSource& other) const override final { - return equalsImpl(other.asArrayPtr()); - } - 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, "\"."); @@ -75,14 +76,12 @@ class SecretKey final: public CryptoKey::Impl { if (format == "jwk") { SubtleCrypto::JsonWebKey jwk; jwk.kty = kj::str("oct"); - jwk.k = fastEncodeBase64Url(keyData.asArrayPtr()); + jwk.k = fastEncodeBase64Url(keyData.asPtr()); jwk.ext = true; return jwk; } - auto backing = jsg::BackingStore::alloc(js, keyData.size()); - backing.asArrayPtr().copyFrom(keyData.asArrayPtr()); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsArrayBuffer::create(js, keyData.asPtr()).addRef(js); } kj::StringPtr jsgGetMemoryName() const override { @@ -92,19 +91,17 @@ class SecretKey final: public CryptoKey::Impl { return sizeof(SecretKey); } void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override { - tracker.trackField("keyData", keyData); + tracker.trackFieldWithSize("keyData", keyData.size()); } - void visitForGc(jsg::GcVisitor& visitor) override { - visitor.visit(keyData); - } + void visitForGc(jsg::GcVisitor& visitor) override {} const kj::ArrayPtr rawKeyData() const { - return keyData.asArrayPtr().asConst(); + return keyData.asPtr(); } private: - jsg::BufferSource keyData; + kj::Array keyData; }; CryptoKey::AsymmetricKeyDetails getRsaKeyDetails(jsg::Lock& js, const ncrypto::EVPKeyPointer& key) { @@ -117,10 +114,12 @@ CryptoKey::AsymmetricKeyDetails getRsaKeyDetails(jsg::Lock& js, const ncrypto::E // need to update this. KJ_ASSERT(!rsa.getPssParams().has_value()); + auto pubExp = JSG_REQUIRE_NONNULL( + bignumToArrayPadded(js, *rsa.getPublicKey().e), Error, "Failed to extract public exponent"); + auto ab = jsg::JsArrayBuffer::create(js, pubExp.asArrayPtr()); return CryptoKey::AsymmetricKeyDetails{ .modulusLength = key.bits(), - .publicExponent = JSG_REQUIRE_NONNULL( - bignumToArrayPadded(js, *rsa.getPublicKey().e), Error, "Failed to extract public exponent"), + .publicExponent = ab.addRef(js), }; } @@ -262,13 +261,13 @@ class AsymmetricKey final: public CryptoKey::Impl { return {}; } - jsg::BufferSource exportKeyExt(jsg::Lock& js, + jsg::JsUint8Array exportKeyExt(jsg::Lock& js, kj::StringPtr format, kj::StringPtr type, jsg::Optional cipher = kj::none, jsg::Optional> passphrase = kj::none) const override { if (!key) { - return JSG_REQUIRE_NONNULL(jsg::BufferSource::tryAlloc(js, 0), Error, "Failed to export key"); + return jsg::JsUint8Array::create(js, 0); } auto formatType = JSG_REQUIRE_NONNULL(trySelectKeyFormat(format), Error, "Invalid key format"); @@ -298,12 +297,9 @@ class AsymmetricKey final: public CryptoKey::Impl { BUF_MEM* mem = maybeBio.value; kj::ArrayPtr source(reinterpret_cast(mem->data), mem->length); if (source.size() > 0) { - auto backing = jsg::BackingStore::alloc(js, source.size()); - backing.asArrayPtr().copyFrom(source); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, source); } else { - return JSG_REQUIRE_NONNULL( - jsg::BufferSource::tryAlloc(js, 0), Error, "Failed to export key"); + return jsg::JsUint8Array::create(js, 0); } } @@ -317,7 +313,10 @@ class AsymmetricKey final: public CryptoKey::Impl { return kj::mv(res); } - return exportKeyExt(js, format, "pkcs8"_kj); + // exportKeyExt returns JsUint8Array. We need to wrap it in a JsRef + // since ExportKeyData is OneOf, JsonWebKey>. + return jsg::JsArrayBuffer::create(js, exportKeyExt(js, format, "pkcs8"_kj).asArrayPtr()) + .addRef(js); } bool equals(const CryptoKey::Impl& other) const override final { @@ -358,15 +357,29 @@ int getCurveFromName(kj::StringPtr name) { } } // namespace -kj::OneOf 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"); kj::StringPtr format = JSG_REQUIRE_NONNULL(opts.format, TypeError, "Missing format option"); + + auto convertExportKeyData = [&](SubtleCrypto::ExportKeyData&& exportData) + -> kj::OneOf { + KJ_SWITCH_ONEOF(exportData) { + KJ_CASE_ONEOF(buf, jsg::JsRef) { + return buf.getHandle(js); + } + KJ_CASE_ONEOF(jwk, SubtleCrypto::JsonWebKey) { + return kj::mv(jwk); + } + } + KJ_UNREACHABLE; + }; + if (format == "jwk"_kj) { // When format is jwk, all other options are ignored. - return key->impl->exportKey(js, format); + return convertExportKeyData(key->impl->exportKey(js, format)); } if (key->getType() == "secret"_kj) { @@ -374,7 +387,7 @@ kj::OneOf CryptoImpl::e // 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(js, "raw"_kj); + return convertExportKeyData(key->impl->exportKey(js, "raw"_kj)); } kj::StringPtr type = JSG_REQUIRE_NONNULL(opts.type, TypeError, "Missing type option"); @@ -384,7 +397,8 @@ kj::OneOf CryptoImpl::e // TODO(perf): As a later performance optimization, change this so that it doesn't copy. return kj::str(data.asArrayPtr().asChars()); } - return kj::mv(data); + // exportKeyExt returns JsUint8Array; copy to ArrayBuffer for the Node.js TS layer. + return jsg::JsArrayBuffer::create(js, data.asArrayPtr()); } bool CryptoImpl::equals(jsg::Lock& js, jsg::Ref key, jsg::Ref otherKey) { @@ -415,17 +429,17 @@ kj::StringPtr CryptoImpl::getAsymmetricKeyType(jsg::Lock& js, jsg::Refsecond : name; } -jsg::Ref CryptoImpl::createSecretKey(jsg::Lock& js, jsg::BufferSource keyData) { +jsg::Ref CryptoImpl::createSecretKey(jsg::Lock& js, jsg::JsBufferSource keyData) { // The keyData we receive here should be an exclusive copy of the key data. // It will have been copied on the JS side before being passed to this function. - // We do not detach the key data, however, because we want to ensure that it - // remains associated with the isolate for memory accounting purposes. - return js.alloc(kj::heap(kj::mv(keyData))); + // We copy the raw bytes into a kj::Array for persistent storage. + return js.alloc(kj::heap(keyData.copy())); } namespace { -std::optional tryParsingPrivate( - const CryptoImpl::CreateAsymmetricKeyOptions& options, const jsg::BufferSource& buffer) { +std::optional tryParsingPrivate(jsg::Lock& js, + const CryptoImpl::CreateAsymmetricKeyOptions& options, + kj::ArrayPtr buffer) { // As a private key the format can be either 'pem' or 'der', // while type can be one of `pkcs1`, `pkcs8`, or `sec1`. // The type is only required when format is 'der'. @@ -443,14 +457,14 @@ std::optional tryParsingPrivate( KJ_IF_SOME(passphrase, options.passphrase) { // TODO(later): Avoid using DataPointer for passphrase... so we // can avoid the copy... - auto dp = ncrypto::DataPointer::Alloc(passphrase.size()); + auto passphrasePtr = passphrase.getHandle(js).asArrayPtr(); + auto dp = ncrypto::DataPointer::Alloc(passphrasePtr.size()); kj::ArrayPtr ptr(dp.get(), dp.size()); - ptr.copyFrom(passphrase.asArrayPtr()); + ptr.copyFrom(passphrasePtr); config.passphrase = kj::mv(dp); } - auto result = - ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ToNcryptoBuffer(buffer.asArrayPtr())); + auto result = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ToNcryptoBuffer(buffer)); if (result.has_value) return kj::mv(result.value); return std::nullopt; @@ -466,11 +480,12 @@ jsg::Ref CryptoImpl::createPrivateKey( // that can be used for multiple kinds of operations. KJ_SWITCH_ONEOF(options.key) { - KJ_CASE_ONEOF(buffer, jsg::BufferSource) { + KJ_CASE_ONEOF(bs, jsg::JsRef) { JSG_REQUIRE(options.format == "pem"_kj || options.format == "der"_kj, TypeError, "Invalid format for private key creation"); - if (auto maybePrivate = tryParsingPrivate(options, buffer)) { + auto bufferPtr = bs.getHandle(js).asArrayPtr(); + if (auto maybePrivate = tryParsingPrivate(js, options, bufferPtr)) { return js.alloc(AsymmetricKey::NewPrivate(kj::mv(maybePrivate.value()))); } @@ -498,10 +513,12 @@ jsg::Ref CryptoImpl::createPublicKey(jsg::Lock& js, CreateAsymmetricK ncrypto::ClearErrorOnReturn clearErrorOnReturn; KJ_SWITCH_ONEOF(options.key) { - KJ_CASE_ONEOF(buffer, jsg::BufferSource) { + KJ_CASE_ONEOF(bs, jsg::JsRef) { JSG_REQUIRE(options.format == "pem"_kj || options.format == "der"_kj, TypeError, "Invalid format for public key creation"); + auto bufferPtr = bs.getHandle(js).asArrayPtr(); + // As a public key the format can be either 'pem' or 'der', // while type can be one of either `pkcs1` or `spki` @@ -520,8 +537,8 @@ jsg::Ref CryptoImpl::createPublicKey(jsg::Lock& js, CreateAsymmetricK ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config(true, format, enc); - auto result = ncrypto::EVPKeyPointer::TryParsePublicKey( - config, ToNcryptoBuffer(buffer.asArrayPtr().asConst())); + auto result = + ncrypto::EVPKeyPointer::TryParsePublicKey(config, ToNcryptoBuffer(bufferPtr.asConst())); if (result.has_value) { return js.alloc(AsymmetricKey::NewPublic(kj::mv(result.value))); @@ -529,7 +546,7 @@ jsg::Ref CryptoImpl::createPublicKey(jsg::Lock& js, CreateAsymmetricK } // Otherwise, let's try parsing as a private key... - if (auto maybePrivate = tryParsingPrivate(options, buffer)) { + if (auto maybePrivate = tryParsingPrivate(js, options, bufferPtr)) { return js.alloc(AsymmetricKey::NewPublic(kj::mv(maybePrivate.value()))); } @@ -770,8 +787,9 @@ CryptoKeyPair CryptoImpl::generateDhKeyPair(jsg::Lock& js, DhKeyPairOptions opti key_params = ncrypto::EVPKeyPointer::NewDH(kj::mv(dh)); } - KJ_CASE_ONEOF(prime, jsg::BufferSource) { - ncrypto::BignumPointer bn(prime.asArrayPtr().begin(), prime.size()); + KJ_CASE_ONEOF(prime, jsg::JsRef) { + auto primePtr = prime.getHandle(js).asArrayPtr(); + ncrypto::BignumPointer bn(primePtr.begin(), primePtr.size()); auto bn_g = ncrypto::BignumPointer::New(); JSG_REQUIRE(bn_g && bn_g.setWord(generator), Error, "Failed to set generator"); @@ -811,7 +829,7 @@ CryptoKeyPair CryptoImpl::generateDhKeyPair(jsg::Lock& js, DhKeyPairOptions opti }; } -jsg::BufferSource CryptoImpl::statelessDH( +jsg::JsUint8Array CryptoImpl::statelessDH( jsg::Lock& js, jsg::Ref privateKey, jsg::Ref publicKey) { auto privateKeyAlg = privateKey->getAlgorithmName(); auto publicKeyAlg = publicKey->getAlgorithmName(); @@ -824,10 +842,8 @@ jsg::BufferSource CryptoImpl::statelessDH( KJ_IF_SOME(pvtKey, kj::dynamicDowncastIfAvailable(*privateKey->impl)) { auto data = ncrypto::DHPointer::stateless(pubKey, pvtKey); JSG_REQUIRE(data, Error, "Failed to derive shared diffie-hellman secret"); - auto backing = jsg::BackingStore::alloc(js, data.size()); - kj::ArrayPtr ptr(static_cast(data.get()), data.size()); - backing.asArrayPtr().copyFrom(ptr); - return jsg::BufferSource(js, kj::mv(backing)); + kj::ArrayPtr ptr(static_cast(data.get()), data.size()); + return jsg::JsUint8Array::create(js, ptr); } } JSG_FAIL_REQUIRE(Error, "Unsupported keys for stateless diffie-hellman"); diff --git a/src/workerd/api/node/crypto.c++ b/src/workerd/api/node/crypto.c++ index 1a9ef8bd7cd..b1087492f2c 100644 --- a/src/workerd/api/node/crypto.c++ +++ b/src/workerd/api/node/crypto.c++ @@ -21,7 +21,7 @@ namespace workerd::api::node { // ====================================================================================== #pragma region KDF -jsg::BufferSource CryptoImpl::getHkdf(jsg::Lock& js, +jsg::JsArrayBuffer CryptoImpl::getHkdf(jsg::Lock& js, kj::String hash, kj::Array key, kj::Array salt, @@ -47,7 +47,7 @@ jsg::BufferSource CryptoImpl::getHkdf(jsg::Lock& js, return JSG_REQUIRE_NONNULL(api::hkdf(js, length, digest, key, salt, info), Error, "Hkdf failed"); } -jsg::BufferSource CryptoImpl::getPbkdf(jsg::Lock& js, +jsg::JsArrayBuffer CryptoImpl::getPbkdf(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t num_iterations, @@ -72,7 +72,7 @@ jsg::BufferSource CryptoImpl::getPbkdf(jsg::Lock& js, api::pbkdf2(js, keylen, num_iterations, digest, password, salt), Error, "Pbkdf2 failed"); } -jsg::BufferSource CryptoImpl::getScrypt(jsg::Lock& js, +jsg::JsArrayBuffer CryptoImpl::getScrypt(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t N, @@ -95,12 +95,12 @@ bool CryptoImpl::verifySpkac(kj::Array input) { return workerd::api::verifySpkac(input); } -kj::Maybe CryptoImpl::exportPublicKey( +kj::Maybe CryptoImpl::exportPublicKey( jsg::Lock& js, kj::Array input) { return workerd::api::exportPublicKey(js, input); } -kj::Maybe CryptoImpl::exportChallenge( +kj::Maybe CryptoImpl::exportChallenge( jsg::Lock& js, kj::Array input) { return workerd::api::exportChallenge(js, input); } @@ -109,7 +109,7 @@ kj::Maybe CryptoImpl::exportChallenge( // ====================================================================================== #pragma region Primes -jsg::BufferSource CryptoImpl::randomPrime(jsg::Lock& js, +jsg::JsArrayBuffer CryptoImpl::randomPrime(jsg::Lock& js, uint32_t size, bool safe, jsg::Optional> add_buf, @@ -144,11 +144,11 @@ int CryptoImpl::HmacHandle::update(kj::Array data) { return 1; // This just always returns 1 no matter what. } -jsg::BufferSource CryptoImpl::HmacHandle::digest(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::HmacHandle::digest(jsg::Lock& js) { return ctx.digest(js); } -jsg::BufferSource CryptoImpl::HmacHandle::oneshot(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::HmacHandle::oneshot(jsg::Lock& js, kj::String algorithm, CryptoImpl::HmacHandle::KeyParam key, kj::Array data) { @@ -184,7 +184,7 @@ int CryptoImpl::HashHandle::update(kj::Array data) { return 1; } -jsg::BufferSource CryptoImpl::HashHandle::digest(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::HashHandle::digest(jsg::Lock& js) { return ctx.digest(js); } @@ -197,7 +197,7 @@ void CryptoImpl::HashHandle::visitForMemoryInfo(jsg::MemoryTracker& tracker) con tracker.trackFieldWithSize("digest", ctx.size()); } -jsg::BufferSource CryptoImpl::HashHandle::oneshot( +jsg::JsUint8Array CryptoImpl::HashHandle::oneshot( jsg::Lock& js, kj::String algorithm, kj::Array data, kj::Maybe xofLen) { HashContext ctx(algorithm, xofLen); ctx.update(data); @@ -232,28 +232,28 @@ void CryptoImpl::DiffieHellmanHandle::setPublicKey(kj::Array key) { dh.setPublicKey(key); } -jsg::BufferSource CryptoImpl::DiffieHellmanHandle::getPublicKey(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPublicKey(jsg::Lock& js) { return dh.getPublicKey(js); } -jsg::BufferSource CryptoImpl::DiffieHellmanHandle::getPrivateKey(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPrivateKey(jsg::Lock& js) { return dh.getPrivateKey(js); } -jsg::BufferSource CryptoImpl::DiffieHellmanHandle::getGenerator(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getGenerator(jsg::Lock& js) { return dh.getGenerator(js); } -jsg::BufferSource CryptoImpl::DiffieHellmanHandle::getPrime(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::getPrime(jsg::Lock& js) { return dh.getPrime(js); } -jsg::BufferSource CryptoImpl::DiffieHellmanHandle::computeSecret( +jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::computeSecret( jsg::Lock& js, kj::Array key) { return dh.computeSecret(js, key); } -jsg::BufferSource CryptoImpl::DiffieHellmanHandle::generateKeys(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::DiffieHellmanHandle::generateKeys(jsg::Lock& js) { return dh.generateKeys(js); } @@ -266,7 +266,7 @@ int CryptoImpl::DiffieHellmanHandle::getVerifyError() { #pragma region SignVerify namespace { -jsg::BackingStore signFinal(jsg::Lock& js, +jsg::JsUint8Array signFinal(jsg::Lock& js, ncrypto::EVPMDCtxPointer&& mdctx, const ncrypto::EVPKeyPointer& pkey, int padding, @@ -279,7 +279,7 @@ jsg::BackingStore signFinal(jsg::Lock& js, auto data = mdctx.digestFinal(mdctx.getExpectedSize()); JSG_REQUIRE(data, Error, "Failed to generate digest"); - auto sig = jsg::BackingStore::alloc(js, pkey.size()); + auto sig = jsg::JsUint8Array::create(js, pkey.size()); ncrypto::Buffer sig_buf{ .data = sig.asArrayPtr().begin(), .len = sig.size(), @@ -301,16 +301,16 @@ jsg::BackingStore signFinal(jsg::Lock& js, JSG_REQUIRE(pkctx.signInto(data, &sig_buf), Error, "Failed to generate signature"); if (sig_buf.len < sig.size()) { - sig.limit(sig_buf.len); + return sig.slice(js, sig_buf.len); } - return kj::mv(sig); + return sig; } bool verifyFinal(jsg::Lock& js, ncrypto::EVPMDCtxPointer&& mdctx, const ncrypto::EVPKeyPointer& pkey, - const jsg::BufferSource& signature, + jsg::JsBufferSource& signature, int padding, jsg::Optional pss_salt_len) { @@ -348,13 +348,13 @@ bool verifyFinal(jsg::Lock& js, return pkctx.verify(sig, data); } -jsg::BackingStore convertSignatureToP1363( - jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::BackingStore&& signature) { +jsg::JsUint8Array convertSignatureToP1363( + jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::JsUint8Array&& signature) { auto maybeRs = pkey.getBytesOfRS(); if (!maybeRs.has_value()) return kj::mv(signature); unsigned int n = maybeRs.value(); - auto ret = jsg::BackingStore::alloc(js, 2 * n); + auto ret = jsg::JsUint8Array::create(js, 2 * n); ncrypto::Buffer sig_buffer{ .data = signature.asArrayPtr().begin(), @@ -365,20 +365,20 @@ jsg::BackingStore convertSignatureToP1363( return kj::mv(signature); } - return kj::mv(ret); + return ret; } -jsg::BackingStore convertSignatureToDER( - jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::BackingStore&& backing) { +jsg::JsUint8Array convertSignatureToDER( + jsg::Lock& js, const ncrypto::EVPKeyPointer& pkey, jsg::JsUint8Array&& signature) { auto maybeRs = pkey.getBytesOfRS(); - if (!maybeRs.has_value()) return kj::mv(backing); + if (!maybeRs.has_value()) return kj::mv(signature); unsigned int n = maybeRs.value(); - if (backing.size() != 2 * n) { - return jsg::BackingStore::alloc(js, 0); + if (signature.size() != 2 * n) { + return jsg::JsUint8Array::create(js, 0); } - const kj::byte* sig_data = backing.asArrayPtr().begin(); + const kj::byte* sig_data = signature.asArrayPtr().begin(); auto asn1_sig = ncrypto::ECDSASigPointer::New(); JSG_REQUIRE(asn1_sig, Error, "Internal error generating signature"); @@ -391,12 +391,10 @@ jsg::BackingStore convertSignatureToDER( auto buf = asn1_sig.encode(); if (buf.len <= 0) [[unlikely]] { - return jsg::BackingStore::alloc(js, 0); + return jsg::JsUint8Array::create(js, 0); } - auto ret = jsg::BackingStore::alloc(js, buf.len); - ret.asArrayPtr().copyFrom(kj::ArrayPtr(buf.data, buf.len)); - return kj::mv(ret); + return jsg::JsUint8Array::create(js, kj::ArrayPtr(buf.data, buf.len)); } const EVP_MD* maybeGetDigest(jsg::Optional& maybeAlgorithm) { @@ -424,7 +422,7 @@ jsg::Ref CryptoImpl::SignHandle::constructor( return js.alloc(kj::mv(mdctx)); } -void CryptoImpl::SignHandle::update(jsg::Lock& js, jsg::BufferSource data) { +void CryptoImpl::SignHandle::update(jsg::Lock& js, jsg::JsBufferSource data) { ncrypto::ClearErrorOnReturn clear_error_on_return; JSG_REQUIRE(ctx, Error, "Signing context has already been finalized"); auto ptr = data.asArrayPtr(); @@ -435,7 +433,7 @@ void CryptoImpl::SignHandle::update(jsg::Lock& js, jsg::BufferSource data) { JSG_REQUIRE(ctx.digestUpdate(buf), Error, "Failed to update signing context"); } -jsg::BufferSource CryptoImpl::SignHandle::sign(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::SignHandle::sign(jsg::Lock& js, jsg::Ref key, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, @@ -448,18 +446,18 @@ jsg::BufferSource CryptoImpl::SignHandle::sign(jsg::Lock& js, // There's a bug in ncrypto that doesn't clear the EVPMDCtxPointer when // moved with kj::mv so instead we release and wrap again. - auto backing = signFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey, + auto sig = signFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey, rsaPadding.orDefault(pkey.getDefaultSignPadding()), pssSaltLength); KJ_IF_SOME(enc, dsaSigEnc) { static constexpr unsigned kP1363 = 1; JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding"); if (enc == kP1363) { - backing = convertSignatureToP1363(js, pkey, kj::mv(backing)); + sig = convertSignatureToP1363(js, pkey, kj::mv(sig)); } } - return jsg::BufferSource(js, kj::mv(backing)); + return sig; } CryptoImpl::VerifyHandle::VerifyHandle(ncrypto::EVPMDCtxPointer ctx) @@ -478,7 +476,7 @@ jsg::Ref CryptoImpl::VerifyHandle::constructor( return js.alloc(kj::mv(mdctx)); } -void CryptoImpl::VerifyHandle::update(jsg::Lock& js, jsg::BufferSource data) { +void CryptoImpl::VerifyHandle::update(jsg::Lock& js, jsg::JsBufferSource data) { ncrypto::ClearErrorOnReturn clear_error_on_return; JSG_REQUIRE(ctx, Error, "Verification context has already been finalized"); auto ptr = data.asArrayPtr(); @@ -491,7 +489,7 @@ void CryptoImpl::VerifyHandle::update(jsg::Lock& js, jsg::BufferSource data) { bool CryptoImpl::VerifyHandle::verify(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource signature, + jsg::JsBufferSource signature, jsg::Optional rsaPadding, jsg::Optional maybeSaltLen, jsg::Optional dsaSigEnc) { @@ -503,24 +501,26 @@ bool CryptoImpl::VerifyHandle::verify(jsg::Lock& js, JSG_REQUIRE(!pkey.isOneShotVariant(), Error, "Unsupported operation for this key"); - auto clonedSignature = signature.clone(js); + auto sigCopy = jsg::JsUint8Array::create(js, signature.asArrayPtr()); + KJ_IF_SOME(enc, dsaSigEnc) { static constexpr unsigned kP1363 = 1; JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding"); if (enc == kP1363) { - clonedSignature = - jsg::BufferSource(js, convertSignatureToDER(js, pkey, clonedSignature.detach(js))); + sigCopy = convertSignatureToDER(js, pkey, kj::mv(sigCopy)); } } - return verifyFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey, clonedSignature, + auto sigSource = jsg::JsBufferSource(sigCopy); + + return verifyFinal(js, ncrypto::EVPMDCtxPointer(ctx.release()), pkey, sigSource, rsaPadding.orDefault(pkey.getDefaultSignPadding()), maybeSaltLen); } -jsg::BufferSource CryptoImpl::signOneShot(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::signOneShot(jsg::Lock& js, jsg::Ref key, jsg::Optional algorithm, - jsg::BufferSource data, + jsg::JsBufferSource data, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, jsg::Optional dsaSigEnc) { @@ -547,26 +547,25 @@ jsg::BufferSource CryptoImpl::signOneShot(jsg::Lock& js, }; ncrypto::DataPointer sig = mdctx.signOneShot(buf); - kj::ArrayPtr sigPtr(sig.get(), sig.size()); - auto backing = jsg::BackingStore::alloc(js, sig.size()); - backing.asArrayPtr().copyFrom(sigPtr); + auto sigBuf = + jsg::JsUint8Array::create(js, kj::ArrayPtr(sig.get(), sig.size())); KJ_IF_SOME(enc, dsaSigEnc) { static constexpr unsigned kP1363 = 1; JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding"); if (enc == kP1363) { - backing = convertSignatureToP1363(js, pkey, kj::mv(backing)); + sigBuf = convertSignatureToP1363(js, pkey, kj::mv(sigBuf)); } } - return jsg::BufferSource(js, kj::mv(backing)); + return sigBuf; } bool CryptoImpl::verifyOneShot(jsg::Lock& js, jsg::Ref key, jsg::Optional algorithm, - jsg::BufferSource data, - jsg::BufferSource signature, + jsg::JsBufferSource data, + jsg::JsBufferSource signature, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, jsg::Optional dsaSigEnc) { @@ -590,13 +589,13 @@ bool CryptoImpl::verifyOneShot(jsg::Lock& js, JSG_REQUIRE( mdctx.verifyInit(pkey, md).has_value(), Error, "Failed to initialize verification context"); - auto clonedSignature = signature.clone(js); + auto sigCopy = jsg::JsUint8Array::create(js, signature.asArrayPtr()); + KJ_IF_SOME(enc, dsaSigEnc) { static constexpr unsigned kP1363 = 1; JSG_REQUIRE(enc <= kP1363 && enc >= 0, Error, "Invalid DSA signature encoding"); if (enc == kP1363) { - clonedSignature = - jsg::BufferSource(js, convertSignatureToDER(js, pkey, clonedSignature.detach(js))); + sigCopy = convertSignatureToDER(js, pkey, kj::mv(sigCopy)); } } @@ -606,8 +605,8 @@ bool CryptoImpl::verifyOneShot(jsg::Lock& js, }; ncrypto::Buffer sig{ - .data = clonedSignature.asArrayPtr().begin(), - .len = clonedSignature.size(), + .data = sigCopy.asArrayPtr().begin(), + .len = sigCopy.size(), }; return mdctx.verify(buf, sig); @@ -686,15 +685,16 @@ bool passAuthTagToOpenSSL(ncrypto::CipherCtxPointer& ctx, kj::ArrayPtr key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, kj::Maybe maybeAuthInfo) : mode(mode), ctx(kj::mv(ctx)), key(kj::mv(key)), - iv(kj::mv(iv)), + iv(iv.addRef(js)), maybeAuthInfo(kj::mv(maybeAuthInfo)) {} jsg::Ref CryptoImpl::CipherHandle::construct(jsg::Lock& js, @@ -702,7 +702,7 @@ jsg::Ref CryptoImpl::CipherHandle::construct(jsg::Lock kj::StringPtr algorithm, ncrypto::Cipher cipher, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, jsg::Optional maybeAuthTagLength) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; @@ -746,10 +746,11 @@ jsg::Ref CryptoImpl::CipherHandle::construct(jsg::Lock JSG_REQUIRE(ctx.init(ncrypto::Cipher(), encrypt, keyData.begin(), iv.asArrayPtr().begin()), Error, "Failed to initialize cipher/cipher context"); - return js.alloc(mode, kj::mv(ctx), kj::mv(key), kj::mv(iv), kj::mv(maybeAuthInfo)); + return js.alloc( + js, mode, kj::mv(ctx), kj::mv(key), kj::mv(iv), kj::mv(maybeAuthInfo)); } -jsg::BufferSource CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::BufferSource data) { +jsg::JsUint8Array CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::JsBufferSource data) { JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); JSG_REQUIRE(data.size() <= INT_MAX, Error, "Data too large"); @@ -765,7 +766,8 @@ jsg::BufferSource CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::BufferSou if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) { authTagPassed = true; - auto& tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto tag = tagRef.getHandle(js); JSG_REQUIRE( passAuthTagToOpenSSL(ctx, tag.asArrayPtr().asConst()), Error, "Failed to set auth tag"); } @@ -784,18 +786,18 @@ jsg::BufferSource CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::BufferSou JSG_FAIL_REQUIRE(Error, "Failed to process data"); } - auto backing = jsg::BackingStore::alloc(js, buf_len); + auto buf = jsg::JsUint8Array::create(js, buf_len); buffer.data = data.asArrayPtr().begin(); buffer.len = data.size(); - bool r = ctx.update(buffer, backing.asArrayPtr().begin(), &buf_len); + bool r = ctx.update(buffer, buf.asArrayPtr().begin(), &buf_len); - if (buf_len != backing.size()) { - JSG_REQUIRE(buf_len < backing.size(), Error, "Invalid buffer length"); - auto newBacking = jsg::BackingStore::alloc(js, buf_len); + if (buf_len != buf.size()) { + JSG_REQUIRE(buf_len < buf.size(), Error, "Invalid buffer length"); + auto newBuf = jsg::JsUint8Array::create(js, buf_len); if (buf_len > 0) { - newBacking.asArrayPtr().copyFrom(backing.asArrayPtr().slice(0, buf_len)); + newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(buf_len)); } - backing = kj::mv(newBacking); + buf = kj::mv(newBuf); } // When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is @@ -804,21 +806,22 @@ jsg::BufferSource CryptoImpl::CipherHandle::update(jsg::Lock& js, jsg::BufferSou pendingAuthFailed = true; } - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } -jsg::BufferSource CryptoImpl::CipherHandle::final(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::CipherHandle::final(jsg::Lock& js) { JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); ncrypto::ClearErrorOnReturn clearErrorOnReturn; int ctxMode = ctx.getMode(); - auto backing = jsg::BackingStore::alloc(js, ctx.getBlockSize()); + auto buf = jsg::JsUint8Array::create(js, ctx.getBlockSize()); if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) { authTagPassed = true; - auto& tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto tag = tagRef.getHandle(js); JSG_REQUIRE( passAuthTagToOpenSSL(ctx, tag.asArrayPtr().asConst()), Error, "Failed to set auth tag"); } @@ -832,18 +835,18 @@ jsg::BufferSource CryptoImpl::CipherHandle::final(jsg::Lock& js) { bool ok; if (mode == CipherMode::DECIPHER && ctxMode == EVP_CIPH_CCM_MODE) { ok = !pendingAuthFailed; - backing = jsg::BackingStore::alloc(js, 0); + buf = jsg::JsUint8Array::create(js, 0); } else { - int out_len = backing.size(); - ok = ctx.update({}, backing.asArrayPtr().begin(), &out_len, true); + int out_len = buf.size(); + ok = ctx.update({}, buf.asArrayPtr().begin(), &out_len, true); - if (out_len != backing.size()) { - JSG_REQUIRE(out_len < backing.size(), Error, "Invalid buffer length"); - auto newBacking = jsg::BackingStore::alloc(js, out_len); + if (out_len != buf.size()) { + JSG_REQUIRE(out_len < buf.size(), Error, "Invalid buffer length"); + auto newBuf = jsg::JsUint8Array::create(js, out_len); if (out_len > 0) { - newBacking.asArrayPtr().copyFrom(backing.asArrayPtr().slice(0, out_len)); + newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(out_len)); } - backing = kj::mv(newBacking); + buf = kj::mv(newBuf); } if (ok && mode == CipherMode::CIPHER && isAuthenticatedMode(ctx)) { @@ -854,20 +857,20 @@ jsg::BufferSource CryptoImpl::CipherHandle::final(jsg::Lock& js) { if (info.auth_tag_len == kNoAuthTagLength) { info.auth_tag_len = 16; } - auto authTagBacking = jsg::BackingStore::alloc(js, info.auth_tag_len); - ok = ctx.getAeadTag(info.auth_tag_len, authTagBacking.asArrayPtr().begin()); - maybeAuthTag = jsg::BufferSource(js, kj::mv(authTagBacking)); + auto tag = jsg::JsUint8Array::create(js, info.auth_tag_len); + ok = ctx.getAeadTag(info.auth_tag_len, tag.asArrayPtr().begin()); + maybeAuthTag = jsg::JsBufferSource(tag).addRef(js); } } JSG_REQUIRE(ok, Error, "Authentication failed"); ctx.reset(); - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } void CryptoImpl::CipherHandle::setAAD( - jsg::Lock& js, jsg::BufferSource aad, jsg::Optional maybePlaintextLength) { + jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional maybePlaintextLength) { JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); JSG_REQUIRE(isAuthenticatedMode(ctx), Error, "Cipher does not support authenticated mode"); @@ -889,7 +892,8 @@ void CryptoImpl::CipherHandle::setAAD( if (mode == CipherMode::DECIPHER && isAuthenticatedMode(ctx) && !authTagPassed) { authTagPassed = true; - auto& tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto& tagRef = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto tag = tagRef.getHandle(js); JSG_REQUIRE( passAuthTagToOpenSSL(ctx, tag.asArrayPtr().asConst()), Error, "Failed to set auth tag"); } @@ -915,7 +919,7 @@ void CryptoImpl::CipherHandle::setAutoPadding(jsg::Lock& js, bool autoPadding) { JSG_REQUIRE(ctx.setPadding(autoPadding), Error, "Failed to set autopadding"); } -void CryptoImpl::CipherHandle::setAuthTag(jsg::Lock& js, jsg::BufferSource authTag) { +void CryptoImpl::CipherHandle::setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); JSG_REQUIRE(isAuthenticatedMode(ctx), Error, "Cipher does not support authenticated mode"); @@ -942,20 +946,21 @@ void CryptoImpl::CipherHandle::setAuthTag(jsg::Lock& js, jsg::BufferSource authT info.auth_tag_len = authTag.size(); // We defensively copy the auth tag here to prevent modification. - maybeAuthTag = authTag.copy(js); + auto tagCopy = jsg::JsUint8Array::create(js, authTag.asArrayPtr()); + maybeAuthTag = jsg::JsBufferSource(static_cast>(tagCopy)).addRef(js); } -jsg::BufferSource CryptoImpl::CipherHandle::getAuthTag(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::CipherHandle::getAuthTag(jsg::Lock& js) { JSG_REQUIRE(!ctx, Error, "Auth tag is only available once cipher context has been finalized"); JSG_REQUIRE(mode == CipherMode::CIPHER, Error, "Getting the auth tag is only support for cipher"); - KJ_IF_SOME(tag, maybeAuthTag) { + KJ_IF_SOME(ref, maybeAuthTag) { KJ_DEFER(maybeAuthTag = kj::none); - return kj::mv(tag); + auto tag = ref.getHandle(js); + return jsg::JsUint8Array::create(js, tag.asArrayPtr()); } - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } namespace { @@ -1007,17 +1012,18 @@ CryptoImpl::AeadHandle::AuthenticatedInfo initAuthenticated(ncrypto::Aead& aead, } } // namespace -CryptoImpl::AeadHandle::AeadHandle(CipherMode mode, +CryptoImpl::AeadHandle::AeadHandle(jsg::Lock& js, + CipherMode mode, ncrypto::Aead aead, ncrypto::AeadCtxPointer ctx, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, kj::Maybe maybeAuthInfo) : mode(mode), aead(aead), ctx(kj::mv(ctx)), key(kj::mv(key)), - iv(kj::mv(iv)), + iv(iv.addRef(js)), maybeAuthInfo(kj::mv(maybeAuthInfo)) {} jsg::Ref CryptoImpl::AeadHandle::construct(jsg::Lock& js, @@ -1025,7 +1031,7 @@ jsg::Ref CryptoImpl::AeadHandle::construct(jsg::Lock& js kj::StringPtr algorithm, ncrypto::Aead aead, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, jsg::Optional maybeAuthTagLength) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; @@ -1056,10 +1062,10 @@ jsg::Ref CryptoImpl::AeadHandle::construct(jsg::Lock& js aead, ctx, encrypt, algorithm, iv.size(), maybeAuthTagLength.orDefault(kNoAuthTagLength)); return js.alloc( - mode, aead, kj::mv(ctx), kj::mv(key), kj::mv(iv), kj::mv(maybeAuthInfo)); + js, mode, aead, kj::mv(ctx), kj::mv(key), kj::mv(iv), kj::mv(maybeAuthInfo)); } -jsg::BufferSource CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::BufferSource data) { +jsg::JsUint8Array CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::JsBufferSource data) { JSG_REQUIRE(!updated, Error, "update() can only be invoked once on an AEAD"); JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); JSG_REQUIRE(data.size() <= INT_MAX, Error, "Data too large"); @@ -1082,14 +1088,16 @@ jsg::BufferSource CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::BufferSourc .len = data.size(), }; - auto backing = jsg::BackingStore::alloc(js, data.size()); + auto buf = jsg::JsUint8Array::create(js, data.size()); - ncrypto::Buffer outBuf = { - .data = backing.asArrayPtr().begin(), .len = data.size()}; - ncrypto::Buffer ivBuf = {.data = iv.asArrayPtr().begin(), .len = iv.size()}; + ncrypto::Buffer outBuf = {.data = buf.asArrayPtr().begin(), .len = data.size()}; + auto ivHandle = iv.getHandle(js); + ncrypto::Buffer ivBuf = { + .data = ivHandle.asArrayPtr().begin(), .len = ivHandle.size()}; ncrypto::Buffer aadBuf; - KJ_IF_SOME(aad, maybeAad) { + KJ_IF_SOME(aadRef, maybeAad) { + auto aad = aadRef.getHandle(js); aadBuf = {.data = aad.asArrayPtr().begin(), .len = aad.size()}; } @@ -1104,15 +1112,15 @@ jsg::BufferSource CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::BufferSourc info.auth_tag_len = 16; } - auto authTagBacking = jsg::BackingStore::alloc(js, info.auth_tag_len); + auto tag = jsg::JsUint8Array::create(js, info.auth_tag_len); ncrypto::Buffer tagBuf = { - .data = authTagBacking.asArrayPtr().begin(), .len = info.auth_tag_len}; + .data = tag.asArrayPtr().begin(), .len = info.auth_tag_len}; r = ctx.encrypt(buffer, outBuf, tagBuf, ivBuf, aadBuf); - maybeAuthTag = jsg::BufferSource(js, kj::mv(authTagBacking)); + maybeAuthTag = jsg::JsBufferSource(tag).addRef(js); } else { - auto& tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided"); + auto tag = JSG_REQUIRE_NONNULL(maybeAuthTag, Error, "No auth tag provided").getHandle(js); ncrypto::Buffer tagBuf = { .data = tag.asArrayPtr().begin(), .len = tag.size()}; @@ -1123,24 +1131,23 @@ jsg::BufferSource CryptoImpl::AeadHandle::update(jsg::Lock& js, jsg::BufferSourc JSG_REQUIRE(r, Error, "Authentication failed"); // EVP_AEAD operations always return an output of the same size as the input - KJ_REQUIRE(outBuf.len == backing.size(), "Invalid output length for AEAD operation"); + KJ_REQUIRE(outBuf.len == buf.size(), "Invalid output length for AEAD operation"); updated = true; - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } -jsg::BufferSource CryptoImpl::AeadHandle::final(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::AeadHandle::final(jsg::Lock& js) { // There is no finalization operation in the EVP_AEAD API. // Just return an empty value and clean up. JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto backing = jsg::BackingStore::alloc(js, 0); ctx.reset(); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } void CryptoImpl::AeadHandle::setAAD( - jsg::Lock& js, jsg::BufferSource aad, jsg::Optional maybePlaintextLength) { + jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional maybePlaintextLength) { // In EVP_AEAD, the AAD is handled at the same time as the update. // Just save the value until update() is called. @@ -1165,7 +1172,9 @@ void CryptoImpl::AeadHandle::setAAD( } } - maybeAad = aad.copy(js); + // Defensively copy the AAD data. + auto aadCopy = jsg::JsUint8Array::create(js, aad.asArrayPtr()); + maybeAad = jsg::JsBufferSource(static_cast>(aadCopy)).addRef(js); } void CryptoImpl::AeadHandle::setAutoPadding(jsg::Lock&, bool) { @@ -1173,7 +1182,7 @@ void CryptoImpl::AeadHandle::setAutoPadding(jsg::Lock&, bool) { JSG_FAIL_REQUIRE(Error, "Setting autopadding is not supported on AEADs"); } -void CryptoImpl::AeadHandle::setAuthTag(jsg::Lock& js, jsg::BufferSource authTag) { +void CryptoImpl::AeadHandle::setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; JSG_REQUIRE(ctx, Error, "Cipher/decipher context has already been finalized"); JSG_REQUIRE( @@ -1199,20 +1208,21 @@ void CryptoImpl::AeadHandle::setAuthTag(jsg::Lock& js, jsg::BufferSource authTag info.auth_tag_len = authTag.size(); // We defensively copy the auth tag here to prevent modification. - maybeAuthTag = authTag.copy(js); + auto tagCopy = jsg::JsUint8Array::create(js, authTag.asArrayPtr()); + maybeAuthTag = jsg::JsBufferSource(static_cast>(tagCopy)).addRef(js); } -jsg::BufferSource CryptoImpl::AeadHandle::getAuthTag(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::AeadHandle::getAuthTag(jsg::Lock& js) { JSG_REQUIRE(!ctx, Error, "Auth tag is only available once cipher context has been finalized"); JSG_REQUIRE(mode == CipherMode::CIPHER, Error, "Getting the auth tag is only support for cipher"); - KJ_IF_SOME(tag, maybeAuthTag) { + KJ_IF_SOME(ref, maybeAuthTag) { KJ_DEFER(maybeAuthTag = kj::none); - return kj::mv(tag); + auto tag = ref.getHandle(js); + return jsg::JsUint8Array::create(js, tag.asArrayPtr()); } - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } kj::OneOf, jsg::Ref> CryptoImpl:: @@ -1220,7 +1230,7 @@ kj::OneOf, jsg::Ref> kj::uint mode, kj::String algorithm, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, jsg::Optional maybeAuthTagLength) { CipherMode cipherMode = static_cast(mode); @@ -1245,9 +1255,9 @@ using EVP_PKEY_cipher_t = int( EVP_PKEY_CTX* ctx, unsigned char* out, size_t* outlen, const unsigned char* in, size_t inlen); template -jsg::BufferSource Cipher(jsg::Lock& js, +jsg::JsUint8Array Cipher(jsg::Lock& js, ncrypto::EVPKeyCtxPointer&& ctx, - const jsg::BufferSource& buffer, + jsg::JsBufferSource& buffer, const CryptoImpl::PublicPrivateCipherOptions& options) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; @@ -1268,7 +1278,8 @@ jsg::BufferSource Cipher(jsg::Lock& js, "Failed to set the mgf1 digest"); } - KJ_IF_SOME(label, options.oaepLabel) { + KJ_IF_SOME(labelRef, options.oaepLabel) { + auto label = labelRef.getHandle(js); // The ctx takes ownership of the data buffer so we have to copy. auto data = ncrypto::DataPointer::Alloc(label.size()); kj::ArrayPtr dataPtr(data.get(), data.size()); @@ -1284,28 +1295,27 @@ jsg::BufferSource Cipher(jsg::Lock& js, Error, "Failed to determine output size"); if (len == 0) { - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } - auto backing = jsg::BackingStore::alloc(js, len); - JSG_REQUIRE(cipher(ctx.get(), backing.asArrayPtr().begin(), &len, buffer.asArrayPtr().begin(), + auto buf = jsg::JsUint8Array::create(js, len); + JSG_REQUIRE(cipher(ctx.get(), buf.asArrayPtr().begin(), &len, buffer.asArrayPtr().begin(), buffer.size()) == 1, Error, "Failed to cipher/decipher"); - if (len < backing.size()) { - auto newBacking = jsg::BackingStore::alloc(js, len); - newBacking.asArrayPtr().copyFrom(backing.asArrayPtr().slice(0, len)); - backing = kj::mv(newBacking); + if (len < buf.size()) { + auto newBuf = jsg::JsUint8Array::create(js, len); + newBuf.asArrayPtr().copyFrom(buf.asArrayPtr().first(len)); + buf = kj::mv(newBuf); } - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } } // namespace -jsg::BufferSource CryptoImpl::publicEncrypt(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::publicEncrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, CryptoImpl::PublicPrivateCipherOptions options) { auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided"); JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys"); @@ -1314,9 +1324,9 @@ jsg::BufferSource CryptoImpl::publicEncrypt(jsg::Lock& js, return Cipher(js, kj::mv(ctx), buffer, options); } -jsg::BufferSource CryptoImpl::privateDecrypt(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::privateDecrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, CryptoImpl::PublicPrivateCipherOptions options) { auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided"); JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys"); @@ -1325,9 +1335,9 @@ jsg::BufferSource CryptoImpl::privateDecrypt(jsg::Lock& js, return Cipher(js, kj::mv(ctx), buffer, options); } -jsg::BufferSource CryptoImpl::publicDecrypt(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::publicDecrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, CryptoImpl::PublicPrivateCipherOptions options) { auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided"); JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys"); @@ -1339,9 +1349,9 @@ jsg::BufferSource CryptoImpl::publicDecrypt(jsg::Lock& js, .oaepHash = kj::String(), }); } -jsg::BufferSource CryptoImpl::privateEncrypt(jsg::Lock& js, +jsg::JsUint8Array CryptoImpl::privateEncrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, CryptoImpl::PublicPrivateCipherOptions options) { auto& pkey = JSG_REQUIRE_NONNULL(tryGetKey(key), Error, "No key provided"); JSG_REQUIRE(pkey.isRsaVariant(), Error, "publicEncrypt() currently only supports RSA keys"); @@ -1472,7 +1482,7 @@ kj::ArrayPtr CryptoImpl::getCiphers() { #pragma region ECDH namespace { -ncrypto::ECPointPointer bufferToPoint(const EC_GROUP* group, jsg::BufferSource& buf) { +ncrypto::ECPointPointer bufferToPoint(const EC_GROUP* group, jsg::JsBufferSource& buf) { JSG_REQUIRE(buf.size() <= INT32_MAX, Error, "buffer is too big"); auto pub = ncrypto::ECPointPointer::New(group); @@ -1494,18 +1504,17 @@ point_conversion_form_t getFormat(kj::StringPtr format) { JSG_FAIL_REQUIRE(Error, "Invalid ECDH public key format"); } -jsg::BufferSource ecPointToBuffer( +jsg::JsUint8Array ecPointToBuffer( jsg::Lock& js, const EC_GROUP* group, const EC_POINT* point, point_conversion_form_t form) { size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); JSG_REQUIRE(len != 0, Error, "Failed to get public key length"); - auto backing = jsg::BackingStore::alloc(js, len); + auto buf = jsg::JsUint8Array::create(js, len); - len = - EC_POINT_point2oct(group, point, form, backing.asArrayPtr().begin(), backing.size(), nullptr); + len = EC_POINT_point2oct(group, point, form, buf.asArrayPtr().begin(), buf.size(), nullptr); JSG_REQUIRE(len != 0, Error, "Failed to get public key"); - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } bool isKeyValidForCurve(const EC_GROUP* group, const ncrypto::BignumPointer& private_key) { @@ -1536,8 +1545,8 @@ jsg::Ref CryptoImpl::ECDHHandle::constructor( return js.alloc(kj::mv(key)); } -jsg::BufferSource CryptoImpl::ECDHHandle::computeSecret( - jsg::Lock& js, jsg::BufferSource otherPublicKey) { +jsg::JsUint8Array CryptoImpl::ECDHHandle::computeSecret( + jsg::Lock& js, jsg::JsBufferSource otherPublicKey) { ncrypto::ClearErrorOnReturn clear_error_on_return; @@ -1549,12 +1558,12 @@ jsg::BufferSource CryptoImpl::ECDHHandle::computeSecret( int field_size = EC_GROUP_get_degree(group_); size_t out_len = (field_size + 7) / 8; - auto backing = jsg::BackingStore::alloc(js, out_len); + auto buf = jsg::JsUint8Array::create(js, out_len); - JSG_REQUIRE(ECDH_compute_key(backing.asArrayPtr().begin(), out_len, pub, key_.get(), nullptr), - Error, "Failed to compute ECDH key"); + JSG_REQUIRE(ECDH_compute_key(buf.asArrayPtr().begin(), out_len, pub, key_.get(), nullptr), Error, + "Failed to compute ECDH key"); - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } void CryptoImpl::ECDHHandle::generateKeys() { @@ -1562,18 +1571,17 @@ void CryptoImpl::ECDHHandle::generateKeys() { JSG_REQUIRE(key_.generate(), Error, "Failed to generate keys"); } -jsg::BufferSource CryptoImpl::ECDHHandle::getPrivateKey(jsg::Lock& js) { +jsg::JsUint8Array CryptoImpl::ECDHHandle::getPrivateKey(jsg::Lock& js) { auto b = key_.getPrivateKey(); JSG_REQUIRE(b != nullptr, Error, "Failed to get ECDH private key"); - auto backing = - jsg::BackingStore::alloc(js, ncrypto::BignumPointer::GetByteCount(b)); - JSG_REQUIRE(backing.size() == - ncrypto::BignumPointer::EncodePaddedInto(b, backing.asArrayPtr().begin(), backing.size()), + auto buf = jsg::JsUint8Array::create(js, ncrypto::BignumPointer::GetByteCount(b)); + JSG_REQUIRE(buf.size() == + ncrypto::BignumPointer::EncodePaddedInto(b, buf.asArrayPtr().begin(), buf.size()), Error, "Failed to encode the private key"); - return jsg::BufferSource(js, kj::mv(backing)); + return buf; } -jsg::BufferSource CryptoImpl::ECDHHandle::getPublicKey(jsg::Lock& js, kj::String format) { +jsg::JsUint8Array CryptoImpl::ECDHHandle::getPublicKey(jsg::Lock& js, kj::String format) { const auto group = key_.getGroup(); const auto pub = key_.getPublicKey(); JSG_REQUIRE(pub != nullptr, Error, "Failed to get ECDH public key"); @@ -1581,7 +1589,7 @@ jsg::BufferSource CryptoImpl::ECDHHandle::getPublicKey(jsg::Lock& js, kj::String return ecPointToBuffer(js, group, pub, form); } -void CryptoImpl::ECDHHandle::setPrivateKey(jsg::Lock& js, jsg::BufferSource key) { +void CryptoImpl::ECDHHandle::setPrivateKey(jsg::Lock& js, jsg::JsBufferSource key) { JSG_REQUIRE(key.size() <= INT32_MAX, Error, "key is too big"); @@ -1615,14 +1623,13 @@ void CryptoImpl::ECDHHandle::setPrivateKey(jsg::Lock& js, jsg::BufferSource key) group_ = key_.getGroup(); } -jsg::BufferSource CryptoImpl::ECDHHandle::convertKey( - jsg::Lock& js, jsg::BufferSource key, kj::String curveName, kj::String format) { +jsg::JsUint8Array CryptoImpl::ECDHHandle::convertKey( + jsg::Lock& js, jsg::JsBufferSource key, kj::String curveName, kj::String format) { ncrypto::ClearErrorOnReturn clear_error_on_return; JSG_REQUIRE(key.size() <= INT32_MAX, Error, "key is too big"); if (key.size() == 0) { - auto backing = jsg::BackingStore::alloc(js, 0); - return jsg::BufferSource(js, kj::mv(backing)); + return jsg::JsUint8Array::create(js, 0); } int nid = OBJ_sn2nid(curveName.begin()); diff --git a/src/workerd/api/node/crypto.h b/src/workerd/api/node/crypto.h index 80a81bbcf88..b8d369977a9 100644 --- a/src/workerd/api/node/crypto.h +++ b/src/workerd/api/node/crypto.h @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -26,12 +27,12 @@ class CryptoImpl final: public jsg::Object { void setPrivateKey(kj::Array key); void setPublicKey(kj::Array key); - jsg::BufferSource getPublicKey(jsg::Lock& js); - jsg::BufferSource getPrivateKey(jsg::Lock& js); - jsg::BufferSource getGenerator(jsg::Lock& js); - jsg::BufferSource getPrime(jsg::Lock& js); - jsg::BufferSource computeSecret(jsg::Lock& js, kj::Array key); - jsg::BufferSource generateKeys(jsg::Lock& js); + jsg::JsUint8Array getPublicKey(jsg::Lock& js); + jsg::JsUint8Array getPrivateKey(jsg::Lock& js); + jsg::JsUint8Array getGenerator(jsg::Lock& js); + jsg::JsUint8Array getPrime(jsg::Lock& js); + jsg::JsUint8Array computeSecret(jsg::Lock& js, kj::Array key); + jsg::JsUint8Array generateKeys(jsg::Lock& js); int getVerifyError(); JSG_RESOURCE_TYPE(DiffieHellmanHandle) { @@ -53,11 +54,11 @@ class CryptoImpl final: public jsg::Object { jsg::Ref DiffieHellmanGroupHandle(jsg::Lock& js, kj::String name); - jsg::BufferSource statelessDH( + jsg::JsUint8Array statelessDH( jsg::Lock& js, jsg::Ref privateKey, jsg::Ref publicKey); // Primes - jsg::BufferSource randomPrime(jsg::Lock& js, + jsg::JsArrayBuffer randomPrime(jsg::Lock& js, uint32_t size, bool safe, jsg::Optional> add, @@ -71,12 +72,12 @@ class CryptoImpl final: public jsg::Object { static jsg::Ref constructor( jsg::Lock& js, kj::String algorithm, kj::Maybe xofLen); - static jsg::BufferSource oneshot( + static jsg::JsUint8Array oneshot( jsg::Lock&, kj::String algorithm, kj::Array data, kj::Maybe xofLen); jsg::Ref copy(jsg::Lock& js, kj::Maybe xofLen); int update(kj::Array data); - jsg::BufferSource digest(jsg::Lock& js); + jsg::JsUint8Array digest(jsg::Lock& js); JSG_RESOURCE_TYPE(HashHandle) { JSG_METHOD(update); @@ -102,11 +103,11 @@ class CryptoImpl final: public jsg::Object { // Efficiently implement one-shot HMAC that avoids multiple calls // across the C++/JS boundary. - static jsg::BufferSource oneshot( + static jsg::JsUint8Array oneshot( jsg::Lock& js, kj::String algorithm, KeyParam key, kj::Array data); int update(kj::Array data); - jsg::BufferSource digest(jsg::Lock& js); + jsg::JsUint8Array digest(jsg::Lock& js); JSG_RESOURCE_TYPE(HmacHandle) { JSG_METHOD(update); @@ -121,7 +122,7 @@ class CryptoImpl final: public jsg::Object { }; // Hkdf - jsg::BufferSource getHkdf(jsg::Lock& js, + jsg::JsArrayBuffer getHkdf(jsg::Lock& js, kj::String hash, kj::Array key, kj::Array salt, @@ -129,7 +130,7 @@ class CryptoImpl final: public jsg::Object { uint32_t length); // Pbkdf2 - jsg::BufferSource getPbkdf(jsg::Lock& js, + jsg::JsArrayBuffer getPbkdf(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t num_iterations, @@ -137,7 +138,7 @@ class CryptoImpl final: public jsg::Object { kj::String name); // Scrypt - jsg::BufferSource getScrypt(jsg::Lock& js, + jsg::JsArrayBuffer getScrypt(jsg::Lock& js, kj::Array password, kj::Array salt, uint32_t N, @@ -188,10 +189,11 @@ class CryptoImpl final: public jsg::Object { }; struct CreateAsymmetricKeyOptions { - kj::OneOf> key; + kj::OneOf, SubtleCrypto::JsonWebKey, jsg::Ref> + key; kj::String format; jsg::Optional type; - jsg::Optional passphrase; + jsg::Optional> passphrase; // The passphrase is only used for private keys. The format, type, and passphrase // options are only used if the key is a kj::Array. JSG_STRUCT(key, format, type, passphrase); @@ -200,7 +202,7 @@ class CryptoImpl final: public jsg::Object { CryptoImpl() = default; CryptoImpl(jsg::Lock&, const jsg::Url&) {} - kj::OneOf exportKey( + kj::OneOf exportKey( jsg::Lock& js, jsg::Ref key, jsg::Optional options); bool equals(jsg::Lock& js, jsg::Ref key, jsg::Ref otherKey); @@ -208,7 +210,7 @@ class CryptoImpl final: public jsg::Object { CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js, jsg::Ref key); kj::StringPtr getAsymmetricKeyType(jsg::Lock& js, jsg::Ref key); - jsg::Ref createSecretKey(jsg::Lock& js, jsg::BufferSource keyData); + jsg::Ref createSecretKey(jsg::Lock& js, jsg::JsBufferSource keyData); jsg::Ref createPrivateKey(jsg::Lock& js, CreateAsymmetricKeyOptions options); jsg::Ref createPublicKey(jsg::Lock& js, CreateAsymmetricKeyOptions options); static kj::Maybe tryGetKey(jsg::Ref& key); @@ -242,7 +244,7 @@ class CryptoImpl final: public jsg::Object { }; struct DhKeyPairOptions { - kj::OneOf primeOrGroup; + kj::OneOf, uint32_t, kj::String> primeOrGroup; jsg::Optional generator; JSG_STRUCT(primeOrGroup, generator); }; @@ -259,8 +261,8 @@ class CryptoImpl final: public jsg::Object { SignHandle(ncrypto::EVPMDCtxPointer ctx); static jsg::Ref constructor(jsg::Lock& js, kj::String algorithm); - void update(jsg::Lock& js, jsg::BufferSource data); - jsg::BufferSource sign(jsg::Lock& js, + void update(jsg::Lock& js, jsg::JsBufferSource data); + jsg::JsUint8Array sign(jsg::Lock& js, jsg::Ref key, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, @@ -279,10 +281,10 @@ class CryptoImpl final: public jsg::Object { VerifyHandle(ncrypto::EVPMDCtxPointer ctx); static jsg::Ref constructor(jsg::Lock& js, kj::String algorithm); - void update(jsg::Lock& js, jsg::BufferSource data); + void update(jsg::Lock& js, jsg::JsBufferSource data); bool verify(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource signature, + jsg::JsBufferSource signature, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, jsg::Optional dsaSigEnc); @@ -296,18 +298,18 @@ class CryptoImpl final: public jsg::Object { ncrypto::EVPMDCtxPointer ctx; }; - jsg::BufferSource signOneShot(jsg::Lock& js, + jsg::JsUint8Array signOneShot(jsg::Lock& js, jsg::Ref key, jsg::Optional algorithm, - jsg::BufferSource data, + jsg::JsBufferSource data, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, jsg::Optional dsaSigEnc); bool verifyOneShot(jsg::Lock& js, jsg::Ref key, jsg::Optional algorithm, - jsg::BufferSource data, - jsg::BufferSource signature, + jsg::JsBufferSource data, + jsg::JsBufferSource signature, jsg::Optional rsaPadding, jsg::Optional pssSaltLength, jsg::Optional dsaSigEnc); @@ -321,10 +323,11 @@ class CryptoImpl final: public jsg::Object { unsigned int max_message_size = INT_MAX; }; - CipherHandle(CipherMode mode, + CipherHandle(jsg::Lock& js, + CipherMode mode, ncrypto::CipherCtxPointer ctx, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, kj::Maybe maybeAuthInfo); static jsg::Ref construct(jsg::Lock& js, @@ -332,15 +335,16 @@ class CryptoImpl final: public jsg::Object { kj::StringPtr algorithm, ncrypto::Cipher cipher, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, jsg::Optional maybeAuthTagLength); - jsg::BufferSource update(jsg::Lock& js, jsg::BufferSource data); - jsg::BufferSource final(jsg::Lock& js); - void setAAD(jsg::Lock& js, jsg::BufferSource aad, jsg::Optional maybePlaintextLength); + jsg::JsUint8Array update(jsg::Lock& js, jsg::JsBufferSource data); + jsg::JsUint8Array final(jsg::Lock& js); + void setAAD( + jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional maybePlaintextLength); void setAutoPadding(jsg::Lock& js, bool autoPadding); - void setAuthTag(jsg::Lock& js, jsg::BufferSource authTag); - jsg::BufferSource getAuthTag(jsg::Lock& js); + void setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag); + jsg::JsUint8Array getAuthTag(jsg::Lock& js); JSG_RESOURCE_TYPE(CipherHandle) { JSG_METHOD(update); @@ -355,8 +359,8 @@ class CryptoImpl final: public jsg::Object { CipherMode mode; ncrypto::CipherCtxPointer ctx; jsg::Ref key; - jsg::BufferSource iv; - kj::Maybe maybeAuthTag; + jsg::JsRef iv; + kj::Maybe> maybeAuthTag; kj::Maybe maybeAuthInfo; bool authTagPassed = false; bool pendingAuthFailed = false; @@ -397,11 +401,12 @@ class CryptoImpl final: public jsg::Object { unsigned int max_message_size = INT_MAX; }; - AeadHandle(CipherMode mode, + AeadHandle(jsg::Lock& js, + CipherMode mode, ncrypto::Aead aead, ncrypto::AeadCtxPointer ctx, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, kj::Maybe maybeAuthInfo); static jsg::Ref construct(jsg::Lock& js, @@ -409,15 +414,16 @@ class CryptoImpl final: public jsg::Object { kj::StringPtr algorithm, ncrypto::Aead aead, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, jsg::Optional maybeAuthTagLength); - jsg::BufferSource update(jsg::Lock& js, jsg::BufferSource data); - jsg::BufferSource final(jsg::Lock& js); - void setAAD(jsg::Lock& js, jsg::BufferSource aad, jsg::Optional maybePlaintextLength); + jsg::JsUint8Array update(jsg::Lock& js, jsg::JsBufferSource data); + jsg::JsUint8Array final(jsg::Lock& js); + void setAAD( + jsg::Lock& js, jsg::JsBufferSource aad, jsg::Optional maybePlaintextLength); void setAutoPadding(jsg::Lock&, bool); - void setAuthTag(jsg::Lock& js, jsg::BufferSource authTag); - jsg::BufferSource getAuthTag(jsg::Lock& js); + void setAuthTag(jsg::Lock& js, jsg::JsBufferSource authTag); + jsg::JsUint8Array getAuthTag(jsg::Lock& js); JSG_RESOURCE_TYPE(AeadHandle) { JSG_METHOD(update); @@ -433,10 +439,10 @@ class CryptoImpl final: public jsg::Object { ncrypto::Aead aead; ncrypto::AeadCtxPointer ctx; jsg::Ref key; - jsg::BufferSource iv; - kj::Maybe maybeAuthTag; + jsg::JsRef iv; + kj::Maybe> maybeAuthTag; kj::Maybe maybeAuthInfo; - kj::Maybe maybeAad; + kj::Maybe> maybeAad; bool updated = false; }; @@ -444,31 +450,31 @@ class CryptoImpl final: public jsg::Object { kj::uint mode, kj::String algorithm, jsg::Ref key, - jsg::BufferSource iv, + jsg::JsBufferSource iv, jsg::Optional maybeAuthTagLength); struct PublicPrivateCipherOptions { int padding; kj::String oaepHash; - jsg::Optional oaepLabel; + jsg::Optional> oaepLabel; JSG_STRUCT(padding, oaepHash, oaepLabel); }; - jsg::BufferSource publicEncrypt(jsg::Lock& js, + jsg::JsUint8Array publicEncrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, PublicPrivateCipherOptions options); - jsg::BufferSource publicDecrypt(jsg::Lock& js, + jsg::JsUint8Array publicDecrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, PublicPrivateCipherOptions options); - jsg::BufferSource privateEncrypt(jsg::Lock& js, + jsg::JsUint8Array privateEncrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, PublicPrivateCipherOptions options); - jsg::BufferSource privateDecrypt(jsg::Lock& js, + jsg::JsUint8Array privateDecrypt(jsg::Lock& js, jsg::Ref key, - jsg::BufferSource buffer, + jsg::JsBufferSource buffer, PublicPrivateCipherOptions options); struct CipherInfo { @@ -495,8 +501,8 @@ class CryptoImpl final: public jsg::Object { // SPKAC bool verifySpkac(kj::Array input); - kj::Maybe exportPublicKey(jsg::Lock& js, kj::Array input); - kj::Maybe exportChallenge(jsg::Lock& js, kj::Array input); + kj::Maybe exportPublicKey(jsg::Lock& js, kj::Array input); + kj::Maybe exportChallenge(jsg::Lock& js, kj::Array input); // ECDH class ECDHHandle final: public jsg::Object { @@ -504,14 +510,14 @@ class CryptoImpl final: public jsg::Object { ECDHHandle(ncrypto::ECKeyPointer key); static jsg::Ref constructor(jsg::Lock& js, kj::String curveName); - static jsg::BufferSource convertKey( - jsg::Lock& js, jsg::BufferSource key, kj::String curveName, kj::String format); + static jsg::JsUint8Array convertKey( + jsg::Lock& js, jsg::JsBufferSource key, kj::String curveName, kj::String format); - jsg::BufferSource computeSecret(jsg::Lock& js, jsg::BufferSource otherPublicKey); + jsg::JsUint8Array computeSecret(jsg::Lock& js, jsg::JsBufferSource otherPublicKey); void generateKeys(); - jsg::BufferSource getPrivateKey(jsg::Lock& js); - jsg::BufferSource getPublicKey(jsg::Lock& js, kj::String format); - void setPrivateKey(jsg::Lock& js, jsg::BufferSource key); + jsg::JsUint8Array getPrivateKey(jsg::Lock& js); + jsg::JsUint8Array getPublicKey(jsg::Lock& js, kj::String format); + void setPrivateKey(jsg::Lock& js, jsg::JsBufferSource key); JSG_RESOURCE_TYPE(ECDHHandle) { JSG_STATIC_METHOD(convertKey); diff --git a/src/workerd/api/node/tests/crypto_dh-test.js b/src/workerd/api/node/tests/crypto_dh-test.js index 63be4a4d6c0..c54ffc3fc45 100644 --- a/src/workerd/api/node/tests/crypto_dh-test.js +++ b/src/workerd/api/node/tests/crypto_dh-test.js @@ -499,3 +499,47 @@ export const DhLargeGeneratorParam = { }); }, }; + +// Test that invalid public keys are rejected by setPublicKey and computeSecret. +export const dhPubKeyValidation = { + test() { + const dh = crypto.createDiffieHellman(1024); + dh.generateKeys(); + const prime = dh.getPrime(); + + // Public key of 0 should be rejected (too small) + const dh2 = crypto.createDiffieHellman(prime); + dh2.generateKeys(); + assert.throws(() => dh2.setPublicKey(Buffer.from([0x00])), { + message: /too small|invalid public key/, + }); + + // Public key of 1 should be rejected (too small) + assert.throws(() => dh2.setPublicKey(Buffer.from([0x01])), { + message: /too small|invalid public key/, + }); + + // Public key equal to the prime should be rejected (too large) + assert.throws(() => dh2.setPublicKey(prime), { + message: /too large|invalid public key/, + }); + + // computeSecret should also reject invalid peer keys proactively + assert.throws(() => dh2.computeSecret(Buffer.from([0x00])), { + message: /too small|invalid peer public key/, + }); + assert.throws(() => dh2.computeSecret(Buffer.from([0x01])), { + message: /too small|invalid peer public key/, + }); + assert.throws(() => dh2.computeSecret(prime), { + message: /too large|invalid peer public key/, + }); + + // Valid exchange should still work + const dh3 = crypto.createDiffieHellman(prime); + dh3.generateKeys(); + const secret1 = dh.computeSecret(dh3.getPublicKey()); + const secret2 = dh3.computeSecret(dh.getPublicKey()); + assert.deepStrictEqual(secret1, secret2); + }, +}; diff --git a/src/workerd/api/tests/crypto-extras-test.js b/src/workerd/api/tests/crypto-extras-test.js index f3e825c1627..11badb662c0 100644 --- a/src/workerd/api/tests/crypto-extras-test.js +++ b/src/workerd/api/tests/crypto-extras-test.js @@ -253,3 +253,174 @@ export const aesCounterOverflowTest = { ok(crypto.subtle.timingSafeEqual(counter, counter2)); }, }; + +// Test that RSA JWK import with partially invalid fields throws a proper error +// without leaking memory. The valid n/e/d fields cause BIGNUM allocations; the +// invalid p field (bad base64) triggers a throw. Under ASAN this verifies the +// BIGNUMs allocated for n/e/d are properly freed on the error path. +export const rsaJwkPartialImportFailure = { + async test() { + // A minimal RSA-2048 JWK with valid n, e, d but invalid CRT parameters. + // The n/e/d values are from a real key (public, not sensitive). + const jwk = { + kty: 'RSA', + // Valid base64url-encoded values for n and e (from an RSA-2048 test key) + n: + 'sXchDaQebHnPiGvhGPEUBYNmRREkfWAz4CZV0FxTwtQq6R51mJk8qnnU_6DE_XJr' + + 'T2JVNPB-bIXGFNnMLPOsTf5Q4r9Ks3h3S3tPzFqSd9Cjv0eRe-ZhWBYFkl-bLE1h' + + 'ZGnmtQ--KfAiMvAtYNfRJwKL9cSKpGQTmqY6_0IbUqbZ0dXf_5D4rKCiZaQj-lTbm' + + 'Eifn5JeRKnA2VY4dQvVQKhoQp_dEFwjOLGPOJ3yJhAFRrtFI3tzH7jSLNz2FA9gHk' + + 'LaPrGxWF-bSNqlegYCr8CATCNfCAt9lDbCCHJiB5TQ5B-R40gM-y_M44zzX9nbZuA' + + 'rSkBjQ', + e: 'AQAB', + d: + 'VFCWOqXr8nvZNyaaJLXEnFBR3W45lj0nSjpUGSH-wOjK4p5_FDRlaL-eRa-VQvwjJ' + + '38BRJk9_0dKJPCMcuFVlj-B0FNpZ_gkBGC-jlLfCq3SBjRFBasVUR5vh4GGe_pFD3p' + + '0RWjwwl_6yPb_cCeI4XP4kK4JEWndHjvNmBcZI6PU0Lc_8-Fb_Z0-BTN3BA0DBkFS' + + 'GCQN7G4dCdNQ3Onn3y2JBXB-pYlFkiHyR0j0o_GFoH_GE-WxQb7q0PjkNV-sMFQ8' + + '0ql44vEPg0Z8bZ0d1g_j8_Z3PuKCPJxJ6T3IGHPV1D-kBJyBvjJ-rlKr4XQ6XqvAp' + + 'XWPQ', + // Invalid base64 in CRT parameters — should cause import to throw + p: '!!!not-valid-base64!!!', + q: '!!!not-valid-base64!!!', + dp: '!!!not-valid-base64!!!', + dq: '!!!not-valid-base64!!!', + qi: '!!!not-valid-base64!!!', + }; + + let threw = false; + try { + await crypto.subtle.importKey( + 'jwk', + jwk, + { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, + true, + ['sign'] + ); + } catch (e) { + threw = true; + // Should get an error about invalid base64, not crash or silently succeed + ok(e instanceof Error, 'Expected an Error'); + } + ok(threw, 'Import should have thrown for invalid CRT parameters'); + + // Also test with valid n/e but invalid d (earlier failure point) + const jwk2 = { + kty: 'RSA', + n: jwk.n, + e: jwk.e, + d: '!!!not-valid-base64!!!', + p: '!!!not-valid-base64!!!', + q: '!!!not-valid-base64!!!', + dp: '!!!not-valid-base64!!!', + dq: '!!!not-valid-base64!!!', + qi: '!!!not-valid-base64!!!', + }; + + threw = false; + try { + await crypto.subtle.importKey( + 'jwk', + jwk2, + { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, + true, + ['sign'] + ); + } catch (e) { + threw = true; + ok(e instanceof Error, 'Expected an Error'); + } + ok(threw, 'Import should have thrown for invalid private exponent'); + }, +}; + +// Test that operations on detached ArrayBuffers return empty results instead of +// crashing. This exercises the WasDetached() checks in JsArrayBuffer, JsUint8Array, +// JsArrayBufferView, and JsBufferSource. +export const detachedBufferHandling = { + async test() { + // Create a buffer, detach it by transferring, then use it with crypto APIs. + const buf = new ArrayBuffer(16); + const view = new Uint8Array(buf); + + // Detach by transferring to a new ArrayBuffer via structuredClone + structuredClone(buf, { transfer: [buf] }); + + // buf is now detached — byteLength should be 0 + strictEqual(buf.byteLength, 0); + + // getRandomValues should handle the detached view gracefully + let threw = false; + try { + crypto.getRandomValues(view); + } catch (e) { + threw = true; + } + // The detached buffer has 0 length, which should be accepted (0 <= 65536) + // but the view reports 0 bytes so getRandomValues is effectively a no-op. + // Either succeeding with 0 bytes or throwing is acceptable behavior. + + // timingSafeEqual with detached buffers + const detachedBuf2 = new ArrayBuffer(0); + ok( + crypto.subtle.timingSafeEqual(detachedBuf2, new ArrayBuffer(0)), + 'Empty buffers should be timing-safe equal' + ); + + // Verify that encrypt with a detached IV throws rather than crashing + const key = await crypto.subtle.generateKey( + { name: 'AES-GCM', length: 128 }, + false, + ['encrypt'] + ); + const detachedIv = new ArrayBuffer(12); + structuredClone(detachedIv, { transfer: [detachedIv] }); + threw = false; + try { + await crypto.subtle.encrypt( + { name: 'AES-GCM', iv: detachedIv }, + key, + new ArrayBuffer(0) + ); + } catch (e) { + threw = true; + } + ok(threw, 'Encrypt with detached IV should throw'); + }, +}; + +// Test that EC JWK import with mismatched public/private key components is rejected. +// EC_KEY_check_key validates that the private key d corresponds to the public key (x, y). +export const ecJwkKeyConsistencyCheck = { + async test() { + // Generate a valid P-256 key pair to get real x, y values + const keyPair = await crypto.subtle.generateKey( + { name: 'ECDSA', namedCurve: 'P-256' }, + true, + ['sign', 'verify'] + ); + const validJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey); + + // Corrupt the private key d while keeping x, y valid. + // This creates an inconsistency: d does not correspond to (x, y). + const corruptedJwk = { + ...validJwk, + d: validJwk.d.split('').reverse().join(''), + }; + + let threw = false; + try { + await crypto.subtle.importKey( + 'jwk', + corruptedJwk, + { name: 'ECDSA', namedCurve: 'P-256' }, + true, + ['sign'] + ); + } catch (e) { + threw = true; + ok(e instanceof Error, 'Expected an Error'); + } + ok(threw, 'Import should have thrown for inconsistent EC JWK private key'); + }, +}; diff --git a/src/workerd/jsg/jsg.h b/src/workerd/jsg/jsg.h index 3a59bcb4241..a318f3d7595 100644 --- a/src/workerd/jsg/jsg.h +++ b/src/workerd/jsg/jsg.h @@ -2189,10 +2189,15 @@ class JsMessage; JS_TYPE_CLASSES(V) #undef V +// JsBufferSource is not in JS_TYPE_CLASSES because there is no v8::BufferSource +// type (and hence no v8::Value::IsBufferSource() check). It is instead handled +// with special-case logic in JsValue::tryCast and JsValueWrapper. +class JsBufferSource; + #define V(Name) || kj::isSameType() template -concept IsJsValue = - kj::isSameType() || kj::isSameType() JS_TYPE_CLASSES(V); +concept IsJsValue = kj::isSameType() || + kj::isSameType() JS_TYPE_CLASSES(V) || kj::isSameType(); #undef V class DOMException; diff --git a/src/workerd/jsg/jsvalue.c++ b/src/workerd/jsg/jsvalue.c++ index 0868693369e..205c8af99f2 100644 --- a/src/workerd/jsg/jsvalue.c++ +++ b/src/workerd/jsg/jsvalue.c++ @@ -651,6 +651,161 @@ BufferSource Lock::bytes(kj::Array data) { return BufferSource(*this, BackingStore::from(*this, kj::mv(data))); } +// ====================================================================================== +// JsArrayBuffer + +JsArrayBuffer JsArrayBuffer::create(Lock& js, size_t length) { + JSG_REQUIRE(length < v8::ArrayBuffer::kMaxByteLength, RangeError, "The length is too large"); + auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, length, + v8::BackingStoreInitializationMode::kZeroInitialized, + v8::BackingStoreOnFailureMode::kReturnNull); + JSG_REQUIRE(backing != nullptr, RangeError, "Failed to allocate memory for ArrayBuffer"); + return create(js, kj::mv(backing)); +} + +JsArrayBuffer JsArrayBuffer::create(Lock& js, kj::ArrayPtr data) { + auto buf = create(js, data.size()); + buf.asArrayPtr().copyFrom(data); + return buf; +} + +JsArrayBuffer JsArrayBuffer::create(Lock& js, std::unique_ptr backingStore) { + return JsArrayBuffer(v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore))); +} + +kj::ArrayPtr JsArrayBuffer::asArrayPtr() { + v8::Local inner = *this; + if (inner->WasDetached()) [[unlikely]] { + return nullptr; + } + void* data = inner->GetBackingStore()->Data(); + size_t length = inner->ByteLength(); + return kj::ArrayPtr(static_cast(data), length); +} + +kj::ArrayPtr JsArrayBuffer::asArrayPtr() const { + v8::Local inner = *this; + if (inner->WasDetached()) [[unlikely]] { + return nullptr; + } + const void* data = inner->GetBackingStore()->Data(); + size_t length = inner->ByteLength(); + return kj::ArrayPtr(static_cast(data), length); +} + +JsArrayBuffer JsArrayBuffer::slice(Lock& js, size_t newLength) const { + JSG_REQUIRE(newLength <= size(), RangeError, "New length exceeds buffer length"); + auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, newLength, + v8::BackingStoreInitializationMode::kUninitialized, + v8::BackingStoreOnFailureMode::kReturnNull); + JSG_REQUIRE(backing != nullptr, RangeError, "Failed to allocate memory for ArrayBuffer"); + auto dest = kj::ArrayPtr(static_cast(backing->Data()), newLength); + v8::Local inner = *this; + dest.copyFrom( + kj::ArrayPtr(static_cast(inner->GetBackingStore()->Data()), newLength)); + return JsArrayBuffer(v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backing))); +} + +size_t JsArrayBuffer::size() const { + v8::Local inner = *this; + return inner->ByteLength(); +} + +kj::Array JsArrayBuffer::copy() { + auto ptr = asArrayPtr(); + return kj::heapArray(ptr); +} + +// ====================================================================================== +// JsArrayBufferView + +kj::ArrayPtr JsArrayBufferView::asArrayPtr() { + v8::Local inner = *this; + auto buf = inner->Buffer(); + if (buf->WasDetached()) [[unlikely]] + return nullptr; + kj::byte* data = static_cast(buf->Data()) + inner->ByteOffset(); + return kj::ArrayPtr(data, inner->ByteLength()); +} + +size_t JsArrayBufferView::size() const { + v8::Local inner = *this; + return inner->ByteLength(); +} + +bool JsArrayBufferView::isIntegerType() const { + v8::Local inner = *this; + return inner->IsUint8Array() || inner->IsUint8ClampedArray() || inner->IsInt8Array() || + inner->IsUint16Array() || inner->IsInt16Array() || inner->IsUint32Array() || + inner->IsInt32Array() || inner->IsBigInt64Array() || inner->IsBigUint64Array(); +} + +// ====================================================================================== +// JsBufferSource + +kj::ArrayPtr JsBufferSource::asArrayPtr() { + v8::Local inner = *this; + if (inner->IsArrayBuffer()) { + auto buf = inner.As(); + if (buf->WasDetached()) [[unlikely]] { + return nullptr; + } + return kj::ArrayPtr(static_cast(buf->Data()), buf->ByteLength()); + } else { + KJ_DASSERT(inner->IsArrayBufferView()); + auto view = inner.As(); + auto buf = view->Buffer(); + if (buf->WasDetached()) [[unlikely]] { + return nullptr; + } + kj::byte* data = static_cast(buf->Data()) + view->ByteOffset(); + return kj::ArrayPtr(data, view->ByteLength()); + } +} + +size_t JsBufferSource::size() const { + v8::Local inner = *this; + if (inner->IsArrayBuffer()) { + auto buf = inner.As(); + if (buf->WasDetached()) [[unlikely]] { + return 0; + } + return buf->ByteLength(); + } else { + KJ_DASSERT(inner->IsArrayBufferView()); + auto view = inner.As(); + if (view->Buffer()->WasDetached()) [[unlikely]] { + return 0; + } + return view->ByteLength(); + } +} + +bool JsBufferSource::isIntegerType() const { + v8::Local inner = *this; + return inner->IsUint8Array() || inner->IsUint8ClampedArray() || inner->IsInt8Array() || + inner->IsUint16Array() || inner->IsInt16Array() || inner->IsUint32Array() || + inner->IsInt32Array() || inner->IsBigInt64Array() || inner->IsBigUint64Array(); +} + +bool JsBufferSource::isArrayBuffer() const { + v8::Local inner = *this; + return inner->IsArrayBuffer(); +} + +bool JsBufferSource::isArrayBufferView() const { + v8::Local inner = *this; + return inner->IsArrayBufferView(); +} + +kj::Array JsBufferSource::copy() { + auto ptr = asArrayPtr(); + return kj::heapArray(ptr); +} + +// ====================================================================================== +// JsUint8Array + JsUint8Array JsUint8Array::create(Lock& js, size_t length) { JSG_REQUIRE(length < v8::ArrayBuffer::kMaxByteLength, RangeError, "The length is too large"); auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, length, @@ -660,6 +815,17 @@ JsUint8Array JsUint8Array::create(Lock& js, size_t length) { return create(js, kj::mv(backing), 0, length); } +JsUint8Array JsUint8Array::create(Lock& js, kj::ArrayPtr data) { + auto buf = create(js, data.size()); + buf.asArrayPtr().copyFrom(data); + return buf; +} + +JsUint8Array JsUint8Array::create(Lock& js, JsArrayBuffer& buffer) { + v8::Local ab = buffer; + return JsUint8Array(v8::Uint8Array::New(ab, 0, ab->ByteLength())); +} + JsUint8Array JsUint8Array::create( Lock& js, std::unique_ptr backingStore, size_t byteOffset, size_t length) { return JsUint8Array(v8::Uint8Array::New( @@ -672,8 +838,23 @@ JsUint8Array JsUint8Array::slice(Lock& js, size_t newLength) const { return JsUint8Array(u8); } +kj::ArrayPtr JsUint8Array::asArrayPtr() const { + auto buf = inner->Buffer(); + if (buf->WasDetached()) [[unlikely]] { + return nullptr; + } + const kj::byte* data = static_cast(buf->Data()) + inner->ByteOffset(); + size_t length = inner->ByteLength(); + return kj::ArrayPtr(data, length); +} + size_t JsUint8Array::size() const { return inner->ByteLength(); } +kj::Array JsUint8Array::copy() { + auto ptr = asArrayPtr(); + return kj::heapArray(ptr); +} + } // namespace workerd::jsg diff --git a/src/workerd/jsg/jsvalue.h b/src/workerd/jsg/jsvalue.h index db52413910d..85fe0ea1c64 100644 --- a/src/workerd/jsg/jsvalue.h +++ b/src/workerd/jsg/jsvalue.h @@ -180,7 +180,11 @@ class JsBase { operator v8::Local() const { return inner; } - operator v8::Local() const { + // Only provide the typed conversion when T is not already v8::Value, + // to avoid a duplicate operator signature (e.g. for JsBufferSource). + operator v8::Local() const + requires(!kj::isSameType()) + { return inner; } operator JsValue() const { @@ -228,26 +232,36 @@ class JsArray final: public JsBase { class JsArrayBuffer final: public JsBase { public: - kj::ArrayPtr asArrayPtr() { - v8::Local inner = *this; - void* data = inner->GetBackingStore()->Data(); - size_t length = inner->ByteLength(); - return kj::ArrayPtr(static_cast(data), length); - } + static JsArrayBuffer create(Lock& js, size_t length); + + // Allocate and copy data from the given ArrayPtr in a single step. + static JsArrayBuffer create(Lock& js, kj::ArrayPtr data); + + static JsArrayBuffer create(Lock& js, std::unique_ptr backingStore); + + JsArrayBuffer slice(Lock& js, size_t newLength) const; + + kj::ArrayPtr asArrayPtr(); + kj::ArrayPtr asArrayPtr() const; + + size_t size() const; + + // Return a copy of this buffer's data as a kj::Array. + kj::Array copy(); using JsBase::JsBase; }; class JsArrayBufferView final: public JsBase { public: - template - kj::ArrayPtr asArrayPtr() { - v8::Local inner = *this; - auto buf = inner->Buffer(); - T* data = static_cast(buf->Data()) + inner->ByteOffset(); - size_t length = inner->ByteLength(); - return kj::ArrayPtr(data, length); - } + kj::ArrayPtr asArrayPtr(); + + size_t size() const; + + // Returns true if the underlying view is an integer-typed TypedArray + // (e.g. Uint8Array, Int32Array, BigUint64Array) as opposed to a float-typed + // TypedArray or DataView. + bool isIntegerType() const; using JsBase::JsBase; }; @@ -256,6 +270,12 @@ class JsUint8Array final: public JsBase { public: static JsUint8Array create(Lock& js, size_t length); + // Allocate and copy data from the given ArrayPtr in a single step. + static JsUint8Array create(Lock& js, kj::ArrayPtr data); + + // Create a Uint8Array view over the given ArrayBuffer. + static JsUint8Array create(Lock& js, JsArrayBuffer& buffer); + static JsUint8Array create( Lock& js, std::unique_ptr backingStore, size_t byteOffset, size_t length); @@ -265,15 +285,53 @@ class JsUint8Array final: public JsBase { kj::ArrayPtr asArrayPtr() { v8::Local inner = *this; auto buf = inner->Buffer(); - T* data = static_cast(buf->Data()) + inner->ByteOffset(); - return kj::ArrayPtr(data, size()); + if (buf->WasDetached()) [[unlikely]] { + return nullptr; + } + auto byteLength = inner->ByteLength(); + T* data = reinterpret_cast(static_cast(buf->Data()) + inner->ByteOffset()); + return kj::ArrayPtr(data, byteLength / sizeof(T)); } + kj::ArrayPtr asArrayPtr() const; + size_t size() const; + // Return a copy of this buffer's data as a kj::Array. + kj::Array copy(); + using JsBase::JsBase; }; +// A lightweight wrapper for ArrayBuffer | ArrayBufferView (the Web IDL "BufferSource" +// type). Unlike jsg::BufferSource, this does NOT maintain a BackingStore, does NOT +// support detach, and is stack-only. Use JsRef for persistent storage. +// +// This type is based on v8::Value (not a specific V8 type) because there is no single +// V8 type that represents both ArrayBuffer and ArrayBufferView. It is NOT included in +// JS_TYPE_CLASSES; instead, JsValue::tryCast and JsValueWrapper handle it specially. +class JsBufferSource final: public JsBase { + public: + JsBufferSource(JsArrayBuffer& buffer): JsBase(static_cast>(buffer)) {} + JsBufferSource(JsUint8Array& buffer): JsBase(static_cast>(buffer)) {} + JsBufferSource(JsArrayBufferView& buffer): JsBase(static_cast>(buffer)) {} + + kj::ArrayPtr asArrayPtr(); + + size_t size() const; + + // Returns true if the underlying value is an integer-typed TypedArray. + bool isIntegerType() const; + + bool isArrayBuffer() const; + bool isArrayBufferView() const; + + // Return a copy of this buffer's data as a kj::Array. + kj::Array copy(); + + using JsBase::JsBase; +}; + class JsString final: public JsBase { public: int length(Lock& js) const KJ_WARN_UNUSED_RESULT; @@ -537,6 +595,12 @@ inline kj::Maybe JsValue::tryCast() const { } JS_TYPE_CLASSES(V) #undef V + // JsBufferSource is not in JS_TYPE_CLASSES because there is no + // v8::Value::IsBufferSource() method. Handle it explicitly. + else if constexpr (kj::isSameType()) { + if (!inner->IsArrayBuffer() && !inner->IsArrayBufferView()) return kj::none; + return T(inner); + } else { return kj::none; } @@ -757,6 +821,22 @@ struct JsValueWrapper { TYPES_TO_WRAP(V) #undef V + // Manual wrap overloads for JsBufferSource which is not in JS_TYPE_CLASSES. + // The underlying V8 type is v8::Value since BufferSource spans both + // ArrayBuffer and ArrayBufferView. + v8::Local wrap(jsg::Lock& js, + v8::Local context, + kj::Maybe> creator, + JsBufferSource value) { + return value; + } + v8::Local wrap(jsg::Lock& js, + v8::Local context, + kj::Maybe> creator, + JsRef value) { + return value.getHandle(js); + } + template kj::Maybe tryUnwrap(Lock& js, v8::Local context, diff --git a/src/workerd/jsg/rtti.h b/src/workerd/jsg/rtti.h index d86c888d820..1e726982fe9 100644 --- a/src/workerd/jsg/rtti.h +++ b/src/workerd/jsg/rtti.h @@ -469,6 +469,9 @@ struct BuildRtti { #define FOR_EACH_BUILTIN_TYPE(F, ...) \ F(jsg::JsUint8Array, BuiltinType::Type::V8_UINT8_ARRAY) \ + F(jsg::JsArrayBuffer, BuiltinType::Type::V8_ARRAY_BUFFER) \ + F(jsg::JsArrayBufferView, BuiltinType::Type::V8_ARRAY_BUFFER_VIEW) \ + F(jsg::JsBufferSource, BuiltinType::Type::JSG_BUFFER_SOURCE) \ F(jsg::BufferSource, BuiltinType::Type::JSG_BUFFER_SOURCE) \ F(kj::Date, BuiltinType::Type::KJ_DATE) \ F(v8::ArrayBufferView, BuiltinType::Type::V8_ARRAY_BUFFER_VIEW) \