akkoma/lib/pleroma/web/salmon/salmon.ex

73 lines
2.3 KiB
Elixir

defmodule Pleroma.Web.Salmon do
use Bitwise
def decode(salmon) do
{doc, _rest} = :xmerl_scan.string(to_charlist(salmon))
{:xmlObj, :string, data} = :xmerl_xpath.string('string(//me:data[1])', doc)
{:xmlObj, :string, sig} = :xmerl_xpath.string('string(//me:sig[1])', doc)
{:xmlObj, :string, alg} = :xmerl_xpath.string('string(//me:alg[1])', doc)
{:xmlObj, :string, encoding} = :xmerl_xpath.string('string(//me:encoding[1])', doc)
{:xmlObj, :string, type} = :xmerl_xpath.string('string(//me:data[1]/@type)', doc)
{:ok, data} = Base.url_decode64(to_string(data), ignore: :whitespace)
{:ok, sig} = Base.url_decode64(to_string(sig), ignore: :whitespace)
alg = to_string(alg)
encoding = to_string(encoding)
type = to_string(type)
[data, type, encoding, alg, sig]
end
def fetch_magic_key(salmon) do
[data, _, _, _, _] = decode(salmon)
{doc, _rest} = :xmerl_scan.string(to_charlist(data))
{:xmlObj, :string, uri} = :xmerl_xpath.string('string(//author[1]/uri)', doc)
uri = to_string(uri)
base = URI.parse(uri).host
# TODO: Find out if this endpoint is mandated by the standard.
{:ok, response} = HTTPoison.get(base <> "/.well-known/webfinger", ["Accept": "application/xrd+xml"], [params: [resource: uri]])
{doc, _rest} = :xmerl_scan.string(to_charlist(response.body))
{:xmlObj, :string, magickey} = :xmerl_xpath.string('string(//Link[@rel="magic-public-key"]/@href)', doc)
"data:application/magic-public-key," <> magickey = to_string(magickey)
magickey
end
def decode_and_validate(magickey, salmon) do
[data, type, encoding, alg, sig] = decode(salmon)
signed_text = [data, type, encoding, alg]
|> Enum.map(&Base.url_encode64/1)
|> Enum.join(".")
key = decode_key(magickey)
verify = :public_key.verify(signed_text, :sha256, sig, key)
if verify do
{:ok, data}
else
:error
end
end
defp decode_key("RSA." <> magickey) do
make_integer = fn(bin) ->
list = :erlang.binary_to_list(bin)
Enum.reduce(list, 0, fn (el, acc) -> (acc <<< 8) ||| el end)
end
[modulus, exponent] = magickey
|> String.split(".")
|> Enum.map(&Base.url_decode64!/1)
|> Enum.map(make_integer)
{:RSAPublicKey, modulus, exponent}
end
end