| // 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 |