| /* |
| * 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 (auto 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 |