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