[WebAuthn] Warn users when no credentials are found
https://bugs.webkit.org/show_bug.cgi?id=203147
<rdar://problem/55931123>

Reviewed by Brent Fulgham.

Source/WebKit:

This patch returns _WKWebAuthenticationPanelUpdateNoCredentialsFound to client via
-[_WKWebAuthenticationPanelDelegate panel:updateWebAuthenticationPanel:] when either
CtapAuthenticator receives kCtap2ErrNoCredentials or U2fAuthenticator exhausts the
allow list.

This patch also enhances CtapAuthenticator::tryDowngrade to check if the CTAP command
can be converted to U2F commands to ensure kCtap2ErrNoCredentials is returned if it
is the case. Otherwise, after downgrading, U2fAuthenticator will return NotSupportedError
given it can't convert the commands.

* UIProcess/API/APIUIClient.h:
* UIProcess/API/APIWebAuthenticationPanelClient.h:
(API::WebAuthenticationPanelClient::updatePanel const):
* UIProcess/WebAuthentication/Authenticator.h:
* UIProcess/WebAuthentication/AuthenticatorManager.cpp:
(WebKit::AuthenticatorManager::authenticatorStatusUpdated):
* UIProcess/WebAuthentication/AuthenticatorManager.h:
* UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h:
* UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm:
(WebKit::WebAuthenticationPanelClient::WebAuthenticationPanelClient):
(WebKit::wkWebAuthenticationPanelUpdate):
(WebKit::WebAuthenticationPanelClient::updatePanel const):
* UIProcess/WebAuthentication/WebAuthenticationFlags.h: Renamed from Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationPanelFlags.h.
* UIProcess/WebAuthentication/WebAuthenticationRequestData.h:
* UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp:
* UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp:
(WebKit::CtapAuthenticator::continueGetAssertionAfterResponseReceived):
(WebKit::CtapAuthenticator::tryDowngrade):
* UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp:
(WebKit::U2fAuthenticator::issueSignCommand):
* WebKit.xcodeproj/project.pbxproj:

Tools:

Adds new tests for _WKWebAuthenticationPanelUpdateNoCredentialsFound.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
(-[TestWebAuthenticationPanelDelegate panel:updateWebAuthenticationPanel:]):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-cancel.html:
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-no-credentials.html: Added.
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html:
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html:
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-u2f-no-credentials.html: Added.

LayoutTests:

Adds new tests for CtapAuthenticator::tryDowngrade enhancement.

* http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt:
* http/wpt/webauthn/public-key-credential-get-failure-hid.https.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251317 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 221d40d..fa7f7f7 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,16 @@
+2019-10-18  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Warn users when no credentials are found
+        https://bugs.webkit.org/show_bug.cgi?id=203147
+        <rdar://problem/55931123>
+
+        Reviewed by Brent Fulgham.
+
+        Adds new tests for CtapAuthenticator::tryDowngrade enhancement.
+
+        * http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt:
+        * http/wpt/webauthn/public-key-credential-get-failure-hid.https.html:
+
 2019-10-18  Russell Epstein  <repstein@apple.com>
 
         [ Mac Debug WK1 ] REGRESSION (r251262?): fast/scrolling/latching/scroll-select-bottom-test.html is a Flaky Failure
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt b/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt
index 61b3afa..54bb508 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt
@@ -3,5 +3,6 @@
 PASS PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator. 
 PASS PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator. 
 PASS PublicKeyCredential's [[get]] with authenticator downgrade failed in a mock hid authenticator. 
-PASS PublicKeyCredential's [[get]] with authenticator downgrade succeeded and then U2F failed in a mock hid authenticator. 2 
+PASS PublicKeyCredential's [[get]] with authenticator downgrade failed in a mock hid authenticator. 2 
+PASS PublicKeyCredential's [[get]] with authenticator downgrade succeeded and then U2F failed in a mock hid authenticator. 
 
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https.html
index 0022ea9..68e3ce2 100644
--- a/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https.html
+++ b/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https.html
@@ -64,13 +64,24 @@
     promise_test(function(t) {
         const options = {
             publicKey: {
-                challenge: asciiToUint8Array("123456"),
-                extensions: { appid: "" }
+                challenge: asciiToUint8Array("123456")
             }
         };
 
         if (window.internals)
             internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64] } });
-        return promiseRejects(t, "NotSupportedError", navigator.credentials.get(options), "Cannot convert the request to U2F command.");
-    }, "PublicKeyCredential's [[get]] with authenticator downgrade succeeded and then U2F failed in a mock hid authenticator. 2");
+        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: 34");
+    }, "PublicKeyCredential's [[get]] with authenticator downgrade failed in a mock hid authenticator. 2");
+
+    promise_test(function(t) {
+        const options = {
+            publicKey: {
+                challenge: asciiToUint8Array("123456")
+            }
+        };
+
+        if (window.internals)
+            internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
+        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: 34");
+    }, "PublicKeyCredential's [[get]] with authenticator downgrade succeeded and then U2F failed in a mock hid authenticator.");
 </script>
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index d68e3e9..707dc4f 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,43 @@
+2019-10-18  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Warn users when no credentials are found
+        https://bugs.webkit.org/show_bug.cgi?id=203147
+        <rdar://problem/55931123>
+
+        Reviewed by Brent Fulgham.
+
+        This patch returns _WKWebAuthenticationPanelUpdateNoCredentialsFound to client via
+        -[_WKWebAuthenticationPanelDelegate panel:updateWebAuthenticationPanel:] when either
+        CtapAuthenticator receives kCtap2ErrNoCredentials or U2fAuthenticator exhausts the
+        allow list.
+
+        This patch also enhances CtapAuthenticator::tryDowngrade to check if the CTAP command
+        can be converted to U2F commands to ensure kCtap2ErrNoCredentials is returned if it
+        is the case. Otherwise, after downgrading, U2fAuthenticator will return NotSupportedError
+        given it can't convert the commands.
+
+        * UIProcess/API/APIUIClient.h:
+        * UIProcess/API/APIWebAuthenticationPanelClient.h:
+        (API::WebAuthenticationPanelClient::updatePanel const):
+        * UIProcess/WebAuthentication/Authenticator.h:
+        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
+        (WebKit::AuthenticatorManager::authenticatorStatusUpdated):
+        * UIProcess/WebAuthentication/AuthenticatorManager.h:
+        * UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h:
+        * UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm:
+        (WebKit::WebAuthenticationPanelClient::WebAuthenticationPanelClient):
+        (WebKit::wkWebAuthenticationPanelUpdate):
+        (WebKit::WebAuthenticationPanelClient::updatePanel const):
+        * UIProcess/WebAuthentication/WebAuthenticationFlags.h: Renamed from Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationPanelFlags.h.
+        * UIProcess/WebAuthentication/WebAuthenticationRequestData.h:
+        * UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp:
+        * UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp:
+        (WebKit::CtapAuthenticator::continueGetAssertionAfterResponseReceived):
+        (WebKit::CtapAuthenticator::tryDowngrade):
+        * UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp:
+        (WebKit::U2fAuthenticator::issueSignCommand):
+        * WebKit.xcodeproj/project.pbxproj:
+
 2019-10-18  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Clipboard API] Refactor Pasteboard::read() to take an optional item index
diff --git a/Source/WebKit/UIProcess/API/APIUIClient.h b/Source/WebKit/UIProcess/API/APIUIClient.h
index ddd6e62..d97d391 100644
--- a/Source/WebKit/UIProcess/API/APIUIClient.h
+++ b/Source/WebKit/UIProcess/API/APIUIClient.h
@@ -39,7 +39,7 @@
 #endif
 
 #if ENABLE(WEB_AUTHN)
-#include "WebAuthenticationPanelFlags.h"
+#include "WebAuthenticationFlags.h"
 #endif
 
 namespace WebCore {
diff --git a/Source/WebKit/UIProcess/API/APIWebAuthenticationPanelClient.h b/Source/WebKit/UIProcess/API/APIWebAuthenticationPanelClient.h
index 5b6a9c7..555b561 100644
--- a/Source/WebKit/UIProcess/API/APIWebAuthenticationPanelClient.h
+++ b/Source/WebKit/UIProcess/API/APIWebAuthenticationPanelClient.h
@@ -28,6 +28,7 @@
 #if ENABLE(WEB_AUTHN)
 
 namespace WebKit {
+enum class WebAuthenticationStatus : bool;
 enum class WebAuthenticationResult : bool;
 }
 
@@ -38,6 +39,7 @@
 public:
     virtual ~WebAuthenticationPanelClient() = default;
 
+    virtual void updatePanel(WebKit::WebAuthenticationStatus) const { }
     virtual void dismissPanel(WebKit::WebAuthenticationResult) const { }
 };
 
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h b/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h
index cb0111a..dcc809e 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h
+++ b/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h
@@ -27,6 +27,7 @@
 
 #if ENABLE(WEB_AUTHN)
 
+#include "WebAuthenticationFlags.h"
 #include "WebAuthenticationRequestData.h"
 #include <WebCore/ExceptionData.h>
 #include <WebCore/PublicKeyCredentialData.h>
@@ -45,6 +46,7 @@
         virtual ~Observer() = default;
         virtual void respondReceived(Respond&&) = 0;
         virtual void downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator) = 0;
+        virtual void authenticatorStatusUpdated(WebAuthenticationStatus) = 0;
     };
 
     virtual ~Authenticator() = default;
diff --git a/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp b/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
index d032880..4c819ea 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
+++ b/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
@@ -243,6 +243,12 @@
     authenticatorAdded(WTFMove(downgradedAuthenticator));
 }
 
+void AuthenticatorManager::authenticatorStatusUpdated(WebAuthenticationStatus status)
+{
+    if (auto* panel = m_pendingRequestData.panel.get())
+        panel->client().updatePanel(status);
+}
+
 UniqueRef<AuthenticatorTransportService> AuthenticatorManager::createService(WebCore::AuthenticatorTransport transport, AuthenticatorTransportService::Observer& observer) const
 {
     return AuthenticatorTransportService::create(transport, observer);
diff --git a/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h b/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h
index 5026267..494a44d 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h
+++ b/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h
@@ -77,6 +77,7 @@
     // Authenticator::Observer
     void respondReceived(Respond&&) final;
     void downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator) final;
+    void authenticatorStatusUpdated(WebAuthenticationStatus) final;
 
     // Overriden by MockAuthenticatorManager.
     virtual UniqueRef<AuthenticatorTransportService> createService(WebCore::AuthenticatorTransport, AuthenticatorTransportService::Observer&) const;
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h
index c9c8558..3b0c891 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h
+++ b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h
@@ -39,7 +39,7 @@
 
 namespace WebKit {
 
-class WebAuthenticationPanelClient : public API::WebAuthenticationPanelClient, public CanMakeWeakPtr<WebAuthenticationPanelClient> {
+class WebAuthenticationPanelClient final : public API::WebAuthenticationPanelClient, public CanMakeWeakPtr<WebAuthenticationPanelClient> {
 public:
     WebAuthenticationPanelClient(_WKWebAuthenticationPanel *, id <_WKWebAuthenticationPanelDelegate>);
 
@@ -47,12 +47,14 @@
 
 private:
     // API::WebAuthenticationPanelClient
+    void updatePanel(WebAuthenticationStatus) const final;
     void dismissPanel(WebAuthenticationResult) const final;
 
     _WKWebAuthenticationPanel *m_panel;
     WeakObjCPtr<id <_WKWebAuthenticationPanelDelegate> > m_delegate;
 
     struct {
+        bool panelUpdateWebAuthenticationPanel : 1;
         bool panelDismissWebAuthenticationPanelWithResult : 1;
     } m_delegateMethods;
 };
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm
index c184e58..3a40303 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm
+++ b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm
@@ -28,7 +28,7 @@
 
 #if ENABLE(WEB_AUTHN)
 
-#import "WebAuthenticationPanelFlags.h"
+#import "WebAuthenticationFlags.h"
 #import "_WKWebAuthenticationPanel.h"
 
 namespace WebKit {
@@ -37,6 +37,7 @@
     : m_panel(panel)
     , m_delegate(delegate)
 {
+    m_delegateMethods.panelUpdateWebAuthenticationPanel = [delegate respondsToSelector:@selector(panel:updateWebAuthenticationPanel:)];
     m_delegateMethods.panelDismissWebAuthenticationPanelWithResult = [delegate respondsToSelector:@selector(panel:dismissWebAuthenticationPanelWithResult:)];
 }
 
@@ -45,6 +46,28 @@
     return m_delegate.get();
 }
 
+static _WKWebAuthenticationPanelUpdate wkWebAuthenticationPanelUpdate(WebAuthenticationStatus status)
+{
+    if (status == WebAuthenticationStatus::MultipleNFCTagsPresent)
+        return _WKWebAuthenticationPanelUpdateMultipleNFCTagsPresent;
+    if (status == WebAuthenticationStatus::NoCredentialsFound)
+        return _WKWebAuthenticationPanelUpdateNoCredentialsFound;
+    ASSERT_NOT_REACHED();
+    return _WKWebAuthenticationPanelUpdateMultipleNFCTagsPresent;
+}
+
+void WebAuthenticationPanelClient::updatePanel(WebAuthenticationStatus status) const
+{
+    if (!m_delegateMethods.panelUpdateWebAuthenticationPanel)
+        return;
+
+    auto delegate = m_delegate.get();
+    if (!delegate)
+        return;
+
+    [delegate panel:m_panel updateWebAuthenticationPanel:wkWebAuthenticationPanelUpdate(status)];
+}
+
 static _WKWebAuthenticationResult wkWebAuthenticationResult(WebAuthenticationResult result)
 {
     switch (result) {
diff --git a/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationPanelFlags.h b/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationFlags.h
similarity index 93%
rename from Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationPanelFlags.h
rename to Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationFlags.h
index 0d146b2..64116d4 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationPanelFlags.h
+++ b/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationFlags.h
@@ -40,6 +40,11 @@
     Failed
 };
 
+enum class WebAuthenticationStatus : bool {
+    MultipleNFCTagsPresent,
+    NoCredentialsFound
+};
+
 } // namespace WebKit
 
 #endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationRequestData.h b/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationRequestData.h
index 9786054..99ae0fb 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationRequestData.h
+++ b/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticationRequestData.h
@@ -28,7 +28,7 @@
 #if ENABLE(WEB_AUTHN)
 
 #include "APIWebAuthenticationPanel.h"
-#include "WebAuthenticationPanelFlags.h"
+#include "WebAuthenticationFlags.h"
 #include <WebCore/GlobalFrameIdentifier.h>
 #include <WebCore/PublicKeyCredentialCreationOptions.h>
 #include <WebCore/PublicKeyCredentialRequestOptions.h>
diff --git a/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp b/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp
index 6df8ca7..a6d3b91 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp
+++ b/Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp
@@ -30,7 +30,7 @@
 
 #include "AuthenticatorManager.h"
 #include "LocalService.h"
-#include "WebAuthenticationPanelFlags.h"
+#include "WebAuthenticationFlags.h"
 #include "WebAuthenticatorCoordinatorProxyMessages.h"
 #include "WebPageProxy.h"
 #include "WebProcessProxy.h"
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp
index ace4c0c..754cedb 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp
+++ b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp
@@ -34,6 +34,7 @@
 #include <WebCore/DeviceRequestConverter.h>
 #include <WebCore/DeviceResponseConverter.h>
 #include <WebCore/ExceptionData.h>
+#include <WebCore/U2fCommandConstructor.h>
 #include <wtf/RunLoop.h>
 #include <wtf/text/StringConcatenateNumbers.h>
 
@@ -94,6 +95,8 @@
         auto error = getResponseCode(data);
         if (error != CtapDeviceResponseCode::kCtap2ErrInvalidCBOR && tryDowngrade())
             return;
+        if (error == CtapDeviceResponseCode::kCtap2ErrNoCredentials && observer())
+            observer()->authenticatorStatusUpdated(WebAuthenticationStatus::NoCredentialsFound);
         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
         return;
     }
@@ -104,6 +107,8 @@
 {
     if (m_info.versions().find(ProtocolVersion::kU2f) == m_info.versions().end())
         return false;
+    if (!isConvertibleToU2fSignCommand(WTF::get<PublicKeyCredentialRequestOptions>(requestData().options)))
+        return false;
     if (!observer())
         return false;
 
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp b/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp
index ab46536..7936c5b 100644
--- a/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp
+++ b/Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp
@@ -99,6 +99,8 @@
 {
     auto& requestOptions = WTF::get<PublicKeyCredentialRequestOptions>(requestData().options);
     if (index >= requestOptions.allowCredentials.size()) {
+        if (auto* observer = this->observer())
+            observer->authenticatorStatusUpdated(WebAuthenticationStatus::NoCredentialsFound);
         receiveRespond(ExceptionData { NotAllowedError, "No credentials from the allowCredentials list is found in the authenticator."_s });
         return;
     }
diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj
index 08e152d..ca1bf54 100644
--- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj
+++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj
@@ -1060,7 +1060,7 @@
 		57B8264823050C5100B72EB0 /* FidoService.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B8264623050C5100B72EB0 /* FidoService.h */; };
 		57B8264C230603C100B72EB0 /* MockNfcService.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B8264A230603C100B72EB0 /* MockNfcService.h */; };
 		57BBEA6D22BC0BFE00273995 /* SOAuthorizationLoadPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 57BBEA6C22BC0BFE00273995 /* SOAuthorizationLoadPolicy.h */; };
-		57C6244B234679A400383FE7 /* WebAuthenticationPanelFlags.h in Headers */ = {isa = PBXBuildFile; fileRef = 57C6244A234679A400383FE7 /* WebAuthenticationPanelFlags.h */; };
+		57C6244B234679A400383FE7 /* WebAuthenticationFlags.h in Headers */ = {isa = PBXBuildFile; fileRef = 57C6244A234679A400383FE7 /* WebAuthenticationFlags.h */; };
 		57DCED6F2142EE630016B847 /* WebAuthenticatorCoordinatorMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCED6A2142EAE20016B847 /* WebAuthenticatorCoordinatorMessages.h */; };
 		57DCED702142EE680016B847 /* WebAuthenticatorCoordinatorProxyMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57DCED6C2142EAF90016B847 /* WebAuthenticatorCoordinatorProxyMessageReceiver.cpp */; };
 		57DCED712142EE6C0016B847 /* WebAuthenticatorCoordinatorProxyMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCED6D2142EAFA0016B847 /* WebAuthenticatorCoordinatorProxyMessages.h */; };
@@ -3548,7 +3548,7 @@
 		57B8E3D52355864A00D5C5D1 /* FidoAuthenticator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FidoAuthenticator.h; sourceTree = "<group>"; };
 		57B8E3D62355864A00D5C5D1 /* FidoAuthenticator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FidoAuthenticator.cpp; sourceTree = "<group>"; };
 		57BBEA6C22BC0BFE00273995 /* SOAuthorizationLoadPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SOAuthorizationLoadPolicy.h; sourceTree = "<group>"; };
-		57C6244A234679A400383FE7 /* WebAuthenticationPanelFlags.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAuthenticationPanelFlags.h; sourceTree = "<group>"; };
+		57C6244A234679A400383FE7 /* WebAuthenticationFlags.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAuthenticationFlags.h; sourceTree = "<group>"; };
 		57DCED6A2142EAE20016B847 /* WebAuthenticatorCoordinatorMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebAuthenticatorCoordinatorMessages.h; path = DerivedSources/WebKit2/WebAuthenticatorCoordinatorMessages.h; sourceTree = BUILT_PRODUCTS_DIR; };
 		57DCED6B2142EAE20016B847 /* WebAuthenticatorCoordinatorMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebAuthenticatorCoordinatorMessageReceiver.cpp; path = DerivedSources/WebKit2/WebAuthenticatorCoordinatorMessageReceiver.cpp; sourceTree = BUILT_PRODUCTS_DIR; };
 		57DCED6C2142EAF90016B847 /* WebAuthenticatorCoordinatorProxyMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebAuthenticatorCoordinatorProxyMessageReceiver.cpp; path = DerivedSources/WebKit2/WebAuthenticatorCoordinatorProxyMessageReceiver.cpp; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -7079,7 +7079,7 @@
 				57DCED842147363A0016B847 /* AuthenticatorManager.h */,
 				57DCED9921489F4D0016B847 /* AuthenticatorTransportService.cpp */,
 				57DCED8A214853130016B847 /* AuthenticatorTransportService.h */,
-				57C6244A234679A400383FE7 /* WebAuthenticationPanelFlags.h */,
+				57C6244A234679A400383FE7 /* WebAuthenticationFlags.h */,
 				57DCEDA62149F9DA0016B847 /* WebAuthenticationRequestData.h */,
 				57608296202BD8BA00116678 /* WebAuthenticatorCoordinatorProxy.cpp */,
 				57608295202BD8BA00116678 /* WebAuthenticatorCoordinatorProxy.h */,
@@ -9850,8 +9850,8 @@
 				1AF4CEF018BC481800BC2D34 /* VisitedLinkTableController.h in Headers */,
 				1A8E7D3D18C15149005A702A /* VisitedLinkTableControllerMessages.h in Headers */,
 				CEDA12E3152CD1B300D9E08D /* WebAlternativeTextClient.h in Headers */,
+				57C6244B234679A400383FE7 /* WebAuthenticationFlags.h in Headers */,
 				577FF7852346ECAA004EDFB9 /* WebAuthenticationPanelClient.h in Headers */,
-				57C6244B234679A400383FE7 /* WebAuthenticationPanelFlags.h in Headers */,
 				57DCEDB2214C604C0016B847 /* WebAuthenticationRequestData.h in Headers */,
 				57DCED6F2142EE630016B847 /* WebAuthenticatorCoordinatorMessages.h in Headers */,
 				57DCEDB3214C60530016B847 /* WebAuthenticatorCoordinatorProxy.h in Headers */,
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index d0819d7..085db8b 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,23 @@
+2019-10-18  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Warn users when no credentials are found
+        https://bugs.webkit.org/show_bug.cgi?id=203147
+        <rdar://problem/55931123>
+
+        Reviewed by Brent Fulgham.
+
+        Adds new tests for _WKWebAuthenticationPanelUpdateNoCredentialsFound.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
+        (-[TestWebAuthenticationPanelDelegate panel:updateWebAuthenticationPanel:]):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-cancel.html:
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-no-credentials.html: Added.
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html:
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html:
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-u2f-no-credentials.html: Added.
+
 2019-10-18  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Clipboard API] Refactor Pasteboard::read() to take an optional item index
diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
index 14887b7..b80e824 100644
--- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
+++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
@@ -341,6 +341,8 @@
 		57663DEC234F1F9300E85E09 /* web-authentication-get-assertion-hid.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */; };
 		57663DF32357E48900E85E09 /* web-authentication-get-assertion-hid-cancel.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 57663DF22357E45D00E85E09 /* web-authentication-get-assertion-hid-cancel.html */; };
 		5769C50B1D9B0002000847FB /* SerializedCryptoKeyWrap.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5769C50A1D9B0001000847FB /* SerializedCryptoKeyWrap.mm */; };
+		577454D02359B378008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 577454CF2359B338008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html */; };
+		577454D22359BB01008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 577454D12359BAD5008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html */; };
 		5774AA6821FBBF7800AF2A1B /* TestSOAuthorization.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5774AA6721FBBF7800AF2A1B /* TestSOAuthorization.mm */; };
 		5778D05622110A2600899E3B /* LoadWebArchive.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5778D05522110A2600899E3B /* LoadWebArchive.mm */; };
 		578CBD67204FB2C80083B9F2 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 578CBD66204FB2C70083B9F2 /* LocalAuthentication.framework */; };
@@ -1122,7 +1124,6 @@
 			dstPath = TestWebKitAPI.resources;
 			dstSubfolderSpec = 7;
 			files = (
-				41661C662355E85E00D33C27 /* getUserMedia-webaudio.html in Copy Resources */,
 				55A817FF2181021A0004A39A /* 100x100-red.tga in Copy Resources */,
 				1A9E52C913E65EF4006917F5 /* 18-characters.html in Copy Resources */,
 				55A81800218102210004A39A /* 400x400-green.png in Copy Resources */,
@@ -1254,6 +1255,7 @@
 				26F52EB318288F240023D412 /* geolocationWatchPositionWithHighAccuracy.html in Copy Resources */,
 				07E1F6A21FFC44FA0096C7EC /* getDisplayMedia.html in Copy Resources */,
 				467C565321B5ED130057516D /* GetSessionCookie.html in Copy Resources */,
+				41661C662355E85E00D33C27 /* getUserMedia-webaudio.html in Copy Resources */,
 				074994421EA5034B000DA44E /* getUserMedia.html in Copy Resources */,
 				074994421EA5034B000DA45E /* getUserMediaAudioVideoCapture.html in Copy Resources */,
 				F46A095B1ED8A6E600D4AA55 /* gif-and-file-input.html in Copy Resources */,
@@ -1445,8 +1447,10 @@
 				2EBD9D0A2134730D002DA758 /* video.html in Copy Resources */,
 				CD577799211CE0E4001B371E /* web-audio-only.html in Copy Resources */,
 				57663DF32357E48900E85E09 /* web-authentication-get-assertion-hid-cancel.html in Copy Resources */,
+				577454D02359B378008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html in Copy Resources */,
 				57663DEC234F1F9300E85E09 /* web-authentication-get-assertion-hid.html in Copy Resources */,
 				57663DEA234EA66D00E85E09 /* web-authentication-get-assertion-nfc.html in Copy Resources */,
+				577454D22359BB01008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html in Copy Resources */,
 				57C624502346C21E00383FE7 /* web-authentication-get-assertion.html in Copy Resources */,
 				1C2B81861C89259D00A5529F /* webfont.html in Copy Resources */,
 				51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */,
@@ -1869,6 +1873,8 @@
 		57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-get-assertion-hid.html"; sourceTree = "<group>"; };
 		57663DF22357E45D00E85E09 /* web-authentication-get-assertion-hid-cancel.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-get-assertion-hid-cancel.html"; sourceTree = "<group>"; };
 		5769C50A1D9B0001000847FB /* SerializedCryptoKeyWrap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SerializedCryptoKeyWrap.mm; sourceTree = "<group>"; };
+		577454CF2359B338008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-get-assertion-hid-no-credentials.html"; sourceTree = "<group>"; };
+		577454D12359BAD5008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-get-assertion-u2f-no-credentials.html"; sourceTree = "<group>"; };
 		5774AA6721FBBF7800AF2A1B /* TestSOAuthorization.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TestSOAuthorization.mm; sourceTree = "<group>"; };
 		5778D05522110A2600899E3B /* LoadWebArchive.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LoadWebArchive.mm; sourceTree = "<group>"; };
 		578CBD66204FB2C70083B9F2 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
@@ -3391,8 +3397,10 @@
 				CD57779B211CE6CE001B371E /* video-with-audio-and-web-audio.html */,
 				CD577798211CDE8F001B371E /* web-audio-only.html */,
 				57663DF22357E45D00E85E09 /* web-authentication-get-assertion-hid-cancel.html */,
+				577454CF2359B338008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html */,
 				57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */,
 				57663DE9234EA60B00E85E09 /* web-authentication-get-assertion-nfc.html */,
+				577454D12359BAD5008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html */,
 				57C6244F2346C1EC00383FE7 /* web-authentication-get-assertion.html */,
 				51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
 				51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
@@ -3756,8 +3764,8 @@
 				26F52EAE18288C040023D412 /* geolocationGetCurrentPositionWithHighAccuracy.html */,
 				26F52EB018288F0F0023D412 /* geolocationWatchPosition.html */,
 				26F52EB118288F0F0023D412 /* geolocationWatchPositionWithHighAccuracy.html */,
-				4A410F4D19AF7BEF002EBAB5 /* getUserMedia.html */,
 				41661C652355D98B00D33C27 /* getUserMedia-webaudio.html */,
+				4A410F4D19AF7BEF002EBAB5 /* getUserMedia.html */,
 				4A410F4D19AF7BEF002EBAC5 /* getUserMediaAudioVideoCapture.html */,
 				BCBD372E125ABBE600D2C29F /* icon.png */,
 				CE3524F51B142BBB0028A7C5 /* input-focus-blur.html */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
index e7bb606..6f418ae 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
@@ -42,6 +42,7 @@
 static bool webAuthenticationPanelRan = false;
 static bool webAuthenticationPanelFailed = false;
 static bool webAuthenticationPanelSucceded = false;
+static bool webAuthenticationPanelUpdateNoCredentialsFound = false;
 static RetainPtr<_WKWebAuthenticationPanel> gPanel;
 
 @interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
@@ -49,6 +50,13 @@
 
 @implementation TestWebAuthenticationPanelDelegate
 
+- (void)panel:(_WKWebAuthenticationPanel *)panel updateWebAuthenticationPanel:(_WKWebAuthenticationPanelUpdate)update
+{
+    ASSERT_NE(panel, nil);
+    if (update == _WKWebAuthenticationPanelUpdateNoCredentialsFound)
+        webAuthenticationPanelUpdateNoCredentialsFound = true;
+}
+
 - (void)panel:(_WKWebAuthenticationPanel *)panel dismissWebAuthenticationPanelWithResult:(_WKWebAuthenticationResult)result
 {
     ASSERT_NE(panel, nil);
@@ -64,13 +72,22 @@
 
 @end
 
+@interface TestWebAuthenticationPanelFakeDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
+@end
+
+@implementation TestWebAuthenticationPanelFakeDelegate
+@end
+
 @interface TestWebAuthenticationPanelUIDelegate : NSObject <WKUIDelegatePrivate>
 @property bool isRacy;
+@property bool isFake;
+@property bool isNull;
+
 - (instancetype)init;
 @end
 
 @implementation TestWebAuthenticationPanelUIDelegate {
-    RetainPtr<TestWebAuthenticationPanelDelegate> _delegate;
+    RetainPtr<NSObject<_WKWebAuthenticationPanelDelegate>> _delegate;
     BlockPtr<void(_WKWebAuthenticationPanelResult)> _callback;
 }
 
@@ -85,7 +102,12 @@
 {
     webAuthenticationPanelRan = true;
 
-    _delegate = adoptNS([[TestWebAuthenticationPanelDelegate alloc] init]);
+    if (!_isNull) {
+        if (!_isFake)
+            _delegate = adoptNS([[TestWebAuthenticationPanelDelegate alloc] init]);
+        else
+            _delegate = adoptNS([[TestWebAuthenticationPanelFakeDelegate alloc] init]);
+    }
     ASSERT_NE(panel, nil);
     gPanel = panel;
     [gPanel setDelegate:_delegate.get()];
@@ -450,6 +472,112 @@
     EXPECT_FALSE(webAuthenticationPanelSucceded);
 }
 
+TEST(WebAuthenticationPanel, PanelHidCtapNoCredentialsFound)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid-no-credentials" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    Util::run(&webAuthenticationPanelUpdateNoCredentialsFound);
+}
+
+TEST(WebAuthenticationPanel, PanelU2fCtapNoCredentialsFound)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-u2f-no-credentials" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    Util::run(&webAuthenticationPanelUpdateNoCredentialsFound);
+}
+
+TEST(WebAuthenticationPanel, FakePanelHidSuccess)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [delegate setIsFake:true];
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    [webView waitForMessage:@"Succeeded!"];
+}
+
+TEST(WebAuthenticationPanel, FakePanelHidCtapNoCredentialsFound)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid-no-credentials" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [delegate setIsFake:true];
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    [webView waitForMessage:@"Operation timed out."];
+}
+
+TEST(WebAuthenticationPanel, NullPanelHidSuccess)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [delegate setIsNull:true];
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    [webView waitForMessage:@"Succeeded!"];
+}
+
+TEST(WebAuthenticationPanel, NullPanelHidCtapNoCredentialsFound)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid-no-credentials" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [delegate setIsNull:true];
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    [webView waitForMessage:@"Operation timed out."];
+}
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(WEB_AUTHN)
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-cancel.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-cancel.html
index cf12481..2cb6596 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-cancel.html
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-cancel.html
@@ -1,7 +1,7 @@
 <input type="text" id="input">
 <script>
     if (window.internals) {
-        internals.setMockWebAuthenticationConfiguration({ hid: { expectCancel: true } });
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { expectCancel: true } });
         internals.withUserGesture(() => { input.focus(); });
     }
 
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-no-credentials.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-no-credentials.html
new file mode 100644
index 0000000..10a7092
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-no-credentials.html
@@ -0,0 +1,23 @@
+<input type="text" id="input">
+<script>
+    const testCtapErrNoCredentialsResponseBase64 = "Lg==";
+    if (window.internals) {
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testCtapErrNoCredentialsResponseBase64] } });
+        internals.withUserGesture(() => { input.focus(); });
+    }
+
+    const options = {
+        publicKey: {
+            challenge: new Uint8Array(16),
+            timeout: 100
+        }
+    };
+
+    navigator.credentials.get(options).then(credential => {
+        // console.log("Succeeded!");
+        window.webkit.messageHandlers.testHandler.postMessage("Succeeded!");
+    }, error => {
+        // console.log(error.message);
+        window.webkit.messageHandlers.testHandler.postMessage(error.message);
+    });
+</script>
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html
index 65510d1..328f846 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid.html
@@ -7,7 +7,7 @@
         "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
         "QoJ1L7Fe64G9uBc=";
     if (window.internals) {
-        internals.setMockWebAuthenticationConfiguration({ hid: { payloadBase64: [testAssertionMessageBase64] } });
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { payloadBase64: [testAssertionMessageBase64] } });
         internals.withUserGesture(() => { input.focus(); });
     }
 
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html
index 945b6b6..d05c39d 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-nfc.html
@@ -11,7 +11,7 @@
         "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
         "QoJ1L7Fe64G9uBeQAA==";
     if (window.internals) {
-        internals.setMockWebAuthenticationConfiguration({ nfc: { payloadBase64: [testNfcCtapVersionBase64, testGetInfoResponseApduBase64, testAssertionMessageApduBase64] } });
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, nfc: { payloadBase64: [testNfcCtapVersionBase64, testGetInfoResponseApduBase64, testAssertionMessageApduBase64] } });
         internals.withUserGesture(() => { input.focus(); });
     }
 
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-u2f-no-credentials.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-u2f-no-credentials.html
new file mode 100644
index 0000000..4e7e218
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-u2f-no-credentials.html
@@ -0,0 +1,17 @@
+<input type="text" id="input">
+<script>
+    const testU2fApduWrongDataOnlyResponseBase64 = "aoA=";
+    if (window.internals) {
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64] } });
+        internals.withUserGesture(() => { input.focus(); });
+    }
+
+    const options = {
+        publicKey: {
+            challenge: new Uint8Array(16),
+            allowCredentials: [{ type: "public-key", id: new Uint8Array(16) }]
+        }
+    };
+
+    navigator.credentials.get(options);
+</script>