Skip to content
Closed
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
86 changes: 73 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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/
```
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/cryptography/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/rust/cryptography-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ license.workspace = true

[dependencies]
openssl.workspace = true
zeroize.workspace = true
3 changes: 3 additions & 0 deletions src/rust/cryptography-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ pub mod constant_time;
pub mod encoding;
pub mod pbkdf1;
pub mod pkcs12;
pub mod secret;

pub use zeroize;
169 changes: 169 additions & 0 deletions src/rust/cryptography-crypto/src/secret.rs
Original file line number Diff line number Diff line change
@@ -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<u8>` and ensures that the contents are zeroed
/// when the buffer is dropped, preventing secrets from lingering in memory.
#[derive(Clone)]
pub struct SecretBuffer(Vec<u8>);

impl SecretBuffer {
/// Create a new SecretBuffer from bytes.
pub fn new(data: impl Into<Vec<u8>>) -> 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<u8>.
///
/// 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<u8> {
// 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<Vec<u8>> for SecretBuffer {
fn from(vec: Vec<u8>) -> 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<String>) -> 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<String> 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");
}
}
Loading