blob: 2235291692abab2398c1df12511ffe0b96f433a6 [file] [log] [blame]
[email protected]75d93a82013-05-06 19:51:561// 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/network/cert_loader.h"
6
7#include <algorithm>
8
9#include "base/chromeos/chromeos_version.h"
10#include "base/observer_list.h"
[email protected]53419052013-05-14 04:37:5611#include "base/strings/string_number_conversions.h"
[email protected]75d93a82013-05-06 19:51:5612#include "base/task_runner_util.h"
13#include "base/threading/worker_pool.h"
14#include "chromeos/dbus/cryptohome_client.h"
15#include "chromeos/dbus/dbus_thread_manager.h"
16#include "crypto/encryptor.h"
17#include "crypto/nss_util.h"
18#include "crypto/sha2.h"
19#include "crypto/symmetric_key.h"
20#include "net/cert/nss_cert_database.h"
21
22namespace chromeos {
23
24namespace {
25
[email protected]7d3d0c02013-05-22 12:32:1326const int64 kInitialRequestDelayMs = 100;
27const int64 kMaxRequestDelayMs = 300000; // 5 minutes
[email protected]75d93a82013-05-06 19:51:5628
[email protected]7d3d0c02013-05-22 12:32:1329// Calculates the delay before running next attempt to initiatialize the TPM
30// token, if |last_delay| was the last or initial delay.
31base::TimeDelta GetNextRequestDelayMs(base::TimeDelta last_delay) {
32 // This implements an exponential backoff, as we don't know in which order of
33 // magnitude the TPM token changes it's state.
34 base::TimeDelta next_delay = last_delay * 2;
35
36 // Cap the delay to prevent an overflow. This threshold is arbitrarily chosen.
37 const base::TimeDelta max_delay =
38 base::TimeDelta::FromMilliseconds(kMaxRequestDelayMs);
39 if (next_delay > max_delay)
40 next_delay = max_delay;
41 return next_delay;
42}
43
44void LoadNSSCertificates(net::CertificateList* cert_list) {
[email protected]53419052013-05-14 04:37:5645 if (base::chromeos::IsRunningOnChromeOS())
46 net::NSSCertDatabase::GetInstance()->ListCerts(cert_list);
[email protected]75d93a82013-05-06 19:51:5647}
48
49} // namespace
50
51static CertLoader* g_cert_loader = NULL;
52
53// static
54void CertLoader::Initialize() {
55 CHECK(!g_cert_loader);
56 g_cert_loader = new CertLoader();
57}
58
59// static
60void CertLoader::Shutdown() {
61 CHECK(g_cert_loader);
62 delete g_cert_loader;
63 g_cert_loader = NULL;
64}
65
66// static
67CertLoader* CertLoader::Get() {
68 CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()";
69 return g_cert_loader;
70}
71
72// static
73bool CertLoader::IsInitialized() {
74 return g_cert_loader;
75}
76
77CertLoader::CertLoader()
[email protected]7d3d0c02013-05-22 12:32:1378 : certificates_requested_(false),
[email protected]75d93a82013-05-06 19:51:5679 certificates_loaded_(false),
[email protected]7d3d0c02013-05-22 12:32:1380 certificates_update_required_(false),
81 certificates_update_running_(false),
82 tpm_token_state_(TPM_STATE_UNKNOWN),
83 tpm_request_delay_(
84 base::TimeDelta::FromMilliseconds(kInitialRequestDelayMs)),
85 initialize_token_factory_(this),
86 update_certificates_factory_(this) {
[email protected]75d93a82013-05-06 19:51:5687 net::CertDatabase::GetInstance()->AddObserver(this);
88 LoginState::Get()->AddObserver(this);
[email protected]7d3d0c02013-05-22 12:32:1389 RequestCertificates();
[email protected]75d93a82013-05-06 19:51:5690}
91
92CertLoader::~CertLoader() {
[email protected]75d93a82013-05-06 19:51:5693 net::CertDatabase::GetInstance()->RemoveObserver(this);
94 LoginState::Get()->RemoveObserver(this);
95}
96
97void CertLoader::AddObserver(CertLoader::Observer* observer) {
98 observers_.AddObserver(observer);
99}
100
101void CertLoader::RemoveObserver(CertLoader::Observer* observer) {
102 observers_.RemoveObserver(observer);
103}
104
105bool CertLoader::CertificatesLoading() const {
106 return certificates_requested_ && !certificates_loaded_;
107}
108
109bool CertLoader::IsHardwareBacked() const {
110 return !tpm_token_name_.empty();
111}
112
113void CertLoader::RequestCertificates() {
114 CHECK(thread_checker_.CalledOnValidThread());
115 VLOG(1) << "RequestCertificates: " << LoginState::Get()->IsUserLoggedIn();
[email protected]7d3d0c02013-05-22 12:32:13116 if (certificates_requested_ || !LoginState::Get()->IsUserLoggedIn())
117 return;
[email protected]75d93a82013-05-06 19:51:56118
119 certificates_requested_ = true;
120
[email protected]7d3d0c02013-05-22 12:32:13121 // Ensure we've opened the user's key/certificate database.
122 crypto::OpenPersistentNSSDB();
123 if (base::chromeos::IsRunningOnChromeOS())
124 crypto::EnableTPMTokenForNSS();
[email protected]75d93a82013-05-06 19:51:56125
[email protected]7d3d0c02013-05-22 12:32:13126 // This is the entry point to the TPM token initialization process, which we
127 // should do at most once.
128 DCHECK(!initialize_token_factory_.HasWeakPtrs());
129 InitializeTokenAndLoadCertificates();
130}
131
132void CertLoader::InitializeTokenAndLoadCertificates() {
133 CHECK(thread_checker_.CalledOnValidThread());
134 VLOG(1) << "InitializeTokenAndLoadCertificates";
135
136 switch(tpm_token_state_) {
137 case TPM_STATE_UNKNOWN: {
138 DBusThreadManager::Get()->GetCryptohomeClient()->TpmIsEnabled(
139 base::Bind(&CertLoader::OnTpmIsEnabled,
140 initialize_token_factory_.GetWeakPtr()));
141 return;
142 }
143 case TPM_DISABLED: {
144 // TPM is disabled, so proceed with empty tpm token name.
145 StartLoadCertificates();
146 return;
147 }
148 case TPM_ENABLED: {
149 DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11IsTpmTokenReady(
150 base::Bind(&CertLoader::OnPkcs11IsTpmTokenReady,
151 initialize_token_factory_.GetWeakPtr()));
152 return;
153 }
154 case TPM_TOKEN_READY: {
155 // Retrieve token_name_ and user_pin_ here since they will never change
156 // and CryptohomeClient calls are not thread safe.
157 DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11GetTpmTokenInfo(
158 base::Bind(&CertLoader::OnPkcs11GetTpmTokenInfo,
159 initialize_token_factory_.GetWeakPtr()));
160 return;
161 }
162 case TPM_TOKEN_INFO_RECEIVED: {
163 InitializeNSSForTPMToken();
164 return;
165 }
166 case TPM_TOKEN_NSS_INITIALIZED: {
167 StartLoadCertificates();
168 return;
169 }
170 }
171}
172
173void CertLoader::RetryTokenInitializationLater() {
174 LOG(WARNING) << "Re-Requesting Certificates later.";
175 MessageLoop::current()->PostDelayedTask(
176 FROM_HERE,
177 base::Bind(&CertLoader::InitializeTokenAndLoadCertificates,
178 initialize_token_factory_.GetWeakPtr()),
179 tpm_request_delay_);
180 tpm_request_delay_ = GetNextRequestDelayMs(tpm_request_delay_);
[email protected]75d93a82013-05-06 19:51:56181}
182
[email protected]53419052013-05-14 04:37:56183// For background see this discussion on dev-tech-crypto.lists.mozilla.org:
184// http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX
185//
186// NOTE: This function relies on the convention that the same PKCS#11 ID
187// is shared between a certificate and its associated private and public
188// keys. I tried to implement this with PK11_GetLowLevelKeyIDForCert(),
189// but that always returns NULL on Chrome OS for me.
190std::string CertLoader::GetPkcs11IdForCert(
191 const net::X509Certificate& cert) const {
192 if (!IsHardwareBacked())
193 return std::string();
194
195 CERTCertificateStr* cert_handle = cert.os_cert_handle();
196 SECKEYPrivateKey *priv_key =
197 PK11_FindKeyByAnyCert(cert_handle, NULL /* wincx */);
198 if (!priv_key)
199 return std::string();
200
201 // Get the CKA_ID attribute for a key.
202 SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key);
203 std::string pkcs11_id;
204 if (sec_item) {
205 pkcs11_id = base::HexEncode(sec_item->data, sec_item->len);
206 SECITEM_FreeItem(sec_item, PR_TRUE);
207 }
208 SECKEY_DestroyPrivateKey(priv_key);
209
210 return pkcs11_id;
211}
212
[email protected]75d93a82013-05-06 19:51:56213void CertLoader::OnTpmIsEnabled(DBusMethodCallStatus call_status,
214 bool tpm_is_enabled) {
215 VLOG(1) << "OnTpmIsEnabled: " << tpm_is_enabled;
[email protected]7d3d0c02013-05-22 12:32:13216
217 if (call_status == DBUS_METHOD_CALL_SUCCESS && tpm_is_enabled)
218 tpm_token_state_ = TPM_ENABLED;
219 else
220 tpm_token_state_ = TPM_DISABLED;
221
222 InitializeTokenAndLoadCertificates();
[email protected]75d93a82013-05-06 19:51:56223}
224
225void CertLoader::OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status,
226 bool is_tpm_token_ready) {
227 VLOG(1) << "OnPkcs11IsTpmTokenReady: " << is_tpm_token_ready;
[email protected]7d3d0c02013-05-22 12:32:13228
229 if (call_status == DBUS_METHOD_CALL_FAILURE || !is_tpm_token_ready) {
230 RetryTokenInitializationLater();
[email protected]75d93a82013-05-06 19:51:56231 return;
232 }
233
[email protected]7d3d0c02013-05-22 12:32:13234 tpm_token_state_ = TPM_TOKEN_READY;
235 InitializeTokenAndLoadCertificates();
[email protected]75d93a82013-05-06 19:51:56236}
237
238void CertLoader::OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status,
239 const std::string& token_name,
240 const std::string& user_pin) {
241 VLOG(1) << "OnPkcs11GetTpmTokenInfo: " << token_name;
[email protected]7d3d0c02013-05-22 12:32:13242
243 if (call_status == DBUS_METHOD_CALL_FAILURE) {
244 RetryTokenInitializationLater();
[email protected]75d93a82013-05-06 19:51:56245 return;
246 }
[email protected]7d3d0c02013-05-22 12:32:13247
[email protected]75d93a82013-05-06 19:51:56248 tpm_token_name_ = token_name;
249 // TODO(stevenjb): The network code expects a slot ID, not a label. See
250 // crbug.com/201101. For now, use a hard coded, well known slot instead.
251 const char kHardcodedTpmSlot[] = "0";
252 tpm_token_slot_ = kHardcodedTpmSlot;
253 tpm_user_pin_ = user_pin;
[email protected]7d3d0c02013-05-22 12:32:13254 tpm_token_state_ = TPM_TOKEN_INFO_RECEIVED;
[email protected]75d93a82013-05-06 19:51:56255
[email protected]7d3d0c02013-05-22 12:32:13256 InitializeTokenAndLoadCertificates();
[email protected]75d93a82013-05-06 19:51:56257}
258
[email protected]7d3d0c02013-05-22 12:32:13259void CertLoader::InitializeNSSForTPMToken() {
260 VLOG(1) << "InitializeNSSForTPMToken";
261
[email protected]53419052013-05-14 04:37:56262 if (base::chromeos::IsRunningOnChromeOS() &&
263 !crypto::InitializeTPMToken(tpm_token_name_, tpm_user_pin_)) {
[email protected]7d3d0c02013-05-22 12:32:13264 RetryTokenInitializationLater();
[email protected]75d93a82013-05-06 19:51:56265 return;
266 }
[email protected]7d3d0c02013-05-22 12:32:13267
268 tpm_token_state_ = TPM_TOKEN_NSS_INITIALIZED;
269 InitializeTokenAndLoadCertificates();
[email protected]75d93a82013-05-06 19:51:56270}
271
272void CertLoader::StartLoadCertificates() {
[email protected]7d3d0c02013-05-22 12:32:13273 VLOG(1) << "StartLoadCertificates";
274
275 if (certificates_update_running_) {
276 certificates_update_required_ = true;
277 return;
278 }
279
280 net::CertificateList* cert_list = new net::CertificateList;
281 certificates_update_running_ = true;
282 base::WorkerPool::GetTaskRunner(true /* task_is_slow */)->
283 PostTaskAndReply(
284 FROM_HERE,
285 base::Bind(LoadNSSCertificates, cert_list),
286 base::Bind(&CertLoader::UpdateCertificates,
287 update_certificates_factory_.GetWeakPtr(),
288 base::Owned(cert_list)));
[email protected]75d93a82013-05-06 19:51:56289}
290
291void CertLoader::UpdateCertificates(net::CertificateList* cert_list) {
292 CHECK(thread_checker_.CalledOnValidThread());
[email protected]7d3d0c02013-05-22 12:32:13293 DCHECK(certificates_update_running_);
294 VLOG(1) << "UpdateCertificates: " << cert_list->size();
[email protected]75d93a82013-05-06 19:51:56295
[email protected]7d3d0c02013-05-22 12:32:13296 // Ignore any existing certificates.
[email protected]75d93a82013-05-06 19:51:56297 cert_list_.swap(*cert_list);
298
[email protected]7d3d0c02013-05-22 12:32:13299 NotifyCertificatesLoaded(!certificates_loaded_);
300 certificates_loaded_ = true;
[email protected]75d93a82013-05-06 19:51:56301
[email protected]7d3d0c02013-05-22 12:32:13302 certificates_update_running_ = false;
303 if (certificates_update_required_)
304 StartLoadCertificates();
[email protected]75d93a82013-05-06 19:51:56305}
306
307void CertLoader::NotifyCertificatesLoaded(bool initial_load) {
308 FOR_EACH_OBSERVER(Observer, observers_,
309 OnCertificatesLoaded(cert_list_, initial_load));
310}
311
312void CertLoader::OnCertTrustChanged(const net::X509Certificate* cert) {
313}
314
315void CertLoader::OnCertAdded(const net::X509Certificate* cert) {
[email protected]7d3d0c02013-05-22 12:32:13316 VLOG(1) << "OnCertAdded";
[email protected]75d93a82013-05-06 19:51:56317 StartLoadCertificates();
318}
319
320void CertLoader::OnCertRemoved(const net::X509Certificate* cert) {
[email protected]7d3d0c02013-05-22 12:32:13321 VLOG(1) << "OnCertRemoved";
[email protected]75d93a82013-05-06 19:51:56322 StartLoadCertificates();
323}
324
325void CertLoader::LoggedInStateChanged(LoginState::LoggedInState state) {
326 VLOG(1) << "LoggedInStateChanged: " << state;
[email protected]7d3d0c02013-05-22 12:32:13327 RequestCertificates();
[email protected]75d93a82013-05-06 19:51:56328}
329
330} // namespace chromeos