Skip to main content

tuwunel_api/client/keys/
upload_keys.rs

1use axum::extract::State;
2use ruma::{
3	DeviceId, UserId, api::client::keys::upload_keys, encryption::DeviceKeys, serde::Raw,
4};
5use tuwunel_core::{Err, Result, debug, err};
6use tuwunel_service::Services;
7
8use crate::Ruma;
9
10/// # `POST /_matrix/client/r0/keys/upload`
11///
12/// Publish end-to-end encryption keys for the sender device.
13///
14/// - Adds one time keys
15/// - If there are no device keys yet: Adds device keys (TODO: merge with
16///   existing keys?)
17pub(crate) async fn upload_keys_route(
18	State(services): State<crate::State>,
19	body: Ruma<upload_keys::v3::Request>,
20) -> Result<upload_keys::v3::Response> {
21	let sender_user = body.sender_user();
22	let sender_device = body.sender_device()?;
23
24	services
25		.users
26		.add_one_time_keys(
27			sender_user,
28			sender_device,
29			&body.one_time_keys,
30			services.config.one_time_key_limit,
31		)
32		.await?;
33
34	let fallback_keys = body
35		.fallback_keys
36		.iter()
37		.map(|(id, val)| (id.as_ref(), val));
38
39	services
40		.users
41		.add_fallback_keys(sender_user, sender_device, fallback_keys)
42		.await?;
43
44	if let Some(device_keys) = body.device_keys.as_ref() {
45		store_device_keys(&services, sender_user, sender_device, device_keys).await?;
46	}
47
48	Ok(upload_keys::v3::Response {
49		one_time_key_counts: services
50			.users
51			.count_one_time_keys(sender_user, sender_device)
52			.await,
53	})
54}
55
56async fn store_device_keys(
57	services: &Services,
58	sender_user: &UserId,
59	sender_device: &DeviceId,
60	device_keys: &Raw<DeviceKeys>,
61) -> Result {
62	let new_keys = device_keys.deserialize().map_err(|e| {
63		err!(Request(BadJson(debug_warn!(
64			?device_keys,
65			"Invalid device keys JSON uploaded by client: {e}"
66		))))
67	})?;
68
69	if new_keys.user_id != sender_user {
70		return Err!(Request(Unknown(
71			"User ID in keys uploaded does not match your own user ID"
72		)));
73	}
74	if new_keys.device_id != sender_device {
75		return Err!(Request(Unknown(
76			"Device ID in keys uploaded does not match your own device ID"
77		)));
78	}
79
80	// Workaround for a nheko bug which omits cross-signing signatures when
81	// re-uploading the same DeviceKeys: ignore an exact-copy re-upload so the
82	// existing signatures are preserved.
83	let unchanged = services
84		.users
85		.get_device_keys(sender_user, sender_device)
86		.await
87		.and_then(|keys| keys.deserialize().map_err(Into::into))
88		.is_ok_and(|existing| existing.keys == new_keys.keys);
89
90	if unchanged {
91		debug!(
92			?sender_user,
93			?sender_device,
94			?device_keys,
95			"Ignoring user uploaded keys as they are an exact copy already in the database"
96		);
97
98		return Ok(());
99	}
100
101	services
102		.users
103		.add_device_keys(sender_user, sender_device, device_keys)
104		.await;
105
106	Ok(())
107}