+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.
--- /dev/null
+
+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.
+
--- /dev/null
+<!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>
--- /dev/null
+
+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.
+
--- /dev/null
+<!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>
--- /dev/null
+
+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.
+
--- /dev/null
+<!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>
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
--- /dev/null
+
+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.
+
--- /dev/null
+<!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>
--- /dev/null
+
+PASS PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator.
+PASS PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.
+
--- /dev/null
+<!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>
--- /dev/null
+
+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.
+
--- /dev/null
+<!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>
--- /dev/null
+
+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.
+
--- /dev/null
+<!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>
"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/";
return arrayBuffer;
}
-function decodeAuthData(authDataUint8Array)
+function decodeAuthData(authDataUint8Array, littleEndian = true)
{
let authDataObject = { };
let pos = 0;
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)
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
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
+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
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);
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;
};
struct AuthenticatorSelectionCriteria {
- std::optional<AuthenticatorAttachment> authenticatorAttachment;
+ // FIXME(191522)
+ AuthenticatorAttachment authenticatorAttachment { AuthenticatorAttachment::CrossPlatform };
bool requireResidentKey { false };
UserVerificationRequirement userVerification { UserVerificationRequirement::Preferred };
{
PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria result;
- std::optional<std::optional<AuthenticatorAttachment>> authenticatorAttachment;
+ std::optional<AuthenticatorAttachment> authenticatorAttachment;
decoder >> authenticatorAttachment;
if (!authenticatorAttachment)
return std::nullopt;
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())
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 {
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)
+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.
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;
};
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
WebProcess/UserContent/WebUserContentController.cpp
+WebProcess/WebAuthentication/WebAuthenticatorCoordinator.cpp
+
WebProcess/WebCoreSupport/SessionStateConversion.cpp
WebProcess/WebCoreSupport/WebChromeClient.cpp
WebProcess/WebCoreSupport/WebColorChooser.cpp
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
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
#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())));
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
}
#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;
}
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
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;
}
} // 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;
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)
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));
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
#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 {
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&&);
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
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;
#if ENABLE(WEB_AUTHN)
+#include "HidService.h"
#include "LocalService.h"
+#include "MockHidService.h"
#include "MockLocalService.h"
#include <wtf/RunLoop.h>
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)
{
}
-void AuthenticatorTransportService::startDiscovery() const
+void AuthenticatorTransportService::startDiscovery()
{
// Enforce asynchronous execution of makeCredential.
RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)] {
virtual ~AuthenticatorTransportService() = default;
// This operation is guaranteed to execute asynchronously.
- void startDiscovery() const;
+ void startDiscovery();
protected:
explicit AuthenticatorTransportService(Observer&);
Observer* observer() const { return m_observer.get(); }
private:
- virtual void startDiscoveryInternal() const = 0;
+ virtual void startDiscoveryInternal() = 0;
WeakPtr<Observer> m_observer;
};
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
+
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
#pragma once
+#import <LocalAuthentication/LocalAuthentication.h>
#import <wtf/SoftLinking.h>
SOFT_LINK_FRAMEWORK(LocalAuthentication);
#if ENABLE(WEB_AUTHN)
#import "DeviceIdentitySPI.h"
-#import <LocalAuthentication/LocalAuthentication.h>
#import <WebCore/ExceptionData.h>
#import <wtf/BlockPtr.h>
static bool isAvailable();
private:
- void startDiscoveryInternal() const final;
+ void startDiscoveryInternal() final;
// Overrided by MockLocalService.
virtual bool platformStartDiscovery() const;
virtual UniqueRef<LocalConnection> createLocalConnection() const;
#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
return;
pendingCompletionHandler()(WTFMove(respond));
- clearState();
- requestTimeOutTimer()->stop();
+ clearStateAsync();
+ requestTimeOutTimer().stop();
}
} // namespace WebKit
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
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>
#if ENABLE(WEB_AUTHN)
+#include <wtf/text/WTFString.h>
+
namespace WebKit {
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
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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)
#if ENABLE(DATALIST_ELEMENT) && USE(APPKIT)
+#import "WebPageProxy.h"
#import <WebCore/IntRect.h>
#import <pal/spi/cocoa/NSColorSPI.h>
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 */,
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);
+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
<!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>
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());