architex/lib/matrix_server/signing_server.ex

72 lines
2 KiB
Elixir

defmodule MatrixServer.SigningServer do
use GenServer
# 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_object(object) do
GenServer.call(__MODULE__, {:sign_object, object})
end
def get_signing_keys(encoded \\ false) do
GenServer.call(__MODULE__, {:get_signing_keys, encoded})
end
## Implementation
@impl true
def init(_opts) do
{public_key, private_key} = get_keys()
{:ok, %{public_key: public_key, private_key: private_key}}
end
@impl true
def handle_call({:sign_object, object}, _from, %{private_key: private_key} = state) do
case sign_object(object, private_key) do
{:ok, signature} -> {:reply, {:ok, signature, @signing_key_id}, state}
{:error, _reason} -> {:reply, :error, state}
end
end
def handle_call({:get_signing_keys, encoded}, _from, %{public_key: public_key} = state) do
result = if encoded, do: MatrixServer.encode_unpadded_base64(public_key), else: public_key
{:reply, [{@signing_key_id, result}], state}
end
# https://blog.swwomm.com/2020/09/elixir-ed25519-signatures-with-enacl.html
defp sign_object(object, private_key) do
with {:ok, json} <- MatrixServer.encode_canonical_json(object) do
signature =
json
|> :enacl.sign_detached(private_key)
|> MatrixServer.encode_unpadded_base64()
{:ok, signature}
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