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/)
`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).
+11 -7
View File
@@ -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,15 +29,16 @@
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.
@@ -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
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+1
View File
@@ -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").
+41 -32
View File
@@ -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 }) ->
handle_msgs(State = #s{msgbuf = []}) ->
State;
handle_msgs(State = #s{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 }) ->
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} }
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) ->