Home | History | Annotate | Download | only in chromeos
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chromeos/cert_loader.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/message_loop/message_loop_proxy.h"
     10 #include "base/observer_list.h"
     11 #include "base/sequenced_task_runner.h"
     12 #include "base/strings/string_number_conversions.h"
     13 #include "base/sys_info.h"
     14 #include "base/task_runner_util.h"
     15 #include "base/threading/worker_pool.h"
     16 #include "chromeos/dbus/cryptohome_client.h"
     17 #include "chromeos/dbus/dbus_thread_manager.h"
     18 #include "crypto/encryptor.h"
     19 #include "crypto/nss_util.h"
     20 #include "crypto/sha2.h"
     21 #include "crypto/symmetric_key.h"
     22 #include "net/cert/nss_cert_database.h"
     23 
     24 namespace chromeos {
     25 
     26 namespace {
     27 
     28 const int64 kInitialRequestDelayMs = 100;
     29 const int64 kMaxRequestDelayMs = 300000; // 5 minutes
     30 
     31 // Calculates the delay before running next attempt to initiatialize the TPM
     32 // token, if |last_delay| was the last or initial delay.
     33 base::TimeDelta GetNextRequestDelayMs(base::TimeDelta last_delay) {
     34   // This implements an exponential backoff, as we don't know in which order of
     35   // magnitude the TPM token changes it's state.
     36   base::TimeDelta next_delay = last_delay * 2;
     37 
     38   // Cap the delay to prevent an overflow. This threshold is arbitrarily chosen.
     39   const base::TimeDelta max_delay =
     40       base::TimeDelta::FromMilliseconds(kMaxRequestDelayMs);
     41   if (next_delay > max_delay)
     42     next_delay = max_delay;
     43   return next_delay;
     44 }
     45 
     46 void LoadNSSCertificates(net::CertificateList* cert_list) {
     47   net::NSSCertDatabase::GetInstance()->ListCerts(cert_list);
     48 }
     49 
     50 void CallOpenPersistentNSSDB() {
     51   // Called from crypto_task_runner_.
     52   VLOG(1) << "CallOpenPersistentNSSDB";
     53 
     54   // Ensure we've opened the user's key/certificate database.
     55   if (base::SysInfo::IsRunningOnChromeOS())
     56     crypto::OpenPersistentNSSDB();
     57   crypto::EnableTPMTokenForNSS();
     58 }
     59 
     60 }  // namespace
     61 
     62 static CertLoader* g_cert_loader = NULL;
     63 
     64 // static
     65 void CertLoader::Initialize() {
     66   CHECK(!g_cert_loader);
     67   g_cert_loader = new CertLoader();
     68 }
     69 
     70 // static
     71 void CertLoader::Shutdown() {
     72   CHECK(g_cert_loader);
     73   delete g_cert_loader;
     74   g_cert_loader = NULL;
     75 }
     76 
     77 // static
     78 CertLoader* CertLoader::Get() {
     79   CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()";
     80   return g_cert_loader;
     81 }
     82 
     83 // static
     84 bool CertLoader::IsInitialized() {
     85   return g_cert_loader;
     86 }
     87 
     88 CertLoader::CertLoader()
     89     : initialize_tpm_for_test_(false),
     90       certificates_requested_(false),
     91       certificates_loaded_(false),
     92       certificates_update_required_(false),
     93       certificates_update_running_(false),
     94       tpm_token_state_(TPM_STATE_UNKNOWN),
     95       tpm_request_delay_(
     96           base::TimeDelta::FromMilliseconds(kInitialRequestDelayMs)),
     97       tpm_token_slot_id_(-1),
     98       initialize_token_factory_(this),
     99       update_certificates_factory_(this) {
    100   if (LoginState::IsInitialized())
    101     LoginState::Get()->AddObserver(this);
    102 }
    103 
    104 void CertLoader::InitializeTPMForTest() {
    105   initialize_tpm_for_test_ = true;
    106 }
    107 
    108 void CertLoader::SetCryptoTaskRunner(
    109     const scoped_refptr<base::SequencedTaskRunner>& crypto_task_runner) {
    110   crypto_task_runner_ = crypto_task_runner;
    111   MaybeRequestCertificates();
    112 }
    113 
    114 void CertLoader::SetSlowTaskRunnerForTest(
    115     const scoped_refptr<base::TaskRunner>& task_runner) {
    116   slow_task_runner_for_test_ = task_runner;
    117 }
    118 
    119 CertLoader::~CertLoader() {
    120   net::CertDatabase::GetInstance()->RemoveObserver(this);
    121   if (LoginState::IsInitialized())
    122     LoginState::Get()->RemoveObserver(this);
    123 }
    124 
    125 void CertLoader::AddObserver(CertLoader::Observer* observer) {
    126   observers_.AddObserver(observer);
    127 }
    128 
    129 void CertLoader::RemoveObserver(CertLoader::Observer* observer) {
    130   observers_.RemoveObserver(observer);
    131 }
    132 
    133 bool CertLoader::CertificatesLoading() const {
    134   return certificates_requested_ && !certificates_loaded_;
    135 }
    136 
    137 bool CertLoader::IsHardwareBacked() const {
    138   return !tpm_token_name_.empty();
    139 }
    140 
    141 void CertLoader::MaybeRequestCertificates() {
    142   CHECK(thread_checker_.CalledOnValidThread());
    143 
    144   // This is the entry point to the TPM token initialization process,
    145   // which we should do at most once.
    146   if (certificates_requested_ || !crypto_task_runner_.get())
    147     return;
    148 
    149   if (!LoginState::IsInitialized())
    150     return;
    151 
    152   bool request_certificates = LoginState::Get()->IsUserLoggedIn() ||
    153       LoginState::Get()->IsInSafeMode();
    154 
    155   VLOG(1) << "RequestCertificates: " << request_certificates;
    156   if (!request_certificates)
    157     return;
    158 
    159   certificates_requested_ = true;
    160 
    161   // Ensure we only initialize the TPM token once.
    162   DCHECK_EQ(tpm_token_state_, TPM_STATE_UNKNOWN);
    163   if (!initialize_tpm_for_test_ && !base::SysInfo::IsRunningOnChromeOS())
    164     tpm_token_state_ = TPM_DISABLED;
    165 
    166   // Treat TPM as disabled for guest users since they do not store certs.
    167   if (LoginState::Get()->IsGuestUser())
    168     tpm_token_state_ = TPM_DISABLED;
    169 
    170   InitializeTokenAndLoadCertificates();
    171 }
    172 
    173 void CertLoader::InitializeTokenAndLoadCertificates() {
    174   CHECK(thread_checker_.CalledOnValidThread());
    175   VLOG(1) << "InitializeTokenAndLoadCertificates: " << tpm_token_state_;
    176 
    177   switch (tpm_token_state_) {
    178     case TPM_STATE_UNKNOWN: {
    179       crypto_task_runner_->PostTaskAndReply(
    180           FROM_HERE,
    181           base::Bind(&CallOpenPersistentNSSDB),
    182           base::Bind(&CertLoader::OnPersistentNSSDBOpened,
    183                      initialize_token_factory_.GetWeakPtr()));
    184       return;
    185     }
    186     case TPM_DB_OPENED: {
    187       DBusThreadManager::Get()->GetCryptohomeClient()->TpmIsEnabled(
    188           base::Bind(&CertLoader::OnTpmIsEnabled,
    189                      initialize_token_factory_.GetWeakPtr()));
    190       return;
    191     }
    192     case TPM_DISABLED: {
    193       // TPM is disabled, so proceed with empty tpm token name.
    194       StartLoadCertificates();
    195       return;
    196     }
    197     case TPM_ENABLED: {
    198       DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11IsTpmTokenReady(
    199           base::Bind(&CertLoader::OnPkcs11IsTpmTokenReady,
    200                      initialize_token_factory_.GetWeakPtr()));
    201       return;
    202     }
    203     case TPM_TOKEN_READY: {
    204       // Retrieve token_name_ and user_pin_ here since they will never change
    205       // and CryptohomeClient calls are not thread safe.
    206       DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11GetTpmTokenInfo(
    207           base::Bind(&CertLoader::OnPkcs11GetTpmTokenInfo,
    208                      initialize_token_factory_.GetWeakPtr()));
    209       return;
    210     }
    211     case TPM_TOKEN_INFO_RECEIVED: {
    212       base::PostTaskAndReplyWithResult(
    213           crypto_task_runner_.get(),
    214           FROM_HERE,
    215           base::Bind(&crypto::InitializeTPMToken, tpm_token_slot_id_),
    216           base::Bind(&CertLoader::OnTPMTokenInitialized,
    217                      initialize_token_factory_.GetWeakPtr()));
    218       return;
    219     }
    220     case TPM_TOKEN_INITIALIZED: {
    221       StartLoadCertificates();
    222       return;
    223     }
    224   }
    225 }
    226 
    227 void CertLoader::RetryTokenInitializationLater() {
    228   CHECK(thread_checker_.CalledOnValidThread());
    229   LOG(WARNING) << "Retry token initialization later.";
    230   base::MessageLoop::current()->PostDelayedTask(
    231       FROM_HERE,
    232       base::Bind(&CertLoader::InitializeTokenAndLoadCertificates,
    233                  initialize_token_factory_.GetWeakPtr()),
    234       tpm_request_delay_);
    235   tpm_request_delay_ = GetNextRequestDelayMs(tpm_request_delay_);
    236 }
    237 
    238 void CertLoader::OnPersistentNSSDBOpened() {
    239   VLOG(1) << "PersistentNSSDBOpened";
    240   tpm_token_state_ = TPM_DB_OPENED;
    241   InitializeTokenAndLoadCertificates();
    242 }
    243 
    244 // This is copied from chrome/common/net/x509_certificate_model_nss.cc.
    245 // For background see this discussion on dev-tech-crypto.lists.mozilla.org:
    246 // http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX
    247 //
    248 // NOTE: This function relies on the convention that the same PKCS#11 ID
    249 // is shared between a certificate and its associated private and public
    250 // keys.  I tried to implement this with PK11_GetLowLevelKeyIDForCert(),
    251 // but that always returns NULL on Chrome OS for me.
    252 
    253 // static
    254 std::string CertLoader::GetPkcs11IdForCert(const net::X509Certificate& cert) {
    255   CERTCertificateStr* cert_handle = cert.os_cert_handle();
    256   SECKEYPrivateKey *priv_key =
    257       PK11_FindKeyByAnyCert(cert_handle, NULL /* wincx */);
    258   if (!priv_key)
    259     return std::string();
    260 
    261   // Get the CKA_ID attribute for a key.
    262   SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
    263   std::string pkcs11_id;
    264   if (sec_item) {
    265     pkcs11_id = base::HexEncode(sec_item->data, sec_item->len);
    266     SECITEM_FreeItem(sec_item, PR_TRUE);
    267   }
    268   SECKEY_DestroyPrivateKey(priv_key);
    269 
    270   return pkcs11_id;
    271 }
    272 
    273 void CertLoader::OnTpmIsEnabled(DBusMethodCallStatus call_status,
    274                                 bool tpm_is_enabled) {
    275   VLOG(1) << "OnTpmIsEnabled: " << tpm_is_enabled;
    276 
    277   if (call_status == DBUS_METHOD_CALL_SUCCESS && tpm_is_enabled)
    278     tpm_token_state_ = TPM_ENABLED;
    279   else
    280     tpm_token_state_ = TPM_DISABLED;
    281 
    282   InitializeTokenAndLoadCertificates();
    283 }
    284 
    285 void CertLoader::OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status,
    286                                          bool is_tpm_token_ready) {
    287   VLOG(1) << "OnPkcs11IsTpmTokenReady: " << is_tpm_token_ready;
    288 
    289   if (call_status == DBUS_METHOD_CALL_FAILURE || !is_tpm_token_ready) {
    290     RetryTokenInitializationLater();
    291     return;
    292   }
    293 
    294   tpm_token_state_ = TPM_TOKEN_READY;
    295   InitializeTokenAndLoadCertificates();
    296 }
    297 
    298 void CertLoader::OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status,
    299                                          const std::string& token_name,
    300                                          const std::string& user_pin,
    301                                          int token_slot_id) {
    302   VLOG(1) << "OnPkcs11GetTpmTokenInfo: " << token_name;
    303 
    304   if (call_status == DBUS_METHOD_CALL_FAILURE) {
    305     RetryTokenInitializationLater();
    306     return;
    307   }
    308 
    309   tpm_token_name_ = token_name;
    310   tpm_token_slot_id_ = token_slot_id;
    311   tpm_user_pin_ = user_pin;
    312   tpm_token_state_ = TPM_TOKEN_INFO_RECEIVED;
    313 
    314   InitializeTokenAndLoadCertificates();
    315 }
    316 
    317 void CertLoader::OnTPMTokenInitialized(bool success) {
    318   VLOG(1) << "OnTPMTokenInitialized: " << success;
    319   if (!success) {
    320     RetryTokenInitializationLater();
    321     return;
    322   }
    323   tpm_token_state_ = TPM_TOKEN_INITIALIZED;
    324   InitializeTokenAndLoadCertificates();
    325 }
    326 
    327 void CertLoader::StartLoadCertificates() {
    328   DCHECK(!certificates_loaded_ && !certificates_update_running_);
    329   net::CertDatabase::GetInstance()->AddObserver(this);
    330   LoadCertificates();
    331 }
    332 
    333 void CertLoader::LoadCertificates() {
    334   CHECK(thread_checker_.CalledOnValidThread());
    335   VLOG(1) << "LoadCertificates: " << certificates_update_running_;
    336 
    337   if (certificates_update_running_) {
    338     certificates_update_required_ = true;
    339     return;
    340   }
    341 
    342   net::CertificateList* cert_list = new net::CertificateList;
    343   certificates_update_running_ = true;
    344   certificates_update_required_ = false;
    345 
    346   base::TaskRunner* task_runner = slow_task_runner_for_test_.get();
    347   if (!task_runner)
    348     task_runner = base::WorkerPool::GetTaskRunner(true /* task is slow */);
    349   task_runner->PostTaskAndReply(
    350       FROM_HERE,
    351       base::Bind(LoadNSSCertificates, cert_list),
    352       base::Bind(&CertLoader::UpdateCertificates,
    353                  update_certificates_factory_.GetWeakPtr(),
    354                  base::Owned(cert_list)));
    355 }
    356 
    357 void CertLoader::UpdateCertificates(net::CertificateList* cert_list) {
    358   CHECK(thread_checker_.CalledOnValidThread());
    359   DCHECK(certificates_update_running_);
    360   VLOG(1) << "UpdateCertificates: " << cert_list->size();
    361 
    362   // Ignore any existing certificates.
    363   cert_list_.swap(*cert_list);
    364 
    365   bool initial_load = !certificates_loaded_;
    366   certificates_loaded_ = true;
    367   NotifyCertificatesLoaded(initial_load);
    368 
    369   certificates_update_running_ = false;
    370   if (certificates_update_required_)
    371     LoadCertificates();
    372 }
    373 
    374 void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
    375   FOR_EACH_OBSERVER(Observer, observers_,
    376                     OnCertificatesLoaded(cert_list_, initial_load));
    377 }
    378 
    379 void CertLoader::OnCACertChanged(const net::X509Certificate* cert) {
    380   // This is triggered when a CA certificate is modified.
    381   VLOG(1) << "OnCACertChanged";
    382   LoadCertificates();
    383 }
    384 
    385 void CertLoader::OnCertAdded(const net::X509Certificate* cert) {
    386   // This is triggered when a client certificate is added.
    387   VLOG(1) << "OnCertAdded";
    388   LoadCertificates();
    389 }
    390 
    391 void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
    392   VLOG(1) << "OnCertRemoved";
    393   LoadCertificates();
    394 }
    395 
    396 void CertLoader::LoggedInStateChanged() {
    397   VLOG(1) << "LoggedInStateChanged";
    398   MaybeRequestCertificates();
    399 }
    400 
    401 }  // namespace chromeos
    402