forked from QPQ-AG/enoise
WIP
This commit is contained in:
@@ -3,29 +3,4 @@ zNoise
|
|||||||
|
|
||||||
An Erlang implementation of the [Noise protocol](https://noiseprotocol.org/)
|
An Erlang implementation of the [Noise protocol](https://noiseprotocol.org/)
|
||||||
|
|
||||||
`zNoise` provides a generic handshake mechanism, that can be used in a couple
|
This is a fork of [enoise](https://git.qpq.swiss/QPQ-AG/enoise).
|
||||||
of different ways. There is also a plain `gen_tcp` wrapper, where you can
|
|
||||||
"upgrade" a TCP socket to a Noise socket and use it in much the same way as you
|
|
||||||
would use `gen_tcp`.
|
|
||||||
|
|
||||||
Interactive handshake
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
When using `zNoise` to do an interactive handshake, `zNoise` will only take
|
|
||||||
care of message composition/decompositiona and encryption/decryption - i.e. the
|
|
||||||
user has to do the actual sending and receiving.
|
|
||||||
|
|
||||||
An example of the interactive handshake can be seen in the `noise_interactive`
|
|
||||||
test in `test/enoise_tests.erl`.
|
|
||||||
|
|
||||||
Generic handshake
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
There is also the option to use an automated handshake procedure. If provided
|
|
||||||
with a generic _Communication state_ that describe how data is sent and
|
|
||||||
received, the handshake procedure is done automatically. The result of a
|
|
||||||
successful handshake is two Cipher states that can be used to encrypt/decrypt a
|
|
||||||
RX channel and a TX channel respectively.
|
|
||||||
|
|
||||||
The provided `gen_tcp`-wrapper is implemented using the generic handshake, see
|
|
||||||
`src/enoise.erl`.
|
|
||||||
|
|||||||
+11
-7
@@ -4,6 +4,8 @@
|
|||||||
%%% @doc
|
%%% @doc
|
||||||
%%% Interface to the Noise protocol: https://noiseprotocol.org
|
%%% Interface to the Noise protocol: https://noiseprotocol.org
|
||||||
%%%
|
%%%
|
||||||
|
%%% This is a fork of the `enoise' project: https://git.qpq.swiss/QPQ-AG/enoise
|
||||||
|
%%%
|
||||||
%%% For convenience there is also an API to use Noise over TCP (i.e. `gen_tcp')
|
%%% For convenience there is also an API to use Noise over TCP (i.e. `gen_tcp')
|
||||||
%%% and after "upgrading" a `gen_tcp'-socket into a `znoise'-socket it has a
|
%%% and after "upgrading" a `gen_tcp'-socket into a `znoise'-socket it has a
|
||||||
%%% similar API as `gen_tcp'.
|
%%% similar API as `gen_tcp'.
|
||||||
@@ -12,6 +14,7 @@
|
|||||||
-module(znoise).
|
-module(znoise).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
@@ -26,15 +29,16 @@
|
|||||||
send/2,
|
send/2,
|
||||||
set_active/2]).
|
set_active/2]).
|
||||||
|
|
||||||
-record(znoise, {pid}).
|
-record(znoise,
|
||||||
|
{pid = none | pid()}).
|
||||||
|
|
||||||
-type key() :: binary().
|
-type key() :: binary().
|
||||||
-type keypair() :: znoise_keypair:keypair().
|
-type keypair() :: znoise_keypair:keypair().
|
||||||
|
|
||||||
-type options() :: [option()].
|
-type options() :: [option()].
|
||||||
%% A list of Noise options is a proplist, it *must* contain a value `noise'
|
%% A list of Noise options is a proplist, it *must* contain a value `noise'
|
||||||
%% that describes which Noise configuration to use. It is possible to give a
|
%% that describes which Noise configuration to use. It is possible to give
|
||||||
%% `prologue' to the protocol. And for the protocol to work, the correct
|
%% a `prologue' to the protocol. And for the protocol to work, the correct
|
||||||
%% configuration of pre-defined keys (`s', `e', `rs', `re') should also be
|
%% configuration of pre-defined keys (`s', `e', `rs', `re') should also be
|
||||||
%% provided.
|
%% provided.
|
||||||
|
|
||||||
@@ -186,17 +190,17 @@ close(#znoise{ pid = Pid }) ->
|
|||||||
znoise_tcp:close(Pid).
|
znoise_tcp:close(Pid).
|
||||||
|
|
||||||
|
|
||||||
-spec controlling_process(Socket, Pid) -> Outcome
|
-spec controlling_process(Socket, PID) -> Outcome
|
||||||
when Socket :: socket(),
|
when Socket :: socket(),
|
||||||
Pid :: pid(),
|
PID :: pid(),
|
||||||
Outcome :: ok | {error, term()}.
|
Outcome :: ok | {error, term()}.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Assigns a new controlling process to the Noise socket. A controlling
|
%% Assigns a new controlling process to the Noise socket. A controlling
|
||||||
%% process is the owner of an Noise socket, and receives all messages from the
|
%% process is the owner of an Noise socket, and receives all messages from the
|
||||||
%% socket.
|
%% socket.
|
||||||
|
|
||||||
controlling_process(#znoise{ pid = Pid }, NewPid) ->
|
controlling_process(#znoise{pid = PID}, NewPID) ->
|
||||||
znoise_tcp:controlling_process(Pid, NewPid).
|
znoise_tcp:controlling_process(PID, NewPID).
|
||||||
|
|
||||||
|
|
||||||
-spec set_active(Socket, Mode) -> Outcome
|
-spec set_active(Socket, Mode) -> Outcome
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
-module(znoise_cipher_state).
|
-module(znoise_cipher_state).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
-module(znoise_crypto).
|
-module(znoise_crypto).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
-module(znoise_hs_state).
|
-module(znoise_hs_state).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
-module(znoise_keypair).
|
-module(znoise_keypair).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
-module(znoise_protocol).
|
-module(znoise_protocol).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
-module(znoise_sym_state).
|
-module(znoise_sym_state).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
|
|||||||
+43
-34
@@ -11,6 +11,7 @@
|
|||||||
-module(znoise_tcp).
|
-module(znoise_tcp).
|
||||||
-vsn("0.1.0").
|
-vsn("0.1.0").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
|
-author("Hans Svensson <hanssv@gmail.com>").
|
||||||
-copyright("QPQ AG <info@qpq.swiss>").
|
-copyright("QPQ AG <info@qpq.swiss>").
|
||||||
-license("ISC").
|
-license("ISC").
|
||||||
|
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-record(znoise, {pid}).
|
-record(znoise,
|
||||||
|
{pid}).
|
||||||
|
|
||||||
-record(s,
|
-record(s,
|
||||||
{rx = ,
|
{rx = ,
|
||||||
@@ -158,69 +160,75 @@ code_change(_OldVsn, State, _Extra) ->
|
|||||||
|
|
||||||
%%% Handlers
|
%%% Handlers
|
||||||
|
|
||||||
handle_control_change(S = #state{ owner = Pid, owner_ref = OldRef }, Pid, NewPid) ->
|
handle_control_change(State = #s{owner = PID, owner_ref = OldRef}, PID, NewPID) ->
|
||||||
NewRef = erlang:monitor(process, NewPid),
|
NewRef = erlang:monitor(process, NewPID),
|
||||||
erlang:demonitor(OldRef, [flush]),
|
erlang:demonitor(OldRef, [flush]),
|
||||||
{ok, S#state{ owner = NewPid, owner_ref = NewRef }};
|
{ok, State#s{owner = NewPID, owner_ref = NewRef}};
|
||||||
handle_control_change(S, _OldPid, _NewPid) ->
|
handle_control_change(State, _, _) ->
|
||||||
{{error, not_owner}, S}.
|
{{error, not_owner}, State}.
|
||||||
|
|
||||||
handle_active(S = #state{ owner = Pid, tcp_sock = TcpSock }, Pid, Active) ->
|
|
||||||
|
handle_active(State = #s{owner = PID, tcp_sock = TcpSock}, PID, Active) ->
|
||||||
case Active of
|
case Active of
|
||||||
true ->
|
true ->
|
||||||
inet:setopts(TcpSock, [{active, true}]),
|
inet:setopts(TcpSock, [{active, true}]),
|
||||||
{ok, handle_msgs(S#state{ active = true })};
|
{ok, handle_msgs(State#s{active = true})};
|
||||||
once ->
|
once ->
|
||||||
S1 = handle_msgs(S#state{ active = {once, false} }),
|
NewState = handle_msgs(State#s{active = {once, false}}),
|
||||||
set_active(S1),
|
set_active(NewState),
|
||||||
{ok, S1}
|
{ok, NewState}
|
||||||
end;
|
end;
|
||||||
handle_active(S, _Pid, _NewActive) ->
|
handle_active(State, _, _) ->
|
||||||
{{error, not_owner}, S}.
|
{{error, not_owner}, State}.
|
||||||
|
|
||||||
handle_data(S = #state{ rawbuf = Buf, rx = Rx }, Data) ->
|
|
||||||
|
handle_data(State = #s{rawbuf = Buf, rx = RX}, Data) ->
|
||||||
case <<Buf/binary, Data/binary>> of
|
case <<Buf/binary, Data/binary>> of
|
||||||
B = <<Len:16, Rest/binary>> when Len > byte_size(Rest) ->
|
B = <<Len:16, Rest/binary>> when Len > byte_size(Rest) ->
|
||||||
{S#state{ rawbuf = B }, []}; %% Not a full Noise message - save it
|
{State#s{rawbuf = B}, []}; %% Not a full Noise message - save it
|
||||||
<<Len:16, Rest/binary>> ->
|
<<Len:16, Rest/binary>> ->
|
||||||
<<Msg:Len/binary, Rest2/binary>> = Rest,
|
<<Msg:Len/binary, Rest2/binary>> = Rest,
|
||||||
case znoise_cipher_state:decrypt_with_ad(Rx, <<>>, Msg) of
|
case znoise_cipher_state:decrypt_with_ad(RX, <<>>, Msg) of
|
||||||
{ok, Rx1, Msg1} ->
|
{ok, NewRX, NewMsg} ->
|
||||||
{S1, Msgs} = handle_data(S#state{ rawbuf = Rest2, rx = Rx1 }, <<>>),
|
{NewState, Msgs} = handle_data(State#s{rawbuf = Rest2, rx = NewRX}, <<>>),
|
||||||
{S1, [Msg1 | Msgs]};
|
{NewState, [NewMsg | Msgs]};
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
error({znoise_error, decrypt_input_failed})
|
error({znoise_error, decrypt_input_failed})
|
||||||
end;
|
end;
|
||||||
EmptyOrSingleByte ->
|
EmptyOrSingleByte ->
|
||||||
{S#state{ rawbuf = EmptyOrSingleByte }, []}
|
{State#s{rawbuf = EmptyOrSingleByte}, []}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_msgs(S = #state{ msgbuf = [] }) ->
|
|
||||||
S;
|
handle_msgs(State = #s{msgbuf = []}) ->
|
||||||
handle_msgs(S = #state{ msgbuf = Msgs, active = true, owner = Owner }) ->
|
State;
|
||||||
[ Owner ! {noise, #znoise{ pid = self() }, Msg} || Msg <- Msgs ],
|
handle_msgs(State = #s{msgbuf = Msgs, active = true, owner = Owner}) ->
|
||||||
S#state{ msgbuf = [] };
|
[ Owner ! {noise, #znoise{pid = self()}, Msg} || Msg <- Msgs ],
|
||||||
handle_msgs(S = #state{ msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner }) ->
|
State#s{msgbuf = []};
|
||||||
|
handle_msgs(State = #s{msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner}) ->
|
||||||
case Delivered of
|
case Delivered of
|
||||||
true ->
|
true ->
|
||||||
S;
|
State;
|
||||||
false ->
|
false ->
|
||||||
Owner ! {noise, #znoise{ pid = self() }, Msg},
|
Owner ! {noise, #znoise{pid = self()}, Msg},
|
||||||
S#state{ msgbuf = Msgs, active = {once, true} }
|
State#s{msgbuf = Msgs, active = {once, true}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_send(S = #state{ tcp_sock = TcpSock, tx = Tx }, Data) ->
|
|
||||||
{ok, Tx1, Msg} = znoise_cipher_state:encrypt_with_ad(Tx, <<>>, Data),
|
handle_send(State = #s{tcp_sock = TcpSock, tx = TX}, Data) ->
|
||||||
|
{ok, MewTX, Msg} = znoise_cipher_state:encrypt_with_ad(TX, <<>>, Data),
|
||||||
case gen_tcp:send(TcpSock, <<(byte_size(Msg)):16, Msg/binary>>) of
|
case gen_tcp:send(TcpSock, <<(byte_size(Msg)):16, Msg/binary>>) of
|
||||||
ok -> {ok, S#state{ tx = Tx1 }};
|
ok -> {ok, State#s{tx = MewTX}};
|
||||||
Err = {error, _} -> {Err, S}
|
Error -> {Error, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
set_active(#state{ msgbuf = [], active = {once, _}, tcp_sock = TcpSock }) ->
|
|
||||||
|
set_active(#s{msgbuf = [], active = {once, _}, tcp_sock = TcpSock}) ->
|
||||||
inet:setopts(TcpSock, [{active, once}]);
|
inet:setopts(TcpSock, [{active, once}]);
|
||||||
set_active(_) ->
|
set_active(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
flush_tcp(Pid, TcpSock) ->
|
flush_tcp(Pid, TcpSock) ->
|
||||||
receive {tcp, TcpSock, Data} ->
|
receive {tcp, TcpSock, Data} ->
|
||||||
Pid ! {tcp, TcpSock, Data},
|
Pid ! {tcp, TcpSock, Data},
|
||||||
@@ -228,6 +236,7 @@ flush_tcp(Pid, TcpSock) ->
|
|||||||
after 1 -> ok
|
after 1 -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
close_tcp(closed) ->
|
close_tcp(closed) ->
|
||||||
ok;
|
ok;
|
||||||
close_tcp(Sock) ->
|
close_tcp(Sock) ->
|
||||||
|
|||||||
Reference in New Issue
Block a user