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:
parent
ff3dd38369
commit
c5de486dba
5 changed files with 52 additions and 40 deletions
|
@ -116,4 +116,14 @@ defmodule MatrixServer do
|
||||||
|
|
||||||
Ecto.Changeset.validate_change(changeset, field, augmented_func)
|
Ecto.Changeset.validate_change(changeset, field, augmented_func)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns a Boolean whether the signature is valid.
|
||||||
|
# Also returns false on ArgumentError.
|
||||||
|
def sign_verify(sig, text, key) do
|
||||||
|
try do
|
||||||
|
:enacl.sign_verify_detached(sig, text, key)
|
||||||
|
rescue
|
||||||
|
ArgumentError -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,10 +33,16 @@ defmodule MatrixServer.ServerKeyInfo do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp refresh_signing_keys(server_name) do
|
defp refresh_signing_keys(server_name) do
|
||||||
|
# TODO: Handle expired keys.
|
||||||
in_a_week = System.os_time(:millisecond) + 1000 * 60 * 60 * 24 * 7
|
in_a_week = System.os_time(:millisecond) + 1000 * 60 * 60 * 24 * 7
|
||||||
client = FederationClient.client(server_name)
|
client = FederationClient.client(server_name)
|
||||||
|
|
||||||
with {:ok, %GetSigningKeys{verify_keys: verify_keys, valid_until_ts: valid_until}} <-
|
with {:ok,
|
||||||
|
%GetSigningKeys{
|
||||||
|
server_name: server_name,
|
||||||
|
verify_keys: verify_keys,
|
||||||
|
valid_until_ts: valid_until
|
||||||
|
}} <-
|
||||||
FederationClient.get_signing_keys(client) do
|
FederationClient.get_signing_keys(client) do
|
||||||
signing_keys =
|
signing_keys =
|
||||||
Enum.map(verify_keys, fn {key_id, %{"key" => key}} ->
|
Enum.map(verify_keys, fn {key_id, %{"key" => key}} ->
|
||||||
|
@ -47,7 +53,7 @@ defmodule MatrixServer.ServerKeyInfo do
|
||||||
ski = %ServerKeyInfo{server_name: server_name, valid_until: min(valid_until, in_a_week)}
|
ski = %ServerKeyInfo{server_name: server_name, valid_until: min(valid_until, in_a_week)}
|
||||||
|
|
||||||
case upsert_multi(server_name, ski, signing_keys) |> Repo.transaction() do
|
case upsert_multi(server_name, ski, signing_keys) |> Repo.transaction() do
|
||||||
{:ok, %{ski: ski}} -> {:ok, ski}
|
{:ok, %{new_ski: ski}} -> {:ok, ski}
|
||||||
{:error, _} -> :error
|
{:error, _} -> :error
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -63,7 +69,7 @@ defmodule MatrixServer.ServerKeyInfo do
|
||||||
conflict_target: [:server_name]
|
conflict_target: [:server_name]
|
||||||
)
|
)
|
||||||
|> Multi.insert_all(:insert_keys, SigningKey, signing_keys, on_conflict: :nothing)
|
|> Multi.insert_all(:insert_keys, SigningKey, signing_keys, on_conflict: :nothing)
|
||||||
|> Multi.run(:ski, fn _, _ ->
|
|> Multi.run(:new_ski, fn _, _ ->
|
||||||
case with_signing_keys(server_name) do
|
case with_signing_keys(server_name) do
|
||||||
nil -> {:error, :ski}
|
nil -> {:error, :ski}
|
||||||
ski -> {:ok, ski}
|
ski -> {:ok, ski}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule MatrixServerWeb.AuthenticateServer do
|
defmodule MatrixServerWeb.AuthenticateServer do
|
||||||
import MatrixServerWeb.Plug.Error
|
import MatrixServerWeb.Plug.Error
|
||||||
|
|
||||||
alias MatrixServer.SigningKey
|
alias MatrixServer.{SigningKey, ServerKeyInfo}
|
||||||
|
|
||||||
@auth_header_regex ~r/^X-Matrix origin=(?<origin>.*),key="(?<key>.*)",sig="(?<sig>.*)"$/
|
@auth_header_regex ~r/^X-Matrix origin=(?<origin>.*),key="(?<key>.*)",sig="(?<sig>.*)"$/
|
||||||
|
|
||||||
|
@ -27,18 +27,22 @@ defmodule MatrixServerWeb.AuthenticateServer do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp authenticate_with_headers(headers, object_fun) do
|
defp authenticate_with_headers(headers, object_fun) do
|
||||||
|
# TODO: Only query once per origin.
|
||||||
headers
|
headers
|
||||||
|> parse_authorization_headers()
|
|> parse_authorization_headers()
|
||||||
|> Enum.find(:error, fn {origin, _, sig} ->
|
|> Enum.find(:error, fn {origin, _, sig} ->
|
||||||
object = object_fun.(origin)
|
object = object_fun.(origin)
|
||||||
|
|
||||||
with {:ok, raw_sig} <- MatrixServer.decode_base64(sig),
|
with {:ok, raw_sig} <- MatrixServer.decode_base64(sig),
|
||||||
{:ok, encoded_object} <- MatrixServer.encode_canonical_json(object) do
|
{:ok, encoded_object} <- MatrixServer.encode_canonical_json(object),
|
||||||
# TODO: Only query once per origin.
|
{:ok, %ServerKeyInfo{signing_keys: keys}} <-
|
||||||
# TODO: Handle expired keys.
|
ServerKeyInfo.with_fresh_signing_keys(origin) do
|
||||||
SigningKey.for_server(origin)
|
Enum.find_value(keys, false, fn %SigningKey{signing_key: signing_key} ->
|
||||||
|> Enum.find_value(false, fn %SigningKey{signing_key: signing_key} ->
|
with {:ok, decoded_key} <- MatrixServer.decode_base64(signing_key) do
|
||||||
:enacl.sign_verify_detached(raw_sig, encoded_object, signing_key)
|
MatrixServer.sign_verify(raw_sig, encoded_object, decoded_key)
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
_ -> false
|
_ -> false
|
||||||
|
|
|
@ -23,7 +23,7 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
||||||
|> cast(params, [:user_id, :field])
|
|> cast(params, [:user_id, :field])
|
||||||
|> validate_required([:user_id])
|
|> validate_required([:user_id])
|
||||||
|> validate_inclusion(:field, ["displayname", "avatar_url"])
|
|> validate_inclusion(:field, ["displayname", "avatar_url"])
|
||||||
|> tap(fn
|
|> then(fn
|
||||||
%Ecto.Changeset{valid?: true} = cs -> {:ok, apply_changes(cs)}
|
%Ecto.Changeset{valid?: true} = cs -> {:ok, apply_changes(cs)}
|
||||||
_ -> :error
|
_ -> :error
|
||||||
end)
|
end)
|
||||||
|
@ -31,7 +31,7 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def profile(conn, params) do
|
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
|
if MatrixServer.get_domain(user_id) == MatrixServer.server_name() do
|
||||||
localpart = MatrixServer.get_localpart(user_id)
|
localpart = MatrixServer.get_localpart(user_id)
|
||||||
|
|
||||||
|
@ -43,10 +43,10 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
||||||
|> json(%{})
|
|> json(%{})
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
put_error(:not_found, "User does not exist.")
|
put_error(conn, :not_found, "User does not exist.")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
put_error(:not_found, "Wrong server name.")
|
put_error(conn, :not_found, "Wrong server name.")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_ -> put_error(conn, :bad_json)
|
_ -> put_error(conn, :bad_json)
|
||||||
|
|
|
@ -17,39 +17,31 @@ defmodule MatrixServerWeb.FederationClient do
|
||||||
Tesla.client([{Tesla.Middleware.BaseUrl, "http://" <> server_name} | @middleware], @adapter)
|
Tesla.client([{Tesla.Middleware.BaseUrl, "http://" <> server_name} | @middleware], @adapter)
|
||||||
end
|
end
|
||||||
|
|
||||||
@path RouteHelpers.key_path(Endpoint, :get_signing_keys)
|
|
||||||
def get_signing_keys(client) do
|
def get_signing_keys(client) do
|
||||||
# TODO: Which server_name should we take?
|
path = RouteHelpers.key_path(Endpoint, :get_signing_keys)
|
||||||
# TODO: Should probably catch enacl exceptions and just return error atom,
|
|
||||||
# create seperate function for this.
|
|
||||||
with {:ok,
|
with {:ok,
|
||||||
%GetSigningKeys{server_name: server_name, verify_keys: verify_keys, signatures: sigs} =
|
%GetSigningKeys{server_name: server_name, verify_keys: verify_keys, signatures: sigs} =
|
||||||
response} <- tesla_request(:get, client, @path, GetSigningKeys),
|
response} <- tesla_request(:get, client, path, GetSigningKeys),
|
||||||
{:ok, encoded_body} <- MatrixServer.serialize_and_encode(response) do
|
{: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.
|
# For each verify key, check if there is a matching signature.
|
||||||
# If not, invalidate the whole response.
|
# 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}} ->
|
Enum.all?(verify_keys, fn {key_id, %{"key" => key}} ->
|
||||||
with true <- Map.has_key?(server_sigs, key_id),
|
with true <- Map.has_key?(server_sigs, key_id),
|
||||||
{:ok, decoded_key} <- MatrixServer.decode_base64(key),
|
{:ok, decoded_key} <- MatrixServer.decode_base64(key),
|
||||||
{:ok, decoded_sig} <- MatrixServer.decode_base64(server_sigs[key_id]) do
|
{:ok, decoded_sig} <- MatrixServer.decode_base64(server_sigs[key_id]) do
|
||||||
:enacl.sign_verify_detached(decoded_sig, encoded_body, decoded_key)
|
MatrixServer.sign_verify(decoded_sig, encoded_body, decoded_key)
|
||||||
else
|
else
|
||||||
_ -> false
|
_ -> false
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|> then(fn
|
||||||
if all_keys_signed? do
|
true -> {:ok, response}
|
||||||
{:ok, response}
|
false -> :error
|
||||||
|
end)
|
||||||
else
|
else
|
||||||
:error
|
_ -> :error
|
||||||
end
|
|
||||||
else
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue