initial commit
This commit is contained in:
commit
fbd7bfb178
16 changed files with 637 additions and 0 deletions
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
http_signatures-*.tar
|
||||||
|
|
165
LICENSE
Normal file
165
LICENSE
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
20
README.md
Normal file
20
README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# HttpSignatures
|
||||||
|
|
||||||
|
**Elixir library for manipulating and validating HTTP signatures.**
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||||
|
by adding `http_signatures` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
def deps do
|
||||||
|
[
|
||||||
|
{:http_signatures, "~> 0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||||
|
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||||
|
be found at [https://hexdocs.pm/http_signatures](https://hexdocs.pm/http_signatures).
|
7
config/config.exs
Normal file
7
config/config.exs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
config :http_signatures, adapter: HTTPSignatures.NullAdapter
|
||||||
|
|
||||||
|
if Mix.env() == :test do
|
||||||
|
config :http_signatures, adapter: HTTPSignatures.TestAdapter
|
||||||
|
end
|
29
lib/http_signatures/adapter.ex
Normal file
29
lib/http_signatures/adapter.ex
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule HTTPSignatures.Adapter do
|
||||||
|
@moduledoc """
|
||||||
|
Contract for HTTPSignatures adapters.
|
||||||
|
|
||||||
|
Projects making use of the HTTPSignatures library use an adapter in order
|
||||||
|
to provide and refresh the keys used to validate signatures.
|
||||||
|
|
||||||
|
To set the adapter in your project, use the config system:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :http_signatures, adapter: YourAdapter
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Fetch a public key, given a `Plug.Conn` structure.
|
||||||
|
"""
|
||||||
|
@callback fetch_public_key(Plug.Conn.t()) :: {:ok, any()} | {:error, any()}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Refetch a public key, given a `Plug.Conn` structure.
|
||||||
|
Called when the initial key supplied failed to validate the signature.
|
||||||
|
"""
|
||||||
|
@callback refetch_public_key(Plug.Conn.t()) :: {:ok, any()} | {:error, any()}
|
||||||
|
end
|
81
lib/http_signatures/http_signatures.ex
Normal file
81
lib/http_signatures/http_signatures.ex
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
|
||||||
|
# https://tools.ietf.org/html/draft-cavage-http-signatures-08
|
||||||
|
defmodule HTTPSignatures do
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def split_signature(sig) do
|
||||||
|
default = %{"headers" => "date"}
|
||||||
|
|
||||||
|
sig =
|
||||||
|
sig
|
||||||
|
|> String.trim()
|
||||||
|
|> String.split(",")
|
||||||
|
|> Enum.reduce(default, fn part, acc ->
|
||||||
|
[key | rest] = String.split(part, "=")
|
||||||
|
value = Enum.join(rest, "=")
|
||||||
|
Map.put(acc, key, String.trim(value, "\""))
|
||||||
|
end)
|
||||||
|
|
||||||
|
Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate(headers, signature, public_key) do
|
||||||
|
sigstring = build_signing_string(headers, signature["headers"])
|
||||||
|
Logger.debug("Signature: #{signature["signature"]}")
|
||||||
|
Logger.debug("Sigstring: #{sigstring}")
|
||||||
|
{:ok, sig} = Base.decode64(signature["signature"])
|
||||||
|
:public_key.verify(sigstring, :sha256, sig, public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_conn(conn) do
|
||||||
|
adapter = Application.get_env(:http_signatures, :adapter)
|
||||||
|
|
||||||
|
with {:ok, public_key} <- adapter.fetch_public_key(conn) do
|
||||||
|
if validate_conn(conn, public_key) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
Logger.debug("Could not validate, trying to refetch any relevant keys")
|
||||||
|
|
||||||
|
with {:ok, public_key} <- adapter.refetch_public_key(conn) do
|
||||||
|
validate_conn(conn, public_key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.debug("Could not validate against known public keys: #{inspect(e)}")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_conn(conn, public_key) do
|
||||||
|
headers = Enum.into(conn.req_headers, %{})
|
||||||
|
signature = split_signature(headers["signature"])
|
||||||
|
validate(headers, signature, public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_signing_string(headers, used_headers) do
|
||||||
|
used_headers
|
||||||
|
|> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
|
||||||
|
|> Enum.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign(private_key, key_id, headers) do
|
||||||
|
sigstring = build_signing_string(headers, Map.keys(headers))
|
||||||
|
|
||||||
|
signature =
|
||||||
|
:public_key.sign(sigstring, :sha256, private_key)
|
||||||
|
|> Base.encode64()
|
||||||
|
|
||||||
|
[
|
||||||
|
keyId: key_id,
|
||||||
|
algorithm: "rsa-sha256",
|
||||||
|
headers: Map.keys(headers) |> Enum.join(" "),
|
||||||
|
signature: signature
|
||||||
|
]
|
||||||
|
|> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
|
||||||
|
|> Enum.join(",")
|
||||||
|
end
|
||||||
|
end
|
33
mix.exs
Normal file
33
mix.exs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule HttpSignatures.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :http_signatures,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.7",
|
||||||
|
elixirc_options: [warnings_as_errors: true],
|
||||||
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help deps" to learn about dependencies.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
|
defp elixirc_paths(_), do: ["lib"]
|
||||||
|
end
|
9
test/admin@mastodon.example.org.key
Normal file
9
test/admin@mastodon.example.org.key
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW
|
||||||
|
OU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG
|
||||||
|
NX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy
|
||||||
|
I4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+
|
||||||
|
ZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz
|
||||||
|
NqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry
|
||||||
|
BwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
191
test/http_signatures_test.exs
Normal file
191
test/http_signatures_test.exs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
|
||||||
|
# Test data from https://tools.ietf.org/html/draft-cavage-http-signatures-08#appendix-C
|
||||||
|
defmodule HttpSignaturesTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
@public_key hd(:public_key.pem_decode(File.read!("test/public.key")))
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
@private_key hd(:public_key.pem_decode(File.read!("test/private.key")))
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
@headers %{
|
||||||
|
"(request-target)" => "post /foo?param=value&pet=dog",
|
||||||
|
"host" => "example.com",
|
||||||
|
"date" => "Thu, 05 Jan 2014 21:31:40 GMT",
|
||||||
|
"content-type" => "application/json",
|
||||||
|
"digest" => "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
|
||||||
|
"content-length" => "18"
|
||||||
|
}
|
||||||
|
|
||||||
|
@default_signature """
|
||||||
|
keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w="
|
||||||
|
"""
|
||||||
|
|
||||||
|
@basic_signature """
|
||||||
|
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="HUxc9BS3P/kPhSmJo+0pQ4IsCo007vkv6bUm4Qehrx+B1Eo4Mq5/6KylET72ZpMUS80XvjlOPjKzxfeTQj4DiKbAzwJAb4HX3qX6obQTa00/qPDXlMepD2JtTw33yNnm/0xV7fQuvILN/ys+378Ysi082+4xBQFwvhNvSoVsGv4="
|
||||||
|
"""
|
||||||
|
|
||||||
|
@all_headers_signature """
|
||||||
|
keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0="
|
||||||
|
"""
|
||||||
|
|
||||||
|
test "split up a signature" do
|
||||||
|
expected = %{
|
||||||
|
"keyId" => "Test",
|
||||||
|
"algorithm" => "rsa-sha256",
|
||||||
|
"signature" =>
|
||||||
|
"jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=",
|
||||||
|
"headers" => ["date"]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert HTTPSignatures.split_signature(@default_signature) == expected
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates the default case" do
|
||||||
|
signature = HTTPSignatures.split_signature(@default_signature)
|
||||||
|
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates the basic case" do
|
||||||
|
signature = HTTPSignatures.split_signature(@basic_signature)
|
||||||
|
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validates the all-headers case" do
|
||||||
|
signature = HTTPSignatures.split_signature(@all_headers_signature)
|
||||||
|
assert HTTPSignatures.validate(@headers, signature, @public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it contructs a signing string" do
|
||||||
|
expected = "date: Thu, 05 Jan 2014 21:31:40 GMT\ncontent-length: 18"
|
||||||
|
assert expected == HTTPSignatures.build_signing_string(@headers, ["date", "content-length"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it validates a conn" do
|
||||||
|
public_key_pem =
|
||||||
|
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGb42rPZIapY4Hfhxrgn\nxKVJczBkfDviCrrYaYjfGxawSw93dWTUlenCVTymJo8meBlFgIQ70ar4rUbzl6GX\nMYvRdku072d1WpglNHXkjKPkXQgngFDrh2sGKtNB/cEtJcAPRO8OiCgPFqRtMiNM\nc8VdPfPdZuHEIZsJ/aUM38EnqHi9YnVDQik2xxDe3wPghOhqjxUM6eLC9jrjI+7i\naIaEygUdyst9qVg8e2FGQlwAeS2Eh8ygCxn+bBlT5OyV59jSzbYfbhtF2qnWHtZy\nkL7KOOwhIfGs7O9SoR2ZVpTEQ4HthNzainIe/6iCR5HGrao/T8dygweXFYRv+k5A\nPQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
|
||||||
|
[public_key] = :public_key.pem_decode(public_key_pem)
|
||||||
|
|
||||||
|
public_key =
|
||||||
|
public_key
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
conn = %{
|
||||||
|
req_headers: [
|
||||||
|
{"host", "localtesting.pleroma.lol"},
|
||||||
|
{"connection", "close"},
|
||||||
|
{"content-length", "2316"},
|
||||||
|
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
||||||
|
{"date", "Sun, 10 Dec 2017 14:23:49 GMT"},
|
||||||
|
{"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="},
|
||||||
|
{"content-type", "application/activity+json"},
|
||||||
|
{"(request-target)", "post /users/demiurge/inbox"},
|
||||||
|
{"signature",
|
||||||
|
"keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\""}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert HTTPSignatures.validate_conn(conn, public_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it validates a conn and fetches the key" do
|
||||||
|
conn = %{
|
||||||
|
params: %{"actor" => "http://mastodon.example.org/users/admin"},
|
||||||
|
req_headers: [
|
||||||
|
{"host", "localtesting.pleroma.lol"},
|
||||||
|
{"x-forwarded-for", "127.0.0.1"},
|
||||||
|
{"connection", "close"},
|
||||||
|
{"content-length", "2307"},
|
||||||
|
{"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"},
|
||||||
|
{"date", "Sun, 11 Feb 2018 17:12:01 GMT"},
|
||||||
|
{"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="},
|
||||||
|
{"content-type", "application/activity+json"},
|
||||||
|
{"signature",
|
||||||
|
"keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\""},
|
||||||
|
{"(request-target)", "post /users/demiurge/inbox"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert HTTPSignatures.validate_conn(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validate this" do
|
||||||
|
conn = %{
|
||||||
|
params: %{"actor" => "https://niu.moe/users/rye"},
|
||||||
|
req_headers: [
|
||||||
|
{"x-forwarded-for", "149.202.73.191"},
|
||||||
|
{"host", "testing.pleroma.lol"},
|
||||||
|
{"x-cluster-client-ip", "149.202.73.191"},
|
||||||
|
{"connection", "upgrade"},
|
||||||
|
{"content-length", "2396"},
|
||||||
|
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
||||||
|
{"date", "Sun, 18 Feb 2018 20:31:51 GMT"},
|
||||||
|
{"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="},
|
||||||
|
{"content-type", "application/activity+json"},
|
||||||
|
{"signature",
|
||||||
|
"keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\""},
|
||||||
|
{"(request-target)", "post /inbox"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert HTTPSignatures.validate_conn(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "validate this too" do
|
||||||
|
conn = %{
|
||||||
|
params: %{"actor" => "https://niu.moe/users/rye"},
|
||||||
|
req_headers: [
|
||||||
|
{"x-forwarded-for", "149.202.73.191"},
|
||||||
|
{"host", "testing.pleroma.lol"},
|
||||||
|
{"x-cluster-client-ip", "149.202.73.191"},
|
||||||
|
{"connection", "upgrade"},
|
||||||
|
{"content-length", "2342"},
|
||||||
|
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"},
|
||||||
|
{"date", "Sun, 18 Feb 2018 21:44:46 GMT"},
|
||||||
|
{"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="},
|
||||||
|
{"content-type", "application/activity+json"},
|
||||||
|
{"signature",
|
||||||
|
"keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\""},
|
||||||
|
{"(request-target)", "post /inbox"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert HTTPSignatures.validate_conn(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it generates a signature" do
|
||||||
|
assert HTTPSignatures.sign(
|
||||||
|
@private_key,
|
||||||
|
"http://mastodon.example.org/users/admin#main-key",
|
||||||
|
%{host: "mastodon.example.org"}
|
||||||
|
) =~ "keyId=\""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "this too" do
|
||||||
|
conn = %{
|
||||||
|
params: %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"},
|
||||||
|
req_headers: [
|
||||||
|
{"host", "soc.canned-death.us"},
|
||||||
|
{"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"},
|
||||||
|
{"date", "Sun, 11 Mar 2018 12:19:36 GMT"},
|
||||||
|
{"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="},
|
||||||
|
{"content-type", "application/activity+json"},
|
||||||
|
{"signature",
|
||||||
|
"keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""},
|
||||||
|
{"x-forwarded-for", "2607:5300:203:2899::31:1337"},
|
||||||
|
{"x-forwarded-host", "soc.canned-death.us"},
|
||||||
|
{"x-forwarded-server", "soc.canned-death.us"},
|
||||||
|
{"connection", "Keep-Alive"},
|
||||||
|
{"content-length", "2006"},
|
||||||
|
{"(request-target)", "post /inbox"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert HTTPSignatures.validate_conn(conn)
|
||||||
|
end
|
||||||
|
end
|
9
test/lucifermysticus@mst3k.interlinked.me.key
Normal file
9
test/lucifermysticus@mst3k.interlinked.me.key
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2+Q4KKYrzM7K/IglGGq
|
||||||
|
QrpQmgs1ZhxE6hI4y5Jsv2b9FWxaVOWgf0YcDG2XO4wZoBmcyqJTaKUFnMCwasaC
|
||||||
|
N2YbzUgMWk3krT2yYZMQD6rbHPb8c3Ou/iI84UbLrFeCBu9RgnzPXcvRyH2ktjmZ
|
||||||
|
pSTvTcys+QARa//NNVS+nns4ZkZbHF9hQm4+kEOpotzOgMratVSTteQVfIwziwHh
|
||||||
|
7SZIfLYCgamBsF7wu7DtEDh317SYiyoZD9cj+kF0eXs/tT8oY+yVUYOAtbhkDgcj
|
||||||
|
BuK9TKEmIKGTTDeLYCsXh5B8Attb9+A/9ZUmBc0/DgrMtXSI2daGPiRBeAbH7BZP
|
||||||
|
KwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
15
test/private.key
Normal file
15
test/private.key
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
|
||||||
|
NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
|
||||||
|
UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
|
||||||
|
AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
|
||||||
|
QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
|
||||||
|
kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
|
||||||
|
f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
|
||||||
|
412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
|
||||||
|
mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
|
||||||
|
kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
|
||||||
|
gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
|
||||||
|
G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
|
||||||
|
7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
6
test/public.key
Normal file
6
test/public.key
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
|
||||||
|
6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
|
||||||
|
Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
|
||||||
|
oYi+1hqp1fIekaxsyQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
9
test/rye@niu.moe.key
Normal file
9
test/rye@niu.moe.key
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv
|
||||||
|
EL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk
|
||||||
|
KM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA
|
||||||
|
TNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q
|
||||||
|
xfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG
|
||||||
|
C65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9
|
||||||
|
TwIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
34
test/support/test_adapter.ex
Normal file
34
test/support/test_adapter.ex
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule HTTPSignatures.TestAdapter do
|
||||||
|
@behaviour HTTPSignatures.Adapter
|
||||||
|
|
||||||
|
@mastodon_admin_pubkey hd(
|
||||||
|
:public_key.pem_decode(
|
||||||
|
File.read!("test/admin@mastodon.example.org.key")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
@rye_pubkey hd(:public_key.pem_decode(File.read!("test/rye@niu.moe.key")))
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
@lm_pubkey hd(
|
||||||
|
:public_key.pem_decode(File.read!("test/lucifermysticus@mst3k.interlinked.me.key"))
|
||||||
|
)
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
def fetch_public_key(_), do: {:ok, @mastodon_admin_pubkey}
|
||||||
|
|
||||||
|
def refetch_public_key(%{params: %{"actor" => "https://niu.moe/users/rye"}}),
|
||||||
|
do: {:ok, @rye_pubkey}
|
||||||
|
|
||||||
|
def refetch_public_key(%{
|
||||||
|
params: %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"}
|
||||||
|
}),
|
||||||
|
do: {:ok, @lm_pubkey}
|
||||||
|
|
||||||
|
def refetch_public_key(_), do: {:error, "no public key found"}
|
||||||
|
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
Loading…
Reference in a new issue