diff --git a/dotnet/src/webdriver/DriverCommand.cs b/dotnet/src/webdriver/DriverCommand.cs index c61c082f3f601..99f36f941fd7a 100644 --- a/dotnet/src/webdriver/DriverCommand.cs +++ b/dotnet/src/webdriver/DriverCommand.cs @@ -361,6 +361,16 @@ public static class DriverCommand /// public static readonly string GetLog = "getLog"; + // Virtual Authenticator API + // http://w3c.github.io/webauthn#sctn-automation + public static readonly string AddVirtualAuthenticator = "addVirtualAuthenticator"; + public static readonly string RemoveVirtualAuthenticator = "removeVirtualAuthenticator"; + public static readonly string AddCredential = "addCredential"; + public static readonly string GetCredentials = "getCredentials"; + public static readonly string RemoveCredential = "removeCredential"; + public static readonly string RemoveAllCredentials = "removeAllCredentials"; + public static readonly string SetUserVerified = "setUserVerified"; + public static readonly IList KnownCommands = new List() { Status, @@ -426,7 +436,14 @@ public static class DriverCommand IsElementDisplayed, UploadFile, GetLog, - GetAvailableLogTypes + GetAvailableLogTypes, + AddVirtualAuthenticator, + RemoveVirtualAuthenticator, + AddCredential, + GetCredentials, + RemoveCredential, + RemoveAllCredentials, + SetUserVerified }.AsReadOnly(); } } diff --git a/dotnet/src/webdriver/Remote/W3CWireProtocolCommandInfoRepository.cs b/dotnet/src/webdriver/Remote/W3CWireProtocolCommandInfoRepository.cs index 9697caa569b30..671be8c3027f1 100644 --- a/dotnet/src/webdriver/Remote/W3CWireProtocolCommandInfoRepository.cs +++ b/dotnet/src/webdriver/Remote/W3CWireProtocolCommandInfoRepository.cs @@ -120,6 +120,13 @@ protected override void InitializeCommandDictionary() this.TryAddCommand(DriverCommand.Screenshot, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/screenshot")); this.TryAddCommand(DriverCommand.ElementScreenshot, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/element/{id}/screenshot")); this.TryAddCommand(DriverCommand.Print, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/print")); + this.TryAddCommand(DriverCommand.AddVirtualAuthenticator, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/webauthn/authenticator")); + this.TryAddCommand(DriverCommand.RemoveVirtualAuthenticator, new HttpCommandInfo(HttpCommandInfo.DeleteCommand, "/session/{sessionId}/webauthn/authenticator/{authenticatorId}")); + this.TryAddCommand(DriverCommand.AddCredential, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/webauthn/authenticator/{authenticatorId}/credential")); + this.TryAddCommand(DriverCommand.GetCredentials, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/webauthn/authenticator/{authenticatorId}/credentials")); + this.TryAddCommand(DriverCommand.RemoveCredential, new HttpCommandInfo(HttpCommandInfo.DeleteCommand, "/session/{sessionId}/webauthn/authenticator/{authenticatorId}/credentials/{credentialId}")); + this.TryAddCommand(DriverCommand.RemoveAllCredentials, new HttpCommandInfo(HttpCommandInfo.DeleteCommand, "/session/{sessionId}/webauthn/authenticator/{authenticatorId}/credentials")); + this.TryAddCommand(DriverCommand.SetUserVerified, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/webauthn/authenticator/{authenticatorId}/uv")); // Commands below here are not included in the W3C specification, // but are required for full fidelity of execution with Selenium's diff --git a/dotnet/src/webdriver/VirtualAuth/Credential.cs b/dotnet/src/webdriver/VirtualAuth/Credential.cs new file mode 100644 index 0000000000000..9df9ec1f6beac --- /dev/null +++ b/dotnet/src/webdriver/VirtualAuth/Credential.cs @@ -0,0 +1,135 @@ +// +// 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. +// + +using System.Collections.Generic; +using Microsoft.IdentityModel.Tokens; + +namespace OpenQA.Selenium.VirtualAuth +{ + /// + /// A credential stored in a virtual authenticator. + /// Refer https://w3c.github.io/webauthn/#credential-parameters + /// + public class Credential + { + private readonly byte[] id; + private readonly bool isResidentCredential; + private readonly string rpId; + private readonly string privateKey; + private readonly byte[] userHandle; + private readonly int signCount; + + private Credential( + byte[] id, + bool isResidentCredential, + string rpId, + string privateKey, + byte[] userHandle, + int signCount) + { + this.id = id; + this.isResidentCredential = isResidentCredential; + this.rpId = rpId; + this.privateKey = privateKey; + this.userHandle = userHandle; + this.signCount = signCount; + } + + public static Credential CreateNonResidentCredential( + byte[] id, + string rpId, + string privateKey, + int signCount) + { + return new Credential(id, false, rpId, privateKey, null, signCount); + } + + public static Credential CreateResidentCredential( + byte[] id, + string rpId, + string privateKey, + byte[] userHandle, + int signCount) + { + return new Credential( + id, + true, + rpId, + privateKey, + userHandle, + signCount); + } + + public byte[] Id + { + get { return (byte[])id.Clone(); } + } + + public bool IsResidentCredential + { + get { return this.isResidentCredential; } + } + public string RpId + { + get { return this.rpId; } + } + + public string PrivateKey + { + get { return this.privateKey; } + } + + public byte[] UserHandle + { + get { return userHandle == null ? null : (byte[])userHandle.Clone(); } + } + + public int SignCount + { + get { return this.signCount; } + } + + public static Credential FromDictionary(Dictionary dictionary) + { + return new Credential( + Base64UrlEncoder.DecodeBytes((string)dictionary["credentialId"]), + (bool)dictionary["isResidentCredential"], + dictionary.ContainsKey("rpId") ? (string)dictionary["rpId"] : null, + (string)dictionary["privateKey"], + dictionary.ContainsKey("userHandle") ? Base64UrlEncoder.DecodeBytes((string)dictionary["userHandle"]) : null, + (int)((long)dictionary["signCount"])); + } + + public Dictionary ToDictionary() + { + Dictionary toReturn = new Dictionary(); + + toReturn["credentialId"] = Base64UrlEncoder.Encode(this.id); + toReturn["isResidentCredential"] = this.isResidentCredential; + toReturn["rpId"] = this.rpId; + toReturn["privateKey"] = this.privateKey; + toReturn["signCount"] = this.signCount; + if (this.userHandle != null) + { + toReturn["userHandle"] = Base64UrlEncoder.Encode(this.userHandle); + } + + return toReturn; + } + } +} \ No newline at end of file diff --git a/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs b/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs new file mode 100644 index 0000000000000..de17b99f8e963 --- /dev/null +++ b/dotnet/src/webdriver/VirtualAuth/IHasVirtualAuthenticator.cs @@ -0,0 +1,41 @@ +// +// 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. +// +using System.Collections.Generic; + +namespace OpenQA.Selenium.VirtualAuth +{ + public interface IHasVirtualAuthenticator + { + string AddVirtualAuthenticator(VirtualAuthenticatorOptions options); + + void RemoveVirtualAuthenticator(string id); + + void AddCredential(Credential credential); + + List GetCredentials(); + + void RemoveCredential(byte[] credentialId); + + void RemoveCredential(string credentialId); + + void RemoveAllCredentials(); + + void SetUserVerified(bool verified); + } + +} \ No newline at end of file diff --git a/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs b/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs new file mode 100644 index 0000000000000..e3aa8d8f40d98 --- /dev/null +++ b/dotnet/src/webdriver/VirtualAuth/VirtualAuthenticatorOptions.cs @@ -0,0 +1,154 @@ +// +// 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. +// + +using System; +using System.Collections.Generic; + +namespace OpenQA.Selenium.VirtualAuth +{ + /// + /// Options for the creation of virtual authenticators. + /// Refer https://w3c.github.io/webauthn/#sctn-automation + /// + public class VirtualAuthenticatorOptions + { + public static class Protocol + { + public static readonly string CTAP2 = "ctap2"; + public static readonly string U2F = "ctap1/u2f"; + } + + public static class Transport + { + public static readonly string BLE = "ble"; + public static readonly string INTERNAL = "internal"; + public static readonly string NFC = "nfc"; + public static readonly string USB = "usb"; + } + + private string protocol = Protocol.CTAP2; + private string transport = Transport.USB; + private bool hasResidentKey = false; + private bool hasUserVerification = false; + private bool isUserConsenting = true; + private bool isUserVerified = false; + + /// + /// Sets the protocol the Virtual Authenticator speaks + /// + /// Valid protocol value + /// VirtualAuthenticatorOptions + public VirtualAuthenticatorOptions SetProtocol(string protocol) + { + if (string.Equals(Protocol.CTAP2, protocol) || string.Equals(Protocol.U2F, protocol)) + { + this.protocol = protocol; + return this; + } + else + { + throw new ArgumentException("Enter a valid protocol value." + + "Refer to https://www.w3.org/TR/webauthn-2/#sctn-automation-virtual-authenticators for supported protocols."); + } + } + + /// + /// Sets the transport authenticator needs to implement to communicate with clients + /// + /// Valid transport value + /// VirtualAuthenticatorOptions + public VirtualAuthenticatorOptions SetTransport(string transport) + { + if (Transport.BLE.Equals(transport) || + Transport.INTERNAL.Equals(transport) || + Transport.NFC.Equals(transport) || + Transport.USB.Equals(transport)) + { + this.transport = transport; + return this; + } + else + { + throw new ArgumentException("Enter a valid transport value." + + "Refer to https://www.w3.org/TR/webauthn-2/#enum-transport for supported transport values."); + } + } + + /// + /// If set to true the authenticator will support client-side discoverable credentials. + /// Refer https://w3c.github.io/webauthn/#client-side-discoverable-credential + /// + /// boolean value to set + /// VirtualAuthenticatorOptions + public VirtualAuthenticatorOptions SetHasResidentKey(bool hasResidentKey) + { + this.hasResidentKey = hasResidentKey; + return this; + } + + /// + /// If set to true, the authenticator supports user verification. + /// Refer https://w3c.github.io/webauthn/#user-verification. + /// + /// boolean value to set + /// + public VirtualAuthenticatorOptions SetHasUserVerification(bool hasUserVerification) + { + this.hasUserVerification = hasUserVerification; + return this; + } + + /// + /// If set to true, a user consent will always be granted. + /// Refer https://w3c.github.io/webauthn/#user-consent + /// + /// boolean value to set + /// VirtualAuthenticatorOptions + public VirtualAuthenticatorOptions SetIsUserConsenting(bool isUserConsenting) + { + this.isUserConsenting = isUserConsenting; + return this; + } + + /// + /// If set to true, User Verification will always succeed. + /// Refer https://w3c.github.io/webauthn/#user-verification + /// + /// boolean value to set + /// VirtualAuthenticatorOptions + public VirtualAuthenticatorOptions SetIsUserVerified(bool isUserVerified) + { + this.isUserVerified = isUserVerified; + return this; + } + + public Dictionary ToDictionary() + { + Dictionary toReturn = new Dictionary(); + + toReturn["protocol"] = this.protocol; + toReturn["transport"] = this.transport; + toReturn["hasResidentKey"] = this.hasResidentKey; + toReturn["hasUserVerification"] = this.hasUserVerification; + toReturn["isUserConsenting"] = this.isUserConsenting; + toReturn["isUserVerified"] = this.isUserVerified; + + return toReturn; + } + } +} \ No newline at end of file diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 2c05d3ad4a81a..9215e1aa84c51 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -23,10 +23,12 @@ using System.Globalization; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Internal; +using OpenQA.Selenium.VirtualAuth; +using Microsoft.IdentityModel.Tokens; namespace OpenQA.Selenium { - public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFindsElement, ITakesScreenshot, ISupportsPrint, IActionExecutor, IAllowsFileDetection, IHasCapabilities, IHasCommandExecutor, IHasSessionId, ICustomDriverCommandExecutor + public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFindsElement, ITakesScreenshot, ISupportsPrint, IActionExecutor, IAllowsFileDetection, IHasCapabilities, IHasCommandExecutor, IHasSessionId, ICustomDriverCommandExecutor, IHasVirtualAuthenticator { /// /// The default command timeout for HTTP requests in a RemoteWebDriver instance. @@ -39,6 +41,7 @@ public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFinds private NetworkManager network; private WebElementFactory elementFactory; private SessionId sessionId; + private String authenticatorId; private List registeredCommands = new List(); /// @@ -967,5 +970,112 @@ private object ParseJavaScriptReturnValue(object responseValue) return returnValue; } + + /// + /// Creates a Virtual Authenticator. + /// + /// VirtualAuthenticator Options (https://w3c.github.io/webauthn/#sctn-automation-virtual-authenticators) + /// Authenticator id as string + public string AddVirtualAuthenticator(VirtualAuthenticatorOptions options) + { + Response commandResponse = this.Execute(DriverCommand.AddVirtualAuthenticator, options.ToDictionary()); + string id = commandResponse.Value.ToString(); + this.authenticatorId = id; + return this.authenticatorId; + } + + /// + /// Removes the Virtual Authenticator + /// + /// Id as string that uniquely identifies a Virtual Authenticator + public void RemoveVirtualAuthenticator(string authenticatorId) + { + Dictionary parameters = new Dictionary(); + parameters.Add("authenticatorId", this.authenticatorId); + this.Execute(DriverCommand.RemoveVirtualAuthenticator, parameters); + this.authenticatorId = null; + } + + public string AuthenticatorId { get; } + + /// + /// Add a credential to the Virtual Authenticator/ + /// + /// The credential to be stored in the Virtual Authenticator + public void AddCredential(Credential credential) + { + Dictionary parameters = new Dictionary(credential.ToDictionary()); + parameters.Add("authenticatorId", this.authenticatorId); + + this.Execute(driverCommandToExecute: DriverCommand.AddCredential, parameters); + } + + /// + /// Retrieves all the credentials stored in the Virtual Authenticator + /// + /// List of credentials + public List GetCredentials() + { + Dictionary parameters = new Dictionary(); + parameters.Add("authenticatorId", this.authenticatorId); + + object[] commandResponse = (object[])this.Execute(driverCommandToExecute: DriverCommand.GetCredentials, parameters).Value; + + List credentials = new List(); + + foreach (object dictionary in commandResponse) + { + Credential credential = Credential.FromDictionary((Dictionary)dictionary); + credentials.Add(credential); + } + + return credentials; + } + + /// + /// Removes the credential identified by the credentialId from the Virtual Authenticator. + /// + /// The id as byte array that uniquely identifies a credential + public void RemoveCredential(byte[] credentialId) + { + RemoveCredential(Base64UrlEncoder.Encode(credentialId)); + } + + /// + /// Removes the credential identified by the credentialId from the Virtual Authenticator. + /// + /// The id as string that uniquely identifies a credential + public void RemoveCredential(string credentialId) + { + Dictionary parameters = new Dictionary(); + parameters.Add("authenticatorId", this.authenticatorId); + parameters.Add("credentialId", credentialId); + + this.Execute(driverCommandToExecute: DriverCommand.RemoveCredential, parameters); + } + + /// + /// Removes all the credentials stored in the Virtual Authenticator. + /// + public void RemoveAllCredentials() + { + Dictionary parameters = new Dictionary(); + parameters.Add("authenticatorId", this.authenticatorId); + + this.Execute(driverCommandToExecute: DriverCommand.RemoveAllCredentials, parameters); + } + + /// + /// Sets the isUserVerified property for the Virtual Authenticator. + /// + /// The boolean value representing value to be set + public void SetUserVerified(bool verified) + { + Dictionary parameters = new Dictionary(); + parameters.Add("authenticatorId", this.authenticatorId); + parameters.Add("isUserVerified", verified); + + this.Execute(driverCommandToExecute: DriverCommand.SetUserVerified, parameters); + } } } diff --git a/dotnet/src/webdriver/WebDriver.csproj b/dotnet/src/webdriver/WebDriver.csproj index 16a7fffe4c8e9..f2c128b22c308 100644 --- a/dotnet/src/webdriver/WebDriver.csproj +++ b/dotnet/src/webdriver/WebDriver.csproj @@ -109,6 +109,7 @@ + diff --git a/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs b/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs new file mode 100644 index 0000000000000..27db2d01330e9 --- /dev/null +++ b/dotnet/test/common/VirtualAuthn/VirtualAuthenticatorTest.cs @@ -0,0 +1,484 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using OpenQA.Selenium.Environment; +using NUnit.Framework; +using Microsoft.IdentityModel.Tokens; + +using static OpenQA.Selenium.VirtualAuth.VirtualAuthenticatorOptions; + +namespace OpenQA.Selenium.VirtualAuth +{ + [TestFixture] + public class VirtualAuthenticatorTest : DriverTestFixture + { + private IJavaScriptExecutor jsDriver; + private WebDriver webDriver; + private string base64EncodedPK; + + [SetUp] + public void Setup() + { + //A pkcs#8 encoded encrypted RSA private key as a base64 string. + string base64EncodedRSAPK = + "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbBOu5Lhs4vpowbCnmCyLUpIE7JM9sm9QXzye2G+jr+Kr" + + "MsinWohEce47BFPJlTaDzHSvOW2eeunBO89ZcvvVc8RLz4qyQ8rO98xS1jtgqi1NcBPETDrtzthODu/gd0sjB2Tk3TLuB" + + "GVoPXt54a+Oo4JbBJ6h3s0+5eAfGplCbSNq6hN3Jh9YOTw5ZA6GCEy5l8zBaOgjXytd2v2OdSVoEDNiNQRkjJd2rmS2oi" + + "9AyQFR3B7BrPSiDlCcITZFOWgLF5C31Wp/PSHwQhlnh7/6YhnE2y9tzsUvzx0wJXrBADW13+oMxrneDK3WGbxTNYgIi1P" + + "vSqXlqGjHtCK+R2QkXAgMBAAECggEAVc6bu7VAnP6v0gDOeX4razv4FX/adCao9ZsHZ+WPX8PQxtmWYqykH5CY4TSfsui" + + "zAgyPuQ0+j4Vjssr9VODLqFoanspT6YXsvaKanncUYbasNgUJnfnLnw3an2XpU2XdmXTNYckCPRX9nsAAURWT3/n9ljc/" + + "XYY22ecYxM8sDWnHu2uKZ1B7M3X60bQYL5T/lVXkKdD6xgSNLeP4AkRx0H4egaop68hoW8FIwmDPVWYVAvo8etzWCtib" + + "RXz5FcNld9MgD/Ai7ycKy4Q1KhX5GBFI79MVVaHkSQfxPHpr7/XcmpQOEAr+BMPon4s4vnKqAGdGB3j/E3d/+4F2swyko" + + "QKBgQD8hCsp6FIQ5umJlk9/j/nGsMl85LgLaNVYpWlPRKPc54YNumtvj5vx1BG+zMbT7qIE3nmUPTCHP7qb5ERZG4CdMC" + + "S6S64/qzZEqijLCqepwj6j4fV5SyPWEcpxf6ehNdmcfgzVB3Wolfwh1ydhx/96L1jHJcTKchdJJzlfTvq8wwKBgQDeCnK" + + "ws1t5GapfE1rmC/h4olL2qZTth9oQmbrXYohVnoqNFslDa43ePZwL9Jmd9kYb0axOTNMmyrP0NTj41uCfgDS0cJnNTc63" + + "ojKjegxHIyYDKRZNVUR/dxAYB/vPfBYZUS7M89pO6LLsHhzS3qpu3/hppo/Uc/AM /r8PSflNHQKBgDnWgBh6OQncChPUl" + + "OLv9FMZPR1ZOfqLCYrjYEqiuzGm6iKM13zXFO4AGAxu1P/IAd5BovFcTpg79Z8tWqZaUUwvscnl+cRlj+mMXAmdqCeO8V" + + "ASOmqM1ml667axeZDIR867ZG8K5V029Wg+4qtX5uFypNAAi6GfHkxIKrD04yOHAoGACdh4wXESi0oiDdkz3KOHPwIjn6B" + + "hZC7z8mx+pnJODU3cYukxv3WTctlUhAsyjJiQ/0bK1yX87ulqFVgO0Knmh+wNajrb9wiONAJTMICG7tiWJOm7fW5cfTJw" + + "WkBwYADmkfTRmHDvqzQSSvoC2S7aa9QulbC3C/qgGFNrcWgcT9kCgYAZTa1P9bFCDU7hJc2mHwJwAW7/FQKEJg8SL33KI" + + "NpLwcR8fqaYOdAHWWz636osVEqosRrHzJOGpf9x2RSWzQJ+dq8+6fACgfFZOVpN644+sAHfNPAI/gnNKU5OfUv+eav8fB" + + "nzlf1A3y3GIkyMyzFN3DE7e0n/lyqxE4HBYGpI8g=="; + + byte[] bytes = System.Convert.FromBase64String(base64EncodedRSAPK); + base64EncodedPK = Base64UrlEncoder.Encode(bytes); + + jsDriver = (IJavaScriptExecutor)driver; + webDriver = (WebDriver)driver; + + webDriver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("virtual-authenticator.html"); + } + + [TearDown] + public void Teardown() + { + if (webDriver.AuthenticatorId != null) + { + webDriver.RemoveVirtualAuthenticator(webDriver.AuthenticatorId); + } + } + + private void CreateRKEnabledU2FAuthenticator() + { + VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions() + .SetProtocol(Protocol.U2F) + .SetHasResidentKey(true); + + webDriver.AddVirtualAuthenticator(options); + } + + private void CreateRKDisabledU2FAuthenticator() + { + VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions() + .SetProtocol(Protocol.U2F) + .SetHasResidentKey(false); + + webDriver.AddVirtualAuthenticator(options); + } + + private void CreateRKEnabledCTAP2Authenticator() + { + VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions() + .SetProtocol(Protocol.CTAP2) + .SetHasResidentKey(true) + .SetHasUserVerification(true) + .SetIsUserVerified(true); + + webDriver.AddVirtualAuthenticator(options); + } + + private void CreateRKDisabledCTAP2Authenticator() + { + VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions() + .SetProtocol(Protocol.CTAP2) + .SetHasResidentKey(false) + .SetHasUserVerification(true) + .SetIsUserVerified(true); + + webDriver.AddVirtualAuthenticator(options); + } + + private List ExtractRawIdFrom(object response) + { + Dictionary responseJson = (Dictionary)response; + Dictionary credentialJson = (Dictionary)responseJson["credential"]; + ReadOnlyCollection readOnlyCollection = (ReadOnlyCollection)credentialJson["rawId"]; + List rawIdList = new List(); + foreach (object id in readOnlyCollection) + { + rawIdList.Add((long)id); + } + return rawIdList; + } + + private string ExtractIdFrom(object response) + { + Dictionary responseJson = (Dictionary)response; + Dictionary credentialJson = (Dictionary)responseJson["credential"]; + return (string)credentialJson["id"]; + } + + private object GetAssertionFor(object credentialId) + { + return jsDriver.ExecuteAsyncScript( + "getCredential([{" + + " \"type\": \"public-key\"," + + " \"id\": Int8Array.from(arguments[0])," + + "}]).then(arguments[arguments.length - 1]);", credentialId); + } + + private byte[] ConvertListIntoArrayOfBytes(List list) + { + byte[] ret = new byte[list.Count]; + for (int i = 0; i < list.Count; i++) + { + ret[i] = ((byte)list[i]); + } + + return ret; + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldCreateAuthenticator() + { + // Register a credential on the Virtual Authenticator. + CreateRKDisabledU2FAuthenticator(); + object response = jsDriver.ExecuteAsyncScript( + "registerCredential().then(arguments[arguments.length - 1]);"); + + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + + object assertionResponse = GetAssertionFor(ExtractRawIdFrom(response)); + + // Attempt to use the credential to get an assertion. + Assert.AreEqual("OK", ((Dictionary)assertionResponse)["status"]); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldRemoveAuthenticator() + { + VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions(); + string authenticatorId = webDriver.AddVirtualAuthenticator(options); + webDriver.RemoveVirtualAuthenticator(authenticatorId); + + Assert.IsNull(webDriver.AuthenticatorId); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldAddNonResidentCredential() + { + CreateRKDisabledCTAP2Authenticator(); + byte[] credentialId = { 1, 2, 3, 4 }; + Credential credential = Credential.CreateNonResidentCredential( + credentialId, "localhost", base64EncodedPK, /*signCount=*/0); + + webDriver.AddCredential(credential); + + List id = new List(); + id.Add(1); + id.Add(2); + id.Add(3); + id.Add(4); + + // Attempt to use the credential to generate an assertion. + object response = GetAssertionFor(id); + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldAddNonResidentCredentialWhenAuthenticatorUsesU2FProtocol() + { + CreateRKDisabledU2FAuthenticator(); + + /** + * A pkcs#8 encoded unencrypted EC256 private key as a base64url string. + */ + string base64EncodedEC256PK = + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q" + + "hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU" + + "RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB"; + + byte[] credentialId = { 1, 2, 3, 4 }; + Credential credential = Credential.CreateNonResidentCredential( + credentialId, "localhost", base64EncodedEC256PK, /*signCount=*/0); + webDriver.AddCredential(credential); + + List id = new List(); + id.Add(1); + id.Add(2); + id.Add(3); + id.Add(4); + + // Attempt to use the credential to generate an assertion. + object response = GetAssertionFor(id); + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldAddResidentCredential() + { + // Add a resident credential using the testing API. + CreateRKEnabledCTAP2Authenticator(); + byte[] credentialId = { 1, 2, 3, 4 }; + byte[] userHandle = { 1 }; + + Credential credential = Credential.CreateResidentCredential( + credentialId, "localhost", base64EncodedPK, userHandle, /*signCount=*/0); + + webDriver.AddCredential(credential); + + // Attempt to use the credential to generate an assertion. Notice we use an + // empty allowCredentials array. + object response = jsDriver.ExecuteAsyncScript( + "getCredential([]).then(arguments[arguments.length - 1]);"); + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + + Dictionary attestation = (Dictionary)((Dictionary)response)["attestation"]; + + ReadOnlyCollection returnedUserHandle = (ReadOnlyCollection)attestation["userHandle"]; + + Assert.AreEqual(1, returnedUserHandle.Count); + Assert.AreEqual(0, returnedUserHandle.IndexOf(1L)); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void AddResidentCredentialNotSupportedWhenAuthenticatorUsesU2FProtocol() + { + // Add a resident credential using the testing API. + CreateRKEnabledU2FAuthenticator(); + + /** + * A pkcs#8 encoded unencrypted EC256 private key as a base64url string. + */ + string base64EncodedEC256PK = + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8_zMDQDYAxlU-Q" + + "hk1Dwkf0v18GZca1DMF3SaJ9HPdmShRANCAASNYX5lyVCOZLzFZzrIKmeZ2jwU" + + "RmgsJYxGP__fWN_S-j5sN4tT15XEpN_7QZnt14YvI6uvAgO0uJEboFaZlOEB"; + + byte[] credentialId = { 1, 2, 3, 4 }; + byte[] userHandle = { 1 }; + Credential credential = Credential.CreateResidentCredential( + credentialId, "localhost", base64EncodedEC256PK, userHandle, /*signCount=*/0); + Assert.Throws(() => webDriver.AddCredential(credential)); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldGetCredential() + { + CreateRKEnabledCTAP2Authenticator(); + // Create an authenticator and add two credentials. + + // Register a resident credential. + object response1 = jsDriver.ExecuteAsyncScript( + "registerCredential({authenticatorSelection: {requireResidentKey: true}})" + + " .then(arguments[arguments.length - 1]);"); + Assert.AreEqual("OK", ((Dictionary)response1)["status"]); + + // Register a non resident credential. + object response2 = jsDriver.ExecuteAsyncScript( + "registerCredential().then(arguments[arguments.length - 1]);"); + Assert.AreEqual("OK", ((Dictionary)response2)["status"]); + + byte[] credential1Id = ConvertListIntoArrayOfBytes(ExtractRawIdFrom(response1)); + byte[] credential2Id = ConvertListIntoArrayOfBytes(ExtractRawIdFrom(response2)); + + Assert.AreNotEqual(credential1Id, credential2Id); + + // Retrieve the two credentials. + List credentials = webDriver.GetCredentials(); + Assert.AreEqual(2, credentials.Count); + + Credential credential1 = null; + Credential credential2 = null; + foreach (Credential credential in credentials) + { + if (Enumerable.SequenceEqual(credential.Id, credential1Id)) + { + credential1 = credential; + } + else if (Enumerable.SequenceEqual(credential.Id, credential2Id)) + { + credential2 = credential; + } + else + { + Assert.Fail("Unrecognized credential id"); + } + } + + Assert.True(credential1.IsResidentCredential); + Assert.NotNull(credential1.PrivateKey); + Assert.AreEqual("localhost", credential1.RpId); + Assert.AreEqual(new byte[] { 1 }, credential1.UserHandle); + Assert.AreEqual(1, credential1.SignCount); + + Assert.False(credential2.IsResidentCredential); + Assert.NotNull(credential2.PrivateKey); + Assert.IsNull(credential2.RpId); + Assert.IsNull(credential2.UserHandle); + Assert.AreEqual(1, credential2.SignCount); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldRemoveCredentialByRawId() + { + CreateRKDisabledU2FAuthenticator(); + + // Register credential. + object response = jsDriver.ExecuteAsyncScript( + "registerCredential().then(arguments[arguments.length - 1]);"); + + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + + // Remove a credential by its ID as an array of bytes. + List rawId = ExtractRawIdFrom(response); + byte[] rawCredentialId = ConvertListIntoArrayOfBytes(rawId); + webDriver.RemoveCredential(rawCredentialId); + + // Trying to get an assertion should fail. + object assertionResponse = GetAssertionFor(rawId); + string error = (string)((Dictionary)assertionResponse)["status"]; + + Assert.True(error.StartsWith("NotAllowedError")); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldRemoveCredentialByBase64UrlId() + { + CreateRKDisabledU2FAuthenticator(); + + // Register credential. + object response = jsDriver.ExecuteAsyncScript( + "registerCredential().then(arguments[arguments.length - 1]);"); + + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + + // Remove a credential by its base64url ID. + String credentialId = ExtractIdFrom(response); + webDriver.RemoveCredential(credentialId); + + // Trying to get an assertion should fail. + object assertionResponse = GetAssertionFor(credentialId); + string error = (string)((Dictionary)assertionResponse)["status"]; + + Assert.True(error.StartsWith("NotAllowedError")); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void ShouldRemoveAllCredentials() + { + CreateRKDisabledU2FAuthenticator(); + + // Register two credentials. + object response1 = jsDriver.ExecuteAsyncScript( + "registerCredential().then(arguments[arguments.length - 1]);"); + Assert.AreEqual("OK", ((Dictionary)response1)["status"]); + List rawId1 = ExtractRawIdFrom(response1); + + object response2 = jsDriver.ExecuteAsyncScript( + "registerCredential().then(arguments[arguments.length - 1]);"); + Assert.AreEqual("OK", ((Dictionary)response2)["status"]); + List rawId2 = ExtractRawIdFrom(response1); + + // Remove all credentials. + webDriver.RemoveAllCredentials(); + + // Trying to get an assertion allowing for any of both should fail. + object response = jsDriver.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); + + string error = (string)((Dictionary)response)["status"]; + + Assert.True(error.StartsWith("NotAllowedError")); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Virtual Authenticator")] + [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Virtual Authenticator")] + public void testSetUserVerified() + { + CreateRKEnabledCTAP2Authenticator(); + + // Register a credential requiring UV. + Object response = jsDriver.ExecuteAsyncScript( + "registerCredential({authenticatorSelection: {userVerification: 'required'}})" + + " .then(arguments[arguments.length - 1]);"); + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + List rawId = ExtractRawIdFrom(response); + + // Getting an assertion requiring user verification should succeed. + response = jsDriver.ExecuteAsyncScript( + "getCredential([{" + + " \"type\": \"public-key\"," + + " \"id\": Int8Array.from(arguments[0])," + + "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);", + rawId); + Assert.AreEqual("OK", ((Dictionary)response)["status"]); + + // Disable user verification. + webDriver.SetUserVerified(false); + + // Getting an assertion requiring user verification should fail. + response = jsDriver.ExecuteAsyncScript( + "getCredential([{" + + " \"type\": \"public-key\"," + + " \"id\": Int8Array.from(arguments[0])," + + "}], {userVerification: 'required'}).then(arguments[arguments.length - 1]);", + rawId); + + string error = (string)((Dictionary)response)["status"]; + + Assert.True(error.StartsWith("NotAllowedError")); + } + } +} \ No newline at end of file diff --git a/dotnet/test/common/WebDriver.Common.Tests.csproj b/dotnet/test/common/WebDriver.Common.Tests.csproj index ad6a9e67aa5e2..32be5d39d3e62 100644 --- a/dotnet/test/common/WebDriver.Common.Tests.csproj +++ b/dotnet/test/common/WebDriver.Common.Tests.csproj @@ -38,6 +38,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/third_party/dotnet/nuget/packages/microsoft.identitymodel.tokens.6.19.0.nupkg b/third_party/dotnet/nuget/packages/microsoft.identitymodel.tokens.6.19.0.nupkg new file mode 100644 index 0000000000000..f1a74afc3e055 Binary files /dev/null and b/third_party/dotnet/nuget/packages/microsoft.identitymodel.tokens.6.19.0.nupkg differ