chromium/chrome/browser/ash/attestation/platform_verification_flow.h

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

#ifndef CHROME_BROWSER_ASH_ATTESTATION_PLATFORM_VERIFICATION_FLOW_H_
#define CHROME_BROWSER_ASH_ATTESTATION_PLATFORM_VERIFICATION_FLOW_H_

#include <memory>
#include <set>
#include <string>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/dbus/attestation/interface.pb.h"
#include "chromeos/ash/components/dbus/constants/attestation_constants.h"
#include "components/account_id/account_id.h"
#include "url/gurl.h"

class AccountId;

namespace content {
class WebContents;
}  // namespace content

namespace user_manager {
class User;
}  // namespace user_manager

namespace ash {

class AttestationClient;

namespace attestation {

class AttestationFlow;
class PlatformVerificationFlowTest;

// This class allows platform verification for the content protection use case.
// All methods must only be called on the UI thread.  Example:
//   scoped_refptr<PlatformVerificationFlow> verifier =
//       new PlatformVerificationFlow();
//   PlatformVerificationFlow::ChallengeCallback callback =
//       base::BindOnce(&MyCallback);
//   verifier->ChallengePlatformKey(my_web_contents, "my_id", "some_challenge",
//                                  std::move(callback));
//
// This class is RefCountedThreadSafe because it may need to outlive its caller.
// The attestation flow that needs to happen to establish a certified platform
// key may take minutes on some hardware.  This class will timeout after a much
// shorter time so the caller can proceed without platform verification but it
// is important that the pending operation be allowed to finish.  If the
// attestation flow is aborted at any stage, it will need to start over.  If we
// use weak pointers, the attestation flow will stop when the next callback is
// run.  So we need the instance to stay alive until the platform key is fully
// certified so the next time ChallengePlatformKey() is invoked it will be
// quick.
class PlatformVerificationFlow
    : public base::RefCountedThreadSafe<PlatformVerificationFlow> {
 public:
  // These values are reported to UMA. DO NOT CHANGE THE EXISTING VALUES!
  enum Result {
    SUCCESS,                // The operation succeeded.
    INTERNAL_ERROR,         // The operation failed unexpectedly.
    PLATFORM_NOT_VERIFIED,  // The platform cannot be verified.  For example:
                            // - It is not a Chrome device.
                            // - It is not running a verified OS image.
    POLICY_REJECTED,        // The operation is not allowed by policy/settings.
    TIMEOUT,                // The operation timed out.
    RESULT_MAX
  };

  // These values are reported to UMA. DO NOT CHANGE THE EXISTING VALUES!
  enum ExpiryStatus {
    EXPIRY_STATUS_OK,
    EXPIRY_STATUS_EXPIRING_SOON,
    EXPIRY_STATUS_EXPIRED,
    EXPIRY_STATUS_INVALID_PEM_CHAIN,
    EXPIRY_STATUS_INVALID_X509,
    EXPIRY_STATUS_MAX
  };

  // An interface which allows settings and UI to be abstracted for testing
  // purposes.  For normal operation the default implementation should be used.
  class Delegate {
   public:
    virtual ~Delegate() {}

    // Returns true iff the device is in a mode that supports platform
    // verification. For example, platform verification is not supported in dev
    // mode unless overridden by a flag.
    virtual bool IsInSupportedMode() = 0;
  };

  // This callback will be called when a challenge operation completes.  If
  // |result| is SUCCESS then |signed_data| holds the data which was signed
  // by the platform key (this is the original challenge appended with a random
  // nonce) and |signature| holds the RSA-PKCS1-v1.5 signature.  The
  // |platform_key_certificate| certifies the key used to generate the
  // signature.  This key may be generated on demand and is not guaranteed to
  // persist across multiple calls to this method.  The browser does not check
  // the validity of |signature| or |platform_key_certificate|.
  using ChallengeCallback =
      base::OnceCallback<void(Result result,
                              const std::string& signed_data,
                              const std::string& signature,
                              const std::string& platform_key_certificate)>;

  // A constructor that uses the default implementation of all dependencies
  // including Delegate.
  PlatformVerificationFlow();

  // An alternate constructor which specifies dependent objects explicitly.
  // This is useful in testing.  The caller retains ownership of all pointers.
  PlatformVerificationFlow(AttestationFlow* attestation_flow,
                           AttestationClient* attestation_client,
                           Delegate* delegate);

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

  // Invokes an asynchronous operation to challenge a platform key.  Any user
  // interaction will be associated with |web_contents|.  The |service_id| is an
  // arbitrary value but it should uniquely identify the origin of the request
  // and should not be determined by that origin; its purpose is to prevent
  // collusion between multiple services.  The |challenge| is also an arbitrary
  // value but it should be time sensitive or associated to some kind of session
  // because its purpose is to prevent certificate replay.  The |callback| will
  // be called when the operation completes.  The duration of the operation can
  // vary depending on system state, hardware capabilities, and interaction with
  // the user.
  void ChallengePlatformKey(content::WebContents* web_contents,
                            const std::string& service_id,
                            const std::string& challenge,
                            ChallengeCallback callback);

  // Identical to ChallengePlatformKey above except the User has been extracted
  // from the input |web_contents|. The former is needed since non-Ash callsites
  // of this class cannot directly reference User*.
  void ChallengePlatformKey(const user_manager::User* user,
                            const std::string& service_id,
                            const std::string& challenge,
                            ChallengeCallback callback);

  void set_timeout_delay(const base::TimeDelta& timeout_delay) {
    timeout_delay_ = timeout_delay;
  }

  // Public for tests.
  static bool IsAttestationAllowedByPolicy();

 private:
  friend class base::RefCountedThreadSafe<PlatformVerificationFlow>;
  friend class PlatformVerificationFlowTest;

  // Holds the arguments of a ChallengePlatformKey call.  This is convenient for
  // use with base::Bind so we don't get too many arguments.
  struct ChallengeContext {
    ChallengeContext(const AccountId& account_id,
                     const std::string& service_id,
                     const std::string& challenge,
                     ChallengeCallback callback);
    ChallengeContext(ChallengeContext&& other);
    ~ChallengeContext();

    AccountId account_id;
    std::string service_id;
    std::string challenge;
    ChallengeCallback callback;
  };

  ~PlatformVerificationFlow();

  // Callback for attestation preparation. The arguments to ChallengePlatformKey
  // are in |context|, and |reply| is the result of |GetEnrollmentPreparations|.
  void OnAttestationPrepared(
      ChallengeContext context,
      const ::attestation::GetEnrollmentPreparationsReply& reply);

  // Initiates the flow to get a platform key certificate.  The arguments to
  // ChallengePlatformKey are in |context|.  If |force_new_key| is true then any
  // existing key for the same user and service will be ignored and a new key
  // will be generated and certified.
  void GetCertificate(
      scoped_refptr<base::RefCountedData<ChallengeContext>> context,
      bool force_new_key);

  // A callback called when an attestation certificate request operation
  // completes.  The arguments to ChallengePlatformKey are in |context|.
  // |account_id| identifies the user for which the certificate was requested.
  // |operation_success| is true iff the certificate request operation
  // succeeded.  |certificate_chain| holds the certificate for the platform
  // key on success.  If the certificate request was successful, this method
  // invokes a request to sign the challenge.  If the operation timed out
  // prior to this method being called, this method does nothing - notably,
  // the callback is not invoked.
  void OnCertificateReady(
      scoped_refptr<base::RefCountedData<ChallengeContext>> context,
      const AccountId& account_id,
      std::unique_ptr<base::OneShotTimer> timer,
      AttestationStatus operation_status,
      const std::string& certificate_chain);

  // A callback run after a constant delay to handle timeouts for lengthy
  // certificate requests.  |context.callback| will be invoked with a TIMEOUT
  // result.
  void OnCertificateTimeout(
      scoped_refptr<base::RefCountedData<ChallengeContext>> context);

  // A callback called when a challenge signing request has completed.  The
  // `certificate_chain` is the platform certificate chain for the key which
  // signed the `challenge`.  The arguments to ChallengePlatformKey are in
  // `context`. `account_id` identifies the user for which the certificate was
  // requested. `is_expiring_soon` will be set iff a certificate in the
  // `certificate_chain` is expiring soon. `reply` is returned from
  // `AttestationClient`. Upon success, the method will invoke
  // `context.callback`.
  void OnChallengeReady(ChallengeContext context,
                        const AccountId& account_id,
                        const std::string& certificate_chain,
                        bool is_expiring_soon,
                        const ::attestation::SignSimpleChallengeReply& reply);

  // Checks if |certificate_chain| is a PEM certificate chain that contains a
  // certificate this is expired or expiring soon. Returns the expiry status.
  ExpiryStatus CheckExpiry(const std::string& certificate_chain);

  // An AttestationFlow::CertificateCallback that handles renewal completion.
  // |old_certificate_chain| contains the chain that has been replaced.
  void RenewCertificateCallback(const std::string& old_certificate_chain,
                                AttestationStatus operation_status,
                                const std::string& certificate_chain);

  raw_ptr<AttestationFlow> attestation_flow_;
  std::unique_ptr<AttestationFlow> default_attestation_flow_;
  const raw_ptr<AttestationClient, DanglingUntriaged> attestation_client_;
  raw_ptr<Delegate> delegate_;
  std::unique_ptr<Delegate> default_delegate_;
  base::TimeDelta timeout_delay_;
  std::set<std::string> renewals_in_progress_;
};

}  // namespace attestation
}  // namespace ash

#endif  // CHROME_BROWSER_ASH_ATTESTATION_PLATFORM_VERIFICATION_FLOW_H_