OLD | NEW |
| (Empty) |
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/network/cert_loader.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/chromeos/chromeos_version.h" | |
10 #include "base/observer_list.h" | |
11 #include "base/strings/string_number_conversions.h" | |
12 #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 | |
22 namespace chromeos { | |
23 | |
24 namespace { | |
25 | |
26 const int64 kInitialRequestDelayMs = 100; | |
27 const int64 kMaxRequestDelayMs = 300000; // 5 minutes | |
28 | |
29 // Calculates the delay before running next attempt to initiatialize the TPM | |
30 // token, if |last_delay| was the last or initial delay. | |
31 base::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 | |
44 void LoadNSSCertificates(net::CertificateList* cert_list) { | |
45 if (base::chromeos::IsRunningOnChromeOS()) | |
46 net::NSSCertDatabase::GetInstance()->ListCerts(cert_list); | |
47 } | |
48 | |
49 } // namespace | |
50 | |
51 CertLoader::CertLoader() | |
52 : certificates_requested_(false), | |
53 certificates_loaded_(false), | |
54 certificates_update_required_(false), | |
55 certificates_update_running_(false), | |
56 tpm_token_state_(TPM_STATE_UNKNOWN), | |
57 tpm_request_delay_( | |
58 base::TimeDelta::FromMilliseconds(kInitialRequestDelayMs)), | |
59 initialize_token_factory_(this), | |
60 update_certificates_factory_(this) { | |
61 net::CertDatabase::GetInstance()->AddObserver(this); | |
62 if (LoginState::IsInitialized()) | |
63 LoginState::Get()->AddObserver(this); | |
64 RequestCertificates(); | |
65 } | |
66 | |
67 CertLoader::~CertLoader() { | |
68 net::CertDatabase::GetInstance()->RemoveObserver(this); | |
69 if (LoginState::IsInitialized()) | |
70 LoginState::Get()->RemoveObserver(this); | |
71 } | |
72 | |
73 void CertLoader::AddObserver(CertLoader::Observer* observer) { | |
74 observers_.AddObserver(observer); | |
75 } | |
76 | |
77 void CertLoader::RemoveObserver(CertLoader::Observer* observer) { | |
78 observers_.RemoveObserver(observer); | |
79 } | |
80 | |
81 bool CertLoader::CertificatesLoading() const { | |
82 return certificates_requested_ && !certificates_loaded_; | |
83 } | |
84 | |
85 bool CertLoader::IsHardwareBacked() const { | |
86 return !tpm_token_name_.empty(); | |
87 } | |
88 | |
89 void CertLoader::RequestCertificates() { | |
90 CHECK(thread_checker_.CalledOnValidThread()); | |
91 const bool logged_in = LoginState::IsInitialized() ? | |
92 LoginState::Get()->IsUserLoggedIn() : false; | |
93 VLOG(1) << "RequestCertificates: " << logged_in; | |
94 if (certificates_requested_ || !logged_in) | |
95 return; | |
96 | |
97 certificates_requested_ = true; | |
98 | |
99 // Ensure we've opened the user's key/certificate database. | |
100 crypto::OpenPersistentNSSDB(); | |
101 if (base::chromeos::IsRunningOnChromeOS()) | |
102 crypto::EnableTPMTokenForNSS(); | |
103 | |
104 // This is the entry point to the TPM token initialization process, which we | |
105 // should do at most once. | |
106 DCHECK(!initialize_token_factory_.HasWeakPtrs()); | |
107 InitializeTokenAndLoadCertificates(); | |
108 } | |
109 | |
110 void CertLoader::InitializeTokenAndLoadCertificates() { | |
111 CHECK(thread_checker_.CalledOnValidThread()); | |
112 VLOG(1) << "InitializeTokenAndLoadCertificates"; | |
113 | |
114 switch (tpm_token_state_) { | |
115 case TPM_STATE_UNKNOWN: { | |
116 DBusThreadManager::Get()->GetCryptohomeClient()->TpmIsEnabled( | |
117 base::Bind(&CertLoader::OnTpmIsEnabled, | |
118 initialize_token_factory_.GetWeakPtr())); | |
119 return; | |
120 } | |
121 case TPM_DISABLED: { | |
122 // TPM is disabled, so proceed with empty tpm token name. | |
123 StartLoadCertificates(); | |
124 return; | |
125 } | |
126 case TPM_ENABLED: { | |
127 DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11IsTpmTokenReady( | |
128 base::Bind(&CertLoader::OnPkcs11IsTpmTokenReady, | |
129 initialize_token_factory_.GetWeakPtr())); | |
130 return; | |
131 } | |
132 case TPM_TOKEN_READY: { | |
133 // Retrieve token_name_ and user_pin_ here since they will never change | |
134 // and CryptohomeClient calls are not thread safe. | |
135 DBusThreadManager::Get()->GetCryptohomeClient()->Pkcs11GetTpmTokenInfo( | |
136 base::Bind(&CertLoader::OnPkcs11GetTpmTokenInfo, | |
137 initialize_token_factory_.GetWeakPtr())); | |
138 return; | |
139 } | |
140 case TPM_TOKEN_INFO_RECEIVED: { | |
141 InitializeNSSForTPMToken(); | |
142 return; | |
143 } | |
144 case TPM_TOKEN_NSS_INITIALIZED: { | |
145 StartLoadCertificates(); | |
146 return; | |
147 } | |
148 } | |
149 } | |
150 | |
151 void CertLoader::RetryTokenInitializationLater() { | |
152 LOG(WARNING) << "Re-Requesting Certificates later."; | |
153 base::MessageLoop::current()->PostDelayedTask( | |
154 FROM_HERE, | |
155 base::Bind(&CertLoader::InitializeTokenAndLoadCertificates, | |
156 initialize_token_factory_.GetWeakPtr()), | |
157 tpm_request_delay_); | |
158 tpm_request_delay_ = GetNextRequestDelayMs(tpm_request_delay_); | |
159 } | |
160 | |
161 // For background see this discussion on dev-tech-crypto.lists.mozilla.org: | |
162 // http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX | |
163 // | |
164 // NOTE: This function relies on the convention that the same PKCS#11 ID | |
165 // is shared between a certificate and its associated private and public | |
166 // keys. I tried to implement this with PK11_GetLowLevelKeyIDForCert(), | |
167 // but that always returns NULL on Chrome OS for me. | |
168 std::string CertLoader::GetPkcs11IdForCert( | |
169 const net::X509Certificate& cert) const { | |
170 if (!IsHardwareBacked()) | |
171 return std::string(); | |
172 | |
173 CERTCertificateStr* cert_handle = cert.os_cert_handle(); | |
174 SECKEYPrivateKey *priv_key = | |
175 PK11_FindKeyByAnyCert(cert_handle, NULL /* wincx */); | |
176 if (!priv_key) | |
177 return std::string(); | |
178 | |
179 // Get the CKA_ID attribute for a key. | |
180 SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key); | |
181 std::string pkcs11_id; | |
182 if (sec_item) { | |
183 pkcs11_id = base::HexEncode(sec_item->data, sec_item->len); | |
184 SECITEM_FreeItem(sec_item, PR_TRUE); | |
185 } | |
186 SECKEY_DestroyPrivateKey(priv_key); | |
187 | |
188 return pkcs11_id; | |
189 } | |
190 | |
191 void CertLoader::OnTpmIsEnabled(DBusMethodCallStatus call_status, | |
192 bool tpm_is_enabled) { | |
193 VLOG(1) << "OnTpmIsEnabled: " << tpm_is_enabled; | |
194 | |
195 if (call_status == DBUS_METHOD_CALL_SUCCESS && tpm_is_enabled) | |
196 tpm_token_state_ = TPM_ENABLED; | |
197 else | |
198 tpm_token_state_ = TPM_DISABLED; | |
199 | |
200 InitializeTokenAndLoadCertificates(); | |
201 } | |
202 | |
203 void CertLoader::OnPkcs11IsTpmTokenReady(DBusMethodCallStatus call_status, | |
204 bool is_tpm_token_ready) { | |
205 VLOG(1) << "OnPkcs11IsTpmTokenReady: " << is_tpm_token_ready; | |
206 | |
207 if (call_status == DBUS_METHOD_CALL_FAILURE || !is_tpm_token_ready) { | |
208 RetryTokenInitializationLater(); | |
209 return; | |
210 } | |
211 | |
212 tpm_token_state_ = TPM_TOKEN_READY; | |
213 InitializeTokenAndLoadCertificates(); | |
214 } | |
215 | |
216 void CertLoader::OnPkcs11GetTpmTokenInfo(DBusMethodCallStatus call_status, | |
217 const std::string& token_name, | |
218 const std::string& user_pin) { | |
219 VLOG(1) << "OnPkcs11GetTpmTokenInfo: " << token_name; | |
220 | |
221 if (call_status == DBUS_METHOD_CALL_FAILURE) { | |
222 RetryTokenInitializationLater(); | |
223 return; | |
224 } | |
225 | |
226 tpm_token_name_ = token_name; | |
227 // TODO(stevenjb): The network code expects a slot ID, not a label. See | |
228 // crbug.com/201101. For now, use a hard coded, well known slot instead. | |
229 const char kHardcodedTpmSlot[] = "0"; | |
230 tpm_token_slot_ = kHardcodedTpmSlot; | |
231 tpm_user_pin_ = user_pin; | |
232 tpm_token_state_ = TPM_TOKEN_INFO_RECEIVED; | |
233 | |
234 InitializeTokenAndLoadCertificates(); | |
235 } | |
236 | |
237 void CertLoader::InitializeNSSForTPMToken() { | |
238 VLOG(1) << "InitializeNSSForTPMToken"; | |
239 | |
240 if (base::chromeos::IsRunningOnChromeOS() && | |
241 !crypto::InitializeTPMToken(tpm_token_name_, tpm_user_pin_)) { | |
242 RetryTokenInitializationLater(); | |
243 return; | |
244 } | |
245 | |
246 tpm_token_state_ = TPM_TOKEN_NSS_INITIALIZED; | |
247 InitializeTokenAndLoadCertificates(); | |
248 } | |
249 | |
250 void CertLoader::StartLoadCertificates() { | |
251 VLOG(1) << "StartLoadCertificates"; | |
252 | |
253 if (certificates_update_running_) { | |
254 certificates_update_required_ = true; | |
255 return; | |
256 } | |
257 | |
258 net::CertificateList* cert_list = new net::CertificateList; | |
259 certificates_update_running_ = true; | |
260 certificates_update_required_ = false; | |
261 base::WorkerPool::GetTaskRunner(true /* task_is_slow */)-> | |
262 PostTaskAndReply( | |
263 FROM_HERE, | |
264 base::Bind(LoadNSSCertificates, cert_list), | |
265 base::Bind(&CertLoader::UpdateCertificates, | |
266 update_certificates_factory_.GetWeakPtr(), | |
267 base::Owned(cert_list))); | |
268 } | |
269 | |
270 void CertLoader::UpdateCertificates(net::CertificateList* cert_list) { | |
271 CHECK(thread_checker_.CalledOnValidThread()); | |
272 DCHECK(certificates_update_running_); | |
273 VLOG(1) << "UpdateCertificates: " << cert_list->size(); | |
274 | |
275 // Ignore any existing certificates. | |
276 cert_list_.swap(*cert_list); | |
277 | |
278 NotifyCertificatesLoaded(!certificates_loaded_); | |
279 certificates_loaded_ = true; | |
280 | |
281 certificates_update_running_ = false; | |
282 if (certificates_update_required_) | |
283 StartLoadCertificates(); | |
284 } | |
285 | |
286 void CertLoader::NotifyCertificatesLoaded(bool initial_load) { | |
287 FOR_EACH_OBSERVER(Observer, observers_, | |
288 OnCertificatesLoaded(cert_list_, initial_load)); | |
289 } | |
290 | |
291 void CertLoader::OnCertTrustChanged(const net::X509Certificate* cert) { | |
292 } | |
293 | |
294 void CertLoader::OnCertAdded(const net::X509Certificate* cert) { | |
295 VLOG(1) << "OnCertAdded"; | |
296 StartLoadCertificates(); | |
297 } | |
298 | |
299 void CertLoader::OnCertRemoved(const net::X509Certificate* cert) { | |
300 VLOG(1) << "OnCertRemoved"; | |
301 StartLoadCertificates(); | |
302 } | |
303 | |
304 void CertLoader::LoggedInStateChanged(LoginState::LoggedInState state) { | |
305 VLOG(1) << "LoggedInStateChanged: " << state; | |
306 RequestCertificates(); | |
307 } | |
308 | |
309 } // namespace chromeos | |
OLD | NEW |