1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "device/fido/fido_request_handler_base.h"
6 
7 #include <utility>
8 
9 #include "base/barrier_closure.h"
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/strings/string_piece.h"
14 #include "base/threading/sequenced_task_runner_handle.h"
15 #include "base/time/time.h"
16 #include "base/timer/elapsed_timer.h"
17 #include "build/build_config.h"
18 #include "components/device_event_log/device_event_log.h"
19 #include "device/fido/ble_adapter_manager.h"
20 #include "device/fido/fido_authenticator.h"
21 #include "device/fido/fido_discovery_factory.h"
22 
23 #if defined(OS_WIN)
24 #include "device/fido/win/authenticator.h"
25 #endif
26 
27 namespace {
28 // Authenticators that return a response in less than this time are likely to
29 // have done so without interaction from the user.
30 static const base::TimeDelta kMinExpectedAuthenticatorResponseTime =
31     base::TimeDelta::FromMilliseconds(300);
32 }  // namespace
33 
34 namespace device {
35 
36 // FidoRequestHandlerBase::AuthenticatorState ---------------------------------
37 
AuthenticatorState(FidoAuthenticator * authenticator)38 FidoRequestHandlerBase::AuthenticatorState::AuthenticatorState(
39     FidoAuthenticator* authenticator)
40     : authenticator(authenticator) {}
41 
42 FidoRequestHandlerBase::AuthenticatorState::~AuthenticatorState() = default;
43 
44 // FidoRequestHandlerBase::TransportAvailabilityInfo --------------------------
45 
46 FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() =
47     default;
48 
49 FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo(
50     const TransportAvailabilityInfo& data) = default;
51 
52 FidoRequestHandlerBase::TransportAvailabilityInfo&
53 FidoRequestHandlerBase::TransportAvailabilityInfo::operator=(
54     const TransportAvailabilityInfo& other) = default;
55 
56 FidoRequestHandlerBase::TransportAvailabilityInfo::
57     ~TransportAvailabilityInfo() = default;
58 
59 // FidoRequestHandlerBase::Observer ----------------------
60 
61 FidoRequestHandlerBase::Observer::~Observer() = default;
62 
63 // FidoRequestHandlerBase -----------------------------------------------------
64 
FidoRequestHandlerBase(FidoDiscoveryFactory * fido_discovery_factory,const base::flat_set<FidoTransportProtocol> & available_transports)65 FidoRequestHandlerBase::FidoRequestHandlerBase(
66     FidoDiscoveryFactory* fido_discovery_factory,
67     const base::flat_set<FidoTransportProtocol>& available_transports) {
68 #if defined(OS_WIN)
69   InitDiscoveriesWin(fido_discovery_factory, available_transports);
70 #else
71   InitDiscoveries(fido_discovery_factory, available_transports);
72 #endif  // !defined(OS_WIN)
73 }
74 
InitDiscoveries(FidoDiscoveryFactory * fido_discovery_factory,const base::flat_set<FidoTransportProtocol> & available_transports)75 void FidoRequestHandlerBase::InitDiscoveries(
76     FidoDiscoveryFactory* fido_discovery_factory,
77     const base::flat_set<FidoTransportProtocol>& available_transports) {
78   transport_availability_info_.available_transports = available_transports;
79   for (const auto transport : available_transports) {
80     std::unique_ptr<FidoDiscoveryBase> discovery =
81         fido_discovery_factory->Create(transport);
82     if (discovery == nullptr) {
83       // This can occur in tests when a ScopedVirtualU2fDevice is in effect and
84       // HID transports are not configured or when caBLE discovery data isn't
85       // available.
86       transport_availability_info_.available_transports.erase(transport);
87       continue;
88     }
89 
90     discovery->set_observer(this);
91     discoveries_.push_back(std::move(discovery));
92   }
93 
94   bool has_ble = false;
95   if (base::Contains(transport_availability_info_.available_transports,
96                      FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) ||
97       base::Contains(transport_availability_info_.available_transports,
98                      FidoTransportProtocol::kBluetoothLowEnergy)) {
99     has_ble = true;
100     base::SequencedTaskRunnerHandle::Get()->PostTask(
101         FROM_HERE,
102         base::BindOnce(&FidoRequestHandlerBase::ConstructBleAdapterPowerManager,
103                        weak_factory_.GetWeakPtr()));
104   }
105 
106   // Initialize |notify_observer_callback_| with the number of times it has to
107   // be invoked before Observer::OnTransportAvailabilityEnumerated is
108   // dispatched.
109   // Essentially this is used to wait until the parts
110   // |transport_availability_info_| are filled out; the
111   // |notify_observer_callback_| is invoked once for each discovery once it is
112   // ready, and additionally:
113   //
114   // 1) [If BLE or caBLE are enabled] once BLE adapters have been enumerated
115   // 2) When |observer_| is set, so that OnTransportAvailabilityEnumerated is
116   // never called before it is set.
117   notify_observer_callback_ = base::BarrierClosure(
118       discoveries_.size() + has_ble + 1,
119       base::BindOnce(
120           &FidoRequestHandlerBase::NotifyObserverTransportAvailability,
121           weak_factory_.GetWeakPtr()));
122 }
123 
124 #if defined(OS_WIN)
InitDiscoveriesWin(FidoDiscoveryFactory * fido_discovery_factory,const base::flat_set<FidoTransportProtocol> & available_transports)125 void FidoRequestHandlerBase::InitDiscoveriesWin(
126     FidoDiscoveryFactory* fido_discovery_factory,
127     const base::flat_set<FidoTransportProtocol>& available_transports) {
128   // Try to instantiate the discovery for proxying requests to the native
129   // Windows WebAuthn API; or fall back to using the regular device transport
130   // discoveries if the API is unavailable.
131   auto discovery = fido_discovery_factory->MaybeCreateWinWebAuthnApiDiscovery();
132   if (!discovery) {
133     InitDiscoveries(fido_discovery_factory, available_transports);
134     return;
135   }
136 
137   // The Windows WebAuthn API is available. On this platform, communicating
138   // with authenticator devices directly is blocked by the OS, so we need to go
139   // through the native API instead. No device discoveries may be instantiated.
140   discovery->set_observer(this);
141   discoveries_.push_back(std::move(discovery));
142 
143   //  Setting |has_win_native_api_authenticator| ensures
144   //  NotifyObserverTransportAvailability() will not be invoked before
145   //  Windows Authenticator has been added. The embedder will be
146   //  responsible for dispatch of the authenticator and whether they
147   //  display any UI in addition to the one provided by the OS.
148   transport_availability_info_.has_win_native_api_authenticator = true;
149 
150   // Allow caBLE as a potential additional transport if requested by
151   // the implementing class because it is not subject to the OS'
152   // device communication block (only GetAssertionRequestHandler uses
153   // caBLE). Otherwise, do not instantiate any other transports.
154   base::flat_set<FidoTransportProtocol> other_transports = {};
155   if (base::Contains(available_transports,
156                      FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy)) {
157     other_transports = {
158         FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy};
159   }
160 
161   InitDiscoveries(fido_discovery_factory, other_transports);
162 }
163 #endif  // defined(OS_WIN)
164 
~FidoRequestHandlerBase()165 FidoRequestHandlerBase::~FidoRequestHandlerBase() {
166   CancelActiveAuthenticators();
167 }
168 
StartAuthenticatorRequest(const std::string & authenticator_id)169 void FidoRequestHandlerBase::StartAuthenticatorRequest(
170     const std::string& authenticator_id) {
171   InitializeAuthenticatorAndDispatchRequest(authenticator_id);
172 }
173 
CancelActiveAuthenticators(base::StringPiece exclude_device_id)174 void FidoRequestHandlerBase::CancelActiveAuthenticators(
175     base::StringPiece exclude_device_id) {
176   for (auto task_it = active_authenticators_.begin();
177        task_it != active_authenticators_.end();) {
178     DCHECK(!task_it->first.empty());
179     if (task_it->first != exclude_device_id) {
180       DCHECK(task_it->second);
181       task_it->second->authenticator->Cancel();
182 
183       // Note that the pointer being erased is non-owning. The actual
184       // FidoAuthenticator instance is owned by its discovery (which in turn is
185       // owned by |discoveries_|.
186       task_it = active_authenticators_.erase(task_it);
187     } else {
188       ++task_it;
189     }
190   }
191 }
192 
OnBluetoothAdapterEnumerated(bool is_present,bool is_powered_on,bool can_power_on)193 void FidoRequestHandlerBase::OnBluetoothAdapterEnumerated(bool is_present,
194                                                           bool is_powered_on,
195                                                           bool can_power_on) {
196   if (!is_present) {
197     transport_availability_info_.available_transports.erase(
198         FidoTransportProtocol::kBluetoothLowEnergy);
199     transport_availability_info_.available_transports.erase(
200         FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy);
201   }
202 
203   transport_availability_info_.is_ble_powered = is_powered_on;
204   transport_availability_info_.can_power_on_ble_adapter = can_power_on;
205   DCHECK(notify_observer_callback_);
206   notify_observer_callback_.Run();
207 }
208 
OnBluetoothAdapterPowerChanged(bool is_powered_on)209 void FidoRequestHandlerBase::OnBluetoothAdapterPowerChanged(
210     bool is_powered_on) {
211   transport_availability_info_.is_ble_powered = is_powered_on;
212 
213   if (observer_)
214     observer_->BluetoothAdapterPowerChanged(is_powered_on);
215 }
216 
PowerOnBluetoothAdapter()217 void FidoRequestHandlerBase::PowerOnBluetoothAdapter() {
218   if (!bluetooth_adapter_manager_)
219     return;
220 
221   bluetooth_adapter_manager_->SetAdapterPower(true /* set_power_on */);
222 }
223 
InitiatePairingWithDevice(std::string authenticator_id,base::Optional<std::string> pin_code,base::OnceClosure success_callback,base::OnceClosure error_callback)224 void FidoRequestHandlerBase::InitiatePairingWithDevice(
225     std::string authenticator_id,
226     base::Optional<std::string> pin_code,
227     base::OnceClosure success_callback,
228     base::OnceClosure error_callback) {
229   if (!bluetooth_adapter_manager_)
230     return;
231 
232   bluetooth_adapter_manager_->InitiatePairing(
233       std::move(authenticator_id), std::move(pin_code),
234       std::move(success_callback), std::move(error_callback));
235 }
236 
GetWeakPtr()237 base::WeakPtr<FidoRequestHandlerBase> FidoRequestHandlerBase::GetWeakPtr() {
238   return weak_factory_.GetWeakPtr();
239 }
240 
Start()241 void FidoRequestHandlerBase::Start() {
242   for (const auto& discovery : discoveries_)
243     discovery->Start();
244 }
245 
AuthenticatorMayHaveReturnedImmediately(const std::string & authenticator_id)246 bool FidoRequestHandlerBase::AuthenticatorMayHaveReturnedImmediately(
247     const std::string& authenticator_id) {
248   auto it = active_authenticators_.find(authenticator_id);
249   if (it == active_authenticators_.end())
250     return false;
251 
252   if (!it->second->timer)
253     return true;
254 
255   FIDO_LOG(DEBUG) << "Authenticator returned in "
256                   << it->second->timer->Elapsed();
257   return it->second->timer->Elapsed() < kMinExpectedAuthenticatorResponseTime;
258 }
259 
AuthenticatorRemoved(FidoDiscoveryBase * discovery,FidoAuthenticator * authenticator)260 void FidoRequestHandlerBase::AuthenticatorRemoved(
261     FidoDiscoveryBase* discovery,
262     FidoAuthenticator* authenticator) {
263   // Device connection has been lost or device has already been removed.
264   // Thus, calling CancelTask() is not necessary. Also, below
265   // ongoing_tasks_.erase() will have no effect for the devices that have been
266   // already removed due to processing error or due to invocation of
267   // CancelOngoingTasks().
268   DCHECK(authenticator);
269   active_authenticators_.erase(authenticator->GetId());
270 
271   if (observer_)
272     observer_->FidoAuthenticatorRemoved(authenticator->GetId());
273 }
274 
AuthenticatorIdChanged(FidoDiscoveryBase * discovery,const std::string & previous_id,std::string new_id)275 void FidoRequestHandlerBase::AuthenticatorIdChanged(
276     FidoDiscoveryBase* discovery,
277     const std::string& previous_id,
278     std::string new_id) {
279   DCHECK_EQ(FidoTransportProtocol::kBluetoothLowEnergy, discovery->transport());
280   auto it = active_authenticators_.find(previous_id);
281   if (it == active_authenticators_.end())
282     return;
283 
284   active_authenticators_.emplace(new_id, std::move(it->second));
285   active_authenticators_.erase(it);
286 
287   if (observer_)
288     observer_->FidoAuthenticatorIdChanged(previous_id, std::move(new_id));
289 }
290 
AuthenticatorPairingModeChanged(FidoDiscoveryBase * discovery,const std::string & device_id,bool is_in_pairing_mode)291 void FidoRequestHandlerBase::AuthenticatorPairingModeChanged(
292     FidoDiscoveryBase* discovery,
293     const std::string& device_id,
294     bool is_in_pairing_mode) {
295   DCHECK_EQ(FidoTransportProtocol::kBluetoothLowEnergy, discovery->transport());
296   auto it = active_authenticators_.find(device_id);
297   if (it == active_authenticators_.end())
298     return;
299 
300   if (observer_) {
301     observer_->FidoAuthenticatorPairingModeChanged(
302         device_id, is_in_pairing_mode,
303         it->second->authenticator->GetDisplayName());
304   }
305 }
306 
DiscoveryStarted(FidoDiscoveryBase * discovery,bool success,std::vector<FidoAuthenticator * > authenticators)307 void FidoRequestHandlerBase::DiscoveryStarted(
308     FidoDiscoveryBase* discovery,
309     bool success,
310     std::vector<FidoAuthenticator*> authenticators) {
311   if (!success) {
312     transport_availability_info_.available_transports.erase(
313         discovery->transport());
314   } else {
315     for (auto* authenticator : authenticators) {
316       AuthenticatorAdded(discovery, authenticator);
317     }
318   }
319   DCHECK(notify_observer_callback_);
320   notify_observer_callback_.Run();
321 }
322 
AuthenticatorAdded(FidoDiscoveryBase * discovery,FidoAuthenticator * authenticator)323 void FidoRequestHandlerBase::AuthenticatorAdded(
324     FidoDiscoveryBase* discovery,
325     FidoAuthenticator* authenticator) {
326   DCHECK(!authenticator->GetId().empty());
327   bool was_inserted;
328   std::tie(std::ignore, was_inserted) = active_authenticators_.emplace(
329        authenticator->GetId(),
330        std::make_unique<AuthenticatorState>(authenticator));
331   if (!was_inserted) {
332     NOTREACHED();
333     FIDO_LOG(ERROR) << "Authenticator with duplicate ID "
334                     << authenticator->GetId();
335     return;
336   }
337 
338   // If |observer_| exists, dispatching request to |authenticator| is
339   // delegated to |observer_|. Else, dispatch request to |authenticator|
340   // immediately.
341   bool embedder_controls_dispatch = false;
342   if (observer_) {
343     embedder_controls_dispatch =
344         observer_->EmbedderControlsAuthenticatorDispatch(*authenticator);
345     observer_->FidoAuthenticatorAdded(*authenticator);
346   }
347 
348   if (!embedder_controls_dispatch) {
349     // Post |InitializeAuthenticatorAndDispatchRequest| into its own task. This
350     // avoids hairpinning, even if the authenticator immediately invokes the
351     // request callback.
352     VLOG(2)
353         << "Request handler dispatching request to authenticator immediately.";
354     base::SequencedTaskRunnerHandle::Get()->PostTask(
355         FROM_HERE,
356         base::BindOnce(
357             &FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest,
358             GetWeakPtr(), authenticator->GetId()));
359   } else {
360     VLOG(2) << "Embedder controls the dispatch.";
361   }
362 
363 #if defined(OS_WIN)
364   if (authenticator->IsWinNativeApiAuthenticator()) {
365     DCHECK(transport_availability_info_.has_win_native_api_authenticator);
366     transport_availability_info_.win_native_api_authenticator_id =
367         authenticator->GetId();
368     transport_availability_info_
369         .win_native_ui_shows_resident_credential_notice =
370         static_cast<WinWebAuthnApiAuthenticator*>(authenticator)
371             ->ShowsPrivacyNotice();
372   }
373 #endif  // defined(OS_WIN)
374 }
375 
HasAuthenticator(const std::string & authenticator_id) const376 bool FidoRequestHandlerBase::HasAuthenticator(
377     const std::string& authenticator_id) const {
378   return base::Contains(active_authenticators_, authenticator_id);
379 }
380 
NotifyObserverTransportAvailability()381 void FidoRequestHandlerBase::NotifyObserverTransportAvailability() {
382   DCHECK(observer_);
383   observer_->OnTransportAvailabilityEnumerated(transport_availability_info_);
384 }
385 
InitializeAuthenticatorAndDispatchRequest(const std::string & authenticator_id)386 void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest(
387     const std::string& authenticator_id) {
388   auto authenticator_it = active_authenticators_.find(authenticator_id);
389   if (authenticator_it == active_authenticators_.end()) {
390     return;
391   }
392   AuthenticatorState* authenticator_state = authenticator_it->second.get();
393   authenticator_state->timer = std::make_unique<base::ElapsedTimer>();
394   authenticator_state->authenticator->InitializeAuthenticator(base::BindOnce(
395       &FidoRequestHandlerBase::DispatchRequest, weak_factory_.GetWeakPtr(),
396       authenticator_state->authenticator));
397 }
398 
ConstructBleAdapterPowerManager()399 void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() {
400   bluetooth_adapter_manager_ = std::make_unique<BleAdapterManager>(this);
401 }
402 
StopDiscoveries()403 void FidoRequestHandlerBase::StopDiscoveries() {
404   for (const auto& discovery : discoveries_) {
405     discovery->MaybeStop();
406   }
407 }
408 
409 }  // namespace device
410