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