blob: 4c8abe83ad93843af5f46da83d4123700e7df909 [file] [log] [blame]
// Copyright 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 "chromeos/network/onc/onc_certificate_importer_impl.h"
#include <cert.h>
#include <keyhi.h>
#include <pk11pub.h>
#include <stddef.h>
#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/onc/onc_parsed_certificates.h"
#include "chromeos/network/onc/onc_utils.h"
#include "crypto/scoped_nss_types.h"
#include "net/base/net_errors.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_util_nss.h"
namespace chromeos {
namespace onc {
CertificateImporterImpl::CertificateImporterImpl(
const scoped_refptr<base::SequencedTaskRunner>& io_task_runner,
net::NSSCertDatabase* target_nssdb)
: io_task_runner_(io_task_runner),
target_nssdb_(target_nssdb),
weak_factory_(this) {
CHECK(target_nssdb);
}
CertificateImporterImpl::~CertificateImporterImpl() = default;
void CertificateImporterImpl::ImportAllCertificatesUserInitiated(
const std::vector<OncParsedCertificates::ServerOrAuthorityCertificate>&
server_or_authority_certificates,
const std::vector<OncParsedCertificates::ClientCertificate>&
client_certificates,
DoneCallback done_callback) {
VLOG(2) << "Importing " << server_or_authority_certificates.size()
<< " server/authority certificates and " << client_certificates.size()
<< " client certificates";
RunTaskOnIOTaskRunnerAndCallDoneCallback(
base::BindOnce(&StoreAllCertificatesUserInitiated,
server_or_authority_certificates, client_certificates,
target_nssdb_),
std::move(done_callback));
}
void CertificateImporterImpl::ImportClientCertificates(
const std::vector<OncParsedCertificates::ClientCertificate>&
client_certificates,
DoneCallback done_callback) {
VLOG(2) << "Permanently importing " << client_certificates.size()
<< " client certificates";
RunTaskOnIOTaskRunnerAndCallDoneCallback(
base::BindOnce(&StoreClientCertificates, client_certificates,
target_nssdb_),
std::move(done_callback));
}
void CertificateImporterImpl::RunTaskOnIOTaskRunnerAndCallDoneCallback(
base::OnceCallback<bool()> task,
DoneCallback done_callback) {
// Thereforce, call back to |this|. This check of |this| must happen last and
// on the origin thread.
DoneCallback callback_to_this =
base::BindOnce(&CertificateImporterImpl::RunDoneCallback,
weak_factory_.GetWeakPtr(), std::move(done_callback));
// The NSSCertDatabase must be accessed on |io_task_runner_|
base::PostTaskAndReplyWithResult(io_task_runner_.get(), FROM_HERE,
std::move(task),
std::move(callback_to_this));
}
void CertificateImporterImpl::RunDoneCallback(DoneCallback callback,
bool success) {
if (!success)
NET_LOG_ERROR("ONC Certificate Import Error", "");
std::move(callback).Run(success);
}
// static
bool CertificateImporterImpl::StoreClientCertificates(
const std::vector<OncParsedCertificates::ClientCertificate>&
client_certificates,
net::NSSCertDatabase* nssdb) {
bool success = true;
for (const OncParsedCertificates::ClientCertificate& client_certificate :
client_certificates) {
if (!StoreClientCertificate(client_certificate, nssdb)) {
success = false;
} else {
VLOG(2) << "Successfully imported certificate with GUID "
<< client_certificate.guid();
}
}
return success;
}
// static
bool CertificateImporterImpl::StoreAllCertificatesUserInitiated(
const std::vector<OncParsedCertificates::ServerOrAuthorityCertificate>&
server_or_authority_certificates,
const std::vector<OncParsedCertificates::ClientCertificate>&
client_certificates,
net::NSSCertDatabase* nssdb) {
bool success = true;
for (const OncParsedCertificates::ServerOrAuthorityCertificate&
server_or_authority_cert : server_or_authority_certificates) {
if (!StoreServerOrCaCertificateUserInitiated(server_or_authority_cert,
nssdb)) {
success = false;
} else {
VLOG(2) << "Successfully imported certificate with GUID "
<< server_or_authority_cert.guid();
}
}
if (!StoreClientCertificates(client_certificates, nssdb))
success = false;
return success;
}
// static
bool CertificateImporterImpl::StoreServerOrCaCertificateUserInitiated(
const OncParsedCertificates::ServerOrAuthorityCertificate& certificate,
net::NSSCertDatabase* nssdb) {
net::ScopedCERTCertificate x509_cert =
net::x509_util::CreateCERTCertificateFromX509Certificate(
certificate.certificate().get());
if (!x509_cert.get()) {
LOG(ERROR) << "Unable to create certificate: " << certificate.guid();
return false;
}
// Permanent web trust is granted to certificates imported by the user - and
// StoreServerOrCaCertificateUserInitiated is only used if the user initiated
// the import.
net::NSSCertDatabase::TrustBits trust =
(certificate.web_trust_requested() ? net::NSSCertDatabase::TRUSTED_SSL
: net::NSSCertDatabase::TRUST_DEFAULT);
if (x509_cert.get()->isperm) {
net::CertType net_cert_type =
certificate.type() == OncParsedCertificates::
ServerOrAuthorityCertificate::Type::kServer
? net::SERVER_CERT
: net::CA_CERT;
VLOG(1) << "Certificate is already installed.";
net::NSSCertDatabase::TrustBits missing_trust_bits =
trust & ~nssdb->GetCertTrust(x509_cert.get(), net_cert_type);
if (missing_trust_bits) {
std::string error_reason;
bool success = false;
if (nssdb->IsReadOnly(x509_cert.get())) {
error_reason = " Certificate is stored read-only.";
} else {
success = nssdb->SetCertTrust(x509_cert.get(), net_cert_type, trust);
}
if (!success) {
LOG(ERROR) << "Certificate " << certificate.guid()
<< " was already present, but trust couldn't be set."
<< error_reason;
}
}
} else {
net::ScopedCERTCertificateList cert_list;
cert_list.push_back(net::x509_util::DupCERTCertificate(x509_cert.get()));
net::NSSCertDatabase::ImportCertFailureList failures;
bool success = false;
if (certificate.type() ==
OncParsedCertificates::ServerOrAuthorityCertificate::Type::kServer)
success = nssdb->ImportServerCert(cert_list, trust, &failures);
else // Authority cert
success = nssdb->ImportCACerts(cert_list, trust, &failures);
if (!failures.empty()) {
std::string error_string = net::ErrorToString(failures[0].net_error);
LOG(ERROR) << "Error ( " << error_string << " ) importing certificate "
<< certificate.guid();
return false;
}
if (!success) {
LOG(ERROR) << "Unknown error importing certificate "
<< certificate.guid();
return false;
}
}
return true;
}
// static
bool CertificateImporterImpl::StoreClientCertificate(
const OncParsedCertificates::ClientCertificate& certificate,
net::NSSCertDatabase* nssdb) {
// Since this has a private key, always use the private module.
crypto::ScopedPK11Slot private_slot(nssdb->GetPrivateSlot());
if (!private_slot)
return false;
net::ScopedCERTCertificateList imported_certs;
int import_result =
nssdb->ImportFromPKCS12(private_slot.get(), certificate.pkcs12_data(),
base::string16(), false, &imported_certs);
if (import_result != net::OK) {
std::string error_string = net::ErrorToString(import_result);
LOG(ERROR) << "Unable to import client certificate with guid "
<< certificate.guid() << ", error: " << error_string;
return false;
}
if (imported_certs.size() == 0) {
LOG(WARNING) << "PKCS12 data contains no importable certificates for guid "
<< certificate.guid();
return true;
}
if (imported_certs.size() != 1) {
LOG(WARNING) << "PKCS12 data for guid " << certificate.guid()
<< " contains more than one certificate. "
"Only the first one will be imported.";
}
CERTCertificate* cert_result = imported_certs[0].get();
// Find the private key associated with this certificate, and set the
// nickname on it. This is used by |ClientCertResolver| as a handle to resolve
// onc ClientCertRef GUID references.
SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
cert_result->slot, cert_result, nullptr /* wincx */);
if (private_key) {
PK11_SetPrivateKeyNickname(private_key,
const_cast<char*>(certificate.guid().c_str()));
SECKEY_DestroyPrivateKey(private_key);
} else {
LOG(WARNING) << "Unable to find private key for certificate "
<< certificate.guid();
}
return true;
}
} // namespace onc
} // namespace chromeos