1#![allow(deprecated)]
2use super::encrypt::derive_aes_key_sync;
6use super::{KeyPair, SeaError};
7use aes_gcm::{
8 Aes256Gcm, Nonce,
9 aead::{Aead, KeyInit},
10};
11use base64::prelude::*;
12use serde_json::Value;
13
14pub async fn decrypt(
20 encrypted: &Value,
21 pair: &KeyPair,
22 their_epub: Option<&str>,
23) -> Result<Value, SeaError> {
24 let ct = encrypted
26 .get("ct")
27 .and_then(|v| v.as_str())
28 .ok_or_else(|| SeaError::Decryption("missing ct".to_string()))?;
29
30 let iv = encrypted
31 .get("iv")
32 .and_then(|v| v.as_str())
33 .ok_or_else(|| SeaError::Decryption("missing iv".to_string()))?;
34
35 let s = encrypted
36 .get("s")
37 .and_then(|v| v.as_str())
38 .ok_or_else(|| SeaError::Decryption("missing s".to_string()))?;
39
40 let ciphertext = BASE64_URL_SAFE_NO_PAD
42 .decode(ct)
43 .map_err(|_| SeaError::Decryption("invalid ct base64".to_string()))?;
44
45 let nonce_bytes = BASE64_URL_SAFE_NO_PAD
46 .decode(iv)
47 .map_err(|_| SeaError::Decryption("invalid iv base64".to_string()))?;
48
49 let salt_bytes = BASE64_URL_SAFE_NO_PAD
50 .decode(s)
51 .map_err(|_| SeaError::Decryption("invalid s base64".to_string()))?;
52
53 let pair = pair.clone();
55 let their_epub = their_epub.map(|s| s.to_string());
56 let salt_owned = salt_bytes;
57 let nonce_owned = nonce_bytes;
58
59 let plaintext = tokio::task::spawn_blocking(move || {
61 let aes_key = if let Some(ref their_pub) = their_epub {
63 let shared_secret = super::secret::secret_sync(their_pub, &pair)?;
65 derive_aes_key_sync(&shared_secret, &salt_owned)?
66 } else {
67 let epriv = pair
69 .epriv_key
70 .as_ref()
71 .ok_or_else(|| SeaError::Decryption("missing epriv key".to_string()))?;
72 derive_aes_key_sync(epriv, &salt_owned)?
73 };
74
75 let cipher = Aes256Gcm::new_from_slice(&aes_key)
77 .map_err(|e| SeaError::Decryption(format!("failed to create cipher: {}", e)))?;
78
79 let nonce = Nonce::from_slice(&nonce_owned);
81
82 let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).map_err(|_| {
84 SeaError::Decryption("decryption failed — tampered or wrong key".to_string())
85 })?;
86
87 Ok::<Vec<u8>, SeaError>(plaintext)
88 })
89 .await
90 .map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?;
91
92 let plaintext = plaintext?;
93
94 let plaintext_str = String::from_utf8(plaintext)
96 .map_err(|_| SeaError::Decryption("invalid UTF-8 in plaintext".to_string()))?;
97
98 serde_json::from_str(&plaintext_str)
99 .map_err(|e| SeaError::Decryption(format!("invalid JSON in plaintext: {}", e)))
100}
101
102pub async fn decrypt_symmetric(encrypted: &Value, key: &[u8]) -> Result<Value, SeaError> {
110 if key.len() != 32 {
111 return Err(SeaError::Decryption(format!(
112 "decrypt_symmetric: key must be 32 bytes, got {}",
113 key.len()
114 )));
115 }
116
117 let ct = encrypted
118 .get("ct")
119 .and_then(|v| v.as_str())
120 .ok_or_else(|| SeaError::Decryption("missing ct".to_string()))?;
121
122 let iv = encrypted
123 .get("iv")
124 .and_then(|v| v.as_str())
125 .ok_or_else(|| SeaError::Decryption("missing iv".to_string()))?;
126
127 let ciphertext = BASE64_URL_SAFE_NO_PAD
128 .decode(ct)
129 .map_err(|_| SeaError::Decryption("invalid ct base64".to_string()))?;
130
131 let nonce_bytes = BASE64_URL_SAFE_NO_PAD
132 .decode(iv)
133 .map_err(|_| SeaError::Decryption("invalid iv base64".to_string()))?;
134
135 let key_owned = key.to_vec();
136 let nonce_owned = nonce_bytes;
137
138 let plaintext = tokio::task::spawn_blocking(move || {
139 let cipher = Aes256Gcm::new_from_slice(&key_owned)
140 .map_err(|e| SeaError::Decryption(format!("failed to create cipher: {}", e)))?;
141
142 let nonce = Nonce::from_slice(&nonce_owned);
143
144 let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).map_err(|_| {
145 SeaError::Decryption("symmetric decryption failed — tampered or wrong key".to_string())
146 })?;
147
148 Ok::<Vec<u8>, SeaError>(plaintext)
149 })
150 .await
151 .map_err(|e| SeaError::Crypto(format!("task join error: {}", e)))?;
152
153 let plaintext = plaintext?;
154
155 let plaintext_str = String::from_utf8(plaintext)
156 .map_err(|_| SeaError::Decryption("invalid UTF-8 in plaintext".to_string()))?;
157
158 serde_json::from_str(&plaintext_str)
159 .map_err(|e| SeaError::Decryption(format!("invalid JSON in plaintext: {}", e)))
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::sea::generate_pair;
166 use serde_json::json;
167
168 #[tokio::test]
169 async fn test_decrypt_self_encrypted() {
170 let pair = generate_pair().await.unwrap();
171 let data = json!({"secret": "data"});
172 let encrypted = super::super::encrypt::encrypt(&data, &pair, None)
173 .await
174 .unwrap();
175 let decrypted = decrypt(&encrypted, &pair, None).await.unwrap();
176 assert_eq!(decrypted, data);
177 }
178
179 #[tokio::test]
180 async fn test_decrypt_wrong_key_fails() {
181 let alice = generate_pair().await.unwrap();
182 let bob = generate_pair().await.unwrap();
183 let data = json!("secret");
184 let encrypted = super::super::encrypt::encrypt(&data, &alice, None)
185 .await
186 .unwrap();
187 assert!(decrypt(&encrypted, &bob, None).await.is_err());
188 }
189
190 #[tokio::test]
191 async fn test_decrypt_missing_field_fails() {
192 let pair = generate_pair().await.unwrap();
193 let bad_data = json!({"ct": "abc"}); assert!(decrypt(&bad_data, &pair, None).await.is_err());
195 }
196
197 #[tokio::test]
198 async fn test_decrypt_symmetric_bad_key_length() {
199 let key = [0u8; 16]; let encrypted = json!({"ct": "abc", "iv": "def"});
201 assert!(decrypt_symmetric(&encrypted, &key).await.is_err());
202 }
203}