1 // Copyright 2012 The Chromium Authors
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 <memory>
16 #include <utility>
17
18 #include "base/base_paths.h"
19 #include "base/containers/flat_map.h"
20 #include "base/containers/heap_array.h"
21 #include "base/debug/alias.h"
22 #include "base/files/file_path.h"
23 #include "base/files/file_util.h"
24 #include "base/lazy_instance.h"
25 #include "base/logging.h"
26 #include "base/path_service.h"
27 #include "base/strings/stringprintf.h"
28 #include "base/threading/scoped_blocking_call.h"
29 #include "base/threading/thread_restrictions.h"
30 #include "build/build_config.h"
31 #include "build/chromeos_buildflags.h"
32 #include "crypto/nss_crypto_module_delegate.h"
33 #include "crypto/nss_util_internal.h"
34
35 namespace crypto {
36
37 namespace {
38
39 #if !(BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS))
GetDefaultConfigDirectory()40 base::FilePath GetDefaultConfigDirectory() {
41 base::FilePath dir;
42 base::PathService::Get(base::DIR_HOME, &dir);
43 if (dir.empty()) {
44 LOG(ERROR) << "Failed to get home directory.";
45 return dir;
46 }
47 dir = dir.AppendASCII(".pki").AppendASCII("nssdb");
48 if (!base::CreateDirectory(dir)) {
49 LOG(ERROR) << "Failed to create " << dir.value() << " directory.";
50 dir.clear();
51 }
52 DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
53 return dir;
54 }
55 #endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
56
57 // On non-Chrome OS platforms, return the default config directory. On Chrome
58 // OS return a empty path which will result in NSS being initialized without a
59 // persistent database.
GetInitialConfigDirectory()60 base::FilePath GetInitialConfigDirectory() {
61 #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
62 return base::FilePath();
63 #else
64 return GetDefaultConfigDirectory();
65 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
66 }
67
68 // This callback for NSS forwards all requests to a caller-specified
69 // CryptoModuleBlockingPasswordDelegate object.
PKCS11PasswordFunc(PK11SlotInfo * slot,PRBool retry,void * arg)70 char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) {
71 crypto::CryptoModuleBlockingPasswordDelegate* delegate =
72 reinterpret_cast<crypto::CryptoModuleBlockingPasswordDelegate*>(arg);
73 if (delegate) {
74 bool cancelled = false;
75 std::string password = delegate->RequestPassword(
76 PK11_GetTokenName(slot), retry != PR_FALSE, &cancelled);
77 if (cancelled)
78 return nullptr;
79 char* result = PORT_Strdup(password.c_str());
80 password.replace(0, password.size(), password.size(), 0);
81 return result;
82 }
83 DLOG(ERROR) << "PK11 password requested with nullptr arg";
84 return nullptr;
85 }
86
87 // A singleton to initialize/deinitialize NSPR.
88 // Separate from the NSS singleton because we initialize NSPR on the UI thread.
89 // Now that we're leaking the singleton, we could merge back with the NSS
90 // singleton.
91 class NSPRInitSingleton {
92 private:
93 friend struct base::LazyInstanceTraitsBase<NSPRInitSingleton>;
94
NSPRInitSingleton()95 NSPRInitSingleton() { PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); }
96
97 // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
98 // to prevent non-joinable threads from using NSS after it's already been
99 // shut down.
100 ~NSPRInitSingleton() = delete;
101 };
102
103 base::LazyInstance<NSPRInitSingleton>::Leaky g_nspr_singleton =
104 LAZY_INSTANCE_INITIALIZER;
105
106 // Force a crash with error info on NSS_NoDB_Init failure.
CrashOnNSSInitFailure()107 void CrashOnNSSInitFailure() {
108 int nss_error = PR_GetError();
109 int os_error = PR_GetOSError();
110 base::debug::Alias(&nss_error);
111 base::debug::Alias(&os_error);
112 LOG(ERROR) << "Error initializing NSS without a persistent database: "
113 << GetNSSErrorMessage();
114 LOG(FATAL) << "nss_error=" << nss_error << ", os_error=" << os_error;
115 }
116
117 class NSSInitSingleton {
118 public:
119 // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
120 // to prevent non-joinable threads from using NSS after it's already been
121 // shut down.
122 ~NSSInitSingleton() = delete;
123
OpenSoftwareNSSDB(const base::FilePath & path,const std::string & description)124 ScopedPK11Slot OpenSoftwareNSSDB(const base::FilePath& path,
125 const std::string& description) {
126 base::AutoLock lock(slot_map_lock_);
127
128 auto slot_map_iter = slot_map_.find(path);
129 if (slot_map_iter != slot_map_.end()) {
130 // PK11_ReferenceSlot returns a new PK11Slot instance which refers
131 // to the same slot.
132 return ScopedPK11Slot(PK11_ReferenceSlot(slot_map_iter->second.get()));
133 }
134
135 const std::string modspec =
136 base::StringPrintf("configDir='sql:%s' tokenDescription='%s'",
137 path.value().c_str(), description.c_str());
138
139 // TODO(crbug.com/1163303): Presumably there's a race condition with
140 // session_manager around creating/opening the software NSS database. The
141 // retry loop is a temporary workaround that should at least reduce the
142 // amount of failures until a proper fix is implemented.
143 PK11SlotInfo* db_slot_info = nullptr;
144 int attempts_counter = 0;
145 for (; !db_slot_info && (attempts_counter < 10); ++attempts_counter) {
146 db_slot_info = SECMOD_OpenUserDB(modspec.c_str());
147 }
148 if (db_slot_info && (attempts_counter > 1)) {
149 LOG(ERROR) << "Opening persistent database failed "
150 << attempts_counter - 1 << " times before succeeding";
151 }
152
153 if (db_slot_info) {
154 if (PK11_NeedUserInit(db_slot_info))
155 PK11_InitPin(db_slot_info, nullptr, nullptr);
156 slot_map_[path] = ScopedPK11Slot(PK11_ReferenceSlot(db_slot_info));
157 } else {
158 LOG(ERROR) << "Error opening persistent database (" << modspec
159 << "): " << GetNSSErrorMessage();
160 #if BUILDFLAG(IS_CHROMEOS_ASH)
161 DiagnosePublicSlotAndCrash(path);
162 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
163 }
164
165 return ScopedPK11Slot(db_slot_info);
166 }
167
CloseSoftwareNSSDB(PK11SlotInfo * slot)168 SECStatus CloseSoftwareNSSDB(PK11SlotInfo* slot) {
169 if (!slot) {
170 return SECFailure;
171 }
172
173 base::AutoLock lock(slot_map_lock_);
174 CK_SLOT_ID slot_id = PK11_GetSlotID(slot);
175 for (auto const& [stored_path, stored_slot] : slot_map_) {
176 if (PK11_GetSlotID(stored_slot.get()) == slot_id) {
177 slot_map_.erase(stored_path);
178 return SECMOD_CloseUserDB(slot);
179 }
180 }
181 return SECFailure;
182 }
183
184 private:
185 friend struct base::LazyInstanceTraitsBase<NSSInitSingleton>;
186
NSSInitSingleton()187 NSSInitSingleton() {
188 // Initializing NSS causes us to do blocking IO.
189 // Temporarily allow it until we fix
190 // http://code.google.com/p/chromium/issues/detail?id=59847
191 ScopedAllowBlockingForNSS allow_blocking;
192
193 EnsureNSPRInit();
194
195 // We *must* have NSS >= 3.35 at compile time.
196 static_assert((NSS_VMAJOR == 3 && NSS_VMINOR >= 35) || (NSS_VMAJOR > 3),
197 "nss version check failed");
198 // Also check the run-time NSS version.
199 // NSS_VersionCheck is a >= check, not strict equality.
200 if (!NSS_VersionCheck("3.35")) {
201 LOG(FATAL) << "NSS_VersionCheck(\"3.35\") failed. NSS >= 3.35 is "
202 "required. Please upgrade to the latest NSS, and if you "
203 "still get this error, contact your distribution "
204 "maintainer.";
205 }
206
207 SECStatus status = SECFailure;
208 base::FilePath database_dir = GetInitialConfigDirectory();
209 // In MSAN, all loaded libraries needs to be instrumented. But the user
210 // config may reference an uninstrumented module, so load NSS without cert
211 // DBs instead. Tests should ideally be run under
212 // testing/run_with_dummy_home.py to eliminate dependencies on user
213 // configuration, but the bots are not currently configured to do so. This
214 // workaround may be removed if/when the bots use run_with_dummy_home.py.
215 #if !defined(MEMORY_SANITIZER)
216 if (!database_dir.empty()) {
217 // Initialize with a persistent database (likely, ~/.pki/nssdb).
218 // Use "sql:" which can be shared by multiple processes safely.
219 std::string nss_config_dir =
220 base::StringPrintf("sql:%s", database_dir.value().c_str());
221 #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
222 status = NSS_Init(nss_config_dir.c_str());
223 #else
224 status = NSS_InitReadWrite(nss_config_dir.c_str());
225 #endif
226 if (status != SECSuccess) {
227 LOG(ERROR) << "Error initializing NSS with a persistent "
228 "database ("
229 << nss_config_dir << "): " << GetNSSErrorMessage();
230 }
231 }
232 #endif // !defined(MEMORY_SANITIZER)
233 if (status != SECSuccess) {
234 VLOG(1) << "Initializing NSS without a persistent database.";
235 status = NSS_NoDB_Init(nullptr);
236 if (status != SECSuccess) {
237 CrashOnNSSInitFailure();
238 return;
239 }
240 }
241
242 PK11_SetPasswordFunc(PKCS11PasswordFunc);
243
244 // If we haven't initialized the password for the NSS databases,
245 // initialize an empty-string password so that we don't need to
246 // log in.
247 PK11SlotInfo* slot = PK11_GetInternalKeySlot();
248 if (slot) {
249 // PK11_InitPin may write to the keyDB, but no other thread can use NSS
250 // yet, so we don't need to lock.
251 if (PK11_NeedUserInit(slot))
252 PK11_InitPin(slot, nullptr, nullptr);
253 PK11_FreeSlot(slot);
254 }
255
256 // Load nss's built-in root certs.
257 //
258 // TODO(mattm): DCHECK this succeeded when crbug.com/310972 is fixed.
259 // Failing to load root certs will it hard to talk to anybody via https.
260 LoadNSSModule("Root Certs", "libnssckbi.so", nullptr);
261
262 // Disable MD5 certificate signatures. (They are disabled by default in
263 // NSS 3.14.)
264 NSS_SetAlgorithmPolicy(SEC_OID_MD5, 0, NSS_USE_ALG_IN_CERT_SIGNATURE);
265 NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, 0,
266 NSS_USE_ALG_IN_CERT_SIGNATURE);
267 }
268
269 // Stores opened software NSS databases.
270 base::flat_map<base::FilePath, /*slot=*/ScopedPK11Slot> slot_map_
271 GUARDED_BY(slot_map_lock_);
272 // Ensures thread-safety for the methods that modify slot_map_.
273 // Performance considerations:
274 // Opening/closing a database is a rare operation in Chrome. Actually opening
275 // a database is a blocking I/O operation. Chrome doesn't open a lot of
276 // different databases in parallel. So, waiting for another thread to finish
277 // opening a database and (almost certainly) reusing the result is comparable
278 // to opening the same database twice in parallel (but the latter is not
279 // supported by NSS).
280 base::Lock slot_map_lock_;
281 };
282
283 base::LazyInstance<NSSInitSingleton>::Leaky g_nss_singleton =
284 LAZY_INSTANCE_INITIALIZER;
285 } // namespace
286
OpenSoftwareNSSDB(const base::FilePath & path,const std::string & description)287 ScopedPK11Slot OpenSoftwareNSSDB(const base::FilePath& path,
288 const std::string& description) {
289 return g_nss_singleton.Get().OpenSoftwareNSSDB(path, description);
290 }
291
CloseSoftwareNSSDB(PK11SlotInfo * slot)292 SECStatus CloseSoftwareNSSDB(PK11SlotInfo* slot) {
293 return g_nss_singleton.Get().CloseSoftwareNSSDB(slot);
294 }
295
EnsureNSPRInit()296 void EnsureNSPRInit() {
297 g_nspr_singleton.Get();
298 }
299
EnsureNSSInit()300 void EnsureNSSInit() {
301 g_nss_singleton.Get();
302 }
303
CheckNSSVersion(const char * version)304 bool CheckNSSVersion(const char* version) {
305 return !!NSS_VersionCheck(version);
306 }
307
AutoSECMODListReadLock()308 AutoSECMODListReadLock::AutoSECMODListReadLock()
309 : lock_(SECMOD_GetDefaultModuleListLock()) {
310 SECMOD_GetReadLock(lock_);
311 }
312
~AutoSECMODListReadLock()313 AutoSECMODListReadLock::~AutoSECMODListReadLock() {
314 SECMOD_ReleaseReadLock(lock_);
315 }
316
PRTimeToBaseTime(PRTime prtime)317 base::Time PRTimeToBaseTime(PRTime prtime) {
318 return base::Time::FromInternalValue(
319 prtime + base::Time::UnixEpoch().ToInternalValue());
320 }
321
BaseTimeToPRTime(base::Time time)322 PRTime BaseTimeToPRTime(base::Time time) {
323 return time.ToInternalValue() - base::Time::UnixEpoch().ToInternalValue();
324 }
325
LoadNSSModule(const char * name,const char * library_path,const char * params)326 SECMODModule* LoadNSSModule(const char* name,
327 const char* library_path,
328 const char* params) {
329 std::string modparams =
330 base::StringPrintf("name=\"%s\" library=\"%s\" %s", name, library_path,
331 params ? params : "");
332
333 // Shouldn't need to const_cast here, but SECMOD doesn't properly declare
334 // input string arguments as const. Bug
335 // https://bugzilla.mozilla.org/show_bug.cgi?id=642546 was filed on NSS
336 // codebase to address this.
337 SECMODModule* module = SECMOD_LoadUserModule(
338 const_cast<char*>(modparams.c_str()), nullptr, PR_FALSE);
339 if (!module) {
340 LOG(ERROR) << "Error loading " << name
341 << " module into NSS: " << GetNSSErrorMessage();
342 return nullptr;
343 }
344 if (!module->loaded) {
345 LOG(ERROR) << "After loading " << name
346 << ", loaded==false: " << GetNSSErrorMessage();
347 SECMOD_DestroyModule(module);
348 return nullptr;
349 }
350 return module;
351 }
352
GetNSSErrorMessage()353 std::string GetNSSErrorMessage() {
354 std::string result;
355 if (PR_GetErrorTextLength()) {
356 auto error_text =
357 base::HeapArray<char>::Uninit(PR_GetErrorTextLength() + 1);
358 PRInt32 copied = PR_GetErrorText(error_text.data());
359 result = std::string(error_text.data(), copied);
360 } else {
361 result = base::StringPrintf("NSS error code: %d", PR_GetError());
362 }
363 return result;
364 }
365
366 } // namespace crypto
367