tuwunel_api/router/auth/
server.rs1use axum::RequestPartsExt;
2use axum_extra::{TypedHeader, headers::Authorization, typed_header::TypedHeaderRejectionReason};
3use http::uri::PathAndQuery;
4use ruma::{
5 CanonicalJsonName, CanonicalJsonObject, CanonicalJsonValue,
6 api::federation::authentication::XMatrix,
7};
8use tuwunel_core::{Err, Result, debug_error, err, warn};
9use tuwunel_service::{
10 Services,
11 server_keys::{PubKeyMap, PubKeys},
12};
13
14use super::{Auth, Request};
15
16pub(super) async fn auth_server(
17 services: &Services,
18 request: &mut Request,
19 body: Option<&CanonicalJsonValue>,
20) -> Result<Auth> {
21 type Member = (CanonicalJsonName, CanonicalJsonValue);
22 type Object = CanonicalJsonObject;
23 type Value = CanonicalJsonValue;
24
25 let x_matrix = parse_x_matrix(request).await?;
26 auth_server_checks(services, &x_matrix)?;
27
28 let destination = services.globals.server_name();
29 let origin = &x_matrix.origin;
30 let signature_uri = request
31 .parts
32 .uri
33 .path_and_query()
34 .map(PathAndQuery::as_str)
35 .unwrap_or("/");
36
37 let signature: [Member; 1] =
38 [(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))];
39
40 let signatures: [Member; 1] = [(origin.as_str().into(), Value::Object(signature.into()))];
41
42 let authorization: Object = if let Some(body) = body.cloned() {
43 let authorization: [Member; 6] = [
44 ("content".into(), body),
45 ("destination".into(), Value::String(destination.into())),
46 ("method".into(), Value::String(request.parts.method.as_str().into())),
47 ("origin".into(), Value::String(origin.as_str().into())),
48 ("signatures".into(), Value::Object(signatures.into())),
49 ("uri".into(), Value::String(signature_uri.into())),
50 ];
51
52 authorization.into()
53 } else {
54 let authorization: [Member; 5] = [
55 ("destination".into(), Value::String(destination.into())),
56 ("method".into(), Value::String(request.parts.method.as_str().into())),
57 ("origin".into(), Value::String(origin.as_str().into())),
58 ("signatures".into(), Value::Object(signatures.into())),
59 ("uri".into(), Value::String(signature_uri.into())),
60 ];
61
62 authorization.into()
63 };
64
65 let key = services
66 .server_keys
67 .get_verify_key(origin, &x_matrix.key)
68 .await
69 .map_err(|e| {
70 err!(Request(Forbidden(debug_warn!("Failed to fetch signing keys: {e}"))))
71 })?;
72
73 let keys: PubKeys = [(x_matrix.key.as_str().into(), key.key)].into();
74 let keys: PubKeyMap = [(origin.as_str().into(), keys)].into();
75 if let Err(e) = ruma::signatures::verify_json(&keys, &authorization) {
76 debug_error!("Failed to verify federation request from {origin}: {e}");
77 if request.parts.uri.to_string().contains('@') {
78 warn!(
79 "Request uri contained '@' character. Make sure your reverse proxy gives \
80 tuwunel the raw uri (apache: use nocanon)"
81 );
82 }
83
84 return Err!(Request(Forbidden("Failed to verify X-Matrix signatures.")));
85 }
86
87 Ok(Auth {
88 origin: origin.to_owned().into(),
89 ..Auth::default()
90 })
91}
92
93fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result {
94 if !services.server.config.allow_federation {
95 return Err!(Config("allow_federation", "Federation is disabled."));
96 }
97
98 let destination = services.globals.server_name();
99 if x_matrix.destination.as_deref() != Some(destination) {
100 return Err!(Request(Forbidden("Invalid destination.")));
101 }
102
103 let origin = &x_matrix.origin;
104 if services
105 .config
106 .is_forbidden_remote_server_name(origin)
107 {
108 return Err!(Request(Forbidden(debug_warn!(
109 "Federation requests from {origin} denied."
110 ))));
111 }
112
113 Ok(())
114}
115
116async fn parse_x_matrix(request: &mut Request) -> Result<XMatrix> {
117 let TypedHeader(Authorization(x_matrix)) = request
118 .parts
119 .extract::<TypedHeader<Authorization<XMatrix>>>()
120 .await
121 .map_err(|e| {
122 let msg = match e.reason() {
123 | TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
124 | TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
125 | _ => "Unknown header-related error",
126 };
127
128 err!(Request(Forbidden(debug_warn!("{msg}: {e}"))))
129 })?;
130
131 Ok(x_matrix)
132}