| // 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 |