1
0
forked from QPQ-AG/enoise

1 Commits

Author SHA1 Message Date
zxq9 853f6b5a8d WIP 2026-06-10 16:53:57 +09:00
8 changed files with 188 additions and 218 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ _*
*.swp *.swp
*.swo *.swo
.erlang.cookie .erlang.cookie
ebin/*.beam ebin
log log
erl_crash.dump erl_crash.dump
.rebar .rebar
-1
View File
@@ -1 +0,0 @@
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
+8 -7
View File
@@ -1,14 +1,15 @@
Copyright 2025 Aeternity Anstalt, QPQ AG <ulf@wiger.net> ISC License
Copyright (c) 2018, aeternity developers
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies. copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED AS IS AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
-12
View File
@@ -1,12 +0,0 @@
{application,enoise,
[{description,"Noise protocol"},
{vsn,"1.3.0"},
{registered,[]},
{applications,[kernel,stdlib,crypto]},
{env,[]},
{modules,[enoise,enoise_cipher_state,enoise_connection,
enoise_crypto,enoise_hs_state,enoise_keypair,
enoise_protocol,enoise_sym_state]},
{maintainers,["Hans Svensson"]},
{licenses,["ISC"]},
{links,[{"Github","https://github.com/aeternity/enoise"}]}]}.
+143 -101
View File
@@ -1,16 +1,14 @@
%%% ------------------------------------------------------------------ %%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt %%% @copyright 2018, Aeternity Anstalt
%%% %%%
%%% @doc Module is an interface to the Noise protocol %%% @doc
%%% [https://noiseprotocol.org] %%% Interface to the [Noise protocol](https://noiseprotocol.org)
%%%
%%% The module implements Noise handshake in `handshake/3'.
%%% %%%
%%% 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 `enoise'-socket it has a %%% and after "upgrading" a `gen_tcp'-socket into a `enoise'-socket it has a
%%% similar API as `gen_tcp'. %%% similar API as `gen_tcp'.
%%% %%%
%%% @end ------------------------------------------------------------------ %%% @end
-module(enoise). -module(enoise).
@@ -18,95 +16,106 @@
-export([handshake/2, handshake/3, step_handshake/2]). -export([handshake/2, handshake/3, step_handshake/2]).
%% API exports - Mainly mimicing gen_tcp %% API exports - Mainly mimicing gen_tcp
-export([ accept/2 -export([accept/2,
, close/1 close/1,
, connect/2 connect/2,
, controlling_process/2 controlling_process/2,
, send/2 send/2,
, set_active/2 ]). set_active/2]).
-record(enoise, {pid}). -record(enoise, {pid}).
-type noise_key() :: binary(). -type key() :: binary().
-type noise_keypair() :: enoise_keypair:keypair(). -type keypair() :: enoise_keypair:keypair().
-type noise_options() :: [noise_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 a
%% `prologue' to the protocol. And for the protocol to work, the correct %% `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.
-type noise_option() :: {noise, noise_protocol_option()} %% Required -type option() :: {noise, protocol_option()} %% Required
| {e, noise_keypair()} %% Mandatary depending on `noise' | {e, keypair()} %% Mandatary depending on `noise'
| {s, noise_keypair()} | {s, keypair()}
| {re, noise_key()} | {re, key()}
| {rs, noise_key()} | {rs, key()}
| {prologue, binary()} %% Optional | {prologue, binary()} %% Optional
| {timeout, integer() | infinity}. %% Optional | {timeout, integer() | infinity}. %% Optional
-type noise_protocol_option() :: enoise_protocol:protocol() | string() | -type protocol_option() :: enoise_protocol:protocol()
binary(). | string()
| binary().
%% Either an instantiated Noise protocol configuration or the name of a Noise %% Either an instantiated Noise protocol configuration or the name of a Noise
%% configuration (either as a string or a binary string). %% configuration (either as a string or a binary string).
-type com_state_state() :: term(). -type com_state_state() :: term().
%% The state part of a communiction state %% The state part of a communiction state
-type recv_msg_fun() :: fun((com_state_state(), integer() | infinity) -> -type timeout() :: pos_integer() | infinity.
{ok, binary(), com_state_state()} | {error, term()}). -type recv_return() :: {ok, binary(), com_state_state()}
%% Function that receive a message | {error, term()}).
-type recv_msg_fun() :: fun((com_state_state(), timeout()) -> recv_return().
-type send_msg_fun() :: fun((com_state_state(), binary()) -> ok). -type send_msg_fun() :: fun((com_state_state(), binary()) -> ok).
%% Function that sends a message
-type noise_com_state() :: #{ recv_msg := recv_msg_fun(), -type com_state() :: #{recv_msg := recv_msg_fun(),
send_msg := send_msg_fun(), send_msg := send_msg_fun(),
state := term()}. state := term()}.
%% Noise communication state - used to parameterize a handshake. Consists of a %% Noise communication state - used to parameterize a handshake. Consists of a
%% send function, one receive function, and an internal state. %% send function, one receive function, and an internal state.
-type noise_split_state() :: enoise_hs_state:noise_split_state(). -type split_state() :: enoise_hs_state:noise_split_state().
%% Return value from the final `split' operation. Provides a CipherState for %% Return value from the final `split' operation. Provides a CipherState for
%% receiving and a CipherState transmission. Also includes the final handshake %% receiving and a CipherState transmission. Also includes the final handshake
%% hash for channel binding. %% hash for channel binding.
-opaque noise_socket() :: #enoise{}. -opaque socket() :: #enoise{}.
%% An abstract Noise socket - holds a reference to a socket that has completed %% An abstract Noise socket - holds a reference to a socket that has completed
%% a Noise handshake. %% a Noise handshake.
-export_type([noise_socket/0]). -export_type([socket/0]).
%%====================================================================
%% API functions
%%====================================================================
%% @doc Start an interactive handshake
%% @end %%% API functions
-spec handshake(Options :: noise_options(),
Role :: enoise_hs_state:noise_role()) -> -spec handshake(Options, Role) -> Outcome
{ok, enoise_hs_state:state()} | {error, term()}. when Options :: options(),
Role :: enoise_hs_state:noise_role(),
Outcome :: {ok, enoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Start an interactive handshake
handshake(Options, Role) -> handshake(Options, Role) ->
create_hstate(Options, Role). create_hstate(Options, Role).
%% @doc Do a step (either `{send, Payload}', `{rcvd, EncryptedData}',
%% or `done') -spec step_handshake(HState, Data) -> Next
%% @end when HState :: enoise_hs_state:state(),
-spec step_handshake(HState :: enoise_hs_state:state(), Data :: {rcvd, binary()}
Data :: {rcvd, binary()} | {send, binary()}) -> | {send, binary()},
{ok, send, binary(), enoise_hs_state:state()} Next :: {ok, send, binary(), enoise_hs_state:state()}
| {ok, rcvd, binary(), enoise_hs_state:state()} | {ok, rcvd, binary(), enoise_hs_state:state()}
| {ok, done, noise_split_state()} | {ok, done, split_state()}
| {error, term()}. | {error, term()}.
%% @doc
%% Do a step (one of `{send, Payload}', `{rcvd, EncryptedData}', or `done')
step_handshake(HState, Data) -> step_handshake(HState, Data) ->
do_step_handshake(HState, Data). do_step_handshake(HState, Data).
%% @doc Perform a Noise handshake
%% @end -spec handshake(Options, Role, ComState) -> Outcome
-spec handshake(Options :: noise_options(), when Options :: options(),
Role :: enoise_hs_state:noise_role(), Role :: enoise_hs_state:noise_role(),
ComState :: noise_com_state()) -> ComState :: com_state(),
{ok, noise_split_state(), noise_com_state()} | {error, term()}. Outcome :: {ok, split_state(), com_state()}
| {error, term()}.
%% @doc
%% Perform a Noise handshake
handshake(Options, Role, ComState) -> handshake(Options, Role, ComState) ->
case create_hstate(Options, Role) of case create_hstate(Options, Role) of
{ok, HState} -> {ok, HState} ->
@@ -116,66 +125,92 @@ handshake(Options, Role, ComState) ->
Err Err
end. end.
%% @doc Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
-spec connect(TcpSock, Options) -> Outcome
when TcpSock :: gen_tcp:socket(),
Options :: options(),
Outcome :: {ok, socket(), enoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
%% that is, performs the client-side noise handshake. %% that is, performs the client-side noise handshake.
%% %%
%% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}', %% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}',
%% passive receive is not supported. %% passive receive is not supported.
%% %%
%% {@link noise_options()} is a proplist. %% {@link options()} is a proplist.
%% @end
-spec connect(TcpSock :: gen_tcp:socket(),
Options :: noise_options()) ->
{ok, noise_socket(), enoise_hs_state:state()} | {error, term()}.
connect(TcpSock, Options) -> connect(TcpSock, Options) ->
tcp_handshake(TcpSock, initiator, Options). tcp_handshake(TcpSock, initiator, Options).
%% @doc Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
-spec accept(TcpSock, Options) -> Outcome
when TcpSock :: gen_tcp:socket(),
Options :: options(),
Outcome :: {ok, socket(), enoise_hs_state:state()}
| {error, term()}.
%% @doc
%% Upgrades a gen_tcp, or equivalent, connected socket to a Noise socket,
%% that is, performs the server-side noise handshake. %% that is, performs the server-side noise handshake.
%% %%
%% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}', %% Note: The TCP socket has to be in mode `{active, true}' or `{active, once}',
%% passive receive is not supported. %% passive receive is not supported.
%% %%
%% {@link noise_options()} is a proplist. %% {@link options()} is a proplist.
%% @end
-spec accept(TcpSock :: gen_tcp:socket(),
Options :: noise_options()) ->
{ok, noise_socket(), enoise_hs_state:state()} | {error, term()}.
accept(TcpSock, Options) -> accept(TcpSock, Options) ->
tcp_handshake(TcpSock, responder, Options). tcp_handshake(TcpSock, responder, Options).
%% @doc Writes `Data' to `Socket'
%% @end -spec send(Socket, Data) -> Outcome
-spec send(Socket :: noise_socket(), Data :: binary()) -> ok | {error, term()}. when Socket :: socket(),
Data :: binary(),
Outcome :: ok | {error, term()}.
%% @doc
%% Writes `Data' to `Socket'
send(#enoise{ pid = Pid }, Data) -> send(#enoise{ pid = Pid }, Data) ->
enoise_connection:send(Pid, Data). enoise_connection:send(Pid, Data).
%% @doc Closes a Noise connection.
%% @end -spec close(NoiseSock) -> Outcome
-spec close(NoiseSock :: noise_socket()) -> ok | {error, term()}. when NoiseSock :: socket()
Outcome :: ok | {error, term()}.
%% @doc
%% Closes a Noise connection.
close(#enoise{ pid = Pid }) -> close(#enoise{ pid = Pid }) ->
enoise_connection:close(Pid). enoise_connection:close(Pid).
%% @doc Assigns a new controlling process to the Noise socket. A controlling
-spec controlling_process(Socket, Pid) -> Outcome
when Socket :: socket(),
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 %% process is the owner of an Noise socket, and receives all messages from the
%% socket. %% socket.
%% @end
-spec controlling_process(Socket :: noise_socket(), Pid :: pid()) ->
ok | {error, term()}.
controlling_process(#enoise{ pid = Pid }, NewPid) -> controlling_process(#enoise{ pid = Pid }, NewPid) ->
enoise_connection:controlling_process(Pid, NewPid). enoise_connection:controlling_process(Pid, NewPid).
%% @doc Set the active option `true | once'. Note that `N' and `false' are
-spec set_active(Socket, Mode) -> Outcome
when Socket :: socket(),
Mode :: true | once,
Outcome :: ok | {error, term()}.
%% @doc
%% Set the active option `true | once'. Note that `N' and `false' are
%% not valid options for a Noise socket. %% not valid options for a Noise socket.
%% @end
-spec set_active(Socket :: noise_socket(), Mode :: true | once) ->
ok | {error, term()}.
set_active(#enoise{ pid = Pid }, ActiveMode) -> set_active(#enoise{ pid = Pid }, ActiveMode) ->
enoise_connection:set_active(Pid, ActiveMode). enoise_connection:set_active(Pid, ActiveMode).
%%====================================================================
%% Internal functions
%%==================================================================== %%% Internal functions
do_handshake(HState, ComState, Timeout) -> do_handshake(HState, ComState, Timeout) ->
case enoise_hs_state:next_message(HState) of case enoise_hs_state:next_message(HState) of
in -> in ->
@@ -248,6 +283,7 @@ tcp_handshake(TcpSock, Role, Options) ->
Err Err
end. end.
do_tcp_handshake(Options, Role, TcpSock, Active) -> do_tcp_handshake(Options, Role, TcpSock, Active) ->
ComState = #{recv_msg => fun gen_tcp_rcv_msg/2, ComState = #{recv_msg => fun gen_tcp_rcv_msg/2,
send_msg => fun gen_tcp_snd_msg/2, send_msg => fun gen_tcp_snd_msg/2,
@@ -256,30 +292,27 @@ do_tcp_handshake(Options, Role, TcpSock, Active) ->
{ok, #{rx := Rx, tx := Tx, final_state := FState}, #{state := {_, _, Buf}}} -> {ok, #{rx := Rx, tx := Tx, final_state := FState}, #{state := {_, _, Buf}}} ->
case enoise_connection:start_link(TcpSock, Rx, Tx, self(), {Active, Buf}) of case enoise_connection:start_link(TcpSock, Rx, Tx, self(), {Active, Buf}) of
{ok, Pid} -> {ok, #enoise{ pid = Pid }, FState}; {ok, Pid} -> {ok, #enoise{ pid = Pid }, FState};
Err = {error, _} -> Err Error -> Error
end; end;
Err = {error, _} -> Error ->
Err Error
end. end.
create_hstate(Options, Role) -> create_hstate(Options, Role) ->
Prologue = proplists:get_value(prologue, Options, <<>>), Prologue = proplists:get_value(prologue, Options, <<>>),
NoiseProtocol0 = proplists:get_value(noise, Options), Noise = proplists:get_value(noise, Options),
Protocol =
NoiseProtocol = case is_binary(Noise) orelse is_list(Noise) of
case NoiseProtocol0 of true -> enoise_protocol:from_name(X);
X when is_binary(X); is_list(X) -> false -> Noise
enoise_protocol:from_name(X);
_ -> NoiseProtocol0
end, end,
DH = enoise_protocol:dh(NoiseProtocol), DH = enoise_protocol:dh(Protocol),
S = proplists:get_value(s, Options, undefined), S = proplists:get_value(s, Options, undefined),
E = proplists:get_value(e, Options, undefined), E = proplists:get_value(e, Options, undefined),
RS = remote_keypair(DH, proplists:get_value(rs, Options, undefined)), RS = remote_keypair(DH, proplists:get_value(rs, Options, undefined)),
RE = remote_keypair(DH, proplists:get_value(re, Options, undefined)), RE = remote_keypair(DH, proplists:get_value(re, Options, undefined)),
enoise_hs_state:init(Protocol, Role, Prologue, {S, E, RS, RE}).
enoise_hs_state:init(NoiseProtocol, Role,
Prologue, {S, E, RS, RE}).
check_gen_tcp(TcpSock) -> check_gen_tcp(TcpSock) ->
@@ -290,27 +323,33 @@ check_gen_tcp(TcpSock) ->
Header = proplists:get_value(header, TcpOpts, 0), Header = proplists:get_value(header, TcpOpts, 0),
PSize = proplists:get_value(packet_size, TcpOpts, undefined), PSize = proplists:get_value(packet_size, TcpOpts, undefined),
Mode = proplists:get_value(mode, TcpOpts, binary), Mode = proplists:get_value(mode, TcpOpts, binary),
case (Packet == 0 orelse Packet == raw) case
(Packet == 0 orelse Packet == raw)
andalso (Active == true orelse Active == once) andalso (Active == true orelse Active == once)
andalso Header == 0 andalso PSize == 0 andalso Mode == binary of andalso Header == 0
andalso PSize == 0
andalso Mode == binary of
true -> true ->
gen_tcp:controlling_process(TcpSock, self()); gen_tcp:controlling_process(TcpSock, self());
false -> false ->
{error, {invalid_tcp_options, TcpOpts}} {error, {invalid_tcp_options, TcpOpts}}
end; end;
Err = {error, _} -> Error ->
Err Error
end. end.
gen_tcp_snd_msg(S = {TcpSock, _, _}, Msg) -> gen_tcp_snd_msg(S = {TcpSock, _, _}, Msg) ->
Len = byte_size(Msg), Len = byte_size(Msg),
case gen_tcp:send(TcpSock, <<Len:16, Msg/binary>>) of case gen_tcp:send(TcpSock, <<Len:16, Msg/binary>>) of
ok -> {ok, S}; ok -> {ok, S};
Err = {error, _} -> Err Error -> Error
end. end.
gen_tcp_rcv_msg({TcpSock, Active, Buf}, Timeout) -> gen_tcp_rcv_msg({TcpSock, Active, Buf}, Timeout) ->
receive {tcp, TcpSock, Data} -> receive
{tcp, TcpSock, Data} ->
%% Immediately re-set {active, once} %% Immediately re-set {active, once}
[inet:setopts(TcpSock, [{active, once}]) || Active == once], [inet:setopts(TcpSock, [{active, once}]) || Active == once],
case <<Buf/binary, Data/binary>> of case <<Buf/binary, Data/binary>> of
@@ -324,5 +363,8 @@ gen_tcp_rcv_msg({TcpSock, Active, Buf}, Timeout) ->
{error, timeout} {error, timeout}
end. end.
remote_keypair(_DH, undefined) -> undefined;
remote_keypair(DH, RemotePub) when is_binary(RemotePub) -> enoise_keypair:new(DH, RemotePub). remote_keypair(_DH, undefined) ->
undefined;
remote_keypair(DH, RemotePub) when is_binary(RemotePub) ->
enoise_keypair:new(DH, RemotePub).
+9 -10
View File
@@ -1,19 +1,18 @@
%%% ------------------------------------------------------------------ %%% @copyright 2026, QPQ AG
%%% @copyright 2018, Aeternity Anstalt %%% @copyright 2018, Aeternity Anstalt
%%% %%%
%%% @doc Module encapsulating a Noise handshake state %%% @doc
%%% %%% Module encapsulating a Noise handshake state
%%% @end %%% @end
%%% ------------------------------------------------------------------
-module(enoise_hs_state). -module(enoise_hs_state).
-export([ finalize/1 -export([finalize/1,
, init/4 init/4,
, next_message/1 next_message/1,
, read_message/2 read_message/2,
, remote_keys/1 remote_keys/1,
, write_message/2]). write_message/2]).
-include("enoise.hrl"). -include("enoise.hrl").
-17
View File
@@ -1,17 +0,0 @@
{name,"enoise"}.
{type,app}.
{modules,[]}.
{prefix,"enoise"}.
{desc,"Noise protocol"}.
{author,"Hans Svensson, QPQ AG"}.
{package_id,{"uwiger","enoise",{1,3,0}}}.
{deps,[]}.
{key_name,none}.
{a_email,"ulf@wiger.net"}.
{c_email,"ulf@wiger.net"}.
{copyright,"Aeternity Anstalt, QPQ AG"}.
{file_exts,[]}.
{license,"ISC"}.
{repo_url,"https://git.qpq.swiss/QPQ-AG/enoise"}.
{tags,[]}.
{ws_url,[]}.
-42
View File
@@ -1,42 +0,0 @@
#!/bin/sh
set -e
APP=$(basename "$PWD")
SRC="_build/default/lib/$APP"
DST="$PWD/_build/zomp/lib/$APP"
IGNORE_FILE="zomp.ignore"
mkdir -p "$DST"
# Remove broken symlinks
find "$SRC" -type l ! -exec test -e {} \; -delete || true
# Build ignore matcher
IGNORE_TEMP=$(mktemp)
trap "rm -f $IGNORE_TEMP" EXIT
# Expand globs in zomp.ignore to patterns suitable for grep
if [ -e "$IGNORE_FILE" ]; then
grep -v '^\s*#' "$IGNORE_FILE" | sed 's#/#\\/#g' | sed 's/\./\\./g' | sed 's/\*/.*/g' > "$IGNORE_TEMP"
fi
# Copy Git-tracked and Zomp-allowed files
git ls-files -z | while IFS= read -r -d '' file; do
# Skip if ignored
echo "$file" | grep -Eq -f "$IGNORE_TEMP" && continue
# Only copy if file exists in the build dir
if [ -e "$SRC/$file" ]; then
mkdir -p "$DST/$(dirname "$file")"
cp -a "$SRC/$file" "$DST/$file"
fi
done
rm "$IGNORE_TEMP"
# Copy metadata
cp "$PWD/zomp.meta" "$DST/"
cp "$PWD/Emakefile" "$DST/"
# Clean up beam files just in case
[ -d "$DST/ebin" ] && find "$DST/ebin" -name '*.beam' -exec rm -f {} + || true