diff --git a/CHANGELOG.md b/CHANGELOG.md index 29cb91070b..f73d6260f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ### `rc_crypto` - New low level bindings for dealing with primary password. - New feature flag `keydb` in `rc_crypto/nss`, which enables NSS key persistence: `ensure_initialized_with_profile_dir(path: impl AsRef)` initializes NSS with a profile directory and appropriate flags to persist keys (and certificates) in its internal PKCS11 software implementation. This function must be called first; if `ensure_initialized` is called before, it will fail. +- New methods for dealing with primary password and key persistence, available within the `keydb` feature: + * `authorization_with_primary_password_is_needed()`: checks weather a primary password is set and needs to be authorized + * `authorize_with_primary_password(primary_password: &str)`: method for authorizing NSS key store against a user-provided primary password + * `get_or_create_aes256_key(name: &str)`: retrieve a key by `name` from the internal NSS key store. If none exists, create one, persist, and return. [Full Changelog](In progress) diff --git a/components/support/rc_crypto/nss/src/pk11/sym_key.rs b/components/support/rc_crypto/nss/src/pk11/sym_key.rs index f67dbde556..5dce9b2d1e 100644 --- a/components/support/rc_crypto/nss/src/pk11/sym_key.rs +++ b/components/support/rc_crypto/nss/src/pk11/sym_key.rs @@ -7,6 +7,8 @@ use crate::{ pk11::{context::HashAlgorithm, slot, types::SymKey}, util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr}, }; +#[cfg(feature = "keydb")] +use std::ffi::{c_char, CString}; use std::{ mem, os::raw::{c_uchar, c_uint, c_ulong}, @@ -90,3 +92,177 @@ pub(crate) fn import_sym_key( )) } } + +/// Check weather a primary password has been set and NSS needs to be authorized. +/// Only available with the `keydb` feature. +#[cfg(feature = "keydb")] +pub fn authorization_with_primary_password_is_needed() -> Result { + let slot = slot::get_internal_key_slot()?; + + unsafe { Ok(nss_sys::PK11_NeedLogin(slot.as_mut_ptr()) == nss_sys::PR_TRUE) } +} + +/// Authorize NSS key store against a user-provided primary password. +/// Only available with the `keydb` feature. +#[cfg(feature = "keydb")] +pub fn authorize_with_primary_password(primary_password: &str) -> Result { + let slot = slot::get_internal_key_slot()?; + + let password_cstr = CString::new(primary_password).unwrap(); + unsafe { + Ok( + nss_sys::PK11_CheckUserPassword(slot.as_mut_ptr(), password_cstr.as_ptr()) + == nss_sys::SECStatus::SECSuccess, + ) + } +} + +/// Retrieve a key, identified by `name`, from the internal NSS key store. If none exists, create +/// one, persist, and return. +/// Only available with the `keydb` feature. +#[cfg(feature = "keydb")] +pub fn get_or_create_aes256_key(name: &str) -> Result> { + let sym_key = match get_aes256_key(name) { + Ok(sym_key) => { + map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?; + sym_key + } + Err(err) => { + println!("No key found, generating one {:?}", err); + create_aes256_key(name).expect("could not import key") + } + }; + + // This doesn't leak, because the SECItem* returned by PK11_GetKeyData + // just refers to a buffer managed by `sym_key` which we copy into `out`. + let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) }; + if key_data.len != nss_sys::AES_256_KEY_LENGTH { + return Err(ErrorKind::InternalError.into()); + } + let buf = unsafe { sec_item_as_slice(&mut key_data)? }; + Ok(buf.to_vec()) +} + +#[cfg(feature = "keydb")] +fn get_aes256_key(name: &str) -> Result { + let slot = slot::get_internal_key_slot()?; + let name = CString::new(name).unwrap(); + let sym_key = unsafe { + SymKey::from_ptr(nss_sys::PK11_ListFixedKeysInSlot( + slot.as_mut_ptr(), + name.as_ptr() as *mut c_char, + ptr::null_mut(), + )) + }; + match sym_key { + Ok(sym_key) => { + // See + // https://searchfox.org/mozilla-central/source/security/manager/ssl/NSSKeyStore.cpp#163-201 + // Unfortunately we can't use PK11_ExtractKeyValue(symKey.get()) here because softoken + // marks all token objects of type CKO_SECRET_KEY as sensitive. So we have to wrap and + // unwrap symKey to obtain a non-sensitive copy of symKey as a session object. + let wrapping_key = unsafe { + SymKey::from_ptr(nss_sys::PK11_KeyGen( + slot.as_mut_ptr(), + nss_sys::CKM_AES_KEY_GEN, + ptr::null_mut(), + 16, + ptr::null_mut(), + )) + .expect("could not create wrapping key") + }; + let mut wrap_len = nss_sys::SECItem { + type_: nss_sys::SECItemType::siBuffer as u32, + data: ptr::null_mut(), + len: 0, + }; + map_nss_secstatus(|| unsafe { + nss_sys::PK11_WrapSymKey( + nss_sys::CKM_AES_KEY_WRAP_KWP, + ptr::null_mut(), + wrapping_key.as_mut_ptr(), + sym_key.as_mut_ptr(), + &mut wrap_len, + ) + }) + .expect("could not wrap key"); + let wrapped_key = unsafe { + nss_sys::SECITEM_AllocItem(ptr::null_mut(), ptr::null_mut(), wrap_len.len + 8) + }; + map_nss_secstatus(|| unsafe { + nss_sys::PK11_WrapSymKey( + nss_sys::CKM_AES_KEY_WRAP_KWP, + ptr::null_mut(), + wrapping_key.as_mut_ptr(), + sym_key.as_mut_ptr(), + wrapped_key, + ) + }) + .expect("could not wrap key again"); + unsafe { + SymKey::from_ptr(nss_sys::PK11_UnwrapSymKey( + wrapping_key.as_mut_ptr(), + nss_sys::CKM_AES_KEY_WRAP_KWP, + ptr::null_mut(), + wrapped_key, + nss_sys::CKM_AES_GCM.into(), + (nss_sys::CKA_ENCRYPT | nss_sys::CKA_DECRYPT).into(), + wrap_len.len as i32, + )) + } + } + Err(e) => Err(e), + } +} + +#[cfg(feature = "keydb")] +fn create_aes256_key(name: &str) -> Result { + let mut key_bytes: [u8; nss_sys::AES_256_KEY_LENGTH as usize] = + [0; nss_sys::AES_256_KEY_LENGTH as usize]; + map_nss_secstatus(|| unsafe { + nss_sys::PK11_GenerateRandom(key_bytes.as_mut_ptr(), nss_sys::AES_256_KEY_LENGTH as i32) + })?; + match import_and_persist_sym_key( + nss_sys::CKM_AES_GCM.into(), + nss_sys::PK11Origin::PK11_OriginGenerated, + (nss_sys::CKA_ENCRYPT | nss_sys::CKA_DECRYPT).into(), + &key_bytes, + ) { + Ok(sym_key) => { + let name = CString::new(name).unwrap(); + unsafe { nss_sys::PK11_SetSymKeyNickname(sym_key.as_mut_ptr(), name.as_ptr()) }; + Ok(sym_key) + } + Err(e) => Err(e), + } +} + +/// Safe wrapper around PK11_ImportSymKey that +/// de-allocates memory when the key goes out of +/// scope, and persists key in key4.db. +#[cfg(feature = "keydb")] +fn import_and_persist_sym_key( + mechanism: nss_sys::CK_MECHANISM_TYPE, + origin: nss_sys::PK11Origin, + operation: nss_sys::CK_ATTRIBUTE_TYPE, + buf: &[u8], +) -> Result { + let mut item = nss_sys::SECItem { + type_: nss_sys::SECItemType::siBuffer as u32, + data: buf.as_ptr() as *mut c_uchar, + len: c_uint::try_from(buf.len())?, + }; + let slot = slot::get_internal_key_slot()?; + unsafe { + SymKey::from_ptr(nss_sys::PK11_ImportSymKeyWithFlags( + slot.as_mut_ptr(), + mechanism, + origin as u32, + operation, + &mut item, + nss_sys::CK_FLAGS::default(), + nss_sys::PR_TRUE, + ptr::null_mut(), + )) + } +}