diff options
author | stevenjb@chromium.org <stevenjb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-06 19:51:56 +0000 |
---|---|---|
committer | stevenjb@chromium.org <stevenjb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-06 19:51:56 +0000 |
commit | 75d93a876b019d2b55ed127350f515f29661beca (patch) | |
tree | d80a223360c76d9214d56523a5c73835de9b911f /chromeos/network | |
parent | ccc1722eae2c4f93e519007dbfd5c78ce73c6d71 (diff) | |
download | chromium_src-75d93a876b019d2b55ed127350f515f29661beca.zip chromium_src-75d93a876b019d2b55ed127350f515f29661beca.tar.gz chromium_src-75d93a876b019d2b55ed127350f515f29661beca.tar.bz2 |
Separate cert loading code from CertLibrary and move to src/chromeos.
The intention of this CL is to move certificate loading code without chrome/i18n dependencies into src/chromeos. This is so that networking code that needs access to certificates can also be moved to src/chromeos.
This also extracts CertLibrary from CrosLibrary.
BUG=133752
R=gspencer@chromium.org, pneubeck@chromium.org
Review URL: https://codereview.chromium.org/14522013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@198511 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos/network')
-rw-r--r-- | chromeos/network/cert_loader.cc | 260 | ||||
-rw-r--r-- | chromeos/network/cert_loader.h | 139 |
2 files changed, 399 insertions, 0 deletions
diff --git a/chromeos/network/cert_loader.cc b/chromeos/network/cert_loader.cc new file mode 100644 index 0000000..1198acc --- /dev/null +++ b/chromeos/network/cert_loader.cc @@ -0,0 +1,260 @@ +// 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 "chromeos/network/cert_loader.h" + +#include <algorithm> + +#include "base/chromeos/chromeos_version.h" +#include "base/observer_list.h" +#include "base/task_runner_util.h" +#include "base/threading/worker_pool.h" +#include "chromeos/dbus/cryptohome_client.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "crypto/encryptor.h" +#include "crypto/nss_util.h" +#include "crypto/sha2.h" +#include "crypto/symmetric_key.h" +#include "net/cert/nss_cert_database.h" + +namespace chromeos { + +namespace { + +// Delay between certificate requests while waiting for TPM/PKCS#11 init. +const int kRequestDelayMs = 500; + +net::CertificateList* LoadNSSCertificates() { + net::CertificateList* cert_list(new net::CertificateList()); + net::NSSCertDatabase::GetInstance()->ListCerts(cert_list); + return cert_list; +} + +} // namespace + +static CertLoader* g_cert_loader = NULL; + +// static +void CertLoader::Initialize() { + CHECK(!g_cert_loader); + g_cert_loader = new CertLoader(); +} + +// static +void CertLoader::Shutdown() { + CHECK(g_cert_loader); + delete g_cert_loader; + g_cert_loader = NULL; +} + +// static +CertLoader* CertLoader::Get() { + CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()"; + return g_cert_loader; +} + +// static +bool CertLoader::IsInitialized() { + return g_cert_loader; +} + +CertLoader::CertLoader() + : tpm_token_ready_(false), + certificates_requested_(false), + certificates_loaded_(false), + key_store_loaded_(false), + weak_ptr_factory_(this) { + net::CertDatabase::GetInstance()->AddObserver(this); + LoginState::Get()->AddObserver(this); + if (LoginState::Get()->IsUserLoggedIn()) + RequestCertificates(); +} + +CertLoader::~CertLoader() { + request_task_.Reset(); + net::CertDatabase::GetInstance()->RemoveObserver(this); + LoginState::Get()->RemoveObserver(this); +} + +void CertLoader::AddObserver(CertLoader::Observer* observer) { + observers_.AddObserver(observer); +} + +void CertLoader::RemoveObserver(CertLoader::Observer* observer) { + observers_.RemoveObserver(observer); +} + +bool CertLoader::CertificatesLoading() const { + return certificates_requested_ && !certificates_loaded_; +} + +bool CertLoader::IsHardwareBacked() const { + return !tpm_token_name_.empty(); +} + +void CertLoader::RequestCertificates() { + CHECK(thread_checker_.CalledOnValidThread()); + VLOG(1) << "RequestCertificates: " << LoginState::Get()->IsUserLoggedIn(); + if (!LoginState::Get()->IsUserLoggedIn()) + return; // Certificates will be requested on login. + + if (!key_store_loaded_) { + // Ensure we've opened the real user's key/certificate database. + crypto::OpenPersistentNSSDB(); + + if (base::chromeos::IsRunningOnChromeOS()) + crypto::EnableTPMTokenForNSS(); + + key_store_loaded_ = true; + } + + certificates_requested_ = true; + + VLOG(1) << "Requesting Certificates."; + DBusThreadManager::Get()->GetCryptohomeClient()->TpmIsEnabled( + base::Bind(&CertLoader::OnTpmIsEnabled, + weak_ptr_factory_.GetWeakPtr())); + + return; +} + +void CertLoader::OnTpmIsEnabled(DBusMethodCallStatus call_status, + bool tpm_is_enabled) { + VLOG(1) << "OnTpmIsEnabled: " << tpm_is_enabled; + if (call_status != DBUS_METHOD_CALL_SUCCESS || !tpm_is_enabled) { + // TPM is not enabled, so proceed with empty tpm token name. + VLOG(1) << "TPM not available."; + StartLoadCertificates(); + } else if (tpm_token_ready_) { + // Once the TPM token is ready, initialize it. + InitializeTPMToken(); + } else { + DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11IsTpmTokenReady( + base::Bind(&CertLoader::OnPkcs11IsTpmTokenReady, + weak_ptr_factory_.GetWeakPtr())); + } +} + +void CertLoader::OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status, + bool is_tpm_token_ready) { + VLOG(1) << "OnPkcs11IsTpmTokenReady: " << is_tpm_token_ready; + if (call_status != DBUS_METHOD_CALL_SUCCESS || !is_tpm_token_ready) { + MaybeRetryRequestCertificates(); + return; + } + + // Retrieve token_name_ and user_pin_ here since they will never change + // and CryptohomeClient calls are not thread safe. + DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11GetTpmTokenInfo( + base::Bind(&CertLoader::OnPkcs11GetTpmTokenInfo, + weak_ptr_factory_.GetWeakPtr())); +} + +void CertLoader::OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status, + const std::string& token_name, + const std::string& user_pin) { + VLOG(1) << "OnPkcs11GetTpmTokenInfo: " << token_name; + if (call_status != DBUS_METHOD_CALL_SUCCESS) { + MaybeRetryRequestCertificates(); + return; + } + tpm_token_name_ = token_name; + // TODO(stevenjb): The network code expects a slot ID, not a label. See + // crbug.com/201101. For now, use a hard coded, well known slot instead. + const char kHardcodedTpmSlot[] = "0"; + tpm_token_slot_ = kHardcodedTpmSlot; + tpm_user_pin_ = user_pin; + tpm_token_ready_ = true; + + InitializeTPMToken(); +} + +void CertLoader::InitializeTPMToken() { + VLOG(1) << "InitializeTPMToken"; + if (!crypto::InitializeTPMToken(tpm_token_name_, tpm_user_pin_)) { + MaybeRetryRequestCertificates(); + return; + } + StartLoadCertificates(); +} + +void CertLoader::StartLoadCertificates() { + VLOG(1) << "Start Load Certificates"; + base::PostTaskAndReplyWithResult( + base::WorkerPool::GetTaskRunner(true /* task_is_slow */), + FROM_HERE, + base::Bind(LoadNSSCertificates), + base::Bind(&CertLoader::UpdateCertificates, + weak_ptr_factory_.GetWeakPtr())); +} + +void CertLoader::UpdateCertificates(net::CertificateList* cert_list) { + CHECK(thread_checker_.CalledOnValidThread()); + DCHECK(cert_list); + VLOG(1) << "Update Certificates: " << cert_list->size(); + + // Clear any existing certificates. + cert_list_.swap(*cert_list); + + // cert_list is constructed in LoadCertificates. + delete cert_list; + + // Set loaded state and notify observers. + if (!certificates_loaded_) { + certificates_loaded_ = true; + NotifyCertificatesLoaded(true); + } else { + NotifyCertificatesLoaded(false); + } +} + +void CertLoader::MaybeRetryRequestCertificates() { + if (!request_task_.is_null()) + return; + + // Cryptohome does not notify us when the token is ready, so call + // this again after a delay. + request_task_ = base::Bind(&CertLoader::RequestCertificatesTask, + weak_ptr_factory_.GetWeakPtr()); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + request_task_, + base::TimeDelta::FromMilliseconds(kRequestDelayMs)); +} + +void CertLoader::RequestCertificatesTask() { + // Reset the task to the initial state so is_null() returns true. + request_task_.Reset(); + RequestCertificates(); +} + +void CertLoader::NotifyCertificatesLoaded(bool initial_load) { + FOR_EACH_OBSERVER(Observer, observers_, + OnCertificatesLoaded(cert_list_, initial_load)); +} + +void CertLoader::OnCertTrustChanged(const net::X509Certificate* cert) { +} + +void CertLoader::OnCertAdded(const net::X509Certificate* cert) { + // Only load certificates if we have completed an initial request. + if (!certificates_loaded_) + return; + StartLoadCertificates(); +} + +void CertLoader::OnCertRemoved(const net::X509Certificate* cert) { + // Only load certificates if we have completed an initial request. + if (!certificates_loaded_) + return; + StartLoadCertificates(); +} + +void CertLoader::LoggedInStateChanged(LoginState::LoggedInState state) { + VLOG(1) << "LoggedInStateChanged: " << state; + if (LoginState::Get()->IsUserLoggedIn() && !certificates_requested_) + RequestCertificates(); +} + +} // namespace chromeos diff --git a/chromeos/network/cert_loader.h b/chromeos/network/cert_loader.h new file mode 100644 index 0000000..f3b69ae --- /dev/null +++ b/chromeos/network/cert_loader.h @@ -0,0 +1,139 @@ +// 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. + +#ifndef CHROMEOS_NETWORK_CERT_LOADER_H_ +#define CHROMEOS_NETWORK_CERT_LOADER_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list_threadsafe.h" +#include "base/threading/thread_checker.h" +#include "chromeos/chromeos_export.h" +#include "chromeos/dbus/dbus_method_call_status.h" +#include "chromeos/login/login_state.h" +#include "net/cert/cert_database.h" +#include "net/cert/x509_certificate.h" + +namespace crypto { +class SymmetricKey; +} + +namespace chromeos { + +// This class is responsible for initializing the TPM token and loading +// certificates once the TPM is initialized. It is expected to be constructed +// on the UI thread and public methods should all be called from the UI thread. +// When certificates have been loaded (after login completes), or the cert +// database changes, observers are called with OnCertificatesLoaded(). +class CHROMEOS_EXPORT CertLoader : public net::CertDatabase::Observer, + public LoginState::Observer { + public: + class Observer { + public: + virtual ~Observer() {} + + // Called when the certificates, passed for convenience as |cert_list|, + // have completed loading. |initial_load| is true the first time this + // is called. + virtual void OnCertificatesLoaded(const net::CertificateList& cert_list, + bool initial_load) = 0; + + protected: + Observer() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Observer); + }; + + // Manage the global instance. + static void Initialize(); + static void Shutdown(); + static CertLoader* Get(); + static bool IsInitialized(); + + void AddObserver(CertLoader::Observer* observer); + void RemoveObserver(CertLoader::Observer* observer); + + // Returns true when the certificate list has been requested but not loaded. + bool CertificatesLoading() const; + + // Returns true if the TPM is available for hardware-backed certificates. + bool IsHardwareBacked() const; + + bool certificates_loaded() const { return certificates_loaded_; } + + // TPM info is only valid once the TPM is available (IsHardwareBacked is + // true). Otherwise empty strings will be returned. + const std::string& tpm_token_name() const { return tpm_token_name_; } + const std::string& tpm_token_slot() const { return tpm_token_slot_; } + const std::string& tpm_user_pin() const { return tpm_user_pin_; } + + // This will be empty until certificates_loaded() is true. + const net::CertificateList& cert_list() const { return cert_list_; } + + private: + CertLoader(); + virtual ~CertLoader(); + + void RequestCertificates(); + + void OnTpmIsEnabled(DBusMethodCallStatus call_status, + bool tpm_is_enabled); + void OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status, + bool is_tpm_token_ready); + void OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status, + const std::string& token_name, + const std::string& user_pin); + void InitializeTPMToken(); + void StartLoadCertificates(); + void UpdateCertificates(net::CertificateList* cert_list); + void MaybeRetryRequestCertificates(); + void RequestCertificatesTask(); + + void NotifyCertificatesLoaded(bool initial_load); + + // net::CertDatabase::Observer + virtual void OnCertTrustChanged(const net::X509Certificate* cert) OVERRIDE; + virtual void OnCertAdded(const net::X509Certificate* cert) OVERRIDE; + virtual void OnCertRemoved(const net::X509Certificate* cert) OVERRIDE; + + // LoginState::Observer + virtual void LoggedInStateChanged(LoginState::LoggedInState state) OVERRIDE; + + ObserverList<Observer> observers_; + + // Active request task for re-requests while waiting for TPM init. + base::Closure request_task_; + + // Local state. + bool tpm_token_ready_; + bool certificates_requested_; + bool certificates_loaded_; + // The key store for the current user has been loaded. This flag is needed to + // ensure that the key store will not be loaded twice in the policy recovery + // "safe-mode". + bool key_store_loaded_; + + // Cached TPM token info. + std::string tpm_token_name_; + std::string tpm_token_slot_; + std::string tpm_user_pin_; + + // Cached Certificates. + net::CertificateList cert_list_; + + base::ThreadChecker thread_checker_; + + // TODO(stevenjb): Use multiple factories to track callback chains. + base::WeakPtrFactory<CertLoader> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(CertLoader); +}; + +} // namespace chromeos + +#endif // CHROMEOS_NETWORK_CERT_LOADER_H_ |