1
0
forked from QPQ-AG/enoise
This commit is contained in:
2026-06-12 20:45:55 +09:00
parent d7c8f1ec29
commit a6eaf3e17e
9 changed files with 67 additions and 73 deletions
+1 -26
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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
View File
@@ -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) ->