Skip to main content

tuwunel_api/client/keys/
upload_signing_keys.rs

1use axum::extract::State;
2use ruma::{
3	UserId,
4	api::client::{
5		keys::upload_signing_keys,
6		uiaa::{AuthFlow, AuthType, UiaaInfo},
7	},
8	encryption::{CrossSigningKey, KeyUsage},
9	serde::Raw,
10};
11use serde_json::{json, value::to_raw_value};
12use tuwunel_core::{
13	Err, Error, Result, debug, debug_error, err,
14	result::NotFound,
15	utils,
16	utils::{BoolExt, OptionExt},
17};
18use tuwunel_service::{Services, uiaa::SESSION_ID_LENGTH, users::parse_master_key};
19
20use crate::{Ruma, router::auth_uiaa};
21
22struct Keys<'a> {
23	user_id: &'a UserId,
24	master_key: &'a Option<Raw<CrossSigningKey>>,
25	self_signing_key: &'a Option<Raw<CrossSigningKey>>,
26	user_signing_key: &'a Option<Raw<CrossSigningKey>>,
27}
28
29struct ValidatedKeys<'a>(Keys<'a>);
30
31/// # `POST /_matrix/client/r0/keys/device_signing/upload`
32///
33/// Uploads end-to-end key information for the sender user.
34///
35/// - Requires UIAA to verify password
36/// - For OIDC devices, requires OAuth re-authentication via SSO (MSC4312)
37/// - For appservices with `device_management` enabled, UIAA is skipped even
38///   when cross-signing keys already exist (MSC4190)
39pub(crate) async fn upload_signing_keys_route(
40	State(services): State<crate::State>,
41	body: Ruma<upload_signing_keys::v3::Request>,
42) -> Result<upload_signing_keys::v3::Response> {
43	let sender_user = body.sender_user();
44
45	let keys = validate_keys(Keys {
46		user_id: sender_user,
47		master_key: &body.master_key,
48		self_signing_key: &body.self_signing_key,
49		user_signing_key: &body.user_signing_key,
50	})?;
51
52	// Access token is required for this endpoint regardless of conditional UIAA so
53	// we'll always have a sender_user.
54	if let Ok(exists) = check_for_new_keys(
55		&services,
56		sender_user,
57		body.self_signing_key.as_ref(),
58		body.user_signing_key.as_ref(),
59		body.master_key.as_ref(),
60	)
61	.await
62	.inspect_err(|e| debug_error!(?e))
63	{
64		if let Some(result) = exists {
65			// No-op, they tried to reupload the same set of keys
66			// (lost connection for example)
67			return Ok(result);
68		}
69
70		// Some of the keys weren't found, so we let them upload
71		debug!("Skipping UIA as per MSC3967: user had no existing keys");
72		return persist_signing_keys(&services, keys).await;
73	}
74
75	// MSC4190: appservices with device_management may replace existing
76	// cross-signing keys without UIAA.
77	if body
78		.appservice_info
79		.as_ref()
80		.is_some_and(|appservice| appservice.registration.device_management)
81	{
82		debug!(
83			"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
84			 enabled"
85		);
86
87		return persist_signing_keys(&services, keys).await;
88	}
89
90	let is_oidc = body
91		.sender_device()
92		.ok()
93		.map_async(|sender_device| {
94			services
95				.users
96				.is_oidc_device(sender_user, sender_device)
97		})
98		.await
99		.unwrap_or(false);
100
101	// MSC4312: OIDC devices require OAuth re-authentication for cross-signing
102	// reset. If a bypass was granted via SSO re-auth, skip UIAA entirely.
103	if is_oidc
104		&& services
105			.users
106			.can_replace_cross_signing_keys(sender_user)
107			.await
108	{
109		return persist_signing_keys(&services, keys).await;
110	}
111
112	// First attempt from OIDC device: issue m.oauth flow.
113	if is_oidc && body.auth.is_none() {
114		return Err(Error::Uiaa(create_oauth_uiaa(&services, sender_user, &body)?));
115	}
116
117	let authed_user = auth_uiaa(&services, &body).await?;
118
119	assert_eq!(sender_user, authed_user, "Expected UIAA of {sender_user} and not {authed_user}");
120	persist_signing_keys(&services, keys).await
121}
122
123fn validate_keys(keys: Keys<'_>) -> Result<ValidatedKeys<'_>> {
124	[
125		(keys.master_key.as_ref(), KeyUsage::Master),
126		(keys.self_signing_key.as_ref(), KeyUsage::SelfSigning),
127		(keys.user_signing_key.as_ref(), KeyUsage::UserSigning),
128	]
129	.into_iter()
130	.try_for_each(|(key, usage)| validate_key(keys.user_id, key, &usage))?;
131
132	Ok(ValidatedKeys(keys))
133}
134
135fn validate_key(
136	user_id: &UserId,
137	key: Option<&Raw<CrossSigningKey>>,
138	usage: &KeyUsage,
139) -> Result {
140	let Some(key) = key else {
141		return Ok(());
142	};
143
144	let key = key
145		.deserialize()
146		.map_err(|error| err!(Request(InvalidParam("Invalid cross-signing key: {error}"))))?;
147
148	if key.user_id != user_id {
149		return Err!(Request(InvalidParam("Cross-signing key belongs to another user.")));
150	}
151
152	if !key.usage.contains(usage) {
153		return Err!(Request(InvalidParam(
154			"Cross-signing key does not include the required usage."
155		)));
156	}
157
158	if key.keys.len() != 1 {
159		return Err!(Request(InvalidParam("Cross-signing key must contain exactly one key.")));
160	}
161
162	Ok(())
163}
164
165async fn persist_signing_keys(
166	services: &Services,
167	keys: ValidatedKeys<'_>,
168) -> Result<upload_signing_keys::v3::Response> {
169	let ValidatedKeys(keys) = keys;
170
171	services
172		.users
173		.add_cross_signing_keys(
174			keys.user_id,
175			keys.master_key,
176			keys.self_signing_key,
177			keys.user_signing_key,
178			true, // notify so that other users see the new keys
179		)
180		.await?;
181
182	Ok(upload_signing_keys::v3::Response {})
183}
184
185fn create_oauth_uiaa(
186	services: &Services,
187	sender_user: &UserId,
188	body: &Ruma<upload_signing_keys::v3::Request>,
189) -> Result<UiaaInfo> {
190	let session = utils::random_string(SESSION_ID_LENGTH);
191	let issuer = services.oauth.get_server()?.issuer_url()?;
192	let base = issuer.trim_end_matches('/');
193	let url = format!("{base}/_tuwunel/oidc/account?action=org.matrix.cross_signing_reset");
194
195	let uiaainfo = UiaaInfo {
196		flows: vec![AuthFlow { stages: vec![AuthType::OAuth] }],
197		params: Some(to_raw_value(&json!({"m.oauth": { "url": url }}))?),
198		session: Some(session),
199		..Default::default()
200	};
201
202	services.uiaa.create(
203		sender_user,
204		body.sender_device()?,
205		&uiaainfo,
206		body.json_body
207			.as_ref()
208			.ok_or_else(|| err!(Request(NotJson("JSON body is not valid"))))?,
209	);
210
211	Ok(uiaainfo)
212}
213
214async fn check_for_new_keys(
215	services: &Services,
216	user_id: &UserId,
217	self_signing_key: Option<&Raw<CrossSigningKey>>,
218	user_signing_key: Option<&Raw<CrossSigningKey>>,
219	master_signing_key: Option<&Raw<CrossSigningKey>>,
220) -> Result<Option<upload_signing_keys::v3::Response>> {
221	debug!("checking for existing keys");
222
223	let empty = match master_signing_key {
224		| Some(new_master) => !master_key_matches(services, user_id, new_master).await?,
225		| None => false,
226	};
227
228	if let Some(new_user_signing) = user_signing_key {
229		let fetched = services.users.get_user_signing_key(user_id).await;
230
231		if fetched.is_not_found() {
232			if !empty {
233				return Err!(Request(Forbidden(
234					"Tried to update an existing user signing key, UIA required"
235				)));
236			}
237		} else if fetched?.deserialize()? != new_user_signing.deserialize()? {
238			return Err!(Request(Forbidden(
239				"Tried to change an existing user signing key, UIA required"
240			)));
241		}
242	}
243
244	if let Some(new_self_signing) = self_signing_key {
245		let fetched = services
246			.users
247			.get_self_signing_key(None, user_id, &|_| true)
248			.await;
249
250		if fetched.is_not_found() {
251			if !empty {
252				return Err!(Request(Forbidden(
253					"Tried to add a new signing key independently from the master key"
254				)));
255			}
256		} else if fetched?.deserialize()? != new_self_signing.deserialize()? {
257			return Err!(Request(Forbidden(
258				"Tried to update an existing self signing key, UIA required"
259			)));
260		}
261	}
262
263	Ok(empty
264		.is_false()
265		.into_option()
266		.map(|()| upload_signing_keys::v3::Response {}))
267}
268
269/// Returns `true` if the user already has a master key matching `new_master`,
270/// `false` if they have no master key. Returns `Err` on mismatch or any other
271/// error.
272async fn master_key_matches(
273	services: &Services,
274	user_id: &UserId,
275	new_master: &Raw<CrossSigningKey>,
276) -> Result<bool> {
277	let (new_id, new_value) = parse_master_key(user_id, new_master)?;
278	let existing = services
279		.users
280		.get_master_key(None, user_id, &|_| true)
281		.await;
282
283	if existing.is_not_found() {
284		return Ok(false);
285	}
286
287	let (existing_id, existing_value) = parse_master_key(user_id, &existing?)?;
288	if existing_id != new_id || existing_value != new_value {
289		return Err!(Request(Forbidden("Tried to change an existing master key, UIA required")));
290	}
291
292	Ok(true)
293}
294
295#[cfg(test)]
296mod tests {
297	use ruma::{api::error::ErrorKind::InvalidParam, user_id};
298	use serde_json::Map;
299
300	use super::*;
301
302	#[test]
303	fn accepts_valid_unsigned_keys_and_additional_usages() {
304		let user_id = user_id!("@alice:example.com");
305		let master_key =
306			Some(signing_key(user_id, &["master", "self_signing"], &["ed25519:master"]));
307
308		let self_signing_key =
309			Some(signing_key(user_id, &["self_signing", "user_signing"], &["ed25519:self"]));
310
311		let user_signing_key =
312			Some(signing_key(user_id, &["user_signing", "master"], &["ed25519:user"]));
313
314		validate_keys(Keys {
315			user_id,
316			master_key: &master_key,
317			self_signing_key: &self_signing_key,
318			user_signing_key: &user_signing_key,
319		})
320		.expect("valid unsigned keys should pass structural validation");
321	}
322
323	#[test]
324	fn rejects_wrong_owner_for_each_role() {
325		let user_id = user_id!("@alice:example.com");
326		let other_user = user_id!("@mallory:elsewhere.example");
327		let none = None;
328		let master_key = Some(signing_key(other_user, &["master"], &["ed25519:master"]));
329		let self_signing_key =
330			Some(signing_key(other_user, &["self_signing"], &["ed25519:self"]));
331
332		let user_signing_key =
333			Some(signing_key(other_user, &["user_signing"], &["ed25519:user"]));
334
335		assert_invalid(&validate_keys(Keys {
336			user_id,
337			master_key: &master_key,
338			self_signing_key: &none,
339			user_signing_key: &none,
340		}));
341
342		assert_invalid(&validate_keys(Keys {
343			user_id,
344			master_key: &none,
345			self_signing_key: &self_signing_key,
346			user_signing_key: &none,
347		}));
348
349		assert_invalid(&validate_keys(Keys {
350			user_id,
351			master_key: &none,
352			self_signing_key: &none,
353			user_signing_key: &user_signing_key,
354		}));
355	}
356
357	#[test]
358	fn rejects_missing_usage_for_each_role() {
359		let user_id = user_id!("@alice:example.com");
360		let none = None;
361		let master_key = Some(signing_key(user_id, &["self_signing"], &["ed25519:master"]));
362		let self_signing_key = Some(signing_key(user_id, &["master"], &["ed25519:self"]));
363		let user_signing_key = Some(signing_key(user_id, &["master"], &["ed25519:user"]));
364
365		assert_invalid(&validate_keys(Keys {
366			user_id,
367			master_key: &master_key,
368			self_signing_key: &none,
369			user_signing_key: &none,
370		}));
371
372		assert_invalid(&validate_keys(Keys {
373			user_id,
374			master_key: &none,
375			self_signing_key: &self_signing_key,
376			user_signing_key: &none,
377		}));
378
379		assert_invalid(&validate_keys(Keys {
380			user_id,
381			master_key: &none,
382			self_signing_key: &none,
383			user_signing_key: &user_signing_key,
384		}));
385	}
386
387	#[test]
388	fn rejects_invalid_key_counts() {
389		let user_id = user_id!("@alice:example.com");
390		let none = None;
391		let empty = Some(signing_key(user_id, &["master"], &[]));
392		let multiple =
393			Some(signing_key(user_id, &["self_signing"], &["ed25519:first", "ed25519:second"]));
394
395		assert_invalid(&validate_keys(Keys {
396			user_id,
397			master_key: &empty,
398			self_signing_key: &none,
399			user_signing_key: &none,
400		}));
401
402		assert_invalid(&validate_keys(Keys {
403			user_id,
404			master_key: &none,
405			self_signing_key: &multiple,
406			user_signing_key: &none,
407		}));
408	}
409
410	#[test]
411	fn rejects_a_malformed_later_key_before_persistence() {
412		let user_id = user_id!("@alice:example.com");
413		let master_key = Some(signing_key(user_id, &["master"], &["ed25519:master"]));
414		let self_signing_key = Some(signing_key(user_id, &["self_signing"], &["ed25519:self"]));
415		let user_signing_key = Some(signing_key(user_id, &["master"], &["ed25519:user"]));
416
417		assert_invalid(&validate_keys(Keys {
418			user_id,
419			master_key: &master_key,
420			self_signing_key: &self_signing_key,
421			user_signing_key: &user_signing_key,
422		}));
423	}
424
425	fn signing_key(user_id: &UserId, usage: &[&str], key_ids: &[&str]) -> Raw<CrossSigningKey> {
426		let keys: Map<_, _> = key_ids
427			.iter()
428			.map(|key_id| ((*key_id).to_owned(), json!("public-key")))
429			.collect();
430
431		let key = json!({
432			"user_id": user_id,
433			"usage": usage,
434			"keys": keys,
435		});
436
437		Raw::from_json(to_raw_value(&key).expect("cross-signing key should serialize"))
438	}
439
440	fn assert_invalid(result: &Result<ValidatedKeys<'_>>) {
441		assert!(matches!(result, Err(Error::Request(InvalidParam, ..))));
442	}
443}