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
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  crypto::OpenPersistentNSSDB();
56  crypto::EnableTPMTokenForNSS();
57}
58
59}  // namespace
60
61static CertLoader* g_cert_loader = NULL;
62
63// static
64void CertLoader::Initialize() {
65  CHECK(!g_cert_loader);
66  g_cert_loader = new CertLoader();
67  g_cert_loader->Init();
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    : 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
100void CertLoader::Init() {
101  net::CertDatabase::GetInstance()->AddObserver(this);
102  if (LoginState::IsInitialized())
103    LoginState::Get()->AddObserver(this);
104}
105
106void CertLoader::SetCryptoTaskRunner(
107    const scoped_refptr<base::SequencedTaskRunner>& crypto_task_runner) {
108  crypto_task_runner_ = crypto_task_runner;
109  MaybeRequestCertificates();
110}
111
112void CertLoader::SetSlowTaskRunnerForTest(
113    const scoped_refptr<base::TaskRunner>& task_runner) {
114  slow_task_runner_for_test_ = task_runner;
115}
116
117CertLoader::~CertLoader() {
118  net::CertDatabase::GetInstance()->RemoveObserver(this);
119  if (LoginState::IsInitialized())
120    LoginState::Get()->RemoveObserver(this);
121}
122
123void CertLoader::AddObserver(CertLoader::Observer* observer) {
124  observers_.AddObserver(observer);
125}
126
127void CertLoader::RemoveObserver(CertLoader::Observer* observer) {
128  observers_.RemoveObserver(observer);
129}
130
131bool CertLoader::CertificatesLoading() const {
132  return certificates_requested_ && !certificates_loaded_;
133}
134
135bool CertLoader::IsHardwareBacked() const {
136  return !tpm_token_name_.empty();
137}
138
139void 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
163void 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
224void 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
235void 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
251std::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
270void 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
282void 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
295void 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
316void 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
326void 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
350void 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
367void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
368  FOR_EACH_OBSERVER(Observer, observers_,
369                    OnCertificatesLoaded(cert_list_, initial_load));
370}
371
372void CertLoader::OnCertTrustChanged(const net::X509Certificate* cert) {
373}
374
375void CertLoader::OnCertAdded(const net::X509Certificate* cert) {
376  VLOG(1) << "OnCertAdded";
377  StartLoadCertificates();
378}
379
380void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
381  VLOG(1) << "OnCertRemoved";
382  StartLoadCertificates();
383}
384
385void CertLoader::LoggedInStateChanged(LoginState::LoggedInState state) {
386  VLOG(1) << "LoggedInStateChanged: " << state;
387  MaybeRequestCertificates();
388}
389
390}  // namespace chromeos
391