1use crate::error::*;
6use nss_sys::*;
7use std::{ffi::CString, os::raw::c_char};
8
9use std::path::PathBuf;
10use std::sync::OnceLock;
11
12#[cfg(feature = "keydb")]
13use crate::pk11::slot;
14
15pub const COMPATIBLE_NSS_VERSION: &str = "3.26";
18
19pub fn assert_nss_initialized() {
22 INITIALIZED.get().expect(
23 "NSS has not initialized.
24 Please ensure you include the initialization component and call it early in your code. See
25 https://mozilla.github.io/application-services/book/rust-docs/init_rust_components/index.html",
26 );
27}
28
29enum NssLoaded {
32 External,
33 NoDb,
34 #[cfg(feature = "keydb")]
35 Db,
36}
37
38static INITIALIZED: OnceLock<NssLoaded> = OnceLock::new();
39
40fn assert_compatible_version() {
41 let min_ver = CString::new(COMPATIBLE_NSS_VERSION).unwrap();
42 if unsafe { NSS_VersionCheck(min_ver.as_ptr()) } == PR_FALSE {
43 panic!("Incompatible NSS version!")
44 }
45}
46
47fn init_once(profile_path: Option<PathBuf>) -> NssLoaded {
48 assert_compatible_version();
49
50 if unsafe { NSS_IsInitialized() != PR_FALSE } {
51 return NssLoaded::External;
52 }
53
54 match profile_path {
55 #[cfg(feature = "keydb")]
56 Some(path) => {
57 if !path.is_dir() {
58 panic!("missing profile directory {:?}", path);
59 }
60 let pathstr = path.to_str().expect("invalid path");
61 let dircstr = CString::new(pathstr).expect("could not build CString from path");
62 let empty = CString::new("").expect("could not build empty CString");
63 let flags = NSS_INIT_FORCEOPEN | NSS_INIT_OPTIMIZESPACE;
64
65 let context = unsafe {
66 NSS_InitContext(
67 dircstr.as_ptr(),
68 empty.as_ptr(),
69 empty.as_ptr(),
70 empty.as_ptr(),
71 std::ptr::null_mut(),
72 flags,
73 )
74 };
75 if context.is_null() {
76 let error = get_last_error();
77 panic!("could not initialize context: {}", error);
78 }
79
80 let slot = slot::get_internal_key_slot().expect("could not get internal key slot");
81
82 if unsafe { PK11_NeedUserInit(slot.as_mut_ptr()) } == nss_sys::PR_TRUE {
83 let result = unsafe {
84 PK11_InitPin(
85 slot.as_mut_ptr(),
86 std::ptr::null_mut(),
87 std::ptr::null_mut(),
88 )
89 };
90 if result != SECStatus::SECSuccess {
91 let error = get_last_error();
92 panic!("could not initialize context: {}", error);
93 }
94 }
95
96 NssLoaded::Db
97 }
98
99 #[cfg(not(feature = "keydb"))]
100 Some(_) => panic!("Use the keydb feature to enable nss initialization with profile path"),
101
102 None => {
103 let empty = CString::default();
104 let flags = NSS_INIT_READONLY
105 | NSS_INIT_NOCERTDB
106 | NSS_INIT_NOMODDB
107 | NSS_INIT_FORCEOPEN
108 | NSS_INIT_OPTIMIZESPACE;
109 let context = unsafe {
110 NSS_InitContext(
111 empty.as_ptr(),
112 empty.as_ptr(),
113 empty.as_ptr(),
114 empty.as_ptr(),
115 std::ptr::null_mut(),
116 flags,
117 )
118 };
119 if context.is_null() {
120 let error = get_last_error();
121 panic!("Could not initialize NSS: {}", error);
122 }
123
124 NssLoaded::NoDb
125 }
126 }
127}
128
129pub fn ensure_nss_initialized() {
136 INITIALIZED.get_or_init(|| init_once(None));
137}
138
139#[cfg(feature = "keydb")]
146pub fn ensure_nss_initialized_with_profile_dir<P: Into<PathBuf>>(dir: P) {
147 INITIALIZED.get_or_init(|| init_once(Some(dir.into())));
148}
149
150pub fn map_nss_secstatus<F>(callback: F) -> Result<()>
151where
152 F: FnOnce() -> SECStatus,
153{
154 if callback() == SECStatus::SECSuccess {
155 return Ok(());
156 }
157 Err(get_last_error())
158}
159
160#[cold]
162pub fn get_last_error() -> Error {
163 let error_code = unsafe { PR_GetError() };
164 let error_text: String = usize::try_from(unsafe { PR_GetErrorTextLength() })
165 .map(|error_text_len| {
166 let mut out_str = vec![0u8; error_text_len + 1];
167 unsafe { PR_GetErrorText(out_str.as_mut_ptr() as *mut c_char) };
168 CString::new(&out_str[0..error_text_len])
169 .unwrap_or_else(|_| CString::default())
170 .to_str()
171 .unwrap_or("")
172 .to_owned()
173 })
174 .unwrap_or_else(|_| "".to_string());
175 ErrorKind::NSSError(error_code, error_text).into()
176}
177
178pub(crate) trait ScopedPtr
179where
180 Self: std::marker::Sized,
181{
182 type RawType;
183 unsafe fn from_ptr(ptr: *mut Self::RawType) -> Result<Self>;
184 fn as_ptr(&self) -> *const Self::RawType;
185 fn as_mut_ptr(&self) -> *mut Self::RawType;
186}
187
188#[macro_export]
193macro_rules! scoped_ptr {
194 ($scoped:ident, $target:ty, $dtor:path) => {
195 pub struct $scoped {
196 ptr: *mut $target,
197 }
198
199 impl $crate::util::ScopedPtr for $scoped {
200 type RawType = $target;
201
202 #[allow(dead_code)]
203 unsafe fn from_ptr(ptr: *mut $target) -> $crate::error::Result<$scoped> {
204 if !ptr.is_null() {
205 Ok($scoped { ptr })
206 } else {
207 Err($crate::error::ErrorKind::InternalError.into())
208 }
209 }
210
211 #[inline]
212 fn as_ptr(&self) -> *const $target {
213 self.ptr
214 }
215
216 #[inline]
217 fn as_mut_ptr(&self) -> *mut $target {
218 self.ptr
219 }
220 }
221
222 impl Drop for $scoped {
223 fn drop(&mut self) {
224 assert!(!self.ptr.is_null());
225 unsafe { $dtor(self.ptr) };
226 }
227 }
228 };
229}
230
231pub(crate) unsafe fn sec_item_as_slice(sec_item: &mut SECItem) -> Result<&mut [u8]> {
237 let sec_item_buf_len = usize::try_from(sec_item.len)?;
238 let buf = std::slice::from_raw_parts_mut(sec_item.data, sec_item_buf_len);
239 Ok(buf)
240}
241
242#[cfg(not(feature = "keydb"))]
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use std::thread;
247
248 #[test]
249 fn test_assert_initialized() {
250 ensure_nss_initialized();
251 assert_nss_initialized();
252 }
253
254 #[test]
255 fn test_ensure_initialized_multithread() {
256 let threads: Vec<_> = (0..2)
257 .map(|_| thread::spawn(ensure_nss_initialized))
258 .collect();
259
260 for handle in threads {
261 handle.join().unwrap();
262 }
263 }
264}
265
266#[cfg(feature = "keydb")]
267#[cfg(test)]
268mod tests_keydb {
269 use super::*;
270 use std::thread;
271
272 fn profile_path() -> PathBuf {
273 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/profile")
274 }
275
276 #[test]
277 fn test_assert_initialized_with_profile_dir() {
278 ensure_nss_initialized_with_profile_dir(profile_path());
279 assert_nss_initialized();
280 }
281
282 #[test]
283 fn test_ensure_initialized_with_profile_dir_multithread() {
284 let threads: Vec<_> = (0..2)
285 .map(|_| thread::spawn(move || ensure_nss_initialized_with_profile_dir(profile_path())))
286 .collect();
287
288 for handle in threads {
289 handle.join().unwrap();
290 }
291 }
292}