aboutsummaryrefslogtreecommitdiff
path: root/shard
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2018-10-10 16:48:38 +0200
committerAlex Auvolat <alex@adnab.me>2018-10-10 16:48:38 +0200
commit7bdd60c7f99df417b2589f0e99ff16abc8e925c5 (patch)
tree8508404c74bf91c453f4679363dc25680de02b56 /shard
parent1ee9c3fa6d4259d63685aea95d23b515f59a74cf (diff)
downloadshard-7bdd60c7f99df417b2589f0e99ff16abc8e925c5.tar.gz
shard-7bdd60c7f99df417b2589f0e99ff16abc8e925c5.zip
Connecting works, but the rest probably doesnt
Diffstat (limited to 'shard')
-rw-r--r--shard/lib/app/chat.ex22
-rw-r--r--shard/lib/app/identity.ex10
-rw-r--r--shard/lib/app/pagestore.ex10
-rw-r--r--shard/lib/keys.ex20
-rw-r--r--shard/lib/manager.ex139
-rw-r--r--shard/lib/net/auth.ex3
-rw-r--r--shard/lib/net/tcpconn.ex323
7 files changed, 298 insertions, 229 deletions
diff --git a/shard/lib/app/chat.ex b/shard/lib/app/chat.ex
index 054d7f7..0f4d573 100644
--- a/shard/lib/app/chat.ex
+++ b/shard/lib/app/chat.ex
@@ -101,7 +101,7 @@ defmodule SApp.Chat do
send data for this channel if they have some.
"""
def handle_cast(:init_pull, state) do
- for {_, pid, _, _} <- Shard.Manager.list_peers do
+ for {_, pid, _} <- Shard.Manager.list_connections do
GenServer.cast(pid, {:send_msg, {:interested, [state.id]}})
end
{:noreply, state}
@@ -129,8 +129,8 @@ defmodule SApp.Chat do
end
notif = {:append, prev_root, msgitem, mst.root}
- for peer_id <- Shard.Manager.get_shard_peers(state.id) do
- Shard.Manager.send(peer_id, {state.id, nil, notif})
+ for peer_info <- Shard.Manager.get_shard_peers(state.id) do
+ Shard.Manager.send(peer_info, {state.id, nil, notif})
end
{:noreply, state}
@@ -140,8 +140,8 @@ defmodule SApp.Chat do
Implementation of the :interested handler, this is called when a peer we are
connected to asks to recieve data for this channel.
"""
- def handle_cast({:interested, peer_id}, state) do
- Shard.Manager.send(peer_id, {state.id, nil, {:root, state.mst.root}})
+ def handle_cast({:interested, conn_pid, _auth}, state) do
+ Shard.Manager.send_pid(conn_pid, {state.id, nil, {:root, state.mst.root}})
{:noreply, state}
end
@@ -160,10 +160,10 @@ defmodule SApp.Chat do
- `{:info, start, list, rest}`: put some messages and informs of the
Merkle hash of the store of older messages.
"""
- def handle_cast({:msg, peer_id, _shard_id, nil, msg}, state) do
+ def handle_cast({:msg, conn_pid, _auth, _shard_id, nil, msg}, state) do
state = case msg do
{:get_manifest} ->
- Shard.Manager.send(peer_id, {state.id, nil, {:manifest, state.manifest}})
+ Shard.Manager.send_pid(conn_pid, {state.id, nil, {:manifest, state.manifest}})
state
{:append, prev_root, msgitem, new_root} ->
# Append message: one single mesage has arrived
@@ -189,7 +189,7 @@ defmodule SApp.Chat do
end
else
# Not a simple one-insertion transition, look at the whole tree
- init_merge(state, new_root, peer_id)
+ init_merge(state, new_root, conn_pid)
end
else
Logger.warn("Received message with invalid signature")
@@ -201,7 +201,7 @@ defmodule SApp.Chat do
# already up to date, ignore
state
else
- init_merge(state, new_root, peer_id)
+ init_merge(state, new_root, conn_pid)
end
x ->
Logger.info("Unhandled message: #{inspect x}")
@@ -210,7 +210,7 @@ defmodule SApp.Chat do
{:noreply, state}
end
- defp init_merge(state, new_root, source_peer) do
+ defp init_merge(state, new_root, source_peer_pid) do
if new_root == nil do
state
else
@@ -221,7 +221,7 @@ defmodule SApp.Chat do
prev_last = for {x, true} <- MST.last(state.mst, nil, 100), into: MapSet.new, do: x
mgmst = %{state.mst | root: new_root}
- mgmst = put_in(mgmst.store.prefer_ask, [source_peer])
+ mgmst = put_in(mgmst.store.prefer_ask, [source_peer_pid])
mst = MST.merge(state.mst, mgmst)
new = for {x, true} <- MST.last(mst, nil, 100),
diff --git a/shard/lib/app/identity.ex b/shard/lib/app/identity.ex
index 204dfb1..de39c6d 100644
--- a/shard/lib/app/identity.ex
+++ b/shard/lib/app/identity.ex
@@ -87,25 +87,25 @@ defmodule SApp.Identity do
end
def handle_cast(:init_pull, state) do
- for {_, pid, _, _} <- Shard.Manager.list_peers do
+ for {_, pid, _} <- Shard.Manager.list_connections do
GenServer.cast(pid, {:send_msg, {:interested, [state.id]}})
end
{:noreply, state}
end
- def handle_cast({:interested, peer_id}, state) do
- Shard.Manager.send(peer_id, {state.id, nil, {:update, SData.SignRev.signed(state.state)}})
+ def handle_cast({:interested, peer_pid, _auth}, state) do
+ Shard.Manager.send_pid(peer_pid, {state.id, nil, {:update, SData.SignRev.signed(state.state)}})
{:noreply, state}
end
- def handle_cast({:msg, peer_id, _shard_id, nil, msg}, state) do
+ def handle_cast({:msg, conn_pid, _auth, _shard_id, nil, msg}, state) do
state = case msg do
{:update, signed} when signed != nil ->
case SData.SignRev.merge(state.state, signed, state.pk) do
{true, st2} ->
Shard.Manager.save_state(state.id, st2)
state = put_in(state.state, st2)
- bcast_state(state, [peer_id])
+ bcast_state(state, [conn_pid])
state
{false, _} ->
state
diff --git a/shard/lib/app/pagestore.ex b/shard/lib/app/pagestore.ex
index f093ed4..e09c513 100644
--- a/shard/lib/app/pagestore.ex
+++ b/shard/lib/app/pagestore.ex
@@ -107,7 +107,7 @@ defmodule SApp.PageStore do
case prefer_ask do
[_|_] ->
for peer <- prefer_ask do
- Shard.Manager.send(peer, {state.shard_id, state.path, {:get, key}})
+ Shard.Manager.send_pid(peer, {state.shard_id, state.path, {:get, key}})
end
_ ->
ask_random_peers(state, key)
@@ -123,14 +123,14 @@ defmodule SApp.PageStore do
{:noreply, state}
end
- def handle_cast({:msg, peer_id, _shard_id, _path, msg}, state) do
+ def handle_cast({:msg, conn_pid, _auth, _shard_id, _path, msg}, state) do
state = case msg do
{:get, key} ->
case :dets.lookup state.store, key do
[{_, _, bin}] ->
- Shard.Manager.send(peer_id, {state.shard_id, state.path, {:info, key, bin}})
+ Shard.Manager.send_pid(conn_pid, {state.shard_id, state.path, {:info, key, bin}})
_ ->
- Shard.Manager.send(peer_id, {state.shard_id, state.path, {:not_found, key}})
+ Shard.Manager.send_pid(conn_pid, {state.shard_id, state.path, {:not_found, key}})
end
state
{:info, hash, bin} ->
@@ -157,7 +157,7 @@ defmodule SApp.PageStore do
value = SData.term_unbin bin
for dep <- SData.Page.refs value do
if :dets.lookup state.store, dep == [] do
- init_rec_pull(state, dep, sub_why, [peer_id])
+ init_rec_pull(state, dep, sub_why, [conn_pid])
end
end
end
diff --git a/shard/lib/keys.ex b/shard/lib/keys.ex
index fe63148..d021242 100644
--- a/shard/lib/keys.ex
+++ b/shard/lib/keys.ex
@@ -4,7 +4,6 @@ defmodule Shard.Keys do
"""
use Agent
- require Salty.Sign.Ed25519, as: Sign
require Logger
@key_db [Application.get_env(:shard, :data_path), "key_db"] |> Path.join |> String.to_atom
@@ -20,7 +19,7 @@ defmodule Shard.Keys do
end
defp gen_keypair(suffix, n \\ 0) do
- {:ok, pk, sk} = Sign.keypair
+ %{public: pk, secret: sk} = :enacl.sign_keypair
if rem(n, 10000) == 0 do
Logger.info "#{n}... expected #{:math.pow(256, byte_size(suffix))}"
end
@@ -51,7 +50,7 @@ defmodule Shard.Keys do
{pk, sk} = gen_keypair(Application.get_env(:shard, :identity_suffix))
Logger.info "New identity: #{pk|>Base.encode16}"
:dets.insert @key_db, {pk, sk}
- Shard.Manager.Manifest.start %SApp.Identity.Manifest{pk: pk}
+ Shard.Manifest.start %SApp.Identity.Manifest{pk: pk}
pk
end
@@ -72,7 +71,7 @@ defmodule Shard.Keys do
def sign(pk, bin) do
case :dets.lookup @key_db, pk do
[{^pk, sk}] ->
- Sign.sign(bin, sk)
+ :enacl.sign(bin, sk)
_ -> {:error, :not_found}
end
end
@@ -84,7 +83,7 @@ defmodule Shard.Keys do
"""
def open(pk, signed) do
if valid_identity_pk? pk do
- Sign.open(signed, pk)
+ :enacl.sign_open(signed, pk)
else
{:error, :invalid_pk_suffix}
end
@@ -97,6 +96,13 @@ defmodule Shard.Keys do
end
end
+ def get_sk(pk) do
+ case :dets.lookup @key_db, pk do
+ [{^pk, sk}] -> sk
+ _ -> nil
+ end
+ end
+
@doc"""
Lookup the secret key for a pk and generate a detached signature for a message.
@@ -108,7 +114,7 @@ defmodule Shard.Keys do
def sign_detached(pk, bin) do
case :dets.lookup @key_db, pk do
[{^pk, sk}] ->
- Sign.sign_detached(bin, sk)
+ :enacl.sign_detached(bin, sk)
_ -> {:error, :not_found}
end
end
@@ -120,7 +126,7 @@ defmodule Shard.Keys do
"""
def verify(pk, bin, sign) do
if valid_identity_pk? pk do
- Sign.verify_detached(sign, bin, pk)
+ :enacl.sign_verify_detached(sign, bin, pk)
else
{:error, :invalid_pk_suffix}
end
diff --git a/shard/lib/manager.ex b/shard/lib/manager.ex
index ccb750f..dd602bf 100644
--- a/shard/lib/manager.ex
+++ b/shard/lib/manager.ex
@@ -44,7 +44,7 @@ defmodule Shard.Manager do
- :connections (not persistent)
List of
- { nil | his_pk, nil | my_pk, pid, peer_info }
+ { peer_info, pid, nil | {my_pk, his_pk} }
And an internal table :
@@ -108,69 +108,62 @@ defmodule Shard.Manager do
{:noreply, state}
end
- def handle_cast({:interested, peer_info, shards}, state) do
+ def handle_cast({:interested, conn_pid, peer_info, auth, shards}, state) do
for shard_id <- shards do
case :dets.lookup(@shard_db, shard_id) do
[{ ^shard_id, _, pid }] ->
:dets.insert(@peer_db, {shard_id, peer_info})
- GenServer.cast(pid, {:interested, peer_info})
+ GenServer.cast(pid, {:interested, conn_pid, auth})
[] -> nil
end
end
{:noreply, state}
end
- def handle_cast({:not_interested, peer_id, shard_id}, state) do
- :dets.match_delete(@shard_peer_db, {shard_id, peer_id})
+ def handle_cast({:not_interested, peer_info, shard_id}, state) do
+ :dets.match_delete(@peer_db, {shard_id, peer_info})
{:noreply, state}
end
- def handle_cast({:shard_peer_db_insert, shard_id, peer_id}, state) do
- :dets.insert(@shard_peer_db, {shard_id, peer_id})
+ def handle_cast({:shard_peer_db_insert, shard_id, peer_info}, state) do
+ :dets.insert(@peer_db, {shard_id, peer_info})
{:noreply, state}
end
- def handle_cast({:peer_up, pk, pid, ip, port}, state) do
- for [pk2] <- :dets.match(@peer_db, {:'$1', :_, ip, port}) do
- if pk2 != pk do
- # obsolete peer information
- :dets.delete(@peer_db, pk2)
- :dets.match_delete(@shard_peer_db, {:_, pk2})
- end
- end
- :dets.insert(@peer_db, {pk, pid, ip, port})
+ def handle_cast({:peer_up, pid, peer_info, auth}, state) do
+ :ets.insert(:connections, {peer_info, pid, auth})
# Send interested message for all our shards
id_list = (for [{id, _, _}] <- :dets.match(@shard_db, :"$1"), do: id)
GenServer.cast(pid, {:send_msg, {:interested, id_list}})
- # Send queued messages
- for {_, msg, _} <- :ets.lookup(state.outbox, pk) do
- GenServer.cast(pid, {:send_msg, msg})
- end
- :ets.delete(state.outbox, pk)
+ # # Send queued messages
+ # for {_, msg, _} <- :ets.lookup(state.outbox, pk) do
+ # GenServer.cast(pid, {:send_msg, msg})
+ # end
+ # :ets.delete(state.outbox, pk)
{:noreply, state}
end
- def handle_cast({:peer_down, pk, ip, port}, state) do
- :dets.insert(@peer_db, {pk, nil, ip, port})
+ def handle_cast({:peer_down, peer_pid, peer_info, auth}, state) do
+ :ets.match_delete(:connections, {peer_info, peer_pid, auth})
{:noreply, state}
end
- def handle_cast({:connect_and_send, peer_id, msg}, state) do
- case :dets.lookup(@peer_db, peer_id) do
- [{^peer_id, nil, ip, port}] ->
- add_peer(ip, port, state)
- currtime = System.os_time :second
- :ets.insert(state.outbox, {peer_id, msg, currtime})
- outbox_cleanup = [ {{:_, :_, :'$1'}, [{:<, :'$1', currtime - 60}], [true]} ]
- :ets.select_delete(state.outbox, outbox_cleanup)
- _ ->
- Logger.info "Dropping message #{inspect msg} for peer #{inspect peer_id}: peer not in database"
- end
- {:noreply, state}
- end
+ # def handle_cast({:connect_and_send, peer_id, msg}, state) do
+ # case :dets.lookup(@peer_db, peer_id) do
+ # [{^peer_id, nil, ip, port}] ->
+ # add_peer(ip, port, state)
+ # currtime = System.os_time :second
+ # :ets.insert(state.outbox, {peer_id, msg, currtime})
+ # outbox_cleanup = [ {{:_, :_, :'$1'}, [{:<, :'$1', currtime - 60}], [true]} ]
+ # :ets.select_delete(state.outbox, outbox_cleanup)
+ # _ ->
+ # Logger.info "Dropping message #{inspect msg} for peer #{inspect peer_id}: peer not in database"
+ # end
+ # {:noreply, state}
+ # end
def handle_cast({:try_connect, pk_list}, state) do
for pk <- pk_list do
@@ -197,7 +190,7 @@ defmodule Shard.Manager do
spawn fn ->
case :gen_tcp.connect(ip, port, [:binary, packet: 2, active: false]) do
{:ok, client} ->
- {:ok, pid} = DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SNet.TCPConn, %{socket: client, my_port: state.my_port}})
+ {:ok, pid} = DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SNet.TCPConn, %{socket: client, my_port: state.my_port, is_client: true, auth: nil}})
:ok = :gen_tcp.controlling_process(client, pid)
_ ->
Logger.info "Could not connect to #{inspect ip}:#{port}, some messages may be dropped"
@@ -206,35 +199,34 @@ defmodule Shard.Manager do
end
- # ====================
- # INTERFACE WITH PEERS
- # ====================
+ # ======================
+ # CALLED BY SNet.TcpConn
+ # ======================
- def incoming(conn_pid, {:interested, shards}) do
- GenServer.cast(__MODULE__, {:interested, peer_id, shards})
+ @doc"""
+ Dispatch incoming message to correct shard process
+ """
+ def incoming(conn_pid, peer_info, auth, {:interested, shards}) do
+ GenServer.cast(__MODULE__, {:interested, conn_pid, peer_info, auth, shards})
end
- def incoming(conn_pid, {:not_interested, shard}) do
- GenServer.cast(__MODULE__, {:not_interested, peer_id, shard})
+ def incoming(_conn_pid, peer_info, _auth, {:not_interested, shard}) do
+ GenServer.cast(__MODULE__, {:not_interested, peer_info, shard})
end
- @doc"""
- Dispatch incoming message to correct shard process
- """
- defp dispatch(conn_pid, {shard_id, path, msg}) do
- # TODO: auth
+ def incoming(conn_pid, peer_info, auth, {shard_id, path, msg}) do
case :dets.lookup(@shard_db, shard_id) do
[] ->
- __MODULE__.send(peer_id, {:not_interested, shard_id})
+ GenServer.cast(conn_pid, {:send_msg, {:not_interested, shard_id}})
[_] ->
- case :dets.match(@shard_peer_db, {shard_id, peer_id}) do
+ case :dets.match(@peer_db, {shard_id, peer_info}) do
[] ->
- GenServer.cast(__MODULE__, {:shard_peer_db_insert, shard_id, peer_id})
+ GenServer.cast(__MODULE__, {:shard_peer_db_insert, shard_id, peer_info})
_ -> nil
end
case :ets.lookup(:shard_procs, {shard_id, path}) do
[{ {^shard_id, ^path}, pid }] ->
- GenServer.cast(pid, {:msg, peer_id, shard_id, path, msg})
+ GenServer.cast(pid, {:msg, conn_pid, auth, shard_id, path, msg})
[] ->
Logger.info("Warning: dropping message for #{inspect shard_id}/#{inspect path}, no handler running.\n\t#{inspect msg}")
end
@@ -242,31 +234,22 @@ defmodule Shard.Manager do
end
- # =====================
- # INTERFACE WITH SHARDS
- # =====================
+ # ================
+ # CALLED BY Sapp.*
+ # ================
@doc"""
Send message to a peer specified by peer id
"""
- def send(peer_id, msg) do
- case :dets.lookup(@peer_db, peer_id) do
- [{ ^peer_id, pid, _, _}] when pid != nil->
- GenServer.cast(pid, {:send_msg, msg})
- _ ->
- GenServer.cast(__MODULE__, {:connect_and_send, peer_id, msg})
- end
+ def send_pid(pid, msg) do
+ GenServer.cast(pid, {:send_msg, msg})
end
@doc"""
- Send message to a peer through an authenticated channel
-
- his_auth: accepted users to talk to, either single pk or list of pk
-
- Returns true if a corresponding channel was open and msg was sent,
- false otherwise.
+ Send message to a peer specified by peer info.
+ Opens a connection if necessary.
"""
- def send(peer_id, my_auth, his_auth, msg) do
+ def send(_peer_info, _msg) do
# TODO
end
@@ -287,10 +270,10 @@ defmodule Shard.Manager do
end
@doc"""
- Return the list of all peer IDs that are interested in a certain shard
+ Return the list of all peer info for peers that are interested in a certain shard
"""
def get_shard_peers(shard_id) do
- for [x] <- :dets.match(@shard_peer_db, {shard_id, :"$1"}), do: x
+ for [x] <- :dets.match(@peer_db, {shard_id, :"$1"}), do: x
end
@doc"""
@@ -311,9 +294,9 @@ defmodule Shard.Manager do
end
- # ==========================
- # INTERFACE FOR OTHER THINGS
- # ==========================
+ # ================
+ # CALLED BY ANYONE
+ # ================
@doc"""
Connect to a peer specified by ip address and port
@@ -340,9 +323,9 @@ defmodule Shard.Manager do
end
@doc"""
- Return the list of all peers
+ Return the list of all connected peers
"""
- def list_peers() do
- for [x] <- :dets.match(@peer_db, :"$1"), do: x
+ def list_connections() do
+ for [x] <- :dets.match(:connections, :"$1"), do: x
end
end
diff --git a/shard/lib/net/auth.ex b/shard/lib/net/auth.ex
new file mode 100644
index 0000000..c903093
--- /dev/null
+++ b/shard/lib/net/auth.ex
@@ -0,0 +1,3 @@
+defmodule SNet.Auth do
+ defstruct [:my_pk, :his_pk]
+end
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