Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / proximity_auth / ble / proximity_auth_ble_system.cc
blob652341fbcf6c4e01c86e28e5b70c90b08c9c2644
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"
7 #include <string>
8 #include <vector>
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 {
33 namespace {
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
65 // not allowed.
66 const char kUserPodIconLockedTooltip[] = "Unable to find an unlocked phone.";
68 } // namespace
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);
89 if (connection_)
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))
109 continue;
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";
131 return;
134 switch (screen_type) {
135 case ScreenlockBridge::LockHandler::SIGNIN_SCREEN:
136 connection_finder_.reset();
137 break;
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()));
144 break;
145 case ScreenlockBridge::LockHandler::OTHER_SCREEN:
146 connection_finder_.reset();
147 break;
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;
171 if (connection_) {
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
175 // created.
176 connection_->RemoveObserver(this);
177 connection_->Disconnect();
178 device_authenticated_ = false;
181 connection_.reset();
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();
216 } else {
217 PA_LOG(INFO) << "Key not found. Authentication failed.";
218 connection_->Disconnect();
220 return;
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;
237 return;
240 // Ignore this auth attempt if there is no BLE unlock key.
241 if (!IsAnyUnlockKeyBLE())
242 return;
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.";
254 DCHECK(connection);
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 << " -> "
265 << new_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;
292 return;
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();
315 if (!device_manager)
316 return false;
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())
322 return true;
325 return false;
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)
332 return false;
333 std::string public_key = message.substr(message_prefix.size());
334 if (out_public_key)
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))
342 return false;
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())
364 return;
366 // Check that the lock screen exists.
367 ScreenlockBridge::LockHandler* lock_handler =
368 screenlock_bridge_->lock_handler();
369 if (!lock_handler) {
370 PA_LOG(WARNING) << "No LockHandler";
371 return;
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;
378 else
379 screenlock_ui_state = ScreenlockUIState::AUTHENTICATED;
380 } else if (spinner_timer_.IsRunning()) {
381 screenlock_ui_state = ScreenlockUIState::SPINNER;
382 } else {
383 screenlock_ui_state = ScreenlockUIState::UNAUTHENTICATED;
386 if (screenlock_ui_state == screenlock_ui_state_)
387 return;
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),
402 false);
403 break;
404 case ScreenlockUIState::UNAUTHENTICATED:
405 icon_options.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED);
406 icon_options.SetTooltip(base::UTF8ToUTF16(kUserPodIconLockedTooltip),
407 false);
408 break;
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;
414 break;
415 default:
416 break;
419 lock_handler->ShowUserPodCustomIcon(last_focused_user_, icon_options);
420 lock_handler->SetAuthType(last_focused_user_, auth_type, auth_value);
423 } // namespace proximity_auth