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	let one_time_keys = body
25		.one_time_keys
26		.iter()
27		.take(services.config.one_time_key_limit)
28		.map(|(id, val)| (id.as_ref(), val));
29
30	services
31		.users
32		.add_one_time_keys(sender_user, sender_device, one_time_keys)
33		.await?;
34
35	let fallback_keys = body
36		.fallback_keys
37		.iter()
38		.map(|(id, val)| (id.as_ref(), val));
39
40	services
41		.users
42		.add_fallback_keys(sender_user, sender_device, fallback_keys)
43		.await?;
44
45	if let Some(device_keys) = body.device_keys.as_ref() {
46		store_device_keys(&services, sender_user, sender_device, device_keys).await?;
47	}
48
49	Ok(upload_keys::v3::Response {
50		one_time_key_counts: services
51			.users
52			.count_one_time_keys(sender_user, sender_device)
53			.await,
54	})
55}
56
57async fn store_device_keys(
58	services: &Services,
59	sender_user: &UserId,
60	sender_device: &DeviceId,
61	device_keys: &Raw<DeviceKeys>,
62) -> Result {
63	let new_keys = device_keys.deserialize().map_err(|e| {
64		err!(Request(BadJson(debug_warn!(
65			?device_keys,
66			"Invalid device keys JSON uploaded by client: {e}"
67		))))
68	})?;
69
70	if new_keys.user_id != sender_user {
71		return Err!(Request(Unknown(
72			"User ID in keys uploaded does not match your own user ID"
73		)));
74	}
75	if new_keys.device_id != sender_device {
76		return Err!(Request(Unknown(
77			"Device ID in keys uploaded does not match your own device ID"
78		)));
79	}
80
81	// Workaround for a nheko bug which omits cross-signing signatures when
82	// re-uploading the same DeviceKeys: ignore an exact-copy re-upload so the
83	// existing signatures are preserved.
84	let unchanged = services
85		.users
86		.get_device_keys(sender_user, sender_device)
87		.await
88		.and_then(|keys| keys.deserialize().map_err(Into::into))
89		.is_ok_and(|existing| existing.keys == new_keys.keys);
90
91	if unchanged {
92		debug!(
93			?sender_user,
94			?sender_device,
95			?device_keys,
96			"Ignoring user uploaded keys as they are an exact copy already in the database"
97		);
98
99		return Ok(());
100	}
101
102	services
103		.users
104		.add_device_keys(sender_user, sender_device, device_keys)
105		.await;
106
107	Ok(())
108}