Skip to content

Commit

Permalink
Verify the origin of node public key (#126)
Browse files Browse the repository at this point in the history
* Add list of supported origin keys for the node transactions
* Fix dialyzer issue and test for node connection verification by defining real IP and different port (#123)
  • Loading branch information
Samuel committed Nov 5, 2021
1 parent 0a22d05 commit 6d2e4ec
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 58 deletions.
6 changes: 6 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ config :archethic, ArchEthic.Governance.Pools,
uniris: []
]

config :archethic, ArchEthic.Mining.PendingTransactionValidation,
allowed_node_key_origins:
System.get_env("ARCHETHIC_NODE_ALLOWED_KEY_ORIGINS", "tpm")
|> String.split(";", trim: true)
|> Enum.map(&String.to_existing_atom/1)

config :archethic,
ArchEthic.Networking.IPLookup,
(case(System.get_env("ARCHETHIC_NETWORKING_IMPL", "NAT")) do
Expand Down
29 changes: 27 additions & 2 deletions lib/archethic/crypto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule ArchEthic.Crypto do
```
Ed25519 Software Public key
| / |
| | |-------|
| | |-------|
| | |
<<0, 0, 106, 58, 193, 73, 144, 121, 104, 101, 53, 140, 125, 240, 52, 222, 35, 181,
13, 81, 241, 114, 227, 205, 51, 167, 139, 100, 176, 111, 68, 234, 206, 72>>
Expand Down Expand Up @@ -1070,7 +1070,7 @@ defmodule ArchEthic.Crypto do
end

@doc """
Return the Root CA public key for the given versioned public key
Return the Root CA public key for the given versioned public key
"""
@spec get_root_ca_public_key(key()) :: binary()
def get_root_ca_public_key(<<_::8, origin_id::8, _::binary>>) do
Expand Down Expand Up @@ -1133,4 +1133,29 @@ defmodule ArchEthic.Crypto do
"""
@spec default_curve() :: supported_curve()
def default_curve, do: Application.get_env(:archethic, __MODULE__)[:default_curve]

@doc """
Determine if the origin of the key is allowed
This prevent software keys to be used in prod, as we want secure element to prevent malicious nodes
## Examples
iex> Crypto.authorized_key_origin?(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, [:tpm])
false
iex> Crypto.authorized_key_origin?(<<0::8, 1::8, :crypto.strong_rand_bytes(32)::binary>>, [:tpm])
true
iex> Crypto.authorized_key_origin?(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>, [])
true
"""
@spec authorized_key_origin?(key(), list(supported_origin())) :: boolean()
def authorized_key_origin?(<<_::8, origin_id::8, _::binary>>, allowed_key_origins = [_ | _]) do
ID.to_origin(origin_id) in allowed_key_origins
end

def authorized_key_origin?(<<_::8, _::8, _::binary>>, []) do
true
end
end
42 changes: 26 additions & 16 deletions lib/archethic/mining/pending_transaction_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do

require Logger

@validate_connection Application.compile_env(
:archethic,
[__MODULE__, :validate_connection],
false
)

@doc """
Determines if the transaction is accepted into the network
"""
Expand Down Expand Up @@ -121,21 +115,23 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do
previous_public_key: previous_public_key
}) do
with {:ok, ip, port, _, _, key_certificate} <- Node.decode_transaction_content(content),
true <-
Crypto.authorized_key_origin?(previous_public_key, get_allowed_node_key_origins()),
root_ca_public_key <- Crypto.get_root_ca_public_key(previous_public_key),
true <- valid_connection?(@validate_connection, ip, port, previous_public_key),
true <-
Crypto.verify_key_certificate?(
previous_public_key,
key_certificate,
root_ca_public_key
) do
),
true <- valid_connection?(ip, port, previous_public_key) do
:ok
else
:error ->
{:error, "Invalid node transaction's content"}

false ->
{:error, "Invalid node transaction with invalid key certificate"}
{:error, "Invalid node transaction with invalid key / certificate"}
end
end

Expand Down Expand Up @@ -251,6 +247,12 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do

defp do_accept_transaction(_), do: :ok

defp get_allowed_node_key_origins do
:archethic
|> Application.get_env(__MODULE__, [])
|> Keyword.get(:allowed_node_key_origins, [])
end

defp get_first_public_key(tx = %Transaction{previous_public_key: previous_public_key}) do
previous_address = Transaction.previous_address(tx)

Expand All @@ -268,15 +270,23 @@ defmodule ArchEthic.Mining.PendingTransactionValidation do
end
end

defp valid_connection?(true, ip, port, previous_public_key) do
with true <- Networking.valid_ip?(ip),
false <- P2P.duplicating_node?(ip, port, previous_public_key) do
true
defp valid_connection?(ip, port, previous_public_key) do
if should_validate_connection?() do
with true <- Networking.valid_ip?(ip),
false <- P2P.duplicating_node?(ip, port, previous_public_key) do
true
else
_ ->
false
end
else
_ ->
false
true
end
end

defp valid_connection?(false, _, _, _), do: true
defp should_validate_connection? do
:archethic
|> Application.get_env(__MODULE__, [])
|> Keyword.get(:validate_connection, false)
end
end
72 changes: 36 additions & 36 deletions test/archethic/mining/distributed_workflow_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
Enum.each(BeaconChain.list_subsets(), &Registry.register(SubsetRegistry, &1, []))

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3001,
first_public_key: Crypto.first_node_public_key(),
last_public_key: Crypto.last_node_public_key(),
authorized?: true,
Expand All @@ -58,8 +58,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
{pub, _} = Crypto.generate_deterministic_keypair("seed")

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3002,
first_public_key: pub,
last_public_key: pub,
authorized?: true,
Expand All @@ -76,7 +76,7 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
tx =
Transaction.new(:node, %TransactionData{
content:
<<127, 0, 0, 1, 3000::16, 1, 0, 16, 233, 156, 172, 143, 228, 236, 12, 227, 76, 1, 80,
<<80, 10, 20, 102, 3000::16, 1, 0, 16, 233, 156, 172, 143, 228, 236, 12, 227, 76, 1, 80,
12, 236, 69, 10, 209, 6, 234, 172, 97, 188, 240, 207, 70, 115, 64, 117, 44, 82, 132,
186, byte_size(certificate)::16, certificate::binary>>
})
Expand Down Expand Up @@ -158,7 +158,7 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
)

welcome_node = %Node{
ip: {127, 0, 0, 1},
ip: {80, 10, 20, 102},
port: 3005,
first_public_key: "key1",
last_public_key: "key1",
Expand Down Expand Up @@ -188,8 +188,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
test "should aggregate context and wait enough confirmed validation nodes context building",
%{tx: tx, sorting_seed: sorting_seed} do
P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3006,
last_public_key: "other_validator_key",
first_public_key: "other_validator_key",
authorized?: true,
Expand All @@ -202,8 +202,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
})

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3007,
last_public_key: "other_validator_key2",
first_public_key: "other_validator_key2",
authorized?: true,
Expand Down Expand Up @@ -240,7 +240,7 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
end)

welcome_node = %Node{
ip: {127, 0, 0, 1},
ip: {80, 10, 20, 102},
port: 3005,
first_public_key: "key1",
last_public_key: "key1",
Expand All @@ -257,8 +257,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do

previous_storage_nodes = [
%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3006,
first_public_key: "key10",
last_public_key: "key10",
authorized?: true,
Expand All @@ -267,8 +267,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
network_patch: "AAA"
},
%Node{
ip: {127, 0, 0, 1},
port: 3002,
ip: {80, 10, 20, 102},
port: 3007,
first_public_key: "key23",
last_public_key: "key23",
authorized?: true,
Expand Down Expand Up @@ -333,7 +333,7 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
end)

welcome_node = %Node{
ip: {127, 0, 0, 1},
ip: {80, 10, 20, 102},
port: 3005,
first_public_key: "key1",
last_public_key: "key1",
Expand All @@ -352,8 +352,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do

previous_storage_nodes = [
%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3006,
first_public_key: "key10",
last_public_key: "key10",
reward_address: :crypto.strong_rand_bytes(32),
Expand All @@ -363,8 +363,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
network_patch: "AAA"
},
%Node{
ip: {127, 0, 0, 1},
port: 3002,
ip: {80, 10, 20, 102},
port: 3007,
first_public_key: "key23",
last_public_key: "key23",
reward_address: :crypto.strong_rand_bytes(32),
Expand Down Expand Up @@ -410,8 +410,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
{pub, _} = Crypto.generate_deterministic_keypair("seed3")

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3008,
last_public_key: pub,
first_public_key: pub,
authorized?: true,
Expand Down Expand Up @@ -458,7 +458,7 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
end)

welcome_node = %Node{
ip: {127, 0, 0, 1},
ip: {80, 10, 20, 102},
port: 3005,
first_public_key: "key1",
last_public_key: "key1",
Expand All @@ -485,8 +485,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do

previous_storage_nodes = [
%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3006,
first_public_key: "key10",
last_public_key: "key10",
reward_address: :crypto.strong_rand_bytes(32),
Expand All @@ -496,8 +496,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
network_patch: "AAA"
},
%Node{
ip: {127, 0, 0, 1},
port: 3002,
ip: {80, 10, 20, 102},
port: 3007,
first_public_key: "key23",
last_public_key: "key23",
reward_address: :crypto.strong_rand_bytes(32),
Expand Down Expand Up @@ -639,8 +639,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
end)

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3006,
last_public_key: "key10",
first_public_key: "key10",
network_patch: "AAA",
Expand All @@ -653,8 +653,8 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
})

P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3007,
last_public_key: "key23",
first_public_key: "key23",
network_patch: "AAA",
Expand All @@ -667,7 +667,7 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do
})

welcome_node = %Node{
ip: {127, 0, 0, 1},
ip: {80, 10, 20, 102},
port: 3005,
first_public_key: "key1",
last_public_key: "key1",
Expand All @@ -694,17 +694,17 @@ defmodule ArchEthic.Mining.DistributedWorkflowTest do

previous_storage_nodes = [
%Node{
ip: {127, 0, 0, 1},
port: 3000,
ip: {80, 10, 20, 102},
port: 3007,
first_public_key: "key10",
last_public_key: "key10",
reward_address: :crypto.strong_rand_bytes(32),
authorized?: true,
authorization_date: DateTime.utc_now()
},
%Node{
ip: {127, 0, 0, 1},
port: 3002,
ip: {80, 10, 20, 102},
port: 3008,
first_public_key: "key23",
last_public_key: "key23",
reward_address: :crypto.strong_rand_bytes(32),
Expand Down
Loading

0 comments on commit 6d2e4ec

Please sign in to comment.