| // Copyright 2018 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 "device/fido/fido_request_handler_base.h" |
| |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/strings/string_piece.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "device/fido/ble_adapter_manager.h" |
| #include "device/fido/fido_discovery_factory.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| |
| namespace device { |
| |
| // PlatformAuthenticatorInfo -------------------------- |
| |
| PlatformAuthenticatorInfo::PlatformAuthenticatorInfo( |
| std::unique_ptr<FidoAuthenticator> authenticator_, |
| bool has_recognized_mac_touch_id_credential_) |
| : authenticator(std::move(authenticator_)), |
| has_recognized_mac_touch_id_credential( |
| has_recognized_mac_touch_id_credential_) {} |
| PlatformAuthenticatorInfo::PlatformAuthenticatorInfo( |
| PlatformAuthenticatorInfo&&) = default; |
| PlatformAuthenticatorInfo& PlatformAuthenticatorInfo::operator=( |
| PlatformAuthenticatorInfo&&) = default; |
| PlatformAuthenticatorInfo::~PlatformAuthenticatorInfo() = default; |
| |
| // FidoRequestHandlerBase::TransportAvailabilityInfo -------------------------- |
| |
| FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() = |
| default; |
| |
| FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo( |
| const TransportAvailabilityInfo& data) = default; |
| |
| FidoRequestHandlerBase::TransportAvailabilityInfo& |
| FidoRequestHandlerBase::TransportAvailabilityInfo::operator=( |
| const TransportAvailabilityInfo& other) = default; |
| |
| FidoRequestHandlerBase::TransportAvailabilityInfo:: |
| ~TransportAvailabilityInfo() = default; |
| |
| // FidoRequestHandlerBase::TransportAvailabilityObserver ---------------------- |
| |
| FidoRequestHandlerBase::TransportAvailabilityObserver:: |
| ~TransportAvailabilityObserver() = default; |
| |
| // FidoRequestHandlerBase ----------------------------------------------------- |
| |
| FidoRequestHandlerBase::FidoRequestHandlerBase( |
| service_manager::Connector* connector, |
| const base::flat_set<FidoTransportProtocol>& available_transports) |
| : connector_(connector), weak_factory_(this) { |
| #if defined(OS_WIN) |
| InitDiscoveriesWin(available_transports); |
| #else |
| InitDiscoveries(available_transports); |
| #endif // !defined(OS_WIN) |
| } |
| |
| void FidoRequestHandlerBase::InitDiscoveries( |
| const base::flat_set<FidoTransportProtocol>& available_transports) { |
| // The number of times |notify_observer_callback_| needs to be invoked before |
| // Observer::OnTransportAvailabilityEnumerated is dispatched. Essentially this |
| // is used to wait until all the parts of |transport_availability_info_| are |
| // filled out; the |notify_observer_callback_| is invoked once for each part |
| // once that part is ready, namely: |
| // |
| // 1) Once the platform authenticator related fields are filled out. |
| // 2) [Optionally, if BLE or caBLE enabled] if Bluetooth adapter is present. |
| // |
| // On top of that, we wait for (3) an invocation that happens when the |
| // |observer_| is set, so that OnTransportAvailabilityEnumerated is never |
| // called before the observer is set. |
| size_t transport_info_callback_count = 1u + 0u + 1u; |
| |
| for (const auto transport : available_transports) { |
| // Construction of CaBleDiscovery is handled by the implementing class as it |
| // requires an extension passed on from the relying party. |
| if (transport == FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) |
| continue; |
| |
| if (transport == FidoTransportProtocol::kInternal) { |
| // Platform authenticator availability is always indicated by |
| // |AuthenticatorImpl|. |
| continue; |
| } |
| |
| auto discovery = FidoDiscoveryFactory::Create(transport, connector_); |
| if (discovery == nullptr) { |
| // This can occur in tests when a ScopedVirtualU2fDevice is in effect and |
| // HID transports are not configured. |
| continue; |
| } |
| |
| discovery->set_observer(this); |
| discoveries_.push_back(std::move(discovery)); |
| } |
| |
| if (base::ContainsKey( |
| available_transports, |
| FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) || |
| base::ContainsKey(available_transports, |
| FidoTransportProtocol::kBluetoothLowEnergy)) { |
| ++transport_info_callback_count; |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&FidoRequestHandlerBase::ConstructBleAdapterPowerManager, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| transport_availability_info_.available_transports = available_transports; |
| notify_observer_callback_ = base::BarrierClosure( |
| transport_info_callback_count, |
| base::BindOnce( |
| &FidoRequestHandlerBase::NotifyObserverTransportAvailability, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| #if defined(OS_WIN) |
| void FidoRequestHandlerBase::InitDiscoveriesWin( |
| const base::flat_set<FidoTransportProtocol>& available_transports) { |
| // Try to instantiate the discovery for proxying requests to the native |
| // Windows WebAuthn API; or fall back to using the regular device transport |
| // discoveries if the API is unavailable. |
| auto discovery = FidoDiscoveryFactory::MaybeCreateWinWebAuthnApiDiscovery(); |
| if (!discovery) { |
| InitDiscoveries(available_transports); |
| return; |
| } |
| |
| // The Windows WebAuthn API is available. On this platform, communicating |
| // with authenticator devices directly is blocked by the OS, so we need to go |
| // through the native API instead. No device discoveries may be instantiated. |
| // |
| // The Windows API supports USB, NFC, BLE and platform authenticators, but |
| // not caBLE. Communicating with caBLE devices directly is subject to the |
| // same block by the OS, so this platform is without caBLE support for now. |
| // |
| // TODO(martinkr): Re-enable the caBLE discovery once caBLE has moved to a |
| // different UUID. See crbug.com/905111. |
| |
| discovery->set_observer(this); |
| discoveries_.push_back(std::move(discovery)); |
| |
| // Tell the embedder to not render a UI and ignore all future callbacks. Also |
| // don't report any available transports; the embedder is not supposed to use |
| // this information anyway. |
| transport_availability_info_.disable_embedder_ui = true; |
| transport_availability_info_.available_transports = {}; |
| |
| // The number of times |notify_observer_callback_| needs to be invoked before |
| // Observer::OnTransportAvailabilityEnumerated is dispatched. Essentially this |
| // is used to wait until all the parts of |transport_availability_info_| are |
| // filled out. In the case of Windows, there are no transport discoveries to |
| // wait for, so the |notify_observer_callback_| is only invoked in: |
| // 1) SetPlatformAuthenticatorOrMarkUnavailable(). |
| // 2) set_observer(). |
| constexpr size_t transport_info_callback_count = 2u; |
| |
| notify_observer_callback_ = base::BarrierClosure( |
| transport_info_callback_count, |
| base::BindOnce( |
| &FidoRequestHandlerBase::NotifyObserverTransportAvailability, |
| weak_factory_.GetWeakPtr())); |
| } |
| #endif // defined(OS_WIN) |
| |
| FidoRequestHandlerBase::~FidoRequestHandlerBase() = default; |
| |
| void FidoRequestHandlerBase::StartAuthenticatorRequest( |
| const std::string& authenticator_id) { |
| auto authenticator_it = active_authenticators_.find(authenticator_id); |
| if (authenticator_it == active_authenticators_.end()) |
| return; |
| |
| InitializeAuthenticatorAndDispatchRequest(authenticator_it->second); |
| } |
| |
| void FidoRequestHandlerBase::CancelActiveAuthenticators( |
| base::StringPiece exclude_device_id) { |
| for (auto task_it = active_authenticators_.begin(); |
| task_it != active_authenticators_.end();) { |
| DCHECK(!task_it->first.empty()); |
| if (task_it->first != exclude_device_id) { |
| DCHECK(task_it->second); |
| task_it->second->Cancel(); |
| |
| // Note that the pointer being erased is non-owning. The actual |
| // FidoAuthenticator instance is owned by its discovery (which in turn is |
| // owned by |discoveries_|. |
| task_it = active_authenticators_.erase(task_it); |
| } else { |
| ++task_it; |
| } |
| } |
| } |
| |
| void FidoRequestHandlerBase::OnBluetoothAdapterEnumerated(bool is_present, |
| bool is_powered_on, |
| bool can_power_on) { |
| if (!is_present) { |
| transport_availability_info_.available_transports.erase( |
| FidoTransportProtocol::kBluetoothLowEnergy); |
| transport_availability_info_.available_transports.erase( |
| FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy); |
| } |
| |
| transport_availability_info_.is_ble_powered = is_powered_on; |
| transport_availability_info_.can_power_on_ble_adapter = can_power_on; |
| DCHECK(notify_observer_callback_); |
| notify_observer_callback_.Run(); |
| } |
| |
| void FidoRequestHandlerBase::OnBluetoothAdapterPowerChanged( |
| bool is_powered_on) { |
| transport_availability_info_.is_ble_powered = is_powered_on; |
| |
| if (observer_) |
| observer_->BluetoothAdapterPowerChanged(is_powered_on); |
| } |
| |
| void FidoRequestHandlerBase::PowerOnBluetoothAdapter() { |
| if (!bluetooth_adapter_manager_) |
| return; |
| |
| bluetooth_adapter_manager_->SetAdapterPower(true /* set_power_on */); |
| } |
| |
| void FidoRequestHandlerBase::InitiatePairingWithDevice( |
| std::string authenticator_id, |
| std::string pin_code, |
| base::OnceClosure success_callback, |
| base::OnceClosure error_callback) { |
| if (!bluetooth_adapter_manager_) |
| return; |
| |
| bluetooth_adapter_manager_->InitiatePairing( |
| std::move(authenticator_id), std::move(pin_code), |
| std::move(success_callback), std::move(error_callback)); |
| } |
| |
| base::WeakPtr<FidoRequestHandlerBase> FidoRequestHandlerBase::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void FidoRequestHandlerBase::Start() { |
| for (const auto& discovery : discoveries_) |
| discovery->Start(); |
| } |
| |
| void FidoRequestHandlerBase::AuthenticatorAdded( |
| FidoDiscoveryBase* discovery, |
| FidoAuthenticator* authenticator) { |
| DCHECK(!base::ContainsKey(active_authenticators(), authenticator->GetId())); |
| AddAuthenticator(authenticator); |
| } |
| |
| void FidoRequestHandlerBase::AuthenticatorRemoved( |
| FidoDiscoveryBase* discovery, |
| FidoAuthenticator* authenticator) { |
| // Device connection has been lost or device has already been removed. |
| // Thus, calling CancelTask() is not necessary. Also, below |
| // ongoing_tasks_.erase() will have no effect for the devices that have been |
| // already removed due to processing error or due to invocation of |
| // CancelOngoingTasks(). |
| DCHECK(authenticator); |
| active_authenticators_.erase(authenticator->GetId()); |
| |
| if (observer_) |
| observer_->FidoAuthenticatorRemoved(authenticator->GetId()); |
| } |
| |
| void FidoRequestHandlerBase::AuthenticatorIdChanged( |
| FidoDiscoveryBase* discovery, |
| const std::string& previous_id, |
| std::string new_id) { |
| DCHECK_EQ(FidoTransportProtocol::kBluetoothLowEnergy, discovery->transport()); |
| auto it = active_authenticators_.find(previous_id); |
| if (it == active_authenticators_.end()) |
| return; |
| |
| active_authenticators_.emplace(new_id, std::move(it->second)); |
| active_authenticators_.erase(it); |
| |
| if (observer_) |
| observer_->FidoAuthenticatorIdChanged(previous_id, std::move(new_id)); |
| } |
| |
| void FidoRequestHandlerBase::AuthenticatorPairingModeChanged( |
| FidoDiscoveryBase* discovery, |
| const std::string& device_id, |
| bool is_in_pairing_mode) { |
| DCHECK_EQ(FidoTransportProtocol::kBluetoothLowEnergy, discovery->transport()); |
| auto it = active_authenticators_.find(device_id); |
| if (it == active_authenticators_.end()) |
| return; |
| |
| if (observer_) |
| observer_->FidoAuthenticatorPairingModeChanged(device_id, |
| is_in_pairing_mode); |
| } |
| |
| void FidoRequestHandlerBase::AddAuthenticator( |
| FidoAuthenticator* authenticator) { |
| DCHECK(authenticator && |
| !base::ContainsKey(active_authenticators(), authenticator->GetId())); |
| active_authenticators_.emplace(authenticator->GetId(), authenticator); |
| |
| // If |observer_| exists, dispatching request to |authenticator| is |
| // delegated to |observer_|. Else, dispatch request to |authenticator| |
| // immediately. |
| bool embedder_controls_dispatch = false; |
| if (observer_) { |
| embedder_controls_dispatch = |
| observer_->EmbedderControlsAuthenticatorDispatch(*authenticator); |
| observer_->FidoAuthenticatorAdded(*authenticator); |
| } |
| |
| if (!embedder_controls_dispatch) { |
| // Post |InitializeAuthenticatorAndDispatchRequest| into its own task. This |
| // avoids hairpinning, even if the authenticator immediately invokes the |
| // request callback. |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest, |
| GetWeakPtr(), authenticator)); |
| } |
| } |
| |
| void FidoRequestHandlerBase::SetPlatformAuthenticatorOrMarkUnavailable( |
| base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info) { |
| DCHECK(!platform_authenticator_); |
| if (platform_authenticator_info && |
| base::ContainsKey(transport_availability_info_.available_transports, |
| FidoTransportProtocol::kInternal)) { |
| DCHECK(platform_authenticator_info->authenticator); |
| DCHECK( |
| platform_authenticator_info->authenticator->AuthenticatorTransport() && |
| *platform_authenticator_info->authenticator->AuthenticatorTransport() == |
| FidoTransportProtocol::kInternal); |
| transport_availability_info_.has_recognized_mac_touch_id_credential = |
| platform_authenticator_info->has_recognized_mac_touch_id_credential; |
| platform_authenticator_ = |
| std::move(platform_authenticator_info->authenticator); |
| AddAuthenticator(platform_authenticator_.get()); |
| } else { |
| transport_availability_info_.available_transports.erase( |
| FidoTransportProtocol::kInternal); |
| } |
| |
| DCHECK(notify_observer_callback_); |
| notify_observer_callback_.Run(); |
| } |
| |
| bool FidoRequestHandlerBase::HasAuthenticator( |
| const std::string& authenticator_id) const { |
| return base::ContainsKey(active_authenticators_, authenticator_id); |
| } |
| |
| void FidoRequestHandlerBase::NotifyObserverTransportAvailability() { |
| DCHECK(observer_); |
| observer_->OnTransportAvailabilityEnumerated(transport_availability_info_); |
| } |
| |
| void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest( |
| FidoAuthenticator* authenticator) { |
| authenticator->InitializeAuthenticator( |
| base::BindOnce(&FidoRequestHandlerBase::DispatchRequest, |
| weak_factory_.GetWeakPtr(), authenticator)); |
| } |
| |
| void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() { |
| bluetooth_adapter_manager_ = std::make_unique<BleAdapterManager>(this); |
| } |
| |
| } // namespace device |