diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/identifier.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/identifier.ex index cbbfeecf7a4..4fcc2cf793f 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/identity/identifier.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/identity/identifier.ex @@ -12,7 +12,7 @@ defmodule Ockam.Identity.Identifier do "I" <> Base.encode16(id, case: :lower) end - def from_str(<<"I", hex::binary-size(40)>>) do + def from_str(<<"I", hex::binary-size(64)>>) do {:ok, id} = Base.decode16(hex, case: :lower) %Identifier{id: id} end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex index 7d2f3673055..7a4bad986ca 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex @@ -35,9 +35,9 @@ defmodule Ockam.SecureChannel.Channel do alias Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm.Encryptor alias Ockam.SecureChannel.IdentityProof alias Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol, as: XX + alias Ockam.SecureChannel.Messages alias Ockam.SecureChannel.ServiceMessage alias Ockam.Session.Spawner - alias Ockam.Wire alias Ockam.Worker alias __MODULE__ @@ -512,13 +512,32 @@ defmodule Ockam.SecureChannel.Channel do with {:ok, ciphertext} <- bare_decode_strict(message.payload, :data), {:ok, plaintext, decrypt_st} <- Decryptor.decrypt("", ciphertext, channel_state.decrypt_st) do - case Wire.decode(plaintext, :secure_channel) do - {:ok, message} -> + case Messages.decode(plaintext) do + {:ok, %Messages.Payload{} = payload} -> + message = struct(Ockam.Message, Map.from_struct(payload)) + handle_decrypted_message(message, %Channel{ state | channel_state: %{channel_state | decrypt_st: decrypt_st} }) + {:ok, :close} -> + Logger.debug("Peer closed secure channel, terminating #{inspect(state.address)}") + {:stop, :normal, channel_state} + + ## TODO: add tests + {:ok, %Messages.RefreshCredentials{contact: contact, credentials: credentials}} -> + with {:ok, peer_identity, peer_identity_id} <- Identity.validate_contact_data(contact), + true <- peer_identity_id == channel_state.peer_identity_id, + :ok <- process_credentials(credentials, peer_identity_id, state.authorities) do + {:ok, + %Channel{state | channel_state: %{channel_state | peer_identity: peer_identity}}} + else + error -> + Logger.warning("Invalid credential refresh: #{inspect(error)}") + {:stop, {:error, :invalid_credential_refresh}, state} + end + {:error, reason} -> {:error, reason} end @@ -553,7 +572,10 @@ defmodule Ockam.SecureChannel.Channel do end defp attach_metadata(msg, additional, %Established{peer_identity: i, peer_identity_id: id}) do - Message.with_local_metadata(msg, Map.merge(additional, %{identity: i, identity_id: id})) + Message.with_local_metadata( + msg, + Map.merge(additional, %{identity: i, identity_id: id, channel: :secure_channel}) + ) end defp handle_outer_message_impl(message, %Channel{channel_state: %Established{} = e} = state) do @@ -576,7 +598,9 @@ defmodule Ockam.SecureChannel.Channel do end defp send_over_encrypted_channel(message, encrypt_st, peer_route, inner_address) do - with {:ok, encoded} <- Wire.encode(message), + payload = struct(Messages.Payload, Map.from_struct(message)) + + with {:ok, encoded} <- Messages.encode(payload), {:ok, ciphertext, encrypt_st} <- Encryptor.encrypt("", encoded, encrypt_st) do ciphertext = :bare.encode(ciphertext, :data) envelope = %{onward_route: peer_route, return_route: [inner_address], payload: ciphertext} diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex index 188b964800b..f220012db38 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex @@ -11,7 +11,7 @@ defmodule Ockam.SecureChannel.IdentityProof do def decode(data) do case CBOR.decode(data) do - {:ok, %{1 => change_history, 2 => attestation, 3 => credentials}, ""} -> + {:ok, [change_history, attestation, credentials], ""} -> {:ok, %IdentityProof{ contact: CBOR.encode(change_history), @@ -39,12 +39,11 @@ end defimpl CBOR.Encoder, for: Ockam.SecureChannel.IdentityProof do def encode_into(t, acc) do - %{ - 1 => t.contact, - 2 => t.attestation, - 3 => - Enum.map(t.credentials, fn c -> %Ockam.SecureChannel.IdentityProof.Credential{data: c} end) - } + [ + t.contact, + t.attestation, + Enum.map(t.credentials, fn c -> %Ockam.SecureChannel.IdentityProof.Credential{data: c} end) + ] |> CBOR.Encoder.encode_into(acc) end end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex index 4425aa1a1db..e006e43c870 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex @@ -36,7 +36,7 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do @default_prologue "" @default_payloads %{} - @protocol_name "Noise_XX_25519_AESGCM_SHA256" + @protocol_name "OCKAM_XX_25519_AES256_GCM_SHA256" defmacro zero_padded_protocol_name do quote bind_quoted: binding() do padding_size = (32 - byte_size(@protocol_name)) * 8 diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/messages.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/messages.ex new file mode 100644 index 00000000000..fa296725d50 --- /dev/null +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/messages.ex @@ -0,0 +1,75 @@ +defmodule Ockam.SecureChannel.Messages do + @moduledoc """ + Secure Channel protocol Messages + """ + alias Ockam.Address + alias Ockam.SecureChannel.Messages.RefreshCredentials + alias Ockam.TypedCBOR + + require Logger + + defmodule AddressSchema do + @moduledoc """ + Ockam Address, cbor encoding + """ + use TypedStruct + + @address_schema {:struct, + %{ + type: %{key: 1, schema: :integer, required: true}, + value: %{key: 2, schema: :charlist, required: true} + }} + def from_cbor_term(term) do + addr = TypedCBOR.from_cbor_term(@address_schema, term) + {:ok, Address.denormalize(addr)} + end + + def to_cbor_term(addr) do + {:ok, TypedCBOR.to_cbor_term(@address_schema, Address.normalize(addr))} + end + end + + defmodule Payload do + @moduledoc """ + Secure channel message carrying user data + """ + use TypedStruct + + typedstruct do + plugin(TypedCBOR.Plugin, encode_as: :list) + field(:onward_route, list(Address.t()), minicbor: [key: 0, schema: {:list, AddressSchema}]) + field(:return_route, list(Address.t()), minicbor: [key: 1, schema: {:list, AddressSchema}]) + field(:payload, binary(), minicbor: [key: 2]) + end + end + + defmodule RefreshCredentials do + @moduledoc """ + Secure channel message refreshing sender credentials + """ + defstruct [:contact, :credentials] + + def from_cbor_term([change_history, credentials]) do + {:ok, + %RefreshCredentials{ + contact: CBOR.encode(change_history), + credentials: Enum.map(credentials, fn c -> CBOR.encode(c) end) + }} + end + + def to_cbor_term(%RefreshCredentials{contact: contact, credentials: credentials}) do + {:ok, [contact, credentials]} + end + end + + @enum_schema {:enum, + [{Ockam.SecureChannel.Messages.Payload, 0}, RefreshCredentials: 1, close: 2]} + + def decode(encoded) do + TypedCBOR.decode_strict(@enum_schema, encoded) + end + + def encode(msg) do + TypedCBOR.encode(@enum_schema, msg) + end +end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/transport/tcp/listener.ex b/implementations/elixir/ockam/ockam/lib/ockam/transport/tcp/listener.ex index 95f275accd4..ec34fd55491 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/transport/tcp/listener.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/transport/tcp/listener.ex @@ -19,9 +19,10 @@ if Code.ensure_loaded?(:ranch) do ip = Keyword.get_lazy(options, :ip, &default_ip/0) port = Keyword.get_lazy(options, :port, &default_port/0) + ref = Keyword.get_lazy(options, :ref, &make_ref/0) + handler_options = Keyword.get(options, :handler_options, []) - ref = make_ref() transport = :ranch_tcp transport_options = :ranch.normalize_opts(port: port, ip: ip) protocol = Ockam.Transport.TCP.Handler @@ -44,5 +45,11 @@ if Code.ensure_loaded?(:ranch) do def default_ip, do: {0, 0, 0, 0} def default_port, do: 4000 + + # Get the port used by the listener 'ref'. Used on tests cases to listen on random + # unused ports. Only really useful when the ref is passed explicitly at creation time. + def get_port(ref) do + {:ok, :ranch.get_port(ref)} + end end end diff --git a/implementations/elixir/ockam/ockam/test/ockam/identity_test.exs b/implementations/elixir/ockam/ockam/test/ockam/identity_test.exs new file mode 100644 index 00000000000..9a66219c3ad --- /dev/null +++ b/implementations/elixir/ockam/ockam/test/ockam/identity_test.exs @@ -0,0 +1,34 @@ +defmodule Ockam.Identity.Tests do + use ExUnit.Case, async: true + doctest Ockam.Identity + alias Ockam.Identity + + @existing_secret <<113, 128, 116, 134, 152, 127, 151, 216, 104, 48, 23, 185, 143, 220, 176, 241, + 25, 192, 247, 167, 161, 220, 6, 214, 165, 141, 125, 201, 237, 157, 87, 22>> + @existing_identity <<129, 130, 88, 55, 131, 1, 1, 88, 50, 133, 246, 130, 0, 129, 88, 32, 109, + 21, 101, 217, 183, 252, 195, 8, 12, 242, 10, 36, 37, 165, 233, 80, 220, + 197, 241, 233, 250, 137, 65, 151, 124, 194, 152, 168, 101, 7, 48, 113, 244, + 26, 101, 86, 63, 23, 26, 120, 34, 66, 23, 130, 0, 129, 88, 64, 117, 93, + 149, 142, 37, 229, 76, 223, 234, 124, 175, 116, 136, 206, 48, 67, 95, 15, + 226, 107, 78, 127, 9, 140, 165, 1, 50, 122, 246, 176, 76, 158, 45, 196, + 151, 216, 148, 237, 113, 222, 72, 162, 7, 59, 126, 108, 122, 209, 121, 133, + 147, 62, 138, 249, 186, 146, 249, 74, 88, 173, 176, 136, 148, 10>> + @expected_identifier "I10253701dafcc65a621ad9fb4097cb327c541de78827713320b749cbbdbd2e9f" + + describe "Ockam.Identity.import/2" do + test "existing identity can be imported and used" do + {:ok, identity, identifier} = Identity.import(@existing_identity, @existing_secret) + assert Ockam.Identity.Identifier.to_str(identifier) == @expected_identifier + {:ok, keypair} = Ockam.SecureChannel.Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(identity, keypair) + {:ok, true} = Identity.verify_purpose_key_attestation(identity, keypair.public, attestation) + end + end + + describe "Ockam.Identity.create/1" do + test "identity can be created with explicit key" do + {_pub, secret} = :crypto.generate_key(:eddsa, :ed25519) + {:ok, _identity} = Identity.create(secret) + end + end +end diff --git a/implementations/elixir/ockam/ockam/test/ockam/router_test.exs b/implementations/elixir/ockam/ockam/test/ockam/router_test.exs index 1246b96fa75..1605e4fb54d 100644 --- a/implementations/elixir/ockam/ockam/test/ockam/router_test.exs +++ b/implementations/elixir/ockam/ockam/test/ockam/router_test.exs @@ -156,9 +156,12 @@ defmodule Ockam.Router.Tests do end test "Simple TCP Test", %{printer_pid: printer} do + assert {:ok, _pid} = TCP.start(listen: [port: 0, ref: :listener]) + {:ok, lport} = TCP.Listener.get_port(:listener) + message = %{ onward_route: [ - TCPAddress.new({127, 0, 0, 1}, 4000), + TCPAddress.new({127, 0, 0, 1}, lport), "printer" ], return_route: [], @@ -167,8 +170,6 @@ defmodule Ockam.Router.Tests do :erlang.trace(printer, true, [:receive]) - assert {:ok, _address_b} = TCP.start(listen: [port: 4000]) - Ockam.Router.route(message) assert_receive({:trace, ^printer, :receive, result}, 1_000) @@ -182,9 +183,12 @@ defmodule Ockam.Router.Tests do end test "Simple TCP with hostname", %{printer_pid: printer} do + assert {:ok, _pid} = TCP.start(listen: [port: 0, ref: :listener]) + {:ok, lport} = TCP.Listener.get_port(:listener) + message = %{ onward_route: [ - TCPAddress.new("localhost", 4001), + TCPAddress.new("localhost", lport), "printer" ], return_route: [], @@ -193,8 +197,6 @@ defmodule Ockam.Router.Tests do :erlang.trace(printer, true, [:receive]) - assert {:ok, _address_b} = TCP.start(listen: [port: 4001]) - Ockam.Router.route(message) assert_receive({:trace, ^printer, :receive, result}, 1_000) @@ -207,10 +209,15 @@ defmodule Ockam.Router.Tests do end test "TCP multi hop test", %{printer_pid: printer} do + assert {:ok, _address_a} = TCP.start(listen: [port: 0, ref: :listener_a]) + assert {:ok, _address_b} = TCP.start(listen: [port: 0, ref: :listener_b]) + {:ok, port_a} = TCP.Listener.get_port(:listener_a) + {:ok, port_b} = TCP.Listener.get_port(:listener_b) + message = %{ onward_route: [ - TCPAddress.new({127, 0, 0, 1}, 4002), - TCPAddress.new({127, 0, 0, 1}, 5002), + TCPAddress.new({127, 0, 0, 1}, port_a), + TCPAddress.new({127, 0, 0, 1}, port_b), "printer" ], return_route: [], @@ -219,10 +226,6 @@ defmodule Ockam.Router.Tests do :erlang.trace(printer, true, [:receive]) - assert {:ok, _address_a} = TCP.start(listen: [port: 5002]) - - assert {:ok, _address_b} = TCP.start(listen: [port: 4002]) - Ockam.Router.route(message) assert_receive({:trace, ^printer, :receive, result}, 1_000) @@ -236,6 +239,8 @@ defmodule Ockam.Router.Tests do end test "TCP echo test" do + assert {:ok, _listener_address_b} = TCP.start(listen: [port: 0, ref: :listener]) + {:ok, lport} = TCP.Listener.get_port(:listener) {:ok, "echo"} = Echo.create(address: "echo") {:ok, "client_forwarder"} = Forwarder.create(address: "client_forwarder") echo = Ockam.Node.whereis("echo") @@ -246,7 +251,7 @@ defmodule Ockam.Router.Tests do Ockam.Node.stop("client_forwarder") end) - tcp_address = TCPAddress.new({127, 0, 0, 1}, 6000) + tcp_address = TCPAddress.new({127, 0, 0, 1}, lport) # client request = %{ @@ -262,8 +267,6 @@ defmodule Ockam.Router.Tests do :erlang.trace(echo, true, [:receive]) :erlang.trace(client_forwarder, true, [:receive]) - assert {:ok, _listener_address_b} = TCP.start(listen: [port: 6000]) - Ockam.Router.route(request) assert_receive( @@ -331,6 +334,8 @@ defmodule Ockam.Router.Tests do end test "TCP ping pong test" do + assert {:ok, _listener_address_b} = TCP.start(listen: [port: 0, ref: :listener]) + {:ok, lport} = TCP.Listener.get_port(:listener) {:ok, "ping_pong_server"} = PingPong.create(address: "ping_pong_server") {:ok, "ping_pong_client"} = PingPong.create(address: "ping_pong_client") ping_pong_server = Ockam.Node.whereis("ping_pong_server") @@ -347,7 +352,7 @@ defmodule Ockam.Router.Tests do ## Initial request request = %{ onward_route: [ - TCPAddress.new({127, 0, 0, 1}, 5001), + TCPAddress.new({127, 0, 0, 1}, lport), "ping_pong_server" ], return_route: [ @@ -356,8 +361,6 @@ defmodule Ockam.Router.Tests do payload: "ping 1" } - assert {:ok, _listener_address_b} = TCP.start(listen: [port: 5001]) - Ockam.Router.route(request) assert_receive( diff --git a/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs b/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs index 0fcbdff770b..8fc00ea3368 100644 --- a/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs +++ b/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs @@ -1,103 +1,4 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol.Tests do use ExUnit.Case, async: true doctest Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol - - alias Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol - - @test_case1 %{ - initiator_static: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - initiator_ephemeral: "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", - responder_static: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", - responder_ephemeral: "4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", - message_1_payload: "", - message_1_ciphertext: "358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254", - message_2_payload: "", - message_2_ciphertext: - "64b101b1d0be5a8704bd078f9895001fc03e8e9f9522f188dd128d9846d484665393019dbd6f438795da206db0886610b26108e424142c2e9b5fd1f7ea70cde8767ce62d7e3c0e9bcefe4ab872c0505b9e824df091b74ffe10a2b32809cab21f", - message_3_payload: "", - message_3_ciphertext: - "e610eadc4b00c17708bf223f29a66f02342fbedf6c0044736544b9271821ae40e70144cecd9d265dffdc5bb8e051c3f83db32a425e04d8f510c58a43325fbc56" - } - - @test_case2 %{ - initiator_static: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - initiator_ephemeral: "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", - responder_static: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", - responder_ephemeral: "4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", - message_1_payload: "746573745f6d73675f30", - message_1_ciphertext: - "358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254746573745f6d73675f30", - message_2_payload: "746573745f6d73675f31", - message_2_ciphertext: - "64b101b1d0be5a8704bd078f9895001fc03e8e9f9522f188dd128d9846d484665393019dbd6f438795da206db0886610b26108e424142c2e9b5fd1f7ea70cde8c9f29dcec8d3ab554f4a5330657867fe4917917195c8cf360e08d6dc5f71baf875ec6e3bfc7afda4c9c2", - message_3_payload: "746573745f6d73675f32", - message_3_ciphertext: - "e610eadc4b00c17708bf223f29a66f02342fbedf6c0044736544b9271821ae40232c55cd96d1350af861f6a04978f7d5e070c07602c6b84d25a331242a71c50ae31dd4c164267fd48bd2" - } - - def do_test(test_case) do - test_case = - test_case - |> Enum.map(fn {k, v} -> {k, Base.decode16!(v, case: :lower)} end) - |> Enum.into(%{}) - - keypairs = [ - :initiator_static, - :initiator_ephemeral, - :responder_static, - :responder_ephemeral - ] - - test_case = - Enum.reduce(keypairs, test_case, fn k, test_case -> - private_key = Map.get(test_case, k) - {public_key, ^private_key} = :crypto.generate_key(:ecdh, :x25519, private_key) - %{test_case | k => %{private: private_key, public: public_key}} - end) - - {:ok, initiator_state} = - Protocol.setup(test_case.initiator_static, - ephemeral_keypair: test_case.initiator_ephemeral, - payloads: %{message1: test_case.message_1_payload, message3: test_case.message_3_payload} - ) - - {:ok, responder_state} = - Protocol.setup(test_case.responder_static, - ephemeral_keypair: test_case.responder_ephemeral, - payloads: %{message2: test_case.message_2_payload} - ) - - {:ok, message_1_ciphertext, {:continue, initiator_state}} = - Protocol.out_payload(initiator_state) - - {:ok, {:continue, responder_state}} = - Protocol.in_payload(responder_state, message_1_ciphertext) - - {:ok, message_2_ciphertext, {:continue, responder_state}} = - Protocol.out_payload(responder_state) - - {:ok, {:continue, initiator_state}} = - Protocol.in_payload(initiator_state, message_2_ciphertext) - - {:ok, message_3_ciphertext, {:complete, {k1_i, k2_i, h_i, _rs, p_i}}} = - Protocol.out_payload(initiator_state) - - {:ok, {:complete, {k1_r, k2_r, h_r, _rs, p_r}}} = - Protocol.in_payload(responder_state, message_3_ciphertext) - - assert k1_i == k1_r - assert k2_i == k2_r - assert h_i == h_r - assert message_1_ciphertext == test_case.message_1_ciphertext - assert message_2_ciphertext == test_case.message_2_ciphertext - assert message_3_ciphertext == test_case.message_3_ciphertext - assert test_case.message_1_payload == p_r.message1 - assert test_case.message_2_payload == p_i.message2 - assert test_case.message_3_payload == p_r.message3 - end - - test "test cases" do - assert do_test(@test_case1) - assert do_test(@test_case2) - end end diff --git a/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs b/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs index 9392aa82101..99f7cf6dfab 100644 --- a/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs +++ b/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs @@ -11,8 +11,6 @@ defmodule Ockam.SecureChannel.Tests do alias Ockam.SecureChannel.Crypto alias Ockam.Tests.Helpers.Echoer - @identity_impl Ockam.Identity.Stub - setup do Node.register_address("test") {:ok, alice} = Identity.create() diff --git a/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs b/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs index 9ce56b79fee..73f74717969 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs +++ b/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs @@ -7,7 +7,8 @@ defmodule Ockam.Healthcheck.Test do require Logger setup_all do - start_supervised({Ockam.Transport.TCP, [listen: [port: 4000]]}) + start_supervised({Ockam.Transport.TCP, [listen: [port: 0, ref: :listener]]}) + {:ok, lport} = Ockam.Transport.TCP.Listener.get_port(:listener) {:ok, identity} = Ockam.Identity.create() {:ok, keypair} = Ockam.SecureChannel.Crypto.generate_dh_keypair() {:ok, attestation} = Ockam.Identity.attest_purpose_key(identity, keypair) @@ -29,14 +30,14 @@ defmodule Ockam.Healthcheck.Test do Ockam.Node.stop("endpoint") end) - :ok + [tcp_port: lport] end - test "healthcheck target OK" do + test "healthcheck target OK", %{tcp_port: port} do target = %Target{ name: "target", host: "localhost", - port: 4000, + port: port, api_worker: "api", healthcheck_worker: "healthcheck" } @@ -75,11 +76,11 @@ defmodule Ockam.Healthcheck.Test do 500 end - test "healthcheck ping error" do + test "healthcheck ping error", %{tcp_port: port} do target = %Target{ name: "target", host: "localhost", - port: 4000, + port: port, api_worker: "api", healthcheck_worker: "not_healthcheck" } @@ -118,11 +119,11 @@ defmodule Ockam.Healthcheck.Test do 500 end - test "healthcheck API endpoint target OK" do + test "healthcheck API endpoint target OK", %{tcp_port: port} do target = %APIEndpointTarget{ name: "target", host: "localhost", - port: 4000, + port: port, api_worker: "api", healthcheck_worker: "endpoint", path: "/ok", @@ -162,11 +163,11 @@ defmodule Ockam.Healthcheck.Test do %{target: %{name: "target"}}} end - test "healthcheck API endpoint target Error" do + test "healthcheck API endpoint target Error", %{tcp_port: port} do target = %APIEndpointTarget{ name: "target", host: "localhost", - port: 4000, + port: port, api_worker: "api", healthcheck_worker: "endpoint", path: "/error", @@ -206,11 +207,11 @@ defmodule Ockam.Healthcheck.Test do %{target: %{name: "target"}}} end - test "healthcheck channel error" do + test "healthcheck channel error", %{tcp_port: port} do target = %Target{ name: "target", host: "localhost", - port: 4000, + port: port, api_worker: "not_api", healthcheck_worker: "healthcheck" } diff --git a/implementations/elixir/ockam/ockam_services/test/services/proxy_test.exs b/implementations/elixir/ockam/ockam_services/test/services/proxy_test.exs index 6ad236570b9..e66b57e850c 100644 --- a/implementations/elixir/ockam/ockam_services/test/services/proxy_test.exs +++ b/implementations/elixir/ockam/ockam_services/test/services/proxy_test.exs @@ -9,8 +9,6 @@ defmodule Test.Services.ProxyTest do alias Ockam.Services.Echo, as: EchoService alias Ockam.Services.Proxy - @tcp_port 5000 - ## Helper function to count TCP clients def tcp_clients() do Ockam.Node.list_addresses() @@ -53,11 +51,12 @@ defmodule Test.Services.ProxyTest do Ockam.Node.stop(echo_address) end) - {:ok, _listener} = Ockam.Transport.TCP.start(listen: [port: @tcp_port]) + {:ok, _listener} = Ockam.Transport.TCP.start(listen: [port: 0, ref: :listener]) + {:ok, lport} = Ockam.Transport.TCP.Listener.get_port(:listener) tcp_clients_count = Enum.count(tcp_clients()) - forward_route = [TCPAddress.new("localhost", @tcp_port), echo_address] + forward_route = [TCPAddress.new("localhost", lport), echo_address] {:ok, proxy_address} = Proxy.create(forward_route: forward_route) diff --git a/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor.ex b/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor.ex index e47ad42d940..76715d41edb 100644 --- a/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor.ex +++ b/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor.ex @@ -116,12 +116,22 @@ defmodule Ockam.TypedCBOR do ...> %{a1: "aa", a2: :a})) %{a1: "aa", a2: :a} + iex> to_cbor_term({:struct_values, %{a1: %{key: 1, schema: :string, required: true}, + ...> a2: %{key: 2, schema: {:enum, [a: 0, b: 1]}}}}, + ...> %{a1: "aa", a2: :a}) + [nil,"aa", 0] + + iex> from_cbor_term({:struct_values, %{a1: %{key: 1, schema: :string, required: true}, + ...> a2: %{key: 2, schema: {:enum, [a: 0, b: 1]}}}}, + ...> [nil, "aa", 0]) + %{a1: "aa", a2: :a} + iex> from_cbor_term({:struct_values, %{a1: %{key: 1, schema: :string, required: true}, ...> a2: %{key: 3, schema: {:enum, [a: 0, b: 1]}}}}, ...> to_cbor_term({:struct_values, %{a1: %{key: 1, schema: :string, required: true}, ...> a2: %{key: 3, schema: {:enum, [a: 0, b: 1]}}}}, ...> %{a1: "aa", a2: :a})) - ** (Ockam.TypedCBOR.Exception) invalid struct encoding, keys must be a sequence of integers starting at 1 + ** (Ockam.TypedCBOR.Exception) invalid struct encoding, keys must be a sequence of integers starting at 0 or 1 """ alias Ockam.TypedCBOR.Exception @@ -140,6 +150,10 @@ defmodule Ockam.TypedCBOR do end end + def from_cbor_term(:charlist, val) when is_list(val) do + to_string(val) + end + def from_cbor_term(:binary, %CBOR.Tag{tag: :bytes, value: val}), do: val def from_cbor_term({:enum, vals}, n) when is_integer(n) do @@ -152,6 +166,19 @@ defmodule Ockam.TypedCBOR do end end + def from_cbor_term({:enum, vals}, [n, list]) when is_integer(n) do + case {List.keyfind(vals, n, 1), list} do + {nil, _} -> + raise Exception, message: "invalid enum encoding: #{n}, allowed: #{inspect(vals)}" + + {{val, ^n}, []} -> + val + + {{schema, ^n}, [val]} -> + from_cbor_term(schema, val) + end + end + def from_cbor_term({:list, element_schema}, values) when is_list(values), do: Enum.map(values, fn val -> from_cbor_term(element_schema, val) end) @@ -174,38 +201,30 @@ defmodule Ockam.TypedCBOR do end def from_cbor_term({:struct_values, fields}, values) when is_list(values) do - field_count = Enum.count(fields) - value_count = Enum.count(values) - - values = - case value_count - field_count do - 0 -> - values + keys = + case Enum.sort(Enum.map(fields, fn {_, f} -> f[:key] end)) do + [1 | _] = keys -> [0 | keys] + keys -> keys + end - 1 -> - [nil | rest] = values - rest + key_count = Enum.count(keys) - _ -> - raise Exception, - message: - "invalid struct encoding, expected #{inspect(field_count)} fields, got #{inspect(value_count)}" - end + if keys != Enum.into(0..(key_count - 1), []) do + raise Exception, + message: "invalid struct encoding, keys must be a sequence of integers starting at 0 or 1" + end - keys = - fields - |> Map.values() - |> Enum.map(& &1.key) - |> Enum.sort() + value_count = Enum.count(values) - if keys != Enum.sort(1..field_count) do + if key_count != value_count do raise Exception, - message: "invalid struct encoding, keys must be a sequence of integers starting at 1" + message: + "invalid struct encoding, expected #{inspect(key_count)} fields, got #{inspect(value_count)}" end struct = - keys - |> Enum.zip(values) + values + |> Enum.with_index(&{&2, &1}) |> Enum.into(%{}) from_cbor_term({:struct, fields}, struct) @@ -220,8 +239,11 @@ defmodule Ockam.TypedCBOR do {:ok, val} <- schema.from_cbor_term(data) do val else - _ -> - Logger.error("type mismatch, expected schema #{inspect(schema)}, value: #{inspect(data)}") + r -> + Logger.error( + "type mismatch, expected schema #{inspect(schema)}, value: #{inspect(data)} err: #{inspect(r)}" + ) + raise(Exception, "type mismatch, expected schema #{inspect(schema)}") end end @@ -269,6 +291,14 @@ defmodule Ockam.TypedCBOR do end end + def to_cbor_term(:charlist, val) when is_binary(val) do + if String.valid?(val) do + to_charlist(val) + else + raise Exception, message: "invalid string #{inspect(val)}" + end + end + def to_cbor_term(:binary, val) when is_binary(val), do: %CBOR.Tag{tag: :bytes, value: val} def to_cbor_term({:enum, vals}, val) when is_atom(val) do @@ -282,6 +312,19 @@ defmodule Ockam.TypedCBOR do end end + def to_cbor_term({:enum, vals}, val) when is_struct(val) do + schema = val.__struct__ + + case vals[schema] do + nil -> + raise Exception, + message: "invalid enum val: #{inspect(val)}, allowed: #{inspect(Keyword.keys(vals))}" + + n when is_integer(n) -> + [n, [to_cbor_term(schema, val)]] + end + end + def to_cbor_term({:list, element_schema}, values) when is_list(values), do: Enum.map(values, fn val -> to_cbor_term(element_schema, val) end) diff --git a/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor/plugin.ex b/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor/plugin.ex index 6172b93f582..6f59cf25388 100644 --- a/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor/plugin.ex +++ b/implementations/elixir/ockam/ockam_typed_cbor/lib/typed_cbor/plugin.ex @@ -52,20 +52,24 @@ defmodule Ockam.TypedCBOR.Plugin do defp validate_key_sequence(mod, :list) do unless sequential_keys?(mod) do - raise("keys must be a sequence of integers starting at 1") + raise("keys must be a sequence of integers starting at 0 or 1") end end - defp validate_key_sequence(mod, :struct) do - unless sequential_keys?(mod) do - IO.warn("keys should be a sequence of integers starting at 1") - end + defp validate_key_sequence(_, :struct) do + :ok end defp sequential_keys?(mod) do fields = Module.get_attribute(mod, :tt_fields) - keys = Enum.map(fields, fn {_, f} -> f[:key] end) - Enum.sort(keys) == Enum.sort(1..Enum.count(keys)) + + keys = + case Enum.sort(Enum.map(fields, fn {_, f} -> f[:key] end)) do + [1 | _] = keys -> [0 | keys] + keys -> keys + end + + keys == Enum.into(0..(Enum.count(keys) - 1), []) end def type_to_spec({:binary, _, _}), do: :binary @@ -120,6 +124,11 @@ defmodule Ockam.TypedCBOR.Plugin do def decode_list!(data), do: Ockam.TypedCBOR.decode!({:list, minicbor_schema()}, data) def decode_list(data), do: Ockam.TypedCBOR.decode({:list, minicbor_schema()}, data) + def from_cbor_term(term), + do: {:ok, Ockam.TypedCBOR.from_cbor_term(minicbor_schema(), term)} + + def to_cbor_term(data), do: {:ok, Ockam.TypedCBOR.to_cbor_term(minicbor_schema(), data)} + def decode_list_strict(data), do: Ockam.TypedCBOR.decode_strict({:list, minicbor_schema()}, data) end diff --git a/implementations/elixir/ockam/ockam_typed_cbor/test/plugin_test.exs b/implementations/elixir/ockam/ockam_typed_cbor/test/plugin_test.exs index 4062afed3a3..0adf40c815e 100644 --- a/implementations/elixir/ockam/ockam_typed_cbor/test/plugin_test.exs +++ b/implementations/elixir/ockam/ockam_typed_cbor/test/plugin_test.exs @@ -91,25 +91,6 @@ defmodule Ockam.TypedCBOR.Plugin.Test do """) end end - - test "issues warning when encoding is struct and keys are not sequential" do - capture = - ExUnit.CaptureIO.capture_io(:stderr, fn -> - Code.compile_string(""" - defmodule Test.Keys.When.Struct do - use TypedStruct - - typedstruct do - plugin(Ockam.TypedCBOR.Plugin) - field(:one, integer(), minicbor: [key: 1]) - field(:three, integer(), minicbor: [key: 3]) - end - end - """) - end) - - assert String.contains?(capture, "warning:") - end end test "encode-decode" do diff --git a/implementations/elixir/ockam/ockly/test/ockly_test.exs b/implementations/elixir/ockam/ockly/test/ockly_test.exs index ba21a1f681a..d8b07d9c982 100644 --- a/implementations/elixir/ockam/ockly/test/ockly_test.exs +++ b/implementations/elixir/ockam/ockly/test/ockly_test.exs @@ -5,7 +5,7 @@ defmodule OcklyTest do test "create identity" do {id, exported_identity} = Ockly.Native.create_identity() {pub_key, secret_key} = :crypto.generate_key(:eddh, :x25519) - {pub_key2, secret_key2} = :crypto.generate_key(:eddh, :x25519) + {pub_key2, _secret_key2} = :crypto.generate_key(:eddh, :x25519) attestation = Ockly.Native.attest_secure_channel_key(id, secret_key) assert Ockly.Native.verify_secure_channel_key_attestation( @@ -53,19 +53,19 @@ defmodule OcklyTest do # It must be possible to import them and use (if memory signing vault is used) secret = - <<83, 231, 139, 244, 109, 254, 138, 112, 211, 93, 197, 106, 173, 226, 235, 88, 141, 218, - 113, 168, 209, 229, 28, 241, 69, 249, 106, 70, 50, 54, 218, 217>> + <<113, 128, 116, 134, 152, 127, 151, 216, 104, 48, 23, 185, 143, 220, 176, 241, 25, 192, + 247, 167, 161, 220, 6, 214, 165, 141, 125, 201, 237, 157, 87, 22>> identity = - <<129, 162, 1, 88, 59, 162, 1, 1, 2, 88, 53, 164, 2, 130, 1, 129, 88, 32, 83, 241, 75, 224, - 25, 93, 231, 146, 168, 52, 2, 192, 228, 60, 198, 200, 216, 60, 101, 169, 165, 128, 75, - 221, 124, 29, 3, 224, 11, 89, 124, 70, 3, 244, 4, 26, 100, 248, 141, 178, 5, 26, 119, 196, - 144, 178, 2, 130, 1, 129, 88, 64, 236, 140, 158, 157, 188, 146, 79, 243, 149, 182, 13, 3, - 100, 174, 45, 5, 37, 208, 240, 3, 205, 7, 29, 61, 74, 44, 28, 166, 51, 161, 201, 36, 211, - 72, 21, 1, 200, 238, 124, 183, 24, 26, 236, 66, 106, 172, 219, 61, 169, 171, 103, 167, 2, - 40, 11, 183, 202, 162, 217, 237, 91, 244, 59, 1>> - - identifier = "I31f064878eb4fc0852d55a0fbb7305270b8fa1d7" + <<129, 130, 88, 55, 131, 1, 1, 88, 50, 133, 246, 130, 0, 129, 88, 32, 109, 21, 101, 217, + 183, 252, 195, 8, 12, 242, 10, 36, 37, 165, 233, 80, 220, 197, 241, 233, 250, 137, 65, + 151, 124, 194, 152, 168, 101, 7, 48, 113, 244, 26, 101, 86, 63, 23, 26, 120, 34, 66, 23, + 130, 0, 129, 88, 64, 117, 93, 149, 142, 37, 229, 76, 223, 234, 124, 175, 116, 136, 206, + 48, 67, 95, 15, 226, 107, 78, 127, 9, 140, 165, 1, 50, 122, 246, 176, 76, 158, 45, 196, + 151, 216, 148, 237, 113, 222, 72, 162, 7, 59, 126, 108, 122, 209, 121, 133, 147, 62, 138, + 249, 186, 146, 249, 74, 88, 173, 176, 136, 148, 10>> + + identifier = "I10253701dafcc65a621ad9fb4097cb327c541de78827713320b749cbbdbd2e9f" assert identifier == Ockly.Native.check_identity(identity) _key_id = Ockly.Native.import_signing_secret(secret)