blob: a7b81d64d3725b2cbd964de35df9ad1378e6a4b6 [file] [log] [blame]
// Copyright 2013 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 <memory>
#include <set>
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
// TODO( forward declare AttestatoinFlow
// after //chromeos/attestation is moved to ash.
#include "chromeos/attestation/attestation_flow.h"
// TODO( forward declare AttestatoinClient
// before ChromeOS source migration.
#include "chromeos/dbus/attestation/attestation_client.h"
#include "chromeos/dbus/attestation/interface.pb.h"
#include "chromeos/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 {
namespace attestation {
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 ChallegePlatformKey() is invoked it will be quick.
class PlatformVerificationFlow
: public base::RefCountedThreadSafe<PlatformVerificationFlow> {
// 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.
// These values are reported to UMA. DO NOT CHANGE THE EXISTING VALUES!
enum ExpiryStatus {
// An interface which allows settings and UI to be abstracted for testing
// purposes. For normal operation the default implementation should be used.
class Delegate {
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.
// 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);
// 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();
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);
AccountId account_id;
std::string service_id;
std::string challenge;
ChallengeCallback callback;
// 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);
AttestationFlow* attestation_flow_;
std::unique_ptr<AttestationFlow> default_attestation_flow_;
AttestationClient* const attestation_client_;
Delegate* delegate_;
std::unique_ptr<Delegate> default_delegate_;
base::TimeDelta timeout_delay_;
std::set<std::string> renewals_in_progress_;
} // namespace attestation
} // namespace ash