OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chromeos/cert_loader.h" | 5 #include "chromeos/cert_loader.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <utility> | 8 #include <utility> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/location.h" | 11 #include "base/location.h" |
| 12 #include "base/logging.h" |
12 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
13 #include "base/strings/string_number_conversions.h" | 14 #include "base/strings/string_number_conversions.h" |
14 #include "base/task_scheduler/post_task.h" | 15 #include "base/task_scheduler/post_task.h" |
15 #include "crypto/nss_util.h" | 16 #include "crypto/nss_util.h" |
16 #include "crypto/scoped_nss_types.h" | 17 #include "crypto/scoped_nss_types.h" |
| 18 #include "net/cert/cert_database.h" |
17 #include "net/cert/nss_cert_database.h" | 19 #include "net/cert/nss_cert_database.h" |
18 #include "net/cert/nss_cert_database_chromeos.h" | 20 #include "net/cert/nss_cert_database_chromeos.h" |
19 #include "net/cert/x509_certificate.h" | 21 #include "net/cert/x509_certificate.h" |
20 | 22 |
21 namespace chromeos { | 23 namespace chromeos { |
22 | 24 |
| 25 // Caches certificates from a NSSCertDatabase. Handles reloading of certificates |
| 26 // on update notifications and provides status flags (loading / loaded). |
| 27 // CertLoader can use multiple CertCaches to combine certificates from multiple |
| 28 // sources. |
| 29 class CertLoader::CertCache : public net::CertDatabase::Observer { |
| 30 public: |
| 31 explicit CertCache(base::RepeatingClosure certificates_updated_callback) |
| 32 : certificates_updated_callback_(certificates_updated_callback), |
| 33 weak_factory_(this) {} |
| 34 |
| 35 ~CertCache() override { |
| 36 net::CertDatabase::GetInstance()->RemoveObserver(this); |
| 37 } |
| 38 |
| 39 void SetNSSDB(net::NSSCertDatabase* nss_database) { |
| 40 CHECK(!nss_database_); |
| 41 nss_database_ = nss_database; |
| 42 |
| 43 // Start observing cert database for changes. |
| 44 // Observing net::CertDatabase is preferred over observing |nss_database_| |
| 45 // directly, as |nss_database_| observers receive only events generated |
| 46 // directly by |nss_database_|, so they may miss a few relevant ones. |
| 47 // TODO(tbarzic): Once singleton NSSCertDatabase is removed, investigate if |
| 48 // it would be OK to observe |nss_database_| directly; or change |
| 49 // NSSCertDatabase to send notification on all relevant changes. |
| 50 net::CertDatabase::GetInstance()->AddObserver(this); |
| 51 |
| 52 LoadCertificates(); |
| 53 } |
| 54 |
| 55 net::NSSCertDatabase* nss_database() { return nss_database_; } |
| 56 |
| 57 // net::CertDatabase::Observer |
| 58 void OnCertDBChanged() override { |
| 59 VLOG(1) << "OnCertDBChanged"; |
| 60 LoadCertificates(); |
| 61 } |
| 62 |
| 63 const net::CertificateList& cert_list() const { return cert_list_; } |
| 64 |
| 65 bool initial_load_running() const { |
| 66 return nss_database_ && !initial_load_finished_; |
| 67 } |
| 68 |
| 69 bool initial_load_finished() const { return initial_load_finished_; } |
| 70 |
| 71 // Returns true if the underlying NSSCertDatabase has access to the system |
| 72 // slot. |
| 73 bool has_system_certificates() const { return has_system_certificates_; } |
| 74 |
| 75 private: |
| 76 // Trigger a certificate load. If a certificate loading task is already in |
| 77 // progress, will start a reload once the current task is finished. |
| 78 void LoadCertificates() { |
| 79 CHECK(thread_checker_.CalledOnValidThread()); |
| 80 VLOG(1) << "LoadCertificates: " << certificates_update_running_; |
| 81 |
| 82 if (certificates_update_running_) { |
| 83 certificates_update_required_ = true; |
| 84 return; |
| 85 } |
| 86 |
| 87 certificates_update_running_ = true; |
| 88 certificates_update_required_ = false; |
| 89 |
| 90 if (nss_database_) { |
| 91 has_system_certificates_ = |
| 92 static_cast<bool>(nss_database_->GetSystemSlot()); |
| 93 nss_database_->ListCerts(base::Bind(&CertCache::UpdateCertificates, |
| 94 weak_factory_.GetWeakPtr())); |
| 95 } |
| 96 } |
| 97 |
| 98 // Called if a certificate load task is finished. |
| 99 void UpdateCertificates(std::unique_ptr<net::CertificateList> cert_list) { |
| 100 CHECK(thread_checker_.CalledOnValidThread()); |
| 101 DCHECK(certificates_update_running_); |
| 102 VLOG(1) << "UpdateCertificates: " << cert_list->size(); |
| 103 |
| 104 // Ignore any existing certificates. |
| 105 cert_list_ = std::move(*cert_list); |
| 106 |
| 107 initial_load_finished_ = true; |
| 108 certificates_updated_callback_.Run(); |
| 109 |
| 110 certificates_update_running_ = false; |
| 111 if (certificates_update_required_) |
| 112 LoadCertificates(); |
| 113 } |
| 114 |
| 115 // To be called when certificates have been updated. |
| 116 base::RepeatingClosure certificates_updated_callback_; |
| 117 |
| 118 bool has_system_certificates_ = false; |
| 119 |
| 120 // This is true after certificates have been loaded initially. |
| 121 bool initial_load_finished_ = false; |
| 122 // This is true if a notification about certificate DB changes arrived while |
| 123 // loading certificates and means that we will have to trigger another |
| 124 // certificates load after that. |
| 125 bool certificates_update_required_ = false; |
| 126 // This is true while certificates are being loaded. |
| 127 bool certificates_update_running_ = false; |
| 128 |
| 129 // The NSS certificate database from which the certificates should be loaded. |
| 130 net::NSSCertDatabase* nss_database_ = nullptr; |
| 131 |
| 132 // Cached Certificates loaded from the database. |
| 133 net::CertificateList cert_list_; |
| 134 |
| 135 base::ThreadChecker thread_checker_; |
| 136 |
| 137 base::WeakPtrFactory<CertCache> weak_factory_; |
| 138 |
| 139 DISALLOW_COPY_AND_ASSIGN(CertCache); |
| 140 }; |
| 141 |
23 namespace { | 142 namespace { |
24 | 143 |
25 // Checks if |certificate| is on the given |slot|. | 144 // Checks if |certificate| is on the given |slot|. |
26 bool IsCertificateOnSlot(const net::X509Certificate* certificate, | 145 bool IsCertificateOnSlot(const net::X509Certificate* certificate, |
27 PK11SlotInfo* slot) { | 146 PK11SlotInfo* slot) { |
28 crypto::ScopedPK11SlotList slots_for_cert( | 147 crypto::ScopedPK11SlotList slots_for_cert( |
29 PK11_GetAllSlotsForCert(certificate->os_cert_handle(), nullptr)); | 148 PK11_GetAllSlotsForCert(certificate->os_cert_handle(), nullptr)); |
30 if (!slots_for_cert) | 149 if (!slots_for_cert) |
31 return false; | 150 return false; |
32 | 151 |
33 for (PK11SlotListElement* slot_element = | 152 for (PK11SlotListElement* slot_element = |
34 PK11_GetFirstSafe(slots_for_cert.get()); | 153 PK11_GetFirstSafe(slots_for_cert.get()); |
35 slot_element; slot_element = PK11_GetNextSafe(slots_for_cert.get(), | 154 slot_element; slot_element = PK11_GetNextSafe(slots_for_cert.get(), |
36 slot_element, PR_FALSE)) { | 155 slot_element, PR_FALSE)) { |
37 if (slot_element->slot == slot) { | 156 if (slot_element->slot == slot) { |
38 // All previously visited elements have been freed by PK11_GetNextSafe, | 157 // All previously visited elements have been freed by PK11_GetNextSafe, |
39 // but we're not calling that for the last one, so free it explicitly. | 158 // but we're not calling that for the last one, so free it explicitly. |
40 // The slots_for_cert list itself will be freed because ScopedPK11SlotList | 159 // The slots_for_cert list itself will be freed because ScopedPK11SlotList |
41 // is a unique_ptr. | 160 // is a unique_ptr. |
42 PK11_FreeSlotListElement(slots_for_cert.get(), slot_element); | 161 PK11_FreeSlotListElement(slots_for_cert.get(), slot_element); |
43 return true; | 162 return true; |
44 } | 163 } |
45 } | 164 } |
46 return false; | 165 return false; |
47 } | 166 } |
48 | 167 |
49 // Goes through all certificates in |all_certs| and copies those certificates | 168 // Goes through all certificates in |all_certs| and copies those certificates |
50 // which are on |system_slot| to a new list. | 169 // which are on |system_slot| to a new list. |
51 std::unique_ptr<net::CertificateList> FilterSystemTokenCertificates( | 170 net::CertificateList FilterSystemTokenCertificates( |
52 const net::CertificateList* all_certs, | 171 net::CertificateList certs, |
53 crypto::ScopedPK11Slot system_slot) { | 172 crypto::ScopedPK11Slot system_slot) { |
54 VLOG(1) << "FilterSystemTokenCertificates"; | 173 VLOG(1) << "FilterSystemTokenCertificates"; |
55 std::unique_ptr<net::CertificateList> system_certs = | |
56 base::MakeUnique<net::CertificateList>(); | |
57 if (!system_slot) | 174 if (!system_slot) |
58 return system_certs; | 175 return net::CertificateList(); |
59 | 176 |
60 // Extract certificates which are in the system token into the | 177 // Only keep certificates which are on the |system_slot|. |
61 // |system_certs_| sublist. | 178 PK11SlotInfo* system_slot_ptr = system_slot.get(); |
62 for (const auto& cert : *all_certs) { | 179 certs.erase( |
63 if (IsCertificateOnSlot(cert.get(), system_slot.get())) { | 180 std::remove_if( |
64 system_certs->push_back(cert); | 181 certs.begin(), certs.end(), |
65 } | 182 [system_slot_ptr](const scoped_refptr<net::X509Certificate>& cert) { |
66 } | 183 return !IsCertificateOnSlot(cert.get(), system_slot_ptr); |
67 return system_certs; | 184 }), |
| 185 certs.end()); |
| 186 return certs; |
68 } | 187 } |
69 | 188 |
70 } // namespace | 189 } // namespace |
71 | 190 |
72 static CertLoader* g_cert_loader = nullptr; | 191 static CertLoader* g_cert_loader = nullptr; |
73 static bool g_force_hardware_backed_for_test = false; | 192 static bool g_force_hardware_backed_for_test = false; |
74 | 193 |
75 // static | 194 // static |
76 void CertLoader::Initialize() { | 195 void CertLoader::Initialize() { |
77 CHECK(!g_cert_loader); | 196 CHECK(!g_cert_loader); |
(...skipping 11 matching lines...) Expand all Loading... |
89 CertLoader* CertLoader::Get() { | 208 CertLoader* CertLoader::Get() { |
90 CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()"; | 209 CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()"; |
91 return g_cert_loader; | 210 return g_cert_loader; |
92 } | 211 } |
93 | 212 |
94 // static | 213 // static |
95 bool CertLoader::IsInitialized() { | 214 bool CertLoader::IsInitialized() { |
96 return g_cert_loader; | 215 return g_cert_loader; |
97 } | 216 } |
98 | 217 |
99 CertLoader::CertLoader() | 218 CertLoader::CertLoader() : pending_initial_load_(true), weak_factory_(this) { |
100 : certificates_loaded_(false), | 219 system_cert_cache_ = base::MakeUnique<CertCache>( |
101 certificates_update_required_(false), | 220 base::BindRepeating(&CertLoader::CacheUpdated, base::Unretained(this))); |
102 certificates_update_running_(false), | 221 user_cert_cache_ = base::MakeUnique<CertCache>( |
103 database_(nullptr), | 222 base::BindRepeating(&CertLoader::CacheUpdated, base::Unretained(this))); |
104 all_certs_(base::MakeUnique<net::CertificateList>()), | 223 } |
105 weak_factory_(this) {} | |
106 | 224 |
107 CertLoader::~CertLoader() { | 225 CertLoader::~CertLoader() { |
108 net::CertDatabase::GetInstance()->RemoveObserver(this); | |
109 } | 226 } |
110 | 227 |
111 void CertLoader::StartWithNSSDB(net::NSSCertDatabase* database) { | 228 void CertLoader::SetSystemNSSDB(net::NSSCertDatabase* system_slot_database) { |
112 CHECK(!database_); | 229 system_cert_cache_->SetNSSDB(system_slot_database); |
113 database_ = database; | 230 } |
114 | 231 |
115 // Start observing cert database for changes. | 232 void CertLoader::SetUserNSSDB(net::NSSCertDatabase* user_database) { |
116 // Observing net::CertDatabase is preferred over observing |database_| | 233 user_cert_cache_->SetNSSDB(user_database); |
117 // directly, as |database_| observers receive only events generated directly | |
118 // by |database_|, so they may miss a few relevant ones. | |
119 // TODO(tbarzic): Once singleton NSSCertDatabase is removed, investigate if | |
120 // it would be OK to observe |database_| directly; or change NSSCertDatabase | |
121 // to send notification on all relevant changes. | |
122 net::CertDatabase::GetInstance()->AddObserver(this); | |
123 | |
124 LoadCertificates(); | |
125 } | 234 } |
126 | 235 |
127 void CertLoader::AddObserver(CertLoader::Observer* observer) { | 236 void CertLoader::AddObserver(CertLoader::Observer* observer) { |
128 observers_.AddObserver(observer); | 237 observers_.AddObserver(observer); |
129 } | 238 } |
130 | 239 |
131 void CertLoader::RemoveObserver(CertLoader::Observer* observer) { | 240 void CertLoader::RemoveObserver(CertLoader::Observer* observer) { |
132 observers_.RemoveObserver(observer); | 241 observers_.RemoveObserver(observer); |
133 } | 242 } |
134 | 243 |
135 // static | 244 // static |
136 bool CertLoader::IsCertificateHardwareBacked(const net::X509Certificate* cert) { | 245 bool CertLoader::IsCertificateHardwareBacked(const net::X509Certificate* cert) { |
137 if (g_force_hardware_backed_for_test) | 246 if (g_force_hardware_backed_for_test) |
138 return true; | 247 return true; |
139 PK11SlotInfo* slot = cert->os_cert_handle()->slot; | 248 PK11SlotInfo* slot = cert->os_cert_handle()->slot; |
140 return slot && PK11_IsHW(slot); | 249 return slot && PK11_IsHW(slot); |
141 } | 250 } |
142 | 251 |
143 bool CertLoader::CertificatesLoading() const { | 252 bool CertLoader::initial_load_of_any_database_running() const { |
144 return database_ && !certificates_loaded_; | 253 return system_cert_cache_->initial_load_running() || |
| 254 user_cert_cache_->initial_load_running(); |
| 255 } |
| 256 |
| 257 bool CertLoader::initial_load_finished() const { |
| 258 return system_cert_cache_->initial_load_finished() || |
| 259 user_cert_cache_->initial_load_finished(); |
145 } | 260 } |
146 | 261 |
147 // static | 262 // static |
148 void CertLoader::ForceHardwareBackedForTesting() { | 263 void CertLoader::ForceHardwareBackedForTesting() { |
149 g_force_hardware_backed_for_test = true; | 264 g_force_hardware_backed_for_test = true; |
150 } | 265 } |
151 | 266 |
152 // static | 267 // static |
153 // | 268 // |
154 // For background see this discussion on dev-tech-crypto.lists.mozilla.org: | 269 // For background see this discussion on dev-tech-crypto.lists.mozilla.org: |
(...skipping 21 matching lines...) Expand all Loading... |
176 std::string pkcs11_id; | 291 std::string pkcs11_id; |
177 if (sec_item) { | 292 if (sec_item) { |
178 pkcs11_id = base::HexEncode(sec_item->data, sec_item->len); | 293 pkcs11_id = base::HexEncode(sec_item->data, sec_item->len); |
179 SECITEM_FreeItem(sec_item, PR_TRUE); | 294 SECITEM_FreeItem(sec_item, PR_TRUE); |
180 } | 295 } |
181 SECKEY_DestroyPrivateKey(priv_key); | 296 SECKEY_DestroyPrivateKey(priv_key); |
182 | 297 |
183 return pkcs11_id; | 298 return pkcs11_id; |
184 } | 299 } |
185 | 300 |
186 void CertLoader::LoadCertificates() { | 301 void CertLoader::CacheUpdated() { |
187 DCHECK(thread_checker_.CalledOnValidThread()); | 302 DCHECK(thread_checker_.CalledOnValidThread()); |
188 VLOG(1) << "LoadCertificates: " << certificates_update_running_; | 303 VLOG(1) << "CacheUpdated"; |
189 | 304 |
190 if (certificates_update_running_) { | 305 // If user_cert_cache_ has access to system certificates and it has already |
191 certificates_update_required_ = true; | 306 // finished its initial load, it will contain system certificates which we can |
192 return; | 307 // filter. |
| 308 if (user_cert_cache_->initial_load_finished() && |
| 309 user_cert_cache_->has_system_certificates()) { |
| 310 crypto::ScopedPK11Slot system_slot = |
| 311 user_cert_cache_->nss_database()->GetSystemSlot(); |
| 312 DCHECK(system_slot); |
| 313 base::PostTaskWithTraitsAndReplyWithResult( |
| 314 FROM_HERE, |
| 315 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| 316 base::BindOnce(&FilterSystemTokenCertificates, |
| 317 user_cert_cache_->cert_list(), std::move(system_slot)), |
| 318 base::BindOnce(&CertLoader::UpdateCertificates, |
| 319 weak_factory_.GetWeakPtr(), |
| 320 user_cert_cache_->cert_list())); |
| 321 } else { |
| 322 // The user's cert cache does not contain system certificates. |
| 323 net::CertificateList system_certs = system_cert_cache_->cert_list(); |
| 324 net::CertificateList all_certs = user_cert_cache_->cert_list(); |
| 325 all_certs.insert(all_certs.end(), system_certs.begin(), system_certs.end()); |
| 326 UpdateCertificates(std::move(all_certs), std::move(system_certs)); |
193 } | 327 } |
194 | |
195 certificates_update_running_ = true; | |
196 certificates_update_required_ = false; | |
197 | |
198 database_->ListCerts( | |
199 base::Bind(&CertLoader::CertificatesLoaded, weak_factory_.GetWeakPtr())); | |
200 } | 328 } |
201 | 329 |
202 void CertLoader::CertificatesLoaded( | 330 void CertLoader::UpdateCertificates(net::CertificateList all_certs, |
203 std::unique_ptr<net::CertificateList> all_certs) { | 331 net::CertificateList system_certs) { |
204 DCHECK(thread_checker_.CalledOnValidThread()); | 332 CHECK(thread_checker_.CalledOnValidThread()); |
205 VLOG(1) << "CertificatesLoaded: " << all_certs->size(); | 333 bool initial_load = pending_initial_load_; |
| 334 pending_initial_load_ = false; |
206 | 335 |
207 crypto::ScopedPK11Slot system_slot = database_->GetSystemSlot(); | 336 VLOG(1) << "UpdateCertificates: " << all_certs.size() << " (" |
208 base::PostTaskWithTraitsAndReplyWithResult( | 337 << system_certs.size() << " on system slot)" |
209 FROM_HERE, | 338 << ", initial_load=" << initial_load; |
210 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, | |
211 base::BindOnce(&FilterSystemTokenCertificates, | |
212 base::Unretained(all_certs.get()), std::move(system_slot)), | |
213 base::BindOnce(&CertLoader::UpdateCertificates, | |
214 weak_factory_.GetWeakPtr(), std::move(all_certs))); | |
215 } | |
216 | |
217 void CertLoader::UpdateCertificates( | |
218 std::unique_ptr<net::CertificateList> all_certs, | |
219 std::unique_ptr<net::CertificateList> system_certs) { | |
220 DCHECK(thread_checker_.CalledOnValidThread()); | |
221 DCHECK(certificates_update_running_); | |
222 VLOG(1) << "UpdateCertificates: " << all_certs->size() << " (" | |
223 << system_certs->size() << " on system slot)"; | |
224 | 339 |
225 // Ignore any existing certificates. | 340 // Ignore any existing certificates. |
226 all_certs_ = std::move(all_certs); | 341 all_certs_ = std::move(all_certs); |
227 system_certs_ = std::move(system_certs); | 342 system_certs_ = std::move(system_certs); |
228 | 343 |
229 bool initial_load = !certificates_loaded_; | |
230 certificates_loaded_ = true; | |
231 NotifyCertificatesLoaded(initial_load); | 344 NotifyCertificatesLoaded(initial_load); |
232 | |
233 certificates_update_running_ = false; | |
234 if (certificates_update_required_) | |
235 LoadCertificates(); | |
236 } | 345 } |
237 | 346 |
238 void CertLoader::NotifyCertificatesLoaded(bool initial_load) { | 347 void CertLoader::NotifyCertificatesLoaded(bool initial_load) { |
239 for (auto& observer : observers_) | 348 for (auto& observer : observers_) |
240 observer.OnCertificatesLoaded(*all_certs_, initial_load); | 349 observer.OnCertificatesLoaded(all_certs_, initial_load); |
241 } | |
242 | |
243 void CertLoader::OnCertDBChanged() { | |
244 VLOG(1) << "OnCertDBChanged"; | |
245 LoadCertificates(); | |
246 } | 350 } |
247 | 351 |
248 } // namespace chromeos | 352 } // namespace chromeos |
OLD | NEW |