#![allow(deprecated)]
use super::encrypt::derive_aes_key_sync;
use super::{KeyPair, SeaError};
use aes_gcm::{
Aes256Gcm, Nonce,
aead::{Aead, KeyInit},
};
use base64::prelude::*;
use serde_json::Value;
pub async fn decrypt(
encrypted: &Value,
pair: &KeyPair,
their_epub: Option<&str>,
) -> Result<Value, SeaError> {
let ct = encrypted
.get("ct")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("missing ct".to_string()))?;
let iv = encrypted
.get("iv")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("missing iv".to_string()))?;
let s = encrypted
.get("s")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("missing s".to_string()))?;
let ciphertext = BASE64_URL_SAFE_NO_PAD
.decode(ct)
.map_err(|_| SeaError::Decryption("invalid ct base64".to_string()))?;
let nonce_bytes = BASE64_URL_SAFE_NO_PAD
.decode(iv)
.map_err(|_| SeaError::Decryption("invalid iv base64".to_string()))?;
let salt_bytes = BASE64_URL_SAFE_NO_PAD
.decode(s)
.map_err(|_| SeaError::Decryption("invalid s base64".to_string()))?;
let pair = pair.clone();
let their_epub = their_epub.map(|s| s.to_string());
let salt_owned = salt_bytes;
let nonce_owned = nonce_bytes;
let plaintext = tokio::task::spawn_blocking(move || {
let aes_key = if let Some(ref their_pub) = their_epub {
let shared_secret = super::secret::secret_sync(their_pub, &pair)?;
derive_aes_key_sync(&shared_secret, &salt_owned)?
} else {
let epriv = pair
.epriv_key
.as_ref()
.ok_or_else(|| SeaError::Decryption("missing epriv key".to_string()))?;
derive_aes_key_sync(epriv, &salt_owned)?
};
let cipher = Aes256Gcm::new_from_slice(&aes_key)
.map_err(|e| SeaError::Decryption(format!("failed to create cipher: {}", e)))?;
let nonce = Nonce::from_slice(&nonce_owned);
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).map_err(|_| {
SeaError::Decryption("decryption failed — tampered or wrong key".to_string())
})?;
Ok::<Vec<u8>, SeaError>(plaintext)
})
.await
.map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?;
let plaintext = plaintext?;
let plaintext_str = String::from_utf8(plaintext)
.map_err(|_| SeaError::Decryption("invalid UTF-8 in plaintext".to_string()))?;
serde_json::from_str(&plaintext_str)
.map_err(|e| SeaError::Decryption(format!("invalid JSON in plaintext: {}", e)))
}
pub async fn decrypt_symmetric(encrypted: &Value, key: &[u8]) -> Result<Value, SeaError> {
if key.len() != 32 {
return Err(SeaError::Decryption(format!(
"decrypt_symmetric: key must be 32 bytes, got {}",
key.len()
)));
}
let ct = encrypted
.get("ct")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("missing ct".to_string()))?;
let iv = encrypted
.get("iv")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("missing iv".to_string()))?;
let ciphertext = BASE64_URL_SAFE_NO_PAD
.decode(ct)
.map_err(|_| SeaError::Decryption("invalid ct base64".to_string()))?;
let nonce_bytes = BASE64_URL_SAFE_NO_PAD
.decode(iv)
.map_err(|_| SeaError::Decryption("invalid iv base64".to_string()))?;
let key_owned = key.to_vec();
let nonce_owned = nonce_bytes;
let plaintext = tokio::task::spawn_blocking(move || {
let cipher = Aes256Gcm::new_from_slice(&key_owned)
.map_err(|e| SeaError::Decryption(format!("failed to create cipher: {}", e)))?;
let nonce = Nonce::from_slice(&nonce_owned);
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).map_err(|_| {
SeaError::Decryption("symmetric decryption failed — tampered or wrong key".to_string())
})?;
Ok::<Vec<u8>, SeaError>(plaintext)
})
.await
.map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?;
let plaintext = plaintext?;
let plaintext_str = String::from_utf8(plaintext)
.map_err(|_| SeaError::Decryption("invalid UTF-8 in plaintext".to_string()))?;
serde_json::from_str(&plaintext_str)
.map_err(|e| SeaError::Decryption(format!("invalid JSON in plaintext: {}", e)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sea::generate_pair;
use serde_json::json;
#[tokio::test]
async fn test_decrypt_self_encrypted() {
let pair = generate_pair().await.unwrap();
let data = json!({"secret": "data"});
let encrypted = super::super::encrypt::encrypt(&data, &pair, None)
.await
.unwrap();
let decrypted = decrypt(&encrypted, &pair, None).await.unwrap();
assert_eq!(decrypted, data);
}
#[tokio::test]
async fn test_decrypt_wrong_key_fails() {
let alice = generate_pair().await.unwrap();
let bob = generate_pair().await.unwrap();
let data = json!("secret");
let encrypted = super::super::encrypt::encrypt(&data, &alice, None)
.await
.unwrap();
assert!(decrypt(&encrypted, &bob, None).await.is_err());
}
#[tokio::test]
async fn test_decrypt_missing_field_fails() {
let pair = generate_pair().await.unwrap();
let bad_data = json!({"ct": "abc"}); assert!(decrypt(&bad_data, &pair, None).await.is_err());
}
#[tokio::test]
async fn test_decrypt_symmetric_bad_key_length() {
let key = [0u8; 16]; let encrypted = json!({"ct": "abc", "iv": "def"});
assert!(decrypt_symmetric(&encrypted, &key).await.is_err());
}
}