Salmon creation.
authorRoger Braun <roger@rogerbraun.net>
Wed, 26 Apr 2017 12:25:44 +0000 (14:25 +0200)
committerRoger Braun <roger@rogerbraun.net>
Wed, 26 Apr 2017 12:25:44 +0000 (14:25 +0200)
lib/pleroma/web/salmon/salmon.ex
test/fixtures/private_key.pem [new file with mode: 0644]
test/web/salmon/salmon_test.exs

index 3881f2758e0b82bcf2aff11983df6ce175bc8696..24b5eb0d9664426a3c12bc33856845f162ebefa5 100644 (file)
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.Salmon do
     end
   end
 
-  defp decode_key("RSA." <> magickey) do
+  def 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)
@@ -70,4 +70,58 @@ defmodule Pleroma.Web.Salmon do
 
     {:RSAPublicKey, modulus, exponent}
   end
+
+  def encode_key({:RSAPublicKey, modulus, exponent}) do
+    modulus_enc = :binary.encode_unsigned(modulus) |> Base.url_encode64
+    exponent_enc = :binary.encode_unsigned(exponent) |> Base.url_encode64
+
+    "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
+      {:ok, pem}
+    else
+      :error
+    end
+  end
+
+  def keys_from_pem(pem) do
+    [private_key_code] = :public_key.pem_decode(pem)
+    private_key = :public_key.pem_entry_decode(private_key_code)
+    {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
+    public_key = {:RSAPublicKey, modulus, exponent}
+    {:ok, private_key, public_key}
+  end
+
+  def encode(private_key, doc) do
+    type = "application/atom+xml"
+    encoding = "base64url"
+    alg = "RSA-SHA256"
+
+    signed_text = [doc, type, encoding, alg]
+    |> 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
+
+    # Don't need proper xml building, these strings are safe to leave unescaped
+    salmon = """
+    <?xml version="1.0" encoding="UTF-8"?>
+    <me:env xmlns:me="http://salmon-protocol.org/ns/magic-env">
+      <me:data type="application/atom+xml">#{doc_base64}</me:data>
+      <me:encoding>#{encoding}</me:encoding>
+      <me:alg>#{alg}</me:alg>
+      <me:sig>#{signature}</me:sig>
+    </me:env>
+    """
+
+    {:ok, salmon}
+  end
 end
diff --git a/test/fixtures/private_key.pem b/test/fixtures/private_key.pem
new file mode 100644 (file)
index 0000000..7a4b146
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAqnWeDtrqWasCKNXiuSq1tSCLI5H7BSvIROy5YfuGsXHrIlCq
+LdIm9QlIUUmIi9QyzgiGEDsPCCkA1UguCVgF/UrJ1+FvHcHsTELkkBu/yCl9mrgt
+WzTckhb6KjOhqtxi/TKgRaJ2Rlwz2bvH5sbCP9qffthitdxfh14KC5V0gqDt1xCy
+WgZo79vbYMcVkcQoh5uLtG64ksYFBMfgnLaSj7xg5i2qCDiIY7bqBujo5HllDqeo
+w3LXmsztt1cT8heXEjW0SYJvAHJK00OsG1kp4cqhfKzxLCHNGQJVHQxLOXy97I7o
+HOeuhbxPhjpGSBMgw7YFm3ODXviqf557eqFcaQIDAQABAoIBAC6f+VnK22sncXHF
+/zvyyL0AZ86U8XpanW7s6VA5wn/qzwwV0Fa0Mt+3aEaDvIuywSrF/hWWcegjfwzX
+r2/y2cCMomUgTopvLrk1WttoG68eWjLlydI2xVZYXpkIgmH/4juri1dAtuVL9wrJ
+aEZhe2SH4jSJ74Ya/y5BtLGycaoA9FHyIzHPTx52Ix2jWKWtKimW8J+aERi2uHdN
+7yTnLT2APhs5fnvNnn0tg85CI3Ny2GNiqmAail14yVfRz8Sf6qDIepH5Jfz9oll4
+I+GYUOLs6eTgkHXBn8LGhtHTE/9UJmb42OyWrW8X+nc/Mjz5xh0u/g1Gdp36oUMz
+OotfneECgYEA3cGfQxmxjEqSbXt9jbxiCukU7PmkDDQqBu97URC4N8qEcMF1wW7X
+AddU7Kq/UJU+oqjD/7UQHoS2ZThPtto6SpVdXQzsnrnPWQcrv5b1DV/TpXfwGoZ3
+svUIAcx4vGzhhmHDJCBsdY6n8xWBYtSqfLFXgN5UkdafLGy3EkCEtmUCgYEAxMgl
+7eU2QkWkzgJxOj6xjG2yqM3jxOvvoiRnD0rIQaBS70P/1N94ZkMXzOwddddZ5OW+
+55h/a8TmFKP/+NW4PHRYra/dazGI4IBlw6Yeq6uq/4jbuSqtBbaNn/Dz5kdHBTqM
+PtbBvc9Fztd2zb3InyyLbb4c+WjMqi0AooN027UCgYB4Tax7GJtLwsEBiDcrB4Ig
+7SYfEae/vyT1skIyTmHCUqnbCfk6QUl/hDRcWJ2FuBHM6MW8GZxvEgxpiU0lo+pv
+v+xwqKxNx/wHDm7bd6fl45DMee7WVRDnEyuO3kC56E/JOYxGMxjkBcpzg703wqvj
+Dcqs7PDwVYDw9uGykzHsSQKBgEQnNcvA+RvW1w9qlSChGgkS7S+9r0dCl8pGZVNM
+iTMBfffUS0TE6QQx9IpKtKFdpoq6b3XywR7oIO/BJSRfkOGPQi9Vm5BGpatrjNNI
+M5Mtb5n1InRtLWOvKDnez/pPcW+EKZKR+qPsp7bNtR3ovxUx7lBh6dMP0uKVl4Sx
+lsWJAoGBAIeek9eG+S3m2jaJRHasfKo5mJ2JrrmnjQXUOGUP8/CgO8sW1VmG2WAk
+Av7+BRI2mP2f+3SswG/AoRGmRXXw65ly63ws8ixrhK0MG3MgqDkWc69SbTaaMJ+u
+BQFYMsB1vZdUV3CaRqySkjY68QWGcJ4Z5JKHuTXzKv/GeFmw0V9R
+-----END RSA PRIVATE KEY-----
index 4ebb320814b21d95aba1029be7a979dd23d46f73..6fbabd19f4c423639ca4dc69c98e5534afe1da9a 100644 (file)
@@ -16,4 +16,37 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
     {:ok, salmon} = File.read("test/fixtures/salmon.xml")
     assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error
   end
+
+  test "generates an RSA private key pem" do
+    {:ok, key} = Salmon.generate_rsa_pem
+    assert is_binary(key)
+    assert Regex.match?(~r/RSA/, key)
+  end
+
+  test "it encodes a magic key from a public key" do
+    key = Salmon.decode_key(@magickey)
+    magic_key = Salmon.encode_key(key)
+
+    assert @magickey == magic_key
+  end
+
+  test "returns a public and private key from a pem" do
+    pem = File.read!("test/fixtures/private_key.pem")
+    {:ok, private, public} = Salmon.keys_from_pem(pem)
+
+    assert elem(private, 0) == :RSAPrivateKey
+    assert elem(public, 0) == :RSAPublicKey
+  end
+
+  test "encodes an xml payload with a private key" do
+    doc = File.read!("test/fixtures/incoming_note_activity.xml")
+    pem = File.read!("test/fixtures/private_key.pem")
+    {:ok, private, public} = Salmon.keys_from_pem(pem)
+
+    # Let's try a roundtrip.
+    {:ok, salmon} = Salmon.encode(private, doc)
+    {:ok, decoded_doc} = Salmon.decode_and_validate(Salmon.encode_key(public), salmon)
+
+    assert doc == decoded_doc
+  end
 end