use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tokio::fs;
use super::path_utils::sanitize_key_for_fs;
use super::types::{StorageEngine, StorageError};
use crate::storage::encryption::{ChunkInfo, EncryptedData, EncryptionAlgorithm};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SidecarChunk {
pub nonce: Vec<u8>,
pub plaintext_len: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectSseSidecar {
pub version: u32, pub algorithm: String, pub encrypted_dek: Vec<u8>, pub kek_id: String, pub dek_nonce: Vec<u8>, pub payload_nonce: Vec<u8>, pub aad_bucket: String, pub aad_key: String, pub ciphertext_len: u64, pub kms_master_key_id: Option<String>, pub customer_key_md5: Option<String>, #[serde(default)]
pub chunk_size: u64,
#[serde(default)]
pub chunks: Vec<SidecarChunk>,
}
impl ObjectSseSidecar {
pub fn from_encrypted(e: &EncryptedData, bucket: &str, key: &str) -> Self {
let version = if e.chunks.is_empty() { 1 } else { 2 };
Self {
version,
algorithm: match e.algorithm {
EncryptionAlgorithm::Aes256Gcm => "AES256".to_string(),
EncryptionAlgorithm::ChaCha20Poly1305 => "CHACHA20".to_string(),
},
encrypted_dek: e.encrypted_dek.clone(),
kek_id: e.kek_id.clone(),
dek_nonce: e.dek_nonce.clone(),
payload_nonce: e.payload_nonce.clone(),
aad_bucket: bucket.to_string(),
aad_key: key.to_string(),
ciphertext_len: e.ciphertext.len() as u64,
kms_master_key_id: None,
customer_key_md5: None,
chunk_size: e.chunk_size,
chunks: e
.chunks
.iter()
.map(|c| SidecarChunk {
nonce: c.nonce.clone(),
plaintext_len: c.plaintext_len,
})
.collect(),
}
}
pub fn into_encrypted_data(&self, ciphertext: Vec<u8>) -> EncryptedData {
let aad = format!("{}/{}", self.aad_bucket, self.aad_key);
EncryptedData {
algorithm: EncryptionAlgorithm::Aes256Gcm, encrypted_dek: self.encrypted_dek.clone(),
kek_id: self.kek_id.clone(),
dek_nonce: self.dek_nonce.clone(),
ciphertext,
payload_nonce: self.payload_nonce.clone(),
aad: Some(aad.into_bytes()),
chunks: self
.chunks
.iter()
.map(|c| ChunkInfo {
nonce: c.nonce.clone(),
plaintext_len: c.plaintext_len,
})
.collect(),
chunk_size: self.chunk_size,
}
}
}
impl StorageEngine {
fn sse_sidecar_path(&self, bucket: &str, key: &str) -> PathBuf {
self.get_root_path()
.join(bucket)
.join("sse")
.join(format!("{}.json", sanitize_key_for_fs(key)))
}
pub async fn get_object_sse(
&self,
bucket: &str,
key: &str,
) -> Result<Option<ObjectSseSidecar>, StorageError> {
if !self.bucket_exists(bucket).await? {
return Err(StorageError::BucketNotFound);
}
let path = self.sse_sidecar_path(bucket, key);
if !path.exists() {
return Ok(None);
}
let data = fs::read(&path).await?;
serde_json::from_slice(&data)
.map(Some)
.map_err(|e| StorageError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))
}
pub async fn put_object_sse(
&self,
bucket: &str,
key: &str,
sidecar: &ObjectSseSidecar,
) -> Result<(), StorageError> {
if !self.bucket_exists(bucket).await? {
return Err(StorageError::BucketNotFound);
}
let path = self.sse_sidecar_path(bucket, key);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?;
}
let data = serde_json::to_vec_pretty(sidecar).map_err(|e| {
StorageError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
})?;
let tmp_path = path.with_extension("json.tmp");
fs::write(&tmp_path, &data).await?;
fs::rename(&tmp_path, &path).await?;
Ok(())
}
pub async fn delete_object_sse(&self, bucket: &str, key: &str) -> Result<(), StorageError> {
let path = self.sse_sidecar_path(bucket, key);
match fs::remove_file(&path).await {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(StorageError::Io(e)),
}
}
pub async fn overwrite_object_ciphertext(
&self,
bucket: &str,
key: &str,
ciphertext: &[u8],
) -> Result<(), StorageError> {
let obj_path = self.object_path(bucket, key);
let tmp_path = obj_path.with_extension("ciphertext.tmp");
fs::write(&tmp_path, ciphertext).await?;
fs::rename(&tmp_path, &obj_path).await?;
Ok(())
}
}