• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
GetNextRequestDelayMs(base::TimeDelta last_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 
LoadNSSCertificates(net::CertificateList * cert_list)46 void LoadNSSCertificates(net::CertificateList* cert_list) {
47   net::NSSCertDatabase::GetInstance()->ListCerts(cert_list);
48 }
49 
CallOpenPersistentNSSDB()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
Initialize()65 void CertLoader::Initialize() {
66   CHECK(!g_cert_loader);
67   g_cert_loader = new CertLoader();
68 }
69 
70 // static
Shutdown()71 void CertLoader::Shutdown() {
72   CHECK(g_cert_loader);
73   delete g_cert_loader;
74   g_cert_loader = NULL;
75 }
76 
77 // static
Get()78 CertLoader* CertLoader::Get() {
79   CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()";
80   return g_cert_loader;
81 }
82 
83 // static
IsInitialized()84 bool CertLoader::IsInitialized() {
85   return g_cert_loader;
86 }
87 
CertLoader()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 
InitializeTPMForTest()104 void CertLoader::InitializeTPMForTest() {
105   initialize_tpm_for_test_ = true;
106 }
107 
SetCryptoTaskRunner(const scoped_refptr<base::SequencedTaskRunner> & crypto_task_runner)108 void CertLoader::SetCryptoTaskRunner(
109     const scoped_refptr<base::SequencedTaskRunner>& crypto_task_runner) {
110   crypto_task_runner_ = crypto_task_runner;
111   MaybeRequestCertificates();
112 }
113 
SetSlowTaskRunnerForTest(const scoped_refptr<base::TaskRunner> & task_runner)114 void CertLoader::SetSlowTaskRunnerForTest(
115     const scoped_refptr<base::TaskRunner>& task_runner) {
116   slow_task_runner_for_test_ = task_runner;
117 }
118 
~CertLoader()119 CertLoader::~CertLoader() {
120   net::CertDatabase::GetInstance()->RemoveObserver(this);
121   if (LoginState::IsInitialized())
122     LoginState::Get()->RemoveObserver(this);
123 }
124 
AddObserver(CertLoader::Observer * observer)125 void CertLoader::AddObserver(CertLoader::Observer* observer) {
126   observers_.AddObserver(observer);
127 }
128 
RemoveObserver(CertLoader::Observer * observer)129 void CertLoader::RemoveObserver(CertLoader::Observer* observer) {
130   observers_.RemoveObserver(observer);
131 }
132 
CertificatesLoading() const133 bool CertLoader::CertificatesLoading() const {
134   return certificates_requested_ && !certificates_loaded_;
135 }
136 
IsHardwareBacked() const137 bool CertLoader::IsHardwareBacked() const {
138   return !tpm_token_name_.empty();
139 }
140 
MaybeRequestCertificates()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 
InitializeTokenAndLoadCertificates()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 
RetryTokenInitializationLater()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 
OnPersistentNSSDBOpened()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
GetPkcs11IdForCert(const net::X509Certificate & cert)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 
OnTpmIsEnabled(DBusMethodCallStatus call_status,bool tpm_is_enabled)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 
OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status,bool is_tpm_token_ready)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 
OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status,const std::string & token_name,const std::string & user_pin,int token_slot_id)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 
OnTPMTokenInitialized(bool success)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 
StartLoadCertificates()327 void CertLoader::StartLoadCertificates() {
328   DCHECK(!certificates_loaded_ && !certificates_update_running_);
329   net::CertDatabase::GetInstance()->AddObserver(this);
330   LoadCertificates();
331 }
332 
LoadCertificates()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 
UpdateCertificates(net::CertificateList * cert_list)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 
NotifyCertificatesLoaded(bool initial_load)374 void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
375   FOR_EACH_OBSERVER(Observer, observers_,
376                     OnCertificatesLoaded(cert_list_, initial_load));
377 }
378 
OnCACertChanged(const net::X509Certificate * cert)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 
OnCertAdded(const net::X509Certificate * cert)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 
OnCertRemoved(const net::X509Certificate * cert)391 void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
392   VLOG(1) << "OnCertRemoved";
393   LoadCertificates();
394 }
395 
LoggedInStateChanged()396 void CertLoader::LoggedInStateChanged() {
397   VLOG(1) << "LoggedInStateChanged";
398   MaybeRequestCertificates();
399 }
400 
401 }  // namespace chromeos
402