1 // Copyright 2015 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.
5 #include "components/proximity_auth/ble/proximity_auth_ble_system.h"
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/time/default_tick_clock.h"
15 #include "components/proximity_auth/ble/bluetooth_low_energy_connection.h"
16 #include "components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h"
17 #include "components/proximity_auth/ble/bluetooth_low_energy_device_whitelist.h"
18 #include "components/proximity_auth/ble/fake_wire_message.h"
19 #include "components/proximity_auth/bluetooth_throttler_impl.h"
20 #include "components/proximity_auth/connection.h"
21 #include "components/proximity_auth/cryptauth/base64url.h"
22 #include "components/proximity_auth/cryptauth/cryptauth_client.h"
23 #include "components/proximity_auth/cryptauth/proto/cryptauth_api.pb.h"
24 #include "components/proximity_auth/logging/logging.h"
25 #include "components/proximity_auth/proximity_auth_client.h"
26 #include "components/proximity_auth/remote_device.h"
27 #include "components/proximity_auth/wire_message.h"
28 #include "device/bluetooth/bluetooth_device.h"
29 #include "device/bluetooth/bluetooth_gatt_connection.h"
31 namespace proximity_auth
{
35 // The UUID of the Bluetooth Low Energy service.
36 const char kSmartLockServiceUUID
[] = "b3b7e28e-a000-3e17-bd86-6e97b9e28c11";
38 // The UUID of the characteristic used to send data to the peripheral.
39 const char kToPeripheralCharUUID
[] = "977c6674-1239-4e72-993b-502369b8bb5a";
41 // The UUID of the characteristic used to receive data from the peripheral.
42 const char kFromPeripheralCharUUID
[] = "f4b904a2-a030-43b3-98a8-221c536c03cb";
44 // Polling interval in seconds.
45 const int kPollingIntervalSeconds
= 5;
47 // String received when the remote device's screen is unlocked.
48 const char kScreenUnlocked
[] = "Screen Unlocked";
50 // String received when the remote device's screen is locked.
51 const char kScreenLocked
[] = "Screen Locked";
53 // String send to poll the remote device screen state.
54 const char kPollScreenState
[] = "PollScreenState";
56 // String prefix received with the public key.
57 const char kPublicKeyMessagePrefix
[] = "PublicKey:";
59 // BluetoothLowEnergyConnection parameter, number of attempts to send a write
60 // request before failing.
61 const int kMaxNumberOfTries
= 2;
63 // The time, in seconds, to show a spinner for the user pod immediately after
64 // the screen is locked.
65 const int kSpinnerTimeSeconds
= 15;
67 // Text shown on the user pod when unlock is allowed.
68 const char kUserPodUnlockText
[] = "Click your photo";
70 // Text of tooltip shown on when hovering over the user pod icon when unlock is
72 const char kUserPodIconLockedTooltip
[] = "Unable to find an unlocked phone.";
76 ProximityAuthBleSystem::ProximityAuthBleSystem(
77 ScreenlockBridge
* screenlock_bridge
,
78 ProximityAuthClient
* proximity_auth_client
,
79 scoped_ptr
<CryptAuthClientFactory
> cryptauth_client_factory
,
80 PrefService
* pref_service
)
81 : screenlock_bridge_(screenlock_bridge
),
82 proximity_auth_client_(proximity_auth_client
),
83 cryptauth_client_factory_(cryptauth_client_factory
.Pass()),
84 device_whitelist_(new BluetoothLowEnergyDeviceWhitelist(pref_service
)),
85 bluetooth_throttler_(new BluetoothThrottlerImpl(
86 make_scoped_ptr(new base::DefaultTickClock()))),
87 device_authenticated_(false),
88 is_polling_screen_state_(false),
89 unlock_keys_requested_(false),
90 weak_ptr_factory_(this) {
91 PA_LOG(INFO
) << "Starting Proximity Auth over Bluetooth Low Energy.";
92 screenlock_bridge_
->AddObserver(this);
95 ProximityAuthBleSystem::~ProximityAuthBleSystem() {
96 PA_LOG(INFO
) << "Stopping Proximity over Bluetooth Low Energy.";
97 screenlock_bridge_
->RemoveObserver(this);
99 connection_
->RemoveObserver(this);
102 void ProximityAuthBleSystem::RegisterPrefs(PrefRegistrySimple
* registry
) {
103 BluetoothLowEnergyDeviceWhitelist::RegisterPrefs(registry
);
106 void ProximityAuthBleSystem::OnGetMyDevices(
107 const cryptauth::GetMyDevicesResponse
& response
) {
108 PA_LOG(INFO
) << "Found " << response
.devices_size()
109 << " devices on CryptAuth.";
110 unlock_keys_
.clear();
111 for (const auto& device
: response
.devices()) {
112 // Cache BLE devices (|bluetooth_address().empty() == true|) that are
113 // keys (|unlock_key() == 1|).
114 if (device
.unlock_key() && device
.bluetooth_address().empty()) {
115 std::string base64_public_key
;
116 Base64UrlEncode(device
.public_key(), &base64_public_key
);
117 unlock_keys_
[base64_public_key
] = device
.friendly_device_name();
119 PA_LOG(INFO
) << "friendly_name = " << device
.friendly_device_name();
120 PA_LOG(INFO
) << "public_key = " << base64_public_key
;
123 PA_LOG(INFO
) << "Found " << unlock_keys_
.size() << " unlock keys.";
125 RemoveStaleWhitelistedDevices();
128 void ProximityAuthBleSystem::OnGetMyDevicesError(const std::string
& error
) {
129 PA_LOG(INFO
) << "GetMyDevices failed: " << error
;
132 // This should be called exclusively after the user has logged in. For instance,
133 // calling |GetUnlockKeys| from the constructor cause |GetMyDevices| to always
135 void ProximityAuthBleSystem::GetUnlockKeys() {
136 PA_LOG(INFO
) << "Fetching unlock keys.";
137 unlock_keys_requested_
= true;
138 if (cryptauth_client_factory_
) {
139 cryptauth_client_
= cryptauth_client_factory_
->CreateInstance();
140 cryptauth::GetMyDevicesRequest request
;
141 cryptauth_client_
->GetMyDevices(
142 request
, base::Bind(&ProximityAuthBleSystem::OnGetMyDevices
,
143 weak_ptr_factory_
.GetWeakPtr()),
144 base::Bind(&ProximityAuthBleSystem::OnGetMyDevicesError
,
145 weak_ptr_factory_
.GetWeakPtr()));
149 void ProximityAuthBleSystem::RemoveStaleWhitelistedDevices() {
150 PA_LOG(INFO
) << "Removing stale whitelist devices.";
151 std::vector
<std::string
> public_keys
= device_whitelist_
->GetPublicKeys();
152 PA_LOG(INFO
) << "There were " << public_keys
.size()
153 << " whitelisted devices.";
155 for (const auto& public_key
: public_keys
) {
156 if (unlock_keys_
.find(public_key
) == unlock_keys_
.end()) {
157 PA_LOG(INFO
) << "Removing device: " << public_key
;
158 device_whitelist_
->RemoveDeviceWithPublicKey(public_key
);
161 public_keys
= device_whitelist_
->GetPublicKeys();
162 PA_LOG(INFO
) << "There are " << public_keys
.size() << " whitelisted devices.";
165 void ProximityAuthBleSystem::OnScreenDidLock(
166 ScreenlockBridge::LockHandler::ScreenType screen_type
) {
167 PA_LOG(INFO
) << "OnScreenDidLock: " << screen_type
;
168 switch (screen_type
) {
169 case ScreenlockBridge::LockHandler::SIGNIN_SCREEN
:
170 connection_finder_
.reset();
172 case ScreenlockBridge::LockHandler::LOCK_SCREEN
:
173 DCHECK(!connection_finder_
);
174 connection_finder_
.reset(CreateConnectionFinder());
175 connection_finder_
->Find(
176 base::Bind(&ProximityAuthBleSystem::OnConnectionFound
,
177 weak_ptr_factory_
.GetWeakPtr()));
179 case ScreenlockBridge::LockHandler::OTHER_SCREEN
:
180 connection_finder_
.reset();
184 // Reset the screen lock UI state to the default state.
185 is_remote_screen_locked_
= true;
186 screenlock_ui_state_
= ScreenlockUIState::NO_SCREENLOCK
;
187 last_focused_user_
= screenlock_bridge_
->focused_user_id();
188 spinner_timer_
.Start(FROM_HERE
,
189 base::TimeDelta::FromSeconds(kSpinnerTimeSeconds
), this,
190 &ProximityAuthBleSystem::OnSpinnerTimerFired
);
191 UpdateLockScreenUI();
194 ConnectionFinder
* ProximityAuthBleSystem::CreateConnectionFinder() {
195 return new BluetoothLowEnergyConnectionFinder(
196 kSmartLockServiceUUID
, kToPeripheralCharUUID
, kFromPeripheralCharUUID
,
197 device_whitelist_
.get(), bluetooth_throttler_
.get(), kMaxNumberOfTries
);
200 void ProximityAuthBleSystem::OnScreenDidUnlock(
201 ScreenlockBridge::LockHandler::ScreenType screen_type
) {
202 PA_LOG(INFO
) << "OnScreenDidUnlock: " << screen_type
;
205 // Note: it's important to remove the observer before calling
206 // |Disconnect()|, otherwise |OnConnectedStatusChanged()| will be called
207 // from |connection_| and a new instance for |connection_finder_| will be
209 connection_
->RemoveObserver(this);
210 connection_
->Disconnect();
211 device_authenticated_
= false;
215 connection_finder_
.reset();
218 void ProximityAuthBleSystem::OnFocusedUserChanged(const std::string
& user_id
) {
219 PA_LOG(INFO
) << "OnFocusedUserChanged: " << user_id
;
220 // TODO(tengs): We assume that the last focused user is the one with Smart
221 // Lock enabled. This may not be the case for multiprofile scenarios.
222 last_focused_user_
= user_id
;
223 UpdateLockScreenUI();
226 void ProximityAuthBleSystem::OnMessageReceived(const Connection
& connection
,
227 const WireMessage
& message
) {
228 PA_LOG(INFO
) << "Message with " << message
.payload().size()
229 << " bytes received.";
231 // The first message should contain a public key registered in |unlock_keys_|
232 // to authenticate the device.
233 if (!device_authenticated_
) {
234 std::string out_public_key
;
235 if (HasUnlockKey(message
.payload(), &out_public_key
)) {
236 PA_LOG(INFO
) << "Device authenticated. Adding "
237 << connection_
->remote_device().bluetooth_address
<< ", "
238 << out_public_key
<< " to whitelist.";
239 device_whitelist_
->AddOrUpdateDevice(
240 connection_
->remote_device().bluetooth_address
, out_public_key
);
241 device_authenticated_
= true;
243 // Only start polling the screen state if the device is authenticated.
244 if (!is_polling_screen_state_
) {
245 is_polling_screen_state_
= true;
246 StartPollingScreenState();
250 PA_LOG(INFO
) << "Key not found. Authentication failed.";
252 // Fetch unlock keys from CryptAuth.
254 // This is necessary as fetching the keys before the user is logged in
255 // (e.g. on the constructor) doesn't work and detecting when it logs in
256 // (i.e. on |OnScreenDidUnlock()| when |screen_type ==
257 // ScreenlockBridge::LockHandler::SIGNIN_SCREEN|) also doesn't work in all
258 // cases. See crbug.com/515418.
260 // Note that keys are only fetched once for a given instance. So if
261 // CryptAuth unlock keys are updated after (e.g. adding a new unlock key)
262 // they won't be refetched until a new instance of ProximityAuthBleSystem
263 // is created. Moreover, if an unlock key XXX is removed from CryptAuth,
264 // it'll only be invalidated here (removed from the persistent
265 // |device_white_list_|) when some other key YYY is sent for
267 if (!unlock_keys_requested_
)
269 connection_
->Disconnect();
274 if (message
.payload() == kScreenUnlocked
) {
275 is_remote_screen_locked_
= false;
276 spinner_timer_
.Stop();
277 UpdateLockScreenUI();
278 } else if (message
.payload() == kScreenLocked
) {
279 is_remote_screen_locked_
= true;
280 UpdateLockScreenUI();
284 void ProximityAuthBleSystem::OnAuthAttempted(const std::string
& user_id
) {
285 if (user_id
!= last_focused_user_
) {
286 PA_LOG(ERROR
) << "Unexpected user: " << last_focused_user_
287 << " != " << user_id
;
291 // Accept the auth attempt and authorize the screen unlock if the remote
292 // device is connected and its screen is unlocked.
293 bool accept_auth_attempt
= connection_
&& connection_
->IsConnected() &&
294 device_authenticated_
&& !is_remote_screen_locked_
;
295 proximity_auth_client_
->FinalizeUnlock(accept_auth_attempt
);
298 void ProximityAuthBleSystem::OnConnectionFound(
299 scoped_ptr
<Connection
> connection
) {
300 PA_LOG(INFO
) << "Connection found.";
303 connection_
= connection
.Pass();
304 connection_
->AddObserver(this);
307 void ProximityAuthBleSystem::OnConnectionStatusChanged(
308 Connection
* connection
,
309 Connection::Status old_status
,
310 Connection::Status new_status
) {
311 PA_LOG(INFO
) << "OnConnectionStatusChanged: " << old_status
<< " -> "
313 if (old_status
== Connection::CONNECTED
&&
314 new_status
== Connection::DISCONNECTED
) {
315 device_authenticated_
= false;
316 is_remote_screen_locked_
= true;
317 StopPollingScreenState();
318 UpdateLockScreenUI();
320 // Note: it's not necessary to destroy the |connection_| here, as it's
321 // already in a DISCONNECTED state. Moreover, destroying it here can cause
322 // memory corruption, since the instance |connection_| still accesses some
323 // internal data members after |OnConnectionStatusChanged()| finishes.
324 connection_
->RemoveObserver(this);
326 connection_finder_
.reset(CreateConnectionFinder());
327 connection_finder_
->Find(
328 base::Bind(&ProximityAuthBleSystem::OnConnectionFound
,
329 weak_ptr_factory_
.GetWeakPtr()));
333 void ProximityAuthBleSystem::StartPollingScreenState() {
334 PA_LOG(INFO
) << "Polling screen state.";
335 if (is_polling_screen_state_
) {
336 if (!connection_
|| !connection_
->IsConnected()) {
337 PA_LOG(INFO
) << "Polling stopped.";
338 is_polling_screen_state_
= false;
341 // Sends message requesting screen state.
342 connection_
->SendMessage(
343 make_scoped_ptr(new WireMessage(kPollScreenState
)));
345 // Schedules the next message in |kPollingIntervalSeconds| s.
346 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
347 FROM_HERE
, base::Bind(&ProximityAuthBleSystem::StartPollingScreenState
,
348 weak_ptr_factory_
.GetWeakPtr()),
349 base::TimeDelta::FromSeconds(kPollingIntervalSeconds
));
351 PA_LOG(INFO
) << "Next poll iteration posted.";
355 void ProximityAuthBleSystem::StopPollingScreenState() {
356 is_polling_screen_state_
= false;
359 bool ProximityAuthBleSystem::HasUnlockKey(const std::string
& message
,
360 std::string
* out_public_key
) {
361 std::string
message_prefix(kPublicKeyMessagePrefix
);
362 if (message
.substr(0, message_prefix
.size()) != message_prefix
)
364 std::string public_key
= message
.substr(message_prefix
.size());
366 (*out_public_key
) = public_key
;
367 return unlock_keys_
.find(public_key
) != unlock_keys_
.end() ||
368 device_whitelist_
->HasDeviceWithPublicKey(public_key
);
371 void ProximityAuthBleSystem::OnSpinnerTimerFired() {
372 UpdateLockScreenUI();
375 void ProximityAuthBleSystem::UpdateLockScreenUI() {
376 ScreenlockUIState screenlock_ui_state
= ScreenlockUIState::NO_SCREENLOCK
;
378 // TODO(tengs): We assume that the last focused user is the one with Smart
379 // Lock enabled. This may not be the case for multiprofile scenarios.
380 if (last_focused_user_
.empty())
383 // Check that the lock screen exists.
384 ScreenlockBridge::LockHandler
* lock_handler
=
385 screenlock_bridge_
->lock_handler();
387 PA_LOG(WARNING
) << "No LockHandler";
391 // Check the current authentication state of the phone.
392 if (connection_
&& connection_
->IsConnected()) {
393 if (!device_authenticated_
|| is_remote_screen_locked_
)
394 screenlock_ui_state
= ScreenlockUIState::UNAUTHENTICATED
;
396 screenlock_ui_state
= ScreenlockUIState::AUTHENTICATED
;
397 } else if (spinner_timer_
.IsRunning()) {
398 screenlock_ui_state
= ScreenlockUIState::SPINNER
;
400 screenlock_ui_state
= ScreenlockUIState::UNAUTHENTICATED
;
403 if (screenlock_ui_state
== screenlock_ui_state_
)
405 screenlock_ui_state_
= screenlock_ui_state
;
407 // Customize the user pod for the current UI state.
408 PA_LOG(INFO
) << "Screenlock UI state changed: "
409 << static_cast<int>(screenlock_ui_state_
);
410 ScreenlockBridge::UserPodCustomIconOptions icon_options
;
411 ScreenlockBridge::LockHandler::AuthType auth_type
=
412 ScreenlockBridge::LockHandler::OFFLINE_PASSWORD
;
413 base::string16 auth_value
;
415 switch (screenlock_ui_state_
) {
416 case ScreenlockUIState::SPINNER
:
417 icon_options
.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_SPINNER
);
419 case ScreenlockUIState::UNAUTHENTICATED
:
420 icon_options
.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED
);
422 case ScreenlockUIState::AUTHENTICATED
:
423 auth_value
= base::UTF8ToUTF16(kUserPodUnlockText
);
424 icon_options
.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_UNLOCKED
);
425 icon_options
.SetTooltip(base::UTF8ToUTF16(kUserPodIconLockedTooltip
),
427 auth_type
= ScreenlockBridge::LockHandler::USER_CLICK
;
433 lock_handler
->ShowUserPodCustomIcon(last_focused_user_
, icon_options
);
434 lock_handler
->SetAuthType(last_focused_user_
, auth_type
, auth_value
);
437 } // namespace proximity_auth