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_device_manager.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 // Polling interval in seconds.
39 const int kPollingIntervalSeconds
= 5;
41 // String received when the remote device's screen is unlocked.
42 const char kScreenUnlocked
[] = "Screen Unlocked";
44 // String received when the remote device's screen is locked.
45 const char kScreenLocked
[] = "Screen Locked";
47 // String send to poll the remote device screen state.
48 const char kPollScreenState
[] = "PollScreenState";
50 // String prefix received with the public key.
51 const char kPublicKeyMessagePrefix
[] = "PublicKey:";
53 // BluetoothLowEnergyConnection parameter, number of attempts to send a write
54 // request before failing.
55 const int kMaxNumberOfTries
= 2;
57 // The time, in seconds, to show a spinner for the user pod immediately after
58 // the screen is locked.
59 const int kSpinnerTimeSeconds
= 15;
61 // Text shown on the user pod when unlock is allowed.
62 const char kUserPodUnlockText
[] = "Click your photo";
64 // Text of tooltip shown on when hovering over the user pod icon when unlock is
66 const char kUserPodIconLockedTooltip
[] = "Unable to find an unlocked phone.";
70 ProximityAuthBleSystem::ProximityAuthBleSystem(
71 ScreenlockBridge
* screenlock_bridge
,
72 ProximityAuthClient
* proximity_auth_client
,
73 PrefService
* pref_service
)
74 : screenlock_bridge_(screenlock_bridge
),
75 proximity_auth_client_(proximity_auth_client
),
76 device_whitelist_(new BluetoothLowEnergyDeviceWhitelist(pref_service
)),
77 bluetooth_throttler_(new BluetoothThrottlerImpl(
78 make_scoped_ptr(new base::DefaultTickClock()))),
79 device_authenticated_(false),
80 is_polling_screen_state_(false),
81 weak_ptr_factory_(this) {
82 PA_LOG(INFO
) << "Starting Proximity Auth over Bluetooth Low Energy.";
83 screenlock_bridge_
->AddObserver(this);
86 ProximityAuthBleSystem::~ProximityAuthBleSystem() {
87 PA_LOG(INFO
) << "Stopping Proximity over Bluetooth Low Energy.";
88 screenlock_bridge_
->RemoveObserver(this);
90 connection_
->RemoveObserver(this);
93 void ProximityAuthBleSystem::RegisterPrefs(PrefRegistrySimple
* registry
) {
94 BluetoothLowEnergyDeviceWhitelist::RegisterPrefs(registry
);
97 void ProximityAuthBleSystem::RemoveStaleWhitelistedDevices() {
98 PA_LOG(INFO
) << "Removing stale whitelist devices.";
99 std::vector
<std::string
> b64_public_keys
= device_whitelist_
->GetPublicKeys();
100 PA_LOG(INFO
) << "There were " << b64_public_keys
.size()
101 << " whitelisted devices.";
103 std::vector
<cryptauth::ExternalDeviceInfo
> unlock_keys
=
104 proximity_auth_client_
->GetCryptAuthDeviceManager()->unlock_keys();
106 for (const auto& b64_public_key
: b64_public_keys
) {
107 std::string public_key
;
108 if (!Base64UrlDecode(b64_public_key
, &public_key
))
111 bool public_key_registered
= false;
112 for (const auto& unlock_key
: unlock_keys
) {
113 public_key_registered
|= unlock_key
.public_key() == public_key
;
116 if (!public_key_registered
) {
117 PA_LOG(INFO
) << "Removing device: " << b64_public_key
;
118 device_whitelist_
->RemoveDeviceWithPublicKey(b64_public_key
);
121 b64_public_keys
= device_whitelist_
->GetPublicKeys();
122 PA_LOG(INFO
) << "There are " << b64_public_keys
.size()
123 << " whitelisted devices.";
126 void ProximityAuthBleSystem::OnScreenDidLock(
127 ScreenlockBridge::LockHandler::ScreenType screen_type
) {
128 PA_LOG(INFO
) << "OnScreenDidLock: " << screen_type
;
129 if (!IsAnyUnlockKeyBLE()) {
130 PA_LOG(INFO
) << "No BLE device registered as unlock key";
134 switch (screen_type
) {
135 case ScreenlockBridge::LockHandler::SIGNIN_SCREEN
:
136 connection_finder_
.reset();
138 case ScreenlockBridge::LockHandler::LOCK_SCREEN
:
139 DCHECK(!connection_finder_
);
140 connection_finder_
.reset(CreateConnectionFinder());
141 connection_finder_
->Find(
142 base::Bind(&ProximityAuthBleSystem::OnConnectionFound
,
143 weak_ptr_factory_
.GetWeakPtr()));
145 case ScreenlockBridge::LockHandler::OTHER_SCREEN
:
146 connection_finder_
.reset();
150 // Reset the screen lock UI state to the default state.
151 is_remote_screen_locked_
= true;
152 screenlock_ui_state_
= ScreenlockUIState::NO_SCREENLOCK
;
153 last_focused_user_
= screenlock_bridge_
->focused_user_id();
154 spinner_timer_
.Start(FROM_HERE
,
155 base::TimeDelta::FromSeconds(kSpinnerTimeSeconds
), this,
156 &ProximityAuthBleSystem::OnSpinnerTimerFired
);
157 UpdateLockScreenUI();
160 ConnectionFinder
* ProximityAuthBleSystem::CreateConnectionFinder() {
161 return new BluetoothLowEnergyConnectionFinder(
162 RemoteDevice(), kSmartLockServiceUUID
,
163 BluetoothLowEnergyConnectionFinder::FIND_ANY_DEVICE
,
164 device_whitelist_
.get(), bluetooth_throttler_
.get(), kMaxNumberOfTries
);
167 void ProximityAuthBleSystem::OnScreenDidUnlock(
168 ScreenlockBridge::LockHandler::ScreenType screen_type
) {
169 PA_LOG(INFO
) << "OnScreenDidUnlock: " << screen_type
;
172 // Note: it's important to remove the observer before calling
173 // |Disconnect()|, otherwise |OnConnectedStatusChanged()| will be called
174 // from |connection_| and a new instance for |connection_finder_| will be
176 connection_
->RemoveObserver(this);
177 connection_
->Disconnect();
178 device_authenticated_
= false;
182 connection_finder_
.reset();
185 void ProximityAuthBleSystem::OnFocusedUserChanged(const std::string
& user_id
) {
186 PA_LOG(INFO
) << "OnFocusedUserChanged: " << user_id
;
187 // TODO(tengs): We assume that the last focused user is the one with Smart
188 // Lock enabled. This may not be the case for multiprofile scenarios.
189 last_focused_user_
= user_id
;
190 UpdateLockScreenUI();
193 void ProximityAuthBleSystem::OnMessageReceived(const Connection
& connection
,
194 const WireMessage
& message
) {
195 PA_LOG(INFO
) << "Message with " << message
.payload().size()
196 << " bytes received.";
198 // The first message should contain a public key registered with
199 // CryptAuthDeviceManager to authenticate the device.
200 if (!device_authenticated_
) {
201 std::string out_public_key
;
202 if (HasUnlockKey(message
.payload(), &out_public_key
)) {
203 PA_LOG(INFO
) << "Device authenticated. Adding "
204 << connection_
->remote_device().bluetooth_address
<< ", "
205 << out_public_key
<< " to whitelist.";
206 device_whitelist_
->AddOrUpdateDevice(
207 connection_
->remote_device().bluetooth_address
, out_public_key
);
208 device_authenticated_
= true;
210 // Only start polling the screen state if the device is authenticated.
211 if (!is_polling_screen_state_
) {
212 is_polling_screen_state_
= true;
213 StartPollingScreenState();
217 PA_LOG(INFO
) << "Key not found. Authentication failed.";
218 connection_
->Disconnect();
223 if (message
.payload() == kScreenUnlocked
) {
224 is_remote_screen_locked_
= false;
225 spinner_timer_
.Stop();
226 UpdateLockScreenUI();
227 } else if (message
.payload() == kScreenLocked
) {
228 is_remote_screen_locked_
= true;
229 UpdateLockScreenUI();
233 void ProximityAuthBleSystem::OnAuthAttempted(const std::string
& user_id
) {
234 if (user_id
!= last_focused_user_
) {
235 PA_LOG(ERROR
) << "Unexpected user: " << last_focused_user_
236 << " != " << user_id
;
240 // Ignore this auth attempt if there is no BLE unlock key.
241 if (!IsAnyUnlockKeyBLE())
244 // Accept the auth attempt and authorize the screen unlock if the remote
245 // device is connected and its screen is unlocked.
246 bool accept_auth_attempt
= connection_
&& connection_
->IsConnected() &&
247 device_authenticated_
&& !is_remote_screen_locked_
;
248 proximity_auth_client_
->FinalizeUnlock(accept_auth_attempt
);
251 void ProximityAuthBleSystem::OnConnectionFound(
252 scoped_ptr
<Connection
> connection
) {
253 PA_LOG(INFO
) << "Connection found.";
256 connection_
= connection
.Pass();
257 connection_
->AddObserver(this);
260 void ProximityAuthBleSystem::OnConnectionStatusChanged(
261 Connection
* connection
,
262 Connection::Status old_status
,
263 Connection::Status new_status
) {
264 PA_LOG(INFO
) << "OnConnectionStatusChanged: " << old_status
<< " -> "
266 if (old_status
== Connection::CONNECTED
&&
267 new_status
== Connection::DISCONNECTED
) {
268 device_authenticated_
= false;
269 is_remote_screen_locked_
= true;
270 StopPollingScreenState();
271 UpdateLockScreenUI();
273 // Note: it's not necessary to destroy the |connection_| here, as it's
274 // already in a DISCONNECTED state. Moreover, destroying it here can cause
275 // memory corruption, since the instance |connection_| still accesses some
276 // internal data members after |OnConnectionStatusChanged()| finishes.
277 connection_
->RemoveObserver(this);
279 connection_finder_
.reset(CreateConnectionFinder());
280 connection_finder_
->Find(
281 base::Bind(&ProximityAuthBleSystem::OnConnectionFound
,
282 weak_ptr_factory_
.GetWeakPtr()));
286 void ProximityAuthBleSystem::StartPollingScreenState() {
287 PA_LOG(INFO
) << "Polling screen state.";
288 if (is_polling_screen_state_
) {
289 if (!connection_
|| !connection_
->IsConnected()) {
290 PA_LOG(INFO
) << "Polling stopped.";
291 is_polling_screen_state_
= false;
294 // Sends message requesting screen state.
295 connection_
->SendMessage(
296 make_scoped_ptr(new WireMessage(kPollScreenState
)));
298 // Schedules the next message in |kPollingIntervalSeconds| s.
299 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
300 FROM_HERE
, base::Bind(&ProximityAuthBleSystem::StartPollingScreenState
,
301 weak_ptr_factory_
.GetWeakPtr()),
302 base::TimeDelta::FromSeconds(kPollingIntervalSeconds
));
304 PA_LOG(INFO
) << "Next poll iteration posted.";
308 void ProximityAuthBleSystem::StopPollingScreenState() {
309 is_polling_screen_state_
= false;
312 bool ProximityAuthBleSystem::IsAnyUnlockKeyBLE() {
313 CryptAuthDeviceManager
* device_manager
=
314 proximity_auth_client_
->GetCryptAuthDeviceManager();
318 for (const auto& unlock_key
: device_manager
->unlock_keys()) {
319 // TODO(tengs): We currently assume that any device registered as an unlock
320 // key, but does not have a Bluetooth address, is a BLE device.
321 if (unlock_key
.bluetooth_address().empty())
328 bool ProximityAuthBleSystem::HasUnlockKey(const std::string
& message
,
329 std::string
* out_public_key
) {
330 std::string
message_prefix(kPublicKeyMessagePrefix
);
331 if (message
.substr(0, message_prefix
.size()) != message_prefix
)
333 std::string public_key
= message
.substr(message_prefix
.size());
335 (*out_public_key
) = public_key
;
337 std::vector
<cryptauth::ExternalDeviceInfo
> unlock_keys
=
338 proximity_auth_client_
->GetCryptAuthDeviceManager()->unlock_keys();
340 std::string decoded_public_key
;
341 if (!Base64UrlDecode(public_key
, &decoded_public_key
))
344 bool unlock_key_registered
= false;
345 for (const auto& unlock_key
: unlock_keys
) {
346 unlock_key_registered
|= unlock_key
.public_key() == decoded_public_key
;
349 RemoveStaleWhitelistedDevices();
350 return unlock_key_registered
||
351 device_whitelist_
->HasDeviceWithPublicKey(public_key
);
354 void ProximityAuthBleSystem::OnSpinnerTimerFired() {
355 UpdateLockScreenUI();
358 void ProximityAuthBleSystem::UpdateLockScreenUI() {
359 ScreenlockUIState screenlock_ui_state
= ScreenlockUIState::NO_SCREENLOCK
;
361 // TODO(tengs): We assume that the last focused user is the one with Smart
362 // Lock enabled. This may not be the case for multiprofile scenarios.
363 if (last_focused_user_
.empty())
366 // Check that the lock screen exists.
367 ScreenlockBridge::LockHandler
* lock_handler
=
368 screenlock_bridge_
->lock_handler();
370 PA_LOG(WARNING
) << "No LockHandler";
374 // Check the current authentication state of the phone.
375 if (connection_
&& connection_
->IsConnected()) {
376 if (!device_authenticated_
|| is_remote_screen_locked_
)
377 screenlock_ui_state
= ScreenlockUIState::UNAUTHENTICATED
;
379 screenlock_ui_state
= ScreenlockUIState::AUTHENTICATED
;
380 } else if (spinner_timer_
.IsRunning()) {
381 screenlock_ui_state
= ScreenlockUIState::SPINNER
;
383 screenlock_ui_state
= ScreenlockUIState::UNAUTHENTICATED
;
386 if (screenlock_ui_state
== screenlock_ui_state_
)
388 screenlock_ui_state_
= screenlock_ui_state
;
390 // Customize the user pod for the current UI state.
391 PA_LOG(INFO
) << "Screenlock UI state changed: "
392 << static_cast<int>(screenlock_ui_state_
);
393 ScreenlockBridge::UserPodCustomIconOptions icon_options
;
394 ScreenlockBridge::LockHandler::AuthType auth_type
=
395 ScreenlockBridge::LockHandler::OFFLINE_PASSWORD
;
396 base::string16 auth_value
;
398 switch (screenlock_ui_state_
) {
399 case ScreenlockUIState::SPINNER
:
400 icon_options
.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_SPINNER
);
401 icon_options
.SetTooltip(base::UTF8ToUTF16(kUserPodIconLockedTooltip
),
404 case ScreenlockUIState::UNAUTHENTICATED
:
405 icon_options
.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED
);
406 icon_options
.SetTooltip(base::UTF8ToUTF16(kUserPodIconLockedTooltip
),
409 case ScreenlockUIState::AUTHENTICATED
:
410 auth_value
= base::UTF8ToUTF16(kUserPodUnlockText
);
411 icon_options
.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_UNLOCKED
);
412 icon_options
.SetTooltip(base::UTF8ToUTF16(kUserPodUnlockText
), false);
413 auth_type
= ScreenlockBridge::LockHandler::USER_CLICK
;
419 lock_handler
->ShowUserPodCustomIcon(last_focused_user_
, icon_options
);
420 lock_handler
->SetAuthType(last_focused_user_
, auth_type
, auth_value
);
423 } // namespace proximity_auth