1#![allow(
8 clippy::allow_attributes,
9 reason = "This source file contains enums which allow `clippy::missing_docs_in_private_items`. But an exception to this lint will mistakenly report that it's unfulfilled when used with expect, even though it is not."
10)]
11
12mod tests;
13
14use std::{collections::BTreeMap, time::Duration};
15
16use arminius_lib_authentication::SessionInfo;
17use arminius_lib_component::{
18 Component, ServerState,
19 error::{
20 BadJson, InputJsonError, InternalServerError, MatrixApiError, ResultExt as _,
21 create_custom_matrix_error,
22 },
23 ruma_requests::ArminiusResponse,
24};
25use arminius_lib_db::{Database as _, TransactedDb};
26use arminius_lib_db_keys::{
27 FallbackKeysQueries as _, OneTimeKeysQueries as _, UserKeysQueries as _,
28};
29use arminius_lib_db_signatures::LocalSignatureQueries as _;
30use arminius_lib_macros::{FromDbError, FromNatsError};
31use arminius_lib_model::user_keys::DbKeyType;
32use arminius_lib_user_keys::{
33 FederatedKeyFailureReason,
34 identity_keys::{FederatedIdentityKeysResponse, RawUserKeys},
35 one_time_keys::FederatedOTKResponse,
36 signatures::{SignatureVerificationError, verify_signatures},
37 traits::SigningKey as _,
38};
39use arminius_server_federation_output::clients::FederationNatsClient;
40use axum::{Router, extract::State, routing::post};
41use reqwest::StatusCode;
42use ruma::{
43 DeviceId, OwnedServerName, UserId,
44 api::{
45 client::keys::{claim_keys, get_keys, upload_keys},
46 error::{Error as MatrixError, ErrorKind},
47 },
48 encryption::{DeviceKeys, OneTimeKey},
49 serde::{Base64, base64::Standard},
50};
51use serde_json::json;
52use tokio_util::task::JoinMap;
53
54use crate::client_request::AuthenticatedClientRequest;
55
56#[derive(Debug, thiserror::Error)]
58#[error("Invalid signature: {0}")]
59pub struct SignatureError(String);
60
61impl MatrixApiError for SignatureError {
64 fn create_matrix_error(&self) -> MatrixError {
65 create_custom_matrix_error(
66 StatusCode::BAD_REQUEST,
67 "M_INVALID_SIGNATURE",
68 &self.to_string(),
69 )
70 }
71}
72
73impl From<SignatureVerificationError> for UserKeysError {
76 fn from(signature_error: SignatureVerificationError) -> Self {
77 match signature_error {
78 SignatureVerificationError::BadJson(bad_json) => {
79 Self::InputJsonError(InputJsonError::BadJson(bad_json))
80 }
81 other_error => Self::SignatureVerification(SignatureError(other_error.to_string())),
82 }
83 }
84}
85
86#[allow(
87 missing_docs,
88 clippy::missing_docs_in_private_items,
89 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
90)]
91#[derive(Debug, thiserror::Error, MatrixApiError, FromDbError, FromNatsError)]
92enum UserKeysError {
93 #[error(transparent)]
94 InputJsonError(#[from] InputJsonError),
95
96 #[error("Missing device keys")]
99 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::MissingParam)]
100 MissingDeviceKeys,
101
102 #[error(transparent)]
103 SignatureVerification(#[from] SignatureError),
104
105 #[error(transparent)]
106 InternalServerError(#[from] InternalServerError),
107}
108
109fn check_device_key(
119 expected_user_id: &UserId,
120 expected_device_id: &DeviceId,
121 keys: &DeviceKeys,
122) -> Result<(), InputJsonError> {
123 if keys.keys.keys().any(|key_id| key_id.key_name() != expected_device_id) {
124 return Err(InputJsonError::BadJson(BadJson(
125 "Device keys contains keys for another device".to_owned(),
126 )));
127 }
128
129 if keys.user_id != expected_user_id {
130 return Err(InputJsonError::BadJson(BadJson(
131 "Field `user_id` does not match in device key".to_owned(),
132 )));
133 }
134
135 if keys.device_id != expected_device_id {
136 return Err(InputJsonError::BadJson(BadJson(
137 "Field `device_id` does not match the requester".to_owned(),
138 )));
139 }
140
141 Ok(())
142}
143
144#[allow(
146 clippy::missing_docs_in_private_items,
147 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
148)]
149#[derive(Debug, PartialEq, Clone, Copy)]
150enum ShouldBeFallback {
151 Yes,
152 No,
153}
154
155fn check_onetime_key(
165 should_be_fallback: ShouldBeFallback,
166 otk: &ruma::encryption::SignedKey,
167) -> Result<(), InputJsonError> {
168 if (should_be_fallback == ShouldBeFallback::Yes && !otk.fallback)
169 || (should_be_fallback == ShouldBeFallback::No && otk.fallback)
170 {
171 return Err(InputJsonError::BadJson(BadJson(
172 "One-time key's fallback field does not match".to_owned(),
173 )));
174 }
175
176 Ok(())
177}
178
179async fn handle_new_device_key(
187 transaction: &mut TransactedDb<'_>,
188 session: &SessionInfo,
189 new_keys: &mut DeviceKeys,
190) -> Result<(), UserKeysError> {
191 check_device_key(&session.user_id, &session.device_id, new_keys)?;
192
193 let new_keys_id = new_keys.key_id().map_err(InputJsonError::BadJson)?;
194 verify_signatures(
195 vec![&new_keys.keymap().map_err(InputJsonError::BadJson)?],
196 new_keys,
197 Some(&BTreeMap::from([(session.user_id.clone(), vec![new_keys_id.clone()])])),
199 )?;
200
201 #[expect(
203 clippy::expect_used,
204 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
205 )]
206 let signatures = new_keys
207 .signatures
208 .remove(&session.user_id)
209 .expect("We just checked keys were at least signed by themselves");
210
211 let device_keys_nid =
212 transaction.upsert_device_keys(&session.user_id, &session.device_id, new_keys).await?;
213
214 for (signing_key_id, signature) in signatures {
215 transaction
216 .upsert_local_signature(
217 &session.user_id,
218 if signing_key_id == new_keys_id {
220 DbKeyType::Device
221 } else {
222 DbKeyType::SelfSigning
224 },
225 &signing_key_id,
226 device_keys_nid,
227 Base64::<Standard>::parse(signature)
228 .unrecoverable_error("Failed to parse base64")?
229 .as_bytes(),
230 )
231 .await?;
232 }
233
234 Ok(())
235}
236
237async fn upload_keys_handler(
242 mut state: State<ServerState>,
243 AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
244 upload_keys::v3::Request,
245 >,
246) -> Result<ArminiusResponse<upload_keys::v3::Response>, UserKeysError> {
247 let mut transaction = state.database.begin_transaction().await?;
248
249 let uploaded_device_keys = request.device_keys.is_some();
253
254 let device_keys = match request.device_keys {
255 Some(new_device_keys) => {
256 let mut new_device_keys =
257 new_device_keys.deserialize().map_err(InputJsonError::from)?;
258 handle_new_device_key(&mut transaction, &claims, &mut new_device_keys).await?;
259
260 Some(new_device_keys)
261 }
262 None => transaction
263 .fetch_device_keys_for_users(&BTreeMap::from([(
264 claims.user_id.clone(),
265 vec![claims.device_id.clone()],
266 )]))
267 .await?
268 .remove(&claims.user_id)
269 .and_then(|mut user_keys| user_keys.remove(&claims.device_id))
270 .map(|db_key| db_key.key),
271 };
272
273 if !request.one_time_keys.is_empty() || !request.fallback_keys.is_empty() {
274 let Some(device_keys) = device_keys else {
278 return Err(UserKeysError::MissingDeviceKeys);
279 };
280
281 let keymap = device_keys.keymap().unrecoverable_error("Failed to get keymap")?;
282
283 let required_signers = BTreeMap::from([(
286 claims.user_id.clone(),
287 vec![device_keys.key_id().map_err(InputJsonError::BadJson)?],
288 )]);
289
290 for (key_id, raw_otk) in request.one_time_keys {
291 let one_time_key = raw_otk.deserialize().map_err(InputJsonError::from)?;
292 let OneTimeKey::SignedKey(otk) = &one_time_key else {
293 return Err(InputJsonError::BadJson(BadJson(
294 "Expected one-time keys to be signed keys".to_owned(),
295 ))
296 .into());
297 };
298
299 check_onetime_key(ShouldBeFallback::No, otk)?;
300 verify_signatures(vec![&keymap], otk, Some(&required_signers))?;
301
302 transaction
303 .try_insert_one_time_key(
304 &key_id.algorithm(),
305 key_id.key_name(),
306 &claims.user_id,
307 &claims.device_id,
308 &one_time_key,
309 )
310 .await?;
311 }
312
313 for (key_id, raw_fallback_key) in request.fallback_keys {
314 let fallback_key = raw_fallback_key.deserialize().map_err(InputJsonError::from)?;
315 let OneTimeKey::SignedKey(otk) = &fallback_key else {
316 return Err(InputJsonError::BadJson(BadJson(
317 "Expected fallback keys to be signed keys".to_owned(),
318 ))
319 .into());
320 };
321
322 check_onetime_key(ShouldBeFallback::Yes, otk)?;
323 verify_signatures(vec![&keymap], otk, Some(&required_signers))?;
324
325 transaction
328 .upsert_fallback_key(
329 &key_id.algorithm(),
330 key_id.key_name(),
331 &claims.user_id,
332 &claims.device_id,
333 &fallback_key,
334 )
335 .await?;
336 }
337 }
338
339 let one_time_key_counts =
340 transaction.fetch_one_time_keys_counts(&claims.user_id, &claims.device_id).await?;
341
342 transaction.commit().await?;
343
344 if uploaded_device_keys {
345 state.nats_connection.notify_new_sync_data().await?;
348 }
349
350 Ok(upload_keys::v3::Response::new(one_time_key_counts).into())
351}
352
353async fn get_keys_handler(
358 mut state: State<ServerState>,
359 AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<get_keys::v3::Request>,
360) -> Result<ArminiusResponse<get_keys::v3::Response>, UserKeysError> {
361 let (req_local_keys, req_foreign_keys) = request.device_keys.into_iter().fold(
362 (BTreeMap::new(), BTreeMap::<OwnedServerName, BTreeMap<_, _>>::new()),
363 |(mut local_keys, mut foreign_keys), (user_id, user_keys)| {
364 let server_name = user_id.server_name();
365
366 if server_name == state.config.server_name {
367 local_keys.insert(user_id, user_keys);
368 } else {
369 foreign_keys.entry(server_name.to_owned()).or_default().insert(user_id, user_keys);
370 }
371
372 (local_keys, foreign_keys)
373 },
374 );
375
376 let mut response = get_keys::v3::Response::new();
377
378 {
381 for user_id in req_local_keys.keys().cloned() {
384 response.device_keys.entry(user_id).or_default();
385 }
386
387 let mut local_keys: RawUserKeys = arminius_lib_user_keys::identity_keys::get_local_keys(
388 &mut state.database,
389 Some(&claims.user_id),
390 req_local_keys,
391 )
392 .await?
393 .try_into()?;
394
395 response.device_keys.append(&mut local_keys.device_keys);
396
397 response.master_keys.append(&mut local_keys.master_keys);
398 response.self_signing_keys.append(&mut local_keys.self_signing_keys);
399 response.user_signing_keys.append(&mut local_keys.user_signing_keys);
400 }
401
402 {
403 let timeout = request.timeout.unwrap_or(Duration::from_secs(10));
406
407 let mut tasks = JoinMap::new();
408
409 for (server_name, requested_keys) in req_foreign_keys {
410 let client = FederationNatsClient::new(state.nats_connection.client.clone());
411 tasks.spawn(
412 server_name.clone(),
413 tokio::time::timeout(timeout, async move {
414 client.get_federated_user_keys(server_name, requested_keys, Some(timeout)).await
415 }),
416 );
417 }
418
419 while let Some((server_name, result)) = tasks.join_next().await {
420 let result = result.unrecoverable_error("Failed to join spawned task")?;
421
422 let Ok(federation_response) = result else {
424 response.failures.insert(
425 server_name.to_string(),
426 json!({
427 "reason": FederatedKeyFailureReason::TimedOut.to_string()
428 }),
429 );
430
431 continue;
432 };
433
434 let federation_response =
435 federation_response.unrecoverable_error("Fetching foreign keys failed")?;
436
437 match federation_response {
438 FederatedIdentityKeysResponse::Success(mut federated_keys) => {
439 response.device_keys.append(&mut federated_keys.device_keys);
440 response.master_keys.append(&mut federated_keys.master_keys);
441 response.self_signing_keys.append(&mut federated_keys.self_signing_keys);
442 }
443 FederatedIdentityKeysResponse::Failure(failure) => {
444 response.failures.insert(
445 failure.server_name.to_string(),
446 json!({
447 "reason": failure.reason.to_string()
448 }),
449 );
450 }
451 }
452 }
453 }
454
455 Ok(response.into())
456}
457
458async fn claim_keys_handler(
463 mut state: State<ServerState>,
464 AuthenticatedClientRequest(_, request): AuthenticatedClientRequest<claim_keys::v3::Request>,
465) -> Result<ArminiusResponse<claim_keys::v3::Response>, UserKeysError> {
466 let (req_local_keys, req_foreign_keys): (Vec<_>, Vec<_>) = request
467 .one_time_keys
468 .into_iter()
469 .partition(|(user_id, _)| user_id.server_name() == state.config.server_name);
470
471 let req_local_keys = req_local_keys.into_iter().collect();
472 let req_foreign_keys = req_foreign_keys.into_iter().fold(
473 BTreeMap::new(),
474 |mut keys: BTreeMap<OwnedServerName, BTreeMap<_, _>>, (user_id, user_keys)| {
475 keys.entry(user_id.server_name().to_owned()).or_default().insert(user_id, user_keys);
476 keys
477 },
478 );
479
480 let mut response = claim_keys::v3::Response::new(BTreeMap::new());
481
482 {
485 response.one_time_keys.append(
486 &mut arminius_lib_user_keys::one_time_keys::claim_local_keys(
487 &mut state.database,
488 req_local_keys,
489 )
490 .await?,
491 );
492 }
493
494 {
495 let timeout = request.timeout.unwrap_or(Duration::from_secs(10));
498
499 let mut join_map = JoinMap::new();
500 for (server_name, requested_keys) in req_foreign_keys {
501 let client = FederationNatsClient::new(state.nats_connection.client.clone());
502 join_map.spawn(
503 server_name.clone(),
504 tokio::time::timeout(timeout, async move {
505 client
506 .get_federated_otks(server_name.clone(), requested_keys, Some(timeout))
507 .await
508 }),
509 );
510 }
511
512 while let Some((server_name, result)) = join_map.join_next().await {
513 let result = result.unrecoverable_error("Failed to join spawned task")?;
514
515 let Ok(federation_response) = result else {
517 response.failures.insert(
518 server_name.to_string(),
519 json!({
520 "reason": format!("Server {server_name} failed to respond before the timeout")
521 }),
522 );
523
524 continue;
525 };
526
527 let federation_response =
528 federation_response.unrecoverable_error("Fetching foreign keys failed")?;
529
530 match federation_response {
531 FederatedOTKResponse::Success(mut federated_otks) => {
532 response.one_time_keys.append(&mut federated_otks.one_time_keys);
533 }
534 FederatedOTKResponse::Failure(failure) => {
535 response.failures.insert(
536 failure.server_name.to_string(),
537 json!({
538 "reason": failure.reason
539 }),
540 );
541 }
542 }
543 }
544 }
545
546 Ok(response.into())
547}
548
549#[derive(Debug)]
551pub struct UserKeysComponent;
552
553impl Component for UserKeysComponent {
554 fn router() -> Router<ServerState> {
555 Router::new()
556 .route("/_matrix/client/v3/keys/upload", post(upload_keys_handler))
557 .route("/_matrix/client/v3/keys/query", post(get_keys_handler))
558 .route("/_matrix/client/v3/keys/claim", post(claim_keys_handler))
559 }
560}