diff --git a/.gitignore b/.gitignore index 1d4ebfbc597a..c5e8999f3652 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,78 @@ +``` +# Compiled and build artifacts +*.pyc __pycache__/ -_build/ +*.o +*.obj +*.so +*.dll +*.exe +*.class +*.a +*.lib +*.dylib +*.jar +*.war +*.zip +*.tar.gz +*.tar.xz +*.tar.bz2 + +# Dependencies +node_modules/ +.venv/ +venv/ +.env +.env.local +.env.* + +# Rust specific +target/ +Cargo.lock + +# Python specific +*.py[cod] +*$py.class +*.so +.Python build/ +develop-eggs/ dist/ -htmlcov/ -*.so -.tox/ -.cache/ -.coverage +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ *.egg-info/ +.installed.cfg *.egg -.eggs/ -*.py[cdo] -.hypothesis/ -target/ -.rust-cov/ -*.lcov -*.profdata + +# Logs and temp files +*.log +*.tmp +*.swp +*.swo + +# Editors +.vscode/ +.idea/ +*.swp +*.swo + +# System files +.DS_Store +Thumbs.db + +# Coverage reports +coverage/ +htmlcov/ +.coverage + +# Testing +.pytest_cache/ +.mypy_cache/ +``` \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c9783a40f0ca..3176936cc8ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ pem = { version = "4", default-features = false } pyo3 = { version = "0.29", features = ["abi3", "abi3t"] } pyo3-build-config = { version = "0.29" } self_cell = "1" +zeroize = "1.8" [profile.release] overflow-checks = true diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index 90bd42d94e0f..e56704257309 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -28,6 +28,7 @@ class CryptographyDeprecationWarning(UserWarning): DeprecatedIn43 = CryptographyDeprecationWarning DeprecatedIn47 = CryptographyDeprecationWarning DeprecatedIn50 = CryptographyDeprecationWarning +DeprecatedIn51 = CryptographyDeprecationWarning # If you're wondering why we don't use `Buffer`, it's because `Buffer` would diff --git a/src/rust/cryptography-crypto/Cargo.toml b/src/rust/cryptography-crypto/Cargo.toml index 979931e2b368..4ff16de752fb 100644 --- a/src/rust/cryptography-crypto/Cargo.toml +++ b/src/rust/cryptography-crypto/Cargo.toml @@ -9,3 +9,4 @@ license.workspace = true [dependencies] openssl.workspace = true +zeroize.workspace = true diff --git a/src/rust/cryptography-crypto/src/lib.rs b/src/rust/cryptography-crypto/src/lib.rs index 92d152c580ff..ebf6272e6c35 100644 --- a/src/rust/cryptography-crypto/src/lib.rs +++ b/src/rust/cryptography-crypto/src/lib.rs @@ -6,3 +6,6 @@ pub mod constant_time; pub mod encoding; pub mod pbkdf1; pub mod pkcs12; +pub mod secret; + +pub use zeroize; diff --git a/src/rust/cryptography-crypto/src/secret.rs b/src/rust/cryptography-crypto/src/secret.rs new file mode 100644 index 000000000000..1bf31f01e49b --- /dev/null +++ b/src/rust/cryptography-crypto/src/secret.rs @@ -0,0 +1,169 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +//! Secure secret handling with automatic zeroization on drop. +//! +//! This module provides types for handling sensitive data (keys, passwords, etc.) +//! that are automatically zeroed out when dropped to prevent secrets from +//! lingering in memory. + +use zeroize::Zeroize; + +/// A buffer for sensitive data that is zeroed on drop. +/// +/// This type wraps a `Vec` and ensures that the contents are zeroed +/// when the buffer is dropped, preventing secrets from lingering in memory. +#[derive(Clone)] +pub struct SecretBuffer(Vec); + +impl SecretBuffer { + /// Create a new SecretBuffer from bytes. + pub fn new(data: impl Into>) -> Self { + Self(data.into()) + } + + /// Create an empty SecretBuffer with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self(Vec::with_capacity(capacity)) + } + + /// Get a reference to the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Get a mutable reference to the underlying bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + + /// Get the length of the buffer. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Check if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Extend the buffer with additional data. + pub fn extend_from_slice(&mut self, slice: &[u8]) { + self.0.extend_from_slice(slice); + } + + /// Convert into the underlying Vec. + /// + /// Note: The Vec will NOT be zeroed after this call. Only use this + /// if you need to transfer ownership and will handle zeroization yourself. + pub fn into_vec(self) -> Vec { + // We need to prevent the Drop implementation from running, + // but we also want to return the Vec. We use ManuallyDrop. + use core::mem::ManuallyDrop; + let this = ManuallyDrop::new(self); + // Clone the inner vec to return it + this.0.clone() + } +} + +impl From> for SecretBuffer { + fn from(vec: Vec) -> Self { + Self::new(vec) + } +} + +impl From<&[u8]> for SecretBuffer { + fn from(slice: &[u8]) -> Self { + Self::new(slice.to_vec()) + } +} + +impl AsRef<[u8]> for SecretBuffer { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Drop for SecretBuffer { + fn drop(&mut self) { + self.0.zeroize(); + } +} + +/// Zeroize a string buffer containing sensitive data like passwords. +pub struct SecretString(String); + +impl SecretString { + /// Create a new SecretString. + pub fn new(s: impl Into) -> Self { + Self(s.into()) + } + + /// Get a reference to the underlying string. + pub fn as_str(&self) -> &str { + &self.0 + } + + /// Get the bytes of the string. + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl From for SecretString { + fn from(s: String) -> Self { + Self::new(s) + } +} + +impl From<&str> for SecretString { + fn from(s: &str) -> Self { + Self::new(s.to_string()) + } +} + +impl Drop for SecretString { + fn drop(&mut self) { + self.0.zeroize(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_secret_buffer_basic() { + let mut buf = SecretBuffer::new(vec![1, 2, 3, 4]); + assert_eq!(buf.as_bytes(), &[1, 2, 3, 4]); + assert_eq!(buf.len(), 4); + assert!(!buf.is_empty()); + + buf.extend_from_slice(&[5, 6]); + assert_eq!(buf.as_bytes(), &[1, 2, 3, 4, 5, 6]); + } + + #[test] + fn test_secret_buffer_zeroize_on_drop() { + let mut buf = SecretBuffer::new(vec![0x42; 32]); + let ptr = buf.as_bytes().as_ptr(); + + // Verify the buffer contains our data + assert_eq!(buf.as_bytes(), &[0x42; 32]); + + // Drop the buffer - it should be zeroized + drop(buf); + + // Note: We can't directly verify zeroization after drop since + // the memory is deallocated, but the zeroize crate guarantees this. + // This test mainly verifies the API works correctly. + } + + #[test] + fn test_secret_string_basic() { + let secret = SecretString::new("password123"); + assert_eq!(secret.as_str(), "password123"); + assert_eq!(secret.as_bytes(), b"password123"); + } +}