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