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/cryptauth/cryptauth_device_manager.h"
7 #include "base/prefs/pref_registry_simple.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/prefs/scoped_user_pref_update.h"
10 #include "components/proximity_auth/cryptauth/base64url.h"
11 #include "components/proximity_auth/cryptauth/cryptauth_client.h"
12 #include "components/proximity_auth/cryptauth/pref_names.h"
13 #include "components/proximity_auth/cryptauth/sync_scheduler_impl.h"
14 #include "components/proximity_auth/logging/logging.h"
16 namespace proximity_auth
{
20 // The normal period between successful syncs, in hours.
21 const int kRefreshPeriodHours
= 24;
23 // A more aggressive period between sync attempts to recover when the last
24 // sync attempt fails, in minutes. This is a base time that increases for each
25 // subsequent failure.
26 const int kDeviceSyncBaseRecoveryPeriodMinutes
= 10;
28 // The bound on the amount to jitter the period between syncs.
29 const double kDeviceSyncMaxJitterRatio
= 0.2;
31 // Keys for ExternalDeviceInfo dictionaries that are stored in the user's prefs.
32 const char kExternalDeviceKeyPublicKey
[] = "public_key";
33 const char kExternalDeviceKeyDeviceName
[] = "device_name";
34 const char kExternalDeviceKeyBluetoothAddress
[] = "bluetooth_address";
36 // Converts an unlock key proto to a dictionary that can be stored in user
38 scoped_ptr
<base::DictionaryValue
> UnlockKeyToDictionary(
39 const cryptauth::ExternalDeviceInfo
& device
) {
40 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue());
42 // We store the device information in Base64Url form because dictionary values
43 // must be valid UTF8 strings.
44 std::string public_key_b64
, device_name_b64
, bluetooth_address_b64
;
45 Base64UrlEncode(device
.public_key(), &public_key_b64
);
46 Base64UrlEncode(device
.friendly_device_name(), &device_name_b64
);
47 Base64UrlEncode(device
.bluetooth_address(), &bluetooth_address_b64
);
49 dictionary
->SetString(kExternalDeviceKeyPublicKey
, public_key_b64
);
50 dictionary
->SetString(kExternalDeviceKeyDeviceName
, device_name_b64
);
51 dictionary
->SetString(kExternalDeviceKeyBluetoothAddress
,
52 bluetooth_address_b64
);
53 return dictionary
.Pass();
56 // Converts an unlock key dictionary stored in user prefs to an
57 // ExternalDeviceInfo proto. Returns true if the dictionary is valid, and the
58 // parsed proto is written to |external_device|.
59 bool DictionaryToUnlockKey(const base::DictionaryValue
& dictionary
,
60 cryptauth::ExternalDeviceInfo
* external_device
) {
61 std::string public_key_b64
, device_name_b64
, bluetooth_address_b64
;
62 if (!dictionary
.GetString(kExternalDeviceKeyPublicKey
, &public_key_b64
) ||
63 !dictionary
.GetString(kExternalDeviceKeyDeviceName
, &device_name_b64
) ||
64 !dictionary
.GetString(kExternalDeviceKeyBluetoothAddress
,
65 &bluetooth_address_b64
)) {
69 // We store the device information in Base64Url form because dictionary values
70 // must be valid UTF8 strings.
71 std::string public_key
, device_name
, bluetooth_address
;
72 if (!Base64UrlDecode(public_key_b64
, &public_key
) ||
73 !Base64UrlDecode(device_name_b64
, &device_name
) ||
74 !Base64UrlDecode(bluetooth_address_b64
, &bluetooth_address
)) {
78 external_device
->set_public_key(public_key
);
79 external_device
->set_friendly_device_name(device_name
);
80 external_device
->set_bluetooth_address(bluetooth_address
);
81 external_device
->set_unlock_key(true);
82 external_device
->set_unlockable(false);
88 CryptAuthDeviceManager::CryptAuthDeviceManager(
89 scoped_ptr
<base::Clock
> clock
,
90 scoped_ptr
<CryptAuthClientFactory
> client_factory
,
91 CryptAuthGCMManager
* gcm_manager
,
92 PrefService
* pref_service
)
93 : clock_(clock
.Pass()),
94 client_factory_(client_factory
.Pass()),
95 gcm_manager_(gcm_manager
),
96 pref_service_(pref_service
),
97 weak_ptr_factory_(this) {}
99 CryptAuthDeviceManager::~CryptAuthDeviceManager() {
100 gcm_manager_
->RemoveObserver(this);
104 void CryptAuthDeviceManager::RegisterPrefs(PrefRegistrySimple
* registry
) {
105 registry
->RegisterDoublePref(prefs::kCryptAuthDeviceSyncLastSyncTimeSeconds
,
107 registry
->RegisterBooleanPref(
108 prefs::kCryptAuthDeviceSyncIsRecoveringFromFailure
, false);
109 registry
->RegisterIntegerPref(prefs::kCryptAuthDeviceSyncReason
,
110 cryptauth::INVOCATION_REASON_UNKNOWN
);
111 registry
->RegisterListPref(prefs::kCryptAuthDeviceSyncUnlockKeys
);
114 void CryptAuthDeviceManager::Start() {
115 UpdateUnlockKeysFromPrefs();
117 gcm_manager_
->AddObserver(this);
119 base::Time last_successful_sync
= GetLastSyncTime();
120 base::TimeDelta elapsed_time_since_last_sync
=
121 clock_
->Now() - last_successful_sync
;
123 bool is_recovering_from_failure
=
124 pref_service_
->GetBoolean(
125 prefs::kCryptAuthDeviceSyncIsRecoveringFromFailure
) ||
126 last_successful_sync
.is_null();
128 scheduler_
= CreateSyncScheduler();
129 scheduler_
->Start(elapsed_time_since_last_sync
,
130 is_recovering_from_failure
131 ? SyncScheduler::Strategy::AGGRESSIVE_RECOVERY
132 : SyncScheduler::Strategy::PERIODIC_REFRESH
);
135 void CryptAuthDeviceManager::AddObserver(Observer
* observer
) {
136 observers_
.AddObserver(observer
);
139 void CryptAuthDeviceManager::RemoveObserver(Observer
* observer
) {
140 observers_
.RemoveObserver(observer
);
143 void CryptAuthDeviceManager::ForceSyncNow(
144 cryptauth::InvocationReason invocation_reason
) {
145 pref_service_
->SetInteger(prefs::kCryptAuthDeviceSyncReason
,
147 scheduler_
->ForceSync();
150 base::Time
CryptAuthDeviceManager::GetLastSyncTime() const {
151 return base::Time::FromDoubleT(
152 pref_service_
->GetDouble(prefs::kCryptAuthDeviceSyncLastSyncTimeSeconds
));
155 base::TimeDelta
CryptAuthDeviceManager::GetTimeToNextAttempt() const {
156 return scheduler_
->GetTimeToNextSync();
159 bool CryptAuthDeviceManager::IsSyncInProgress() const {
160 return scheduler_
->GetSyncState() ==
161 SyncScheduler::SyncState::SYNC_IN_PROGRESS
;
164 bool CryptAuthDeviceManager::IsRecoveringFromFailure() const {
165 return scheduler_
->GetStrategy() ==
166 SyncScheduler::Strategy::AGGRESSIVE_RECOVERY
;
169 void CryptAuthDeviceManager::OnGetMyDevicesSuccess(
170 const cryptauth::GetMyDevicesResponse
& response
) {
171 // Update the unlock keys stored in the user's prefs.
172 scoped_ptr
<base::ListValue
> unlock_keys_pref(new base::ListValue());
173 scoped_ptr
<base::ListValue
> devices_as_list(new base::ListValue());
174 for (const auto& device
: response
.devices()) {
175 devices_as_list
->Append(UnlockKeyToDictionary(device
));
176 if (device
.unlock_key())
177 unlock_keys_pref
->Append(UnlockKeyToDictionary(device
));
179 PA_LOG(INFO
) << "Devices Synced:\n" << *devices_as_list
;
181 bool unlock_keys_changed
= !unlock_keys_pref
->Equals(
182 pref_service_
->GetList(prefs::kCryptAuthDeviceSyncUnlockKeys
));
184 ListPrefUpdate
update(pref_service_
, prefs::kCryptAuthDeviceSyncUnlockKeys
);
185 update
.Get()->Swap(unlock_keys_pref
.get());
187 UpdateUnlockKeysFromPrefs();
189 // Reset metadata used for scheduling syncing.
190 pref_service_
->SetBoolean(prefs::kCryptAuthDeviceSyncIsRecoveringFromFailure
,
192 pref_service_
->SetDouble(prefs::kCryptAuthDeviceSyncLastSyncTimeSeconds
,
193 clock_
->Now().ToDoubleT());
194 pref_service_
->SetInteger(prefs::kCryptAuthDeviceSyncReason
,
195 cryptauth::INVOCATION_REASON_UNKNOWN
);
197 sync_request_
->OnDidComplete(true);
198 cryptauth_client_
.reset();
199 sync_request_
.reset();
201 Observer
, observers_
,
202 OnSyncFinished(SyncResult::SUCCESS
, unlock_keys_changed
203 ? DeviceChangeResult::CHANGED
204 : DeviceChangeResult::UNCHANGED
));
207 void CryptAuthDeviceManager::OnGetMyDevicesFailure(const std::string
& error
) {
208 PA_LOG(ERROR
) << "GetMyDevices API failed: " << error
;
209 pref_service_
->SetBoolean(prefs::kCryptAuthDeviceSyncIsRecoveringFromFailure
,
211 sync_request_
->OnDidComplete(false);
212 cryptauth_client_
.reset();
213 sync_request_
.reset();
215 Observer
, observers_
,
216 OnSyncFinished(SyncResult::FAILURE
, DeviceChangeResult::UNCHANGED
));
219 scoped_ptr
<SyncScheduler
> CryptAuthDeviceManager::CreateSyncScheduler() {
220 return make_scoped_ptr(new SyncSchedulerImpl(
221 this, base::TimeDelta::FromHours(kRefreshPeriodHours
),
222 base::TimeDelta::FromMinutes(kDeviceSyncBaseRecoveryPeriodMinutes
),
223 kDeviceSyncMaxJitterRatio
, "CryptAuth DeviceSync"));
226 void CryptAuthDeviceManager::OnResyncMessage() {
227 ForceSyncNow(cryptauth::INVOCATION_REASON_SERVER_INITIATED
);
230 void CryptAuthDeviceManager::UpdateUnlockKeysFromPrefs() {
231 const base::ListValue
* unlock_key_list
=
232 pref_service_
->GetList(prefs::kCryptAuthDeviceSyncUnlockKeys
);
233 unlock_keys_
.clear();
234 for (size_t i
= 0; i
< unlock_key_list
->GetSize(); ++i
) {
235 const base::DictionaryValue
* unlock_key_dictionary
;
236 if (unlock_key_list
->GetDictionary(i
, &unlock_key_dictionary
)) {
237 cryptauth::ExternalDeviceInfo unlock_key
;
238 if (DictionaryToUnlockKey(*unlock_key_dictionary
, &unlock_key
)) {
239 unlock_keys_
.push_back(unlock_key
);
241 PA_LOG(ERROR
) << "Unable to deserialize unlock key dictionary "
242 << "(index=" << i
<< "):\n" << *unlock_key_dictionary
;
245 PA_LOG(ERROR
) << "Can not get dictionary in list of unlock keys "
246 << "(index=" << i
<< "):\n" << *unlock_key_list
;
251 void CryptAuthDeviceManager::OnSyncRequested(
252 scoped_ptr
<SyncScheduler::SyncRequest
> sync_request
) {
253 FOR_EACH_OBSERVER(Observer
, observers_
, OnSyncStarted());
255 sync_request_
= sync_request
.Pass();
256 cryptauth_client_
= client_factory_
->CreateInstance();
258 cryptauth::InvocationReason invocation_reason
=
259 cryptauth::INVOCATION_REASON_UNKNOWN
;
261 int reason_stored_in_prefs
=
262 pref_service_
->GetInteger(prefs::kCryptAuthDeviceSyncReason
);
264 // If the sync attempt is not forced, it is acceptable for CryptAuth to return
265 // a cached copy of the user's devices, rather taking a database hit for the
267 bool is_sync_speculative
=
268 reason_stored_in_prefs
!= cryptauth::INVOCATION_REASON_UNKNOWN
;
270 if (cryptauth::InvocationReason_IsValid(reason_stored_in_prefs
) &&
271 reason_stored_in_prefs
!= cryptauth::INVOCATION_REASON_UNKNOWN
) {
273 static_cast<cryptauth::InvocationReason
>(reason_stored_in_prefs
);
274 } else if (GetLastSyncTime().is_null()) {
275 invocation_reason
= cryptauth::INVOCATION_REASON_INITIALIZATION
;
276 } else if (IsRecoveringFromFailure()) {
277 invocation_reason
= cryptauth::INVOCATION_REASON_FAILURE_RECOVERY
;
279 invocation_reason
= cryptauth::INVOCATION_REASON_PERIODIC
;
282 cryptauth::GetMyDevicesRequest request
;
283 request
.set_invocation_reason(invocation_reason
);
284 request
.set_allow_stale_read(is_sync_speculative
);
285 cryptauth_client_
->GetMyDevices(
286 request
, base::Bind(&CryptAuthDeviceManager::OnGetMyDevicesSuccess
,
287 weak_ptr_factory_
.GetWeakPtr()),
288 base::Bind(&CryptAuthDeviceManager::OnGetMyDevicesFailure
,
289 weak_ptr_factory_
.GetWeakPtr()));
292 } // namespace proximity_auth