libsignal_service/groups_v2/
manager.rs1use std::{collections::HashMap, convert::TryInto};
2
3use crate::{
4 configuration::Endpoint,
5 groups_v2::{
6 model::{Group, GroupChanges},
7 operations::{GroupDecodingError, GroupOperations},
8 },
9 prelude::{PushService, ServiceError},
10 proto::GroupContextV2,
11 push_service::{HttpAuth, HttpAuthOverride, ReqwestExt, ServiceIds},
12 utils::BASE64_RELAXED,
13 websocket::{self, SignalWebSocket},
14};
15
16use base64::prelude::*;
17use bytes::Bytes;
18use chrono::{Days, NaiveDate, NaiveTime, Utc};
19use futures::AsyncReadExt;
20use rand::{CryptoRng, Rng};
21use reqwest::Method;
22use serde::Deserialize;
23use zkgroup::{
24 auth::{AuthCredentialWithPni, AuthCredentialWithPniResponse},
25 groups::{GroupMasterKey, GroupSecretParams},
26 ServerPublicParams,
27};
28
29#[derive(Debug, serde::Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct TemporalCredential {
32 credential: String,
33 redemption_time: u64,
34}
35
36#[derive(Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct CredentialResponse {
39 credentials: Vec<TemporalCredential>,
40}
41
42impl CredentialResponse {
43 pub fn parse(
44 self,
45 ) -> Result<HashMap<u64, AuthCredentialWithPniResponse>, ServiceError> {
46 self.credentials
47 .into_iter()
48 .map(|c| {
49 let bytes = BASE64_RELAXED.decode(c.credential)?;
50 let data = AuthCredentialWithPniResponse::new(&bytes)?;
51 Ok((c.redemption_time, data))
52 })
53 .collect::<Result<_, ServiceError>>()
54 }
55}
56
57#[derive(Debug, thiserror::Error)]
58pub enum CredentialsCacheError {
59 #[error("failed to read values from cache: {0}")]
60 ReadError(String),
61 #[error("failed to write values from cache: {0}")]
62 WriteError(String),
63}
64
65pub trait CredentialsCache {
70 fn clear(&mut self) -> Result<(), CredentialsCacheError>;
71
72 fn get(
74 &self,
75 key: &u64,
76 ) -> Result<Option<&AuthCredentialWithPniResponse>, CredentialsCacheError>;
77
78 fn write(
80 &mut self,
81 map: HashMap<u64, AuthCredentialWithPniResponse>,
82 ) -> Result<(), CredentialsCacheError>;
83}
84
85#[derive(Default)]
86pub struct InMemoryCredentialsCache {
87 map: HashMap<u64, AuthCredentialWithPniResponse>,
88}
89
90impl CredentialsCache for InMemoryCredentialsCache {
91 fn clear(&mut self) -> Result<(), CredentialsCacheError> {
92 self.map.clear();
93 Ok(())
94 }
95
96 fn get(
97 &self,
98 key: &u64,
99 ) -> Result<Option<&AuthCredentialWithPniResponse>, CredentialsCacheError>
100 {
101 Ok(self.map.get(key))
102 }
103
104 fn write(
105 &mut self,
106 map: HashMap<u64, AuthCredentialWithPniResponse>,
107 ) -> Result<(), CredentialsCacheError> {
108 self.map = map;
109 Ok(())
110 }
111}
112
113impl<T: CredentialsCache> CredentialsCache for &mut T {
114 fn clear(&mut self) -> Result<(), CredentialsCacheError> {
115 (**self).clear()
116 }
117
118 fn get(
119 &self,
120 key: &u64,
121 ) -> Result<Option<&AuthCredentialWithPniResponse>, CredentialsCacheError>
122 {
123 (**self).get(key)
124 }
125
126 fn write(
127 &mut self,
128 map: HashMap<u64, AuthCredentialWithPniResponse>,
129 ) -> Result<(), CredentialsCacheError> {
130 (**self).write(map)
131 }
132}
133
134pub struct GroupsManager<C: CredentialsCache> {
135 service_ids: ServiceIds,
136 identified_push_service: PushService,
137 unidentified_websocket: SignalWebSocket<websocket::Unidentified>,
138 credentials_cache: C,
139 server_public_params: ServerPublicParams,
140}
141
142impl<C: CredentialsCache> GroupsManager<C> {
143 pub fn new(
144 service_ids: ServiceIds,
145 identified_push_service: PushService,
146 unidentified_websocket: SignalWebSocket<websocket::Unidentified>,
147 credentials_cache: C,
148 server_public_params: ServerPublicParams,
149 ) -> Self {
150 Self {
151 service_ids,
152 identified_push_service,
153 unidentified_websocket,
154 credentials_cache,
155 server_public_params,
156 }
157 }
158
159 pub async fn get_authorization_for_today<R: Rng + CryptoRng>(
160 &mut self,
161 csprng: &mut R,
162 group_secret_params: GroupSecretParams,
163 ) -> Result<HttpAuth, ServiceError> {
164 let (today, today_plus_7_days) = current_days_seconds();
165
166 let auth_credential_response = if let Some(auth_credential_response) =
167 self.credentials_cache.get(&today)?
168 {
169 auth_credential_response
170 } else {
171 let path =
172 format!("/v1/certificate/auth/group?redemptionStartSeconds={}&redemptionEndSeconds={}&pniAsServiceId=true", today, today_plus_7_days);
173
174 let credentials_response: CredentialResponse = self
175 .identified_push_service
176 .request(
177 Method::GET,
178 Endpoint::service(path),
179 HttpAuthOverride::NoOverride,
180 )?
181 .send()
182 .await?
183 .service_error_for_status()
184 .await?
185 .json()
186 .await?;
187 self.credentials_cache
188 .write(credentials_response.parse()?)?;
189 self.credentials_cache.get(&today)?.ok_or({
190 ServiceError::InvalidFrame {
191 reason:
192 "credentials received did not contain requested day",
193 }
194 })?
195 };
196
197 self.get_authorization_string(
198 csprng,
199 group_secret_params,
200 auth_credential_response.clone(),
201 today,
202 )
203 }
204
205 fn get_authorization_string<R: Rng + CryptoRng>(
206 &self,
207 csprng: &mut R,
208 group_secret_params: GroupSecretParams,
209 credential_response: AuthCredentialWithPniResponse,
210 today: u64,
211 ) -> Result<HttpAuth, ServiceError> {
212 let redemption_time = zkgroup::Timestamp::from_epoch_seconds(today);
213
214 let auth_credential_bytes =
215 zkgroup::serialize(&credential_response.receive(
216 &self.server_public_params,
217 self.service_ids.aci(),
218 self.service_ids.pni(),
219 redemption_time,
220 )?);
221
222 let auth_credential =
223 AuthCredentialWithPni::new(&auth_credential_bytes)
224 .expect("just validated");
225
226 let mut random_bytes = [0u8; 32];
227 csprng.fill_bytes(&mut random_bytes);
228
229 let auth_credential_presentation =
230 zkgroup::serialize(&auth_credential.present(
231 &self.server_public_params,
232 &group_secret_params,
233 random_bytes,
234 ));
235
236 let username = hex::encode(bincode::serialize(
239 &group_secret_params.get_public_params(),
240 )?);
241
242 let password = hex::encode(&auth_credential_presentation);
243
244 Ok(HttpAuth { username, password })
245 }
246
247 pub async fn fetch_encrypted_group<R: Rng + CryptoRng>(
248 &mut self,
249 csprng: &mut R,
250 master_key_bytes: &[u8],
251 ) -> Result<crate::proto::Group, ServiceError> {
252 let group_master_key = GroupMasterKey::new(
253 master_key_bytes
254 .try_into()
255 .map_err(|_| ServiceError::GroupsV2Error)?,
256 );
257 let group_secret_params =
258 GroupSecretParams::derive_from_master_key(group_master_key);
259 let authorization = self
260 .get_authorization_for_today(csprng, group_secret_params)
261 .await?;
262 self.identified_push_service.get_group(authorization).await
263 }
264
265 #[tracing::instrument(
266 skip(self, group_secret_params),
267 fields(path = %path[..4.min(path.len())]),
268 )]
269 pub async fn retrieve_avatar(
270 &mut self,
271 path: &str,
272 group_secret_params: GroupSecretParams,
273 ) -> Result<Option<Vec<u8>>, ServiceError> {
274 let mut encrypted_avatar = self
275 .unidentified_websocket
276 .retrieve_groups_v2_profile_avatar(path)
277 .await?;
278 let mut result = Vec::with_capacity(10 * 1024 * 1024);
279 encrypted_avatar.read_to_end(&mut result).await?;
280 Ok(GroupOperations::new(group_secret_params).decrypt_avatar(&result))
281 }
282
283 pub fn decrypt_group_context(
284 &self,
285 group_context: GroupContextV2,
286 ) -> Result<Option<GroupChanges>, GroupDecodingError> {
287 match (group_context.master_key, group_context.group_change) {
288 (Some(master_key), Some(group_change)) => {
289 let master_key_bytes: [u8; 32] = master_key
290 .try_into()
291 .map_err(|_| GroupDecodingError::WrongBlob)?;
292 let group_master_key = GroupMasterKey::new(master_key_bytes);
293 let group_secret_params =
294 GroupSecretParams::derive_from_master_key(group_master_key);
295 let encrypted_group_change =
296 prost::Message::decode(Bytes::from(group_change))?;
297 let group_change = GroupOperations::new(group_secret_params)
298 .decrypt_group_change(encrypted_group_change)?;
299 Ok(Some(group_change))
300 },
301 _ => Ok(None),
302 }
303 }
304}
305
306pub fn decrypt_group(
307 master_key_bytes: &[u8],
308 encrypted_group: crate::proto::Group,
309) -> Result<Group, ServiceError> {
310 let group_master_key = GroupMasterKey::new(
311 master_key_bytes
312 .try_into()
313 .expect("wrong master key bytes length"),
314 );
315 let group_secret_params =
316 GroupSecretParams::derive_from_master_key(group_master_key);
317
318 Ok(GroupOperations::new(group_secret_params)
319 .decrypt_group(encrypted_group)?)
320}
321
322fn current_days_seconds() -> (u64, u64) {
323 let days_seconds = |date: NaiveDate| {
324 date.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
325 .and_utc()
326 .timestamp() as u64
327 };
328
329 let today = Utc::now().naive_utc().date();
330 let today_plus_7_days = today + Days::new(7);
331
332 (days_seconds(today), days_seconds(today_plus_7_days))
333}