mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-17 02:15:28 +00:00
refactor: Restructure project layout and clean up dependencies (#30)
This commit introduces a significant reorganization of the project structure to improve maintainability and clarity. Key changes include: - Adjusted the directory layout for a more logical module organization. - Removed unused crate dependencies, reducing the overall project size and potentially speeding up build times. - Updated import paths and configuration files to reflect the structural changes.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
pub fn native_aes() -> bool {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
|
||||
std::is_x86_feature_detected!("aes") && std::is_x86_feature_detected!("pclmulqdq")
|
||||
} else if #[cfg(target_arch = "aarch64")] {
|
||||
std::arch::is_aarch64_feature_detected!("aes")
|
||||
} else if #[cfg(target_arch = "powerpc64")] {
|
||||
false
|
||||
} else if #[cfg(target_arch = "s390x")] {
|
||||
std::is_s390x_feature_detected!("aes")
|
||||
&& std::is_s390x_feature_detected!("aescbc")
|
||||
&& std::is_s390x_feature_detected!("aesctr")
|
||||
&& (std::is_s390x_feature_detected!("aesgcm") || std::is_s390x_feature_detected!("ghash"))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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.
|
||||
|
||||
#[cfg(any(test, feature = "crypto"))]
|
||||
pub fn decrypt_data(password: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Error> {
|
||||
use crate::encdec::id::ID;
|
||||
use crate::error::Error;
|
||||
use aes_gcm::{Aes256Gcm, KeyInit as _};
|
||||
use chacha20poly1305::ChaCha20Poly1305;
|
||||
|
||||
// 32: salt
|
||||
// 1: id
|
||||
// 12: nonce
|
||||
const HEADER_LENGTH: usize = 45;
|
||||
if data.len() < HEADER_LENGTH {
|
||||
return Err(Error::ErrUnexpectedHeader);
|
||||
}
|
||||
|
||||
let (salt, id, nonce) = (&data[..32], ID::try_from(data[32])?, &data[33..45]);
|
||||
let data = &data[HEADER_LENGTH..];
|
||||
|
||||
match id {
|
||||
ID::Argon2idChaCHa20Poly1305 => {
|
||||
let key = id.get_key(password, salt)?;
|
||||
decryp(ChaCha20Poly1305::new_from_slice(&key)?, nonce, data)
|
||||
}
|
||||
_ => {
|
||||
let key = id.get_key(password, salt)?;
|
||||
decryp(Aes256Gcm::new_from_slice(&key)?, nonce, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use argon2::{Argon2, PasswordHasher};
|
||||
// use argon2::password_hash::{SaltString};
|
||||
// use aes_gcm::{Aes256Gcm, Key, Nonce}; // For AES-GCM
|
||||
// use chacha20poly1305::{ChaCha20Poly1305, Key as ChaChaKey, Nonce as ChaChaNonce}; // For ChaCha20
|
||||
// use pbkdf2::pbkdf2;
|
||||
// use sha2::Sha256;
|
||||
// use std::io::{self, Read};
|
||||
// use thiserror::Error;
|
||||
|
||||
// #[derive(Debug, Error)]
|
||||
// pub enum DecryptError {
|
||||
// #[error("unexpected header")]
|
||||
// UnexpectedHeader,
|
||||
// #[error("invalid encryption algorithm ID")]
|
||||
// InvalidAlgorithmId,
|
||||
// #[error("IO error")]
|
||||
// Io(#[from] io::Error),
|
||||
// #[error("decryption error")]
|
||||
// DecryptionError,
|
||||
// }
|
||||
|
||||
// pub fn decrypt_data2<R: Read>(password: &str, mut data: R) -> Result<Vec<u8>, DecryptError> {
|
||||
// // Parse the stream header
|
||||
// let mut hdr = [0u8; 32 + 1 + 8];
|
||||
// if data.read_exact(&mut hdr).is_err() {
|
||||
// return Err(DecryptError::UnexpectedHeader);
|
||||
// }
|
||||
|
||||
// let salt = &hdr[0..32];
|
||||
// let id = hdr[32];
|
||||
// let nonce = &hdr[33..41];
|
||||
|
||||
// let key = match id {
|
||||
// // Argon2id + AES-GCM
|
||||
// 0x01 => {
|
||||
// let salt = SaltString::encode_b64(salt).map_err(|_| DecryptError::DecryptionError)?;
|
||||
// let argon2 = Argon2::default();
|
||||
// let hashed_key = argon2.hash_password(password.as_bytes(), &salt)
|
||||
// .map_err(|_| DecryptError::DecryptionError)?;
|
||||
// hashed_key.hash.unwrap().as_bytes().to_vec()
|
||||
// }
|
||||
// // Argon2id + ChaCha20Poly1305
|
||||
// 0x02 => {
|
||||
// let salt = SaltString::encode_b64(salt).map_err(|_| DecryptError::DecryptionError)?;
|
||||
// let argon2 = Argon2::default();
|
||||
// let hashed_key = argon2.hash_password(password.as_bytes(), &salt)
|
||||
// .map_err(|_| DecryptError::DecryptionError)?;
|
||||
// hashed_key.hash.unwrap().as_bytes().to_vec()
|
||||
// }
|
||||
// // PBKDF2 + AES-GCM
|
||||
// // 0x03 => {
|
||||
// // let mut key = [0u8; 32];
|
||||
// // pbkdf2::<Sha256>(password.as_bytes(), salt, 10000, &mut key);
|
||||
// // key.to_vec()
|
||||
// // }
|
||||
// _ => return Err(DecryptError::InvalidAlgorithmId),
|
||||
// };
|
||||
|
||||
// // Decrypt data using the corresponding cipher
|
||||
// let mut encrypted_data = Vec::new();
|
||||
// data.read_to_end(&mut encrypted_data)?;
|
||||
|
||||
// let plaintext = match id {
|
||||
// 0x01 => {
|
||||
// let cipher = Aes256Gcm::new(Key::from_slice(&key));
|
||||
// let nonce = Nonce::from_slice(nonce);
|
||||
// cipher
|
||||
// .decrypt(nonce, encrypted_data.as_ref())
|
||||
// .map_err(|_| DecryptError::DecryptionError)?
|
||||
// }
|
||||
// 0x02 => {
|
||||
// let cipher = ChaCha20Poly1305::new(ChaChaKey::from_slice(&key));
|
||||
// let nonce = ChaChaNonce::from_slice(nonce);
|
||||
// cipher
|
||||
// .decrypt(nonce, encrypted_data.as_ref())
|
||||
// .map_err(|_| DecryptError::DecryptionError)?
|
||||
// }
|
||||
// 0x03 => {
|
||||
|
||||
// let cipher = Aes256Gcm::new(Key::from_slice(&key));
|
||||
// let nonce = Nonce::from_slice(nonce);
|
||||
// cipher
|
||||
// .decrypt(nonce, encrypted_data.as_ref())
|
||||
// .map_err(|_| DecryptError::DecryptionError)?
|
||||
// }
|
||||
// _ => return Err(DecryptError::InvalidAlgorithmId),
|
||||
// };
|
||||
|
||||
// Ok(plaintext)
|
||||
// }
|
||||
|
||||
#[cfg(any(test, feature = "crypto"))]
|
||||
#[inline]
|
||||
fn decryp<T: aes_gcm::aead::Aead>(stream: T, nonce: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Error> {
|
||||
use crate::error::Error;
|
||||
stream
|
||||
.decrypt(aes_gcm::Nonce::from_slice(nonce), data)
|
||||
.map_err(Error::ErrDecryptFailed)
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "crypto")))]
|
||||
pub fn decrypt_data(_password: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Error> {
|
||||
Ok(data.to_vec())
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
#[cfg(any(test, feature = "crypto"))]
|
||||
pub fn encrypt_data(password: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Error> {
|
||||
use crate::encdec::id::ID;
|
||||
use aes_gcm::Aes256Gcm;
|
||||
use aes_gcm::KeyInit as _;
|
||||
use rand::random;
|
||||
|
||||
let salt: [u8; 32] = random();
|
||||
|
||||
#[cfg(feature = "fips")]
|
||||
let id = ID::Pbkdf2AESGCM;
|
||||
|
||||
#[cfg(not(feature = "fips"))]
|
||||
let id = if native_aes() {
|
||||
ID::Argon2idAESGCM
|
||||
} else {
|
||||
ID::Argon2idChaCHa20Poly1305
|
||||
};
|
||||
|
||||
let key = id.get_key(password, &salt)?;
|
||||
|
||||
#[cfg(feature = "fips")]
|
||||
{
|
||||
encrypt(Aes256Gcm::new_from_slice(&key)?, &salt, id, data)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "fips"))]
|
||||
{
|
||||
if native_aes() {
|
||||
encrypt(Aes256Gcm::new_from_slice(&key)?, &salt, id, data)
|
||||
} else {
|
||||
encrypt(ChaCha20Poly1305::new_from_slice(&key)?, &salt, id, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "crypto"))]
|
||||
fn encrypt<T: aes_gcm::aead::Aead>(
|
||||
stream: T,
|
||||
salt: &[u8],
|
||||
id: crate::encdec::id::ID,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, crate::Error> {
|
||||
use crate::error::Error;
|
||||
use aes_gcm::aead::rand_core::OsRng;
|
||||
|
||||
let nonce = T::generate_nonce(&mut OsRng);
|
||||
|
||||
let encryptor = stream.encrypt(&nonce, data).map_err(Error::ErrEncryptFailed)?;
|
||||
|
||||
let mut ciphertext = Vec::with_capacity(salt.len() + 1 + nonce.len() + encryptor.len());
|
||||
ciphertext.extend_from_slice(salt);
|
||||
ciphertext.push(id as u8);
|
||||
ciphertext.extend_from_slice(nonce.as_slice());
|
||||
ciphertext.extend_from_slice(&encryptor);
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "crypto")))]
|
||||
pub fn encrypt_data(_password: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Error> {
|
||||
Ok(data.to_vec())
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// 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.
|
||||
|
||||
use argon2::{Algorithm, Argon2, Params, Version};
|
||||
use pbkdf2::pbkdf2_hmac;
|
||||
use sha2::Sha256;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum ID {
|
||||
Argon2idAESGCM = 0x00,
|
||||
Argon2idChaCHa20Poly1305 = 0x01,
|
||||
Pbkdf2AESGCM = 0x02,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ID {
|
||||
type Error = crate::Error;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x00 => Ok(Self::Argon2idAESGCM),
|
||||
0x01 => Ok(Self::Argon2idChaCHa20Poly1305),
|
||||
0x02 => Ok(Self::Pbkdf2AESGCM),
|
||||
_ => Err(crate::Error::ErrInvalidAlgID(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ID {
|
||||
pub(crate) fn get_key(&self, password: &[u8], salt: &[u8]) -> Result<[u8; 32], crate::Error> {
|
||||
let mut key = [0u8; 32];
|
||||
match self {
|
||||
ID::Pbkdf2AESGCM => pbkdf2_hmac::<Sha256>(password, salt, 8192, &mut key),
|
||||
_ => {
|
||||
let params = Params::new(64 * 1024, 1, 4, Some(32))?;
|
||||
let argon_2id = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
|
||||
argon_2id.hash_password_into(password, salt, &mut key)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_id_enum_values() {
|
||||
// Test enum discriminant values
|
||||
assert_eq!(ID::Argon2idAESGCM as u8, 0x00);
|
||||
assert_eq!(ID::Argon2idChaCHa20Poly1305 as u8, 0x01);
|
||||
assert_eq!(ID::Pbkdf2AESGCM as u8, 0x02);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_id_try_from_valid_values() {
|
||||
// Test valid conversions from u8 to ID
|
||||
assert!(matches!(ID::try_from(0x00), Ok(ID::Argon2idAESGCM)));
|
||||
assert!(matches!(ID::try_from(0x01), Ok(ID::Argon2idChaCHa20Poly1305)));
|
||||
assert!(matches!(ID::try_from(0x02), Ok(ID::Pbkdf2AESGCM)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_id_try_from_invalid_values() {
|
||||
// Test invalid conversions from u8 to ID
|
||||
assert!(ID::try_from(0x03).is_err());
|
||||
assert!(ID::try_from(0xFF).is_err());
|
||||
assert!(ID::try_from(100).is_err());
|
||||
|
||||
// Verify error type
|
||||
if let Err(crate::Error::ErrInvalidAlgID(value)) = ID::try_from(0x03) {
|
||||
assert_eq!(value, 0x03);
|
||||
} else {
|
||||
panic!("Expected ErrInvalidAlgID error");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_id_debug_format() {
|
||||
// Test Debug trait implementation
|
||||
let argon2_aes = ID::Argon2idAESGCM;
|
||||
let argon2_chacha = ID::Argon2idChaCHa20Poly1305;
|
||||
let pbkdf2 = ID::Pbkdf2AESGCM;
|
||||
|
||||
assert_eq!(format!("{argon2_aes:?}"), "Argon2idAESGCM");
|
||||
assert_eq!(format!("{argon2_chacha:?}"), "Argon2idChaCHa20Poly1305");
|
||||
assert_eq!(format!("{pbkdf2:?}"), "Pbkdf2AESGCM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_id_clone_and_copy() {
|
||||
// Test Clone and Copy traits
|
||||
let original = ID::Argon2idAESGCM;
|
||||
let cloned = original;
|
||||
let copied = original;
|
||||
|
||||
assert!(matches!(cloned, ID::Argon2idAESGCM));
|
||||
assert!(matches!(copied, ID::Argon2idAESGCM));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pbkdf2_key_generation() {
|
||||
// Test PBKDF2 key generation
|
||||
let id = ID::Pbkdf2AESGCM;
|
||||
let password = b"test_password";
|
||||
let salt = b"test_salt_16bytes";
|
||||
|
||||
let result = id.get_key(password, salt);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let key = result.expect("PBKDF2 key generation should succeed");
|
||||
assert_eq!(key.len(), 32);
|
||||
|
||||
// Verify deterministic behavior - same inputs should produce same output
|
||||
let result2 = id.get_key(password, salt);
|
||||
assert!(result2.is_ok());
|
||||
assert_eq!(key, result2.expect("PBKDF2 key generation should succeed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_argon2_key_generation() {
|
||||
// Test Argon2id key generation
|
||||
let id = ID::Argon2idAESGCM;
|
||||
let password = b"test_password";
|
||||
let salt = b"test_salt_16bytes";
|
||||
|
||||
let result = id.get_key(password, salt);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let key = result.expect("Argon2id key generation should succeed");
|
||||
assert_eq!(key.len(), 32);
|
||||
|
||||
// Verify deterministic behavior
|
||||
let result2 = id.get_key(password, salt);
|
||||
assert!(result2.is_ok());
|
||||
assert_eq!(key, result2.expect("Argon2id key generation should succeed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_argon2_chacha_key_generation() {
|
||||
// Test Argon2id ChaCha20Poly1305 key generation
|
||||
let id = ID::Argon2idChaCHa20Poly1305;
|
||||
let password = b"test_password";
|
||||
let salt = b"test_salt_16bytes";
|
||||
|
||||
let result = id.get_key(password, salt);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let key = result.expect("Argon2id ChaCha20Poly1305 key generation should succeed");
|
||||
assert_eq!(key.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_generation_with_different_passwords() {
|
||||
// Test that different passwords produce different keys
|
||||
let id = ID::Pbkdf2AESGCM;
|
||||
let salt = b"same_salt_for_all";
|
||||
|
||||
let key1 = id
|
||||
.get_key(b"password1", salt)
|
||||
.expect("Key generation with password1 should succeed");
|
||||
let key2 = id
|
||||
.get_key(b"password2", salt)
|
||||
.expect("Key generation with password2 should succeed");
|
||||
|
||||
assert_ne!(key1, key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_generation_with_different_salts() {
|
||||
// Test that different salts produce different keys
|
||||
let id = ID::Pbkdf2AESGCM;
|
||||
let password = b"same_password";
|
||||
|
||||
let key1 = id
|
||||
.get_key(password, b"salt1_16_bytes__")
|
||||
.expect("Key generation with salt1 should succeed");
|
||||
let key2 = id
|
||||
.get_key(password, b"salt2_16_bytes__")
|
||||
.expect("Key generation with salt2 should succeed");
|
||||
|
||||
assert_ne!(key1, key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_generation_with_empty_inputs() {
|
||||
// Test key generation with empty password and salt
|
||||
let id = ID::Pbkdf2AESGCM;
|
||||
|
||||
let result1 = id.get_key(b"", b"salt");
|
||||
assert!(result1.is_ok());
|
||||
|
||||
let result2 = id.get_key(b"password", b"");
|
||||
assert!(result2.is_ok());
|
||||
|
||||
let result3 = id.get_key(b"", b"");
|
||||
assert!(result3.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_algorithms_produce_valid_keys() {
|
||||
// Test that all algorithm variants can generate valid keys
|
||||
let algorithms = [ID::Argon2idAESGCM, ID::Argon2idChaCHa20Poly1305, ID::Pbkdf2AESGCM];
|
||||
|
||||
let password = b"test_password_123";
|
||||
let salt = b"test_salt_16bytes";
|
||||
|
||||
for algorithm in &algorithms {
|
||||
let result = algorithm.get_key(password, salt);
|
||||
assert!(result.is_ok(), "Algorithm {algorithm:?} should generate valid key");
|
||||
|
||||
let key = result.expect("Key generation should succeed for all algorithms");
|
||||
assert_eq!(key.len(), 32, "Key length should be 32 bytes for {algorithm:?}");
|
||||
|
||||
// Verify key is not all zeros (very unlikely with proper implementation)
|
||||
assert_ne!(key, [0u8; 32], "Key should not be all zeros for {algorithm:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_round_trip_conversion() {
|
||||
// Test round-trip conversion: ID -> u8 -> ID
|
||||
let original_ids = [ID::Argon2idAESGCM, ID::Argon2idChaCHa20Poly1305, ID::Pbkdf2AESGCM];
|
||||
|
||||
for original in &original_ids {
|
||||
let as_u8 = *original as u8;
|
||||
let converted_back = ID::try_from(as_u8).expect("Round-trip conversion should succeed");
|
||||
|
||||
assert!(matches!(
|
||||
(original, converted_back),
|
||||
(ID::Argon2idAESGCM, ID::Argon2idAESGCM)
|
||||
| (ID::Argon2idChaCHa20Poly1305, ID::Argon2idChaCHa20Poly1305)
|
||||
| (ID::Pbkdf2AESGCM, ID::Pbkdf2AESGCM)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_generation_consistency_across_algorithms() {
|
||||
// Test that different algorithms produce different keys for same input
|
||||
let password = b"consistent_password";
|
||||
let salt = b"consistent_salt_";
|
||||
|
||||
let key_argon2_aes = ID::Argon2idAESGCM
|
||||
.get_key(password, salt)
|
||||
.expect("Argon2id AES key generation should succeed");
|
||||
let key_argon2_chacha = ID::Argon2idChaCHa20Poly1305
|
||||
.get_key(password, salt)
|
||||
.expect("Argon2id ChaCha key generation should succeed");
|
||||
let key_pbkdf2 = ID::Pbkdf2AESGCM
|
||||
.get_key(password, salt)
|
||||
.expect("PBKDF2 key generation should succeed");
|
||||
|
||||
// Different algorithms should produce different keys
|
||||
assert_ne!(key_argon2_aes, key_pbkdf2);
|
||||
assert_ne!(key_argon2_chacha, key_pbkdf2);
|
||||
// Note: Argon2 variants might produce same key since they use same algorithm
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
// 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.
|
||||
|
||||
use crate::{decrypt_data, encrypt_data};
|
||||
|
||||
const PASSWORD: &[u8] = "test_password".as_bytes();
|
||||
const LONG_PASSWORD: &[u8] = "very_long_password_with_many_characters_for_testing_purposes_123456789".as_bytes();
|
||||
const EMPTY_PASSWORD: &[u8] = b"";
|
||||
|
||||
#[test_case::test_case("hello world".as_bytes())]
|
||||
#[test_case::test_case(&[])]
|
||||
#[test_case::test_case(&[1, 2, 3])]
|
||||
#[test_case::test_case(&[3, 2, 1])]
|
||||
fn test_basic_encrypt_decrypt_roundtrip(input: &[u8]) -> Result<(), crate::Error> {
|
||||
let encrypted = encrypt_data(PASSWORD, input)?;
|
||||
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
|
||||
assert_eq!(input, decrypted, "input is not equal output");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_with_different_passwords() -> Result<(), crate::Error> {
|
||||
let data = b"sensitive data";
|
||||
let password1 = b"password1";
|
||||
let password2 = b"password2";
|
||||
|
||||
let encrypted = encrypt_data(password1, data)?;
|
||||
|
||||
// Decrypting with correct password should work
|
||||
let decrypted = decrypt_data(password1, &encrypted)?;
|
||||
assert_eq!(data, decrypted.as_slice());
|
||||
|
||||
// Decrypting with wrong password should fail
|
||||
let result = decrypt_data(password2, &encrypted);
|
||||
assert!(result.is_err(), "Decryption with wrong password should fail");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_empty_data() -> Result<(), crate::Error> {
|
||||
let empty_data = b"";
|
||||
let encrypted = encrypt_data(PASSWORD, empty_data)?;
|
||||
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
|
||||
assert_eq!(empty_data, decrypted.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_large_data() -> Result<(), crate::Error> {
|
||||
// Test with 1MB of data
|
||||
let large_data = vec![0xAB; 1024 * 1024];
|
||||
let encrypted = encrypt_data(PASSWORD, &large_data)?;
|
||||
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
|
||||
assert_eq!(large_data, decrypted);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_with_empty_password() -> Result<(), crate::Error> {
|
||||
let data = b"test data";
|
||||
let encrypted = encrypt_data(EMPTY_PASSWORD, data)?;
|
||||
let decrypted = decrypt_data(EMPTY_PASSWORD, &encrypted)?;
|
||||
assert_eq!(data, decrypted.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_with_long_password() -> Result<(), crate::Error> {
|
||||
let data = b"test data with long password";
|
||||
let encrypted = encrypt_data(LONG_PASSWORD, data)?;
|
||||
let decrypted = decrypt_data(LONG_PASSWORD, &encrypted)?;
|
||||
assert_eq!(data, decrypted.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_binary_data() -> Result<(), crate::Error> {
|
||||
// Test with various binary patterns
|
||||
let binary_patterns = [
|
||||
vec![0x00; 100], // All zeros
|
||||
vec![0xFF; 100], // All ones
|
||||
(0..=255u8).cycle().take(1000).collect::<Vec<u8>>(), // Sequential pattern
|
||||
[0xAA, 0x55].repeat(500), // Alternating pattern
|
||||
];
|
||||
|
||||
for pattern in &binary_patterns {
|
||||
let encrypted = encrypt_data(PASSWORD, pattern)?;
|
||||
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
|
||||
assert_eq!(pattern, &decrypted, "Binary pattern mismatch");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_decrypt_unicode_data() -> Result<(), crate::Error> {
|
||||
let unicode_strings = [
|
||||
"Hello, 世界! 🌍",
|
||||
"Тест на русском языке",
|
||||
"العربية اختبار",
|
||||
"🚀🔐💻🌟⭐",
|
||||
"Mixed: ASCII + 中文 + العربية + 🎉",
|
||||
];
|
||||
|
||||
for text in &unicode_strings {
|
||||
let data = text.as_bytes();
|
||||
let encrypted = encrypt_data(PASSWORD, data)?;
|
||||
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
|
||||
assert_eq!(data, decrypted.as_slice(), "Unicode data mismatch for: {text}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_with_corrupted_data() {
|
||||
let data = b"test data";
|
||||
let encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
|
||||
|
||||
// Test various corruption scenarios
|
||||
let corruption_tests = [
|
||||
(0, "Corrupt first byte"),
|
||||
(encrypted.len() - 1, "Corrupt last byte"),
|
||||
(encrypted.len() / 2, "Corrupt middle byte"),
|
||||
];
|
||||
|
||||
for (corrupt_index, description) in &corruption_tests {
|
||||
let mut corrupted = encrypted.clone();
|
||||
corrupted[*corrupt_index] ^= 0xFF; // Flip all bits
|
||||
|
||||
let result = decrypt_data(PASSWORD, &corrupted);
|
||||
assert!(result.is_err(), "{description} should cause decryption to fail");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_with_truncated_data() {
|
||||
let data = b"test data for truncation";
|
||||
let encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
|
||||
|
||||
// Test truncation at various lengths
|
||||
let truncation_lengths = [
|
||||
0, // Empty data
|
||||
10, // Very short
|
||||
32, // Salt length
|
||||
44, // Just before nonce
|
||||
encrypted.len() - 1, // Missing last byte
|
||||
];
|
||||
|
||||
for &length in &truncation_lengths {
|
||||
let truncated = &encrypted[..length.min(encrypted.len())];
|
||||
let result = decrypt_data(PASSWORD, truncated);
|
||||
assert!(result.is_err(), "Truncated data (length {length}) should cause decryption to fail");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_with_invalid_header() {
|
||||
let data = b"test data";
|
||||
let mut encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
|
||||
|
||||
// Corrupt the algorithm ID (byte 32)
|
||||
if encrypted.len() > 32 {
|
||||
encrypted[32] = 0xFF; // Invalid algorithm ID
|
||||
let result = decrypt_data(PASSWORD, &encrypted);
|
||||
assert!(result.is_err(), "Invalid algorithm ID should cause decryption to fail");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encryption_produces_different_outputs() -> Result<(), crate::Error> {
|
||||
let data = b"same data";
|
||||
|
||||
// Encrypt the same data multiple times
|
||||
let encrypted1 = encrypt_data(PASSWORD, data)?;
|
||||
let encrypted2 = encrypt_data(PASSWORD, data)?;
|
||||
|
||||
// Encrypted outputs should be different due to random salt and nonce
|
||||
assert_ne!(encrypted1, encrypted2, "Encryption should produce different outputs for same input");
|
||||
|
||||
// But both should decrypt to the same original data
|
||||
let decrypted1 = decrypt_data(PASSWORD, &encrypted1)?;
|
||||
let decrypted2 = decrypt_data(PASSWORD, &encrypted2)?;
|
||||
assert_eq!(decrypted1, decrypted2);
|
||||
assert_eq!(data, decrypted1.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypted_data_structure() -> Result<(), crate::Error> {
|
||||
let data = b"test data";
|
||||
let encrypted = encrypt_data(PASSWORD, data)?;
|
||||
|
||||
// Encrypted data should be longer than original (due to salt, nonce, tag)
|
||||
assert!(encrypted.len() > data.len(), "Encrypted data should be longer than original");
|
||||
|
||||
// Should have at least: 32 bytes salt + 1 byte ID + 12 bytes nonce + data + 16 bytes tag
|
||||
let min_expected_length = 32 + 1 + 12 + data.len() + 16;
|
||||
assert!(
|
||||
encrypted.len() >= min_expected_length,
|
||||
"Encrypted data length {} should be at least {}",
|
||||
encrypted.len(),
|
||||
min_expected_length
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_variations() -> Result<(), crate::Error> {
|
||||
let data = b"test data";
|
||||
|
||||
let password_variations = [
|
||||
b"a".as_slice(), // Single character
|
||||
b"12345".as_slice(), // Numeric
|
||||
b"!@#$%^&*()".as_slice(), // Special characters
|
||||
b"\x00\x01\x02\x03".as_slice(), // Binary password
|
||||
"密码测试".as_bytes(), // Unicode password
|
||||
&[0xFF; 64], // Long binary password
|
||||
];
|
||||
|
||||
for password in &password_variations {
|
||||
let encrypted = encrypt_data(password, data)?;
|
||||
let decrypted = decrypt_data(password, &encrypted)?;
|
||||
assert_eq!(data, decrypted.as_slice(), "Failed with password: {password:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deterministic_with_same_salt_and_nonce() {
|
||||
// Note: This test is more for understanding the behavior
|
||||
// In real implementation, salt and nonce should be random
|
||||
let data = b"test data";
|
||||
|
||||
let encrypted1 = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
|
||||
let encrypted2 = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
|
||||
|
||||
// Due to random salt and nonce, outputs should be different
|
||||
assert_ne!(encrypted1, encrypted2, "Encryption should use random salt/nonce");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cross_platform_compatibility() -> Result<(), crate::Error> {
|
||||
// Test data that might behave differently on different platforms
|
||||
let test_cases = [
|
||||
vec![0x00, 0x01, 0x02, 0x03], // Low values
|
||||
vec![0xFC, 0xFD, 0xFE, 0xFF], // High values
|
||||
(0..256u16).map(|x| (x % 256) as u8).collect::<Vec<u8>>(), // Full byte range
|
||||
];
|
||||
|
||||
for test_data in &test_cases {
|
||||
let encrypted = encrypt_data(PASSWORD, test_data)?;
|
||||
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
|
||||
assert_eq!(test_data, &decrypted, "Cross-platform compatibility failed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memory_safety_with_large_passwords() -> Result<(), crate::Error> {
|
||||
let data = b"test data";
|
||||
|
||||
// Test with very large passwords
|
||||
let large_passwords = [
|
||||
vec![b'a'; 1024], // 1KB password
|
||||
vec![b'x'; 10 * 1024], // 10KB password
|
||||
(0..=255u8).cycle().take(5000).collect::<Vec<u8>>(), // 5KB varied password
|
||||
];
|
||||
|
||||
for password in &large_passwords {
|
||||
let encrypted = encrypt_data(password, data)?;
|
||||
let decrypted = decrypt_data(password, &encrypted)?;
|
||||
assert_eq!(data, decrypted.as_slice(), "Failed with large password of size {}", password.len());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concurrent_encryption_safety() -> Result<(), crate::Error> {
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
let data = Arc::new(b"concurrent test data".to_vec());
|
||||
let password = Arc::new(b"concurrent_password".to_vec());
|
||||
|
||||
let handles: Vec<_> = (0..10)
|
||||
.map(|i| {
|
||||
let data = Arc::clone(&data);
|
||||
let password = Arc::clone(&password);
|
||||
|
||||
thread::spawn(move || {
|
||||
let encrypted = encrypt_data(&password, &data).expect("Encryption should succeed");
|
||||
let decrypted = decrypt_data(&password, &encrypted).expect("Decryption should succeed");
|
||||
assert_eq!(**data, decrypted, "Thread {i} failed");
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for handle in handles {
|
||||
handle.join().expect("Thread should complete successfully");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user