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
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -26,3 +26,6 @@ matrix_server-*.tar
|
|||
# we ignore priv/static. You may want to comment
|
||||
# this depending on your deployment strategy.
|
||||
/priv/static/
|
||||
|
||||
keys/*
|
||||
!keys/.keep
|
||||
|
|
2
.tool-versions
Normal file
2
.tool-versions
Normal file
|
@ -0,0 +1,2 @@
|
|||
elixir 1.12.2-otp-24
|
||||
erlang 24.0.3
|
12
README.md
12
README.md
|
@ -9,9 +9,11 @@ Some noteworthy contributions:
|
|||
* `lib/matrix_server/state_resolution/authorization.ex`: Implementation of authorization rules for the state resolution algorithm.
|
||||
* `lib/matrix_server/room_server.ex`: A GenServer that holds and manages the state of a room.
|
||||
|
||||
To run the server in development mode, run:
|
||||
Generate the server's ed25510 keys by executing `ssh-keygen -t ed25519 -f keys/id_ed25519 -N ""`
|
||||
|
||||
* Install the latest Erlang, Elixir and Postgresql.
|
||||
* Create the database with name `matrix_server_dev` and credentials `matrix_server:matrix_server`.
|
||||
* Fetch Elixir dependencies with `mix deps.get`.
|
||||
* Run the server using `mix phx.server`.
|
||||
Dependencies:
|
||||
|
||||
* Elixir 1.12.2 compiled for OTP 24
|
||||
* Erlang 24.0.3
|
||||
* PostgreSQL
|
||||
* Libsodium
|
||||
|
|
|
@ -56,4 +56,5 @@ config :phoenix, :stacktrace_depth, 20
|
|||
# Initialize plugs at runtime for faster development compilation
|
||||
config :phoenix, :plug_init_mode, :runtime
|
||||
|
||||
config :matrix_server, :server_name, "localhost"
|
||||
config :matrix_server, server_name: "localhost"
|
||||
config :matrix_server, private_key_file: "keys/id_ed25519"
|
||||
|
|
0
keys/.keep
Normal file
0
keys/.keep
Normal file
|
@ -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
|
3
mix.exs
3
mix.exs
|
@ -43,7 +43,8 @@ defmodule MatrixServer.MixProject do
|
|||
{:plug_cowboy, "~> 2.0"},
|
||||
{:bcrypt_elixir, "~> 2.3"},
|
||||
{:cors_plug, "~> 2.0"},
|
||||
{:ex_machina, "~> 2.7", only: :test}
|
||||
{:ex_machina, "~> 2.7", only: :test},
|
||||
{:enacl, "~> 1.2"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -11,6 +11,7 @@
|
|||
"ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
|
||||
"enacl": {:hex, :enacl, "1.2.1", "7776480b9b3d42a51d66dbbcbf17fa3d79285b3d2adcb4d5b5bd0b70f0ef1949", [:rebar3], [], "hexpm", "67bbbeddd2564dc899a3dcbc3765cd6ad71629134f1e500a50ec071f0f75e552"},
|
||||
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
|
||||
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
|
||||
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
|
||||
|
|
Loading…
Reference in a new issue