chromium/chromeos/ash/services/device_sync/cryptauth_enrollment_manager_impl.h

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_SERVICES_DEVICE_SYNC_CRYPTAUTH_ENROLLMENT_MANAGER_IMPL_H_
#define CHROMEOS_ASH_SERVICES_DEVICE_SYNC_CRYPTAUTH_ENROLLMENT_MANAGER_IMPL_H_

#include <memory>
#include <optional>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chromeos/ash/services/device_sync/cryptauth_enrollment_manager.h"
#include "chromeos/ash/services/device_sync/cryptauth_feature_type.h"
#include "chromeos/ash/services/device_sync/cryptauth_gcm_manager.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_api.pb.h"
#include "chromeos/ash/services/device_sync/sync_scheduler.h"

class PrefRegistrySimple;
class PrefService;

namespace base {
class Clock;
}

namespace ash {

namespace multidevice {
class SecureMessageDelegate;
}

namespace device_sync {

class CryptAuthEnroller;
class CryptAuthEnrollerFactory;

// Concrete CryptAuthEnrollmentManager implementation.
//
// This implementation considers three sources of enrollment requests:
//  1) A sync scheduler requests periodic enrollments and handles any failed
//     attempts.
//  2) The enrollment manager listens to the GCM manager for re-enrollment
//     requests.
//  3) The ForceEnrollmentNow() method allows for immediate requests.
//
// When an enrollment has been requested, this implementation generates a user
// key pair, if one doesn't already exists, and persists these keys as
// preferences. Thus, the user key pair should never rotate.
//
// This implementation also determines the times between enrollment attempts,
// which is roughly 30 days after a successful enrollments and 10 minutes after
// a failed enrollment attempt, exponentially increasing for consecutive
// failures. An enrollment is considered "invalid" after 45 days.
class CryptAuthEnrollmentManagerImpl : public CryptAuthEnrollmentManager,
                                       public SyncScheduler::Delegate,
                                       public CryptAuthGCMManager::Observer {
 public:
  class Factory {
   public:
    static std::unique_ptr<CryptAuthEnrollmentManager> Create(
        base::Clock* clock,
        std::unique_ptr<CryptAuthEnrollerFactory> enroller_factory,
        std::unique_ptr<multidevice::SecureMessageDelegate>
            secure_message_delegate,
        const cryptauth::GcmDeviceInfo& device_info,
        CryptAuthGCMManager* gcm_manager,
        PrefService* pref_service);

    static void SetFactoryForTesting(Factory* factory);

   protected:
    virtual ~Factory();
    virtual std::unique_ptr<CryptAuthEnrollmentManager> CreateInstance(
        base::Clock* clock,
        std::unique_ptr<CryptAuthEnrollerFactory> enroller_factory,
        std::unique_ptr<multidevice::SecureMessageDelegate>
            secure_message_delegate,
        const cryptauth::GcmDeviceInfo& device_info,
        CryptAuthGCMManager* gcm_manager,
        PrefService* pref_service) = 0;

   private:
    static Factory* factory_instance_;
  };

  // Registers the prefs used by this class to the given |pref_service|.
  static void RegisterPrefs(PrefRegistrySimple* registry);

  CryptAuthEnrollmentManagerImpl(const CryptAuthEnrollmentManagerImpl&) =
      delete;
  CryptAuthEnrollmentManagerImpl& operator=(
      const CryptAuthEnrollmentManagerImpl&) = delete;

  ~CryptAuthEnrollmentManagerImpl() override;

  // CryptAuthEnrollmentManager:
  void Start() override;
  void ForceEnrollmentNow(
      cryptauth::InvocationReason invocation_reason,
      const std::optional<std::string>& session_id) override;
  bool IsEnrollmentValid() const override;
  base::Time GetLastEnrollmentTime() const override;
  base::TimeDelta GetTimeToNextAttempt() const override;
  bool IsEnrollmentInProgress() const override;
  bool IsRecoveringFromFailure() const override;
  std::string GetUserPublicKey() const override;
  std::string GetUserPrivateKey() const override;

 protected:
  // Creates the manager:
  // |clock|: Used to determine the time between sync attempts.
  // |enroller_factory|: Creates CryptAuthEnroller instances to perform each
  //                     enrollment attempt.
  // |secure_message_delegate|: Used to generate the user's keypair if it does
  //                            not exist.
  // |device_info|: Contains information about the local device that will be
  //                uploaded to CryptAuth with each enrollment request.
  // |gcm_manager|: Used to perform GCM registrations and also notifies when GCM
  //                push messages trigger re-enrollments.
  //                Not owned and must outlive this instance.
  // |pref_service|: Contains preferences across browser restarts, and should
  //                 have been registered through RegisterPrefs().
  CryptAuthEnrollmentManagerImpl(
      base::Clock* clock,
      std::unique_ptr<CryptAuthEnrollerFactory> enroller_factory,
      std::unique_ptr<multidevice::SecureMessageDelegate>
          secure_message_delegate,
      const cryptauth::GcmDeviceInfo& device_info,
      CryptAuthGCMManager* gcm_manager,
      PrefService* pref_service);

  void SetSyncSchedulerForTest(std::unique_ptr<SyncScheduler> sync_scheduler);

 private:
  // CryptAuthGCMManager::Observer:
  void OnGCMRegistrationResult(bool success) override;
  void OnReenrollMessage(
      const std::optional<std::string>& session_id,
      const std::optional<CryptAuthFeatureType>& feature_type) override;

  // Callback when a new keypair is generated.
  void OnKeyPairGenerated(const std::string& public_key,
                          const std::string& private_key);

  // SyncScheduler::Delegate:
  void OnSyncRequested(
      std::unique_ptr<SyncScheduler::SyncRequest> sync_request) override;

  // Starts a CryptAuth enrollment attempt, generating a new keypair if one is
  // not already stored in the user prefs.
  void DoCryptAuthEnrollment();

  // Starts a CryptAuth enrollment attempt, after a key-pair is stored in the
  // user prefs.
  void DoCryptAuthEnrollmentWithKeys();

  // Callback when |cryptauth_enroller_| completes.
  void OnEnrollmentFinished(bool success);

  // Used to determine the time.
  raw_ptr<base::Clock> clock_;

  // Creates CryptAuthEnroller instances for each enrollment attempt.
  std::unique_ptr<CryptAuthEnrollerFactory> enroller_factory_;

  // The SecureMessageDelegate used to generate the user's keypair if it does
  // not already exist.
  std::unique_ptr<multidevice::SecureMessageDelegate> secure_message_delegate_;

  // The local device information to upload to CryptAuth.
  const cryptauth::GcmDeviceInfo device_info_;

  //  Used to perform GCM registrations and also notifies when GCM push messages
  //  trigger re-enrollments. Not owned and must outlive this instance.
  raw_ptr<CryptAuthGCMManager> gcm_manager_;

  // Contains perferences that outlive the lifetime of this object and across
  // process restarts.
  // Not owned and must outlive this instance.
  raw_ptr<PrefService> pref_service_;

  // Schedules the time between enrollment attempts.
  std::unique_ptr<SyncScheduler> scheduler_;

  // Contains the SyncRequest that |scheduler_| requests when an enrollment
  // attempt is made.
  std::unique_ptr<SyncScheduler::SyncRequest> sync_request_;

  // The CryptAuthEnroller instance for the current enrollment attempt. A new
  // instance will be created for each individual attempt.
  std::unique_ptr<CryptAuthEnroller> cryptauth_enroller_;

  base::WeakPtrFactory<CryptAuthEnrollmentManagerImpl> weak_ptr_factory_{this};
};

}  // namespace device_sync

}  // namespace ash

#endif  // CHROMEOS_ASH_SERVICES_DEVICE_SYNC_CRYPTAUTH_ENROLLMENT_MANAGER_IMPL_H_