Skip to content
Permalink
Browse files
[WebAuthN] Support U2F HID Authenticators on macOS
https://bugs.webkit.org/show_bug.cgi?id=191535
<rdar://problem/47102027>

Reviewed by Brent Fulgham.

Source/WebCore:

This patch changes U2fCommandConstructor to produce register commands with
enforcing test of user presence. Otherwise, authenticators would silently
generate credentials. It also renames readFromU2fSignResponse to
readU2fSignResponse.

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

* Modules/webauthn/fido/U2fCommandConstructor.cpp:
(fido::WebCore::constructU2fRegisterCommand):
* Modules/webauthn/fido/U2fResponseConverter.cpp:
(fido::readU2fSignResponse):
(fido::readFromU2fSignResponse): Deleted.
* Modules/webauthn/fido/U2fResponseConverter.h:

Source/WebKit:

This patch implements the support for U2F authenticators, and enables it for hid devices.
It follows the CTAP spec to map WebAuthN requests to U2F commands and return the responses:
https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#u2f-interoperability
Most of the parts are done before this patch, this patch focues on: 7.2.2 and 7.3.2.

Besides implementing the U2fHidAuthenticator, this patch also adds support in the mocking
environment for U2F authenticators. It is done by extending the stages in MockHidConnection
from 4 to indefinite as multi-round communications are expected to map WebAuthN requests
to U2F requests.

* Sources.txt:
* UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
(WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):
* UIProcess/WebAuthentication/Cocoa/HidService.mm:
(WebKit::HidService::continueAddDeviceAfterGetInfo):
* UIProcess/WebAuthentication/fido/CtapHidDriver.cpp:
(WebKit::CtapHidDriver::continueAfterChannelAllocated):
* UIProcess/WebAuthentication/fido/CtapHidDriver.h:
(WebKit::CtapHidDriver::setProtocol):
* UIProcess/WebAuthentication/fido/U2fHidAuthenticator.cpp: Added.
(WebKit::U2fHidAuthenticator::U2fHidAuthenticator):
(WebKit::U2fHidAuthenticator::makeCredential):
(WebKit::U2fHidAuthenticator::checkExcludeList):
(WebKit::U2fHidAuthenticator::issueRegisterCommand):
(WebKit::U2fHidAuthenticator::getAssertion):
(WebKit::U2fHidAuthenticator::issueSignCommand):
(WebKit::U2fHidAuthenticator::issueNewCommand):
(WebKit::U2fHidAuthenticator::issueCommand):
(WebKit::U2fHidAuthenticator::responseReceived):
(WebKit::U2fHidAuthenticator::continueRegisterCommandAfterResponseReceived):
(WebKit::U2fHidAuthenticator::continueCheckOnlyCommandAfterResponseReceived):
(WebKit::U2fHidAuthenticator::continueBogusCommandAfterResponseReceived):
(WebKit::U2fHidAuthenticator::continueSignCommandAfterResponseReceived):
* UIProcess/WebAuthentication/fido/U2fHidAuthenticator.h: Added.
* UIProcess/WebAuthentication/Mock/MockHidConnection.cpp:
(WebKit::MockHidConnection::parseRequest):
(WebKit::MockHidConnection::feedReports):
* UIProcess/WebAuthentication/Mock/MockHidConnection.h:
* UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:
* WebKit.xcodeproj/project.pbxproj:

Tools:

This patch:
1) adds support for U2F mocking mechanism;
2) updates tests to reflect U2fCommandConstructor changes.

* TestWebKitAPI/Tests/WebCore/CtapResponseTest.cpp:
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebCore/FidoTestData.h:
* WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::TestRunner::setWebAuthenticationMockConfiguration):

LayoutTests:

Besiding adding tests for U2F authenticators, it also changes payloadBase64 from
a string to a vector of strings. New tests are skipped for iOS.

* 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-failure-u2f-silent.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-failure-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-failure-u2f.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-success-hid.https.html:
* http/wpt/webauthn/public-key-credential-create-success-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-success-u2f.https.html: Copied from LayoutTests/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-failure-u2f-silent.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-failure-u2f-silent.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-failure-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-failure-u2f.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-success-hid.https.html:
* http/wpt/webauthn/public-key-credential-get-success-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-success-u2f.https.html: Added.
* http/wpt/webauthn/resources/util.js:
* platform/ios-wk2/TestExpectations:


Canonical link: https://commits.webkit.org/207763@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@239752 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
Jiewen Tan committed Jan 9, 2019
1 parent 7cb7b4a commit 1512d46c7068c89db0ec2729aa1a9bfd7023d5f4
Showing 43 changed files with 1,248 additions and 71 deletions.
@@ -1,3 +1,37 @@
2019-01-08 Jiewen Tan <jiewen_tan@apple.com>

[WebAuthN] Support U2F HID Authenticators on macOS
https://bugs.webkit.org/show_bug.cgi?id=191535
<rdar://problem/47102027>

Reviewed by Brent Fulgham.

Besiding adding tests for U2F authenticators, it also changes payloadBase64 from
a string to a vector of strings. New tests are skipped for iOS.

* 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-failure-u2f-silent.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-failure-u2f-silent.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-failure-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-failure-u2f.https.html: Added.
* http/wpt/webauthn/public-key-credential-create-success-hid.https.html:
* http/wpt/webauthn/public-key-credential-create-success-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-create-success-u2f.https.html: Copied from LayoutTests/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-failure-u2f-silent.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-failure-u2f-silent.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-failure-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-failure-u2f.https.html: Added.
* http/wpt/webauthn/public-key-credential-get-success-hid.https.html:
* http/wpt/webauthn/public-key-credential-get-success-u2f.https-expected.txt: Added.
* http/wpt/webauthn/public-key-credential-get-success-u2f.https.html: Added.
* http/wpt/webauthn/resources/util.js:
* platform/ios-wk2/TestExpectations:

2019-01-08 Youenn Fablet <youenn@apple.com>

service worker fetch handler results in bad referrer
@@ -64,7 +64,7 @@

promise_test(function(t) {
if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "wrong-channel-id", payloadBase64:testDummyMessagePayloadBase64 } });
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>
@@ -21,7 +21,7 @@

promise_test(function(t) {
if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: testCreationMessageBase64, keepAlive: true } });
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);
@@ -30,7 +30,7 @@

promise_test(function(t) {
if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", payloadBase64: testCreationMessageBase64, fastDataArrival: true } });
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);
@@ -39,7 +39,7 @@

promise_test(function(t) {
if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "empty-report", payloadBase64: testCreationMessageBase64, continueAfterErrorData: true } });
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);
@@ -48,7 +48,7 @@

promise_test(function(t) {
if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "wrong-channel-id", payloadBase64: testCreationMessageBase64, continueAfterErrorData: true } });
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);
@@ -57,7 +57,7 @@

promise_test(function(t) {
if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "wrong-nonce", payloadBase64: testCreationMessageBase64, continueAfterErrorData: true } });
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "info", subStage: "init", error: "wrong-nonce", payloadBase64: [testCreationMessageBase64], continueAfterErrorData: true } });
return navigator.credentials.create(defaultOptions).then(credential => {
assert_not_equals(credential, undefined);
assert_not_equals(credential, null);
@@ -22,7 +22,7 @@
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: testDummyMessagePayloadBase64 } });
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.");

@@ -46,7 +46,7 @@
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: testDummyMessagePayloadBase64 } });
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.");

@@ -0,0 +1,9 @@

PASS PublicKeyCredential's [[create]] with malformed APDU payload in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with malformed U2F register response in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with register command error in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with bogus command error in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with second exclude credential matched in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator. Test of user presence.

@@ -0,0 +1,165 @@
<!DOCTYPE html>
<title>Web Authentication API: PublicKeyCredential's [[create]] failure cases with a mock u2f 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", isU2f: true, payloadBase64: ["AQ=="] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with malformed APDU 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 }],
timeout: 10
}
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with malformed U2F register response 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 }],
timeout: 10
}
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduInsNotSupportedOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with register command error 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 }],
excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }],
timeout: 10
}
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduNoErrorOnlyResponseBase64, testU2fApduInsNotSupportedOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with bogus command error 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 }],
excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }],
timeout: 10
}
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduNoErrorOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator.");

// Match the second exclude credential.
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 }],
excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }, { type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }], // The content doesn't matter.
timeout: 10
}
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with second exclude credential matched 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 }],
excludeCredentials: [{ type: "public-key", id: Base64URL.parse(testCredentialIdBase64) }],
timeout: 10
}
};

if (window.testRunner)
testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduConditionsNotSatisfiedOnlyResponseBase64, testU2fApduConditionsNotSatisfiedOnlyResponseBase64, testU2fApduConditionsNotSatisfiedOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.create(options), "Operation timed out.");
}, "PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator. Test of user presence.");
</script>
@@ -0,0 +1,9 @@

PASS PublicKeyCredential's [[create]] with malformed APDU payload in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with malformed U2F register response in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with register command error in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with bogus command error in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with second exclude credential matched in a mock hid authenticator.
PASS PublicKeyCredential's [[create]] with first exclude credential matched in a mock hid authenticator. Test of user presence.

0 comments on commit 1512d46

Please sign in to comment.