blob: 0d67501d5df27018fd3bc9bc7a99f98c9ea3d190 [file] [log] [blame]
// 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 "base/base64.h"
#include "base/callback.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.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/enterprise_install_attributes.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/policy/browser_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/signin_manager.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_constants.h"
#include "chromeos/attestation/attestation_flow.h"
#include "chromeos/cryptohome/async_method_caller.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/user_prefs/pref_registry_syncable.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 std::string& user_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),
user_id(user_id),
key_name(key_name),
certificate_profile(certificate_profile),
require_user_consent(require_user_consent),
callback(callback) {
}
EPKPChallengeKeyBase::PrepareKeyContext::~PrepareKeyContext() {
}
EPKPChallengeKeyBase::EPKPChallengeKeyBase()
: cryptohome_client_(
chromeos::DBusThreadManager::Get()->GetCryptohomeClient()),
async_caller_(cryptohome::AsyncMethodCaller::GetInstance()),
install_attributes_(g_browser_process->browser_policy_connector()->
GetInstallAttributes()) {
scoped_ptr<chromeos::attestation::ServerProxy> ca_client(
new chromeos::attestation::AttestationCAClient());
default_attestation_flow_.reset(
new chromeos::attestation::AttestationFlow(
async_caller_, cryptohome_client_, ca_client.Pass()));
attestation_flow_ = default_attestation_flow_.get();
}
EPKPChallengeKeyBase::EPKPChallengeKeyBase(
chromeos::CryptohomeClient* cryptohome_client,
cryptohome::AsyncMethodCaller* async_caller,
chromeos::attestation::AttestationFlow* attestation_flow,
policy::EnterpriseInstallAttributes* 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, 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_->IsEnterpriseDevice();
}
bool EPKPChallengeKeyBase::IsExtensionWhitelisted() const {
const base::ListValue* list =
GetProfile()->GetPrefs()->GetList(prefs::kAttestationExtensionWhitelist);
StringValue value(extension_->id());
return list->Find(value) != list->end();
}
bool EPKPChallengeKeyBase::IsUserManaged() const {
std::string email = GetUserEmail();
// TODO(davidyu): Use BrowserPolicyConnector::GetUserAffiliation() and fix
// the test.
return email.empty() ? false :
gaia::ExtractDomainName(email) == GetEnterpriseDomain();
}
std::string EPKPChallengeKeyBase::GetEnterpriseDomain() const {
return install_attributes_->GetDomain();
}
std::string EPKPChallengeKeyBase::GetUserEmail() const {
SigninManagerBase* signin_manager =
SigninManagerFactory::GetForProfile(GetProfile());
if (!signin_manager)
return std::string();
return gaia::CanonicalizeEmail(signin_manager->GetAuthenticatedUsername());
}
std::string EPKPChallengeKeyBase::GetDeviceId() const {
return install_attributes_->GetDeviceId();
}
void EPKPChallengeKeyBase::PrepareKey(
chromeos::attestation::AttestationKeyType key_type,
const std::string& user_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,
user_id,
key_name,
certificate_profile,
require_user_consent,
callback);
cryptohome_client_->TpmAttestationIsPrepared(base::Bind(
&EPKPChallengeKeyBase::IsAttestationPreparedCallback, this, context));
}
void EPKPChallengeKeyBase::IsAttestationPreparedCallback(
const PrepareKeyContext& context,
chromeos::DBusMethodCallStatus status,
bool result) {
if (status == chromeos::DBUS_METHOD_CALL_FAILURE) {
context.callback.Run(PREPARE_KEY_DBUS_ERROR);
return;
}
if (!result) {
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, context.user_id, context.key_name, base::Bind(
&EPKPChallengeKeyBase::DoesKeyExistCallback, this, context));
}
void EPKPChallengeKeyBase::DoesKeyExistCallback(
const PrepareKeyContext& context,
chromeos::DBusMethodCallStatus status,
bool result) {
if (status == chromeos::DBUS_METHOD_CALL_FAILURE) {
context.callback.Run(PREPARE_KEY_DBUS_ERROR);
return;
}
if (result) {
// 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, 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.user_id,
std::string(), // Not used.
true, // Force a new key to be generated.
base::Bind(&EPKPChallengeKeyBase::GetCertificateCallback, this,
context.callback));
}
void EPKPChallengeKeyBase::GetCertificateCallback(
const base::Callback<void(PrepareKeyResult)>& callback,
bool success,
const std::string& pem_certificate_chain) {
if (!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::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,
policy::EnterpriseInstallAttributes* install_attributes) :
EPKPChallengeKeyBase(cryptohome_client,
async_caller,
attestation_flow,
install_attributes) {
}
EPKPChallengeMachineKey::~EPKPChallengeMachineKey() {
}
bool EPKPChallengeMachineKey::RunImpl() {
scoped_ptr<api_epkp::ChallengeMachineKey::Params>
params(api_epkp::ChallengeMachineKey::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
std::string challenge;
if (!base::Base64Decode(params->challenge, &challenge)) {
SetError(kChallengeBadBase64Error);
return false;
}
// Check if the device is enterprise enrolled.
if (!IsEnterpriseDevice()) {
SetError(kNonEnterpriseDeviceError);
return false;
}
// Check if the extension is whitelisted in the user policy.
if (!IsExtensionWhitelisted()) {
SetError(kExtensionNotWhitelistedError);
return false;
}
// Check if the user domain is the same as the enrolled enterprise domain.
if (!IsUserManaged()) {
SetError(kUserNotManaged);
return false;
}
// Check if RA is enabled in the device policy.
GetDeviceAttestationEnabled(
base::Bind(&EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback,
this, challenge));
return true;
}
void EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback(
const std::string& challenge, bool enabled) {
if (!enabled) {
SetError(kDevicePolicyDisabledError);
SendResponse(false);
return;
}
PrepareKey(chromeos::attestation::KEY_DEVICE,
std::string(), // Not used.
kKeyName,
chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
false, // user consent is not required.
base::Bind(&EPKPChallengeMachineKey::PrepareKeyCallback, this,
challenge));
}
void EPKPChallengeMachineKey::PrepareKeyCallback(
const std::string& challenge, PrepareKeyResult result) {
if (result != PREPARE_KEY_OK) {
SetError(base::StringPrintf(kGetCertificateFailedError, result));
SendResponse(false);
return;
}
// Everything is checked. Sign the challenge.
async_caller_->TpmAttestationSignEnterpriseChallenge(
chromeos::attestation::KEY_DEVICE,
std::string(), // Not used.
kKeyName,
GetEnterpriseDomain(),
GetDeviceId(),
chromeos::attestation::CHALLENGE_OPTION_NONE,
challenge,
base::Bind(&EPKPChallengeMachineKey::SignChallengeCallback, this));
}
void EPKPChallengeMachineKey::SignChallengeCallback(
bool success, const std::string& response) {
if (!success) {
SetError(kSignChallengeFailedError);
SendResponse(false);
return;
}
std::string encoded_response;
base::Base64Encode(response, &encoded_response);
results_ = api_epkp::ChallengeMachineKey::Results::Create(encoded_response);
SendResponse(true);
}
// 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::kKeyName[] = "attest-ent-user";
EPKPChallengeUserKey::EPKPChallengeUserKey() : EPKPChallengeKeyBase() {
}
EPKPChallengeUserKey::EPKPChallengeUserKey(
chromeos::CryptohomeClient* cryptohome_client,
cryptohome::AsyncMethodCaller* async_caller,
chromeos::attestation::AttestationFlow* attestation_flow,
policy::EnterpriseInstallAttributes* 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,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterListPref(prefs::kAttestationExtensionWhitelist,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
bool EPKPChallengeUserKey::RunImpl() {
scoped_ptr<api_epkp::ChallengeUserKey::Params> params(
api_epkp::ChallengeUserKey::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
std::string challenge;
if (!base::Base64Decode(params->challenge, &challenge)) {
SetError(kChallengeBadBase64Error);
return false;
}
// Check if RA is enabled in the user policy.
if (!IsRemoteAttestationEnabledForUser()) {
SetError(kUserPolicyDisabledError);
return false;
}
// Check if the extension is whitelisted in the user policy.
if (!IsExtensionWhitelisted()) {
SetError(kExtensionNotWhitelistedError);
return false;
}
if (IsEnterpriseDevice()) {
// Check if the user domain is the same as the enrolled enterprise domain.
if (!IsUserManaged()) {
SetError(kUserNotManaged);
return false;
}
// Check if RA is enabled in the device policy.
GetDeviceAttestationEnabled(
base::Bind(&EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback,
this,
challenge,
params->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,
params->register_key,
true, // user consent is required.
true); // attestation is enabled.
}
return true;
}
void EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback(
const std::string& challenge,
bool register_key,
bool require_user_consent,
bool enabled) {
if (!enabled) {
SetError(kDevicePolicyDisabledError);
SendResponse(false);
return;
}
PrepareKey(chromeos::attestation::KEY_USER,
GetUserEmail(),
kKeyName,
chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE,
require_user_consent,
base::Bind(&EPKPChallengeUserKey::PrepareKeyCallback, this,
challenge, register_key));
}
void EPKPChallengeUserKey::PrepareKeyCallback(const std::string& challenge,
bool register_key,
PrepareKeyResult result) {
if (result != PREPARE_KEY_OK) {
SetError(base::StringPrintf(kGetCertificateFailedError, result));
SendResponse(false);
return;
}
// Everything is checked. Sign the challenge.
async_caller_->TpmAttestationSignEnterpriseChallenge(
chromeos::attestation::KEY_USER,
GetUserEmail(),
kKeyName,
GetUserEmail(),
GetDeviceId(),
register_key ?
chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY :
chromeos::attestation::CHALLENGE_OPTION_NONE,
challenge,
base::Bind(&EPKPChallengeUserKey::SignChallengeCallback, this,
register_key));
}
void EPKPChallengeUserKey::SignChallengeCallback(bool register_key,
bool success,
const std::string& response) {
if (!success) {
SetError(kSignChallengeFailedError);
SendResponse(false);
return;
}
if (register_key) {
async_caller_->TpmAttestationRegisterKey(
chromeos::attestation::KEY_USER,
GetUserEmail(),
kKeyName,
base::Bind(&EPKPChallengeUserKey::RegisterKeyCallback, 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) {
SetError(kKeyRegistrationFailedError);
SendResponse(false);
return;
}
std::string encoded_response;
base::Base64Encode(response, &encoded_response);
results_ = api_epkp::ChallengeUserKey::Results::Create(encoded_response);
SendResponse(true);
}
bool EPKPChallengeUserKey::IsRemoteAttestationEnabledForUser() const {
return GetProfile()->GetPrefs()->GetBoolean(prefs::kAttestationEnabled);
}
} // namespace extensions