Only wrapping CryptoKeys for IDB during serialization
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 8 Oct 2019 01:16:08 +0000 (01:16 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 8 Oct 2019 01:16:08 +0000 (01:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=202500
<rdar://problem/52445927>

Reviewed by Chris Dumez.

Source/WebCore:

Wrapping CryptoKeys during IDB serialization is a legacy request from Netflix when WebKit was an
early adopter. It is not necessary for other kinds of serialization. However, given existing keys
stored in users' idb are wrapped, the wrapping/unwrapping mechanism cannot be easily discarded.
Therefore, this patch restricts the wrapping/unwrapping mechanism to idb only.

To do so, a isWrappingRequired flag is added to CryptoKey such that whenever idb sees a CryptoKey,
it can set it. SerializedScriptValue will then only wrap a CryptoKey when this flag is set. Otherwise,
a new tag UnwrappedCryptoKeyTag is used to store unwrapped CryptoKeys in order to keep the old CryptoKeyTag
binaries intact. For deserialization, each type will be deserialized differently.

Besides the above, this patch also hardens WorkerGlobalScope::wrapCryptoKey/unwrapCryptoKey for
any potential racy issues. CryptoBooleanContainer is introduced to capture boolean in the lambda.
workerGlobalScope is replaced with workerMessagingProxy. Now, every variables captured in the lambdas
should be either a copy or a thread safe ref of the original object.

Test: crypto/workers/subtle/aes-indexeddb.html

* Modules/indexeddb/IDBObjectStore.cpp:
(WebCore::JSC::setIsWrappingRequiredForCryptoKey):
(WebCore::IDBObjectStore::putOrAdd):
* bindings/js/SerializedScriptValue.cpp:
(WebCore::CloneSerializer::dumpIfTerminal):
(WebCore::CloneDeserializer::readTerminal):
* crypto/CryptoKey.h:
(WebCore::CryptoKey::isWrappingRequired const):
(WebCore::CryptoKey::setIsWrappingRequired):
* dom/ScriptExecutionContext.h:
* workers/WorkerGlobalScope.cpp:
(WebCore::CryptoBooleanContainer::create):
(WebCore::CryptoBooleanContainer::boolean const):
(WebCore::CryptoBooleanContainer::setBoolean):
(WebCore::WorkerGlobalScope::wrapCryptoKey):
(WebCore::WorkerGlobalScope::unwrapCryptoKey):
* workers/WorkerGlobalScope.h:
* workers/WorkerLoaderProxy.h:
(WebCore::WorkerLoaderProxy::isWorkerMessagingProxy const):
* workers/WorkerMessagingProxy.h:
(isType):

Tools:

Modifies IndexedDB.StructuredCloneBackwardCompatibility test to include CryptoKeys.

* TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.mm:
(-[StructuredCloneBackwardCompatibilityNavigationDelegate _webCryptoMasterKeyForWebView:]):
(TEST):
* TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3:
* TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm:
* TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal:
* TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibilityRead.html:
* TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibilityWrite.html:

LayoutTests:

Adds a new test aes-indexeddb.html to do idb in workers and makes
other tests more deterministic.

* crypto/workers/subtle/aes-indexeddb-expected.txt: Added.
* crypto/workers/subtle/aes-indexeddb.html: Added.
* crypto/workers/subtle/ec-postMessage-worker-expected.txt:
* crypto/workers/subtle/ec-postMessage-worker.html:
* crypto/workers/subtle/hrsa-postMessage-worker-expected.txt:
* crypto/workers/subtle/hrsa-postMessage-worker.html:
* crypto/workers/subtle/resources/aes-indexeddb.js: Added.
* crypto/workers/subtle/rsa-postMessage-worker-expected.txt:
* crypto/workers/subtle/rsa-postMessage-worker.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@250811 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
LayoutTests/ChangeLog
LayoutTests/crypto/workers/subtle/aes-indexeddb-expected.txt [new file with mode: 0644]
LayoutTests/crypto/workers/subtle/aes-indexeddb.html [new file with mode: 0644]
LayoutTests/crypto/workers/subtle/ec-postMessage-worker-expected.txt
LayoutTests/crypto/workers/subtle/ec-postMessage-worker.html
LayoutTests/crypto/workers/subtle/hrsa-postMessage-worker-expected.txt
LayoutTests/crypto/workers/subtle/hrsa-postMessage-worker.html
LayoutTests/crypto/workers/subtle/resources/aes-indexeddb.js [new file with mode: 0644]
LayoutTests/crypto/workers/subtle/rsa-postMessage-worker-expected.txt
LayoutTests/crypto/workers/subtle/rsa-postMessage-worker.html
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBObjectStore.cpp
Source/WebCore/bindings/js/SerializedScriptValue.cpp
Source/WebCore/crypto/CryptoKey.h
Source/WebCore/dom/ScriptExecutionContext.h
Source/WebCore/workers/WorkerGlobalScope.cpp
Source/WebCore/workers/WorkerGlobalScope.h
Source/WebCore/workers/WorkerLoaderProxy.h
Source/WebCore/workers/WorkerMessagingProxy.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3
Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm
Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal
Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibilityRead.html
Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibilityWrite.html

index 56ab36e..3a76517 100644 (file)
@@ -1,3 +1,24 @@
+2019-10-07  Jiewen Tan  <jiewen_tan@apple.com>
+
+        Only wrapping CryptoKeys for IDB during serialization
+        https://bugs.webkit.org/show_bug.cgi?id=202500
+        <rdar://problem/52445927>
+
+        Reviewed by Chris Dumez.
+
+        Adds a new test aes-indexeddb.html to do idb in workers and makes
+        other tests more deterministic.
+
+        * crypto/workers/subtle/aes-indexeddb-expected.txt: Added.
+        * crypto/workers/subtle/aes-indexeddb.html: Added.
+        * crypto/workers/subtle/ec-postMessage-worker-expected.txt:
+        * crypto/workers/subtle/ec-postMessage-worker.html:
+        * crypto/workers/subtle/hrsa-postMessage-worker-expected.txt:
+        * crypto/workers/subtle/hrsa-postMessage-worker.html:
+        * crypto/workers/subtle/resources/aes-indexeddb.js: Added.
+        * crypto/workers/subtle/rsa-postMessage-worker-expected.txt:
+        * crypto/workers/subtle/rsa-postMessage-worker.html:
+
 2019-10-07  Kate Cheney  <katherine_cheney@apple.com>
 
         Domain relationships in the ITP Database should be inserted in a single query and ignore repeat insert attempts. (202604)
diff --git a/LayoutTests/crypto/workers/subtle/aes-indexeddb-expected.txt b/LayoutTests/crypto/workers/subtle/aes-indexeddb-expected.txt
new file mode 100644 (file)
index 0000000..4fb2160
--- /dev/null
@@ -0,0 +1,16 @@
+[Worker] Test generating an AES key using AES-CBC algorithm in workers.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Starting worker: resources/aes-indexeddb.js
+[Worker] Importing a key...
+PASS [Worker] retrievedKey.type is 'secret'
+PASS [Worker] retrievedKey.extractable is true
+PASS [Worker] retrievedKey.algorithm.name is 'AES-CBC'
+PASS [Worker] retrievedKey.algorithm.length is 128
+PASS [Worker] retrievedKey.usages is ['decrypt', 'encrypt', 'unwrapKey', 'wrapKey']
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/crypto/workers/subtle/aes-indexeddb.html b/LayoutTests/crypto/workers/subtle/aes-indexeddb.html
new file mode 100644 (file)
index 0000000..5782e74
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <script src="../../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+    worker = startWorker('resources/aes-indexeddb.js');
+</script>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
index 75bc79e..2f9b3a0 100644 (file)
@@ -3,6 +3,14 @@ Test sending ec crypto keys via postMessage to a worker.
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
+PASS All checks passed in worker for public key
+PASS publicKey.type is 'public'
+PASS publicKey.extractable is true
+PASS publicKey.algorithm.name is 'ECDH'
+PASS publicKey.algorithm.namedCurve is 'P-256'
+PASS publicKey.usages is [ ]
+PASS exportedKey.x is publicKeyJSON.x
+PASS exportedKey.y is publicKeyJSON.y
 PASS All checks passed in worker for private key
 PASS privateKey.type is 'private'
 PASS privateKey.extractable is true
@@ -12,14 +20,6 @@ PASS privateKey.usages is ['deriveBits']
 PASS exportedKey.x is privateKeyJSON.x
 PASS exportedKey.y is privateKeyJSON.y
 PASS exportedKey.d is privateKeyJSON.d
-PASS All checks passed in worker for public key
-PASS publicKey.type is 'public'
-PASS publicKey.extractable is true
-PASS publicKey.algorithm.name is 'ECDH'
-PASS publicKey.algorithm.namedCurve is 'P-256'
-PASS publicKey.usages is [ ]
-PASS exportedKey.x is publicKeyJSON.x
-PASS exportedKey.y is publicKeyJSON.y
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 4a16666..5f91037 100644 (file)
@@ -81,9 +81,9 @@ worker.onmessage = function(evt) {
 
 crypto.subtle.importKey("jwk", publicKeyJSON, { name:"ECDH", namedCurve:"P-256" }, true, [ ]).then(function(localPublicKey) {
     worker.postMessage({ publicKey: localPublicKey });
-});
-crypto.subtle.importKey("jwk", privateKeyJSON, { name:"ECDH", namedCurve:"P-256" }, true, ['deriveBits']).then(function(localPrivateKey) {
-    worker.postMessage({ privateKey: localPrivateKey });
+    crypto.subtle.importKey("jwk", privateKeyJSON, { name:"ECDH", namedCurve:"P-256" }, true, ['deriveBits']).then(function(localPrivateKey) {
+        worker.postMessage({ privateKey: localPrivateKey });
+    });
 });
 </script>
 <script src="../../../resources/js-test-post.js"></script>
index 0ed74f9..6e1d6fe 100644 (file)
@@ -3,14 +3,6 @@ Test sending hashed rsa crypto keys via postMessage to a worker.
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS All checks passed in worker for private key
-PASS privateKey.type is 'private'
-PASS privateKey.extractable is false
-PASS privateKey.algorithm.name is 'RSASSA-PKCS1-v1_5'
-PASS privateKey.algorithm.modulusLength is 2048
-PASS bytesToHexString(privateKey.algorithm.publicExponent) is '010001'
-PASS privateKey.algorithm.hash.name is 'SHA-256'
-PASS privateKey.usages is ['sign']
 PASS All checks passed in worker for public key
 PASS publicKey.type is 'public'
 PASS publicKey.extractable is true
@@ -19,6 +11,14 @@ PASS publicKey.algorithm.modulusLength is 2048
 PASS bytesToHexString(publicKey.algorithm.publicExponent) is '010001'
 PASS publicKey.algorithm.hash.name is 'SHA-256'
 PASS publicKey.usages is ['verify']
+PASS All checks passed in worker for private key
+PASS privateKey.type is 'private'
+PASS privateKey.extractable is false
+PASS privateKey.algorithm.name is 'RSASSA-PKCS1-v1_5'
+PASS privateKey.algorithm.modulusLength is 2048
+PASS bytesToHexString(privateKey.algorithm.publicExponent) is '010001'
+PASS privateKey.algorithm.hash.name is 'SHA-256'
+PASS privateKey.usages is ['sign']
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 7a435fe..e9ed8c9 100644 (file)
@@ -75,9 +75,9 @@ worker.onmessage = function(evt) {
 
 crypto.subtle.importKey("jwk", publicKeyJSON, algorithmKeyGen, true, ['verify']).then(function(localPublicKey) {
     worker.postMessage({ publicKey: localPublicKey });
-});
-crypto.subtle.importKey("jwk", privateKeyJSON, algorithmKeyGen, false, ['sign']).then(function(localPrivateKey) {
-    worker.postMessage({ privateKey: localPrivateKey });
+    crypto.subtle.importKey("jwk", privateKeyJSON, algorithmKeyGen, false, ['sign']).then(function(localPrivateKey) {
+        worker.postMessage({ privateKey: localPrivateKey });
+    });
 });
 </script>
 <script src="../../../resources/js-test-post.js"></script>
diff --git a/LayoutTests/crypto/workers/subtle/resources/aes-indexeddb.js b/LayoutTests/crypto/workers/subtle/resources/aes-indexeddb.js
new file mode 100644 (file)
index 0000000..e2d8dc9
--- /dev/null
@@ -0,0 +1,51 @@
+importScripts('../../../../resources/js-test-pre.js');
+
+description("Test generating an AES key using AES-CBC algorithm in workers.");
+jsTestIsAsync = true;
+
+const jwkKey = { kty: "oct", k: "YWJjZGVmZ2gxMjM0NTY3OA", alg: "A128CBC", use: "enc", key_ops: ['decrypt', 'encrypt', 'unwrapKey', 'wrapKey'], ext: true };
+
+debug("Importing a key...");
+crypto.subtle.importKey("jwk", jwkKey, "aes-cbc", true, ["encrypt", "decrypt", "wrapKey", "unwrapKey"]).then(function(key) {
+    const openRequest = indexedDB.open("crypto_subtle");
+    openRequest.onupgradeneeded = function(event) {
+        event.target.result.createObjectStore("aes-indexeddb");
+    };
+    openRequest.onerror = function(event) {
+        testFailed("Could not open database: " + event.target.error.name);
+        finishJSTest();
+    };
+    openRequest.onsuccess = function(event) {
+        db = event.target.result;
+        storeKey();
+    };
+
+    function storeKey() {
+        const objectStore = db.transaction("aes-indexeddb", "readwrite").objectStore("aes-indexeddb");
+        const req = objectStore.put(key, "myKey");
+        req.onerror = function(event) {
+            testFailed("Could not put a key into database: " + event.target.error.name);
+            finishJSTest();
+        };
+        req.onsuccess = function() { readKey(); }
+    }
+
+    function readKey() {
+        const objectStore = db.transaction("aes-indexeddb").objectStore("aes-indexeddb");
+        const req = objectStore.get("myKey");
+        req.onerror = function(event) {
+            testFailed("Could not get a key from database: " + event.target.error.name);
+            finishJSTest();
+        };
+        req.onsuccess = function(event) {
+            retrievedKey = event.target.result;
+            shouldBe("retrievedKey.type", "'secret'");
+            shouldBe("retrievedKey.extractable", "true");
+            shouldBe("retrievedKey.algorithm.name", "'AES-CBC'");
+            shouldBe("retrievedKey.algorithm.length", "128");
+            shouldBe("retrievedKey.usages", "['decrypt', 'encrypt', 'unwrapKey', 'wrapKey']");
+
+            finishJSTest();
+        }
+    }
+});
index 0c39790..61921c4 100644 (file)
@@ -3,6 +3,16 @@ Test sending rsa crypto keys via postMessage to a worker.
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
+PASS All checks passed in worker for public key
+PASS publicKey.type is 'public'
+PASS publicKey.extractable is true
+PASS publicKey.algorithm.name is 'RSAES-PKCS1-v1_5'
+PASS publicKey.algorithm.modulusLength is 2048
+PASS bytesToHexString(publicKey.algorithm.publicExponent) is '010001'
+PASS publicKey.algorithm.hash is undefined.
+PASS publicKey.usages is ['encrypt']
+PASS exportedKey.n is publicKeyJSON.n
+PASS exportedKey.e is publicKeyJSON.e
 PASS All checks passed in worker for private key
 PASS privateKey.type is 'private'
 PASS privateKey.extractable is true
@@ -19,16 +29,6 @@ PASS exportedKey.q is privateKeyJSON.q
 PASS exportedKey.dp is privateKeyJSON.dp
 PASS exportedKey.dq is privateKeyJSON.dq
 PASS exportedKey.qi is privateKeyJSON.qi
-PASS All checks passed in worker for public key
-PASS publicKey.type is 'public'
-PASS publicKey.extractable is true
-PASS publicKey.algorithm.name is 'RSAES-PKCS1-v1_5'
-PASS publicKey.algorithm.modulusLength is 2048
-PASS bytesToHexString(publicKey.algorithm.publicExponent) is '010001'
-PASS publicKey.algorithm.hash is undefined.
-PASS publicKey.usages is ['encrypt']
-PASS exportedKey.n is publicKeyJSON.n
-PASS exportedKey.e is publicKeyJSON.e
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 4f551ea..22b9ad0 100644 (file)
@@ -98,9 +98,9 @@ worker.onmessage = function(evt) {
 
 crypto.subtle.importKey("jwk", publicKeyJSON, algorithmKeyGen, true, ['encrypt']).then(function(localPublicKey) {
     worker.postMessage({ publicKey: localPublicKey });
-});
-crypto.subtle.importKey("jwk", privateKeyJSON, algorithmKeyGen, true, ['decrypt']).then(function(localPrivateKey) {
-    worker.postMessage({ privateKey: localPrivateKey });
+    crypto.subtle.importKey("jwk", privateKeyJSON, algorithmKeyGen, true, ['decrypt']).then(function(localPrivateKey) {
+        worker.postMessage({ privateKey: localPrivateKey });
+    });
 });
 </script>
 <script src="../../../resources/js-test-post.js"></script>
index 84527e1..bebbe05 100644 (file)
@@ -1,3 +1,50 @@
+2019-10-07  Jiewen Tan  <jiewen_tan@apple.com>
+
+        Only wrapping CryptoKeys for IDB during serialization
+        https://bugs.webkit.org/show_bug.cgi?id=202500
+        <rdar://problem/52445927>
+
+        Reviewed by Chris Dumez.
+
+        Wrapping CryptoKeys during IDB serialization is a legacy request from Netflix when WebKit was an
+        early adopter. It is not necessary for other kinds of serialization. However, given existing keys
+        stored in users' idb are wrapped, the wrapping/unwrapping mechanism cannot be easily discarded.
+        Therefore, this patch restricts the wrapping/unwrapping mechanism to idb only.
+
+        To do so, a isWrappingRequired flag is added to CryptoKey such that whenever idb sees a CryptoKey,
+        it can set it. SerializedScriptValue will then only wrap a CryptoKey when this flag is set. Otherwise,
+        a new tag UnwrappedCryptoKeyTag is used to store unwrapped CryptoKeys in order to keep the old CryptoKeyTag
+        binaries intact. For deserialization, each type will be deserialized differently.
+
+        Besides the above, this patch also hardens WorkerGlobalScope::wrapCryptoKey/unwrapCryptoKey for
+        any potential racy issues. CryptoBooleanContainer is introduced to capture boolean in the lambda.
+        workerGlobalScope is replaced with workerMessagingProxy. Now, every variables captured in the lambdas
+        should be either a copy or a thread safe ref of the original object.
+
+        Test: crypto/workers/subtle/aes-indexeddb.html
+
+        * Modules/indexeddb/IDBObjectStore.cpp:
+        (WebCore::JSC::setIsWrappingRequiredForCryptoKey):
+        (WebCore::IDBObjectStore::putOrAdd):
+        * bindings/js/SerializedScriptValue.cpp:
+        (WebCore::CloneSerializer::dumpIfTerminal):
+        (WebCore::CloneDeserializer::readTerminal):
+        * crypto/CryptoKey.h:
+        (WebCore::CryptoKey::isWrappingRequired const):
+        (WebCore::CryptoKey::setIsWrappingRequired):
+        * dom/ScriptExecutionContext.h:
+        * workers/WorkerGlobalScope.cpp:
+        (WebCore::CryptoBooleanContainer::create):
+        (WebCore::CryptoBooleanContainer::boolean const):
+        (WebCore::CryptoBooleanContainer::setBoolean):
+        (WebCore::WorkerGlobalScope::wrapCryptoKey):
+        (WebCore::WorkerGlobalScope::unwrapCryptoKey):
+        * workers/WorkerGlobalScope.h:
+        * workers/WorkerLoaderProxy.h:
+        (WebCore::WorkerLoaderProxy::isWorkerMessagingProxy const):
+        * workers/WorkerMessagingProxy.h:
+        (isType):
+
 2019-10-07  Yusuke Suzuki  <ysuzuki@apple.com>
 
         Unreviewed, build fix for Windows
index a597ac1..0bddef5 100644 (file)
 #include <JavaScriptCore/JSCJSValueInlines.h>
 #include <wtf/Locker.h>
 
+#if ENABLE(WEB_CRYPTO)
+#include "JSCryptoKey.h"
+#endif
+
 namespace WebCore {
 using namespace JSC;
 
+#if ENABLE(WEB_CRYPTO)
+namespace {
+static inline void setIsWrappingRequiredForCryptoKey(VM& vm, const JSValue& value)
+{
+    if (!value.isObject())
+        return;
+    auto* obj = asObject(value);
+    if (auto* key = JSCryptoKey::toWrapped(vm, obj))
+        key->setIsWrappingRequired();
+}
+}
+#endif
+
 IDBObjectStore::IDBObjectStore(ScriptExecutionContext& context, const IDBObjectStoreInfo& info, IDBTransaction& transaction)
     : ActiveDOMObject(&context)
     , m_info(info)
@@ -339,6 +356,9 @@ ExceptionOr<Ref<IDBRequest>> IDBObjectStore::putOrAdd(ExecState& state, JSValue
     if (m_transaction.isReadOnly())
         return Exception { ReadonlyError, "Failed to store record in an IDBObjectStore: The transaction is read-only."_s };
 
+#if ENABLE(WEB_CRYPTO)
+    setIsWrappingRequiredForCryptoKey(vm, value);
+#endif
     auto serializedValue = SerializedScriptValue::create(state, value);
     if (UNLIKELY(scope.exception()))
         return Exception { DataCloneError, "Failed to store record in an IDBObjectStore: An object could not be cloned."_s };
index 3b000b8..d3436c6 100644 (file)
@@ -167,6 +167,9 @@ enum SerializationTag {
     RTCCertificateTag = 44,
 #endif
     ImageBitmapTag = 45,
+#if ENABLE(WEB_CRYPTO)
+    UnwrappedCryptoKeyTag = 46,
+#endif
     ErrorTag = 255
 };
 
@@ -350,6 +353,7 @@ static const unsigned StringDataIs8BitFlag = 0x80000000;
  *    | ImageBitmapTransferTag <value:uint32_t>
  *    | RTCCertificateTag
  *    | ImageBitmapTag <originClean:uint8_t> <logicalWidth:int32_t> <logicalHeight:int32_t> <resolutionScale:double> <byteLength:uint32_t>(<imageByteData:uint8_t>)
+ *    | UnwrappedCryptoKeyTag <unwrappedKeyLength:uint32_t> <factor:byte{unwrappedKeyLength}>
  *
  * Inside certificate, data is serialized in this format as per spec:
  *
@@ -1083,7 +1087,10 @@ private:
             }
 #if ENABLE(WEB_CRYPTO)
             if (auto* key = JSCryptoKey::toWrapped(vm, obj)) {
-                write(CryptoKeyTag);
+                if (key->isWrappingRequired())
+                    write(CryptoKeyTag);
+                else
+                    write(UnwrappedCryptoKeyTag);
                 Vector<uint8_t> serializedKey;
                 Vector<String> dummyBlobURLs;
                 Vector<RefPtr<MessagePort>> dummyMessagePorts;
@@ -1098,10 +1105,14 @@ private:
 #endif
                     dummyBlobURLs, serializedKey, SerializationContext::Default, dummySharedBuffers);
                 rawKeySerializer.write(key);
-                Vector<uint8_t> wrappedKey;
-                if (!wrapCryptoKey(m_exec, serializedKey, wrappedKey))
-                    return false;
-                write(wrappedKey);
+                if (key->isWrappingRequired()) {
+                    Vector<uint8_t> wrappedKey;
+                    if (!wrapCryptoKey(m_exec, serializedKey, wrappedKey))
+                        return false;
+                    write(wrappedKey);
+                    return true;
+                }
+                write(serializedKey);
                 return true;
             }
 #endif
@@ -3092,6 +3103,28 @@ private:
 #endif
         case ImageBitmapTag:
             return readImageBitmap();
+#if ENABLE(WEB_CRYPTO)
+        case UnwrappedCryptoKeyTag: {
+            Vector<uint8_t> serializedKey;
+            if (!read(serializedKey)) {
+                fail();
+                return JSValue();
+            }
+            JSValue cryptoKey;
+            Vector<RefPtr<MessagePort>> dummyMessagePorts;
+            CloneDeserializer rawKeyDeserializer(m_exec, m_globalObject, dummyMessagePorts, nullptr, { },
+#if ENABLE(WEBASSEMBLY)
+                nullptr,
+#endif
+                serializedKey);
+            if (!rawKeyDeserializer.readCryptoKey(cryptoKey)) {
+                fail();
+                return JSValue();
+            }
+            m_gcBuffer.appendWithCrashOnOverflow(cryptoKey);
+            return cryptoKey;
+        }
+#endif
         default:
             m_ptr--; // Push the tag back
             return JSValue();
index 0ebc572..3865d29 100644 (file)
@@ -72,6 +72,8 @@ public:
     CryptoKeyUsageBitmap usagesBitmap() const { return m_usages; }
     void setUsagesBitmap(CryptoKeyUsageBitmap usage) { m_usages = usage; };
     bool allows(CryptoKeyUsageBitmap usage) const { return usage == (m_usages & usage); }
+    bool isWrappingRequired() const { return m_isWrappingRequired; }
+    void setIsWrappingRequired() { m_isWrappingRequired = true; }
 
     static Vector<uint8_t> randomData(size_t);
 
@@ -80,6 +82,7 @@ private:
     Type m_type;
     bool m_extractable;
     CryptoKeyUsageBitmap m_usages;
+    bool m_isWrappingRequired { false };
 };
 
 inline auto CryptoKey::type() const -> Type
index efb016f..0fbec54 100644 (file)
@@ -221,6 +221,7 @@ public:
     void setDatabaseContext(DatabaseContext*);
 
 #if ENABLE(WEB_CRYPTO)
+    // These two methods are used when CryptoKeys are serialized into IndexedDB.
     virtual bool wrapCryptoKey(const Vector<uint8_t>& key, Vector<uint8_t>& wrappedKey) = 0;
     virtual bool unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, Vector<uint8_t>& key) = 0;
 #endif
index 1e8fc06..953f90e 100644 (file)
@@ -45,6 +45,7 @@
 #include "WorkerInspectorController.h"
 #include "WorkerLoaderProxy.h"
 #include "WorkerLocation.h"
+#include "WorkerMessagingProxy.h"
 #include "WorkerNavigator.h"
 #include "WorkerReportingProxy.h"
 #include "WorkerSWClientConnection.h"
@@ -398,48 +399,60 @@ private:
     Vector<uint8_t> m_buffer;
 };
 
+class CryptoBooleanContainer : public ThreadSafeRefCounted<CryptoBooleanContainer> {
+public:
+    static Ref<CryptoBooleanContainer> create() { return adoptRef(*new CryptoBooleanContainer); }
+    bool boolean() const { return m_boolean; }
+    void setBoolean(bool boolean) { m_boolean = boolean; }
+
+private:
+    std::atomic<bool> m_boolean { false };
+};
+
 bool WorkerGlobalScope::wrapCryptoKey(const Vector<uint8_t>& key, Vector<uint8_t>& wrappedKey)
 {
-    bool result = false;
-    std::atomic<bool> done = false;
-    auto container = CryptoBufferContainer::create();
-    m_thread.workerLoaderProxy().postTaskToLoader([&result, key, containerRef = container.copyRef(), &done, workerGlobalScope = this](ScriptExecutionContext& context) {
-        result = context.wrapCryptoKey(key, containerRef->buffer());
-        done = true;
-        workerGlobalScope->postTask([](ScriptExecutionContext& context) {
+    Ref<WorkerGlobalScope> protectedThis(*this);
+    auto resultContainer = CryptoBooleanContainer::create();
+    auto doneContainer = CryptoBooleanContainer::create();
+    auto wrappedKeyContainer = CryptoBufferContainer::create();
+    m_thread.workerLoaderProxy().postTaskToLoader([resultContainer = resultContainer.copyRef(), key, wrappedKeyContainer = wrappedKeyContainer.copyRef(), doneContainer = doneContainer.copyRef(), workerMessagingProxy = makeRef(downcast<WorkerMessagingProxy>(m_thread.workerLoaderProxy()))](ScriptExecutionContext& context) {
+        resultContainer->setBoolean(context.wrapCryptoKey(key, wrappedKeyContainer->buffer()));
+        doneContainer->setBoolean(true);
+        workerMessagingProxy->postTaskForModeToWorkerGlobalScope([](ScriptExecutionContext& context) {
             ASSERT_UNUSED(context, context.isWorkerGlobalScope());
-        });
+        }, WorkerRunLoop::defaultMode());
     });
 
     auto waitResult = MessageQueueMessageReceived;
-    while (!done && waitResult != MessageQueueTerminated)
+    while (!doneContainer->boolean() && waitResult != MessageQueueTerminated)
         waitResult = m_thread.runLoop().runInMode(this, WorkerRunLoop::defaultMode());
 
-    if (done)
-        wrappedKey.swap(container->buffer());
-    return result;
+    if (doneContainer->boolean())
+        wrappedKey.swap(wrappedKeyContainer->buffer());
+    return resultContainer->boolean();
 }
 
 bool WorkerGlobalScope::unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, Vector<uint8_t>& key)
 {
-    bool result = false;
-    std::atomic<bool> done = false;
-    auto container = CryptoBufferContainer::create();
-    m_thread.workerLoaderProxy().postTaskToLoader([&result, wrappedKey, containerRef = container.copyRef(), &done, workerGlobalScope = this](ScriptExecutionContext& context) {
-        result = context.unwrapCryptoKey(wrappedKey, containerRef->buffer());
-        done = true;
-        workerGlobalScope->postTask([](ScriptExecutionContext& context) {
+    Ref<WorkerGlobalScope> protectedThis(*this);
+    auto resultContainer = CryptoBooleanContainer::create();
+    auto doneContainer = CryptoBooleanContainer::create();
+    auto keyContainer = CryptoBufferContainer::create();
+    m_thread.workerLoaderProxy().postTaskToLoader([resultContainer = resultContainer.copyRef(), wrappedKey, keyContainer = keyContainer.copyRef(), doneContainer = doneContainer.copyRef(), workerMessagingProxy = makeRef(downcast<WorkerMessagingProxy>(m_thread.workerLoaderProxy()))](ScriptExecutionContext& context) {
+        resultContainer->setBoolean(context.unwrapCryptoKey(wrappedKey, keyContainer->buffer()));
+        doneContainer->setBoolean(true);
+        workerMessagingProxy->postTaskForModeToWorkerGlobalScope([](ScriptExecutionContext& context) {
             ASSERT_UNUSED(context, context.isWorkerGlobalScope());
-        });
+        }, WorkerRunLoop::defaultMode());
     });
 
     auto waitResult = MessageQueueMessageReceived;
-    while (!done && waitResult != MessageQueueTerminated)
+    while (!doneContainer->boolean() && waitResult != MessageQueueTerminated)
         waitResult = m_thread.runLoop().runInMode(this, WorkerRunLoop::defaultMode());
 
-    if (done)
-        key.swap(container->buffer());
-    return result;
+    if (doneContainer->boolean())
+        key.swap(keyContainer->buffer());
+    return resultContainer->boolean();
 }
 
 #endif // ENABLE(WEB_CRYPTO)
index bc71b56..39b8fc5 100644 (file)
@@ -167,13 +167,6 @@ private:
     SecurityOrigin& topOrigin() const final { return m_topOrigin.get(); }
 
 #if ENABLE(WEB_CRYPTO)
-    // The following two functions are side effects of providing extra protection to serialized
-    // CryptoKey data that went through the structured clone algorithm to local storage such as
-    // IndexedDB. They don't provide any proctection against communications between mainThread
-    // and workerThreads. In fact, they cause extra expense as workerThreads cannot talk to clients
-    // to unwrap/wrap crypto keys. Hence, workerThreads must always ask mainThread to unwrap/wrap
-    // keys, which results in a second communication and plain keys being transferred between
-    // workerThreads and the mainThread.
     bool wrapCryptoKey(const Vector<uint8_t>& key, Vector<uint8_t>& wrappedKey) final;
     bool unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, Vector<uint8_t>& key) final;
 #endif
index 4a63379..61531ed 100644 (file)
@@ -45,6 +45,8 @@ class WorkerLoaderProxy {
 public:
     virtual ~WorkerLoaderProxy() = default;
 
+    virtual bool isWorkerMessagingProxy() const { return false; }
+
     // Creates a cache storage connection to be used on the main thread. Method must be called on the main thread.
     virtual Ref<CacheStorageConnection> createCacheStorageConnection() = 0;
 
index 5c78332..8bf7573 100644 (file)
@@ -43,6 +43,10 @@ public:
     explicit WorkerMessagingProxy(Worker&);
     virtual ~WorkerMessagingProxy();
 
+    // Implementation of WorkerLoaderProxy.
+    // This method is used in the main thread to post task back to the worker thread.
+    bool postTaskForModeToWorkerGlobalScope(ScriptExecutionContext::Task&&, const String& mode) final;
+
 private:
     // Implementations of WorkerGlobalScopeProxy.
     // (Only use these functions in the worker object thread.)
@@ -70,8 +74,8 @@ private:
     // Implementation of WorkerLoaderProxy.
     // These functions are called on different threads to schedule loading
     // requests and to send callbacks back to WorkerGlobalScope.
+    bool isWorkerMessagingProxy() const final { return true; }
     void postTaskToLoader(ScriptExecutionContext::Task&&) final;
-    bool postTaskForModeToWorkerGlobalScope(ScriptExecutionContext::Task&&, const String& mode) final;
     Ref<CacheStorageConnection> createCacheStorageConnection() final;
 
     void workerThreadCreated(DedicatedWorkerThread&);
@@ -98,3 +102,7 @@ private:
 };
 
 } // namespace WebCore
+
+SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::WorkerMessagingProxy)
+    static bool isType(const WebCore::WorkerLoaderProxy& proxy) { return proxy.isWorkerMessagingProxy(); }
+SPECIALIZE_TYPE_TRAITS_END()
index 8bb053e..8fb986e 100644 (file)
@@ -1,3 +1,22 @@
+2019-10-07  Jiewen Tan  <jiewen_tan@apple.com>
+
+        Only wrapping CryptoKeys for IDB during serialization
+        https://bugs.webkit.org/show_bug.cgi?id=202500
+        <rdar://problem/52445927>
+
+        Reviewed by Chris Dumez.
+
+        Modifies IndexedDB.StructuredCloneBackwardCompatibility test to include CryptoKeys.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.mm:
+        (-[StructuredCloneBackwardCompatibilityNavigationDelegate _webCryptoMasterKeyForWebView:]):
+        (TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3:
+        * TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm:
+        * TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal:
+        * TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibilityRead.html:
+        * TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibilityWrite.html:
+
 2019-10-07  Dean Jackson  <dino@apple.com>
 
         Various filter-build-webkit updates
index f1e4216..af81aa3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017-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
@@ -53,6 +53,18 @@ static Deque<RetainPtr<WKScriptMessage>> scriptMessages;
 
 @end
 
+@interface StructuredCloneBackwardCompatibilityNavigationDelegate : NSObject <WKNavigationDelegate>
+@end
+
+@implementation StructuredCloneBackwardCompatibilityNavigationDelegate
+
+- (NSData *)_webCryptoMasterKeyForWebView:(WKWebView *)webView
+{
+    return [NSData dataWithBytes:(const uint8_t*)"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" length:16];
+}
+
+@end
+
 static WKScriptMessage *getNextMessage()
 {
     if (scriptMessages.isEmpty()) {
@@ -92,6 +104,8 @@ TEST(IndexedDB, StructuredCloneBackwardCompatibility)
 
     // Run the test
     RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    auto delegate = adoptNS([[StructuredCloneBackwardCompatibilityNavigationDelegate alloc] init]);
+    [webView setNavigationDelegate:delegate.get()];
     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"IndexedDBStructuredCloneBackwardCompatibilityRead" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
     [webView loadRequest:request];
 
index 3420cd8..86bbea7 100644 (file)
Binary files a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3 and b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3 differ
index e78158d..b4ec734 100644 (file)
Binary files a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm and b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-shm differ
index c75af57..f3d5ed4 100644 (file)
Binary files a/Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal and b/Tools/TestWebKitAPI/Tests/WebKitCocoa/IndexedDBStructuredCloneBackwardCompatibility.sqlite3-wal differ
index 0f47619..c08b292 100644 (file)
@@ -1,21 +1,21 @@
 <script>
     function log(msg)
     {
-//        alert(msg);
+        // alert(msg);
         window.webkit.messageHandlers.testHandler.postMessage(msg);
     }
 
     function shouldBeType(_a, _type)
     {
-        var exception;
-        var _av;
+        let exception;
+        let _av;
         try {
             _av = eval(_a);
         } catch (e) {
             exception = e;
         }
 
-        var _typev = eval(_type);
+        const _typev = eval(_type);
         if (_av instanceof _typev) {
             return true;
         } else {
         }
     }
 
+    function shouldBeCryptoKey(_a, _key)
+    {
+        if (!(JSON.stringify(_key.key_ops) == JSON.stringify(_a.key_ops)) || !(_key.ext == _a.ext))
+            return false;
+        if (_key.kty  == "oct" && _a.kty == "oct")
+            return (_key.k == _a.k) && (_key.alg == _a.alg);
+        if (_key.kty  == "EC" && _a.kty == "EC")
+            return (_key.k == _a.k) && (_key.crv == _a.crv) && (_key.x == _a.x) && (_key.y == _a.y) && (_key.d == _a.d);
+        if (_key.kty  == "RSA" && _a.kty == "RSA")
+            return (_key.alg == _a.alg) && (_key.n == _a.n) && (_key.e == _a.e) && (_key.d == _a.d) && (_key.p == _a.p) && (_key.q == _a.q) && (_key.dp == _a.dp) && (_key.dq == _a.dq) && (_key.qi == _a.qi);
+        return false;
+    }
+
     // Values that are used to infer the type of a given primitive type.
-    var testValues = [
+    const testValues = [
         undefined,
         null,
         5, // Int
     ];
 
     // Values that are used to infer the type of a given Boolean/String type.
-    var testTypeValues = [
+    const testTypeValues = [
         { item: new Boolean(true), type: "Boolean", value: true },
         { item: new Boolean(false), type: "Boolean", value: false },
         { item: new String(), type: "String", value: "" }
     ];
 
-    var openRequest = indexedDB.open("backward_compatibility");
+    // Values are in JWK formats.
+    const testCryptoKeyValues = [
+        { kty: "oct", k: "YWJjZGVmZ2gxMjM0NTY3OA", alg: "A128CBC", use: "enc", key_ops: ['decrypt', 'encrypt', 'unwrapKey', 'wrapKey'], ext: true },
+        { kty: "EC", use: "sig", ext: true, crv: "P-256", x: "1FSVWieTvikFkG1NOyhkUCaMbdQhxwH6aCu4Ez-sRtA", y: "9jmNTLqM4cjBhdAnHcNI9YQV3O8LFmo-EdZWk8ntAaI", key_ops: ['verify'] },
+        { kty: "EC", use: "sig", ext: true, key_ops: ["sign"], crv: "P-256", x: "1FSVWieTvikFkG1NOyhkUCaMbdQhxwH6aCu4Ez-sRtA", y: "9jmNTLqM4cjBhdAnHcNI9YQV3O8LFmo-EdZWk8ntAaI", d: "ppxBSov3N8_AUcisAuvmLV4yE8e_L_BLE8bZb9Z1Xjg" },
+        { kty: "oct", k: "YWJjZGVmZ2gxMjM0NTY3OA", alg: "HS256", use: "sig", key_ops: ["sign", "verify"], ext: true, },
+        { kty: "RSA", alg: "PS256", use: "sig", key_ops: ["verify"], ext: true, n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw", e: "AQAB" },
+        { kty: "RSA", alg: "PS1", use: "sig", key_ops: ["sign"], ext: true, n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw", e: "AQAB", d: "eNLS37aCz7RXSNPD_DtLBJ6j5T8cSxdzRBCjPaI6WcGqJp16lq3UTwuoDLAqlA9oGYm238dsIWpuucP_lQtbWe-7SpxoI6_vmYGf7YVUHv1-DF9qiOmSrMmdxMnVOzYXY8RaT6thPjn_J5cfLV2xI_LwsrMtmpdSyNlgX0zTUhwtuahgAKMEChYjH2EnjHdHw6sY2-wApdcQI7ULE0oo5RzbQZpmuhcN9hiBc0L3hhF0qo50mbl02_65_GQ7DpVkXBxNgRBLzlPabmzzG2oAhfefLgYmSC1opaCkXE6vRWQNWNL45RZNZFYM3uoJghOMqGeocM0BpjdChHrPOlFvSQ", p: "4miTuAjKMeH5uJ5KB397QUwhbkYEgSbcA2mifmSkvE2018gb55qkBHK1eVryf1_m43LNlc6O_ak6gfzdZIZvS5NCGjPl0q09plUpu8qFOSspBwA67qGH76lFlZLn_d4yglS7wfLru4_5Ys8qLLs-DqVLviwposOnyyWqwM5AXp0", q: "xHYrzkivtmnz_sGchnWGc0q-pDOkKicptRpv2pMFIIXxnFX5aMeEXIZjVujXtwUy1UlFIN2GZJSvy5KJ79mu_XyNnFHMzedH-A3ee3u8h1UUrZF-vUu1_e4U_x67NN1dedzUSKynN7pFl3OkuShMBWGV-cwzOPdcVAfVuZlxUMc", dp: "fBzDzYDUBmBQGop7Hn0dvf_T27V6RqpctWo074CQZcFbP2atFVtKSj3viWT3xid2VHzcgiDHdfpM3nEVlEO1wwIonGCSvdjGEOZiiFVOjrZAOVxA8guOjyyFvqbXke06VwPIIVvfKeSU2zuhbP__1tt6F_fxow4Kb2xonGT0GGk", dq: "jmE2DiIPdhwDgLXAQpIaBqQ81bO3XfVT_LRULAwwwwlPuQV148H04zlh9TJ6Y2GZHYokV1U0eOBpJxfkb7dLYtpJpuiBjRf4yIUEoGlkkI_QlJnFSFr-YjGRdfNHqWBkxlSMZL770R9mIATndGkH7z5x-r9KwBZFC4FCG2hg_zE", qi: "YCX_pLwbMBA1ThVH0WcwmnytqNcrMCEwTm7ByA2eU6nWbQrULvf7m9_kzfLUcjsnpAVlBQG5JMXMy0Sq4ptwbywsa5-G8KAOOOR2L3v4hC-Eys9ftgFM_3i0o40eeQH4b3haPbntrIeMg8IzlOuVYKf9-2QuKDoWeRdd7NsdxTk" }
+    ];
+
+    const openRequest = indexedDB.open("backward_compatibility");
     openRequest.onerror = function(event) {
         log("Error: " + event.target.error.name);
-    }
+    };
     openRequest.onsuccess = function(event) {
         db = event.target.result;
         readType();
-    }
+    };
 
-    var result = true;
+    let result = true;
     function readType()
     {
-        var objectStore = db.transaction("type").objectStore("type");
+        const objectStore = db.transaction("type").objectStore("type");
         objectStore.openCursor().onsuccess = function(event) {
             cursor = event.target.result;
             if (cursor) {
@@ -69,7 +92,7 @@
 
     function readValue()
     {
-        var objectStore = db.transaction("value").objectStore("value");
+        const objectStore = db.transaction("value").objectStore("value");
         objectStore.openCursor().onsuccess = function(event) {
             cursor = event.target.result;
             if (cursor) {
 
     function readTypeValue()
     {
-        var objectStore = db.transaction("typeValue").objectStore("typeValue");
+        const objectStore = db.transaction("typeValue").objectStore("typeValue");
         objectStore.openCursor().onsuccess = function(event) {
             cursor = event.target.result;
             if (cursor) {
                 result = result && shouldBeType("cursor.value", testTypeValues[cursor.key - 1].type) && (cursor.value == testTypeValues[cursor.key - 1].value);
                 cursor.continue();
             } else {
+                readCryptoKey();
+            }
+        };
+    }
+
+    async function readCryptoKey()
+    {
+        const objectStore = db.transaction("cryptoKey").objectStore("cryptoKey");
+        objectStore.openCursor().onsuccess = async function(event) {
+            cursor = event.target.result;
+            if (cursor) {
+                const jwkKey = await crypto.subtle.exportKey("jwk", cursor.value);
+                result = result && shouldBeCryptoKey(jwkKey, testCryptoKeyValues[cursor.key - 1]);
+                cursor.continue();
+            } else {
                 if (result)
                     log("Pass");
                 else
index b266ecc..0e08d43 100644 (file)
@@ -1,21 +1,21 @@
 <script>
     function log(msg)
     {
-//        alert(msg);
+        // alert(msg);
         window.webkit.messageHandlers.testHandler.postMessage(msg);
     }
 
     function shouldBeType(_a, _type)
     {
-        var exception;
-        var _av;
+        let exception;
+        let _av;
         try {
             _av = eval(_a);
         } catch (e) {
             exception = e;
         }
 
-        var _typev = eval(_type);
+        const _typev = eval(_type);
         if (_av instanceof _typev) {
             return true;
         } else {
         }
     }
 
+    function shouldBeCryptoKey(_a, _key)
+    {
+        if (!(JSON.stringify(_key.key_ops) == JSON.stringify(_a.key_ops)) || !(_key.ext == _a.ext))
+            return false;
+        if (_key.kty  == "oct" && _a.kty == "oct")
+            return (_key.k == _a.k) && (_key.alg == _a.alg);
+        if (_key.kty  == "EC" && _a.kty == "EC")
+            return (_key.k == _a.k) && (_key.crv == _a.crv) && (_key.x == _a.x) && (_key.y == _a.y) && (_key.d == _a.d);
+        if (_key.kty  == "RSA" && _a.kty == "RSA")
+            return (_key.alg == _a.alg) && (_key.n == _a.n) && (_key.e == _a.e) && (_key.d == _a.d) && (_key.p == _a.p) && (_key.q == _a.q) && (_key.dp == _a.dp) && (_key.dq == _a.dq) && (_key.qi == _a.qi);
+        return false;
+    }
+
     // Values that are used to infer the type of a given primitive type.
-    var testValues = [
+    const testValues = [
         undefined,
         null,
         5, // Int
     ];
 
     // Values that are used to infer the type of a given Boolean/String type.
-    var testTypeValues = [
+    const testTypeValues = [
         { item: new Boolean(true), type: "Boolean", value: true },
         { item: new Boolean(false), type: "Boolean", value: false },
         { item: new String(), type: "String", value: "" }
     ];
 
+    // Values are in JWK formats.
+    const testCryptoKeyValues = [
+        { kty: "oct", k: "YWJjZGVmZ2gxMjM0NTY3OA", alg: "A128CBC", use: "enc", key_ops: ['decrypt', 'encrypt', 'unwrapKey', 'wrapKey'], ext: true },
+        { kty: "EC", use: "sig", ext: true, crv: "P-256", x: "1FSVWieTvikFkG1NOyhkUCaMbdQhxwH6aCu4Ez-sRtA", y: "9jmNTLqM4cjBhdAnHcNI9YQV3O8LFmo-EdZWk8ntAaI", key_ops: ['verify'] },
+        { kty: "EC", use: "sig", ext: true, key_ops: ["sign"], crv: "P-256", x: "1FSVWieTvikFkG1NOyhkUCaMbdQhxwH6aCu4Ez-sRtA", y: "9jmNTLqM4cjBhdAnHcNI9YQV3O8LFmo-EdZWk8ntAaI", d: "ppxBSov3N8_AUcisAuvmLV4yE8e_L_BLE8bZb9Z1Xjg" },
+        { kty: "oct", k: "YWJjZGVmZ2gxMjM0NTY3OA", alg: "HS256", use: "sig", key_ops: ["sign", "verify"], ext: true, },
+        { kty: "RSA", alg: "PS256", use: "sig", key_ops: ["verify"], ext: true, n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw", e: "AQAB" },
+        { kty: "RSA", alg: "PS1", use: "sig", key_ops: ["sign"], ext: true, n: "rcCUCv7Oc1HVam1DIhCzqknThWawOp8QLk8Ziy2p10ByjQFCajoFiyuAWl-R1WXZaf4xitLRracT9agpzIzc-MbLSHIGgWQGO21lGiImy5ftZ-D8bHAqRz2y15pzD4c4CEou7XSSLDoRnR0QG5MsDhD6s2gV9mwHkrtkCxtMWdBi-77as8wGmlNRldcOSgZDLK8UnCSgA1OguZ989bFyc8tOOEIb0xUSfPSz3LPSCnyYz68aDjmKVeNH-ig857OScyWbGyEy3Biw64qun3juUlNWsJ3zngkOdteYWytx5Qr4XKNs6R-Myyq72KUp02mJDZiiyiglxML_i3-_CeecCw", e: "AQAB", d: "eNLS37aCz7RXSNPD_DtLBJ6j5T8cSxdzRBCjPaI6WcGqJp16lq3UTwuoDLAqlA9oGYm238dsIWpuucP_lQtbWe-7SpxoI6_vmYGf7YVUHv1-DF9qiOmSrMmdxMnVOzYXY8RaT6thPjn_J5cfLV2xI_LwsrMtmpdSyNlgX0zTUhwtuahgAKMEChYjH2EnjHdHw6sY2-wApdcQI7ULE0oo5RzbQZpmuhcN9hiBc0L3hhF0qo50mbl02_65_GQ7DpVkXBxNgRBLzlPabmzzG2oAhfefLgYmSC1opaCkXE6vRWQNWNL45RZNZFYM3uoJghOMqGeocM0BpjdChHrPOlFvSQ", p: "4miTuAjKMeH5uJ5KB397QUwhbkYEgSbcA2mifmSkvE2018gb55qkBHK1eVryf1_m43LNlc6O_ak6gfzdZIZvS5NCGjPl0q09plUpu8qFOSspBwA67qGH76lFlZLn_d4yglS7wfLru4_5Ys8qLLs-DqVLviwposOnyyWqwM5AXp0", q: "xHYrzkivtmnz_sGchnWGc0q-pDOkKicptRpv2pMFIIXxnFX5aMeEXIZjVujXtwUy1UlFIN2GZJSvy5KJ79mu_XyNnFHMzedH-A3ee3u8h1UUrZF-vUu1_e4U_x67NN1dedzUSKynN7pFl3OkuShMBWGV-cwzOPdcVAfVuZlxUMc", dp: "fBzDzYDUBmBQGop7Hn0dvf_T27V6RqpctWo074CQZcFbP2atFVtKSj3viWT3xid2VHzcgiDHdfpM3nEVlEO1wwIonGCSvdjGEOZiiFVOjrZAOVxA8guOjyyFvqbXke06VwPIIVvfKeSU2zuhbP__1tt6F_fxow4Kb2xonGT0GGk", dq: "jmE2DiIPdhwDgLXAQpIaBqQ81bO3XfVT_LRULAwwwwlPuQV148H04zlh9TJ6Y2GZHYokV1U0eOBpJxfkb7dLYtpJpuiBjRf4yIUEoGlkkI_QlJnFSFr-YjGRdfNHqWBkxlSMZL770R9mIATndGkH7z5x-r9KwBZFC4FCG2hg_zE", qi: "YCX_pLwbMBA1ThVH0WcwmnytqNcrMCEwTm7ByA2eU6nWbQrULvf7m9_kzfLUcjsnpAVlBQG5JMXMy0Sq4ptwbywsa5-G8KAOOOR2L3v4hC-Eys9ftgFM_3i0o40eeQH4b3haPbntrIeMg8IzlOuVYKf9-2QuKDoWeRdd7NsdxTk" }
+    ];
+
     indexedDB.deleteDatabase("backward_compatibility");
-    var openRequest = indexedDB.open("backward_compatibility");
+    const openRequest = indexedDB.open("backward_compatibility");
     openRequest.onupgradeneeded = function(event) {
         // This objectStore stores elements that we only need to check its type to determine its serialization tag.
-        var objectStore = event.target.result.createObjectStore("type");
+        event.target.result.createObjectStore("type");
         // This objectStore stores elements that we need to check its value to determine its serialization tag.
-        var objectStore = event.target.result.createObjectStore("value", { autoIncrement : true });
+        event.target.result.createObjectStore("value", { autoIncrement : true });
         // This objectStore stores elements that we need to check both its type and its value to determine its serialization tag.
-        var objectStore = event.target.result.createObjectStore("typeValue", { autoIncrement : true });
-    }
+        event.target.result.createObjectStore("typeValue", { autoIncrement : true });
+        // This objectStore stores crypto keys.
+        event.target.result.createObjectStore("cryptoKey", { autoIncrement : true });
+    };
     openRequest.onerror = function(event) {
         log("Error: " + event.target.error.name);
-    }
+    };
     openRequest.onsuccess = function(event) {
         db = event.target.result;
         storeType();
-    }
+    };
 
     function storeType()
     {
-        var transaction = db.transaction("type", "readwrite");
+        const transaction = db.transaction("type", "readwrite");
         transaction.onerror = function(event) {
             log("Error: " + event.target.error.name);
-        }
-        transaction.oncomplete = function(event) { storeValue(); }
+        };
+        transaction.oncomplete = function(event) { storeValue(); };
 
-        var objectStore = transaction.objectStore("type");
+        const objectStore = transaction.objectStore("type");
         // The key of an item is the type of its value, hence we could determine type mismatch, i.e. serialization tag reorder
         // simply by using an item's key and value.
         objectStore.add([1, 2], "Array");
 
     function storeValue()
     {
-        var transaction = db.transaction("value", "readwrite");
+        const transaction = db.transaction("value", "readwrite");
         transaction.onerror = function(event) {
             log("Error: " + event.target.error.name);
-        }
-        transaction.oncomplete = function(event) { storeTypeValue(); }
+        };
+        transaction.oncomplete = function(event) { storeTypeValue(); };
 
-        var objectStore = transaction.objectStore("value");
+        const objectStore = transaction.objectStore("value");
         // Here we store values in the test matrix in order and read them in order to see if any mismatch happens.
-        for (var i = 0; i < testValues.length; i++)
+        for (let i = 0; i < testValues.length; i++)
             objectStore.add(testValues[i]);
     }
 
     function storeTypeValue()
     {
-        var transaction = db.transaction("typeValue", "readwrite");
+        const transaction = db.transaction("typeValue", "readwrite");
         transaction.onerror = function(event) {
             log("Error: " + event.target.error.name);
-        }
-        transaction.oncomplete = function(event) { readType(); }
+        };
+        transaction.oncomplete = function(event) { storeCryptoKey(); };
 
-        var objectStore = transaction.objectStore("typeValue");
+        const objectStore = transaction.objectStore("typeValue");
         // Here we store items in the test matrix in order and read them in order to see if any mismatches its type and value.
-        for (var i = 0; i < testTypeValues.length; i++)
+        for (let i = 0; i < testTypeValues.length; i++)
             objectStore.add(testTypeValues[i].item);
     }
 
-    var result = true;
+    async function storeCryptoKey()
+    {
+        const transaction = db.transaction("cryptoKey", "readwrite");
+        transaction.onerror = function(event) {
+            log("Error: " + event.target.error.name);
+        };
+        transaction.oncomplete = function(event) { readType(); };
+
+        const objectStore = transaction.objectStore("cryptoKey");
+        // Here we store items in the test matrix in order and read them in order to see if any mismatches its type and value.
+        let cryptoKey = await crypto.subtle.importKey("jwk", testCryptoKeyValues[0], "aes-cbc", true, ["encrypt", "decrypt", "wrapKey", "unwrapKey"]);
+        objectStore.add(cryptoKey);
+        cryptoKey = await crypto.subtle.importKey("jwk", testCryptoKeyValues[1], { name: "ECDSA", namedCurve: "P-256" }, true, ['verify']);
+        objectStore.add(cryptoKey);
+        cryptoKey = await crypto.subtle.importKey("jwk", testCryptoKeyValues[2], { name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]);
+        objectStore.add(cryptoKey);
+        cryptoKey = await crypto.subtle.importKey("jwk", testCryptoKeyValues[3], {name: "hmac", hash: "sha-256"}, true, ["sign", "verify"]);
+        objectStore.add(cryptoKey);
+        cryptoKey = await crypto.subtle.importKey("jwk", testCryptoKeyValues[4], {name: "RSA-PSS", hash: "SHA-256"}, true, ["verify"]);
+        objectStore.add(cryptoKey);
+        cryptoKey = await crypto.subtle.importKey("jwk", testCryptoKeyValues[5], {name: "RSA-PSS", hash: "SHA-1"}, true, ["sign"]);
+        objectStore.add(cryptoKey);
+    }
+
+    let result = true;
     function readType()
     {
-        var objectStore = db.transaction("type").objectStore("type");
+        const objectStore = db.transaction("type").objectStore("type");
         objectStore.openCursor().onsuccess = function(event) {
             cursor = event.target.result;
             if (cursor) {
 
     function readValue()
     {
-        var objectStore = db.transaction("value").objectStore("value");
+        const objectStore = db.transaction("value").objectStore("value");
         objectStore.openCursor().onsuccess = function(event) {
             cursor = event.target.result;
             if (cursor) {
 
     function readTypeValue()
     {
-        var objectStore = db.transaction("typeValue").objectStore("typeValue");
+        const objectStore = db.transaction("typeValue").objectStore("typeValue");
         objectStore.openCursor().onsuccess = function(event) {
             cursor = event.target.result;
             if (cursor) {
                 result = result && shouldBeType("cursor.value", testTypeValues[cursor.key - 1].type) && (cursor.value == testTypeValues[cursor.key - 1].value);
                 cursor.continue();
             } else {
+                readCryptoKey();
+            }
+        };
+    }
+
+    async function readCryptoKey()
+    {
+        const objectStore = db.transaction("cryptoKey").objectStore("cryptoKey");
+        objectStore.openCursor().onsuccess = async function(event) {
+            cursor = event.target.result;
+            if (cursor) {
+                const jwkKey = await crypto.subtle.exportKey("jwk", cursor.value);
+                result = result && shouldBeCryptoKey(jwkKey, testCryptoKeyValues[cursor.key - 1]);
+                cursor.continue();
+            } else {
                 if (result)
                     log("Pass");
                 else