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