blob: e09d20373df019ab3dc43dfe9f0a769982768120 [file] [log] [blame]
// Copyright 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 "content/browser/devtools/protocol/webauthn_handler.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webauth/authenticator_environment_impl.h"
#include "content/browser/webauth/virtual_authenticator.h"
#include "content/browser/webauth/virtual_fido_discovery_factory.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/virtual_fido_device.h"
#include "device/fido/virtual_u2f_device.h"
namespace content {
namespace protocol {
namespace {
static constexpr char kAuthenticatorNotFound[] =
"Could not find a Virtual Authenticator matching the ID";
static constexpr char kCableNotSupportedOnU2f[] =
"U2F only supports the \"usb\", \"ble\" and \"nfc\" transports";
static constexpr char kCouldNotCreateCredential[] =
"An error occurred trying to create the credential";
static constexpr char kCouldNotStoreLargeBlob[] =
"An error occurred trying to store the large blob";
static constexpr char kCredentialNotFound[] =
"Could not find a credential matching the ID";
static constexpr char kDevToolsNotAttached[] =
"The DevTools session is not attached to a frame";
static constexpr char kErrorCreatingAuthenticator[] =
"An error occurred when trying to create the authenticator";
static constexpr char kHandleRequiredForResidentCredential[] =
"The User Handle is required for Resident Credentials";
static constexpr char kInvalidCtapVersion[] =
"Invalid CTAP version. Valid values are \"ctap2_0\" and \"ctap2_1\"";
static constexpr char kInvalidProtocol[] = "The protocol is not valid";
static constexpr char kInvalidTransport[] = "The transport is not valid";
static constexpr char kInvalidUserHandle[] =
"The User Handle must have a maximum size of ";
static constexpr char kLargeBlobRequiresResidentKey[] =
"Large blob requires resident key support";
static constexpr char kRequiresCtap2_1[] =
"Specified options require a CTAP 2.1 authenticator";
static constexpr char kResidentCredentialNotSupported[] =
"The Authenticator does not support Resident Credentials.";
static constexpr char kRpIdRequired[] =
"The Relying Party ID is a required parameter";
static constexpr char kVirtualEnvironmentNotEnabled[] =
"The Virtual Authenticator Environment has not been enabled for this "
"session";
class GetCredentialCallbackAggregator
: public base::RefCounted<GetCredentialCallbackAggregator> {
public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
explicit GetCredentialCallbackAggregator(
std::unique_ptr<WebAuthn::Backend::GetCredentialsCallback> callback)
: callback_(std::move(callback)) {}
GetCredentialCallbackAggregator(const GetCredentialCallbackAggregator&) =
delete;
GetCredentialCallbackAggregator operator=(
const GetCredentialCallbackAggregator&) = delete;
void OnLargeBlob(std::unique_ptr<WebAuthn::Credential> credential,
const base::Optional<std::vector<uint8_t>>& blob) {
if (blob) {
credential->SetLargeBlob(Binary::fromVector(*blob));
}
credentials_->emplace_back(std::move(credential));
}
private:
friend class base::RefCounted<GetCredentialCallbackAggregator>;
~GetCredentialCallbackAggregator() {
callback_->sendSuccess(std::move(credentials_));
}
std::unique_ptr<WebAuthn::Backend::GetCredentialsCallback> callback_;
std::unique_ptr<Array<WebAuthn::Credential>> credentials_ =
std::make_unique<Array<WebAuthn::Credential>>();
};
device::ProtocolVersion ConvertToProtocolVersion(base::StringPiece protocol) {
if (protocol == WebAuthn::AuthenticatorProtocolEnum::Ctap2)
return device::ProtocolVersion::kCtap2;
if (protocol == WebAuthn::AuthenticatorProtocolEnum::U2f)
return device::ProtocolVersion::kU2f;
return device::ProtocolVersion::kUnknown;
}
base::Optional<device::Ctap2Version> ConvertToCtap2Version(
base::StringPiece version) {
if (version == WebAuthn::Ctap2VersionEnum::Ctap2_0)
return device::Ctap2Version::kCtap2_0;
if (version == WebAuthn::Ctap2VersionEnum::Ctap2_1)
return device::Ctap2Version::kCtap2_1;
return base::nullopt;
}
std::vector<uint8_t> CopyBinaryToVector(const Binary& binary) {
return std::vector<uint8_t>(binary.data(), binary.data() + binary.size());
}
std::unique_ptr<WebAuthn::Credential> BuildCredentialFromRegistration(
const std::pair<const std::vector<uint8_t>,
device::VirtualFidoDevice::RegistrationData>&
registration) {
auto credential =
WebAuthn::Credential::Create()
.SetCredentialId(Binary::fromVector(registration.first))
.SetPrivateKey(Binary::fromVector(
registration.second.private_key->GetPKCS8PrivateKey()))
.SetSignCount(registration.second.counter)
.SetIsResidentCredential(registration.second.is_resident)
.Build();
if (registration.second.rp)
credential->SetRpId(registration.second.rp->id);
if (registration.second.user) {
credential->SetUserHandle(Binary::fromVector(registration.second.user->id));
}
return credential;
}
} // namespace
WebAuthnHandler::WebAuthnHandler()
: DevToolsDomainHandler(WebAuthn::Metainfo::domainName) {}
WebAuthnHandler::~WebAuthnHandler() = default;
void WebAuthnHandler::SetRenderer(int process_host_id,
RenderFrameHostImpl* frame_host) {
if (!frame_host) {
Disable();
}
frame_host_ = frame_host;
}
void WebAuthnHandler::Wire(UberDispatcher* dispatcher) {
WebAuthn::Dispatcher::wire(dispatcher, this);
}
Response WebAuthnHandler::Enable() {
if (!frame_host_)
return Response::ServerError(kDevToolsNotAttached);
AuthenticatorEnvironmentImpl::GetInstance()->EnableVirtualAuthenticatorFor(
frame_host_->frame_tree_node());
return Response::Success();
}
Response WebAuthnHandler::Disable() {
if (frame_host_) {
AuthenticatorEnvironmentImpl::GetInstance()->DisableVirtualAuthenticatorFor(
frame_host_->frame_tree_node());
}
return Response::Success();
}
Response WebAuthnHandler::AddVirtualAuthenticator(
std::unique_ptr<WebAuthn::VirtualAuthenticatorOptions> options,
String* out_authenticator_id) {
VirtualAuthenticatorManagerImpl* authenticator_manager =
AuthenticatorEnvironmentImpl::GetInstance()
->MaybeGetVirtualAuthenticatorManager(frame_host_->frame_tree_node());
if (!authenticator_manager)
return Response::ServerError(kVirtualEnvironmentNotEnabled);
auto transport =
device::ConvertToFidoTransportProtocol(options->GetTransport());
if (!transport)
return Response::InvalidParams(kInvalidTransport);
auto protocol = ConvertToProtocolVersion(options->GetProtocol());
if (protocol == device::ProtocolVersion::kUnknown)
return Response::InvalidParams(kInvalidProtocol);
if (protocol == device::ProtocolVersion::kU2f &&
!device::VirtualU2fDevice::IsTransportSupported(*transport)) {
return Response::InvalidParams(kCableNotSupportedOnU2f);
}
auto ctap2_version = ConvertToCtap2Version(
options->GetCtap2Version(WebAuthn::Ctap2VersionEnum::Ctap2_0));
if (!ctap2_version)
return Response::InvalidParams(kInvalidCtapVersion);
bool has_large_blob = options->GetHasLargeBlob(/*default=*/false);
bool has_cred_blob = options->GetHasCredBlob(/*default=*/false);
bool has_resident_key = options->GetHasResidentKey(/*default=*/false);
if (has_large_blob && !has_resident_key)
return Response::InvalidParams(kLargeBlobRequiresResidentKey);
if ((protocol != device::ProtocolVersion::kCtap2 ||
ctap2_version < device::Ctap2Version::kCtap2_1) &&
(has_large_blob || has_cred_blob)) {
return Response::InvalidParams(kRequiresCtap2_1);
}
auto virt_auth_options =
blink::test::mojom::VirtualAuthenticatorOptions::New();
virt_auth_options->protocol = protocol;
virt_auth_options->transport = *transport;
switch (protocol) {
case device::ProtocolVersion::kU2f:
virt_auth_options->attachment =
device::AuthenticatorAttachment::kCrossPlatform;
break;
case device::ProtocolVersion::kCtap2:
virt_auth_options->ctap2_version = *ctap2_version;
virt_auth_options->attachment =
transport == device::FidoTransportProtocol::kInternal
? device::AuthenticatorAttachment::kPlatform
: device::AuthenticatorAttachment::kCrossPlatform;
virt_auth_options->has_resident_key = has_resident_key;
virt_auth_options->has_user_verification =
options->GetHasUserVerification(/*default=*/false);
virt_auth_options->has_large_blob = has_large_blob;
virt_auth_options->has_cred_blob = has_cred_blob;
break;
case device::ProtocolVersion::kUnknown:
NOTREACHED();
break;
}
VirtualAuthenticator* const authenticator =
authenticator_manager->AddAuthenticatorAndReturnNonOwningPointer(
*virt_auth_options);
if (!authenticator)
return Response::ServerError(kErrorCreatingAuthenticator);
authenticator->SetUserPresence(
options->GetAutomaticPresenceSimulation(true /* default */));
authenticator->set_user_verified(
options->GetIsUserVerified(/*default=*/false));
*out_authenticator_id = authenticator->unique_id();
return Response::Success();
}
Response WebAuthnHandler::RemoveVirtualAuthenticator(
const String& authenticator_id) {
VirtualAuthenticatorManagerImpl* authenticator_manager =
AuthenticatorEnvironmentImpl::GetInstance()
->MaybeGetVirtualAuthenticatorManager(frame_host_->frame_tree_node());
if (!authenticator_manager)
return Response::ServerError(kVirtualEnvironmentNotEnabled);
if (!authenticator_manager->RemoveAuthenticator(authenticator_id))
return Response::InvalidParams(kAuthenticatorNotFound);
return Response::Success();
}
void WebAuthnHandler::AddCredential(
const String& authenticator_id,
std::unique_ptr<WebAuthn::Credential> credential,
std::unique_ptr<AddCredentialCallback> callback) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess()) {
callback->sendFailure(std::move(response));
return;
}
Binary user_handle = credential->GetUserHandle(Binary());
if (credential->HasUserHandle() &&
user_handle.size() > device::kUserHandleMaxLength) {
callback->sendFailure(Response::InvalidParams(
kInvalidUserHandle +
base::NumberToString(device::kUserHandleMaxLength)));
return;
}
if (!credential->HasRpId()) {
callback->sendFailure(Response::InvalidParams(kRpIdRequired));
return;
}
if (credential->HasLargeBlob() && !credential->GetIsResidentCredential()) {
callback->sendFailure(
Response::InvalidParams(kLargeBlobRequiresResidentKey));
return;
}
bool credential_created;
std::vector<uint8_t> credential_id =
CopyBinaryToVector(credential->GetCredentialId());
if (credential->GetIsResidentCredential()) {
if (!authenticator->has_resident_key()) {
callback->sendFailure(
Response::InvalidParams(kResidentCredentialNotSupported));
return;
}
if (!credential->HasUserHandle()) {
callback->sendFailure(
Response::InvalidParams(kHandleRequiredForResidentCredential));
return;
}
credential_created = authenticator->AddResidentRegistration(
credential_id, credential->GetRpId(""), credential->GetPrivateKey(),
credential->GetSignCount(), CopyBinaryToVector(user_handle));
} else {
credential_created = authenticator->AddRegistration(
credential_id, credential->GetRpId(""), credential->GetPrivateKey(),
credential->GetSignCount());
}
if (!credential_created) {
callback->sendFailure(Response::ServerError(kCouldNotCreateCredential));
return;
}
if (credential->HasLargeBlob()) {
authenticator->SetLargeBlob(
credential_id, CopyBinaryToVector(credential->GetLargeBlob({})),
base::BindOnce(
[](std::unique_ptr<AddCredentialCallback> callback, bool success) {
if (!success) {
callback->sendFailure(
Response::ServerError(kCouldNotStoreLargeBlob));
return;
}
callback->sendSuccess();
},
std::move(callback)));
return;
}
callback->sendSuccess();
}
void WebAuthnHandler::GetCredential(
const String& authenticator_id,
const Binary& credential_id,
std::unique_ptr<GetCredentialCallback> callback) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess()) {
callback->sendFailure(response);
return;
}
auto registration =
authenticator->registrations().find(CopyBinaryToVector(credential_id));
if (registration == authenticator->registrations().end()) {
callback->sendFailure(Response::InvalidParams(kCredentialNotFound));
return;
}
authenticator->GetLargeBlob(
registration->first,
base::BindOnce(
[](std::unique_ptr<WebAuthn::Credential> registration,
std::unique_ptr<GetCredentialCallback> callback,
const base::Optional<std::vector<uint8_t>>& blob) {
if (blob) {
registration->SetLargeBlob(Binary::fromVector(*blob));
}
callback->sendSuccess(std::move(registration));
},
BuildCredentialFromRegistration(*registration), std::move(callback)));
}
void WebAuthnHandler::GetCredentials(
const String& authenticator_id,
std::unique_ptr<GetCredentialsCallback> callback) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess()) {
callback->sendFailure(response);
return;
}
auto aggregator = base::MakeRefCounted<GetCredentialCallbackAggregator>(
std::move(callback));
for (const auto& registration : authenticator->registrations()) {
authenticator->GetLargeBlob(
registration.first,
base::BindOnce(&GetCredentialCallbackAggregator::OnLargeBlob,
aggregator,
BuildCredentialFromRegistration(registration)));
}
}
Response WebAuthnHandler::RemoveCredential(const String& authenticator_id,
const Binary& credential_id) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
if (!authenticator->RemoveRegistration(CopyBinaryToVector(credential_id)))
return Response::InvalidParams(kCredentialNotFound);
return Response::Success();
}
Response WebAuthnHandler::ClearCredentials(const String& authenticator_id) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
authenticator->ClearRegistrations();
return Response::Success();
}
Response WebAuthnHandler::SetUserVerified(const String& authenticator_id,
bool is_user_verified) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
authenticator->set_user_verified(is_user_verified);
return Response::Success();
}
Response WebAuthnHandler::SetAutomaticPresenceSimulation(
const String& authenticator_id,
bool enabled) {
VirtualAuthenticator* authenticator;
Response response = FindAuthenticator(authenticator_id, &authenticator);
if (!response.IsSuccess())
return response;
authenticator->SetUserPresence(enabled);
return Response::Success();
}
Response WebAuthnHandler::FindAuthenticator(
const String& id,
VirtualAuthenticator** out_authenticator) {
*out_authenticator = nullptr;
VirtualAuthenticatorManagerImpl* authenticator_manager =
AuthenticatorEnvironmentImpl::GetInstance()
->MaybeGetVirtualAuthenticatorManager(frame_host_->frame_tree_node());
if (!authenticator_manager)
return Response::ServerError(kVirtualEnvironmentNotEnabled);
*out_authenticator = authenticator_manager->GetAuthenticator(id);
if (!*out_authenticator)
return Response::InvalidParams(kAuthenticatorNotFound);
return Response::Success();
}
} // namespace protocol
} // namespace content