blob: 22760dd8720bd199222d28874203ee2053112006 [file] [log] [blame]
// Copyright 2022 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/ash/arc/net/cert_manager_impl.h"
#include <pk11priv.h>
#include <pk11pub.h>
#include <optional>
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_key_util.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/scoped_nss_types.h"
#include "net/cert/x509_util_nss.h"
#include "third_party/boringssl/src/pki/pem.h"
namespace {
void GetCertDBOnIOThread(
NssCertDatabaseGetter database_getter,
base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto split_callback = base::SplitOnceCallback(std::move(callback));
net::NSSCertDatabase* cert_db =
std::move(database_getter).Run(std::move(split_callback.first));
// If the NSS database was already available, |cert_db| is non-null and
// the callback has not been called. Explicitly call the callback.
if (cert_db) {
std::move(split_callback.second).Run(cert_db);
}
}
net::ScopedCERTCertificate TranslatePEMToCert(const std::string& cert_pem) {
bssl::PEMTokenizer tokenizer(cert_pem, {arc::kCertificatePEMHeader});
if (!tokenizer.GetNext()) {
NET_LOG(ERROR) << "Failed to get certificate data";
return nullptr;
}
std::vector<uint8_t> cert_der(tokenizer.data().begin(),
tokenizer.data().end());
return net::x509_util::CreateCERTCertificateFromBytes(cert_der);
}
// Check the status of delete operation for cert and key, then runs |callback|.
void DeleteCertAndKeyCallback(arc::CertManagerImpl::DeleteCertCallback callback,
bool status) {
if (!status) {
NET_LOG(ERROR) << "Failed to delete certificate";
}
// Run the callback regardless of the certificate and key deletion status.
std::move(callback).Run();
}
} // namespace
namespace arc {
CertManagerImpl::CertManagerImpl(Profile* profile) : profile_(profile) {}
CertManagerImpl::~CertManagerImpl() = default;
std::string CertManagerImpl::ImportPrivateKey(const std::string& key_pem,
net::NSSCertDatabase* database) {
if (!database) {
NET_LOG(ERROR) << "Certificate database is not initialized";
return std::string();
}
bssl::PEMTokenizer tokenizer(key_pem, {kPrivateKeyPEMHeader});
if (!tokenizer.GetNext()) {
NET_LOG(ERROR) << "Failed to get private key data";
return std::string();
}
std::vector<uint8_t> key_der(tokenizer.data().begin(),
tokenizer.data().end());
crypto::ScopedPK11Slot private_slot = database->GetPrivateSlot();
if (!private_slot) {
NET_LOG(ERROR) << "Failed to get PK11 slot";
return std::string();
}
crypto::ScopedSECKEYPrivateKey key(crypto::ImportNSSKeyFromPrivateKeyInfo(
private_slot.get(), key_der, true /* permanent */));
if (!key) {
NET_LOG(ERROR) << "Failed to import private key";
return std::string();
}
crypto::ScopedSECItem sec_item(PK11_GetLowLevelKeyIDForPrivateKey(key.get()));
if (!sec_item) {
NET_LOG(ERROR) << "Failed to get private key ID";
return std::string();
}
return base::HexEncode(sec_item->data, sec_item->len);
}
std::string CertManagerImpl::ImportUserCert(const std::string& cert_pem,
net::NSSCertDatabase* database) {
if (!database) {
NET_LOG(ERROR) << "Certificate database is not initialized";
return std::string();
}
net::ScopedCERTCertificate cert = TranslatePEMToCert(cert_pem);
if (!cert) {
NET_LOG(ERROR) << "Failed to translate PEM to certificate object";
return std::string();
}
int status = database->ImportUserCert(cert.get());
if (status != net::OK) {
NET_LOG(ERROR) << "Failed to import user certificate with status code "
<< status;
return std::string();
}
crypto::ScopedSECItem sec_item(
PK11_GetLowLevelKeyIDForCert(nullptr, cert.get(), nullptr));
if (!sec_item) {
NET_LOG(ERROR) << "Failed to get certificate ID";
return std::string();
}
return base::HexEncode(sec_item->data, sec_item->len);
}
void CertManagerImpl::DeleteCertAndKeyAsync(const std::string& cert_pem,
net::NSSCertDatabase* database,
DeleteCertCallback callback) {
if (!database) {
NET_LOG(ERROR) << "Certificate database is not initialized";
return;
}
net::ScopedCERTCertificate cert = TranslatePEMToCert(cert_pem);
if (!cert) {
NET_LOG(ERROR) << "Failed to translate PEM to certificate object";
return;
}
database->DeleteCertAndKeyAsync(
std::move(cert),
base::BindOnce(&DeleteCertAndKeyCallback, std::move(callback)));
}
int CertManagerImpl::GetSlotID(net::NSSCertDatabase* database) {
if (!database) {
NET_LOG(ERROR) << "Certificate database is not initialized";
return -1;
}
crypto::ScopedPK11Slot private_slot = database->GetPrivateSlot();
if (!private_slot) {
NET_LOG(ERROR) << "Failed to get PK11 slot";
return -1;
}
return PK11_GetSlotID(private_slot.get());
}
void CertManagerImpl::DeleteAndImportPrivateKeyAndCertWithDB(
const std::string& key_pem,
const std::string& cert_pem,
ImportPrivateKeyAndCertCallback callback,
net::NSSCertDatabase* database) {
// Attempt to delete the key and certificate first. This is important for an
// edge case below.
// 1. The user removes a set of passpoint credentials. This causes shill to
// delete the key and certificate from chaps.
// 2. The user re-adds passpoint credentials with the same key and
// certificate.
// If there is no Chrome restart between (1) and (2), NSS caches are not
// updated with the result of (1), making (2) fail.
// Deleting the key from NSS ensures that (2) succeeds even in this case.
DeleteCertAndKeyAsync(
cert_pem, database,
base::BindOnce(&CertManagerImpl::ImportPrivateKeyAndCertWithDB,
weak_factory_.GetWeakPtr(), key_pem, cert_pem,
std::move(callback), database));
}
void CertManagerImpl::ImportPrivateKeyAndCertWithDB(
const std::string& key_pem,
const std::string& cert_pem,
ImportPrivateKeyAndCertCallback callback,
net::NSSCertDatabase* database) {
std::string key_id = ImportPrivateKey(key_pem, database);
if (key_id.empty()) {
NET_LOG(ERROR) << "Failed to import private key";
std::move(callback).Run(/*cert_id=*/std::nullopt,
/*slot_id=*/std::nullopt);
return;
}
// Both DeleteCertAndKey parse the passed certificate into a CERTCertificate.
// This is unfortunate but reusing the same CERTCertificate resulted in
// PK11_GetLowLevelKeyIDForCert failing after the DeleteCertAndKey call.
std::string cert_id = ImportUserCert(cert_pem, database);
if (cert_id.empty()) {
NET_LOG(ERROR) << "Failed to import client certificate";
std::move(callback).Run(/*cert_id=*/std::nullopt,
/*slot_id=*/std::nullopt);
return;
}
int slot_id = GetSlotID(database);
// The ID of imported user certificate and private key is the same, use one
// of them.
DCHECK(key_id == cert_id);
std::move(callback).Run(cert_id, slot_id);
}
void CertManagerImpl::ImportPrivateKeyAndCert(
const std::string& key_pem,
const std::string& cert_pem,
ImportPrivateKeyAndCertCallback callback) {
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&GetCertDBOnIOThread,
NssServiceFactory::GetForContext(profile_)
->CreateNSSCertDatabaseGetterForIOThread(),
base::BindPostTaskToCurrentDefault(base::BindOnce(
&CertManagerImpl::DeleteAndImportPrivateKeyAndCertWithDB,
weak_factory_.GetWeakPtr(), key_pem, cert_pem,
std::move(callback)))));
}
} // namespace arc