diff options
author | 2022-10-23 20:25:18 -0700 | |
---|---|---|
committer | 2022-10-23 20:25:18 -0700 | |
commit | 76652ac3cad64dbc2fd54e976ce4bad0a37caa03 (patch) | |
tree | 179865bc417dc6bf2f224dd310b77b931ee45c73 /src/bun.js/bindings/webcrypto/SubtleCrypto.cpp | |
parent | 14cec299f5170b8ed35eed28e53b88724b8cc04f (diff) | |
download | bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.tar.gz bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.tar.zst bun-76652ac3cad64dbc2fd54e976ce4bad0a37caa03.zip |
Add Web Crypto API (#1384)
* Add Web Crypto API
* Duplicate symbols
* Update c_cpp_properties.json
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/bindings/webcrypto/SubtleCrypto.cpp')
-rw-r--r-- | src/bun.js/bindings/webcrypto/SubtleCrypto.cpp | 1193 |
1 files changed, 1193 insertions, 0 deletions
diff --git a/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp new file mode 100644 index 000000000..d8b7d7942 --- /dev/null +++ b/src/bun.js/bindings/webcrypto/SubtleCrypto.cpp @@ -0,0 +1,1193 @@ +/* + * Copyright (C) 2016-2019 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SubtleCrypto.h" + +#if ENABLE(WEB_CRYPTO) + +#include "CryptoAlgorithm.h" +#include "CryptoAlgorithmRegistry.h" +#include "JSAesCbcCfbParams.h" +#include "JSAesCtrParams.h" +#include "JSAesGcmParams.h" +#include "JSAesKeyParams.h" +#include "JSCryptoAlgorithmParameters.h" +#include "JSCryptoKey.h" +#include "JSCryptoKeyPair.h" +#include "JSDOMPromiseDeferred.h" +#include "JSDOMWrapper.h" +#include "JSEcKeyParams.h" +#include "JSEcdhKeyDeriveParams.h" +#include "JSEcdsaParams.h" +#include "JSHkdfParams.h" +#include "JSHmacKeyParams.h" +#include "JSJsonWebKey.h" +#include "JSPbkdf2Params.h" +#include "JSRsaHashedImportParams.h" +#include "JSRsaHashedKeyGenParams.h" +#include "JSRsaKeyGenParams.h" +#include "JSRsaOaepParams.h" +#include "JSRsaPssParams.h" +#include <JavaScriptCore/JSONObject.h> + +namespace WebCore { +using namespace JSC; + +SubtleCrypto::SubtleCrypto(ScriptExecutionContext* context) + : ContextDestructionObserver(context) + , m_workQueue(WorkQueue::create("com.apple.WebKit.CryptoQueue")) +{ +} + +SubtleCrypto::~SubtleCrypto() = default; + +enum class Operations { + Encrypt, + Decrypt, + Sign, + Verify, + Digest, + GenerateKey, + DeriveBits, + ImportKey, + WrapKey, + UnwrapKey, + GetKeyLength +}; + +static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAlgorithmParameters(JSGlobalObject&, WebCore::SubtleCrypto::AlgorithmIdentifier, Operations); + +static ExceptionOr<CryptoAlgorithmIdentifier> toHashIdentifier(JSGlobalObject& state, SubtleCrypto::AlgorithmIdentifier algorithmIdentifier) +{ + auto digestParams = normalizeCryptoAlgorithmParameters(state, algorithmIdentifier, Operations::Digest); + if (digestParams.hasException()) + return digestParams.releaseException(); + return digestParams.returnValue()->identifier; +} + +static ExceptionOr<std::unique_ptr<CryptoAlgorithmParameters>> normalizeCryptoAlgorithmParameters(JSGlobalObject& state, SubtleCrypto::AlgorithmIdentifier algorithmIdentifier, Operations operation) +{ + VM& vm = state.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (std::holds_alternative<String>(algorithmIdentifier)) { + auto newParams = Strong<JSObject>(vm, constructEmptyObject(&state)); + newParams->putDirect(vm, Identifier::fromString(vm, "name"_s), jsString(vm, std::get<String>(algorithmIdentifier))); + + return normalizeCryptoAlgorithmParameters(state, newParams, operation); + } + + auto& value = std::get<JSC::Strong<JSC::JSObject>>(algorithmIdentifier); + + auto params = convertDictionary<CryptoAlgorithmParameters>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + + auto identifier = CryptoAlgorithmRegistry::singleton().identifier(params.name); + if (UNLIKELY(!identifier)) + return Exception { NotSupportedError }; + + std::unique_ptr<CryptoAlgorithmParameters> result; + switch (operation) { + case Operations::Encrypt: + case Operations::Decrypt: + switch (*identifier) { + case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + case CryptoAlgorithmIdentifier::RSA_OAEP: { + auto params = convertDictionary<CryptoAlgorithmRsaOaepParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmRsaOaepParams>(params); + break; + } + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_CFB: { + auto params = convertDictionary<CryptoAlgorithmAesCbcCfbParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmAesCbcCfbParams>(params); + break; + } + case CryptoAlgorithmIdentifier::AES_CTR: { + auto params = convertDictionary<CryptoAlgorithmAesCtrParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmAesCtrParams>(params); + break; + } + case CryptoAlgorithmIdentifier::AES_GCM: { + auto params = convertDictionary<CryptoAlgorithmAesGcmParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmAesGcmParams>(params); + break; + } + default: + return Exception { NotSupportedError }; + } + break; + case Operations::Sign: + case Operations::Verify: + switch (*identifier) { + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::HMAC: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + case CryptoAlgorithmIdentifier::ECDSA: { + auto params = convertDictionary<CryptoAlgorithmEcdsaParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmEcdsaParams>(params); + break; + } + case CryptoAlgorithmIdentifier::RSA_PSS: { + auto params = convertDictionary<CryptoAlgorithmRsaPssParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmRsaPssParams>(params); + break; + } + default: + return Exception { NotSupportedError }; + } + break; + case Operations::Digest: + switch (*identifier) { + case CryptoAlgorithmIdentifier::SHA_1: + case CryptoAlgorithmIdentifier::SHA_224: + case CryptoAlgorithmIdentifier::SHA_256: + case CryptoAlgorithmIdentifier::SHA_384: + case CryptoAlgorithmIdentifier::SHA_512: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + default: + return Exception { NotSupportedError }; + } + break; + case Operations::GenerateKey: + switch (*identifier) { + case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: { + auto params = convertDictionary<CryptoAlgorithmRsaKeyGenParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmRsaKeyGenParams>(params); + break; + } + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::RSA_PSS: + case CryptoAlgorithmIdentifier::RSA_OAEP: { + auto params = convertDictionary<CryptoAlgorithmRsaHashedKeyGenParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmRsaHashedKeyGenParams>(params); + break; + } + case CryptoAlgorithmIdentifier::AES_CTR: + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_GCM: + case CryptoAlgorithmIdentifier::AES_CFB: + case CryptoAlgorithmIdentifier::AES_KW: { + auto params = convertDictionary<CryptoAlgorithmAesKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmAesKeyParams>(params); + break; + } + case CryptoAlgorithmIdentifier::HMAC: { + auto params = convertDictionary<CryptoAlgorithmHmacKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmHmacKeyParams>(params); + break; + } + case CryptoAlgorithmIdentifier::ECDSA: + case CryptoAlgorithmIdentifier::ECDH: { + auto params = convertDictionary<CryptoAlgorithmEcKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmEcKeyParams>(params); + break; + } + default: + return Exception { NotSupportedError }; + } + break; + case Operations::DeriveBits: + switch (*identifier) { + case CryptoAlgorithmIdentifier::ECDH: { + // Remove this hack once https://bugs.webkit.org/show_bug.cgi?id=169333 is fixed. + JSValue nameValue = value.get()->get(&state, Identifier::fromString(vm, "name"_s)); + JSValue publicValue = value.get()->get(&state, Identifier::fromString(vm, "public"_s)); + JSObject* newValue = constructEmptyObject(&state); + newValue->putDirect(vm, Identifier::fromString(vm, "name"_s), nameValue); + newValue->putDirect(vm, Identifier::fromString(vm, "publicKey"_s), publicValue); + + auto params = convertDictionary<CryptoAlgorithmEcdhKeyDeriveParams>(state, newValue); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmEcdhKeyDeriveParams>(params); + break; + } + case CryptoAlgorithmIdentifier::HKDF: { + auto params = convertDictionary<CryptoAlgorithmHkdfParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmHkdfParams>(params); + break; + } + case CryptoAlgorithmIdentifier::PBKDF2: { + auto params = convertDictionary<CryptoAlgorithmPbkdf2Params>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmPbkdf2Params>(params); + break; + } + default: + return Exception { NotSupportedError }; + } + break; + case Operations::ImportKey: + switch (*identifier) { + case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::RSA_PSS: + case CryptoAlgorithmIdentifier::RSA_OAEP: { + auto params = convertDictionary<CryptoAlgorithmRsaHashedImportParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmRsaHashedImportParams>(params); + break; + } + case CryptoAlgorithmIdentifier::AES_CTR: + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_GCM: + case CryptoAlgorithmIdentifier::AES_CFB: + case CryptoAlgorithmIdentifier::AES_KW: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + case CryptoAlgorithmIdentifier::HMAC: { + auto params = convertDictionary<CryptoAlgorithmHmacKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmHmacKeyParams>(params); + break; + } + case CryptoAlgorithmIdentifier::ECDSA: + case CryptoAlgorithmIdentifier::ECDH: { + auto params = convertDictionary<CryptoAlgorithmEcKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmEcKeyParams>(params); + break; + } + case CryptoAlgorithmIdentifier::HKDF: + case CryptoAlgorithmIdentifier::PBKDF2: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + default: + return Exception { NotSupportedError }; + } + break; + case Operations::WrapKey: + case Operations::UnwrapKey: + switch (*identifier) { + case CryptoAlgorithmIdentifier::AES_KW: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + default: + return Exception { NotSupportedError }; + } + break; + case Operations::GetKeyLength: + switch (*identifier) { + case CryptoAlgorithmIdentifier::AES_CTR: + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_GCM: + case CryptoAlgorithmIdentifier::AES_CFB: + case CryptoAlgorithmIdentifier::AES_KW: { + auto params = convertDictionary<CryptoAlgorithmAesKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + result = makeUnique<CryptoAlgorithmAesKeyParams>(params); + break; + } + case CryptoAlgorithmIdentifier::HMAC: { + auto params = convertDictionary<CryptoAlgorithmHmacKeyParams>(state, value.get()); + RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + auto hashIdentifier = toHashIdentifier(state, params.hash); + if (hashIdentifier.hasException()) + return hashIdentifier.releaseException(); + params.hashIdentifier = hashIdentifier.releaseReturnValue(); + result = makeUnique<CryptoAlgorithmHmacKeyParams>(params); + break; + } + case CryptoAlgorithmIdentifier::HKDF: + case CryptoAlgorithmIdentifier::PBKDF2: + result = makeUnique<CryptoAlgorithmParameters>(params); + break; + default: + return Exception { NotSupportedError }; + } + break; + } + + result->identifier = *identifier; + return result; +} + +static CryptoKeyUsageBitmap toCryptoKeyUsageBitmap(CryptoKeyUsage usage) +{ + switch (usage) { + case CryptoKeyUsage::Encrypt: + return CryptoKeyUsageEncrypt; + case CryptoKeyUsage::Decrypt: + return CryptoKeyUsageDecrypt; + case CryptoKeyUsage::Sign: + return CryptoKeyUsageSign; + case CryptoKeyUsage::Verify: + return CryptoKeyUsageVerify; + case CryptoKeyUsage::DeriveKey: + return CryptoKeyUsageDeriveKey; + case CryptoKeyUsage::DeriveBits: + return CryptoKeyUsageDeriveBits; + case CryptoKeyUsage::WrapKey: + return CryptoKeyUsageWrapKey; + case CryptoKeyUsage::UnwrapKey: + return CryptoKeyUsageUnwrapKey; + } + + RELEASE_ASSERT_NOT_REACHED(); +} + +static CryptoKeyUsageBitmap toCryptoKeyUsageBitmap(const Vector<CryptoKeyUsage>& usages) +{ + CryptoKeyUsageBitmap result = 0; + // Maybe we shouldn't silently bypass duplicated usages? + for (auto usage : usages) + result |= toCryptoKeyUsageBitmap(usage); + + return result; +} + +// Maybe we want more specific error messages? +static void rejectWithException(Ref<DeferredPromise>&& passedPromise, ExceptionCode ec) +{ + switch (ec) { + case NotSupportedError: + passedPromise->reject(ec, "The algorithm is not supported"_s); + return; + case SyntaxError: + passedPromise->reject(ec, "A required parameter was missing or out-of-range"_s); + return; + case InvalidStateError: + passedPromise->reject(ec, "The requested operation is not valid for the current state of the provided key"_s); + return; + case InvalidAccessError: + passedPromise->reject(ec, "The requested operation is not valid for the provided key"_s); + return; + case UnknownError: + passedPromise->reject(ec, "The operation failed for an unknown transient reason (e.g. out of memory)"_s); + return; + case DataError: + passedPromise->reject(ec, "Data provided to an operation does not meet requirements"_s); + return; + case OperationError: + passedPromise->reject(ec, "The operation failed for an operation-specific reason"_s); + return; + default: + break; + } + ASSERT_NOT_REACHED(); +} + +static void normalizeJsonWebKey(JsonWebKey& webKey) +{ + // Maybe we shouldn't silently bypass duplicated usages? + webKey.usages = webKey.key_ops ? toCryptoKeyUsageBitmap(webKey.key_ops.value()) : 0; +} + +// FIXME: This returns an std::optional<KeyData> and takes a promise, rather than returning an +// ExceptionOr<KeyData> and letting the caller handle the promise, to work around an issue where +// Variant types (which KeyData is) in ExceptionOr<> cause compile issues on some platforms. This +// should be resolved by adopting a standards compliant std::variant (see https://webkit.org/b/175583) +static std::optional<KeyData> toKeyData(SubtleCrypto::KeyFormat format, SubtleCrypto::KeyDataVariant&& keyDataVariant, Ref<DeferredPromise>& promise) +{ + switch (format) { + case SubtleCrypto::KeyFormat::Spki: + case SubtleCrypto::KeyFormat::Pkcs8: + case SubtleCrypto::KeyFormat::Raw: + return WTF::switchOn( + keyDataVariant, + [&promise](JsonWebKey&) -> std::optional<KeyData> { + promise->reject(Exception { TypeError }); + return std::nullopt; + }, + [](auto& bufferSource) -> std::optional<KeyData> { + return KeyData { Vector { static_cast<const uint8_t*>(bufferSource->data()), bufferSource->byteLength() } }; + }); + case SubtleCrypto::KeyFormat::Jwk: + return WTF::switchOn( + keyDataVariant, + [](JsonWebKey& webKey) -> std::optional<KeyData> { + normalizeJsonWebKey(webKey); + return KeyData { webKey }; + }, + [&promise](auto&) -> std::optional<KeyData> { + promise->reject(Exception { TypeError }); + return std::nullopt; + }); + } + + RELEASE_ASSERT_NOT_REACHED(); +} + +static Vector<uint8_t> copyToVector(BufferSource&& data) +{ + return { data.data(), data.length() }; +} + +static bool isSupportedExportKey(CryptoAlgorithmIdentifier identifier) +{ + switch (identifier) { + case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::RSA_PSS: + case CryptoAlgorithmIdentifier::RSA_OAEP: + case CryptoAlgorithmIdentifier::AES_CTR: + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_GCM: + case CryptoAlgorithmIdentifier::AES_CFB: + case CryptoAlgorithmIdentifier::AES_KW: + case CryptoAlgorithmIdentifier::HMAC: + case CryptoAlgorithmIdentifier::ECDSA: + case CryptoAlgorithmIdentifier::ECDH: + return true; + default: + return false; + } +} + +RefPtr<DeferredPromise> getPromise(DeferredPromise* index, WeakPtr<SubtleCrypto> weakThis) +{ + if (weakThis) + return weakThis->m_pendingPromises.take(index); + return nullptr; +} + +static std::unique_ptr<CryptoAlgorithmParameters> crossThreadCopyImportParams(const CryptoAlgorithmParameters& importParams) +{ + switch (importParams.parametersClass()) { + case CryptoAlgorithmParameters::Class::None: { + auto result = makeUnique<CryptoAlgorithmParameters>(); + result->identifier = importParams.identifier; + return result; + } + case CryptoAlgorithmParameters::Class::EcKeyParams: + return makeUnique<CryptoAlgorithmEcKeyParams>(crossThreadCopy(downcast<CryptoAlgorithmEcKeyParams>(importParams))); + case CryptoAlgorithmParameters::Class::HmacKeyParams: + return makeUnique<CryptoAlgorithmHmacKeyParams>(crossThreadCopy(downcast<CryptoAlgorithmHmacKeyParams>(importParams))); + case CryptoAlgorithmParameters::Class::RsaHashedImportParams: + return makeUnique<CryptoAlgorithmRsaHashedImportParams>(crossThreadCopy(downcast<CryptoAlgorithmRsaHashedImportParams>(importParams))); + default: + ASSERT_NOT_REACHED(); + return nullptr; + } +} + +void SubtleCrypto::addAuthenticatedEncryptionWarningIfNecessary(CryptoAlgorithmIdentifier algorithmIdentifier) +{ + // if (algorithmIdentifier == CryptoAlgorithmIdentifier::AES_CBC || algorithmIdentifier == CryptoAlgorithmIdentifier::AES_CTR) { + // if (!scriptExecutionContext()->hasLoggedAuthenticatedEncryptionWarning()) { + // scriptExecutionContext()->addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "AES-CBC and AES-CTR do not provide authentication by default, and implementing it manually can result in minor, but serious mistakes. We recommended using authenticated encryption like AES-GCM to protect against chosen-ciphertext attacks."_s); + // scriptExecutionContext()->setHasLoggedAuthenticatedEncryptionWarning(true); + // } + // } +} + +// MARK: - Exposed functions. + +void SubtleCrypto::encrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& dataBufferSource, Ref<DeferredPromise>&& promise) +{ + addAuthenticatedEncryptionWarningIfNecessary(key.algorithmIdentifier()); + + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::Encrypt); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto data = copyToVector(WTFMove(dataBufferSource)); + + if (params->identifier != key.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!key.allows(CryptoKeyUsageEncrypt)) { + promise->reject(InvalidAccessError, "CryptoKey doesn't support encryption"_s); + return; + } + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(key.algorithmIdentifier()); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](const Vector<uint8_t>& cipherText) mutable { + if (auto promise = getPromise(index, weakThis)) + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), cipherText.data(), cipherText.size()); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->encrypt(*params, key, WTFMove(data), WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::decrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& dataBufferSource, Ref<DeferredPromise>&& promise) +{ + addAuthenticatedEncryptionWarningIfNecessary(key.algorithmIdentifier()); + + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::Decrypt); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto data = copyToVector(WTFMove(dataBufferSource)); + + if (params->identifier != key.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!key.allows(CryptoKeyUsageDecrypt)) { + promise->reject(InvalidAccessError, "CryptoKey doesn't support decryption"_s); + return; + } + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(key.algorithmIdentifier()); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](const Vector<uint8_t>& plainText) mutable { + if (auto promise = getPromise(index, weakThis)) + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), plainText.data(), plainText.size()); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->decrypt(*params, key, WTFMove(data), WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::sign(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& dataBufferSource, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::Sign); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto data = copyToVector(WTFMove(dataBufferSource)); + + if (params->identifier != key.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!key.allows(CryptoKeyUsageSign)) { + promise->reject(InvalidAccessError, "CryptoKey doesn't support signing"_s); + return; + } + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(key.algorithmIdentifier()); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](const Vector<uint8_t>& signature) mutable { + if (auto promise = getPromise(index, weakThis)) + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), signature.data(), signature.size()); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->sign(*params, key, WTFMove(data), WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::verify(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& signatureBufferSource, BufferSource&& dataBufferSource, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::Verify); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto signature = copyToVector(WTFMove(signatureBufferSource)); + auto data = copyToVector(WTFMove(dataBufferSource)); + + if (params->identifier != key.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!key.allows(CryptoKeyUsageVerify)) { + promise->reject(InvalidAccessError, "CryptoKey doesn't support verification"_s); + return; + } + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(key.algorithmIdentifier()); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](bool result) mutable { + if (auto promise = getPromise(index, weakThis)) + promise->resolve<IDLBoolean>(result); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->verify(*params, key, WTFMove(signature), WTFMove(data), WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::digest(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, BufferSource&& dataBufferSource, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::Digest); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto data = copyToVector(WTFMove(dataBufferSource)); + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(params->identifier); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](const Vector<uint8_t>& digest) mutable { + if (auto promise = getPromise(index, weakThis)) + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), digest.data(), digest.size()); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->digest(WTFMove(data), WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::generateKey(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, bool extractable, Vector<CryptoKeyUsage>&& keyUsages, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::GenerateKey); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto keyUsagesBitmap = toCryptoKeyUsageBitmap(keyUsages); + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(params->identifier); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](KeyOrKeyPair&& keyOrKeyPair) mutable { + if (auto promise = getPromise(index, weakThis)) { + WTF::switchOn( + keyOrKeyPair, + [&promise](RefPtr<CryptoKey>& key) { + if ((key->type() == CryptoKeyType::Private || key->type() == CryptoKeyType::Secret) && !key->usagesBitmap()) { + rejectWithException(promise.releaseNonNull(), SyntaxError); + return; + } + promise->resolve<IDLInterface<CryptoKey>>(*key); + }, + [&promise](CryptoKeyPair& keyPair) { + if (!keyPair.privateKey->usagesBitmap()) { + rejectWithException(promise.releaseNonNull(), SyntaxError); + return; + } + promise->resolve<IDLDictionary<CryptoKeyPair>>(keyPair); + }); + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + // The 26 January 2017 version of the specification suggests we should perform the following task asynchronously + // regardless what kind of keys it produces: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey + // That's simply not efficient for AES, HMAC and EC keys. Therefore, we perform it as an async task only for RSA keys. + algorithm->generateKey(*params, extractable, keyUsagesBitmap, WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext()); +} + +void SubtleCrypto::deriveKey(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& baseKey, AlgorithmIdentifier&& derivedKeyType, bool extractable, Vector<CryptoKeyUsage>&& keyUsages, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::DeriveBits); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto importParamsOrException = normalizeCryptoAlgorithmParameters(state, derivedKeyType, Operations::ImportKey); + if (importParamsOrException.hasException()) { + promise->reject(importParamsOrException.releaseException()); + return; + } + auto importParams = importParamsOrException.releaseReturnValue(); + + auto getLengthParamsOrException = normalizeCryptoAlgorithmParameters(state, derivedKeyType, Operations::GetKeyLength); + if (getLengthParamsOrException.hasException()) { + promise->reject(getLengthParamsOrException.releaseException()); + return; + } + auto getLengthParams = getLengthParamsOrException.releaseReturnValue(); + + auto keyUsagesBitmap = toCryptoKeyUsageBitmap(keyUsages); + + if (params->identifier != baseKey.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!baseKey.allows(CryptoKeyUsageDeriveKey)) { + promise->reject(InvalidAccessError, "CryptoKey doesn't support CryptoKey derivation"_s); + return; + } + + auto getLengthAlgorithm = CryptoAlgorithmRegistry::singleton().create(getLengthParams->identifier); + + auto result = getLengthAlgorithm->getKeyLength(*getLengthParams); + if (result.hasException()) { + promise->reject(result.releaseException().code(), "Cannot get key length from derivedKeyType"_s); + return; + } + size_t length = result.releaseReturnValue(); + + auto importAlgorithm = CryptoAlgorithmRegistry::singleton().create(importParams->identifier); + auto algorithm = CryptoAlgorithmRegistry::singleton().create(params->identifier); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis, importAlgorithm = WTFMove(importAlgorithm), importParams = crossThreadCopyImportParams(*importParams), extractable, keyUsagesBitmap](const Vector<uint8_t>& derivedKey) mutable { + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=169395 + KeyData data = derivedKey; + auto callback = [index, weakThis](CryptoKey& key) mutable { + if (auto promise = getPromise(index, weakThis)) { + if ((key.type() == CryptoKeyType::Private || key.type() == CryptoKeyType::Secret) && !key.usagesBitmap()) { + rejectWithException(promise.releaseNonNull(), SyntaxError); + return; + } + promise->resolve<IDLInterface<CryptoKey>>(key); + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + importAlgorithm->importKey(SubtleCrypto::KeyFormat::Raw, WTFMove(data), *importParams, extractable, keyUsagesBitmap, WTFMove(callback), WTFMove(exceptionCallback)); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->deriveBits(*params, baseKey, length, WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::deriveBits(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& baseKey, unsigned length, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::DeriveBits); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + if (params->identifier != baseKey.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!baseKey.allows(CryptoKeyUsageDeriveBits)) { + promise->reject(InvalidAccessError, "CryptoKey doesn't support bits derivation"_s); + return; + } + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(params->identifier); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](const Vector<uint8_t>& derivedKey) mutable { + if (auto promise = getPromise(index, weakThis)) + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), derivedKey.data(), derivedKey.size()); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + algorithm->deriveBits(*params, baseKey, length, WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +void SubtleCrypto::importKey(JSC::JSGlobalObject& state, KeyFormat format, KeyDataVariant&& keyDataVariant, AlgorithmIdentifier&& algorithmIdentifier, bool extractable, Vector<CryptoKeyUsage>&& keyUsages, Ref<DeferredPromise>&& promise) +{ + auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTFMove(algorithmIdentifier), Operations::ImportKey); + if (paramsOrException.hasException()) { + promise->reject(paramsOrException.releaseException()); + return; + } + auto params = paramsOrException.releaseReturnValue(); + + auto keyDataOrNull = toKeyData(format, WTFMove(keyDataVariant), promise); + if (!keyDataOrNull) { + // When toKeyData, it means the promise has been rejected, and we should return. + return; + } + + auto keyData = *keyDataOrNull; + auto keyUsagesBitmap = toCryptoKeyUsageBitmap(keyUsages); + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(params->identifier); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](CryptoKey& key) mutable { + if (auto promise = getPromise(index, weakThis)) { + if ((key.type() == CryptoKeyType::Private || key.type() == CryptoKeyType::Secret) && !key.usagesBitmap()) { + rejectWithException(promise.releaseNonNull(), SyntaxError); + return; + } + promise->resolve<IDLInterface<CryptoKey>>(key); + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + // The 11 December 2014 version of the specification suggests we should perform the following task asynchronously: + // https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-importKey + // It is not beneficial for less time consuming operations. Therefore, we perform it synchronously. + algorithm->importKey(format, WTFMove(keyData), *params, extractable, keyUsagesBitmap, WTFMove(callback), WTFMove(exceptionCallback)); +} + +void SubtleCrypto::exportKey(KeyFormat format, CryptoKey& key, Ref<DeferredPromise>&& promise) +{ + if (!isSupportedExportKey(key.algorithmIdentifier())) { + promise->reject(Exception { NotSupportedError }); + return; + } + + if (!key.extractable()) { + promise->reject(InvalidAccessError, "The CryptoKey is nonextractable"_s); + return; + } + + auto algorithm = CryptoAlgorithmRegistry::singleton().create(key.algorithmIdentifier()); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis](SubtleCrypto::KeyFormat format, KeyData&& key) mutable { + if (auto promise = getPromise(index, weakThis)) { + switch (format) { + case SubtleCrypto::KeyFormat::Spki: + case SubtleCrypto::KeyFormat::Pkcs8: + case SubtleCrypto::KeyFormat::Raw: { + Vector<uint8_t>& rawKey = std::get<Vector<uint8_t>>(key); + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), rawKey.data(), rawKey.size()); + return; + } + case SubtleCrypto::KeyFormat::Jwk: + promise->resolve<IDLDictionary<JsonWebKey>>(WTFMove(std::get<JsonWebKey>(key))); + return; + } + ASSERT_NOT_REACHED(); + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + // The 11 December 2014 version of the specification suggests we should perform the following task asynchronously: + // https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey + // It is not beneficial for less time consuming operations. Therefore, we perform it synchronously. + algorithm->exportKey(format, key, WTFMove(callback), WTFMove(exceptionCallback)); +} + +void SubtleCrypto::wrapKey(JSC::JSGlobalObject& state, KeyFormat format, CryptoKey& key, CryptoKey& wrappingKey, AlgorithmIdentifier&& wrapAlgorithmIdentifier, Ref<DeferredPromise>&& promise) +{ + bool isEncryption = false; + + auto wrapParamsOrException = normalizeCryptoAlgorithmParameters(state, wrapAlgorithmIdentifier, Operations::WrapKey); + if (wrapParamsOrException.hasException()) { + ASSERT(wrapParamsOrException.exception().code() != ExistingExceptionError); + + wrapParamsOrException = normalizeCryptoAlgorithmParameters(state, wrapAlgorithmIdentifier, Operations::Encrypt); + if (wrapParamsOrException.hasException()) { + promise->reject(wrapParamsOrException.releaseException()); + return; + } + + isEncryption = true; + } + auto wrapParams = wrapParamsOrException.releaseReturnValue(); + + if (wrapParams->identifier != wrappingKey.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "Wrapping CryptoKey doesn't match AlgorithmIdentifier"_s); + return; + } + + if (!wrappingKey.allows(CryptoKeyUsageWrapKey)) { + promise->reject(InvalidAccessError, "Wrapping CryptoKey doesn't support wrapKey operation"_s); + return; + } + + if (!isSupportedExportKey(key.algorithmIdentifier())) { + promise->reject(Exception { NotSupportedError }); + return; + } + + if (!key.extractable()) { + promise->reject(InvalidAccessError, "The CryptoKey is nonextractable"_s); + return; + } + + auto exportAlgorithm = CryptoAlgorithmRegistry::singleton().create(key.algorithmIdentifier()); + auto wrapAlgorithm = CryptoAlgorithmRegistry::singleton().create(wrappingKey.algorithmIdentifier()); + + auto context = scriptExecutionContext(); + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis, wrapAlgorithm, wrappingKey = Ref { wrappingKey }, wrapParams = WTFMove(wrapParams), isEncryption, context, workQueue = m_workQueue](SubtleCrypto::KeyFormat format, KeyData&& key) mutable { + if (weakThis) { + if (auto promise = weakThis->m_pendingPromises.get(index)) { + Vector<uint8_t> bytes; + switch (format) { + case SubtleCrypto::KeyFormat::Spki: + case SubtleCrypto::KeyFormat::Pkcs8: + case SubtleCrypto::KeyFormat::Raw: + bytes = std::get<Vector<uint8_t>>(key); + break; + case SubtleCrypto::KeyFormat::Jwk: { + // FIXME: Converting to JS just to JSON-Stringify seems inefficient. We should find a way to go directly from the struct to JSON. + auto jwk = toJS<IDLDictionary<JsonWebKey>>(*(promise->globalObject()), *(promise->globalObject()), WTFMove(std::get<JsonWebKey>(key))); + String jwkString = JSONStringify(promise->globalObject(), jwk, 0); + CString jwkUTF8String = jwkString.utf8(StrictConversion); + bytes.append(jwkUTF8String.data(), jwkUTF8String.length()); + } + } + + auto callback = [index, weakThis](const Vector<uint8_t>& wrappedKey) mutable { + if (auto promise = getPromise(index, weakThis)) + fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), wrappedKey.data(), wrappedKey.size()); + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + if (!isEncryption) { + // The 11 December 2014 version of the specification suggests we should perform the following task asynchronously: + // https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-wrapKey + // It is not beneficial for less time consuming operations. Therefore, we perform it synchronously. + wrapAlgorithm->wrapKey(wrappingKey.get(), WTFMove(bytes), WTFMove(callback), WTFMove(exceptionCallback)); + return; + } + // The following operation should be performed asynchronously. + wrapAlgorithm->encrypt(*wrapParams, WTFMove(wrappingKey), WTFMove(bytes), WTFMove(callback), WTFMove(exceptionCallback), *context, workQueue); + } + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + // The following operation should be performed synchronously. + exportAlgorithm->exportKey(format, key, WTFMove(callback), WTFMove(exceptionCallback)); +} + +void SubtleCrypto::unwrapKey(JSC::JSGlobalObject& state, KeyFormat format, BufferSource&& wrappedKeyBufferSource, CryptoKey& unwrappingKey, AlgorithmIdentifier&& unwrapAlgorithmIdentifier, AlgorithmIdentifier&& unwrappedKeyAlgorithmIdentifier, bool extractable, Vector<CryptoKeyUsage>&& keyUsages, Ref<DeferredPromise>&& promise) +{ + auto wrappedKey = copyToVector(WTFMove(wrappedKeyBufferSource)); + + bool isDecryption = false; + + auto unwrapParamsOrException = normalizeCryptoAlgorithmParameters(state, unwrapAlgorithmIdentifier, Operations::UnwrapKey); + if (unwrapParamsOrException.hasException()) { + unwrapParamsOrException = normalizeCryptoAlgorithmParameters(state, unwrapAlgorithmIdentifier, Operations::Decrypt); + if (unwrapParamsOrException.hasException()) { + promise->reject(unwrapParamsOrException.releaseException()); + return; + } + + isDecryption = true; + } + auto unwrapParams = unwrapParamsOrException.releaseReturnValue(); + + auto unwrappedKeyAlgorithmOrException = normalizeCryptoAlgorithmParameters(state, unwrappedKeyAlgorithmIdentifier, Operations::ImportKey); + if (unwrappedKeyAlgorithmOrException.hasException()) { + promise->reject(unwrappedKeyAlgorithmOrException.releaseException()); + return; + } + auto unwrappedKeyAlgorithm = unwrappedKeyAlgorithmOrException.releaseReturnValue(); + + auto keyUsagesBitmap = toCryptoKeyUsageBitmap(keyUsages); + + if (unwrapParams->identifier != unwrappingKey.algorithmIdentifier()) { + promise->reject(InvalidAccessError, "Unwrapping CryptoKey doesn't match unwrap AlgorithmIdentifier"_s); + return; + } + + if (!unwrappingKey.allows(CryptoKeyUsageUnwrapKey)) { + promise->reject(InvalidAccessError, "Unwrapping CryptoKey doesn't support unwrapKey operation"_s); + return; + } + + auto importAlgorithm = CryptoAlgorithmRegistry::singleton().create(unwrappedKeyAlgorithm->identifier); + if (UNLIKELY(!importAlgorithm)) { + promise->reject(Exception { NotSupportedError }); + return; + } + + auto unwrapAlgorithm = CryptoAlgorithmRegistry::singleton().create(unwrappingKey.algorithmIdentifier()); + if (UNLIKELY(!unwrapAlgorithm)) { + promise->reject(Exception { NotSupportedError }); + return; + } + + auto index = promise.ptr(); + m_pendingPromises.add(index, WTFMove(promise)); + WeakPtr weakThis { *this }; + auto callback = [index, weakThis, format, importAlgorithm, unwrappedKeyAlgorithm = crossThreadCopyImportParams(*unwrappedKeyAlgorithm), extractable, keyUsagesBitmap](const Vector<uint8_t>& bytes) mutable { + if (weakThis) { + if (RefPtr promise = weakThis->m_pendingPromises.get(index)) { + KeyData keyData; + switch (format) { + case SubtleCrypto::KeyFormat::Spki: + case SubtleCrypto::KeyFormat::Pkcs8: + case SubtleCrypto::KeyFormat::Raw: + keyData = bytes; + break; + case SubtleCrypto::KeyFormat::Jwk: { + auto& state = *(promise->globalObject()); + auto& vm = state.vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + String jwkString(bytes.data(), bytes.size()); + JSLockHolder locker(vm); + auto jwkObject = JSONParse(&state, jwkString); + if (!jwkObject) { + promise->reject(DataError, "WrappedKey cannot be converted to a JSON object"_s); + return; + } + auto jwk = convert<IDLDictionary<JsonWebKey>>(state, jwkObject); + RETURN_IF_EXCEPTION(scope, void()); + normalizeJsonWebKey(jwk); + + keyData = jwk; + break; + } + } + + auto callback = [index, weakThis](CryptoKey& key) mutable { + if (auto promise = getPromise(index, weakThis)) { + if ((key.type() == CryptoKeyType::Private || key.type() == CryptoKeyType::Secret) && !key.usagesBitmap()) { + rejectWithException(promise.releaseNonNull(), SyntaxError); + return; + } + promise->resolve<IDLInterface<CryptoKey>>(key); + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + // The following operation should be performed synchronously. + importAlgorithm->importKey(format, WTFMove(keyData), *unwrappedKeyAlgorithm, extractable, keyUsagesBitmap, WTFMove(callback), WTFMove(exceptionCallback)); + } + } + }; + auto exceptionCallback = [index, weakThis](ExceptionCode ec) mutable { + if (auto promise = getPromise(index, weakThis)) + rejectWithException(promise.releaseNonNull(), ec); + }; + + if (!isDecryption) { + // The 11 December 2014 version of the specification suggests we should perform the following task asynchronously: + // https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey + // It is not beneficial for less time consuming operations. Therefore, we perform it synchronously. + unwrapAlgorithm->unwrapKey(unwrappingKey, WTFMove(wrappedKey), WTFMove(callback), WTFMove(exceptionCallback)); + return; + } + + unwrapAlgorithm->decrypt(*unwrapParams, unwrappingKey, WTFMove(wrappedKey), WTFMove(callback), WTFMove(exceptionCallback), *scriptExecutionContext(), m_workQueue); +} + +} + +#endif |