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/chromeos/chromeos_version.h"
     10 #include "base/message_loop/message_loop_proxy.h"
     11 #include "base/observer_list.h"
     12 #include "base/sequenced_task_runner.h"
     13 #include "base/strings/string_number_conversions.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   crypto::OpenPersistentNSSDB();
     56   crypto::EnableTPMTokenForNSS();
     57 }
     58 
     59 }  // namespace
     60 
     61 static CertLoader* g_cert_loader = NULL;
     62 
     63 // static
     64 void CertLoader::Initialize() {
     65   CHECK(!g_cert_loader);
     66   g_cert_loader = new CertLoader();
     67   g_cert_loader->Init();
     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     : certificates_requested_(false),
     90       certificates_loaded_(false),
     91       certificates_update_required_(false),
     92       certificates_update_running_(false),
     93       tpm_token_state_(TPM_STATE_UNKNOWN),
     94       tpm_request_delay_(
     95           base::TimeDelta::FromMilliseconds(kInitialRequestDelayMs)),
     96       initialize_token_factory_(this),
     97       update_certificates_factory_(this) {
     98 }
     99 
    100 void CertLoader::Init() {
    101   net::CertDatabase::GetInstance()->AddObserver(this);
    102   if (LoginState::IsInitialized())
    103     LoginState::Get()->AddObserver(this);
    104 }
    105 
    106 void CertLoader::SetCryptoTaskRunner(
    107     const scoped_refptr<base::SequencedTaskRunner>& crypto_task_runner) {
    108   crypto_task_runner_ = crypto_task_runner;
    109   MaybeRequestCertificates();
    110 }
    111 
    112 void CertLoader::SetSlowTaskRunnerForTest(
    113     const scoped_refptr<base::TaskRunner>& task_runner) {
    114   slow_task_runner_for_test_ = task_runner;
    115 }
    116 
    117 CertLoader::~CertLoader() {
    118   net::CertDatabase::GetInstance()->RemoveObserver(this);
    119   if (LoginState::IsInitialized())
    120     LoginState::Get()->RemoveObserver(this);
    121 }
    122 
    123 void CertLoader::AddObserver(CertLoader::Observer* observer) {
    124   observers_.AddObserver(observer);
    125 }
    126 
    127 void CertLoader::RemoveObserver(CertLoader::Observer* observer) {
    128   observers_.RemoveObserver(observer);
    129 }
    130 
    131 bool CertLoader::CertificatesLoading() const {
    132   return certificates_requested_ && !certificates_loaded_;
    133 }
    134 
    135 bool CertLoader::IsHardwareBacked() const {
    136   return !tpm_token_name_.empty();
    137 }
    138 
    139 void CertLoader::MaybeRequestCertificates() {
    140   CHECK(thread_checker_.CalledOnValidThread());
    141 
    142   // This is the entry point to the TPM token initialization process,
    143   // which we should do at most once.
    144   if (certificates_requested_ || !crypto_task_runner_.get())
    145     return;
    146 
    147   const bool logged_in = LoginState::IsInitialized() ?
    148       LoginState::Get()->IsUserLoggedIn() : false;
    149   VLOG(1) << "RequestCertificates: " << logged_in;
    150   if (!logged_in)
    151     return;
    152 
    153   certificates_requested_ = true;
    154 
    155   // Ensure we only initialize the TPM token once.
    156   DCHECK_EQ(tpm_token_state_, TPM_STATE_UNKNOWN);
    157   if (!base::chromeos::IsRunningOnChromeOS())
    158     tpm_token_state_ = TPM_DISABLED;
    159 
    160   InitializeTokenAndLoadCertificates();
    161 }
    162 
    163 void CertLoader::InitializeTokenAndLoadCertificates() {
    164   CHECK(thread_checker_.CalledOnValidThread());
    165   VLOG(1) << "InitializeTokenAndLoadCertificates: " << tpm_token_state_;
    166 
    167   // Treat TPM as disabled for guest users since they do not store certs.
    168   if (LoginState::IsInitialized() && LoginState::Get()->IsGuestUser())
    169     tpm_token_state_ = TPM_DISABLED;
    170 
    171   switch (tpm_token_state_) {
    172     case TPM_STATE_UNKNOWN: {
    173       crypto_task_runner_->PostTaskAndReply(
    174           FROM_HERE,
    175           base::Bind(&CallOpenPersistentNSSDB),
    176           base::Bind(&CertLoader::OnPersistentNSSDBOpened,
    177                      initialize_token_factory_.GetWeakPtr()));
    178       return;
    179     }
    180     case TPM_DB_OPENED: {
    181       DBusThreadManager::Get()->GetCryptohomeClient()->TpmIsEnabled(
    182           base::Bind(&CertLoader::OnTpmIsEnabled,
    183                      initialize_token_factory_.GetWeakPtr()));
    184       return;
    185     }
    186     case TPM_DISABLED: {
    187       // TPM is disabled, so proceed with empty tpm token name.
    188       StartLoadCertificates();
    189       return;
    190     }
    191     case TPM_ENABLED: {
    192       DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11IsTpmTokenReady(
    193           base::Bind(&CertLoader::OnPkcs11IsTpmTokenReady,
    194                      initialize_token_factory_.GetWeakPtr()));
    195       return;
    196     }
    197     case TPM_TOKEN_READY: {
    198       // Retrieve token_name_ and user_pin_ here since they will never change
    199       // and CryptohomeClient calls are not thread safe.
    200       DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11GetTpmTokenInfo(
    201           base::Bind(&CertLoader::OnPkcs11GetTpmTokenInfo,
    202                      initialize_token_factory_.GetWeakPtr()));
    203       return;
    204     }
    205     case TPM_TOKEN_INFO_RECEIVED: {
    206       base::PostTaskAndReplyWithResult(
    207           crypto_task_runner_.get(),
    208           FROM_HERE,
    209           base::Bind(
    210               &crypto::InitializeTPMToken, tpm_token_name_, tpm_user_pin_),
    211           base::Bind(&CertLoader::OnTPMTokenInitialized,
    212                      initialize_token_factory_.GetWeakPtr()));
    213       return;
    214       tpm_token_state_ = TPM_TOKEN_INITIALIZED;
    215       // FALL_THROUGH_INTENDED
    216     }
    217     case TPM_TOKEN_INITIALIZED: {
    218       StartLoadCertificates();
    219       return;
    220     }
    221   }
    222 }
    223 
    224 void CertLoader::RetryTokenInitializationLater() {
    225   CHECK(thread_checker_.CalledOnValidThread());
    226   LOG(WARNING) << "Re-Requesting Certificates later.";
    227   base::MessageLoop::current()->PostDelayedTask(
    228       FROM_HERE,
    229       base::Bind(&CertLoader::InitializeTokenAndLoadCertificates,
    230                  initialize_token_factory_.GetWeakPtr()),
    231       tpm_request_delay_);
    232   tpm_request_delay_ = GetNextRequestDelayMs(tpm_request_delay_);
    233 }
    234 
    235 void CertLoader::OnPersistentNSSDBOpened() {
    236   VLOG(1) << "PersistentNSSDBOpened";
    237   tpm_token_state_ = TPM_DB_OPENED;
    238   InitializeTokenAndLoadCertificates();
    239 }
    240 
    241 // This is copied from chrome/common/net/x509_certificate_model_nss.cc.
    242 // For background see this discussion on dev-tech-crypto.lists.mozilla.org:
    243 // http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX
    244 //
    245 // NOTE: This function relies on the convention that the same PKCS#11 ID
    246 // is shared between a certificate and its associated private and public
    247 // keys.  I tried to implement this with PK11_GetLowLevelKeyIDForCert(),
    248 // but that always returns NULL on Chrome OS for me.
    249 
    250 // static
    251 std::string CertLoader::GetPkcs11IdForCert(const net::X509Certificate& cert) {
    252   CERTCertificateStr* cert_handle = cert.os_cert_handle();
    253   SECKEYPrivateKey *priv_key =
    254       PK11_FindKeyByAnyCert(cert_handle, NULL /* wincx */);
    255   if (!priv_key)
    256     return std::string();
    257 
    258   // Get the CKA_ID attribute for a key.
    259   SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
    260   std::string pkcs11_id;
    261   if (sec_item) {
    262     pkcs11_id = base::HexEncode(sec_item->data, sec_item->len);
    263     SECITEM_FreeItem(sec_item, PR_TRUE);
    264   }
    265   SECKEY_DestroyPrivateKey(priv_key);
    266 
    267   return pkcs11_id;
    268 }
    269 
    270 void CertLoader::OnTpmIsEnabled(DBusMethodCallStatus call_status,
    271                                 bool tpm_is_enabled) {
    272   VLOG(1) << "OnTpmIsEnabled: " << tpm_is_enabled;
    273 
    274   if (call_status == DBUS_METHOD_CALL_SUCCESS && tpm_is_enabled)
    275     tpm_token_state_ = TPM_ENABLED;
    276   else
    277     tpm_token_state_ = TPM_DISABLED;
    278 
    279   InitializeTokenAndLoadCertificates();
    280 }
    281 
    282 void CertLoader::OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status,
    283                                          bool is_tpm_token_ready) {
    284   VLOG(1) << "OnPkcs11IsTpmTokenReady: " << is_tpm_token_ready;
    285 
    286   if (call_status == DBUS_METHOD_CALL_FAILURE || !is_tpm_token_ready) {
    287     RetryTokenInitializationLater();
    288     return;
    289   }
    290 
    291   tpm_token_state_ = TPM_TOKEN_READY;
    292   InitializeTokenAndLoadCertificates();
    293 }
    294 
    295 void CertLoader::OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status,
    296                                          const std::string& token_name,
    297                                          const std::string& user_pin) {
    298   VLOG(1) << "OnPkcs11GetTpmTokenInfo: " << token_name;
    299 
    300   if (call_status == DBUS_METHOD_CALL_FAILURE) {
    301     RetryTokenInitializationLater();
    302     return;
    303   }
    304 
    305   tpm_token_name_ = token_name;
    306   // TODO(stevenjb): The network code expects a slot ID, not a label. See
    307   // crbug.com/201101. For now, use a hard coded, well known slot instead.
    308   const char kHardcodedTpmSlot[] = "0";
    309   tpm_token_slot_ = kHardcodedTpmSlot;
    310   tpm_user_pin_ = user_pin;
    311   tpm_token_state_ = TPM_TOKEN_INFO_RECEIVED;
    312 
    313   InitializeTokenAndLoadCertificates();
    314 }
    315 
    316 void CertLoader::OnTPMTokenInitialized(bool success) {
    317   VLOG(1) << "OnTPMTokenInitialized: " << success;
    318   if (!success) {
    319     RetryTokenInitializationLater();
    320     return;
    321   }
    322   tpm_token_state_ = TPM_TOKEN_INITIALIZED;
    323   InitializeTokenAndLoadCertificates();
    324 }
    325 
    326 void CertLoader::StartLoadCertificates() {
    327   CHECK(thread_checker_.CalledOnValidThread());
    328   VLOG(1) << "StartLoadCertificates: " << certificates_update_running_;
    329 
    330   if (certificates_update_running_) {
    331     certificates_update_required_ = true;
    332     return;
    333   }
    334 
    335   net::CertificateList* cert_list = new net::CertificateList;
    336   certificates_update_running_ = true;
    337   certificates_update_required_ = false;
    338 
    339   base::TaskRunner* task_runner = slow_task_runner_for_test_.get();
    340   if (!task_runner)
    341     task_runner = base::WorkerPool::GetTaskRunner(true /* task is slow */);
    342   task_runner->PostTaskAndReply(
    343       FROM_HERE,
    344       base::Bind(LoadNSSCertificates, cert_list),
    345       base::Bind(&CertLoader::UpdateCertificates,
    346                  update_certificates_factory_.GetWeakPtr(),
    347                  base::Owned(cert_list)));
    348 }
    349 
    350 void CertLoader::UpdateCertificates(net::CertificateList* cert_list) {
    351   CHECK(thread_checker_.CalledOnValidThread());
    352   DCHECK(certificates_update_running_);
    353   VLOG(1) << "UpdateCertificates: " << cert_list->size();
    354 
    355   // Ignore any existing certificates.
    356   cert_list_.swap(*cert_list);
    357 
    358   bool initial_load = !certificates_loaded_;
    359   certificates_loaded_ = true;
    360   NotifyCertificatesLoaded(initial_load);
    361 
    362   certificates_update_running_ = false;
    363   if (certificates_update_required_)
    364     StartLoadCertificates();
    365 }
    366 
    367 void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
    368   FOR_EACH_OBSERVER(Observer, observers_,
    369                     OnCertificatesLoaded(cert_list_, initial_load));
    370 }
    371 
    372 void CertLoader::OnCertTrustChanged(const net::X509Certificate* cert) {
    373 }
    374 
    375 void CertLoader::OnCertAdded(const net::X509Certificate* cert) {
    376   VLOG(1) << "OnCertAdded";
    377   StartLoadCertificates();
    378 }
    379 
    380 void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
    381   VLOG(1) << "OnCertRemoved";
    382   StartLoadCertificates();
    383 }
    384 
    385 void CertLoader::LoggedInStateChanged(LoginState::LoggedInState state) {
    386   VLOG(1) << "LoggedInStateChanged: " << state;
    387   MaybeRequestCertificates();
    388 }
    389 
    390 }  // namespace chromeos
    391