mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-09 22:59:59 +00:00
🔒 Upgrade Cryptography Libraries to Latest RC Versions (#837)
* fix * chore: upgrade cryptography libraries to RC versions - Upgrade aes-gcm to 0.11.0-rc.2 with rand_core support - Upgrade chacha20poly1305 to 0.11.0-rc.2 - Upgrade argon2 to 0.6.0-rc.2 with std features - Upgrade hmac to 0.13.0-rc.3 - Upgrade pbkdf2 to 0.13.0-rc.2 - Upgrade rsa to 0.10.0-rc.10 - Upgrade sha1 and sha2 to 0.11.0-rc.3 - Upgrade md-5 to 0.11.0-rc.3 These upgrades provide enhanced security features and performance improvements while maintaining backward compatibility with existing encryption workflows. * add * improve code * fix
This commit is contained in:
@@ -29,6 +29,7 @@ base64-simd = { workspace = true }
|
||||
rsa = { workspace = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
rand.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -12,11 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use rsa::Pkcs1v15Encrypt;
|
||||
use rsa::{
|
||||
RsaPrivateKey, RsaPublicKey,
|
||||
Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey,
|
||||
pkcs8::{DecodePrivateKey, DecodePublicKey},
|
||||
rand_core::OsRng,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Error, Result};
|
||||
@@ -33,8 +31,9 @@ pub struct Token {
|
||||
/// Returns the encrypted string processed by base64
|
||||
pub fn gencode(token: &Token, key: &str) -> Result<String> {
|
||||
let data = serde_json::to_vec(token)?;
|
||||
let mut rng = rand::rng();
|
||||
let public_key = RsaPublicKey::from_public_key_pem(key).map_err(Error::other)?;
|
||||
let encrypted_data = public_key.encrypt(&mut OsRng, Pkcs1v15Encrypt, &data).map_err(Error::other)?;
|
||||
let encrypted_data = public_key.encrypt(&mut rng, Pkcs1v15Encrypt, &data).map_err(Error::other)?;
|
||||
Ok(base64_simd::URL_SAFE_NO_PAD.encode_to_string(&encrypted_data))
|
||||
}
|
||||
|
||||
@@ -76,9 +75,10 @@ mod tests {
|
||||
pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding},
|
||||
};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[test]
|
||||
fn test_gencode_and_parse() {
|
||||
let mut rng = OsRng;
|
||||
let mut rng = rand::rng();
|
||||
let bits = 2048;
|
||||
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
|
||||
let public_key = RsaPublicKey::from(&private_key);
|
||||
@@ -101,7 +101,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_token() {
|
||||
let private_key_pem = RsaPrivateKey::new(&mut OsRng, 2048)
|
||||
let mut rng = rand::rng();
|
||||
let private_key_pem = RsaPrivateKey::new(&mut rng, 2048)
|
||||
.expect("Failed to generate private key")
|
||||
.to_pkcs8_pem(LineEnding::LF)
|
||||
.unwrap();
|
||||
|
||||
@@ -29,7 +29,7 @@ documentation = "https://docs.rs/rustfs-crypto/latest/rustfs_crypto/"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
aes-gcm = { workspace = true, features = ["std"], optional = true }
|
||||
aes-gcm = { workspace = true, optional = true }
|
||||
argon2 = { workspace = true, features = ["std"], optional = true }
|
||||
cfg-if = { workspace = true }
|
||||
chacha20poly1305 = { workspace = true, optional = true }
|
||||
|
||||
@@ -19,127 +19,37 @@ pub fn decrypt_data(password: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Erro
|
||||
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..];
|
||||
let (salt, id, nonce_slice) = (&data[..32], ID::try_from(data[32])?, &data[33..45]);
|
||||
let body = &data[HEADER_LENGTH..];
|
||||
|
||||
match id {
|
||||
ID::Argon2idChaCHa20Poly1305 => {
|
||||
let key = id.get_key(password, salt)?;
|
||||
decrypt(ChaCha20Poly1305::new_from_slice(&key)?, nonce, data)
|
||||
decrypt(ChaCha20Poly1305::new_from_slice(&key)?, nonce_slice, body)
|
||||
}
|
||||
_ => {
|
||||
let key = id.get_key(password, salt)?;
|
||||
decrypt(Aes256Gcm::new_from_slice(&key)?, nonce, data)
|
||||
decrypt(Aes256Gcm::new_from_slice(&key)?, nonce_slice, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 decrypt<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)
|
||||
use aes_gcm::AeadCore;
|
||||
use aes_gcm::aead::array::Array;
|
||||
use core::convert::TryFrom;
|
||||
|
||||
let nonce_arr: Array<u8, <T as AeadCore>::NonceSize> =
|
||||
Array::try_from(nonce).map_err(|_| Error::ErrDecryptFailed(aes_gcm::aead::Error))?;
|
||||
stream.decrypt(&nonce_arr, data).map_err(Error::ErrDecryptFailed)
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "crypto")))]
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn encrypt_data(password: &[u8], data: &[u8]) -> Result<Vec<u8>, crate::Erro
|
||||
if native_aes() {
|
||||
encrypt(Aes256Gcm::new_from_slice(&key)?, &salt, id, data)
|
||||
} else {
|
||||
encrypt(ChaCha20Poly1305::new_from_slice(&key)?, &salt, id, data)
|
||||
encrypt(chacha20poly1305::ChaCha20Poly1305::new_from_slice(&key)?, &salt, id, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,16 +56,19 @@ fn encrypt<T: aes_gcm::aead::Aead>(
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>, crate::Error> {
|
||||
use crate::error::Error;
|
||||
use aes_gcm::aead::rand_core::OsRng;
|
||||
use aes_gcm::AeadCore;
|
||||
use aes_gcm::aead::array::Array;
|
||||
use rand::RngCore;
|
||||
|
||||
let nonce = T::generate_nonce(&mut OsRng);
|
||||
let mut nonce: Array<u8, <T as AeadCore>::NonceSize> = Array::default();
|
||||
rand::rng().fill_bytes(&mut nonce);
|
||||
|
||||
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(&nonce);
|
||||
ciphertext.extend_from_slice(&encryptor);
|
||||
|
||||
Ok(ciphertext)
|
||||
|
||||
@@ -106,6 +106,7 @@ serde_urlencoded.workspace = true
|
||||
google-cloud-storage = { workspace = true }
|
||||
google-cloud-auth = { workspace = true }
|
||||
aws-config = { workspace = true }
|
||||
faster-hex = { workspace = true }
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
nix = { workspace = true }
|
||||
|
||||
@@ -115,10 +115,9 @@ struct ExpiryTask {
|
||||
impl ExpiryOp for ExpiryTask {
|
||||
fn op_hash(&self) -> u64 {
|
||||
let mut hasher = Sha256::new();
|
||||
let _ = hasher.write(format!("{}", self.obj_info.bucket).as_bytes());
|
||||
let _ = hasher.write(format!("{}", self.obj_info.name).as_bytes());
|
||||
hasher.flush();
|
||||
xxh64::xxh64(hasher.clone().finalize().as_slice(), XXHASH_SEED)
|
||||
hasher.update(format!("{}", self.obj_info.bucket).as_bytes());
|
||||
hasher.update(format!("{}", self.obj_info.name).as_bytes());
|
||||
xxh64::xxh64(hasher.finalize().as_slice(), XXHASH_SEED)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@@ -171,10 +170,9 @@ struct FreeVersionTask(ObjectInfo);
|
||||
impl ExpiryOp for FreeVersionTask {
|
||||
fn op_hash(&self) -> u64 {
|
||||
let mut hasher = Sha256::new();
|
||||
let _ = hasher.write(format!("{}", self.0.transitioned_object.tier).as_bytes());
|
||||
let _ = hasher.write(format!("{}", self.0.transitioned_object.name).as_bytes());
|
||||
hasher.flush();
|
||||
xxh64::xxh64(hasher.clone().finalize().as_slice(), XXHASH_SEED)
|
||||
hasher.update(format!("{}", self.0.transitioned_object.tier).as_bytes());
|
||||
hasher.update(format!("{}", self.0.transitioned_object.name).as_bytes());
|
||||
xxh64::xxh64(hasher.finalize().as_slice(), XXHASH_SEED)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@@ -191,10 +189,9 @@ struct NewerNoncurrentTask {
|
||||
impl ExpiryOp for NewerNoncurrentTask {
|
||||
fn op_hash(&self) -> u64 {
|
||||
let mut hasher = Sha256::new();
|
||||
let _ = hasher.write(format!("{}", self.bucket).as_bytes());
|
||||
let _ = hasher.write(format!("{}", self.versions[0].object_name).as_bytes());
|
||||
hasher.flush();
|
||||
xxh64::xxh64(hasher.clone().finalize().as_slice(), XXHASH_SEED)
|
||||
hasher.update(format!("{}", self.bucket).as_bytes());
|
||||
hasher.update(format!("{}", self.versions[0].object_name).as_bytes());
|
||||
xxh64::xxh64(hasher.finalize().as_slice(), XXHASH_SEED)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@@ -415,10 +412,9 @@ struct TransitionTask {
|
||||
impl ExpiryOp for TransitionTask {
|
||||
fn op_hash(&self) -> u64 {
|
||||
let mut hasher = Sha256::new();
|
||||
let _ = hasher.write(format!("{}", self.obj_info.bucket).as_bytes());
|
||||
//let _ = hasher.write(format!("{}", self.obj_info.versions[0].object_name).as_bytes());
|
||||
hasher.flush();
|
||||
xxh64::xxh64(hasher.clone().finalize().as_slice(), XXHASH_SEED)
|
||||
hasher.update(format!("{}", self.obj_info.bucket).as_bytes());
|
||||
// hasher.update(format!("{}", self.obj_info.versions[0].object_name).as_bytes());
|
||||
xxh64::xxh64(hasher.finalize().as_slice(), XXHASH_SEED)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@@ -480,7 +476,7 @@ impl TransitionState {
|
||||
.and_then(|s| s.parse::<i64>().ok())
|
||||
.unwrap_or_else(|| std::cmp::min(num_cpus::get() as i64, 16));
|
||||
let mut n = max_workers;
|
||||
let tw = 8; //globalILMConfig.getTransitionWorkers();
|
||||
let tw = 8; //globalILMConfig.getTransitionWorkers();
|
||||
if tw > 0 {
|
||||
n = tw;
|
||||
}
|
||||
@@ -760,9 +756,8 @@ pub async fn expire_transitioned_object(
|
||||
pub fn gen_transition_objname(bucket: &str) -> Result<String, Error> {
|
||||
let us = Uuid::new_v4().to_string();
|
||||
let mut hasher = Sha256::new();
|
||||
let _ = hasher.write(format!("{}/{}", get_global_deployment_id().unwrap_or_default(), bucket).as_bytes());
|
||||
hasher.flush();
|
||||
let hash = rustfs_utils::crypto::hex(hasher.clone().finalize().as_slice());
|
||||
hasher.update(format!("{}/{}", get_global_deployment_id().unwrap_or_default(), bucket).as_bytes());
|
||||
let hash = rustfs_utils::crypto::hex(hasher.finalize().as_slice());
|
||||
let obj = format!("{}/{}/{}/{}", &hash[0..16], &us[0..2], &us[2..4], &us);
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::any::Any;
|
||||
use std::io::{Cursor, Write};
|
||||
use std::io::Write;
|
||||
use xxhash_rust::xxh64;
|
||||
|
||||
use super::bucket_lifecycle_ops::{ExpiryOp, GLOBAL_ExpiryState, TransitionedObject};
|
||||
@@ -128,10 +128,9 @@ pub struct Jentry {
|
||||
impl ExpiryOp for Jentry {
|
||||
fn op_hash(&self) -> u64 {
|
||||
let mut hasher = Sha256::new();
|
||||
let _ = hasher.write(format!("{}", self.tier_name).as_bytes());
|
||||
let _ = hasher.write(format!("{}", self.obj_name).as_bytes());
|
||||
hasher.flush();
|
||||
xxh64::xxh64(hasher.clone().finalize().as_slice(), XXHASH_SEED)
|
||||
hasher.update(format!("{}", self.tier_name).as_bytes());
|
||||
hasher.update(format!("{}", self.obj_name).as_bytes());
|
||||
xxh64::xxh64(hasher.finalize().as_slice(), XXHASH_SEED)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use crate::global::get_global_action_cred;
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose;
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use http::Method;
|
||||
|
||||
@@ -1323,13 +1323,13 @@ impl SetDisks {
|
||||
|
||||
if etag_only || mod_valid {
|
||||
for part in meta.parts.iter() {
|
||||
let _ = hasher.write(format!("part.{}", part.number).as_bytes())?;
|
||||
let _ = hasher.write(format!("part.{}", part.size).as_bytes())?;
|
||||
hasher.update(format!("part.{}", part.number).as_bytes());
|
||||
hasher.update(format!("part.{}", part.size).as_bytes());
|
||||
}
|
||||
|
||||
if !meta.deleted && meta.size != 0 {
|
||||
let _ = hasher.write(format!("{}+{}", meta.erasure.data_blocks, meta.erasure.parity_blocks).as_bytes())?;
|
||||
let _ = hasher.write(format!("{:?}", meta.erasure.distribution).as_bytes())?;
|
||||
hasher.update(format!("{}+{}", meta.erasure.data_blocks, meta.erasure.parity_blocks).as_bytes());
|
||||
hasher.update(format!("{:?}", meta.erasure.distribution).as_bytes());
|
||||
}
|
||||
|
||||
if meta.is_remote() {
|
||||
@@ -1340,8 +1340,6 @@ impl SetDisks {
|
||||
|
||||
// TODO: IsCompressed
|
||||
|
||||
hasher.flush()?;
|
||||
|
||||
meta_hashes[i] = Some(hex(hasher.clone().finalize().as_slice()));
|
||||
|
||||
hasher.reset();
|
||||
@@ -6553,9 +6551,11 @@ fn get_complete_multipart_md5(parts: &[CompletePart]) -> String {
|
||||
}
|
||||
|
||||
let mut hasher = Md5::new();
|
||||
let _ = hasher.write(&buf);
|
||||
hasher.update(&buf);
|
||||
|
||||
format!("{:x}-{}", hasher.finalize(), parts.len())
|
||||
let digest = hasher.finalize();
|
||||
let etag_hex = faster_hex::hex_string(digest.as_slice());
|
||||
format!("{}-{}", etag_hex, parts.len())
|
||||
}
|
||||
|
||||
pub fn canonicalize_etag(etag: &str) -> String {
|
||||
|
||||
@@ -19,12 +19,12 @@ use crate::config::KmsConfig;
|
||||
use crate::config::LocalConfig;
|
||||
use crate::error::{KmsError, Result};
|
||||
use crate::types::*;
|
||||
use aes_gcm::aead::rand_core::RngCore;
|
||||
use aes_gcm::{
|
||||
Aes256Gcm, Key, Nonce,
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
aead::{Aead, KeyInit},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
@@ -105,8 +105,9 @@ impl LocalKmsClient {
|
||||
hasher.update(master_key.as_bytes());
|
||||
hasher.update(b"rustfs-kms-local"); // Salt to prevent rainbow tables
|
||||
let hash = hasher.finalize();
|
||||
|
||||
Ok(*Key::<Aes256Gcm>::from_slice(&hash))
|
||||
let key = Key::<Aes256Gcm>::try_from(hash.as_slice())
|
||||
.map_err(|_| KmsError::cryptographic_error("key", "Invalid key length"))?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// Get the file path for a master key
|
||||
@@ -117,7 +118,6 @@ impl LocalKmsClient {
|
||||
/// Load a master key from disk
|
||||
async fn load_master_key(&self, key_id: &str) -> Result<MasterKey> {
|
||||
let key_path = self.master_key_path(key_id);
|
||||
|
||||
if !key_path.exists() {
|
||||
return Err(KmsError::key_not_found(key_id));
|
||||
}
|
||||
@@ -127,9 +127,16 @@ impl LocalKmsClient {
|
||||
|
||||
// Decrypt key material if master cipher is available
|
||||
let _key_material = if let Some(ref cipher) = self.master_cipher {
|
||||
let nonce = Nonce::from_slice(&stored_key.nonce);
|
||||
if stored_key.nonce.len() != 12 {
|
||||
return Err(KmsError::cryptographic_error("nonce", "Invalid nonce length"));
|
||||
}
|
||||
|
||||
let mut nonce_array = [0u8; 12];
|
||||
nonce_array.copy_from_slice(&stored_key.nonce);
|
||||
let nonce = Nonce::from(nonce_array);
|
||||
|
||||
cipher
|
||||
.decrypt(nonce, stored_key.encrypted_key_material.as_ref())
|
||||
.decrypt(&nonce, stored_key.encrypted_key_material.as_ref())
|
||||
.map_err(|e| KmsError::cryptographic_error("decrypt", e.to_string()))?
|
||||
} else {
|
||||
stored_key.encrypted_key_material
|
||||
@@ -155,7 +162,10 @@ impl LocalKmsClient {
|
||||
|
||||
// Encrypt key material if master cipher is available
|
||||
let (encrypted_key_material, nonce) = if let Some(ref cipher) = self.master_cipher {
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
rand::rng().fill(&mut nonce_bytes[..]);
|
||||
let nonce = Nonce::from(nonce_bytes);
|
||||
|
||||
let encrypted = cipher
|
||||
.encrypt(&nonce, key_material)
|
||||
.map_err(|e| KmsError::cryptographic_error("encrypt", e.to_string()))?;
|
||||
@@ -202,7 +212,7 @@ impl LocalKmsClient {
|
||||
/// Generate a random 256-bit key
|
||||
fn generate_key_material() -> Vec<u8> {
|
||||
let mut key_material = vec![0u8; 32]; // 256 bits
|
||||
OsRng.fill_bytes(&mut key_material);
|
||||
rand::rng().fill(&mut key_material[..]);
|
||||
key_material
|
||||
}
|
||||
|
||||
@@ -219,9 +229,14 @@ impl LocalKmsClient {
|
||||
|
||||
// Decrypt key material if master cipher is available
|
||||
let key_material = if let Some(ref cipher) = self.master_cipher {
|
||||
let nonce = Nonce::from_slice(&stored_key.nonce);
|
||||
if stored_key.nonce.len() != 12 {
|
||||
return Err(KmsError::cryptographic_error("nonce", "Invalid nonce length"));
|
||||
}
|
||||
let mut nonce_array = [0u8; 12];
|
||||
nonce_array.copy_from_slice(&stored_key.nonce);
|
||||
let nonce = Nonce::from(nonce_array);
|
||||
cipher
|
||||
.decrypt(nonce, stored_key.encrypted_key_material.as_ref())
|
||||
.decrypt(&nonce, stored_key.encrypted_key_material.as_ref())
|
||||
.map_err(|e| KmsError::cryptographic_error("decrypt", e.to_string()))?
|
||||
} else {
|
||||
stored_key.encrypted_key_material
|
||||
@@ -234,25 +249,39 @@ impl LocalKmsClient {
|
||||
async fn encrypt_with_master_key(&self, key_id: &str, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
// Load the actual master key material
|
||||
let key_material = self.get_key_material(key_id).await?;
|
||||
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_material));
|
||||
let key = Key::<Aes256Gcm>::try_from(key_material.as_slice())
|
||||
.map_err(|_| KmsError::cryptographic_error("key", "Invalid key length"))?;
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
rand::rng().fill(&mut nonce_bytes[..]);
|
||||
|
||||
let nonce = Nonce::from(nonce_bytes);
|
||||
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let ciphertext = cipher
|
||||
.encrypt(&nonce, plaintext)
|
||||
.map_err(|e| KmsError::cryptographic_error("encrypt", e.to_string()))?;
|
||||
|
||||
Ok((ciphertext, nonce.to_vec()))
|
||||
Ok((ciphertext, nonce_bytes.to_vec()))
|
||||
}
|
||||
|
||||
/// Decrypt data using a master key
|
||||
async fn decrypt_with_master_key(&self, key_id: &str, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
|
||||
if nonce.len() != 12 {
|
||||
return Err(KmsError::cryptographic_error("nonce", "Invalid nonce length"));
|
||||
}
|
||||
// Load the actual master key material
|
||||
let key_material = self.get_key_material(key_id).await?;
|
||||
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_material));
|
||||
let key = Key::<Aes256Gcm>::try_from(key_material.as_slice())
|
||||
.map_err(|_| KmsError::cryptographic_error("key", "Invalid key length"))?;
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
let mut nonce_array = [0u8; 12];
|
||||
nonce_array.copy_from_slice(nonce);
|
||||
let nonce_ref = Nonce::from(nonce_array);
|
||||
|
||||
let nonce = Nonce::from_slice(nonce);
|
||||
let plaintext = cipher
|
||||
.decrypt(nonce, ciphertext)
|
||||
.decrypt(&nonce_ref, ciphertext)
|
||||
.map_err(|e| KmsError::cryptographic_error("decrypt", e.to_string()))?;
|
||||
|
||||
Ok(plaintext)
|
||||
@@ -275,7 +304,7 @@ impl KmsClient for LocalKmsClient {
|
||||
};
|
||||
|
||||
let mut plaintext_key = vec![0u8; key_length];
|
||||
OsRng.fill_bytes(&mut plaintext_key);
|
||||
rand::rng().fill(&mut plaintext_key[..]);
|
||||
|
||||
// Encrypt the data key with the master key
|
||||
let (encrypted_key, nonce) = self.encrypt_with_master_key(&request.master_key_id, &plaintext_key).await?;
|
||||
@@ -776,9 +805,14 @@ impl KmsBackend for LocalKmsBackend {
|
||||
|
||||
// Decrypt the existing key material to preserve it
|
||||
let existing_key_material = if let Some(ref cipher) = self.client.master_cipher {
|
||||
let nonce = Nonce::from_slice(&stored_key.nonce);
|
||||
if stored_key.nonce.len() != 12 {
|
||||
return Err(KmsError::cryptographic_error("nonce", "Invalid nonce length"));
|
||||
}
|
||||
let mut nonce_array = [0u8; 12];
|
||||
nonce_array.copy_from_slice(&stored_key.nonce);
|
||||
let nonce = Nonce::from(nonce_array);
|
||||
cipher
|
||||
.decrypt(nonce, stored_key.encrypted_key_material.as_ref())
|
||||
.decrypt(&nonce, stored_key.encrypted_key_material.as_ref())
|
||||
.map_err(|e| KmsError::cryptographic_error("decrypt", e.to_string()))?
|
||||
} else {
|
||||
stored_key.encrypted_key_material
|
||||
|
||||
@@ -20,7 +20,6 @@ use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub mod local;
|
||||
|
||||
pub mod vault;
|
||||
|
||||
/// Abstract KMS client interface that all backends must implement
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
use crate::error::{KmsError, Result};
|
||||
use crate::types::EncryptionAlgorithm;
|
||||
use aes_gcm::aead::rand_core::RngCore;
|
||||
use aes_gcm::{
|
||||
Aes256Gcm, Key, Nonce,
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
aead::{Aead, KeyInit},
|
||||
};
|
||||
use chacha20poly1305::ChaCha20Poly1305;
|
||||
use rand::Rng;
|
||||
|
||||
/// Trait for object encryption ciphers
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
@@ -57,8 +57,8 @@ impl AesCipher {
|
||||
return Err(KmsError::invalid_key_size(32, key.len()));
|
||||
}
|
||||
|
||||
let key = Key::<Aes256Gcm>::from_slice(key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
let key = Key::<Aes256Gcm>::try_from(key).map_err(|_| KmsError::cryptographic_error("key", "Invalid key length"))?;
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
Ok(Self { cipher })
|
||||
}
|
||||
@@ -70,12 +70,12 @@ impl ObjectCipher for AesCipher {
|
||||
return Err(KmsError::invalid_key_size(12, iv.len()));
|
||||
}
|
||||
|
||||
let nonce = Nonce::from_slice(iv);
|
||||
let nonce = Nonce::try_from(iv).map_err(|_| KmsError::cryptographic_error("nonce", "Invalid nonce length"))?;
|
||||
|
||||
// AES-GCM includes the tag in the ciphertext
|
||||
let ciphertext_with_tag = self
|
||||
.cipher
|
||||
.encrypt(nonce, aes_gcm::aead::Payload { msg: plaintext, aad })
|
||||
.encrypt(&nonce, aes_gcm::aead::Payload { msg: plaintext, aad })
|
||||
.map_err(KmsError::from_aes_gcm_error)?;
|
||||
|
||||
// Split ciphertext and tag
|
||||
@@ -98,7 +98,7 @@ impl ObjectCipher for AesCipher {
|
||||
return Err(KmsError::invalid_key_size(self.tag_size(), tag.len()));
|
||||
}
|
||||
|
||||
let nonce = Nonce::from_slice(iv);
|
||||
let nonce = Nonce::try_from(iv).map_err(|_| KmsError::cryptographic_error("nonce", "Invalid nonce length"))?;
|
||||
|
||||
// Combine ciphertext and tag for AES-GCM
|
||||
let mut ciphertext_with_tag = ciphertext.to_vec();
|
||||
@@ -107,7 +107,7 @@ impl ObjectCipher for AesCipher {
|
||||
let plaintext = self
|
||||
.cipher
|
||||
.decrypt(
|
||||
nonce,
|
||||
&nonce,
|
||||
aes_gcm::aead::Payload {
|
||||
msg: &ciphertext_with_tag,
|
||||
aad,
|
||||
@@ -147,8 +147,8 @@ impl ChaCha20Cipher {
|
||||
return Err(KmsError::invalid_key_size(32, key.len()));
|
||||
}
|
||||
|
||||
let key = chacha20poly1305::Key::from_slice(key);
|
||||
let cipher = ChaCha20Poly1305::new(key);
|
||||
let key = chacha20poly1305::Key::try_from(key).map_err(|_| KmsError::cryptographic_error("key", "Invalid key length"))?;
|
||||
let cipher = ChaCha20Poly1305::new(&key);
|
||||
|
||||
Ok(Self { cipher })
|
||||
}
|
||||
@@ -160,12 +160,13 @@ impl ObjectCipher for ChaCha20Cipher {
|
||||
return Err(KmsError::invalid_key_size(12, iv.len()));
|
||||
}
|
||||
|
||||
let nonce = chacha20poly1305::Nonce::from_slice(iv);
|
||||
let nonce =
|
||||
chacha20poly1305::Nonce::try_from(iv).map_err(|_| KmsError::cryptographic_error("nonce", "Invalid nonce length"))?;
|
||||
|
||||
// ChaCha20-Poly1305 includes the tag in the ciphertext
|
||||
let ciphertext_with_tag = self
|
||||
.cipher
|
||||
.encrypt(nonce, chacha20poly1305::aead::Payload { msg: plaintext, aad })
|
||||
.encrypt(&nonce, chacha20poly1305::aead::Payload { msg: plaintext, aad })
|
||||
.map_err(KmsError::from_chacha20_error)?;
|
||||
|
||||
// Split ciphertext and tag
|
||||
@@ -188,7 +189,8 @@ impl ObjectCipher for ChaCha20Cipher {
|
||||
return Err(KmsError::invalid_key_size(self.tag_size(), tag.len()));
|
||||
}
|
||||
|
||||
let nonce = chacha20poly1305::Nonce::from_slice(iv);
|
||||
let nonce =
|
||||
chacha20poly1305::Nonce::try_from(iv).map_err(|_| KmsError::cryptographic_error("nonce", "Invalid nonce length"))?;
|
||||
|
||||
// Combine ciphertext and tag for ChaCha20-Poly1305
|
||||
let mut ciphertext_with_tag = ciphertext.to_vec();
|
||||
@@ -197,7 +199,7 @@ impl ObjectCipher for ChaCha20Cipher {
|
||||
let plaintext = self
|
||||
.cipher
|
||||
.decrypt(
|
||||
nonce,
|
||||
&nonce,
|
||||
chacha20poly1305::aead::Payload {
|
||||
msg: &ciphertext_with_tag,
|
||||
aad,
|
||||
@@ -241,7 +243,7 @@ pub fn generate_iv(algorithm: &EncryptionAlgorithm) -> Vec<u8> {
|
||||
};
|
||||
|
||||
let mut iv = vec![0u8; iv_size];
|
||||
OsRng.fill_bytes(&mut iv);
|
||||
rand::rng().fill(&mut iv[..]);
|
||||
iv
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ serde = { workspace = true }
|
||||
bytes.workspace = true
|
||||
reqwest.workspace = true
|
||||
tokio-util.workspace = true
|
||||
faster-hex.workspace = true
|
||||
futures.workspace = true
|
||||
rustfs-utils = { workspace = true, features = ["io", "hash", "compress"] }
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -20,6 +20,7 @@ use aes_gcm::aead::Aead;
|
||||
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
|
||||
use pin_project_lite::pin_project;
|
||||
use rustfs_utils::{put_uvarint, put_uvarint_len};
|
||||
use std::io::Error;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
@@ -98,13 +99,13 @@ where
|
||||
} else {
|
||||
// Encrypt the chunk
|
||||
let cipher = Aes256Gcm::new_from_slice(this.key).expect("key");
|
||||
let nonce = Nonce::from_slice(this.nonce);
|
||||
let nonce = Nonce::try_from(this.nonce.as_slice()).map_err(|_| Error::other("invalid nonce length"))?;
|
||||
let plaintext = &temp_buf.filled()[..n];
|
||||
let plaintext_len = plaintext.len();
|
||||
let crc = crc32fast::hash(plaintext);
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, plaintext)
|
||||
.map_err(|e| std::io::Error::other(format!("encrypt error: {e}")))?;
|
||||
.encrypt(&nonce, plaintext)
|
||||
.map_err(|e| Error::other(format!("encrypt error: {e}")))?;
|
||||
let int_len = put_uvarint_len(plaintext_len as u64);
|
||||
let clen = int_len + ciphertext.len() + 4;
|
||||
// Header: 8 bytes
|
||||
@@ -352,7 +353,7 @@ where
|
||||
|
||||
let Some(payload_len) = len.checked_sub(4) else {
|
||||
tracing::error!("invalid encrypted block length: typ={} len={} header={:?}", typ, len, this.header_buf);
|
||||
return Poll::Ready(Err(std::io::Error::other("Invalid encrypted block length")));
|
||||
return Poll::Ready(Err(Error::other("Invalid encrypted block length")));
|
||||
};
|
||||
|
||||
if this.ciphertext_buf.is_none() {
|
||||
@@ -390,10 +391,10 @@ where
|
||||
let ciphertext = &ciphertext_buf[uvarint_len as usize..];
|
||||
|
||||
let cipher = Aes256Gcm::new_from_slice(this.key).expect("key");
|
||||
let nonce = Nonce::from_slice(this.current_nonce);
|
||||
let nonce = Nonce::try_from(this.current_nonce.as_slice()).map_err(|_| Error::other("invalid nonce length"))?;
|
||||
let plaintext = cipher
|
||||
.decrypt(nonce, ciphertext)
|
||||
.map_err(|e| std::io::Error::other(format!("decrypt error: {e}")))?;
|
||||
.decrypt(&nonce, ciphertext)
|
||||
.map_err(|e| Error::other(format!("decrypt error: {e}")))?;
|
||||
|
||||
debug!(
|
||||
part = *this.current_part,
|
||||
@@ -405,7 +406,7 @@ where
|
||||
this.ciphertext_buf.take();
|
||||
*this.ciphertext_read = 0;
|
||||
*this.ciphertext_len = 0;
|
||||
return Poll::Ready(Err(std::io::Error::other("Plaintext length mismatch")));
|
||||
return Poll::Ready(Err(Error::other("Plaintext length mismatch")));
|
||||
}
|
||||
|
||||
let actual_crc = crc32fast::hash(&plaintext);
|
||||
@@ -413,7 +414,7 @@ where
|
||||
this.ciphertext_buf.take();
|
||||
*this.ciphertext_read = 0;
|
||||
*this.ciphertext_len = 0;
|
||||
return Poll::Ready(Err(std::io::Error::other("CRC32 mismatch")));
|
||||
return Poll::Ready(Err(Error::other("CRC32 mismatch")));
|
||||
}
|
||||
|
||||
*this.buffer = plaintext;
|
||||
|
||||
@@ -120,7 +120,8 @@ mod tests {
|
||||
let data = b"hello world";
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(data);
|
||||
let expected = format!("{:x}", hasher.finalize());
|
||||
let hex = faster_hex::hex_string(hasher.finalize().as_slice());
|
||||
let expected = hex.to_string();
|
||||
let reader = BufReader::new(&data[..]);
|
||||
let reader = Box::new(WarpReader::new(reader));
|
||||
let mut etag_reader = EtagReader::new(reader, None);
|
||||
@@ -139,7 +140,8 @@ mod tests {
|
||||
let data = b"";
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(data);
|
||||
let expected = format!("{:x}", hasher.finalize());
|
||||
let hex = faster_hex::hex_string(hasher.finalize().as_slice());
|
||||
let expected = hex.to_string();
|
||||
let reader = BufReader::new(&data[..]);
|
||||
let reader = Box::new(WarpReader::new(reader));
|
||||
let mut etag_reader = EtagReader::new(reader, None);
|
||||
@@ -158,7 +160,8 @@ mod tests {
|
||||
let data = b"abc123";
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(data);
|
||||
let expected = format!("{:x}", hasher.finalize());
|
||||
let hex = faster_hex::hex_string(hasher.finalize().as_slice());
|
||||
let expected = hex.to_string();
|
||||
let reader = BufReader::new(&data[..]);
|
||||
let reader = Box::new(WarpReader::new(reader));
|
||||
let mut etag_reader = EtagReader::new(reader, None);
|
||||
@@ -195,15 +198,12 @@ mod tests {
|
||||
rand::rng().fill(&mut data[..]);
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(&data);
|
||||
|
||||
let cloned_data = data.clone();
|
||||
|
||||
let expected = format!("{:x}", hasher.finalize());
|
||||
|
||||
let hex = faster_hex::hex_string(hasher.finalize().as_slice());
|
||||
let expected = hex.to_string();
|
||||
let reader = Cursor::new(data.clone());
|
||||
let reader = Box::new(WarpReader::new(reader));
|
||||
let mut etag_reader = EtagReader::new(reader, None);
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let n = etag_reader.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(n, size);
|
||||
|
||||
@@ -660,7 +660,8 @@ mod tests {
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(&data);
|
||||
|
||||
let expected = format!("{:x}", hasher.finalize());
|
||||
let hex = faster_hex::hex_string(hasher.finalize().as_slice());
|
||||
let expected = hex.to_string();
|
||||
|
||||
println!("expected: {expected}");
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn is_sha256_checksum(s: &str) -> bool {
|
||||
|
||||
/// `hmac_sha1(key, data)`
|
||||
pub fn hmac_sha1(key: impl AsRef<[u8]>, data: impl AsRef<[u8]>) -> [u8; 20] {
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use sha1::Sha1;
|
||||
|
||||
let mut m = <Hmac<Sha1>>::new_from_slice(key.as_ref()).unwrap();
|
||||
@@ -48,7 +48,7 @@ pub fn hmac_sha1(key: impl AsRef<[u8]>, data: impl AsRef<[u8]>) -> [u8; 20] {
|
||||
|
||||
/// `hmac_sha256(key, data)`
|
||||
pub fn hmac_sha256(key: impl AsRef<[u8]>, data: impl AsRef<[u8]>) -> [u8; 32] {
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::{Hmac, KeyInit, Mac};
|
||||
use sha2::Sha256;
|
||||
|
||||
let mut m = Hmac::<Sha256>::new_from_slice(key.as_ref()).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user