[WebAuthN] InvalidStateError should be reported to sites
https://bugs.webkit.org/show_bug.cgi?id=193269
<rdar://problem/48298264>

Reviewed by Brent Fulgham.

Source/WebKit:

This patch implements step 20 about InvalidStateError of the spec:
https://www.w3.org/TR/webauthn/#createCredential.

* UIProcess/WebAuthentication/AuthenticatorManager.cpp:
(WebKit::AuthenticatorManager::respondReceived):
* UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp:
(WebKit::CtapHidAuthenticator::continueMakeCredentialAfterResponseReceived const):

LayoutTests:

* http/wpt/webauthn/ctap-hid-failure.https.html:
* http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt:
* http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html:
* http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt:
* http/wpt/webauthn/public-key-credential-create-failure-hid.https.html:
* http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html:
* http/wpt/webauthn/resources/util.js:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@245262 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index e3bc648..b10fe02 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,19 @@
+2019-05-13  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] InvalidStateError should be reported to sites
+        https://bugs.webkit.org/show_bug.cgi?id=193269
+        <rdar://problem/48298264>
+
+        Reviewed by Brent Fulgham.
+
+        * http/wpt/webauthn/ctap-hid-failure.https.html:
+        * http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt:
+        * http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html:
+        * http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt:
+        * http/wpt/webauthn/public-key-credential-create-failure-hid.https.html:
+        * http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html:
+        * http/wpt/webauthn/resources/util.js:
+
 2019-05-13  Devin Rousso  <drousso@apple.com>
 
         [ Mac Debug ] Layout Test inspector/audit/basic.html is a flaky timeout on bots
diff --git a/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html b/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html
index c46ee39..4f0fd6a 100644
--- a/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html
+++ b/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html
@@ -65,6 +65,6 @@
     promise_test(function(t) {
         if (window.testRunner)
             testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "wrong-channel-id", payloadBase64:[testDummyMessagePayloadBase64] } });
-        return promiseRejects(t, "UnknownError", navigator.credentials.create(defaultOptions), "Unknown internal error. Error code: -1");
+        return promiseRejects(t, "UnknownError", navigator.credentials.create(defaultOptions), "Unknown internal error. Error code: 18");
     }, "CTAP HID with request::msg stage wrong channel id error in a mock hid authenticator.");
 </script>
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt
index c708757..f32671e 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt
@@ -3,4 +3,5 @@
 PASS PublicKeyCredential's [[create]] with unsupported options in a mock hid authenticator. 
 PASS PublicKeyCredential's [[create]] with unsupported options in a mock hid authenticator. 2 
 PASS PublicKeyCredential's [[create]] with mixed options in a mock hid authenticator. 
+PASS PublicKeyCredential's [[create]] with InvalidStateError in a mock hid authenticator. 
 
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html
index 66004a3..66b5da4 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html
@@ -94,4 +94,25 @@
             testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "unsupported-options" } });
         return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
     }, "PublicKeyCredential's [[create]] with mixed options in a mock hid authenticator.");
+
+   promise_test(function(t) {
+       const options = {
+           publicKey: {
+               rp: {
+                   name: "example.com"
+               },
+               user: {
+                   name: "John Appleseed",
+                   id: asciiToUint8Array("123456"),
+                   displayName: "John",
+               },
+               challenge: asciiToUint8Array("123456"),
+               pubKeyCredParams: [{ type: "public-key", alg: -7 }]
+           }
+       };
+
+       if (window.testRunner)
+           testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testCtapErrCredentialExcludedOnlyResponseBase64] } });
+       return promiseRejects(t, "InvalidStateError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the authenticator.");
+   }, "PublicKeyCredential's [[create]] with InvalidStateError in a mock hid authenticator.");
 </script>
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt
index e5fc45c..721db3b 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt
@@ -5,4 +5,5 @@
 PASS PublicKeyCredential's [[create]] with unsupported options in a mock hid authenticator. 2 
 PASS PublicKeyCredential's [[create]] with mixed options in a mock hid authenticator. 
 PASS PublicKeyCredential's [[create]] with mixed options in a mock hid authenticator. 2 
+PASS PublicKeyCredential's [[create]] with InvalidStateError in a mock hid authenticator. 
 
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https.html
index f163a39..2f837c6 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https.html
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https.html
@@ -47,7 +47,7 @@
 
         if (window.testRunner)
             testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testDummyMessagePayloadBase64] } });
-        return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Unknown internal error. Error code: -1");
+        return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Unknown internal error. Error code: 255");
     }, "PublicKeyCredential's [[create]] with malicious payload in a mock hid authenticator.");
 
     promise_test(function(t) {
@@ -138,4 +138,25 @@
             testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "unsupported-options" } });
         return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Unknown internal error. Error code: 43");
     }, "PublicKeyCredential's [[create]] with mixed options in a mock hid authenticator. 2");
+
+    promise_test(function(t) {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "example.com"
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: asciiToUint8Array("123456"),
+                    displayName: "John",
+                },
+                challenge: asciiToUint8Array("123456"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }]
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testCtapErrCredentialExcludedOnlyResponseBase64] } });
+        return promiseRejects(t, "InvalidStateError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the authenticator.");
+    }, "PublicKeyCredential's [[create]] with InvalidStateError in a mock hid authenticator.");
 </script>
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html
index 5417998..641c0ef 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html
@@ -113,7 +113,7 @@
 
         if (window.testRunner)
             testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduNoErrorOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
-        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
+        return promiseRejects(t, "InvalidStateError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the authenticator.");
     }, "PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator.");
 
     // Match the second exclude credential.
@@ -137,7 +137,7 @@
 
         if (window.testRunner)
             testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
-        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
+        return promiseRejects(t, "InvalidStateError", navigator.credentials.create(options), "At least one credential matches an entry of the excludeCredentials list in the authenticator.");
     }, "PublicKeyCredential's [[create]] with second exclude credential matched in a mock hid authenticator.");
 
     promise_test(function(t) {
diff --git a/LayoutTests/http/wpt/webauthn/resources/util.js b/LayoutTests/http/wpt/webauthn/resources/util.js
index f60986b..f9a3204 100644
--- a/LayoutTests/http/wpt/webauthn/resources/util.js
+++ b/LayoutTests/http/wpt/webauthn/resources/util.js
@@ -97,6 +97,7 @@
 const testU2fSignResponse =
     "AQAAADswRAIge94KUqwfTIsn4AOjcM1mpMcRjdItVEeDX0W5nGhCP/cCIDxRe0eH" +
     "f4V4LeEAhqeD0effTjY553H19q+jWq1Tc4WOkAA=";
+const testCtapErrCredentialExcludedOnlyResponseBase64 = "GQ==";
 
 const RESOURCES_DIR = "/WebKit/webauthn/resources/";
 
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 94b0840..1e12379 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,19 @@
+2019-05-13  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] InvalidStateError should be reported to sites
+        https://bugs.webkit.org/show_bug.cgi?id=193269
+        <rdar://problem/48298264>
+
+        Reviewed by Brent Fulgham.
+
+        This patch implements step 20 about InvalidStateError of the spec:
+        https://www.w3.org/TR/webauthn/#createCredential.
+
+        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
+        (WebKit::AuthenticatorManager::respondReceived):
+        * UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp:
+        (WebKit::CtapHidAuthenticator::continueMakeCredentialAfterResponseReceived const):
+
 2019-05-13  Jer Noble  <jer.noble@apple.com>
 
         Take out MediaPlayback UI assertion when any WebProcess is playing audible media
diff --git a/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp b/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
index ca7ae5e..0c5d25c 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
+++ b/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
@@ -194,9 +194,12 @@
     ASSERT(RunLoop::isMain());
     if (!m_requestTimeOutTimer.isActive())
         return;
-
     ASSERT(m_pendingCompletionHandler);
-    if (WTF::holds_alternative<PublicKeyCredentialData>(respond)) {
+
+    auto shouldComplete = WTF::holds_alternative<PublicKeyCredentialData>(respond);
+    if (!shouldComplete)
+        shouldComplete = WTF::get<ExceptionData>(respond).code == InvalidStateError;
+    if (shouldComplete) {
         m_pendingCompletionHandler(WTFMove(respond));
         clearStateAsync();
         m_requestTimeOutTimer.stop();
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp
index 2a4bd19..08cd5a3 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp
+++ b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp
@@ -62,7 +62,11 @@
 {
     auto response = readCTAPMakeCredentialResponse(data);
     if (!response) {
-        receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", data.size() == 1 ? data[0] : -1) });
+        auto error = getResponseCode(data);
+        if (error == CtapDeviceResponseCode::kCtap2ErrCredentialExcluded)
+            receiveRespond(ExceptionData { InvalidStateError, "At least one credential matches an entry of the excludeCredentials list in the authenticator."_s });
+        else
+            receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
         return;
     }
     receiveRespond(WTFMove(*response));