[WebAuthN] Support CTAP HID authenticators on macOS
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Nov 2018 06:54:32 +0000 (06:54 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Nov 2018 06:54:32 +0000 (06:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=188623
<rdar://problem/43353777>

Reviewed by Brent Fulgham and Chris Dumez.

Source/WebCore:

This patch removes AuthenticatorCoordinatorClient::~AuthenticatorCoordinatorClient to ignore
any incompleted CompletionHandlers as calling them in destructors could cause unexpected cyclic
dependency. Also, it adds a hack to temporarily deal with nullable userhandle.

Tests: http/wpt/webauthn/ctap-hid-failure.https.html
       http/wpt/webauthn/ctap-hid-success.https.html
       http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html
       http/wpt/webauthn/public-key-credential-create-failure-hid.https.html
       http/wpt/webauthn/public-key-credential-create-success-hid.https.html
       http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html
       http/wpt/webauthn/public-key-credential-get-failure-hid.https.html
       http/wpt/webauthn/public-key-credential-get-success-hid.https.html

* Modules/webauthn/AuthenticatorCoordinatorClient.cpp:
(WebCore::AuthenticatorCoordinatorClient::~AuthenticatorCoordinatorClient): Deleted.
* Modules/webauthn/AuthenticatorCoordinatorClient.h:
* Modules/webauthn/PublicKeyCredentialCreationOptions.h:
* Modules/webauthn/fido/DeviceResponseConverter.cpp:
(fido::readCTAPGetAssertionResponse):
* Modules/webauthn/fido/FidoConstants.h:

Source/WebKit:

This patch introduces a primitive support of CTAP HID authenticators for WebAuthN in macOS.
It involves low level HID device management&communication, high level CTAP HID authenticator
management&communication, and mock testing. The above three aspects will be covered in details:
1) Low level HID device management&communication: HidService&HidConnection
It relies on IOHIDManager to discover appropriate hid devices by passing a matching dictionary:
{ PrimaryUsagePage: 0xf1d0, PrimaryUsage: 0x01}. For communication, it utilizes HID reports.
To send a report, it calls IOHIDDeviceSetReport since the async version is not implemented.
To recieve a report, it calls IOHIDDeviceRegisterInputReportCallback to asynchronously wait
for incoming reports.
Here is the corresponding reference:
https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html#//apple_ref/doc/uid/TP40000970-CH214-SW2
2) High level CTAP HID authenticator management&communication: HidService&CtapHidDriver
Whenever an appropriate hid device is discovered by IOHIDManager, an AuthenticatorGetInfo command
is sent to the device to determine properties of the authenticator, says, which version of protocol
it supports, i.e. CTAP or U2F. So far, we only support CTAP authenticators. Once the authenticator
is determined to support CTAP, we then instantiate CtapHidAuthenticator which will then take care
of even higher level WebAuthN requests&responses.
Binaries are constructed and packaged according to the CTAP HID porotocol. CtapHidDriver takes care
of concurrency and channels, i.e. allocating channel and establishing the actual request/response
transaction. At the meantime, CtapHidDriver::Worker is then responsible for each single transaction.
Here is the corresponding reference:
https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#usb.
3) Mock Testing: MockHidService & MockHidConnection
A CTAP hid authenticator is simulated within MockHidConnection with options of specifying specific
error scenarios and of course could take care of successful cases. Four stages are presented in the
simulated authenticator to reflect: a) allocating channel for AuthenticatorGetInfo, b) sending
AuthenticatorGetInfo, c) allocating channel for actual request, and d) sending the actual request.

Besides implementing the above, it also does a few other things:
1) Make AuthenticatorManager::clearState asynchronous to avoid cyclic dependency:
Authenticator::returnResponse => AuthenticatorManager::respondReceived => AuthenticatorManager::clearState
=> Authenticator::~Authenticator.
2) Reorganize unified build sources to make it clear that which files are .mm and which are .cpp.
3) Import LocalAuthentication.framework in LocalAuthenticationSoftLink instead of being scattered.

* Sources.txt:
* SourcesCocoa.txt:
* Shared/CoordinatedGraphics/threadedcompositor/ThreadedCompositor.h:
* UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
(WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):
* UIProcess/WebAuthentication/AuthenticatorManager.cpp:
(WebKit::AuthenticatorManagerInternal::collectTransports):
(WebKit::AuthenticatorManager::clearStateAsync):
(WebKit::AuthenticatorManager::respondReceived):
(WebKit::AuthenticatorManager::initTimeOutTimer):
* UIProcess/WebAuthentication/AuthenticatorManager.h:
* UIProcess/WebAuthentication/AuthenticatorTransportService.cpp:
(WebKit::AuthenticatorTransportService::create):
(WebKit::AuthenticatorTransportService::createMock):
(WebKit::AuthenticatorTransportService::startDiscovery):
(WebKit::AuthenticatorTransportService::startDiscovery const): Deleted.
* UIProcess/WebAuthentication/AuthenticatorTransportService.h:
* UIProcess/WebAuthentication/Cocoa/HidConnection.h: Copied from Source/WebKit/UIProcess/WebAuthentication/AuthenticatorTransportService.h.
* UIProcess/WebAuthentication/Cocoa/HidConnection.mm: Added.
(WebKit::reportReceived):
(WebKit::HidConnection::HidConnection):
(WebKit::HidConnection::~HidConnection):
(WebKit::HidConnection::initialize):
(WebKit::HidConnection::terminate):
(WebKit::HidConnection::send):
(WebKit::HidConnection::registerDataReceivedCallback):
(WebKit::HidConnection::unregisterDataReceivedCallback):
(WebKit::HidConnection::receiveReport):
(WebKit::HidConnection::consumeReports):
(WebKit::HidConnection::registerDataReceivedCallbackInternal):
* UIProcess/WebAuthentication/Cocoa/HidService.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.h.
* UIProcess/WebAuthentication/Cocoa/HidService.mm: Added.
(WebKit::deviceAddedCallback):
(WebKit::deviceRemovedCallback):
(WebKit::HidService::HidService):
(WebKit::HidService::~HidService):
(WebKit::HidService::startDiscoveryInternal):
(WebKit::HidService::platformStartDiscovery):
(WebKit::HidService::createHidConnection const):
(WebKit::HidService::deviceAdded):
(WebKit::HidService::continueAddDeviceAfterGetInfo):
* UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h:
* UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:
* UIProcess/WebAuthentication/Cocoa/LocalService.h:
* UIProcess/WebAuthentication/Cocoa/LocalService.mm:
(WebKit::LocalService::startDiscoveryInternal):
(WebKit::LocalService::startDiscoveryInternal const): Deleted.
* UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp:
(WebKit::MockAuthenticatorManager::respondReceivedInternal):
* UIProcess/WebAuthentication/Mock/MockHidConnection.cpp: Added.
(WebKit::MockHidConnection::MockHidConnection):
(WebKit::MockHidConnection::initialize):
(WebKit::MockHidConnection::terminate):
(WebKit::MockHidConnection::send):
(WebKit::MockHidConnection::registerDataReceivedCallbackInternal):
(WebKit::MockHidConnection::assembleRequest):
(WebKit::MockHidConnection::parseRequest):
(WebKit::MockHidConnection::feedReports):
(WebKit::MockHidConnection::stagesMatch const):
(WebKit::MockHidConnection::shouldContinueFeedReports):
(WebKit::MockHidConnection::continueFeedReports):
* UIProcess/WebAuthentication/Mock/MockHidConnection.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h.
* UIProcess/WebAuthentication/Mock/MockHidService.cpp: Copied from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalService.cpp.
(WebKit::MockHidService::MockHidService):
(WebKit::MockHidService::platformStartDiscovery):
(WebKit::MockHidService::createHidConnection const):
* UIProcess/WebAuthentication/Mock/MockHidService.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h.
* UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
* UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
* UIProcess/WebAuthentication/Mock/MockLocalService.mm: Renamed from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalService.cpp.
(WebKit::MockLocalService::MockLocalService):
(WebKit::MockLocalService::platformStartDiscovery const):
(WebKit::MockLocalService::createLocalConnection const):
* UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:
* UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp: Added.
(WebKit::CtapHidAuthenticator::CtapHidAuthenticator):
(WebKit::CtapHidAuthenticator::makeCredential):
(WebKit::CtapHidAuthenticator::continueMakeCredentialAfterResponseReceived const):
(WebKit::CtapHidAuthenticator::getAssertion):
(WebKit::CtapHidAuthenticator::continueGetAssertionAfterResponseReceived const):
* UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.h.
* UIProcess/WebAuthentication/fido/CtapHidDriver.cpp: Added.
(WebKit::CtapHidDriver::Worker::Worker):
(WebKit::CtapHidDriver::Worker::~Worker):
(WebKit::CtapHidDriver::Worker::transact):
(WebKit::CtapHidDriver::Worker::write):
(WebKit::CtapHidDriver::Worker::read):
(WebKit::CtapHidDriver::Worker::returnMessage):
(WebKit::CtapHidDriver::CtapHidDriver):
(WebKit::CtapHidDriver::transact):
(WebKit::CtapHidDriver::continueAfterChannelAllocated):
(WebKit::CtapHidDriver::continueAfterResponseReceived):
(WebKit::CtapHidDriver::returnResponse):
* UIProcess/WebAuthentication/fido/CtapHidDriver.h: Added.
* UIProcess/mac/WebDataListSuggestionsDropdownMac.mm:
* WebProcess/WebPage/CoordinatedGraphics/ThreadedCoordinatedLayerTreeHost.h:
* WebKit.xcodeproj/project.pbxproj:

Tools:

This patch adds support for the mock testing and entitlements to allow minibrowser to talk
to hid devices.

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

LayoutTests:

* http/wpt/webauthn/ctap-hid-failure.https-expected.txt: Added.
* http/wpt/webauthn/ctap-hid-failure.https.html: Added.
* http/wpt/webauthn/ctap-hid-success.https-expected.txt: Added.
* http/wpt/webauthn/ctap-hid-success.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-failure-hid.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-success-hid.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-success-hid.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-failure-hid.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-success-hid.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-success-hid.https.html: Added.
* http/wpt/webauthn/resources/util.js:
* platform/ios-wk2/TestExpectations:

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

60 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/webauthn/ctap-hid-failure.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/ctap-hid-success.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/ctap-hid-success.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-hid.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https.html [new file with mode: 0644]
LayoutTests/http/wpt/webauthn/resources/util.js
LayoutTests/platform/ios-wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/webauthn/AuthenticatorCoordinatorClient.cpp
Source/WebCore/Modules/webauthn/AuthenticatorCoordinatorClient.h
Source/WebCore/Modules/webauthn/PublicKeyCredentialCreationOptions.h
Source/WebCore/Modules/webauthn/fido/DeviceResponseConverter.cpp
Source/WebCore/Modules/webauthn/fido/FidoConstants.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/CoordinatedGraphics/threadedcompositor/ThreadedCompositor.h
Source/WebKit/Sources.txt
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreRef.cpp
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorTransportService.cpp
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorTransportService.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidConnection.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidConnection.mm [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidService.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidService.mm [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm
Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.mm
Source/WebKit/UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp
Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidService.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidService.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h
Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalService.mm [moved from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalService.cpp with 95% similarity]
Source/WebKit/UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h
Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidDriver.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidDriver.h [new file with mode: 0644]
Source/WebKit/UIProcess/mac/WebDataListSuggestionsDropdownMac.mm
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/ThreadedCoordinatedLayerTreeHost.h
Tools/ChangeLog
Tools/MiniBrowser/MiniBrowser.entitlements
Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

index 96c3d3d..86c84c2 100644 (file)
@@ -1,3 +1,30 @@
+2018-11-13  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Support CTAP HID authenticators on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=188623
+        <rdar://problem/43353777>
+
+        Reviewed by Brent Fulgham and Chris Dumez.
+
+        * http/wpt/webauthn/ctap-hid-failure.https-expected.txt: Added.
+        * http/wpt/webauthn/ctap-hid-failure.https.html: Added.
+        * http/wpt/webauthn/ctap-hid-success.https-expected.txt: Added.
+        * http/wpt/webauthn/ctap-hid-success.https.html: Added.
+        * http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https-expected.txt: Added.
+        * http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html: Added.
+        * http/wpt/webauthn/public-key-credential-create-failure-hid.https-expected.txt: Added.
+        * http/wpt/webauthn/public-key-credential-create-failure-hid.https.html: Added.
+        * http/wpt/webauthn/public-key-credential-create-success-hid.https-expected.txt: Added.
+        * http/wpt/webauthn/public-key-credential-create-success-hid.https.html: Added.
+        * http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt: Added.
+        * http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html: Added.
+        * http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt: Added.
+        * http/wpt/webauthn/public-key-credential-get-failure-hid.https.html: Added.
+        * http/wpt/webauthn/public-key-credential-get-success-hid.https-expected.txt: Added.
+        * http/wpt/webauthn/public-key-credential-get-success-hid.https.html: Added.
+        * http/wpt/webauthn/resources/util.js:
+        * platform/ios-wk2/TestExpectations:
+
 2018-11-13  Timothy Hatcher  <timothy@apple.com>
 
         Use a light scrollbar for transparent web views in dark mode.
diff --git a/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https-expected.txt b/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https-expected.txt
new file mode 100644 (file)
index 0000000..f7adb82
--- /dev/null
@@ -0,0 +1,9 @@
+
+PASS CTAP HID with init sub stage data not sent error in a mock hid authenticator. 
+PASS CTAP HID with init sub stage empty report error in a mock hid authenticator. 
+PASS CTAP HID with init sub stage wrong channel id error in a mock hid authenticator. 
+PASS CTAP HID with msg sub stage data not sent error in a mock hid authenticator. 
+PASS CTAP HID with msg sub stage empty report error in a mock hid authenticator. 
+PASS CTAP HID with msg sub stage wrong channel id error in a mock hid authenticator. 
+PASS CTAP HID with request::msg stage wrong channel id error in a mock hid authenticator. 
+
diff --git a/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html b/LayoutTests/http/wpt/webauthn/ctap-hid-failure.https.html
new file mode 100644 (file)
index 0000000..e535d8b
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<title>Web Authentication API: CTAP HID failure cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+    const defaultOptions = {
+        publicKey: {
+            rp: {
+                name: "example.com"
+            },
+            user: {
+                name: "John Appleseed",
+                id: asciiToUint8Array("123456"),
+                displayName: "John",
+            },
+            challenge: asciiToUint8Array("123456"),
+            pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+            timeout: 10
+        }
+    };
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "data-not-sent" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(defaultOptions), "Operation timed out.");
+    }, "CTAP HID with init sub stage data not sent error in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "empty-report" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(defaultOptions), "Operation timed out.");
+    }, "CTAP HID with init sub stage empty report error in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "wrong-channel-id" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(defaultOptions), "Operation timed out.");
+    }, "CTAP HID with init sub stage wrong channel id error in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "msg", error: "data-not-sent" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(defaultOptions), "Operation timed out.");
+    }, "CTAP HID with msg sub stage data not sent error in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "msg", error: "empty-report" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(defaultOptions), "Operation timed out.");
+    }, "CTAP HID with msg sub stage empty report error in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "msg", error: "wrong-channel-id" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(defaultOptions), "Operation timed out.");
+    }, "CTAP HID with msg sub stage wrong channel id error in a mock hid authenticator.");
+
+    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");
+    }, "CTAP HID with request::msg stage wrong channel id error in a mock hid authenticator.");
+</script>
diff --git a/LayoutTests/http/wpt/webauthn/ctap-hid-success.https-expected.txt b/LayoutTests/http/wpt/webauthn/ctap-hid-success.https-expected.txt
new file mode 100644 (file)
index 0000000..e2a4533
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS CTAP HID with keep alive message in a mock hid authenticator. 
+PASS CTAP HID with fast data arrival in a mock hid authenticator. 
+PASS CTAP HID with continue after empty report in a mock hid authenticator. 
+PASS CTAP HID with continue after wrong channel id in a mock hid authenticator. 
+
diff --git a/LayoutTests/http/wpt/webauthn/ctap-hid-success.https.html b/LayoutTests/http/wpt/webauthn/ctap-hid-success.https.html
new file mode 100644 (file)
index 0000000..179c069
--- /dev/null
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>Web Authentication API: CTAP HID success cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+    const defaultOptions = {
+        publicKey: {
+            rp: {
+                name: "example.com"
+            },
+            user: {
+                name: "John Appleseed",
+                id: asciiToUint8Array("123456"),
+                displayName: "John",
+            },
+            challenge: asciiToUint8Array("123456"),
+            pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+        }
+    };
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: testCreationMessageBase64, keepAlive: true } });
+        return navigator.credentials.create(defaultOptions).then(credential => {
+            assert_not_equals(credential, undefined);
+            assert_not_equals(credential, null);
+        });
+    }, "CTAP HID with keep alive message in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: testCreationMessageBase64, fastDataArrival: true } });
+        return navigator.credentials.create(defaultOptions).then(credential => {
+            assert_not_equals(credential, undefined);
+            assert_not_equals(credential, null);
+        });
+    }, "CTAP HID with fast data arrival in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "empty-report", payloadBase64: testCreationMessageBase64, continueAfterErrorData: true } });
+        return navigator.credentials.create(defaultOptions).then(credential => {
+            assert_not_equals(credential, undefined);
+            assert_not_equals(credential, null);
+        });
+    }, "CTAP HID with continue after empty report in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "wrong-channel-id", payloadBase64: testCreationMessageBase64, continueAfterErrorData: true } });
+        return navigator.credentials.create(defaultOptions).then(credential => {
+            assert_not_equals(credential, undefined);
+            assert_not_equals(credential, null);
+        });
+    }, "CTAP HID with continue after wrong channel id 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
new file mode 100644 (file)
index 0000000..c708757
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS PublicKeyCredential's [[create]] with malicious payload in a mock hid authenticator. 
+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. 
+
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
new file mode 100644 (file)
index 0000000..b9953f9
--- /dev/null
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<title>Web Authentication API: PublicKeyCredential's [[create]] failure cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+   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 }],
+                timeout: 10
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: testDummyMessagePayloadBase64 } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
+    }, "PublicKeyCredential's [[create]] with malicious payload 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 }],
+                authenticatorSelection: { requireResidentKey: true },
+                timeout: 10
+            }
+        };
+
+        if (window.testRunner)
+            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 unsupported 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 }],
+                authenticatorSelection: { userVerification: "required" },
+                timeout: 10
+            }
+        };
+
+        if (window.testRunner)
+            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 unsupported 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 }],
+                authenticatorSelection: { authenticatorAttachment: "cross-platform", requireResidentKey: true, userVerification: "required" },
+                timeout: 10
+            }
+        };
+
+        if (window.testRunner)
+            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.");
+</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
new file mode 100644 (file)
index 0000000..e5fc45c
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS PublicKeyCredential's [[create]] with timeout in a mock hid authenticator. 
+PASS PublicKeyCredential's [[create]] with malicious payload in a mock hid authenticator. 
+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 mixed options in a mock hid authenticator. 2 
+
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
new file mode 100644 (file)
index 0000000..2dde6d0
--- /dev/null
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<title>Web Authentication API: PublicKeyCredential's [[create]] failure cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+    // Default mock configuration. Tests need to override it if they need different configuration.
+    if (window.testRunner)
+        testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload" } });
+
+    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 }],
+                timeout: 10,
+                authenticatorSelection: { authenticatorAttachment: "platform" }
+            }
+        };
+
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
+    }, "PublicKeyCredential's [[create]] with timeout 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: testDummyMessagePayloadBase64 } });
+        return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Unknown internal error. Error code: -1");
+    }, "PublicKeyCredential's [[create]] with malicious payload 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 }],
+                authenticatorSelection: { requireResidentKey: true }
+            }
+        };
+
+        if (window.testRunner)
+            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 unsupported 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 }],
+                authenticatorSelection: { userVerification: "required" }
+            }
+        };
+
+        if (window.testRunner)
+            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 unsupported 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 }],
+                timeout: 10,
+                authenticatorSelection: { authenticatorAttachment: "platform", requireResidentKey: true, userVerification: "required" }
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ 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 }],
+                authenticatorSelection: { authenticatorAttachment: "cross-platform", requireResidentKey: true, userVerification: "required" }
+            }
+        };
+
+        if (window.testRunner)
+            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");
+</script>
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https-expected.txt b/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https-expected.txt
new file mode 100644 (file)
index 0000000..27fc4a3
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS PublicKeyCredential's [[create]] with minimum options in a mock local authenticator. 
+PASS PublicKeyCredential's [[create]] with authenticatorSelection { 'cross-platform' } in a mock local authenticator. 
+PASS PublicKeyCredential's [[create]] with requireResidentKey { false } in a mock local authenticator. 
+PASS PublicKeyCredential's [[create]] with userVerification { 'preferred' } in a mock local authenticator. 
+PASS PublicKeyCredential's [[create]] with userVerification { 'discouraged' } in a mock local authenticator. 
+PASS PublicKeyCredential's [[create]] with mixed options in a mock local authenticator. 
+
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-hid.https.html
new file mode 100644 (file)
index 0000000..a21ee3c
--- /dev/null
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<title>Web Authentication API: PublicKeyCredential's [[create]] success cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script src="./resources/cbor.js"></script>
+<script>
+    // Default mock configuration. Tests need to override if they need different configuration.
+    if (window.testRunner)
+        testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: testCreationMessageBase64 } });
+
+    function checkResult(credential)
+    {
+        // Check response
+        assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testHidCredentialIdBase64));
+        assert_equals(credential.type, 'public-key');
+        assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testHidCredentialIdBase64));
+        assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.create","challenge":"MTIzNDU2","origin":"https://localhost:9443","hashAlgorithm":"SHA-256"}');
+        assert_throws("NotSupportedError", () => { credential.getClientExtensionResults() });
+
+        // Check attestation
+        const attestationObject = CBOR.decode(credential.response.attestationObject);
+        assert_equals(attestationObject.fmt, "packed");
+        // Check authData
+        const authData = decodeAuthData(attestationObject.authData, false);
+        assert_equals(bytesToHexString(authData.rpIdHash), "46cc7fb9679d55b2db9092e1c8d9e5e1d02b7580f0b4812c770962e1e48f5ad8");
+        assert_equals(authData.flags, 65);
+        assert_equals(authData.counter, 78);
+        assert_equals(bytesToHexString(authData.aaguid), "f8a011f38c0a4d15800617111f9edc7d");
+        assert_array_equals(authData.credentialID, Base64URL.parse(testHidCredentialIdBase64));
+        // Check packed attestation
+        assert_equals(attestationObject.attStmt.alg, -7);
+        assert_true(checkPublicKey(authData.publicKey));
+        assert_equals(attestationObject.attStmt.x5c.length, 1);
+    }
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "localhost",
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: Base64URL.parse(testUserhandleBase64),
+                    displayName: "Appleseed",
+                },
+                challenge: Base64URL.parse("MTIzNDU2"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+            }
+        };
+
+        return navigator.credentials.create(options).then(credential => {
+            checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[create]] with minimum options in a mock local authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "localhost",
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: Base64URL.parse(testUserhandleBase64),
+                    displayName: "Appleseed",
+                },
+                challenge: Base64URL.parse("MTIzNDU2"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+                authenticatorSelection: { authenticatorAttachment: "cross-platform" }
+            }
+        };
+
+        return navigator.credentials.create(options).then(credential => {
+            checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[create]] with authenticatorSelection { 'cross-platform' } in a mock local authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "localhost",
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: Base64URL.parse(testUserhandleBase64),
+                    displayName: "Appleseed",
+                },
+                challenge: Base64URL.parse("MTIzNDU2"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+                authenticatorSelection: { requireResidentKey: false }
+            }
+        };
+
+        return navigator.credentials.create(options).then(credential => {
+            checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[create]] with requireResidentKey { false } in a mock local authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "localhost",
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: Base64URL.parse(testUserhandleBase64),
+                    displayName: "Appleseed",
+                },
+                challenge: Base64URL.parse("MTIzNDU2"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+                authenticatorSelection: { userVerification: "preferred" }
+            }
+        };
+
+        return navigator.credentials.create(options).then(credential => {
+            checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[create]] with userVerification { 'preferred' } in a mock local authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "localhost",
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: Base64URL.parse(testUserhandleBase64),
+                    displayName: "Appleseed",
+                },
+                challenge: Base64URL.parse("MTIzNDU2"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+                authenticatorSelection: { userVerification: "discouraged" }
+            }
+        };
+
+        return navigator.credentials.create(options).then(credential => {
+            checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[create]] with userVerification { 'discouraged' } in a mock local authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                rp: {
+                    name: "localhost",
+                },
+                user: {
+                    name: "John Appleseed",
+                    id: Base64URL.parse(testUserhandleBase64),
+                    displayName: "Appleseed",
+                },
+                challenge: Base64URL.parse("MTIzNDU2"),
+                pubKeyCredParams: [{ type: "public-key", alg: -7 }],
+                authenticatorSelection: { authenticatorAttachment: "cross-platform", requireResidentKey: false, userVerification: "preferred" }
+            }
+        };
+
+        return navigator.credentials.create(options).then(credential => {
+            checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[create]] with mixed options in a mock local authenticator.");
+</script>
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt b/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt
new file mode 100644 (file)
index 0000000..b462a44
--- /dev/null
@@ -0,0 +1,4 @@
+
+PASS PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator. 
+
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html
new file mode 100644 (file)
index 0000000..e3636c3
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Web Authentication API: PublicKeyCredential's [[get]] failure cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+    promise_test(function(t) {
+        const options = {
+            publicKey: {
+                challenge: asciiToUint8Array("123456"),
+                timeout: 10
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: testDummyMessagePayloadBase64 } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
+    }, "PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        const options = {
+            publicKey: {
+                challenge: asciiToUint8Array("123456"),
+                userVerification: "required",
+                timeout: 10
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "unsupported-options" } });
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
+    }, "PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.");
+</script>
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
new file mode 100644 (file)
index 0000000..a53dd15
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS PublicKeyCredential's [[get]] with timeout in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with unsupported options 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
new file mode 100644 (file)
index 0000000..ec6b0a8
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Web Authentication API: PublicKeyCredential's [[get]] failure cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+    // Default mock configuration. Tests need to override if they need different configuration.
+    if (window.testRunner)
+        testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload" } });
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                challenge: asciiToUint8Array("123456"),
+                allowCredentials: [
+                    { type: "public-key", id: Base64URL.parse(testHidCredentialIdBase64), transports: ["nfc"] },
+                    { type: "public-key", id: Base64URL.parse(testHidCredentialIdBase64), transports: ["ble"] },
+                    { type: "public-key", id: Base64URL.parse(testHidCredentialIdBase64), transports: ["internal"] }
+                ],
+                timeout: 10
+            }
+        };
+
+        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
+    }, "PublicKeyCredential's [[get]] with timeout in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        const options = {
+            publicKey: {
+                challenge: asciiToUint8Array("123456")
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: testDummyMessagePayloadBase64 } });
+        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: -1");
+    }, "PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator.");
+
+    promise_test(function(t) {
+        const options = {
+            publicKey: {
+                challenge: asciiToUint8Array("123456"),
+                userVerification: "required"
+            }
+        };
+
+        if (window.testRunner)
+            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "unsupported-options" } });
+        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: 43");
+    }, "PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.");
+</script>
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https-expected.txt b/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https-expected.txt
new file mode 100644 (file)
index 0000000..9058e84
--- /dev/null
@@ -0,0 +1,7 @@
+
+PASS PublicKeyCredential's [[get]] with minimum options in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with matched allow credentials in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with userVerification { preferred } in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with userVerification { discouraged } in a mock hid authenticator. 
+PASS PublicKeyCredential's [[get]] with mixed options in a mock hid authenticator. 
+
diff --git a/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https.html b/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-hid.https.html
new file mode 100644 (file)
index 0000000..7e56478
--- /dev/null
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<title>Web Authentication API: PublicKeyCredential's [[get]] success cases with a mock hid authenticator.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/util.js"></script>
+<script>
+    // Default mock configuration. Tests need to override if they need different configuration.
+    if (window.testRunner)
+        testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: testAssertionMessageBase64 } });
+
+    function checkResult(credential)
+    {
+        // Check respond
+        assert_array_equals(Base64URL.parse(credential.id), Base64URL.parse(testHidCredentialIdBase64));
+        assert_equals(credential.type, 'public-key');
+        assert_array_equals(new Uint8Array(credential.rawId), Base64URL.parse(testHidCredentialIdBase64));
+        assert_equals(bytesToASCIIString(credential.response.clientDataJSON), '{"type":"webauthn.get","challenge":"MTIzNDU2","origin":"https://localhost:9443","hashAlgorithm":"SHA-256"}');
+        assert_equals(bytesToHexString(credential.response.userHandle), "00");
+
+        // Check authData
+        const authData = decodeAuthData(new Uint8Array(credential.response.authenticatorData), false);
+        assert_equals(bytesToHexString(authData.rpIdHash), "46cc7fb9679d55b2db9092e1c8d9e5e1d02b7580f0b4812c770962e1e48f5ad8");
+        assert_equals(authData.flags, 1);
+        assert_equals(authData.counter, 80);
+    }
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                challenge: Base64URL.parse("MTIzNDU2")
+            }
+        };
+
+        return navigator.credentials.get(options).then(credential => {
+            return checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[get]] with minimum options in a mock hid authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                challenge: Base64URL.parse("MTIzNDU2"),
+                allowCredentials: [
+                    { type: "public-key", id: Base64URL.parse(testHidCredentialIdBase64), transports: ["usb"] }
+                ]
+            }
+        };
+
+        return navigator.credentials.get(options).then(credential => {
+            return checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[get]] with matched allow credentials in a mock hid authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                challenge: Base64URL.parse("MTIzNDU2"),
+                userVerification: "preferred"
+            }
+        };
+
+        return navigator.credentials.get(options).then(credential => {
+            return checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[get]] with userVerification { preferred } in a mock hid authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                challenge: Base64URL.parse("MTIzNDU2"),
+                userVerification: "discouraged"
+            }
+        };
+
+        return navigator.credentials.get(options).then(credential => {
+            return checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[get]] with userVerification { discouraged } in a mock hid authenticator.");
+
+    promise_test(t => {
+        const options = {
+            publicKey: {
+                challenge: Base64URL.parse("MTIzNDU2"),
+                allowCredentials: [
+                    { type: "public-key", id: Base64URL.parse(testHidCredentialIdBase64), transports: ["usb"] }
+                ],
+                userVerification: "preferred"
+            }
+        };
+
+        return navigator.credentials.get(options).then(credential => {
+            return checkResult(credential);
+        });
+    }, "PublicKeyCredential's [[get]] with mixed options in a mock hid authenticator.");
+</script>
index 1bdcf83..cd5980d 100644 (file)
@@ -33,6 +33,42 @@ const testAttestationIssuingCACertificateBase64 =
     "MAoGCCqGSM49BAMCA2kAMGYCMQC3M360LLtJS60Z9q3vVjJxMgMcFQ1roGTUcKqv" +
     "W+4hJ4CeJjySXTgq6IEHn/yWab4CMQCm5NnK6SOSK+AqWum9lL87W3E6AA1f2TvJ" +
     "/hgok/34jr93nhS87tOQNdxDS8zyiqw=";
+const testDummyMessagePayloadBase64 =
+    "/////wYAEQABAgMEBQYHAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+    "AAAAAAAAAAAAAAAAAAAAAAAAAAEQADoAAQIDBAUGBwECAwQAAAAAAAAAAAAAAAAA" +
+    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAgMEAAAAAAAAAAAAAAAA" +
+    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+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=";
+const testHidCredentialIdBase64 =
+    "KAitzuj-Tslzelf3_vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2w_mooa7tQtGg" +
+    "DdwZIjOhjcuZ0pQ1ajoE4A";
+const testAssertionMessageBase64 =
+    "AKMBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
+    "w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
+    "Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
+    "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
+    "QoJ1L7Fe64G9uBc=";
 
 const RESOURCES_DIR = "/WebKit/webauthn/resources/";
 
@@ -98,7 +134,7 @@ function hexStringToUint8Array(hexString)
     return arrayBuffer;
 }
 
-function decodeAuthData(authDataUint8Array)
+function decodeAuthData(authDataUint8Array, littleEndian = true)
 {
     let authDataObject = { };
     let pos = 0;
@@ -122,7 +158,10 @@ function decodeAuthData(authDataUint8Array)
     size = 4;
     if (pos + size > authDataUint8Array.byteLength)
         return { };
-    authDataObject.counter = new Uint32Array(authDataUint8Array.slice(pos, pos + size))[0];
+    if (littleEndian)
+        authDataObject.counter = new Uint32Array(authDataUint8Array.slice(pos, pos + size))[0];
+    else
+        authDataObject.counter = (authDataUint8Array[pos] << 24) + (authDataUint8Array[pos + 1] << 16) + (authDataUint8Array[pos + 2] << 8) + authDataUint8Array[pos + 3];
     pos = pos + size;
 
     if (pos == authDataUint8Array.byteLength)
@@ -139,8 +178,10 @@ function decodeAuthData(authDataUint8Array)
     size = 2;
     if (pos + size > authDataUint8Array.byteLength)
         return { };
-    // Little Endian
-    authDataObject.l = new Uint16Array(authDataUint8Array.slice(pos, pos + size))[0];
+    if (littleEndian)
+        authDataObject.l = new Uint16Array(authDataUint8Array.slice(pos, pos + size))[0];
+    else
+        authDataObject.l = (authDataUint8Array[pos] << 8) + authDataUint8Array[pos + 1];
     pos = pos + size;
 
     // Credential ID
index 7bf6600..3f81902 100644 (file)
@@ -1309,3 +1309,12 @@ webkit.org/b/190850 [ Debug ] platform/ios/ios/plugin/youtube-flash-plugin-ifram
 
 webkit.org/b/189641 [ Debug ] webgl/2.0.0/conformance/attribs/gl-vertexattribpointer.html [ Slow ]
 
+# Skip WebAuthN tests for hid authenticators
+http/wpt/webauthn/ctap-hid-failure.https.html [ Skip ]
+http/wpt/webauthn/ctap-hid-success.https.html [ Skip ]
+http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html [ Skip ]
+http/wpt/webauthn/public-key-credential-create-failure-hid.https.html [ Skip ]
+http/wpt/webauthn/public-key-credential-create-success-hid.https.html [ Skip ]
+http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html [ Skip ]
+http/wpt/webauthn/public-key-credential-get-failure-hid.https.html [ Skip ]
+http/wpt/webauthn/public-key-credential-get-success-hid.https.html [ Skip ]
\ No newline at end of file
index b807598..0efd0eb 100644 (file)
@@ -1,3 +1,32 @@
+2018-11-13  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Support CTAP HID authenticators on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=188623
+        <rdar://problem/43353777>
+
+        Reviewed by Brent Fulgham and Chris Dumez.
+
+        This patch removes AuthenticatorCoordinatorClient::~AuthenticatorCoordinatorClient to ignore
+        any incompleted CompletionHandlers as calling them in destructors could cause unexpected cyclic
+        dependency. Also, it adds a hack to temporarily deal with nullable userhandle.
+
+        Tests: http/wpt/webauthn/ctap-hid-failure.https.html
+               http/wpt/webauthn/ctap-hid-success.https.html
+               http/wpt/webauthn/public-key-credential-create-failure-hid-silent.https.html
+               http/wpt/webauthn/public-key-credential-create-failure-hid.https.html
+               http/wpt/webauthn/public-key-credential-create-success-hid.https.html
+               http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html
+               http/wpt/webauthn/public-key-credential-get-failure-hid.https.html
+               http/wpt/webauthn/public-key-credential-get-success-hid.https.html
+
+        * Modules/webauthn/AuthenticatorCoordinatorClient.cpp:
+        (WebCore::AuthenticatorCoordinatorClient::~AuthenticatorCoordinatorClient): Deleted.
+        * Modules/webauthn/AuthenticatorCoordinatorClient.h:
+        * Modules/webauthn/PublicKeyCredentialCreationOptions.h:
+        * Modules/webauthn/fido/DeviceResponseConverter.cpp:
+        (fido::readCTAPGetAssertionResponse):
+        * Modules/webauthn/fido/FidoConstants.h:
+
 2018-11-13  Ross Kirsling  <ross.kirsling@sony.com>
 
         [WebRTC] Provide default implementation of LibWebRTCProvider
index c0c80ca..3c658a3 100644 (file)
 
 namespace WebCore {
 
-AuthenticatorCoordinatorClient::~AuthenticatorCoordinatorClient()
-{
-    // Just to call handlers to avoid any assertion failures.
-    if (m_pendingCompletionHandler)
-        m_pendingCompletionHandler({ }, { NotAllowedError, "Operation timed out."_s });
-    for (auto itr = m_pendingQueryCompletionHandlers.begin(); itr !=  m_pendingQueryCompletionHandlers.end(); ++itr)
-        itr->value(false);
-}
-
 void AuthenticatorCoordinatorClient::requestReply(const WebCore::PublicKeyCredentialData& data, const WebCore::ExceptionData& exception)
 {
     m_pendingCompletionHandler(data, exception);
index eba3d91..162aaea 100644 (file)
@@ -48,7 +48,7 @@ class WEBCORE_EXPORT AuthenticatorCoordinatorClient : public CanMakeWeakPtr<Auth
     WTF_MAKE_NONCOPYABLE(AuthenticatorCoordinatorClient);
 public:
     AuthenticatorCoordinatorClient() = default;
-    virtual ~AuthenticatorCoordinatorClient();
+    virtual ~AuthenticatorCoordinatorClient() = default;
 
     // Senders.
     virtual void makeCredential(const Vector<uint8_t>& hash, const PublicKeyCredentialCreationOptions&, RequestCompletionHandler&&) = 0;
index ba68df9..31f3229 100644 (file)
@@ -66,7 +66,8 @@ struct PublicKeyCredentialCreationOptions {
     };
 
     struct AuthenticatorSelectionCriteria {
-        std::optional<AuthenticatorAttachment> authenticatorAttachment;
+        // FIXME(191522)
+        AuthenticatorAttachment authenticatorAttachment { AuthenticatorAttachment::CrossPlatform };
         bool requireResidentKey { false };
         UserVerificationRequirement userVerification { UserVerificationRequirement::Preferred };
 
@@ -116,7 +117,7 @@ std::optional<PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria
 {
     PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria result;
 
-    std::optional<std::optional<AuthenticatorAttachment>> authenticatorAttachment;
+    std::optional<AuthenticatorAttachment> authenticatorAttachment;
     decoder >> authenticatorAttachment;
     if (!authenticatorAttachment)
         return std::nullopt;
index 3f6a959..45365c9 100644 (file)
@@ -158,11 +158,10 @@ std::optional<PublicKeyCredentialData> readCTAPGetAssertionResponse(const Vector
         return std::nullopt;
     auto& signature = it->second.getByteString();
 
-    RefPtr<ArrayBuffer> userHandle;
-    {
-        it = responseMap.find(CBOR(4));
-        if (it == responseMap.end() || !it->second.isMap())
-            return std::nullopt;
+    // FIXME(191521): Properly handle null userHandle.
+    RefPtr<ArrayBuffer> userHandle = ArrayBuffer::create(1, 1);
+    it = responseMap.find(CBOR(4));
+    if (it != responseMap.end() && it->second.isMap()) {
         auto& user = it->second.getMap();
         auto itr = user.find(CBOR(kEntityIdMapKey));
         if (itr == user.end() || !itr->second.isByteString())
index 5cd7ae8..f8d3ea3 100644 (file)
@@ -159,12 +159,16 @@ const size_t kHidContinuationPacketHeader = 5;
 const size_t kHidMaxPacketSize = 64;
 const size_t kHidInitPacketDataSize = kHidMaxPacketSize - kHidInitPacketHeaderSize;
 const size_t kHidContinuationPacketDataSize = kHidMaxPacketSize - kHidContinuationPacketHeader;
+const size_t kHidInitResponseSize = 17;
 
 const uint8_t kHidMaxLockSeconds = 10;
 
 // Messages are limited to an initiation packet and 128 continuation packets.
 const size_t kHidMaxMessageSize = 7609;
 
+// CTAP/U2F devices only provide a single report so specify a report ID of 0 here.
+const uint8_t kHidReportId = 0x00;
+
 // Authenticator API commands supported by CTAP devices, as specified in
 // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api
 enum class CtapRequestCommand : uint8_t {
@@ -193,6 +197,11 @@ const char* publicKeyCredentialTypeToString(WebCore::PublicKeyCredentialType);
 const char kCtap2Version[] = "FIDO_2_0";
 const char kU2fVersion[] = "U2F_V2";
 
+// CTAPHID Usage Page and Usage
+// https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#hid-report-descriptor-and-device-discovery
+const uint32_t kCTAPHIDUsagePage = 0xF1D0;
+const uint32_t kCTAPHIDUsage = 0x01;
+
 } // namespace fido
 
 #endif // ENABLE(WEB_AUTHN)
index 569edc1..7176373 100644 (file)
@@ -1,3 +1,144 @@
+2018-11-13  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Support CTAP HID authenticators on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=188623
+        <rdar://problem/43353777>
+
+        Reviewed by Brent Fulgham and Chris Dumez.
+
+        This patch introduces a primitive support of CTAP HID authenticators for WebAuthN in macOS.
+        It involves low level HID device management&communication, high level CTAP HID authenticator
+        management&communication, and mock testing. The above three aspects will be covered in details:
+        1) Low level HID device management&communication: HidService&HidConnection
+        It relies on IOHIDManager to discover appropriate hid devices by passing a matching dictionary:
+        { PrimaryUsagePage: 0xf1d0, PrimaryUsage: 0x01}. For communication, it utilizes HID reports.
+        To send a report, it calls IOHIDDeviceSetReport since the async version is not implemented.
+        To recieve a report, it calls IOHIDDeviceRegisterInputReportCallback to asynchronously wait
+        for incoming reports.
+        Here is the corresponding reference:
+        https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/HID/new_api_10_5/tn2187.html#//apple_ref/doc/uid/TP40000970-CH214-SW2
+        2) High level CTAP HID authenticator management&communication: HidService&CtapHidDriver
+        Whenever an appropriate hid device is discovered by IOHIDManager, an AuthenticatorGetInfo command
+        is sent to the device to determine properties of the authenticator, says, which version of protocol
+        it supports, i.e. CTAP or U2F. So far, we only support CTAP authenticators. Once the authenticator
+        is determined to support CTAP, we then instantiate CtapHidAuthenticator which will then take care
+        of even higher level WebAuthN requests&responses.
+        Binaries are constructed and packaged according to the CTAP HID porotocol. CtapHidDriver takes care
+        of concurrency and channels, i.e. allocating channel and establishing the actual request/response
+        transaction. At the meantime, CtapHidDriver::Worker is then responsible for each single transaction.
+        Here is the corresponding reference:
+        https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#usb.
+        3) Mock Testing: MockHidService & MockHidConnection
+        A CTAP hid authenticator is simulated within MockHidConnection with options of specifying specific
+        error scenarios and of course could take care of successful cases. Four stages are presented in the
+        simulated authenticator to reflect: a) allocating channel for AuthenticatorGetInfo, b) sending
+        AuthenticatorGetInfo, c) allocating channel for actual request, and d) sending the actual request.
+
+        Besides implementing the above, it also does a few other things:
+        1) Make AuthenticatorManager::clearState asynchronous to avoid cyclic dependency:
+        Authenticator::returnResponse => AuthenticatorManager::respondReceived => AuthenticatorManager::clearState
+        => Authenticator::~Authenticator.
+        2) Reorganize unified build sources to make it clear that which files are .mm and which are .cpp.
+        3) Import LocalAuthentication.framework in LocalAuthenticationSoftLink instead of being scattered.
+
+        * Sources.txt:
+        * SourcesCocoa.txt:
+        * Shared/CoordinatedGraphics/threadedcompositor/ThreadedCompositor.h:
+        * UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
+        (WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):
+        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
+        (WebKit::AuthenticatorManagerInternal::collectTransports):
+        (WebKit::AuthenticatorManager::clearStateAsync):
+        (WebKit::AuthenticatorManager::respondReceived):
+        (WebKit::AuthenticatorManager::initTimeOutTimer):
+        * UIProcess/WebAuthentication/AuthenticatorManager.h:
+        * UIProcess/WebAuthentication/AuthenticatorTransportService.cpp:
+        (WebKit::AuthenticatorTransportService::create):
+        (WebKit::AuthenticatorTransportService::createMock):
+        (WebKit::AuthenticatorTransportService::startDiscovery):
+        (WebKit::AuthenticatorTransportService::startDiscovery const): Deleted.
+        * UIProcess/WebAuthentication/AuthenticatorTransportService.h:
+        * UIProcess/WebAuthentication/Cocoa/HidConnection.h: Copied from Source/WebKit/UIProcess/WebAuthentication/AuthenticatorTransportService.h.
+        * UIProcess/WebAuthentication/Cocoa/HidConnection.mm: Added.
+        (WebKit::reportReceived):
+        (WebKit::HidConnection::HidConnection):
+        (WebKit::HidConnection::~HidConnection):
+        (WebKit::HidConnection::initialize):
+        (WebKit::HidConnection::terminate):
+        (WebKit::HidConnection::send):
+        (WebKit::HidConnection::registerDataReceivedCallback):
+        (WebKit::HidConnection::unregisterDataReceivedCallback):
+        (WebKit::HidConnection::receiveReport):
+        (WebKit::HidConnection::consumeReports):
+        (WebKit::HidConnection::registerDataReceivedCallbackInternal):
+        * UIProcess/WebAuthentication/Cocoa/HidService.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.h.
+        * UIProcess/WebAuthentication/Cocoa/HidService.mm: Added.
+        (WebKit::deviceAddedCallback):
+        (WebKit::deviceRemovedCallback):
+        (WebKit::HidService::HidService):
+        (WebKit::HidService::~HidService):
+        (WebKit::HidService::startDiscoveryInternal):
+        (WebKit::HidService::platformStartDiscovery):
+        (WebKit::HidService::createHidConnection const):
+        (WebKit::HidService::deviceAdded):
+        (WebKit::HidService::continueAddDeviceAfterGetInfo):
+        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticationSoftLink.h:
+        * UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:
+        * UIProcess/WebAuthentication/Cocoa/LocalService.h:
+        * UIProcess/WebAuthentication/Cocoa/LocalService.mm:
+        (WebKit::LocalService::startDiscoveryInternal):
+        (WebKit::LocalService::startDiscoveryInternal const): Deleted.
+        * UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp:
+        (WebKit::MockAuthenticatorManager::respondReceivedInternal):
+        * UIProcess/WebAuthentication/Mock/MockHidConnection.cpp: Added.
+        (WebKit::MockHidConnection::MockHidConnection):
+        (WebKit::MockHidConnection::initialize):
+        (WebKit::MockHidConnection::terminate):
+        (WebKit::MockHidConnection::send):
+        (WebKit::MockHidConnection::registerDataReceivedCallbackInternal):
+        (WebKit::MockHidConnection::assembleRequest):
+        (WebKit::MockHidConnection::parseRequest):
+        (WebKit::MockHidConnection::feedReports):
+        (WebKit::MockHidConnection::stagesMatch const):
+        (WebKit::MockHidConnection::shouldContinueFeedReports):
+        (WebKit::MockHidConnection::continueFeedReports):
+        * UIProcess/WebAuthentication/Mock/MockHidConnection.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h.
+        * UIProcess/WebAuthentication/Mock/MockHidService.cpp: Copied from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalService.cpp.
+        (WebKit::MockHidService::MockHidService):
+        (WebKit::MockHidService::platformStartDiscovery):
+        (WebKit::MockHidService::createHidConnection const):
+        * UIProcess/WebAuthentication/Mock/MockHidService.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h.
+        * UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
+        * UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
+        * UIProcess/WebAuthentication/Mock/MockLocalService.mm: Renamed from Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalService.cpp.
+        (WebKit::MockLocalService::MockLocalService):
+        (WebKit::MockLocalService::platformStartDiscovery const):
+        (WebKit::MockLocalService::createLocalConnection const):
+        * UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:
+        * UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp: Added.
+        (WebKit::CtapHidAuthenticator::CtapHidAuthenticator):
+        (WebKit::CtapHidAuthenticator::makeCredential):
+        (WebKit::CtapHidAuthenticator::continueMakeCredentialAfterResponseReceived const):
+        (WebKit::CtapHidAuthenticator::getAssertion):
+        (WebKit::CtapHidAuthenticator::continueGetAssertionAfterResponseReceived const):
+        * UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.h.
+        * UIProcess/WebAuthentication/fido/CtapHidDriver.cpp: Added.
+        (WebKit::CtapHidDriver::Worker::Worker):
+        (WebKit::CtapHidDriver::Worker::~Worker):
+        (WebKit::CtapHidDriver::Worker::transact):
+        (WebKit::CtapHidDriver::Worker::write):
+        (WebKit::CtapHidDriver::Worker::read):
+        (WebKit::CtapHidDriver::Worker::returnMessage):
+        (WebKit::CtapHidDriver::CtapHidDriver):
+        (WebKit::CtapHidDriver::transact):
+        (WebKit::CtapHidDriver::continueAfterChannelAllocated):
+        (WebKit::CtapHidDriver::continueAfterResponseReceived):
+        (WebKit::CtapHidDriver::returnResponse):
+        * UIProcess/WebAuthentication/fido/CtapHidDriver.h: Added.
+        * UIProcess/mac/WebDataListSuggestionsDropdownMac.mm:
+        * WebProcess/WebPage/CoordinatedGraphics/ThreadedCoordinatedLayerTreeHost.h:
+        * WebKit.xcodeproj/project.pbxproj:
+
 2018-11-13  Ross Kirsling  <ross.kirsling@sony.com>
 
         Unreviewed correction to previous build fix to avoid any internal/downstream repercussions.
index b5042fe..6d70538 100644 (file)
@@ -56,7 +56,7 @@ public:
         virtual uint64_t nativeSurfaceHandleForCompositing() = 0;
         virtual void didDestroyGLContext() = 0;
 
-        virtual void resize(const IntSize&) = 0;
+        virtual void resize(const WebCore::IntSize&) = 0;
         virtual void willRenderFrame() = 0;
         virtual void didRenderFrame() = 0;
     };
index edbb5e1..4066128 100644 (file)
@@ -384,6 +384,15 @@ UIProcess/Plugins/PluginProcessProxy.cpp
 UIProcess/UserContent/WebScriptMessageHandler.cpp
 UIProcess/UserContent/WebUserContentControllerProxy.cpp
 
+UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp
+UIProcess/WebAuthentication/Mock/MockHidConnection.cpp
+UIProcess/WebAuthentication/Mock/MockHidService.cpp
+
+UIProcess/WebAuthentication/AuthenticatorManager.cpp
+UIProcess/WebAuthentication/AuthenticatorTransportService.cpp
+UIProcess/WebAuthentication/Authenticator.cpp
+UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp
+
 UIProcess/WebStorage/LocalStorageDatabase.cpp
 UIProcess/WebStorage/LocalStorageDatabaseTracker.cpp
 
@@ -494,6 +503,8 @@ WebProcess/Storage/WebServiceWorkerProvider.cpp
 
 WebProcess/UserContent/WebUserContentController.cpp
 
+WebProcess/WebAuthentication/WebAuthenticatorCoordinator.cpp
+
 WebProcess/WebCoreSupport/SessionStateConversion.cpp
 WebProcess/WebCoreSupport/WebChromeClient.cpp
 WebProcess/WebCoreSupport/WebColorChooser.cpp
index e4dc710..3abaa80 100644 (file)
@@ -451,18 +451,14 @@ UIProcess/RemoteLayerTree/RemoteLayerTreeScrollingPerformanceData.mm
 UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp
 UIProcess/RemoteLayerTree/RemoteScrollingTree.cpp
 
+UIProcess/WebAuthentication/Cocoa/HidConnection.mm
+UIProcess/WebAuthentication/Cocoa/HidService.mm
 UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm
 UIProcess/WebAuthentication/Cocoa/LocalConnection.mm
 UIProcess/WebAuthentication/Cocoa/LocalService.mm
 
-UIProcess/WebAuthentication/Mock/MockAuthenticatorManager.cpp
 UIProcess/WebAuthentication/Mock/MockLocalConnection.mm
-UIProcess/WebAuthentication/Mock/MockLocalService.cpp
-
-UIProcess/WebAuthentication/AuthenticatorManager.cpp
-UIProcess/WebAuthentication/AuthenticatorTransportService.cpp
-UIProcess/WebAuthentication/Authenticator.cpp
-UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp
+UIProcess/WebAuthentication/Mock/MockLocalService.mm
 
 UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm
 
@@ -517,8 +513,6 @@ WebProcess/Plugins/PDF/PDFPluginChoiceAnnotation.mm
 WebProcess/Plugins/PDF/PDFPluginPasswordField.mm
 WebProcess/Plugins/PDF/PDFPluginTextAnnotation.mm
 
-WebProcess/WebAuthentication/WebAuthenticatorCoordinator.cpp
-
 WebProcess/WebCoreSupport/WebDataListSuggestionPicker.cpp
 WebProcess/WebCoreSupport/WebPasteboardOverrides.cpp
 WebProcess/WebCoreSupport/WebValidationMessageClient.cpp
index 60acf64..4012808 100644 (file)
@@ -581,12 +581,10 @@ void WKWebsiteDataStoreSetWebAuthenticationMockConfiguration(WKWebsiteDataStoreR
 #if ENABLE(WEB_AUTHN)
     MockWebAuthenticationConfiguration configuration;
 
-    auto silentFailureRef = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(configurationRef, adoptWK(WKStringCreateWithUTF8CString("SilentFailure")).get()));
-    if (silentFailureRef)
+    if (auto silentFailureRef = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(configurationRef, adoptWK(WKStringCreateWithUTF8CString("SilentFailure")).get())))
         configuration.silentFailure = WKBooleanGetValue(silentFailureRef);
 
-    auto localRef = static_cast<WKDictionaryRef>(WKDictionaryGetItemForKey(configurationRef, adoptWK(WKStringCreateWithUTF8CString("Local")).get()));
-    if (localRef) {
+    if (auto localRef = static_cast<WKDictionaryRef>(WKDictionaryGetItemForKey(configurationRef, adoptWK(WKStringCreateWithUTF8CString("Local")).get()))) {
         MockWebAuthenticationConfiguration::Local local;
         local.acceptAuthentication = WKBooleanGetValue(static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("AcceptAuthentication")).get())));
         local.acceptAttestation = WKBooleanGetValue(static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(localRef, adoptWK(WKStringCreateWithUTF8CString("AcceptAttestation")).get())));
@@ -598,6 +596,51 @@ void WKWebsiteDataStoreSetWebAuthenticationMockConfiguration(WKWebsiteDataStoreR
         configuration.local = WTFMove(local);
     }
 
+    if (auto hidRef = static_cast<WKDictionaryRef>(WKDictionaryGetItemForKey(configurationRef, adoptWK(WKStringCreateWithUTF8CString("Hid")).get()))) {
+        MockWebAuthenticationConfiguration::Hid hid;
+
+        auto stage = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("Stage")).get())))->string();
+        if (stage == "info")
+            hid.stage = MockWebAuthenticationConfiguration::Hid::Stage::Info;
+        if (stage == "request")
+            hid.stage = MockWebAuthenticationConfiguration::Hid::Stage::Request;
+
+        auto subStage = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("SubStage")).get())))->string();
+        if (subStage == "init")
+            hid.subStage = MockWebAuthenticationConfiguration::Hid::SubStage::Init;
+        if (subStage == "msg")
+            hid.subStage = MockWebAuthenticationConfiguration::Hid::SubStage::Msg;
+
+
+        auto error = WebKit::toImpl(static_cast<WKStringRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("Error")).get())))->string();
+        if (error == "success")
+            hid.error = MockWebAuthenticationConfiguration::Hid::Error::Success;
+        if (error == "data-not-sent")
+            hid.error = MockWebAuthenticationConfiguration::Hid::Error::DataNotSent;
+        if (error == "empty-report")
+            hid.error = MockWebAuthenticationConfiguration::Hid::Error::EmptyReport;
+        if (error == "wrong-channel-id")
+            hid.error = MockWebAuthenticationConfiguration::Hid::Error::WrongChannelId;
+        if (error == "malicious-payload")
+            hid.error = MockWebAuthenticationConfiguration::Hid::Error::MaliciousPayload;
+        if (error == "unsupported-options")
+            hid.error = MockWebAuthenticationConfiguration::Hid::Error::UnsupportedOptions;
+
+        if (auto payloadBase64 = static_cast<WKStringRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("PayloadBase64")).get())))
+            hid.payloadBase64 = WebKit::toImpl(payloadBase64)->string();
+
+        if (auto keepAlive = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("KeepAlive")).get())))
+            hid.keepAlive = WKBooleanGetValue(keepAlive);
+
+        if (auto fastDataArrival = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("FastDataArrival")).get())))
+            hid.fastDataArrival = WKBooleanGetValue(fastDataArrival);
+
+        if (auto continueAfterErrorData = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("ContinueAfterErrorData")).get())))
+            hid.continueAfterErrorData = WKBooleanGetValue(continueAfterErrorData);
+
+        configuration.hid = WTFMove(hid);
+    }
+
     WebKit::toImpl(dataStoreRef)->websiteDataStore().setMockWebAuthenticationConfiguration(WTFMove(configuration));
 #endif
 }
index ac9ba97..2c4e76a 100644 (file)
 
 #include <WebCore/AuthenticatorTransport.h>
 #include <WebCore/PublicKeyCredentialCreationOptions.h>
-#include <wtf/RunLoop.h>
 
 namespace WebKit {
 using namespace WebCore;
 
 namespace AuthenticatorManagerInternal {
 
+#if PLATFORM(MAC)
+const size_t maxTransportNumber = 2;
+#else
 const size_t maxTransportNumber = 1;
+#endif
+
 // Suggested by WebAuthN spec as of 7 August 2018.
 const unsigned maxTimeOutValue = 120000;
 
-// FIXME(188623, 188624, 188625): Support USB, NFC and BLE authenticators.
+// FIXME(188624, 188625): Support NFC and BLE authenticators.
 static AuthenticatorManager::TransportSet collectTransports(const std::optional<PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria>& authenticatorSelection)
 {
     AuthenticatorManager::TransportSet result;
     if (!authenticatorSelection) {
         auto addResult = result.add(AuthenticatorTransport::Internal);
         ASSERT_UNUSED(addResult, addResult.isNewEntry);
+#if PLATFORM(MAC)
+        addResult = result.add(AuthenticatorTransport::Usb);
+        ASSERT_UNUSED(addResult, addResult.isNewEntry);
+#endif
         return result;
     }
 
@@ -56,14 +64,19 @@ static AuthenticatorManager::TransportSet collectTransports(const std::optional<
         ASSERT_UNUSED(addResult, addResult.isNewEntry);
         return result;
     }
-    if (authenticatorSelection->authenticatorAttachment == PublicKeyCredentialCreationOptions::AuthenticatorAttachment::CrossPlatform)
+    if (authenticatorSelection->authenticatorAttachment == PublicKeyCredentialCreationOptions::AuthenticatorAttachment::CrossPlatform) {
+#if PLATFORM(MAC)
+        auto addResult = result.add(AuthenticatorTransport::Usb);
+        ASSERT_UNUSED(addResult, addResult.isNewEntry);
+#endif
         return result;
+    }
 
     ASSERT_NOT_REACHED();
     return result;
 }
 
-// FIXME(188623, 188624, 188625): Support USB, NFC and BLE authenticators.
+// FIXME(188624, 188625): Support NFC and BLE authenticators.
 // The goal is to find a union of different transports from allowCredentials.
 // If it is not specified or any of its credentials doesn't specify its own. We should discover all.
 // This is a variant of Step. 18.*.4 from https://www.w3.org/TR/webauthn/#discover-from-external-source
@@ -74,16 +87,27 @@ static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicK
     if (allowCredentials.isEmpty()) {
         auto addResult = result.add(AuthenticatorTransport::Internal);
         ASSERT_UNUSED(addResult, addResult.isNewEntry);
+#if PLATFORM(MAC)
+        addResult = result.add(AuthenticatorTransport::Usb);
+        ASSERT_UNUSED(addResult, addResult.isNewEntry);
+#endif
         return result;
     }
 
     for (auto& allowCredential : allowCredentials) {
         if (allowCredential.transports.isEmpty()) {
             result.add(AuthenticatorTransport::Internal);
+#if PLATFORM(MAC)
+            result.add(AuthenticatorTransport::Usb);
             return result;
+#endif
         }
         if (!result.contains(AuthenticatorTransport::Internal) && allowCredential.transports.contains(AuthenticatorTransport::Internal))
             result.add(AuthenticatorTransport::Internal);
+#if PLATFORM(MAC)
+        if (!result.contains(AuthenticatorTransport::Usb) && allowCredential.transports.contains(AuthenticatorTransport::Usb))
+            result.add(AuthenticatorTransport::Usb);
+#endif
         if (result.size() >= maxTransportNumber)
             return result;
     }
@@ -94,6 +118,11 @@ static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicK
 
 } // namespace AuthenticatorManagerInternal
 
+AuthenticatorManager::AuthenticatorManager()
+    : m_requestTimeOutTimer(RunLoop::main(), this, &AuthenticatorManager::timeOutTimerFired)
+{
+}
+
 void AuthenticatorManager::makeCredential(const Vector<uint8_t>& hash, const PublicKeyCredentialCreationOptions& options, Callback&& callback)
 {
     using namespace AuthenticatorManagerInternal;
@@ -131,12 +160,16 @@ void AuthenticatorManager::getAssertion(const Vector<uint8_t>& hash, const Publi
     startDiscovery(collectTransports(options.allowCredentials));
 }
 
-void AuthenticatorManager::clearState()
+void AuthenticatorManager::clearStateAsync()
 {
-    m_pendingRequestData = { };
-    ASSERT(!m_pendingCompletionHandler);
-    m_services.clear();
-    m_authenticators.clear();
+    RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)] {
+        if (!weakThis)
+            return;
+        weakThis->m_pendingRequestData = { };
+        ASSERT(!weakThis->m_pendingCompletionHandler);
+        weakThis->m_services.clear();
+        weakThis->m_authenticators.clear();
+    });
 }
 
 void AuthenticatorManager::authenticatorAdded(Ref<Authenticator>&& authenticator)
@@ -151,15 +184,14 @@ void AuthenticatorManager::authenticatorAdded(Ref<Authenticator>&& authenticator
 void AuthenticatorManager::respondReceived(Respond&& respond)
 {
     ASSERT(RunLoop::isMain());
-    ASSERT(m_requestTimeOutTimer);
-    if (!m_requestTimeOutTimer->isActive())
+    if (!m_requestTimeOutTimer.isActive())
         return;
 
     ASSERT(m_pendingCompletionHandler);
     if (WTF::holds_alternative<PublicKeyCredentialData>(respond)) {
         m_pendingCompletionHandler(WTFMove(respond));
-        clearState();
-        m_requestTimeOutTimer->stop();
+        clearStateAsync();
+        m_requestTimeOutTimer.stop();
         return;
     }
     respondReceivedInternal(WTFMove(respond));
@@ -191,12 +223,13 @@ void AuthenticatorManager::initTimeOutTimer(const std::optional<unsigned>& timeO
     using namespace AuthenticatorManagerInternal;
 
     unsigned timeOutInMsValue = std::min(maxTimeOutValue, timeOutInMs.value_or(maxTimeOutValue));
+    m_requestTimeOutTimer.startOneShot(Seconds::fromMilliseconds(timeOutInMsValue));
+}
 
-    m_requestTimeOutTimer = std::make_unique<Timer>([context = this]() mutable {
-        context->m_pendingCompletionHandler((ExceptionData { NotAllowedError, "Operation timed out."_s }));
-        context->clearState();
-    });
-    m_requestTimeOutTimer->startOneShot(Seconds::fromMilliseconds(timeOutInMsValue));
+void AuthenticatorManager::timeOutTimerFired()
+{
+    m_pendingCompletionHandler((ExceptionData { NotAllowedError, "Operation timed out."_s }));
+    clearStateAsync();
 }
 
 } // namespace WebKit
index ba22fd0..2437ed6 100644 (file)
 #include "WebAuthenticationRequestData.h"
 #include <WebCore/ExceptionData.h>
 #include <WebCore/PublicKeyCredentialData.h>
-#include <WebCore/Timer.h>
 #include <wtf/CompletionHandler.h>
 #include <wtf/HashSet.h>
 #include <wtf/Noncopyable.h>
+#include <wtf/RunLoop.h>
 #include <wtf/Vector.h>
 
 namespace WebKit {
@@ -48,7 +48,9 @@ public:
     using Callback = CompletionHandler<void(Respond&&)>;
     using TransportSet = HashSet<WebCore::AuthenticatorTransport, WTF::IntHash<WebCore::AuthenticatorTransport>, WTF::StrongEnumHashTraits<WebCore::AuthenticatorTransport>>;
 
-    AuthenticatorManager() = default;
+    using AuthenticatorTransportService::Observer::weakPtrFactory;
+
+    AuthenticatorManager();
     virtual ~AuthenticatorManager() = default;
 
     void makeCredential(const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialCreationOptions&, Callback&&);
@@ -58,8 +60,8 @@ public:
 
 protected:
     Callback& pendingCompletionHandler() { return m_pendingCompletionHandler; }
-    WebCore::Timer* requestTimeOutTimer() { return m_requestTimeOutTimer.get(); }
-    void clearState();
+    RunLoop::Timer<AuthenticatorManager>& requestTimeOutTimer() { return m_requestTimeOutTimer; }
+    void clearStateAsync(); // To void cyclic dependence.
 
 private:
     // AuthenticatorTransportService::Observer
@@ -75,11 +77,12 @@ private:
 
     void startDiscovery(const TransportSet&);
     void initTimeOutTimer(const std::optional<unsigned>& timeOutInMs);
+    void timeOutTimerFired();
 
     // Request: We only allow one request per time.
     WebAuthenticationRequestData m_pendingRequestData;
     Callback m_pendingCompletionHandler;
-    std::unique_ptr<WebCore::Timer> m_requestTimeOutTimer;
+    RunLoop::Timer<AuthenticatorManager> m_requestTimeOutTimer;
 
     Vector<UniqueRef<AuthenticatorTransportService>> m_services;
     HashSet<Ref<Authenticator>> m_authenticators;
index 314d4a4..f45657b 100644 (file)
@@ -28,7 +28,9 @@
 
 #if ENABLE(WEB_AUTHN)
 
+#include "HidService.h"
 #include "LocalService.h"
+#include "MockHidService.h"
 #include "MockLocalService.h"
 #include <wtf/RunLoop.h>
 
@@ -36,14 +38,32 @@ namespace WebKit {
 
 UniqueRef<AuthenticatorTransportService> AuthenticatorTransportService::create(WebCore::AuthenticatorTransport transport, Observer& observer)
 {
-    ASSERT(transport == WebCore::AuthenticatorTransport::Internal);
-    return makeUniqueRef<LocalService>(observer);
+    switch (transport) {
+    case WebCore::AuthenticatorTransport::Internal:
+        return makeUniqueRef<LocalService>(observer);
+#if PLATFORM(MAC)
+    case WebCore::AuthenticatorTransport::Usb:
+        return makeUniqueRef<HidService>(observer);
+#endif
+    default:
+        ASSERT_NOT_REACHED();
+        return makeUniqueRef<LocalService>(observer);
+    }
 }
 
 UniqueRef<AuthenticatorTransportService> AuthenticatorTransportService::createMock(WebCore::AuthenticatorTransport transport, Observer& observer, const MockWebAuthenticationConfiguration& configuration)
 {
-    ASSERT(transport == WebCore::AuthenticatorTransport::Internal);
-    return makeUniqueRef<MockLocalService>(observer, configuration);
+    switch (transport) {
+    case WebCore::AuthenticatorTransport::Internal:
+        return makeUniqueRef<MockLocalService>(observer, configuration);
+#if PLATFORM(MAC)
+    case WebCore::AuthenticatorTransport::Usb:
+        return makeUniqueRef<MockHidService>(observer, configuration);
+#endif
+    default:
+        ASSERT_NOT_REACHED();
+        return makeUniqueRef<MockLocalService>(observer, configuration);
+    }
 }
 
 AuthenticatorTransportService::AuthenticatorTransportService(Observer& observer)
@@ -51,7 +71,7 @@ AuthenticatorTransportService::AuthenticatorTransportService(Observer& observer)
 {
 }
 
-void AuthenticatorTransportService::startDiscovery() const
+void AuthenticatorTransportService::startDiscovery()
 {
     // Enforce asynchronous execution of makeCredential.
     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)] {
index 05d59b3..6ce4618 100644 (file)
@@ -54,7 +54,7 @@ public:
     virtual ~AuthenticatorTransportService() = default;
 
     // This operation is guaranteed to execute asynchronously.
-    void startDiscovery() const;
+    void startDiscovery();
 
 protected:
     explicit AuthenticatorTransportService(Observer&);
@@ -62,7 +62,7 @@ protected:
     Observer* observer() const { return m_observer.get(); }
 
 private:
-    virtual void startDiscoveryInternal() const = 0;
+    virtual void startDiscoveryInternal() = 0;
 
     WeakPtr<Observer> m_observer;
 };
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidConnection.h b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidConnection.h
new file mode 100644 (file)
index 0000000..21bec7e
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include <IOKit/hid/IOHIDDevice.h>
+#include <wtf/CompletionHandler.h>
+#include <wtf/Deque.h>
+#include <wtf/Forward.h>
+#include <wtf/Function.h>
+#include <wtf/Noncopyable.h>
+#include <wtf/RetainPtr.h>
+
+namespace WebKit {
+
+class HidConnection {
+    WTF_MAKE_FAST_ALLOCATED;
+    WTF_MAKE_NONCOPYABLE(HidConnection);
+public:
+    enum class DataSent {
+        No,
+        Yes
+    };
+
+    using DataSentCallback = CompletionHandler<void(DataSent)>;
+    using DataReceivedCallback = Function<void(Vector<uint8_t>&&)>;
+
+    explicit HidConnection(IOHIDDeviceRef);
+    virtual ~HidConnection();
+
+    // Overrided by MockHidConnection.
+    virtual void initialize();
+    virtual void terminate();
+    // Caller should send data again after callback is invoked to control flow.
+    virtual void send(Vector<uint8_t>&& data, DataSentCallback&&);
+    void registerDataReceivedCallback(DataReceivedCallback&&);
+    void unregisterDataReceivedCallback();
+
+    void receiveReport(Vector<uint8_t>&&);
+
+protected:
+    bool m_initialized { false };
+    bool m_terminated { false };
+
+private:
+    void consumeReports();
+
+    // Overrided by MockHidConnection.
+    virtual void registerDataReceivedCallbackInternal();
+
+    RetainPtr<IOHIDDeviceRef> m_device;
+    Vector<uint8_t> m_inputBuffer;
+    // Could queue data requested by other applications.
+    Deque<Vector<uint8_t>> m_inputReports;
+    DataReceivedCallback m_inputCallback;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidConnection.mm b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidConnection.mm
new file mode 100644 (file)
index 0000000..00620bf
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "HidConnection.h"
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#import <WebCore/FidoConstants.h>
+#import <wtf/BlockPtr.h>
+
+namespace WebKit {
+using namespace fido;
+
+// FIXME(191518)
+static void reportReceived(void* context, IOReturn status, void*, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex reportLength)
+{
+    ASSERT(RunLoop::isMain());
+    auto* connection = static_cast<HidConnection*>(context);
+
+    if (status) {
+        LOG_ERROR("Couldn't receive report from the authenticator: %d", status);
+        connection->receiveReport({ });
+        return;
+    }
+    ASSERT(type == kIOHIDReportTypeInput);
+    // FIXME(191519): Determine report id and max report size dynamically.
+    ASSERT(reportID == kHidReportId);
+    ASSERT(reportLength == kHidMaxPacketSize);
+
+    Vector<uint8_t> reportData;
+    reportData.append(report, reportLength);
+    connection->receiveReport(WTFMove(reportData));
+}
+
+HidConnection::HidConnection(IOHIDDeviceRef device)
+    : m_device(device)
+{
+}
+
+HidConnection::~HidConnection()
+{
+    ASSERT(m_terminated);
+}
+
+void HidConnection::initialize()
+{
+    IOHIDDeviceOpen(m_device.get(), kIOHIDOptionsTypeNone);
+    IOHIDDeviceScheduleWithRunLoop(m_device.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    m_inputBuffer.resize(kHidMaxPacketSize);
+    IOHIDDeviceRegisterInputReportCallback(m_device.get(), m_inputBuffer.data(), m_inputBuffer.size(), &reportReceived, this);
+    m_initialized = true;
+}
+
+void HidConnection::terminate()
+{
+    IOHIDDeviceRegisterInputReportCallback(m_device.get(), m_inputBuffer.data(), m_inputBuffer.size(), nullptr, nullptr);
+    IOHIDDeviceUnscheduleFromRunLoop(m_device.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    IOHIDDeviceClose(m_device.get(), kIOHIDOptionsTypeNone);
+    m_terminated = true;
+}
+
+void HidConnection::send(Vector<uint8_t>&& data, DataSentCallback&& callback)
+{
+    ASSERT(m_initialized);
+    auto task = BlockPtr<void()>::fromCallable([device = m_device, data = WTFMove(data), callback = WTFMove(callback)]() mutable {
+        ASSERT(!RunLoop::isMain());
+
+        DataSent sent = DataSent::Yes;
+        auto status = IOHIDDeviceSetReport(device.get(), kIOHIDReportTypeOutput, kHidReportId, data.data(), data.size());
+        if (status) {
+            LOG_ERROR("Couldn't send report to the authenticator: %d", status);
+            sent = DataSent::No;
+        }
+        RunLoop::main().dispatch([callback = WTFMove(callback), sent]() mutable {
+            callback(sent);
+        });
+    });
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), task.get());
+}
+
+void HidConnection::registerDataReceivedCallback(DataReceivedCallback&& callback)
+{
+    ASSERT(m_initialized);
+    ASSERT(!m_inputCallback);
+    m_inputCallback = WTFMove(callback);
+    consumeReports();
+    registerDataReceivedCallbackInternal();
+}
+
+void HidConnection::unregisterDataReceivedCallback()
+{
+    m_inputCallback = nullptr;
+}
+
+void HidConnection::receiveReport(Vector<uint8_t>&& report)
+{
+    m_inputReports.append(WTFMove(report));
+    consumeReports();
+}
+
+void HidConnection::consumeReports()
+{
+    while (!m_inputReports.isEmpty() && m_inputCallback)
+        m_inputCallback(m_inputReports.takeFirst());
+}
+
+void HidConnection::registerDataReceivedCallbackInternal()
+{
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidService.h b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidService.h
new file mode 100644 (file)
index 0000000..bc36323
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "AuthenticatorTransportService.h"
+#include <IOKit/hid/IOHIDManager.h>
+#include <wtf/UniqueRef.h>
+
+namespace WebKit {
+
+class CtapHidDriver;
+class HidConnection;
+
+class HidService : public AuthenticatorTransportService {
+public:
+    explicit HidService(Observer&);
+    ~HidService();
+
+    void deviceAdded(IOHIDDeviceRef);
+
+private:
+    void startDiscoveryInternal() final;
+
+    // Overrided by MockHidService.
+    virtual void platformStartDiscovery();
+    virtual UniqueRef<HidConnection> createHidConnection(IOHIDDeviceRef) const;
+
+    void continueAddDeviceAfterGetInfo(CtapHidDriver* deviceRef, Vector<uint8_t>&& info);
+
+    RetainPtr<IOHIDManagerRef> m_manager;
+    // Keeping drivers alive when they are initializing authenticators.
+    HashSet<std::unique_ptr<CtapHidDriver>> m_drivers;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidService.mm b/Source/WebKit/UIProcess/WebAuthentication/Cocoa/HidService.mm
new file mode 100644 (file)
index 0000000..554d826
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "HidService.h"
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#import "CtapHidAuthenticator.h"
+#import "CtapHidDriver.h"
+#import "HidConnection.h"
+#import <WebCore/DeviceRequestConverter.h>
+#import <WebCore/DeviceResponseConverter.h>
+#import <WebCore/FidoConstants.h>
+#import <WebCore/FidoHidMessage.h>
+
+namespace WebKit {
+using namespace fido;
+
+// FIXME(191518)
+static void deviceAddedCallback(void* context, IOReturn, void*, IOHIDDeviceRef device)
+{
+    ASSERT(device);
+    auto* listener = static_cast<HidService*>(context);
+    listener->deviceAdded(device);
+}
+
+// FIXME(191518)
+static void deviceRemovedCallback(void* context, IOReturn, void*, IOHIDDeviceRef device)
+{
+    // FIXME(191525)
+}
+
+HidService::HidService(Observer& observer)
+    : AuthenticatorTransportService(observer)
+{
+    m_manager = adoptCF(IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone));
+    NSDictionary *matchingDictionary = @{
+        @kIOHIDPrimaryUsagePageKey: adoptNS([NSNumber numberWithInt:kCTAPHIDUsagePage]).get(),
+        @kIOHIDPrimaryUsageKey: adoptNS([NSNumber numberWithInt:kCTAPHIDUsage]).get()
+    };
+    IOHIDManagerSetDeviceMatching(m_manager.get(), (__bridge CFDictionaryRef)matchingDictionary);
+    IOHIDManagerRegisterDeviceMatchingCallback(m_manager.get(), deviceAddedCallback, this);
+    IOHIDManagerRegisterDeviceRemovalCallback(m_manager.get(), deviceRemovedCallback, this);
+}
+
+HidService::~HidService()
+{
+    IOHIDManagerUnscheduleFromRunLoop(m_manager.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    IOHIDManagerClose(m_manager.get(), kIOHIDOptionsTypeNone);
+}
+
+void HidService::startDiscoveryInternal()
+{
+    platformStartDiscovery();
+}
+
+void HidService::platformStartDiscovery()
+{
+    IOHIDManagerScheduleWithRunLoop(m_manager.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    IOHIDManagerOpen(m_manager.get(), kIOHIDOptionsTypeNone);
+}
+
+UniqueRef<HidConnection> HidService::createHidConnection(IOHIDDeviceRef device) const
+{
+    return makeUniqueRef<HidConnection>(device);
+}
+
+void HidService::deviceAdded(IOHIDDeviceRef device)
+{
+    auto driver = std::make_unique<CtapHidDriver>(createHidConnection(device));
+    // Get authenticator info from the device.
+    driver->transact(encodeEmptyAuthenticatorRequest(CtapRequestCommand::kAuthenticatorGetInfo), [weakThis = makeWeakPtr(*this), ptr = driver.get()](Vector<uint8_t>&& response) {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->continueAddDeviceAfterGetInfo(ptr, WTFMove(response));
+    });
+    auto addResult = m_drivers.add(WTFMove(driver));
+    ASSERT_UNUSED(addResult, addResult.isNewEntry);
+}
+
+void HidService::continueAddDeviceAfterGetInfo(CtapHidDriver* ptr, Vector<uint8_t>&& response)
+{
+    std::unique_ptr<CtapHidDriver> driver = m_drivers.take(ptr);
+    if (!driver || !observer())
+        return;
+
+    auto info = readCTAPGetInfoResponse(response);
+    if (info && info->versions().find(ProtocolVersion::kCtap) != info->versions().end()) {
+        observer()->authenticatorAdded(CtapHidAuthenticator::create(WTFMove(driver), WTFMove(*info)));
+        return;
+    }
+    // FIXME(191535): Support U2F authenticators.
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
index abee43f..dedb7fc 100644 (file)
@@ -29,7 +29,6 @@
 #if ENABLE(WEB_AUTHN)
 
 #import "DeviceIdentitySPI.h"
-#import <LocalAuthentication/LocalAuthentication.h>
 #import <WebCore/ExceptionData.h>
 #import <wtf/BlockPtr.h>
 
index 70cd6fb..2ef3c0b 100644 (file)
@@ -40,7 +40,7 @@ public:
     static bool isAvailable();
 
 private:
-    void startDiscoveryInternal() const final;
+    void startDiscoveryInternal() final;
     // Overrided by MockLocalService.
     virtual bool platformStartDiscovery() const;
     virtual UniqueRef<LocalConnection> createLocalConnection() const;
index 2cc31ed..02b63a6 100644 (file)
@@ -56,13 +56,11 @@ bool LocalService::isAvailable()
 #endif
 }
 
-void LocalService::startDiscoveryInternal() const
+void LocalService::startDiscoveryInternal()
 {
-    if (!platformStartDiscovery())
+    if (!platformStartDiscovery() || !observer())
         return;
-
-    if (observer())
-        observer()->authenticatorAdded(LocalAuthenticator::create(createLocalConnection()));
+    observer()->authenticatorAdded(LocalAuthenticator::create(createLocalConnection()));
 }
 
 bool LocalService::platformStartDiscovery() const
index 837aa26..4db2760 100644 (file)
@@ -46,8 +46,8 @@ void MockAuthenticatorManager::respondReceivedInternal(Respond&& respond)
         return;
 
     pendingCompletionHandler()(WTFMove(respond));
-    clearState();
-    requestTimeOutTimer()->stop();
+    clearStateAsync();
+    requestTimeOutTimer().stop();
 }
 
 } // namespace WebKit
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.cpp b/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.cpp
new file mode 100644 (file)
index 0000000..e648bb8
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MockHidConnection.h"
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include <WebCore/AuthenticatorGetInfoResponse.h>
+#include <WebCore/CBORReader.h>
+#include <WebCore/FidoConstants.h>
+#include <wtf/BlockPtr.h>
+#include <wtf/CryptographicallyRandomNumber.h>
+#include <wtf/RunLoop.h>
+#include <wtf/text/Base64.h>
+
+namespace WebKit {
+using Mock = MockWebAuthenticationConfiguration::Hid;
+using namespace WebCore;
+using namespace cbor;
+using namespace fido;
+
+namespace MockHidConnectionInternal {
+// https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#mandatory-commands
+const size_t CtapChannelIdSize = 4;
+const uint8_t CtapKeepAliveStatusProcessing = 1;
+// https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#commands
+const int64_t CtapMakeCredentialRequestOptionsKey = 7;
+const int64_t CtapGetAssertionRequestOptionsKey = 5;
+}
+
+MockHidConnection::MockHidConnection(IOHIDDeviceRef device, const MockWebAuthenticationConfiguration& configuration)
+    : HidConnection(device)
+    , m_configuration(configuration)
+{
+}
+
+void MockHidConnection::initialize()
+{
+    m_initialized = true;
+}
+
+void MockHidConnection::terminate()
+{
+    m_terminated = true;
+}
+
+void MockHidConnection::send(Vector<uint8_t>&& data, DataSentCallback&& callback)
+{
+    ASSERT(m_initialized);
+    assembleRequest(WTFMove(data));
+
+    auto sent = DataSent::Yes;
+    if (stagesMatch() && m_configuration.hid->error == Mock::Error::DataNotSent)
+        sent = DataSent::No;
+
+    auto task = BlockPtr<void()>::fromCallable([callback = WTFMove(callback), sent]() mutable {
+        ASSERT(!RunLoop::isMain());
+        RunLoop::main().dispatch([callback = WTFMove(callback), sent]() mutable {
+            callback(sent);
+        });
+    });
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), task.get());
+}
+
+void MockHidConnection::registerDataReceivedCallbackInternal()
+{
+    if (stagesMatch() && m_configuration.hid->error == Mock::Error::EmptyReport) {
+        receiveReport({ });
+        shouldContinueFeedReports();
+        return;
+    }
+    if (!m_configuration.hid->fastDataArrival)
+        feedReports();
+}
+
+void MockHidConnection::assembleRequest(Vector<uint8_t>&& data)
+{
+    if (!m_requestMessage) {
+        m_requestMessage = FidoHidMessage::createFromSerializedData(data);
+        ASSERT(m_requestMessage);
+    } else {
+        auto status = m_requestMessage->addContinuationPacket(data);
+        ASSERT_UNUSED(status, status);
+    }
+
+    if (m_requestMessage->messageComplete())
+        parseRequest();
+}
+
+void MockHidConnection::parseRequest()
+{
+    using namespace MockHidConnectionInternal;
+
+    ASSERT(m_requestMessage);
+    // Set stages.
+    if (m_requestMessage->cmd() == FidoHidDeviceCommand::kInit) {
+        auto previousSubStage = m_subStage;
+        m_subStage = Mock::SubStage::Init;
+        if (previousSubStage == Mock::SubStage::Msg)
+            m_stage = Mock::Stage::Request;
+    }
+    if (m_requestMessage->cmd() == FidoHidDeviceCommand::kCbor)
+        m_subStage = Mock::SubStage::Msg;
+
+    // Set options.
+    if (m_stage == Mock::Stage::Request && m_subStage == Mock::SubStage::Msg) {
+        m_requireResidentKey = false;
+        m_requireUserVerification = false;
+
+        auto payload = m_requestMessage->getMessagePayload();
+        ASSERT(payload.size());
+        auto cmd = static_cast<CtapRequestCommand>(payload[0]);
+        payload.remove(0);
+        auto requestMap = CBORReader::read(payload);
+        ASSERT(requestMap);
+
+        if (cmd == CtapRequestCommand::kAuthenticatorMakeCredential) {
+            auto it = requestMap->getMap().find(CBORValue(CtapMakeCredentialRequestOptionsKey)); // Find options.
+            if (it != requestMap->getMap().end()) {
+                auto& optionMap = it->second.getMap();
+
+                auto itr = optionMap.find(CBORValue(kResidentKeyMapKey));
+                if (itr != optionMap.end())
+                    m_requireResidentKey = itr->second.getBool();
+
+                itr = optionMap.find(CBORValue(kUserVerificationMapKey));
+                if (itr != optionMap.end())
+                    m_requireUserVerification = itr->second.getBool();
+            }
+        }
+
+        if (cmd == CtapRequestCommand::kAuthenticatorGetAssertion) {
+            auto it = requestMap->getMap().find(CBORValue(CtapGetAssertionRequestOptionsKey)); // Find options.
+            if (it != requestMap->getMap().end()) {
+                auto& optionMap = it->second.getMap();
+                auto itr = optionMap.find(CBORValue(kUserVerificationMapKey));
+                if (itr != optionMap.end())
+                    m_requireUserVerification = itr->second.getBool();
+            }
+        }
+    }
+
+    m_currentChannel = m_requestMessage->channelId();
+    m_requestMessage = std::nullopt;
+    if (m_configuration.hid->fastDataArrival)
+        feedReports();
+}
+
+void MockHidConnection::feedReports()
+{
+    using namespace MockHidConnectionInternal;
+
+    if (m_subStage == Mock::SubStage::Init) {
+        Vector<uint8_t> payload;
+        payload.reserveInitialCapacity(kHidInitResponseSize);
+        // FIXME(191533): Use a real nonce.
+        payload.appendVector(Vector<uint8_t>({ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }));
+        payload.grow(kHidInitResponseSize);
+        cryptographicallyRandomValues(payload.data() + payload.size(), CtapChannelIdSize);
+        auto channel = kHidBroadcastChannel;
+        if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
+            channel--;
+        FidoHidInitPacket initPacket(channel, FidoHidDeviceCommand::kInit, WTFMove(payload), payload.size());
+        receiveReport(initPacket.getSerializedData());
+        shouldContinueFeedReports();
+        return;
+    }
+
+    std::optional<FidoHidMessage> message;
+    if (m_stage == Mock::Stage::Info && m_subStage == Mock::SubStage::Msg) {
+        auto infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap }, Vector<uint8_t>(kAaguidLength)));
+        infoData.insert(0, static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess)); // Prepend status code.
+        if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
+            message = FidoHidMessage::create(m_currentChannel - 1, FidoHidDeviceCommand::kCbor, infoData);
+        else
+            message = FidoHidMessage::create(m_currentChannel, FidoHidDeviceCommand::kCbor, infoData);
+    }
+
+    if (m_stage == Mock::Stage::Request && m_subStage == Mock::SubStage::Msg) {
+        if (m_configuration.hid->keepAlive) {
+            m_configuration.hid->keepAlive = false;
+            FidoHidInitPacket initPacket(m_currentChannel, FidoHidDeviceCommand::kKeepAlive, { CtapKeepAliveStatusProcessing }, 1);
+            receiveReport(initPacket.getSerializedData());
+            continueFeedReports();
+            return;
+        }
+        if (stagesMatch() && m_configuration.hid->error == Mock::Error::UnsupportedOptions && (m_requireResidentKey || m_requireUserVerification))
+            message = FidoHidMessage::create(m_currentChannel, FidoHidDeviceCommand::kCbor, { static_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrUnsupportedOption) });
+        else {
+            Vector<uint8_t> payload;
+            auto status = base64Decode(m_configuration.hid->payloadBase64, payload);
+            ASSERT_UNUSED(status, status);
+            message = FidoHidMessage::create(m_currentChannel, FidoHidDeviceCommand::kCbor, payload);
+        }
+    }
+
+    ASSERT(message);
+    bool isFirst = true;
+    while (message->numPackets()) {
+        auto report = message->popNextPacket();
+        if (!isFirst && stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
+            report = FidoHidContinuationPacket(m_currentChannel - 1, 0, { }).getSerializedData();
+        // Packets are feed asynchronously to mimic actual data transmission.
+        RunLoop::main().dispatch([report = WTFMove(report), weakThis = makeWeakPtr(*this)]() mutable {
+            if (!weakThis)
+                return;
+            weakThis->receiveReport(WTFMove(report));
+        });
+        isFirst = false;
+    }
+}
+
+bool MockHidConnection::stagesMatch() const
+{
+    return m_configuration.hid->stage == m_stage && m_configuration.hid->subStage == m_subStage;
+}
+
+void MockHidConnection::shouldContinueFeedReports()
+{
+    if (!m_configuration.hid->continueAfterErrorData)
+        return;
+    m_configuration.hid->continueAfterErrorData = false;
+    m_configuration.hid->error = Mock::Error::Success;
+    continueFeedReports();
+}
+
+void MockHidConnection::continueFeedReports()
+{
+    // Send actual response for the next run.
+    RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)]() mutable {
+        if (!weakThis)
+            return;
+        weakThis->feedReports();
+    });
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.h b/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.h
new file mode 100644 (file)
index 0000000..8b5969e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "HidConnection.h"
+#include "MockWebAuthenticationConfiguration.h"
+#include <WebCore/FidoHidMessage.h>
+#include <wtf/WeakPtr.h>
+
+namespace WebKit {
+
+// The following basically simulates an external HID token that:
+//    1. Only supports CTAP2 protocol,
+//    2. Doesn't support resident keys,
+//    3. Doesn't support user verification.
+// There are four stages for each WebAuthN request:
+// FSM: Info::Init => Info::Msg => Request::Init => Request::Msg
+// According to different combinations of error and stages, error will manifest differently.
+class MockHidConnection final : public CanMakeWeakPtr<MockHidConnection>, public HidConnection {
+public:
+    MockHidConnection(IOHIDDeviceRef, const MockWebAuthenticationConfiguration&);
+
+private:
+    void send(Vector<uint8_t>&& data, DataSentCallback&&) final;
+    void initialize() final;
+    void terminate() final;
+    void registerDataReceivedCallbackInternal() final;
+
+    void assembleRequest(Vector<uint8_t>&&);
+    void parseRequest();
+    void feedReports();
+    bool stagesMatch() const;
+    void shouldContinueFeedReports();
+    void continueFeedReports();
+
+    MockWebAuthenticationConfiguration m_configuration;
+    std::optional<fido::FidoHidMessage> m_requestMessage;
+    MockWebAuthenticationConfiguration::Hid::Stage m_stage { MockWebAuthenticationConfiguration::Hid::Stage::Info };
+    MockWebAuthenticationConfiguration::Hid::SubStage m_subStage { MockWebAuthenticationConfiguration::Hid::SubStage::Init };
+    uint32_t m_currentChannel { fido::kHidBroadcastChannel };
+    bool m_requireResidentKey { false };
+    bool m_requireUserVerification  { false };
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidService.cpp b/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidService.cpp
new file mode 100644 (file)
index 0000000..b35a873
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MockHidService.h"
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "MockHidConnection.h"
+#include <wtf/RunLoop.h>
+
+namespace WebKit {
+
+MockHidService::MockHidService(Observer& observer, const MockWebAuthenticationConfiguration& configuration)
+    : HidService(observer)
+    , m_configuration(configuration)
+{
+}
+
+void MockHidService::platformStartDiscovery()
+{
+    if (!!m_configuration.hid)
+        deviceAdded(nullptr);
+}
+
+UniqueRef<HidConnection> MockHidService::createHidConnection(IOHIDDeviceRef device) const
+{
+    return makeUniqueRef<MockHidConnection>(device, m_configuration);
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidService.h b/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidService.h
new file mode 100644 (file)
index 0000000..271e1b8
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "HidService.h"
+#include "MockWebAuthenticationConfiguration.h"
+
+namespace WebKit {
+
+struct MockWebAuthenticationConfiguration;
+
+class MockHidService final : public HidService {
+public:
+    MockHidService(Observer&, const MockWebAuthenticationConfiguration&);
+
+private:
+    void platformStartDiscovery() final;
+    UniqueRef<HidConnection> createHidConnection(IOHIDDeviceRef) const final;
+
+    MockWebAuthenticationConfiguration m_configuration;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
index 9524fbc..0d99786 100644 (file)
@@ -36,11 +36,11 @@ class MockLocalConnection final : public LocalConnection {
 public:
     explicit MockLocalConnection(const MockWebAuthenticationConfiguration&);
 
+private:
     void getUserConsent(const String& reason, UserConsentCallback&&) const final;
     void getUserConsent(const String& reason, SecAccessControlRef, UserConsentContextCallback&&) const final;
     void getAttestation(const String& rpId, const String& username, const Vector<uint8_t>& hash, AttestationCallback&&) const final;
 
-private:
     MockWebAuthenticationConfiguration m_configuration;
 };
 
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "config.h"
-#include "MockLocalService.h"
+#import "config.h"
+#import "MockLocalService.h"
 
 #if ENABLE(WEB_AUTHN)
 
-#import "LocalAuthenticator.h"
 #import "MockLocalConnection.h"
 #import <wtf/RunLoop.h>
 
index 639a12b..c4216c6 100644 (file)
@@ -27,6 +27,8 @@
 
 #if ENABLE(WEB_AUTHN)
 
+#include <wtf/text/WTFString.h>
+
 namespace WebKit {
 
 struct MockWebAuthenticationConfiguration {
@@ -38,8 +40,38 @@ struct MockWebAuthenticationConfiguration {
         String intermediateCACertificateBase64;
     };
 
+    struct Hid {
+        enum class Stage : bool {
+            Info,
+            Request
+        };
+
+        enum class SubStage : bool {
+            Init,
+            Msg
+        };
+
+        enum class Error : uint8_t {
+            Success,
+            DataNotSent,
+            EmptyReport,
+            WrongChannelId,
+            MaliciousPayload,
+            UnsupportedOptions
+        };
+
+        String payloadBase64;
+        Stage stage { Stage::Info };
+        SubStage subStage { SubStage::Init };
+        Error error { Error::Success };
+        bool keepAlive { false };
+        bool fastDataArrival { false };
+        bool continueAfterErrorData { false };
+    };
+
     bool silentFailure { false };
     std::optional<Local> local;
+    std::optional<Hid> hid;
 };
 
 } // namespace WebKit
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp
new file mode 100644 (file)
index 0000000..2a4bd19
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CtapHidAuthenticator.h"
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "CtapHidDriver.h"
+#include <WebCore/DeviceRequestConverter.h>
+#include <WebCore/DeviceResponseConverter.h>
+#include <WebCore/ExceptionData.h>
+#include <wtf/RunLoop.h>
+#include <wtf/text/StringConcatenateNumbers.h>
+
+namespace WebKit {
+using namespace WebCore;
+using namespace fido;
+
+CtapHidAuthenticator::CtapHidAuthenticator(std::unique_ptr<CtapHidDriver>&& driver, AuthenticatorGetInfoResponse&& info)
+    : m_driver(WTFMove(driver))
+    , m_info(WTFMove(info))
+{
+    // FIXME(191520): We need a way to convert std::unique_ptr to UniqueRef.
+    ASSERT(m_driver);
+}
+
+void CtapHidAuthenticator::makeCredential()
+{
+    auto cborCmd = encodeMakeCredenitalRequestAsCBOR(requestData().hash, requestData().creationOptions, m_info.options().userVerificationAvailability());
+    m_driver->transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->continueMakeCredentialAfterResponseReceived(WTFMove(data));
+    });
+}
+
+void CtapHidAuthenticator::continueMakeCredentialAfterResponseReceived(Vector<uint8_t>&& data) const
+{
+    auto response = readCTAPMakeCredentialResponse(data);
+    if (!response) {
+        receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", data.size() == 1 ? data[0] : -1) });
+        return;
+    }
+    receiveRespond(WTFMove(*response));
+}
+
+void CtapHidAuthenticator::getAssertion()
+{
+    auto cborCmd = encodeGetAssertionRequestAsCBOR(requestData().hash, requestData().requestOptions, m_info.options().userVerificationAvailability());
+    m_driver->transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->continueGetAssertionAfterResponseReceived(WTFMove(data));
+    });
+}
+
+void CtapHidAuthenticator::continueGetAssertionAfterResponseReceived(Vector<uint8_t>&& data) const
+{
+    auto response = readCTAPGetAssertionResponse(data);
+    if (!response) {
+        receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", data.size() == 1 ? data[0] : -1) });
+        return;
+    }
+    receiveRespond(WTFMove(*response));
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h
new file mode 100644 (file)
index 0000000..968ece2
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "Authenticator.h"
+#include <WebCore/AuthenticatorGetInfoResponse.h>
+
+namespace WebKit {
+
+class CtapHidDriver;
+
+class CtapHidAuthenticator final : public Authenticator {
+public:
+    // FIXME(191526): Should store AuthenticatorSupportedOptions::UserVerificationAvailability.
+    static Ref<CtapHidAuthenticator> create(std::unique_ptr<CtapHidDriver>&& driver, fido::AuthenticatorGetInfoResponse&& info)
+    {
+        return adoptRef(*new CtapHidAuthenticator(WTFMove(driver), WTFMove(info)));
+    }
+
+private:
+    explicit CtapHidAuthenticator(std::unique_ptr<CtapHidDriver>&&, fido::AuthenticatorGetInfoResponse&&);
+
+    void makeCredential() final;
+    void continueMakeCredentialAfterResponseReceived(Vector<uint8_t>&&) const;
+    void getAssertion() final;
+    void continueGetAssertionAfterResponseReceived(Vector<uint8_t>&&) const;
+
+    std::unique_ptr<CtapHidDriver> m_driver;
+    fido::AuthenticatorGetInfoResponse m_info;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidDriver.cpp b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidDriver.cpp
new file mode 100644 (file)
index 0000000..39d7c1a
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CtapHidDriver.h"
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include <WebCore/FidoConstants.h>
+#include <wtf/RunLoop.h>
+#include <wtf/text/Base64.h>
+
+namespace WebKit {
+using namespace fido;
+
+CtapHidDriver::Worker::Worker(UniqueRef<HidConnection>&& connection)
+    : m_connection(WTFMove(connection))
+{
+    m_connection->initialize();
+}
+
+CtapHidDriver::Worker::~Worker()
+{
+    m_connection->terminate();
+}
+
+void CtapHidDriver::Worker::transact(fido::FidoHidMessage&& requestMessage, MessageCallback&& callback)
+{
+    ASSERT(m_state == State::Idle);
+    m_state = State::Write;
+    m_requestMessage = WTFMove(requestMessage);
+    m_responseMessage.reset();
+    m_callback = WTFMove(callback);
+
+    m_connection->send(m_requestMessage->popNextPacket(), [weakThis = makeWeakPtr(*this)](HidConnection::DataSent sent) mutable {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->write(sent);
+    });
+}
+
+void CtapHidDriver::Worker::write(HidConnection::DataSent sent)
+{
+    ASSERT(m_state == State::Write);
+    if (sent != HidConnection::DataSent::Yes) {
+        returnMessage(std::nullopt);
+        return;
+    }
+
+    if (!m_requestMessage->numPackets()) {
+        m_state = State::Read;
+        m_connection->registerDataReceivedCallback([weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) mutable {
+            ASSERT(RunLoop::isMain());
+            if (!weakThis)
+                return;
+            weakThis->read(data);
+        });
+        return;
+    }
+
+    m_connection->send(m_requestMessage->popNextPacket(), [weakThis = makeWeakPtr(*this)](HidConnection::DataSent sent) mutable {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->write(sent);
+    });
+}
+
+void CtapHidDriver::Worker::read(const Vector<uint8_t>& data)
+{
+    ASSERT(m_state == State::Read);
+    if (!m_responseMessage) {
+        m_responseMessage = FidoHidMessage::createFromSerializedData(data);
+        // The first few reports could be for other applications, and therefore ignore those.
+        if (!m_responseMessage || m_responseMessage->channelId() != m_requestMessage->channelId()) {
+            LOG_ERROR("Couldn't parse a hid init packet: %s", m_responseMessage ? "wrong channel id." : "bad data.");
+            m_responseMessage.reset();
+            return;
+        }
+    } else {
+        if (!m_responseMessage->addContinuationPacket(data)) {
+            LOG_ERROR("Couldn't parse a hid continuation packet.");
+            returnMessage(std::nullopt);
+            return;
+        }
+    }
+
+    if (m_responseMessage->messageComplete()) {
+        // A KeepAlive cmd could be sent between a request and a response to indicate that
+        // the authenticator is waiting for user consent. Keep listening for the response.
+        if (m_responseMessage->cmd() == FidoHidDeviceCommand::kKeepAlive) {
+            m_responseMessage.reset();
+            return;
+        }
+        returnMessage(WTFMove(m_responseMessage));
+        return;
+    }
+}
+
+void CtapHidDriver::Worker::returnMessage(std::optional<fido::FidoHidMessage>&& message)
+{
+    m_state = State::Idle;
+    m_connection->unregisterDataReceivedCallback();
+    m_callback(WTFMove(message));
+}
+
+CtapHidDriver::CtapHidDriver(UniqueRef<HidConnection>&& connection)
+    : m_worker(makeUniqueRef<Worker>(WTFMove(connection)))
+{
+}
+
+void CtapHidDriver::transact(Vector<uint8_t>&& data, ResponseCallback&& callback)
+{
+    ASSERT(m_state == State::Idle);
+    m_state = State::AllocateChannel;
+    m_requestData = WTFMove(data);
+    m_responseCallback = WTFMove(callback);
+
+    // Allocate a channel.
+    // FIXME(191533): Get a real nonce.
+    auto initCommand = FidoHidMessage::create(kHidBroadcastChannel, FidoHidDeviceCommand::kInit, { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 });
+    ASSERT(initCommand);
+    m_worker->transact(WTFMove(*initCommand), [weakThis = makeWeakPtr(*this)](std::optional<FidoHidMessage>&& response) mutable {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->continueAfterChannelAllocated(WTFMove(response));
+    });
+}
+
+void CtapHidDriver::continueAfterChannelAllocated(std::optional<FidoHidMessage>&& message)
+{
+    ASSERT(m_state == State::AllocateChannel);
+    if (!message) {
+        returnResponse({ });
+        return;
+    }
+    m_state = State::Ready;
+    ASSERT(message->channelId() == m_channelId);
+
+    // FIXME(191534): Check Payload including nonce and everything
+    auto payload = message->getMessagePayload();
+    size_t index = 8;
+    ASSERT(payload.size() == kHidInitResponseSize);
+    m_channelId = static_cast<uint32_t>(payload[index++]) << 24;
+    m_channelId |= static_cast<uint32_t>(payload[index++]) << 16;
+    m_channelId |= static_cast<uint32_t>(payload[index++]) << 8;
+    m_channelId |= static_cast<uint32_t>(payload[index]);
+    auto cmd = FidoHidMessage::create(m_channelId, FidoHidDeviceCommand::kCbor, m_requestData);
+    ASSERT(cmd);
+    m_worker->transact(WTFMove(*cmd), [weakThis = makeWeakPtr(*this)](std::optional<FidoHidMessage>&& response) mutable {
+        ASSERT(RunLoop::isMain());
+        if (!weakThis)
+            return;
+        weakThis->continueAfterResponseReceived(WTFMove(response));
+    });
+}
+
+void CtapHidDriver::continueAfterResponseReceived(std::optional<fido::FidoHidMessage>&& message)
+{
+    ASSERT(m_state == State::Ready);
+    ASSERT(!message || message->channelId() == m_channelId);
+    returnResponse(message ? message->getMessagePayload() : Vector<uint8_t>());
+}
+
+void CtapHidDriver::returnResponse(Vector<uint8_t>&& response)
+{
+    // Reset state before calling the response callback to avoid being deleted.
+    m_state = State::Idle;
+    m_channelId = fido::kHidBroadcastChannel;
+    m_requestData = { };
+    m_responseCallback(WTFMove(response));
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
diff --git a/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidDriver.h b/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidDriver.h
new file mode 100644 (file)
index 0000000..68b1c46
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN) && PLATFORM(MAC)
+
+#include "HidConnection.h"
+#include <WebCore/FidoConstants.h>
+#include <WebCore/FidoHidMessage.h>
+#include <wtf/CompletionHandler.h>
+#include <wtf/Forward.h>
+#include <wtf/Noncopyable.h>
+#include <wtf/UniqueRef.h>
+#include <wtf/WeakPtr.h>
+
+namespace WebKit {
+
+// The following implements the CTAP HID protocol:
+// https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#usb
+// FSM: Idle => AllocateChannel => Ready
+class CtapHidDriver : public CanMakeWeakPtr<CtapHidDriver> {
+    WTF_MAKE_FAST_ALLOCATED;
+    WTF_MAKE_NONCOPYABLE(CtapHidDriver);
+public:
+    using ResponseCallback = Function<void(Vector<uint8_t>&&)>;
+
+    enum class State : uint8_t {
+        Idle,
+        AllocateChannel,
+        Ready,
+        // FIXME(191528)
+        Busy
+    };
+
+    explicit CtapHidDriver(UniqueRef<HidConnection>&&);
+
+    void transact(Vector<uint8_t>&& data, ResponseCallback&&);
+
+private:
+    // Worker is the helper that maintains the transaction.
+    // https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#arbitration
+    // FSM: Idle => Write => Read
+    class Worker : public CanMakeWeakPtr<Worker> {
+        WTF_MAKE_FAST_ALLOCATED;
+        WTF_MAKE_NONCOPYABLE(Worker);
+    public:
+        using MessageCallback = Function<void(std::optional<fido::FidoHidMessage>&&)>;
+
+        enum class State : uint8_t  {
+            Idle,
+            Write,
+            Read
+        };
+
+        explicit Worker(UniqueRef<HidConnection>&&);
+        ~Worker();
+
+        void transact(fido::FidoHidMessage&&, MessageCallback&&);
+
+    private:
+        void write(HidConnection::DataSent);
+        void read(const Vector<uint8_t>&);
+        void returnMessage(std::optional<fido::FidoHidMessage>&&);
+
+        UniqueRef<HidConnection> m_connection;
+        State m_state { State::Idle };
+        std::optional<fido::FidoHidMessage> m_requestMessage;
+        std::optional<fido::FidoHidMessage> m_responseMessage;
+        MessageCallback m_callback;
+    };
+
+    void continueAfterChannelAllocated(std::optional<fido::FidoHidMessage>&&);
+    void continueAfterResponseReceived(std::optional<fido::FidoHidMessage>&&);
+    void returnResponse(Vector<uint8_t>&&);
+
+    UniqueRef<Worker> m_worker;
+    State m_state { State::Idle };
+    uint32_t m_channelId { fido::kHidBroadcastChannel };
+    // One request at a time.
+    Vector<uint8_t> m_requestData;
+    ResponseCallback m_responseCallback;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(WEB_AUTHN) && PLATFORM(MAC)
index 8d4f070..20edfcf 100644 (file)
@@ -28,6 +28,7 @@
 
 #if ENABLE(DATALIST_ELEMENT) && USE(APPKIT)
 
+#import "WebPageProxy.h"
 #import <WebCore/IntRect.h>
 #import <pal/spi/cocoa/NSColorSPI.h>
 
index d7a77be..603e9ec 100644 (file)
                53BA47D11DC2EF5E004DF4AD /* NetworkDataTaskBlob.h in Headers */ = {isa = PBXBuildFile; fileRef = 539EB5471DC2EE40009D48CF /* NetworkDataTaskBlob.h */; };
                53DEA3661DDE423100E82648 /* json.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 53DEA3651DDE422E00E82648 /* json.hpp */; };
                570AB8F320AE3BD700B8BE87 /* SecKeyProxyStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 570AB8F220AE3BD700B8BE87 /* SecKeyProxyStore.h */; };
+               57597EB921811D9A0037F924 /* CtapHidDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 57597EB721811D9A0037F924 /* CtapHidDriver.h */; };
+               57597EBD218184900037F924 /* CtapHidAuthenticator.h in Headers */ = {isa = PBXBuildFile; fileRef = 57597EBB2181848F0037F924 /* CtapHidAuthenticator.h */; };
+               57597EBE218184900037F924 /* CtapHidAuthenticator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57597EBC2181848F0037F924 /* CtapHidAuthenticator.cpp */; };
+               57597EC121818BE20037F924 /* CtapHidDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57597EC021818BE20037F924 /* CtapHidDriver.cpp */; };
+               5772F206217DBD6A0056BF2C /* HidService.h in Headers */ = {isa = PBXBuildFile; fileRef = 5772F204217DBD6A0056BF2C /* HidService.h */; };
                578DC2982155A0020074E815 /* LocalAuthenticationSoftLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 578DC2972155A0010074E815 /* LocalAuthenticationSoftLink.h */; };
+               57AC8F50217FEED90055438C /* HidConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC8F4E217FEED90055438C /* HidConnection.h */; };
                57B4B46020B504AC00D4AD79 /* ClientCertificateAuthenticationXPCConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B4B45E20B504AB00D4AD79 /* ClientCertificateAuthenticationXPCConstants.h */; };
                57DCED6E2142EE5E0016B847 /* WebAuthenticatorCoordinatorMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57DCED6B2142EAE20016B847 /* WebAuthenticatorCoordinatorMessageReceiver.cpp */; };
                57DCED6F2142EE630016B847 /* WebAuthenticatorCoordinatorMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCED6A2142EAE20016B847 /* WebAuthenticatorCoordinatorMessages.h */; };
                570AB90320B2541C00B8BE87 /* SecKeyProxyStore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SecKeyProxyStore.mm; sourceTree = "<group>"; };
                575075A720AB763600693EA9 /* WebCredentialMac.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WebCredentialMac.mm; sourceTree = "<group>"; };
                5750F32A2032D4E500389347 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; };
+               5756DD74218D104900D4EE6A /* MockHidService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockHidService.h; sourceTree = "<group>"; };
+               5756DD75218D104900D4EE6A /* MockHidService.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MockHidService.cpp; sourceTree = "<group>"; };
+               5756DD76218D14A200D4EE6A /* MockHidConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockHidConnection.h; sourceTree = "<group>"; };
+               5756DD77218D14A200D4EE6A /* MockHidConnection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MockHidConnection.cpp; sourceTree = "<group>"; };
+               57597EB721811D9A0037F924 /* CtapHidDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CtapHidDriver.h; sourceTree = "<group>"; };
+               57597EBB2181848F0037F924 /* CtapHidAuthenticator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CtapHidAuthenticator.h; sourceTree = "<group>"; };
+               57597EBC2181848F0037F924 /* CtapHidAuthenticator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CtapHidAuthenticator.cpp; sourceTree = "<group>"; };
+               57597EC021818BE20037F924 /* CtapHidDriver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CtapHidDriver.cpp; sourceTree = "<group>"; };
                5760828B2029854200116678 /* WebAuthenticatorCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAuthenticatorCoordinator.h; sourceTree = "<group>"; };
                5760828C2029854200116678 /* WebAuthenticatorCoordinator.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WebAuthenticatorCoordinator.cpp; sourceTree = "<group>"; };
                5760828D202987E600116678 /* WebAuthenticatorCoordinator.messages.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = WebAuthenticatorCoordinator.messages.in; sourceTree = "<group>"; };
                57608295202BD8BA00116678 /* WebAuthenticatorCoordinatorProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAuthenticatorCoordinatorProxy.h; sourceTree = "<group>"; };
                57608296202BD8BA00116678 /* WebAuthenticatorCoordinatorProxy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WebAuthenticatorCoordinatorProxy.cpp; sourceTree = "<group>"; };
                57608299202BDAE200116678 /* WebAuthenticatorCoordinatorProxy.messages.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = WebAuthenticatorCoordinatorProxy.messages.in; sourceTree = "<group>"; };
+               5772F204217DBD6A0056BF2C /* HidService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HidService.h; sourceTree = "<group>"; };
+               5772F205217DBD6A0056BF2C /* HidService.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HidService.mm; sourceTree = "<group>"; };
                578DC2972155A0010074E815 /* LocalAuthenticationSoftLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalAuthenticationSoftLink.h; sourceTree = "<group>"; };
+               57AC8F4E217FEED90055438C /* HidConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HidConnection.h; sourceTree = "<group>"; };
+               57AC8F4F217FEED90055438C /* HidConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HidConnection.mm; sourceTree = "<group>"; };
                57B4B45D20B504AB00D4AD79 /* AuthenticationManagerCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AuthenticationManagerCocoa.mm; path = Authentication/cocoa/AuthenticationManagerCocoa.mm; sourceTree = "<group>"; };
                57B4B45E20B504AB00D4AD79 /* ClientCertificateAuthenticationXPCConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClientCertificateAuthenticationXPCConstants.h; path = Authentication/cocoa/ClientCertificateAuthenticationXPCConstants.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; };
                57DCEDAA214B9B430016B847 /* DeviceIdentitySPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceIdentitySPI.h; sourceTree = "<group>"; };
                57DCEDBE214CA01B0016B847 /* MockWebAuthenticationConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockWebAuthenticationConfiguration.h; sourceTree = "<group>"; };
                57DCEDC1214F114C0016B847 /* MockLocalService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockLocalService.h; sourceTree = "<group>"; };
-               57DCEDC2214F114C0016B847 /* MockLocalService.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MockLocalService.cpp; sourceTree = "<group>"; };
+               57DCEDC2214F114C0016B847 /* MockLocalService.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MockLocalService.mm; sourceTree = "<group>"; };
                57DCEDC5214F18300016B847 /* MockLocalConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockLocalConnection.h; sourceTree = "<group>"; };
                57DCEDC6214F18300016B847 /* MockLocalConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MockLocalConnection.mm; sourceTree = "<group>"; };
                57DCEDC9214F4E420016B847 /* MockAuthenticatorManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockAuthenticatorManager.h; sourceTree = "<group>"; };
                        name = Frameworks;
                        sourceTree = "<group>";
                };
+               57597EBF218184B20037F924 /* fido */ = {
+                       isa = PBXGroup;
+                       children = (
+                               57597EBC2181848F0037F924 /* CtapHidAuthenticator.cpp */,
+                               57597EBB2181848F0037F924 /* CtapHidAuthenticator.h */,
+                               57597EC021818BE20037F924 /* CtapHidDriver.cpp */,
+                               57597EB721811D9A0037F924 /* CtapHidDriver.h */,
+                       );
+                       path = fido;
+                       sourceTree = "<group>";
+               };
                5760828A202984C900116678 /* WebAuthentication */ = {
                        isa = PBXGroup;
                        children = (
                        isa = PBXGroup;
                        children = (
                                57DCED9E2148F9D10016B847 /* Cocoa */,
+                               57597EBF218184B20037F924 /* fido */,
                                57DCEDBD214C9FA90016B847 /* Mock */,
                                57DCEDA42149E64A0016B847 /* Authenticator.cpp */,
                                57DCED8B21485BD70016B847 /* Authenticator.h */,
                57DCED9E2148F9D10016B847 /* Cocoa */ = {
                        isa = PBXGroup;
                        children = (
+                               57AC8F4E217FEED90055438C /* HidConnection.h */,
+                               57AC8F4F217FEED90055438C /* HidConnection.mm */,
+                               5772F204217DBD6A0056BF2C /* HidService.h */,
+                               5772F205217DBD6A0056BF2C /* HidService.mm */,
                                578DC2972155A0010074E815 /* LocalAuthenticationSoftLink.h */,
                                57DCEDA12149C1E20016B847 /* LocalAuthenticator.h */,
                                57DCEDA32149DFF50016B847 /* LocalAuthenticator.mm */,
                        children = (
                                57DCEDCD214F51680016B847 /* MockAuthenticatorManager.cpp */,
                                57DCEDC9214F4E420016B847 /* MockAuthenticatorManager.h */,
+                               5756DD77218D14A200D4EE6A /* MockHidConnection.cpp */,
+                               5756DD76218D14A200D4EE6A /* MockHidConnection.h */,
+                               5756DD75218D104900D4EE6A /* MockHidService.cpp */,
+                               5756DD74218D104900D4EE6A /* MockHidService.h */,
                                57DCEDC5214F18300016B847 /* MockLocalConnection.h */,
                                57DCEDC6214F18300016B847 /* MockLocalConnection.mm */,
-                               57DCEDC2214F114C0016B847 /* MockLocalService.cpp */,
                                57DCEDC1214F114C0016B847 /* MockLocalService.h */,
+                               57DCEDC2214F114C0016B847 /* MockLocalService.mm */,
                                57DCEDBE214CA01B0016B847 /* MockWebAuthenticationConfiguration.h */,
                        );
                        path = Mock;
                                37C21CAE1E994C0C0029D5F9 /* CorePredictionSPI.h in Headers */,
                                B878B615133428DC006888E9 /* CorrectionPanel.h in Headers */,
                                A1FB68271F6E51C100C43F9F /* CrashReporterClientSPI.h in Headers */,
+                               57597EBD218184900037F924 /* CtapHidAuthenticator.h in Headers */,
+                               57597EB921811D9A0037F924 /* CtapHidDriver.h in Headers */,
                                C55F91711C59676E0029E92D /* DataDetectionResult.h in Headers */,
                                1AC75380183BE50F0072CB15 /* DataReference.h in Headers */,
                                BC032DA610F437D10058C15A /* Decoder.h in Headers */,
                                2DA049B8180CCD0A00AAFA9E /* GraphicsLayerCARemote.h in Headers */,
                                C0CE72AD1247E78D00BC0EC4 /* HandleMessage.h in Headers */,
                                1AC75A1B1B3368270056745B /* HangDetectionDisabler.h in Headers */,
+                               57AC8F50217FEED90055438C /* HidConnection.h in Headers */,
                                2DD5A72B1EBF09A7009BA597 /* HiddenPageThrottlingAutoIncreasesCounter.h in Headers */,
+                               5772F206217DBD6A0056BF2C /* HidService.h in Headers */,
                                312CC9F2215B06F100DE40CA /* HighPerformanceGPUManager.h in Headers */,
                                839A2F321E2067450039057E /* HighPerformanceGraphicsUsageSampler.h in Headers */,
                                37F90DE31376560E0051CF68 /* HTTPCookieAcceptPolicy.h in Headers */,
                                2D92A786212B6AB100F493FD /* ChildProcess.cpp in Sources */,
                                51FAEC3B1B0657680009C4E7 /* ChildProcessMessageReceiver.cpp in Sources */,
                                2D92A77D212B6A7100F493FD /* Connection.cpp in Sources */,
+                               57597EBE218184900037F924 /* CtapHidAuthenticator.cpp in Sources */,
+                               57597EC121818BE20037F924 /* CtapHidDriver.cpp in Sources */,
                                2D92A77E212B6A7100F493FD /* DataReference.cpp in Sources */,
                                2D92A77F212B6A7100F493FD /* Decoder.cpp in Sources */,
                                1AB7D6191288B9D900CFD08C /* DownloadProxyMessageReceiver.cpp in Sources */,
index f6c231f..b784681 100644 (file)
@@ -91,7 +91,7 @@ private:
             m_layerTreeHost.didDestroyGLContext();
         }
 
-        void resize(const IntSize& size)
+        void resize(const WebCore::IntSize& size)
         {
             if (m_layerTreeHost.m_surface)
                 m_layerTreeHost.m_surface->clientResize(size);
index cc36242..dfef827 100644 (file)
@@ -1,3 +1,18 @@
+2018-11-13  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthN] Support CTAP HID authenticators on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=188623
+        <rdar://problem/43353777>
+
+        Reviewed by Brent Fulgham and Chris Dumez.
+
+        This patch adds support for the mock testing and entitlements to allow minibrowser to talk
+        to hid devices.
+
+        * MiniBrowser/MiniBrowser.entitlements:
+        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
+        (WTR::TestRunner::setWebAuthenticationMockConfiguration):
+
 2018-11-13  Ryosuke Niwa  <rniwa@webkit.org>
 
         WebKit.GeolocationTransitionToLowAccuracy API crashes when enabling PSON
index 1c0cc11..e085f4e 100644 (file)
@@ -2,6 +2,8 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+       <key>com.apple.security.device.usb</key>
+       <true/>
        <key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
        <string>com.apple.Safari.SafeBrowsing.Service</string>
        <key>com.apple.security.app-sandbox</key>
index 6b9d703..5da546a 100644 (file)
@@ -2431,6 +2431,89 @@ void TestRunner::setWebAuthenticationMockConfiguration(JSValueRef configurationV
         configurationValues.append({ AdoptWK, WKDictionaryCreate(rawLocalKeys.data(), rawLocalValues.data(), rawLocalKeys.size()) });
     }
 
+    JSRetainPtr<JSStringRef> hidPropertyName(Adopt, JSStringCreateWithUTF8CString("hid"));
+    JSValueRef hidValue = JSObjectGetProperty(context, configuration, hidPropertyName.get(), 0);
+    if (!JSValueIsUndefined(context, hidValue) && !JSValueIsNull(context, hidValue)) {
+        if (!JSValueIsObject(context, hidValue))
+            return;
+        JSObjectRef hid = JSValueToObject(context, hidValue, 0);
+
+        JSRetainPtr<JSStringRef> stagePropertyName(Adopt, JSStringCreateWithUTF8CString("stage"));
+        JSValueRef stageValue = JSObjectGetProperty(context, hid, stagePropertyName.get(), 0);
+        if (!JSValueIsString(context, stageValue))
+            return;
+
+        JSRetainPtr<JSStringRef> subStagePropertyName(Adopt, JSStringCreateWithUTF8CString("subStage"));
+        JSValueRef subStageValue = JSObjectGetProperty(context, hid, subStagePropertyName.get(), 0);
+        if (!JSValueIsString(context, subStageValue))
+            return;
+
+        JSRetainPtr<JSStringRef> errorPropertyName(Adopt, JSStringCreateWithUTF8CString("error"));
+        JSValueRef errorValue = JSObjectGetProperty(context, hid, errorPropertyName.get(), 0);
+        if (!JSValueIsString(context, errorValue))
+            return;
+
+        Vector<WKRetainPtr<WKStringRef>> hidKeys;
+        Vector<WKRetainPtr<WKTypeRef>> hidValues;
+        hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("Stage") });
+        hidValues.append(toWK(adopt(JSValueToStringCopy(context, stageValue, 0)).get()));
+        hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("SubStage") });
+        hidValues.append(toWK(adopt(JSValueToStringCopy(context, subStageValue, 0)).get()));
+        hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("Error") });
+        hidValues.append(toWK(adopt(JSValueToStringCopy(context, errorValue, 0)).get()));
+
+        JSRetainPtr<JSStringRef> payloadBase64PropertyName(Adopt, JSStringCreateWithUTF8CString("payloadBase64"));
+        JSValueRef payloadBase64Value = JSObjectGetProperty(context, hid, payloadBase64PropertyName.get(), 0);
+        if (!JSValueIsUndefined(context, payloadBase64Value) && !JSValueIsNull(context, payloadBase64Value)) {
+            if (!JSValueIsString(context, payloadBase64Value))
+                return;
+            hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("PayloadBase64") });
+            hidValues.append(toWK(adopt(JSValueToStringCopy(context, payloadBase64Value, 0)).get()));
+        }
+
+        JSRetainPtr<JSStringRef> keepAlivePropertyName(Adopt, JSStringCreateWithUTF8CString("keepAlive"));
+        JSValueRef keepAliveValue = JSObjectGetProperty(context, hid, keepAlivePropertyName.get(), 0);
+        if (!JSValueIsUndefined(context, keepAliveValue) && !JSValueIsNull(context, keepAliveValue)) {
+            if (!JSValueIsBoolean(context, keepAliveValue))
+                return;
+            bool keepAlive = JSValueToBoolean(context, keepAliveValue);
+            hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("KeepAlive") });
+            hidValues.append(adoptWK(WKBooleanCreate(keepAlive)).get());
+        }
+
+        JSRetainPtr<JSStringRef> fastDataArrivalPropertyName(Adopt, JSStringCreateWithUTF8CString("fastDataArrival"));
+        JSValueRef fastDataArrivalValue = JSObjectGetProperty(context, hid, fastDataArrivalPropertyName.get(), 0);
+        if (!JSValueIsUndefined(context, fastDataArrivalValue) && !JSValueIsNull(context, fastDataArrivalValue)) {
+            if (!JSValueIsBoolean(context, fastDataArrivalValue))
+                return;
+            bool fastDataArrival = JSValueToBoolean(context, fastDataArrivalValue);
+            hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("FastDataArrival") });
+            hidValues.append(adoptWK(WKBooleanCreate(fastDataArrival)).get());
+        }
+
+        JSRetainPtr<JSStringRef> continueAfterErrorDataPropertyName(Adopt, JSStringCreateWithUTF8CString("continueAfterErrorData"));
+        JSValueRef continueAfterErrorDataValue = JSObjectGetProperty(context, hid, continueAfterErrorDataPropertyName.get(), 0);
+        if (!JSValueIsUndefined(context, continueAfterErrorDataValue) && !JSValueIsNull(context, continueAfterErrorDataValue)) {
+            if (!JSValueIsBoolean(context, continueAfterErrorDataValue))
+                return;
+            bool continueAfterErrorData = JSValueToBoolean(context, continueAfterErrorDataValue);
+            hidKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("ContinueAfterErrorData") });
+            hidValues.append(adoptWK(WKBooleanCreate(continueAfterErrorData)).get());
+        }
+
+        Vector<WKStringRef> rawHidKeys;
+        Vector<WKTypeRef> rawHidValues;
+        rawHidKeys.resize(hidKeys.size());
+        rawHidValues.resize(hidValues.size());
+        for (size_t i = 0; i < hidKeys.size(); ++i) {
+            rawHidKeys[i] = hidKeys[i].get();
+            rawHidValues[i] = hidValues[i].get();
+        }
+
+        configurationKeys.append({ AdoptWK, WKStringCreateWithUTF8CString("Hid") });
+        configurationValues.append({ AdoptWK, WKDictionaryCreate(rawHidKeys.data(), rawHidValues.data(), rawHidKeys.size()) });
+    }
+
     Vector<WKStringRef> rawConfigurationKeys;
     Vector<WKTypeRef> rawConfigurationValues;
     rawConfigurationKeys.resize(configurationKeys.size());