Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSS keydb #6548

Merged
merged 1 commit into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

### `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:
* `authentication_with_primary_password_is_needed()`: checks whether a primary password is set and needs to be authenticated
* `authenticate_with_primary_password(primary_password: &str)`: method for authenticate 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.

### Remote Settings
- Added support of content signatures verification ([#6534](https://github.com/mozilla/application-services/pull/6534))
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/support/rc_crypto/nss/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ error-support = { path = "../../error" }
nss_sys = { path = "nss_sys" }
serde = "1"
serde_derive = "1"
once_cell = { version = "1.20.2", optional = true }

[features]
default = []
gecko = ["nss_sys/gecko"]
keydb = ["dep:once_cell"]
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern "C" {
pub fn PK11_GetInternalKeySlot() -> *mut PK11SlotInfo;
pub fn PK11_NeedUserInit(slot: *mut PK11SlotInfo) -> PRBool;
pub fn PK11_NeedLogin(slot: *mut PK11SlotInfo) -> PRBool;
pub fn PK11_IsLoggedIn(slot: *mut PK11SlotInfo, wincx: *mut c_void) -> PRBool;
pub fn PK11_CheckUserPassword(slot: *mut PK11SlotInfo, password: *const c_char) -> SECStatus;
pub fn PK11_GenerateRandom(data: *mut c_uchar, len: c_int) -> SECStatus;
pub fn PK11_FreeSymKey(key: *mut PK11SymKey);
Expand Down
8 changes: 6 additions & 2 deletions components/support/rc_crypto/nss/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

#[derive(Debug, thiserror::Error)]
pub enum ErrorKind {
#[error("NSS could not be initialized")]
NSSInitFailure,
#[error("NSS could not be initialized: {0}")]
NSSInitFailure(String),
#[error("NSS error: {0} {1}")]
NSSError(i32, String),
#[error("SSL error: {0} {1}")]
Expand All @@ -16,6 +16,10 @@ pub enum ErrorKind {
InputError(String),
#[error("Internal crypto error")]
InternalError,
#[error("invalid key length")]
InvalidKeyLength,
#[error("Interior nul byte was found")]
NulError,
#[error("Conversion error: {0}")]
ConversionError(#[from] std::num::TryFromIntError),
#[error("Base64 decode error: {0}")]
Expand Down
3 changes: 3 additions & 0 deletions components/support/rc_crypto/nss/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ pub mod pkixc;
pub mod secport;
pub use crate::error::{Error, ErrorKind, Result};
pub use util::ensure_nss_initialized as ensure_initialized;

#[cfg(feature = "keydb")]
pub use util::ensure_nss_initialized_with_profile_dir as ensure_initialized_with_profile_dir;
8 changes: 8 additions & 0 deletions components/support/rc_crypto/nss/src/pk11/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ pub fn generate_random(data: &mut [u8]) -> Result<()> {
pub(crate) fn get_internal_slot() -> Result<Slot> {
unsafe { Slot::from_ptr(nss_sys::PK11_GetInternalSlot()) }
}

/// Safe wrapper around `PK11_GetInternalKeySlot` that
/// de-allocates memory when the slot goes out of
/// scope.
#[cfg(feature = "keydb")]
pub(crate) fn get_internal_key_slot() -> Result<Slot> {
unsafe { Slot::from_ptr(nss_sys::PK11_GetInternalKeySlot()) }
}
189 changes: 187 additions & 2 deletions components/support/rc_crypto/nss/src/pk11/sym_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#[cfg(feature = "keydb")]
use crate::util::get_last_error;
use crate::{
error::*,
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 @@ -54,8 +58,8 @@ pub fn hkdf_expand(
)?
};
map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
// 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`.
// SAFETY: 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 u32::try_from(len)? > key_data.len {
return Err(ErrorKind::InternalError.into());
Expand Down Expand Up @@ -90,3 +94,184 @@ pub(crate) fn import_sym_key(
))
}
}

/// Check weather a primary password has been set and NSS needs to be authenticated.
/// Only available with the `keydb` feature.
#[cfg(feature = "keydb")]
pub fn authentication_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
&& nss_sys::PK11_IsLoggedIn(slot.as_mut_ptr(), ptr::null_mut()) != 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 authenticate_with_primary_password(primary_password: &str) -> Result<bool> {
let slot = slot::get_internal_key_slot()?;

let password_cstr = CString::new(primary_password).map_err(|_| ErrorKind::NulError)?;
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) => sym_key,
Err(_) => create_aes256_key(name)?,
};
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::InvalidKeyLength.into());
}
let buf = unsafe { sec_item_as_slice(&mut key_data)? };
jo marked this conversation as resolved.
Show resolved Hide resolved
// SAFETY: `to_vec` copies the data out before there's any chance for `sym_key` to be
// destroyed.
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).map_err(|_| ErrorKind::NulError)?;
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(),
))
.map_err(|_| get_last_error())?
};
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,
)
})
.map_err(|_| get_last_error())?;
// PK11_UnwrapSymKey takes an int keySize
if wrap_len.len > u32::MAX - 8 {
return Err(ErrorKind::InvalidKeyLength.into());
}
// Allocate an extra 8 bytes for CKM_AES_KEY_WRAP_KWP overhead.
let wrapped_key = unsafe {
nss_sys::SECITEM_AllocItem(ptr::null_mut(), ptr::null_mut(), wrap_len.len + 8)
jo marked this conversation as resolved.
Show resolved Hide resolved
};
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,
)
})
.map_err(|_| get_last_error())?;
let sym_key = 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,
))
}
.map_err(|_| get_last_error())?;

map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
Ok(sym_key)
}
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).map_err(|_| ErrorKind::NulError)?;
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(),
))
}
}
Loading