arminius_server_client/handlers/
cross_signing.rs1#![allow(
12 clippy::allow_attributes,
13 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."
14)]
15
16use std::collections::BTreeMap;
17
18use arminius_lib_authentication::SessionInfo;
19use arminius_lib_component::{
20 Component, ServerState,
21 error::{
22 BadJson, InputJsonError, InternalServerError, MatrixApiError, ResultExt as _,
23 create_custom_matrix_error,
24 },
25 ruma_requests::ArminiusResponse,
26};
27use arminius_lib_db::{Database as _, TransactedDb};
28use arminius_lib_db_devices::{Device, DeviceQueries as _};
29use arminius_lib_db_keys::UserKeysQueries as _;
30use arminius_lib_db_signatures::LocalSignatureQueries as _;
31use arminius_lib_macros::{FromDbError, FromNatsError};
32use arminius_lib_model::user_keys::DbKeyType;
33use arminius_lib_user_keys::{
34 signatures::{SignatureVerificationError, verify_signatures},
35 traits::SigningKey as _,
36};
37use axum::{Router, extract::State, http::StatusCode, routing::post};
38use ruma::{
39 UserId,
40 api::{
41 client::keys::upload_signing_keys,
42 error::{Error as MatrixError, ErrorKind},
43 },
44 encryption::{CrossSigningKey, KeyUsage},
45 serde::{Base64, base64::Standard},
46};
47
48use crate::client_request::AuthenticatedClientRequest;
49
50#[derive(Debug, thiserror::Error)]
52#[error("Invalid signature: {0}")]
53pub struct SignatureError(String);
54
55impl MatrixApiError for SignatureError {
58 fn create_matrix_error(&self) -> MatrixError {
59 create_custom_matrix_error(
60 StatusCode::BAD_REQUEST,
61 "M_INVALID_SIGNATURE",
62 &self.to_string(),
63 )
64 }
65}
66
67impl From<SignatureVerificationError> for CrossSigningKeysError {
70 fn from(signature_error: SignatureVerificationError) -> Self {
71 match signature_error {
72 SignatureVerificationError::BadJson(bad_json) => {
73 Self::InputJsonError(InputJsonError::BadJson(bad_json))
74 }
75 other_error => Self::SignatureVerification(SignatureError(other_error.to_string())),
76 }
77 }
78}
79
80#[allow(
81 missing_docs,
82 clippy::missing_docs_in_private_items,
83 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
84)]
85#[derive(Debug, thiserror::Error, MatrixApiError, FromDbError, FromNatsError)]
86enum CrossSigningKeysError {
87 #[error(transparent)]
88 InternalServerError(#[from] InternalServerError),
89
90 #[error(transparent)]
91 InputJsonError(#[from] InputJsonError),
92
93 #[error(transparent)]
94 SignatureVerification(#[from] SignatureError),
95
96 #[error("Key ID in use")]
99 #[matrix_api_error(status_code = StatusCode::FORBIDDEN, error_kind = ErrorKind::Forbidden)]
100 SameIdAsDevice,
101
102 #[error("Missing Master key")]
105 #[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::MissingParam)]
106 MissingMasterKey,
107}
108
109fn check_cross_signing_key(
120 expected_user_id: &UserId,
121 expected_usage: &KeyUsage,
122 user_devices: &[Device],
123 key: &CrossSigningKey,
124) -> Result<(), CrossSigningKeysError> {
125 if key.user_id != expected_user_id {
126 return Err(InputJsonError::BadJson(BadJson(
127 "Cross-signing key's `user_id` mismatch".to_owned(),
128 ))
129 .into());
130 }
131
132 if !key.usage.contains(expected_usage) {
133 return Err(InputJsonError::BadJson(BadJson(format!(
134 "Missing {expected_usage} in cross-signing key's `usage` field"
135 )))
136 .into());
137 }
138
139 let key_id = key.key_id().map_err(InputJsonError::BadJson)?;
141 if user_devices.iter().any(|device| device.device_id.as_str() == key_id.as_str()) {
142 return Err(CrossSigningKeysError::SameIdAsDevice);
143 }
144
145 Ok(())
146}
147
148async fn handle_new_master_key(
153 transaction: &mut TransactedDb<'_>,
154 session: &SessionInfo,
155 user_devices: &[Device],
156 key: &mut CrossSigningKey,
157) -> Result<(), CrossSigningKeysError> {
158 check_cross_signing_key(&session.user_id, &KeyUsage::Master, user_devices, key)?;
159
160 if key.signatures.is_empty() {
163 transaction
164 .upsert_cross_signing_key(&session.user_id, DbKeyType::Master, key)
165 .await
166 .unrecoverable_error("Inserting cross-signing key")?;
167
168 return Ok(());
169 }
170
171 if let Some(device_key) = transaction
173 .fetch_device_keys_for_users(&BTreeMap::from([(
174 session.user_id.clone(),
175 vec![session.device_id.clone()],
176 )]))
177 .await?
178 .get(&session.user_id)
179 .and_then(|user_keys| user_keys.get(&session.device_id))
180 {
181 verify_signatures(
182 vec![
183 &device_key
184 .key
185 .keymap()
186 .unrecoverable_error("Failed to get keymap of stored device keys")?,
187 &key.keymap().map_err(InputJsonError::BadJson)?,
188 ],
189 key,
190 None,
191 )?;
192 }
193
194 let signatures = key.signatures.remove(&session.user_id);
195
196 let master_key_id = key.key_id().map_err(InputJsonError::BadJson)?;
197
198 let master_key_nid =
199 transaction.upsert_cross_signing_key(&session.user_id, DbKeyType::Master, key).await?;
200
201 if let Some(signatures) = signatures {
202 for (signing_key_id, signature) in signatures {
203 transaction
204 .upsert_local_signature(
205 &session.user_id,
206 if signing_key_id == master_key_id {
207 DbKeyType::Master
208 } else {
209 DbKeyType::Device
210 },
211 &signing_key_id,
212 master_key_nid,
213 Base64::<Standard>::parse(signature)
214 .unrecoverable_error("Failed to deserialize base64")?
215 .as_bytes(),
216 )
217 .await
218 .unrecoverable_error("Failed to insert signature")?;
219 }
220 }
221
222 Ok(())
223}
224
225#[expect(
228 clippy::too_many_lines,
229 reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
230)]
231async fn upload_signing_keys_handler(
232 mut state: State<ServerState>,
233 AuthenticatedClientRequest(claims, request): AuthenticatedClientRequest<
234 upload_signing_keys::v3::Request,
235 >,
236) -> Result<ArminiusResponse<upload_signing_keys::v3::Response>, CrossSigningKeysError> {
237 let mut transaction = state.database.begin_transaction().await?;
238
239 let uploaded_cross_signing_keys = request.master_key.is_some()
243 || request.self_signing_key.is_some()
244 || request.user_signing_key.is_some();
245
246 let user_devices = transaction.fetch_devices_for_user(&claims.user_id).await?;
249
250 let master_key = match request.master_key {
251 Some(new_master_key) => {
252 let mut new_master_key = new_master_key.deserialize().map_err(InputJsonError::from)?;
253
254 handle_new_master_key(&mut transaction, &claims, &user_devices, &mut new_master_key)
255 .await?;
256
257 new_master_key
258 }
259 None => transaction
260 .fetch_single_cross_signing_key_for_user(&claims.user_id, DbKeyType::Master)
261 .await?
262 .ok_or(CrossSigningKeysError::MissingMasterKey)?,
263 };
264
265 let master_key_id =
267 master_key.key_id().unrecoverable_error("Failed to get key ID from master key")?;
268 let keymap = master_key.keymap().unrecoverable_error("Failed to get keymap")?;
269 let required_signers = BTreeMap::from([(claims.user_id.clone(), vec![master_key_id.clone()])]);
270
271 if let Some(self_signing_key) = request.self_signing_key {
272 let mut self_signing_key = self_signing_key.deserialize().map_err(InputJsonError::from)?;
273
274 check_cross_signing_key(
277 &claims.user_id,
278 &KeyUsage::SelfSigning,
279 &user_devices,
280 &self_signing_key,
281 )?;
282
283 verify_signatures(vec![&keymap], &self_signing_key, Some(&required_signers))?;
284
285 let signatures = self_signing_key
286 .signatures
287 .remove(&claims.user_id)
288 .unrecoverable_error("Self-signing keys MUST be signed at least by themselves")?;
289
290 let self_signing_key_id = self_signing_key.key_id().map_err(InputJsonError::BadJson)?;
291 let self_signing_key_nid = transaction
292 .upsert_cross_signing_key(&claims.user_id, DbKeyType::SelfSigning, &self_signing_key)
293 .await?;
294
295 for (signing_key_id, signature) in signatures {
296 let signing_key_type = match &signing_key_id {
299 key_id if *key_id == master_key_id => DbKeyType::Master,
300 key_id if key_id == &self_signing_key_id => DbKeyType::SelfSigning,
301 other_key_id => {
302 tracing::warn!("Ignoring signature on user-signing key from {other_key_id}");
303 continue;
304 }
305 };
306
307 transaction
308 .upsert_local_signature(
309 &claims.user_id,
310 signing_key_type,
311 &signing_key_id,
312 self_signing_key_nid,
313 Base64::<Standard>::parse(signature)
314 .unrecoverable_error("Failed to parse base64")?
315 .as_bytes(),
316 )
317 .await?;
318 }
319 }
320
321 if let Some(user_signing_key) = request.user_signing_key {
322 let mut user_signing_key = user_signing_key.deserialize().map_err(InputJsonError::from)?;
323
324 check_cross_signing_key(
327 &claims.user_id,
328 &KeyUsage::UserSigning,
329 &user_devices,
330 &user_signing_key,
331 )?;
332
333 verify_signatures(vec![&keymap], &user_signing_key, Some(&required_signers))?;
334
335 let signatures = user_signing_key
336 .signatures
337 .remove(&claims.user_id)
338 .unrecoverable_error("User signing keys MUST be signed at least by the Master key")?;
339
340 let user_signing_key_id = user_signing_key.key_id().map_err(InputJsonError::BadJson)?;
341 let user_signing_key_nid = transaction
342 .upsert_cross_signing_key(&claims.user_id, DbKeyType::UserSigning, &user_signing_key)
343 .await?;
344
345 for (signing_key_id, signature) in signatures {
346 let signing_key_type = match &signing_key_id {
349 key_id if *key_id == master_key_id => DbKeyType::Master,
350 key_id if key_id == &user_signing_key_id => DbKeyType::UserSigning,
351 other_key_id => {
352 tracing::warn!("Ignoring signature on user-signing key from {other_key_id}");
353 continue;
354 }
355 };
356
357 transaction
358 .upsert_local_signature(
359 &claims.user_id,
360 signing_key_type,
361 &signing_key_id,
362 user_signing_key_nid,
363 Base64::<Standard>::parse(signature)
364 .unrecoverable_error("Failed to parse base64")?
365 .as_bytes(),
366 )
367 .await?;
368 }
369 }
370
371 transaction.commit().await?;
372
373 if uploaded_cross_signing_keys {
374 state.nats_connection.notify_new_sync_data().await?;
377 }
378
379 Ok(upload_signing_keys::v3::Response::new().into())
380}
381
382#[derive(Debug)]
384pub struct CrossSigningKeysComponent;
385
386impl Component for CrossSigningKeysComponent {
387 fn router() -> Router<ServerState> {
388 Router::new().route(
389 "/_matrix/client/v3/keys/device_signing/upload",
390 post(upload_signing_keys_handler),
391 )
392 }
393}