Commit abd0bbca authored by ap@apple.com's avatar ap@apple.com

Implement WebCrypto wrapKey

https://bugs.webkit.org/show_bug.cgi?id=124738

Reviewed by Anders Carlsson.

Source/WebCore: 

Tests: crypto/subtle/aes-cbc-wrap-rsa-non-extractable.html
       crypto/subtle/aes-cbc-wrap-rsa.html

* bindings/js/JSSubtleCryptoCustom.cpp:
(WebCore::exportKey): Factored out the actual operation that can be chained with
encryption for wrapKey.
(WebCore::JSSubtleCrypto::exportKey):
(WebCore::JSSubtleCrypto::wrapKey):
(WebCore::JSSubtleCrypto::unwrapKey): Fixed a memory leak in failure code path.

* crypto/SubtleCrypto.idl: Added wrapKey.

LayoutTests: 

* crypto/subtle/aes-cbc-wrap-rsa-expected.txt: Added.
* crypto/subtle/aes-cbc-wrap-rsa-non-extractable-expected.txt: Added.
* crypto/subtle/aes-cbc-wrap-rsa-non-extractable.html: Added.
* crypto/subtle/aes-cbc-wrap-rsa.html: Added.

* crypto/subtle/aes-export-key-expected.txt:
* crypto/subtle/hmac-export-key-expected.txt:
There is no longer a console message, the error is in an exception.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159644 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 35569e79
2013-11-21 Alexey Proskuryakov <ap@apple.com>
Implement WebCrypto wrapKey
https://bugs.webkit.org/show_bug.cgi?id=124738
Reviewed by Anders Carlsson.
* crypto/subtle/aes-cbc-wrap-rsa-expected.txt: Added.
* crypto/subtle/aes-cbc-wrap-rsa-non-extractable-expected.txt: Added.
* crypto/subtle/aes-cbc-wrap-rsa-non-extractable.html: Added.
* crypto/subtle/aes-cbc-wrap-rsa.html: Added.
* crypto/subtle/aes-export-key-expected.txt:
* crypto/subtle/hmac-export-key-expected.txt:
There is no longer a console message, the error is in an exception.
2013-11-21 Radu Stavila <stavila@adobe.com>
Added test for :hover and javascript events on the visual overflow of a region.
......
Test wrapping an RSA key with AES-CBC.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
Importing a key to wrap...
PASS key.algorithm.name is 'rsassa-pkcs1-v1_5'
Importing a key encryption key...
PASS wrappingKey.algorithm.name is 'aes-cbc'
PASS wrappedKey.toString() is '[object ArrayBuffer]'
PASS successfullyParsed is true
TEST COMPLETE
Test wrapping an RSA key with AES-CBC.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
Importing a key to wrap...
PASS key.algorithm.name is 'rsassa-pkcs1-v1_5'
Importing a key encryption key...
PASS wrappingKey.algorithm.name is 'aes-cbc'
wrapping the key...
PASS Rejected
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<script src="../../resources/js-test-pre.js"></script>
<script src="resources/common.js"></script>
</head>
<body>
<p id="description"></p>
<div id="console"></div>
<script>
description("Test wrapping an RSA key with AES-CBC.");
jsTestIsAsync = true;
var extractable = true;
var nonExtractable = false;
var publicKeyJSON = {
kty: "RSA",
alg: "RS256",
n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw",
e: "AQAB",
extractable: false,
use: "sig"
};
var jwkKeyAsArrayBuffer = asciiToUint8Array(JSON.stringify(publicKeyJSON));
var wrappingKeyOctets = hexStringToUint8Array("2a00e0e776e94e4dc89bf947cebdebe1");
debug("Importing a key to wrap...");
crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, null, extractable, ["sign", "verify"]).then(function(result) {
key = result;
shouldBe("key.algorithm.name", "'rsassa-pkcs1-v1_5'");
debug("Importing a key encryption key...");
return crypto.subtle.importKey("raw", wrappingKeyOctets, "AES-CBC", nonExtractable, ["wrapKey"]);
}).then(function(result) {
wrappingKey = result;
shouldBe("wrappingKey.algorithm.name", "'aes-cbc'");
var wrapAlgorithm = {name: "AES-CBC", iv: hexStringToUint8Array("000102030405060708090a0b0c0d0e0f")};
debug("wrapping the key...");
return crypto.subtle.wrapKey("jwk", key, wrappingKey, wrapAlgorithm);
}).then(undefined, function() {
testPassed("Rejected");
finishJSTest();
});
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../../resources/js-test-pre.js"></script>
<script src="resources/common.js"></script>
</head>
<body>
<p id="description"></p>
<div id="console"></div>
<script>
description("Test wrapping an RSA key with AES-CBC.");
jsTestIsAsync = true;
var extractable = true;
var nonExtractable = false;
var publicKeyJSON = {
kty: "RSA",
alg: "RS256",
n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw",
e: "AQAB",
extractable: true,
use: "sig"
};
var jwkKeyAsArrayBuffer = asciiToUint8Array(JSON.stringify(publicKeyJSON));
var wrappingKeyOctets = hexStringToUint8Array("2a00e0e776e94e4dc89bf947cebdebe1");
debug("Importing a key to wrap...");
crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, null, extractable, ["sign", "verify"]).then(function(result) {
key = result;
shouldBe("key.algorithm.name", "'rsassa-pkcs1-v1_5'");
debug("Importing a key encryption key...");
return crypto.subtle.importKey("raw", wrappingKeyOctets, "AES-CBC", nonExtractable, ["wrapKey"]);
}).then(function(result) {
wrappingKey = result;
shouldBe("wrappingKey.algorithm.name", "'aes-cbc'");
var wrapAlgorithm = {name: "AES-CBC", iv: hexStringToUint8Array("000102030405060708090a0b0c0d0e0f")};
return crypto.subtle.wrapKey("jwk", key, wrappingKey, wrapAlgorithm);
}).then(function(result) {
wrappedKey = result;
shouldBe("wrappedKey.toString()", "'[object ArrayBuffer]'");
finishJSTest();
});
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
CONSOLE MESSAGE: Key is not extractable
CONSOLE MESSAGE: Key is not extractable
Test exporting an AES key.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
......
CONSOLE MESSAGE: Key is not extractable
CONSOLE MESSAGE: Key is not extractable
Test exporting an AES key.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
......
2013-11-21 Alexey Proskuryakov <ap@apple.com>
Implement WebCrypto wrapKey
https://bugs.webkit.org/show_bug.cgi?id=124738
Reviewed by Anders Carlsson.
Tests: crypto/subtle/aes-cbc-wrap-rsa-non-extractable.html
crypto/subtle/aes-cbc-wrap-rsa.html
* bindings/js/JSSubtleCryptoCustom.cpp:
(WebCore::exportKey): Factored out the actual operation that can be chained with
encryption for wrapKey.
(WebCore::JSSubtleCrypto::exportKey):
(WebCore::JSSubtleCrypto::wrapKey):
(WebCore::JSSubtleCrypto::unwrapKey): Fixed a memory leak in failure code path.
* crypto/SubtleCrypto.idl: Added wrapKey.
2013-11-21 Alexey Proskuryakov <ap@apple.com>
Implement WebCrypto unwrapKey
......@@ -563,6 +563,38 @@ JSValue JSSubtleCrypto::importKey(ExecState* exec)
return promise;
}
static void exportKey(ExecState* exec, CryptoKeyFormat keyFormat, const CryptoKey& key, CryptoAlgorithm::VectorCallback callback, CryptoAlgorithm::VoidCallback failureCallback)
{
if (!key.extractable()) {
throwTypeError(exec, "Key is not extractable");
return;
}
switch (keyFormat) {
case CryptoKeyFormat::Raw: {
Vector<uint8_t> result;
if (CryptoKeySerializationRaw::serialize(key, result))
callback(result);
else
failureCallback();
break;
}
case CryptoKeyFormat::JWK: {
String result = JSCryptoKeySerializationJWK::serialize(exec, key);
if (exec->hadException())
return;
CString utf8String = result.utf8(StrictConversion);
Vector<uint8_t> resultBuffer;
resultBuffer.append(utf8String.data(), utf8String.length());
callback(resultBuffer);
break;
}
default:
throwTypeError(exec, "Unsupported key format for export");
break;
}
}
JSValue JSSubtleCrypto::exportKey(ExecState* exec)
{
if (exec->argumentCount() < 2)
......@@ -581,35 +613,83 @@ JSValue JSSubtleCrypto::exportKey(ExecState* exec)
JSPromise* promise = JSPromise::createWithResolver(exec->vm(), globalObject());
PromiseWrapper promiseWrapper(globalObject(), promise);
if (!key->extractable()) {
m_impl->document()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, "Key is not extractable");
auto successCallback = [promiseWrapper](const Vector<uint8_t>& result) mutable {
promiseWrapper.fulfill(result);
};
auto failureCallback = [promiseWrapper]() mutable {
promiseWrapper.reject(nullptr);
return promise;
};
WebCore::exportKey(exec, keyFormat, *key, successCallback, failureCallback);
if (exec->hadException())
return jsUndefined();
return promise;
}
JSValue JSSubtleCrypto::wrapKey(ExecState* exec)
{
if (exec->argumentCount() < 4)
return exec->vm().throwException(exec, createNotEnoughArgumentsError(exec));
CryptoKeyFormat keyFormat;
if (!cryptoKeyFormatFromJSValue(exec, exec->argument(0), keyFormat)) {
ASSERT(exec->hadException());
return jsUndefined();
}
switch (keyFormat) {
case CryptoKeyFormat::Raw: {
Vector<uint8_t> result;
if (CryptoKeySerializationRaw::serialize(*key, result))
promiseWrapper.fulfill(result);
else {
m_impl->document()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, "Key cannot be exported to raw format");
promiseWrapper.reject(nullptr);
}
break;
RefPtr<CryptoKey> key = toCryptoKey(exec->uncheckedArgument(1));
if (!key)
return throwTypeError(exec);
RefPtr<CryptoKey> wrappingKey = toCryptoKey(exec->uncheckedArgument(2));
if (!key)
return throwTypeError(exec);
auto algorithm = createAlgorithmFromJSValue(exec, exec->uncheckedArgument(3));
if (!algorithm) {
ASSERT(exec->hadException());
return jsUndefined();
}
case CryptoKeyFormat::JWK: {
String result = JSCryptoKeySerializationJWK::serialize(exec, *key);
if (exec->hadException())
return jsUndefined();
CString utf8String = result.utf8(StrictConversion);
Vector<uint8_t> resultBuffer;
resultBuffer.append(utf8String.data(), utf8String.length());
promiseWrapper.fulfill(resultBuffer);
break;
auto parameters = JSCryptoAlgorithmDictionary::createParametersForEncrypt(exec, algorithm->identifier(), exec->uncheckedArgument(3));
if (!parameters) {
ASSERT(exec->hadException());
return jsUndefined();
}
default:
throwTypeError(exec, "Unsupported key format for export");
JSPromise* promise = JSPromise::createWithResolver(exec->vm(), globalObject());
PromiseWrapper promiseWrapper(globalObject(), promise);
CryptoAlgorithm* algorithmPtr = algorithm.release();
CryptoAlgorithmParameters* parametersPtr = parameters.release();
auto exportSuccessCallback = [keyFormat, algorithmPtr, parametersPtr, wrappingKey, promiseWrapper](const Vector<uint8_t>& exportedKeyData) mutable {
auto encryptSuccessCallback = [promiseWrapper](const Vector<uint8_t>& encryptedData) mutable {
promiseWrapper.fulfill(encryptedData);
};
auto encryptFailureCallback = [promiseWrapper]() mutable {
promiseWrapper.reject(nullptr);
};
ExceptionCode ec = 0;
algorithmPtr->encryptForWrapKey(*parametersPtr, *wrappingKey, std::make_pair(exportedKeyData.data(), exportedKeyData.size()), std::move(encryptSuccessCallback), std::move(encryptFailureCallback), ec);
if (ec) {
// FIXME: Report failure details to console, and possibly to calling script once there is a standardized way to pass errors to WebCrypto promise reject functions.
encryptFailureCallback();
}
};
auto exportFailureCallback = [promiseWrapper, algorithmPtr, parametersPtr]() mutable {
delete algorithmPtr;
delete parametersPtr;
promiseWrapper.reject(nullptr);
};
ExceptionCode ec = 0;
WebCore::exportKey(exec, keyFormat, *key, exportSuccessCallback, exportFailureCallback);
if (ec) {
setDOMException(exec, ec);
return jsUndefined();
}
......@@ -693,25 +773,30 @@ JSValue JSSubtleCrypto::unwrapKey(ExecState* exec)
CryptoAlgorithm* unwrappedKeyAlgorithmPtr = unwrappedKeyAlgorithm.release();
CryptoAlgorithmParameters* unwrappedKeyAlgorithmParametersPtr = unwrappedKeyAlgorithmParameters.release();
auto failureCallback = [promiseWrapper]() mutable {
promiseWrapper.reject(nullptr);
};
auto successCallback = [domGlobalObject, keyFormat, unwrappedKeyAlgorithmPtr, unwrappedKeyAlgorithmParametersPtr, extractable, keyUsages, promiseWrapper, failureCallback](const Vector<uint8_t>& result) mutable {
auto decryptSuccessCallback = [domGlobalObject, keyFormat, unwrappedKeyAlgorithmPtr, unwrappedKeyAlgorithmParametersPtr, extractable, keyUsages, promiseWrapper](const Vector<uint8_t>& result) mutable {
auto importSuccessCallback = [promiseWrapper](CryptoKey& key) mutable {
promiseWrapper.fulfill(&key);
};
auto importFailureCallback = [promiseWrapper]() mutable {
promiseWrapper.reject(nullptr);
};
ExecState* exec = domGlobalObject->globalExec();
WebCore::importKey(exec, keyFormat, std::make_pair(result.data(), result.size()), unwrappedKeyAlgorithmPtr, unwrappedKeyAlgorithmParametersPtr, extractable, keyUsages, importSuccessCallback, failureCallback);
WebCore::importKey(exec, keyFormat, std::make_pair(result.data(), result.size()), unwrappedKeyAlgorithmPtr, unwrappedKeyAlgorithmParametersPtr, extractable, keyUsages, importSuccessCallback, importFailureCallback);
if (exec->hadException()) {
// FIXME: Report exception details to console, and possibly to calling script once there is a standardized way to pass errors to WebCrypto promise reject functions.
exec->clearException();
failureCallback();
importFailureCallback();
}
};
auto decryptFailureCallback = [promiseWrapper, unwrappedKeyAlgorithmPtr, unwrappedKeyAlgorithmParametersPtr]() mutable {
delete unwrappedKeyAlgorithmPtr;
delete unwrappedKeyAlgorithmParametersPtr;
promiseWrapper.reject(nullptr);
};
ExceptionCode ec = 0;
unwrapAlgorithm->decryptForUnwrapKey(*unwrapAlgorithmParameters, *unwrappingKey, wrappedKeyData, std::move(successCallback), std::move(failureCallback), ec);
unwrapAlgorithm->decryptForUnwrapKey(*unwrapAlgorithmParameters, *unwrappingKey, wrappedKeyData, std::move(decryptSuccessCallback), std::move(decryptFailureCallback), ec);
if (ec) {
setDOMException(exec, ec);
return jsUndefined();
......
......@@ -37,5 +37,6 @@
[Custom] Promise generateKey(AlgorithmIdentifier algorithm, optional boolean extractable, optional KeyUsage[] keyUsages);
[Custom] Promise importKey(KeyFormat format, CryptoOperationData keyData, AlgorithmIdentifier? algorithm, optional boolean extractable, optional KeyUsage[] keyUsages);
[Custom] Promise exportKey(KeyFormat format, Key key);
[Custom] Promise wrapKey(KeyFormat format, Key key, Key wrappingKey, AlgorithmIdentifier wrapAlgorithm);
[Custom] Promise unwrapKey(KeyFormat format, CryptoOperationData wrappedKey, Key unwrappingKey, AlgorithmIdentifier unwrapAlgorithm, AlgorithmIdentifier? unwrappedKeyAlgorithm, optional boolean extractable, optional KeyUsage[] keyUsages);
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment