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

#ifndef COMPONENTS_TRUSTED_VAULT_TRUSTED_VAULT_CONNECTION_H_
#define COMPONENTS_TRUSTED_VAULT_TRUSTED_VAULT_CONNECTION_H_

#include <memory>
#include <optional>
#include <set>
#include <variant>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/time/time.h"
#include "base/types/strong_alias.h"

struct CoreAccountInfo;

namespace trusted_vault_pb {
enum SecurityDomainMember_MemberType : int;
class RotateSharedKeyRequest;
}  // namespace trusted_vault_pb

namespace trusted_vault {

class SecureBoxKeyPair;
class SecureBoxPublicKey;

enum class TrustedVaultRegistrationStatus {
  // Used when registration was not attempted (e.g. factor is already
  // registered or local state is not sufficient).
  kRegistrationNotAttempted,
  // Used when an in-flight registration was cancelled.
  kRegistrationCancelled,
  // Used when registration was successful.
  kSuccess,
  // Used when member corresponding to authentication factor already exists and
  // local keys that were sent as part of the request aren't stale.
  kAlreadyRegistered,
  // Used when trusted vault request can't be completed successfully due to
  // vault key being outdated or device key being not registered.
  kLocalDataObsolete,
  // Used when request wasn't sent due to transient auth error that prevented
  // fetching an access token.
  kTransientAccessTokenFetchError,
  // Used when request wasn't sent due to persistent auth error that prevented
  // fetching an access token.
  kPersistentAccessTokenFetchError,
  // Used when request wasn't sent because primary account changed meanwhile.
  kPrimaryAccountChangeAccessTokenFetchError,
  // Used for all network errors.
  kNetworkError,
  // Used for all http and protocol errors not covered by the above.
  kOtherError,
};

enum class TrustedVaultDownloadKeysStatus {
  kSuccess,
  // Member corresponding to the authentication factor doesn't exist.
  kMemberNotFound,
  // Member corresponding to the authentication factor not registered in the
  // security domain.
  kMembershipNotFound,
  // Membership exists but is corrupted.
  kMembershipCorrupted,
  // Membership exists but is empty.
  kMembershipEmpty,
  // Keys were successfully downloaded and verified, but no new keys exist.
  kNoNewKeys,
  // At least one of the key proofs isn't valid or unable to verify them using
  // latest local trusted vault key (e.g. it's too old).
  kKeyProofsVerificationFailed,
  // Used when request isn't sent due to access token fetching failure.
  kAccessTokenFetchingFailure,
  // Used for all network errors.
  kNetworkError,
  // Used for all http and protocol errors, when no statuses above fits.
  kOtherError,
};

enum class TrustedVaultDownloadPasswordPublicKeyStatus {
  kSuccess,
  // Used when request wasn't sent due to transient auth error that prevented
  // fetching an access token.
  kTransientAccessTokenFetchError,
  // Used when request wasn't sent due to persistent auth error that prevented
  // fetching an access token.
  kPersistentAccessTokenFetchError,
  // Used when request wasn't sent because primary account changed meanwhile.
  kPrimaryAccountChangeAccessTokenFetchError,
  // Used for all network errors.
  kNetworkError,
  // Used for all http and protocol errors, when no statuses above fits.
  kOtherError,
};

enum class TrustedVaultKeyRotationStatus {
  kSuccess,
  // Used when base epoch doesn't match current epoch of the security domain.
  kLocalDataObsolete,
  // Used when rotated shared member keys don't match current members.
  kMembershipMismatch,
  // Used when security domain has no members.
  kEmptySecurityDomain,
  // Used when request wasn't sent due to transient auth error that prevented
  // fetching an access token.
  kTransientAccessTokenFetchError,
  // Used when request wasn't sent due to persistent auth error that prevented
  // fetching an access token.
  kPersistentAccessTokenFetchError,
  // Used when request wasn't sent because primary account changed meanwhile.
  kPrimaryAccountChangeAccessTokenFetchError,
  // Used for all network errors.
  kNetworkError,
  // Used for all http and protocol errors not covered by the above.
  kOtherError,
};

// This enum is used in histograms. These values are persisted to logs. Entries
// should not be renumbered and numeric values should never be reused, only add
// at the end and. Also remember to update in tools/metrics/histograms/enums.xml
// TrustedVaultRecoverabilityStatus enum.
// LINT.IfChange(TrustedVaultRecoverabilityStatus)
enum class TrustedVaultRecoverabilityStatus {
  // Recoverability status not retrieved due to network, http or protocol error.
  kNotDegraded = 0,
  kDegraded = 1,
  kError = 2,
  kMaxValue = kError,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/trusted_vault/enums.xml:TrustedVaultRecoverabilityStatus)

// Contains metadata about a Google Password Manager PIN that is stored in
// a trusted vault and can be used for recovery.
struct UsableRecoveryPinMetadata {
  UsableRecoveryPinMetadata(std::string wrapped_pin, base::Time expiry);
  UsableRecoveryPinMetadata(const UsableRecoveryPinMetadata&);
  UsableRecoveryPinMetadata& operator=(const UsableRecoveryPinMetadata&);
  UsableRecoveryPinMetadata(UsableRecoveryPinMetadata&&);
  UsableRecoveryPinMetadata& operator=(UsableRecoveryPinMetadata&&);
  ~UsableRecoveryPinMetadata();

  bool operator==(const UsableRecoveryPinMetadata&) const;

  // The encrypted PIN value, for validation.
  std::string wrapped_pin;
  // The time when the underlying recovery-key-store entry will expire. Ignored
  // when uploading.
  base::Time expiry;
};

// Contains information about a Google Password Manager PIN that is stored in
// a trusted vault.
struct GpmPinMetadata {
  GpmPinMetadata(std::optional<std::string> public_key,
                 std::optional<UsableRecoveryPinMetadata> pin_metadata);
  GpmPinMetadata(const GpmPinMetadata&);
  GpmPinMetadata& operator=(const GpmPinMetadata&);
  GpmPinMetadata(GpmPinMetadata&&);
  GpmPinMetadata& operator=(GpmPinMetadata&&);
  ~GpmPinMetadata();

  bool operator==(const GpmPinMetadata&) const;

  // The securebox public key for the virtual member. This will always have a
  // value when this metadata is downloaded with
  // `DownloadAuthenticationFactorsRegistrationState`. When used with
  // `RegisterAuthenticationFactor`, this can be empty to upload the first GPM
  // PIN to an account, but it must be set to replace a GPM PIN.
  std::optional<std::string> public_key;

  // Contains metadata about the PIN member. If present, the PIN can be used for
  // recovery. If not present, the PIN cannot be used for recovery, but it might
  // be necessary to refer to its properties when changing or renewing it.
  std::optional<UsableRecoveryPinMetadata> usable_pin_metadata;
};

// A MemberKeys contains the cryptographic outputs needed to add or use an
// authentication factor: the trusted vault key, encrypted to the public key of
// the member, and an authenticator of that public key.
struct MemberKeys {
  MemberKeys(int version,
             std::vector<uint8_t> wrapped_key,
             std::vector<uint8_t> proof);
  MemberKeys(const MemberKeys&) = delete;
  MemberKeys& operator=(const MemberKeys&) = delete;
  MemberKeys(MemberKeys&&);
  MemberKeys& operator=(MemberKeys&&);
  ~MemberKeys();

  int version;
  std::vector<uint8_t> wrapped_key;
  std::vector<uint8_t> proof;
};

// A vault member public key and its member keys.
struct VaultMember {
  VaultMember(std::unique_ptr<SecureBoxPublicKey> public_key,
              std::vector<MemberKeys> member_keys);
  VaultMember(const VaultMember&) = delete;
  VaultMember& operator=(const VaultMember&) = delete;
  VaultMember(VaultMember&&);
  VaultMember& operator=(VaultMember&&);
  ~VaultMember();

  std::unique_ptr<SecureBoxPublicKey> public_key;
  std::vector<MemberKeys> member_keys;
};

// The result of calling
// DownloadAuthenticationFactorsRegistrationState.
struct DownloadAuthenticationFactorsRegistrationStateResult {
  DownloadAuthenticationFactorsRegistrationStateResult();
  DownloadAuthenticationFactorsRegistrationStateResult(
      DownloadAuthenticationFactorsRegistrationStateResult&&);
  DownloadAuthenticationFactorsRegistrationStateResult& operator=(
      DownloadAuthenticationFactorsRegistrationStateResult&&);
  ~DownloadAuthenticationFactorsRegistrationStateResult();

  // These values are persisted in histograms. Entries should not be renumbered
  // and numeric values should never be reused.
  enum class State {
    // The state of the security domain could not be determined.
    kError = 0,
    // The security domain is empty and thus doesn't have any secrets.
    kEmpty = 1,
    // The security domain is non-empty, but has virtual devices that are valid
    // for recovery.
    kRecoverable = 2,
    // The security domain is non-empty, but has no virtual devices that can be
    // used for recovery.
    kIrrecoverable = 3,
    kMaxValue = kIrrecoverable,
  };
  State state = State::kError;

  // If there are members in the domain then this will contain the current key
  // version.
  std::optional<int> key_version;

  // The expiry time of any LSKF virtual devices.
  std::vector<base::Time> lskf_expiries;

  // If a Google Password Manager PIN is a member of the domain, then this will
  // contain its metadata.
  std::optional<GpmPinMetadata> gpm_pin_metadata;

  // The list of iCloud recovery key domain members.
  std::vector<VaultMember> icloud_keys;
};

// Authentication factor types:
using LocalPhysicalDevice =
    base::StrongAlias<class LocalPhysicalDeviceTag, std::monostate>;
using LockScreenKnowledgeFactor =
    base::StrongAlias<class VirtualDeviceTag, std::monostate>;
using ICloudKeychain =
    base::StrongAlias<class ICloudKeychainTag, std::monostate>;
// UnspecifiedAuthenticationFactorType carries a type hint for the backend.
using UnspecifiedAuthenticationFactorType =
    base::StrongAlias<class UnspecifiedAuthenticationFactorTypeTag, int>;

using AuthenticationFactorTypeAndRegistrationParams =
    std::variant<LocalPhysicalDevice,
                 LockScreenKnowledgeFactor,
                 UnspecifiedAuthenticationFactorType,
                 GpmPinMetadata,
                 ICloudKeychain>;

struct TrustedVaultKeyAndVersion {
  TrustedVaultKeyAndVersion(const std::vector<uint8_t>& key, int version);
  TrustedVaultKeyAndVersion(const TrustedVaultKeyAndVersion& other);
  TrustedVaultKeyAndVersion& operator=(const TrustedVaultKeyAndVersion& other);
  ~TrustedVaultKeyAndVersion();

  bool operator==(const TrustedVaultKeyAndVersion& other) const;

  std::vector<uint8_t> key;
  int version;
};

// Returns a vector of `TrustedVaultKeyAndVersion` given a vector of keys and
// the version of the last key, assuming that the versions are sequential.
std::vector<TrustedVaultKeyAndVersion> GetTrustedVaultKeysWithVersions(
    const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
    int last_key_version);

// A MemberKeysSource provides a method of calculating the values needed to
// add an authenticator factor.
using MemberKeysSource =
    std::variant<std::vector<TrustedVaultKeyAndVersion>, MemberKeys>;

// Supports interaction with vault service, all methods must called on trusted
// vault backend sequence.
class TrustedVaultConnection {
 public:
  // The result of attempting to add a member to the security domain. If the
  // status is successful then `key_version` carries the current version of
  // the security domain, otherwise it's zero.
  using RegisterAuthenticationFactorCallback =
      base::OnceCallback<void(TrustedVaultRegistrationStatus,
                              /*key_version=*/int)>;
  using DownloadNewKeysCallback =
      base::OnceCallback<void(TrustedVaultDownloadKeysStatus,
                              const std::vector<std::vector<uint8_t>>& /*keys*/,
                              int /*last_key_version*/)>;
  using IsRecoverabilityDegradedCallback =
      base::OnceCallback<void(TrustedVaultRecoverabilityStatus)>;
  using DownloadAuthenticationFactorsRegistrationStateCallback =
      base::OnceCallback<void(
          DownloadAuthenticationFactorsRegistrationStateResult)>;
  using DownloadGaiaPasswordPublicKeyCallback =
      base::OnceCallback<void(TrustedVaultDownloadPasswordPublicKeyStatus,
                              const std::vector<uint8_t>&)>;
  using RotateSharedKeyCallback =
      base::OnceCallback<void(TrustedVaultKeyRotationStatus, int)>;

  // Used to control ongoing request lifetime, destroying Request object causes
  // request cancellation.
  class Request {
   public:
    Request() = default;
    Request(const Request& other) = delete;
    Request& operator=(const Request& other) = delete;
    virtual ~Request() = default;
  };

  TrustedVaultConnection() = default;
  TrustedVaultConnection(const TrustedVaultConnection& other) = delete;
  TrustedVaultConnection& operator=(const TrustedVaultConnection& other) =
      delete;
  virtual ~TrustedVaultConnection() = default;

  // Asynchronously attempts to register the authentication factor on the
  // trusted vault server to allow further vault server API calls using this
  // authentication factor. Calls |callback| upon completion, unless the
  // returned object is destroyed earlier. Caller should hold returned request
  // object until |callback| call or until request needs to be cancelled.
  // |trusted_vault_keys| must be ordered by version and must not be empty.
  // TODO(crbug.com/406191378): Rename to ...RecoveryFactor.
  [[nodiscard]] virtual std::unique_ptr<Request> RegisterAuthenticationFactor(
      const CoreAccountInfo& account_info,
      const MemberKeysSource& member_keys_source,
      const SecureBoxPublicKey& authentication_factor_public_key,
      AuthenticationFactorTypeAndRegistrationParams
          authentication_factor_type_and_registration_params,
      RegisterAuthenticationFactorCallback callback) = 0;

  // Special version of the above for the case where the caller has no local
  // keys available. Attempts to register the device using constant key. May
  // succeed only if constant key is the only key known server-side.
  [[nodiscard]] virtual std::unique_ptr<Request> RegisterLocalDeviceWithoutKeys(
      const CoreAccountInfo& account_info,
      const SecureBoxPublicKey& device_public_key,
      RegisterAuthenticationFactorCallback callback) = 0;

  // Asynchronously attempts to download new vault keys (e.g. keys with version
  // greater than the on in |last_trusted_vault_key_and_version|) from the
  // trusted vault server. Caller should hold returned request object until
  // |callback| call or until request needs to be cancelled.
  [[nodiscard]] virtual std::unique_ptr<Request> DownloadNewKeys(
      const CoreAccountInfo& account_info,
      const TrustedVaultKeyAndVersion& last_trusted_vault_key_and_version,
      std::unique_ptr<SecureBoxKeyPair> device_key_pair,
      DownloadNewKeysCallback callback) = 0;

  // Asynchronously attempts to download degraded recoverability status from the
  // trusted vault server. Caller should hold returned request object until
  // |callback| call or until request needs to be cancelled.
  [[nodiscard]] virtual std::unique_ptr<Request>
  DownloadIsRecoverabilityDegraded(
      const CoreAccountInfo& account_info,
      IsRecoverabilityDegradedCallback callback) = 0;

  // Asynchronously attempts to download the Gaia password public key from the
  // trusted vault server. Caller should hold returned request object until
  // |callback| call or until request needs to be cancelled.
  [[nodiscard]] virtual std::unique_ptr<Request> DownloadGaiaPasswordPublicKey(
      const CoreAccountInfo& account_info,
      DownloadGaiaPasswordPublicKeyCallback callback) = 0;

  // Asynchronously attempts to rotate the shared key stored in a security
  // domain. Caller should hold returned request object until |callback| call or
  // until request needs to be cancelled.
  [[nodiscard]] virtual std::unique_ptr<Request> RotateSharedKey(
      const CoreAccountInfo& account_info,
      const trusted_vault_pb::RotateSharedKeyRequest& request,
      RotateSharedKeyCallback callback) = 0;

  // Enumerates the members of the security domain and determines the
  // recoverability of the security domain. (See the values of
  // `DownloadAuthenticationFactorsRegistrationStateResult`.)
  // |keep_alive_callback| will be called whenever there's a partial response
  // from the server, i.e. we got a response but we still need more data.
  // TODO(crbug.com/406191378): Rename to ...RecoveryFactor.
  [[nodiscard]] virtual std::unique_ptr<Request>
  DownloadAuthenticationFactorsRegistrationState(
      const CoreAccountInfo& account_info,
      DownloadAuthenticationFactorsRegistrationStateCallback callback,
      base::RepeatingClosure keep_alive_callback) = 0;

  // Enumerates the members of the security domain and determines the
  // recoverability of the security domain. (See the values of
  // `DownloadAuthenticationFactorsRegistrationStateResult`.)
  // If |AuthenticationFactorType| is non-empty, then only information from the
  // authentication factor types in that set is fetched and taken into account
  // when constructing the result. |keep_alive_callback| will be called whenever
  // there's a partial response from the server, i.e. we got a response but we
  // still need more data.
  // TODO(crbug.com/406191378): Rename to ...RecoveryFactor.
  [[nodiscard]] virtual std::unique_ptr<Request>
  DownloadAuthenticationFactorsRegistrationState(
      const CoreAccountInfo& account_info,
      std::set<trusted_vault_pb::SecurityDomainMember_MemberType>
          recovery_factor_filter,
      DownloadAuthenticationFactorsRegistrationStateCallback callback,
      base::RepeatingClosure keep_alive_callback) = 0;
};

}  // namespace trusted_vault

#endif  // COMPONENTS_TRUSTED_VAULT_TRUSTED_VAULT_CONNECTION_H_
