blob: 963ba0dd5661a092a607e0b25bceb5c96207c5c7 [file] [log] [blame]
// Copyright 2020 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/ash/arc/enterprise/cert_store/cert_store_service.h"
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/arc/enterprise/cert_store/arc_cert_installer_utils.h"
#include "chrome/browser/ash/arc/keymaster/arc_keymaster_bridge.h"
#include "chrome/browser/ash/arc/policy/arc_policy_bridge.h"
#include "chrome/browser/ash/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_service_factory.h"
#include "chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_service_impl.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service_factory.h"
#include "chrome/browser/net/nss_context.h"
#include "chrome/browser/platform_keys/platform_keys.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/net/x509_certificate_model_nss.h"
#include "chrome/services/keymaster/public/mojom/cert_store.mojom.h"
#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "content/public/browser/browser_context.h"
#include "crypto/rsa_private_key.h"
#include "net/cert/x509_util_nss.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace arc {
namespace {
// Singleton factory for CertStoreService.
class CertStoreServiceFactory : public BrowserContextKeyedServiceFactory {
public:
static CertStoreService* GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<CertStoreService*>(
GetInstance()->GetServiceForBrowserContext(context, true));
}
static CertStoreServiceFactory* GetInstance() {
return base::Singleton<CertStoreServiceFactory>::get();
}
CertStoreServiceFactory(const CertStoreServiceFactory&) = delete;
CertStoreServiceFactory& operator=(const CertStoreServiceFactory&) = delete;
private:
friend base::DefaultSingletonTraits<CertStoreServiceFactory>;
CertStoreServiceFactory()
: BrowserContextKeyedServiceFactory(
"CertStoreService",
BrowserContextDependencyManager::GetInstance()) {}
// BrowserContextKeyedServiceFactory overrides:
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override {
return context;
}
bool ServiceIsNULLWhileTesting() const override { return true; }
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override {
return new CertStoreService(context);
}
};
using IsCertificateAllowedCallback = base::OnceCallback<void(bool allowed)>;
void CheckCorporateFlag(
IsCertificateAllowedCallback callback,
absl::optional<bool> corporate_key,
chromeos::platform_keys::Status is_corporate_key_status) {
if (is_corporate_key_status != chromeos::platform_keys::Status::kSuccess) {
LOG(ERROR) << "Error checking whether key is corporate. Will not install "
"key in ARC";
std::move(callback).Run(/* allowed */ false);
return;
}
DCHECK(corporate_key.has_value());
std::move(callback).Run(/* allowed */ corporate_key.value());
}
void CheckKeyLocationAndCorporateFlag(
IsCertificateAllowedCallback callback,
const std::string& public_key_spki_der,
content::BrowserContext* const context,
absl::optional<bool> key_on_user_token,
chromeos::platform_keys::Status is_key_on_token_status) {
if (is_key_on_token_status != chromeos::platform_keys::Status::kSuccess) {
LOG(WARNING) << "Error while checking key location: "
<< chromeos::platform_keys::StatusToString(
is_key_on_token_status);
std::move(callback).Run(/* allowed */ false);
return;
}
DCHECK(key_on_user_token.has_value());
if (!key_on_user_token.value_or(false)) {
std::move(callback).Run(/* allowed */ false);
return;
}
// Check if the key is marked for corporate usage.
chromeos::platform_keys::KeyPermissionsServiceFactory::GetForBrowserContext(
context)
->IsCorporateKey(
public_key_spki_der,
base::BindOnce(&CheckCorporateFlag, std::move(callback)));
}
// Returns true if the certificate is allowed to be used by ARC. The certificate
// is allowed to be used by ARC if its key is marked for corporate usage and
// resides on a user token. |cert| must be non-null.
void IsCertificateAllowed(IsCertificateAllowedCallback callback,
scoped_refptr<net::X509Certificate> cert,
content::BrowserContext* const context) {
DCHECK(cert);
const std::string public_key_spki_der =
chromeos::platform_keys::GetSubjectPublicKeyInfo(cert);
// Check if the key is on the user token.
chromeos::platform_keys::PlatformKeysServiceFactory::GetForBrowserContext(
context)
->IsKeyOnToken(
chromeos::platform_keys::TokenId::kUser, public_key_spki_der,
base::BindOnce(&CheckKeyLocationAndCorporateFlag, std::move(callback),
public_key_spki_der, context));
}
std::vector<CertDescription> PrepareCertDescriptions(
net::ScopedCERTCertificateList nss_certs) {
std::vector<CertDescription> certificates;
for (auto& nss_cert : nss_certs) {
if (!nss_cert)
continue;
// Generate the placeholder RSA key that will be installed in ARC.
auto placeholder_key = crypto::RSAPrivateKey::Create(2048);
DCHECK(placeholder_key);
certificates.emplace_back(placeholder_key.release(), nss_cert.release());
}
return certificates;
}
std::vector<keymaster::mojom::ChromeOsKeyPtr> PrepareChromeOsKeys(
const std::vector<CertDescription>& certificates) {
std::vector<keymaster::mojom::ChromeOsKeyPtr> chrome_os_keys;
for (const auto& certificate : certificates) {
CERTCertificate* nss_cert = certificate.nss_cert.get();
DCHECK(nss_cert);
// Fetch PKCS#11 CKA_LABEL.
SECKEYPrivateKey* priv_key =
PK11_FindKeyByAnyCert(nss_cert, nullptr /* wincx */);
if (!priv_key)
continue;
crypto::ScopedSECKEYPrivateKey priv_key_destroyer(priv_key);
char* nickname = PK11_GetPrivateKeyNickname(priv_key);
if (!nickname)
continue;
std::string pkcs11_label(nickname);
// Fetch PKCS#11 CKA_ID.
SECItem* id_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
if (!id_item)
continue;
crypto::ScopedSECItem sec_item_destroyer(id_item);
std::string pkcs11_id(id_item->data, id_item->data + id_item->len);
// Build a mojo ChromeOsKey and store it in the output vector.
keymaster::mojom::ChapsKeyDataPtr key_data =
keymaster::mojom::ChapsKeyData::New(pkcs11_label, pkcs11_id);
keymaster::mojom::ChromeOsKeyPtr key = keymaster::mojom::ChromeOsKey::New(
ExportSpki(certificate.placeholder_key.get()),
keymaster::mojom::KeyData::NewChapsKeyData(std::move(key_data)));
chrome_os_keys.push_back(std::move(key));
}
return chrome_os_keys;
}
} // namespace
// static
CertStoreService* CertStoreService::GetForBrowserContext(
content::BrowserContext* context) {
return CertStoreServiceFactory::GetForBrowserContext(context);
}
// static
BrowserContextKeyedServiceFactory* CertStoreService::GetFactory() {
return CertStoreServiceFactory::GetInstance();
}
CertStoreService::CertStoreService(content::BrowserContext* context)
: CertStoreService(context, std::make_unique<ArcCertInstaller>(context)) {}
CertStoreService::CertStoreService(content::BrowserContext* context,
std::unique_ptr<ArcCertInstaller> installer)
: context_(context), installer_(std::move(installer)) {
// Do not perform any actions if context is nullptr for unit tests.
if (!context_)
return;
net::CertDatabase::GetInstance()->AddObserver(this);
UpdateCertificates();
}
CertStoreService::~CertStoreService() {
if (context_)
net::CertDatabase::GetInstance()->RemoveObserver(this);
}
void CertStoreService::OnCertDBChanged() {
UpdateCertificates();
}
absl::optional<CertStoreService::KeyInfo>
CertStoreService::GetKeyInfoForDummySpki(const std::string& dummy_spki) {
return certificate_cache_.GetKeyInfoForDummySpki(dummy_spki);
}
void CertStoreService::UpdateCertificates() {
GetNSSCertDatabaseForProfile(
Profile::FromBrowserContext(context_),
base::BindOnce(&CertStoreService::OnGetNSSCertDatabaseForProfile,
weak_ptr_factory_.GetWeakPtr()));
}
void CertStoreService::FilterAllowedCertificatesRecursively(
FilterAllowedCertificatesCallback callback,
base::queue<net::ScopedCERTCertificate> cert_queue,
net::ScopedCERTCertificateList allowed_certs) const {
if (cert_queue.empty()) {
std::move(callback).Run(std::move(allowed_certs));
return;
}
net::ScopedCERTCertificate cert = std::move(cert_queue.front());
cert_queue.pop();
scoped_refptr<net::X509Certificate> x509_cert =
net::x509_util::CreateX509CertificateFromCERTCertificate(cert.get());
if (!x509_cert) {
FilterAllowedCertificatesRecursively(
std::move(callback), std::move(cert_queue), std::move(allowed_certs));
return;
}
IsCertificateAllowed(
base::BindOnce(&CertStoreService::FilterAllowedCertificateAndRecurse,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(cert_queue), std::move(allowed_certs),
std::move(cert)),
std::move(x509_cert), context_);
}
void CertStoreService::FilterAllowedCertificateAndRecurse(
FilterAllowedCertificatesCallback callback,
base::queue<net::ScopedCERTCertificate> cert_queue,
net::ScopedCERTCertificateList allowed_certs,
net::ScopedCERTCertificate cert,
bool certificate_allowed) const {
if (certificate_allowed)
allowed_certs.push_back(std::move(cert));
FilterAllowedCertificatesRecursively(
std::move(callback), std::move(cert_queue), std::move(allowed_certs));
}
void CertStoreService::OnGetNSSCertDatabaseForProfile(
net::NSSCertDatabase* database) {
DCHECK(database);
database->ListCertsInSlot(
base::BindOnce(&CertStoreService::OnCertificatesListed,
weak_ptr_factory_.GetWeakPtr()),
database->GetPrivateSlot().get());
}
void CertStoreService::OnCertificatesListed(
net::ScopedCERTCertificateList cert_list) {
base::queue<net::ScopedCERTCertificate> cert_queue;
for (auto& cert : cert_list) {
cert_queue.push(std::move(cert));
}
net::ScopedCERTCertificateList allowed_certs;
FilterAllowedCertificatesRecursively(
base::BindOnce(&CertStoreService::OnFilteredAllowedCertificates,
weak_ptr_factory_.GetWeakPtr()),
std::move(cert_queue), std::move(allowed_certs));
}
void CertStoreService::OnFilteredAllowedCertificates(
net::ScopedCERTCertificateList allowed_certs) {
ArcKeymasterBridge* const keymaster_bridge =
ArcKeymasterBridge::GetForBrowserContext(context_);
if (!keymaster_bridge) {
LOG(ERROR) << "Missing instance of ArcKeymasterBridge.";
return;
}
std::vector<CertDescription> certificates =
PrepareCertDescriptions(std::move(allowed_certs));
std::vector<keymaster::mojom::ChromeOsKeyPtr> keys =
PrepareChromeOsKeys(certificates);
keymaster_bridge->UpdatePlaceholderKeys(
std::move(keys),
base::BindOnce(&CertStoreService::OnUpdatedKeymasterKeys,
weak_ptr_factory_.GetWeakPtr(), std::move(certificates)));
}
void CertStoreService::OnUpdatedKeymasterKeys(
std::vector<CertDescription> certificate_descriptions,
bool success) {
if (!success) {
LOG(WARNING) << "Could not update placeholder keys with keymaster.";
return;
}
certificate_cache_.clear_need_policy_update();
certificate_cache_.Update(certificate_descriptions);
// Maps cert name to dummy SPKI.
std::map<std::string, std::string> installed_keys =
installer_->InstallArcCerts(
std::move(certificate_descriptions),
base::BindOnce(&CertStoreService::OnArcCertsInstalled,
weak_ptr_factory_.GetWeakPtr()));
certificate_cache_.Update(installed_keys);
}
CertStoreService::CertificateCache::CertificateCache() = default;
CertStoreService::CertificateCache::~CertificateCache() = default;
void CertStoreService::CertificateCache::Update(
const std::vector<CertDescription>& certificates) {
// Map cert name to real SPKI.
key_info_by_name_cache_.clear();
std::set<std::string> new_required_cert_names;
for (const auto& certificate : certificates) {
CERTCertificate* nss_cert = certificate.nss_cert.get();
DCHECK(nss_cert);
// Fetch certificate name.
std::string cert_name =
x509_certificate_model::GetCertNameOrNickname(nss_cert);
// Fetch PKCS#11 CKA_ID.
SECKEYPrivateKey* priv_key =
PK11_FindKeyByAnyCert(nss_cert, nullptr /* wincx */);
if (!priv_key)
continue;
crypto::ScopedSECKEYPrivateKey priv_key_destroyer(priv_key);
SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
std::string pkcs11_id;
if (sec_item) {
pkcs11_id = std::string(sec_item->data, sec_item->data + sec_item->len);
SECITEM_FreeItem(sec_item, PR_TRUE);
}
key_info_by_name_cache_[cert_name] = {cert_name, pkcs11_id};
new_required_cert_names.insert(cert_name);
}
need_policy_update_ = (required_cert_names_ != new_required_cert_names);
for (auto cert_name : required_cert_names_) {
if (!new_required_cert_names.count(cert_name)) {
key_info_by_dummy_spki_cache_.erase(dummy_spki_by_name_cache_[cert_name]);
dummy_spki_by_name_cache_.erase(cert_name);
}
}
required_cert_names_ = new_required_cert_names;
}
void CertStoreService::CertificateCache::Update(
std::map<std::string, std::string> dummy_spki_by_name) {
if (required_cert_names_.size() != dummy_spki_by_name.size())
return;
for (const auto& cert : dummy_spki_by_name) {
const std::string& name = cert.first;
if (!required_cert_names_.count(name)) {
VLOG(1) << "An attempt to add a non-required key " << name;
continue;
}
std::string dummy_spki = cert.second;
if (dummy_spki.empty() && dummy_spki_by_name_cache_.count(name))
dummy_spki = dummy_spki_by_name_cache_[name];
if (!dummy_spki.empty()) {
dummy_spki_by_name_cache_[name] = dummy_spki;
key_info_by_dummy_spki_cache_[dummy_spki] = key_info_by_name_cache_[name];
}
}
}
void CertStoreService::OnArcCertsInstalled(bool success) {
VLOG(1) << "ARC certificates installation has finished with result="
<< success;
if (certificate_cache_.need_policy_update()) {
ArcPolicyBridge* const policy_bridge =
ArcPolicyBridge::GetForBrowserContext(context_);
if (policy_bridge) {
policy_bridge->OnPolicyUpdated(policy::PolicyNamespace(),
policy::PolicyMap(), policy::PolicyMap());
}
}
}
absl::optional<CertStoreService::KeyInfo>
CertStoreService::CertificateCache::GetKeyInfoForDummySpki(
const std::string& dummy_spki) {
if (key_info_by_dummy_spki_cache_.count(dummy_spki))
return key_info_by_dummy_spki_cache_[dummy_spki];
return absl::nullopt;
}
} // namespace arc