Add HTTP signature verification
This commit is contained in:
parent
f3995911d7
commit
1b8f3e212c
|
@ -29,6 +29,8 @@ config :mime, :types, %{
|
|||
"application/activity+json" => ["activity+json"]
|
||||
}
|
||||
|
||||
config :http_signatures, adapter: Clacks.SignatureAdapter
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Clacks.Keys do
|
|||
{:ok, pem}
|
||||
end
|
||||
|
||||
def keys_from_pem(pem) do
|
||||
def keys_from_private_key_pem(pem) do
|
||||
with [private_key_code] <- :public_key.pem_decode(pem),
|
||||
private_key <- :public_key.pem_entry_decode(private_key_code),
|
||||
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
|
||||
|
@ -17,6 +17,16 @@ defmodule Clacks.Keys do
|
|||
end
|
||||
end
|
||||
|
||||
def key_from_pem(pem) do
|
||||
with [entry] <- :public_key.pem_decode(pem),
|
||||
key <- :public_key.pem_entry_decode(entry) do
|
||||
{:ok, key}
|
||||
else
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
def public_key_pem({:RSAPublicKey, _, _} = key) do
|
||||
entry = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, key)
|
||||
pem = :public_key.pem_encode([entry])
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
defmodule Clacks.SignatureAdapter do
|
||||
alias Clacks.{Actor, Keys}
|
||||
@behaviour HTTPSignatures.Adapter
|
||||
|
||||
def fetch_public_key(conn) do
|
||||
get_key(conn, false)
|
||||
end
|
||||
|
||||
def refetch_public_key(conn) do
|
||||
get_key(conn, true)
|
||||
end
|
||||
|
||||
defp get_key(conn, force_refetch) do
|
||||
actor_id = get_actor_id(conn)
|
||||
|
||||
case actor_id do
|
||||
nil ->
|
||||
{:error, "couldn't get actor id"}
|
||||
|
||||
_ ->
|
||||
case Actor.get_by_ap_id(actor_id, force_refetch) do
|
||||
%Actor{data: %{"publicKey" => %{"publicKeyPem" => pem}}} ->
|
||||
Keys.key_from_pem(pem)
|
||||
|
||||
_ ->
|
||||
{:error, "couldn't get pem from actor #{actor_id}"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_actor_id(conn) do
|
||||
case HTTPSignatures.signature_for_conn(conn) do
|
||||
%{"keyId" => key_id} ->
|
||||
key_id_to_actor_id(key_id)
|
||||
|
||||
_ ->
|
||||
case conn.body_params do
|
||||
%{"actor" => actor} when is_binary(actor) -> actor
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp key_id_to_actor_id(key_id) do
|
||||
%URI{URI.parse(key_id) | fragment: nil}
|
||||
|> URI.to_string()
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
defmodule ClacksWeb.InboxController do
|
||||
use ClacksWeb, :controller
|
||||
|
||||
plug Plug.Parsers, parsers: [:urlencoded, :json], json_decoder: Jason
|
||||
plug ClacksWeb.Plug.HTTPSignature
|
||||
|
||||
def shared(conn, params) do
|
||||
IO.inspect(params)
|
||||
end
|
||||
|
||||
def user_specific(conn, params) do
|
||||
IO.inspect(params)
|
||||
end
|
||||
end
|
|
@ -29,7 +29,8 @@ defmodule ClacksWeb.Endpoint do
|
|||
plug Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Phoenix.json_library()
|
||||
json_decoder: Jason,
|
||||
body_reader: {ClacksWeb.Plug.Digest, :read_body, []}
|
||||
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule ClacksWeb.Plug.Digest do
|
||||
alias Plug.Conn
|
||||
|
||||
def read_body(conn, opts) do
|
||||
{:ok, body, conn} = Conn.read_body(conn, opts)
|
||||
digest = "SHA-256=" <> Base.encode64(:crypto.hash(:sha256, body))
|
||||
{:ok, body, Conn.assign(conn, :digest, digest)}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
defmodule ClacksWeb.Plug.HTTPSignature do
|
||||
require Logger
|
||||
import Plug.Conn
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, _opts) do
|
||||
case get_req_header(conn, "signature") do
|
||||
[_signature | _] ->
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"(request-target)",
|
||||
String.downcase(conn.method) <> " " <> conn.request_path
|
||||
)
|
||||
|> case do
|
||||
%Plug.Conn{assigns: %{digest: digest}} = conn when is_binary(digest) ->
|
||||
put_req_header(conn, "digest", digest)
|
||||
|
||||
_ ->
|
||||
conn
|
||||
end
|
||||
|
||||
if HTTPSignatures.validate_conn(conn) do
|
||||
conn
|
||||
else
|
||||
Logger.debug("Could not validate signature for #{inspect(conn)}")
|
||||
|
||||
conn
|
||||
|> put_status(401)
|
||||
|> halt()
|
||||
end
|
||||
|
||||
_ ->
|
||||
Logger.debug("No signature header for #{inspect(conn)}")
|
||||
|
||||
conn
|
||||
|> put_status(401)
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,6 +23,9 @@ defmodule ClacksWeb.Router do
|
|||
get "/objects/:id", ObjectsController, :get
|
||||
get "/users/:nickname", ActorController, :get
|
||||
|
||||
post "/inbox", InboxController, :shared
|
||||
post "/users/:nickname/inbox", InboxController, :user_specific
|
||||
|
||||
get "/.well-known/webfinger", WebFingerController, :get
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule Mix.Tasks.Clacks.User do
|
|||
# password = IO.gets("Password: ") |> String.trim()
|
||||
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
{:ok, _private, public} = Keys.keys_from_pem(pem)
|
||||
{:ok, _private, public} = Keys.keys_from_private_key_pem(pem)
|
||||
{:ok, public_key_pem} = Keys.public_key_pem(public)
|
||||
|
||||
changeset = User.changeset(%User{}, %{username: username, private_key: pem})
|
||||
|
|
Loading…
Reference in New Issue