chromium/chrome/browser/webauthn/enclave_manager.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/webauthn/enclave_manager.h"

#include <array>
#include <cstdint>
#include <deque>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/overloaded.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/sequence_checker.h"
#include "base/stl_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/types/strong_alias.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/webauthn/proto/enclave_local_state.pb.h"
#include "chrome/browser/webauthn/unexportable_key_utils.h"
#include "components/cbor/diagnostic_writer.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "components/device_event_log/device_event_log.h"
#include "components/os_crypt/sync/os_crypt.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/signin/public/identity_manager/primary_account_change_event.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "components/trusted_vault/frontend_trusted_vault_connection.h"
#include "components/trusted_vault/proto/recovery_key_store.pb.h"
#include "components/trusted_vault/recovery_key_store_connection.h"
#include "components/trusted_vault/recovery_key_store_connection_impl.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/trusted_vault_access_token_fetcher_frontend.h"
#include "components/trusted_vault/trusted_vault_access_token_fetcher_impl.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "components/unexportable_keys/ref_counted_unexportable_signing_key.h"
#include "components/unexportable_keys/unexportable_key_id.h"
#include "content/public/browser/render_frame_host.h"
#include "crypto/aead.h"
#include "crypto/hkdf.h"
#include "crypto/random.h"
#include "crypto/sha2.h"
#include "crypto/unexportable_key.h"
#include "crypto/user_verifying_key.h"
#include "device/fido/enclave/constants.h"
#include "device/fido/enclave/transact.h"
#include "device/fido/enclave/types.h"
#include "device/fido/features.h"
#include "device/fido/network_context_factory.h"
#include "google_apis/gaia/core_account_id.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/url_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/boringssl/src/include/openssl/base.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/rand.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/shell.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "crypto/scoped_lacontext.h"
#include "device/fido/enclave/icloud_recovery_key_mac.h"
#include "device/fido/mac/util.h"
#endif  // BUILDFLAG(IS_MAC)

enclave;
EnclaveLocalState;

// Holds the arguments to `StoreKeys` so that they can be processed when the
// state machine is ready for them.
struct EnclaveManager::StoreKeysArgs {};

struct EnclaveManager::PendingAction {};

namespace {

// Used so the EnclaveManager can be forced into invalid states for testing.
static bool g_invariant_override_ =;

// The maximum number of bytes that will be downloaded from the above two URLs.
constexpr size_t kMaxFetchBodyBytes =;

// The number of days between GPM PIN Vault refreshes.
constexpr int kRefreshDays =;

const net::NetworkTrafficAnnotationTag kTrafficAnnotation =;

// This prefix is the protobuf encoding for a 32-byte value with tag 1024.
// This means that, with the hash appended, the serialised state file is still a
// valid protobuf, which is handly for debugging.
static const uint8_t kHashPrefix[] =;

// Since protobuf maps `bytes` to `std::string` (rather than
// `std::vector<uint8_t>`), functions for jumping between these representations
// are needed.

base::span<const uint8_t> ToSpan(const std::string& s) {}

template <size_t N>
base::span<const uint8_t, N> ToSizedSpan(const std::string& s) {}

template <size_t N>
std::array<uint8_t, N> ToArray(base::span<const uint8_t, N> in) {}

std::vector<uint8_t> ToVector(const std::string& s) {}

std::string VecToString(base::span<const uint8_t> v) {}

bool IsValidSubjectPublicKeyInfo(base::span<const uint8_t> spki) {}

bool IsValidUncompressedP256X962(base::span<const uint8_t> x962) {}

std::optional<int> CheckPINInvariants(
    const EnclaveLocalState::WrappedPIN& wrapped_pin) {}

// CheckInvariants checks all the invariants of `user`, returning either a
// line-number for the failing check, or else `nullopt` to indicate success.
std::optional<int> CheckInvariants(const EnclaveLocalState::User& user) {}

// Build an enclave request that registers a new device and requests a new
// wrapped asymmetric key which will be used to join the security domain.
cbor::Value BuildRegistrationMessage(
    const std::string& device_id,
    const crypto::UnexportableSigningKey& identity_key,
    scoped_refptr<crypto::RefCountedUserVerifyingSigningKey> uv_key,
    bool defer_uv_key) {}

cbor::Value BuildUnregisterMessage(const std::string& device_id) {}

EnclaveLocalState::User* StateForUser(EnclaveLocalState* local_state,
                                      const CoreAccountInfo& account) {}

EnclaveLocalState::User* CreateStateForUser(EnclaveLocalState* local_state,
                                            const CoreAccountInfo& account) {}

// Returns true if `response` contains exactly `num_responses` results, and none
// of them is an error. This is used for checking whether an enclave response is
// successful or not.
bool IsAllOk(const cbor::Value& response, const size_t num_responses) {}

// Update `user` with the wrapped security domain member key in `response`.
// This is used when registering with the enclave, which provides a wrapped
// asymmetric key that becomes the security domain member key for this device.
bool SetSecurityDomainMemberKey(EnclaveLocalState::User* user,
                                const cbor::Value& wrap_response) {}

// Build an enclave request to wrap the given security domain secrets.
cbor::Value::ArrayValue BuildSecretWrappingEnclaveRequest(
    const base::flat_map<int32_t, std::vector<uint8_t>>
        new_security_domain_secrets) {}

// Build an enclave request to encrypt a PIN to the recovery key store.
cbor::Value::ArrayValue BuildRecoveryKeyStorePINWrappingEnclaveRequest(
    base::span<const uint8_t> hashed_pin,
    std::string cert_xml,
    std::string sig_xml) {}

// Build an enclave request to wrap a PIN with the security domain secret.
cbor::Value::ArrayValue BuildPINWrappingEnclaveRequest(
    base::span<const uint8_t> hashed_pin,
    int64_t generation,
    base::span<const uint8_t, 32> claim_key,
    base::span<const uint8_t, enclave::kCounterIDLen> counter_id,
    base::span<const uint8_t, enclave::kVaultHandleLen - 1>
        vault_handle_without_type,
    base::span<const uint8_t> wrapped_secret) {}

// Build an enclave request to unwrap a security domain secret and encrypt it to
// a fresh recovery key store entry.
cbor::Value::ArrayValue BuildRecoveryKeyStorePINChangeEnclaveRequest(
    base::span<const uint8_t> hashed_pin,
    std::string cert_xml,
    std::string sig_xml,
    base::span<const uint8_t, enclave::kCounterIDLen> counter_id,
    base::span<const uint8_t, enclave::kVaultHandleLen - 1>
        vault_handle_without_type,
    base::span<const uint8_t> wrapped_secret) {}

// Build an enclave request to renew a PIN.
cbor::Value BuildPINRenewalRequest(std::string cert_xml,
                                   std::string sig_xml,
                                   base::span<const uint8_t> wrapped_secret,
                                   base::span<const uint8_t> wrapped_pin) {}

cbor::Value ConcatEnclaveRequests(cbor::Value::ArrayValue head,
                                  cbor::Value::ArrayValue tail) {}

// Update `user` with the wrapped secrets in `response`. The
// `new_security_domain_secrets` argument is used to determine the version
// numbers of the wrapped secrets and this value must be the same as was passed
// to `BuildSecretWrappingEnclaveRequest` to generate the enclave request.
bool StoreWrappedSecrets(EnclaveLocalState::User* user,
                         const base::flat_map<int32_t, std::vector<uint8_t>>
                             new_security_domain_secrets,
                         base::span<const cbor::Value> responses) {}

const char* TrustedVaultRegistrationStatusToString(
    trusted_vault::TrustedVaultRegistrationStatus status) {}

// Parse the contents of the decrypted state file. In the event of an error, an
// empty state is returned. This causes a corrupt state file to reset the
// enclave state for the current profile. Users will have to re-register with
// the enclave.
std::unique_ptr<EnclaveLocalState> ParseStateFile(
    const std::string& contents_str) {}

base::flat_set<std::string> GetGaiaIDs(
    const std::vector<gaia::ListedAccount>& listed_accounts) {}

base::flat_set<std::string> GetGaiaIDs(
    const google::protobuf::Map<std::string, EnclaveLocalState::User>& users) {}

std::string UserVerifyingLabelToString(crypto::UserVerifyingKeyLabel label) {}

std::optional<crypto::UserVerifyingKeyLabel> UserVerifyingKeyLabelFromString(
    std::string saved_label) {}

// Fetch the contents of the given URL.
std::unique_ptr<network::SimpleURLLoader> FetchURL(
    network::mojom::URLLoaderFactory* url_loader_factory,
    std::string_view url,
    base::OnceCallback<void(std::optional<std::string>)> callback) {}

// Takes a CBOR array of bytestrings and returns those bytestrings assembled
// into an ASN.1 SEQUENCE.
std::optional<std::string> CBORListOfBytestringToASN1Sequence(
    const cbor::Value& array) {}

// Stores public metadata about a PIN. This is recorded in, for example, the
// Vault metadata so that MagicArch can show the correct UI and accept GPM PIN
// entries.
struct PinMetadata {};

// Convert the response to an enclave "recovery_key_store/wrap" command, into a
// protobuf that can be sent to the recovery key store service.
std::optional<std::unique_ptr<trusted_vault_pb::Vault>>
RecoveryKeyStoreWrapResponseToProto(
    const PinMetadata& pin_metadata,
    const cbor::Value& recovery_key_store_wrap_response) {}

base::flat_map<int32_t, std::vector<uint8_t>> GetNewSecretsToStore(
    const EnclaveLocalState::User& user,
    const EnclaveManager::StoreKeysArgs& args) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
UserVerifyingKeyProviderConfigChromeos MakeUserVerifyingKeyConfig(
    EnclaveManager::UVKeyOptions options) {
  UserVerifyingKeyProviderConfigChromeos config{options.dialog_controller,
                                                /*window=*/nullptr,
                                                options.rp_id};
  if (options.render_frame_host_id) {
    auto* rfh = content::RenderFrameHost::FromID(options.render_frame_host_id);
    // This is ultimately invoked from GpmEnclaveController, which can't outlive
    // the RFH where the request originated.
    CHECK(rfh);
    config.window = rfh->GetNativeView()->GetToplevelWindow();
  }
  return config;
}
#else
crypto::UserVerifyingKeyProvider::Config MakeUserVerifyingKeyConfig(
    EnclaveManager::UVKeyOptions options) {}
#endif

std::unique_ptr<crypto::UserVerifyingKeyProvider>
GetUserVerifyingKeyProviderForSigning(EnclaveManager::UVKeyOptions options) {}

std::unique_ptr<crypto::UserVerifyingKeyProvider>
GetUserVerifyingKeyProviderForCreateAndDeleteOnly() {}

struct HashedPIN {};

std::unique_ptr<HashedPIN> HashPINSlowly(std::string_view pin) {}

std::pair<int32_t, std::vector<uint8_t>> GetCurrentWrappedSecretForUser(
    const EnclaveLocalState::User* user) {}

std::vector<uint8_t> EncryptWrappedPIN(
    base::span<const uint8_t> security_domain_secret,
    base::span<const uint8_t> cbor_bytes) {}

// Parse a Vault and security domain member keys from a CBOR map. These maps
// result from enclave operations that return a Vault for insertion into the
// security domain.
static std::optional<std::pair<std::unique_ptr<trusted_vault_pb::Vault>,
                               trusted_vault::MemberKeysSource>>
ParseVaultAndMemberResponse(const int32_t key_version,
                            const PinMetadata& pin_metadata,
                            const cbor::Value::MapValue& response) {}

}  // namespace

// StateMachine performs a sequence of actions, as specified by the public
// `set_` functions, when `Start` is called. It always operates within the
// context of a specific Google account and will be destroyed by the
// EnclaveManager if the currently signed-in user changes. It works on a copy of
// the EnclaveLocalState and writes updated versions to the EnclaveManager
// once they are ready. A StateMachine is owned by the EnclaveManager and at
// most one exists at any given time.
class EnclaveManager::StateMachine {};

EnclaveManager::UVKeyOptions::UVKeyOptions() = default;
EnclaveManager::UVKeyOptions::~UVKeyOptions() = default;
EnclaveManager::UVKeyOptions::UVKeyOptions(UVKeyOptions&&) = default;
EnclaveManager::UVKeyOptions& EnclaveManager::UVKeyOptions::operator=(
    EnclaveManager::UVKeyOptions&& other) = default;

EnclaveManager::EnclaveManager(
    const base::FilePath& base_dir,
    signin::IdentityManager* identity_manager,
    device::NetworkContextFactory network_context_factory,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
    :{}

EnclaveManager::~EnclaveManager() = default;

EnclaveManager* EnclaveManager::GetEnclaveManager() {}

bool EnclaveManager::is_idle() const {}

bool EnclaveManager::is_loaded() const {}

bool EnclaveManager::is_registered() const {}

bool EnclaveManager::has_pending_keys() const {}

bool EnclaveManager::is_ready() const {}

unsigned EnclaveManager::store_keys_count() const {}

void EnclaveManager::Load(base::OnceClosure closure) {}

void EnclaveManager::RegisterIfNeeded(EnclaveManager::Callback callback) {}

void EnclaveManager::SetupWithPIN(std::string pin,
                                  EnclaveManager::Callback callback) {}

bool EnclaveManager::AddDeviceToAccount(
    std::optional<trusted_vault::GpmPinMetadata> pin_metadata,
    EnclaveManager::Callback callback) {}

void EnclaveManager::AddDeviceAndPINToAccount(
    std::string pin,
    EnclaveManager::Callback callback) {}

void EnclaveManager::ChangePIN(std::string updated_pin,
                               std::string rapt,
                               EnclaveManager::Callback callback) {}

void EnclaveManager::RenewPIN(EnclaveManager::Callback callback) {}

#if BUILDFLAG(IS_MAC)
void EnclaveManager::AddICloudRecoveryKey(
    std::unique_ptr<device::enclave::ICloudRecoveryKey> icloud_recovery_key,
    EnclaveManager::Callback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(user_->registered());
  CHECK(!secret_.empty())
      << "AddICloudRecoveryKey must be called immediately after registration "
         "and before discarding the security domain secret";
  auto action = std::make_unique<PendingAction>();
  action->callback = std::move(callback);
  action->icloud_recovery_key = std::move(icloud_recovery_key);
  pending_actions_.emplace_back(std::move(action));
  Act();
}
#endif  // BUILDFLAG(IS_MAC)

void EnclaveManager::Unenroll(EnclaveManager::Callback callback) {}

bool EnclaveManager::ConsiderSecurityDomainState(
    const trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult&
        state,
    EnclaveManager::Callback callback) {}

void EnclaveManager::GetIdentityKeyForSignature(
    base::OnceCallback<void(
        scoped_refptr<unexportable_keys::RefCountedUnexportableSigningKey>)>
        callback) {}

enclave::SigningCallback EnclaveManager::IdentityKeySigningCallback() {}

void EnclaveManager::GetUserVerifyingKeyForSignature(
    UVKeyOptions options,
    base::OnceCallback<void(
        scoped_refptr<crypto::RefCountedUserVerifyingSigningKey>)> callback) {}

enclave::SigningCallback EnclaveManager::UserVerifyingKeySigningCallback(
    UVKeyOptions options) {}

device::enclave::UVKeyCreationCallback
EnclaveManager::UserVerifyingKeyCreationCallback() {}

std::optional<std::vector<uint8_t>> EnclaveManager::GetWrappedSecret(
    int32_t version) {}

std::pair<int32_t, std::vector<uint8_t>>
EnclaveManager::GetCurrentWrappedSecret() {}

std::optional<std::pair<int32_t, std::vector<uint8_t>>>
EnclaveManager::TakeSecret() {}

bool EnclaveManager::has_wrapped_pin() const {}

bool EnclaveManager::wrapped_pin_is_arbitrary() const {}

std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN>
EnclaveManager::GetWrappedPIN() {}

EnclaveManager::UvKeyState EnclaveManager::uv_key_state(
    bool platform_has_biometrics) const {}

// static
void EnclaveManager::AreUserVerifyingKeysSupported(Callback callback) {}

std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
EnclaveManager::GetAccessToken(
    base::OnceCallback<void(std::optional<std::string>)> callback) {}

void EnclaveManager::AddObserver(Observer* observer) {}

void EnclaveManager::RemoveObserver(Observer* observer) {}

void EnclaveManager::StoreKeys(const std::string& gaia_id,
                               std::vector<std::vector<uint8_t>> keys,
                               int last_key_version) {}

std::unique_ptr<enclave::ClaimedPIN> EnclaveManager::MakeClaimedPINSlowly(
    std::string pin,
    std::unique_ptr<webauthn_pb::EnclaveLocalState_WrappedPIN> wrapped_pin) {}

bool EnclaveManager::RunWhenStoppedForTesting(base::OnceClosure on_stop) {}

EnclaveLocalState& EnclaveManager::local_state_for_testing() const {}

void EnclaveManager::ClearCachedKeysForTesting() {}

void EnclaveManager::ResetForTesting() {}

void EnclaveManager::ClearRegistrationForTesting() {}

// static
void EnclaveManager::EnableInvariantChecksForTesting(bool enabled) {}

unsigned EnclaveManager::renewal_checks_for_testing() const {}

unsigned EnclaveManager::renewal_attempts_for_testing() const {}

// static
std::string EnclaveManager::MakeWrappedPINForTesting(
    base::span<const uint8_t> security_domain_secret,
    std::string_view pin) {}

// Observes the `IdentityManager` and tells the `EnclaveManager` when the
// primary account for the profile has changed.
class EnclaveManager::IdentityObserver
    : public signin::IdentityManager::Observer {};

void EnclaveManager::Act() {}

void EnclaveManager::LoadComplete(std::optional<std::string> contents) {}

void EnclaveManager::HandleIdentityChange(bool is_post_load) {}

void EnclaveManager::Stopped() {}

void EnclaveManager::CancelAllActions() {}

void EnclaveManager::WriteState(EnclaveLocalState* new_state) {}

void EnclaveManager::DoWriteState(std::string serialized) {}

void EnclaveManager::WriteStateComplete(bool success) {}

void EnclaveManager::ClearRegistration() {}

void EnclaveManager::UnregisterComplete(EnclaveManager::Callback callback,
                                        bool success) {}

void EnclaveManager::SetSecret(int32_t key_version,
                               base::span<const uint8_t> secret) {}

void EnclaveManager::ConsiderPinRenewal() {}

void EnclaveManager::OnRenewalComplete(bool success) {}

base::WeakPtr<EnclaveManager> EnclaveManager::GetWeakPtr() {}