1 // Copyright (c) 2012 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 "crypto/nss_util.h"
6
7 #include <nss.h>
8 #include <pk11pub.h>
9 #include <plarena.h>
10 #include <prerror.h>
11 #include <prinit.h>
12 #include <prtime.h>
13 #include <secmod.h>
14
15 #include <map>
16 #include <memory>
17 #include <utility>
18 #include <vector>
19
20 #include "base/base_paths.h"
21 #include "base/bind.h"
22 #include "base/debug/alias.h"
23 #include "base/debug/stack_trace.h"
24 #include "base/files/file_path.h"
25 #include "base/files/file_util.h"
26 #include "base/lazy_instance.h"
27 #include "base/location.h"
28 #include "base/logging.h"
29 #include "base/memory/ptr_util.h"
30 #include "base/path_service.h"
31 #include "base/strings/stringprintf.h"
32 #include "base/task_scheduler/post_task.h"
33 #include "base/threading/scoped_blocking_call.h"
34 #include "base/threading/thread_checker.h"
35 #include "base/threading/thread_restrictions.h"
36 #include "base/threading/thread_task_runner_handle.h"
37 #include "build/build_config.h"
38 #include "crypto/nss_crypto_module_delegate.h"
39 #include "crypto/nss_util_internal.h"
40
41 #if defined(OS_CHROMEOS)
42 #include <dlfcn.h>
43 #endif
44
45 namespace crypto {
46
47 namespace {
48
49 #if defined(OS_CHROMEOS)
50 const char kUserNSSDatabaseName[] = "UserNSSDB";
51
52 // Constants for loading the Chrome OS TPM-backed PKCS #11 library.
53 const char kChapsModuleName[] = "Chaps";
54 const char kChapsPath[] = "libchaps.so";
55
56 // Fake certificate authority database used for testing.
57 static const base::FilePath::CharType kReadOnlyCertDB[] =
58 FILE_PATH_LITERAL("/etc/fake_root_ca/nssdb");
59 #endif // defined(OS_CHROMEOS)
60
GetNSSErrorMessage()61 std::string GetNSSErrorMessage() {
62 std::string result;
63 if (PR_GetErrorTextLength()) {
64 std::unique_ptr<char[]> error_text(new char[PR_GetErrorTextLength() + 1]);
65 PRInt32 copied = PR_GetErrorText(error_text.get());
66 result = std::string(error_text.get(), copied);
67 } else {
68 result = base::StringPrintf("NSS error code: %d", PR_GetError());
69 }
70 return result;
71 }
72
73 #if !defined(OS_CHROMEOS)
GetDefaultConfigDirectory()74 base::FilePath GetDefaultConfigDirectory() {
75 base::FilePath dir;
76 base::PathService::Get(base::DIR_HOME, &dir);
77 if (dir.empty()) {
78 LOG(ERROR) << "Failed to get home directory.";
79 return dir;
80 }
81 dir = dir.AppendASCII(".pki").AppendASCII("nssdb");
82 if (!base::CreateDirectory(dir)) {
83 LOG(ERROR) << "Failed to create " << dir.value() << " directory.";
84 dir.clear();
85 }
86 DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
87 return dir;
88 }
89 #endif // !defined(OS_CHROMEOS)
90
91 // On non-Chrome OS platforms, return the default config directory. On Chrome OS
92 // test images, return a read-only directory with fake root CA certs (which are
93 // used by the local Google Accounts server mock we use when testing our login
94 // code). On Chrome OS non-test images (where the read-only directory doesn't
95 // exist), return an empty path.
GetInitialConfigDirectory()96 base::FilePath GetInitialConfigDirectory() {
97 #if defined(OS_CHROMEOS)
98 base::FilePath database_dir = base::FilePath(kReadOnlyCertDB);
99 if (!base::PathExists(database_dir))
100 database_dir.clear();
101 return database_dir;
102 #else
103 return GetDefaultConfigDirectory();
104 #endif // defined(OS_CHROMEOS)
105 }
106
107 // This callback for NSS forwards all requests to a caller-specified
108 // CryptoModuleBlockingPasswordDelegate object.
PKCS11PasswordFunc(PK11SlotInfo * slot,PRBool retry,void * arg)109 char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) {
110 crypto::CryptoModuleBlockingPasswordDelegate* delegate =
111 reinterpret_cast<crypto::CryptoModuleBlockingPasswordDelegate*>(arg);
112 if (delegate) {
113 bool cancelled = false;
114 std::string password = delegate->RequestPassword(PK11_GetTokenName(slot),
115 retry != PR_FALSE,
116 &cancelled);
117 if (cancelled)
118 return nullptr;
119 char* result = PORT_Strdup(password.c_str());
120 password.replace(0, password.size(), password.size(), 0);
121 return result;
122 }
123 DLOG(ERROR) << "PK11 password requested with nullptr arg";
124 return nullptr;
125 }
126
127 // A singleton to initialize/deinitialize NSPR.
128 // Separate from the NSS singleton because we initialize NSPR on the UI thread.
129 // Now that we're leaking the singleton, we could merge back with the NSS
130 // singleton.
131 class NSPRInitSingleton {
132 private:
133 friend struct base::LazyInstanceTraitsBase<NSPRInitSingleton>;
134
NSPRInitSingleton()135 NSPRInitSingleton() {
136 PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
137 }
138
139 // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
140 // to prevent non-joinable threads from using NSS after it's already been
141 // shut down.
142 ~NSPRInitSingleton() = delete;
143 };
144
145 base::LazyInstance<NSPRInitSingleton>::Leaky
146 g_nspr_singleton = LAZY_INSTANCE_INITIALIZER;
147
148 // Force a crash with error info on NSS_NoDB_Init failure.
CrashOnNSSInitFailure()149 void CrashOnNSSInitFailure() {
150 int nss_error = PR_GetError();
151 int os_error = PR_GetOSError();
152 base::debug::Alias(&nss_error);
153 base::debug::Alias(&os_error);
154 LOG(ERROR) << "Error initializing NSS without a persistent database: "
155 << GetNSSErrorMessage();
156 LOG(FATAL) << "nss_error=" << nss_error << ", os_error=" << os_error;
157 }
158
159 #if defined(OS_CHROMEOS)
160 class ChromeOSUserData {
161 public:
ChromeOSUserData(ScopedPK11Slot public_slot)162 explicit ChromeOSUserData(ScopedPK11Slot public_slot)
163 : public_slot_(std::move(public_slot)),
164 private_slot_initialization_started_(false) {}
~ChromeOSUserData()165 ~ChromeOSUserData() {
166 if (public_slot_) {
167 SECStatus status = SECMOD_CloseUserDB(public_slot_.get());
168 if (status != SECSuccess)
169 PLOG(ERROR) << "SECMOD_CloseUserDB failed: " << PORT_GetError();
170 }
171 }
172
GetPublicSlot()173 ScopedPK11Slot GetPublicSlot() {
174 return ScopedPK11Slot(public_slot_ ? PK11_ReferenceSlot(public_slot_.get())
175 : nullptr);
176 }
177
GetPrivateSlot(base::OnceCallback<void (ScopedPK11Slot)> callback)178 ScopedPK11Slot GetPrivateSlot(
179 base::OnceCallback<void(ScopedPK11Slot)> callback) {
180 if (private_slot_)
181 return ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()));
182 if (!callback.is_null())
183 tpm_ready_callback_list_.push_back(std::move(callback));
184 return ScopedPK11Slot();
185 }
186
SetPrivateSlot(ScopedPK11Slot private_slot)187 void SetPrivateSlot(ScopedPK11Slot private_slot) {
188 DCHECK(!private_slot_);
189 private_slot_ = std::move(private_slot);
190
191 SlotReadyCallbackList callback_list;
192 callback_list.swap(tpm_ready_callback_list_);
193 for (SlotReadyCallbackList::iterator i = callback_list.begin();
194 i != callback_list.end();
195 ++i) {
196 std::move(*i).Run(
197 ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())));
198 }
199 }
200
private_slot_initialization_started() const201 bool private_slot_initialization_started() const {
202 return private_slot_initialization_started_;
203 }
204
set_private_slot_initialization_started()205 void set_private_slot_initialization_started() {
206 private_slot_initialization_started_ = true;
207 }
208
209 private:
210 ScopedPK11Slot public_slot_;
211 ScopedPK11Slot private_slot_;
212
213 bool private_slot_initialization_started_;
214
215 typedef std::vector<base::OnceCallback<void(ScopedPK11Slot)>>
216 SlotReadyCallbackList;
217 SlotReadyCallbackList tpm_ready_callback_list_;
218 };
219
220 class ScopedChapsLoadFixup {
221 public:
222 ScopedChapsLoadFixup();
223 ~ScopedChapsLoadFixup();
224
225 private:
226 #if defined(COMPONENT_BUILD)
227 void* chaps_handle_;
228 #endif
229 };
230
231 #if defined(COMPONENT_BUILD)
232
ScopedChapsLoadFixup()233 ScopedChapsLoadFixup::ScopedChapsLoadFixup() {
234 // HACK: libchaps links the system protobuf and there are symbol conflicts
235 // with the bundled copy. Load chaps with RTLD_DEEPBIND to workaround.
236 chaps_handle_ = dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
237 }
238
~ScopedChapsLoadFixup()239 ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {
240 // LoadModule() will have taken a 2nd reference.
241 if (chaps_handle_)
242 dlclose(chaps_handle_);
243 }
244
245 #else
246
ScopedChapsLoadFixup()247 ScopedChapsLoadFixup::ScopedChapsLoadFixup() {}
~ScopedChapsLoadFixup()248 ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {}
249
250 #endif // defined(COMPONENT_BUILD)
251 #endif // defined(OS_CHROMEOS)
252
253 class NSSInitSingleton {
254 public:
255 #if defined(OS_CHROMEOS)
256 // Used with PostTaskAndReply to pass handles to worker thread and back.
257 struct TPMModuleAndSlot {
TPMModuleAndSlotcrypto::__anonb0fc516a0111::NSSInitSingleton::TPMModuleAndSlot258 explicit TPMModuleAndSlot(SECMODModule* init_chaps_module)
259 : chaps_module(init_chaps_module) {}
260 SECMODModule* chaps_module;
261 crypto::ScopedPK11Slot tpm_slot;
262 };
263
OpenPersistentNSSDBForPath(const std::string & db_name,const base::FilePath & path)264 ScopedPK11Slot OpenPersistentNSSDBForPath(const std::string& db_name,
265 const base::FilePath& path) {
266 DCHECK(thread_checker_.CalledOnValidThread());
267 // NSS is allowed to do IO on the current thread since dispatching
268 // to a dedicated thread would still have the affect of blocking
269 // the current thread, due to NSS's internal locking requirements
270 base::ThreadRestrictions::ScopedAllowIO allow_io;
271
272 base::FilePath nssdb_path = path.AppendASCII(".pki").AppendASCII("nssdb");
273 if (!base::CreateDirectory(nssdb_path)) {
274 LOG(ERROR) << "Failed to create " << nssdb_path.value() << " directory.";
275 return ScopedPK11Slot();
276 }
277 return OpenSoftwareNSSDB(nssdb_path, db_name);
278 }
279
EnableTPMTokenForNSS()280 void EnableTPMTokenForNSS() {
281 DCHECK(thread_checker_.CalledOnValidThread());
282
283 // If this gets set, then we'll use the TPM for certs with
284 // private keys, otherwise we'll fall back to the software
285 // implementation.
286 tpm_token_enabled_for_nss_ = true;
287 }
288
IsTPMTokenEnabledForNSS()289 bool IsTPMTokenEnabledForNSS() {
290 DCHECK(thread_checker_.CalledOnValidThread());
291 return tpm_token_enabled_for_nss_;
292 }
293
InitializeTPMTokenAndSystemSlot(int system_slot_id,base::OnceCallback<void (bool)> callback)294 void InitializeTPMTokenAndSystemSlot(
295 int system_slot_id,
296 base::OnceCallback<void(bool)> callback) {
297 DCHECK(thread_checker_.CalledOnValidThread());
298 // Should not be called while there is already an initialization in
299 // progress.
300 DCHECK(!initializing_tpm_token_);
301 // If EnableTPMTokenForNSS hasn't been called, return false.
302 if (!tpm_token_enabled_for_nss_) {
303 base::ThreadTaskRunnerHandle::Get()->PostTask(
304 FROM_HERE, base::BindOnce(std::move(callback), false));
305 return;
306 }
307
308 // If everything is already initialized, then return true.
309 // Note that only |tpm_slot_| is checked, since |chaps_module_| could be
310 // nullptr in tests while |tpm_slot_| has been set to the test DB.
311 if (tpm_slot_) {
312 base::ThreadTaskRunnerHandle::Get()->PostTask(
313 FROM_HERE, base::BindOnce(std::move(callback), true));
314 return;
315 }
316
317 // Note that a reference is not taken to chaps_module_. This is safe since
318 // NSSInitSingleton is Leaky, so the reference it holds is never released.
319 std::unique_ptr<TPMModuleAndSlot> tpm_args(
320 new TPMModuleAndSlot(chaps_module_));
321 TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
322 base::PostTaskWithTraitsAndReply(
323 FROM_HERE,
324 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
325 base::BindOnce(&NSSInitSingleton::InitializeTPMTokenInThreadPool,
326 system_slot_id, tpm_args_ptr),
327 base::BindOnce(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
328 base::Unretained(this), // NSSInitSingleton is leaky
329 std::move(callback), std::move(tpm_args)));
330 initializing_tpm_token_ = true;
331 }
332
InitializeTPMTokenInThreadPool(CK_SLOT_ID token_slot_id,TPMModuleAndSlot * tpm_args)333 static void InitializeTPMTokenInThreadPool(CK_SLOT_ID token_slot_id,
334 TPMModuleAndSlot* tpm_args) {
335 // NSS functions may reenter //net via extension hooks. If the reentered
336 // code needs to synchronously wait for a task to run but the thread pool in
337 // which that task must run doesn't have enough threads to schedule it, a
338 // deadlock occurs. To prevent that, the base::ScopedBlockingCall below
339 // increments the thread pool capacity for the duration of the TPM
340 // initialization.
341 base::ScopedBlockingCall scoped_blocking_call(
342 base::BlockingType::WILL_BLOCK);
343
344 if (!tpm_args->chaps_module) {
345 ScopedChapsLoadFixup chaps_loader;
346
347 DVLOG(3) << "Loading chaps...";
348 tpm_args->chaps_module = LoadModule(
349 kChapsModuleName,
350 kChapsPath,
351 // For more details on these parameters, see:
352 // https://developer.mozilla.org/en/PKCS11_Module_Specs
353 // slotFlags=[PublicCerts] -- Certificates and public keys can be
354 // read from this slot without requiring a call to C_Login.
355 // askpw=only -- Only authenticate to the token when necessary.
356 "NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\"");
357 }
358 if (tpm_args->chaps_module) {
359 tpm_args->tpm_slot =
360 GetTPMSlotForIdInThreadPool(tpm_args->chaps_module, token_slot_id);
361 }
362 }
363
OnInitializedTPMTokenAndSystemSlot(base::OnceCallback<void (bool)> callback,std::unique_ptr<TPMModuleAndSlot> tpm_args)364 void OnInitializedTPMTokenAndSystemSlot(
365 base::OnceCallback<void(bool)> callback,
366 std::unique_ptr<TPMModuleAndSlot> tpm_args) {
367 DCHECK(thread_checker_.CalledOnValidThread());
368 DVLOG(2) << "Loaded chaps: " << !!tpm_args->chaps_module
369 << ", got tpm slot: " << !!tpm_args->tpm_slot;
370
371 chaps_module_ = tpm_args->chaps_module;
372 tpm_slot_ = std::move(tpm_args->tpm_slot);
373 if (!chaps_module_ && test_system_slot_) {
374 // chromeos_unittests try to test the TPM initialization process. If we
375 // have a test DB open, pretend that it is the TPM slot.
376 tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
377 }
378 initializing_tpm_token_ = false;
379
380 if (tpm_slot_)
381 RunAndClearTPMReadyCallbackList();
382
383 std::move(callback).Run(!!tpm_slot_);
384 }
385
RunAndClearTPMReadyCallbackList()386 void RunAndClearTPMReadyCallbackList() {
387 TPMReadyCallbackList callback_list;
388 callback_list.swap(tpm_ready_callback_list_);
389 for (TPMReadyCallbackList::iterator i = callback_list.begin();
390 i != callback_list.end();
391 ++i) {
392 std::move(*i).Run();
393 }
394 }
395
IsTPMTokenReady(base::OnceClosure callback)396 bool IsTPMTokenReady(base::OnceClosure callback) {
397 if (!callback.is_null()) {
398 // Cannot DCHECK in the general case yet, but since the callback is
399 // a new addition to the API, DCHECK to make sure at least the new uses
400 // don't regress.
401 DCHECK(thread_checker_.CalledOnValidThread());
402 } else if (!thread_checker_.CalledOnValidThread()) {
403 // TODO(mattm): Change to DCHECK when callers have been fixed.
404 DVLOG(1) << "Called on wrong thread.\n"
405 << base::debug::StackTrace().ToString();
406 }
407
408 if (tpm_slot_)
409 return true;
410
411 if (!callback.is_null())
412 tpm_ready_callback_list_.push_back(std::move(callback));
413
414 return false;
415 }
416
417 // Note that CK_SLOT_ID is an unsigned long, but cryptohome gives us the slot
418 // id as an int. This should be safe since this is only used with chaps, which
419 // we also control.
GetTPMSlotForIdInThreadPool(SECMODModule * chaps_module,CK_SLOT_ID slot_id)420 static crypto::ScopedPK11Slot GetTPMSlotForIdInThreadPool(
421 SECMODModule* chaps_module,
422 CK_SLOT_ID slot_id) {
423 DCHECK(chaps_module);
424
425 DVLOG(3) << "Poking chaps module.";
426 SECStatus rv = SECMOD_UpdateSlotList(chaps_module);
427 if (rv != SECSuccess)
428 PLOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError();
429
430 PK11SlotInfo* slot = SECMOD_LookupSlot(chaps_module->moduleID, slot_id);
431 if (!slot)
432 LOG(ERROR) << "TPM slot " << slot_id << " not found.";
433 return crypto::ScopedPK11Slot(slot);
434 }
435
InitializeNSSForChromeOSUser(const std::string & username_hash,const base::FilePath & path)436 bool InitializeNSSForChromeOSUser(const std::string& username_hash,
437 const base::FilePath& path) {
438 DCHECK(thread_checker_.CalledOnValidThread());
439 if (chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()) {
440 // This user already exists in our mapping.
441 DVLOG(2) << username_hash << " already initialized.";
442 return false;
443 }
444
445 DVLOG(2) << "Opening NSS DB " << path.value();
446 std::string db_name = base::StringPrintf(
447 "%s %s", kUserNSSDatabaseName, username_hash.c_str());
448 ScopedPK11Slot public_slot(OpenPersistentNSSDBForPath(db_name, path));
449 chromeos_user_map_[username_hash] =
450 std::make_unique<ChromeOSUserData>(std::move(public_slot));
451 return true;
452 }
453
ShouldInitializeTPMForChromeOSUser(const std::string & username_hash)454 bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) {
455 DCHECK(thread_checker_.CalledOnValidThread());
456 DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
457
458 return !chromeos_user_map_[username_hash]
459 ->private_slot_initialization_started();
460 }
461
WillInitializeTPMForChromeOSUser(const std::string & username_hash)462 void WillInitializeTPMForChromeOSUser(const std::string& username_hash) {
463 DCHECK(thread_checker_.CalledOnValidThread());
464 DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
465
466 chromeos_user_map_[username_hash]
467 ->set_private_slot_initialization_started();
468 }
469
InitializeTPMForChromeOSUser(const std::string & username_hash,CK_SLOT_ID slot_id)470 void InitializeTPMForChromeOSUser(const std::string& username_hash,
471 CK_SLOT_ID slot_id) {
472 DCHECK(thread_checker_.CalledOnValidThread());
473 DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
474 DCHECK(chromeos_user_map_[username_hash]->
475 private_slot_initialization_started());
476
477 if (!chaps_module_)
478 return;
479
480 // Note that a reference is not taken to chaps_module_. This is safe since
481 // NSSInitSingleton is Leaky, so the reference it holds is never released.
482 std::unique_ptr<TPMModuleAndSlot> tpm_args(
483 new TPMModuleAndSlot(chaps_module_));
484 TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
485 base::PostTaskWithTraitsAndReply(
486 FROM_HERE,
487 {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
488 base::BindOnce(&NSSInitSingleton::InitializeTPMTokenInThreadPool,
489 slot_id, tpm_args_ptr),
490 base::BindOnce(&NSSInitSingleton::OnInitializedTPMForChromeOSUser,
491 base::Unretained(this), // NSSInitSingleton is leaky
492 username_hash, std::move(tpm_args)));
493 }
494
OnInitializedTPMForChromeOSUser(const std::string & username_hash,std::unique_ptr<TPMModuleAndSlot> tpm_args)495 void OnInitializedTPMForChromeOSUser(
496 const std::string& username_hash,
497 std::unique_ptr<TPMModuleAndSlot> tpm_args) {
498 DCHECK(thread_checker_.CalledOnValidThread());
499 DVLOG(2) << "Got tpm slot for " << username_hash << " "
500 << !!tpm_args->tpm_slot;
501 chromeos_user_map_[username_hash]->SetPrivateSlot(
502 std::move(tpm_args->tpm_slot));
503 }
504
InitializePrivateSoftwareSlotForChromeOSUser(const std::string & username_hash)505 void InitializePrivateSoftwareSlotForChromeOSUser(
506 const std::string& username_hash) {
507 DCHECK(thread_checker_.CalledOnValidThread());
508 VLOG(1) << "using software private slot for " << username_hash;
509 DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
510 DCHECK(chromeos_user_map_[username_hash]->
511 private_slot_initialization_started());
512
513 if (prepared_test_private_slot_) {
514 chromeos_user_map_[username_hash]->SetPrivateSlot(
515 std::move(prepared_test_private_slot_));
516 return;
517 }
518
519 chromeos_user_map_[username_hash]->SetPrivateSlot(
520 chromeos_user_map_[username_hash]->GetPublicSlot());
521 }
522
GetPublicSlotForChromeOSUser(const std::string & username_hash)523 ScopedPK11Slot GetPublicSlotForChromeOSUser(
524 const std::string& username_hash) {
525 DCHECK(thread_checker_.CalledOnValidThread());
526
527 if (username_hash.empty()) {
528 DVLOG(2) << "empty username_hash";
529 return ScopedPK11Slot();
530 }
531
532 if (chromeos_user_map_.find(username_hash) == chromeos_user_map_.end()) {
533 LOG(ERROR) << username_hash << " not initialized.";
534 return ScopedPK11Slot();
535 }
536 return chromeos_user_map_[username_hash]->GetPublicSlot();
537 }
538
GetPrivateSlotForChromeOSUser(const std::string & username_hash,base::OnceCallback<void (ScopedPK11Slot)> callback)539 ScopedPK11Slot GetPrivateSlotForChromeOSUser(
540 const std::string& username_hash,
541 base::OnceCallback<void(ScopedPK11Slot)> callback) {
542 DCHECK(thread_checker_.CalledOnValidThread());
543
544 if (username_hash.empty()) {
545 DVLOG(2) << "empty username_hash";
546 if (!callback.is_null()) {
547 base::ThreadTaskRunnerHandle::Get()->PostTask(
548 FROM_HERE, base::BindOnce(std::move(callback), ScopedPK11Slot()));
549 }
550 return ScopedPK11Slot();
551 }
552
553 DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
554
555 return chromeos_user_map_[username_hash]->GetPrivateSlot(
556 std::move(callback));
557 }
558
CloseChromeOSUserForTesting(const std::string & username_hash)559 void CloseChromeOSUserForTesting(const std::string& username_hash) {
560 DCHECK(thread_checker_.CalledOnValidThread());
561 auto i = chromeos_user_map_.find(username_hash);
562 DCHECK(i != chromeos_user_map_.end());
563 chromeos_user_map_.erase(i);
564 }
565
SetSystemKeySlotForTesting(ScopedPK11Slot slot)566 void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
567 DCHECK(thread_checker_.CalledOnValidThread());
568
569 // Ensure that a previous value of test_system_slot_ is not overwritten.
570 // Unsetting, i.e. setting a nullptr, however is allowed.
571 DCHECK(!slot || !test_system_slot_);
572 test_system_slot_ = std::move(slot);
573 if (test_system_slot_) {
574 tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
575 RunAndClearTPMReadyCallbackList();
576 } else {
577 tpm_slot_.reset();
578 }
579 }
580
SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot)581 void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
582 DCHECK(thread_checker_.CalledOnValidThread());
583
584 // Ensure that a previous value of prepared_test_private_slot_ is not
585 // overwritten. Unsetting, i.e. setting a nullptr, however is allowed.
586 DCHECK(!slot || !prepared_test_private_slot_);
587 prepared_test_private_slot_ = std::move(slot);
588 }
589 #endif // defined(OS_CHROMEOS)
590
591 #if !defined(OS_CHROMEOS)
GetPersistentNSSKeySlot()592 PK11SlotInfo* GetPersistentNSSKeySlot() {
593 // TODO(mattm): Change to DCHECK when callers have been fixed.
594 if (!thread_checker_.CalledOnValidThread()) {
595 DVLOG(1) << "Called on wrong thread.\n"
596 << base::debug::StackTrace().ToString();
597 }
598
599 return PK11_GetInternalKeySlot();
600 }
601 #endif
602
603 #if defined(OS_CHROMEOS)
GetSystemNSSKeySlotCallback(base::OnceCallback<void (ScopedPK11Slot)> callback)604 void GetSystemNSSKeySlotCallback(
605 base::OnceCallback<void(ScopedPK11Slot)> callback) {
606 std::move(callback).Run(
607 ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())));
608 }
609
GetSystemNSSKeySlot(base::OnceCallback<void (ScopedPK11Slot)> callback)610 ScopedPK11Slot GetSystemNSSKeySlot(
611 base::OnceCallback<void(ScopedPK11Slot)> callback) {
612 DCHECK(thread_checker_.CalledOnValidThread());
613 // TODO(mattm): chromeos::TPMTokenloader always calls
614 // InitializeTPMTokenAndSystemSlot with slot 0. If the system slot is
615 // disabled, tpm_slot_ will be the first user's slot instead. Can that be
616 // detected and return nullptr instead?
617
618 base::OnceClosure wrapped_callback;
619 if (!callback.is_null()) {
620 wrapped_callback = base::BindOnce(
621 &NSSInitSingleton::GetSystemNSSKeySlotCallback,
622 base::Unretained(this) /* singleton is leaky */, std::move(callback));
623 }
624 if (IsTPMTokenReady(std::move(wrapped_callback)))
625 return ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get()));
626 return ScopedPK11Slot();
627 }
628 #endif
629
630 private:
631 friend struct base::LazyInstanceTraitsBase<NSSInitSingleton>;
632
NSSInitSingleton()633 NSSInitSingleton()
634 : tpm_token_enabled_for_nss_(false),
635 initializing_tpm_token_(false),
636 chaps_module_(nullptr),
637 root_(nullptr) {
638 // Initializing NSS causes us to do blocking IO.
639 // Temporarily allow it until we fix
640 // http://code.google.com/p/chromium/issues/detail?id=59847
641 base::ThreadRestrictions::ScopedAllowIO allow_io;
642
643 // It's safe to construct on any thread, since LazyInstance will prevent any
644 // other threads from accessing until the constructor is done.
645 thread_checker_.DetachFromThread();
646
647 EnsureNSPRInit();
648
649 // We *must* have NSS >= 3.26 at compile time.
650 static_assert((NSS_VMAJOR == 3 && NSS_VMINOR >= 26) || (NSS_VMAJOR > 3),
651 "nss version check failed");
652 // Also check the run-time NSS version.
653 // NSS_VersionCheck is a >= check, not strict equality.
654 if (!NSS_VersionCheck("3.26")) {
655 LOG(FATAL) << "NSS_VersionCheck(\"3.26\") failed. NSS >= 3.26 is "
656 "required. Please upgrade to the latest NSS, and if you "
657 "still get this error, contact your distribution "
658 "maintainer.";
659 }
660
661 SECStatus status = SECFailure;
662 base::FilePath database_dir = GetInitialConfigDirectory();
663 if (!database_dir.empty()) {
664 // Initialize with a persistent database (likely, ~/.pki/nssdb).
665 // Use "sql:" which can be shared by multiple processes safely.
666 std::string nss_config_dir =
667 base::StringPrintf("sql:%s", database_dir.value().c_str());
668 #if defined(OS_CHROMEOS)
669 status = NSS_Init(nss_config_dir.c_str());
670 #else
671 status = NSS_InitReadWrite(nss_config_dir.c_str());
672 #endif
673 if (status != SECSuccess) {
674 LOG(ERROR) << "Error initializing NSS with a persistent "
675 "database (" << nss_config_dir
676 << "): " << GetNSSErrorMessage();
677 }
678 }
679 if (status != SECSuccess) {
680 VLOG(1) << "Initializing NSS without a persistent database.";
681 status = NSS_NoDB_Init(nullptr);
682 if (status != SECSuccess) {
683 CrashOnNSSInitFailure();
684 return;
685 }
686 }
687
688 PK11_SetPasswordFunc(PKCS11PasswordFunc);
689
690 // If we haven't initialized the password for the NSS databases,
691 // initialize an empty-string password so that we don't need to
692 // log in.
693 PK11SlotInfo* slot = PK11_GetInternalKeySlot();
694 if (slot) {
695 // PK11_InitPin may write to the keyDB, but no other thread can use NSS
696 // yet, so we don't need to lock.
697 if (PK11_NeedUserInit(slot))
698 PK11_InitPin(slot, nullptr, nullptr);
699 PK11_FreeSlot(slot);
700 }
701
702 root_ = InitDefaultRootCerts();
703
704 // Disable MD5 certificate signatures. (They are disabled by default in
705 // NSS 3.14.)
706 NSS_SetAlgorithmPolicy(SEC_OID_MD5, 0, NSS_USE_ALG_IN_CERT_SIGNATURE);
707 NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
708 0, NSS_USE_ALG_IN_CERT_SIGNATURE);
709 }
710
711 // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
712 // to prevent non-joinable threads from using NSS after it's already been
713 // shut down.
714 ~NSSInitSingleton() = delete;
715
716 // Load nss's built-in root certs.
InitDefaultRootCerts()717 SECMODModule* InitDefaultRootCerts() {
718 SECMODModule* root = LoadModule("Root Certs", "libnssckbi.so", nullptr);
719 if (root)
720 return root;
721
722 // Aw, snap. Can't find/load root cert shared library.
723 // This will make it hard to talk to anybody via https.
724 // TODO(mattm): Re-add the NOTREACHED here when crbug.com/310972 is fixed.
725 return nullptr;
726 }
727
728 // Load the given module for this NSS session.
LoadModule(const char * name,const char * library_path,const char * params)729 static SECMODModule* LoadModule(const char* name,
730 const char* library_path,
731 const char* params) {
732 std::string modparams = base::StringPrintf(
733 "name=\"%s\" library=\"%s\" %s",
734 name, library_path, params ? params : "");
735
736 // Shouldn't need to const_cast here, but SECMOD doesn't properly
737 // declare input string arguments as const. Bug
738 // https://bugzilla.mozilla.org/show_bug.cgi?id=642546 was filed
739 // on NSS codebase to address this.
740 SECMODModule* module = SECMOD_LoadUserModule(
741 const_cast<char*>(modparams.c_str()), nullptr, PR_FALSE);
742 if (!module) {
743 LOG(ERROR) << "Error loading " << name << " module into NSS: "
744 << GetNSSErrorMessage();
745 return nullptr;
746 }
747 if (!module->loaded) {
748 LOG(ERROR) << "After loading " << name << ", loaded==false: "
749 << GetNSSErrorMessage();
750 SECMOD_DestroyModule(module);
751 return nullptr;
752 }
753 return module;
754 }
755
756 bool tpm_token_enabled_for_nss_;
757 bool initializing_tpm_token_;
758 typedef std::vector<base::OnceClosure> TPMReadyCallbackList;
759 TPMReadyCallbackList tpm_ready_callback_list_;
760 SECMODModule* chaps_module_;
761 crypto::ScopedPK11Slot tpm_slot_;
762 SECMODModule* root_;
763 #if defined(OS_CHROMEOS)
764 std::map<std::string, std::unique_ptr<ChromeOSUserData>> chromeos_user_map_;
765 ScopedPK11Slot test_system_slot_;
766 ScopedPK11Slot prepared_test_private_slot_;
767 #endif
768
769 base::ThreadChecker thread_checker_;
770 };
771
772 base::LazyInstance<NSSInitSingleton>::Leaky
773 g_nss_singleton = LAZY_INSTANCE_INITIALIZER;
774 } // namespace
775
OpenSoftwareNSSDB(const base::FilePath & path,const std::string & description)776 ScopedPK11Slot OpenSoftwareNSSDB(const base::FilePath& path,
777 const std::string& description) {
778 const std::string modspec =
779 base::StringPrintf("configDir='sql:%s' tokenDescription='%s'",
780 path.value().c_str(),
781 description.c_str());
782 PK11SlotInfo* db_slot = SECMOD_OpenUserDB(modspec.c_str());
783 if (db_slot) {
784 if (PK11_NeedUserInit(db_slot))
785 PK11_InitPin(db_slot, nullptr, nullptr);
786 } else {
787 LOG(ERROR) << "Error opening persistent database (" << modspec
788 << "): " << GetNSSErrorMessage();
789 }
790 return ScopedPK11Slot(db_slot);
791 }
792
EnsureNSPRInit()793 void EnsureNSPRInit() {
794 g_nspr_singleton.Get();
795 }
796
EnsureNSSInit()797 void EnsureNSSInit() {
798 g_nss_singleton.Get();
799 }
800
CheckNSSVersion(const char * version)801 bool CheckNSSVersion(const char* version) {
802 return !!NSS_VersionCheck(version);
803 }
804
AutoSECMODListReadLock()805 AutoSECMODListReadLock::AutoSECMODListReadLock()
806 : lock_(SECMOD_GetDefaultModuleListLock()) {
807 SECMOD_GetReadLock(lock_);
808 }
809
~AutoSECMODListReadLock()810 AutoSECMODListReadLock::~AutoSECMODListReadLock() {
811 SECMOD_ReleaseReadLock(lock_);
812 }
813
814 #if defined(OS_CHROMEOS)
GetSystemNSSKeySlot(base::OnceCallback<void (ScopedPK11Slot)> callback)815 ScopedPK11Slot GetSystemNSSKeySlot(
816 base::OnceCallback<void(ScopedPK11Slot)> callback) {
817 return g_nss_singleton.Get().GetSystemNSSKeySlot(std::move(callback));
818 }
819
SetSystemKeySlotForTesting(ScopedPK11Slot slot)820 void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
821 g_nss_singleton.Get().SetSystemKeySlotForTesting(std::move(slot));
822 }
823
EnableTPMTokenForNSS()824 void EnableTPMTokenForNSS() {
825 g_nss_singleton.Get().EnableTPMTokenForNSS();
826 }
827
IsTPMTokenEnabledForNSS()828 bool IsTPMTokenEnabledForNSS() {
829 return g_nss_singleton.Get().IsTPMTokenEnabledForNSS();
830 }
831
IsTPMTokenReady(base::OnceClosure callback)832 bool IsTPMTokenReady(base::OnceClosure callback) {
833 return g_nss_singleton.Get().IsTPMTokenReady(std::move(callback));
834 }
835
InitializeTPMTokenAndSystemSlot(int token_slot_id,base::OnceCallback<void (bool)> callback)836 void InitializeTPMTokenAndSystemSlot(int token_slot_id,
837 base::OnceCallback<void(bool)> callback) {
838 g_nss_singleton.Get().InitializeTPMTokenAndSystemSlot(token_slot_id,
839 std::move(callback));
840 }
841
InitializeNSSForChromeOSUser(const std::string & username_hash,const base::FilePath & path)842 bool InitializeNSSForChromeOSUser(const std::string& username_hash,
843 const base::FilePath& path) {
844 return g_nss_singleton.Get().InitializeNSSForChromeOSUser(username_hash,
845 path);
846 }
847
ShouldInitializeTPMForChromeOSUser(const std::string & username_hash)848 bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) {
849 return g_nss_singleton.Get().ShouldInitializeTPMForChromeOSUser(
850 username_hash);
851 }
852
WillInitializeTPMForChromeOSUser(const std::string & username_hash)853 void WillInitializeTPMForChromeOSUser(const std::string& username_hash) {
854 g_nss_singleton.Get().WillInitializeTPMForChromeOSUser(username_hash);
855 }
856
InitializeTPMForChromeOSUser(const std::string & username_hash,CK_SLOT_ID slot_id)857 void InitializeTPMForChromeOSUser(
858 const std::string& username_hash,
859 CK_SLOT_ID slot_id) {
860 g_nss_singleton.Get().InitializeTPMForChromeOSUser(username_hash, slot_id);
861 }
862
InitializePrivateSoftwareSlotForChromeOSUser(const std::string & username_hash)863 void InitializePrivateSoftwareSlotForChromeOSUser(
864 const std::string& username_hash) {
865 g_nss_singleton.Get().InitializePrivateSoftwareSlotForChromeOSUser(
866 username_hash);
867 }
868
GetPublicSlotForChromeOSUser(const std::string & username_hash)869 ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) {
870 return g_nss_singleton.Get().GetPublicSlotForChromeOSUser(username_hash);
871 }
872
GetPrivateSlotForChromeOSUser(const std::string & username_hash,base::OnceCallback<void (ScopedPK11Slot)> callback)873 ScopedPK11Slot GetPrivateSlotForChromeOSUser(
874 const std::string& username_hash,
875 base::OnceCallback<void(ScopedPK11Slot)> callback) {
876 return g_nss_singleton.Get().GetPrivateSlotForChromeOSUser(
877 username_hash, std::move(callback));
878 }
879
CloseChromeOSUserForTesting(const std::string & username_hash)880 void CloseChromeOSUserForTesting(const std::string& username_hash) {
881 g_nss_singleton.Get().CloseChromeOSUserForTesting(username_hash);
882 }
883
SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot)884 void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
885 g_nss_singleton.Get().SetPrivateSoftwareSlotForChromeOSUserForTesting(
886 std::move(slot));
887 }
888 #endif // defined(OS_CHROMEOS)
889
PRTimeToBaseTime(PRTime prtime)890 base::Time PRTimeToBaseTime(PRTime prtime) {
891 return base::Time::FromInternalValue(
892 prtime + base::Time::UnixEpoch().ToInternalValue());
893 }
894
BaseTimeToPRTime(base::Time time)895 PRTime BaseTimeToPRTime(base::Time time) {
896 return time.ToInternalValue() - base::Time::UnixEpoch().ToInternalValue();
897 }
898
899 #if !defined(OS_CHROMEOS)
GetPersistentNSSKeySlot()900 PK11SlotInfo* GetPersistentNSSKeySlot() {
901 return g_nss_singleton.Get().GetPersistentNSSKeySlot();
902 }
903 #endif
904
905 } // namespace crypto
906