// Copyright (c) 2012 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 "net/cert/nss_cert_database.h"
#include <cert.h>
#include <certdb.h>
#include <keyhi.h>
#include <pk11pub.h>
#include <secmod.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/observer_list_threadsafe.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/worker_pool.h"
#include "crypto/scoped_nss_types.h"
#include "net/base/crypto_module.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_database.h"
#include "net/cert/x509_certificate.h"
#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
#include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
// In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use
// the new name of the macro.
#if !defined(CERTDB_TERMINAL_RECORD)
#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER
#endif
// PSM = Mozilla's Personal Security Manager.
namespace psm = mozilla_security_manager;
namespace net {
namespace {
// TODO(pneubeck): Move this class out of NSSCertDatabase and to the caller of
// the c'tor of NSSCertDatabase, see https://crbug.com/395983 .
// Helper that observes events from the NSSCertDatabase and forwards them to
// the given CertDatabase.
class CertNotificationForwarder : public NSSCertDatabase::Observer {
public:
explicit CertNotificationForwarder(CertDatabase* cert_db)
: cert_db_(cert_db) {}
virtual ~CertNotificationForwarder() {}
// NSSCertDatabase::Observer implementation:
virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE {
cert_db_->NotifyObserversOfCertAdded(cert);
}
virtual void OnCertRemoved(const X509Certificate* cert) OVERRIDE {
cert_db_->NotifyObserversOfCertRemoved(cert);
}
virtual void OnCACertChanged(const X509Certificate* cert) OVERRIDE {
cert_db_->NotifyObserversOfCACertChanged(cert);
}
private:
CertDatabase* cert_db_;
DISALLOW_COPY_AND_ASSIGN(CertNotificationForwarder);
};
} // namespace
NSSCertDatabase::ImportCertFailure::ImportCertFailure(
const scoped_refptr<X509Certificate>& cert,
int err)
: certificate(cert), net_error(err) {}
NSSCertDatabase::ImportCertFailure::~ImportCertFailure() {}
NSSCertDatabase::NSSCertDatabase(crypto::ScopedPK11Slot public_slot,
crypto::ScopedPK11Slot private_slot)
: public_slot_(public_slot.Pass()),
private_slot_(private_slot.Pass()),
observer_list_(new ObserverListThreadSafe<Observer>),
weak_factory_(this) {
CHECK(public_slot_);
// This also makes sure that NSS has been initialized.
CertDatabase* cert_db = CertDatabase::GetInstance();
cert_notification_forwarder_.reset(new CertNotificationForwarder(cert_db));
AddObserver(cert_notification_forwarder_.get());
psm::EnsurePKCS12Init();
}
NSSCertDatabase::~NSSCertDatabase() {}
void NSSCertDatabase::ListCertsSync(CertificateList* certs) {
ListCertsImpl(crypto::ScopedPK11Slot(), certs);
}
void NSSCertDatabase::ListCerts(
const base::Callback<void(scoped_ptr<CertificateList> certs)>& callback) {
scoped_ptr<CertificateList> certs(new CertificateList());
// base::Passed will NULL out |certs|, so cache the underlying pointer here.
CertificateList* raw_certs = certs.get();
GetSlowTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&NSSCertDatabase::ListCertsImpl,
base::Passed(crypto::ScopedPK11Slot()),
base::Unretained(raw_certs)),
base::Bind(callback, base::Passed(&certs)));
}
void NSSCertDatabase::ListCertsInSlot(const ListCertsCallback& callback,
PK11SlotInfo* slot) {
DCHECK(slot);
scoped_ptr<CertificateList> certs(new CertificateList());
// base::Passed will NULL out |certs|, so cache the underlying pointer here.
CertificateList* raw_certs = certs.get();
GetSlowTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&NSSCertDatabase::ListCertsImpl,
base::Passed(crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot))),
base::Unretained(raw_certs)),
base::Bind(callback, base::Passed(&certs)));
}
#if defined(OS_CHROMEOS)
crypto::ScopedPK11Slot NSSCertDatabase::GetSystemSlot() const {
return crypto::ScopedPK11Slot();
}
#endif
crypto::ScopedPK11Slot NSSCertDatabase::GetPublicSlot() const {
return crypto::ScopedPK11Slot(PK11_ReferenceSlot(public_slot_.get()));
}
crypto::ScopedPK11Slot NSSCertDatabase::GetPrivateSlot() const {
if (!private_slot_)
return crypto::ScopedPK11Slot();
return crypto::ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()));
}
CryptoModule* NSSCertDatabase::GetPublicModule() const {
crypto::ScopedPK11Slot slot(GetPublicSlot());
return CryptoModule::CreateFromHandle(slot.get());
}
CryptoModule* NSSCertDatabase::GetPrivateModule() const {
crypto::ScopedPK11Slot slot(GetPrivateSlot());
return CryptoModule::CreateFromHandle(slot.get());
}
void NSSCertDatabase::ListModules(CryptoModuleList* modules,
bool need_rw) const {
modules->clear();
// The wincx arg is unused since we don't call PK11_SetIsLoggedInFunc.
crypto::ScopedPK11SlotList slot_list(
PK11_GetAllTokens(CKM_INVALID_MECHANISM,
need_rw ? PR_TRUE : PR_FALSE, // needRW
PR_TRUE, // loadCerts (unused)
NULL)); // wincx
if (!slot_list) {
LOG(ERROR) << "PK11_GetAllTokens failed: " << PORT_GetError();
return;
}
PK11SlotListElement* slot_element = PK11_GetFirstSafe(slot_list.get());
while (slot_element) {
modules->push_back(CryptoModule::CreateFromHandle(slot_element->slot));
slot_element = PK11_GetNextSafe(slot_list.get(), slot_element,
PR_FALSE); // restart
}
}
int NSSCertDatabase::ImportFromPKCS12(
CryptoModule* module,
const std::string& data,
const base::string16& password,
bool is_extractable,
net::CertificateList* imported_certs) {
DVLOG(1) << __func__ << " "
<< PK11_GetModuleID(module->os_module_handle()) << ":"
<< PK11_GetSlotID(module->os_module_handle());
int result = psm::nsPKCS12Blob_Import(module->os_module_handle(),
data.data(), data.size(),
password,
is_extractable,
imported_certs);
if (result == net::OK)
NotifyObserversOfCertAdded(NULL);
return result;
}
int NSSCertDatabase::ExportToPKCS12(
const CertificateList& certs,
const base::string16& password,
std::string* output) const {
return psm::nsPKCS12Blob_Export(output, certs, password);
}
X509Certificate* NSSCertDatabase::FindRootInList(
const CertificateList& certificates) const {
DCHECK_GT(certificates.size(), 0U);
if (certificates.size() == 1)
return certificates[0].get();
X509Certificate* cert0 = certificates[0].get();
X509Certificate* cert1 = certificates[1].get();
X509Certificate* certn_2 = certificates[certificates.size() - 2].get();
X509Certificate* certn_1 = certificates[certificates.size() - 1].get();
if (CERT_CompareName(&cert1->os_cert_handle()->issuer,
&cert0->os_cert_handle()->subject) == SECEqual)
return cert0;
if (CERT_CompareName(&certn_2->os_cert_handle()->issuer,
&certn_1->os_cert_handle()->subject) == SECEqual)
return certn_1;
LOG(WARNING) << "certificate list is not a hierarchy";
return cert0;
}
bool NSSCertDatabase::ImportCACerts(const CertificateList& certificates,
TrustBits trust_bits,
ImportCertFailureList* not_imported) {
crypto::ScopedPK11Slot slot(GetPublicSlot());
X509Certificate* root = FindRootInList(certificates);
bool success = psm::ImportCACerts(
slot.get(), certificates, root, trust_bits, not_imported);
if (success)
NotifyObserversOfCACertChanged(NULL);
return success;
}
bool NSSCertDatabase::ImportServerCert(const CertificateList& certificates,
TrustBits trust_bits,
ImportCertFailureList* not_imported) {
crypto::ScopedPK11Slot slot(GetPublicSlot());
return psm::ImportServerCert(
slot.get(), certificates, trust_bits, not_imported);
}
NSSCertDatabase::TrustBits NSSCertDatabase::GetCertTrust(
const X509Certificate* cert,
CertType type) const {
CERTCertTrust trust;
SECStatus srv = CERT_GetCertTrust(cert->os_cert_handle(), &trust);
if (srv != SECSuccess) {
LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError();
return TRUST_DEFAULT;
}
// We define our own more "friendly" TrustBits, which means we aren't able to
// round-trip all possible NSS trust flag combinations. We try to map them in
// a sensible way.
switch (type) {
case CA_CERT: {
const unsigned kTrustedCA = CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
const unsigned kCAFlags = kTrustedCA | CERTDB_TERMINAL_RECORD;
TrustBits trust_bits = TRUST_DEFAULT;
if ((trust.sslFlags & kCAFlags) == CERTDB_TERMINAL_RECORD)
trust_bits |= DISTRUSTED_SSL;
else if (trust.sslFlags & kTrustedCA)
trust_bits |= TRUSTED_SSL;
if ((trust.emailFlags & kCAFlags) == CERTDB_TERMINAL_RECORD)
trust_bits |= DISTRUSTED_EMAIL;
else if (trust.emailFlags & kTrustedCA)
trust_bits |= TRUSTED_EMAIL;
if ((trust.objectSigningFlags & kCAFlags) == CERTDB_TERMINAL_RECORD)
trust_bits |= DISTRUSTED_OBJ_SIGN;
else if (trust.objectSigningFlags & kTrustedCA)
trust_bits |= TRUSTED_OBJ_SIGN;
return trust_bits;
}
case SERVER_CERT:
if (trust.sslFlags & CERTDB_TERMINAL_RECORD) {
if (trust.sslFlags & CERTDB_TRUSTED)
return TRUSTED_SSL;
return DISTRUSTED_SSL;
}
return TRUST_DEFAULT;
default:
return TRUST_DEFAULT;
}
}
bool NSSCertDatabase::IsUntrusted(const X509Certificate* cert) const {
CERTCertTrust nsstrust;
SECStatus rv = CERT_GetCertTrust(cert->os_cert_handle(), &nsstrust);
if (rv != SECSuccess) {
LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError();
return false;
}
// The CERTCertTrust structure contains three trust records:
// sslFlags, emailFlags, and objectSigningFlags. The three
// trust records are independent of each other.
//
// If the CERTDB_TERMINAL_RECORD bit in a trust record is set,
// then that trust record is a terminal record. A terminal
// record is used for explicit trust and distrust of an
// end-entity or intermediate CA cert.
//
// In a terminal record, if neither CERTDB_TRUSTED_CA nor
// CERTDB_TRUSTED is set, then the terminal record means
// explicit distrust. On the other hand, if the terminal
// record has either CERTDB_TRUSTED_CA or CERTDB_TRUSTED bit
// set, then the terminal record means explicit trust.
//
// For a root CA, the trust record does not have
// the CERTDB_TERMINAL_RECORD bit set.
static const unsigned int kTrusted = CERTDB_TRUSTED_CA | CERTDB_TRUSTED;
if ((nsstrust.sslFlags & CERTDB_TERMINAL_RECORD) != 0 &&
(nsstrust.sslFlags & kTrusted) == 0) {
return true;
}
if ((nsstrust.emailFlags & CERTDB_TERMINAL_RECORD) != 0 &&
(nsstrust.emailFlags & kTrusted) == 0) {
return true;
}
if ((nsstrust.objectSigningFlags & CERTDB_TERMINAL_RECORD) != 0 &&
(nsstrust.objectSigningFlags & kTrusted) == 0) {
return true;
}
// Self-signed certificates that don't have any trust bits set are untrusted.
// Other certificates that don't have any trust bits set may still be trusted
// if they chain up to a trust anchor.
if (CERT_CompareName(&cert->os_cert_handle()->issuer,
&cert->os_cert_handle()->subject) == SECEqual) {
return (nsstrust.sslFlags & kTrusted) == 0 &&
(nsstrust.emailFlags & kTrusted) == 0 &&
(nsstrust.objectSigningFlags & kTrusted) == 0;
}
return false;
}
bool NSSCertDatabase::SetCertTrust(const X509Certificate* cert,
CertType type,
TrustBits trust_bits) {
bool success = psm::SetCertTrust(cert, type, trust_bits);
if (success)
NotifyObserversOfCACertChanged(cert);
return success;
}
bool NSSCertDatabase::DeleteCertAndKey(X509Certificate* cert) {
if (!DeleteCertAndKeyImpl(cert))
return false;
NotifyObserversOfCertRemoved(cert);
return true;
}
void NSSCertDatabase::DeleteCertAndKeyAsync(
const scoped_refptr<X509Certificate>& cert,
const DeleteCertCallback& callback) {
base::PostTaskAndReplyWithResult(
GetSlowTaskRunner().get(),
FROM_HERE,
base::Bind(&NSSCertDatabase::DeleteCertAndKeyImpl, cert),
base::Bind(&NSSCertDatabase::NotifyCertRemovalAndCallBack,
weak_factory_.GetWeakPtr(),
cert,
callback));
}
bool NSSCertDatabase::IsReadOnly(const X509Certificate* cert) const {
PK11SlotInfo* slot = cert->os_cert_handle()->slot;
return slot && PK11_IsReadOnly(slot);
}
bool NSSCertDatabase::IsHardwareBacked(const X509Certificate* cert) const {
PK11SlotInfo* slot = cert->os_cert_handle()->slot;
return slot && PK11_IsHW(slot);
}
void NSSCertDatabase::AddObserver(Observer* observer) {
observer_list_->AddObserver(observer);
}
void NSSCertDatabase::RemoveObserver(Observer* observer) {
observer_list_->RemoveObserver(observer);
}
void NSSCertDatabase::SetSlowTaskRunnerForTest(
const scoped_refptr<base::TaskRunner>& task_runner) {
slow_task_runner_for_test_ = task_runner;
}
// static
void NSSCertDatabase::ListCertsImpl(crypto::ScopedPK11Slot slot,
CertificateList* certs) {
certs->clear();
CERTCertList* cert_list = NULL;
if (slot)
cert_list = PK11_ListCertsInSlot(slot.get());
else
cert_list = PK11_ListCerts(PK11CertListUnique, NULL);
CERTCertListNode* node;
for (node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list);
node = CERT_LIST_NEXT(node)) {
certs->push_back(X509Certificate::CreateFromHandle(
node->cert, X509Certificate::OSCertHandles()));
}
CERT_DestroyCertList(cert_list);
}
scoped_refptr<base::TaskRunner> NSSCertDatabase::GetSlowTaskRunner() const {
if (slow_task_runner_for_test_.get())
return slow_task_runner_for_test_;
return base::WorkerPool::GetTaskRunner(true /*task is slow*/);
}
void NSSCertDatabase::NotifyCertRemovalAndCallBack(
scoped_refptr<X509Certificate> cert,
const DeleteCertCallback& callback,
bool success) {
if (success)
NotifyObserversOfCertRemoved(cert.get());
callback.Run(success);
}
void NSSCertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) {
observer_list_->Notify(&Observer::OnCertAdded, make_scoped_refptr(cert));
}
void NSSCertDatabase::NotifyObserversOfCertRemoved(
const X509Certificate* cert) {
observer_list_->Notify(&Observer::OnCertRemoved, make_scoped_refptr(cert));
}
void NSSCertDatabase::NotifyObserversOfCACertChanged(
const X509Certificate* cert) {
observer_list_->Notify(
&Observer::OnCACertChanged, make_scoped_refptr(cert));
}
// static
bool NSSCertDatabase::DeleteCertAndKeyImpl(
scoped_refptr<X509Certificate> cert) {
// For some reason, PK11_DeleteTokenCertAndKey only calls
// SEC_DeletePermCertificate if the private key is found. So, we check
// whether a private key exists before deciding which function to call to
// delete the cert.
SECKEYPrivateKey* privKey =
PK11_FindKeyByAnyCert(cert->os_cert_handle(), NULL);
if (privKey) {
SECKEY_DestroyPrivateKey(privKey);
if (PK11_DeleteTokenCertAndKey(cert->os_cert_handle(), NULL)) {
LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError();
return false;
}
} else {
if (SEC_DeletePermCertificate(cert->os_cert_handle())) {
LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError();
return false;
}
}
return true;
}
} // namespace net