From f16973d3a492ae6d4890c40d77b0a93d3293bf3a Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 26 Sep 2018 14:37:10 +0200 Subject: Signing and stuff --- shard/lib/app/chat.ex | 77 +++++++++++++++++++++---------- shard/lib/app/identity.ex | 113 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 shard/lib/app/identity.ex (limited to 'shard/lib/app') diff --git a/shard/lib/app/chat.ex b/shard/lib/app/chat.ex index db2cb64..e61d648 100644 --- a/shard/lib/app/chat.ex +++ b/shard/lib/app/chat.ex @@ -28,7 +28,7 @@ defmodule SApp.Chat do defimpl Shard.Manifest, for: Manifest do def start(m) do - SApp.Chat.start_link(m.channel) + DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Chat, m.channel}) end end @@ -69,6 +69,12 @@ defmodule SApp.Chat do end end + def find_proc(chan) do + manifest = %Manifest{channel: chan} + id = SData.term_hash manifest + Shard.Manager.find_proc id + end + @doc """ Implementation of the :manifest call that returns the chat room's manifest """ @@ -98,10 +104,11 @@ defmodule SApp.Chat do to send a message to the chat room. Puts the message in the store and syncs with all connected peers. """ - def handle_cast({:chat_send, msg}, state) do - msgitem = {(System.os_time :seconds), - Shard.Identity.get_nickname(), - msg} + def handle_cast({:chat_send, pk, msg}, state) do + msgbin = SData.term_bin {(System.os_time :seconds), msg} + {:ok, sign} = Shard.Keys.sign_detached(pk, msgbin) + msgitem = {pk, msgbin, sign} + prev_root = state.mst.root mst = MST.insert(state.mst, msgitem) state = %{state | mst: mst} @@ -157,21 +164,26 @@ defmodule SApp.Chat do state else # Try adding the message - if prev_root == state.mst.root do - mst2 = MST.insert(state.mst, msgitem) - if mst2.root == new_root do - # This was the only message missing, we are happy! - state = %{state | mst: mst2} - Shard.Manager.save_state(state.id, mst2.root) - GenServer.cast(state.page_store, {:set_roots, [mst2.root]}) - msg_callback(state, msgitem) - state + {pk, bin, sign} = msgitem + if Shard.Keys.verify(pk, bin, sign) == :ok do + if prev_root == state.mst.root do + mst2 = MST.insert(state.mst, msgitem) + if mst2.root == new_root do + # This was the only message missing, we are happy! + state = %{state | mst: mst2} + Shard.Manager.save_state(state.id, mst2.root) + GenServer.cast(state.page_store, {:set_roots, [mst2.root]}) + msg_callback(state, msgitem) + state + else + Logger.warn("Invalid new root after inserting same message item!") + state + end else - # More messages are missing, start a full merge init_merge(state, new_root, peer_id) end else - init_merge(state, new_root, peer_id) + state end end {:root, new_root} -> @@ -190,21 +202,33 @@ defmodule SApp.Chat do defp init_merge(state, new_root, source_peer) do # TODO: make the merge asynchronous + + Logger.info("Starting merge for #{inspect state.manifest}, merging root: #{new_root|>Base.encode16}") + 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]) mst = MST.merge(state.mst, mgmst) - for {x, true} <- MST.last(mst, nil, 100) do + correct = for {x, true} <- MST.last(mst, nil, 100) do if not MapSet.member? prev_last, x do msg_callback(state, x) + {pk, bin, sign} = x + Shard.Keys.verify(pk, bin, sign) + else + true end end - GenServer.cast(state.page_store, {:set_roots, [mst.root]}) - Shard.Manager.save_state(state.id, mst.root) - %{state | mst: mst} + if Enum.all? correct do + GenServer.cast(state.page_store, {:set_roots, [mst.root]}) + Shard.Manager.save_state(state.id, mst.root) + %{state | mst: mst} + else + Logger.warn("Incorrect signatures somewhere while merging, dropping merged data") + state + end end def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do @@ -212,20 +236,23 @@ defmodule SApp.Chat do {:noreply, %{ state | subs: new_subs }} end - defp msg_callback(state, {ts, nick, msg}) do + defp msg_callback(state, {pk, msgbin, _sign}) do + {ts, msg} = SData.term_unbin msgbin for pid <- state.subs do if Process.alive?(pid) do - send(pid, {:chat_recv, state.channel, {ts, nick, msg}}) + send(pid, {:chat_recv, state.channel, {ts, pk, msg}}) end end end - defp msg_cmp({ts1, nick1, msg1}, {ts2, nick2, msg2}) do + defp msg_cmp({pk1, msgbin1, _sign1}, {pk2, msgbin2, _sign2}) do + {ts1, msg1} = SData.term_unbin msgbin1 + {ts2, msg2} = SData.term_unbin msgbin2 cond do ts1 > ts2 -> :after ts1 < ts2 -> :before - nick1 > nick2 -> :after - nick1 < nick2 -> :before + pk1 > pk2 -> :after + pk1 < pk2 -> :before msg1 > msg2 -> :after msg1 < msg2 -> :before true -> :duplicate diff --git a/shard/lib/app/identity.ex b/shard/lib/app/identity.ex new file mode 100644 index 0000000..7e97897 --- /dev/null +++ b/shard/lib/app/identity.ex @@ -0,0 +1,113 @@ +defmodule SApp.Identity do + use GenServer + + require Logger + + defmodule Manifest do + defstruct [:pk] + end + + defmodule State do + defstruct [:info, :rev, :signed] + end + + defimpl Shard.Manifest, for: Manifest do + def start(m) do + DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Identity, m.pk}) + end + end + + def start_link(pk) do + GenServer.start_link(__MODULE__, pk) + end + + def init(pk) do + manifest = %Manifest{pk: pk} + id = SData.term_hash manifest + + case Shard.Manager.register(id, manifest, self()) do + :ok -> + Shard.Manager.dispatch_to(id, nil, self()) + state = case Shard.Manager.load_state(id) do + nil -> + info = %{nick: default_nick(pk)} + SData.SignRev.new info + st -> + st + end + GenServer.cast(self(), :init_pull) + {:ok, %{pk: pk, id: id, state: state}} + :redundant -> + exit(:redundant) + end + end + + def default_nick(pk) do + nick_suffix = pk + |> binary_part(0, 4) + |> Base.encode16 + |> String.downcase + "Anon" <> nick_suffix + end + + def find_proc(pk) do + manifest = %Manifest{pk: pk} + id = SData.term_hash manifest + Shard.Manager.find_proc id + end + + def handle_call(:manifest, _from, state) do + {:replyl, state.manifest, state} + end + + def handle_call(:get_info, _from, state) do + {:reply, SData.SignRev.get(state.state), state} + end + + def handle_call({:set_info, new_info}, _from, state) do + case SData.SignRev.set(state.state, new_info, state.pk) do + {:ok, st2} -> + Shard.Manager.save_state(state.id, st2) + state = put_in(state.state, st2) + bcast_state(state) + {:reply, :ok, state} + err -> + {:reply, err, state} + end + end + + def handle_cast(:init_pull, state) do + for {_, pid, _, _} <- Shard.Manager.list_peers 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)}}) + end + + def handle_cast({:msg, peer_id, _shard_id, nil, msg}, state) do + state = case msg do + {:update, signed} -> + 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]) + state + {false, _} -> + state + end + end + {:noreply, state} + end + + def bcast_state(state, exclude \\ []) do + for peer_id <- Shard.Manager.get_shard_peers(state.id) do + if not Enum.member? exclude, peer_id do + Shard.Manager.send(peer_id, {state.id, nil, {:update, SData.SignRev.signed(state.state)}}) + end + end + end +end -- cgit v1.2.3