[WebAuthN] Import a JS CBOR coder
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 1 Oct 2018 19:47:05 +0000 (19:47 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 1 Oct 2018 19:47:05 +0000 (19:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189877
<rdar://problem/44701124>

Reviewed by Chris Dumez.

Source/WebKit:

Update MockWebAuthenticationConfiguration to have userCertificateBase64 and intermediateCACertificateBase64
as Local's memeber such that tests can pass those certificates to MockLocalConnection instead of letting it
holds some static ones.

* UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
(WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):
* UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
(WebKit::MockLocalConnection::getAttestation const):
* UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:

Tools:

Add logic to only process privateKeyBase64, userCertificateBase64 and intermediateCACertificateBase64
only if acceptAttestation is true.

* WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::setWebAuthenticationMockConfiguration):

LayoutTests:

This patch import a 3rd party JS CBOR coder from https://github.com/paroga/cbor-js.
The library is MIT licensed, which should be fine to use within WebKit.

As a benefit from the library, tests are updated to check CBOR binaries.

* http/wpt/credential-management/credentialscontainer-store-basics.https.html:
* http/wpt/webauthn/idl.https.html:
* http/wpt/webauthn/public-key-credential-create-failure-local.https.html:
* http/wpt/webauthn/public-key-credential-create-success-local.https.html:
* http/wpt/webauthn/public-key-credential-get-failure-local.https.html:
* http/wpt/webauthn/public-key-credential-get-success-local.https.html:
* http/wpt/webauthn/resources/cbor.js: Added.
* http/wpt/webauthn/resources/util.js:

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

15 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/credential-management/credentialscontainer-store-basics.https.html
LayoutTests/http/wpt/webauthn/idl.https.html
LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https.html
LayoutTests/http/wpt/webauthn/public-key-credential-create-success-local.https.html
LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-local.https.html
LayoutTests/http/wpt/webauthn/public-key-credential-get-success-local.https.html
LayoutTests/http/wpt/webauthn/resources/cbor.js [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/resources/util.js
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreRef.cpp
Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.mm
Source/WebKit/UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h
Tools/ChangeLog
Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

index f988257..31e0ccd 100644 (file)
@@ -1,3 +1,25 @@
+2018-10-01  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import a JS CBOR coder
+        https://bugs.webkit.org/show_bug.cgi?id=189877
+        <rdar://problem/44701124>
+
+        Reviewed by Chris Dumez.
+
+        This patch import a 3rd party JS CBOR coder from https://github.com/paroga/cbor-js.
+        The library is MIT licensed, which should be fine to use within WebKit.
+
+        As a benefit from the library, tests are updated to check CBOR binaries.
+
+        * http/wpt/credential-management/credentialscontainer-store-basics.https.html:
+        * http/wpt/webauthn/idl.https.html:
+        * http/wpt/webauthn/public-key-credential-create-failure-local.https.html:
+        * http/wpt/webauthn/public-key-credential-create-success-local.https.html:
+        * http/wpt/webauthn/public-key-credential-get-failure-local.https.html:
+        * http/wpt/webauthn/public-key-credential-get-success-local.https.html:
+        * http/wpt/webauthn/resources/cbor.js: Added.
+        * http/wpt/webauthn/resources/util.js:
+
 2018-10-01  Daniel Bates  <dabates@apple.com>
 
         [iOS] Special keys are misidentified in DOM keyboard events
index c5df029..10d8356 100644 (file)
@@ -7,6 +7,31 @@
         "BDj/zxSkzKgaBuS3cdWDF558of8AaIpgFpsjF/Qm1749VBJPgqUIwfhWHJ91nb7U" +
         "PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6" +
         "RQ==";
+    const testAttestationCertificateBase64 =
+        "MIIB6jCCAZCgAwIBAgIGAWHAxcjvMAoGCCqGSM49BAMCMFMxJzAlBgNVBAMMHkJh" +
+        "c2ljIEF0dGVzdGF0aW9uIFVzZXIgU3ViIENBMTETMBEGA1UECgwKQXBwbGUgSW5j" +
+        "LjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0xODAyMjMwMzM3MjJaFw0xODAyMjQw" +
+        "MzQ3MjJaMGoxIjAgBgNVBAMMGTAwMDA4MDEwLTAwMEE0OUEyMzBBMDIxM0ExGjAY" +
+        "BgNVBAsMEUJBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMw" +
+        "EQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvCje" +
+        "Pzr6Sg76XMoHuGabPaG6zjpLFL8Zd8/74Hh5PcL2Zq+o+f7ENXX+7nEXXYt0S8Ux" +
+        "5TIRw4hgbfxXQbWLEqM5MDcwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAw" +
+        "FwYJKoZIhvdjZAgCBAowCKEGBAR0ZXN0MAoGCCqGSM49BAMCA0gAMEUCIAlK8A8I" +
+        "k43TbvKuYGHZs1DTgpTwmKTBvIUw5bwgZuYnAiEAtuJjDLKbGNJAJFMi5deEBqno" +
+        "pBTCqbfbDJccfyQpjnY=";
+    const testAttestationIssuingCACertificateBase64 =
+        "MIICIzCCAaigAwIBAgIIeNjhG9tnDGgwCgYIKoZIzj0EAwIwUzEnMCUGA1UEAwwe" +
+        "QmFzaWMgQXR0ZXN0YXRpb24gVXNlciBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ" +
+        "bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTE3MDQyMDAwNDIwMFoXDTMyMDMy" +
+        "MjAwMDAwMFowUzEnMCUGA1UEAwweQmFzaWMgQXR0ZXN0YXRpb24gVXNlciBTdWIg" +
+        "Q0ExMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkw" +
+        "EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoSZ/1t9eBAEVp5a8PrXacmbGb8zNC1X3" +
+        "StLI9YO6Y0CL7blHmSGmjGWTwD4Q+i0J2BY3+bPHTGRyA9jGB3MSbaNmMGQwEgYD" +
+        "VR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBSD5aMhnrB0w/lhkP2XTiMQdqSj" +
+        "8jAdBgNVHQ4EFgQU5mWf1DYLTXUdQ9xmOH/uqeNSD80wDgYDVR0PAQH/BAQDAgEG" +
+        "MAoGCCqGSM49BAMCA2kAMGYCMQC3M360LLtJS60Z9q3vVjJxMgMcFQ1roGTUcKqv" +
+        "W+4hJ4CeJjySXTgq6IEHn/yWab4CMQCm5NnK6SOSK+AqWum9lL87W3E6AA1f2TvJ" +
+        "/hgok/34jr93nhS87tOQNdxDS8zyiqw=";
     const testRpId = "localhost";
     function asciiToUint8Array(str)
     {
             chars.push(str.charCodeAt(i));
         return new Uint8Array(chars);
     }
-    function hexStringToUint8Array(hexString)
-    {
-        if (hexString.length % 2 != 0)
-            throw "Invalid hexString";
-        var arrayBuffer = new Uint8Array(hexString.length / 2);
-
-        for (var i = 0; i < hexString.length; i += 2) {
-            var byteValue = parseInt(hexString.substr(i, 2), 16);
-            if (byteValue == NaN)
-                throw "Invalid hexString";
-            arrayBuffer[i/2] = byteValue;
-        }
-
-        return arrayBuffer;
-    }
 
     promise_test(async function(t) {
         const options = {
         };
         // A mock attestation object
         if (window.testRunner)
-            testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: true, privateKeyBase64: testES256PrivateKeyBase64 } });
+            testRunner.setWebAuthenticationMockConfiguration({
+                local: {
+                    acceptAuthentication: true,
+                    acceptAttestation: true,
+                    privateKeyBase64: testES256PrivateKeyBase64,
+                    userCertificateBase64: testAttestationCertificateBase64,
+                    intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64
+                }
+            });
         const credential = await navigator.credentials.create(options);
 
         return promise_rejects(t, "NotSupportedError",
index ab41829..db63f6c 100644 (file)
 
 <script>
 if (window.testRunner)
-    testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: true, privateKeyBase64: testES256PrivateKeyBase64 } });
+    testRunner.setWebAuthenticationMockConfiguration({
+        local: {
+            acceptAuthentication: true,
+            acceptAttestation: true,
+            privateKeyBase64: testES256PrivateKeyBase64,
+            userCertificateBase64: testAttestationCertificateBase64,
+            intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64
+        }
+    });
 
 promise_test(async () => {
     const idlURL = ["WebAuthN.idl"];
index 2bc4d1d..3a6e49a 100644 (file)
@@ -6,7 +6,7 @@
 <script>
     // Default mock configuration. Tests need to override if they need different configuration.
     if (window.testRunner)
-        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, privateKeyBase64: "" } });
+        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false } });
 
     promise_test(t => {
         const options = {
@@ -39,7 +39,7 @@
                 },
                 challenge: asciiToUint8Array("123456"),
                 pubKeyCredParams: [{ type: "public-key", alg: -7 }],
-                excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64url) }]
+                excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }]
             }
         };
         if (window.testRunner)
                 challenge: asciiToUint8Array("123456"),
                 pubKeyCredParams: [{ type: "public-key", alg: -7 }],
                 excludeCredentials: [
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["usb"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["nfc"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["ble"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["internal"] }
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["usb"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["nfc"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["ble"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["internal"] }
                 ]
             }
         };
             }
         };
         if (window.testRunner)
-            testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: "" } });
+            testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } });
         return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Unknown internal error.");
     }, "PublicKeyCredential's [[create]] without attestation in a mock local authenticator.");
 
             }
         };
         if (window.testRunner) {
-            testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: "" } });
+            testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } });
             testRunner.addTestKeyToKeychain(testES256PrivateKeyBase64, testRpId, testUserhandleBase64);
         }
         return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Unknown internal error.").then(() => {
index 415fea2..9c2d116 100644 (file)
@@ -3,10 +3,19 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="./resources/util.js"></script>
+<script src="./resources/cbor.js"></script>
 <script>
     // Default mock configuration. Tests need to override if they need different configuration.
     if (window.testRunner)
-        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: true, privateKeyBase64: testES256PrivateKeyBase64 } });
+        testRunner.setWebAuthenticationMockConfiguration({
+            local: {
+                acceptAuthentication: true,
+                acceptAttestation: true,
+                privateKeyBase64: testES256PrivateKeyBase64,
+                userCertificateBase64: testAttestationCertificateBase64,
+                intermediateCACertificateBase64: testAttestationIssuingCACertificateBase64
+            }
+        });
 
     function checkResult(credential)
     {
         }
 
         // Check respond
-        assert_equals(credential.id, testCredentialIdBase64url);
+        assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testCredentialIdBase64));
         assert_equals(credential.type, 'public-key');
-        assert_equals(bytesToHexString(credential.rawId), "48c4971e7805ee110eb04940ef70b7458fbc6d1e");
+        assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testCredentialIdBase64));
         assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443","hashAlgorithm":"SHA-256"}');
-        // FIXME(): Check attestation object
         assert_throws("NotSupportedError", () => { credential.getClientExtensionResults() });
+
+        // Check attestation
+        const attestationObject = CBOR.decode(credential.response.attestationObject);
+        assert_equals(attestationObject.fmt, "Apple");
+        // Check authData
+        const authData = decodeAuthData(attestationObject.authData);
+        assert_equals(bytesToHexString(authData.rpIdHash), "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763");
+        assert_equals(authData.flags, 69);
+        assert_equals(authData.counter, 0);
+        assert_equals(bytesToHexString(authData.aaguid), "00000000000000000000000000000000");
+        assert_array_equals(authData.credentialID, Base64URL.parse(testCredentialIdBase64));
+        // Check self attestation
+        assert_equals(attestationObject.attStmt.alg, -7);
+        assert_true(checkPublicKey(authData.publicKey));
+        assert_equals(attestationObject.attStmt.x5c.length, 2);
+        assert_array_equals(attestationObject.attStmt.x5c[0], Base64URL.parse(testAttestationCertificateBase64));
+        assert_array_equals(attestationObject.attStmt.x5c[1], Base64URL.parse(testAttestationIssuingCACertificateBase64));
+
+        // Check signature
+        let publicKeyData = new Uint8Array(65);
+        publicKeyData[0] = 0x04;
+        publicKeyData.set(authData.publicKey['-2'], 1);
+        publicKeyData.set(authData.publicKey['-3'], 33);
+        return crypto.subtle.importKey("raw", publicKeyData, { name: "ECDSA", namedCurve: "P-256" }, false, ['verify']).then( publicKey => {
+            return crypto.subtle.verify({name: "ECDSA", hash: "SHA-256"}, publicKey, extractRawSignature(attestationObject.attStmt.sig), attestationObject.authData).then( verified => {
+                assert_true(verified);
+            });
+        });
     }
 
     promise_test(t => {
                 challenge: asciiToUint8Array("123456"),
                 pubKeyCredParams: [{ type: "public-key", alg: -7 }],
                 excludeCredentials: [
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["usb"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["nfc"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["ble"] }
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["usb"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["nfc"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["ble"] }
                 ]
             }
         };
index 7f4dd96..f305a43 100644 (file)
@@ -6,17 +6,17 @@
 <script>
     // Default mock configuration. Tests need to override if they need different configuration.
     if (window.testRunner)
-        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false, privateKeyBase64: "" } });
+        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: false, acceptAttestation: false } });
 
     promise_test(t => {
         const options = {
             publicKey: {
                 challenge: asciiToUint8Array("123456"),
                 allowCredentials: [
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["usb"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["nfc"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["ble"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["internal"] }
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["usb"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["nfc"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["ble"] },
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["internal"] }
                 ]
             }
         };
index 67771c2..c617e75 100644 (file)
@@ -6,7 +6,7 @@
 <script>
     // Default mock configuration. Tests need to override if they need different configuration.
     if (window.testRunner)
-        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: "" } });
+        testRunner.setWebAuthenticationMockConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } });
 
     function checkResult(credential)
     {
@@ -14,9 +14,9 @@
             testRunner.cleanUpKeychain(testRpId);
 
          // Check respond
-        assert_equals(credential.id, testCredentialIdBase64url);
+        assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testCredentialIdBase64));
         assert_equals(credential.type, 'public-key');
-        assert_equals(bytesToHexString(credential.rawId), "48c4971e7805ee110eb04940ef70b7458fbc6d1e");
+        assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testCredentialIdBase64));
         assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.get","challenge":"MTIzNDU2","origin":"https://localhost:9443","hashAlgorithm":"SHA-256"}');
         assert_equals(bytesToHexString(credential.response.userHandle), "00010203040506070809");
 
@@ -27,7 +27,7 @@
         assert_equals(authData.counter, 0);
 
         // Check signature
-        return crypto.subtle.importKey("raw", Base64URL.parse(testES256PublicKeyBase64url), { name: "ECDSA", namedCurve: "P-256" }, false, ['verify']).then( publicKey => {
+        return crypto.subtle.importKey("raw", Base64URL.parse(testES256PublicKeyBase64), { name: "ECDSA", namedCurve: "P-256" }, false, ['verify']).then( publicKey => {
             return crypto.subtle.digest("sha-256", credential.response.clientDataJSON).then ( hash => {
                 // credential.response.signature is in ASN.1 and WebCrypto expect signatures provides in r|s.
                 return crypto.subtle.verify({name: "ECDSA", hash: "SHA-256"}, publicKey, extractRawSignature(credential.response.signature), concatenateBuffers(credential.response.authenticatorData, hash)).then( verified => {
@@ -58,7 +58,7 @@
                 challenge: Base64URL.parse("MTIzNDU2"),
                 allowCredentials: [
                     { type: "public-key", id: Base64URL.parse(testUserhandleBase64), transports: ["internal"] },
-                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64url), transports: ["internal"] }
+                    { type: "public-key", id: Base64URL.parse(testCredentialIdBase64), transports: ["internal"] }
                 ]
             }
         };
diff --git a/LayoutTests/http/wpt/webauthn/resources/cbor.js b/LayoutTests/http/wpt/webauthn/resources/cbor.js
new file mode 100644 (file)
index 0000000..3e1f300
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+(function(global, undefined) { "use strict";
+var POW_2_24 = 5.960464477539063e-8,
+    POW_2_32 = 4294967296,
+    POW_2_53 = 9007199254740992;
+
+function encode(value) {
+  var data = new ArrayBuffer(256);
+  var dataView = new DataView(data);
+  var lastLength;
+  var offset = 0;
+
+  function prepareWrite(length) {
+    var newByteLength = data.byteLength;
+    var requiredLength = offset + length;
+    while (newByteLength < requiredLength)
+      newByteLength <<= 1;
+    if (newByteLength !== data.byteLength) {
+      var oldDataView = dataView;
+      data = new ArrayBuffer(newByteLength);
+      dataView = new DataView(data);
+      var uint32count = (offset + 3) >> 2;
+      for (var i = 0; i < uint32count; ++i)
+        dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));
+    }
+
+    lastLength = length;
+    return dataView;
+  }
+  function commitWrite() {
+    offset += lastLength;
+  }
+  function writeFloat64(value) {
+    commitWrite(prepareWrite(8).setFloat64(offset, value));
+  }
+  function writeUint8(value) {
+    commitWrite(prepareWrite(1).setUint8(offset, value));
+  }
+  function writeUint8Array(value) {
+    var dataView = prepareWrite(value.length);
+    for (var i = 0; i < value.length; ++i)
+      dataView.setUint8(offset + i, value[i]);
+    commitWrite();
+  }
+  function writeUint16(value) {
+    commitWrite(prepareWrite(2).setUint16(offset, value));
+  }
+  function writeUint32(value) {
+    commitWrite(prepareWrite(4).setUint32(offset, value));
+  }
+  function writeUint64(value) {
+    var low = value % POW_2_32;
+    var high = (value - low) / POW_2_32;
+    var dataView = prepareWrite(8);
+    dataView.setUint32(offset, high);
+    dataView.setUint32(offset + 4, low);
+    commitWrite();
+  }
+  function writeTypeAndLength(type, length) {
+    if (length < 24) {
+      writeUint8(type << 5 | length);
+    } else if (length < 0x100) {
+      writeUint8(type << 5 | 24);
+      writeUint8(length);
+    } else if (length < 0x10000) {
+      writeUint8(type << 5 | 25);
+      writeUint16(length);
+    } else if (length < 0x100000000) {
+      writeUint8(type << 5 | 26);
+      writeUint32(length);
+    } else {
+      writeUint8(type << 5 | 27);
+      writeUint64(length);
+    }
+  }
+
+  function encodeItem(value) {
+    var i;
+
+    if (value === false)
+      return writeUint8(0xf4);
+    if (value === true)
+      return writeUint8(0xf5);
+    if (value === null)
+      return writeUint8(0xf6);
+    if (value === undefined)
+      return writeUint8(0xf7);
+
+    switch (typeof value) {
+      case "number":
+        if (Math.floor(value) === value) {
+          if (0 <= value && value <= POW_2_53)
+            return writeTypeAndLength(0, value);
+          if (-POW_2_53 <= value && value < 0)
+            return writeTypeAndLength(1, -(value + 1));
+        }
+        writeUint8(0xfb);
+        return writeFloat64(value);
+
+      case "string":
+        var utf8data = [];
+        for (i = 0; i < value.length; ++i) {
+          var charCode = value.charCodeAt(i);
+          if (charCode < 0x80) {
+            utf8data.push(charCode);
+          } else if (charCode < 0x800) {
+            utf8data.push(0xc0 | charCode >> 6);
+            utf8data.push(0x80 | charCode & 0x3f);
+          } else if (charCode < 0xd800) {
+            utf8data.push(0xe0 | charCode >> 12);
+            utf8data.push(0x80 | (charCode >> 6)  & 0x3f);
+            utf8data.push(0x80 | charCode & 0x3f);
+          } else {
+            charCode = (charCode & 0x3ff) << 10;
+            charCode |= value.charCodeAt(++i) & 0x3ff;
+            charCode += 0x10000;
+
+            utf8data.push(0xf0 | charCode >> 18);
+            utf8data.push(0x80 | (charCode >> 12)  & 0x3f);
+            utf8data.push(0x80 | (charCode >> 6)  & 0x3f);
+            utf8data.push(0x80 | charCode & 0x3f);
+          }
+        }
+
+        writeTypeAndLength(3, utf8data.length);
+        return writeUint8Array(utf8data);
+
+      default:
+        var length;
+        if (Array.isArray(value)) {
+          length = value.length;
+          writeTypeAndLength(4, length);
+          for (i = 0; i < length; ++i)
+            encodeItem(value[i]);
+        } else if (value instanceof Uint8Array) {
+          writeTypeAndLength(2, value.length);
+          writeUint8Array(value);
+        } else {
+          var keys = Object.keys(value);
+          length = keys.length;
+          writeTypeAndLength(5, length);
+          for (i = 0; i < length; ++i) {
+            var key = keys[i];
+            encodeItem(key);
+            encodeItem(value[key]);
+          }
+        }
+    }
+  }
+
+  encodeItem(value);
+
+  if ("slice" in data)
+    return data.slice(0, offset);
+
+  var ret = new ArrayBuffer(offset);
+  var retView = new DataView(ret);
+  for (var i = 0; i < offset; ++i)
+    retView.setUint8(i, dataView.getUint8(i));
+  return ret;
+}
+
+function decode(data, tagger, simpleValue) {
+  var dataView = new DataView(data);
+  var offset = 0;
+
+  if (typeof tagger !== "function")
+    tagger = function(value) { return value; };
+  if (typeof simpleValue !== "function")
+    simpleValue = function() { return undefined; };
+
+  function commitRead(length, value) {
+    offset += length;
+    return value;
+  }
+  function readArrayBuffer(length) {
+    return commitRead(length, new Uint8Array(data, offset, length));
+  }
+  function readFloat16() {
+    var tempArrayBuffer = new ArrayBuffer(4);
+    var tempDataView = new DataView(tempArrayBuffer);
+    var value = readUint16();
+
+    var sign = value & 0x8000;
+    var exponent = value & 0x7c00;
+    var fraction = value & 0x03ff;
+
+    if (exponent === 0x7c00)
+      exponent = 0xff << 10;
+    else if (exponent !== 0)
+      exponent += (127 - 15) << 10;
+    else if (fraction !== 0)
+      return (sign ? -1 : 1) * fraction * POW_2_24;
+
+    tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
+    return tempDataView.getFloat32(0);
+  }
+  function readFloat32() {
+    return commitRead(4, dataView.getFloat32(offset));
+  }
+  function readFloat64() {
+    return commitRead(8, dataView.getFloat64(offset));
+  }
+  function readUint8() {
+    return commitRead(1, dataView.getUint8(offset));
+  }
+  function readUint16() {
+    return commitRead(2, dataView.getUint16(offset));
+  }
+  function readUint32() {
+    return commitRead(4, dataView.getUint32(offset));
+  }
+  function readUint64() {
+    return readUint32() * POW_2_32 + readUint32();
+  }
+  function readBreak() {
+    if (dataView.getUint8(offset) !== 0xff)
+      return false;
+    offset += 1;
+    return true;
+  }
+  function readLength(additionalInformation) {
+    if (additionalInformation < 24)
+      return additionalInformation;
+    if (additionalInformation === 24)
+      return readUint8();
+    if (additionalInformation === 25)
+      return readUint16();
+    if (additionalInformation === 26)
+      return readUint32();
+    if (additionalInformation === 27)
+      return readUint64();
+    if (additionalInformation === 31)
+      return -1;
+    throw "Invalid length encoding";
+  }
+  function readIndefiniteStringLength(majorType) {
+    var initialByte = readUint8();
+    if (initialByte === 0xff)
+      return -1;
+    var length = readLength(initialByte & 0x1f);
+    if (length < 0 || (initialByte >> 5) !== majorType)
+      throw "Invalid indefinite length element";
+    return length;
+  }
+
+  function appendUtf16Data(utf16data, length) {
+    for (var i = 0; i < length; ++i) {
+      var value = readUint8();
+      if (value & 0x80) {
+        if (value < 0xe0) {
+          value = (value & 0x1f) <<  6
+                | (readUint8() & 0x3f);
+          length -= 1;
+        } else if (value < 0xf0) {
+          value = (value & 0x0f) << 12
+                | (readUint8() & 0x3f) << 6
+                | (readUint8() & 0x3f);
+          length -= 2;
+        } else {
+          value = (value & 0x0f) << 18
+                | (readUint8() & 0x3f) << 12
+                | (readUint8() & 0x3f) << 6
+                | (readUint8() & 0x3f);
+          length -= 3;
+        }
+      }
+
+      if (value < 0x10000) {
+        utf16data.push(value);
+      } else {
+        value -= 0x10000;
+        utf16data.push(0xd800 | (value >> 10));
+        utf16data.push(0xdc00 | (value & 0x3ff));
+      }
+    }
+  }
+
+  function decodeItem() {
+    var initialByte = readUint8();
+    var majorType = initialByte >> 5;
+    var additionalInformation = initialByte & 0x1f;
+    var i;
+    var length;
+
+    if (majorType === 7) {
+      switch (additionalInformation) {
+        case 25:
+          return readFloat16();
+        case 26:
+          return readFloat32();
+        case 27:
+          return readFloat64();
+      }
+    }
+
+    length = readLength(additionalInformation);
+    if (length < 0 && (majorType < 2 || 6 < majorType))
+      throw "Invalid length";
+
+    switch (majorType) {
+      case 0:
+        return length;
+      case 1:
+        return -1 - length;
+      case 2:
+        if (length < 0) {
+          var elements = [];
+          var fullArrayLength = 0;
+          while ((length = readIndefiniteStringLength(majorType)) >= 0) {
+            fullArrayLength += length;
+            elements.push(readArrayBuffer(length));
+          }
+          var fullArray = new Uint8Array(fullArrayLength);
+          var fullArrayOffset = 0;
+          for (i = 0; i < elements.length; ++i) {
+            fullArray.set(elements[i], fullArrayOffset);
+            fullArrayOffset += elements[i].length;
+          }
+          return fullArray;
+        }
+        return readArrayBuffer(length);
+      case 3:
+        var utf16data = [];
+        if (length < 0) {
+          while ((length = readIndefiniteStringLength(majorType)) >= 0)
+            appendUtf16Data(utf16data, length);
+        } else
+          appendUtf16Data(utf16data, length);
+        return String.fromCharCode.apply(null, utf16data);
+      case 4:
+        var retArray;
+        if (length < 0) {
+          retArray = [];
+          while (!readBreak())
+            retArray.push(decodeItem());
+        } else {
+          retArray = new Array(length);
+          for (i = 0; i < length; ++i)
+            retArray[i] = decodeItem();
+        }
+        return retArray;
+      case 5:
+        var retObject = {};
+        for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
+          var key = decodeItem();
+          retObject[key] = decodeItem();
+        }
+        return retObject;
+      case 6:
+        return tagger(decodeItem(), length);
+      case 7:
+        switch (length) {
+          case 20:
+            return false;
+          case 21:
+            return true;
+          case 22:
+            return null;
+          case 23:
+            return undefined;
+          default:
+            return simpleValue(length);
+        }
+    }
+  }
+
+  var ret = decodeItem();
+  if (offset !== data.byteLength)
+    throw "Remaining bytes";
+  return ret;
+}
+
+var obj = { encode: encode, decode: decode };
+
+if (typeof define === "function" && define.amd)
+  define("cbor/cbor", obj);
+else if (typeof module !== "undefined" && module.exports)
+  module.exports = obj;
+else if (!global.CBOR)
+  global.CBOR = obj;
+
+})(this);
index 1e0649f..531f1ad 100644 (file)
@@ -1,13 +1,38 @@
-const testCredentialIdBase64url = "SMSXHngF7hEOsElA73C3RY-8bR4";
+const testCredentialIdBase64 = "SMSXHngF7hEOsElA73C3RY+8bR4=";
 const testES256PrivateKeyBase64 =
     "BDj/zxSkzKgaBuS3cdWDF558of8AaIpgFpsjF/Qm1749VBJPgqUIwfhWHJ91nb7U" +
     "PH76c0+WFOzZKslPyyFse4goGIW2R7k9VHLPEZl5nfnBgEVFh5zev+/xpHQIvuq6" +
     "RQ==";
-const testES256PublicKeyBase64url =
-    "BDj_zxSkzKgaBuS3cdWDF558of8AaIpgFpsjF_Qm1749VBJPgqUIwfhWHJ91nb7U" +
-    "PH76c0-WFOzZKslPyyFse4g";
+const testES256PublicKeyBase64 =
+    "BDj/zxSkzKgaBuS3cdWDF558of8AaIpgFpsjF/Qm1749VBJPgqUIwfhWHJ91nb7U" +
+    "PH76c0+WFOzZKslPyyFse4g=";
 const testRpId = "localhost";
 const testUserhandleBase64 = "AAECAwQFBgcICQ==";
+const testAttestationCertificateBase64 =
+    "MIIB6jCCAZCgAwIBAgIGAWHAxcjvMAoGCCqGSM49BAMCMFMxJzAlBgNVBAMMHkJh" +
+    "c2ljIEF0dGVzdGF0aW9uIFVzZXIgU3ViIENBMTETMBEGA1UECgwKQXBwbGUgSW5j" +
+    "LjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0xODAyMjMwMzM3MjJaFw0xODAyMjQw" +
+    "MzQ3MjJaMGoxIjAgBgNVBAMMGTAwMDA4MDEwLTAwMEE0OUEyMzBBMDIxM0ExGjAY" +
+    "BgNVBAsMEUJBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMw" +
+    "EQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvCje" +
+    "Pzr6Sg76XMoHuGabPaG6zjpLFL8Zd8/74Hh5PcL2Zq+o+f7ENXX+7nEXXYt0S8Ux" +
+    "5TIRw4hgbfxXQbWLEqM5MDcwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAw" +
+    "FwYJKoZIhvdjZAgCBAowCKEGBAR0ZXN0MAoGCCqGSM49BAMCA0gAMEUCIAlK8A8I" +
+    "k43TbvKuYGHZs1DTgpTwmKTBvIUw5bwgZuYnAiEAtuJjDLKbGNJAJFMi5deEBqno" +
+    "pBTCqbfbDJccfyQpjnY=";
+const testAttestationIssuingCACertificateBase64 =
+    "MIICIzCCAaigAwIBAgIIeNjhG9tnDGgwCgYIKoZIzj0EAwIwUzEnMCUGA1UEAwwe" +
+    "QmFzaWMgQXR0ZXN0YXRpb24gVXNlciBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ" +
+    "bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTE3MDQyMDAwNDIwMFoXDTMyMDMy" +
+    "MjAwMDAwMFowUzEnMCUGA1UEAwweQmFzaWMgQXR0ZXN0YXRpb24gVXNlciBTdWIg" +
+    "Q0ExMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkw" +
+    "EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoSZ/1t9eBAEVp5a8PrXacmbGb8zNC1X3" +
+    "StLI9YO6Y0CL7blHmSGmjGWTwD4Q+i0J2BY3+bPHTGRyA9jGB3MSbaNmMGQwEgYD" +
+    "VR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBSD5aMhnrB0w/lhkP2XTiMQdqSj" +
+    "8jAdBgNVHQ4EFgQU5mWf1DYLTXUdQ9xmOH/uqeNSD80wDgYDVR0PAQH/BAQDAgEG" +
+    "MAoGCCqGSM49BAMCA2kAMGYCMQC3M360LLtJS60Z9q3vVjJxMgMcFQ1roGTUcKqv" +
+    "W+4hJ4CeJjySXTgq6IEHn/yWab4CMQCm5NnK6SOSK+AqWum9lL87W3E6AA1f2TvJ" +
+    "/hgok/34jr93nhS87tOQNdxDS8zyiqw=";
 
 const RESOURCES_DIR = "/WebKit/webauthn/resources/";
 
@@ -125,7 +150,10 @@ function decodeAuthData(authDataUint8Array)
     authDataObject.credentialID = authDataUint8Array.slice(pos, pos + size);
     pos = pos + size;
 
-    // FIXME(): Add CBOR decoder to parse the public key.
+    // Public Key
+    authDataObject.publicKey = CBOR.decode(authDataUint8Array.slice(pos).buffer);
+    if (!authDataObject.publicKey)
+        return { };
 
     // Assume no extensions.
     return authDataObject;
@@ -178,4 +206,20 @@ function promiseRejects(test, expected, promise, description)
         assert_throws(expected, function() { throw e }, description);
         assert_equals(e.message, description);
     });
-}
\ No newline at end of file
+}
+
+// COSE Key Format: https://www.w3.org/TR/webauthn/#sctn-encoded-credPubKey-examples.
+function checkPublicKey(publicKey)
+{
+    if (publicKey['1'] != 2)
+        return false;
+    if (publicKey['3'] != -7)
+        return false;
+    if (publicKey['-1'] != 1)
+        return false;
+    if (publicKey['-2'].byteLength != 32)
+        return false;
+    if (publicKey['-3'].byteLength != 32)
+        return false;
+    return true;
+}
index df01544..5568755 100644 (file)
@@ -1,3 +1,21 @@
+2018-10-01  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import a JS CBOR coder
+        https://bugs.webkit.org/show_bug.cgi?id=189877
+        <rdar://problem/44701124>
+
+        Reviewed by Chris Dumez.
+
+        Update MockWebAuthenticationConfiguration to have userCertificateBase64 and intermediateCACertificateBase64
+        as Local's memeber such that tests can pass those certificates to MockLocalConnection instead of letting it
+        holds some static ones.
+
+        * UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
+        (WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):
+        * UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
+        (WebKit::MockLocalConnection::getAttestation const):
+        * UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:
+
 2018-10-01  Chris Dumez  <cdumez@apple.com>
 
         Regression(r236512): http/tests/navigation/keyboard-events-during-provisional-navigation.html is flaky
index 2ee53d4..67a5423 100644 (file)
@@ -582,7 +582,11 @@ void WKWebsiteDataStoreSetWebAuthenticationMockConfiguration(WKWebsiteDataStoreR
         MockWebAuthenticationConfiguration::Local local;
         local.acceptAuthentication = WKBooleanGetValue(static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("AcceptAuthentication")).get())));
         local.acceptAttestation = WKBooleanGetValue(static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("AcceptAttestation")).get())));
-        local.privateKeyBase64 = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("PrivateKeyBase64")).get())))->string();
+        if (local.acceptAttestation) {
+            local.privateKeyBase64 = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("PrivateKeyBase64")).get())))->string();
+            local.userCertificateBase64 = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("UserCertificateBase64")).get())))->string();
+            local.intermediateCACertificateBase64 = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("IntermediateCACertificateBase64")).get())))->string();
+        }
         configuration.local = WTFMove(local);
     }
 
index 091d44d..ca765e5 100644 (file)
 
 namespace WebKit {
 
-namespace MockLocalConnectionInternal {
-const char* const testAttestationCertificateBase64 =
-    "MIIB6jCCAZCgAwIBAgIGAWHAxcjvMAoGCCqGSM49BAMCMFMxJzAlBgNVBAMMHkJh"
-    "c2ljIEF0dGVzdGF0aW9uIFVzZXIgU3ViIENBMTETMBEGA1UECgwKQXBwbGUgSW5j"
-    "LjETMBEGA1UECAwKQ2FsaWZvcm5pYTAeFw0xODAyMjMwMzM3MjJaFw0xODAyMjQw"
-    "MzQ3MjJaMGoxIjAgBgNVBAMMGTAwMDA4MDEwLTAwMEE0OUEyMzBBMDIxM0ExGjAY"
-    "BgNVBAsMEUJBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMw"
-    "EQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvCje"
-    "Pzr6Sg76XMoHuGabPaG6zjpLFL8Zd8/74Hh5PcL2Zq+o+f7ENXX+7nEXXYt0S8Ux"
-    "5TIRw4hgbfxXQbWLEqM5MDcwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAw"
-    "FwYJKoZIhvdjZAgCBAowCKEGBAR0ZXN0MAoGCCqGSM49BAMCA0gAMEUCIAlK8A8I"
-    "k43TbvKuYGHZs1DTgpTwmKTBvIUw5bwgZuYnAiEAtuJjDLKbGNJAJFMi5deEBqno"
-    "pBTCqbfbDJccfyQpjnY=";
-const char* const testAttestationIssuingCACertificateBase64 =
-    "MIICIzCCAaigAwIBAgIIeNjhG9tnDGgwCgYIKoZIzj0EAwIwUzEnMCUGA1UEAwwe"
-    "QmFzaWMgQXR0ZXN0YXRpb24gVXNlciBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ"
-    "bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTE3MDQyMDAwNDIwMFoXDTMyMDMy"
-    "MjAwMDAwMFowUzEnMCUGA1UEAwweQmFzaWMgQXR0ZXN0YXRpb24gVXNlciBTdWIg"
-    "Q0ExMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkw"
-    "EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoSZ/1t9eBAEVp5a8PrXacmbGb8zNC1X3"
-    "StLI9YO6Y0CL7blHmSGmjGWTwD4Q+i0J2BY3+bPHTGRyA9jGB3MSbaNmMGQwEgYD"
-    "VR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBSD5aMhnrB0w/lhkP2XTiMQdqSj"
-    "8jAdBgNVHQ4EFgQU5mWf1DYLTXUdQ9xmOH/uqeNSD80wDgYDVR0PAQH/BAQDAgEG"
-    "MAoGCCqGSM49BAMCA2kAMGYCMQC3M360LLtJS60Z9q3vVjJxMgMcFQ1roGTUcKqv"
-    "W+4hJ4CeJjySXTgq6IEHn/yWab4CMQCm5NnK6SOSK+AqWum9lL87W3E6AA1f2TvJ"
-    "/hgok/34jr93nhS87tOQNdxDS8zyiqw=";
-}
-
 MockLocalConnection::MockLocalConnection(const MockWebAuthenticationConfiguration& configuration)
     : m_configuration(configuration)
 {
@@ -97,8 +69,6 @@ void MockLocalConnection::getUserConsent(const String&, SecAccessControlRef, Use
 
 void MockLocalConnection::getAttestation(const String& rpId, const String& username, const Vector<uint8_t>& hash, AttestationCallback&& callback) const
 {
-    using namespace MockLocalConnectionInternal;
-
     // Mock async operations.
     RunLoop::main().dispatch([configuration = m_configuration, rpId, username, hash, callback = WTFMove(callback)]() mutable {
         ASSERT(configuration.local);
@@ -131,10 +101,8 @@ void MockLocalConnection::getAttestation(const String& rpId, const String& usern
         OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
         ASSERT_UNUSED(status, !status);
 
-        // Construct dummy certificates. The content of certificates is not important. We need them to present to
-        // check the CBOR encoding procedure.
-        auto attestationCertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:[NSString stringWithCString:testAttestationCertificateBase64 encoding:NSASCIIStringEncoding] options:NSDataBase64DecodingIgnoreUnknownCharacters]).get()));
-        auto attestationIssuingCACertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:[NSString stringWithCString:testAttestationIssuingCACertificateBase64 encoding:NSASCIIStringEncoding] options:NSDataBase64DecodingIgnoreUnknownCharacters]).get()));
+        auto attestationCertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:configuration.local->userCertificateBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get()));
+        auto attestationIssuingCACertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:configuration.local->intermediateCACertificateBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get()));
 
         callback(key.get(), [NSArray arrayWithObjects: (__bridge id)attestationCertificate.get(), (__bridge id)attestationIssuingCACertificate.get(), nil], NULL);
     });
index fdcff37..8afd8fe 100644 (file)
@@ -34,6 +34,8 @@ struct MockWebAuthenticationConfiguration {
         bool acceptAuthentication { false };
         bool acceptAttestation { false };
         String privateKeyBase64;
+        String userCertificateBase64;
+        String intermediateCACertificateBase64;
     };
 
     std::optional<Local> local;
index e6cf23e..b909e85 100644 (file)
@@ -1,3 +1,17 @@
+2018-10-01  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Import a JS CBOR coder
+        https://bugs.webkit.org/show_bug.cgi?id=189877
+        <rdar://problem/44701124>
+
+        Reviewed by Chris Dumez.
+
+        Add logic to only process privateKeyBase64, userCertificateBase64 and intermediateCACertificateBase64
+        only if acceptAttestation is true.
+
+        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+        (WTR::TestRunner::setWebAuthenticationMockConfiguration):
+
 2018-10-01  Chris Dumez  <cdumez@apple.com>
 
         Regression(r236512): http/tests/navigation/keyboard-events-during-provisional-navigation.html is flaky
index dd4e396..c18dfc6 100644 (file)
@@ -2380,19 +2380,36 @@ void TestRunner::setWebAuthenticationMockConfiguration(JSValueRef configurationV
             return;
         bool acceptAttestation = JSValueToBoolean(context, acceptAttestationValue);
 
-        JSRetainPtr<JSStringRef> privateKeyBase64PropertyName(Adopt, JSStringCreateWithUTF8CString("privateKeyBase64"));
-        JSValueRef privateKeyBase64Value = JSObjectGetProperty(context, local, privateKeyBase64PropertyName.get(), 0);
-        if (!JSValueIsString(context, privateKeyBase64Value))
-            return;
-
         Vector<WKRetainPtr<WKStringRef>> localKeys;
         Vector<WKRetainPtr<WKTypeRef>> localValues;
         localKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("AcceptAuthentication") });
         localValues.append(adoptWK(WKBooleanCreate(acceptAuthentication)).get());
         localKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("AcceptAttestation") });
         localValues.append(adoptWK(WKBooleanCreate(acceptAttestation)).get());
-        localKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("PrivateKeyBase64") });
-        localValues.append(toWK(adopt(JSValueToStringCopy(context, privateKeyBase64Value, 0)).get()));
+
+        if (acceptAttestation) {
+            JSRetainPtr<JSStringRef> privateKeyBase64PropertyName(Adopt, JSStringCreateWithUTF8CString("privateKeyBase64"));
+            JSValueRef privateKeyBase64Value = JSObjectGetProperty(context, local, privateKeyBase64PropertyName.get(), 0);
+            if (!JSValueIsString(context, privateKeyBase64Value))
+                return;
+
+            JSRetainPtr<JSStringRef> userCertificateBase64PropertyName(Adopt, JSStringCreateWithUTF8CString("userCertificateBase64"));
+            JSValueRef userCertificateBase64Value = JSObjectGetProperty(context, local, userCertificateBase64PropertyName.get(), 0);
+            if (!JSValueIsString(context, userCertificateBase64Value))
+                return;
+
+            JSRetainPtr<JSStringRef> intermediateCACertificateBase64PropertyName(Adopt, JSStringCreateWithUTF8CString("intermediateCACertificateBase64"));
+            JSValueRef intermediateCACertificateBase64Value = JSObjectGetProperty(context, local, intermediateCACertificateBase64PropertyName.get(), 0);
+            if (!JSValueIsString(context, intermediateCACertificateBase64Value))
+            return;
+
+            localKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("PrivateKeyBase64") });
+            localValues.append(toWK(adopt(JSValueToStringCopy(context, privateKeyBase64Value, 0)).get()));
+            localKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("UserCertificateBase64") });
+            localValues.append(toWK(adopt(JSValueToStringCopy(context, userCertificateBase64Value, 0)).get()));
+            localKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("IntermediateCACertificateBase64") });
+            localValues.append(toWK(adopt(JSValueToStringCopy(context, intermediateCACertificateBase64Value, 0)).get()));
+        }
 
         Vector<WKStringRef> rawLocalKeys;
         Vector<WKTypeRef> rawLocalValues;