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

#include "content/browser/webauth/virtual_authenticator.h"

#include <optional>
#include <utility>

#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/uuid.h"
#include "crypto/hash.h"
#include "device/fido/public/fido_constants.h"
#include "device/fido/public/public_key_credential_rp_entity.h"
#include "device/fido/public/public_key_credential_user_entity.h"
#include "device/fido/virtual_ctap2_device.h"
#include "device/fido/virtual_u2f_device.h"
#include "mojo/public/cpp/base/big_buffer.h"

namespace content {

VirtualAuthenticator::Options::Options() = default;
VirtualAuthenticator::Options::~Options() = default;

VirtualAuthenticator::VirtualAuthenticator(const Options& options)
    : protocol_(options.protocol),
      ctap2_version_(options.ctap2_version),
      attachment_(options.attachment),
      has_resident_key_(options.has_resident_key),
      has_user_verification_(options.has_user_verification),
      has_large_blob_(options.has_large_blob),
      has_cred_blob_(options.has_cred_blob),
      has_min_pin_length_(options.has_min_pin_length),
      has_prf_(options.has_prf),
      has_hmac_secret_(options.has_hmac_secret),
      has_hmac_secret_mc_(options.has_hmac_secret_mc),
      has_cmtg_key_(options.has_cmtg_key),
      unique_id_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
      state_(base::MakeRefCounted<device::VirtualFidoDevice::State>()) {
  state_->transport = options.transport;
  // If the authenticator has user verification, simulate having set it up
  // already.
  state_->fingerprints_enrolled = has_user_verification_;
  state_->default_backup_eligibility = options.default_backup_eligibility;
  state_->default_backup_state = options.default_backup_state;
  observation_.Observe(state_.get());
  SetUserPresence(true);
}

VirtualAuthenticator::~VirtualAuthenticator() {
  for (Observer& observer : observers_) {
    observer.OnAuthenticatorWillBeDestroyed(this);
  }
}

bool VirtualAuthenticator::AddRegistration(
    std::vector<uint8_t> key_handle,
    const std::string& rp_id,
    base::span<const uint8_t> private_key,
    std::optional<uint32_t> counter) {
  std::optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
      fido_private_key =
          device::VirtualFidoDevice::PrivateKey::FromPKCS8(private_key);
  if (!fido_private_key) {
    return false;
  }

  device::VirtualFidoDevice::RegistrationData registration(
      std::move(*fido_private_key), crypto::hash::Sha256(rp_id), counter);
  registration.rp = device::PublicKeyCredentialRpEntity(rp_id);
  return state_->registrations
      .emplace(std::move(key_handle), std::move(registration))
      .second;
}

bool VirtualAuthenticator::AddResidentRegistration(
    std::vector<uint8_t> key_handle,
    std::string rp_id,
    base::span<const uint8_t> private_key,
    std::optional<uint32_t> counter,
    std::vector<uint8_t> user_handle,
    std::optional<std::string> user_name,
    std::optional<std::string> user_display_name) {
  std::optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
      fido_private_key =
          device::VirtualFidoDevice::PrivateKey::FromPKCS8(private_key);
  if (!fido_private_key) {
    return false;
  }

  return state_->InjectResidentKey(
      std::move(key_handle),
      device::PublicKeyCredentialRpEntity(std::move(rp_id)),
      device::PublicKeyCredentialUserEntity(std::move(user_handle),
                                            std::move(user_name),
                                            std::move(user_display_name)),
      counter, std::move(*fido_private_key));
}

void VirtualAuthenticator::ClearRegistrations() {
  device::VirtualFidoDevice::State::RegistrationsMap erased;
  state_->registrations.swap(erased);
  for (const auto& registration : erased) {
    state_->NotifyCredentialDeleted(registration.first);
  }
}

bool VirtualAuthenticator::RemoveRegistration(
    const std::vector<uint8_t>& key_handle) {
  bool removed = state_->registrations.erase(key_handle) != 0;
  if (removed) {
    state_->NotifyCredentialDeleted(key_handle);
  }
  return removed;
}

void VirtualAuthenticator::UpdateUserDetails(std::string_view relying_party_id,
                                             base::span<const uint8_t> user_id,
                                             std::string_view name,
                                             std::string_view display_name) {
  for (auto& registration : state_->registrations) {
    if (registration.second.user && registration.second.rp &&
        registration.second.rp->id == relying_party_id &&
        registration.second.user->id == user_id) {
      registration.second.user->name = name;
      registration.second.user->display_name = display_name;
      state_->NotifyCredentialUpdated(
          std::make_pair(registration.first, &registration.second));
    }
  }
}

void VirtualAuthenticator::SetUserPresence(bool is_user_present) {
  is_user_present_ = is_user_present;
  state_->simulate_press_callback = base::BindRepeating(
      [](bool is_user_present, device::VirtualFidoDevice* device) {
        return is_user_present;
      },
      is_user_present);
}

void VirtualAuthenticator::SetSignatureCounter(
    base::span<const uint8_t> key_handle,
    std::optional<uint32_t> counter) {
  auto it = state_->registrations.find(key_handle);
  if (it != state_->registrations.end()) {
    it->second.counter = counter;
  }
}

std::unique_ptr<device::VirtualFidoDevice>
VirtualAuthenticator::ConstructDevice() {
  switch (protocol_) {
    case device::ProtocolVersion::kU2f:
      return std::make_unique<device::VirtualU2fDevice>(state_);
    case device::ProtocolVersion::kCtap2: {
      device::VirtualCtap2Device::Config config;
      switch (ctap2_version_) {
        case device::Ctap2Version::kCtap2_0:
          config.ctap2_versions = {std::begin(device::kCtap2Versions2_0),
                                   std::end(device::kCtap2Versions2_0)};
          break;
        case device::Ctap2Version::kCtap2_1:
          config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
                                   std::end(device::kCtap2Versions2_1)};
          break;
        case device::Ctap2Version::kCtap2_2:
          config.ctap2_versions = {std::begin(device::kCtap2Versions2_2),
                                   std::end(device::kCtap2Versions2_2)};
          break;
      }
      config.resident_key_support = has_resident_key_;
      config.large_blob_support = has_large_blob_;
      config.cred_protect_support = config.cred_blob_support = has_cred_blob_;
      config.min_pin_length_extension_support = has_min_pin_length_;
      if (has_prf_) {
        config.prf_support = true;
        // This is required when `prf_support` is set.
        config.internal_account_chooser = true;
      }
      if (has_hmac_secret_) {
        config.hmac_secret_support = true;
      }
      if (has_hmac_secret_mc_) {
        config.hmac_secret_support = true;
        config.hmac_secret_mc_support = true;
      }

      if (
          // Writing a large blob requires obtaining a PinUvAuthToken with
          // permissions if the authenticator is protected by user verification.
          (has_large_blob_ && has_user_verification_) ||
          // prf, hmac-secret, and hmac-secret-mc support always requires PIN
          // support because the exchange is encrypted.
          has_prf_ || has_hmac_secret_ || has_hmac_secret_mc_) {
        config.pin_uv_auth_token_support = true;
      }
      config.internal_uv_support = has_user_verification_;
      config.cmtg_key_support = has_cmtg_key_;
      config.is_platform_authenticator =
          attachment_ == device::AuthenticatorAttachment::kPlatform;
      config.user_verification_succeeds = is_user_verified_;
      config.advertised_algorithms = {
          device::CoseAlgorithmIdentifier::kEdDSA,
          device::CoseAlgorithmIdentifier::kEs256,
          device::CoseAlgorithmIdentifier::kRs256,
      };
      return std::make_unique<device::VirtualCtap2Device>(state_, config);
    }
    default:
      NOTREACHED();
  }
}

void VirtualAuthenticator::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void VirtualAuthenticator::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

bool VirtualAuthenticator::HasObserversForTest() {
  return !observers_.empty();
}

void VirtualAuthenticator::SetBackupEligibility(
    const std::vector<uint8_t>& key_handle,
    bool backup_eligibility) {
  state_->registrations.at(key_handle).backup_eligible = backup_eligibility;
}

void VirtualAuthenticator::SetBackupState(
    const std::vector<uint8_t>& key_handle,
    bool backup_state) {
  state_->registrations.at(key_handle).backup_state = backup_state;
}

void VirtualAuthenticator::GetLargeBlob(const std::vector<uint8_t>& key_handle,
                                        GetLargeBlobCallback callback) {
  auto registration = state_->registrations.find(key_handle);
  if (registration == state_->registrations.end()) {
    std::move(callback).Run(std::nullopt);
    return;
  }
  std::optional<device::LargeBlob> blob =
      state_->GetLargeBlob(registration->second);
  if (!blob) {
    std::move(callback).Run(std::nullopt);
    return;
  }
  data_decoder_.Inflate(
      std::move(blob->compressed_data), blob->original_size,
      base::BindOnce(&VirtualAuthenticator::OnLargeBlobUncompressed,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void VirtualAuthenticator::SetLargeBlob(const std::vector<uint8_t>& key_handle,
                                        const std::vector<uint8_t>& blob,
                                        SetLargeBlobCallback callback) {
  data_decoder_.Deflate(
      blob, base::BindOnce(&VirtualAuthenticator::OnLargeBlobCompressed,
                           weak_factory_.GetWeakPtr(), key_handle, blob.size(),
                           std::move(callback)));
}

void VirtualAuthenticator::OnCredentialCreated(
    const device::VirtualFidoDevice::Credential& credential) {
  for (Observer& observer : observers_) {
    observer.OnCredentialCreated(this, credential);
  }
}

void VirtualAuthenticator::OnCredentialDeleted(
    base::span<const uint8_t> credential_id) {
  for (Observer& observer : observers_) {
    observer.OnCredentialDeleted(this, credential_id);
  }
}

void VirtualAuthenticator::OnCredentialUpdated(
    const device::VirtualFidoDevice::Credential& credential) {
  for (Observer& observer : observers_) {
    observer.OnCredentialUpdated(this, credential);
  }
}

void VirtualAuthenticator::OnAssertion(
    const device::VirtualFidoDevice::Credential& credential) {
  for (Observer& observer : observers_) {
    observer.OnAssertion(this, credential);
  }
}

void VirtualAuthenticator::OnLargeBlobUncompressed(
    GetLargeBlobCallback callback,
    base::expected<mojo_base::BigBuffer, std::string> result) {
  std::optional<mojo_base::BigBuffer> value;
  if (result.has_value())
    value = std::move(*result);

  std::move(callback).Run(value ? std::make_optional(base::ToVector(*value))
                                : std::nullopt);
}

void VirtualAuthenticator::OnLargeBlobCompressed(
    base::span<const uint8_t> key_handle,
    uint64_t original_size,
    SetLargeBlobCallback callback,
    base::expected<mojo_base::BigBuffer, std::string> result) {
  auto registration = state_->registrations.find(key_handle);
  if (registration == state_->registrations.end()) {
    std::move(callback).Run(false);
    return;
  }
  if (result.has_value()) {
    state_->InjectLargeBlob(
        &registration->second,
        device::LargeBlob(base::ToVector(*result), original_size));
  }
  std::move(callback).Run(result.has_value());
}

bool VirtualAuthenticator::AddCmtgKey(
    base::span<const uint8_t> key_handle,
    std::unique_ptr<device::VirtualFidoDevice::PrivateKey> cmtg_key) {
  auto registration = state_->registrations.find(key_handle);
  if (registration == state_->registrations.end()) {
    return false;
  }
  if (!cmtg_key) {
    return false;
  }
  registration->second.cmtg_keys.push_back(std::move(cmtg_key));
  return true;
}

bool VirtualAuthenticator::SetSelectedCmtgKeyIndex(
    base::span<const uint8_t> key_handle,
    size_t index) {
  auto registration = state_->registrations.find(key_handle);
  if (registration == state_->registrations.end()) {
    return false;
  }
  registration->second.selected_cmtg_key_index = index;
  return true;
}

bool VirtualAuthenticator::SetGenerateCmtgKeyOnNextOperation(
    base::span<const uint8_t> key_handle,
    bool generate) {
  auto registration = state_->registrations.find(key_handle);
  if (registration == state_->registrations.end()) {
    return false;
  }
  registration->second.generate_cmtg_key_on_next_operation = generate;
  return true;
}

}  // namespace content
