Skip to content

Commit

Permalink
[JS] feat: Added virtual authenticator (#10663)
Browse files Browse the repository at this point in the history
* [JS] feat: Added virtual authenticator

Relates #10541

* [JS] fix: code review changes

Co-authored-by: Puja Jagani <[email protected]>
Co-authored-by: Sri Harsha <[email protected]>
  • Loading branch information
3 people authored May 31, 2022
1 parent f2e40dc commit 1c2240d
Show file tree
Hide file tree
Showing 9 changed files with 1,209 additions and 1 deletion.
9 changes: 9 additions & 0 deletions javascript/node/selenium-webdriver/lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',

Expand Down
11 changes: 11 additions & 0 deletions javascript/node/selenium-webdriver/lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')],
])

/**
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -530,6 +540,7 @@ function parseHttpResponse(command, httpResponse) {
}

let parsed = tryParse(httpResponse.body)

if (parsed && typeof parsed === 'object') {
let value = parsed.value
let isW3C =
Expand Down
1 change: 1 addition & 0 deletions javascript/node/selenium-webdriver/lib/test/fileserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const Pages = (function () {
addPage('webComponents', 'webComponents.html')
addPage('xhtmlTestPage', 'xhtmlTest.html')
addPage('uploadInvisibleTestPage', 'upload_invisible.html')
addPage('virtualAuthenticator', 'virtual-authenticator.html')

return pages
})()
Expand Down
230 changes: 230 additions & 0 deletions javascript/node/selenium-webdriver/lib/virtual_authenticator.js
Original file line number Diff line number Diff line change
@@ -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,
}
Loading

0 comments on commit 1c2240d

Please sign in to comment.