Implement MSC3383: include destination in X-Matrix auth header (#11398)

Co-authored-by: Jan Christian Grünhage <jan.christian@gruenhage.xyz>
Co-authored-by: Marcus Hoffmann <bubu@bubu1.eu>
This commit is contained in:
Jan Christian Grünhage 2022-04-19 17:23:53 +02:00 committed by GitHub
parent fbdee86004
commit a1f87f57ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 11 deletions

View File

@ -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.

View File

@ -124,7 +124,12 @@ def request(
authorization_headers = [] authorization_headers = []
for key, sig in signed_json["signatures"][origin_name].items(): 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")) authorization_headers.append(header.encode("ascii"))
print("Authorization: %s" % header, file=sys.stderr) print("Authorization: %s" % header, file=sys.stderr)

View File

@ -16,7 +16,8 @@ import functools
import logging import logging
import re import re
import time 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.errors import Codes, FederationDeniedError, SynapseError
from synapse.api.urls import FEDERATION_V1_PREFIX from synapse.api.urls import FEDERATION_V1_PREFIX
@ -86,15 +87,24 @@ class Authenticator:
if not auth_headers: if not auth_headers:
raise NoAuthenticationError( raise NoAuthenticationError(
401, "Missing Authorization headers", Codes.UNAUTHORIZED HTTPStatus.UNAUTHORIZED,
"Missing Authorization headers",
Codes.UNAUTHORIZED,
) )
for auth in auth_headers: for auth in auth_headers:
if auth.startswith(b"X-Matrix"): 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["origin"] = origin
json_request["signatures"].setdefault(origin, {})[key] = sig 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 ( if (
self.federation_domain_whitelist is not None self.federation_domain_whitelist is not None
and origin not in self.federation_domain_whitelist and origin not in self.federation_domain_whitelist
@ -103,7 +113,9 @@ class Authenticator:
if origin is None or not json_request["signatures"]: if origin is None or not json_request["signatures"]:
raise NoAuthenticationError( raise NoAuthenticationError(
401, "Missing Authorization headers", Codes.UNAUTHORIZED HTTPStatus.UNAUTHORIZED,
"Missing Authorization headers",
Codes.UNAUTHORIZED,
) )
await self.keyring.verify_json_for_server( await self.keyring.verify_json_for_server(
@ -142,13 +154,14 @@ class Authenticator:
logger.exception("Error resetting retry timings on %s", origin) 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 """Parse an X-Matrix auth header
Args: Args:
header_bytes: header value header_bytes: header value
Returns: Returns:
origin, key id, signature, destination.
origin, key id, signature. origin, key id, signature.
Raises: Raises:
@ -157,7 +170,9 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
try: try:
header_str = header_bytes.decode("utf-8") header_str = header_bytes.decode("utf-8")
params = header_str.split(" ")[1].split(",") 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: def strip_quotes(value: str) -> str:
if value.startswith('"'): if value.startswith('"'):
@ -172,7 +187,15 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
key = strip_quotes(param_dict["key"]) key = strip_quotes(param_dict["key"])
sig = strip_quotes(param_dict["sig"]) 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: except Exception as e:
logger.warning( logger.warning(
"Error parsing auth header '%s': %s", "Error parsing auth header '%s': %s",
@ -180,7 +203,7 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
e, e,
) )
raise AuthenticationError( raise AuthenticationError(
400, "Malformed Authorization header", Codes.UNAUTHORIZED HTTPStatus.BAD_REQUEST, "Malformed Authorization header", Codes.UNAUTHORIZED
) )

View File

@ -704,6 +704,9 @@ class MatrixFederationHttpClient:
Returns: Returns:
A list of headers to be added as "Authorization:" headers 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!")
request: JsonDict = { request: JsonDict = {
"method": method.decode("ascii"), "method": method.decode("ascii"),
"uri": url_bytes.decode("ascii"), "uri": url_bytes.decode("ascii"),
@ -726,8 +729,13 @@ class MatrixFederationHttpClient:
for key, sig in request["signatures"][self.server_name].items(): for key, sig in request["signatures"][self.server_name].items():
auth_headers.append( auth_headers.append(
( (
'X-Matrix origin=%s,key="%s",sig="%s"' 'X-Matrix origin=%s,key="%s",sig="%s",destination="%s"'
% (self.server_name, key, sig) % (
self.server_name,
key,
sig,
request.get("destination") or request["destination_is"],
)
).encode("ascii") ).encode("ascii")
) )
return auth_headers return auth_headers