diff --git a/crates/rustirc-core/src/auth.rs b/crates/rustirc-core/src/auth.rs index c930a58..9e0ffba 100644 --- a/crates/rustirc-core/src/auth.rs +++ b/crates/rustirc-core/src/auth.rs @@ -4,8 +4,6 @@ //! implementing PLAIN, EXTERNAL, and SCRAM-SHA-256 mechanisms as specified //! in Phase 2 requirements. -#![allow(unused_assignments)] // Zeroize derive generates field assignments for security - use anyhow::Result; use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; use std::collections::HashMap; @@ -28,16 +26,18 @@ pub enum AuthState { /// SASL credentials with secure password storage #[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)] pub struct SaslCredentials { + #[allow(unused_assignments)] // Zeroize derive generates field assignments for security pub username: String, - #[zeroize(skip)] + #[allow(unused_assignments)] // Zeroize derive generates field assignments for security pub password: SecureString, + #[allow(unused_assignments)] // Zeroize derive generates field assignments for security pub authzid: Option, } /// Secure string that automatically zeroes memory on drop #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct SecureString { - #[zeroize(skip)] + #[allow(unused_assignments)] // Zeroize derive generates field assignments for security inner: Vec, } @@ -332,4 +332,89 @@ mod tests { auth.handle_success(); assert_eq!(auth.state(), &AuthState::Success); } + + #[test] + fn test_secure_string_zeroization() { + use std::mem::ManuallyDrop; + use zeroize::Zeroize; + + // Create a SecureString with sensitive data + let sensitive_data = "password123".to_string(); + let mut secure_str = ManuallyDrop::new(SecureString::new(sensitive_data.clone())); + + // Verify the data is present before zeroization + assert_eq!(secure_str.as_str(), "password123"); + + // Manually call zeroize (this is what ZeroizeOnDrop does in Drop) + secure_str.zeroize(); + + // After zeroization, the inner bytes should be zeroed + assert!( + secure_str.inner.iter().all(|&b| b == 0), + "SecureString memory should be zeroed after calling zeroize()" + ); + + // Clean up the ManuallyDrop wrapper + unsafe { ManuallyDrop::drop(&mut secure_str); } + } + + #[test] + fn test_sasl_credentials_zeroization() { + use std::mem::ManuallyDrop; + use zeroize::Zeroize; + + // Create SaslCredentials with sensitive data + let username = "testuser".to_string(); + let password = SecureString::new("secretpass".to_string()); + let authzid = Some("admin".to_string()); + + let mut creds = ManuallyDrop::new(SaslCredentials { + username: username.clone(), + password: password.clone(), + authzid: authzid.clone(), + }); + + // Verify the data is present before zeroization + assert_eq!(creds.username, "testuser"); + assert_eq!(creds.password.as_str(), "secretpass"); + assert_eq!(creds.authzid.as_deref(), Some("admin")); + + // Manually call zeroize (this is what ZeroizeOnDrop does in Drop) + creds.zeroize(); + + // After zeroization, all string fields should be zeroed + assert!( + creds.username.as_bytes().iter().all(|&b| b == 0), + "SaslCredentials username should be zeroed after calling zeroize()" + ); + assert!( + creds.password.inner.iter().all(|&b| b == 0), + "SaslCredentials password should be zeroed after calling zeroize()" + ); + if let Some(ref authzid_val) = creds.authzid { + assert!( + authzid_val.as_bytes().iter().all(|&b| b == 0), + "SaslCredentials authzid should be zeroed after calling zeroize()" + ); + } + + // Clean up the ManuallyDrop wrapper + unsafe { ManuallyDrop::drop(&mut creds); } + } + + #[test] + fn test_secure_string_zeroize_on_drop() { + // This test verifies that ZeroizeOnDrop is properly derived + // by ensuring the struct implements the trait + fn assert_zeroize_on_drop() {} + assert_zeroize_on_drop::(); + } + + #[test] + fn test_sasl_credentials_zeroize_on_drop() { + // This test verifies that ZeroizeOnDrop is properly derived + // by ensuring the struct implements the trait + fn assert_zeroize_on_drop() {} + assert_zeroize_on_drop::(); + } }