diff --git a/common/src/web/virtualAuthenticator.html b/common/src/web/virtualAuthenticator.html
new file mode 100644
index 0000000000000..20e98044d8795
--- /dev/null
+++ b/common/src/web/virtualAuthenticator.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+ Virtual Authenticator Tests
+
+
+
+
+ Virtual Authenticator Tests
+
+
+
+
+
\ No newline at end of file
diff --git a/javascript/node/selenium-webdriver/lib/command.js b/javascript/node/selenium-webdriver/lib/command.js
index 290a77f5889f0..c34875f396b21 100644
--- a/javascript/node/selenium-webdriver/lib/command.js
+++ b/javascript/node/selenium-webdriver/lib/command.js
@@ -164,6 +164,15 @@ const Name = {
FIND_ELEMENT_FROM_SHADOWROOT: 'findElementFromShadowRoot',
FIND_ELEMENTS_FROM_SHADOWROOT: 'findElementsFromShadowRoot',
+ // Virtual Authenticator Commands
+ ADD_VIRTUAL_AUTHENTICATOR : 'addVirtualAuthenticator',
+ REMOVE_VIRTUAL_AUTHENTICATOR : 'removeVirtualAuthenticator',
+ ADD_CREDENTIAL : 'addCredential',
+ GET_CREDENTIALS : 'getCredentials',
+ REMOVE_CREDENTIAL : 'removeCredential',
+ REMOVE_ALL_CREDENTIALS : 'removeAllCredentials',
+ SET_USER_VERIFIED : 'setUserVerified',
+
GET_AVAILABLE_LOG_TYPES: 'getAvailableLogTypes',
GET_LOG: 'getLog',
diff --git a/javascript/node/selenium-webdriver/lib/http.js b/javascript/node/selenium-webdriver/lib/http.js
index f96faed6e6794..178f3ff7737ab 100644
--- a/javascript/node/selenium-webdriver/lib/http.js
+++ b/javascript/node/selenium-webdriver/lib/http.js
@@ -347,6 +347,15 @@ const W3C_COMMAND_MAP = new Map([
// Server Extensions
[cmd.Name.UPLOAD_FILE, post('/session/:sessionId/se/file')],
+
+ // Virtual Authenticator
+ [cmd.Name.ADD_VIRTUAL_AUTHENTICATOR, post('/session/:sessionId/webauthn/authenticator')],
+ [cmd.Name.REMOVE_VIRTUAL_AUTHENTICATOR, del('/session/:sessionId/webauthn/authenticator/:authenticatorId')],
+ [cmd.Name.ADD_CREDENTIAL, post('/session/:sessionId/webauthn/authenticator/:authenticatorId/credential')],
+ [cmd.Name.GET_CREDENTIALS, get('/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials')],
+ [cmd.Name.REMOVE_CREDENTIAL, del('/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials/:credentialId')],
+ [cmd.Name.REMOVE_ALL_CREDENTIALS, del('/session/:sessionId/webauthn/authenticator/:authenticatorId/credentials')],
+ [cmd.Name.SET_USER_VERIFIED, post('/session/:sessionId/webauthn/authenticator/:authenticatorId/uv')],
])
/**
@@ -472,6 +481,7 @@ class Executor {
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
let httpResponse = /** @type {!Response} */ (response)
+
let { isW3C, value } = parseHttpResponse(command, httpResponse)
if (command.getName() === cmd.Name.NEW_SESSION) {
@@ -530,6 +540,7 @@ function parseHttpResponse(command, httpResponse) {
}
let parsed = tryParse(httpResponse.body)
+
if (parsed && typeof parsed === 'object') {
let value = parsed.value
let isW3C =
diff --git a/javascript/node/selenium-webdriver/lib/test/fileserver.js b/javascript/node/selenium-webdriver/lib/test/fileserver.js
index 80cfb9e389659..9fb6f8a556e9b 100644
--- a/javascript/node/selenium-webdriver/lib/test/fileserver.js
+++ b/javascript/node/selenium-webdriver/lib/test/fileserver.js
@@ -106,6 +106,7 @@ const Pages = (function () {
addPage('webComponents', 'webComponents.html')
addPage('xhtmlTestPage', 'xhtmlTest.html')
addPage('uploadInvisibleTestPage', 'upload_invisible.html')
+ addPage('virtualAuthenticator', 'virtualAuthenticator.html')
return pages
})()
diff --git a/javascript/node/selenium-webdriver/lib/virtual_authenticator.js b/javascript/node/selenium-webdriver/lib/virtual_authenticator.js
new file mode 100644
index 0000000000000..3bca125ac8128
--- /dev/null
+++ b/javascript/node/selenium-webdriver/lib/virtual_authenticator.js
@@ -0,0 +1,230 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict'
+
+/**
+ * Options for the creation of virtual authenticators.
+ * @see http://w3c.github.io/webauthn/#sctn-automation
+ */
+class VirtualAuthenticatorOptions {
+
+ static Protocol = {
+ "CTAP2": 'ctap2',
+ "U2F": 'ctap1/u2f',
+ }
+
+ static Transport = {
+ "BLE": 'ble',
+ "USB": 'usb',
+ "NFC": 'nfc',
+ "INTERNAL": 'internal',
+ }
+
+ /**
+ * Constructor to initialise VirtualAuthenticatorOptions object.
+ */
+ constructor() {
+ this._protocol = VirtualAuthenticatorOptions.Protocol["CTAP2"]
+ this._transport = VirtualAuthenticatorOptions.Transport["USB"]
+ this._hasResidentKey = false
+ this._hasUserVerification = false
+ this._isUserConsenting = true
+ this._isUserVerified = false
+ }
+
+ getProtocol() {
+ return this._protocol
+ }
+
+ setProtocol(protocol) {
+ this._protocol = protocol
+ }
+
+ getTransport() {
+ return this._transport
+ }
+
+ setTransport(transport) {
+ this._transport = transport
+ }
+
+ getHasResidentKey() {
+ return this._hasResidentKey
+ }
+
+ setHasResidentKey(value) {
+ this._hasResidentKey = value
+ }
+
+ getHasUserVerification() {
+ return this._hasUserVerification
+ }
+
+ setHasUserVerification(value) {
+ this._hasUserVerification = value
+ }
+
+ getIsUserConsenting() {
+ return this._isUserConsenting
+ }
+
+ setIsUserConsenting(value) {
+ this._isUserConsenting = value
+ }
+
+ getIsUserVerified() {
+ return this._isUserVerified
+ }
+
+ setIsUserVerified(value) {
+ this._isUserVerified = value
+ }
+
+ toDict() {
+ return {
+ "protocol": this.getProtocol(),
+ "transport": this.getTransport(),
+ "hasResidentKey": this.getHasResidentKey(),
+ "hasUserVerification": this.getHasUserVerification(),
+ "isUserConsenting": this.getIsUserConsenting(),
+ "isUserVerified": this.getIsUserVerified(),
+
+ }
+ }
+}
+
+/**
+ * A credential stored in a virtual authenticator.
+ * @see https://w3c.github.io/webauthn/#credential-parameters
+ */
+class Credential {
+ constructor(
+ credentialId,
+ isResidentCredential,
+ rpId,
+ userHandle,
+ privateKey,
+ signCount
+ ) {
+ this._id = credentialId
+ this._isResidentCredential = isResidentCredential
+ this._rpId = rpId
+ this._userHandle = userHandle
+ this._privateKey = privateKey
+ this._signCount = signCount
+ }
+
+ id() {
+ return this._id
+ }
+
+ isResidentCredential() {
+ return this._isResidentCredential
+ }
+
+ rpId() {
+ return this._rpId
+ }
+
+ userHandle() {
+ if (this._userHandle != null) {
+ return this._userHandle
+ }
+ return null
+ }
+
+ privateKey() {
+ return this._privateKey
+ }
+
+ signCount() {
+ return this._signCount
+ }
+
+ /**
+ * Creates a resident (i.e. stateless) credential.
+ * @param id Unique base64 encoded string.
+ * @param rpId Relying party identifier.
+ * @param userHandle userHandle associated to the credential. Must be Base64 encoded string.
+ * @param privateKey Base64 encoded PKCS
+ * @param signCount intital value for a signature counter.
+ * @returns A resident credential
+ */
+ createResidentCredential(id, rpId, userHandle, privateKey, signCount) {
+ return new Credential(id, true, rpId, userHandle, privateKey, signCount)
+ }
+
+ /**
+ * Creates a non resident (i.e. stateless) credential.
+ * @param id Unique base64 encoded string.
+ * @param rpId Relying party identifier.
+ * @param privateKey Base64 encoded PKCS
+ * @param signCount intital value for a signature counter.
+ * @returns A non-resident credential
+ */
+ createNonResidentCredential(id, rpId, privateKey, signCount) {
+ return new Credential(id, false, rpId, null, privateKey, signCount)
+ }
+
+ toDict() {
+ let credentialData = {
+ 'credentialId': Buffer.from(this._id).toString('base64url'),
+ 'isResidentCredential': this._isResidentCredential,
+ 'rpId': this._rpId,
+ 'privateKey': Buffer.from(this._privateKey, 'binary').toString('base64url'),
+ 'signCount': this._signCount,
+ }
+
+ if (this.userHandle() != null) {
+ credentialData['userHandle'] = Buffer.from(this._userHandle).toString('base64url')
+ }
+
+ return credentialData
+ }
+
+ /**
+ * Creates a credential from a map.
+ */
+ fromDict(data) {
+ let id = new Uint8Array(Buffer.from(data['credentialId'], 'base64url'))
+ let isResidentCredential = data['isResidentCredential']
+ let rpId = data['rpId']
+ let privateKey = Buffer.from(data['privateKey'], 'base64url').toString('binary')
+ let signCount = data['signCount']
+ let userHandle
+
+ if ('userHandle' in data) {
+ userHandle = new Uint8Array(Buffer.from(data['userHandle'], 'base64url'))
+ } else {
+ userHandle = null
+ }
+ return new Credential(
+ id,
+ isResidentCredential,
+ rpId,
+ userHandle,
+ privateKey,
+ signCount
+ )
+ }
+}
+
+module.exports = {
+ Credential,
+ VirtualAuthenticatorOptions,
+}
diff --git a/javascript/node/selenium-webdriver/lib/webdriver.js b/javascript/node/selenium-webdriver/lib/webdriver.js
index a7340e2e3216a..a98d0b87e880f 100644
--- a/javascript/node/selenium-webdriver/lib/webdriver.js
+++ b/javascript/node/selenium-webdriver/lib/webdriver.js
@@ -37,6 +37,8 @@ const { Capabilities } = require('./capabilities')
const path = require('path')
const { NoSuchElementError } = require('./error')
const cdpTargets = ['page', 'browser']
+const Credential =
+ require('./virtual_authenticator').Credential
// Capability names that are defined in the W3C spec.
const W3C_CAPABILITY_NAMES = new Set([
@@ -679,6 +681,9 @@ class WebDriver {
/** @private @const {(function(this: void): ?|undefined)} */
this.onQuit_ = onQuit
+
+ /** @private {./virtual_authenticator}*/
+ this.authenticatorId_ = null
}
/**
@@ -730,6 +735,7 @@ class WebDriver {
/** @override */
async execute(command) {
command.setParameter('sessionId', this.session_)
+
let parameters = await toWireValue(command.getParameters())
command.setParameters(parameters)
let value = await this.executor_.execute(command)
@@ -1515,6 +1521,113 @@ class WebDriver {
}
})
}
+
+ /**
+ *
+ * @returns The value of authenticator ID added
+ */
+ virtualAuthenticatorId() {
+ return this.authenticatorId_
+ }
+
+ /**
+ * Adds a virtual authenticator with the given options.
+ * @param options VirtualAuthenticatorOptions object to set authenticator optons.
+ */
+ async addVirtualAuthenticator(options) {
+ this.authenticatorId_ = await this.execute(
+ new command.Command(command.Name.ADD_VIRTUAL_AUTHENTICATOR).setParameters(
+ options.toDict()
+ )
+ )
+ }
+
+ /**
+ * Removes a previously added virtual authenticator. The authenticator is no
+ * longer valid after removal, so no methods may be called.
+ */
+ async removeVirtualAuthenticator() {
+ await this.execute(
+ new command.Command(
+ command.Name.REMOVE_VIRTUAL_AUTHENTICATOR
+ ).setParameter('authenticatorId', this.authenticatorId_)
+ )
+ this.authenticatorId_ = null
+ }
+
+ /**
+ * Injects a credential into the authenticator.
+ * @param credential Credential to be added
+ */
+ async addCredential(credential) {
+ credential = credential.toDict()
+ credential['authenticatorId'] = this.authenticatorId_
+ await this.execute(
+ new command.Command(command.Name.ADD_CREDENTIAL).setParameters(credential)
+ )
+ }
+
+ /**
+ *
+ * @returns The list of credentials owned by the authenticator.
+ */
+ async getCredentials() {
+ let credential_data = await this.execute(
+ new command.Command(command.Name.GET_CREDENTIALS).setParameter(
+ 'authenticatorId',
+ this.virtualAuthenticatorId()
+ )
+ )
+ var credential_list = []
+ for(var i = 0; i < credential_data.length; i++) {
+ credential_list.push(new Credential().fromDict(credential_data[i]))
+ }
+ return credential_list
+ }
+
+ /**
+ * Removes a credential from the authenticator.
+ * @param credential_id The ID of the credential to be removed.
+ */
+ async removeCredential(credential_id) {
+
+ // If credential_id is not a base64url, then convert it to base64url.
+ if (Array.isArray(credential_id)) {
+ credential_id = Buffer.from(credential_id).toString('base64url')
+ }
+
+ await this.execute(
+ new command.Command(command.Name.REMOVE_CREDENTIAL)
+ .setParameter('credentialId', credential_id)
+ .setParameter('authenticatorId', this.authenticatorId_)
+ )
+ }
+
+ /**
+ * Removes all the credentials from the authenticator.
+ */
+ async removeAllCredentials() {
+ await this.execute(
+ new command.Command(command.Name.REMOVE_ALL_CREDENTIALS).setParameter(
+ 'authenticatorId',
+ this.authenticatorId_
+ )
+ )
+ }
+
+ /**
+ * Sets whether the authenticator will simulate success or fail on user verification.
+ * @param verified true if the authenticator will pass user verification, false otherwise.
+ */
+ async setUserVerified(verified) {
+ await this.execute(
+ new command.Command(command.Name.SET_USER_VERIFIED)
+ .setParameter(
+ 'authenticatorId',
+ this.authenticatorId_
+ ).setParameter('isUserVerified', verified)
+ )
+ }
}
/**
diff --git a/javascript/node/selenium-webdriver/net/index.js b/javascript/node/selenium-webdriver/net/index.js
index f87119bcb6ace..e3bab3ff44ec5 100644
--- a/javascript/node/selenium-webdriver/net/index.js
+++ b/javascript/node/selenium-webdriver/net/index.js
@@ -73,7 +73,11 @@ function getAddress(family = 'IPv4') {
* @return {(string|undefined)} The IP address or undefined if not available.
*/
function getLoopbackAddress(family = 'IPv4') {
- return getIPAddress(true, family)
+ let address = getIPAddress(true, family)
+ if (address === '127.0.0.1') {
+ address = 'localhost'
+ }
+ return address
}
/**
diff --git a/javascript/node/selenium-webdriver/test/lib/credentials_test.js b/javascript/node/selenium-webdriver/test/lib/credentials_test.js
new file mode 100644
index 0000000000000..0e09536fa719c
--- /dev/null
+++ b/javascript/node/selenium-webdriver/test/lib/credentials_test.js
@@ -0,0 +1,230 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict'
+
+const assert = require('assert')
+const virtualAuthenticatorCredential =
+ require('../../lib/virtual_authenticator').Credential
+
+describe('Credentials', function () {
+ const BASE64_ENCODED_PK = `MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr
+ MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuBGV
+ oPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi9AyQ
+ FR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1PvSqXlq
+ GjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsuizAgyPuQ0
+ +j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/XYY22ecYxM
+ 8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtibRXz5FcNld9MgD
+ /Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swykoQKBgQD8hCsp6FIQ
+ 5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMCS6S64/qzZEqijLCqe
+ pwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnKws1t5GapfE1rmC/h4ol
+ L2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63ojKjegxHIyYDKRZNVUR/d
+ xAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM/r8PSflNHQKBgDnWgBh6OQncChPUlOLv9FMZPR1ZOfqLCYrjYEqi
+ uzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8VASOmqM1ml667axeZDIR867ZG8
+ K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6BhZC7z8mx+pnJODU3cYukxv3WTct
+ lUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJwWkBwYADmkfTRmHDvqzQSSvoC2S7aa
+ 9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KINpLwcR8fqaYOdAHWWz636osVEqosRrH
+ zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H
+ BYGpI8g==`
+
+ const data = {
+ _id: new Uint8Array([1, 2, 3, 4]),
+ rpId: 'localhost',
+ userHandle: new Uint8Array([1]),
+ privateKey: Buffer.from(BASE64_ENCODED_PK, 'base64').toString('binary'),
+ signCount: 0,
+ }
+
+ it('can testRkEnabledCredential', function () {
+ const { _id, rpId, userHandle, privateKey, signCount } = data
+ const credential =
+ new virtualAuthenticatorCredential().createResidentCredential(
+ _id,
+ rpId,
+ userHandle,
+ privateKey,
+ signCount
+ )
+
+ let testCredentialId = new Uint8Array([1, 2, 3, 4])
+
+ /**
+ * Checking if credential.id() matches with testCredentialId. Both values are
+ * arrays so we check if the lengths of both are equal and if one array has
+ * all its elements in the other array and vice-versa.
+ */
+ assert.equal(
+ credential.id().length == testCredentialId.length &&
+ credential.id().every((item) => testCredentialId.includes(item)) &&
+ testCredentialId.every((item) => credential.id().includes(item)),
+ true
+ )
+ if (credential.isResidentCredential() == true) {
+ assert(true)
+ } else {
+ assert(false)
+ }
+ assert.equal(credential.rpId(), 'localhost')
+
+ let testUserHandle = new Uint8Array([1])
+
+ /**
+ * Checking if credential.userHandle() matches with testUserHandle. Both values are
+ * arrays so we check if the lengths of both are equal and if one array has
+ * all its elements in the other array and vice-versa.
+ */
+ assert.equal(
+ credential.userHandle().length == testUserHandle.length &&
+ credential
+ .userHandle()
+ .every((item) => testUserHandle.includes(item)) &&
+ testUserHandle.every((item) => credential.userHandle().includes(item)),
+ true
+ )
+ assert.equal(
+ credential.privateKey(),
+ Buffer.from(BASE64_ENCODED_PK, 'base64url').toString('binary')
+ )
+ assert.equal(credential.signCount(), 0)
+ })
+
+ it('can testRkDisabledCredential', function () {
+ const { _id, rpId, userHandle, privateKey, signCount } = data
+ const credential =
+ new virtualAuthenticatorCredential().createNonResidentCredential(
+ _id,
+ rpId,
+ privateKey,
+ signCount
+ )
+
+ let testCredentialId = new Uint8Array([1, 2, 3, 4])
+
+ /**
+ * Checking if credential.id() matches with testCredentialId. Both values are
+ * arrays so we check if the lengths of both are equal and if one array has
+ * all its elements in the other array and vice-versa.
+ */
+ assert.equal(
+ credential.id().length == testCredentialId.length &&
+ credential.id().every((item) => testCredentialId.includes(item)) &&
+ testCredentialId.every((item) => credential.id().includes(item)),
+ true
+ )
+
+ if (credential.isResidentCredential() == false) {
+ assert(true)
+ } else {
+ assert(false)
+ }
+
+ if (credential.userHandle() == null) {
+ assert(true)
+ } else {
+ assert(false)
+ }
+ })
+
+ it('can testToDict', function () {
+ const { _id, rpId, userHandle, privateKey, signCount } = data
+ const credential =
+ new virtualAuthenticatorCredential().createResidentCredential(
+ _id,
+ rpId,
+ userHandle,
+ privateKey,
+ signCount
+ )
+
+ let credential_dict = credential.toDict()
+ assert.equal(
+ credential_dict['credentialId'],
+ Buffer.from(new Uint8Array([1, 2, 3, 4])).toString('base64url')
+ )
+
+ if (credential_dict['isResidentCredential'] == true) {
+ assert(true)
+ } else {
+ assert(false)
+ }
+
+ assert.equal(credential_dict['rpId'], 'localhost')
+ assert.equal(
+ credential_dict['userHandle'],
+ Buffer.from(new Uint8Array([1])).toString('base64url')
+ )
+ assert.equal(
+ credential_dict['privateKey'],
+ Buffer.from(privateKey, 'binary').toString('base64url')
+ )
+ assert.equal(credential_dict['signCount'], 0)
+ })
+
+ it('can testFromDict', function () {
+ let credential_data = {
+ credentialId: Buffer.from(new Uint8Array([1, 2, 3, 4])).toString(
+ 'base64url'
+ ),
+ isResidentCredential: true,
+ rpId: 'localhost',
+ userHandle: Buffer.from(new Uint8Array([1])).toString('base64url'),
+ privateKey: BASE64_ENCODED_PK,
+ signCount: 0,
+ }
+
+ let credential = new virtualAuthenticatorCredential().fromDict(
+ credential_data
+ )
+ let testCredentialId = new Uint8Array([1, 2, 3, 4])
+ assert.equal(
+ credential.id().length == testCredentialId.length &&
+ credential.id().every((item) => testCredentialId.includes(item)) &&
+ testCredentialId.every((item) => credential.id().includes(item)),
+ true
+ )
+
+ if (credential.isResidentCredential() == true) {
+ assert(true)
+ } else {
+ assert(false)
+ }
+
+ assert.equal(credential.rpId(), 'localhost')
+
+ let testUserHandle = new Uint8Array([1])
+
+ /**
+ * Checking if credential.userHandle() matches with testUserHandle. Both values are
+ * arrays so we check if the lengths of both are equal and if one array has
+ * all its elements in the other array and vice-versa.
+ */
+ assert.equal(
+ credential.userHandle().length == testUserHandle.length &&
+ credential
+ .userHandle()
+ .every((item) => testUserHandle.includes(item)) &&
+ testUserHandle.every((item) => credential.userHandle().includes(item)),
+ true
+ )
+
+ assert.equal(
+ credential.privateKey(),
+ Buffer.from(BASE64_ENCODED_PK, 'base64url').toString('binary')
+ )
+ assert.equal(credential.signCount(), 0)
+ })
+})
diff --git a/javascript/node/selenium-webdriver/test/lib/virtualauthenticatoroptions_test.js b/javascript/node/selenium-webdriver/test/lib/virtualauthenticatoroptions_test.js
new file mode 100644
index 0000000000000..852d57a94a61b
--- /dev/null
+++ b/javascript/node/selenium-webdriver/test/lib/virtualauthenticatoroptions_test.js
@@ -0,0 +1,118 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict'
+
+const assert = require('assert')
+const virtualAuthenticatorOptions =
+ require('../../lib/virtual_authenticator').VirtualAuthenticatorOptions
+
+let options
+
+describe('VirtualAuthenticatorOptions', function () {
+ beforeEach(function () {
+ options = new virtualAuthenticatorOptions()
+ })
+
+ it('can testSetTransport', function () {
+ options.setTransport(virtualAuthenticatorOptions.Transport['USB'])
+ assert.equal(
+ options.getTransport(),
+ virtualAuthenticatorOptions.Transport['USB']
+ )
+ })
+
+ it('can testGetTransport', function () {
+ options._transport = virtualAuthenticatorOptions.Transport['NFC']
+ assert.equal(
+ options.getTransport(),
+ virtualAuthenticatorOptions.Transport['NFC']
+ )
+ })
+
+ it('can testSetProtocol', function () {
+ options.setProtocol(virtualAuthenticatorOptions.Protocol['U2F'])
+ assert.equal(
+ options.getProtocol(),
+ virtualAuthenticatorOptions.Protocol['U2F']
+ )
+ })
+
+ it('can testGetProtocol', function () {
+ options._protocol = virtualAuthenticatorOptions.Protocol['CTAP2']
+ assert.equal(
+ options.getProtocol(),
+ virtualAuthenticatorOptions.Protocol['CTAP2']
+ )
+ })
+
+ it('can testSetHasResidentKey', function () {
+ options.setHasResidentKey(true)
+ assert.equal(options.getHasResidentKey(), true)
+ })
+
+ it('can testGetHasResidentKey', function () {
+ options._hasResidentKey = false
+ assert.equal(options.getHasResidentKey(), false)
+ })
+
+ it('can testSetHasUserVerification', function () {
+ options.setHasUserVerification(true)
+ assert.equal(options.getHasUserVerification(), true)
+ })
+
+ it('can testGetHasUserVerification', function () {
+ options._hasUserVerification = false
+ assert.equal(options.getHasUserVerification(), false)
+ })
+
+ it('can testSetIsUserConsenting', function () {
+ options.setIsUserConsenting(true)
+ assert.equal(options.getIsUserConsenting(), true)
+ })
+
+ it('can testGetIsUserConsenting', function () {
+ options._isUserConsenting = false
+ assert.equal(options.getIsUserConsenting(), false)
+ })
+
+ it('can testSetIsUserVerified', function () {
+ options.setIsUserVerified(true)
+ assert.equal(options.getIsUserVerified(), true)
+ })
+
+ it('can testGetIsUserVerified', function () {
+ options._isUserVerified = false
+ assert.equal(options.getIsUserVerified(), false)
+ })
+
+ it('can testToDictWithDefaults', function () {
+ let default_options = options.toDict()
+ assert.equal(
+ default_options['transport'],
+ virtualAuthenticatorOptions.Transport['USB']
+ )
+ assert.equal(
+ default_options['protocol'],
+ virtualAuthenticatorOptions.Protocol['CTAP2']
+ )
+ assert.equal(default_options['hasResidentKey'], false)
+ assert.equal(default_options['hasUserVerification'], false)
+ assert.equal(default_options['isUserConsenting'], true)
+ assert.equal(default_options['isUserVerified'], false)
+ })
+})
\ No newline at end of file
diff --git a/javascript/node/selenium-webdriver/test/virtualAuthenticator_test.js b/javascript/node/selenium-webdriver/test/virtualAuthenticator_test.js
new file mode 100644
index 0000000000000..383c52ed1f759
--- /dev/null
+++ b/javascript/node/selenium-webdriver/test/virtualAuthenticator_test.js
@@ -0,0 +1,519 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict'
+
+const assert = require('assert')
+const virtualAuthenticatorCredential =
+ require('../lib/virtual_authenticator').Credential
+const virtualAuthenticatorOptions =
+ require('../lib/virtual_authenticator').VirtualAuthenticatorOptions
+const { ignore, suite } = require('../lib/test')
+const { Browser } = require('../lib/capabilities')
+const fileserver = require('../lib/test/fileserver')
+const invalidArgumentError = require('../lib/error').InvalidArgumentError
+
+async function createRkEnabledU2fAuthenticator(driver) {
+ let options
+ options = new virtualAuthenticatorOptions()
+ options.setProtocol(virtualAuthenticatorOptions.Protocol['U2F'])
+ options.setHasResidentKey(true)
+ await driver.addVirtualAuthenticator(options)
+ return driver
+}
+
+async function createRkDisabledU2fAuthenticator(driver) {
+ let options
+ options = new virtualAuthenticatorOptions()
+ options.setProtocol(virtualAuthenticatorOptions.Protocol['U2F'])
+ options.setHasResidentKey(false)
+ await driver.addVirtualAuthenticator(options)
+ return driver
+}
+
+async function createRkEnabledCTAP2Authenticator(driver) {
+ let options
+ options = new virtualAuthenticatorOptions()
+ options.setProtocol(virtualAuthenticatorOptions.Protocol['CTAP2'])
+ options.setHasResidentKey(true)
+ options.setHasUserVerification(true)
+ options.setIsUserVerified(true)
+ await driver.addVirtualAuthenticator(options)
+ return driver
+}
+
+async function createRkDisabledCTAP2Authenticator(driver) {
+ let options
+ options = new virtualAuthenticatorOptions()
+ options.setProtocol(virtualAuthenticatorOptions.Protocol['CTAP2'])
+ options.setHasResidentKey(false)
+ options.setHasUserVerification(true)
+ options.setIsUserVerified(true)
+ await driver.addVirtualAuthenticator(options)
+ return driver
+}
+
+async function getAssertionFor(driver, credentialId) {
+ return await driver.executeAsyncScript(
+ 'getCredential([{' +
+ ' "type": "public-key",' +
+ ' "id": Uint8Array.from(arguments[0]),' +
+ '}]).then(arguments[arguments.length - 1]);',
+ credentialId
+ )
+}
+
+/**
+ * Checks if the two arrays are equal or not. Conditions to check are:
+ * 1. If the length of both arrays is equal
+ * 2. If all elements of array1 are present in array2
+ * 3. If all elements of array2 are present in array1
+ * @param array1 First array to be checked for equality
+ * @param array2 Second array to be checked for equality
+ * @returns true if equal, otherwise false.
+ */
+function arraysEqual(array1, array2) {
+ return (
+ array1.length == array2.length &&
+ array1.every((item) => array2.includes(item)) &&
+ array2.every((item) => array1.includes(item))
+ )
+}
+
+/**
+ * * * * * * TESTS * * * * *
+ */
+
+suite(function (env) {
+ /**
+ * A pkcs#8 encoded encrypted RSA private key as a base64url string.
+ */
+ const BASE64_ENCODED_PK =
+ 'MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr' +
+ 'MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuBGV' +
+ 'oPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi9AyQ' +
+ 'FR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1PvSqXlq' +
+ 'GjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsuizAgyPuQ0' +
+ '+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/XYY22ecYxM' +
+ '8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtibRXz5FcNld9MgD' +
+ '/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swykoQKBgQD8hCsp6FIQ' +
+ '5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMCS6S64/qzZEqijLCqe' +
+ 'pwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnKws1t5GapfE1rmC/h4ol' +
+ 'L2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63ojKjegxHIyYDKRZNVUR/d' +
+ 'xAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM/r8PSflNHQKBgDnWgBh6OQncChPUlOLv9FMZPR1ZOfqLCYrjYEqi' +
+ 'uzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8VASOmqM1ml667axeZDIR867ZG8' +
+ 'K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6BhZC7z8mx+pnJODU3cYukxv3WTct' +
+ 'lUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJwWkBwYADmkfTRmHDvqzQSSvoC2S7aa' +
+ '9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KINpLwcR8fqaYOdAHWWz636osVEqosRrH' +
+ 'zJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fBnzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4H' +
+ 'BYGpI8g=='
+
+ const browsers = (...args) => env.browsers(...args)
+ let driver
+
+ beforeEach(async function () {
+ driver = await env
+ .builder()
+ .build()
+ await driver.get(fileserver.Pages.virtualAuthenticator)
+ assert.strictEqual(await driver.getTitle(), 'Virtual Authenticator Tests')
+ })
+
+ afterEach(async function () {
+ // if (driver.virtualAuthenticatorId() != null) {
+ // await driver.removeVirtualAuthenticator()
+ // }
+ return driver.quit()
+ })
+
+ describe('VirtualAuthenticator Test Suit 2', function () {
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test create authenticator',
+ async function () {
+ /**
+ * Register a credential on the Virtual Authenticator.
+ */
+ driver = await createRkDisabledU2fAuthenticator(driver)
+ assert((await driver.virtualAuthenticatorId()) != null)
+
+ let response = await driver.executeAsyncScript(
+ 'registerCredential().then(arguments[arguments.length - 1]);'
+ )
+ assert(response['status'] === 'OK')
+
+ /**
+ * Attempt to use the credential to get an assertion.
+ */
+ response = await getAssertionFor(driver, response.credential.rawId)
+ assert(response['status'] === 'OK')
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test remove authenticator',
+ async function () {
+ let options = new virtualAuthenticatorOptions()
+ await driver.addVirtualAuthenticator(options)
+ assert((await driver.virtualAuthenticatorId()) != null)
+
+ await driver.removeVirtualAuthenticator()
+ assert((await driver.virtualAuthenticatorId()) == null)
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test add non-resident credential',
+ async function () {
+ /**
+ * Add a non-resident credential using the testing API.
+ */
+ driver = await createRkDisabledCTAP2Authenticator(driver)
+ let credential =
+ new virtualAuthenticatorCredential().createNonResidentCredential(
+ new Uint8Array([1, 2, 3, 4]),
+ 'localhost',
+ Buffer.from(BASE64_ENCODED_PK, 'base64').toString('binary'),
+ 0
+ )
+ await driver.addCredential(credential)
+
+ /**
+ * Attempt to use the credential to generate an assertion.
+ */
+ let response = await getAssertionFor(driver, [1, 2, 3, 4])
+ assert(response['status'] === 'OK')
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test add non-resident credential when authenticator uses U2F protocol',
+ async function () {
+ /**
+ * Add a non-resident credential using the testing API.
+ */
+ driver = await createRkDisabledU2fAuthenticator(driver)
+
+ /**
+ * A pkcs#8 encoded unencrypted EC256 private key as a base64url string.
+ */
+ const base64EncodedPK =
+ 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q' +
+ 'hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU' +
+ 'RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB'
+
+ let credential =
+ new virtualAuthenticatorCredential().createNonResidentCredential(
+ new Uint8Array([1, 2, 3, 4]),
+ 'localhost',
+ Buffer.from(base64EncodedPK, 'base64').toString('binary'),
+ 0
+ )
+ await driver.addCredential(credential)
+
+ /**
+ * Attempt to use the credential to generate an assertion.
+ */
+ let response = await getAssertionFor(driver, [1, 2, 3, 4])
+ assert(response['status'] === 'OK')
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test add resident credential',
+ async function () {
+ /**
+ * Add a resident credential using the testing API.
+ */
+ driver = await createRkEnabledCTAP2Authenticator(driver)
+
+ let credential =
+ new virtualAuthenticatorCredential().createResidentCredential(
+ new Uint8Array([1, 2, 3, 4]),
+ 'localhost',
+ new Uint8Array([1]),
+ Buffer.from(BASE64_ENCODED_PK, 'base64').toString('binary'),
+ 0
+ )
+ await driver.addCredential(credential)
+
+ /**
+ * Attempt to use the credential to generate an assertion. Notice we use an
+ * empty allowCredentials array.
+ */
+ let response = await driver.executeAsyncScript(
+ 'getCredential([]).then(arguments[arguments.length - 1]);'
+ )
+ assert(response['status'] === 'OK')
+ assert(response.attestation.userHandle.includes(1))
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test add resident credential not supported when authenticator uses U2F protocol',
+ async function () {
+ /**
+ * Add a resident credential using the testing API.
+ */
+ driver = await createRkEnabledU2fAuthenticator(driver)
+
+ /**
+ * A pkcs#8 encoded unencrypted EC256 private key as a base64url string.
+ */
+ const base64EncodedPK =
+ 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q' +
+ 'hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU' +
+ 'RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB'
+
+ let credential =
+ new virtualAuthenticatorCredential().createResidentCredential(
+ new Uint8Array([1, 2, 3, 4]),
+ 'localhost',
+ new Uint8Array([1]),
+ Buffer.from(base64EncodedPK, 'base64').toString('binary'),
+ 0
+ )
+
+ /**
+ * Throws InvalidArgumentError
+ */
+ try {
+ await driver.addCredential(credential)
+ } catch (e) {
+ if (e instanceof invalidArgumentError) {
+ assert(true)
+ } else {
+ assert(false)
+ }
+ }
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test get credentials',
+ async function () {
+ /**
+ * Create an authenticator and add two credentials.
+ */
+ driver = await createRkEnabledCTAP2Authenticator(driver)
+
+ /**
+ * Register a resident credential.
+ */
+ let response1 = await driver.executeAsyncScript(
+ 'registerCredential({authenticatorSelection: {requireResidentKey: true}})' +
+ ' .then(arguments[arguments.length - 1]);'
+ )
+ assert(response1['status'] === 'OK')
+
+ /**
+ * Register a non resident credential.
+ */
+ let response2 = await driver.executeAsyncScript(
+ 'registerCredential().then(arguments[arguments.length - 1]);'
+ )
+ assert(response2['status'] === 'OK')
+
+ let credential1Id = response1.credential.rawId
+ let credential2Id = response2.credential.rawId
+
+ assert.equal(arraysEqual(credential1Id, credential2Id), false)
+
+ /**
+ * Retrieve the two credentials.
+ */
+ let credentials = await driver.getCredentials()
+ assert.equal(credentials.length, 2)
+
+ let credential1 = null
+ let credential2 = null
+
+ credentials.forEach(function (credential) {
+ if (arraysEqual(credential.id(), credential1Id)) {
+ credential1 = credential
+ } else if (arraysEqual(credential.id(), credential2Id)) {
+ credential2 = credential
+ } else {
+ done(new Error('Unrecognized credential id'))
+ }
+ })
+
+ assert.equal(credential1.isResidentCredential(), true)
+ assert.notEqual(credential1.privateKey(), null)
+ assert.equal(credential1.rpId(), 'localhost')
+ assert.equal(
+ arraysEqual(credential1.userHandle(), new Uint8Array([1])),
+ true
+ )
+ assert.equal(credential1.signCount(), 1)
+
+ assert.equal(credential2.isResidentCredential(), false)
+ assert.notEqual(credential2.privateKey(), null)
+ /**
+ * Non resident keys do not store raw RP IDs or user handles.
+ */
+ assert.equal(credential2.rpId(), null)
+ assert.equal(credential2.userHandle(), null)
+ assert.equal(credential2.signCount(), 1)
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test remove credential by rawID',
+ async function () {
+ driver = await createRkDisabledU2fAuthenticator(driver)
+
+ /**
+ * Register credential.
+ */
+ let response = await driver.executeAsyncScript(
+ 'registerCredential().then(arguments[arguments.length - 1]);'
+ )
+ assert(response['status'] === 'OK')
+
+ /**
+ * Remove a credential by its ID as an array of bytes.
+ */
+ let rawId = response.credential.rawId
+ await driver.removeCredential(rawId)
+
+ /**
+ * Trying to get an assertion should fail.
+ */
+ response = await getAssertionFor(driver, rawId)
+ assert(response['status'].startsWith('NotAllowedError'))
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test remove credential by base64url Id',
+ async function () {
+ driver = await createRkDisabledU2fAuthenticator(driver)
+
+ /**
+ * Register credential.
+ */
+ let response = await driver.executeAsyncScript(
+ 'registerCredential().then(arguments[arguments.length - 1]);'
+ )
+ assert(response['status'] === 'OK')
+
+ let rawId = response.credential.rawId
+ let credentialId = response.credential.id
+
+ /**
+ * Remove a credential by its base64url ID.
+ */
+ await driver.removeCredential(credentialId)
+
+ /**
+ * Trying to get an assertion should fail.
+ */
+ response = await getAssertionFor(driver, rawId)
+ assert(response['status'].startsWith('NotAllowedError'))
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test remove all credentials',
+ async function () {
+ driver = await createRkDisabledU2fAuthenticator(driver)
+
+ /**
+ * Register two credentials.
+ */
+ let response1 = await driver.executeAsyncScript(
+ 'registerCredential().then(arguments[arguments.length - 1]);'
+ )
+ assert(response1['status'] === 'OK')
+ let rawId1 = response1.credential.rawId
+
+ let response2 = await driver.executeAsyncScript(
+ 'registerCredential().then(arguments[arguments.length - 1]);'
+ )
+ assert(response2['status'] === 'OK')
+ let rawId2 = response2.credential.rawId
+
+ /**
+ * Remove all credentials.
+ */
+ await driver.removeAllCredentials()
+
+ /**
+ * Trying to get an assertion allowing for any of both should fail.
+ */
+ let response = await driver.executeAsyncScript(
+ 'getCredential([{' +
+ ' "type": "public-key",' +
+ ' "id": Int8Array.from(arguments[0]),' +
+ '}, {' +
+ ' "type": "public-key",' +
+ ' "id": Int8Array.from(arguments[1]),' +
+ '}]).then(arguments[arguments.length - 1]);',
+ rawId1,
+ rawId2
+ )
+ assert(response['status'].startsWith('NotAllowedError'))
+ }
+ )
+
+ ignore(browsers(Browser.SAFARI, Browser.FIREFOX)).it(
+ 'should test set user verified',
+ async function () {
+ driver = await createRkEnabledCTAP2Authenticator(driver)
+
+ /**
+ * Register a credential requiring UV.
+ */
+ let response = await driver.executeAsyncScript(
+ "registerCredential({authenticatorSelection: {userVerification: 'required'}})" +
+ ' .then(arguments[arguments.length - 1]);'
+ )
+ assert(response['status'] === 'OK')
+ let rawId = response.credential.rawId
+
+ /**
+ * Getting an assertion requiring user verification should succeed.
+ */
+ response = await driver.executeAsyncScript(
+ 'getCredential([{' +
+ ' "type": "public-key",' +
+ ' "id": Int8Array.from(arguments[0]),' +
+ "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);",
+ rawId
+ )
+ assert(response['status'] === 'OK')
+
+ /**
+ * Disable user verification.
+ */
+ await driver.setUserVerified(false)
+
+ /**
+ * Getting an assertion requiring user verification should fail.
+ */
+ response = await driver.executeAsyncScript(
+ 'getCredential([{' +
+ ' "type": "public-key",' +
+ ' "id": Int8Array.from(arguments[0]),' +
+ "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);",
+ rawId
+ )
+ assert(response['status'].startsWith('NotAllowedError'))
+ }
+ )
+ })
+})