| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/cryptauth/cryptauth_enrollment_manager.h" |
| |
| #include <utility> |
| |
| #include "base/base64url.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "components/cryptauth/cryptauth_enroller.h" |
| #include "components/cryptauth/pref_names.h" |
| #include "components/cryptauth/secure_message_delegate.h" |
| #include "components/cryptauth/sync_scheduler_impl.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/proximity_auth/logging/logging.h" |
| |
| namespace cryptauth { |
| |
| namespace { |
| |
| // The number of days that an enrollment is valid. Note that we try to refresh |
| // the enrollment well before this time elapses. |
| const int kValidEnrollmentPeriodDays = 45; |
| |
| // The normal period between successful enrollments in days. |
| const int kEnrollmentRefreshPeriodDays = 30; |
| |
| // A more aggressive period between enrollments to recover when the last |
| // enrollment fails, in minutes. This is a base time that increases for each |
| // subsequent failure. |
| const int kEnrollmentBaseRecoveryPeriodMinutes = 10; |
| |
| // The bound on the amount to jitter the period between enrollments. |
| const double kEnrollmentMaxJitterRatio = 0.2; |
| |
| // The value of the device_software_package field in the device info uploaded |
| // during enrollment. This value must be the same as the app id used for GCM |
| // registration. |
| const char kDeviceSoftwarePackage[] = "com.google.chrome.cryptauth"; |
| |
| } // namespace |
| |
| CryptAuthEnrollmentManager::CryptAuthEnrollmentManager( |
| std::unique_ptr<base::Clock> clock, |
| std::unique_ptr<CryptAuthEnrollerFactory> enroller_factory, |
| std::unique_ptr<SecureMessageDelegate> secure_message_delegate, |
| const GcmDeviceInfo& device_info, |
| CryptAuthGCMManager* gcm_manager, |
| PrefService* pref_service) |
| : clock_(std::move(clock)), |
| enroller_factory_(std::move(enroller_factory)), |
| secure_message_delegate_(std::move(secure_message_delegate)), |
| device_info_(device_info), |
| gcm_manager_(gcm_manager), |
| pref_service_(pref_service), |
| weak_ptr_factory_(this) {} |
| |
| CryptAuthEnrollmentManager::~CryptAuthEnrollmentManager() { |
| gcm_manager_->RemoveObserver(this); |
| } |
| |
| // static |
| void CryptAuthEnrollmentManager::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterBooleanPref( |
| prefs::kCryptAuthEnrollmentIsRecoveringFromFailure, false); |
| registry->RegisterDoublePref( |
| prefs::kCryptAuthEnrollmentLastEnrollmentTimeSeconds, 0.0); |
| registry->RegisterIntegerPref(prefs::kCryptAuthEnrollmentReason, |
| INVOCATION_REASON_UNKNOWN); |
| registry->RegisterStringPref(prefs::kCryptAuthEnrollmentUserPublicKey, |
| std::string()); |
| registry->RegisterStringPref(prefs::kCryptAuthEnrollmentUserPrivateKey, |
| std::string()); |
| } |
| |
| void CryptAuthEnrollmentManager::Start() { |
| gcm_manager_->AddObserver(this); |
| |
| bool is_recovering_from_failure = |
| pref_service_->GetBoolean( |
| prefs::kCryptAuthEnrollmentIsRecoveringFromFailure) || |
| !IsEnrollmentValid(); |
| |
| base::Time last_successful_enrollment = GetLastEnrollmentTime(); |
| base::TimeDelta elapsed_time_since_last_sync = |
| clock_->Now() - last_successful_enrollment; |
| |
| scheduler_ = CreateSyncScheduler(); |
| scheduler_->Start(elapsed_time_since_last_sync, |
| is_recovering_from_failure |
| ? SyncScheduler::Strategy::AGGRESSIVE_RECOVERY |
| : SyncScheduler::Strategy::PERIODIC_REFRESH); |
| } |
| |
| void CryptAuthEnrollmentManager::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void CryptAuthEnrollmentManager::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void CryptAuthEnrollmentManager::ForceEnrollmentNow( |
| InvocationReason invocation_reason) { |
| // We store the invocation reason in a preference so that it can persist |
| // across browser restarts. If the sync fails, the next retry should still use |
| // this original reason instead of INVOCATION_REASON_FAILURE_RECOVERY. |
| pref_service_->SetInteger(prefs::kCryptAuthEnrollmentReason, |
| invocation_reason); |
| scheduler_->ForceSync(); |
| } |
| |
| bool CryptAuthEnrollmentManager::IsEnrollmentValid() const { |
| base::Time last_enrollment_time = GetLastEnrollmentTime(); |
| return !last_enrollment_time.is_null() && |
| (clock_->Now() - last_enrollment_time) < |
| base::TimeDelta::FromDays(kValidEnrollmentPeriodDays); |
| } |
| |
| base::Time CryptAuthEnrollmentManager::GetLastEnrollmentTime() const { |
| return base::Time::FromDoubleT(pref_service_->GetDouble( |
| prefs::kCryptAuthEnrollmentLastEnrollmentTimeSeconds)); |
| } |
| |
| base::TimeDelta CryptAuthEnrollmentManager::GetTimeToNextAttempt() const { |
| return scheduler_->GetTimeToNextSync(); |
| } |
| |
| bool CryptAuthEnrollmentManager::IsEnrollmentInProgress() const { |
| return scheduler_->GetSyncState() == |
| SyncScheduler::SyncState::SYNC_IN_PROGRESS; |
| } |
| |
| bool CryptAuthEnrollmentManager::IsRecoveringFromFailure() const { |
| return scheduler_->GetStrategy() == |
| SyncScheduler::Strategy::AGGRESSIVE_RECOVERY; |
| } |
| |
| void CryptAuthEnrollmentManager::OnEnrollmentFinished(bool success) { |
| if (success) { |
| pref_service_->SetDouble( |
| prefs::kCryptAuthEnrollmentLastEnrollmentTimeSeconds, |
| clock_->Now().ToDoubleT()); |
| pref_service_->SetInteger(prefs::kCryptAuthEnrollmentReason, |
| INVOCATION_REASON_UNKNOWN); |
| } |
| |
| pref_service_->SetBoolean(prefs::kCryptAuthEnrollmentIsRecoveringFromFailure, |
| !success); |
| |
| sync_request_->OnDidComplete(success); |
| cryptauth_enroller_.reset(); |
| sync_request_.reset(); |
| for (auto& observer : observers_) |
| observer.OnEnrollmentFinished(success); |
| } |
| |
| std::unique_ptr<SyncScheduler> |
| CryptAuthEnrollmentManager::CreateSyncScheduler() { |
| return base::MakeUnique<SyncSchedulerImpl>( |
| this, base::TimeDelta::FromDays(kEnrollmentRefreshPeriodDays), |
| base::TimeDelta::FromMinutes(kEnrollmentBaseRecoveryPeriodMinutes), |
| kEnrollmentMaxJitterRatio, "CryptAuth Enrollment"); |
| } |
| |
| std::string CryptAuthEnrollmentManager::GetUserPublicKey() const { |
| std::string public_key; |
| if (!base::Base64UrlDecode( |
| pref_service_->GetString(prefs::kCryptAuthEnrollmentUserPublicKey), |
| base::Base64UrlDecodePolicy::REQUIRE_PADDING, &public_key)) { |
| PA_LOG(ERROR) << "Invalid public key stored in user prefs."; |
| return std::string(); |
| } |
| return public_key; |
| } |
| |
| std::string CryptAuthEnrollmentManager::GetUserPrivateKey() const { |
| std::string private_key; |
| if (!base::Base64UrlDecode( |
| pref_service_->GetString(prefs::kCryptAuthEnrollmentUserPrivateKey), |
| base::Base64UrlDecodePolicy::REQUIRE_PADDING, &private_key)) { |
| PA_LOG(ERROR) << "Invalid private key stored in user prefs."; |
| return std::string(); |
| } |
| return private_key; |
| } |
| |
| void CryptAuthEnrollmentManager::OnGCMRegistrationResult(bool success) { |
| if (!sync_request_) |
| return; |
| |
| PA_LOG(INFO) << "GCM registration for CryptAuth Enrollment completed: " |
| << success; |
| if (success) |
| DoCryptAuthEnrollment(); |
| else |
| OnEnrollmentFinished(false); |
| } |
| |
| void CryptAuthEnrollmentManager::OnKeyPairGenerated( |
| const std::string& public_key, |
| const std::string& private_key) { |
| if (!public_key.empty() && !private_key.empty()) { |
| PA_LOG(INFO) << "Key pair generated for CryptAuth enrollment"; |
| // Store the keypair in Base64 format because pref values require readable |
| // string values. |
| std::string public_key_b64, private_key_b64; |
| base::Base64UrlEncode(public_key, |
| base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
| &public_key_b64); |
| base::Base64UrlEncode(private_key, |
| base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
| &private_key_b64); |
| pref_service_->SetString(prefs::kCryptAuthEnrollmentUserPublicKey, |
| public_key_b64); |
| pref_service_->SetString(prefs::kCryptAuthEnrollmentUserPrivateKey, |
| private_key_b64); |
| DoCryptAuthEnrollment(); |
| } else { |
| OnEnrollmentFinished(false); |
| } |
| } |
| |
| void CryptAuthEnrollmentManager::OnReenrollMessage() { |
| ForceEnrollmentNow(INVOCATION_REASON_SERVER_INITIATED); |
| } |
| |
| void CryptAuthEnrollmentManager::OnSyncRequested( |
| std::unique_ptr<SyncScheduler::SyncRequest> sync_request) { |
| for (auto& observer : observers_) |
| observer.OnEnrollmentStarted(); |
| |
| sync_request_ = std::move(sync_request); |
| if (gcm_manager_->GetRegistrationId().empty() || |
| pref_service_->GetInteger(prefs::kCryptAuthEnrollmentReason) == |
| INVOCATION_REASON_MANUAL) { |
| gcm_manager_->RegisterWithGCM(); |
| } else { |
| DoCryptAuthEnrollment(); |
| } |
| } |
| |
| void CryptAuthEnrollmentManager::DoCryptAuthEnrollment() { |
| if (GetUserPublicKey().empty() || GetUserPrivateKey().empty()) { |
| secure_message_delegate_->GenerateKeyPair( |
| base::Bind(&CryptAuthEnrollmentManager::OnKeyPairGenerated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| DoCryptAuthEnrollmentWithKeys(); |
| } |
| } |
| |
| void CryptAuthEnrollmentManager::DoCryptAuthEnrollmentWithKeys() { |
| DCHECK(sync_request_); |
| InvocationReason invocation_reason = |
| INVOCATION_REASON_UNKNOWN; |
| |
| int reason_stored_in_prefs = |
| pref_service_->GetInteger(prefs::kCryptAuthEnrollmentReason); |
| |
| if (InvocationReason_IsValid(reason_stored_in_prefs) && |
| reason_stored_in_prefs != INVOCATION_REASON_UNKNOWN) { |
| invocation_reason = |
| static_cast<InvocationReason>(reason_stored_in_prefs); |
| } else if (GetLastEnrollmentTime().is_null()) { |
| invocation_reason = INVOCATION_REASON_INITIALIZATION; |
| } else if (!IsEnrollmentValid()) { |
| invocation_reason = INVOCATION_REASON_EXPIRATION; |
| } else if (scheduler_->GetStrategy() == |
| SyncScheduler::Strategy::PERIODIC_REFRESH) { |
| invocation_reason = INVOCATION_REASON_PERIODIC; |
| } else if (scheduler_->GetStrategy() == |
| SyncScheduler::Strategy::AGGRESSIVE_RECOVERY) { |
| invocation_reason = INVOCATION_REASON_FAILURE_RECOVERY; |
| } |
| |
| // Fill in the current GCM registration id before enrolling, and explicitly |
| // make sure that the software package is the same as the GCM app id. |
| GcmDeviceInfo device_info(device_info_); |
| device_info.set_gcm_registration_id(gcm_manager_->GetRegistrationId()); |
| device_info.set_device_software_package(kDeviceSoftwarePackage); |
| |
| std::string public_key_b64; |
| base::Base64UrlEncode(GetUserPublicKey(), |
| base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
| &public_key_b64); |
| PA_LOG(INFO) << "Making enrollment:\n" |
| << " public_key: " << public_key_b64 << "\n" |
| << " invocation_reason: " << invocation_reason << "\n" |
| << " gcm_registration_id: " |
| << device_info.gcm_registration_id(); |
| |
| cryptauth_enroller_ = enroller_factory_->CreateInstance(); |
| cryptauth_enroller_->Enroll( |
| GetUserPublicKey(), GetUserPrivateKey(), device_info, invocation_reason, |
| base::Bind(&CryptAuthEnrollmentManager::OnEnrollmentFinished, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| } // namespace cryptauth |