chromium/chrome/browser/webauthn/unexportable_key_utils_chromeos.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.

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

#include <cstdint>
#include <string>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/auth/active_session_auth_controller.h"
#include "ash/public/cpp/webauthn_dialog_controller.h"
#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/overloaded.h"
#include "chromeos/ash/components/osauth/impl/request/webauthn_auth_request.h"
#include "crypto/unexportable_key.h"
#include "crypto/user_verifying_key.h"
#include "ui/aura/window.h"

namespace {

std::unique_ptr<crypto::UserVerifyingKeyProvider> (*g_test_override)() =
    nullptr;

// UserVerifyingSigningKeyCros wraps an `UnexportableSigningKey` and presents an
// Ash InSessionAuth dialog prior to releasing a signature. The wrapped key is
// currently not hardware bound.
class UserVerifyingSigningKeyCros : public crypto::UserVerifyingSigningKey {
 public:
  explicit UserVerifyingSigningKeyCros(
      UserVerifyingKeyProviderConfigChromeos config,
      std::unique_ptr<crypto::UnexportableSigningKey> signing_key,
      std::string label)
      : config_(std::move(config)),
        signing_key_(std::move(signing_key)),
        label_(std::move(label)) {
    CHECK(signing_key_);
  }

  ~UserVerifyingSigningKeyCros() override = default;

  void Sign(base::span<const uint8_t> data,
            UserVerifyingKeySignatureCallback callback) override {
    CHECK(config_.window);
    VerifyUser(base::BindOnce(
        &UserVerifyingSigningKeyCros::DoSign, weak_factory_.GetWeakPtr(),
        std::vector<uint8_t>(data.begin(), data.end()), std::move(callback)));
  }

  std::vector<uint8_t> GetPublicKey() const override {
    return signing_key_->GetSubjectPublicKeyInfo();
  }

  const crypto::UserVerifyingKeyLabel& GetKeyLabel() const override {
    return label_;
  }

 private:
  void VerifyUser(base::OnceCallback<void(bool)> callback) {
    auto visitor = base::Overloaded(
        // Legacy code path.
        [&](raw_ptr<ash::WebAuthNDialogController> controller) {
          CHECK(controller);
          controller->ShowAuthenticationDialog(config_.window, config_.rp_id,
                                               std::move(callback));
        },

        // New code path.
        [&](raw_ptr<ash::ActiveSessionAuthController> controller) {
          CHECK(controller);
          auto webauthn_auth_request =
              std::make_unique<ash::WebAuthNAuthRequest>(config_.rp_id,
                                                         std::move(callback));

          controller->ShowAuthDialog(std::move(webauthn_auth_request));
        });

    std::visit(visitor, config_.dialog_controller);
  }

  void DoSign(std::vector<uint8_t> data,
              UserVerifyingKeySignatureCallback callback,
              bool uv_success) {
    if (!uv_success) {
      std::move(callback).Run(base::unexpected(
          crypto::UserVerifyingKeySigningError::kUserCancellation));
      return;
    }
    std::optional<std::vector<uint8_t>> signature =
        signing_key_->SignSlowly(data);
    if (!signature) {
      std::move(callback).Run(base::unexpected(
          crypto::UserVerifyingKeySigningError::kUnknownError));
      return;
    }
    std::move(callback).Run(std::move(*signature));
  }

  const UserVerifyingKeyProviderConfigChromeos config_;
  const std::unique_ptr<crypto::UnexportableSigningKey> signing_key_;
  const std::string label_;

  base::WeakPtrFactory<UserVerifyingSigningKeyCros> weak_factory_{this};
};

class UserVerifyingKeyProviderCros : public crypto::UserVerifyingKeyProvider {
 public:
  explicit UserVerifyingKeyProviderCros(
      UserVerifyingKeyProviderConfigChromeos config)
      : config_(config) {}

  ~UserVerifyingKeyProviderCros() override = default;

  void GenerateUserVerifyingSigningKey(
      base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
          acceptable_algorithms,
      UserVerifyingKeyCreationCallback callback) override {
    std::unique_ptr<crypto::UnexportableSigningKey> key =
        crypto::GetSoftwareUnsecureUnexportableKeyProvider()
            ->GenerateSigningKeySlowly(acceptable_algorithms);
    std::string label = base::Base64Encode(key->GetWrappedKey());
    std::move(callback).Run(std::make_unique<UserVerifyingSigningKeyCros>(
        config_, std::move(key), std::move(label)));
  }

  void GetUserVerifyingSigningKey(
      crypto::UserVerifyingKeyLabel key_label,
      UserVerifyingKeyCreationCallback callback) override {
    std::optional<std::vector<uint8_t>> wrapped_private_key =
        base::Base64Decode(key_label);
    if (!wrapped_private_key) {
      std::move(callback).Run(
          base::unexpected(crypto::UserVerifyingKeyCreationError::kNotFound));
      return;
    }
    std::unique_ptr<crypto::UnexportableSigningKey> signing_key =
        crypto::GetSoftwareUnsecureUnexportableKeyProvider()
            ->FromWrappedSigningKeySlowly(*wrapped_private_key);
    std::move(callback).Run(std::make_unique<UserVerifyingSigningKeyCros>(
        config_, std::move(signing_key), std::move(key_label)));
  }

  void DeleteUserVerifyingKey(
      crypto::UserVerifyingKeyLabel key_label,
      base::OnceCallback<void(bool)> callback) override {
    // UserVerifyingSigningKeyCros is stateless, so this is a no-op.
    std::move(callback).Run(true);
  }

 private:
  UserVerifyingKeyProviderConfigChromeos config_;
};

}  // namespace

#if BUILDFLAG(IS_CHROMEOS_ASH)
UserVerifyingKeyProviderConfigChromeos::UserVerifyingKeyProviderConfigChromeos(
    AuthDialogController dialog_controller,
    aura::Window* window,
    std::string rp_id)
    : dialog_controller(dialog_controller),
      window(window),
      rp_id(std::move(rp_id)) {}

UserVerifyingKeyProviderConfigChromeos::
    ~UserVerifyingKeyProviderConfigChromeos() = default;

UserVerifyingKeyProviderConfigChromeos::UserVerifyingKeyProviderConfigChromeos(
    const UserVerifyingKeyProviderConfigChromeos&) = default;

UserVerifyingKeyProviderConfigChromeos&
UserVerifyingKeyProviderConfigChromeos::operator=(
    const UserVerifyingKeyProviderConfigChromeos&) = default;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

std::unique_ptr<crypto::UserVerifyingKeyProvider>
GetWebAuthnUserVerifyingKeyProvider(
    UserVerifyingKeyProviderConfigChromeos config) {
  if (g_test_override) {
    return g_test_override();
  }
  return std::make_unique<UserVerifyingKeyProviderCros>(std::move(config));
}

// ChromeOS doesn't use the UserVerifyingKeyProvider provided by //crypto, so
// the test override is handled separately as well.
void OverrideWebAuthnChromeosUserVerifyingKeyProviderForTesting(
    std::unique_ptr<crypto::UserVerifyingKeyProvider> (*func)()) {
  if (g_test_override) {
    // Prevent nesting of scoped providers.
    CHECK(!func);
    g_test_override = nullptr;
  } else {
    g_test_override = func;
  }
}