Bug 1846574 - support webdriver webauthn virtual authenticator functions in marionette r=whimboo,webdriver-reviewers

Depends on D183893

Differential Revision: https://phabricator.services.mozilla.com/D185198
This commit is contained in:
Dana Keeler 2023-08-09 20:47:56 +00:00
Родитель ac14ed9ee0
Коммит 3aafc7b14f
8 изменённых файлов: 543 добавлений и 0 удалений

Просмотреть файл

@ -48,6 +48,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
"chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
waitForInitialNavigationCompleted:
"chrome://remote/content/shared/Navigate.sys.mjs",
webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs",
WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
@ -3185,6 +3186,158 @@ GeckoDriver.prototype.print = async function (cmd) {
return btoa(binaryString);
};
GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
const {
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified,
} = cmd.parameters;
lazy.assert.string(
protocol,
"addVirtualAuthenticator: protocol must be a string"
);
lazy.assert.string(
transport,
"addVirtualAuthenticator: transport must be a string"
);
lazy.assert.boolean(
hasResidentKey,
"addVirtualAuthenticator: hasResidentKey must be a boolean"
);
lazy.assert.boolean(
hasUserVerification,
"addVirtualAuthenticator: hasUserVerification must be a boolean"
);
lazy.assert.boolean(
isUserConsenting,
"addVirtualAuthenticator: isUserConsenting must be a boolean"
);
lazy.assert.boolean(
isUserVerified,
"addVirtualAuthenticator: isUserVerified must be a boolean"
);
return lazy.webauthn.addVirtualAuthenticator(
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified
);
};
GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
const { authenticatorId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"removeVirtualAuthenticator: authenticatorId must be a positiveInteger"
);
lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
};
GeckoDriver.prototype.addCredential = function (cmd) {
const {
authenticatorId,
credentialId,
isResidentCredential,
rpId,
privateKey,
userHandle,
signCount,
} = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"addCredential: authenticatorId must be a positiveInteger"
);
lazy.assert.string(
credentialId,
"addCredential: credentialId must be a string"
);
lazy.assert.boolean(
isResidentCredential,
"addCredential: isResidentCredential must be a boolean"
);
lazy.assert.string(rpId, "addCredential: rpId must be a string");
lazy.assert.string(privateKey, "addCredential: privateKey must be a string");
if (userHandle) {
lazy.assert.string(
userHandle,
"addCredential: userHandle must be a string if present"
);
}
lazy.assert.number(signCount, "addCredential: signCount must be a number");
lazy.webauthn.addCredential(
authenticatorId,
credentialId,
isResidentCredential,
rpId,
privateKey,
userHandle,
signCount
);
};
GeckoDriver.prototype.getCredentials = function (cmd) {
const { authenticatorId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"getCredentials: authenticatorId must be a positiveInteger"
);
return lazy.webauthn.getCredentials(authenticatorId);
};
GeckoDriver.prototype.removeCredential = function (cmd) {
const { authenticatorId, credentialId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"removeCredential: authenticatorId must be a positiveInteger"
);
lazy.assert.string(
credentialId,
"removeCredential: credentialId must be a string"
);
lazy.webauthn.removeCredential(authenticatorId, credentialId);
};
GeckoDriver.prototype.removeAllCredentials = function (cmd) {
const { authenticatorId } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"removeAllCredentials: authenticatorId must be a positiveInteger"
);
lazy.webauthn.removeAllCredentials(authenticatorId);
};
GeckoDriver.prototype.setUserVerified = function (cmd) {
const { authenticatorId, isUserVerified } = cmd.parameters;
lazy.assert.positiveInteger(
authenticatorId,
"setUserVerified: authenticatorId must be a positiveInteger"
);
lazy.assert.boolean(
isUserVerified,
"setUserVerified: isUserVerified must be a boolean"
);
lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
};
GeckoDriver.prototype.setPermission = async function (cmd) {
const { descriptor, state, oneRealm = false } = cmd.parameters;
@ -3327,6 +3480,17 @@ GeckoDriver.prototype.commands = {
"WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
"WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
"WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
// WebAuthn
"WebAuthn:AddVirtualAuthenticator":
GeckoDriver.prototype.addVirtualAuthenticator,
"WebAuthn:RemoveVirtualAuthenticator":
GeckoDriver.prototype.removeVirtualAuthenticator,
"WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
"WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
"WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
"WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
"WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
};
async function exitFullscreen(win) {

Просмотреть файл

@ -36,6 +36,7 @@ remote.jar:
content/marionette/sync.sys.mjs (sync.sys.mjs)
content/marionette/transport.sys.mjs (transport.sys.mjs)
content/marionette/web-reference.sys.mjs (web-reference.sys.mjs)
content/marionette/webauthn.sys.mjs (webauthn.sys.mjs)
#ifdef ENABLE_TESTS
content/marionette/test_dialog.dtd (chrome/test_dialog.dtd)
content/marionette/test_dialog.properties (chrome/test_dialog.properties)

Просмотреть файл

@ -0,0 +1,134 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
lazy,
"webauthnTransport",
"@mozilla.org/webauthn/transport;1",
"nsIWebAuthnTransport"
);
/** @namespace */
export const webauthn = {};
/**
* Add a virtual authenticator.
*
* @param {string} protocol one of "ctap1/u2f", "ctap2", "ctap2_1"
* @param {string} transport one of "usb", "nfc", "ble", "smart-card",
* "hybrid", "internal"
* @param {boolean} hasResidentKey
* @param {boolean} hasUserVerification
* @param {boolean} isUserConsenting
* @param {boolean} isUserVerified
* @returns {id} the id of the added authenticator
*/
webauthn.addVirtualAuthenticator = function (
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified
) {
return lazy.webauthnTransport.addVirtualAuthenticator(
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified
);
};
/**
* Removes a virtual authenticator.
*
* @param {id} authenticatorId the id of the virtual authenticator
*/
webauthn.removeVirtualAuthenticator = function (authenticatorId) {
lazy.webauthnTransport.removeVirtualAuthenticator(authenticatorId);
};
/**
* Adds a credential to a previously-added virtual authenticator.
*
* @param {id} authenticatorId the id of the virtual authenticator
* @param {string} credentialId a probabilistically-unique byte sequence
* identifying a public key credential source and its
* authentication assertions (encoded using Base64url
* Encoding).
* @param {boolean} isResidentCredential if set to true, a client-side
* discoverable credential is created. If set to false, a
* server-side credential is created instead.
* @param {string} rpId The Relying Party ID the credential is scoped to.
* @param {string} privateKey An asymmetric key package containing a single
* private key per RFC5958, encoded using Base64url Encoding.
* @param {string} userHandle The userHandle associated to the credential
* encoded using Base64url Encoding.
* @param {number} signCount The initial value for a signature counter
* associated to the public key credential source.
*/
webauthn.addCredential = function (
authenticatorId,
credentialId,
isResidentCredential,
rpId,
privateKey,
userHandle,
signCount
) {
lazy.webauthnTransport.addCredential(
authenticatorId,
credentialId,
isResidentCredential,
rpId,
privateKey,
userHandle,
signCount
);
};
/**
* Gets all credentials from a virtual authenticator.
*
* @param {id} authenticatorId the id of the virtual authenticator
* @returns {object} the credentials on the authenticator
*/
webauthn.getCredentials = function (authenticatorId) {
return lazy.webauthnTransport.getCredentials(authenticatorId);
};
/**
* Removes a credential from a virtual authenticator.
*
* @param {id} authenticatorId the id of the virtual authenticator
* @param {string} credentialId the id of the credential
*/
webauthn.removeCredential = function (authenticatorId, credentialId) {
lazy.webauthnTransport.removeCredential(authenticatorId, credentialId);
};
/**
* Removes all credentials from a virtual authenticator.
*
* @param {id} authenticatorId the id of the virtual authenticator
*/
webauthn.removeAllCredentials = function (authenticatorId) {
lazy.webauthnTransport.removeAllCredentials(authenticatorId);
};
/**
* Sets the "isUserVerified" bit on a virtual authenticator.
*
* @param {id} authenticatorId the id of the virtual authenticator
* @param {bool} isUserVerified the value to set the "isUserVerified" bit to
*/
webauthn.setUserVerified = function (authenticatorId, isUserVerified) {
lazy.webauthnTransport.setUserVerified(authenticatorId, isUserVerified);
};

Просмотреть файл

@ -602,6 +602,41 @@ export class Capabilities extends Map {
}
break;
case "webauthn:virtualAuthenticators":
lazy.assert.boolean(
v,
lazy.pprint`Expected ${k} to be a boolean, got ${v}`
);
break;
case "webauthn:extension:uvm":
lazy.assert.boolean(
v,
lazy.pprint`Expected ${k} to be a boolean, got ${v}`
);
break;
case "webauthn:extension:prf":
lazy.assert.boolean(
v,
lazy.pprint`Expected ${k} to be a boolean, got ${v}`
);
break;
case "webauthn:extension:largeBlob":
lazy.assert.boolean(
v,
lazy.pprint`Expected ${k} to be a boolean, got ${v}`
);
break;
case "webauthn:extension:credBlob":
lazy.assert.boolean(
v,
lazy.pprint`Expected ${k} to be a boolean, got ${v}`
);
break;
case "moz:accessibilityChecks":
lazy.assert.boolean(
v,
@ -730,6 +765,27 @@ export class Capabilities extends Map {
}
return value;
case "webauthn:virtualAuthenticators":
lazy.assert.boolean(
value,
lazy.pprint`Expected ${name} to be a boolean, got ${value}`
);
return value;
case "webauthn:extension:uvm":
lazy.assert.boolean(
value,
lazy.pprint`Expected ${name} to be a boolean, got ${value}`
);
return value;
case "webauthn:extension:largeBlob":
lazy.assert.boolean(
value,
lazy.pprint`Expected ${name} to be a boolean, got ${value}`
);
return value;
case "moz:firefoxOptions":
return lazy.assert.object(
value,

Просмотреть файл

@ -83,6 +83,30 @@ export class WebDriverSession {
* <dd>Use a WebDriver conforming <i>WebDriver::ElementClick</i>.
* </dl>
*
* <h4>WebAuthn</h4>
*
* <dl>
* <dt><code>webauthn:virtualAuthenticators</code> (boolean)
* <dd>Indicates whether the endpoint node supports all Virtual
* Authenticators commands.
*
* <dt><code>webauthn:extension:uvm</code> (boolean)
* <dd>Indicates whether the endpoint node WebAuthn WebDriver
* implementation supports the User Verification Method extension.
*
* <dt><code>webauthn:extension:prf</code> (boolean)
* <dd>Indicates whether the endpoint node WebAuthn WebDriver
* implementation supports the prf extension.
*
* <dt><code>webauthn:extension:largeBlob</code> (boolean)
* <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
* supports the largeBlob extension.
*
* <dt><code>webauthn:extension:credBlob</code> (boolean)
* <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
* supports the credBlob extension.
* </dl>
*
* <h4>Timeouts object</h4>
*
* <dl>

Просмотреть файл

@ -470,6 +470,51 @@ add_task(function test_Capabilities_fromJSON() {
caps = fromJSON({ webSocketUrl: true });
equal(true, caps.get("webSocketUrl"));
caps = fromJSON({ "webauthn:virtualAuthenticators": true });
equal(true, caps.get("webauthn:virtualAuthenticators"));
caps = fromJSON({ "webauthn:virtualAuthenticators": false });
equal(false, caps.get("webauthn:virtualAuthenticators"));
Assert.throws(
() => fromJSON({ "webauthn:virtualAuthenticators": "foo" }),
/InvalidArgumentError/
);
caps = fromJSON({ "webauthn:extension:uvm": true });
equal(true, caps.get("webauthn:extension:uvm"));
caps = fromJSON({ "webauthn:extension:uvm": false });
equal(false, caps.get("webauthn:extension:uvm"));
Assert.throws(
() => fromJSON({ "webauthn:extension:uvm": "foo" }),
/InvalidArgumentError/
);
caps = fromJSON({ "webauthn:extension:prf": true });
equal(true, caps.get("webauthn:extension:prf"));
caps = fromJSON({ "webauthn:extension:prf": false });
equal(false, caps.get("webauthn:extension:prf"));
Assert.throws(
() => fromJSON({ "webauthn:extension:prf": "foo" }),
/InvalidArgumentError/
);
caps = fromJSON({ "webauthn:extension:largeBlob": true });
equal(true, caps.get("webauthn:extension:largeBlob"));
caps = fromJSON({ "webauthn:extension:largeBlob": false });
equal(false, caps.get("webauthn:extension:largeBlob"));
Assert.throws(
() => fromJSON({ "webauthn:extension:largeBlob": "foo" }),
/InvalidArgumentError/
);
caps = fromJSON({ "webauthn:extension:credBlob": true });
equal(true, caps.get("webauthn:extension:credBlob"));
caps = fromJSON({ "webauthn:extension:credBlob": false });
equal(false, caps.get("webauthn:extension:credBlob"));
Assert.throws(
() => fromJSON({ "webauthn:extension:credBlob": "foo" }),
/InvalidArgumentError/
);
caps = fromJSON({ "moz:accessibilityChecks": true });
equal(true, caps.get("moz:accessibilityChecks"));
caps = fromJSON({ "moz:accessibilityChecks": false });

Просмотреть файл

@ -0,0 +1,63 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
__all__ = ["WebAuthn"]
class WebAuthn(object):
def __init__(self, marionette):
self.marionette = marionette
def add_virtual_authenticator(self, config):
body = {
"protocol": config["protocol"],
"transport": config["transport"],
"hasResidentKey": config.get("hasResidentKey", False),
"hasUserVerification": config.get("hasUserVerification", False),
"isUserConsenting": config.get("isUserConsenting", True),
"isUserVerified": config.get("isUserVerified", False),
}
return self.marionette._send_message(
"WebAuthn:AddVirtualAuthenticator", body, key="value"
)
def remove_virtual_authenticator(self, authenticator_id):
body = {"authenticatorId": authenticator_id}
return self.marionette._send_message(
"WebAuthn:RemoveVirtualAuthenticator", body
)
def add_credential(self, authenticator_id, credential):
body = {
"authenticatorId": authenticator_id,
"credentialId": credential["credentialId"],
"isResidentCredential": credential["isResidentCredential"],
"rpId": credential["rpId"],
"privateKey": credential["privateKey"],
"userHandle": credential.get("userHandle"),
"signCount": credential.get("signCount", 0),
}
return self.marionette._send_message("WebAuthn:AddCredential", body)
def get_credentials(self, authenticator_id):
body = {"authenticatorId": authenticator_id}
return self.marionette._send_message(
"WebAuthn:GetCredentials", body, key="value"
)
def remove_credential(self, authenticator_id, credential_id):
body = {"authenticatorId": authenticator_id, "credentialId": credential_id}
return self.marionette._send_message("WebAuthn:RemoveCredential", body)
def remove_all_credentials(self, authenticator_id):
body = {"authenticatorId": authenticator_id}
return self.marionette._send_message("WebAuthn:RemoveAllCredentials", body)
def set_user_verified(self, authenticator_id, uv):
body = {
"authenticatorId": authenticator_id,
"isUserVerified": uv["isUserVerified"],
}
return self.marionette._send_message("WebAuthn:SetUserVerified", body)

Просмотреть файл

@ -264,3 +264,59 @@ class TestCapabilityMatching(MarionetteTestCase):
self.marionette.start_session({"webSocketUrl": True})
# Remote Agent is not active by default
self.assertNotIn("webSocketUrl", self.marionette.session_capabilities)
def test_webauthn_extension_cred_blob(self):
for value in ["", 42, {}, []]:
print(" type {}".format(type(value)))
with self.assertRaises(errors.SessionNotCreatedException):
self.marionette.start_session({"webauthn:extension:credBlob": value})
self.delete_session()
self.marionette.start_session({"webauthn:extension:credBlob": True})
self.assertTrue(
self.marionette.session_capabilities["webauthn:extension:credBlob"]
)
def test_webauthn_extension_large_blob(self):
for value in ["", 42, {}, []]:
print(" type {}".format(type(value)))
with self.assertRaises(errors.SessionNotCreatedException):
self.marionette.start_session({"webauthn:extension:largeBlob": value})
self.delete_session()
self.marionette.start_session({"webauthn:extension:largeBlob": True})
self.assertTrue(
self.marionette.session_capabilities["webauthn:extension:largeBlob"]
)
def test_webauthn_extension_prf(self):
for value in ["", 42, {}, []]:
print(" type {}".format(type(value)))
with self.assertRaises(errors.SessionNotCreatedException):
self.marionette.start_session({"webauthn:extension:prf": value})
self.delete_session()
self.marionette.start_session({"webauthn:extension:prf": True})
self.assertTrue(self.marionette.session_capabilities["webauthn:extension:prf"])
def test_webauthn_extension_uvm(self):
for value in ["", 42, {}, []]:
print(" type {}".format(type(value)))
with self.assertRaises(errors.SessionNotCreatedException):
self.marionette.start_session({"webauthn:extension:uvm": value})
self.delete_session()
self.marionette.start_session({"webauthn:extension:uvm": True})
self.assertTrue(self.marionette.session_capabilities["webauthn:extension:uvm"])
def test_webauthn_virtual_authenticators(self):
for value in ["", 42, {}, []]:
print(" type {}".format(type(value)))
with self.assertRaises(errors.SessionNotCreatedException):
self.marionette.start_session({"webauthn:virtualAuthenticators": value})
self.delete_session()
self.marionette.start_session({"webauthn:virtualAuthenticators": True})
self.assertTrue(
self.marionette.session_capabilities["webauthn:virtualAuthenticators"]
)