[WebAuthn] Add more information to _WKWebAuthenticationPanel
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Oct 2019 02:50:31 +0000 (02:50 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 30 Oct 2019 02:50:31 +0000 (02:50 +0000)
https://bugs.webkit.org/show_bug.cgi?id=202561
<rdar://problem/55973910>

Reviewed by Youenn Fablet and Brent Fulgham.

Source/WebCore:

Covered by new tests within existing test files.

* Modules/webauthn/AuthenticatorCoordinator.cpp:
* Modules/webauthn/WebAuthenticationConstants.h:

Source/WebKit:

This change adds transports and type to _WKWebAuthenticationPanel such that
clients can know what transport the current ceremony demands and the type of
the current ceremony. These extra information allow clients to give users
more specific instructions to interact with authenticators.

To pass transports to client, the way how them is collected is changed significantly:
1) The timing is moved to runPanel before the client delegate call.
2) NfcService::isAvailable is added for AuthenticatorManager to determine if NFC
is available in the current device.
3) AuthenticatorManager::filterTransports is added to filter transports requested
by RP to ones that are available. This process is handled by each service naturally
before.
4) AuthenticatorManager::startRequest is now being splitted into AuthenticatorManager::handleRequest,
AuthenticatorManager::runPanel and AuthenticatorManager::getTransports.

To pass type to _WKWebAuthenticationPanel, ClientDataType is moved from
WebCore::AuthenticatorCoordinator to WebCore::WebAuthenticationConstants in
order to be reused to indicate the ceremony type.

* UIProcess/API/APIWebAuthenticationPanel.cpp:
(API::WebAuthenticationPanel::create):
(API::WebAuthenticationPanel::WebAuthenticationPanel):
* UIProcess/API/APIWebAuthenticationPanel.h:
* UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
* UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
(-[_WKWebAuthenticationPanel relyingPartyID]):
(wkWebAuthenticationTransport):
(-[_WKWebAuthenticationPanel transports]):
(wkWebAuthenticationType):
(-[_WKWebAuthenticationPanel type]):
* UIProcess/WebAuthentication/AuthenticatorManager.cpp:
(WebKit::WebCore::collectTransports):
(WebKit::WebCore::getClientDataType):
(WebKit::AuthenticatorManager::handleRequest):
(WebKit::AuthenticatorManager::filterTransports const):
(WebKit::AuthenticatorManager::startDiscovery):
(WebKit::AuthenticatorManager::initTimeOutTimer):
(WebKit::AuthenticatorManager::runPanel):
(WebKit::AuthenticatorManager::getTransports const):
(WebKit::AuthenticatorManager::respondReceivedInternal): Deleted.
(WebKit::AuthenticatorManager::startRequest): Deleted.
* UIProcess/WebAuthentication/AuthenticatorManager.h:
(WebKit::AuthenticatorManager::respondReceivedInternal):
* UIProcess/WebAuthentication/Cocoa/NfcService.h:
* UIProcess/WebAuthentication/Cocoa/NfcService.mm:
(WebKit::NfcService::isAvailable):
(WebKit::NfcService::platformStartDiscovery):
* UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp:
(WebKit::MockAuthenticatorManager::filterTransports const):
* UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.h:

Tools:

Adds new API tests.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
(-[TestWebAuthenticationPanelUIDelegate _webView:runWebAuthenticationPanel:initiatedByFrame:completionHandler:]):
(-[TestWebAuthenticationPanelUIDelegate panel]):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html: Added.

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

18 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Modules/webauthn/AuthenticatorCoordinator.cpp
Source/WebCore/Modules/webauthn/WebAuthenticationConstants.h
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/APIWebAuthenticationPanel.cpp
Source/WebKit/UIProcess/API/APIWebAuthenticationPanel.h
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/NfcService.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/NfcService.mm
Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp
Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html [new file with mode: 0644]

index ef0e772..2de0d3e 100644 (file)
@@ -1,3 +1,16 @@
+2019-10-29  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Add more information to _WKWebAuthenticationPanel
+        https://bugs.webkit.org/show_bug.cgi?id=202561
+        <rdar://problem/55973910>
+
+        Reviewed by Youenn Fablet and Brent Fulgham.
+
+        Covered by new tests within existing test files.
+
+        * Modules/webauthn/AuthenticatorCoordinator.cpp:
+        * Modules/webauthn/WebAuthenticationConstants.h:
+
 2019-10-29  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         REGRESSION (r251693): [iOS] Unable to change selection after focusing an element with keyboard attached
index 66e5ba1..f80f64b 100644 (file)
@@ -42,6 +42,7 @@
 #include "RegistrableDomain.h"
 #include "LegacySchemeRegistry.h"
 #include "SecurityOrigin.h"
+#include "WebAuthenticationConstants.h"
 #include <pal/crypto/CryptoDigest.h>
 #include <wtf/JSONValues.h>
 #include <wtf/NeverDestroyed.h>
@@ -51,11 +52,6 @@ namespace WebCore {
 
 namespace AuthenticatorCoordinatorInternal {
 
-enum class ClientDataType {
-    Create,
-    Get
-};
-
 // FIXME(181948): Add token binding ID.
 static Ref<ArrayBuffer> produceClientDataJson(ClientDataType type, const BufferSource& challenge, const SecurityOrigin& origin)
 {
index e4fd12b..182e2ea 100644 (file)
@@ -70,4 +70,10 @@ const size_t ES256FieldElementLength = 32;
 // https://www.w3.org/TR/webauthn/#none-attestation
 const char noneAttestationValue[] = "none";
 
+// https://www.w3.org/TR/webauthn-1/#dom-collectedclientdata-type
+enum class ClientDataType : bool {
+    Create,
+    Get
+};
+
 } // namespace WebCore
index f692ace..304558f 100644 (file)
@@ -1,3 +1,62 @@
+2019-10-29  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Add more information to _WKWebAuthenticationPanel
+        https://bugs.webkit.org/show_bug.cgi?id=202561
+        <rdar://problem/55973910>
+
+        Reviewed by Youenn Fablet and Brent Fulgham.
+
+        This change adds transports and type to _WKWebAuthenticationPanel such that
+        clients can know what transport the current ceremony demands and the type of
+        the current ceremony. These extra information allow clients to give users
+        more specific instructions to interact with authenticators.
+
+        To pass transports to client, the way how them is collected is changed significantly:
+        1) The timing is moved to runPanel before the client delegate call.
+        2) NfcService::isAvailable is added for AuthenticatorManager to determine if NFC
+        is available in the current device.
+        3) AuthenticatorManager::filterTransports is added to filter transports requested
+        by RP to ones that are available. This process is handled by each service naturally
+        before.
+        4) AuthenticatorManager::startRequest is now being splitted into AuthenticatorManager::handleRequest,
+        AuthenticatorManager::runPanel and AuthenticatorManager::getTransports.
+
+        To pass type to _WKWebAuthenticationPanel, ClientDataType is moved from
+        WebCore::AuthenticatorCoordinator to WebCore::WebAuthenticationConstants in
+        order to be reused to indicate the ceremony type.
+
+        * UIProcess/API/APIWebAuthenticationPanel.cpp:
+        (API::WebAuthenticationPanel::create):
+        (API::WebAuthenticationPanel::WebAuthenticationPanel):
+        * UIProcess/API/APIWebAuthenticationPanel.h:
+        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
+        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
+        (-[_WKWebAuthenticationPanel relyingPartyID]):
+        (wkWebAuthenticationTransport):
+        (-[_WKWebAuthenticationPanel transports]):
+        (wkWebAuthenticationType):
+        (-[_WKWebAuthenticationPanel type]):
+        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
+        (WebKit::WebCore::collectTransports):
+        (WebKit::WebCore::getClientDataType):
+        (WebKit::AuthenticatorManager::handleRequest):
+        (WebKit::AuthenticatorManager::filterTransports const):
+        (WebKit::AuthenticatorManager::startDiscovery):
+        (WebKit::AuthenticatorManager::initTimeOutTimer):
+        (WebKit::AuthenticatorManager::runPanel):
+        (WebKit::AuthenticatorManager::getTransports const):
+        (WebKit::AuthenticatorManager::respondReceivedInternal): Deleted.
+        (WebKit::AuthenticatorManager::startRequest): Deleted.
+        * UIProcess/WebAuthentication/AuthenticatorManager.h:
+        (WebKit::AuthenticatorManager::respondReceivedInternal):
+        * UIProcess/WebAuthentication/Cocoa/NfcService.h:
+        * UIProcess/WebAuthentication/Cocoa/NfcService.mm:
+        (WebKit::NfcService::isAvailable):
+        (WebKit::NfcService::platformStartDiscovery):
+        * UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp:
+        (WebKit::MockAuthenticatorManager::filterTransports const):
+        * UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.h:
+
 2019-10-29  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         REGRESSION (r251693): [iOS] Unable to change selection in a focused element if the element's bounds change
index a5c0e30..0cddbcf 100644 (file)
 
 #include "APIWebAuthenticationPanelClient.h"
 #include "AuthenticatorManager.h"
+#include <WebCore/WebAuthenticationConstants.h>
 
 namespace API {
+using namespace WebCore;
 using namespace WebKit;
 
-Ref<WebAuthenticationPanel> WebAuthenticationPanel::create(const AuthenticatorManager& manager, const WTF::String& rpId)
+Ref<WebAuthenticationPanel> WebAuthenticationPanel::create(const AuthenticatorManager& manager, const WTF::String& rpId, const TransportSet& transports, ClientDataType type)
 {
-    return adoptRef(*new WebAuthenticationPanel(manager, rpId));
+    return adoptRef(*new WebAuthenticationPanel(manager, rpId, transports, type));
 }
 
-WebAuthenticationPanel::WebAuthenticationPanel(const AuthenticatorManager& manager, const WTF::String& rpId)
+WebAuthenticationPanel::WebAuthenticationPanel(const AuthenticatorManager& manager, const WTF::String& rpId, const TransportSet& transports, ClientDataType type)
     : m_manager(makeWeakPtr(manager))
     , m_rpId(rpId)
     , m_client(WTF::makeUniqueRef<WebAuthenticationPanelClient>())
+    , m_clientDataType(type)
 {
+    m_transports = Vector<AuthenticatorTransport>();
+    m_transports.reserveInitialCapacity(AuthenticatorManager::maxTransportNumber);
+    if (transports.contains(AuthenticatorTransport::Usb))
+        m_transports.uncheckedAppend(AuthenticatorTransport::Usb);
+    if (transports.contains(AuthenticatorTransport::Nfc))
+        m_transports.uncheckedAppend(AuthenticatorTransport::Nfc);
 }
 
 WebAuthenticationPanel::~WebAuthenticationPanel() = default;
index c3f403f..cc42a5f 100644 (file)
 #if ENABLE(WEB_AUTHN)
 
 #include "APIObject.h"
+#include <WebCore/AuthenticatorTransport.h>
 #include <wtf/UniqueRef.h>
 #include <wtf/WeakPtr.h>
 #include <wtf/text/WTFString.h>
 
+namespace WebCore {
+enum class ClientDataType : bool;
+}
+
 namespace WebKit {
 class AuthenticatorManager;
 }
@@ -42,21 +47,27 @@ class WebAuthenticationPanelClient;
 
 class WebAuthenticationPanel final : public ObjectImpl<Object::Type::WebAuthenticationPanel>, public CanMakeWeakPtr<WebAuthenticationPanel> {
 public:
-    static Ref<WebAuthenticationPanel> create(const WebKit::AuthenticatorManager&, const WTF::String& rpId);
+    using TransportSet = HashSet<WebCore::AuthenticatorTransport, WTF::IntHash<WebCore::AuthenticatorTransport>, WTF::StrongEnumHashTraits<WebCore::AuthenticatorTransport>>;
+
+    static Ref<WebAuthenticationPanel> create(const WebKit::AuthenticatorManager&, const WTF::String& rpId, const TransportSet&, WebCore::ClientDataType);
     ~WebAuthenticationPanel();
 
     WTF::String rpId() const { return m_rpId; }
+    const Vector<WebCore::AuthenticatorTransport>& transports() const { return m_transports; }
+    WebCore::ClientDataType clientDataType() const { return m_clientDataType; }
     void cancel() const;
 
     const WebAuthenticationPanelClient& client() const { return m_client.get(); }
     void setClient(UniqueRef<WebAuthenticationPanelClient>&&);
 
 private:
-    WebAuthenticationPanel(const WebKit::AuthenticatorManager&, const WTF::String& rpId);
+    WebAuthenticationPanel(const WebKit::AuthenticatorManager&, const WTF::String& rpId, const TransportSet&, WebCore::ClientDataType);
 
     WeakPtr<WebKit::AuthenticatorManager> m_manager;
     WTF::String m_rpId;
     UniqueRef<WebAuthenticationPanelClient> m_client;
+    Vector<WebCore::AuthenticatorTransport> m_transports;
+    WebCore::ClientDataType m_clientDataType;
 };
 
 } // namespace API
index fc6c961..3c874ab 100644 (file)
@@ -49,6 +49,16 @@ typedef NS_ENUM(NSInteger, _WKWebAuthenticationResult) {
     _WKWebAuthenticationResultFailed,
 } WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
+typedef NS_ENUM(NSInteger, _WKWebAuthenticationTransport) {
+    _WKWebAuthenticationTransportUSB,
+    _WKWebAuthenticationTransportNFC,
+} WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+typedef NS_ENUM(NSInteger, _WKWebAuthenticationType) {
+    _WKWebAuthenticationTypeCreate,
+    _WKWebAuthenticationTypeGet,
+} WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 @protocol _WKWebAuthenticationPanelDelegate <NSObject>
 
 @optional
@@ -63,6 +73,8 @@ WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
 
 @property (nullable, nonatomic, weak) id <_WKWebAuthenticationPanelDelegate> delegate;
 @property (nonatomic, readonly, copy) NSString *relyingPartyID;
+@property (nonatomic, readonly, copy) NSArray *transports;
+@property (nonatomic, readonly) _WKWebAuthenticationType type;
 
 - (void)cancel;
 
index 1728e6f..3ad3075 100644 (file)
 #import "config.h"
 #import "WebAuthenticationPanelClient.h"
 #import "_WKWebAuthenticationPanelInternal.h"
+#import <WebCore/WebAuthenticationConstants.h>
 
 #import <wtf/RetainPtr.h>
 
 @implementation _WKWebAuthenticationPanel {
 #if ENABLE(WEB_AUTHN)
     WeakPtr<WebKit::WebAuthenticationPanelClient> _client;
+    RetainPtr<NSMutableArray> _transports;
 #endif
 }
 
     [super dealloc];
 }
 
-- (NSString *)relyingPartyID
-{
-    return _panel->rpId();
-}
-
 - (id <_WKWebAuthenticationPanelDelegate>)delegate
 {
     if (!_client)
     _panel->setClient(WTFMove(client));
 }
 
+
+- (NSString *)relyingPartyID
+{
+    return _panel->rpId();
+}
+
+static _WKWebAuthenticationTransport wkWebAuthenticationTransport(WebCore::AuthenticatorTransport transport)
+{
+    switch (transport) {
+    case WebCore::AuthenticatorTransport::Usb:
+        return _WKWebAuthenticationTransportUSB;
+    case WebCore::AuthenticatorTransport::Nfc:
+        return _WKWebAuthenticationTransportNFC;
+    default:
+        ASSERT_NOT_REACHED();
+        return _WKWebAuthenticationTransportUSB;
+    }
+}
+
+- (NSArray *)transports
+{
+    if (_transports)
+        return _transports.get();
+
+    auto& transports = _panel->transports();
+    _transports = [[NSMutableArray alloc] initWithCapacity:transports.size()];
+    for (auto& transport : transports)
+        [_transports addObject:adoptNS([[NSNumber alloc] initWithInt:wkWebAuthenticationTransport(transport)]).get()];
+    return _transports.get();
+}
+
+static _WKWebAuthenticationType wkWebAuthenticationType(WebCore::ClientDataType type)
+{
+    switch (type) {
+    case WebCore::ClientDataType::Create:
+        return _WKWebAuthenticationTypeCreate;
+    case WebCore::ClientDataType::Get:
+        return _WKWebAuthenticationTypeGet;
+    default:
+        ASSERT_NOT_REACHED();
+        return _WKWebAuthenticationTypeCreate;
+    }
+}
+
+- (_WKWebAuthenticationType)type
+{
+    return wkWebAuthenticationType(_panel->clientDataType());
+}
+
 #endif // ENABLE(WEB_AUTHN)
 
 - (void)cancel
index 4db48f3..cce8379 100644 (file)
 #include "APIUIClient.h"
 #include "APIWebAuthenticationPanel.h"
 #include "APIWebAuthenticationPanelClient.h"
+#include "LocalService.h"
+#include "NfcService.h"
 #include "WebPageProxy.h"
 #include "WebPreferencesKeys.h"
 #include <WebCore/AuthenticatorTransport.h>
 #include <WebCore/PublicKeyCredentialCreationOptions.h>
+#include <WebCore/WebAuthenticationConstants.h>
 #include <wtf/MonotonicTime.h>
 
 namespace WebKit {
@@ -42,8 +45,6 @@ using namespace WebCore;
 
 namespace {
 
-const size_t maxTransportNumber = 3;
-
 // Suggested by WebAuthN spec as of 7 August 2018.
 const unsigned maxTimeOutValue = 120000;
 
@@ -108,12 +109,12 @@ static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicK
             if (transport == AuthenticatorTransport::Ble)
                 continue;
             result.add(transport);
-            if (result.size() >= maxTransportNumber)
+            if (result.size() >= AuthenticatorManager::maxTransportNumber)
                 return result;
         }
     }
 
-    ASSERT(result.size() < maxTransportNumber);
+    ASSERT(result.size() < AuthenticatorManager::maxTransportNumber);
     return result;
 }
 
@@ -141,8 +142,17 @@ static String getRpId(const Variant<PublicKeyCredentialCreationOptions, PublicKe
     return WTF::get<PublicKeyCredentialRequestOptions>(options).rpId;
 }
 
+static ClientDataType getClientDataType(const Variant<PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions>& options)
+{
+    if (WTF::holds_alternative<PublicKeyCredentialCreationOptions>(options))
+        return ClientDataType::Create;
+    return ClientDataType::Get;
+}
+
 } // namespace
 
+const size_t AuthenticatorManager::maxTransportNumber = 3;
+
 AuthenticatorManager::AuthenticatorManager()
     : m_requestTimeOutTimer(RunLoop::main(), this, &AuthenticatorManager::timeOutTimerFired)
 {
@@ -161,6 +171,7 @@ void AuthenticatorManager::handleRequest(WebAuthenticationRequestData&& data, Ca
     m_pendingCompletionHandler = WTFMove(callback);
 
     // 2. Ask clients to show appropriate UI if any and then start the request.
+    initTimeOutTimer();
     runPanel();
 }
 
@@ -261,16 +272,19 @@ UniqueRef<AuthenticatorTransportService> AuthenticatorManager::createService(Web
     return AuthenticatorTransportService::create(transport, observer);
 }
 
-void AuthenticatorManager::respondReceivedInternal(Respond&&)
+void AuthenticatorManager::filterTransports(TransportSet& transports) const
 {
+    if (!NfcService::isAvailable())
+        transports.remove(AuthenticatorTransport::Nfc);
+    if (!LocalService::isAvailable())
+        transports.remove(AuthenticatorTransport::Internal);
 }
 
 void AuthenticatorManager::startDiscovery(const TransportSet& transports)
 {
+    ASSERT(RunLoop::isMain());
     ASSERT(m_services.isEmpty() && transports.size() <= maxTransportNumber);
     for (auto& transport : transports) {
-        if (transport == AuthenticatorTransport::Internal && !isFeatureEnabled(m_pendingRequestData.page.get(), WebPreferencesKey::webAuthenticationLocalAuthenticatorEnabledKey()))
-            continue;
         // Only allow USB authenticators when clients don't have dedicated UI.
         if (transport != AuthenticatorTransport::Usb && (m_pendingRequestData.panelResult == WebAuthenticationPanelResult::Unavailable))
             continue;
@@ -280,8 +294,15 @@ void AuthenticatorManager::startDiscovery(const TransportSet& transports)
     }
 }
 
-void AuthenticatorManager::initTimeOutTimer(const Optional<unsigned>& timeOutInMs)
+void AuthenticatorManager::initTimeOutTimer()
 {
+    Optional<unsigned> timeOutInMs;
+    WTF::switchOn(m_pendingRequestData.options, [&](const PublicKeyCredentialCreationOptions& options) {
+        timeOutInMs = options.timeout;
+    }, [&](const PublicKeyCredentialRequestOptions& options) {
+        timeOutInMs = options.timeout;
+    });
+
     unsigned timeOutInMsValue = std::min(maxTimeOutValue, timeOutInMs.valueOr(maxTimeOutValue));
     m_requestTimeOutTimer.startOneShot(Seconds::fromMilliseconds(timeOutInMsValue));
 }
@@ -303,32 +324,19 @@ void AuthenticatorManager::runPanel()
     if (!frame)
         return;
 
-    m_pendingRequestData.panel = API::WebAuthenticationPanel::create(*this, getRpId(m_pendingRequestData.options));
+    // Get available transports and start discovering authenticators on them.
+    auto& options = m_pendingRequestData.options;
+    auto transports = getTransports();
+    m_pendingRequestData.panel = API::WebAuthenticationPanel::create(*this, getRpId(options), transports, getClientDataType(options));
     auto& panel = *m_pendingRequestData.panel;
-    page->uiClient().runWebAuthenticationPanel(*page, panel, *frame, m_pendingRequestData.origin, [weakPanel = makeWeakPtr(panel), weakThis = makeWeakPtr(*this), this] (WebAuthenticationPanelResult result) {
+    page->uiClient().runWebAuthenticationPanel(*page, panel, *frame, m_pendingRequestData.origin, [transports = WTFMove(transports), weakPanel = makeWeakPtr(panel), weakThis = makeWeakPtr(*this), this] (WebAuthenticationPanelResult result) {
         // The panel address is used to determine if the current pending request is still the same.
         if (!weakThis || !weakPanel
             || (result == WebAuthenticationPanelResult::DidNotPresent)
             || (weakPanel.get() != m_pendingRequestData.panel.get()))
             return;
         m_pendingRequestData.panelResult = result;
-        startRequest();
-    });
-}
-
-void AuthenticatorManager::startRequest()
-{
-    ASSERT(RunLoop::isMain());
-    // Get available transports and start discovering authenticators on them.
-    WTF::switchOn(m_pendingRequestData.options, [&](const PublicKeyCredentialCreationOptions& options) {
-        initTimeOutTimer(options.timeout);
-
-        auto transports = collectTransports(options.authenticatorSelection);
-        processGoogleLegacyAppIdSupportExtension(options.extensions, transports);
-        startDiscovery(collectTransports(options.authenticatorSelection));
-    }, [&](const  PublicKeyCredentialRequestOptions& options) {
-        initTimeOutTimer(options.timeout);
-        startDiscovery(collectTransports(options.allowCredentials));
+        startDiscovery(transports);
     });
 }
 
@@ -337,7 +345,7 @@ void AuthenticatorManager::invokePendingCompletionHandler(Respond&& respond)
     if (auto *panel = m_pendingRequestData.panel.get()) {
         WTF::switchOn(respond, [&](const WebCore::PublicKeyCredentialData&) {
             panel->client().dismissPanel(WebAuthenticationResult::Succeeded);
-        }, [&](const  WebCore::ExceptionData&) {
+        }, [&](const WebCore::ExceptionData&) {
             panel->client().dismissPanel(WebAuthenticationResult::Failed);
         });
     }
@@ -358,6 +366,21 @@ void AuthenticatorManager::restartDiscovery()
         service->restartDiscovery();
 }
 
+auto AuthenticatorManager::getTransports() const -> TransportSet
+{
+    TransportSet transports;
+    WTF::switchOn(m_pendingRequestData.options, [&](const PublicKeyCredentialCreationOptions& options) {
+        transports = collectTransports(options.authenticatorSelection);
+        processGoogleLegacyAppIdSupportExtension(options.extensions, transports);
+    }, [&](const PublicKeyCredentialRequestOptions& options) {
+        transports = collectTransports(options.allowCredentials);
+    });
+    if (!isFeatureEnabled(m_pendingRequestData.page.get(), WebPreferencesKey::webAuthenticationLocalAuthenticatorEnabledKey()))
+        transports.remove(AuthenticatorTransport::Internal);
+    filterTransports(transports);
+    return transports;
+}
+
 } // namespace WebKit
 
 #endif // ENABLE(WEB_AUTHN)
index 3628d59..05a2487 100644 (file)
@@ -55,6 +55,8 @@ public:
     using AuthenticatorTransportService::Observer::weakPtrFactory;
     using WeakValueType = AuthenticatorTransportService::Observer::WeakValueType;
 
+    const static size_t maxTransportNumber;
+
     AuthenticatorManager();
     virtual ~AuthenticatorManager() = default;
 
@@ -83,15 +85,16 @@ private:
     // Overriden by MockAuthenticatorManager.
     virtual UniqueRef<AuthenticatorTransportService> createService(WebCore::AuthenticatorTransport, AuthenticatorTransportService::Observer&) const;
     // Overriden to return every exception for tests to confirm.
-    virtual void respondReceivedInternal(Respond&&);
+    virtual void respondReceivedInternal(Respond&&) { }
+    virtual void filterTransports(TransportSet&) const;
 
     void startDiscovery(const TransportSet&);
-    void initTimeOutTimer(const Optional<unsigned>& timeOutInMs);
+    void initTimeOutTimer();
     void timeOutTimerFired();
     void runPanel();
-    void startRequest();
     void resetState();
     void restartDiscovery();
+    TransportSet getTransports() const;
 
     // Request: We only allow one request per time. A new request will cancel any pending ones.
     WebAuthenticationRequestData m_pendingRequestData;
index 2150344..e2d2c8f 100644 (file)
@@ -41,6 +41,8 @@ public:
     explicit NfcService(Observer&);
     ~NfcService();
 
+    static bool isAvailable();
+
     // For NfcConnection.
     void didConnectTag();
     void didDetectMultipleTags() const;
index 58327b2..653a984 100644 (file)
@@ -49,6 +49,15 @@ NfcService::~NfcService()
 {
 }
 
+bool NfcService::isAvailable()
+{
+#if HAVE(NEAR_FIELD)
+    return [[getNFHardwareManagerClass() sharedHardwareManager] areFeaturesSupported:NFFeatureReaderMode outError:nil];
+#else
+    return false;
+#endif
+}
+
 void NfcService::didConnectTag()
 {
 #if HAVE(NEAR_FIELD)
@@ -88,7 +97,7 @@ void NfcService::restartDiscoveryInternal()
 void NfcService::platformStartDiscovery()
 {
 #if HAVE(NEAR_FIELD)
-    if (![[getNFHardwareManagerClass() sharedHardwareManager] areFeaturesSupported:NFFeatureReaderMode outError:nil])
+    if (!isAvailable())
         return;
 
     // Will be executed in a different thread.
index f8ff2d6..2c4f9dd 100644 (file)
@@ -50,6 +50,14 @@ void MockAuthenticatorManager::respondReceivedInternal(Respond&& respond)
     requestTimeOutTimer().stop();
 }
 
+void MockAuthenticatorManager::filterTransports(TransportSet& transports) const
+{
+    if (!m_testConfiguration.nfc)
+        transports.remove(AuthenticatorTransport::Nfc);
+    if (!m_testConfiguration.local)
+        transports.remove(AuthenticatorTransport::Internal);
+}
+
 } // namespace WebKit
 
 #endif // ENABLE(WEB_AUTHN)
index a8eb80a..315f251 100644 (file)
@@ -42,6 +42,7 @@ public:
 private:
     UniqueRef<AuthenticatorTransportService> createService(WebCore::AuthenticatorTransport, AuthenticatorTransportService::Observer&) const final;
     void respondReceivedInternal(Respond&&) final;
+    void filterTransports(TransportSet&) const;
 
     WebCore::MockWebAuthenticationConfiguration m_testConfiguration;
 };
index dffcde8..a96fce4 100644 (file)
@@ -1,3 +1,20 @@
+2019-10-29  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Add more information to _WKWebAuthenticationPanel
+        https://bugs.webkit.org/show_bug.cgi?id=202561
+        <rdar://problem/55973910>
+
+        Reviewed by Youenn Fablet and Brent Fulgham.
+
+        Adds new API tests.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
+        (-[TestWebAuthenticationPanelUIDelegate _webView:runWebAuthenticationPanel:initiatedByFrame:completionHandler:]):
+        (-[TestWebAuthenticationPanelUIDelegate panel]):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html: Added.
+
 2019-10-07  Jer Noble  <jer.noble@apple.com>
 
         Implement the Remote Playback API.
index ed9c3f9..3597dc8 100644 (file)
                579651E7216BFDED006EBFE5 /* FidoHidMessageTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 579651E6216BFD53006EBFE5 /* FidoHidMessageTest.cpp */; };
                5797FE311EB15A6800B2F4A0 /* NavigationClientDefaultCrypto.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5797FE2F1EB15A5F00B2F4A0 /* NavigationClientDefaultCrypto.cpp */; };
                5797FE331EB15AB100B2F4A0 /* navigation-client-default-crypto.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5797FE321EB15A8900B2F4A0 /* navigation-client-default-crypto.html */; };
-               5798337C235EB689008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5798337B235EB65C008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html */; };
+               5798337E236019A4008E5547 /* web-authentication-make-credential-hid.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5798337D2360196D008E5547 /* web-authentication-make-credential-hid.html */; };
+               579833922368FA37008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5798337B235EB65C008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html */; };
                57A79857224AB34E00A7F6F1 /* WebCryptoMasterKey.mm in Sources */ = {isa = PBXBuildFile; fileRef = 57A79856224AB34E00A7F6F1 /* WebCryptoMasterKey.mm */; };
                57C3FA661F7C248F009D4B80 /* WeakPtr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB9BC371A67482300FE5678 /* WeakPtr.cpp */; };
                57C6244E2346BCFA00383FE7 /* _WKWebAuthenticationPanel.mm in Sources */ = {isa = PBXBuildFile; fileRef = 57C6244D2346BCFA00383FE7 /* _WKWebAuthenticationPanel.mm */; };
                                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 */,
+                               579833922368FA37008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html in Copy Resources */,
                                5798337C235EB689008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.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 */,
+                               5798337E236019A4008E5547 /* web-authentication-make-credential-hid.html in Copy Resources */,
                                1C2B81861C89259D00A5529F /* webfont.html in Copy Resources */,
                                51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */,
                                51714EB51CF8C78C004723C4 /* WebProcessKillIDBCleanup-2.html in Copy Resources */,
                5797FE2F1EB15A5F00B2F4A0 /* NavigationClientDefaultCrypto.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NavigationClientDefaultCrypto.cpp; sourceTree = "<group>"; };
                5797FE321EB15A8900B2F4A0 /* navigation-client-default-crypto.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "navigation-client-default-crypto.html"; sourceTree = "<group>"; };
                5798337B235EB65C008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-get-assertion-nfc-multiple-tags.html"; sourceTree = "<group>"; };
+               5798337D2360196D008E5547 /* web-authentication-make-credential-hid.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-make-credential-hid.html"; sourceTree = "<group>"; };
                5798E2AF1CAF5C2800C5CBA0 /* ProvisionalURLNotChange.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProvisionalURLNotChange.mm; sourceTree = "<group>"; };
                57A79856224AB34E00A7F6F1 /* WebCryptoMasterKey.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WebCryptoMasterKey.mm; sourceTree = "<group>"; };
                57C6244D2346BCFA00383FE7 /* _WKWebAuthenticationPanel.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKWebAuthenticationPanel.mm; sourceTree = "<group>"; };
                                57663DE9234EA60B00E85E09 /* web-authentication-get-assertion-nfc.html */,
                                577454D12359BAD5008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html */,
                                57C6244F2346C1EC00383FE7 /* web-authentication-get-assertion.html */,
+                               5798337D2360196D008E5547 /* web-authentication-make-credential-hid.html */,
                                51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
                                51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
                                5120C83B1E674E350025B250 /* WebsiteDataStoreCustomPaths.html */,
index 7016233..41ea4ce 100644 (file)
@@ -44,7 +44,6 @@ static bool webAuthenticationPanelFailed = false;
 static bool webAuthenticationPanelSucceded = false;
 static bool webAuthenticationPanelUpdateMultipleNFCTagsPresent = false;
 static bool webAuthenticationPanelUpdateNoCredentialsFound = false;
-static RetainPtr<_WKWebAuthenticationPanel> gPanel;
 
 @interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
 @end
@@ -97,6 +96,7 @@ static RetainPtr<_WKWebAuthenticationPanel> gPanel;
     RetainPtr<NSObject<_WKWebAuthenticationPanelDelegate>> _delegate;
     BlockPtr<void(_WKWebAuthenticationPanelResult)> _callback;
     RetainPtr<WKFrameInfo> _frameInfo;
+    RetainPtr<_WKWebAuthenticationPanel> _panel;
 }
 
 - (instancetype)init
@@ -121,10 +121,8 @@ static RetainPtr<_WKWebAuthenticationPanel> gPanel;
             _delegate = adoptNS([[TestWebAuthenticationPanelFakeDelegate alloc] init]);
     }
     ASSERT_NE(panel, nil);
-    gPanel = panel;
-    [gPanel setDelegate:_delegate.get()];
-
-    EXPECT_TRUE([[gPanel relyingPartyID] isEqual:@""] || [[gPanel relyingPartyID] isEqual:@"localhost"]);
+    _panel = panel;
+    [_panel setDelegate:_delegate.get()];
 
     if (_isRacy) {
         if (!_callback) {
@@ -141,6 +139,11 @@ static RetainPtr<_WKWebAuthenticationPanel> gPanel;
     return _frameInfo.get();
 }
 
+- (_WKWebAuthenticationPanel *)panel
+{
+    return _panel.get();
+}
+
 @end
 
 namespace TestWebKitAPI {
@@ -186,7 +189,27 @@ static void reset()
     webAuthenticationPanelRan = false;
     webAuthenticationPanelFailed = false;
     webAuthenticationPanelSucceded = false;
-    gPanel = nullptr;
+}
+
+static void checkPanel(_WKWebAuthenticationPanel *panel, NSString *relyingPartyID, NSArray *transports, _WKWebAuthenticationType type)
+{
+    EXPECT_WK_STREQ(panel.relyingPartyID, relyingPartyID);
+
+    // Brute force given the maximum size of the array is 4.
+    auto *theTransports = panel.transports;
+    EXPECT_EQ(theTransports.count, transports.count);
+    size_t count = 0;
+    for (NSNumber *transport : transports) {
+        for (NSNumber *theTransport : theTransports) {
+            if (transport == theTransport) {
+                count++;
+                break;
+            }
+        }
+    }
+    EXPECT_EQ(count, transports.count);
+
+    EXPECT_EQ(panel.type, type);
 }
 
 static void checkFrameInfo(WKFrameInfo *frame, bool isMainFrame, NSString *url, NSString *protocol, NSString *host, int port, WKWebView *webView)
@@ -244,7 +267,7 @@ TEST(WebAuthenticationPanel, PanelTimeout)
     Util::run(&webAuthenticationPanelFailed);
 }
 
-TEST(WebAuthenticationPanel, PanelHidSuccess)
+TEST(WebAuthenticationPanel, PanelHidSuccess1)
 {
     reset();
     RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
@@ -262,6 +285,27 @@ TEST(WebAuthenticationPanel, PanelHidSuccess)
 
     // A bit of extra checks.
     checkFrameInfo([delegate frame], true, [testURL absoluteString], @"file", @"", 0, webView.get());
+    checkPanel([delegate panel], @"", @[adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportUSB]).get()], _WKWebAuthenticationTypeGet);
+}
+
+TEST(WebAuthenticationPanel, PanelHidSuccess2)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-make-credential-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]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    Util::run(&webAuthenticationPanelRan);
+    Util::run(&webAuthenticationPanelSucceded);
+
+    // A bit of extra checks.
+    checkPanel([delegate panel], @"", @[adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportUSB]).get()], _WKWebAuthenticationTypeCreate);
 }
 
 #if HAVE(NEAR_FIELD)
@@ -286,6 +330,9 @@ TEST(WebAuthenticationPanel, PanelRacy1)
     Util::run(&webAuthenticationPanelRan);
     [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     [webView waitForMessage:@"Succeeded!"];
+
+    // A bit of extra checks.
+    checkPanel([delegate panel], @"", @[adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportNFC]).get()], _WKWebAuthenticationTypeGet);
 }
 
 // Unlike the previous one, this one focuses on the order of the delegate callbacks.
@@ -448,6 +495,7 @@ TEST(WebAuthenticationPanel, SubFrameChangeLocationHidCancel)
 
     // A bit of extra checks.
     checkFrameInfo([delegate frame], false, (id)makeString(url, "/iFrame.html"), @"http", @"localhost", port, webView.get());
+    checkPanel([delegate panel], @"localhost", @[adoptNS([[NSNumber alloc] initWithInt:_WKWebAuthenticationTransportUSB]).get()], _WKWebAuthenticationTypeGet);
 }
 
 TEST(WebAuthenticationPanel, SubFrameDestructionHidCancel)
@@ -501,7 +549,7 @@ TEST(WebAuthenticationPanel, PanelHidCancel)
 
     [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
     Util::run(&webAuthenticationPanelRan);
-    [gPanel cancel];
+    [[delegate panel] cancel];
     [webView waitForMessage:@"Operation timed out."];
     EXPECT_FALSE(webAuthenticationPanelFailed);
     EXPECT_FALSE(webAuthenticationPanelSucceded);
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html
new file mode 100644 (file)
index 0000000..5b6aebe
--- /dev/null
@@ -0,0 +1,47 @@
+<input type="text" id="input">
+<script>
+    const testCreationMessageBase64 =
+        "AKMBZnBhY2tlZAJYxEbMf7lnnVWy25CS4cjZ5eHQK3WA8LSBLHcJYuHkj1rYQQAA" +
+        "AE74oBHzjApNFYAGFxEfntx9AEAoCK3O6P5OyXN6V/f+9nAga0NA2Cgp4V3mgSJ5" +
+        "jOHLMDrmxp/S0rbD+aihru1C0aAN3BkiM6GNy5nSlDVqOgTgpQECAyYgASFYIEFb" +
+        "he3RkNud6sgyraBGjlh1pzTlCZehQlL/b18HZ6WGIlggJgfUd/en9p5AIqMQbUni" +
+        "nEeXdFLkvW0/zV5BpEjjNxADo2NhbGcmY3NpZ1hHMEUCIQDKg+ZBmEBtf0lWq4Re" +
+        "dH4/i/LOYqOR4uR2NAj2zQmw9QIgbTXb4hvFbj4T27bv/rGrc+y+0puoYOBkBk9P" +
+        "mCewWlNjeDVjgVkCwjCCAr4wggGmoAMCAQICBHSG/cIwDQYJKoZIhvcNAQELBQAw" +
+        "LjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEw" +
+        "IBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG8xCzAJBgNVBAYTAlNF" +
+        "MRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0" +
+        "ZXN0YXRpb24xKDAmBgNVBAMMH1l1YmljbyBVMkYgRUUgU2VyaWFsIDE5NTUwMDM4" +
+        "NDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASVXfOt9yR9MXXv/ZzE8xpOh466" +
+        "4YEJVmFQ+ziLLl9lJ79XQJqlgaUNCsUvGERcChNUihNTyKTlmnBOUjvATevto2ww" +
+        "ajAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMTATBgsrBgEEAYLl" +
+        "HAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBD4oBHzjApNFYAGFxEfntx9MAwG" +
+        "A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBADFcSIDmmlJ+OGaJvWn9Cqhv" +
+        "SeueToVFQVVvqtALOgCKHdwB+Wx29mg2GpHiMsgQp5xjB0ybbnpG6x212FxESJ+G" +
+        "inZD0ipchi7APwPlhIvjgH16zVX44a4e4hOsc6tLIOP71SaMsHuHgCcdH0vg5d2s" +
+        "c006WJe9TXO6fzV+ogjJnYpNKQLmCXoAXE3JBNwKGBIOCvfQDPyWmiiG5bGxYfPt" +
+        "y8Z3pnjX+1MDnM2hhr40ulMxlSNDnX/ZSnDyMGIbk8TOQmjTF02UO8auP8k3wt5D" +
+        "1rROIRU9+FCSX5WQYi68RuDrGMZB8P5+byoJqbKQdxn2LmE1oZAyohPAmLcoPO4=";
+    if (window.internals) {
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { payloadBase64: [testCreationMessageBase64] } });
+        internals.withUserGesture(() => { input.focus(); });
+    }
+
+    const options = {
+        publicKey: {
+            rp: {
+                name: "localhost",
+            },
+            user: {
+                name: "John Appleseed",
+                id: new Uint8Array(16),
+                displayName: "Appleseed",
+            },
+            challenge: new Uint8Array(16),
+            pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+            timeout: 100
+        }
+    };
+
+    navigator.credentials.create(options);
+</script>