tuwunel_api/client/keys/
upload_signing_keys.rs1use axum::extract::State;
2use ruma::{
3 UserId,
4 api::client::{
5 keys::upload_signing_keys,
6 uiaa::{AuthFlow, AuthType, UiaaInfo},
7 },
8 encryption::CrossSigningKey,
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
22pub(crate) async fn upload_signing_keys_route(
31 State(services): State<crate::State>,
32 body: Ruma<upload_signing_keys::v3::Request>,
33) -> Result<upload_signing_keys::v3::Response> {
34 let sender_user = body.sender_user();
35
36 if let Ok(exists) = check_for_new_keys(
39 &services,
40 sender_user,
41 body.self_signing_key.as_ref(),
42 body.user_signing_key.as_ref(),
43 body.master_key.as_ref(),
44 )
45 .await
46 .inspect_err(|e| debug_error!(?e))
47 {
48 if let Some(result) = exists {
49 return Ok(result);
52 }
53
54 debug!("Skipping UIA as per MSC3967: user had no existing keys");
56 return persist_signing_keys(&services, &body).await;
57 }
58
59 if body
62 .appservice_info
63 .as_ref()
64 .is_some_and(|appservice| appservice.registration.device_management)
65 {
66 debug!(
67 "Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
68 enabled"
69 );
70
71 return persist_signing_keys(&services, &body).await;
72 }
73
74 let is_oidc = body
75 .sender_device()
76 .ok()
77 .map_async(|sender_device| {
78 services
79 .users
80 .is_oidc_device(sender_user, sender_device)
81 })
82 .await
83 .unwrap_or(false);
84
85 if is_oidc
88 && services
89 .users
90 .can_replace_cross_signing_keys(sender_user)
91 .await
92 {
93 return persist_signing_keys(&services, &body).await;
94 }
95
96 if is_oidc && body.auth.is_none() {
98 return Err(Error::Uiaa(create_oauth_uiaa(&services, sender_user, &body)?));
99 }
100
101 let authed_user = auth_uiaa(&services, &body).await?;
102
103 assert_eq!(sender_user, authed_user, "Expected UIAA of {sender_user} and not {authed_user}");
104 persist_signing_keys(&services, &body).await
105}
106
107async fn persist_signing_keys(
108 services: &Services,
109 body: &Ruma<upload_signing_keys::v3::Request>,
110) -> Result<upload_signing_keys::v3::Response> {
111 services
112 .users
113 .add_cross_signing_keys(
114 body.sender_user(),
115 &body.master_key,
116 &body.self_signing_key,
117 &body.user_signing_key,
118 true, )
120 .await?;
121
122 Ok(upload_signing_keys::v3::Response {})
123}
124
125fn create_oauth_uiaa(
126 services: &Services,
127 sender_user: &UserId,
128 body: &Ruma<upload_signing_keys::v3::Request>,
129) -> Result<UiaaInfo> {
130 let session = utils::random_string(SESSION_ID_LENGTH);
131 let base = services
132 .config
133 .well_known
134 .client
135 .as_ref()
136 .map(|url| url.to_string().trim_end_matches('/').to_owned())
137 .ok_or_else(|| {
138 err!(Config(
139 "well_known.client",
140 "well_known.client must be set for cross-signing reset"
141 ))
142 })?;
143
144 let fallback_url =
145 format!("{base}/_matrix/client/v3/auth/m.login.sso/fallback/web?session={session}");
146
147 let uiaainfo = UiaaInfo {
148 flows: vec![AuthFlow { stages: vec![AuthType::OAuth] }],
149 params: Some(to_raw_value(&json!({"m.oauth": { "url": fallback_url }}))?),
150 session: Some(session),
151 ..Default::default()
152 };
153
154 services.uiaa.create(
155 sender_user,
156 body.sender_device()?,
157 &uiaainfo,
158 body.json_body
159 .as_ref()
160 .ok_or_else(|| err!(Request(NotJson("JSON body is not valid"))))?,
161 );
162
163 Ok(uiaainfo)
164}
165
166async fn check_for_new_keys(
167 services: &Services,
168 user_id: &UserId,
169 self_signing_key: Option<&Raw<CrossSigningKey>>,
170 user_signing_key: Option<&Raw<CrossSigningKey>>,
171 master_signing_key: Option<&Raw<CrossSigningKey>>,
172) -> Result<Option<upload_signing_keys::v3::Response>> {
173 debug!("checking for existing keys");
174
175 let empty = match master_signing_key {
176 | Some(new_master) => !master_key_matches(services, user_id, new_master).await?,
177 | None => false,
178 };
179
180 if let Some(new_user_signing) = user_signing_key {
181 let fetched = services.users.get_user_signing_key(user_id).await;
182
183 if fetched.is_not_found() {
184 if !empty {
185 return Err!(Request(Forbidden(
186 "Tried to update an existing user signing key, UIA required"
187 )));
188 }
189 } else if fetched?.deserialize()? != new_user_signing.deserialize()? {
190 return Err!(Request(Forbidden(
191 "Tried to change an existing user signing key, UIA required"
192 )));
193 }
194 }
195
196 if let Some(new_self_signing) = self_signing_key {
197 let fetched = services
198 .users
199 .get_self_signing_key(None, user_id, &|_| true)
200 .await;
201
202 if fetched.is_not_found() {
203 if !empty {
204 return Err!(Request(Forbidden(
205 "Tried to add a new signing key independently from the master key"
206 )));
207 }
208 } else if fetched?.deserialize()? != new_self_signing.deserialize()? {
209 return Err!(Request(Forbidden(
210 "Tried to update an existing self signing key, UIA required"
211 )));
212 }
213 }
214
215 Ok(empty
216 .is_false()
217 .into_option()
218 .map(|()| upload_signing_keys::v3::Response {}))
219}
220
221async fn master_key_matches(
225 services: &Services,
226 user_id: &UserId,
227 new_master: &Raw<CrossSigningKey>,
228) -> Result<bool> {
229 let (new_id, new_value) = parse_master_key(user_id, new_master)?;
230 let existing = services
231 .users
232 .get_master_key(None, user_id, &|_| true)
233 .await;
234
235 if existing.is_not_found() {
236 return Ok(false);
237 }
238
239 let (existing_id, existing_value) = parse_master_key(user_id, &existing?)?;
240 if existing_id != new_id || existing_value != new_value {
241 return Err!(Request(Forbidden("Tried to change an existing master key, UIA required")));
242 }
243
244 Ok(true)
245}