diff --git a/server/src/internal_auth.rs b/server/src/internal_auth.rs index 4ebc0525..6bd23b14 100644 --- a/server/src/internal_auth.rs +++ b/server/src/internal_auth.rs @@ -453,17 +453,22 @@ pub(crate) async fn send_json( #[cfg(test)] mod tests { use super::*; + fn keyring(byte: u8) -> String { let secret = data_encoding::BASE64.encode(&[byte; 32]); format!(r#"{{"active":"{secret}"}}"#) } + fn nonce() -> String { + data_encoding::BASE64URL_NOPAD.encode(&shared::internal_auth::new_nonce()) + } + #[test] fn config_requires_distinct_directional_keyrings_and_valid_active_ids() { let ce = keyring(1); let ee = keyring(2); let config = Config::from_values(&ce, "active", &ee, "active").unwrap(); - let nonce = [3; 32]; + let nonce = shared::internal_auth::new_nonce(); let ce_signature = shared::internal_auth::sign_request( &config.ce_to_ee[0], Direction::CeToEe, @@ -498,14 +503,15 @@ mod tests { async fn nonce_is_consumed_once_and_persists_in_sqlite() { let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(); init_nonce_schema(&pool).await.unwrap(); + let nonce = nonce(); assert!( - consume_nonce(&pool, Direction::EeToCe, "k1", "nonce", 100) + consume_nonce(&pool, Direction::EeToCe, "k1", &nonce, 100) .await .is_ok() ); assert!(matches!( - consume_nonce(&pool, Direction::EeToCe, "k1", "nonce", 101).await, + consume_nonce(&pool, Direction::EeToCe, "k1", &nonce, 101).await, Err(Error::Replay) )); } @@ -514,7 +520,8 @@ mod tests { async fn expired_nonce_is_pruned_before_insert() { let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap(); init_nonce_schema(&pool).await.unwrap(); - consume_nonce(&pool, Direction::EeToCe, "k1", "nonce", 100) + let nonce = nonce(); + consume_nonce(&pool, Direction::EeToCe, "k1", &nonce, 100) .await .unwrap(); assert!( @@ -522,7 +529,7 @@ mod tests { &pool, Direction::EeToCe, "k1", - "nonce", + &nonce, 100 + NONCE_TTL_SECS + 1, ) .await diff --git a/shared/src/internal_auth.rs b/shared/src/internal_auth.rs index b6e228a9..72048ea3 100644 --- a/shared/src/internal_auth.rs +++ b/shared/src/internal_auth.rs @@ -3,7 +3,10 @@ use std::fmt; use base64::Engine; use hmac::{Hmac, Mac}; -use rand::RngCore; +use rand::{ + distributions::{Distribution, Standard}, + rngs::OsRng, +}; use sha2::Sha256; use zeroize::Zeroizing; @@ -214,9 +217,8 @@ fn validate_nonce(encoded: &str) -> Result<(), AuthError> { } pub fn new_nonce() -> [u8; 32] { - let mut nonce = [0u8; 32]; - rand::rngs::OsRng.fill_bytes(&mut nonce); - nonce + let mut rng = OsRng; + Standard.sample(&mut rng) } pub fn parse_keyring(json: &str) -> Result, AuthError> { @@ -362,11 +364,12 @@ mod tests { #[test] fn request_signature_binds_body_path_identity_and_direction() { let key = key("k1", 7); + let nonce = new_nonce(); let signed = sign_request( &key, Direction::CeToEe, 100, - &[9; 32], + &nonce, "POST", "/api/ee/acl?x=1", br#"{"a":1}"#, @@ -426,14 +429,8 @@ mod tests { #[test] fn response_signature_binds_request_nonce_status_and_body() { let key = key("response", 11); - let response = sign_response( - &key, - Direction::EeToCe, - "CQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQk", - 200, - b"ok", - ) - .unwrap(); + let request_nonce = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(new_nonce()); + let response = sign_response(&key, Direction::EeToCe, &request_nonce, 200, b"ok").unwrap(); assert!( verify_response( @@ -461,11 +458,12 @@ mod tests { #[test] fn request_rejects_stale_timestamp_and_malformed_nonce() { let key = key("k1", 7); + let nonce = new_nonce(); let mut signed = sign_request( &key, Direction::CeToEe, 100, - &[9; 32], + &nonce, "GET", "/api/ee/audit", b"", @@ -522,4 +520,9 @@ mod tests { Err(AuthError::InvalidKeyring) )); } + + #[test] + fn new_nonce_returns_fresh_values() { + assert_ne!(new_nonce(), new_nonce()); + } }