Skip to main content

mail4agent_server/
federation.rs

1//! Federation stage F0: this server's signing keys, canonical-JSON signing,
2//! `X-Matrix` request authentication, and remote key resolution/cache.
3//!
4//! Nothing here sends events or joins rooms (that is F1+). The server name is
5//! always a parameter, so the module is testable without the process-wide
6//! name. Remote key fetching is behind [`RemoteKeys`] so tests (and staged
7//! deployments) can substitute the network.
8
9use 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
21/// Validity of a published key response (a week, within the spec's limit).
22pub const KEY_VALIDITY_MS: i64 = 7 * 24 * 3600 * 1000;
23/// Largest remote key/well-known body we read.
24const MAX_REMOTE_BODY: usize = 64 * 1024;
25/// A server whose keys were fetched this recently is not re-fetched for an unknown key id.
26const REFETCH_FLOOR_MS: i64 = 30_000;
27
28/// Federation-layer failure. Mapped to `M_UNAUTHORIZED` at the HTTP edge.
29#[derive(Debug, Clone, PartialEq)]
30pub enum FedError {
31    /// Malformed header, key response, or signature material.
32    Malformed(String),
33    /// Signature did not verify.
34    BadSignature,
35    /// Remote could not be resolved or reached.
36    Network(String),
37    /// Storage failure.
38    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
58/// Current time in milliseconds since the epoch.
59pub 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
63/// Matrix canonical JSON: keys sorted, no whitespace. `serde_json` keeps
64/// object keys in a sorted map (the `preserve_order` feature is not enabled),
65/// and the output is compact UTF-8, which is what the signing rules need for
66/// the integer/string/array/object values used here.
67pub 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
87/// The active signing key, generated and stored on first use.
88pub 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
110/// Retire the active key (rotation). The next call to [`active_signing_key`] mints a new one.
111pub 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
116/// Sign `object` in place: adds `signatures[server][key_id]` over the
117/// canonical form without `signatures` and `unsigned`.
118pub 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
132/// Verify `object.signatures[server][key_id]` with a base64 public key.
133pub 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
154/// Body of `GET /_matrix/key/v2/server`, self-signed.
155pub 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// ---------------------------------------------------------------- X-Matrix
177
178/// Parsed `Authorization: X-Matrix ...` header.
179#[derive(Debug, Clone, PartialEq)]
180pub struct XMatrix {
181    /// Sending server.
182    pub origin: String,
183    /// Receiving server (absent from pre-1.3 senders).
184    pub destination: Option<String>,
185    /// Key id the signature was made with.
186    pub key: String,
187    /// Unpadded base64 signature.
188    pub sig: String,
189}
190
191/// Parse an `X-Matrix` Authorization value (quoted or bare parameters).
192pub 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
236/// The object a sender signs for an authenticated federation request.
237/// `uri` is the full path and query as the receiver sees it, including `/_matrix`.
238pub 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
250/// Build the `Authorization` header value for an outgoing signed request.
251pub 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
265/// Verify a signed request given the sender's public key.
266pub 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// ------------------------------------------------------- remote key cache
279
280/// Verify keys parsed from a remote `key/v2/server` response.
281#[derive(Debug, Clone, PartialEq)]
282pub struct ParsedKeys {
283    /// `(key_id, base64 public key)`.
284    pub keys: Vec<(String, String)>,
285    /// Expiry, already capped to one validity window from now.
286    pub valid_until_ms: i64,
287}
288
289/// Validate a remote key response: right server name, not expired, and
290/// self-signed by every listed key that carries a signature (at least one).
291pub 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
316/// Cached public key for `(server, key_id)` that is still valid.
317pub 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
327/// Last time any key of `server` was fetched, if ever.
328pub 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
332/// Store the keys of a validated response.
333pub 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
344/// Whether a refetch for an unknown key is allowed right now.
345pub 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
349// ------------------------------------------------------------ resolution
350
351/// Boxed future returned by [`RemoteKeys::fetch_server_keys`].
352pub type FetchFuture<'a> = Pin<Box<dyn Future<Output = Result<Value, FedError>> + Send + 'a>>;
353
354/// Fetches a remote server's `key/v2/server` document.
355pub trait RemoteKeys: Send + Sync {
356    /// Resolve `server` and return its raw key response (unvalidated).
357    fn fetch_server_keys<'a>(&'a self, server: &'a str) -> FetchFuture<'a>;
358}
359
360/// Where a server name points after delegation.
361#[derive(Debug, Clone, PartialEq)]
362pub struct Target {
363    /// Host (or IP literal) to connect to.
364    pub host: String,
365    /// Port; `None` means the default federation port 8448.
366    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
378/// Split `host[:port]`, handling bracketed IPv6. Returns `(host, port, is_ip_literal)`.
379pub 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
400/// Resolve a server name to a connection target. `well_known` is the
401/// delegated `m.server` value, when the name served one. SRV records are not
402/// consulted (F0); a name that needs SRV must publish `.well-known`.
403pub 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
415/// Production fetcher: `.well-known` delegation, then HTTPS GET of the key document.
416pub struct HttpKeyFetcher {
417    client: reqwest::Client,
418    overrides: HashMap<String, String>,
419}
420
421impl HttpKeyFetcher {
422    /// New fetcher with a 10 s timeout and no redirects.
423    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    /// Staging aid: talk to `server_name` at `base_url` (e.g. `http://127.0.0.1:9000`) instead of resolving it.
433    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    /// Parse `name=base_url,name2=base_url2`.
439    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    /// Unauthenticated GET of `uri` (full path and query) on `server`; `(status, json)`.
469    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    /// Base URL (`scheme://authority`) for a server name: staging override,
481    /// else `.well-known` delegation, else `name:8448`.
482    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
510/// Boxed future returned by [`FedTransport::request`].
511/// A binary federation answer.
512pub 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
520/// Sends one signed federation request and returns `(status, json body)`.
521/// `uri` is the full path and query including `/_matrix`.
522pub trait FedTransport: Send + Sync {
523    /// Like [`request`](Self::request) but for a binary answer (media): the raw body, capped at
524    /// `max_bytes` (a longer body is an error). Transports that cannot do it say so.
525    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    /// Deliver to `destination`; `authorization` is the complete `X-Matrix` header value.
530    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
571/// Percent-encode one path segment (RFC 3986 unreserved characters pass through).
572pub 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}