Skip to content

Commit

Permalink
proof of concept for bonfire-networks/bonfire-app#1048
Browse files Browse the repository at this point in the history
  • Loading branch information
mayel committed Oct 18, 2024
1 parent 57fff8e commit 978f1d0
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 6 deletions.
93 changes: 93 additions & 0 deletions lib/safety/encryption.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
defmodule ActivityPub.Safety.Encryption do
@moduledoc """
Provides encryption and decryption functionality using RSA keys managed by ActivityPub.Safety.Keys.
NOTE: not used at the moment, simply intended as a proof-of-concept
"""
import Untangle

alias ActivityPub.Safety.Keys
alias ActivityPub.Actor

@doc """
Encrypts data for a given actor using their public key.
## Parameters
- data: The data to encrypt (binary or string)
- actor: The Actor struct or AP ID of the recipient
## Returns
- {:ok, encrypted_data} on success
- {:error, reason} on failure
"""
def encrypt(data, ap_id) when is_binary(ap_id) do
with {:ok, public_key} <- Keys.get_public_key_for_ap_id(ap_id) do
encrypt_with_public_key(data, public_key)
end
end

def encrypt(data, actor) do
with {:ok, public_key} <- Keys.public_key_from_data(actor) do
encrypt_with_public_key(data, public_key)
end
end

@doc """
Decrypts data for a given actor using their private key.
## Parameters
- encrypted_data: The data to decrypt (binary)
- actor: The Actor struct with private keys
## Returns
- {:ok, decrypted_data} on success
- {:error, reason} on failure
"""
def decrypt(encrypted_data, %Actor{local: true, keys: keys} = actor) when not is_nil(keys) do
with {:ok, private_key, _public_key} <- Keys.keypair_from_pem(keys) do
decrypt_with_private_key(encrypted_data, private_key)
end
end

def decrypt(encrypted_data, ap_id) when is_binary(ap_id) do
with {:ok, actor} <- Actor.get_cached(ap_id: ap_id) do
decrypt(encrypted_data, actor)
end
end

def decrypt(_encrypted_data, %Actor{local: false} = actor) do
error(actor, "Cannot perform decryption for remote actors")
end

def decrypt(_encrypted_data, actor) do
error(actor, "Could not find a private key to use for decryption")
end

# Private functions

defp encrypt_with_public_key(data, public_key) do
with {:ok, decoded_key} <- Keys.public_key_decode(public_key) do
encrypted = :public_key.encrypt_public(data, decoded_key)
{:ok, encrypted}
end
rescue
e -> error(e)
end

defp decrypt_with_private_key(encrypted_data, private_key) do
decrypted = :public_key.decrypt_private(encrypted_data, private_key)
{:ok, decrypted}
rescue
e in ErlangError ->
case e do
%ErlangError{original: {:error, _, msg}} ->
error(to_string(msg))

_ ->
error(e)
end

e ->
error(e)
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule ActivityPub.Safety.Signatures do
defmodule ActivityPub.Safety.HTTP.Signatures do
@moduledoc """
Implementation for behaviour from `HTTPSignatures` library
"""
Expand Down
4 changes: 2 additions & 2 deletions lib/safety/keys.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule ActivityPub.Safety.Keys do
alias ActivityPub.Actor
alias ActivityPub.Utils
alias ActivityPub.Safety.Keys
# alias ActivityPub.Safety.Signatures
# alias ActivityPub.Safety.HTTP.Signatures
# alias ActivityPub.Federator.Fetcher
alias ActivityPub.Federator.Adapter

Expand Down Expand Up @@ -122,7 +122,7 @@ defmodule ActivityPub.Safety.Keys do

with {:ok, pem} <- generate_rsa_pem(),
{:ok, actor} <- Adapter.update_local_actor(actor, %{keys: pem}),
{:ok, actor} <- Actor.set_cache(actor) do
{:ok, actor} <- Actor.set_cache(actor) |> debug("donz") do
{:ok, actor}
else
e -> error(e, "Could not generate or save keys")
Expand Down
82 changes: 82 additions & 0 deletions test/activity_pub/data/encryption_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
defmodule ActivityPub.Safety.EncryptionTest do
# this isn't used but simply a proof of concept
use ActivityPub.DataCase, async: false
alias ActivityPub.Safety.Encryption
alias ActivityPub.Safety.Keys
alias ActivityPub.Actor

# Import the test helpers that contain the local_actor() function
import ActivityPub.Factory

describe "encrypt/2" do
test "successfully encrypts data for a local actor" do
actor = local_actor()
{:ok, actor} = Keys.ensure_keys_present(actor.actor)

assert {:ok, encrypted} = Encryption.encrypt("secret message", actor)
assert is_binary(encrypted)
assert encrypted != "secret message"
end

test "returns error for actor without public key" do
actor = local_actor()

actor = %{actor | data: Map.delete(actor.data, "publicKey")}

assert {:error, "Public key not found"} = Encryption.encrypt("secret message", actor)
end

test "encrypts data using AP ID" do
actor = local_actor()
{:ok, actor} = Keys.ensure_keys_present(actor.actor)

assert {:ok, encrypted} = Encryption.encrypt("secret message", actor.data["id"])
assert is_binary(encrypted)
assert encrypted != "secret message"
end
end

describe "decrypt/2" do
test "successfully decrypts data for a local actor" do
actor = local_actor()
{:ok, actor} = Keys.ensure_keys_present(actor.actor)

assert {:ok, encrypted} = Encryption.encrypt("secret message", actor)
assert encrypted != "secret message"
assert {:ok, "secret message"} = Encryption.decrypt(encrypted, actor)
end

test "fails decryption for remote actor" do
actor = local_actor()
{:ok, actor} = Keys.ensure_keys_present(actor.actor)
remote_actor = %{actor | local: false}

assert {:error, "Cannot perform decryption for remote actors"} =
Encryption.decrypt("fake encrypted data", remote_actor)
end

test "fails decryption with invalid data" do
actor = local_actor()
{:ok, actor} = Keys.ensure_keys_present(actor.actor)

assert {:error, _} = Encryption.decrypt("non-encrypted data", actor)
end

test "fails decryption with someone else's keys" do
actor = local_actor()
{:ok, actor} = Keys.ensure_keys_present(actor.actor)

actor2 = local_actor()
{:ok, actor2} = Keys.ensure_keys_present(actor2.actor)

assert {:ok, encrypted} = Encryption.encrypt("secret message", actor)
assert {:error, _} = Encryption.decrypt(encrypted, actor2)
end

test "fails decryption with missing keys" do
actor = local_actor().actor

assert {:error, _} = Encryption.decrypt("non-encrypted data", actor)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule ActivityPub.Web.Plugs.MappedSignatureToIdentityPlugTest do
use ActivityPub.Web.ConnCase, async: false
alias ActivityPub.Web.Plugs.MappedSignatureToIdentityPlug
alias ActivityPub.Config
alias ActivityPub.Safety.Signatures
alias ActivityPub.Safety.HTTP.Signatures

import Tesla.Mock
import Plug.Conn
Expand Down
2 changes: 1 addition & 1 deletion test/activity_pub/safety/signatures/signature_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule ActivityPub.Safety.SignatureTest do
alias ActivityPub.Actor
alias ActivityPub.Safety.Keys
alias ActivityPub.Utils
alias ActivityPub.Safety.Signatures
alias ActivityPub.Safety.HTTP.Signatures
alias ActivityPub.Federator.Fetcher

@private_key "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA48qb4v6kqigZutO9Ot0wkp27GIF2LiVaADgxQORZozZR63jH\nTaoOrS3Xhngbgc8SSOhfXET3omzeCLqaLNfXnZ8OXmuhJfJSU6mPUvmZ9QdT332j\nfN/g3iWGhYMf/M9ftCKh96nvFVO/tMruzS9xx7tkrfJjehdxh/3LlJMMImPtwcD7\nkFXwyt1qZTAU6Si4oQAJxRDQXHp1ttLl3Ob829VM7IKkrVmY8TD+JSlV0jtVJPj6\n1J19ytKTx/7UaucYvb9HIiBpkuiy5n/irDqKLVf5QEdZoNCdojOZlKJmTLqHhzKP\n3E9TxsUjhrf4/EqegNc/j982RvOxeu4i40zMQwIDAQABAoIBAQDH5DXjfh21i7b4\ncXJuw0cqget617CDUhemdakTDs9yH+rHPZd3mbGDWuT0hVVuFe4vuGpmJ8c+61X0\nRvugOlBlavxK8xvYlsqTzAmPgKUPljyNtEzQ+gz0I+3mH2jkin2rL3D+SksZZgKm\nfiYMPIQWB2WUF04gB46DDb2mRVuymGHyBOQjIx3WC0KW2mzfoFUFRlZEF+Nt8Ilw\nT+g/u0aZ1IWoszbsVFOEdghgZET0HEarum0B2Je/ozcPYtwmU10iBANGMKdLqaP/\nj954BPunrUf6gmlnLZKIKklJj0advx0NA+cL79+zeVB3zexRYSA5o9q0WPhiuTwR\n/aedWHnBAoGBAP0sDWBAM1Y4TRAf8ZI9PcztwLyHPzfEIqzbObJJnx1icUMt7BWi\n+/RMOnhrlPGE1kMhOqSxvXYN3u+eSmWTqai2sSH5Hdw2EqnrISSTnwNUPINX7fHH\njEkgmXQ6ixE48SuBZnb4w1EjdB/BA6/sjL+FNhggOc87tizLTkMXmMtTAoGBAOZV\n+wPuAMBDBXmbmxCuDIjoVmgSlgeRunB1SA8RCPAFAiUo3+/zEgzW2Oz8kgI+xVwM\n33XkLKrWG1Orhpp6Hm57MjIc5MG+zF4/YRDpE/KNG9qU1tiz0UD5hOpIU9pP4bR/\ngxgPxZzvbk4h5BfHWLpjlk8UUpgk6uxqfti48c1RAoGBALBOKDZ6HwYRCSGMjUcg\n3NPEUi84JD8qmFc2B7Tv7h2he2ykIz9iFAGpwCIyETQsJKX1Ewi0OlNnD3RhEEAy\nl7jFGQ+mkzPSeCbadmcpYlgIJmf1KN/x7fDTAepeBpCEzfZVE80QKbxsaybd3Dp8\nCfwpwWUFtBxr4c7J+gNhAGe/AoGAPn8ZyqkrPv9wXtyfqFjxQbx4pWhVmNwrkBPi\nZ2Qh3q4dNOPwTvTO8vjghvzIyR8rAZzkjOJKVFgftgYWUZfM5gE7T2mTkBYq8W+U\n8LetF+S9qAM2gDnaDx0kuUTCq7t87DKk6URuQ/SbI0wCzYjjRD99KxvChVGPBHKo\n1DjqMuECgYEAgJGNm7/lJCS2wk81whfy/ttKGsEIkyhPFYQmdGzSYC5aDc2gp1R3\nxtOkYEvdjfaLfDGEa4UX8CHHF+w3t9u8hBtcdhMH6GYb9iv6z0VBTt4A/11HUR49\n3Z7TQ18Iyh3jAUCzFV9IJlLIExq5Y7P4B3ojWFBN607sDCt8BMPbDYs=\n-----END RSA PRIVATE KEY-----"
Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ActivityPub.Test.Helpers
# import ActivityPub.Test.Helpers
import ActivityPub.Utils

{:ok, _} = Application.ensure_all_started(:ex_machina)
Expand Down

0 comments on commit 978f1d0

Please sign in to comment.