Extract Keys, Actor module.
This commit is contained in:
parent
eba9a62024
commit
c1b42e3dae
|
@ -0,0 +1,48 @@
|
|||
defmodule Pleroma.Keys 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}
|
||||
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
|
||||
[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 pems_from_pem(pem) do
|
||||
{:ok, _, public_key} = keys_from_pem(pem)
|
||||
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
||||
pubkey_pem = :public_key.pem_encode([public_key])
|
||||
|
||||
{:ok, pem, pubkey_pem}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
defmodule Pleroma.Web.ActivityPub.Actor do
|
||||
alias Pleroma.Keys
|
||||
|
||||
def build(base) do
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
{:ok, privkey, pubkey} = Keys.pems_from_pem(pem)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
"id" => base,
|
||||
"inbox" => "#{base}/inbox",
|
||||
"outbox" => "#{base}/outbox",
|
||||
"following" => "#{base}/following",
|
||||
"followers" => "#{base}/followers",
|
||||
"privateKey" => %{
|
||||
"privateKeyPem" => privkey
|
||||
},
|
||||
"publicKey" => %{
|
||||
"publicKeyPem" => pubkey
|
||||
}
|
||||
}}
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Pleroma.Web.ActivityPub.UserView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Keys
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
|
@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
# the instance itself is not a Person, but instead an Application
|
||||
def render("user.json", %{user: %{nickname: nil} = user}) do
|
||||
{:ok, user} = WebFinger.ensure_keys_present(user)
|
||||
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
||||
{:ok, _, public_key} = Keys.keys_from_pem(user.info["keys"])
|
||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||
public_key = :public_key.pem_encode([public_key])
|
||||
|
||||
|
@ -40,7 +40,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|
||||
def render("user.json", %{user: user}) do
|
||||
{:ok, user} = WebFinger.ensure_keys_present(user)
|
||||
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
|
||||
{:ok, _, public_key} = Keys.keys_from_pem(user.info["keys"])
|
||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||
public_key = :public_key.pem_encode([public_key])
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ defmodule Pleroma.Web.HTTPSignatures do
|
|||
|
||||
def sign(user, headers) do
|
||||
with {:ok, %{info: %{"keys" => keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
|
||||
{:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
|
||||
{:ok, private_key, _} = Pleroma.Keys.keys_from_pem(keys) do
|
||||
sigstring = build_signing_string(headers, Map.keys(headers))
|
||||
|
||||
signature =
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Web.Salmon do
|
|||
alias Pleroma.Web.XML
|
||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Keys
|
||||
require Logger
|
||||
|
||||
def decode(salmon) do
|
||||
|
@ -76,45 +77,6 @@ defmodule Pleroma.Web.Salmon do
|
|||
"RSA.#{modulus_enc}.#{exponent_enc}"
|
||||
end
|
||||
|
||||
# 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}
|
||||
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
|
||||
[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"
|
||||
|
@ -195,7 +157,7 @@ defmodule Pleroma.Web.Salmon do
|
|||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
{:ok, private, _} = keys_from_pem(keys)
|
||||
{:ok, private, _} = Keys.keys_from_pem(keys)
|
||||
{:ok, feed} = encode(private, feed)
|
||||
|
||||
remote_users(activity)
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
alias Pleroma.{User, XmlBuilder}
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.{XML, Salmon, OStatus}
|
||||
alias Pleroma.Keys
|
||||
require Jason
|
||||
require Logger
|
||||
|
||||
|
@ -45,7 +46,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
def represent_user(user, "JSON") do
|
||||
{:ok, user} = ensure_keys_present(user)
|
||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
||||
{:ok, _private, public} = Keys.keys_from_pem(user.info["keys"])
|
||||
magic_key = Salmon.encode_key(public)
|
||||
|
||||
%{
|
||||
|
@ -83,7 +84,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
def represent_user(user, "XML") do
|
||||
{:ok, user} = ensure_keys_present(user)
|
||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info["keys"])
|
||||
{:ok, _private, public} = Keys.keys_from_pem(user.info["keys"])
|
||||
magic_key = Salmon.encode_key(public)
|
||||
|
||||
{
|
||||
|
@ -118,7 +119,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
if info["keys"] do
|
||||
{:ok, user}
|
||||
else
|
||||
{:ok, pem} = Salmon.generate_rsa_pem()
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
info = Map.put(info, "keys", pem)
|
||||
|
||||
Ecto.Changeset.change(user, info: info)
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
defmodule Pleroma.KeysTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Keys
|
||||
|
||||
test "it generates private keys" do
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
|
||||
assert pem =~ "RSA PRIVATE KEY"
|
||||
end
|
||||
|
||||
test "it gives private and public key pems for a private key pem" do
|
||||
{:ok, pem} = Keys.generate_rsa_pem()
|
||||
{:ok, private, public} = Keys.pems_from_pem(pem)
|
||||
|
||||
assert private =~ "RSA PRIVATE KEY"
|
||||
assert public =~ "RSA PUBLIC KEY"
|
||||
end
|
||||
|
||||
test "returns a public and private key from a pem" do
|
||||
pem = File.read!("test/fixtures/private_key.pem")
|
||||
{:ok, private, public} = Keys.keys_from_pem(pem)
|
||||
|
||||
assert elem(private, 0) == :RSAPrivateKey
|
||||
assert elem(public, 0) == :RSAPublicKey
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ActorTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.ActivityPub.Actor
|
||||
|
||||
test "it builds and actor, including keys and inbox, outbox, followers" do
|
||||
{:ok, user} = Actor.build("http://example.org/users/lain")
|
||||
|
||||
assert user["id"] == "http://example.org/users/lain"
|
||||
assert user["inbox"] == "http://example.org/users/lain/inbox"
|
||||
assert user["outbox"] == "http://example.org/users/lain/outbox"
|
||||
assert user["following"] == "http://example.org/users/lain/following"
|
||||
assert user["followers"] == "http://example.org/users/lain/followers"
|
||||
assert user["privateKey"]["privateKeyPem"] =~ "RSA PRIVATE KEY"
|
||||
assert user["publicKey"]["publicKeyPem"] =~ "RSA PUBLIC KEY"
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
|
|||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.{Repo, Activity, User}
|
||||
alias Pleroma.Keys
|
||||
import Pleroma.Factory
|
||||
|
||||
@magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB"
|
||||
|
@ -21,12 +22,6 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
|
|||
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)
|
||||
|
@ -38,18 +33,10 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
|
|||
_key = Salmon.decode_key(@magickey_friendica)
|
||||
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)
|
||||
{:ok, private, public} = Keys.keys_from_pem(pem)
|
||||
|
||||
# Let's try a roundtrip.
|
||||
{:ok, salmon} = Salmon.encode(private, doc)
|
||||
|
|
Loading…
Reference in New Issue