Add function to sign event
Add signing server to perform signing Add enacl library to perform ed25519 crypto
This commit is contained in:
parent
0c40c26bca
commit
38af22fea6
11 changed files with 124 additions and 19 deletions
|
@ -49,7 +49,7 @@ defmodule MatrixServer do
|
|||
end
|
||||
|
||||
# https://matrix.org/docs/spec/appendices#unpadded-base64
|
||||
def unpadded_base64(data) do
|
||||
def encode_unpadded_base64(data) do
|
||||
data
|
||||
|> Base.encode64()
|
||||
|> String.trim_trailing("=")
|
||||
|
|
|
@ -12,7 +12,8 @@ defmodule MatrixServer.Application do
|
|||
{Phoenix.PubSub, name: MatrixServer.PubSub},
|
||||
MatrixServerWeb.Endpoint,
|
||||
{Registry, keys: :unique, name: MatrixServer.RoomServer.Registry},
|
||||
{DynamicSupervisor, name: MatrixServer.RoomServer.Supervisor, strategy: :one_for_one}
|
||||
{DynamicSupervisor, name: MatrixServer.RoomServer.Supervisor, strategy: :one_for_one},
|
||||
MatrixServer.SigningServer
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, name: MatrixServer.Supervisor, strategy: :one_for_one)
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule MatrixServer.Event do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
alias MatrixServer.{Repo, Room, Event, Account, OrderedMap}
|
||||
alias MatrixServer.{Repo, Room, Event, Account, OrderedMap, SigningServer}
|
||||
|
||||
@schema_meta_fields [:__meta__]
|
||||
@primary_key {:event_id, :string, []}
|
||||
|
@ -15,6 +15,11 @@ defmodule MatrixServer.Event do
|
|||
field :content, :map
|
||||
field :prev_events, {:array, :string}
|
||||
field :auth_events, {:array, :string}
|
||||
# TODO: make these database fields eventually?
|
||||
field :signing_keys, :map, virtual: true, default: %{}
|
||||
field :unsigned, :map, virtual: true, default: %{}
|
||||
field :signatures, :map, virtual: true, default: %{}
|
||||
field :hashes, :map, virtual: true, default: %{}
|
||||
belongs_to :room, Room, type: :string
|
||||
end
|
||||
|
||||
|
@ -270,7 +275,16 @@ defmodule MatrixServer.Event do
|
|||
end)
|
||||
end
|
||||
|
||||
def calculate_content_hash(event) do
|
||||
def sign(event) do
|
||||
content_hash = calculate_content_hash(event)
|
||||
|
||||
event
|
||||
|> Map.put(:hashes, %{"sha256" => content_hash})
|
||||
|> redact()
|
||||
|> SigningServer.sign_event()
|
||||
end
|
||||
|
||||
defp calculate_content_hash(event) do
|
||||
result =
|
||||
event
|
||||
|> to_map()
|
||||
|
@ -281,14 +295,14 @@ defmodule MatrixServer.Event do
|
|||
case result do
|
||||
{:ok, json} ->
|
||||
:crypto.hash(:sha256, json)
|
||||
|> MatrixServer.unpadded_base64()
|
||||
|> MatrixServer.encode_unpadded_base64()
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
def redact(%Event{type: type, content: content} = event) do
|
||||
defp redact(%Event{type: type, content: content} = event) do
|
||||
redacted_event =
|
||||
event
|
||||
|> to_map()
|
||||
|
@ -313,15 +327,17 @@ defmodule MatrixServer.Event do
|
|||
%{redacted_event | content: redact_content(type, content)}
|
||||
end
|
||||
|
||||
defp redact_content("m.room.member", content), do: Map.take(["membership"])
|
||||
defp redact_content("m.room.create", content), do: Map.take(["creator"])
|
||||
defp redact_content("m.room.join_rules", content), do: Map.take(["join_rule"])
|
||||
defp redact_content("m.room.aliases", content), do: Map.take(["aliases"])
|
||||
defp redact_content("m.room.history_visibility", content), do: Map.take(["history_visibility"])
|
||||
defp redact_content("m.room.member", content), do: Map.take(content, ["membership"])
|
||||
defp redact_content("m.room.create", content), do: Map.take(content, ["creator"])
|
||||
defp redact_content("m.room.join_rules", content), do: Map.take(content, ["join_rule"])
|
||||
defp redact_content("m.room.aliases", content), do: Map.take(content, ["aliases"])
|
||||
|
||||
defp redact_content("m.room.history_visibility", content),
|
||||
do: Map.take(content, ["history_visibility"])
|
||||
|
||||
defp redact_content("m.room.power_levels", content),
|
||||
do:
|
||||
Map.take([
|
||||
Map.take(content, [
|
||||
"ban",
|
||||
"events",
|
||||
"events_default",
|
||||
|
|
78
lib/matrix_server/signing_server.ex
Normal file
78
lib/matrix_server/signing_server.ex
Normal file
|
@ -0,0 +1,78 @@
|
|||
defmodule MatrixServer.SigningServer do
|
||||
use GenServer
|
||||
|
||||
alias MatrixServer.OrderedMap
|
||||
|
||||
# TODO: only support one signing key for now.
|
||||
@signing_key_id "ed25519:1"
|
||||
|
||||
## Interface
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
end
|
||||
|
||||
def sign_event(event) do
|
||||
GenServer.call(__MODULE__, {:sign_event, event})
|
||||
end
|
||||
|
||||
## Implementation
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
{public_key, private_key} = get_keys()
|
||||
{:ok, %{public_key: public_key, private_key: private_key}}
|
||||
end
|
||||
|
||||
# https://blog.swwomm.com/2020/09/elixir-ed25519-signatures-with-enacl.html
|
||||
@impl true
|
||||
def handle_call(
|
||||
{:sign_event, event},
|
||||
_from,
|
||||
%{private_key: private_key} = state
|
||||
) do
|
||||
ordered_map =
|
||||
event
|
||||
|> Map.drop([:signatures, :unsigned])
|
||||
|> OrderedMap.from_map()
|
||||
|
||||
case Jason.encode(ordered_map) do
|
||||
{:ok, json} ->
|
||||
signature =
|
||||
json
|
||||
|> :enacl.sign_detached(private_key)
|
||||
|> MatrixServer.encode_unpadded_base64()
|
||||
|
||||
signature_map = %{@signing_key_id => signature}
|
||||
servername = MatrixServer.server_name()
|
||||
|
||||
event =
|
||||
Map.update(event, :signatures, %{servername => signature_map}, fn signatures ->
|
||||
Map.put(signatures, servername, signature_map)
|
||||
end)
|
||||
|
||||
{:reply, event, state}
|
||||
|
||||
{:error, _msg} ->
|
||||
{:reply, {:error, :json_encode}, state}
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: not sure if there is a better way to do this...
|
||||
defp get_keys do
|
||||
raw_priv_key =
|
||||
Application.get_env(:matrix_server, :private_key_file)
|
||||
|> File.read!()
|
||||
|
||||
"-----BEGIN OPENSSH PRIVATE KEY-----\n" <> rest = raw_priv_key
|
||||
|
||||
%{public: public, secret: private} =
|
||||
String.split(rest, "\n")
|
||||
|> Enum.take_while(&(&1 != "-----END OPENSSH PRIVATE KEY-----"))
|
||||
|> Enum.join()
|
||||
|> Base.decode64!()
|
||||
|> :enacl.sign_seed_keypair()
|
||||
|
||||
{public, private}
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue