1190 lines
54 KiB
C++
1190 lines
54 KiB
C++
/*
|
|
* 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 (WTF::holds_alternative<String>(algorithmIdentifier)) {
|
|
auto newParams = Strong<JSObject>(vm, constructEmptyObject(&state));
|
|
newParams->putDirect(vm, Identifier::fromString(vm, "name"), jsString(vm, WTF::get<String>(algorithmIdentifier)));
|
|
|
|
return normalizeCryptoAlgorithmParameters(state, newParams, operation);
|
|
}
|
|
|
|
auto& value = WTF::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"));
|
|
JSValue publicValue = value.get()->get(&state, Identifier::fromString(vm, "public"));
|
|
JSObject* newValue = constructEmptyObject(&state);
|
|
newValue->putDirect(vm, Identifier::fromString(vm, "name"), nameValue);
|
|
newValue->putDirect(vm, Identifier::fromString(vm, "publicKey"), 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> subtleCryptoWeakPointer)
|
|
{
|
|
if (subtleCryptoWeakPointer)
|
|
return subtleCryptoWeakPointer->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)
|
|
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.");
|
|
}
|
|
|
|
// 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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](const Vector<uint8_t>& cipherText) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), cipherText.data(), cipherText.size());
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](const Vector<uint8_t>& plainText) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), plainText.data(), plainText.size());
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](const Vector<uint8_t>& signature) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), signature.data(), signature.size());
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](bool result) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
promise->resolve<IDLBoolean>(result);
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](const Vector<uint8_t>& digest) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), digest.data(), digest.size());
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](KeyOrKeyPair&& keyOrKeyPair) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer)) {
|
|
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, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer, 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, subtleCryptoWeakPointer](CryptoKey& key) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer)) {
|
|
if ((key.type() == CryptoKeyType::Private || key.type() == CryptoKeyType::Secret) && !key.usagesBitmap()) {
|
|
rejectWithException(promise.releaseNonNull(), SyntaxError);
|
|
return;
|
|
}
|
|
promise->resolve<IDLInterface<CryptoKey>>(key);
|
|
}
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
rejectWithException(promise.releaseNonNull(), ec);
|
|
};
|
|
|
|
importAlgorithm->importKey(SubtleCrypto::KeyFormat::Raw, WTFMove(data), *importParams, extractable, keyUsagesBitmap, WTFMove(callback), WTFMove(exceptionCallback));
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](const Vector<uint8_t>& derivedKey) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), derivedKey.data(), derivedKey.size());
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](CryptoKey& key) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer)) {
|
|
if ((key.type() == CryptoKeyType::Private || key.type() == CryptoKeyType::Secret) && !key.usagesBitmap()) {
|
|
rejectWithException(promise.releaseNonNull(), SyntaxError);
|
|
return;
|
|
}
|
|
promise->resolve<IDLInterface<CryptoKey>>(key);
|
|
}
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer](SubtleCrypto::KeyFormat format, KeyData&& key) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer)) {
|
|
switch (format) {
|
|
case SubtleCrypto::KeyFormat::Spki:
|
|
case SubtleCrypto::KeyFormat::Pkcs8:
|
|
case SubtleCrypto::KeyFormat::Raw: {
|
|
Vector<uint8_t>& rawKey = WTF::get<Vector<uint8_t>>(key);
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), rawKey.data(), rawKey.size());
|
|
return;
|
|
}
|
|
case SubtleCrypto::KeyFormat::Jwk:
|
|
promise->resolve<IDLDictionary<JsonWebKey>>(WTFMove(WTF::get<JsonWebKey>(key)));
|
|
return;
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer, wrapAlgorithm, wrappingKey = makeRef(wrappingKey), wrapParams = WTFMove(wrapParams), isEncryption, context, workQueue = m_workQueue](SubtleCrypto::KeyFormat format, KeyData&& key) mutable {
|
|
if (subtleCryptoWeakPointer) {
|
|
if (auto promise = subtleCryptoWeakPointer->m_pendingPromises.get(index)) {
|
|
Vector<uint8_t> bytes;
|
|
switch (format) {
|
|
case SubtleCrypto::KeyFormat::Spki:
|
|
case SubtleCrypto::KeyFormat::Pkcs8:
|
|
case SubtleCrypto::KeyFormat::Raw:
|
|
bytes = WTF::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(WTF::get<JsonWebKey>(key)));
|
|
String jwkString = JSONStringify(promise->globalObject(), jwk, 0);
|
|
CString jwkUtf8String = jwkString.utf8(StrictConversion);
|
|
bytes.append(jwkUtf8String.data(), jwkUtf8String.length());
|
|
}
|
|
}
|
|
|
|
auto callback = [index, subtleCryptoWeakPointer](const Vector<uint8_t>& wrappedKey) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
fulfillPromiseWithArrayBuffer(promise.releaseNonNull(), wrappedKey.data(), wrappedKey.size());
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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));
|
|
auto subtleCryptoWeakPointer = makeWeakPtr(*this);
|
|
auto callback = [index, subtleCryptoWeakPointer, format, importAlgorithm, unwrappedKeyAlgorithm = crossThreadCopyImportParams(*unwrappedKeyAlgorithm), extractable, keyUsagesBitmap](const Vector<uint8_t>& bytes) mutable {
|
|
if (subtleCryptoWeakPointer) {
|
|
if (auto promise = subtleCryptoWeakPointer->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, subtleCryptoWeakPointer](CryptoKey& key) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer)) {
|
|
if ((key.type() == CryptoKeyType::Private || key.type() == CryptoKeyType::Secret) && !key.usagesBitmap()) {
|
|
rejectWithException(promise.releaseNonNull(), SyntaxError);
|
|
return;
|
|
}
|
|
promise->resolve<IDLInterface<CryptoKey>>(key);
|
|
}
|
|
};
|
|
auto exceptionCallback = [index, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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, subtleCryptoWeakPointer](ExceptionCode ec) mutable {
|
|
if (auto promise = getPromise(index, subtleCryptoWeakPointer))
|
|
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
|