From e504ff613a83ddbfb7766bc216fdc571be2d1583 Mon Sep 17 00:00:00 2001 From: Johannes Salas Schmidt Date: Thu, 27 Nov 2025 14:04:25 +0100 Subject: [PATCH] WIP: expose KeyManager in JWCrypto --- Cargo.lock | 2 + components/support/jwcrypto/Cargo.toml | 7 + components/support/jwcrypto/src/error.rs | 4 + components/support/jwcrypto/src/keystore.rs | 188 ++++++++++++++++++++ components/support/jwcrypto/src/lib.rs | 3 + 5 files changed, 204 insertions(+) create mode 100644 components/support/jwcrypto/src/keystore.rs diff --git a/Cargo.lock b/Cargo.lock index 6100233b90..e6b0e422bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2349,8 +2349,10 @@ dependencies = [ name = "jwcrypto" version = "0.1.0" dependencies = [ + "async-trait", "base64 0.21.2", "error-support", + "futures", "nss", "rc_crypto", "serde", diff --git a/components/support/jwcrypto/Cargo.toml b/components/support/jwcrypto/Cargo.toml index 70aac7c988..d1d28d2a56 100644 --- a/components/support/jwcrypto/Cargo.toml +++ b/components/support/jwcrypto/Cargo.toml @@ -12,10 +12,17 @@ crate-type = ["lib"] base64 = "0.21" error-support = { path = "../error" } rc_crypto = { path = "../rc_crypto" } +nss = { path = "../rc_crypto/nss", default-features = false } serde = "1" serde_derive = "1" serde_json = "1" thiserror = "2" +async-trait = { version = "0.1", optional = true } +futures = { version = "0.3", optional = true, features = ["executor"] } [dev-dependencies] nss = { path = "../rc_crypto/nss" } + +[features] +default = [] +keydb = ["nss/keydb", "dep:async-trait", "dep:futures"] diff --git a/components/support/jwcrypto/src/error.rs b/components/support/jwcrypto/src/error.rs index 86a2dbf4a9..1ec88ce3ca 100644 --- a/components/support/jwcrypto/src/error.rs +++ b/components/support/jwcrypto/src/error.rs @@ -26,4 +26,8 @@ pub enum JwCryptoError { InvalidKey, #[error("EmptyCyphertext")] EmptyCyphertext, + #[error("NSS error during authentication: {reason}")] + NSSAuthenticationError { reason: String }, + #[error("Encryption key is missing.")] + MissingKey, } diff --git a/components/support/jwcrypto/src/keystore.rs b/components/support/jwcrypto/src/keystore.rs new file mode 100644 index 0000000000..b755b54c44 --- /dev/null +++ b/components/support/jwcrypto/src/keystore.rs @@ -0,0 +1,188 @@ +use crate::Jwk; +use crate::error::{JwCryptoError, Result}; +use std::sync::Arc; + +#[cfg(feature = "keydb")] +use futures::executor::block_on; + +#[cfg(feature = "keydb")] +use async_trait::async_trait; + +#[cfg(feature = "keydb")] +use nss::assert_initialized as assert_nss_initialized; +#[cfg(feature = "keydb")] +use nss::pk11::sym_key::{ + authenticate_with_primary_password, authentication_with_primary_password_is_needed, + get_or_create_aes256_key, +}; + +/// Identifier for the as key, under which the key is stored in NSS. +#[cfg(feature = "keydb")] +static KEY_NAME: &str = "as-key"; + +/// Consumers can implement the KeyManager in combination with the ManagedEncryptorDecryptor to hand +/// over the encryption key whenever encryption or decryption happens. +pub trait KeyManager: Send + Sync { + fn get_key(&self) -> Result>; +} + +/// Last but not least we provide a StaticKeyManager, which can be +/// used in cases where there is a single key during runtime, for example in tests. +pub struct StaticKeyManager { + key: String, +} + +impl StaticKeyManager { + pub fn new(key: String) -> Self { + Self { key } + } +} + +impl KeyManager for StaticKeyManager { + fn get_key(&self) -> Result> { + Ok(self.key.as_bytes().into()) + } +} + +/// `PrimaryPasswordAuthenticator` is used in conjunction with `NSSKeyManager` to provide the +/// primary password and the success or failure actions of the authentication process. +#[cfg(feature = "keydb")] +// #[uniffi::export(with_foreign)] +#[async_trait] +pub trait PrimaryPasswordAuthenticator: Send + Sync { + /// Get a primary password for authentication, otherwise return the + /// AuthenticationCancelled error to cancel the authentication process. + async fn get_primary_password(&self) -> Result; + async fn on_authentication_success(&self) -> Result<()>; + async fn on_authentication_failure(&self) -> Result<()>; +} + +/// Use the `NSSKeyManager` to use NSS for key management. +/// +/// NSS stores keys in `key4.db` within the profile and wraps the key with a key derived from the +/// primary password, if set. It defers to the provided `PrimaryPasswordAuthenticator` +/// implementation to handle user authentication. Note that if no primary password is set, the +/// wrapping key is deterministically derived from an empty string. +/// +/// Make sure to initialize NSS using `ensure_initialized_with_profile_dir` before creating a +/// NSSKeyManager. +/// +/// # Examples +/// ```no_run +/// use async_trait::async_trait; +/// use jwcrypto::{ +/// JwCryptoError, +/// keystore::{KeyManager, PrimaryPasswordAuthenticator, NSSKeyManager} +/// }; +/// use std::sync::Arc; +/// +/// struct MyPrimaryPasswordAuthenticator {} +/// +/// #[async_trait] +/// impl PrimaryPasswordAuthenticator for MyPrimaryPasswordAuthenticator { +/// async fn get_primary_password(&self) -> Result { +/// // Most likely, you would want to prompt for a password. +/// // let password = prompt_string("primary password").unwrap_or_default(); +/// Ok("secret".to_string()) +/// } +/// +/// async fn on_authentication_success(&self) -> Result<(), JwCryptoError> { +/// println!("success"); +/// Ok(()) +/// } +/// +/// async fn on_authentication_failure(&self) -> Result<(), JwCryptoError> { +/// println!("this did not work, please try again:"); +/// Ok(()) +/// } +/// } +/// let key_manager = NSSKeyManager::new(Arc::new(MyPrimaryPasswordAuthenticator {})); +/// assert_eq!(key_manager.get_key().unwrap().len(), 63); +/// ``` +#[cfg(feature = "keydb")] +// #[derive(uniffi::Object)] +pub struct NSSKeyManager { + primary_password_authenticator: Arc, +} + +#[cfg(feature = "keydb")] +// #[uniffi::export] +impl NSSKeyManager { + /// Initialize new `NSSKeyManager` with a given `PrimaryPasswordAuthenticator`. + /// There must be a previous initializiation of NSS before initializing + /// `NSSKeyManager`, otherwise this panics. + // #[uniffi::constructor()] + pub fn new(primary_password_authenticator: Arc) -> Self { + assert_nss_initialized(); + Self { + primary_password_authenticator, + } + } + + pub fn into_dyn_key_manager(self: Arc) -> Arc { + self + } +} + +// wrapp `authentication_with_primary_password_is_needed` into an Result +#[cfg(feature = "keydb")] +fn api_authentication_with_primary_password_is_needed() -> Result { + authentication_with_primary_password_is_needed().map_err(|e: nss::Error| { + JwCryptoError::NSSAuthenticationError { + reason: e.to_string(), + } + }) +} + +// wrapp `authenticate_with_primary_password` into an Result +#[cfg(feature = "keydb")] +fn api_authenticate_with_primary_password(primary_password: &str) -> Result { + authenticate_with_primary_password(primary_password).map_err(|e: nss::Error| { + JwCryptoError::NSSAuthenticationError { + reason: e.to_string(), + } + }) +} + +#[cfg(feature = "keydb")] +impl KeyManager for NSSKeyManager { + fn get_key(&self) -> Result> { + if api_authentication_with_primary_password_is_needed()? { + let primary_password = + block_on(self.primary_password_authenticator.get_primary_password())?; + let mut result = api_authenticate_with_primary_password(&primary_password)?; + + if result { + block_on( + self.primary_password_authenticator + .on_authentication_success(), + )?; + } else { + while !result { + block_on( + self.primary_password_authenticator + .on_authentication_failure(), + )?; + + let primary_password = + block_on(self.primary_password_authenticator.get_primary_password())?; + result = api_authenticate_with_primary_password(&primary_password)?; + } + block_on( + self.primary_password_authenticator + .on_authentication_success(), + )?; + } + } + + let key = get_or_create_aes256_key(KEY_NAME).map_err(|_| JwCryptoError::MissingKey)?; + let mut bytes: Vec = Vec::new(); + serde_json::to_writer( + &mut bytes, + &Jwk::new_direct_from_bytes(None, &key), + ) + .unwrap(); + Ok(bytes) + } +} + diff --git a/components/support/jwcrypto/src/lib.rs b/components/support/jwcrypto/src/lib.rs index fea6b2cf01..f558e67759 100644 --- a/components/support/jwcrypto/src/lib.rs +++ b/components/support/jwcrypto/src/lib.rs @@ -30,6 +30,9 @@ pub mod ec; mod encdec; mod error; +#[cfg(feature = "keydb")] +pub mod keystore; + pub use encdec::EncryptorDecryptor; /// Specifies the mode, algorithm and keys of the encryption operation.