// Copyright (c) 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 "chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h"

#include <string>
#include <utility>

#include "base/base64.h"
#include "base/callback.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/settings/install_attributes.h"
#include "chrome/browser/extensions/chrome_extension_function_details.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/common/extensions/api/enterprise_platform_keys_private.h"
#include "chrome/common/pref_names.h"
#include "chromeos/attestation/attestation_flow.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/attestation_constants.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_method_call_status.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/settings/cros_settings_names.h"
#include "components/account_id/account_id.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/common/manifest.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace extensions {

namespace api_epkp = api::enterprise_platform_keys_private;

// Base class

const char EPKPChallengeKeyBase::kChallengeBadBase64Error[] =
    "Challenge is not base64 encoded.";
const char EPKPChallengeKeyBase::kDevicePolicyDisabledError[] =
    "Remote attestation is not enabled for your device.";
const char EPKPChallengeKeyBase::kExtensionNotWhitelistedError[] =
    "The extension does not have permission to call this function.";
const char EPKPChallengeKeyBase::kResponseBadBase64Error[] =
    "Response cannot be encoded in base64.";
const char EPKPChallengeKeyBase::kSignChallengeFailedError[] =
    "Failed to sign the challenge.";
const char EPKPChallengeKeyBase::kUserNotManaged[] =
    "The user account is not enterprise managed.";

EPKPChallengeKeyBase::PrepareKeyContext::PrepareKeyContext(
    chromeos::attestation::AttestationKeyType key_type,
    const AccountId& account_id,
    const std::string& key_name,
    chromeos::attestation::AttestationCertificateProfile certificate_profile,
    bool require_user_consent,
    const base::Callback<void(PrepareKeyResult)>& callback)
    : key_type(key_type),
      account_id(account_id),
      key_name(key_name),
      certificate_profile(certificate_profile),
      require_user_consent(require_user_consent),
      callback(callback) {}

EPKPChallengeKeyBase::PrepareKeyContext::PrepareKeyContext(
    const PrepareKeyContext& other) = default;

EPKPChallengeKeyBase::PrepareKeyContext::~PrepareKeyContext() {
}

EPKPChallengeKeyBase::EPKPChallengeKeyBase()
    : cryptohome_client_(
          chromeos::DBusThreadManager::Get()->GetCryptohomeClient()),
      async_caller_(cryptohome::AsyncMethodCaller::GetInstance()),
      install_attributes_(g_browser_process->platform_part()
                              ->browser_policy_connector_chromeos()
                              ->GetInstallAttributes()) {
  std::unique_ptr<chromeos::attestation::ServerProxy> ca_client(
      new chromeos::attestation::AttestationCAClient());
  default_attestation_flow_.reset(new chromeos::attestation::AttestationFlow(
      async_caller_, cryptohome_client_, std::move(ca_client)));
  attestation_flow_ = default_attestation_flow_.get();
}

EPKPChallengeKeyBase::EPKPChallengeKeyBase(
    chromeos::CryptohomeClient* cryptohome_client,
    cryptohome::AsyncMethodCaller* async_caller,
    chromeos::attestation::AttestationFlow* attestation_flow,
    chromeos::InstallAttributes* install_attributes) :
    cryptohome_client_(cryptohome_client),
    async_caller_(async_caller),
    attestation_flow_(attestation_flow),
    install_attributes_(install_attributes) {
}

EPKPChallengeKeyBase::~EPKPChallengeKeyBase() {
}

void EPKPChallengeKeyBase::GetDeviceAttestationEnabled(
    const base::Callback<void(bool)>& callback) const {
  chromeos::CrosSettings* settings = chromeos::CrosSettings::Get();
  chromeos::CrosSettingsProvider::TrustedStatus status =
      settings->PrepareTrustedValues(
          base::Bind(&EPKPChallengeKeyBase::GetDeviceAttestationEnabled,
                     base::Unretained(this), callback));

  bool value = false;
  switch (status) {
    case chromeos::CrosSettingsProvider::TRUSTED:
      if (!settings->GetBoolean(chromeos::kDeviceAttestationEnabled, &value))
        value = false;
      break;
    case chromeos::CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
      // Do nothing. This function will be called again when the values are
      // ready.
      return;
    case chromeos::CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
      // If the value cannot be trusted, we assume that the device attestation
      // is false to be on the safe side.
      break;
  }

  callback.Run(value);
}

bool EPKPChallengeKeyBase::IsEnterpriseDevice() const {
  return install_attributes_->IsEnterpriseManaged();
}

bool EPKPChallengeKeyBase::IsExtensionWhitelisted() const {
  if (!chromeos::ProfileHelper::Get()->GetUserByProfile(profile_)) {
    // Only allow remote attestation for apps that were force-installed on the
    // login/signin screen.
    // TODO(drcrash): Use a separate device-wide policy for the API.
    return Manifest::IsPolicyLocation(extension_->location());
  }
  if (Manifest::IsComponentLocation(extension_->location())) {
    // Note: For this to even be called, the component extension must also be
    // whitelisted in chrome/common/extensions/api/_permission_features.json
    return true;
  }
  const base::ListValue* list =
      profile_->GetPrefs()->GetList(prefs::kAttestationExtensionWhitelist);
  base::Value value(extension_->id());
  return list->Find(value) != list->end();
}

AccountId EPKPChallengeKeyBase::GetAccountId() const {
  const user_manager::User* user =
      chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);

  // Signin profile doesn't have associated user.
  if (!user) {
    return EmptyAccountId();
  }

  return user->GetAccountId();
}

bool EPKPChallengeKeyBase::IsUserAffiliated() const {
  const user_manager::User* const user =
      user_manager::UserManager::Get()->FindUser(GetAccountId());

  if (user) {
    return user->IsAffiliated();
  }

  return false;
}

std::string EPKPChallengeKeyBase::GetEnterpriseDomain() const {
  return install_attributes_->GetDomain();
}

std::string EPKPChallengeKeyBase::GetUserEmail() const {
  return GetAccountId().GetUserEmail();
}

std::string EPKPChallengeKeyBase::GetDeviceId() const {
  return install_attributes_->GetDeviceId();
}

void EPKPChallengeKeyBase::PrepareKey(
    chromeos::attestation::AttestationKeyType key_type,
    const AccountId& account_id,
    const std::string& key_name,
    chromeos::attestation::AttestationCertificateProfile certificate_profile,
    bool require_user_consent,
    const base::Callback<void(PrepareKeyResult)>& callback) {
  const PrepareKeyContext context = PrepareKeyContext(key_type,
                                                      account_id,
                                                      key_name,
                                                      certificate_profile,
                                                      require_user_consent,
                                                      callback);
  cryptohome_client_->TpmAttestationIsPrepared(
      base::BindOnce(&EPKPChallengeKeyBase::IsAttestationPreparedCallback,
                     base::Unretained(this), context));
}

void EPKPChallengeKeyBase::IsAttestationPreparedCallback(
    const PrepareKeyContext& context,
    base::Optional<bool> result) {
  if (!result.has_value()) {
    context.callback.Run(PREPARE_KEY_DBUS_ERROR);
    return;
  }
  if (!result.value()) {
    context.callback.Run(PREPARE_KEY_RESET_REQUIRED);
    return;
  }
  // Attestation is available, see if the key we need already exists.
  cryptohome_client_->TpmAttestationDoesKeyExist(
      context.key_type, cryptohome::Identification(context.account_id),
      context.key_name,
      base::BindOnce(&EPKPChallengeKeyBase::DoesKeyExistCallback,
                     base::Unretained(this), context));
}

void EPKPChallengeKeyBase::DoesKeyExistCallback(
    const PrepareKeyContext& context,
    base::Optional<bool> result) {
  if (!result.has_value()) {
    context.callback.Run(PREPARE_KEY_DBUS_ERROR);
    return;
  }

  if (result.value()) {
    // The key exists. Do nothing more.
    context.callback.Run(PREPARE_KEY_OK);
  } else {
    // The key does not exist. Create a new key and have it signed by PCA.
    if (context.require_user_consent) {
      // We should ask the user explicitly before sending any private
      // information to PCA.
      AskForUserConsent(
          base::Bind(&EPKPChallengeKeyBase::AskForUserConsentCallback,
                     base::Unretained(this), context));
    } else {
      // User consent is not required. Skip to the next step.
      AskForUserConsentCallback(context, true);
    }
  }
}

void EPKPChallengeKeyBase::AskForUserConsent(
    const base::Callback<void(bool)>& callback) const {
  // TODO(davidyu): right now we just simply reject the request before we have
  // a way to ask for user consent.
  callback.Run(false);
}

void EPKPChallengeKeyBase::AskForUserConsentCallback(
    const PrepareKeyContext& context,
    bool result) {
  if (!result) {
    // The user rejects the request.
    context.callback.Run(PREPARE_KEY_USER_REJECTED);
    return;
  }

  // Generate a new key and have it signed by PCA.
  attestation_flow_->GetCertificate(
      context.certificate_profile, context.account_id,
      std::string(),  // Not used.
      true,           // Force a new key to be generated.
      base::Bind(&EPKPChallengeKeyBase::GetCertificateCallback,
                 base::Unretained(this), context.callback));
}

void EPKPChallengeKeyBase::GetCertificateCallback(
    const base::Callback<void(PrepareKeyResult)>& callback,
    chromeos::attestation::AttestationStatus status,
    const std::string& pem_certificate_chain) {
  if (status != chromeos::attestation::ATTESTATION_SUCCESS) {
    callback.Run(PREPARE_KEY_GET_CERTIFICATE_FAILED);
    return;
  }

  callback.Run(PREPARE_KEY_OK);
}

// Implementation of ChallengeMachineKey()

const char EPKPChallengeMachineKey::kGetCertificateFailedError[] =
    "Failed to get Enterprise machine certificate. Error code = %d";
const char EPKPChallengeMachineKey::kKeyRegistrationFailedError[] =
    "Machine key registration failed.";
const char EPKPChallengeMachineKey::kNonEnterpriseDeviceError[] =
    "The device is not enterprise enrolled.";

const char EPKPChallengeMachineKey::kKeyName[] = "attest-ent-machine";

EPKPChallengeMachineKey::EPKPChallengeMachineKey() : EPKPChallengeKeyBase() {
}

EPKPChallengeMachineKey::EPKPChallengeMachineKey(
    chromeos::CryptohomeClient* cryptohome_client,
    cryptohome::AsyncMethodCaller* async_caller,
    chromeos::attestation::AttestationFlow* attestation_flow,
    chromeos::InstallAttributes* install_attributes) :
    EPKPChallengeKeyBase(cryptohome_client,
                         async_caller,
                         attestation_flow,
                         install_attributes) {
}

EPKPChallengeMachineKey::~EPKPChallengeMachineKey() {
}

void EPKPChallengeMachineKey::Run(
    scoped_refptr<UIThreadExtensionFunction> caller,
    const ChallengeKeyCallback& callback,
    const std::string& challenge,
    bool register_key) {
  callback_ = callback;
  profile_ = ChromeExtensionFunctionDetails(caller.get()).GetProfile();
  extension_ = scoped_refptr<const Extension>(caller->extension());

  // Check if the device is enterprise enrolled.
  if (!IsEnterpriseDevice()) {
    callback_.Run(false, kNonEnterpriseDeviceError);
    return;
  }

  // Check if the extension is whitelisted in the user policy.
  if (!IsExtensionWhitelisted()) {
    callback_.Run(false, kExtensionNotWhitelistedError);
    return;
  }

  // Check whether the user is managed unless the signin profile is used.
  if (chromeos::ProfileHelper::Get()->GetUserByProfile(profile_) &&
      !IsUserAffiliated()) {
    callback_.Run(false, kUserNotManaged);
    return;
  }

  // Check if RA is enabled in the device policy.
  GetDeviceAttestationEnabled(
      base::Bind(&EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback,
                 base::Unretained(this), challenge, register_key));
}

void EPKPChallengeMachineKey::DecodeAndRun(
    scoped_refptr<UIThreadExtensionFunction> caller,
    const ChallengeKeyCallback& callback,
    const std::string& encoded_challenge,
    bool register_key) {
  std::string challenge;
  if (!base::Base64Decode(encoded_challenge, &challenge)) {
    callback.Run(false, kChallengeBadBase64Error);
    return;
  }
  Run(caller, callback, challenge, register_key);
}

void EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback(
    const std::string& challenge,
    bool register_key,
    bool enabled) {
  if (!enabled) {
    callback_.Run(false, kDevicePolicyDisabledError);
    return;
  }

  PrepareKey(chromeos::attestation::KEY_DEVICE,
             EmptyAccountId(),  // Not used.
             kKeyName,
             chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
             false,  // user consent is not required.
             base::Bind(&EPKPChallengeMachineKey::PrepareKeyCallback,
                        base::Unretained(this), challenge, register_key));
}

void EPKPChallengeMachineKey::PrepareKeyCallback(const std::string& challenge,
                                                 bool register_key,
                                                 PrepareKeyResult result) {
  if (result != PREPARE_KEY_OK) {
    callback_.Run(false,
                  base::StringPrintf(kGetCertificateFailedError, result));
    return;
  }

  // Everything is checked. Sign the challenge.
  async_caller_->TpmAttestationSignEnterpriseChallenge(
      chromeos::attestation::KEY_DEVICE,
      cryptohome::Identification(),  // Not used.
      kKeyName, GetEnterpriseDomain(), GetDeviceId(),
      register_key ? chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY
                   : chromeos::attestation::CHALLENGE_OPTION_NONE,
      challenge,
      base::Bind(&EPKPChallengeMachineKey::SignChallengeCallback,
                 base::Unretained(this), register_key));
}

void EPKPChallengeMachineKey::SignChallengeCallback(
    bool register_key,
    bool success,
    const std::string& response) {
  if (!success) {
    callback_.Run(false, kSignChallengeFailedError);
    return;
  }
  if (register_key) {
    async_caller_->TpmAttestationRegisterKey(
        chromeos::attestation::KEY_DEVICE,
        cryptohome::Identification(),  // Not used.
        kKeyName,
        base::Bind(&EPKPChallengeMachineKey::RegisterKeyCallback,
                   base::Unretained(this), response));
  } else {
    RegisterKeyCallback(response, true, cryptohome::MOUNT_ERROR_NONE);
  }
}

void EPKPChallengeMachineKey::RegisterKeyCallback(
    const std::string& response,
    bool success,
    cryptohome::MountError return_code) {
  if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) {
    callback_.Run(false, kKeyRegistrationFailedError);
    return;
  }
  callback_.Run(true, response);
}

// Implementation of ChallengeUserKey()

const char EPKPChallengeUserKey::kGetCertificateFailedError[] =
    "Failed to get Enterprise user certificate. Error code = %d";
const char EPKPChallengeUserKey::kKeyRegistrationFailedError[] =
    "Key registration failed.";
const char EPKPChallengeUserKey::kUserPolicyDisabledError[] =
    "Remote attestation is not enabled for your account.";
const char EPKPChallengeUserKey::kUserKeyNotAvailable[] =
    "User keys cannot be challenged in this profile.";

const char EPKPChallengeUserKey::kKeyName[] = "attest-ent-user";

EPKPChallengeUserKey::EPKPChallengeUserKey() : EPKPChallengeKeyBase() {
}

EPKPChallengeUserKey::EPKPChallengeUserKey(
    chromeos::CryptohomeClient* cryptohome_client,
    cryptohome::AsyncMethodCaller* async_caller,
    chromeos::attestation::AttestationFlow* attestation_flow,
    chromeos::InstallAttributes* install_attributes) :
    EPKPChallengeKeyBase(cryptohome_client,
                         async_caller,
                         attestation_flow,
                         install_attributes) {
}

EPKPChallengeUserKey::~EPKPChallengeUserKey() {
}

void EPKPChallengeUserKey::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(prefs::kAttestationEnabled, false);
  registry->RegisterListPref(prefs::kAttestationExtensionWhitelist);
}

void EPKPChallengeUserKey::Run(scoped_refptr<UIThreadExtensionFunction> caller,
                               const ChallengeKeyCallback& callback,
                               const std::string& challenge,
                               bool register_key) {
  callback_ = callback;
  profile_ = ChromeExtensionFunctionDetails(caller.get()).GetProfile();
  extension_ = scoped_refptr<const Extension>(caller->extension());

  // Check if user keys are available in this profile.
  if (!chromeos::ProfileHelper::Get()->GetUserByProfile(profile_)) {
    callback_.Run(false, EPKPChallengeUserKey::kUserKeyNotAvailable);
    return;
  }

  // Check if RA is enabled in the user policy.
  if (!IsRemoteAttestationEnabledForUser()) {
    callback_.Run(false, kUserPolicyDisabledError);
    return;
  }

  // Check if the extension is whitelisted in the user policy.
  if (!IsExtensionWhitelisted()) {
    callback_.Run(false, kExtensionNotWhitelistedError);
    return;
  }

  if (IsEnterpriseDevice()) {
    if (!IsUserAffiliated()) {
      callback_.Run(false, kUserNotManaged);
      return;
    }

    // Check if RA is enabled in the device policy.
    GetDeviceAttestationEnabled(
        base::Bind(&EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback,
                   base::Unretained(this), challenge, register_key,
                   false));  // user consent is not required.
  } else {
    // For personal devices, we don't need to check if RA is enabled in the
    // device, but we need to ask for user consent if the key does not exist.
    GetDeviceAttestationEnabledCallback(challenge, register_key,
                                        true,   // user consent is required.
                                        true);  // attestation is enabled.
  }
}

void EPKPChallengeUserKey::DecodeAndRun(
    scoped_refptr<UIThreadExtensionFunction> caller,
    const ChallengeKeyCallback& callback,
    const std::string& encoded_challenge,
    bool register_key) {
  std::string challenge;
  if (!base::Base64Decode(encoded_challenge, &challenge)) {
    callback.Run(false, kChallengeBadBase64Error);
    return;
  }
  Run(caller, callback, challenge, register_key);
}

void EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback(
    const std::string& challenge,
    bool register_key,
    bool require_user_consent,
    bool enabled) {
  if (!enabled) {
    callback_.Run(false, kDevicePolicyDisabledError);
    return;
  }

  PrepareKey(chromeos::attestation::KEY_USER, GetAccountId(), kKeyName,
             chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE,
             require_user_consent,
             base::Bind(&EPKPChallengeUserKey::PrepareKeyCallback,
                        base::Unretained(this), challenge, register_key));
}

void EPKPChallengeUserKey::PrepareKeyCallback(const std::string& challenge,
                                              bool register_key,
                                              PrepareKeyResult result) {
  if (result != PREPARE_KEY_OK) {
    callback_.Run(false,
                  base::StringPrintf(kGetCertificateFailedError, result));
    return;
  }

  // Everything is checked. Sign the challenge.
  async_caller_->TpmAttestationSignEnterpriseChallenge(
      chromeos::attestation::KEY_USER,
      cryptohome::Identification(GetAccountId()), kKeyName, GetUserEmail(),
      GetDeviceId(),
      register_key ? chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY
                   : chromeos::attestation::CHALLENGE_OPTION_NONE,
      challenge, base::Bind(&EPKPChallengeUserKey::SignChallengeCallback,
                            base::Unretained(this), register_key));
}

void EPKPChallengeUserKey::SignChallengeCallback(bool register_key,
                                                 bool success,
                                                 const std::string& response) {
  if (!success) {
    callback_.Run(false, kSignChallengeFailedError);
    return;
  }

  if (register_key) {
    async_caller_->TpmAttestationRegisterKey(
        chromeos::attestation::KEY_USER,
        cryptohome::Identification(GetAccountId()), kKeyName,
        base::Bind(&EPKPChallengeUserKey::RegisterKeyCallback,
                   base::Unretained(this), response));
  } else {
    RegisterKeyCallback(response, true, cryptohome::MOUNT_ERROR_NONE);
  }
}

void EPKPChallengeUserKey::RegisterKeyCallback(
    const std::string& response,
    bool success,
    cryptohome::MountError return_code) {
  if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) {
    callback_.Run(false, kKeyRegistrationFailedError);
    return;
  }
  callback_.Run(true, response);
}

bool EPKPChallengeUserKey::IsRemoteAttestationEnabledForUser() const {
  return profile_->GetPrefs()->GetBoolean(prefs::kAttestationEnabled);
}

EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
    EnterprisePlatformKeysPrivateChallengeMachineKeyFunction()
    : default_impl_(new EPKPChallengeMachineKey), impl_(default_impl_.get()) {}

EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
    EnterprisePlatformKeysPrivateChallengeMachineKeyFunction(
        EPKPChallengeMachineKey* impl_for_testing)
    : impl_(impl_for_testing) {}

EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
    ~EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() = default;

ExtensionFunction::ResponseAction
EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::Run() {
  std::unique_ptr<api_epkp::ChallengeMachineKey::Params> params(
      api_epkp::ChallengeMachineKey::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params);
  ChallengeKeyCallback callback =
      base::Bind(&EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::
                     OnChallengedKey,
                 this);
  // base::Unretained is safe on impl_ since its life-cycle matches |this| and
  // |callback| holds a reference to |this|.
  base::Closure task = base::Bind(
      &EPKPChallengeMachineKey::DecodeAndRun, base::Unretained(impl_),
      scoped_refptr<UIThreadExtensionFunction>(AsUIThreadExtensionFunction()),
      callback, params->challenge, false);
  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, task);
  return RespondLater();
}

void EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::OnChallengedKey(
    bool success,
    const std::string& data) {
  if (success) {
    std::string encoded_response;
    base::Base64Encode(data, &encoded_response);
    Respond(ArgumentList(
        api_epkp::ChallengeMachineKey::Results::Create(encoded_response)));
  } else {
    Respond(Error(data));
  }
}

EnterprisePlatformKeysPrivateChallengeUserKeyFunction::
    EnterprisePlatformKeysPrivateChallengeUserKeyFunction()
    : default_impl_(new EPKPChallengeUserKey), impl_(default_impl_.get()) {}

EnterprisePlatformKeysPrivateChallengeUserKeyFunction::
    EnterprisePlatformKeysPrivateChallengeUserKeyFunction(
        EPKPChallengeUserKey* impl_for_testing)
    : impl_(impl_for_testing) {}

EnterprisePlatformKeysPrivateChallengeUserKeyFunction::
    ~EnterprisePlatformKeysPrivateChallengeUserKeyFunction() = default;

ExtensionFunction::ResponseAction
EnterprisePlatformKeysPrivateChallengeUserKeyFunction::Run() {
  std::unique_ptr<api_epkp::ChallengeUserKey::Params> params(
      api_epkp::ChallengeUserKey::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params);
  ChallengeKeyCallback callback = base::Bind(
      &EnterprisePlatformKeysPrivateChallengeUserKeyFunction::OnChallengedKey,
      this);
  // base::Unretained is safe on impl_ since its life-cycle matches |this| and
  // |callback| holds a reference to |this|.
  base::Closure task = base::Bind(
      &EPKPChallengeUserKey::DecodeAndRun, base::Unretained(impl_),
      scoped_refptr<UIThreadExtensionFunction>(AsUIThreadExtensionFunction()),
      callback, params->challenge, params->register_key);
  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, task);
  return RespondLater();
}

void EnterprisePlatformKeysPrivateChallengeUserKeyFunction::OnChallengedKey(
    bool success,
    const std::string& data) {
  if (success) {
    std::string encoded_response;
    base::Base64Encode(data, &encoded_response);
    Respond(ArgumentList(
        api_epkp::ChallengeUserKey::Results::Create(encoded_response)));
  } else {
    Respond(Error(data));
  }
}

}  // namespace extensions
