| // Copyright (c) 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/test/chromedriver/webauthn_commands.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/base64url.h" |
| #include "base/callback.h" |
| #include "base/containers/flat_map.h" |
| #include "base/values.h" |
| #include "chrome/test/chromedriver/chrome/status.h" |
| #include "chrome/test/chromedriver/chrome/web_view.h" |
| #include "chrome/test/chromedriver/session.h" |
| |
| namespace { |
| |
| static constexpr char kBase64UrlError[] = " must be a base64url encoded string"; |
| static constexpr char kExtensionsMustBeList[] = |
| "extensions must be a list of strings"; |
| static constexpr char kDevToolsDidNotReturnExpectedValue[] = |
| "DevTools did not return the expected value"; |
| static constexpr char kUnrecognizedExtension[] = |
| " is not a recognized extension"; |
| static constexpr char kUnrecognizedProtocol[] = |
| " is not a recognized protocol version"; |
| |
| // Creates a base::DictionaryValue by cloning the parameters specified by |
| // |mapping| from |params|. |
| base::DictionaryValue MapParams( |
| const base::flat_map<const char*, const char*>& mapping, |
| const base::Value& params) { |
| base::DictionaryValue options; |
| for (const std::pair<const char*, const char*>& pair : mapping) { |
| const base::Value* value = params.FindKey(pair.second); |
| if (value) |
| options.SetPath(pair.first, value->Clone()); |
| } |
| return options; |
| } |
| |
| // Converts the string |keys| in |params| from base64url to base64. Returns a |
| // status error if conversion of one of the keys failed. |
| Status ConvertBase64UrlToBase64(base::Value* params, |
| const std::vector<std::string> keys) { |
| for (const std::string& key : keys) { |
| base::Value* maybe_value = params->FindKey(key); |
| if (!maybe_value) |
| continue; |
| |
| if (!maybe_value->is_string()) |
| return Status(kInvalidArgument, key + kBase64UrlError); |
| |
| std::string& value = maybe_value->GetString(); |
| std::string temp; |
| if (!Base64UrlDecode(value, base::Base64UrlDecodePolicy::IGNORE_PADDING, |
| &temp)) { |
| return Status(kInvalidArgument, key + kBase64UrlError); |
| } |
| |
| base::Base64Encode(temp, &value); |
| } |
| |
| return Status(kOk); |
| } |
| |
| // Converts the string |keys| in |params| from base64 to base64url. |
| void ConvertBase64ToBase64Url(base::Value* params, |
| const std::vector<std::string> keys) { |
| for (const std::string& key : keys) { |
| std::string* maybe_value = params->FindStringKey(key); |
| if (!maybe_value) |
| continue; |
| |
| std::string temp; |
| bool result = base::Base64Decode(*maybe_value, &temp); |
| DCHECK(result); |
| |
| base::Base64UrlEncode(temp, base::Base64UrlEncodePolicy::OMIT_PADDING, |
| maybe_value); |
| } |
| } |
| |
| } // namespace |
| |
| Status ExecuteWebAuthnCommand(const WebAuthnCommand& command, |
| Session* session, |
| const base::DictionaryValue& params, |
| std::unique_ptr<base::Value>* value) { |
| WebView* web_view = nullptr; |
| Status status = session->GetTargetWindow(&web_view); |
| if (status.IsError()) |
| return status; |
| |
| status = web_view->ConnectIfNecessary(); |
| if (status.IsError()) |
| return status; |
| |
| status = web_view->SendCommand("WebAuthn.enable", base::DictionaryValue()); |
| if (status.IsError()) |
| return status; |
| |
| return command.Run(web_view, params, value); |
| } |
| |
| Status ExecuteAddVirtualAuthenticator(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| base::DictionaryValue mapped_params = MapParams( |
| { |
| {"options.protocol", "protocol"}, |
| {"options.transport", "transport"}, |
| {"options.hasResidentKey", "hasResidentKey"}, |
| {"options.hasUserVerification", "hasUserVerification"}, |
| {"options.automaticPresenceSimulation", "isUserConsenting"}, |
| {"options.isUserVerified", "isUserVerified"}, |
| }, |
| params); |
| |
| const base::Value* extensions = params.FindKey("extensions"); |
| if (extensions) { |
| if (!extensions->is_list()) |
| return Status(kInvalidArgument, kExtensionsMustBeList); |
| for (const base::Value& extension : extensions->GetListDeprecated()) { |
| if (!extension.is_string()) |
| return Status(kInvalidArgument, kExtensionsMustBeList); |
| const std::string& extension_string = extension.GetString(); |
| if (extension_string == "largeBlob") { |
| mapped_params.SetPath("options.hasLargeBlob", base::Value(true)); |
| } else if (extension_string == "credBlob") { |
| mapped_params.SetPath("options.hasCredBlob", base::Value(true)); |
| } else if (extension_string == "minPinLength") { |
| mapped_params.SetPath("options.minPinLength", base::Value(true)); |
| } else { |
| return Status(kUnsupportedOperation, |
| extension_string + kUnrecognizedExtension); |
| } |
| } |
| } |
| |
| // The spec calls u2f "ctap1/u2f", convert the value here since devtools does |
| // not support slashes on enums. |
| std::string* protocol = mapped_params.FindStringPath("options.protocol"); |
| if (protocol) { |
| if (*protocol == "ctap1/u2f") { |
| *protocol = "u2f"; |
| } else if (*protocol == "ctap2") { |
| mapped_params.SetPath("options.ctap2Version", base::Value("ctap2_0")); |
| } else if (*protocol == "ctap2_1") { |
| *protocol = "ctap2"; |
| mapped_params.SetPath("options.ctap2Version", base::Value("ctap2_1")); |
| } else { |
| return Status(kUnsupportedOperation, *protocol + kUnrecognizedProtocol); |
| } |
| } |
| |
| std::unique_ptr<base::Value> result; |
| Status status = web_view->SendCommandAndGetResult( |
| "WebAuthn.addVirtualAuthenticator", std::move(mapped_params), &result); |
| if (status.IsError()) |
| return status; |
| |
| absl::optional<base::Value> authenticator_id = |
| result->ExtractKey("authenticatorId"); |
| if (!authenticator_id) |
| return Status(kUnknownError, kDevToolsDidNotReturnExpectedValue); |
| |
| *value = std::make_unique<base::Value>(std::move(*authenticator_id)); |
| return status; |
| } |
| |
| Status ExecuteRemoveVirtualAuthenticator(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| return web_view->SendCommandAndGetResult( |
| "WebAuthn.removeVirtualAuthenticator", |
| MapParams({{"authenticatorId", "authenticatorId"}}, params), value); |
| } |
| |
| Status ExecuteAddCredential(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| base::DictionaryValue mapped_params = MapParams( |
| { |
| {"authenticatorId", "authenticatorId"}, |
| {"credential.credentialId", "credentialId"}, |
| {"credential.isResidentCredential", "isResidentCredential"}, |
| {"credential.rpId", "rpId"}, |
| {"credential.privateKey", "privateKey"}, |
| {"credential.userHandle", "userHandle"}, |
| {"credential.signCount", "signCount"}, |
| {"credential.largeBlob", "largeBlob"}, |
| }, |
| params); |
| Status status = ConvertBase64UrlToBase64( |
| mapped_params.FindKey("credential"), |
| {"credentialId", "privateKey", "userHandle", "largeBlob"}); |
| if (status.IsError()) |
| return status; |
| |
| return web_view->SendCommandAndGetResult("WebAuthn.addCredential", |
| std::move(mapped_params), value); |
| } |
| |
| Status ExecuteGetCredentials(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| std::unique_ptr<base::Value> result; |
| Status status = web_view->SendCommandAndGetResult( |
| "WebAuthn.getCredentials", |
| MapParams({{"authenticatorId", "authenticatorId"}}, params), &result); |
| if (status.IsError()) |
| return status; |
| |
| absl::optional<base::Value> credentials = result->ExtractKey("credentials"); |
| if (!credentials) |
| return Status(kUnknownError, kDevToolsDidNotReturnExpectedValue); |
| |
| for (base::Value& credential : credentials->GetListDeprecated()) { |
| ConvertBase64ToBase64Url( |
| &credential, {"credentialId", "privateKey", "userHandle", "largeBlob"}); |
| } |
| *value = std::make_unique<base::Value>(std::move(*credentials)); |
| return status; |
| } |
| |
| Status ExecuteRemoveCredential(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| base::DictionaryValue mapped_params = MapParams( |
| { |
| {"authenticatorId", "authenticatorId"}, |
| {"credentialId", "credentialId"}, |
| }, |
| params); |
| Status status = ConvertBase64UrlToBase64(&mapped_params, {"credentialId"}); |
| if (status.IsError()) |
| return status; |
| |
| return web_view->SendCommandAndGetResult("WebAuthn.removeCredential", |
| std::move(mapped_params), value); |
| } |
| |
| Status ExecuteRemoveAllCredentials(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| return web_view->SendCommandAndGetResult( |
| "WebAuthn.clearCredentials", |
| MapParams({{"authenticatorId", "authenticatorId"}}, params), value); |
| } |
| |
| Status ExecuteSetUserVerified(WebView* web_view, |
| const base::Value& params, |
| std::unique_ptr<base::Value>* value) { |
| return web_view->SendCommandAndGetResult( |
| "WebAuthn.setUserVerified", |
| MapParams( |
| { |
| {"authenticatorId", "authenticatorId"}, |
| {"isUserVerified", "isUserVerified"}, |
| }, |
| params), |
| value); |
| } |