11 Commits

Author SHA1 Message Date
zxq9 853f6b5a8d WIP 2026-06-10 16:53:57 +09:00
uwiger 029292817e Merge pull request 'Ditch enacl, support DH448 and Blake2s, and fix types (#14)' (#2) from hanssv-remove-enoise into master
Reviewed-on: #2
2025-03-30 05:02:52 +09:00
Hans Svensson 2b5f08e156 Ditch enacl, support DH448 and Blake2s, and fix types (#14)
* Remove get_stacktrace (deprecated since OTP-24)

* Add DH448 support and switch to crypto:generate_key for DH25519

* Switch to crypto:hash/2 for Blake2b and support Blake2s

* Switch last enacl calls to crypto - no more enacl

* Eqwalizer fixes

Ewqalizer fix

Eqwalizer fix

Eqwalizer fix

Eqwalizer fix

Eqwalizer support

Eqwalizer fix

Fix tests to follow types (remote keys)

* More error handling on setup

* Dialyzer fix

* Write CHANGELOG

* Note about type-checking in README
2025-03-30 05:02:35 +09:00
Ulf Wiger 91916908a0 Revert "Update enacl dep and fix some minor details (#1)"
This reverts commit 479ec70870.
2025-03-29 20:57:45 +01:00
uwiger 479ec70870 Update enacl dep and fix some minor details (#1)
Co-authored-by: Ulf Wiger <ulf@wiger.net>
Reviewed-on: #1
2025-03-08 00:28:12 +09:00
Hans Svensson 8acbce9269 Merge pull request #13 from aeternity/prepare_1.2.0
Bump version to 1.2.0
2021-10-28 15:46:01 +02:00
Hans Svensson be39bbc464 Bump version to 1.2.0 2021-10-28 15:35:00 +02:00
Hans Svensson dd94b371e6 Merge pull request #12 from aeternity/support-otp-24
Use new crypto:block_encrypt api
2021-10-28 15:05:56 +02:00
Hans Svensson 11ca32c72f Merge pull request #11 from lrascao/fix-rekey
Fix rekey, improve coverage
2021-10-28 15:00:58 +02:00
Sean Hinde 71300ba5b6 Use new crypto:block_encrypt api 2021-10-28 14:54:41 +02:00
Luis Rascao ffde489e53 Fix rekey, improve coverage
ChaChaPoly key is expected to be 256 bits long. It's safe to disregard
the MAC portion.
2021-04-27 15:40:58 +01:00
17 changed files with 321 additions and 225 deletions
+12 -5
View File
@@ -6,15 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Support for 448 DH function and Blake2s hash function.
### Changed ### Changed
- Using `crypto` over `enacl` (and removing a call to `get_stacktrace/1`) makes `enoise`
up to date for (at least) OTP-27.
- Added test dependency `eqwalizer_support` to enable checking types with Eqwalizer.
### Removed ### Removed
- The dependency on `enacl` is not needed anymore, OTP's `crypto` library now cover all
necessary operations.
## [4.3.1] - 2020-04-21 ## [1.2.0] - 2021-10-28
### Added ### Added
### Changed ### Changed
- Fixed included compiler binary file, which was broken due to incorrect local system dependencies. - Use the new AEAD crypto interface introduced in OTP 22. This makes `enoise` OPT 24 compatible
Because the aesophia version hasn't changed, the compiler in this release but it also means it no longer works on OTP 21 and earlier. You can't win them all.
continues to report as `v4.3.0`. - Fixed ChaChaPoly20 rekey
### Removed ### Removed
## [1.1.0] - 2020-09-24 ## [1.1.0] - 2020-09-24
@@ -41,7 +47,8 @@ Initial version the following map describe what is supported:
, dh => [dh25519] } , dh => [dh25519] }
``` ```
[Unreleased]: https://github.com/aeternity/aesophia_cli/compare/v1.1.0...HEAD [Unreleased]: https://github.com/aeternity/aesophia_cli/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/aeternity/aesophia_cli/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/aeternity/aesophia_cli/compare/v1.0.1...v1.1.0 [1.1.0]: https://github.com/aeternity/aesophia_cli/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/aeternity/aesophia_cli/compare/v1.0.0...v1.0.1 [1.0.1]: https://github.com/aeternity/aesophia_cli/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/aeternity/enoise/releases/tag/v1.0.0 [1.0.0]: https://github.com/aeternity/enoise/releases/tag/v1.0.0
+6
View File
@@ -39,3 +39,9 @@ Test
---- ----
$ rebar3 eunit $ rebar3 eunit
Typecheck
---------
$ rebar3 dialyzer
$ elp --eqwalize-all --rebar
+4 -2
View File
@@ -1,8 +1,10 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{plugins, [rebar3_hex]}. {plugins, [rebar3_hex]}.
{deps, [{enacl, "1.1.1"}]}.
{profiles, [{test, [{deps, [{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}]}]} {profiles, [{test, [{deps, [ {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
, {eqwalizer_support, {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"}, "eqwalizer_support"}}
]}
]}
]}. ]}.
{xref_checks, [undefined_function_calls, undefined_functions, {xref_checks, [undefined_function_calls, undefined_functions,
+1 -6
View File
@@ -1,6 +1 @@
{"1.1.0", [].
[{<<"enacl">>,{pkg,<<"enacl">>,<<"1.1.1">>},0}]}.
[
{pkg_hash,[
{<<"enacl">>, <<"F65DC64D9BFF2D8A534CB77AEF14DA5E7A2FA148987D87856F79A4745C9C2627">>}]}
].
+2 -2
View File
@@ -1,11 +1,11 @@
{application, enoise, {application, enoise,
[{description, "An Erlang implementation of the Noise protocol"}, [{description, "An Erlang implementation of the Noise protocol"},
{vsn, "1.1.0"}, {vsn, "1.2.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
stdlib, stdlib,
enacl crypto
]}, ]},
{env,[]}, {env,[]},
{modules, []}, {modules, []},
+153 -106
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,162 +16,201 @@
-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
-spec handshake(Options :: noise_options(),
Role :: enoise_hs_state:noise_role()) ->
{ok, enoise_hs_state:state()} | {error, term()}.
handshake(Options, Role) ->
HState = create_hstate(Options, Role),
{ok, HState}.
%% @doc Do a step (either `{send, Payload}', `{rcvd, EncryptedData}', %%% API functions
%% or `done')
%% @end -spec handshake(Options, Role) -> Outcome
-spec step_handshake(HState :: enoise_hs_state:state(), when Options :: options(),
Data :: {rcvd, binary()} | {send, binary()}) -> Role :: enoise_hs_state:noise_role(),
{ok, send, binary(), enoise_hs_state:state()} Outcome :: {ok, enoise_hs_state:state()}
| {ok, rcvd, binary(), enoise_hs_state:state()}
| {ok, done, noise_split_state()}
| {error, term()}. | {error, term()}.
%% @doc
%% Start an interactive handshake
handshake(Options, Role) ->
create_hstate(Options, Role).
-spec step_handshake(HState, Data) -> Next
when HState :: enoise_hs_state:state(),
Data :: {rcvd, binary()}
| {send, binary()},
Next :: {ok, send, binary(), enoise_hs_state:state()}
| {ok, rcvd, binary(), enoise_hs_state:state()}
| {ok, done, split_state()}
| {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) ->
HState = create_hstate(Options, Role), case create_hstate(Options, Role) of
{ok, HState} ->
Timeout = proplists:get_value(timeout, Options, infinity), Timeout = proplists:get_value(timeout, Options, infinity),
do_handshake(HState, ComState, Timeout). do_handshake(HState, ComState, Timeout);
Err = {error, _} ->
Err
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 ->
@@ -246,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,
@@ -254,30 +292,28 @@ 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(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 = proplists:get_value(rs, Options, undefined), RS = remote_keypair(DH, proplists:get_value(rs, Options, undefined)),
RE = 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) ->
case inet:getopts(TcpSock, [mode, packet, active, header, packet_size]) of case inet:getopts(TcpSock, [mode, packet, active, header, packet_size]) of
@@ -287,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
@@ -321,3 +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).
+4 -6
View File
@@ -54,12 +54,8 @@ set_nonce(CState = #noise_cs{}, Nonce) ->
encrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, PlainText) -> encrypt_with_ad(CState = #noise_cs{ k = empty }, _AD, PlainText) ->
{ok, CState, PlainText}; {ok, CState, PlainText};
encrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, PlainText) -> encrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, PlainText) ->
case enoise_crypto:encrypt(Cipher, K, N, AD, PlainText) of CipherText = enoise_crypto:encrypt(Cipher, K, N, AD, PlainText),
Encrypted when is_binary(Encrypted) -> {ok, CState#noise_cs{ n = N+1 }, CipherText}.
{ok, CState#noise_cs{ n = N+1 }, Encrypted};
Err = {error, _} ->
Err
end.
-spec decrypt_with_ad(CState :: state(), AD :: binary(), CipherText :: binary()) -> -spec decrypt_with_ad(CState :: state(), AD :: binary(), CipherText :: binary()) ->
{ok, state(), binary()} | {error, term()}. {ok, state(), binary()} | {error, term()}.
@@ -74,6 +70,8 @@ decrypt_with_ad(CState = #noise_cs{ k = K, n = N, cipher = Cipher }, AD, CipherT
end. end.
-spec rekey(CState :: state()) -> state(). -spec rekey(CState :: state()) -> state().
rekey(CState = #noise_cs{ k = empty }) ->
CState;
rekey(CState = #noise_cs{ k = K, cipher = Cipher }) -> rekey(CState = #noise_cs{ k = K, cipher = Cipher }) ->
CState#noise_cs{ k = enoise_crypto:rekey(Cipher, K) }. CState#noise_cs{ k = enoise_crypto:rekey(Cipher, K) }.
+32 -28
View File
@@ -30,12 +30,17 @@
%% and the public key from `Key2' with algorithm `Algo'. %% and the public key from `Key2' with algorithm `Algo'.
-spec dh(Algo :: enoise_hs_state:noise_dh(), -spec dh(Algo :: enoise_hs_state:noise_dh(),
Key1:: keypair(), Key2 :: keypair()) -> binary(). Key1:: keypair(), Key2 :: keypair()) -> binary().
dh(dh25519, Key1, Key2) -> dh(Type, Key1, Key2) when Type == dh25519; Type == dh448 ->
enacl:curve25519_scalarmult( enoise_keypair:seckey(Key1) dh_(ecdh_type(Type), enoise_keypair:pubkey(Key2), enoise_keypair:seckey(Key1));
, enoise_keypair:pubkey(Key2));
dh(Type, _Key1, _Key2) -> dh(Type, _Key1, _Key2) ->
error({unsupported_diffie_hellman, Type}). error({unsupported_diffie_hellman, Type}).
ecdh_type(dh25519) -> x25519;
ecdh_type(dh448) -> x448.
dh_(DHType, OtherPub, MyPriv) ->
crypto:compute_key(ecdh, OtherPub, MyPriv, DHType).
-spec hmac(Hash :: enoise_sym_state:noise_hash(), -spec hmac(Hash :: enoise_sym_state:noise_hash(),
Key :: binary(), Data :: binary()) -> binary(). Key :: binary(), Data :: binary()) -> binary().
hmac(Hash, Key, Data) -> hmac(Hash, Key, Data) ->
@@ -54,43 +59,42 @@ hkdf(Hash, Key, Data) ->
Output3 = hmac(Hash, TempKey, <<Output2/binary, 3:8>>), Output3 = hmac(Hash, TempKey, <<Output2/binary, 3:8>>),
[Output1, Output2, Output3]. [Output1, Output2, Output3].
-spec rekey(Cipher :: enoise_cipher_state:noise_cipher(), -spec rekey(Cipher :: enoise_cipher_state:noise_cipher(), Key :: binary()) -> binary().
Key :: binary()) -> binary() | {error, term()}. rekey('ChaChaPoly', K0) ->
KLen = 32,
<<K:KLen/binary, _/binary>> = encrypt('ChaChaPoly', K0, ?MAX_NONCE, <<>>, <<0:(32*8)>>),
K;
rekey(Cipher, K) -> rekey(Cipher, K) ->
encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>). encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>).
-spec encrypt(Cipher :: enoise_cipher_state:noise_cipher(), -spec encrypt(Cipher :: enoise_cipher_state:noise_cipher(), Key :: binary(),
Key :: binary(), Nonce :: non_neg_integer(), Nonce :: non_neg_integer(), Ad :: binary(), PlainText :: binary()) -> binary().
Ad :: binary(), PlainText :: binary()) -> encrypt(Cipher, K, N, Ad, PlainText) ->
binary() | {error, term()}. {CText, CTag} = crypto:crypto_one_time_aead(cipher(Cipher), K, nonce(Cipher, N), PlainText, Ad, true),
encrypt('ChaChaPoly', K, N, Ad, PlainText) -> <<CText/binary, CTag/binary>>.
Nonce = <<0:32, N:64/little-unsigned-integer>>,
enacl:aead_chacha20poly1305_ietf_encrypt(PlainText, Ad, Nonce, K);
encrypt('AESGCM', K, N, Ad, PlainText) ->
Nonce = <<0:32, N:64>>,
{CipherText, CipherTag} = crypto:block_encrypt(aes_gcm, K, Nonce, {Ad, PlainText}),
<<CipherText/binary, CipherTag/binary>>.
-spec decrypt(Cipher ::enoise_cipher_state:noise_cipher(), -spec decrypt(Cipher ::enoise_cipher_state:noise_cipher(), Key :: binary(),
Key :: binary(), Nonce :: non_neg_integer(), Nonce :: non_neg_integer(), AD :: binary(),
AD :: binary(), CipherText :: binary()) -> CipherText :: binary()) -> binary() | {error, term()}.
binary() | {error, term()}. decrypt(Cipher, K, N, Ad, CipherText0) ->
decrypt('ChaChaPoly', K, N, Ad, CipherText) ->
Nonce = <<0:32, N:64/little-unsigned-integer>>,
enacl:aead_chacha20poly1305_ietf_decrypt(CipherText, Ad, Nonce, K);
decrypt('AESGCM', K, N, Ad, CipherText0) ->
CTLen = byte_size(CipherText0) - ?MAC_LEN, CTLen = byte_size(CipherText0) - ?MAC_LEN,
<<CipherText:CTLen/binary, MAC:?MAC_LEN/binary>> = CipherText0, <<CText:CTLen/binary, MAC:?MAC_LEN/binary>> = CipherText0,
Nonce = <<0:32, N:64>>, case crypto:crypto_one_time_aead(cipher(Cipher), K, nonce(Cipher, N), CText, Ad, MAC, false) of
case crypto:block_decrypt(aes_gcm, K, Nonce, {Ad, CipherText, MAC}) of
error -> {error, decrypt_failed}; error -> {error, decrypt_failed};
Data -> Data Data -> Data
end. end.
nonce('ChaChaPoly', N) -> <<0:32, N:64/little-unsigned-integer>>;
nonce('AESGCM', N) -> <<0:32, N:64/big-unsigned-integer>>.
cipher('ChaChaPoly') -> chacha20_poly1305;
cipher('AESGCM') -> aes_256_gcm.
-spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary(). -spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary().
hash(blake2s, Data) ->
crypto:hash(blake2s, Data);
hash(blake2b, Data) -> hash(blake2b, Data) ->
Hash = enacl:generichash(64, Data), Hash; crypto:hash(blake2b, Data);
hash(sha256, Data) -> hash(sha256, Data) ->
crypto:hash(sha256, Data); crypto:hash(sha256, Data);
hash(sha512, Data) -> hash(sha512, Data) ->
+27 -21
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").
@@ -26,6 +25,8 @@
hs_hash := binary(), hs_hash := binary(),
final_state => state() }. final_state => state() }.
-type optional_key() :: undefined | keypair().
-type initial_keys() :: {optional_key(), optional_key(), optional_key(), optional_key()}.
-record(noise_hs, { ss :: enoise_sym_state:state() -record(noise_hs, { ss :: enoise_sym_state:state()
, s :: keypair() | undefined , s :: keypair() | undefined
@@ -39,11 +40,8 @@
-opaque state() :: #noise_hs{}. -opaque state() :: #noise_hs{}.
-export_type([noise_dh/0, noise_role/0, noise_split_state/0, noise_token/0, state/0]). -export_type([noise_dh/0, noise_role/0, noise_split_state/0, noise_token/0, state/0]).
-spec init(Protocol :: string() | enoise_protocol:protocol(), -spec init(Protocol :: enoise_protocol:protocol(), Role :: noise_role(),
Role :: noise_role(), Prologue :: binary(), Prologue :: binary(), Keys :: initial_keys()) -> {ok, state()} | {error, term()}.
Keys :: term()) -> state().
init(ProtocolName, Role, Prologue, Keys) when is_list(ProtocolName) ->
init(enoise_protocol:from_name(ProtocolName), Role, Prologue, Keys);
init(Protocol, Role, Prologue, {S, E, RS, RE}) -> init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
SS0 = enoise_sym_state:init(Protocol), SS0 = enoise_sym_state:init(Protocol),
SS1 = enoise_sym_state:mix_hash(SS0, Prologue), SS1 = enoise_sym_state:mix_hash(SS0, Prologue),
@@ -53,11 +51,19 @@ init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
, dh = enoise_protocol:dh(Protocol) , dh = enoise_protocol:dh(Protocol)
, msgs = enoise_protocol:msgs(Role, Protocol) }, , msgs = enoise_protocol:msgs(Role, Protocol) },
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol), PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
lists:foldl(fun({out, [s]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(S)); pre_mix(PreMsgs, HS).
({out, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(E));
({in, [s]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RS)); pre_mix([], HS) -> {ok, HS};
({in, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RE)) pre_mix([{out, [s]} | Msgs], HS = #noise_hs{ s = S }) when S /= undefined ->
end, HS, PreMsgs). pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(S)));
pre_mix([{out, [e]} | Msgs], HS = #noise_hs{ e = E }) when E /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(E)));
pre_mix([{in, [s]} | Msgs], HS = #noise_hs{ rs = RS }) when RS /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(RS)));
pre_mix([{in, [e]} | Msgs], HS = #noise_hs{ re = RE }) when RE /= undefined ->
pre_mix(Msgs, mix_hash(HS, enoise_keypair:pubkey(RE)));
pre_mix(_Msg, _HS) ->
{error, invalid_noise_setup}.
-spec finalize(HS :: state()) -> {ok, noise_split_state()} | {error, term()}. -spec finalize(HS :: state()) -> {ok, noise_split_state()} | {error, term()}.
finalize(HS = #noise_hs{ msgs = [], ss = SS, role = Role }) -> finalize(HS = #noise_hs{ msgs = [], ss = SS, role = Role }) ->
@@ -90,7 +96,7 @@ read_message(HS = #noise_hs{ msgs = [{in, Msg} | Msgs] }, Message) ->
Err = {error, _} -> Err Err = {error, _} -> Err
end. end.
-spec remote_keys(HS :: state()) -> keypair(). -spec remote_keys(HS :: state()) -> undefined | keypair().
remote_keys(#noise_hs{ rs = RS }) -> remote_keys(#noise_hs{ rs = RS }) ->
RS. RS.
+9 -7
View File
@@ -30,7 +30,7 @@
%% @doc Generate a new keypair of type `Type'. %% @doc Generate a new keypair of type `Type'.
-spec new(Type :: key_type()) -> keypair(). -spec new(Type :: key_type()) -> keypair().
new(Type) -> new(Type) ->
{Sec, Pub} = new_key_pair(Type), {Pub, Sec} = new_key_pair(Type),
#kp{ type = Type, sec = Sec, pub = Pub }. #kp{ type = Type, sec = Sec, pub = Pub }.
%% @doc Create a new keypair of type `Type'. If `Public' is `undefined' %% @doc Create a new keypair of type `Type'. If `Public' is `undefined'
@@ -69,12 +69,14 @@ seckey(#kp{ sec = S }) ->
S. S.
%% -- Local functions -------------------------------------------------------- %% -- Local functions --------------------------------------------------------
new_key_pair(dh25519) -> new_key_pair(Type) when Type == dh25519; Type == dh448 ->
KeyPair = enacl:crypto_sign_ed25519_keypair(), crypto:generate_key(ecdh, ecdh_type(Type));
{enacl:crypto_sign_ed25519_secret_to_curve25519(maps:get(secret, KeyPair)),
enacl:crypto_sign_ed25519_public_to_curve25519(maps:get(public, KeyPair))};
new_key_pair(Type) -> new_key_pair(Type) ->
error({unsupported_key_type, Type}). error({unsupported_key_type, Type}).
pubkey_from_secret(dh25519, Secret) -> pubkey_from_secret(Type, Secret) when Type == dh25519; Type == dh448 ->
enacl:curve25519_scalarmult_base(Secret). {Public, Secret} = crypto:generate_key(ecdh, ecdh_type(Type), Secret),
Public.
ecdh_type(dh25519) -> x25519;
ecdh_type(dh448) -> x448.
+7 -7
View File
@@ -19,7 +19,7 @@
, to_name/1]). , to_name/1]).
-ifdef(TEST). -ifdef(TEST).
-export([to_name/4]). -export([to_name/4, from_name_pattern/1, to_name_pattern/1]).
-endif. -endif.
-type noise_pattern() :: nn | kn | nk | kk | nx | kx | xn | in | xk | ik | xx | ix. -type noise_pattern() :: nn | kn | nk | kk | nx | kx | xn | in | xk | ik | xx | ix.
@@ -137,9 +137,9 @@ supported_dh(Dh) ->
-spec supported() -> map(). -spec supported() -> map().
supported() -> supported() ->
#{ hs_pattern => [nn, kn, nk, kk, nx, kx, xn, in, xk, ik, xx, ix] #{ hs_pattern => [nn, kn, nk, kk, nx, kx, xn, in, xk, ik, xx, ix]
, hash => [blake2b, sha256, sha512] , hash => [blake2s, blake2b, sha256, sha512]
, cipher => ['ChaChaPoly', 'AESGCM'] , cipher => ['ChaChaPoly', 'AESGCM']
, dh => [dh25519] , dh => [dh25519, dh448]
}. }.
to_name(Pattern, Dh, Cipher, Hash) -> to_name(Pattern, Dh, Cipher, Hash) ->
@@ -148,16 +148,16 @@ to_name(Pattern, Dh, Cipher, Hash) ->
to_name_pattern(Atom) -> to_name_pattern(Atom) ->
[Simple | Rest] = string:lexemes(atom_to_list(Atom), "_"), [Simple | Rest] = string:lexemes(atom_to_list(Atom), "_"),
string:uppercase(Simple) ++ lists:join("+", Rest). lists:flatten(string:uppercase(Simple) ++ lists:join("+", Rest)).
from_name_pattern(String) -> from_name_pattern(String) ->
[Init | Mod2] = string:lexemes(String, "+"), [Init | Mod2] = string:lexemes(String, "+"),
{Simple, Mod1} = lists:splitwith(fun(C) -> C >= $A andalso C =< $Z end, Init), {Simple, Mod1} = lists:splitwith(fun(C) -> C >= $A andalso C =< $Z end, Init),
list_to_atom(string:lowercase(Simple) ++ list_to_atom(lists:flatten(string:lowercase(Simple) ++
case Mod1 of case Mod1 of
"" -> ""; "" -> "";
_ -> "_" ++ lists:join([Mod1 | Mod2], "_") _ -> "_" ++ lists:join("_", [Mod1 | Mod2])
end). end)).
to_name_dh(dh25519) -> "25519"; to_name_dh(dh25519) -> "25519";
to_name_dh(dh448) -> "448". to_name_dh(dh448) -> "448".
+9
View File
@@ -26,5 +26,14 @@ chachapoly_test() ->
enoise_cipher_state:decrypt_with_ad(CS1, AD, <<CipherText/binary, MAC/binary>>), enoise_cipher_state:decrypt_with_ad(CS1, AD, <<CipherText/binary, MAC/binary>>),
?assertMatch(PlainText, PlainText0), ?assertMatch(PlainText, PlainText0),
% rekey test
CS4 = enoise_cipher_state:rekey(CS1),
{ok, _CS5, <<CipherText1:CTLen/binary, MAC1:MACLen/binary>>} =
enoise_cipher_state:encrypt_with_ad(CS4, AD, PlainText),
{ok, _CS6, <<PlainText1:PTLen/binary>>} =
enoise_cipher_state:decrypt_with_ad(CS4, AD, <<CipherText1/binary, MAC1/binary>>),
?assertMatch(PlainText, PlainText1),
ok. ok.
+7
View File
@@ -44,6 +44,13 @@ chachapoly_test() ->
enoise_crypto:decrypt('ChaChaPoly', Key, Nonce, AD, <<CipherText/binary, MAC/binary>>), enoise_crypto:decrypt('ChaChaPoly', Key, Nonce, AD, <<CipherText/binary, MAC/binary>>),
?assertMatch(PlainText, PlainText0), ?assertMatch(PlainText, PlainText0),
Key1 = enoise_crypto:rekey('ChaChaPoly', Key),
<<CipherText1:CTLen/binary, MAC1:MACLen/binary>> =
enoise_crypto:encrypt('ChaChaPoly', Key1, Nonce, AD, PlainText),
<<PlainText1:PTLen/binary>> =
enoise_crypto:decrypt('ChaChaPoly', Key1, Nonce, AD, <<CipherText1/binary, MAC1/binary>>),
?assertMatch(PlainText, PlainText1),
ok. ok.
blake2b_test() -> blake2b_test() ->
+2 -1
View File
@@ -43,7 +43,8 @@ noise_test(_Name, Protocol, Init, Resp, Messages, HSHash) ->
SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end, SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end,
PubK = fun(undefined) -> undefined; (Pub) -> enoise_keypair:new(DH, Pub) end, PubK = fun(undefined) -> undefined; (Pub) -> enoise_keypair:new(DH, Pub) end,
HSInit = fun(P, R, #{ e := E, s := S, rs := RS, prologue := PL }) -> HSInit = fun(P, R, #{ e := E, s := S, rs := RS, prologue := PL }) ->
enoise_hs_state:init(P, R, PL, {SecK(S), SecK(E), PubK(RS), undefined}) {ok, HS} = enoise_hs_state:init(P, R, PL, {SecK(S), SecK(E), PubK(RS), undefined}),
HS
end, end,
InitHS = HSInit(Protocol, initiator, Init), InitHS = HSInit(Protocol, initiator, Init),
+15 -2
View File
@@ -7,5 +7,18 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
name_test() -> name_test() ->
?assertMatch(<<"Noise_XK_25519_ChaChaPoly_SHA512">>, roundtrip("Noise_XK_25519_ChaChaPoly_SHA512"),
enoise_protocol:to_name(enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_SHA512"))). roundtrip("Noise_NN_25519_AESGCM_BLAKE2b").
name2_test() ->
Name = "Noise_NXpsk2_25519_AESGCM_SHA512",
?assertError({name_not_recognized, Name}, enoise_protocol:from_name(Name)).
name_pattern_test() ->
Pat = "XKfallback+psk0",
RoundPat = enoise_protocol:to_name_pattern(enoise_protocol:from_name_pattern(Pat)),
?assertEqual(Pat, RoundPat).
roundtrip(Name) ->
ExpectedName = iolist_to_binary(Name),
?assertMatch(ExpectedName, enoise_protocol:to_name(enoise_protocol:from_name(Name))).
+3 -4
View File
@@ -41,10 +41,9 @@ noise_interactive(V = #{ protocol_name := Name }) ->
noise_interactive(_Name, Protocol, Init, Resp, Messages, HSHash) -> noise_interactive(_Name, Protocol, Init, Resp, Messages, HSHash) ->
DH = enoise_protocol:dh(Protocol), DH = enoise_protocol:dh(Protocol),
SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end, SecK = fun(undefined) -> undefined; (Sec) -> enoise_keypair:new(DH, Sec, undefined) end,
PubK = fun(undefined) -> undefined; (Pub) -> enoise_keypair:new(DH, Pub) end,
HSInit = fun(#{ e := E, s := S, rs := RS, prologue := PL }, R) -> HSInit = fun(#{ e := E, s := S, rs := RS, prologue := PL }, R) ->
Opts = [{noise, Protocol}, {s, SecK(S)}, {e, SecK(E)}, {rs, PubK(RS)}, {prologue, PL}], Opts = [{noise, Protocol}, {s, SecK(S)}, {e, SecK(E)}, {rs, RS}, {prologue, PL}],
enoise:handshake(Opts, R) enoise:handshake(Opts, R)
end, end,
{ok, InitHS} = HSInit(Init, initiator), {ok, InitHS} = HSInit(Init, initiator),
@@ -149,12 +148,12 @@ noise_test_run_(Conf, SKP, CKP) ->
Protocol = enoise_protocol:from_name(Conf), Protocol = enoise_protocol:from_name(Conf),
Port = 4556, Port = 4556,
SrvOpts = [{echos, 2}, {cpub, CKP}], SrvOpts = [{echos, 2}, {cpub, enoise_keypair:pubkey(CKP)}],
EchoSrv = enoise_utils:echo_srv_start(Port, Protocol, SKP, SrvOpts), EchoSrv = enoise_utils:echo_srv_start(Port, Protocol, SKP, SrvOpts),
{ok, TcpSock} = gen_tcp:connect("localhost", Port, [{active, once}, binary, {reuseaddr, true}], 100), {ok, TcpSock} = gen_tcp:connect("localhost", Port, [{active, once}, binary, {reuseaddr, true}], 100),
Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, SKP} || enoise_utils:need_rs(initiator, Conf) ], Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, enoise_keypair:pubkey(SKP)} || enoise_utils:need_rs(initiator, Conf) ],
{ok, EConn, _} = enoise:connect(TcpSock, Opts), {ok, EConn, _} = enoise:connect(TcpSock, Opts),
ok = enoise:send(EConn, <<"Hello World!">>), ok = enoise:send(EConn, <<"Hello World!">>),
+1 -1
View File
@@ -26,7 +26,7 @@ echo_srv(Port, Protocol, SKP, SrvOpts) ->
AcceptRes = AcceptRes =
try try
enoise:accept(TcpSock, Opts) enoise:accept(TcpSock, Opts)
catch _:R -> gen_tcp:close(TcpSock), {error, {R, erlang:get_stacktrace()}} end, catch _:R:S -> gen_tcp:close(TcpSock), {error, {R, S}} end,
gen_tcp:close(LSock), gen_tcp:close(LSock),