mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
chore(swift): remove placeholder SSE module (#4330)
security(swift): remove placeholder SSE module (backlog#646)
The Swift `encryption` module was a non-functional stub: `encrypt_data`
returned the plaintext unchanged while labeling it AES-256-GCM in the
object metadata, and `generate_iv` derived the IV from a timestamp
rather than a CSPRNG. It had no production caller (only a `pub mod`
declaration and one integration test), so wiring it in as-is would have
silently shipped plaintext advertised as ciphertext.
We are not supporting Swift server-side encryption for now, so remove
the module outright rather than keep a dangerous stub around:
- delete crates/protocols/src/swift/encryption.rs
- drop `pub mod encryption;` from swift/mod.rs
- remove the encryption case (and unused import) from the swift
integration test
The module reached main dubiously: it was introduced together with the
whole Swift API in commit 86e93624 ("fix(heal): canonicalize scanner
object-dir repairs (#3864)"), a 1665-file squash whose PR description
only covered the heal change and never mentioned Swift or SSE.
Verified: cargo fmt; cargo test -p rustfs-protocols --features swift
--test swift_simple_integration (10 passed); arch guardrail scripts pass.
This commit is contained in:
@@ -1,523 +0,0 @@
|
||||
// Copyright 2024 RustFS Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Server-Side Encryption Support for Swift API
|
||||
//!
|
||||
//! This module implements automatic server-side encryption for Swift objects,
|
||||
//! providing encryption at rest with transparent encryption/decryption.
|
||||
//!
|
||||
//! # Encryption Algorithm
|
||||
//!
|
||||
//! Uses AES-256-GCM (Galois/Counter Mode) which provides:
|
||||
//! - Confidentiality (AES-256 encryption)
|
||||
//! - Authenticity (built-in authentication tag)
|
||||
//! - Performance (hardware acceleration on modern CPUs)
|
||||
//!
|
||||
//! # Key Management
|
||||
//!
|
||||
//! Supports multiple key sources:
|
||||
//! - Environment variable (SWIFT_ENCRYPTION_KEY)
|
||||
//! - Configuration file
|
||||
//! - External KMS (future: Barbican, AWS KMS, HashiCorp Vault)
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Encryption is transparent to clients:
|
||||
//!
|
||||
//! ```bash
|
||||
//! # Objects automatically encrypted on upload
|
||||
//! swift upload container file.txt
|
||||
//!
|
||||
//! # Automatically decrypted on download
|
||||
//! swift download container file.txt
|
||||
//! ```
|
||||
//!
|
||||
//! # Metadata
|
||||
//!
|
||||
//! Encrypted objects include metadata:
|
||||
//! - `X-Object-Meta-Crypto-Enabled: true`
|
||||
//! - `X-Object-Meta-Crypto-Algorithm: AES-256-GCM`
|
||||
//! - `X-Object-Meta-Crypto-Key-Id: <key_id>`
|
||||
//! - `X-Object-Meta-Crypto-Iv: <base64_iv>`
|
||||
//!
|
||||
//! # Key Rotation
|
||||
//!
|
||||
//! Objects can be re-encrypted with new keys:
|
||||
//! - Upload with new key ID
|
||||
//! - Old encrypted objects remain readable with old keys
|
||||
//! - Gradual migration to new keys
|
||||
|
||||
use super::{SwiftError, SwiftResult};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||
use std::collections::HashMap;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
|
||||
const LOG_SUBSYSTEM_SWIFT_ENCRYPTION: &str = "swift_encryption";
|
||||
const EVENT_SWIFT_ENCRYPTION_STATE: &str = "swift_encryption_state";
|
||||
|
||||
/// Encryption algorithm identifier
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum EncryptionAlgorithm {
|
||||
/// AES-256-GCM (recommended)
|
||||
Aes256Gcm,
|
||||
/// AES-256-CBC (legacy, less secure)
|
||||
Aes256Cbc,
|
||||
}
|
||||
|
||||
impl EncryptionAlgorithm {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
EncryptionAlgorithm::Aes256Gcm => "AES-256-GCM",
|
||||
EncryptionAlgorithm::Aes256Cbc => "AES-256-CBC",
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse encryption algorithm from string
|
||||
///
|
||||
/// Note: This could implement `FromStr` trait, but returns `SwiftResult` instead of `Result<Self, ParseError>`
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(s: &str) -> SwiftResult<Self> {
|
||||
match s {
|
||||
"AES-256-GCM" => Ok(EncryptionAlgorithm::Aes256Gcm),
|
||||
"AES-256-CBC" => Ok(EncryptionAlgorithm::Aes256Cbc),
|
||||
_ => Err(SwiftError::BadRequest(format!("Unsupported encryption algorithm: {s}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encryption configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EncryptionConfig {
|
||||
/// Whether encryption is enabled globally
|
||||
pub enabled: bool,
|
||||
|
||||
/// Default encryption algorithm
|
||||
pub algorithm: EncryptionAlgorithm,
|
||||
|
||||
/// Master encryption key ID
|
||||
pub key_id: String,
|
||||
|
||||
/// Master encryption key (32 bytes for AES-256)
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EncryptionConfig {
|
||||
/// Create new encryption configuration
|
||||
pub fn new(enabled: bool, key_id: String, key: Vec<u8>) -> SwiftResult<Self> {
|
||||
if enabled && key.len() != 32 {
|
||||
return Err(SwiftError::BadRequest("Encryption key must be exactly 32 bytes for AES-256".to_string()));
|
||||
}
|
||||
|
||||
Ok(EncryptionConfig {
|
||||
enabled,
|
||||
algorithm: EncryptionAlgorithm::Aes256Gcm,
|
||||
key_id,
|
||||
key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Load encryption config from environment
|
||||
pub fn from_env() -> SwiftResult<Self> {
|
||||
let enabled = std::env::var("SWIFT_ENCRYPTION_ENABLED")
|
||||
.unwrap_or_else(|_| "false".to_string())
|
||||
.parse::<bool>()
|
||||
.unwrap_or(false);
|
||||
|
||||
if !enabled {
|
||||
// Return disabled config with dummy key
|
||||
return Ok(EncryptionConfig {
|
||||
enabled: false,
|
||||
algorithm: EncryptionAlgorithm::Aes256Gcm,
|
||||
key_id: "disabled".to_string(),
|
||||
key: vec![0u8; 32],
|
||||
});
|
||||
}
|
||||
|
||||
let key_id = std::env::var("SWIFT_ENCRYPTION_KEY_ID").unwrap_or_else(|_| "default".to_string());
|
||||
|
||||
let key_hex = std::env::var("SWIFT_ENCRYPTION_KEY")
|
||||
.map_err(|_| SwiftError::InternalServerError("SWIFT_ENCRYPTION_KEY not set but encryption is enabled".to_string()))?;
|
||||
|
||||
let key = hex::decode(&key_hex).map_err(|_| SwiftError::BadRequest("Invalid encryption key hex format".to_string()))?;
|
||||
|
||||
Self::new(enabled, key_id, key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encryption metadata stored with encrypted objects
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EncryptionMetadata {
|
||||
/// Encryption algorithm used
|
||||
pub algorithm: EncryptionAlgorithm,
|
||||
|
||||
/// Key ID used for encryption
|
||||
pub key_id: String,
|
||||
|
||||
/// Initialization vector (base64 encoded)
|
||||
pub iv: String,
|
||||
|
||||
/// Authentication tag for AES-GCM (base64 encoded, optional for CBC)
|
||||
pub auth_tag: Option<String>,
|
||||
}
|
||||
|
||||
impl EncryptionMetadata {
|
||||
/// Create new encryption metadata
|
||||
pub fn new(algorithm: EncryptionAlgorithm, key_id: String, iv: Vec<u8>) -> Self {
|
||||
EncryptionMetadata {
|
||||
algorithm,
|
||||
key_id,
|
||||
iv: BASE64.encode(&iv),
|
||||
auth_tag: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set authentication tag (for AES-GCM)
|
||||
pub fn with_auth_tag(mut self, tag: Vec<u8>) -> Self {
|
||||
self.auth_tag = Some(BASE64.encode(&tag));
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert to HTTP headers for object metadata
|
||||
pub fn to_headers(&self) -> HashMap<String, String> {
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("x-object-meta-crypto-enabled".to_string(), "true".to_string());
|
||||
headers.insert("x-object-meta-crypto-algorithm".to_string(), self.algorithm.as_str().to_string());
|
||||
headers.insert("x-object-meta-crypto-key-id".to_string(), self.key_id.clone());
|
||||
headers.insert("x-object-meta-crypto-iv".to_string(), self.iv.clone());
|
||||
|
||||
if let Some(tag) = &self.auth_tag {
|
||||
headers.insert("x-object-meta-crypto-auth-tag".to_string(), tag.clone());
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
|
||||
/// Parse from object metadata
|
||||
pub fn from_metadata(metadata: &HashMap<String, String>) -> SwiftResult<Option<Self>> {
|
||||
// Check if encryption is enabled
|
||||
let enabled = metadata
|
||||
.get("x-object-meta-crypto-enabled")
|
||||
.map(|v| v == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
if !enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let algorithm_str = metadata
|
||||
.get("x-object-meta-crypto-algorithm")
|
||||
.ok_or_else(|| SwiftError::InternalServerError("Missing crypto algorithm metadata".to_string()))?;
|
||||
|
||||
let algorithm = EncryptionAlgorithm::from_str(algorithm_str)?;
|
||||
|
||||
let key_id = metadata
|
||||
.get("x-object-meta-crypto-key-id")
|
||||
.ok_or_else(|| SwiftError::InternalServerError("Missing crypto key ID metadata".to_string()))?
|
||||
.clone();
|
||||
|
||||
let iv = metadata
|
||||
.get("x-object-meta-crypto-iv")
|
||||
.ok_or_else(|| SwiftError::InternalServerError("Missing crypto IV metadata".to_string()))?
|
||||
.clone();
|
||||
|
||||
let auth_tag = metadata.get("x-object-meta-crypto-auth-tag").cloned();
|
||||
|
||||
Ok(Some(EncryptionMetadata {
|
||||
algorithm,
|
||||
key_id,
|
||||
iv,
|
||||
auth_tag,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Decode IV from base64
|
||||
pub fn decode_iv(&self) -> SwiftResult<Vec<u8>> {
|
||||
BASE64
|
||||
.decode(&self.iv)
|
||||
.map_err(|_| SwiftError::InternalServerError("Invalid IV base64 encoding".to_string()))
|
||||
}
|
||||
|
||||
/// Decode auth tag from base64
|
||||
pub fn decode_auth_tag(&self) -> SwiftResult<Option<Vec<u8>>> {
|
||||
match &self.auth_tag {
|
||||
Some(tag) => {
|
||||
let decoded = BASE64
|
||||
.decode(tag)
|
||||
.map_err(|_| SwiftError::InternalServerError("Invalid auth tag base64 encoding".to_string()))?;
|
||||
Ok(Some(decoded))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if object should be encrypted based on configuration and headers
|
||||
pub fn should_encrypt(config: &EncryptionConfig, headers: &http::HeaderMap) -> bool {
|
||||
// Check if encryption is globally enabled
|
||||
if !config.enabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if client explicitly disabled encryption
|
||||
if let Some(disable) = headers.get("x-object-meta-crypto-disable")
|
||||
&& disable.to_str().unwrap_or("") == "true"
|
||||
{
|
||||
debug!(
|
||||
event = EVENT_SWIFT_ENCRYPTION_STATE,
|
||||
component = LOG_COMPONENT_PROTOCOLS,
|
||||
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
|
||||
state = "disabled_by_client",
|
||||
"swift encryption state changed"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encrypt by default if enabled
|
||||
true
|
||||
}
|
||||
|
||||
/// Generate random initialization vector for encryption
|
||||
///
|
||||
/// TODO: Integrate with proper random number generator
|
||||
/// For now, uses a simple timestamp-based approach (NOT cryptographically secure!)
|
||||
pub fn generate_iv(size: usize) -> Vec<u8> {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// WARNING: This is a placeholder! In production, use a proper CSPRNG
|
||||
// like rand::thread_rng() or getrandom crate
|
||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos();
|
||||
|
||||
let mut iv = Vec::with_capacity(size);
|
||||
let bytes = timestamp.to_le_bytes();
|
||||
|
||||
// Fill IV with timestamp bytes (repeated if necessary)
|
||||
for i in 0..size {
|
||||
iv.push(bytes[i % bytes.len()]);
|
||||
}
|
||||
|
||||
iv
|
||||
}
|
||||
|
||||
/// Placeholder for actual encryption (requires crypto crate integration)
|
||||
///
|
||||
/// In production, this would use a proper crypto library like `aes-gcm` or `ring`.
|
||||
/// This is a stub that demonstrates the API structure.
|
||||
pub fn encrypt_data(data: &[u8], config: &EncryptionConfig) -> SwiftResult<(Vec<u8>, EncryptionMetadata)> {
|
||||
debug!(
|
||||
event = EVENT_SWIFT_ENCRYPTION_STATE,
|
||||
component = LOG_COMPONENT_PROTOCOLS,
|
||||
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
|
||||
state = "encrypting",
|
||||
input_bytes = data.len(),
|
||||
algorithm = config.algorithm.as_str(),
|
||||
"swift encryption state changed"
|
||||
);
|
||||
|
||||
// Generate IV (12 bytes for GCM, 16 bytes for CBC)
|
||||
let iv_size = match config.algorithm {
|
||||
EncryptionAlgorithm::Aes256Gcm => 12,
|
||||
EncryptionAlgorithm::Aes256Cbc => 16,
|
||||
};
|
||||
let iv = generate_iv(iv_size);
|
||||
|
||||
// TODO: Implement actual encryption
|
||||
// For now, return unencrypted data with metadata
|
||||
// In production, integrate with aes-gcm crate:
|
||||
//
|
||||
// use aes_gcm::{Aes256Gcm, Key, Nonce};
|
||||
// use aes_gcm::aead::{Aead, KeyInit};
|
||||
//
|
||||
// let key = Key::<Aes256Gcm>::from_slice(&config.key);
|
||||
// let cipher = Aes256Gcm::new(key);
|
||||
// let nonce = Nonce::from_slice(&iv);
|
||||
// let ciphertext = cipher.encrypt(nonce, data)
|
||||
// .map_err(|e| SwiftError::InternalServerError(format!("Encryption failed: {}", e)))?;
|
||||
|
||||
warn!(
|
||||
event = EVENT_SWIFT_ENCRYPTION_STATE,
|
||||
component = LOG_COMPONENT_PROTOCOLS,
|
||||
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
|
||||
result = "not_implemented",
|
||||
mode = "encrypt",
|
||||
"swift encryption state changed"
|
||||
);
|
||||
|
||||
let metadata = EncryptionMetadata::new(config.algorithm.clone(), config.key_id.clone(), iv);
|
||||
|
||||
// In production, return ciphertext
|
||||
Ok((data.to_vec(), metadata))
|
||||
}
|
||||
|
||||
/// Placeholder for actual decryption (requires crypto crate integration)
|
||||
///
|
||||
/// In production, this would use a proper crypto library like `aes-gcm` or `ring`.
|
||||
/// This is a stub that demonstrates the API structure.
|
||||
pub fn decrypt_data(encrypted_data: &[u8], metadata: &EncryptionMetadata, config: &EncryptionConfig) -> SwiftResult<Vec<u8>> {
|
||||
debug!(
|
||||
event = EVENT_SWIFT_ENCRYPTION_STATE,
|
||||
component = LOG_COMPONENT_PROTOCOLS,
|
||||
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
|
||||
state = "decrypting",
|
||||
input_bytes = encrypted_data.len(),
|
||||
algorithm = metadata.algorithm.as_str(),
|
||||
"swift encryption state changed"
|
||||
);
|
||||
|
||||
// Verify key ID matches
|
||||
if metadata.key_id != config.key_id {
|
||||
return Err(SwiftError::InternalServerError(format!(
|
||||
"Key ID mismatch: object encrypted with '{}', but current key is '{}'",
|
||||
metadata.key_id, config.key_id
|
||||
)));
|
||||
}
|
||||
|
||||
// In production, integrate with aes-gcm crate:
|
||||
//
|
||||
// use aes_gcm::{Aes256Gcm, Key, Nonce};
|
||||
// use aes_gcm::aead::{Aead, KeyInit};
|
||||
//
|
||||
// let key = Key::<Aes256Gcm>::from_slice(&config.key);
|
||||
// let cipher = Aes256Gcm::new(key);
|
||||
// let nonce = Nonce::from_slice(&iv);
|
||||
// let plaintext = cipher.decrypt(nonce, encrypted_data)
|
||||
// .map_err(|e| SwiftError::InternalServerError(format!("Decryption failed: {}", e)))?;
|
||||
|
||||
warn!(
|
||||
event = EVENT_SWIFT_ENCRYPTION_STATE,
|
||||
component = LOG_COMPONENT_PROTOCOLS,
|
||||
subsystem = LOG_SUBSYSTEM_SWIFT_ENCRYPTION,
|
||||
result = "not_implemented",
|
||||
mode = "decrypt",
|
||||
"swift encryption state changed"
|
||||
);
|
||||
|
||||
// In production, return plaintext
|
||||
Ok(encrypted_data.to_vec())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_encryption_config_creation() {
|
||||
let key = vec![0u8; 32]; // 32 bytes for AES-256
|
||||
let config = EncryptionConfig::new(true, "test-key".to_string(), key).unwrap();
|
||||
|
||||
assert!(config.enabled);
|
||||
assert_eq!(config.key_id, "test-key");
|
||||
assert_eq!(config.key.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_config_invalid_key_size() {
|
||||
let key = vec![0u8; 16]; // Too short
|
||||
let result = EncryptionConfig::new(true, "test-key".to_string(), key);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_algorithm_conversion() {
|
||||
assert_eq!(EncryptionAlgorithm::Aes256Gcm.as_str(), "AES-256-GCM");
|
||||
assert_eq!(EncryptionAlgorithm::Aes256Cbc.as_str(), "AES-256-CBC");
|
||||
|
||||
assert!(EncryptionAlgorithm::from_str("AES-256-GCM").is_ok());
|
||||
assert!(EncryptionAlgorithm::from_str("AES-256-CBC").is_ok());
|
||||
assert!(EncryptionAlgorithm::from_str("INVALID").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_metadata_to_headers() {
|
||||
let metadata = EncryptionMetadata::new(
|
||||
EncryptionAlgorithm::Aes256Gcm,
|
||||
"test-key".to_string(),
|
||||
vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
);
|
||||
|
||||
let headers = metadata.to_headers();
|
||||
|
||||
assert_eq!(headers.get("x-object-meta-crypto-enabled"), Some(&"true".to_string()));
|
||||
assert_eq!(headers.get("x-object-meta-crypto-algorithm"), Some(&"AES-256-GCM".to_string()));
|
||||
assert_eq!(headers.get("x-object-meta-crypto-key-id"), Some(&"test-key".to_string()));
|
||||
assert!(headers.contains_key("x-object-meta-crypto-iv"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_metadata_from_metadata() {
|
||||
let mut metadata_map = HashMap::new();
|
||||
metadata_map.insert("x-object-meta-crypto-enabled".to_string(), "true".to_string());
|
||||
metadata_map.insert("x-object-meta-crypto-algorithm".to_string(), "AES-256-GCM".to_string());
|
||||
metadata_map.insert("x-object-meta-crypto-key-id".to_string(), "test-key".to_string());
|
||||
metadata_map.insert(
|
||||
"x-object-meta-crypto-iv".to_string(),
|
||||
BASE64.encode([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
|
||||
);
|
||||
|
||||
let metadata = EncryptionMetadata::from_metadata(&metadata_map).unwrap();
|
||||
|
||||
assert!(metadata.is_some());
|
||||
let metadata = metadata.unwrap();
|
||||
assert_eq!(metadata.algorithm, EncryptionAlgorithm::Aes256Gcm);
|
||||
assert_eq!(metadata.key_id, "test-key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_metadata_from_metadata_not_encrypted() {
|
||||
let metadata_map = HashMap::new();
|
||||
let result = EncryptionMetadata::from_metadata(&metadata_map).unwrap();
|
||||
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_encrypt() {
|
||||
let key = vec![0u8; 32];
|
||||
let config = EncryptionConfig::new(true, "test".to_string(), key).unwrap();
|
||||
|
||||
let headers = http::HeaderMap::new();
|
||||
assert!(should_encrypt(&config, &headers));
|
||||
|
||||
// Test with disabled config
|
||||
let disabled_config = EncryptionConfig::new(false, "test".to_string(), vec![0u8; 32]).unwrap();
|
||||
assert!(!should_encrypt(&disabled_config, &headers));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_iv() {
|
||||
let iv1 = generate_iv(12);
|
||||
std::thread::sleep(std::time::Duration::from_nanos(1)); // Ensure timestamp changes
|
||||
let iv2 = generate_iv(12);
|
||||
|
||||
assert_eq!(iv1.len(), 12);
|
||||
assert_eq!(iv2.len(), 12);
|
||||
// IVs should be different (random)
|
||||
// Note: This uses a placeholder implementation. In production, use proper CSPRNG.
|
||||
assert_ne!(iv1, iv2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_roundtrip() {
|
||||
let key = vec![0u8; 32];
|
||||
let config = EncryptionConfig::new(true, "test-key".to_string(), key).unwrap();
|
||||
|
||||
let plaintext = b"Hello, World!";
|
||||
let (ciphertext, metadata) = encrypt_data(plaintext, &config).unwrap();
|
||||
|
||||
let decrypted = decrypt_data(&ciphertext, &metadata, &config).unwrap();
|
||||
|
||||
assert_eq!(decrypted, plaintext);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ pub mod bulk;
|
||||
pub mod container;
|
||||
pub mod cors;
|
||||
pub mod dlo;
|
||||
pub mod encryption;
|
||||
pub mod errors;
|
||||
pub mod expiration;
|
||||
pub mod expiration_worker;
|
||||
|
||||
@@ -16,25 +16,9 @@
|
||||
|
||||
#![cfg(feature = "swift")]
|
||||
|
||||
use rustfs_protocols::swift::{encryption, quota, ratelimit, slo, symlink, sync, tempurl, versioning};
|
||||
use rustfs_protocols::swift::{quota, ratelimit, slo, symlink, sync, tempurl, versioning};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Test that encryption metadata can coexist with user metadata
|
||||
#[test]
|
||||
fn test_encryption_with_user_metadata() {
|
||||
let key = vec![0u8; 32];
|
||||
let config = encryption::EncryptionConfig::new(true, "test-key".to_string(), key).unwrap();
|
||||
|
||||
let plaintext = b"Sensitive data";
|
||||
let (_ciphertext, enc_metadata) = encryption::encrypt_data(plaintext, &config).unwrap();
|
||||
|
||||
let mut all_metadata = enc_metadata.to_headers();
|
||||
all_metadata.insert("x-object-meta-author".to_string(), "alice".to_string());
|
||||
|
||||
assert_eq!(all_metadata.get("x-object-meta-crypto-enabled"), Some(&"true".to_string()));
|
||||
assert_eq!(all_metadata.get("x-object-meta-author"), Some(&"alice".to_string()));
|
||||
}
|
||||
|
||||
/// Test sync configuration parsing
|
||||
#[test]
|
||||
fn test_sync_config_parsing() {
|
||||
|
||||
Reference in New Issue
Block a user