1use std::collections::{BTreeMap, HashMap};
10use std::future::Future;
11use std::pin::Pin;
12use std::time::Duration;
13
14use base64::engine::general_purpose::STANDARD_NO_PAD;
15use base64::Engine;
16use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
17use rand::RngCore;
18use rusqlite::{params, Connection, OptionalExtension};
19use serde_json::{json, Map, Value};
20
21pub const KEY_VALIDITY_MS: i64 = 7 * 24 * 3600 * 1000;
23const MAX_REMOTE_BODY: usize = 64 * 1024;
25const REFETCH_FLOOR_MS: i64 = 30_000;
27
28#[derive(Debug, Clone, PartialEq)]
30pub enum FedError {
31 Malformed(String),
33 BadSignature,
35 Network(String),
37 Db(String),
39}
40
41impl std::fmt::Display for FedError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 FedError::Malformed(m) => write!(f, "malformed: {m}"),
45 FedError::BadSignature => write!(f, "signature verification failed"),
46 FedError::Network(m) => write!(f, "network: {m}"),
47 FedError::Db(m) => write!(f, "db: {m}"),
48 }
49 }
50}
51
52impl From<rusqlite::Error> for FedError {
53 fn from(e: rusqlite::Error) -> Self {
54 FedError::Db(e.to_string())
55 }
56}
57
58pub fn now_ms() -> i64 {
60 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as i64).unwrap_or(0)
61}
62
63pub fn canonical_json(value: &Value) -> Vec<u8> {
68 serde_json::to_vec(value).unwrap_or_default()
69}
70
71fn b64(bytes: &[u8]) -> String {
72 STANDARD_NO_PAD.encode(bytes)
73}
74
75fn unb64(s: &str) -> Result<Vec<u8>, FedError> {
76 STANDARD_NO_PAD.decode(s.trim_end_matches('=')).map_err(|_| FedError::Malformed("bad base64".into()))
77}
78
79fn random_key_id() -> String {
80 const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
81 let mut raw = [0u8; 6];
82 rand::thread_rng().fill_bytes(&mut raw);
83 let tail: String = raw.iter().map(|b| ALPHABET[*b as usize % ALPHABET.len()] as char).collect();
84 format!("ed25519:{tail}")
85}
86
87pub fn active_signing_key(conn: &Connection, now_ms: i64) -> Result<(String, SigningKey), FedError> {
89 let row: Option<(String, Vec<u8>)> = conn
90 .query_row(
91 "SELECT key_id, secret FROM fed_signing_keys WHERE retired_ms IS NULL ORDER BY created_ms DESC, key_id LIMIT 1",
92 [],
93 |r| Ok((r.get(0)?, r.get(1)?)),
94 )
95 .optional()?;
96 if let Some((id, secret)) = row {
97 let bytes: [u8; 32] = secret.try_into().map_err(|_| FedError::Malformed("stored key length".into()))?;
98 return Ok((id, SigningKey::from_bytes(&bytes)));
99 }
100 let mut seed = [0u8; 32];
101 rand::thread_rng().fill_bytes(&mut seed);
102 let id = random_key_id();
103 conn.execute(
104 "INSERT INTO fed_signing_keys (key_id, secret, created_ms) VALUES (?1, ?2, ?3)",
105 params![id, seed.to_vec(), now_ms],
106 )?;
107 Ok((id, SigningKey::from_bytes(&seed)))
108}
109
110pub fn retire_active_key(conn: &Connection, now_ms: i64) -> Result<(), FedError> {
112 conn.execute("UPDATE fed_signing_keys SET retired_ms = ?1 WHERE retired_ms IS NULL", params![now_ms])?;
113 Ok(())
114}
115
116pub fn sign_json(object: &mut Map<String, Value>, server: &str, key_id: &str, key: &SigningKey) {
119 let mut bare = object.clone();
120 bare.remove("signatures");
121 bare.remove("unsigned");
122 let sig = key.sign(&canonical_json(&Value::Object(bare)));
123 let sigs = object.entry("signatures").or_insert_with(|| json!({}));
124 if let Some(map) = sigs.as_object_mut() {
125 let per = map.entry(server.to_string()).or_insert_with(|| json!({}));
126 if let Some(per) = per.as_object_mut() {
127 per.insert(key_id.to_string(), Value::String(b64(&sig.to_bytes())));
128 }
129 }
130}
131
132pub fn verify_json(object: &Value, server: &str, key_id: &str, public_key_b64: &str) -> Result<(), FedError> {
134 let obj = object.as_object().ok_or_else(|| FedError::Malformed("not an object".into()))?;
135 let sig_b64 = obj
136 .get("signatures")
137 .and_then(|s| s.get(server))
138 .and_then(|s| s.get(key_id))
139 .and_then(Value::as_str)
140 .ok_or(FedError::BadSignature)?;
141 let mut bare = obj.clone();
142 bare.remove("signatures");
143 bare.remove("unsigned");
144 verify_bytes(&canonical_json(&Value::Object(bare)), sig_b64, public_key_b64)
145}
146
147fn verify_bytes(message: &[u8], sig_b64: &str, public_key_b64: &str) -> Result<(), FedError> {
148 let pk: [u8; 32] = unb64(public_key_b64)?.try_into().map_err(|_| FedError::Malformed("public key length".into()))?;
149 let vk = VerifyingKey::from_bytes(&pk).map_err(|_| FedError::Malformed("public key".into()))?;
150 let sig: [u8; 64] = unb64(sig_b64)?.try_into().map_err(|_| FedError::BadSignature)?;
151 vk.verify(message, &Signature::from_bytes(&sig)).map_err(|_| FedError::BadSignature)
152}
153
154pub fn server_keys_response(conn: &Connection, server: &str, now_ms: i64) -> Result<Value, FedError> {
156 let (key_id, key) = active_signing_key(conn, now_ms)?;
157 let mut old = Map::new();
158 let mut stmt = conn.prepare("SELECT key_id, secret, retired_ms FROM fed_signing_keys WHERE retired_ms IS NOT NULL")?;
159 let rows = stmt.query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, Vec<u8>>(1)?, r.get::<_, i64>(2)?)))?;
160 for row in rows {
161 let (id, secret, retired) = row?;
162 if let Ok(bytes) = <[u8; 32]>::try_from(secret) {
163 let pk = SigningKey::from_bytes(&bytes).verifying_key();
164 old.insert(id, json!({ "key": b64(pk.as_bytes()), "expired_ts": retired }));
165 }
166 }
167 let mut obj = Map::new();
168 obj.insert("server_name".into(), json!(server));
169 obj.insert("verify_keys".into(), json!({ key_id.clone(): { "key": b64(key.verifying_key().as_bytes()) } }));
170 obj.insert("old_verify_keys".into(), Value::Object(old));
171 obj.insert("valid_until_ts".into(), json!(now_ms + KEY_VALIDITY_MS));
172 sign_json(&mut obj, server, &key_id, &key);
173 Ok(Value::Object(obj))
174}
175
176#[derive(Debug, Clone, PartialEq)]
180pub struct XMatrix {
181 pub origin: String,
183 pub destination: Option<String>,
185 pub key: String,
187 pub sig: String,
189}
190
191pub fn parse_x_matrix(value: &str) -> Result<XMatrix, FedError> {
193 let rest = value.trim().strip_prefix("X-Matrix ").ok_or_else(|| FedError::Malformed("not X-Matrix".into()))?;
194 let mut map: HashMap<String, String> = HashMap::new();
195 for part in split_params(rest) {
196 let (k, v) = part.split_once('=').ok_or_else(|| FedError::Malformed("parameter".into()))?;
197 let v = v.trim();
198 let v = v.strip_prefix('"').and_then(|x| x.strip_suffix('"')).unwrap_or(v);
199 map.insert(k.trim().to_ascii_lowercase(), v.replace("\\\"", "\"").replace("\\\\", "\\"));
200 }
201 let get = |k: &str| map.get(k).cloned().filter(|s| !s.is_empty());
202 Ok(XMatrix {
203 origin: get("origin").ok_or_else(|| FedError::Malformed("origin".into()))?,
204 destination: get("destination"),
205 key: get("key").ok_or_else(|| FedError::Malformed("key".into()))?,
206 sig: get("sig").ok_or_else(|| FedError::Malformed("sig".into()))?,
207 })
208}
209
210fn split_params(s: &str) -> Vec<String> {
211 let (mut out, mut cur, mut quoted, mut esc) = (Vec::new(), String::new(), false, false);
212 for c in s.chars() {
213 match c {
214 _ if esc => {
215 cur.push(c);
216 esc = false;
217 }
218 '\\' if quoted => {
219 cur.push(c);
220 esc = true;
221 }
222 '"' => {
223 quoted = !quoted;
224 cur.push(c);
225 }
226 ',' if !quoted => out.push(std::mem::take(&mut cur)),
227 _ => cur.push(c),
228 }
229 }
230 if !cur.trim().is_empty() {
231 out.push(cur);
232 }
233 out
234}
235
236pub fn request_signing_object(method: &str, uri: &str, origin: &str, destination: &str, content: Option<&Value>) -> Value {
239 let mut o = BTreeMap::new();
240 o.insert("method", json!(method.to_ascii_uppercase()));
241 o.insert("uri", json!(uri));
242 o.insert("origin", json!(origin));
243 o.insert("destination", json!(destination));
244 if let Some(c) = content {
245 o.insert("content", c.clone());
246 }
247 json!(o)
248}
249
250pub fn build_x_matrix_header(
252 origin: &str,
253 destination: &str,
254 key_id: &str,
255 key: &SigningKey,
256 method: &str,
257 uri: &str,
258 content: Option<&Value>,
259) -> String {
260 let obj = request_signing_object(method, uri, origin, destination, content);
261 let sig = b64(&key.sign(&canonical_json(&obj)).to_bytes());
262 format!("X-Matrix origin=\"{origin}\",destination=\"{destination}\",key=\"{key_id}\",sig=\"{sig}\"")
263}
264
265pub fn verify_request_signature(
267 header: &XMatrix,
268 method: &str,
269 uri: &str,
270 local_server: &str,
271 content: Option<&Value>,
272 public_key_b64: &str,
273) -> Result<(), FedError> {
274 let obj = request_signing_object(method, uri, &header.origin, header.destination.as_deref().unwrap_or(local_server), content);
275 verify_bytes(&canonical_json(&obj), &header.sig, public_key_b64)
276}
277
278#[derive(Debug, Clone, PartialEq)]
282pub struct ParsedKeys {
283 pub keys: Vec<(String, String)>,
285 pub valid_until_ms: i64,
287}
288
289pub fn parse_server_keys(resp: &Value, expect_server: &str, now_ms: i64) -> Result<ParsedKeys, FedError> {
292 if resp.get("server_name").and_then(Value::as_str) != Some(expect_server) {
293 return Err(FedError::Malformed("server_name mismatch".into()));
294 }
295 let valid_until = resp.get("valid_until_ts").and_then(Value::as_i64).ok_or_else(|| FedError::Malformed("valid_until_ts".into()))?;
296 if valid_until <= now_ms {
297 return Err(FedError::Malformed("keys expired".into()));
298 }
299 let vk = resp.get("verify_keys").and_then(Value::as_object).ok_or_else(|| FedError::Malformed("verify_keys".into()))?;
300 let (mut keys, mut verified) = (Vec::new(), 0);
301 for (id, v) in vk {
302 let pk = v.get("key").and_then(Value::as_str).ok_or_else(|| FedError::Malformed("key".into()))?;
303 let signed = resp.get("signatures").and_then(|s| s.get(expect_server)).and_then(|s| s.get(id)).is_some();
304 if signed {
305 verify_json(resp, expect_server, id, pk)?;
306 verified += 1;
307 }
308 keys.push((id.clone(), pk.to_string()));
309 }
310 if verified == 0 {
311 return Err(FedError::BadSignature);
312 }
313 Ok(ParsedKeys { keys, valid_until_ms: valid_until.min(now_ms + KEY_VALIDITY_MS) })
314}
315
316pub fn cached_remote_key(conn: &Connection, server: &str, key_id: &str, now_ms: i64) -> Result<Option<String>, FedError> {
318 Ok(conn
319 .query_row(
320 "SELECT public_key FROM fed_remote_keys WHERE server_name=?1 AND key_id=?2 AND valid_until_ms > ?3",
321 params![server, key_id, now_ms],
322 |r| r.get(0),
323 )
324 .optional()?)
325}
326
327pub fn last_fetch_ms(conn: &Connection, server: &str) -> Result<Option<i64>, FedError> {
329 Ok(conn.query_row("SELECT MAX(fetched_ms) FROM fed_remote_keys WHERE server_name=?1", params![server], |r| r.get(0))?)
330}
331
332pub fn store_remote_keys(conn: &Connection, server: &str, parsed: &ParsedKeys, now_ms: i64) -> Result<(), FedError> {
334 for (id, pk) in &parsed.keys {
335 conn.execute(
336 "INSERT INTO fed_remote_keys (server_name, key_id, public_key, valid_until_ms, fetched_ms) VALUES (?1,?2,?3,?4,?5)
337 ON CONFLICT(server_name, key_id) DO UPDATE SET public_key=excluded.public_key, valid_until_ms=excluded.valid_until_ms, fetched_ms=excluded.fetched_ms",
338 params![server, id, pk, parsed.valid_until_ms, now_ms],
339 )?;
340 }
341 Ok(())
342}
343
344pub fn may_refetch(conn: &Connection, server: &str, now_ms: i64) -> Result<bool, FedError> {
346 Ok(last_fetch_ms(conn, server)?.map_or(true, |t| now_ms - t >= REFETCH_FLOOR_MS))
347}
348
349pub type FetchFuture<'a> = Pin<Box<dyn Future<Output = Result<Value, FedError>> + Send + 'a>>;
353
354pub trait RemoteKeys: Send + Sync {
356 fn fetch_server_keys<'a>(&'a self, server: &'a str) -> FetchFuture<'a>;
358}
359
360#[derive(Debug, Clone, PartialEq)]
362pub struct Target {
363 pub host: String,
365 pub port: Option<u16>,
367}
368
369impl Target {
370 fn authority(&self) -> String {
371 match self.port {
372 Some(p) => format!("{}:{}", self.host, p),
373 None => format!("{}:8448", self.host),
374 }
375 }
376}
377
378pub fn parse_server_name(name: &str) -> Option<(String, Option<u16>, bool)> {
380 if name.is_empty() || name.contains('/') || name.contains(' ') {
381 return None;
382 }
383 if let Some(rest) = name.strip_prefix('[') {
384 let (host, tail) = rest.split_once(']')?;
385 let port = match tail.strip_prefix(':') {
386 Some(p) => Some(p.parse().ok()?),
387 None if tail.is_empty() => None,
388 None => return None,
389 };
390 return Some((format!("[{host}]"), port, true));
391 }
392 let (host, port) = match name.rsplit_once(':') {
393 Some((h, p)) => (h, Some(p.parse::<u16>().ok()?)),
394 None => (name, None),
395 };
396 let is_ip = host.parse::<std::net::Ipv4Addr>().is_ok();
397 Some((host.to_string(), port, is_ip))
398}
399
400pub fn resolve_target(name: &str, well_known: Option<&str>) -> Option<Target> {
404 let (host, port, is_ip) = parse_server_name(name)?;
405 if is_ip || port.is_some() {
406 return Some(Target { host, port });
407 }
408 if let Some(delegate) = well_known {
409 let (dh, dp, _) = parse_server_name(delegate)?;
410 return Some(Target { host: dh, port: dp });
411 }
412 Some(Target { host, port: None })
413}
414
415pub struct HttpKeyFetcher {
417 client: reqwest::Client,
418 overrides: HashMap<String, String>,
419}
420
421impl HttpKeyFetcher {
422 pub fn new() -> Self {
424 let client = reqwest::Client::builder()
425 .timeout(Duration::from_secs(10))
426 .redirect(reqwest::redirect::Policy::none())
427 .build()
428 .unwrap_or_default();
429 Self { client, overrides: HashMap::new() }
430 }
431
432 pub fn with_override(mut self, server_name: &str, base_url: &str) -> Self {
434 self.overrides.insert(server_name.to_string(), base_url.trim_end_matches('/').to_string());
435 self
436 }
437
438 pub fn with_overrides_from(mut self, spec: &str) -> Self {
440 for pair in spec.split(',').filter(|s| !s.trim().is_empty()) {
441 if let Some((n, u)) = pair.split_once('=') {
442 self = self.with_override(n.trim(), u.trim());
443 }
444 }
445 self
446 }
447
448 async fn get_limited(&self, url: &str) -> Result<Vec<u8>, FedError> {
449 let resp = self.client.get(url).send().await.map_err(|e| FedError::Network(e.to_string()))?;
450 if !resp.status().is_success() {
451 return Err(FedError::Network(format!("status {}", resp.status().as_u16())));
452 }
453 let bytes = resp.bytes().await.map_err(|e| FedError::Network(e.to_string()))?;
454 if bytes.len() > MAX_REMOTE_BODY {
455 return Err(FedError::Network("body too large".into()));
456 }
457 Ok(bytes.to_vec())
458 }
459}
460
461impl Default for HttpKeyFetcher {
462 fn default() -> Self {
463 Self::new()
464 }
465}
466
467impl HttpKeyFetcher {
468 pub async fn get_unsigned(&self, server: &str, uri: &str) -> Result<(u16, Value), FedError> {
470 let base = self.base_url(server).await?;
471 let resp = self.client.get(format!("{base}{uri}")).send().await.map_err(|e| FedError::Network(e.to_string()))?;
472 let status = resp.status().as_u16();
473 let bytes = resp.bytes().await.map_err(|e| FedError::Network(e.to_string()))?;
474 if bytes.len() > 64 * 1024 {
475 return Err(FedError::Network("body too large".into()));
476 }
477 Ok((status, serde_json::from_slice(&bytes).unwrap_or(Value::Null)))
478 }
479
480 pub async fn base_url(&self, server: &str) -> Result<String, FedError> {
483 if let Some(base) = self.overrides.get(server) {
484 return Ok(base.clone());
485 }
486 let (host, port, is_ip) = parse_server_name(server).ok_or_else(|| FedError::Malformed("server name".into()))?;
487 let well_known = if is_ip || port.is_some() {
488 None
489 } else {
490 match self.get_limited(&format!("https://{host}/.well-known/matrix/server")).await {
491 Ok(b) => serde_json::from_slice::<Value>(&b).ok().and_then(|v| v.get("m.server").and_then(Value::as_str).map(str::to_string)),
492 Err(_) => None,
493 }
494 };
495 let target = resolve_target(server, well_known.as_deref()).ok_or_else(|| FedError::Malformed("delegate".into()))?;
496 Ok(format!("https://{}", target.authority()))
497 }
498}
499
500impl RemoteKeys for HttpKeyFetcher {
501 fn fetch_server_keys<'a>(&'a self, server: &'a str) -> FetchFuture<'a> {
502 Box::pin(async move {
503 let base = self.base_url(server).await?;
504 let body = self.get_limited(&format!("{base}/_matrix/key/v2/server")).await?;
505 serde_json::from_slice(&body).map_err(|_| FedError::Malformed("key json".into()))
506 })
507 }
508}
509
510pub struct RawResponse {
513 pub status: u16,
514 pub content_type: Option<String>,
515 pub body: Vec<u8>,
516}
517pub type RawFuture<'a> = Pin<Box<dyn Future<Output = Result<RawResponse, FedError>> + Send + 'a>>;
518pub type ReqFuture<'a> = Pin<Box<dyn Future<Output = Result<(u16, Value), FedError>> + Send + 'a>>;
519
520pub trait FedTransport: Send + Sync {
523 fn request_raw<'a>(&'a self, _destination: &'a str, _method: &'a str, _uri: &'a str, _authorization: &'a str, _max_bytes: usize) -> RawFuture<'a> {
526 Box::pin(async { Err(FedError::Network("raw requests are not supported by this transport".into())) })
527 }
528
529 fn request<'a>(&'a self, destination: &'a str, method: &'a str, uri: &'a str, authorization: &'a str, body: Option<&'a Value>) -> ReqFuture<'a>;
531}
532
533impl FedTransport for HttpKeyFetcher {
534 fn request_raw<'a>(&'a self, destination: &'a str, method: &'a str, uri: &'a str, authorization: &'a str, max_bytes: usize) -> RawFuture<'a> {
535 Box::pin(async move {
536 let base = self.base_url(destination).await?;
537 let m = reqwest::Method::from_bytes(method.as_bytes()).map_err(|_| FedError::Malformed("method".into()))?;
538 let mut resp = self.client.request(m, format!("{base}{uri}")).header("Authorization", authorization).send().await.map_err(|e| FedError::Network(e.to_string()))?;
539 let status = resp.status().as_u16();
540 let content_type = resp.headers().get("content-type").and_then(|v| v.to_str().ok()).map(str::to_string);
541 let mut body = Vec::new();
542 while let Some(chunk) = resp.chunk().await.map_err(|e| FedError::Network(e.to_string()))? {
543 if body.len() + chunk.len() > max_bytes {
544 return Err(FedError::Network("body too large".into()));
545 }
546 body.extend_from_slice(&chunk);
547 }
548 Ok(RawResponse { status, content_type, body })
549 })
550 }
551
552 fn request<'a>(&'a self, destination: &'a str, method: &'a str, uri: &'a str, authorization: &'a str, body: Option<&'a Value>) -> ReqFuture<'a> {
553 Box::pin(async move {
554 let base = self.base_url(destination).await?;
555 let m = reqwest::Method::from_bytes(method.as_bytes()).map_err(|_| FedError::Malformed("method".into()))?;
556 let mut req = self.client.request(m, format!("{base}{uri}")).header("Authorization", authorization);
557 if let Some(b) = body {
558 req = req.header("Content-Type", "application/json").body(canonical_json(b));
559 }
560 let resp = req.send().await.map_err(|e| FedError::Network(e.to_string()))?;
561 let status = resp.status().as_u16();
562 let bytes = resp.bytes().await.map_err(|e| FedError::Network(e.to_string()))?;
563 if bytes.len() > 8 * 1024 * 1024 {
564 return Err(FedError::Network("body too large".into()));
565 }
566 Ok((status, serde_json::from_slice(&bytes).unwrap_or(Value::Null)))
567 })
568 }
569}
570
571pub fn enc(segment: &str) -> String {
573 let mut out = String::new();
574 for b in segment.bytes() {
575 if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') {
576 out.push(b as char);
577 } else {
578 out.push_str(&format!("%{b:02X}"));
579 }
580 }
581 out
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 fn db() -> Connection {
589 let c = Connection::open_in_memory().unwrap();
590 crate::store::create_matrix_schema(&c).unwrap();
591 c
592 }
593
594 #[test]
595 fn canonical_json_sorts_keys_and_is_compact() {
596 let v = json!({"b": 1, "a": {"d": [1, 2], "c": "x"}});
597 assert_eq!(String::from_utf8(canonical_json(&v)).unwrap(), r#"{"a":{"c":"x","d":[1,2]},"b":1}"#);
598 }
599
600 #[test]
601 fn signing_key_is_stable_until_retired() {
602 let c = db();
603 let (id1, k1) = active_signing_key(&c, 1000).unwrap();
604 let (id2, k2) = active_signing_key(&c, 2000).unwrap();
605 assert_eq!((id1.clone(), k1.to_bytes()), (id2, k2.to_bytes()));
606 assert!(id1.starts_with("ed25519:"));
607 retire_active_key(&c, 3000).unwrap();
608 let (id3, _) = active_signing_key(&c, 4000).unwrap();
609 assert_ne!(id1, id3);
610 let resp = server_keys_response(&c, "a.example", 5000).unwrap();
611 assert!(resp["old_verify_keys"][&id1]["expired_ts"].as_i64() == Some(3000));
612 }
613
614 #[test]
615 fn sign_and_verify_json_ignores_signatures_and_unsigned() {
616 let c = db();
617 let (id, key) = active_signing_key(&c, 0).unwrap();
618 let pk = b64(key.verifying_key().as_bytes());
619 let mut o = Map::new();
620 o.insert("x".into(), json!(1));
621 sign_json(&mut o, "a.example", &id, &key);
622 let mut v = Value::Object(o);
623 verify_json(&v, "a.example", &id, &pk).unwrap();
624 v["unsigned"] = json!({"age": 5});
625 verify_json(&v, "a.example", &id, &pk).unwrap();
626 v["x"] = json!(2);
627 assert_eq!(verify_json(&v, "a.example", &id, &pk), Err(FedError::BadSignature));
628 assert_eq!(verify_json(&v, "b.example", &id, &pk), Err(FedError::BadSignature));
629 }
630
631 #[test]
632 fn published_keys_validate_and_reject_tampering() {
633 let c = db();
634 let resp = server_keys_response(&c, "a.example", 1000).unwrap();
635 let parsed = parse_server_keys(&resp, "a.example", 2000).unwrap();
636 assert_eq!(parsed.keys.len(), 1);
637 assert_eq!(parse_server_keys(&resp, "other.example", 2000), Err(FedError::Malformed("server_name mismatch".into())));
638 assert!(parse_server_keys(&resp, "a.example", 1000 + KEY_VALIDITY_MS + 1).is_err(), "expired");
639 let mut bad = resp.clone();
640 bad["valid_until_ts"] = json!(i64::MAX / 2);
641 assert_eq!(parse_server_keys(&bad, "a.example", 2000), Err(FedError::BadSignature));
642 let mut unsigned = resp.clone();
643 unsigned.as_object_mut().unwrap().remove("signatures");
644 assert_eq!(parse_server_keys(&unsigned, "a.example", 2000), Err(FedError::BadSignature));
645 }
646
647 #[test]
648 fn x_matrix_header_parses_quoted_bare_and_old_forms() {
649 let h = parse_x_matrix(r#"X-Matrix origin="a.example",destination="b.example",key="ed25519:k1",sig="AbC""#).unwrap();
650 assert_eq!((h.origin.as_str(), h.destination.as_deref(), h.key.as_str(), h.sig.as_str()), ("a.example", Some("b.example"), "ed25519:k1", "AbC"));
651 let old = parse_x_matrix("X-Matrix origin=a.example,key=ed25519:k1,sig=AbC").unwrap();
652 assert_eq!(old.destination, None);
653 assert!(parse_x_matrix("Bearer x").is_err());
654 assert!(parse_x_matrix(r#"X-Matrix origin="a",key="k""#).is_err());
655 }
656
657 #[test]
658 fn request_signature_roundtrip_and_binding() {
659 let c = db();
660 let (id, key) = active_signing_key(&c, 0).unwrap();
661 let pk = b64(key.verifying_key().as_bytes());
662 let body = json!({"k": "v"});
663 let hdr = build_x_matrix_header("a.example", "b.example", &id, &key, "PUT", "/_matrix/federation/v1/send/1", Some(&body));
664 let parsed = parse_x_matrix(&hdr).unwrap();
665 verify_request_signature(&parsed, "PUT", "/_matrix/federation/v1/send/1", "b.example", Some(&body), &pk).unwrap();
666 assert!(verify_request_signature(&parsed, "PUT", "/_matrix/federation/v1/send/2", "b.example", Some(&body), &pk).is_err(), "uri bound");
667 assert!(verify_request_signature(&parsed, "GET", "/_matrix/federation/v1/send/1", "b.example", Some(&body), &pk).is_err(), "method bound");
668 assert!(verify_request_signature(&parsed, "PUT", "/_matrix/federation/v1/send/1", "b.example", Some(&json!({"k":"w"})), &pk).is_err(), "body bound");
669 let mut other = parsed.clone();
670 other.destination = Some("c.example".into());
671 assert!(verify_request_signature(&other, "PUT", "/_matrix/federation/v1/send/1", "b.example", Some(&body), &pk).is_err(), "destination bound");
672 }
673
674 #[test]
675 fn remote_key_cache_respects_expiry_and_refetch_floor() {
676 let c = db();
677 let parsed = ParsedKeys { keys: vec![("ed25519:k".into(), "AAAA".into())], valid_until_ms: 10_000 };
678 assert_eq!(cached_remote_key(&c, "a.example", "ed25519:k", 100).unwrap(), None);
679 store_remote_keys(&c, "a.example", &parsed, 100).unwrap();
680 assert_eq!(cached_remote_key(&c, "a.example", "ed25519:k", 5000).unwrap().as_deref(), Some("AAAA"));
681 assert_eq!(cached_remote_key(&c, "a.example", "ed25519:k", 10_000).unwrap(), None, "expired");
682 assert!(!may_refetch(&c, "a.example", 100 + REFETCH_FLOOR_MS - 1).unwrap());
683 assert!(may_refetch(&c, "a.example", 100 + REFETCH_FLOOR_MS).unwrap());
684 assert!(may_refetch(&c, "never.example", 0).unwrap());
685 }
686
687 #[test]
688 fn server_name_resolution_rules() {
689 assert_eq!(parse_server_name("a.example"), Some(("a.example".into(), None, false)));
690 assert_eq!(parse_server_name("a.example:8449"), Some(("a.example".into(), Some(8449), false)));
691 assert_eq!(parse_server_name("192.0.2.1"), Some(("192.0.2.1".into(), None, true)));
692 assert_eq!(parse_server_name("[2001:db8::1]:9"), Some(("[2001:db8::1]".into(), Some(9), true)));
693 assert_eq!(parse_server_name("a/b"), None);
694 assert_eq!(resolve_target("a.example", None), Some(Target { host: "a.example".into(), port: None }));
695 assert_eq!(resolve_target("a.example", Some("edge.example:443")), Some(Target { host: "edge.example".into(), port: Some(443) }));
696 assert_eq!(resolve_target("a.example:8449", Some("edge.example")), Some(Target { host: "a.example".into(), port: Some(8449) }), "explicit port wins, no well-known");
697 assert_eq!(Target { host: "h".into(), port: None }.authority(), "h:8448");
698 }
699}