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
24namespace chromeos {
25
26namespace {
27
28const int64 kInitialRequestDelayMs = 100;
29const 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.
33base::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
46void LoadNSSCertificates(net::CertificateList* cert_list) {
47  net::NSSCertDatabase::GetInstance()->ListCerts(cert_list);
48}
49
50void 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
62static CertLoader* g_cert_loader = NULL;
63
64// static
65void CertLoader::Initialize() {
66  CHECK(!g_cert_loader);
67  g_cert_loader = new CertLoader();
68}
69
70// static
71void CertLoader::Shutdown() {
72  CHECK(g_cert_loader);
73  delete g_cert_loader;
74  g_cert_loader = NULL;
75}
76
77// static
78CertLoader* CertLoader::Get() {
79  CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()";
80  return g_cert_loader;
81}
82
83// static
84bool CertLoader::IsInitialized() {
85  return g_cert_loader;
86}
87
88CertLoader::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
104void CertLoader::InitializeTPMForTest() {
105  initialize_tpm_for_test_ = true;
106}
107
108void CertLoader::SetCryptoTaskRunner(
109    const scoped_refptr<base::SequencedTaskRunner>& crypto_task_runner) {
110  crypto_task_runner_ = crypto_task_runner;
111  MaybeRequestCertificates();
112}
113
114void CertLoader::SetSlowTaskRunnerForTest(
115    const scoped_refptr<base::TaskRunner>& task_runner) {
116  slow_task_runner_for_test_ = task_runner;
117}
118
119CertLoader::~CertLoader() {
120  net::CertDatabase::GetInstance()->RemoveObserver(this);
121  if (LoginState::IsInitialized())
122    LoginState::Get()->RemoveObserver(this);
123}
124
125void CertLoader::AddObserver(CertLoader::Observer* observer) {
126  observers_.AddObserver(observer);
127}
128
129void CertLoader::RemoveObserver(CertLoader::Observer* observer) {
130  observers_.RemoveObserver(observer);
131}
132
133bool CertLoader::CertificatesLoading() const {
134  return certificates_requested_ && !certificates_loaded_;
135}
136
137bool CertLoader::IsHardwareBacked() const {
138  return !tpm_token_name_.empty();
139}
140
141void 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
173void 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
227void 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
238void 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
254std::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
273void 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
285void 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
298void 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
317void 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
327void CertLoader::StartLoadCertificates() {
328  DCHECK(!certificates_loaded_ && !certificates_update_running_);
329  net::CertDatabase::GetInstance()->AddObserver(this);
330  LoadCertificates();
331}
332
333void 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
357void 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
374void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
375  FOR_EACH_OBSERVER(Observer, observers_,
376                    OnCertificatesLoaded(cert_list_, initial_load));
377}
378
379void CertLoader::OnCACertChanged(const net::X509Certificate* cert) {
380  // This is triggered when a CA certificate is modified.
381  VLOG(1) << "OnCACertChanged";
382  LoadCertificates();
383}
384
385void CertLoader::OnCertAdded(const net::X509Certificate* cert) {
386  // This is triggered when a client certificate is added.
387  VLOG(1) << "OnCertAdded";
388  LoadCertificates();
389}
390
391void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
392  VLOG(1) << "OnCertRemoved";
393  LoadCertificates();
394}
395
396void CertLoader::LoggedInStateChanged() {
397  VLOG(1) << "LoggedInStateChanged";
398  MaybeRequestCertificates();
399}
400
401}  // namespace chromeos
402