1use std::collections::{BTreeMap, HashMap, HashSet};
5
6use base64::engine::general_purpose::STANDARD_NO_PAD;
7use base64::Engine;
8use ed25519_dalek::{Signature, VerifyingKey};
9use rusqlite::Connection;
10
11use crate::error::MatrixError;
12use crate::keys::CrossSigningUsage;
13use crate::store::{Membership, TxnDedupEntry};
14
15pub const SEND_TO_DEVICE_MAX_TARGETS: usize = 1000;
18
19pub const SEND_TO_DEVICE_MAX_CONTENT_BYTES: usize = 64 * 1024;
21
22pub const BACKUP_ALGORITHM: &str = "m.megolm_backup.v1.aes-hmac-sha2";
26
27
28pub fn peers_sharing_a_room_with(conn: &Connection, user_id: i64) -> rusqlite::Result<HashSet<i64>> {
42 let mut ids = HashSet::new();
43 ids.insert(user_id);
44 for room_id in crate::store::rooms_for_user(conn, user_id, Some(Membership::Join))? {
45 for member in crate::store::room_members(conn, &room_id, None)? {
46 if matches!(member.membership, Membership::Join | Membership::Invite) {
47 ids.insert(member.user_id);
48 }
49 }
50 }
51 Ok(ids)
52}
53
54
55pub struct DeviceListDelta {
62 pub changed: Vec<String>,
67 pub left: Vec<String>,
71}
72
73
74pub fn shared_room_transitions(conn: &Connection, caller_user_id: i64, from_exclusive: i64, to_inclusive: i64) -> rusqlite::Result<(Vec<i64>, Vec<i64>)> {
90 let mut entered = Vec::new();
91 let mut departed = Vec::new();
92 let Some(caller_mxid) = crate::store::mxid_of(conn, caller_user_id)? else {
93 return Ok((entered, departed));
94 };
95 let in_room = |membership: Membership| matches!(membership, Membership::Join | Membership::Invite);
96
97 let caller_rooms = crate::store::rooms_for_user(conn, caller_user_id, None)?;
102 for room_id in crate::store::rooms_with_member_events_in_window(conn, &caller_rooms, from_exclusive, to_inclusive)? {
103 let Some(room) = crate::store::get_room(conn, &room_id)? else { continue };
104 if !room.is_encrypted {
105 continue;
106 }
107 let Some(own) = crate::store::room_member(conn, &room_id, caller_user_id)? else { continue };
108 let was_joined = crate::store::membership_at(conn, &room_id, &caller_mxid, from_exclusive)? == Some(Membership::Join);
109
110 match own.membership {
111 Membership::Join if was_joined => {
112 for mxid in crate::store::member_state_keys_in_window(conn, &room_id, from_exclusive, to_inclusive)? {
113 let Some(user_id) = crate::store::user_id_of(conn, &mxid)? else { continue };
114 if user_id == caller_user_id {
115 continue;
116 }
117 let now_in = crate::store::room_member(conn, &room_id, user_id)?.is_some_and(|m| in_room(m.membership));
118 let before_in = crate::store::membership_at(conn, &room_id, &mxid, from_exclusive)?.is_some_and(in_room);
119 if now_in && !before_in {
120 entered.push(user_id);
121 }
122 }
123 }
124 Membership::Join => {
125 for member in crate::store::room_members(conn, &room_id, None)? {
126 if member.user_id != caller_user_id && in_room(member.membership) {
127 entered.push(member.user_id);
128 }
129 }
130 }
131 Membership::Leave | Membership::Ban if was_joined => {
132 for member in crate::store::room_members(conn, &room_id, None)? {
133 if member.user_id != caller_user_id && in_room(member.membership) {
134 departed.push(member.user_id);
135 }
136 }
137 }
138 Membership::Leave | Membership::Ban | Membership::Invite => {}
139 }
140 }
141 Ok((entered, departed))
142}
143
144
145pub fn sorted_mxids(conn: &Connection, user_ids: &HashSet<i64>) -> rusqlite::Result<Vec<String>> {
148 let mut mxids = Vec::with_capacity(user_ids.len());
149 for &user_id in user_ids {
150 if let Some(mxid) = crate::store::mxid_of(conn, user_id)? {
151 mxids.push(mxid);
152 }
153 }
154 mxids.sort();
155 mxids.dedup();
156 Ok(mxids)
157}
158
159
160pub fn device_list_delta(conn: &Connection, caller_user_id: i64, from_exclusive: i64, to_inclusive: i64) -> rusqlite::Result<DeviceListDelta> {
165 let visible = peers_sharing_a_room_with(conn, caller_user_id)?;
166 let (entered, departed) = shared_room_transitions(conn, caller_user_id, from_exclusive, to_inclusive)?;
167
168 let mut changed_ids: HashSet<i64> = HashSet::new();
169 for user_id in crate::keys::device_list_changes_between(conn, from_exclusive, to_inclusive)? {
170 if visible.contains(&user_id) {
171 changed_ids.insert(user_id);
172 }
173 }
174 changed_ids.extend(entered.into_iter().filter(|user_id| visible.contains(user_id)));
175
176 let caller_rooms = crate::store::rooms_for_user(conn, caller_user_id, None)?;
177 let mut left_ids: HashSet<i64> = HashSet::new();
178 for user_id in crate::store::user_ids_with_leave_transition_in_rooms(conn, &caller_rooms, from_exclusive, to_inclusive)? {
179 if !visible.contains(&user_id) {
180 left_ids.insert(user_id);
181 }
182 }
183 left_ids.extend(departed.into_iter().filter(|user_id| !visible.contains(user_id)));
184
185 Ok(DeviceListDelta { changed: sorted_mxids(conn, &changed_ids)?, left: sorted_mxids(conn, &left_ids)? })
186}
187
188
189#[derive(serde::Deserialize, Default)]
194pub struct KeysUploadRequest {
195 #[serde(default)]
196 pub device_keys: Option<serde_json::Value>,
197 #[serde(default)]
198 pub one_time_keys: Option<BTreeMap<String, serde_json::Value>>,
199 #[serde(default)]
200 pub fallback_keys: Option<BTreeMap<String, serde_json::Value>>,
201}
202
203
204pub fn check_device_keys_ownership(device_keys: &serde_json::Value, caller_mxid: &str, caller_device_id: &str) -> Result<(), MatrixError> {
207 let user_id = device_keys.get("user_id").and_then(|v| v.as_str());
208 let device_id = device_keys.get("device_id").and_then(|v| v.as_str());
209 if user_id != Some(caller_mxid) || device_id != Some(caller_device_id) {
210 return Err(MatrixError::invalid_param("device_keys.user_id/device_id must name the authenticated caller"));
211 }
212 Ok(())
213}
214
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum DeviceKeysUploadAction {
218 Insert,
220 Noop,
222 Reset,
225}
226
227
228pub fn decide_device_keys_upload(existing_keys_json: Option<&str>, new_keys_value: &serde_json::Value) -> Result<DeviceKeysUploadAction, MatrixError> {
242 let Some(existing_keys_json) = existing_keys_json else {
243 return Ok(DeviceKeysUploadAction::Insert);
244 };
245 let existing_keys: serde_json::Value = serde_json::from_str(existing_keys_json)?;
246 if &existing_keys == new_keys_value {
247 Ok(DeviceKeysUploadAction::Noop)
248 } else {
249 Ok(DeviceKeysUploadAction::Reset)
250 }
251}
252
253
254pub fn split_algorithm_key_id(full_id: &str) -> Result<(&str, &str), MatrixError> {
257 let (algorithm, key_id) = full_id
258 .split_once(':')
259 .ok_or_else(|| MatrixError::invalid_param(format!("malformed one-time key id: {full_id}")))?;
260 if algorithm.is_empty() || key_id.is_empty() {
261 return Err(MatrixError::invalid_param(format!("malformed one-time key id: {full_id}")));
262 }
263 Ok((algorithm, key_id))
264}
265
266
267pub struct KeysUploadOutcome {
268 pub otk_counts: HashMap<String, i64>,
269 pub device_keys_changed: bool,
270}
271
272
273pub fn apply_keys_upload(
280 conn: &mut Connection,
281 caller_user_id: i64,
282 caller_mxid: &str,
283 caller_device_id: &str,
284 request: &KeysUploadRequest,
285 now: &str,
286) -> Result<KeysUploadOutcome, MatrixError> {
287 let mut device_keys_changed = false;
288
289 if let Some(device_keys) = &request.device_keys {
290 check_device_keys_ownership(device_keys, caller_mxid, caller_device_id)?;
291 let new_keys_value = device_keys.get("keys").cloned().unwrap_or(serde_json::Value::Null);
292 let existing = crate::keys::device_keys_for(conn, &[caller_user_id])?
293 .into_iter()
294 .find(|d| d.device_id == caller_device_id);
295 let action = decide_device_keys_upload(existing.as_ref().map(|d| d.keys.as_str()), &new_keys_value)?;
296 if matches!(action, DeviceKeysUploadAction::Insert | DeviceKeysUploadAction::Reset) {
297 if action == DeviceKeysUploadAction::Reset {
298 crate::keys::clear_device_one_time_material(conn, caller_user_id, caller_device_id)?;
299 }
300 let algorithms_json = device_keys.get("algorithms").cloned().unwrap_or(serde_json::json!([])).to_string();
301 let keys_json = new_keys_value.to_string();
302 let signatures_json = device_keys.get("signatures").cloned().unwrap_or(serde_json::json!({})).to_string();
303 crate::keys::upsert_device_keys(conn, caller_user_id, caller_device_id, &algorithms_json, &keys_json, &signatures_json, now)?;
304 device_keys_changed = true;
305 }
306 }
307
308 if let Some(one_time_keys) = &request.one_time_keys {
309 let mut batch = Vec::with_capacity(one_time_keys.len());
310 for (full_id, value) in one_time_keys {
311 let (algorithm, _) = split_algorithm_key_id(full_id)?;
312 batch.push((full_id.clone(), algorithm.to_string(), value.to_string()));
313 }
314 crate::keys::add_one_time_keys(conn, caller_user_id, caller_device_id, &batch)?;
315 }
316
317 if let Some(fallback_keys) = &request.fallback_keys {
318 for (full_id, value) in fallback_keys {
319 let (algorithm, _) = split_algorithm_key_id(full_id)?;
320 crate::keys::upsert_fallback_key(conn, caller_user_id, caller_device_id, algorithm, full_id, &value.to_string(), now)?;
321 }
322 }
323
324 let otk_counts = crate::keys::count_one_time_keys(conn, caller_user_id, caller_device_id)?;
325 Ok(KeysUploadOutcome { otk_counts, device_keys_changed })
326}
327
328
329#[derive(serde::Deserialize, Default)]
339pub struct KeysQueryRequest {
340 #[serde(default)]
341 pub device_keys: BTreeMap<String, Vec<String>>,
342}
343
344
345pub fn build_keys_query_response(
355 conn: &Connection,
356 caller_user_id: i64,
357 caller_mxid: &str,
358 requested: &BTreeMap<String, Vec<String>>,
359) -> Result<serde_json::Value, MatrixError> {
360 let visible = peers_sharing_a_room_with(conn, caller_user_id)?;
361 build_keys_query_visible(conn, &visible, Some((caller_user_id, caller_mxid)), requested)
362}
363
364pub fn build_keys_query_visible(
367 conn: &Connection,
368 visible: &HashSet<i64>,
369 caller: Option<(i64, &str)>,
370 requested: &BTreeMap<String, Vec<String>>,
371) -> Result<serde_json::Value, MatrixError> {
372
373 let mut device_keys_out = serde_json::Map::new();
374 let mut master_keys_out = serde_json::Map::new();
375 let mut self_signing_keys_out = serde_json::Map::new();
376
377 for (mxid, requested_device_ids) in requested {
378 let allowed_user_id = crate::store::user_id_of(conn, mxid)?.filter(|uid| visible.contains(uid));
379 let Some(target_user_id) = allowed_user_id else {
380 device_keys_out.insert(mxid.clone(), serde_json::json!({}));
381 continue;
382 };
383
384 let mut devices_out = serde_json::Map::new();
385 for device_keys in crate::keys::device_keys_for(conn, &[target_user_id])? {
386 if !requested_device_ids.is_empty() && !requested_device_ids.contains(&device_keys.device_id) {
387 continue;
388 }
389 let algorithms: serde_json::Value = serde_json::from_str(&device_keys.algorithms)?;
390 let keys: serde_json::Value = serde_json::from_str(&device_keys.keys)?;
391 let signatures: serde_json::Value = serde_json::from_str(&device_keys.signatures)?;
392 devices_out.insert(
393 device_keys.device_id.clone(),
394 serde_json::json!({
395 "user_id": mxid,
396 "device_id": device_keys.device_id,
397 "algorithms": algorithms,
398 "keys": keys,
399 "signatures": signatures,
400 }),
401 );
402 }
403 device_keys_out.insert(mxid.clone(), serde_json::Value::Object(devices_out));
404
405 for cross_signing_key in crate::keys::cross_signing_keys_for(conn, &[target_user_id])? {
406 let value: serde_json::Value = serde_json::from_str(&cross_signing_key.key_json)?;
407 match cross_signing_key.usage {
408 CrossSigningUsage::Master => {
409 master_keys_out.insert(mxid.clone(), value);
410 }
411 CrossSigningUsage::SelfSigning => {
412 self_signing_keys_out.insert(mxid.clone(), value);
413 }
414 CrossSigningUsage::UserSigning => {} }
416 }
417 }
418
419 let mut user_signing_keys_out = serde_json::Map::new();
420 if let Some((caller_user_id, caller_mxid)) = caller {
421 if let Some(row) = crate::keys::cross_signing_key_for(conn, caller_user_id, CrossSigningUsage::UserSigning)? {
422 user_signing_keys_out.insert(caller_mxid.to_string(), serde_json::from_str(&row.key_json)?);
423 }
424 }
425
426 Ok(serde_json::json!({
427 "device_keys": device_keys_out,
428 "master_keys": master_keys_out,
429 "self_signing_keys": self_signing_keys_out,
430 "user_signing_keys": user_signing_keys_out,
431 "failures": {},
432 }))
433}
434
435
436#[derive(serde::Deserialize, Default)]
441pub struct KeysClaimRequest {
442 #[serde(default)]
443 pub one_time_keys: BTreeMap<String, BTreeMap<String, String>>,
444}
445
446
447pub fn count_claim_targets(requested: &BTreeMap<String, BTreeMap<String, String>>) -> usize {
452 requested.values().map(BTreeMap::len).sum()
453}
454
455
456pub fn build_keys_claim_response(conn: &mut Connection, caller_user_id: i64, requested: &BTreeMap<String, BTreeMap<String, String>>) -> Result<serde_json::Value, MatrixError> {
462 let visible = peers_sharing_a_room_with(conn, caller_user_id)?;
463 build_keys_claim_visible(conn, &visible, requested)
464}
465
466pub fn build_keys_claim_visible(conn: &mut Connection, visible: &HashSet<i64>, requested: &BTreeMap<String, BTreeMap<String, String>>) -> Result<serde_json::Value, MatrixError> {
468 let mut out = serde_json::Map::new();
469
470 for (mxid, per_device) in requested {
471 let Some(target_user_id) = crate::store::user_id_of(conn, mxid)?.filter(|uid| visible.contains(uid)) else {
472 continue;
473 };
474 let mut devices_out = serde_json::Map::new();
475 for (device_id, algorithm) in per_device {
476 let Some((key_id, key_json)) = crate::keys::claim_one_time_key(conn, target_user_id, device_id, algorithm)? else {
477 continue;
478 };
479 let value: serde_json::Value = serde_json::from_str(&key_json)?;
480 devices_out.insert(device_id.clone(), serde_json::json!({ key_id: value }));
481 }
482 if !devices_out.is_empty() {
483 out.insert(mxid.clone(), serde_json::Value::Object(devices_out));
484 }
485 }
486
487 Ok(serde_json::json!({ "one_time_keys": out, "failures": {} }))
488}
489
490
491#[derive(serde::Deserialize, Default)]
496pub struct KeysChangesQuery {
497 pub from: Option<String>,
498 pub to: Option<String>,
499}
500
501
502pub fn build_keys_changes_response(conn: &Connection, caller_user_id: i64, from_exclusive: i64, to_inclusive: i64) -> Result<serde_json::Value, MatrixError> {
508 let delta = device_list_delta(conn, caller_user_id, from_exclusive, to_inclusive)?;
509 Ok(serde_json::json!({ "changed": delta.changed, "left": delta.left }))
510}
511
512
513#[derive(serde::Deserialize, Default)]
518pub struct DeviceSigningUploadRequest {
519 #[serde(default)]
520 pub master_key: Option<serde_json::Value>,
521 #[serde(default)]
522 pub self_signing_key: Option<serde_json::Value>,
523 #[serde(default)]
524 pub user_signing_key: Option<serde_json::Value>,
525}
526
527
528pub fn master_verifying_key(master_key_object: &serde_json::Value) -> Result<(String, VerifyingKey), MatrixError> {
532 let keys = master_key_object
533 .get("keys")
534 .and_then(|v| v.as_object())
535 .ok_or_else(|| MatrixError::invalid_param("master_key.keys missing"))?;
536 let (full_key_id, value) = keys
537 .iter()
538 .find(|(k, _)| k.starts_with("ed25519:"))
539 .ok_or_else(|| MatrixError::invalid_param("master_key has no ed25519 key"))?;
540 let key_id = full_key_id.trim_start_matches("ed25519:").to_string();
541 let b64 = value.as_str().ok_or_else(|| MatrixError::invalid_param("master_key.keys value must be a string"))?;
542 let bytes = STANDARD_NO_PAD.decode(b64).map_err(|_| MatrixError::invalid_param("master_key public key is not valid base64"))?;
543 let array: [u8; 32] = bytes.try_into().map_err(|_| MatrixError::invalid_param("master_key public key must be 32 bytes"))?;
544 let verifying_key = VerifyingKey::from_bytes(&array).map_err(|_| MatrixError::invalid_param("master_key is not a valid ed25519 key"))?;
545 Ok((key_id, verifying_key))
546}
547
548
549pub fn canonical_json_without_signatures(value: &serde_json::Value) -> String {
560 let mut stripped = value.clone();
561 if let Some(obj) = stripped.as_object_mut() {
562 obj.remove("signatures");
563 obj.remove("unsigned");
564 }
565 stripped.to_string()
566}
567
568
569pub fn verify_signed_by_master(target_key_object: &serde_json::Value, caller_mxid: &str, master_key_object: &serde_json::Value) -> Result<(), MatrixError> {
575 let (master_key_id, verifying_key) = master_verifying_key(master_key_object)?;
576 let signature_b64 = target_key_object
577 .get("signatures")
578 .and_then(|s| s.get(caller_mxid))
579 .and_then(|by_user| by_user.get(format!("ed25519:{master_key_id}").as_str()))
580 .and_then(|v| v.as_str())
581 .ok_or_else(|| MatrixError::invalid_param("missing signature by the master key"))?;
582 let sig_bytes = STANDARD_NO_PAD.decode(signature_b64).map_err(|_| MatrixError::invalid_param("signature is not valid base64"))?;
583 let sig_array: [u8; 64] = sig_bytes.try_into().map_err(|_| MatrixError::invalid_param("signature must be 64 bytes"))?;
584 let signature = Signature::from_bytes(&sig_array);
585 let message = canonical_json_without_signatures(target_key_object);
586 verifying_key
587 .verify_strict(message.as_bytes(), &signature)
588 .map_err(|_| MatrixError::invalid_param("signature does not verify against the master key"))
589}
590
591
592pub fn validate_cross_signing_key_object(value: &serde_json::Value, caller_mxid: &str, expected_usage: &str) -> Result<(), MatrixError> {
595 let obj = value.as_object().ok_or_else(|| MatrixError::invalid_param("cross-signing key must be an object"))?;
596 if obj.get("user_id").and_then(|v| v.as_str()) != Some(caller_mxid) {
597 return Err(MatrixError::invalid_param("cross-signing key user_id must be the caller"));
598 }
599 let usage = obj.get("usage").and_then(|v| v.as_array()).ok_or_else(|| MatrixError::invalid_param("cross-signing key missing usage"))?;
600 if usage.len() != 1 || usage[0].as_str() != Some(expected_usage) {
601 return Err(MatrixError::invalid_param(format!("cross-signing key usage must be exactly [\"{expected_usage}\"]")));
602 }
603 if obj.get("keys").and_then(|v| v.as_object()).is_none_or(|k| k.is_empty()) {
604 return Err(MatrixError::invalid_param("cross-signing key missing keys"));
605 }
606 Ok(())
607}
608
609
610pub fn apply_device_signing_upload(conn: &mut Connection, caller_user_id: i64, caller_mxid: &str, request: &DeviceSigningUploadRequest, now: &str) -> Result<bool, MatrixError> {
619 if let Some(master) = &request.master_key {
620 validate_cross_signing_key_object(master, caller_mxid, "master")?;
621 }
622 let master_for_verification: Option<serde_json::Value> = match &request.master_key {
623 Some(master) => Some(master.clone()),
624 None => crate::keys::cross_signing_key_for(conn, caller_user_id, CrossSigningUsage::Master)?
625 .map(|row| serde_json::from_str(&row.key_json))
626 .transpose()?,
627 };
628
629 if let Some(self_signing) = &request.self_signing_key {
630 validate_cross_signing_key_object(self_signing, caller_mxid, "self_signing")?;
631 let master = master_for_verification
632 .as_ref()
633 .ok_or_else(|| MatrixError::invalid_param("no master key on file to verify self_signing_key against"))?;
634 verify_signed_by_master(self_signing, caller_mxid, master)?;
635 }
636 if let Some(user_signing) = &request.user_signing_key {
637 validate_cross_signing_key_object(user_signing, caller_mxid, "user_signing")?;
638 let master = master_for_verification
639 .as_ref()
640 .ok_or_else(|| MatrixError::invalid_param("no master key on file to verify user_signing_key against"))?;
641 verify_signed_by_master(user_signing, caller_mxid, master)?;
642 }
643
644 let mut wrote = false;
645 if let Some(master) = &request.master_key {
646 crate::keys::upsert_cross_signing_key(conn, caller_user_id, CrossSigningUsage::Master, &master.to_string(), now)?;
647 wrote = true;
648 }
649 if let Some(self_signing) = &request.self_signing_key {
650 crate::keys::upsert_cross_signing_key(conn, caller_user_id, CrossSigningUsage::SelfSigning, &self_signing.to_string(), now)?;
651 wrote = true;
652 }
653 if let Some(user_signing) = &request.user_signing_key {
654 crate::keys::upsert_cross_signing_key(conn, caller_user_id, CrossSigningUsage::UserSigning, &user_signing.to_string(), now)?;
655 wrote = true;
656 }
657 Ok(wrote)
658}
659
660
661pub fn master_key_names_local_id(conn: &Connection, user_id: i64, local_id: &str) -> Result<bool, MatrixError> {
670 let Some(row) = crate::keys::cross_signing_key_for(conn, user_id, CrossSigningUsage::Master)? else {
671 return Ok(false);
672 };
673 let value: serde_json::Value = serde_json::from_str(&row.key_json)?;
674 let Some(keys) = value.get("keys").and_then(|k| k.as_object()) else {
675 return Ok(false);
676 };
677 Ok(keys.keys().any(|k| k.trim_start_matches("ed25519:") == local_id))
678}
679
680
681fn cross_signing_key_names_local_id(conn: &Connection, user_id: i64, local_id: &str) -> Result<bool, MatrixError> {
685 for usage in [CrossSigningUsage::Master, CrossSigningUsage::SelfSigning, CrossSigningUsage::UserSigning] {
686 let Some(row) = crate::keys::cross_signing_key_for(conn, user_id, usage)? else {
687 continue;
688 };
689 let value: serde_json::Value = serde_json::from_str(&row.key_json)?;
690 if let Some(keys) = value.get("keys").and_then(|k| k.as_object()) {
691 if keys.keys().any(|k| k.trim_start_matches("ed25519:") == local_id) {
692 return Ok(true);
693 }
694 }
695 }
696 Ok(false)
697}
698
699
700pub fn signature_target_is_authorized(conn: &Connection, caller_mxid: &str, target_mxid: &str, target_user_id: i64, target_key_id: &str) -> Result<bool, MatrixError> {
710 if target_mxid == caller_mxid {
711 Ok(crate::keys::get_device(conn, target_user_id, target_key_id)?.is_some() || cross_signing_key_names_local_id(conn, target_user_id, target_key_id)?)
712 } else {
713 master_key_names_local_id(conn, target_user_id, target_key_id)
714 }
715}
716
717
718pub fn apply_signatures_upload(conn: &mut Connection, caller_user_id: i64, caller_mxid: &str, body: &serde_json::Map<String, serde_json::Value>, now: &str) -> Result<serde_json::Value, MatrixError> {
725 let mut failures = serde_json::Map::new();
726 for (target_mxid, per_key) in body {
727 let Some(per_key_obj) = per_key.as_object() else { continue };
728 let mut user_failures = serde_json::Map::new();
729 let target_user_id = crate::store::user_id_of(conn, target_mxid)?;
730
731 for (target_key_id, signed_value) in per_key_obj {
732 let authorized = match target_user_id {
733 Some(target_user_id) => signature_target_is_authorized(conn, caller_mxid, target_mxid, target_user_id, target_key_id)?,
734 None => false,
735 };
736 if !authorized {
737 user_failures.insert(
738 target_key_id.clone(),
739 serde_json::json!({ "errcode": "M_INVALID_PARAM", "error": "signature target not permitted" }),
740 );
741 continue;
742 }
743 let target_user_id = target_user_id.ok_or_else(MatrixError::internal)?;
744 crate::keys::add_signatures(conn, &[(caller_user_id, target_user_id, target_key_id.clone(), signed_value.to_string(), now.to_string())])?;
745 }
746 if !user_failures.is_empty() {
747 failures.insert(target_mxid.clone(), serde_json::Value::Object(user_failures));
748 }
749 }
750 Ok(serde_json::Value::Object(failures))
751}
752
753
754#[derive(serde::Deserialize)]
759pub struct SendToDeviceRequest {
760 pub messages: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
761}
762
763
764pub fn expand_send_to_device_targets(conn: &Connection, sender_user_id: i64, messages: &BTreeMap<String, BTreeMap<String, serde_json::Value>>) -> Result<Vec<(i64, String, String)>, MatrixError> {
776 let visible = peers_sharing_a_room_with(conn, sender_user_id)?;
777 let mut targets = Vec::new();
778
779 for (mxid, per_device) in messages {
780 let Some(recipient_user_id) = crate::store::user_id_of(conn, mxid)?.filter(|uid| visible.contains(uid)) else {
781 continue;
782 };
783
784 let mut per_recipient: HashMap<String, String> = HashMap::new();
785 if let Some(wildcard_content) = per_device.get("*") {
786 let content_str = wildcard_content.to_string();
787 if content_str.len() > SEND_TO_DEVICE_MAX_CONTENT_BYTES {
788 return Err(MatrixError::bad_json("to-device content too large"));
789 }
790 for device in crate::keys::list_devices(conn, recipient_user_id)? {
791 per_recipient.insert(device.device_id, content_str.clone());
792 }
793 }
794 for (device_selector, content) in per_device {
795 if device_selector == "*" {
796 continue;
797 }
798 let content_str = content.to_string();
799 if content_str.len() > SEND_TO_DEVICE_MAX_CONTENT_BYTES {
800 return Err(MatrixError::bad_json("to-device content too large"));
801 }
802 per_recipient.insert(device_selector.clone(), content_str);
803 }
804
805 for (device_id, content_str) in per_recipient {
806 targets.push((recipient_user_id, device_id, content_str));
807 }
808 }
809
810 if targets.len() > SEND_TO_DEVICE_MAX_TARGETS {
811 return Err(MatrixError::invalid_param("too many target devices in one call"));
812 }
813 Ok(targets)
814}
815
816
817#[derive(Debug, Clone, PartialEq, Eq)]
818pub enum SendToDeviceOutcome {
819 New(HashSet<i64>),
820 AlreadySent,
821}
822
823
824pub fn apply_send_to_device(
829 conn: &mut Connection,
830 sender_user_id: i64,
831 sender_device_id: &str,
832 event_type: &str,
833 txn_id: &str,
834 messages: &BTreeMap<String, BTreeMap<String, serde_json::Value>>,
835 now: &str,
836) -> Result<SendToDeviceOutcome, MatrixError> {
837 if let TxnDedupEntry::Seen(_) = crate::store::txn_dedup_lookup(conn, sender_user_id, sender_device_id, txn_id)? {
838 return Ok(SendToDeviceOutcome::AlreadySent);
839 }
840 let targets = expand_send_to_device_targets(conn, sender_user_id, messages)?;
841 let wake_ids: HashSet<i64> = targets.iter().map(|(uid, ..)| *uid).collect();
842 let rows: Vec<(i64, String, String, String)> = targets.into_iter().map(|(uid, dev, content)| (uid, dev, event_type.to_string(), content)).collect();
843
844 match crate::keys::enqueue_to_device_deduped(conn, sender_user_id, sender_device_id, txn_id, &rows, now)? {
845 crate::keys::ToDeviceDedupOutcome::New => Ok(SendToDeviceOutcome::New(wake_ids)),
846 crate::keys::ToDeviceDedupOutcome::AlreadySent => Ok(SendToDeviceOutcome::AlreadySent),
847 }
848}
849
850
851#[derive(serde::Deserialize, Default)]
856pub struct PutDeviceRequest {
857 #[serde(default)]
858 pub display_name: Option<String>,
859}
860
861
862#[derive(serde::Deserialize)]
863pub struct DeleteDevicesRequest {
864 pub devices: Vec<String>,
865}
866
867
868pub fn device_to_json(device: &crate::keys::Device) -> serde_json::Value {
869 let last_seen_ts = chrono::DateTime::parse_from_rfc3339(&device.last_seen_at).ok().map(|dt| dt.timestamp_millis());
870 serde_json::json!({
871 "device_id": device.device_id,
872 "display_name": device.display_name,
873 "last_seen_ts": last_seen_ts,
874 })
875}
876
877
878#[derive(serde::Deserialize)]
883pub struct BackupVersionCreateRequest {
884 pub algorithm: String,
885 pub auth_data: serde_json::Value,
886}
887
888
889#[derive(serde::Deserialize)]
890pub struct BackupVersionUpdateRequest {
891 #[serde(default)]
892 pub algorithm: Option<String>,
893 pub auth_data: serde_json::Value,
894}
895
896
897pub fn validate_backup_algorithm(algorithm: &str) -> Result<(), MatrixError> {
901 if algorithm == BACKUP_ALGORITHM {
902 Ok(())
903 } else {
904 Err(MatrixError::invalid_param(format!("unsupported key-backup algorithm: {algorithm}")))
905 }
906}
907
908
909pub fn backup_version_to_response(row: &crate::keys::KeyBackupVersion, count: i64) -> Result<serde_json::Value, MatrixError> {
910 Ok(serde_json::json!({
911 "version": row.version.to_string(),
912 "algorithm": row.algorithm,
913 "auth_data": serde_json::from_str::<serde_json::Value>(&row.auth_data)?,
914 "etag": row.etag.to_string(),
915 "count": count,
916 }))
917}
918
919
920#[derive(serde::Deserialize, Default)]
925pub struct VersionQuery {
926 pub version: Option<String>,
927}
928
929
930pub fn parse_required_version(raw: Option<&str>) -> Result<i64, MatrixError> {
931 let raw = raw.ok_or_else(|| MatrixError::invalid_param("version is required"))?;
932 raw.parse::<i64>().map_err(|_| MatrixError::invalid_param("version must be an integer"))
933}
934
935
936pub fn require_current_backup_version(conn: &Connection, user_id: i64, requested_version: i64) -> Result<(), MatrixError> {
941 let current = crate::keys::current_backup_version(conn, user_id)?;
942 match current {
943 Some(row) if row.version == requested_version => Ok(()),
944 Some(row) => Err(MatrixError::wrong_room_keys_version(Some(row.version))),
945 None => Err(MatrixError::wrong_room_keys_version(None)),
946 }
947}
948
949
950pub fn normalize_put_backup_body(room_id: Option<&str>, session_id: Option<&str>, body: &serde_json::Value) -> Result<Vec<(String, String, String)>, MatrixError> {
956 match (room_id, session_id) {
957 (Some(room_id), Some(session_id)) => Ok(vec![(room_id.to_string(), session_id.to_string(), body.to_string())]),
958 (Some(room_id), None) => {
959 let sessions = body
960 .get("sessions")
961 .and_then(|v| v.as_object())
962 .ok_or_else(|| MatrixError::invalid_param("body must have a sessions object"))?;
963 Ok(sessions.iter().map(|(sid, data)| (room_id.to_string(), sid.clone(), data.to_string())).collect())
964 }
965 (None, _) => {
966 let rooms = body
967 .get("rooms")
968 .and_then(|v| v.as_object())
969 .ok_or_else(|| MatrixError::invalid_param("body must have a rooms object"))?;
970 let mut out = Vec::new();
971 for (rid, room_value) in rooms {
972 let sessions = room_value
973 .get("sessions")
974 .and_then(|v| v.as_object())
975 .ok_or_else(|| MatrixError::invalid_param("each room must have a sessions object"))?;
976 for (sid, data) in sessions {
977 out.push((rid.clone(), sid.clone(), data.to_string()));
978 }
979 }
980 Ok(out)
981 }
982 }
983}
984
985
986pub fn backup_sessions_to_response(room_id: Option<&str>, session_id: Option<&str>, sessions: Vec<crate::keys::KeyBackupSession>) -> Result<serde_json::Value, MatrixError> {
989 match (room_id, session_id) {
990 (Some(_), Some(_)) => {
991 let one = sessions.into_iter().next().ok_or_else(|| MatrixError::not_found("no such backup session"))?;
992 Ok(serde_json::from_str(&one.session_data)?)
993 }
994 (Some(_), None) => {
995 let mut sessions_out = serde_json::Map::new();
996 for s in sessions {
997 sessions_out.insert(s.session_id.clone(), serde_json::from_str(&s.session_data)?);
998 }
999 Ok(serde_json::json!({ "sessions": sessions_out }))
1000 }
1001 (None, _) => {
1002 let mut rooms_out = serde_json::Map::new();
1003 for s in sessions {
1004 let room_entry = rooms_out.entry(s.room_id.clone()).or_insert_with(|| serde_json::json!({ "sessions": {} }));
1005 room_entry["sessions"][s.session_id.as_str()] = serde_json::from_str(&s.session_data)?;
1006 }
1007 Ok(serde_json::json!({ "rooms": rooms_out }))
1008 }
1009 }
1010}
1011
1012
1013#[cfg(test)]
1014mod device_keys_upload_tests {
1015 use super::*;
1016 use rusqlite::Connection;
1017
1018 const T0: &str = "2026-10-06T00:00:00+00:00";
1019
1020 fn test_conn() -> Connection {
1021 let conn = Connection::open_in_memory().expect("memory");
1022 crate::store::create_matrix_schema(&conn).expect("schema");
1023 crate::keys::create_matrix_keys_schema(&conn).expect("keys");
1024 conn
1025 }
1026
1027 #[test]
1028 fn decide_insert_noop_and_reset() {
1029 let keys_a = serde_json::json!({"curve25519:D":"aaa","ed25519:D":"bbb"});
1030 let keys_b = serde_json::json!({"curve25519:D":"ccc","ed25519:D":"ddd"});
1031 assert_eq!(decide_device_keys_upload(None, &keys_a).unwrap(), DeviceKeysUploadAction::Insert);
1032 assert_eq!(
1033 decide_device_keys_upload(Some(&keys_a.to_string()), &keys_a).unwrap(),
1034 DeviceKeysUploadAction::Noop
1035 );
1036 assert_eq!(
1037 decide_device_keys_upload(Some(&keys_a.to_string()), &keys_b).unwrap(),
1038 DeviceKeysUploadAction::Reset
1039 );
1040 }
1041
1042 #[test]
1043 fn apply_keys_upload_reset_replaces_identity_clears_otks_and_logs_change() {
1044 let mut conn = test_conn();
1045 crate::store::ensure_matrix_user(&conn, 1, "alice00000000000000000000000001", T0).expect("user");
1046 let device_id = crate::keys::create_device(&conn, 1, crate::keys::CredentialKind::Bearer, "tok", T0).expect("device");
1047 let mxid = crate::store::mxid_of(&conn, 1).expect("mxid").expect("row");
1048
1049 let first = KeysUploadRequest {
1050 device_keys: Some(serde_json::json!({
1051 "user_id": mxid,
1052 "device_id": device_id,
1053 "algorithms": ["m.olm.v1.curve25519-aes-sha2"],
1054 "keys": {"curve25519:D":"oldcurve","ed25519:D":"olded"},
1055 "signatures": {}
1056 })),
1057 one_time_keys: Some(std::collections::BTreeMap::from([(
1058 "signed_curve25519:AAAAAQ".to_string(),
1059 serde_json::json!({"key":"otk1"}),
1060 )])),
1061 fallback_keys: Some(std::collections::BTreeMap::from([(
1062 "signed_curve25519:FALLBACK".to_string(),
1063 serde_json::json!({"key":"fb1"}),
1064 )])),
1065 };
1066 let first_out = apply_keys_upload(&mut conn, 1, &mxid, &device_id, &first, T0).expect("first");
1067 assert!(first_out.device_keys_changed);
1068 assert_eq!(crate::keys::count_one_time_keys(&conn, 1, &device_id).unwrap().get("signed_curve25519"), Some(&1));
1069
1070 let before_changes: i64 = conn
1071 .query_row("SELECT COUNT(*) FROM device_list_changes WHERE user_id = 1", [], |r| r.get(0))
1072 .unwrap();
1073
1074 let reset = KeysUploadRequest {
1075 device_keys: Some(serde_json::json!({
1076 "user_id": mxid,
1077 "device_id": device_id,
1078 "algorithms": ["m.olm.v1.curve25519-aes-sha2"],
1079 "keys": {"curve25519:D":"newcurve","ed25519:D":"newed"},
1080 "signatures": {}
1081 })),
1082 one_time_keys: Some(std::collections::BTreeMap::from([(
1083 "signed_curve25519:BBBBBQ".to_string(),
1084 serde_json::json!({"key":"otk2"}),
1085 )])),
1086 fallback_keys: None,
1087 };
1088 let reset_out = apply_keys_upload(&mut conn, 1, &mxid, &device_id, &reset, T0).expect("reset");
1089 assert!(reset_out.device_keys_changed);
1090
1091 let stored = crate::keys::device_keys_for(&conn, &[1]).unwrap();
1092 assert_eq!(stored.len(), 1);
1093 assert!(stored[0].keys.contains("newcurve"), "new identity stored: {}", stored[0].keys);
1094 assert!(!stored[0].keys.contains("oldcurve"));
1095
1096 let otk = crate::keys::count_one_time_keys(&conn, 1, &device_id).unwrap();
1097 assert_eq!(otk.get("signed_curve25519"), Some(&1), "old OTKs wiped; only the new batch remains");
1098 let fallback = crate::keys::unused_fallback_key_types(&conn, 1, &device_id).unwrap();
1099 assert!(fallback.is_empty(), "old fallback wiped on reset");
1100
1101 let after_changes: i64 = conn
1102 .query_row("SELECT COUNT(*) FROM device_list_changes WHERE user_id = 1", [], |r| r.get(0))
1103 .unwrap();
1104 assert_eq!(after_changes, before_changes + 1, "reset logs device_lists.changed");
1105 }
1106}