2021-08-14 13:20:42 +00:00
|
|
|
defmodule MatrixServerWeb.Federation.AuthenticateServer do
|
|
|
|
import MatrixServerWeb.Error
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-13 15:36:34 +00:00
|
|
|
alias MatrixServer.{SigningKey, ServerKeyInfo}
|
2021-08-08 17:20:10 +00:00
|
|
|
|
|
|
|
@auth_header_regex ~r/^X-Matrix origin=(?<origin>.*),key="(?<key>.*)",sig="(?<sig>.*)"$/
|
|
|
|
|
2021-08-12 22:45:07 +00:00
|
|
|
def authenticate(%Plug.Conn{
|
|
|
|
body_params: body_params,
|
|
|
|
req_headers: headers,
|
|
|
|
request_path: path,
|
|
|
|
method: method,
|
|
|
|
query_string: query_string
|
|
|
|
}) do
|
2021-08-10 16:02:53 +00:00
|
|
|
object_to_sign = %{
|
|
|
|
uri: path <> "?" <> URI.decode_www_form(query_string),
|
|
|
|
method: method,
|
|
|
|
destination: MatrixServer.server_name()
|
|
|
|
}
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
object_to_sign =
|
|
|
|
if method != "GET", do: Map.put(object_to_sign, :content, body_params), else: object_to_sign
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
object_fun = &Map.put(object_to_sign, :origin, &1)
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
authenticate_with_headers(headers, object_fun)
|
2021-08-08 17:20:10 +00:00
|
|
|
end
|
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
defp authenticate_with_headers(headers, object_fun) do
|
2021-08-13 15:36:34 +00:00
|
|
|
# TODO: Only query once per origin.
|
2021-08-10 16:02:53 +00:00
|
|
|
headers
|
|
|
|
|> parse_authorization_headers()
|
|
|
|
|> Enum.find(:error, fn {origin, _, sig} ->
|
|
|
|
object = object_fun.(origin)
|
2021-08-08 17:20:10 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
with {:ok, raw_sig} <- MatrixServer.decode_base64(sig),
|
2021-08-13 15:36:34 +00:00
|
|
|
{:ok, encoded_object} <- MatrixServer.encode_canonical_json(object),
|
|
|
|
{:ok, %ServerKeyInfo{signing_keys: keys}} <-
|
|
|
|
ServerKeyInfo.with_fresh_signing_keys(origin) do
|
|
|
|
Enum.find_value(keys, false, fn %SigningKey{signing_key: signing_key} ->
|
|
|
|
with {:ok, decoded_key} <- MatrixServer.decode_base64(signing_key) do
|
|
|
|
MatrixServer.sign_verify(raw_sig, encoded_object, decoded_key)
|
|
|
|
else
|
|
|
|
_ -> false
|
|
|
|
end
|
2021-08-12 22:45:07 +00:00
|
|
|
end)
|
2021-08-08 17:20:10 +00:00
|
|
|
else
|
|
|
|
_ -> false
|
|
|
|
end
|
2021-08-10 16:02:53 +00:00
|
|
|
end)
|
2021-08-08 17:20:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def parse_authorization_headers(headers) do
|
|
|
|
headers
|
|
|
|
|> Enum.filter(&(elem(&1, 0) == "authorization"))
|
|
|
|
|> Enum.map(fn {_, auth_header} ->
|
|
|
|
Regex.named_captures(@auth_header_regex, auth_header)
|
|
|
|
end)
|
|
|
|
|> Enum.reject(&Kernel.is_nil/1)
|
2021-08-10 16:02:53 +00:00
|
|
|
|> Enum.map(fn %{"origin" => origin, "key" => key, "sig" => sig} ->
|
|
|
|
{origin, key, sig}
|
2021-08-08 17:20:10 +00:00
|
|
|
end)
|
2021-08-10 16:02:53 +00:00
|
|
|
|> Enum.filter(fn {_, key, _} -> String.starts_with?(key, "ed25519:") end)
|
2021-08-08 17:20:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defmacro __using__(opts) do
|
|
|
|
except = Keyword.get(opts, :except) || []
|
|
|
|
|
|
|
|
quote do
|
|
|
|
def action(conn, _) do
|
|
|
|
action = action_name(conn)
|
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
if action not in unquote(except) do
|
2021-08-14 13:20:42 +00:00
|
|
|
case MatrixServerWeb.Federation.AuthenticateServer.authenticate(conn) do
|
2021-08-10 16:02:53 +00:00
|
|
|
{origin, _key, _sig} ->
|
|
|
|
conn = Plug.Conn.assign(conn, :origin, origin)
|
|
|
|
apply(__MODULE__, action, [conn, conn.params])
|
2021-08-12 22:45:07 +00:00
|
|
|
|
2021-08-10 16:02:53 +00:00
|
|
|
:error ->
|
|
|
|
put_error(conn, :unauthorized, "Signature verification failed.")
|
|
|
|
end
|
2021-08-08 17:20:10 +00:00
|
|
|
else
|
|
|
|
apply(__MODULE__, action, [conn, conn.params])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|