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/)
|
||||
|
||||
`zNoise` provides a generic handshake mechanism, that can be used in a couple
|
||||
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`.
|
||||
This is a fork of [enoise](https://git.qpq.swiss/QPQ-AG/enoise).
|
||||
|
||||
+17
-13
@@ -4,6 +4,8 @@
|
||||
%%% @doc
|
||||
%%% 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')
|
||||
%%% and after "upgrading" a `gen_tcp'-socket into a `znoise'-socket it has a
|
||||
%%% similar API as `gen_tcp'.
|
||||
@@ -12,6 +14,7 @@
|
||||
-module(znoise).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
@@ -26,25 +29,26 @@
|
||||
send/2,
|
||||
set_active/2]).
|
||||
|
||||
-record(znoise, {pid}).
|
||||
-record(znoise,
|
||||
{pid = none | pid()}).
|
||||
|
||||
-type key() :: binary().
|
||||
-type keypair() :: znoise_keypair:keypair().
|
||||
|
||||
-type options() :: [option()].
|
||||
%% 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
|
||||
%% `prologue' to the protocol. And for the protocol to work, the correct
|
||||
%% that describes which Noise configuration to use. It is possible to give
|
||||
%% 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
|
||||
%% provided.
|
||||
|
||||
-type option() :: {noise, protocol_option()} %% Required
|
||||
| {e, keypair()} %% Mandatary depending on `noise'
|
||||
| {s, keypair()}
|
||||
| {re, key()}
|
||||
| {rs, key()}
|
||||
-type option() :: {noise, protocol_option()} %% Required
|
||||
| {e, keypair()} %% Mandatary depending on `noise'
|
||||
| {s, keypair()}
|
||||
| {re, key()}
|
||||
| {rs, key()}
|
||||
| {prologue, binary()} %% Optional
|
||||
| {timeout, integer() | infinity}. %% Optional
|
||||
| {timeout, integer() | infinity}. %% Optional
|
||||
|
||||
-type protocol_option() :: znoise_protocol:protocol()
|
||||
| string()
|
||||
@@ -186,17 +190,17 @@ close(#znoise{ pid = Pid }) ->
|
||||
znoise_tcp:close(Pid).
|
||||
|
||||
|
||||
-spec controlling_process(Socket, Pid) -> Outcome
|
||||
-spec controlling_process(Socket, PID) -> Outcome
|
||||
when Socket :: socket(),
|
||||
Pid :: pid(),
|
||||
PID :: pid(),
|
||||
Outcome :: ok | {error, term()}.
|
||||
%% @doc
|
||||
%% 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
|
||||
%% socket.
|
||||
|
||||
controlling_process(#znoise{ pid = Pid }, NewPid) ->
|
||||
znoise_tcp:controlling_process(Pid, NewPid).
|
||||
controlling_process(#znoise{pid = PID}, NewPID) ->
|
||||
znoise_tcp:controlling_process(PID, NewPID).
|
||||
|
||||
|
||||
-spec set_active(Socket, Mode) -> Outcome
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
-module(znoise_cipher_state).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
-module(znoise_crypto).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
-module(znoise_hs_state).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
-module(znoise_keypair).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
-module(znoise_protocol).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
-module(znoise_sym_state).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
|
||||
+43
-34
@@ -11,6 +11,7 @@
|
||||
-module(znoise_tcp).
|
||||
-vsn("0.1.0").
|
||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||
-author("Hans Svensson <hanssv@gmail.com>").
|
||||
-copyright("QPQ AG <info@qpq.swiss>").
|
||||
-license("ISC").
|
||||
|
||||
@@ -24,7 +25,8 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-record(znoise, {pid}).
|
||||
-record(znoise,
|
||||
{pid}).
|
||||
|
||||
-record(s,
|
||||
{rx = ,
|
||||
@@ -158,69 +160,75 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
|
||||
%%% Handlers
|
||||
|
||||
handle_control_change(S = #state{ owner = Pid, owner_ref = OldRef }, Pid, NewPid) ->
|
||||
NewRef = erlang:monitor(process, NewPid),
|
||||
handle_control_change(State = #s{owner = PID, owner_ref = OldRef}, PID, NewPID) ->
|
||||
NewRef = erlang:monitor(process, NewPID),
|
||||
erlang:demonitor(OldRef, [flush]),
|
||||
{ok, S#state{ owner = NewPid, owner_ref = NewRef }};
|
||||
handle_control_change(S, _OldPid, _NewPid) ->
|
||||
{{error, not_owner}, S}.
|
||||
{ok, State#s{owner = NewPID, owner_ref = NewRef}};
|
||||
handle_control_change(State, _, _) ->
|
||||
{{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
|
||||
true ->
|
||||
inet:setopts(TcpSock, [{active, true}]),
|
||||
{ok, handle_msgs(S#state{ active = true })};
|
||||
{ok, handle_msgs(State#s{active = true})};
|
||||
once ->
|
||||
S1 = handle_msgs(S#state{ active = {once, false} }),
|
||||
set_active(S1),
|
||||
{ok, S1}
|
||||
NewState = handle_msgs(State#s{active = {once, false}}),
|
||||
set_active(NewState),
|
||||
{ok, NewState}
|
||||
end;
|
||||
handle_active(S, _Pid, _NewActive) ->
|
||||
{{error, not_owner}, S}.
|
||||
handle_active(State, _, _) ->
|
||||
{{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
|
||||
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>> ->
|
||||
<<Msg:Len/binary, Rest2/binary>> = Rest,
|
||||
case znoise_cipher_state:decrypt_with_ad(Rx, <<>>, Msg) of
|
||||
{ok, Rx1, Msg1} ->
|
||||
{S1, Msgs} = handle_data(S#state{ rawbuf = Rest2, rx = Rx1 }, <<>>),
|
||||
{S1, [Msg1 | Msgs]};
|
||||
case znoise_cipher_state:decrypt_with_ad(RX, <<>>, Msg) of
|
||||
{ok, NewRX, NewMsg} ->
|
||||
{NewState, Msgs} = handle_data(State#s{rawbuf = Rest2, rx = NewRX}, <<>>),
|
||||
{NewState, [NewMsg | Msgs]};
|
||||
{error, _} ->
|
||||
error({znoise_error, decrypt_input_failed})
|
||||
end;
|
||||
EmptyOrSingleByte ->
|
||||
{S#state{ rawbuf = EmptyOrSingleByte }, []}
|
||||
{State#s{rawbuf = EmptyOrSingleByte}, []}
|
||||
end.
|
||||
|
||||
handle_msgs(S = #state{ msgbuf = [] }) ->
|
||||
S;
|
||||
handle_msgs(S = #state{ msgbuf = Msgs, active = true, owner = Owner }) ->
|
||||
[ Owner ! {noise, #znoise{ pid = self() }, Msg} || Msg <- Msgs ],
|
||||
S#state{ msgbuf = [] };
|
||||
handle_msgs(S = #state{ msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner }) ->
|
||||
|
||||
handle_msgs(State = #s{msgbuf = []}) ->
|
||||
State;
|
||||
handle_msgs(State = #s{msgbuf = Msgs, active = true, owner = Owner}) ->
|
||||
[ Owner ! {noise, #znoise{pid = self()}, Msg} || Msg <- Msgs ],
|
||||
State#s{msgbuf = []};
|
||||
handle_msgs(State = #s{msgbuf = [Msg | Msgs], active = {once, Delivered}, owner = Owner}) ->
|
||||
case Delivered of
|
||||
true ->
|
||||
S;
|
||||
State;
|
||||
false ->
|
||||
Owner ! {noise, #znoise{ pid = self() }, Msg},
|
||||
S#state{ msgbuf = Msgs, active = {once, true} }
|
||||
Owner ! {noise, #znoise{pid = self()}, Msg},
|
||||
State#s{msgbuf = Msgs, active = {once, true}}
|
||||
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
|
||||
ok -> {ok, S#state{ tx = Tx1 }};
|
||||
Err = {error, _} -> {Err, S}
|
||||
ok -> {ok, State#s{tx = MewTX}};
|
||||
Error -> {Error, State}
|
||||
end.
|
||||
|
||||
set_active(#state{ msgbuf = [], active = {once, _}, tcp_sock = TcpSock }) ->
|
||||
|
||||
set_active(#s{msgbuf = [], active = {once, _}, tcp_sock = TcpSock}) ->
|
||||
inet:setopts(TcpSock, [{active, once}]);
|
||||
set_active(_) ->
|
||||
ok.
|
||||
|
||||
|
||||
flush_tcp(Pid, TcpSock) ->
|
||||
receive {tcp, TcpSock, Data} ->
|
||||
Pid ! {tcp, TcpSock, Data},
|
||||
@@ -228,6 +236,7 @@ flush_tcp(Pid, TcpSock) ->
|
||||
after 1 -> ok
|
||||
end.
|
||||
|
||||
|
||||
close_tcp(closed) ->
|
||||
ok;
|
||||
close_tcp(Sock) ->
|
||||
|
||||
Reference in New Issue
Block a user