xref: /aosp_15_r20/external/cronet/crypto/nss_util.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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