1use aes::cipher::Unsigned;
27use aes_gcm::aead::{Aead, AeadCore, AeadInPlace};
28use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
29use base64::Engine;
30use hkdf::Hkdf;
31use hmac::{Hmac, Mac};
32use prost::Message;
33use rand::TryRngCore;
34use reqwest::Method;
35use serde::Deserialize;
36use sha2::Sha256;
37
38use crate::configuration::Endpoint;
39use crate::master_key::StorageServiceKey;
40use crate::proto::{
41 ManifestRecord, ReadOperation, StorageItem, StorageItems, StorageManifest,
42 StorageRecord,
43};
44use crate::push_service::protobuf::ProtobufResponseExt;
45use crate::push_service::ReqwestExt;
46use crate::push_service::{
47 HttpAuth, HttpAuthOverride, PushService, ServiceError,
48};
49
50const IV_LEN: usize = 12;
51const ITEM_KEY_INFO_PREFIX: &[u8] = b"20240801_SIGNAL_STORAGE_SERVICE_ITEM_";
52
53type HmacSha256 = Hmac<Sha256>;
54
55#[derive(Debug, thiserror::Error)]
57pub enum StorageServiceError {
58 #[error("invalid storage service blob")]
62 Invalid,
63 #[error("network / service error: {0}")]
64 Service(#[from] ServiceError),
65}
66
67impl From<prost::DecodeError> for StorageServiceError {
68 fn from(_: prost::DecodeError) -> Self {
69 StorageServiceError::Invalid
70 }
71}
72
73impl From<reqwest::Error> for StorageServiceError {
74 fn from(e: reqwest::Error) -> Self {
75 StorageServiceError::Service(e.into())
76 }
77}
78
79#[derive(Debug, Deserialize)]
81struct StorageAuthResponse {
82 username: String,
83 password: String,
84}
85
86pub struct StorageService {
92 service: PushService,
93 credentials: HttpAuth,
94 storage_key: StorageServiceKey,
95}
96
97impl StorageService {
98 pub async fn new(
103 service: PushService,
104 storage_key: StorageServiceKey,
105 ) -> Result<Self, ServiceError> {
106 let resp: StorageAuthResponse = service
107 .request(
108 Method::GET,
109 Endpoint::service("/v1/storage/auth"),
110 HttpAuthOverride::NoOverride,
111 )?
112 .send()
113 .await?
114 .service_error_for_status()
115 .await?
116 .json()
117 .await?;
118 let credentials = HttpAuth {
119 username: resp.username,
120 password: resp.password,
121 };
122 Ok(Self {
123 service,
124 credentials,
125 storage_key,
126 })
127 }
128
129 pub async fn manifest(
131 &self,
132 ) -> Result<ManifestRecord, StorageServiceError> {
133 let manifest: StorageManifest = self
134 .service
135 .request(
136 Method::GET,
137 Endpoint::storage("/v1/storage/manifest"),
138 HttpAuthOverride::Identified(self.credentials.clone()),
139 )?
140 .send()
141 .await?
142 .service_error_for_status()
143 .await?
144 .protobuf()
145 .await?;
146 Self::decrypt_manifest(&self.storage_key, &manifest)
147 }
148
149 pub async fn manifest_if_changed(
152 &self,
153 version: u64,
154 ) -> Result<Option<ManifestRecord>, StorageServiceError> {
155 let response = self
156 .service
157 .request(
158 Method::GET,
159 Endpoint::storage(format!(
160 "/v1/storage/manifest/version/{version}"
161 )),
162 HttpAuthOverride::Identified(self.credentials.clone()),
163 )?
164 .send()
165 .await?
166 .service_error_for_status()
167 .await?;
168
169 if response.status().as_u16() == 204 {
170 return Ok(None);
171 }
172 let manifest: StorageManifest = response.protobuf().await?;
173 Ok(Some(Self::decrypt_manifest(&self.storage_key, &manifest)?))
174 }
175
176 pub async fn read_items(
184 &self,
185 keys: Vec<Vec<u8>>,
186 record_ikm: Option<&[u8]>,
187 ) -> Result<Vec<StorageRecord>, StorageServiceError> {
188 let body = ReadOperation { read_key: keys };
189 let mut buf = Vec::with_capacity(body.encoded_len());
190 body.encode(&mut buf).expect("infallible encode into Vec");
191
192 let items: StorageItems = self
193 .service
194 .request(
195 Method::PUT,
196 Endpoint::storage("/v1/storage/read"),
197 HttpAuthOverride::Identified(self.credentials.clone()),
198 )?
199 .header("Content-Type", "application/x-protobuf")
200 .body(buf)
201 .send()
202 .await?
203 .service_error_for_status()
204 .await?
205 .protobuf()
206 .await?;
207
208 items
209 .items
210 .iter()
211 .map(|item| Self::decrypt_item(&self.storage_key, item, record_ikm))
212 .collect()
213 }
214
215 pub fn decrypt_manifest(
219 storage_key: &StorageServiceKey,
220 manifest: &StorageManifest,
221 ) -> Result<ManifestRecord, StorageServiceError> {
222 let key = Self::manifest_key(storage_key, manifest.version);
223 let plaintext = decrypt(&key, &manifest.value)?;
224 Ok(ManifestRecord::decode(&*plaintext)?)
225 }
226
227 pub fn encrypt_manifest(
229 storage_key: &StorageServiceKey,
230 record: &ManifestRecord,
231 ) -> StorageManifest {
232 let key = Self::manifest_key(storage_key, record.version);
233 StorageManifest {
234 version: record.version,
235 value: encrypt(&key, &record.encode_to_vec()),
236 }
237 }
238
239 pub fn decrypt_item(
241 storage_key: &StorageServiceKey,
242 item: &StorageItem,
243 record_ikm: Option<&[u8]>,
244 ) -> Result<StorageRecord, StorageServiceError> {
245 let key = Self::item_key(storage_key, &item.key, record_ikm);
246 let plaintext = decrypt(&key, &item.value)?;
247 Ok(StorageRecord::decode(&*plaintext)?)
248 }
249
250 pub fn encrypt_item(
255 storage_key: &StorageServiceKey,
256 raw_id: Vec<u8>,
257 record: &StorageRecord,
258 record_ikm: Option<&[u8]>,
259 ) -> StorageItem {
260 let key = Self::item_key(storage_key, &raw_id, record_ikm);
261 StorageItem {
262 key: raw_id,
263 value: encrypt(&key, &record.encode_to_vec()),
264 }
265 }
266
267 fn manifest_key(storage_key: &StorageServiceKey, version: u64) -> [u8; 32] {
269 let mut mac = <HmacSha256 as Mac>::new_from_slice(&storage_key.inner)
270 .expect("HMAC accepts any key length");
271 mac.update(b"Manifest_");
272 mac.update(version.to_string().as_bytes());
273 mac.finalize().into_bytes().into()
274 }
275
276 fn item_key(
279 storage_key: &StorageServiceKey,
280 raw_id: &[u8],
281 record_ikm: Option<&[u8]>,
282 ) -> [u8; 32] {
283 match record_ikm {
284 Some(ikm) if !ikm.is_empty() => {
285 let hk = Hkdf::<Sha256>::new(None, ikm);
286 let mut okm = [0u8; 32];
287 hk.expand_multi_info(&[ITEM_KEY_INFO_PREFIX, raw_id], &mut okm)
288 .expect("32-byte HKDF output is valid");
289 okm
290 },
291 _ => {
292 let b64 =
293 base64::engine::general_purpose::STANDARD.encode(raw_id);
294 let mut mac =
295 <HmacSha256 as Mac>::new_from_slice(&storage_key.inner)
296 .expect("HMAC accepts any key length");
297 mac.update(b"Item_");
298 mac.update(b64.as_bytes());
299 mac.finalize().into_bytes().into()
300 },
301 }
302 }
303}
304
305fn decrypt(
307 key: &[u8; 32],
308 blob: &[u8],
309) -> Result<Vec<u8>, StorageServiceError> {
310 if blob.len() < IV_LEN {
311 return Err(StorageServiceError::Invalid);
312 }
313 let (iv, ct) = blob.split_at(IV_LEN);
314 Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key))
315 .decrypt(Nonce::from_slice(iv), ct)
316 .map_err(|_| StorageServiceError::Invalid)
317}
318
319fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Vec<u8> {
322 let mut iv = [0u8; IV_LEN];
323 rand::rngs::OsRng
324 .try_fill_bytes(&mut iv)
325 .expect("OS RNG available");
326
327 let mut out = Vec::with_capacity(
329 IV_LEN + plaintext.len() + <Aes256Gcm as AeadCore>::TagSize::to_usize(),
330 );
331 out.extend_from_slice(&iv);
332 out.extend_from_slice(plaintext);
333
334 let tag = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key))
336 .encrypt_in_place_detached(
337 Nonce::from_slice(&iv),
338 b"",
339 &mut out[IV_LEN..],
340 )
341 .expect("AES-256-GCM encryption is infallible for valid keys");
342
343 out.extend_from_slice(&tag);
345
346 out
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn manifest_round_trip() {
355 let storage_key = StorageServiceKey { inner: [7u8; 32] };
356 let record = ManifestRecord {
357 version: 42,
358 source_device: 1,
359 identifiers: vec![],
360 record_ikm: vec![],
361 };
362 let encrypted = StorageService::encrypt_manifest(&storage_key, &record);
363 assert_eq!(encrypted.version, 42);
364 let decrypted =
365 StorageService::decrypt_manifest(&storage_key, &encrypted).unwrap();
366 assert_eq!(decrypted, record);
367 }
368
369 #[test]
370 fn item_round_trip_modern_and_legacy() {
371 let storage_key = StorageServiceKey { inner: [9u8; 32] };
372 let raw_id = vec![0xABu8; 16];
373 let record = StorageRecord { record: None };
374
375 let legacy = StorageService::encrypt_item(
377 &storage_key,
378 raw_id.clone(),
379 &record,
380 None,
381 );
382 assert_eq!(
383 StorageService::decrypt_item(&storage_key, &legacy, None).unwrap(),
384 record
385 );
386
387 let ikm = [4u8; 32];
389 let modern = StorageService::encrypt_item(
390 &storage_key,
391 raw_id.clone(),
392 &record,
393 Some(&ikm),
394 );
395 assert_eq!(
396 StorageService::decrypt_item(&storage_key, &modern, Some(&ikm))
397 .unwrap(),
398 record
399 );
400 }
401
402 #[test]
403 fn modern_and_legacy_keys_differ() {
404 let storage_key = StorageServiceKey { inner: [3u8; 32] };
405 let raw_id = [5u8; 16];
406 let legacy = StorageService::item_key(&storage_key, &raw_id, None);
407 let modern =
408 StorageService::item_key(&storage_key, &raw_id, Some(&[4u8; 32]));
409 assert_ne!(legacy, modern);
410 }
411
412 #[test]
413 fn manifest_key_changes_with_version() {
414 let storage_key = StorageServiceKey { inner: [1u8; 32] };
415 assert_ne!(
416 StorageService::manifest_key(&storage_key, 1),
417 StorageService::manifest_key(&storage_key, 2)
418 );
419 }
420
421 #[test]
422 fn wrong_key_fails_to_decrypt() {
423 let a = StorageServiceKey { inner: [1u8; 32] };
424 let b = StorageServiceKey { inner: [2u8; 32] };
425 let record = ManifestRecord {
426 version: 1,
427 source_device: 0,
428 identifiers: vec![],
429 record_ikm: vec![],
430 };
431 let encrypted = StorageService::encrypt_manifest(&a, &record);
432 assert!(matches!(
433 StorageService::decrypt_manifest(&b, &encrypted),
434 Err(StorageServiceError::Invalid)
435 ));
436 }
437}