blob: 6a945a182ff0816cf6e3abedd30a0cf8d4d90ee3 [file] [log] [blame]
/*
* Copyright (C) 2016 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 "JSSubtleCrypto.h"
#if ENABLE(SUBTLE_CRYPTO)
#include "CryptoAlgorithm.h"
#include "CryptoAlgorithmRegistry.h"
#include "JSAesKeyGenParams.h"
#include "JSCryptoAlgorithmParameters.h"
#include "JSCryptoKey.h"
#include "JSCryptoKeyPair.h"
#include "JSDOMPromise.h"
#include "JSHmacKeyGenParams.h"
#include "JSRsaHashedKeyGenParams.h"
#include "JSRsaKeyGenParams.h"
#include "ScriptState.h"
#include <runtime/Error.h>
#include <runtime/IteratorOperations.h>
using namespace JSC;
namespace WebCore {
enum class Operations {
GenerateKey,
Digest,
};
static std::unique_ptr<CryptoAlgorithmParameters> normalizeCryptoAlgorithmParameters(ExecState&, JSValue, Operations);
static CryptoAlgorithmIdentifier toHashIdentifier(ExecState& state, JSValue value)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto digestParams = normalizeCryptoAlgorithmParameters(state, value, Operations::Digest);
RETURN_IF_EXCEPTION(scope, { });
return digestParams->identifier;
}
static std::unique_ptr<CryptoAlgorithmParameters> normalizeCryptoAlgorithmParameters(ExecState& state, JSValue value, Operations operation)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (value.isString()) {
JSObject* newParams = constructEmptyObject(&state);
newParams->putDirect(vm, Identifier::fromString(&vm, "name"), value);
return normalizeCryptoAlgorithmParameters(state, newParams, operation);
}
if (value.isObject()) {
auto params = convertDictionary<CryptoAlgorithmParameters>(state, value);
RETURN_IF_EXCEPTION(scope, nullptr);
CryptoAlgorithmIdentifier identifier;
if (!CryptoAlgorithmRegistry::singleton().getIdentifierForName(params.name, identifier)) {
setDOMException(&state, NOT_SUPPORTED_ERR);
return nullptr;
}
std::unique_ptr<CryptoAlgorithmParameters> result;
switch (operation) {
case Operations::GenerateKey:
switch (identifier) {
case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: {
auto params = convertDictionary<CryptoAlgorithmRsaKeyGenParams>(state, value);
RETURN_IF_EXCEPTION(scope, nullptr);
result = std::make_unique<CryptoAlgorithmRsaKeyGenParams>(params);
break;
}
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5:
case CryptoAlgorithmIdentifier::RSA_PSS:
case CryptoAlgorithmIdentifier::RSA_OAEP: {
auto params = convertDictionary<CryptoAlgorithmRsaHashedKeyGenParams>(state, value);
RETURN_IF_EXCEPTION(scope, nullptr);
params.hashIdentifier = toHashIdentifier(state, params.hash);
RETURN_IF_EXCEPTION(scope, nullptr);
result = std::make_unique<CryptoAlgorithmRsaHashedKeyGenParams>(params);
break;
}
case CryptoAlgorithmIdentifier::AES_CTR:
case CryptoAlgorithmIdentifier::AES_CBC:
case CryptoAlgorithmIdentifier::AES_CMAC:
case CryptoAlgorithmIdentifier::AES_GCM:
case CryptoAlgorithmIdentifier::AES_CFB:
case CryptoAlgorithmIdentifier::AES_KW: {
auto params = convertDictionary<CryptoAlgorithmAesKeyGenParams>(state, value);
RETURN_IF_EXCEPTION(scope, nullptr);
result = std::make_unique<CryptoAlgorithmAesKeyGenParams>(params);
break;
}
case CryptoAlgorithmIdentifier::HMAC: {
auto params = convertDictionary<CryptoAlgorithmHmacKeyGenParams>(state, value);
RETURN_IF_EXCEPTION(scope, nullptr);
params.hashIdentifier = toHashIdentifier(state, params.hash);
RETURN_IF_EXCEPTION(scope, nullptr);
result = std::make_unique<CryptoAlgorithmHmacKeyGenParams>(params);
break;
}
default:
setDOMException(&state, NOT_SUPPORTED_ERR);
return nullptr;
}
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 = std::make_unique<CryptoAlgorithmParameters>(params);
break;
default:
setDOMException(&state, NOT_SUPPORTED_ERR);
return nullptr;
}
break;
default:
ASSERT_NOT_REACHED();
return nullptr;
}
result->identifier = identifier;
return result;
}
throwTypeError(&state, scope, ASCIILiteral("Invalid AlgorithmIdentifier"));
return nullptr;
}
static CryptoKeyUsage cryptoKeyUsagesFromJSValue(ExecState& state, JSValue iterable)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
CryptoKeyUsage result = 0;
forEachInIterable(&state, iterable, [&result](VM& vm, ExecState* state, JSValue nextItem) {
auto scope = DECLARE_THROW_SCOPE(vm);
String usageString = nextItem.toWTFString(state);
RETURN_IF_EXCEPTION(scope, void());
if (usageString == "encrypt")
result |= CryptoKeyUsageEncrypt;
else if (usageString == "decrypt")
result |= CryptoKeyUsageDecrypt;
else if (usageString == "sign")
result |= CryptoKeyUsageSign;
else if (usageString == "verify")
result |= CryptoKeyUsageVerify;
else if (usageString == "deriveKey")
result |= CryptoKeyUsageDeriveKey;
else if (usageString == "deriveBits")
result |= CryptoKeyUsageDeriveBits;
else if (usageString == "wrapKey")
result |= CryptoKeyUsageWrapKey;
else if (usageString == "unwrapKey")
result |= CryptoKeyUsageUnwrapKey;
else
throwTypeError(state, scope, ASCIILiteral("Invalid KeyUsages"));
});
RETURN_IF_EXCEPTION(scope, 0);
return result;
}
static RefPtr<CryptoAlgorithm> createAlgorithm(ExecState& state, CryptoAlgorithmIdentifier identifier)
{
auto result = CryptoAlgorithmRegistry::singleton().create(identifier);
if (!result)
setDOMException(&state, NOT_SUPPORTED_ERR);
return result;
}
// Maybe we want more specific error messages?
static void rejectWithException(Ref<DeferredPromise>&& passedPromise, ExceptionCode ec)
{
switch (ec) {
case NOT_SUPPORTED_ERR:
passedPromise->reject(ec, ASCIILiteral("The algorithm is not supported"));
return;
case SYNTAX_ERR:
passedPromise->reject(ec, ASCIILiteral("A required parameter was missing or out-of-range"));
return;
case INVALID_STATE_ERR:
passedPromise->reject(ec, ASCIILiteral("The requested operation is not valid for the current state of the provided key"));
return;
case INVALID_ACCESS_ERR:
passedPromise->reject(ec, ASCIILiteral("The requested operation is not valid for the provided key"));
return;
case UnknownError:
passedPromise->reject(ec, ASCIILiteral("The operation failed for an unknown transient reason (e.g. out of memory)"));
return;
case DataError:
passedPromise->reject(ec, ASCIILiteral("Data provided to an operation does not meet requirements"));
return;
case OperationError:
passedPromise->reject(ec, ASCIILiteral("The operation failed for an operation-specific reason"));
return;
}
ASSERT_NOT_REACHED();
}
static void jsSubtleCryptoFunctionGenerateKeyPromise(ExecState& state, Ref<DeferredPromise>&& promise)
{
VM& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (UNLIKELY(state.argumentCount() < 3)) {
promise->reject<JSValue>(createNotEnoughArgumentsError(&state));
return;
}
auto params = normalizeCryptoAlgorithmParameters(state, state.uncheckedArgument(0), Operations::GenerateKey);
RETURN_IF_EXCEPTION(scope, void());
auto extractable = state.uncheckedArgument(1).toBoolean(&state);
RETURN_IF_EXCEPTION(scope, void());
auto keyUsages = cryptoKeyUsagesFromJSValue(state, state.argument(2));
RETURN_IF_EXCEPTION(scope, void());
auto algorithm = createAlgorithm(state, params->identifier);
RETURN_IF_EXCEPTION(scope, void());
auto callback = [capturedPromise = promise.copyRef()](CryptoKey* key, CryptoKeyPair* keyPair) mutable {
ASSERT(key || keyPair);
ASSERT(!key || !keyPair);
if (key) {
if ((key->type() == CryptoKeyType::Private || key->type() == CryptoKeyType::Secret) && !key->usagesBitmap()) {
rejectWithException(WTFMove(capturedPromise), SYNTAX_ERR);
return;
}
capturedPromise->resolve(key);
} else {
if (!keyPair->privateKey()->usagesBitmap()) {
rejectWithException(WTFMove(capturedPromise), SYNTAX_ERR);
return;
}
capturedPromise->resolve(keyPair);
}
};
auto exceptionCallback = [capturedPromise = promise.copyRef()](ExceptionCode ec) mutable {
rejectWithException(WTFMove(capturedPromise), ec);
};
// The spec suggests we should perform the following task asynchronously regardless what kind of keys it produces
// as of 11 December 2014: https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-generateKey
// That's simply not efficient for AES and HMAC keys. Therefore, we perform it as an async task conditionally.
algorithm->generateKey(WTFMove(params), extractable, keyUsages, WTFMove(callback), WTFMove(exceptionCallback), scriptExecutionContextFromExecState(&state));
}
JSValue JSSubtleCrypto::generateKey(ExecState& state)
{
return callPromiseFunction<jsSubtleCryptoFunctionGenerateKeyPromise, PromiseExecutionScope::WindowOrWorker>(state);
}
} // namespace WebCore
#endif