1#[cfg(feature = "keydb")]
6use crate::pk11::types::Slot;
7#[cfg(feature = "keydb")]
8use crate::util::get_last_error;
9use crate::{
10 error::*,
11 pk11::{context::HashAlgorithm, slot, types::SymKey},
12 util::{assert_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
13};
14#[cfg(feature = "keydb")]
15use std::ffi::{c_char, CString};
16#[cfg(feature = "keydb")]
17use std::sync::{Mutex, OnceLock};
18use std::{
19 mem,
20 os::raw::{c_uchar, c_uint, c_ulong},
21 ptr,
22};
23
24#[cfg(feature = "keydb")]
26static GLOBAL_TOKEN_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
27
28pub fn hkdf_expand(
29 digest_alg: &HashAlgorithm,
30 key_bytes: &[u8],
31 info: &[u8],
32 len: usize,
33) -> Result<Vec<u8>> {
34 assert_nss_initialized();
35 let mech = digest_alg.as_hkdf_mechanism();
36 let mut hkdf_params = nss_sys::CK_NSS_HKDFParams {
40 bExtract: nss_sys::CK_FALSE,
41 pSalt: ptr::null_mut(),
42 ulSaltLen: 0,
43 bExpand: nss_sys::CK_TRUE,
44 pInfo: info.as_ptr() as *mut u8,
45 ulInfoLen: c_ulong::try_from(info.len())?,
46 };
47 let mut params = nss_sys::SECItem {
48 type_: nss_sys::SECItemType::siBuffer as u32,
49 data: &mut hkdf_params as *mut _ as *mut c_uchar,
50 len: u32::try_from(mem::size_of::<nss_sys::CK_NSS_HKDFParams>())?,
51 };
52 let base_key = import_sym_key(mech.into(), nss_sys::CKA_WRAP.into(), key_bytes)?;
53 let derived_len = i32::try_from(len)?;
54 let sym_key = unsafe {
55 SymKey::from_ptr(
56 nss_sys::PK11_Derive(
59 base_key.as_mut_ptr(),
60 mech.into(),
61 &mut params,
62 nss_sys::CKM_SHA512_HMAC.into(),
63 nss_sys::CKA_SIGN.into(),
64 derived_len,
65 ),
66 )?
67 };
68 map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
69 let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
72 if u32::try_from(len)? > key_data.len {
73 return Err(ErrorKind::InternalError.into());
74 }
75 let buf = unsafe { sec_item_as_slice(&mut key_data)? };
76 Ok(buf.to_vec())
77}
78
79pub(crate) fn import_sym_key(
83 mechanism: nss_sys::CK_MECHANISM_TYPE,
84 operation: nss_sys::CK_ATTRIBUTE_TYPE,
85 buf: &[u8],
86) -> Result<SymKey> {
87 assert_nss_initialized();
88 let mut item = nss_sys::SECItem {
89 type_: nss_sys::SECItemType::siBuffer as u32,
90 data: buf.as_ptr() as *mut c_uchar,
91 len: c_uint::try_from(buf.len())?,
92 };
93 let slot = slot::get_internal_slot()?;
94 unsafe {
95 SymKey::from_ptr(nss_sys::PK11_ImportSymKey(
96 slot.as_mut_ptr(),
97 mechanism,
98 nss_sys::PK11Origin::PK11_OriginUnwrap as u32,
99 operation,
100 &mut item,
101 ptr::null_mut(),
102 ))
103 }
104}
105
106#[cfg(feature = "keydb")]
109pub fn authentication_with_primary_password_is_needed() -> Result<bool> {
110 let lock = GLOBAL_TOKEN_LOCK.get_or_init(|| Mutex::new(()));
112 let _guard = lock.lock().unwrap();
113
114 let slot = slot::get_internal_key_slot()?;
115
116 unsafe {
117 Ok(
118 nss_sys::PK11_NeedLogin(slot.as_mut_ptr()) == nss_sys::PR_TRUE
119 && nss_sys::PK11_IsLoggedIn(slot.as_mut_ptr(), ptr::null_mut()) != nss_sys::PR_TRUE,
120 )
121 }
122}
123
124#[cfg(feature = "keydb")]
127pub fn authenticate_with_primary_password(primary_password: &str) -> Result<bool> {
128 let lock = GLOBAL_TOKEN_LOCK.get_or_init(|| Mutex::new(()));
130 let _guard = lock.lock().unwrap();
131
132 let slot = slot::get_internal_key_slot()?;
133
134 let password_cstr = CString::new(primary_password).map_err(|_| ErrorKind::NulError)?;
135 unsafe {
136 Ok(
137 nss_sys::PK11_CheckUserPassword(slot.as_mut_ptr(), password_cstr.as_ptr())
138 == nss_sys::SECStatus::SECSuccess,
139 )
140 }
141}
142
143#[cfg(feature = "keydb")]
147pub fn get_or_create_aes256_key(name: &str) -> Result<Vec<u8>> {
148 let sym_key = match get_aes256_key(name)? {
149 Some(sym_key) => sym_key,
150 None => create_aes256_key(name)?,
151 };
152 let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
153 if key_data.len != nss_sys::AES_256_KEY_LENGTH {
154 return Err(ErrorKind::InvalidKeyLength.into());
155 }
156 let buf = unsafe { sec_item_as_slice(&mut key_data)? };
157 Ok(buf.to_vec())
160}
161
162#[cfg(feature = "keydb")]
163pub fn get_aes256_key(name: &str) -> Result<Option<SymKey>> {
164 let lock = GLOBAL_TOKEN_LOCK.get_or_init(|| Mutex::new(()));
166 let _guard = lock.lock().unwrap();
167
168 let slot = slot::get_internal_key_slot()?;
169 let c_name = CString::new(name).map_err(|_| ErrorKind::NulError)?;
170 let sym_key = unsafe {
171 SymKey::from_ptr(nss_sys::PK11_ListFixedKeysInSlot(
173 slot.as_mut_ptr(),
174 c_name.as_ptr() as *mut c_char,
175 ptr::null_mut(),
176 ))
177 };
178 match sym_key {
179 Ok(sym_key) => match extract_aes256_key_value(slot, sym_key) {
180 Ok(key) => Ok(Some(key)),
181 Err(e) => Err(e),
182 },
183 Err(_) => Ok(None),
184 }
185}
186
187#[cfg(feature = "keydb")]
188fn extract_aes256_key_value(slot: Slot, sym_key: SymKey) -> Result<SymKey> {
195 let wrapping_key = unsafe {
196 SymKey::from_ptr(nss_sys::PK11_KeyGen(
197 slot.as_mut_ptr(),
198 nss_sys::CKM_AES_KEY_GEN,
199 ptr::null_mut(),
200 16,
201 ptr::null_mut(),
202 ))
203 .map_err(|_| get_last_error())?
204 };
205
206 let mut buf = vec![
208 0;
209 (nss_sys::AES_256_KEY_LENGTH + 8)
210 .try_into()
211 .expect("invalid key length")
212 ];
213 let mut wrapped_key = nss_sys::SECItem {
214 type_: nss_sys::SECItemType::siBuffer as u32,
215 data: buf.as_mut_ptr(),
216 len: buf.len() as u32,
217 };
218
219 map_nss_secstatus(|| unsafe {
224 nss_sys::PK11_WrapSymKey(
225 nss_sys::CKM_AES_KEY_WRAP_KWP,
226 ptr::null_mut(),
227 wrapping_key.as_mut_ptr(),
228 sym_key.as_mut_ptr(),
229 &mut wrapped_key,
230 )
231 })
232 .map_err(|_| get_last_error())?;
233
234 let sym_key = unsafe {
235 SymKey::from_ptr(nss_sys::PK11_UnwrapSymKey(
236 wrapping_key.as_mut_ptr(),
237 nss_sys::CKM_AES_KEY_WRAP_KWP,
238 ptr::null_mut(),
239 &mut wrapped_key,
240 nss_sys::CKM_AES_GCM.into(),
241 (nss_sys::CKA_ENCRYPT | nss_sys::CKA_DECRYPT).into(),
242 nss_sys::AES_256_KEY_LENGTH as i32,
243 ))
244 }
245 .map_err(|_| get_last_error())?;
246
247 map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
248
249 Ok(sym_key)
250}
251
252#[cfg(feature = "keydb")]
253fn create_aes256_key(name: &str) -> Result<SymKey> {
254 let mut key_bytes: [u8; nss_sys::AES_256_KEY_LENGTH as usize] =
255 [0; nss_sys::AES_256_KEY_LENGTH as usize];
256 map_nss_secstatus(|| unsafe {
257 nss_sys::PK11_GenerateRandom(key_bytes.as_mut_ptr(), nss_sys::AES_256_KEY_LENGTH as i32)
258 })?;
259 match import_and_persist_sym_key(
260 nss_sys::CKM_AES_GCM.into(),
261 nss_sys::PK11Origin::PK11_OriginGenerated,
262 (nss_sys::CKA_ENCRYPT | nss_sys::CKA_DECRYPT).into(),
263 &key_bytes,
264 ) {
265 Ok(sym_key) => {
266 let name = CString::new(name).map_err(|_| ErrorKind::NulError)?;
267 unsafe { nss_sys::PK11_SetSymKeyNickname(sym_key.as_mut_ptr(), name.as_ptr()) };
268 Ok(sym_key)
269 }
270 Err(e) => Err(e),
271 }
272}
273
274#[cfg(feature = "keydb")]
278fn import_and_persist_sym_key(
279 mechanism: nss_sys::CK_MECHANISM_TYPE,
280 origin: nss_sys::PK11Origin,
281 operation: nss_sys::CK_ATTRIBUTE_TYPE,
282 buf: &[u8],
283) -> Result<SymKey> {
284 let lock = GLOBAL_TOKEN_LOCK.get_or_init(|| Mutex::new(()));
287 let _guard = lock.lock().unwrap();
288
289 let mut item = nss_sys::SECItem {
290 type_: nss_sys::SECItemType::siBuffer as u32,
291 data: buf.as_ptr() as *mut c_uchar,
292 len: c_uint::try_from(buf.len())?,
293 };
294 let slot = slot::get_internal_key_slot()?;
295 unsafe {
296 SymKey::from_ptr(nss_sys::PK11_ImportSymKeyWithFlags(
297 slot.as_mut_ptr(),
298 mechanism,
299 origin as u32,
300 operation,
301 &mut item,
302 nss_sys::CK_FLAGS::default(),
303 nss_sys::PR_TRUE,
304 ptr::null_mut(),
305 ))
306 }
307}
308
309#[cfg(feature = "keydb")]
310#[cfg(test)]
311mod keydb_test {
312 use super::*;
313 use crate::ensure_initialized_with_profile_dir;
314 use std::path::PathBuf;
315 use std::thread;
316
317 fn profile_path() -> PathBuf {
318 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/profile")
319 }
320
321 #[test]
322 fn test_get_aes256_key_not_found() {
323 ensure_initialized_with_profile_dir(profile_path());
324 authenticate_with_primary_password("password").unwrap();
325 let result = get_aes256_key("unknown-key").unwrap();
326 assert!(result.is_none());
327 }
328
329 #[test]
330 fn test_get_aes256_key() {
331 ensure_initialized_with_profile_dir(profile_path());
332 authenticate_with_primary_password("password").unwrap();
333 let result = get_aes256_key("as-logins-key").unwrap();
334 assert!(result.is_some());
335 }
336
337 #[test]
338 fn test_get_aes256_key_parallel() {
339 ensure_initialized_with_profile_dir(profile_path());
340
341 let threads: Vec<_> = (0..100)
342 .map(|_| {
343 thread::spawn(move || {
344 let authenticated = authenticate_with_primary_password("password").unwrap();
345 assert!(authenticated);
346 let result = get_aes256_key("as-logins-key").unwrap();
347 assert!(result.is_some());
348 })
349 })
350 .collect();
351
352 for handle in threads {
353 handle.join().unwrap();
354 }
355 }
356}