feat: add decrypt_json_gcm for encrypted status storages

This commit is contained in:
charles-gauthereau
2026-07-08 20:43:01 +02:00
parent b37a4f2180
commit ee97c6056a
2 changed files with 53 additions and 0 deletions
+24
View File
@@ -86,3 +86,27 @@ async fn encrypt_stream_starts_with_json_header_line() -> Result<()> {
Ok(())
}
use crate::utils::file::decrypt_json_gcm;
const VECTOR_KEY_B64: &str = "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc=";
const VECTOR_ENVELOPE_B64: &str = "AQIDBAUGBwgJCgsM4AfK9w7I2A7UDzMvpJaScfnUYAGDZgPWT5Chrp1pdzMPPQVpNjb6ZEiFea9YdWVFv1UEo9RGmmf+zYUv4I3gE4SU/SBrMwkCHEpJGJOzJtK3tSpJmzLVX3+7EeUNwp4qjZheL8p0pe1x6dRUtx3JmLjz1W/RhWd6zuReDItv6+0jg4CaPOHvFXBreaGNCTRslxbImD+lFBoEOvw8lsbH";
const VECTOR_PLAINTEXT: &str = "[{\"id\":\"11111111-1111-1111-1111-111111111111\",\"config\":{\"bucket\":\"my-bucket\",\"accessKeyId\":\"AKIA\",\"secretAccessKey\":\"s3cr3t\"},\"provider\":\"s3\"}]";
#[test]
fn decrypt_json_gcm_decrypts_node_vector() {
let plaintext = decrypt_json_gcm(VECTOR_ENVELOPE_B64, VECTOR_KEY_B64).unwrap();
assert_eq!(String::from_utf8(plaintext).unwrap(), VECTOR_PLAINTEXT);
}
#[test]
fn decrypt_json_gcm_rejects_wrong_key() {
let wrong_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
assert!(decrypt_json_gcm(VECTOR_ENVELOPE_B64, wrong_key).is_err());
}
#[test]
fn decrypt_json_gcm_rejects_short_input() {
// 8 bytes base64 -> shorter than nonce(12)+tag(16)
assert!(decrypt_json_gcm("AAAAAAAAAAA=", VECTOR_KEY_B64).is_err());
}
+29
View File
@@ -175,3 +175,32 @@ pub async fn decrypt_file_stream_gcm(
writer.flush().await?;
Ok(())
}
/// Decrypt a base64 `nonce(12) ‖ ciphertext ‖ tag(16)` AES-256-GCM envelope
/// using the raw master key (STANDARD base64). Returns the plaintext bytes.
pub fn decrypt_json_gcm(ciphertext_b64: &str, master_key_b64: &str) -> Result<Vec<u8>> {
let master_key_bytes = general_purpose::STANDARD
.decode(master_key_b64)
.map_err(|_| anyhow::anyhow!("Invalid base64 master key"))?;
let data = general_purpose::STANDARD
.decode(ciphertext_b64)
.map_err(|_| anyhow::anyhow!("Invalid base64 ciphertext"))?;
if data.len() < 12 + 16 {
return Err(anyhow::anyhow!("Ciphertext too short"));
}
let key = Key::<Aes256Gcm>::try_from(master_key_bytes.as_slice())
.map_err(|_| anyhow::anyhow!("Invalid AES-256 key length"))?;
let cipher = Aes256Gcm::new(&key);
let nonce = Nonce::try_from(&data[..12])
.map_err(|_| anyhow::anyhow!("Invalid nonce length"))?;
let plaintext = cipher
.decrypt(&nonce, &data[12..])
.map_err(|e| anyhow::anyhow!("AES-GCM decryption failed: {:?}", e))?;
Ok(plaintext)
}