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(test)]
243mod test {
244 use super::*;
245 use std::thread;
246
247 #[test]
248 fn test_assert_initialized() {
249 ensure_nss_initialized();
250 assert_nss_initialized();
251 }
252
253 #[cfg(feature = "keydb")]
254 #[test]
255 fn test_assert_initialized_with_profile_dir() {
256 ensure_nss_initialized_with_profile_dir("./");
257 assert_nss_initialized();
258 }
259
260 #[test]
261 fn test_ensure_initialized_multithread() {
262 let threads: Vec<_> = (0..2)
263 .map(|_| thread::spawn(ensure_nss_initialized))
264 .collect();
265
266 for handle in threads {
267 handle.join().unwrap();
268 }
269 }
270
271 #[cfg(feature = "keydb")]
272 #[test]
273 fn test_ensure_initialized_with_profile_dir_multithread() {
274 let threads: Vec<_> = (0..2)
275 .map(|_| thread::spawn(move || ensure_nss_initialized_with_profile_dir("./")))
276 .collect();
277
278 for handle in threads {
279 handle.join().unwrap();
280 }
281 }
282}