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