Skip to main content

tuwunel_api_client/keys/
get_keys.rs

1use std::collections::{BTreeMap, HashMap};
2
3use axum::extract::State;
4use futures::{
5	FutureExt, StreamExt,
6	future::{join, join4},
7};
8use ruma::{
9	CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedUserId, ServerName,
10	UserId,
11	api::{
12		client::{device::Device, keys::get_keys},
13		federation::keys::get_keys::v1::{
14			Request as FederationRequest, Response as FederationResponse,
15		},
16	},
17	encryption::{CrossSigningKey, DeviceKeys},
18	serde::Raw,
19};
20use serde_json::value::to_raw_value;
21use tuwunel_core::{
22	Result, debug_warn, implement,
23	utils::{
24		BoolExt, IterStream,
25		future::TryExtExt,
26		json,
27		stream::{BroadbandExt, ReadyExt},
28	},
29	warn,
30};
31use tuwunel_service::{
32	Services,
33	federation::feds::{Outcome, OutcomeExt, fanout_with},
34	users::parse_master_key,
35};
36
37use super::{FailureMap, federation_failures, federation_opts};
38use crate::Ruma;
39
40#[derive(Default)]
41struct Keys {
42	device_keys: DeviceKeyMap,
43	master_keys: CrossSigningKeys,
44	self_signing_keys: CrossSigningKeys,
45	user_signing_keys: CrossSigningKeys,
46	failures: FailureMap,
47}
48
49type DeviceLists = BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>;
50type DeviceKeyMap = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Raw<DeviceKeys>>>;
51type ServerDevices<'a> = HashMap<&'a ServerName, DeviceLists>;
52type LocalDeviceUser<'a> = (&'a UserId, &'a Vec<OwnedDeviceId>);
53type CrossSigningKeys = BTreeMap<OwnedUserId, Raw<CrossSigningKey>>;
54
55/// # `POST /_matrix/client/r0/keys/query`
56///
57/// Get end-to-end encryption keys for the given users.
58///
59/// - Always fetches users from other servers over federation
60/// - Gets master keys, self-signing keys, user signing keys and device keys.
61/// - The master and self-signing keys contain signatures that the user is
62///   allowed to see
63pub(crate) async fn get_keys_route(
64	State(services): State<crate::State>,
65	body: Ruma<get_keys::v3::Request>,
66) -> Result<get_keys::v3::Response> {
67	let sender_user = body.sender_user();
68
69	get_keys_helper(
70		&services,
71		Some(sender_user),
72		&body.device_keys,
73		|u| u == sender_user,
74		true, // Always allow local users to see device names of other local users
75	)
76	.await
77}
78
79/// Queries device and cross-signing keys for the requested users.
80///
81/// The signature predicate filters returned signatures, and the display-name
82/// switch selects configured device names rather than device IDs.
83pub async fn get_keys_helper<F>(
84	services: &Services,
85	sender_user: Option<&UserId>,
86	device_keys_input: &DeviceLists,
87	allowed_signatures: F,
88	include_display_names: bool,
89) -> Result<get_keys::v3::Response>
90where
91	F: Fn(&UserId) -> bool + Send + Sync,
92{
93	let (local_users, remote_users): (Vec<LocalDeviceUser<'_>>, Vec<_>) = device_keys_input
94		.iter()
95		.map(|(uid, dids)| (uid.as_ref(), dids))
96		.partition(|(user_id, _)| services.globals.user_is_local(user_id));
97
98	let server: ServerDevices<'_> =
99		remote_users
100			.into_iter()
101			.fold(HashMap::new(), |mut acc, (user_id, device_ids)| {
102				acc.entry(user_id.server_name())
103					.or_default()
104					.insert(user_id.to_owned(), device_ids.clone());
105				acc
106			});
107
108	let local = collect_local_keys(
109		services,
110		&local_users,
111		sender_user,
112		&allowed_signatures,
113		include_display_names,
114	);
115
116	let federation = collect_federation_keys(services, server, sender_user, &allowed_signatures);
117
118	let (local, federation) = join(local, federation).await;
119	Ok(local.merge(federation).into_response())
120}
121
122async fn collect_local_keys<F>(
123	services: &Services,
124	users: &[LocalDeviceUser<'_>],
125	sender_user: Option<&UserId>,
126	allowed_signatures: &F,
127	include_display_names: bool,
128) -> Keys
129where
130	F: Fn(&UserId) -> bool + Send + Sync,
131{
132	users
133		.iter()
134		.copied()
135		.stream()
136		.broad_then(async |(user_id, device_ids)| {
137			collect_local_user_keys(
138				services,
139				user_id,
140				device_ids,
141				sender_user,
142				allowed_signatures,
143				include_display_names,
144			)
145			.await
146		})
147		.ready_fold(Keys::default(), Keys::merge)
148		.await
149}
150
151async fn collect_local_user_keys<F>(
152	services: &Services,
153	user_id: &UserId,
154	device_ids: &[OwnedDeviceId],
155	sender_user: Option<&UserId>,
156	allowed_signatures: &F,
157	include_display_names: bool,
158) -> Keys
159where
160	F: Fn(&UserId) -> bool + Send + Sync,
161{
162	let device_keys =
163		collect_local_device_keys(services, user_id, device_ids, include_display_names);
164
165	let master_key = services
166		.users
167		.get_master_key(sender_user, user_id, allowed_signatures)
168		.ok();
169
170	let self_signing_key = services
171		.users
172		.get_self_signing_key(sender_user, user_id, allowed_signatures)
173		.ok();
174
175	let user_signing_key = (Some(user_id) == sender_user)
176		.then_async(|| services.users.get_user_signing_key(user_id).ok())
177		.map(Option::flatten);
178
179	let (device_keys, master_key, self_signing_key, user_signing_key) =
180		join4(device_keys, master_key, self_signing_key, user_signing_key).await;
181
182	let owned = || user_id.to_owned();
183	Keys {
184		device_keys: BTreeMap::from([(owned(), device_keys)]),
185		master_keys: master_key
186			.map(|k| (owned(), k))
187			.into_iter()
188			.collect(),
189
190		self_signing_keys: self_signing_key
191			.map(|k| (owned(), k))
192			.into_iter()
193			.collect(),
194
195		user_signing_keys: user_signing_key
196			.map(|k| (owned(), k))
197			.into_iter()
198			.collect(),
199
200		..Default::default()
201	}
202}
203
204async fn collect_local_device_keys(
205	services: &Services,
206	user_id: &UserId,
207	device_ids: &[OwnedDeviceId],
208	include_display_names: bool,
209) -> BTreeMap<OwnedDeviceId, Raw<DeviceKeys>> {
210	let stream = if device_ids.is_empty() {
211		services
212			.users
213			.all_device_ids(user_id)
214			.map(ToOwned::to_owned)
215			.left_stream()
216	} else {
217		device_ids.iter().cloned().stream().right_stream()
218	};
219
220	stream
221		.broad_filter_map(async |device_id| {
222			get_local_device_keys(services, user_id, &device_id, include_display_names)
223				.await
224				.map(|keys| (device_id, keys))
225		})
226		.collect()
227		.await
228}
229
230async fn get_local_device_keys(
231	services: &Services,
232	user_id: &UserId,
233	device_id: &DeviceId,
234	include_display_names: bool,
235) -> Option<Raw<DeviceKeys>> {
236	let mut keys = services
237		.users
238		.get_device_keys(user_id, device_id)
239		.await
240		.ok()?;
241
242	let metadata = services
243		.users
244		.get_device_metadata(user_id, device_id)
245		.await
246		.inspect_err(|e| debug_warn!(?user_id, ?device_id, "device metadata missing: {e}"))
247		.ok()?;
248
249	add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
250		.inspect_err(|e| debug_warn!(?user_id, ?device_id, "invalid device keys: {e}"))
251		.ok()?;
252
253	Some(keys)
254}
255
256async fn collect_federation_keys<F>(
257	services: &Services,
258	server: ServerDevices<'_>,
259	sender_user: Option<&UserId>,
260	allowed_signatures: &F,
261) -> Keys
262where
263	F: Fn(&UserId) -> bool + Send + Sync,
264{
265	let requests = server
266		.into_iter()
267		.stream()
268		.map(|(server, device_keys)| (server.to_owned(), FederationRequest { device_keys }));
269
270	let outcomes = fanout_with(
271		requests,
272		async |server, request| {
273			services
274				.federation
275				.execute_keys(&server, request)
276				.await
277		},
278		federation_opts(services),
279	)
280	.broad_then(async |outcome| {
281		process_federation_outcome(services, sender_user, allowed_signatures, outcome).await
282	});
283
284	let (keys, faults) = outcomes.merge(Keys::default(), Keys::merge).await;
285
286	Keys {
287		failures: federation_failures("get_keys", faults).collect(),
288		..keys
289	}
290}
291
292async fn process_federation_outcome<F>(
293	services: &Services,
294	sender_user: Option<&UserId>,
295	allowed_signatures: &F,
296	outcome: Outcome<FederationResponse>,
297) -> Outcome<Keys>
298where
299	F: Fn(&UserId) -> bool + Send + Sync,
300{
301	let Outcome { origin, elapsed, result } = outcome;
302	let result = match result {
303		| Err(fault) => Err(fault),
304		| Ok(response) => {
305			let response = retain_origin_users(&origin, response);
306			let keys =
307				process_federation_response(services, sender_user, allowed_signatures, response)
308					.await;
309
310			Ok(keys)
311		},
312	};
313
314	Outcome { origin, elapsed, result }
315}
316
317/// Drops the keys of users who do not belong to the server that answered.
318///
319/// A server is only authoritative for its own users; any other entry, local
320/// users included, would otherwise be stored or served as that user's keys.
321fn retain_origin_users(
322	origin: &ServerName,
323	mut response: FederationResponse,
324) -> FederationResponse {
325	response
326		.device_keys
327		.retain(|user, _| user.server_name().eq(origin));
328
329	response
330		.master_keys
331		.retain(|user, _| user.server_name().eq(origin));
332
333	response
334		.self_signing_keys
335		.retain(|user, _| user.server_name().eq(origin));
336
337	response
338}
339
340async fn process_federation_response<F>(
341	services: &Services,
342	sender_user: Option<&UserId>,
343	allowed_signatures: &F,
344	response: FederationResponse,
345) -> Keys
346where
347	F: Fn(&UserId) -> bool + Send + Sync,
348{
349	let FederationResponse {
350		master_keys,
351		self_signing_keys,
352		device_keys,
353	} = response;
354
355	let master_keys = master_keys
356		.into_iter()
357		.stream()
358		.broad_filter_map(async |(user, master_key)| {
359			merge_remote_master_key(services, sender_user, allowed_signatures, &user, master_key)
360				.await
361				.inspect_err(|e| debug_warn!(?user, "skipping master key from federation: {e}"))
362				.map(|raw| (user, raw))
363				.ok()
364		})
365		.collect()
366		.await;
367
368	Keys {
369		device_keys,
370		master_keys,
371		self_signing_keys,
372		user_signing_keys: BTreeMap::new(),
373		failures: BTreeMap::new(),
374	}
375}
376
377/// Merges our cached signatures onto a remote-supplied master key.
378///
379/// The returned copy carries only the cached signatures the requester may see,
380/// while the stored copy carries every signature of our cached key. A failed
381/// store is logged and leaves the returned copy as it is.
382async fn merge_remote_master_key<F>(
383	services: &Services,
384	sender_user: Option<&UserId>,
385	allowed_signatures: &F,
386	user: &UserId,
387	master_key_raw: Raw<CrossSigningKey>,
388) -> Result<Raw<CrossSigningKey>>
389where
390	F: Fn(&UserId) -> bool + Send + Sync,
391{
392	let (row_key, master_key) = parse_master_key(user, &master_key_raw)?;
393	let our_raw = services
394		.users
395		.get_key(&row_key, sender_user, user, allowed_signatures)
396		.await;
397
398	let master_key = merge_signatures(user, master_key, our_raw);
399	let raw = json::to_raw(&master_key)?;
400
401	services
402		.users
403		.store_remote_master_key(user, &master_key_raw)
404		.await
405		.inspect_err(|error| warn!(%user, %error, "Not storing a master key from federation."))
406		.ok();
407
408	Ok(raw)
409}
410
411fn merge_signatures(
412	user: &UserId,
413	mut master_key: CrossSigningKey,
414	our_raw: Result<Raw<CrossSigningKey>>,
415) -> CrossSigningKey {
416	if let Ok(our_raw) = our_raw
417		&& let Ok((_, mut ours)) = parse_master_key(user, &our_raw)
418	{
419		master_key.signatures.append(&mut ours.signatures);
420	}
421
422	master_key
423}
424
425fn add_unsigned_device_display_name(
426	keys: &mut Raw<DeviceKeys>,
427	metadata: Device,
428	include_display_names: bool,
429) -> Result {
430	let Some(display_name) = metadata.display_name else {
431		return Ok(());
432	};
433
434	let mut object = keys.deserialize_as_unchecked::<CanonicalJsonObject>()?;
435
436	if let CanonicalJsonValue::Object(unsigned) = object
437		.entry("unsigned".into())
438		.or_insert_with(|| CanonicalJsonObject::default().into())
439	{
440		let display_name = if include_display_names {
441			CanonicalJsonValue::String(display_name.to_string())
442		} else {
443			CanonicalJsonValue::String(metadata.device_id.into())
444		};
445
446		unsigned.insert("device_display_name".into(), display_name);
447	}
448
449	*keys = Raw::from_json(to_raw_value(&object)?);
450
451	Ok(())
452}
453
454#[implement(Keys)]
455fn merge(mut self, other: Self) -> Self {
456	self.failures.extend(other.failures);
457	self.device_keys.extend(other.device_keys);
458	self.master_keys.extend(other.master_keys);
459	self.self_signing_keys
460		.extend(other.self_signing_keys);
461	self.user_signing_keys
462		.extend(other.user_signing_keys);
463	self
464}
465
466#[implement(Keys)]
467fn into_response(self) -> get_keys::v3::Response {
468	get_keys::v3::Response {
469		failures: self.failures,
470		device_keys: self.device_keys,
471		master_keys: self.master_keys,
472		self_signing_keys: self.self_signing_keys,
473		user_signing_keys: self.user_signing_keys,
474	}
475}
476
477#[cfg(test)]
478mod tests {
479	use ruma::{server_name, user_id};
480	use serde_json::json;
481
482	use super::*;
483
484	#[test]
485	fn drops_keys_of_users_on_other_servers() {
486		let remote = user_id!("@bob:remote.example");
487		let users = [remote, user_id!("@alice:local.example"), user_id!("@carol:third.example")];
488		let devices = |user: &UserId| (user.to_owned(), BTreeMap::new());
489		let key = |user: &UserId| (user.to_owned(), Raw::from_json_value(&json!({})));
490		let response = FederationResponse {
491			device_keys: users.map(devices).into(),
492			master_keys: users.map(key).into(),
493			self_signing_keys: users.map(key).into(),
494		};
495
496		let response = retain_origin_users(server_name!("remote.example"), response);
497
498		assert!(response.device_keys.keys().eq([remote]));
499		assert!(response.master_keys.keys().eq([remote]));
500		assert!(response.self_signing_keys.keys().eq([remote]));
501	}
502}