Skip to content

Commit

Permalink
methods for managing aes256 keys
Browse files Browse the repository at this point in the history
Add a few methods for managing AES256 keys with NSS:
* `authorization_with_primary_password_is_needed`: check wheather primary password is enabled
* `authorize_with_primary_password`: authorize with primary password against NSS key database
* `get_or_create_aes256_key`: retrieve a key from key4.db or, if not present, create one
  • Loading branch information
jo committed Jan 7, 2025
1 parent a65cd49 commit abcd64b
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Path>)` 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)

Expand Down
176 changes: 176 additions & 0 deletions components/support/rc_crypto/nss/src/pk11/sym_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<bool> {
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<bool> {
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<Vec<u8>> {
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<SymKey> {
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<SymKey> {
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<SymKey> {
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(),
))
}
}

0 comments on commit abcd64b

Please sign in to comment.