Vectorize website settings icons in omnibox
[chromium-blink-merge.git] / components / proximity_auth / ble / proximity_auth_ble_system.cc
blob73ab88cb481855dcfb8518fdf1ebe66b18504cfb
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_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 {
33 namespace {
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
71 // not allowed.
72 const char kUserPodIconLockedTooltip[] = "Unable to find an unlocked phone.";
74 } // namespace
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);
98 if (connection_)
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
134 // return an error.
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();
171 break;
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()));
178 break;
179 case ScreenlockBridge::LockHandler::OTHER_SCREEN:
180 connection_finder_.reset();
181 break;
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;
204 if (connection_) {
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
208 // created.
209 connection_->RemoveObserver(this);
210 connection_->Disconnect();
211 device_authenticated_ = false;
214 connection_.reset();
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();
249 } else {
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
266 // authentication.
267 if (!unlock_keys_requested_)
268 GetUnlockKeys();
269 connection_->Disconnect();
271 return;
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;
288 return;
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.";
301 DCHECK(connection);
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 << " -> "
312 << new_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;
339 return;
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)
363 return false;
364 std::string public_key = message.substr(message_prefix.size());
365 if (out_public_key)
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())
381 return;
383 // Check that the lock screen exists.
384 ScreenlockBridge::LockHandler* lock_handler =
385 screenlock_bridge_->lock_handler();
386 if (!lock_handler) {
387 PA_LOG(WARNING) << "No LockHandler";
388 return;
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;
395 else
396 screenlock_ui_state = ScreenlockUIState::AUTHENTICATED;
397 } else if (spinner_timer_.IsRunning()) {
398 screenlock_ui_state = ScreenlockUIState::SPINNER;
399 } else {
400 screenlock_ui_state = ScreenlockUIState::UNAUTHENTICATED;
403 if (screenlock_ui_state == screenlock_ui_state_)
404 return;
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);
418 break;
419 case ScreenlockUIState::UNAUTHENTICATED:
420 icon_options.SetIcon(ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED);
421 break;
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),
426 false);
427 auth_type = ScreenlockBridge::LockHandler::USER_CLICK;
428 break;
429 default:
430 break;
433 lock_handler->ShowUserPodCustomIcon(last_focused_user_, icon_options);
434 lock_handler->SetAuthType(last_focused_user_, auth_type, auth_value);
437 } // namespace proximity_auth