- add sources.
[platform/framework/web/crosswalk.git] / src / chromeos / cert_loader.cc
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,
216                      tpm_token_name_,
217                      tpm_token_slot_id_,
218                      tpm_user_pin_),
219           base::Bind(&CertLoader::OnTPMTokenInitialized,
220                      initialize_token_factory_.GetWeakPtr()));
221       return;
222     }
223     case TPM_TOKEN_INITIALIZED: {
224       StartLoadCertificates();
225       return;
226     }
227   }
228 }
229
230 void CertLoader::RetryTokenInitializationLater() {
231   CHECK(thread_checker_.CalledOnValidThread());
232   LOG(WARNING) << "Retry token initialization later.";
233   base::MessageLoop::current()->PostDelayedTask(
234       FROM_HERE,
235       base::Bind(&CertLoader::InitializeTokenAndLoadCertificates,
236                  initialize_token_factory_.GetWeakPtr()),
237       tpm_request_delay_);
238   tpm_request_delay_ = GetNextRequestDelayMs(tpm_request_delay_);
239 }
240
241 void CertLoader::OnPersistentNSSDBOpened() {
242   VLOG(1) << "PersistentNSSDBOpened";
243   tpm_token_state_ = TPM_DB_OPENED;
244   InitializeTokenAndLoadCertificates();
245 }
246
247 // This is copied from chrome/common/net/x509_certificate_model_nss.cc.
248 // For background see this discussion on dev-tech-crypto.lists.mozilla.org:
249 // http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX
250 //
251 // NOTE: This function relies on the convention that the same PKCS#11 ID
252 // is shared between a certificate and its associated private and public
253 // keys.  I tried to implement this with PK11_GetLowLevelKeyIDForCert(),
254 // but that always returns NULL on Chrome OS for me.
255
256 // static
257 std::string CertLoader::GetPkcs11IdForCert(const net::X509Certificate& cert) {
258   CERTCertificateStr* cert_handle = cert.os_cert_handle();
259   SECKEYPrivateKey *priv_key =
260       PK11_FindKeyByAnyCert(cert_handle, NULL /* wincx */);
261   if (!priv_key)
262     return std::string();
263
264   // Get the CKA_ID attribute for a key.
265   SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
266   std::string pkcs11_id;
267   if (sec_item) {
268     pkcs11_id = base::HexEncode(sec_item->data, sec_item->len);
269     SECITEM_FreeItem(sec_item, PR_TRUE);
270   }
271   SECKEY_DestroyPrivateKey(priv_key);
272
273   return pkcs11_id;
274 }
275
276 void CertLoader::OnTpmIsEnabled(DBusMethodCallStatus call_status,
277                                 bool tpm_is_enabled) {
278   VLOG(1) << "OnTpmIsEnabled: " << tpm_is_enabled;
279
280   if (call_status == DBUS_METHOD_CALL_SUCCESS && tpm_is_enabled)
281     tpm_token_state_ = TPM_ENABLED;
282   else
283     tpm_token_state_ = TPM_DISABLED;
284
285   InitializeTokenAndLoadCertificates();
286 }
287
288 void CertLoader::OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status,
289                                          bool is_tpm_token_ready) {
290   VLOG(1) << "OnPkcs11IsTpmTokenReady: " << is_tpm_token_ready;
291
292   if (call_status == DBUS_METHOD_CALL_FAILURE || !is_tpm_token_ready) {
293     RetryTokenInitializationLater();
294     return;
295   }
296
297   tpm_token_state_ = TPM_TOKEN_READY;
298   InitializeTokenAndLoadCertificates();
299 }
300
301 void CertLoader::OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status,
302                                          const std::string& token_name,
303                                          const std::string& user_pin,
304                                          int token_slot_id) {
305   VLOG(1) << "OnPkcs11GetTpmTokenInfo: " << token_name;
306
307   if (call_status == DBUS_METHOD_CALL_FAILURE) {
308     RetryTokenInitializationLater();
309     return;
310   }
311
312   tpm_token_name_ = token_name;
313   tpm_token_slot_id_ = token_slot_id;
314   tpm_user_pin_ = user_pin;
315   tpm_token_state_ = TPM_TOKEN_INFO_RECEIVED;
316
317   InitializeTokenAndLoadCertificates();
318 }
319
320 void CertLoader::OnTPMTokenInitialized(bool success) {
321   VLOG(1) << "OnTPMTokenInitialized: " << success;
322   if (!success) {
323     RetryTokenInitializationLater();
324     return;
325   }
326   tpm_token_state_ = TPM_TOKEN_INITIALIZED;
327   InitializeTokenAndLoadCertificates();
328 }
329
330 void CertLoader::StartLoadCertificates() {
331   DCHECK(!certificates_loaded_ && !certificates_update_running_);
332   net::CertDatabase::GetInstance()->AddObserver(this);
333   LoadCertificates();
334 }
335
336 void CertLoader::LoadCertificates() {
337   CHECK(thread_checker_.CalledOnValidThread());
338   VLOG(1) << "LoadCertificates: " << certificates_update_running_;
339
340   if (certificates_update_running_) {
341     certificates_update_required_ = true;
342     return;
343   }
344
345   net::CertificateList* cert_list = new net::CertificateList;
346   certificates_update_running_ = true;
347   certificates_update_required_ = false;
348
349   base::TaskRunner* task_runner = slow_task_runner_for_test_.get();
350   if (!task_runner)
351     task_runner = base::WorkerPool::GetTaskRunner(true /* task is slow */);
352   task_runner->PostTaskAndReply(
353       FROM_HERE,
354       base::Bind(LoadNSSCertificates, cert_list),
355       base::Bind(&CertLoader::UpdateCertificates,
356                  update_certificates_factory_.GetWeakPtr(),
357                  base::Owned(cert_list)));
358 }
359
360 void CertLoader::UpdateCertificates(net::CertificateList* cert_list) {
361   CHECK(thread_checker_.CalledOnValidThread());
362   DCHECK(certificates_update_running_);
363   VLOG(1) << "UpdateCertificates: " << cert_list->size();
364
365   // Ignore any existing certificates.
366   cert_list_.swap(*cert_list);
367
368   bool initial_load = !certificates_loaded_;
369   certificates_loaded_ = true;
370   NotifyCertificatesLoaded(initial_load);
371
372   certificates_update_running_ = false;
373   if (certificates_update_required_)
374     LoadCertificates();
375 }
376
377 void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
378   FOR_EACH_OBSERVER(Observer, observers_,
379                     OnCertificatesLoaded(cert_list_, initial_load));
380 }
381
382 void CertLoader::OnCACertChanged(const net::X509Certificate* cert) {
383   // This is triggered when a CA certificate is modified.
384   VLOG(1) << "OnCACertChanged";
385   LoadCertificates();
386 }
387
388 void CertLoader::OnCertAdded(const net::X509Certificate* cert) {
389   // This is triggered when a client certificate is added.
390   VLOG(1) << "OnCertAdded";
391   LoadCertificates();
392 }
393
394 void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
395   VLOG(1) << "OnCertRemoved";
396   LoadCertificates();
397 }
398
399 void CertLoader::LoggedInStateChanged() {
400   VLOG(1) << "LoggedInStateChanged";
401   MaybeRequestCertificates();
402 }
403
404 }  // namespace chromeos