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

Support exporting symmetric keys as JWK

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

Reviewed by Sam Weinig.

Source/JavaScriptCore: 

* runtime/JSONObject.h: Export JSONStringify.

Source/WebCore: 

Error handling is not consistent yet - some errors cause exceptions, and others
result in rejected promises. This part of spec is incomplete, so I basically did
what was most straightforward in each case.

* bindings/js/JSCryptoKeySerializationJWK.h: 
* bindings/js/JSCryptoKeySerializationJWK.cpp:
(WebCore::JSCryptoKeySerializationJWK::reconcileUsages): Updated a comment with a better link.
(WebCore::JSCryptoKeySerializationJWK::buildJSONForOctetSequence): A helper to building JWK.
(WebCore::JSCryptoKeySerializationJWK::addToJSON): Ditto.
(WebCore::JSCryptoKeySerializationJWK::addBoolToJSON): Ditto.
(WebCore::JSCryptoKeySerializationJWK::addJWKAlgorithmToJSON): Ditto. The code for
mapping is my best guess, this all needs to be specified.
(WebCore::JSCryptoKeySerializationJWK::addJWKUseToJSON): A helper to building JWK.
(WebCore::JSCryptoKeySerializationJWK::serialize): Build a JSON string for the key.

* bindings/js/JSSubtleCryptoCustom.cpp:
(WebCore::JSSubtleCrypto::importKey): Updated a comment.
(WebCore::JSSubtleCrypto::exportKey): Use CryptoKeySerialization (also for raw keys,
for consistency).

* crypto/CryptoKey.h:
(WebCore::CryptoKey::algorithmIdentifier):
(WebCore::CryptoKey::usagesBitmap):
Exposed data needed for building JWK (it used to be only exposed in a form suitable
for DOM accessors).

* crypto/keys/CryptoKeyHMAC.h: Ditto, added an accessor for JWK.

* crypto/keys/CryptoKeySerializationRaw.cpp: (WebCore::CryptoKeySerializationRaw::serialize):
* crypto/keys/CryptoKeySerializationRaw.h:
Moved from JSSubtleCryptoCustom.cpp for consistency.

Source/WTF: 

Base64URL encoding doesn't use '=' padding, and doesn't need any other options.
Added this mode for encode, and removed policy arguments from exposed functions.

* wtf/text/Base64.cpp:
(WTF::base64EncodeInternal):
(WTF::base64URLEncode):
(WTF::base64URLDecode):
* wtf/text/Base64.h:
(WTF::base64URLEncode):

LayoutTests: 

* crypto/subtle/aes-export-key-expected.txt:
* crypto/subtle/aes-export-key.html:
* crypto/subtle/hmac-export-key-expected.txt:
* crypto/subtle/hmac-export-key.html:



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159377 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent c5d9e32c
2013-11-15 Alexey Proskuryakov <ap@apple.com>
Support exporting symmetric keys as JWK
https://bugs.webkit.org/show_bug.cgi?id=124442
Reviewed by Sam Weinig.
* crypto/subtle/aes-export-key-expected.txt:
* crypto/subtle/aes-export-key.html:
* crypto/subtle/hmac-export-key-expected.txt:
* crypto/subtle/hmac-export-key.html:
2013-11-15 Simon Fraser <simon.fraser@apple.com>
Make results.html more sane
......
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".
PASS crypto.subtle.exportKey("raw") threw exception TypeError: Not enough arguments.
PASS crypto.subtle.exportKey("raw", null) threw exception TypeError: Type error.
PASS crypto.subtle.exportKey("raw", undefined) threw exception TypeError: Type error.
PASS crypto.subtle.exportKey("raw", {}) threw exception TypeError: Type error.
PASS crypto.subtle.exportKey("raw", 1) threw exception TypeError: Type error.
Importing a JWK key...
PASS crypto.subtle.exportKey(null, key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey(undefined, key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey({}, key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey("", key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey("foobar", key) threw exception TypeError: Unknown key format.
Exporting the key as raw data...
PASS bytesToHexString(new Uint8Array(exportedData)) is '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'
Exporting the key as JWK...
PASS exportedJWK.kty is 'oct'
PASS exportedJWK.k is 'jnOw99oOZFLIEPMrgJB55WL46tJSLGt7'
PASS exportedJWK.alg is 'A192CBC'
PASS exportedJWK.extractable is true
PASS exportedJWK.use is 'enc'
Importing a key that's not extractable...
Trying to export as raw...
PASS Rejected, as expected
Trying to export as jwk...
PASS Rejected, as expected
Importing a key with only encrypt+decrypt usage (this doesn't map to anything in JWK, as 'enc' also allows wrap/unwrap)...
PASS crypto.subtle.exportKey("jwk", key) threw exception TypeError: Key usages cannot be represented in JWK. Only two variants are supported: sign+verify and encrypt+decrypt+wrapKey+unwrapKey.
PASS successfullyParsed is true
TEST COMPLETE
......
......@@ -14,24 +14,78 @@ description("Test exporting an AES key.");
jsTestIsAsync = true;
var extractable = true;
var nonExtractable = false;
var jwkKey = {
"kty": "oct",
"k": "jnOw99oOZFLIEPMrgJB55WL46tJSLGt7"
kty: "oct",
k: "jnOw99oOZFLIEPMrgJB55WL46tJSLGt7"
};
var jwkKeyAsArrayBuffer = asciiToUint8Array(JSON.stringify(jwkKey));
debug("Importing a JWK key...");
crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, "AES-CBC", extractable, []).then(function(result) {
shouldThrow('crypto.subtle.exportKey("raw")');
shouldThrow('crypto.subtle.exportKey("raw", null)');
shouldThrow('crypto.subtle.exportKey("raw", undefined)');
shouldThrow('crypto.subtle.exportKey("raw", {})');
shouldThrow('crypto.subtle.exportKey("raw", 1)');
debug("\nImporting a JWK key...");
crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, "AES-CBC", extractable, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']).then(function(result) {
key = result;
debug("Exporting the key as raw data...");
shouldThrow('crypto.subtle.exportKey(null, key)');
shouldThrow('crypto.subtle.exportKey(undefined, key)');
shouldThrow('crypto.subtle.exportKey({}, key)');
shouldThrow('crypto.subtle.exportKey("", key)');
shouldThrow('crypto.subtle.exportKey("foobar", key)');
debug("\nExporting the key as raw data...");
return crypto.subtle.exportKey("raw", key);
}).then(function(result) {
exportedData = result;
shouldBe("bytesToHexString(new Uint8Array(exportedData))", "'8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b'");
debug("Exporting the key as JWK...");
return crypto.subtle.exportKey("jwk", key);
}).then(function(result) {
exportedJWK = JSON.parse(result);
shouldBe("exportedJWK.kty", "'oct'");
shouldBe("exportedJWK.k", "'jnOw99oOZFLIEPMrgJB55WL46tJSLGt7'");
shouldBe("exportedJWK.alg", "'A192CBC'");
shouldBe("exportedJWK.extractable", "true");
shouldBe("exportedJWK.use", "'enc'");
debug("\nImporting a key that's not extractable...");
return crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, "AES-CBC", nonExtractable, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])
}).then(function(result) {
key = result;
debug("\nTrying to export as raw...");
return crypto.subtle.exportKey("raw", key);
}).then(function(result) {
testFailed("Promise wasn't rejected");
finishJSTest();
}, function() {
testPassed("Rejected, as expected");
debug("Trying to export as jwk...");
return crypto.subtle.exportKey("jwk", key);
}).then(function(result) {
testFailed("Promise wasn't rejected");
finishJSTest();
}, function() {
testPassed("Rejected, as expected");
// If this test starts to fail because a way to encode encrypt+decrypt is added to JWK, change to something that's not there.
// If all WebCrypto modes are added to JWK, then this test will be obsolete.
debug("\nImporting a key with only encrypt+decrypt usage (this doesn't map to anything in JWK, as 'enc' also allows wrap/unwrap)...");
return crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, "AES-CBC", extractable, ['encrypt', 'decrypt'])
}).then(function(result) {
key = result;
// Maybe this should be a rejected promise, not an exception. We'll need to find a way to provide meaningful error messages with such failures.
shouldThrow('crypto.subtle.exportKey("jwk", key)');
finishJSTest();
});
</script>
......
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".
Importing a JWK key...
PASS crypto.subtle.exportKey(null, key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey(undefined, key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey({}, key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey("", key) threw exception TypeError: Unknown key format.
PASS crypto.subtle.exportKey("foobar", key) threw exception TypeError: Unknown key format.
Exporting the key as raw data...
PASS bytesToHexString(new Uint8Array(exportedData)) is '6a18e49feff7f3b7e09ec89b7f6deab2f6a18e49feff7f3b7e09ec89b7f6deab'
Exporting the key as JWK...
PASS exportedJWK.kty is 'oct'
PASS exportedJWK.k is 'ahjkn-_387fgnsibf23qsvahjkn-_387fgnsibf23qs'
PASS exportedJWK.alg is 'HS256'
PASS exportedJWK.extractable is true
PASS exportedJWK.use is 'sig'
Importing a key that's not extractable...
Trying to export as raw...
PASS Rejected, as expected
Trying to export as jwk...
PASS Rejected, as expected
Importing a key with only sign usage (this doesn't map to anything in JWK, as 'sig' allows both sign and verify)...
PASS crypto.subtle.exportKey("jwk", key) threw exception TypeError: Key usages cannot be represented in JWK. Only two variants are supported: sign+verify and encrypt+decrypt+wrapKey+unwrapKey.
PASS successfullyParsed is true
TEST COMPLETE
......
......@@ -14,6 +14,7 @@ description("Test exporting an AES key.");
jsTestIsAsync = true;
var extractable = true;
var nonExtractable = false;
var jwkKey = {
"kty": "oct",
......@@ -23,15 +24,62 @@ var jwkKey = {
var jwkKeyAsArrayBuffer = asciiToUint8Array(JSON.stringify(jwkKey));
debug("Importing a JWK key...");
crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, { name: "HMAC", hash: "SHA-512" }, extractable, ["sign", "verify"]).then(function(result) {
crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, { name: "HMAC", hash: "SHA-256" }, extractable, ["sign", "verify"]).then(function(result) {
key = result;
shouldThrow('crypto.subtle.exportKey(null, key)');
shouldThrow('crypto.subtle.exportKey(undefined, key)');
shouldThrow('crypto.subtle.exportKey({}, key)');
shouldThrow('crypto.subtle.exportKey("", key)');
shouldThrow('crypto.subtle.exportKey("foobar", key)');
debug("Exporting the key as raw data...");
return crypto.subtle.exportKey("raw", key);
}).then(function(result) {
exportedData = result;
shouldBe("bytesToHexString(new Uint8Array(exportedData))", "'6a18e49feff7f3b7e09ec89b7f6deab2f6a18e49feff7f3b7e09ec89b7f6deab'");
debug("Exporting the key as JWK...");
return crypto.subtle.exportKey("jwk", key);
}).then(function(result) {
exportedJWK = JSON.parse(result);
shouldBe("exportedJWK.kty", "'oct'");
shouldBe("exportedJWK.k", "'ahjkn-_387fgnsibf23qsvahjkn-_387fgnsibf23qs'");
shouldBe("exportedJWK.alg", "'HS256'");
shouldBe("exportedJWK.extractable", "true");
shouldBe("exportedJWK.use", "'sig'");
debug("\nImporting a key that's not extractable...");
return crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, { name: "HMAC", hash: "SHA-256" }, nonExtractable, ["sign", "verify"]);
}).then(function(result) {
key = result;
debug("\nTrying to export as raw...");
return crypto.subtle.exportKey("raw", key);
}).then(function(result) {
testFailed("Promise wasn't rejected");
finishJSTest();
}, function() {
testPassed("Rejected, as expected");
debug("Trying to export as jwk...");
return crypto.subtle.exportKey("jwk", key);
}).then(function(result) {
testFailed("Promise wasn't rejected");
finishJSTest();
}, function() {
testPassed("Rejected, as expected");
// If this test starts to fail because a way to encode encrypt+decrypt is added to JWK, change to something that's not there.
// If all WebCrypto modes are added to JWK, then this test will be obsolete.
debug("\nImporting a key with only sign usage (this doesn't map to anything in JWK, as 'sig' allows both sign and verify)...");
return crypto.subtle.importKey("jwk", jwkKeyAsArrayBuffer, { name: "HMAC", hash: "SHA-256" }, extractable, ['sign'])
}).then(function(result) {
key = result;
// Maybe this should be a rejected promise, not an exception. We'll need to find a way to provide meaningful error messages with such failures.
shouldThrow('crypto.subtle.exportKey("jwk", key)');
finishJSTest();
});
</script>
......
2013-11-15 Alexey Proskuryakov <ap@apple.com>
Support exporting symmetric keys as JWK
https://bugs.webkit.org/show_bug.cgi?id=124442
Reviewed by Sam Weinig.
* runtime/JSONObject.h: Export JSONStringify.
2013-11-15 peavo@outlook.com <peavo@outlook.com>
[Win] JavaScript crashes on 64-bit with JIT enabled.
......
......@@ -61,7 +61,7 @@ namespace JSC {
};
JS_EXPORT_PRIVATE JSValue JSONParse(ExecState*, const String&);
String JSONStringify(ExecState*, JSValue, unsigned indent);
JS_EXPORT_PRIVATE String JSONStringify(ExecState*, JSValue, unsigned indent);
void escapeStringToBuilder(StringBuilder&, const String&);
......
2013-11-15 Alexey Proskuryakov <ap@apple.com>
Support exporting symmetric keys as JWK
https://bugs.webkit.org/show_bug.cgi?id=124442
Reviewed by Sam Weinig.
Base64URL encoding doesn't use '=' padding, and doesn't need any other options.
Added this mode for encode, and removed policy arguments from exposed functions.
* wtf/text/Base64.cpp:
(WTF::base64EncodeInternal):
(WTF::base64URLEncode):
(WTF::base64URLDecode):
* wtf/text/Base64.h:
(WTF::base64URLEncode):
2013-11-15 Mark Hahnenberg <mhahnenberg@apple.com>
Remove VTableSpectrum
......
......@@ -147,9 +147,13 @@ inline void base64EncodeInternal(const char* data, unsigned len, Vector<char>& o
}
// Add padding
while (didx < out.size()) {
out[didx] = '=';
++didx;
if (policy == Base64URLPolicy)
out.resize(didx);
else {
while (didx < out.size()) {
out[didx] = '=';
++didx;
}
}
}
......@@ -165,16 +169,16 @@ void base64Encode(const char* data, unsigned len, Vector<char>& out, Base64Encod
base64EncodeInternal(data, len, out, policy, base64EncMap);
}
String base64URLEncode(const char* data, unsigned length, Base64EncodePolicy policy)
String base64URLEncode(const char* data, unsigned length)
{
Vector<char> result;
base64EncodeInternal(data, length, result, policy, base64URLEncMap);
base64EncodeInternal(data, length, result, Base64URLPolicy, base64URLEncMap);
return String(result.data(), result.size());
}
void base64URLEncode(const char* data, unsigned len, Vector<char>& out, Base64EncodePolicy policy)
void base64URLEncode(const char* data, unsigned len, Vector<char>& out)
{
base64EncodeInternal(data, len, out, policy, base64URLEncMap);
base64EncodeInternal(data, len, out, Base64URLPolicy, base64URLEncMap);
}
template<typename T>
......@@ -265,12 +269,12 @@ bool base64Decode(const char* data, unsigned len, Vector<char>& out, Base64Decod
return base64DecodeInternal<char>(data, len, out, policy, base64DecMap);
}
bool base64URLDecode(const String& in, Vector<char>& out, Base64DecodePolicy policy)
bool base64URLDecode(const String& in, Vector<char>& out)
{
return base64DecodeInternal<UChar>(in.characters(), in.length(), out, policy, base64URLDecMap);
return base64DecodeInternal<UChar>(in.characters(), in.length(), out, Base64FailOnInvalidCharacter, base64URLDecMap);
}
bool base64URLDecode(const Vector<char>& in, Vector<char>& out, Base64DecodePolicy policy)
bool base64URLDecode(const Vector<char>& in, Vector<char>& out)
{
out.clear();
......@@ -278,12 +282,12 @@ bool base64URLDecode(const Vector<char>& in, Vector<char>& out, Base64DecodePoli
if (in.size() > UINT_MAX)
return false;
return base64DecodeInternal<char>(in.data(), in.size(), out, policy, base64URLDecMap);
return base64DecodeInternal<char>(in.data(), in.size(), out, Base64FailOnInvalidCharacter, base64URLDecMap);
}
bool base64URLDecode(const char* data, unsigned len, Vector<char>& out, Base64DecodePolicy policy)
bool base64URLDecode(const char* data, unsigned len, Vector<char>& out)
{
return base64DecodeInternal<char>(data, len, out, policy, base64URLDecMap);
return base64DecodeInternal<char>(data, len, out, Base64FailOnInvalidCharacter, base64URLDecMap);
}
} // namespace WTF
......@@ -35,7 +35,8 @@ namespace WTF {
enum Base64EncodePolicy {
Base64DoNotInsertLFs,
Base64InsertLFs
Base64InsertLFs,
Base64URLPolicy // No padding, no LFs.
};
enum Base64DecodePolicy {
......@@ -81,35 +82,35 @@ inline String base64Encode(const CString& in, Base64EncodePolicy policy)
// This format uses '-' and '_' instead of '+' and '/' respectively.
// ======================================================================================
WTF_EXPORT_PRIVATE void base64URLEncode(const char*, unsigned, Vector<char>&, Base64EncodePolicy = Base64DoNotInsertLFs);
WTF_EXPORT_PRIVATE void base64URLEncode(const Vector<char>&, Vector<char>&, Base64EncodePolicy = Base64DoNotInsertLFs);
WTF_EXPORT_PRIVATE void base64URLEncode(const CString&, Vector<char>&, Base64EncodePolicy = Base64DoNotInsertLFs);
WTF_EXPORT_PRIVATE String base64URLEncode(const char*, unsigned, Base64EncodePolicy = Base64DoNotInsertLFs);
WTF_EXPORT_PRIVATE String base64URLEncode(const Vector<char>&, Base64EncodePolicy = Base64DoNotInsertLFs);
WTF_EXPORT_PRIVATE String base64URLEncode(const CString&, Base64EncodePolicy = Base64DoNotInsertLFs);
WTF_EXPORT_PRIVATE void base64URLEncode(const char*, unsigned, Vector<char>&);
WTF_EXPORT_PRIVATE void base64URLEncode(const Vector<char>&, Vector<char>&);
WTF_EXPORT_PRIVATE void base64URLEncode(const CString&, Vector<char>&);
WTF_EXPORT_PRIVATE String base64URLEncode(const char*, unsigned);
WTF_EXPORT_PRIVATE String base64URLEncode(const Vector<char>&);
WTF_EXPORT_PRIVATE String base64URLEncode(const CString&);
WTF_EXPORT_PRIVATE bool base64URLDecode(const String&, Vector<char>&, Base64DecodePolicy = Base64FailOnInvalidCharacter);
WTF_EXPORT_PRIVATE bool base64URLDecode(const Vector<char>&, Vector<char>&, Base64DecodePolicy = Base64FailOnInvalidCharacter);
WTF_EXPORT_PRIVATE bool base64URLDecode(const char*, unsigned, Vector<char>&, Base64DecodePolicy = Base64FailOnInvalidCharacter);
WTF_EXPORT_PRIVATE bool base64URLDecode(const String&, Vector<char>&);
WTF_EXPORT_PRIVATE bool base64URLDecode(const Vector<char>&, Vector<char>&);
WTF_EXPORT_PRIVATE bool base64URLDecode(const char*, unsigned, Vector<char>&);
inline void base64URLEncode(const Vector<char>& in, Vector<char>& out, Base64EncodePolicy policy)
inline void base64URLEncode(const Vector<char>& in, Vector<char>& out)
{
base64URLEncode(in.data(), in.size(), out, policy);
base64URLEncode(in.data(), in.size(), out);
}
inline void base64URLEncode(const CString& in, Vector<char>& out, Base64EncodePolicy policy)
inline void base64URLEncode(const CString& in, Vector<char>& out)
{
base64URLEncode(in.data(), in.length(), out, policy);
base64URLEncode(in.data(), in.length(), out);
}
inline String base64URLEncode(const Vector<char>& in, Base64EncodePolicy policy)
inline String base64URLEncode(const Vector<char>& in)
{
return base64URLEncode(in.data(), in.size(), policy);
return base64URLEncode(in.data(), in.size());
}
inline String base64URLEncode(const CString& in, Base64EncodePolicy policy)
inline String base64URLEncode(const CString& in)
{
return base64URLEncode(in.data(), in.length(), policy);
return base64URLEncode(in.data(), in.length());
}
} // namespace WTF
......
2013-11-15 Alexey Proskuryakov <ap@apple.com>
Support exporting symmetric keys as JWK
https://bugs.webkit.org/show_bug.cgi?id=124442
Reviewed by Sam Weinig.
Error handling is not consistent yet - some errors cause exceptions, and others
result in rejected promises. This part of spec is incomplete, so I basically did
what was most straightforward in each case.
* bindings/js/JSCryptoKeySerializationJWK.h:
* bindings/js/JSCryptoKeySerializationJWK.cpp:
(WebCore::JSCryptoKeySerializationJWK::reconcileUsages): Updated a comment with a better link.
(WebCore::JSCryptoKeySerializationJWK::buildJSONForOctetSequence): A helper to building JWK.
(WebCore::JSCryptoKeySerializationJWK::addToJSON): Ditto.
(WebCore::JSCryptoKeySerializationJWK::addBoolToJSON): Ditto.
(WebCore::JSCryptoKeySerializationJWK::addJWKAlgorithmToJSON): Ditto. The code for
mapping is my best guess, this all needs to be specified.
(WebCore::JSCryptoKeySerializationJWK::addJWKUseToJSON): A helper to building JWK.
(WebCore::JSCryptoKeySerializationJWK::serialize): Build a JSON string for the key.
* bindings/js/JSSubtleCryptoCustom.cpp:
(WebCore::JSSubtleCrypto::importKey): Updated a comment.
(WebCore::JSSubtleCrypto::exportKey): Use CryptoKeySerialization (also for raw keys,
for consistency).
* crypto/CryptoKey.h:
(WebCore::CryptoKey::algorithmIdentifier):
(WebCore::CryptoKey::usagesBitmap):
Exposed data needed for building JWK (it used to be only exposed in a form suitable
for DOM accessors).
* crypto/keys/CryptoKeyHMAC.h: Ditto, added an accessor for JWK.
* crypto/keys/CryptoKeySerializationRaw.cpp: (WebCore::CryptoKeySerializationRaw::serialize):
* crypto/keys/CryptoKeySerializationRaw.h:
Moved from JSSubtleCryptoCustom.cpp for consistency.
2013-11-15 Brady Eidson <beidson@apple.com>
Move IDBCursorBackend operations into their own files
......
......@@ -32,12 +32,17 @@
#include "CryptoAlgorithmHmacParams.h"
#include "CryptoAlgorithmRegistry.h"
#include "CryptoAlgorithmRsaSsaKeyParams.h"
#include "CryptoKey.h"
#include "CryptoKeyAES.h"
#include "CryptoKeyDataOctetSequence.h"
#include "CryptoKeyDataRSAComponents.h"
#include "CryptoKeyHMAC.h"
#include "ExceptionCode.h"
#include "JSDOMBinding.h"
#include <heap/StrongInlines.h>
#include <runtime/JSONObject.h>
#include <runtime/ObjectConstructor.h>
#include <runtime/Operations.h>
#include <wtf/text/Base64.h>
using namespace JSC;
......@@ -236,7 +241,7 @@ void JSCryptoKeySerializationJWK::reconcileUsages(CryptoKeyUsage& suggestedUsage
}
// FIXME: CryptoKeyUsageDeriveKey, CryptoKeyUsageDeriveBits - should these be implicitly allowed by any JWK use value?
// FIXME: There is a mismatch between specs for wrap/unwrap usages, <http://lists.w3.org/Archives/Public/public-webcrypto/2013Nov/0016.html>.
// FIXME: "use" mapping is in flux, see <https://www.w3.org/Bugs/Public/show_bug.cgi?id=23796>.
if (jwkUseString == "sig")
suggestedUsage = suggestedUsage & (CryptoKeyUsageSign | CryptoKeyUsageVerify);
else if (jwkUseString == "enc")
......@@ -411,6 +416,122 @@ std::unique_ptr<CryptoKeyData> JSCryptoKeySerializationJWK::keyData() const
return nullptr;
}
void JSCryptoKeySerializationJWK::buildJSONForOctetSequence(ExecState* exec, const Vector<char>& keyData, JSObject* result)
{
addToJSON(exec, result, "kty", "oct");
addToJSON(exec, result, "k", base64URLEncode(keyData));
}
void JSCryptoKeySerializationJWK::addToJSON(ExecState* exec, JSObject* json, const char* key, const String& value)
{
VM& vm = exec->vm();
Identifier identifier(&vm, key);
json->putDirect(vm, identifier, jsString(exec, value));
}
void JSCryptoKeySerializationJWK::addBoolToJSON(ExecState* exec, JSObject* json, const char* key, bool value)
{
VM& vm = exec->vm();
Identifier identifier(&vm, key);
json->putDirect(vm, identifier, jsBoolean(value));
}
void JSCryptoKeySerializationJWK::addJWKAlgorithmToJSON(ExecState* exec, JSObject* json, const CryptoKey& key)
{
String jwkAlgorithm;
switch (key.algorithmIdentifier()) {
case CryptoAlgorithmIdentifier::HMAC:
switch (toCryptoKeyHMAC(key).hashAlgorithmIdentifier()) {
case CryptoAlgorithmIdentifier::SHA_256:
if (toCryptoKeyHMAC(key).key().size() * 8 >= 256)
jwkAlgorithm = "HS256";
break;
case CryptoAlgorithmIdentifier::SHA_384:
if (toCryptoKeyHMAC(key).key().size() * 8 >= 384)
jwkAlgorithm = "HS384";
break;
case CryptoAlgorithmIdentifier::SHA_512:
if (toCryptoKeyHMAC(key).key().size() * 8 >= 512)
jwkAlgorithm = "HS512";
break;
default:
break;
}
break;
case CryptoAlgorithmIdentifier::AES_CBC:
switch (toCryptoKeyAES(key).key().size() * 8) {
case 128:
jwkAlgorithm = "A128CBC";
break;
case 192:
jwkAlgorithm = "A192CBC";
break;
case 256:
jwkAlgorithm = "A256CBC";
break;
}
break;
default:
break;
}
if (jwkAlgorithm.isNull()) {
// The spec doesn't currently tell whether export should fail, or just skip "alg" (which is an optional key in JWK).
// Perhaps this should depend on whether the key is extractable?
throwTypeError(exec, "Key algorithm and size do not map to any JWK algorithm identifier");
return;
}
addToJSON(exec, json, "alg", jwkAlgorithm);
}
void JSCryptoKeySerializationJWK::addJWKUseToJSON(ExecState* exec, JSObject* json, CryptoKeyUsage usages)
{
// FIXME: "use" mapping is in flux, see <https://www.w3.org/Bugs/Public/show_bug.cgi?id=23796>.
switch (usages) {
case CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt | CryptoKeyUsageWrapKey | CryptoKeyUsageUnwrapKey:
addToJSON(exec, json, "use", "enc");
break;
case CryptoKeyUsageSign | CryptoKeyUsageVerify:
addToJSON(exec, json, "use", "sig");
break;
default:
throwTypeError(exec, "Key usages cannot be represented in JWK. Only two variants are supported: sign+verify and encrypt+decrypt+wrapKey+unwrapKey");
}
}
String JSCryptoKeySerializationJWK::serialize(ExecState* exec, const CryptoKey& key)
{
std::unique_ptr<CryptoKeyData> keyData = key.exportData();
if (!keyData) {
// FIXME: Shouldn't happen once all key types implement exportData().
throwTypeError(exec, "Key doesn't support exportKey");
return String();
}
JSObject* result = constructEmptyObject(exec);
addJWKAlgorithmToJSON(exec, result, key);
if (exec->hadException())
return String();
addBoolToJSON(exec, result, "extractable", key.extractable());
addJWKUseToJSON(exec, result, key.usagesBitmap());
if (exec->hadException())
return String();
if (isCryptoKeyDataOctetSequence(*keyData))
buildJSONForOctetSequence(exec, toCryptoKeyDataOctetSequence(*keyData).octetSequence(), result);
else {
throwTypeError(exec, "Key doesn't support exportKey");
return String();
}
ASSERT(!exec->hadException());