Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/11398.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement [MSC3383](https://github.com/matrix-org/matrix-spec-proposals/pull/3383) for including the destination in server-to-server authentication headers. Contributed by @Bubu and @jcgruenhage for Famedly GmbH.
7 changes: 6 additions & 1 deletion scripts-dev/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,12 @@ def request(
authorization_headers = []

for key, sig in signed_json["signatures"][origin_name].items():
header = 'X-Matrix origin=%s,key="%s",sig="%s"' % (origin_name, key, sig)
header = 'X-Matrix origin=%s,key="%s",sig="%s",destination="%s"' % (
origin_name,
key,
sig,
destination,
)
authorization_headers.append(header.encode("ascii"))
print("Authorization: %s" % header, file=sys.stderr)

Expand Down
39 changes: 31 additions & 8 deletions synapse/federation/transport/server/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
import logging
import re
import time
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Tuple, cast
from http import HTTPStatus
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Tuple, cast

from synapse.api.errors import Codes, FederationDeniedError, SynapseError
from synapse.api.urls import FEDERATION_V1_PREFIX
Expand Down Expand Up @@ -86,15 +87,24 @@ async def authenticate_request(

if not auth_headers:
raise NoAuthenticationError(
401, "Missing Authorization headers", Codes.UNAUTHORIZED
HTTPStatus.UNAUTHORIZED,
"Missing Authorization headers",
Codes.UNAUTHORIZED,
)

for auth in auth_headers:
if auth.startswith(b"X-Matrix"):
(origin, key, sig) = _parse_auth_header(auth)
(origin, key, sig, destination) = _parse_auth_header(auth)
json_request["origin"] = origin
json_request["signatures"].setdefault(origin, {})[key] = sig

# if the origin_server sent a destination along it needs to match our own server_name
if destination is not None and destination != self.server_name:
raise AuthenticationError(
HTTPStatus.UNAUTHORIZED,
"Destination mismatch in auth header",
Codes.UNAUTHORIZED,
)
if (
self.federation_domain_whitelist is not None
and origin not in self.federation_domain_whitelist
Expand All @@ -103,7 +113,9 @@ async def authenticate_request(

if origin is None or not json_request["signatures"]:
raise NoAuthenticationError(
401, "Missing Authorization headers", Codes.UNAUTHORIZED
HTTPStatus.UNAUTHORIZED,
"Missing Authorization headers",
Codes.UNAUTHORIZED,
)

await self.keyring.verify_json_for_server(
Expand Down Expand Up @@ -142,13 +154,14 @@ async def reset_retry_timings(self, origin: str) -> None:
logger.exception("Error resetting retry timings on %s", origin)


def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str, Optional[str]]:
"""Parse an X-Matrix auth header

Args:
header_bytes: header value

Returns:
origin, key id, signature, destination.
origin, key id, signature.

Raises:
Expand All @@ -157,7 +170,9 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
try:
header_str = header_bytes.decode("utf-8")
params = header_str.split(" ")[1].split(",")
param_dict = {k: v for k, v in (kv.split("=", maxsplit=1) for kv in params)}
param_dict: Dict[str, str] = {
k: v for k, v in [param.split("=", maxsplit=1) for param in params]
}

def strip_quotes(value: str) -> str:
if value.startswith('"'):
Expand All @@ -172,15 +187,23 @@ def strip_quotes(value: str) -> str:

key = strip_quotes(param_dict["key"])
sig = strip_quotes(param_dict["sig"])
return origin, key, sig

# get the destination server_name from the auth header if it exists
destination = param_dict.get("destination")
if destination is not None:
destination = strip_quotes(destination)
else:
destination = None

return origin, key, sig, destination
except Exception as e:
logger.warning(
"Error parsing auth header '%s': %s",
header_bytes.decode("ascii", "replace"),
e,
)
raise AuthenticationError(
400, "Malformed Authorization header", Codes.UNAUTHORIZED
HTTPStatus.BAD_REQUEST, "Malformed Authorization header", Codes.UNAUTHORIZED
)


Expand Down
12 changes: 10 additions & 2 deletions synapse/http/matrixfederationclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,9 @@ def build_auth_headers(
Returns:
A list of headers to be added as "Authorization:" headers
"""
if destination is None and destination_is is None:
raise ValueError("destination and destination_is cannot both be None!")
Comment thread
clokep marked this conversation as resolved.
Outdated

request: JsonDict = {
"method": method.decode("ascii"),
"uri": url_bytes.decode("ascii"),
Expand All @@ -726,8 +729,13 @@ def build_auth_headers(
for key, sig in request["signatures"][self.server_name].items():
auth_headers.append(
(
'X-Matrix origin=%s,key="%s",sig="%s"'
% (self.server_name, key, sig)
'X-Matrix origin=%s,key="%s",sig="%s",destination="%s"'
% (
self.server_name,
key,
sig,
request.get("destination") or request["destination_is"],
)
).encode("ascii")
)
return auth_headers
Expand Down