Handle enacl exceptions during signature checks

Fix usage of undecoded signing key during server authentication
Fix several bugs in profile query endpoint
This commit is contained in:
Pim Kunis 2021-08-13 17:36:34 +02:00
parent ff3dd38369
commit c5de486dba
5 changed files with 52 additions and 40 deletions

View file

@ -1,7 +1,7 @@
defmodule MatrixServerWeb.AuthenticateServer do
import MatrixServerWeb.Plug.Error
alias MatrixServer.SigningKey
alias MatrixServer.{SigningKey, ServerKeyInfo}
@auth_header_regex ~r/^X-Matrix origin=(?<origin>.*),key="(?<key>.*)",sig="(?<sig>.*)"$/
@ -27,18 +27,22 @@ defmodule MatrixServerWeb.AuthenticateServer do
end
defp authenticate_with_headers(headers, object_fun) do
# TODO: Only query once per origin.
headers
|> parse_authorization_headers()
|> Enum.find(:error, fn {origin, _, sig} ->
object = object_fun.(origin)
with {:ok, raw_sig} <- MatrixServer.decode_base64(sig),
{:ok, encoded_object} <- MatrixServer.encode_canonical_json(object) do
# TODO: Only query once per origin.
# TODO: Handle expired keys.
SigningKey.for_server(origin)
|> Enum.find_value(false, fn %SigningKey{signing_key: signing_key} ->
:enacl.sign_verify_detached(raw_sig, encoded_object, signing_key)
{: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
end)
else
_ -> false

View file

@ -23,7 +23,7 @@ defmodule MatrixServerWeb.Federation.QueryController do
|> cast(params, [:user_id, :field])
|> validate_required([:user_id])
|> validate_inclusion(:field, ["displayname", "avatar_url"])
|> tap(fn
|> then(fn
%Ecto.Changeset{valid?: true} = cs -> {:ok, apply_changes(cs)}
_ -> :error
end)
@ -31,7 +31,7 @@ defmodule MatrixServerWeb.Federation.QueryController do
end
def profile(conn, params) do
with %ProfileRequest{user_id: user_id} <- ProfileRequest.validate(params) do
with {:ok, %ProfileRequest{user_id: user_id}} <- ProfileRequest.validate(params) do
if MatrixServer.get_domain(user_id) == MatrixServer.server_name() do
localpart = MatrixServer.get_localpart(user_id)
@ -43,10 +43,10 @@ defmodule MatrixServerWeb.Federation.QueryController do
|> json(%{})
nil ->
put_error(:not_found, "User does not exist.")
put_error(conn, :not_found, "User does not exist.")
end
else
put_error(:not_found, "Wrong server name.")
put_error(conn, :not_found, "Wrong server name.")
end
else
_ -> put_error(conn, :bad_json)

View file

@ -17,39 +17,31 @@ defmodule MatrixServerWeb.FederationClient do
Tesla.client([{Tesla.Middleware.BaseUrl, "http://" <> server_name} | @middleware], @adapter)
end
@path RouteHelpers.key_path(Endpoint, :get_signing_keys)
def get_signing_keys(client) do
# TODO: Which server_name should we take?
# TODO: Should probably catch enacl exceptions and just return error atom,
# create seperate function for this.
path = RouteHelpers.key_path(Endpoint, :get_signing_keys)
with {:ok,
%GetSigningKeys{server_name: server_name, verify_keys: verify_keys, signatures: sigs} =
response} <- tesla_request(:get, client, @path, GetSigningKeys),
{:ok, encoded_body} <- MatrixServer.serialize_and_encode(response) do
response} <- tesla_request(:get, client, path, GetSigningKeys),
{:ok, encoded_body} <- MatrixServer.serialize_and_encode(response),
server_sigs when not is_nil(server_sigs) <- sigs[server_name] do
# For each verify key, check if there is a matching signature.
# If not, invalidate the whole response.
if Map.has_key?(sigs, server_name) do
server_sigs = sigs[server_name]
all_keys_signed? =
Enum.all?(verify_keys, fn {key_id, %{"key" => key}} ->
with true <- Map.has_key?(server_sigs, key_id),
{:ok, decoded_key} <- MatrixServer.decode_base64(key),
{:ok, decoded_sig} <- MatrixServer.decode_base64(server_sigs[key_id]) do
:enacl.sign_verify_detached(decoded_sig, encoded_body, decoded_key)
else
_ -> false
end
end)
if all_keys_signed? do
{:ok, response}
Enum.all?(verify_keys, fn {key_id, %{"key" => key}} ->
with true <- Map.has_key?(server_sigs, key_id),
{:ok, decoded_key} <- MatrixServer.decode_base64(key),
{:ok, decoded_sig} <- MatrixServer.decode_base64(server_sigs[key_id]) do
MatrixServer.sign_verify(decoded_sig, encoded_body, decoded_key)
else
:error
_ -> false
end
else
:error
end
end)
|> then(fn
true -> {:ok, response}
false -> :error
end)
else
_ -> :error
end
end