X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fsalmon%2Fsalmon.ex;h=ab0f97d5544e10dd059c799fc3871449e3c83a15;hb=83d4a1495ce530b6b308bb47668268d68a8fb3e2;hp=24b5eb0d9664426a3c12bc33856845f162ebefa5;hpb=57bd59e4071adf847f94229479e5ffa0951721fd;p=akkoma diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 24b5eb0d9..ab0f97d55 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -1,8 +1,14 @@ defmodule Pleroma.Web.Salmon do + @httpoison Application.get_env(:pleroma, :httpoison) + use Bitwise + alias Pleroma.Web.XML + alias Pleroma.Web.OStatus.ActivityRepresenter + alias Pleroma.User + require Logger def decode(salmon) do - {doc, _rest} = :xmerl_scan.string(to_charlist(salmon)) + doc = XML.parse_document(salmon) {:xmlObj, :string, data} = :xmerl_xpath.string('string(//me:data[1])', doc) {:xmlObj, :string, sig} = :xmerl_xpath.string('string(//me:sig[1])', doc) @@ -10,7 +16,6 @@ defmodule Pleroma.Web.Salmon do {: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) @@ -21,22 +26,13 @@ defmodule Pleroma.Web.Salmon do 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 + with [data, _, _, _, _] <- decode(salmon), + doc <- XML.parse_document(data), + uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc), + {:ok, public_key} <- User.get_public_key_for_ap_id(uri), + magic_key <- encode_key(public_key) do + {:ok, magic_key} + end end def decode_and_validate(magickey, salmon) do @@ -65,7 +61,7 @@ defmodule Pleroma.Web.Salmon do [modulus, exponent] = magickey |> String.split(".") - |> Enum.map(&Base.url_decode64!/1) + |> Enum.map(fn (n) -> Base.url_decode64!(n, padding: false) end) |> Enum.map(make_integer) {:RSAPublicKey, modulus, exponent} @@ -78,17 +74,30 @@ defmodule Pleroma.Web.Salmon do "RSA.#{modulus_enc}.#{exponent_enc}" end - def generate_rsa_pem do - port = Port.open({:spawn, "openssl genrsa"}, [:binary]) - {:ok, pem} = receive do - {^port, {:data, pem}} -> {:ok, pem} - end - Port.close(port) - if Regex.match?(~r/RSA PRIVATE KEY/, pem) do + # Native generation of RSA keys is only available since OTP 20+ and in default build conditions + # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. + try do + _ = :public_key.generate_key({:rsa, 2048, 65537}) + def generate_rsa_pem do + key = :public_key.generate_key({:rsa, 2048, 65537}) + entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) + pem = :public_key.pem_encode([entry]) |> String.trim_trailing {:ok, pem} - else - :error end + rescue + _ -> + def generate_rsa_pem do + port = Port.open({:spawn, "openssl genrsa"}, [:binary]) + {:ok, pem} = receive do + {^port, {:data, pem}} -> {:ok, pem} + end + Port.close(port) + if Regex.match?(~r/RSA PRIVATE KEY/, pem) do + {:ok, pem} + else + :error + end + end end def keys_from_pem(pem) do @@ -108,8 +117,13 @@ defmodule Pleroma.Web.Salmon do |> Enum.map(&Base.url_encode64/1) |> Enum.join(".") - signature = :public_key.sign(signed_text, :sha256, private_key) |> to_string |> Base.url_encode64 - doc_base64= doc |> Base.url_encode64 + signature = signed_text + |> :public_key.sign(:sha256, private_key) + |> to_string + |> Base.url_encode64 + + doc_base64 = doc + |> Base.url_encode64 # Don't need proper xml building, these strings are safe to leave unescaped salmon = """ @@ -124,4 +138,52 @@ defmodule Pleroma.Web.Salmon do {:ok, salmon} end + + def remote_users(%{data: %{"to" => to} = data}) do + to = to ++ (data["cc"] || []) + to + |> Enum.map(fn(id) -> User.get_cached_by_ap_id(id) end) + |> Enum.filter(fn(user) -> user && !user.local end) + end + + defp send_to_user(%{info: %{"salmon" => salmon}}, feed, poster) do + with {:ok, %{status_code: code}} <- poster.(salmon, feed, [{"Content-Type", "application/magic-envelope+xml"}], timeout: 10000, recv_timeout: 20000, hackney: [pool: :default]) do + Logger.debug(fn -> "Pushed to #{salmon}, code #{code}" end) + else + e -> Logger.debug(fn -> "Pushing Salmon to #{salmon} failed, #{inspect(e)}" end) + end + end + + defp send_to_user(_,_,_), do: nil + + @supported_activities [ + "Create", + "Follow", + "Like", + "Announce", + "Undo", + "Delete" + ] + def publish(user, activity, poster \\ &@httpoison.post/4) + def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster) when type in @supported_activities do + feed = ActivityRepresenter.to_simple_form(activity, user, true) + |> ActivityRepresenter.wrap_with_entry + |> :xmerl.export_simple(:xmerl_xml) + |> to_string + + if feed do + {:ok, private, _} = keys_from_pem(keys) + {:ok, feed} = encode(private, feed) + + remote_users(activity) + |> Enum.each(fn(remote_user) -> + Task.start(fn -> + Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end) + send_to_user(remote_user, feed, poster) + end) + end) + end + end + + def publish(%{id: id}, _, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end) end