aboutsummaryrefslogtreecommitdiff
path: root/shard/lib/net/tcpconn.ex
diff options
context:
space:
mode:
Diffstat (limited to 'shard/lib/net/tcpconn.ex')
-rw-r--r--shard/lib/net/tcpconn.ex323
1 files changed, 200 insertions, 123 deletions
diff --git a/shard/lib/net/tcpconn.ex b/shard/lib/net/tcpconn.ex
index 35bf9d1..aaab9e1 100644
--- a/shard/lib/net/tcpconn.ex
+++ b/shard/lib/net/tcpconn.ex
@@ -12,6 +12,22 @@ defmodule SNet.TCPConn do
require Logger
+ @doc"""
+ Start a connection handler on a given socket.
+ The socket is assumed to be already open.
+
+ Expected initial state: a dict with the following keys:
+
+ - socket: the socket
+ - is_client: true if we are the initiator of the connection, false otherwise
+ - my_port: if we are the client, what port should the other dial to recontact us
+
+ Optionnally, and only if we are the initiator of the connection, the following key:
+
+ - auth: nil | {my_pk, list_accepted_his_pk}
+
+ If we are initiator of the connection, we will use crypto if and only if auth is not nil.
+ """
def start_link(state) do
GenServer.start_link(__MODULE__, state)
end
@@ -23,13 +39,12 @@ defmodule SNet.TCPConn do
GenServer.cast(self(), :server_handshake)
end
- {:ok, {addr, port}} = :inet.peername state.socket
- {:ok, %{state | addr: addr, port: port}}
+ {:ok, state}
end
def handle_call(:get_peer_info, _from, state) do
- {:reply, {:tcp4, state.addr, state.port}, state}
+ {:reply, state.peer_info, state}
end
@@ -37,14 +52,10 @@ defmodule SNet.TCPConn do
socket = state.socket
net_key = Application.get_env(:shard, :network_key)
- {:ok, cli_eph_pk, cli_eph_sk} = :enacl.box_keypair
-
- [srv_longterm_pk] = state.his_auth
- cli_longterm_pk = state.my_auth
- cli_longterm_sk = Keys.get_sk cli_longterm_pk
+ %{public: cli_eph_pk, secret: cli_eph_sk} = :enacl.box_keypair
# 1. Client hello
- {:ok, cli_hello_hmac} = :enacl.auth(cli_eph_pk, net_key)
+ cli_hello_hmac = :enacl.auth(cli_eph_pk, net_key)
cli_hello = cli_hello_hmac <> cli_eph_pk
:gen_tcp.send(socket, cli_hello)
@@ -57,50 +68,89 @@ defmodule SNet.TCPConn do
# Shared secret derivation
sh_sec_ab = :enacl.curve25519_scalarmult(cli_eph_sk, srv_eph_pk)
- sh_sec_aB = :enacl.curve25519_scalarmult(cli_eph_sk, :enacl.crypto_sign_ed25519_public_to_curve25519(srv_longterm_pk))
- # 3. Client authenticate
- msg1 = net_key <> srv_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab)
- det_sign_A = :enacl.sign_detached(msg1, cli_longterm_sk)
- key3 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB)
- cli_auth = :crypto.secretbox(det_sign_A <> cli_longterm_pk, <<0 :: 24*8>>, key3)
- :gen_tcp.send(socket, cli_auth)
-
- # Shared secret derivation, again
- sh_sec_Ab = :enacl.curve25519_scalarmult(:enacl.crypto_sign_ed25519_secret_to_curve25519(cli_longterm_sk), srv_eph_pk)
-
- # 4. Server accept
- {:ok, srv_accept} = :gen_tcp.recv(socket, 0)
- key4 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab)
- {:ok, det_sign_B} = :enacl.secretbox_open(srv_accept, <<0 :: 24*8>>, key4)
- true = :enacl.sign_verify_detached(det_sign_B, net_key <> det_sign_A <> cli_longterm_pk <> :crypto.sha256(sh_sec_ab), srv_longterm_pk)
-
- # Derive secrets and initial nonces for stream communication
- secret_common = :crypto.hash(:sha256, :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab))
- secret_cli2srv = :crypto.hash(:sha256, secret_common <> srv_longterm_pk)
- secret_srv2cli = :crypto.hash(:sha256, secret_common <> cli_longterm_pk)
- {:ok, hmac1} = :enacl.auth(srv_eph_pk, net_key)
- nonce_cli2srv = :binary.part(hmac1, 0, 24)
- {:ok, hmac2} = :enacl.auth(cli_eph_pk, net_key)
- nonce_srv2cli = :binary.part(hmac2, 0, 24)
+ stream_param = case state.auth do
+ nil ->
+ # 3. Client doesn't authenticate
+ bytes = :enacl.randombytes(32)
+ key3 = :crypto.hash(:sha256, net_key <> sh_sec_ab)
+ cli_noauth = :enacl.secretbox(bytes, <<0 :: 24*8>>, key3)
+ :gen_tcp.send(socket, cli_noauth)
+
+ # 4. Server accept
+ {:ok, srv_accept} = :gen_tcp.recv(socket, 0)
+ key4 = :crypto.hash(:sha256, sh_sec_ab <> net_key)
+ {:ok, ^bytes} = :enacl.secretbox_open(srv_accept, <<0 :: 24*8>>, key4)
+
+ # Derive secrets and initial nonces bla bla bla
+ secret_common = :crypto.hash(:sha256, :crypto.hash(:sha256, net_key <> sh_sec_ab))
+ secret_cli2srv = :crypto.hash(:sha256, secret_common <> srv_eph_pk)
+ secret_srv2cli = :crypto.hash(:sha256, secret_common <> cli_eph_pk)
+ hmac1 = :enacl.auth(srv_eph_pk, net_key)
+ nonce_cli2srv = :binary.part(hmac1, 0, 24)
+ hmac2 = :enacl.auth(cli_eph_pk, net_key)
+ nonce_srv2cli = :binary.part(hmac2, 0, 24)
+ %{
+ secret_send: secret_cli2srv,
+ secret_recv: secret_srv2cli,
+ nonce_send: nonce_cli2srv,
+ nonce_recv: nonce_srv2cli,
+ auth: nil,
+ }
+
+ {cli_longterm_pk, srv_list_pk} ->
+ [srv_longterm_pk] = srv_list_pk
+ cli_longterm_sk = Shard.Keys.get_sk cli_longterm_pk
+
+ sh_sec_aB = :enacl.curve25519_scalarmult(cli_eph_sk, :enacl.crypto_sign_ed25519_public_to_curve25519(srv_longterm_pk))
+
+ # 3. Client authenticate
+ msg1 = net_key <> srv_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab)
+ det_sign_A = :enacl.sign_detached(msg1, cli_longterm_sk)
+ key3 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB)
+ cli_auth = :enacl.secretbox(det_sign_A <> cli_longterm_pk, <<0 :: 24*8>>, key3)
+ :gen_tcp.send(socket, cli_auth)
+
+ # Shared secret derivation, again
+ sh_sec_Ab = :enacl.curve25519_scalarmult(:enacl.crypto_sign_ed25519_secret_to_curve25519(cli_longterm_sk), srv_eph_pk)
+
+ # 4. Server accept
+ {:ok, srv_accept} = :gen_tcp.recv(socket, 0)
+ key4 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab)
+ {:ok, det_sign_B} = :enacl.secretbox_open(srv_accept, <<0 :: 24*8>>, key4)
+ true = :enacl.sign_verify_detached(det_sign_B, net_key <> det_sign_A <> cli_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab), srv_longterm_pk)
+
+ # Derive secrets and initial nonces for stream communication
+ secret_common = :crypto.hash(:sha256, :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab))
+ secret_cli2srv = :crypto.hash(:sha256, secret_common <> srv_longterm_pk)
+ secret_srv2cli = :crypto.hash(:sha256, secret_common <> cli_longterm_pk)
+ hmac1 = :enacl.auth(srv_eph_pk, net_key)
+ nonce_cli2srv = :binary.part(hmac1, 0, 24)
+ hmac2 = :enacl.auth(cli_eph_pk, net_key)
+ nonce_srv2cli = :binary.part(hmac2, 0, 24)
+ %{
+ secret_send: secret_cli2srv,
+ secret_recv: secret_srv2cli,
+ nonce_send: nonce_cli2srv,
+ nonce_recv: nonce_srv2cli,
+ auth: %SNet.Auth{my_pk: cli_longterm_pk, his_pk: srv_longterm_pk},
+ }
+ end
+
+ # Tell our port
+ port_msg = :enacl.secretbox(<<state.my_port::16>>, stream_param.nonce_send, stream_param.secret_send)
+ stream_param = %{stream_param | nonce_send: next_nonce(stream_param.nonce_send)}
+ :gen_tcp.send(socket, port_msg)
# Set up the rest
:inet.setopts(socket, [active: true])
{:ok, {addr, port}} = :inet.peername socket
- state = %{
- socket: socket,
- my_pk: cli_longterm_pk,
- his_pk: srv_longterm_pk,
- secret_send: secret_cli2srv,
- secret_recv: secret_srv2cli,
- nonce_send: nonce_cli2srv,
- nonce_recv: nonce_srv2cli,
- addr: addr,
- port: port,
- # his_port: his_port
- }
-
- GenServer.cast(Shard.Manager, {:peer_up, state.his_pk, self(), addr, port})
+ state = stream_param
+ |> Map.put(:socket, socket)
+ |> Map.put(:peer_info, {:tcp4, addr, port})
+ |> Map.put(:my_port, state.my_port)
+
+ GenServer.cast(Shard.Manager, {:peer_up, self(), state.peer_info, state.auth})
Logger.info "New peer: #{print_id state} at #{inspect addr}:#{port}"
{:noreply, state}
@@ -110,10 +160,7 @@ defmodule SNet.TCPConn do
socket = state.socket
net_key = Application.get_env(:shard, :network_key)
- {:ok, srv_eph_pk, srv_eph_sk} = :enacl.box_keypair
-
- srv_longterm_pk = state.my_auth
- srv_longterm_sk = Keys.get_sk srv_longterm_pk
+ %{public: srv_eph_pk, secret: srv_eph_sk} = :enacl.box_keypair
# 1. Client hello
{:ok, cli_hello} = :gen_tcp.recv(socket, 0)
@@ -123,107 +170,137 @@ defmodule SNet.TCPConn do
true = :enacl.auth_verify(cli_hmac, cli_eph_pk, net_key)
# 2. Server hello
- {:ok, srv_hello_hmac} = :enacl.auth(srv_eph_pk, net_key)
+ srv_hello_hmac = :enacl.auth(srv_eph_pk, net_key)
srv_hello = srv_hello_hmac <> srv_eph_pk
:gen_tcp.send(socket, srv_hello)
# Shared secret derivation
sh_sec_ab = :enacl.curve25519_scalarmult(srv_eph_sk, cli_eph_pk)
- sh_sec_aB = :enacl.curve25519_scalarmult(:enacl.crypto_sign_ed25519_secret_to_curve25519(srv_longterm_sk), cli_eph_pk)
# 3. Client authenticate
{:ok, cli_auth} = :gen_tcp.recv(socket, 0)
- key3 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB)
- {:ok, cli_auth_plain} = :enacl.secretbox_open(cli_auth, <<0 :: 24*8>>, key3)
- 96 = byte_size cli_auth_plain
- det_sign_A = :binary.part(cli_auth_plain, 0, 64)
- cli_longterm_pk = :binary.part(cli_auth_plain, 64, 32)
- true = :enacl.sign_verify_deteached(det_sign_A, net_key <> srv_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab), cli_longterm_pk)
-
- # Shared secret derivation
- sh_sec_Ab = :enacl.curve25519_scalarmult(srv_eph_sk, :enacl.crypto_sign_ed25519_public_to_curve25519(cli_longterm_pk))
- # TODO: here we can stop if we don't like the client's longterm pk
-
- # 4. Server accept
- det_sign_B = :enacl.sign_detached(net_key <> det_sign_A <> cli_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab), srv_longterm_sk)
- key4 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab)
- msg4 = :enacl.secretbox(det_sign_B, <<0 :: 24*8>>, key4)
- :gen_tcp.send(socket, msg4)
+ stream_param = case byte_size cli_auth do
+ 48 ->
+ # 3. Client does not authenticate
+ key3 = :crypto.hash(:sha256, net_key <> sh_sec_ab)
+ {:ok, randbytes} = :enacl.secretbox_open(cli_auth, <<0 :: 24*8>>, key3)
+
+ # 4. Server accept
+ key4 = :crypto.hash(:sha256, sh_sec_ab <> net_key)
+ srv_accept = :enacl.secretbox(randbytes, <<0 :: 24*8>>, key4)
+ :gen_tcp.send(socket, srv_accept)
+
+ # Derive secrets and initial nonces bla bla bla
+ secret_common = :crypto.hash(:sha256, :crypto.hash(:sha256, net_key <> sh_sec_ab))
+ secret_cli2srv = :crypto.hash(:sha256, secret_common <> srv_eph_pk)
+ secret_srv2cli = :crypto.hash(:sha256, secret_common <> cli_eph_pk)
+ hmac1 = :enacl.auth(srv_eph_pk, net_key)
+ nonce_cli2srv = :binary.part(hmac1, 0, 24)
+ hmac2 = :enacl.auth(cli_eph_pk, net_key)
+ nonce_srv2cli = :binary.part(hmac2, 0, 24)
+ %{
+ secret_recv: secret_cli2srv,
+ secret_send: secret_srv2cli,
+ nonce_recv: nonce_cli2srv,
+ nonce_send: nonce_srv2cli,
+ auth: nil,
+ }
+
+ _ ->
+ # Client authenticates
+ srv_longterm_pk = state.my_auth # TODO this is not ok
+ srv_longterm_sk = Shard.Keys.get_sk srv_longterm_pk
+
+ sh_sec_aB = :enacl.curve25519_scalarmult(:enacl.crypto_sign_ed25519_secret_to_curve25519(srv_longterm_sk), cli_eph_pk)
+
+ key3 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB)
+ {:ok, cli_auth_plain} = :enacl.secretbox_open(cli_auth, <<0 :: 24*8>>, key3)
+ 96 = byte_size cli_auth_plain
+ det_sign_A = :binary.part(cli_auth_plain, 0, 64)
+ cli_longterm_pk = :binary.part(cli_auth_plain, 64, 32)
+ true = :enacl.sign_verify_detached(det_sign_A, net_key <> srv_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab), cli_longterm_pk)
+
+ # Shared secret derivation
+ sh_sec_Ab = :enacl.curve25519_scalarmult(srv_eph_sk, :enacl.crypto_sign_ed25519_public_to_curve25519(cli_longterm_pk))
+
+ # TODO: here we can stop if we don't like the client's longterm pk
+
+ # 4. Server accept
+ det_sign_B = :enacl.sign_detached(net_key <> det_sign_A <> cli_longterm_pk <> :crypto.hash(:sha256, sh_sec_ab), srv_longterm_sk)
+ key4 = :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab)
+ msg4 = :enacl.secretbox(det_sign_B, <<0 :: 24*8>>, key4)
+ :gen_tcp.send(socket, msg4)
+
+ # Derive secrets and initial nonces for stream communication
+ secret_common = :crypto.hash(:sha256, :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab))
+ secret_cli2srv = :crypto.hash(:sha256, secret_common <> srv_longterm_pk)
+ secret_srv2cli = :crypto.hash(:sha256, secret_common <> cli_longterm_pk)
+ hmac1 = :enacl.auth(srv_eph_pk, net_key)
+ nonce_cli2srv = :binary.part(hmac1, 0, 24)
+ hmac2 = :enacl.auth(cli_eph_pk, net_key)
+ nonce_srv2cli = :binary.part(hmac2, 0, 24)
+ %{
+ secret_send: secret_srv2cli,
+ secret_recv: secret_cli2srv,
+ nonce_send: nonce_srv2cli,
+ nonce_recv: nonce_cli2srv,
+ auth: %SNet.Auth{my_pk: srv_longterm_pk, his_pk: cli_longterm_pk},
+ }
+ end
- # Derive secrets and initial nonces for stream communication
- secret_common = :crypto.hash(:sha256, :crypto.hash(:sha256, net_key <> sh_sec_ab <> sh_sec_aB <> sh_sec_Ab))
- secret_cli2srv = :crypto.hash(:sha256, secret_common <> srv_longterm_pk)
- secret_srv2cli = :crypto.hash(:sha256, secret_common <> cli_longterm_pk)
- {:ok, hmac1} = :enacl.auth(srv_eph_pk, net_key)
- nonce_cli2srv = :binary.part(hmac1, 0, 24)
- {:ok, hmac2} = :enacl.auth(cli_eph_pk, net_key)
- nonce_srv2cli = :binary.part(hmac2, 0, 24)
+ # Receive his actual port
+ {:ok, port_msg} = :gen_tcp.recv(socket, 0)
+ {:ok, <<his_port::16>>} = :enacl.secretbox_open(port_msg, stream_param.nonce_recv, stream_param.secret_recv)
+ stream_param = %{stream_param | nonce_recv: next_nonce(stream_param.nonce_recv)}
# Set up the rest
:inet.setopts(socket, [active: true])
{:ok, {addr, port}} = :inet.peername socket
- state = %{
- socket: socket,
- my_pk: cli_longterm_pk,
- his_pk: srv_longterm_pk,
- secret_send: secret_srv2cli,
- secret_recv: secret_cli2srv,
- nonce_send: nonce_srv2cli,
- nonce_recv: nonce_cli2srv,
- addr: addr,
- port: port,
- # his_port: his_port
- }
-
- GenServer.cast(Shard.Manager, {:peer_up, state.his_pk, self(), addr, port})
- Logger.info "New peer: #{print_id state} at #{inspect addr}:#{port}"
+ state = stream_param
+ |> Map.put(:socket, socket)
+ |> Map.put(:peer_info, {:tcp4, addr, his_port})
+ |> Map.put(:my_port, state.my_port)
+
+ GenServer.cast(Shard.Manager, {:peer_up, self(), state.peer_info, state.auth})
+ Logger.info "New peer: #{print_id state} at #{inspect state.peer_info} (#{port})"
{:noreply, state}
end
def handle_cast({:send_msg, msg}, state) do
msgbin = :erlang.term_to_binary msg
- enc = encode_pkt(msgbin, state.conn_his_pkey, state.conn_my_skey)
+ enc = :enacl.secretbox(msgbin, state.nonce_send, state.secret_send)
:gen_tcp.send(state.socket, enc)
- {:noreply, state}
- end
-
- defp next_nonce(nonce) do
- i = :crypto.bytes_to_integer(nonce)
- <<i+1 :: 24*8>>
- end
-
- defp encode_pkt(pkt, pk, sk) do
- {:ok, n} = Salty.Random.buf Box.noncebytes
- {:ok, msg} = Box.easy(pkt, n, pk, sk)
- n <> msg
- end
-
- defp decode_pkt(pkt, pk, sk) do
- n = binary_part(pkt, 0, Box.noncebytes)
- enc = binary_part(pkt, Box.noncebytes, (byte_size pkt) - Box.noncebytes)
- {:ok, msg} = Box.open_easy(enc, n, pk, sk)
- msg
+ {:noreply, %{state | nonce_send: next_nonce(state.nonce_send) }}
end
def handle_info({:tcp, _socket, raw_data}, state) do
- msg = decode_pkt(raw_data, state.conn_his_pkey, state.conn_my_skey)
- msg_data = :erlang.binary_to_term(msg, [:safe])
- Shard.Manager.incoming(state.his_pkey, msg_data)
- {:noreply, state}
+ {:ok, msgbin} = :enacl.secretbox_open(raw_data, state.nonce_recv, state.secret_recv)
+ msg_data = :erlang.binary_to_term(msgbin, [:safe])
+ Shard.Manager.incoming(self(), state.peer_info, state.auth, msg_data)
+ {:noreply, %{state | nonce_recv: next_nonce(state.nonce_recv) }}
end
def handle_info({:tcp_closed, _socket}, state) do
- Logger.info "Disconnected: #{print_id state} at #{inspect state.addr}:#{state.port}"
- GenServer.cast(Shard.Manager, {:peer_down, state.his_pkey, state.addr, state.his_port})
+ Logger.info "Disconnected: #{print_id state} at #{inspect state.peer_info}"
+ GenServer.cast(Shard.Manager, {:peer_down, self(), state.peer_info, state.auth})
exit(:normal)
end
+ defp next_nonce(nonce) do
+ i = :crypto.bytes_to_integer(nonce)
+ <<i+1 :: 24*8>>
+ end
+
defp print_id(state) do
- state.his_pk
- |> binary_part(0, 8)
- |> Base.encode16
- |> String.downcase
+ case state.auth do
+ nil -> "(no auth)"
+ %SNet.Auth{my_pk: _my_pk, his_pk: his_pk} ->
+ his_pk
+ |> binary_part(0, 8)
+ |> Base.encode16
+ |> String.downcase
+ end
end
end