Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 853f6b5a8d | |||
| 029292817e | |||
| 2b5f08e156 | |||
| 91916908a0 | |||
| 479ec70870 | |||
| 8acbce9269 | |||
| be39bbc464 | |||
| dd94b371e6 | |||
| 11ca32c72f | |||
| 71300ba5b6 | |||
| ffde489e53 | |||
| 991d7390ea | |||
| 83fa0d5a00 | |||
| 336b331b8a | |||
| fc4a41f13d | |||
| 3819ba5c0f | |||
| 98d18bcaa5 | |||
| a3e803fc1a | |||
| 1e6ee6703f | |||
| 7c7ad54a6a |
@@ -0,0 +1,54 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Support for 448 DH function and Blake2s hash function.
|
||||||
|
### 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
|
||||||
|
- The dependency on `enacl` is not needed anymore, OTP's `crypto` library now cover all
|
||||||
|
necessary operations.
|
||||||
|
|
||||||
|
## [1.2.0] - 2021-10-28
|
||||||
|
### Added
|
||||||
|
### Changed
|
||||||
|
- Use the new AEAD crypto interface introduced in OTP 22. This makes `enoise` OPT 24 compatible
|
||||||
|
but it also means it no longer works on OTP 21 and earlier. You can't win them all.
|
||||||
|
- Fixed ChaChaPoly20 rekey
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
## [1.1.0] - 2020-09-24
|
||||||
|
### Added
|
||||||
|
Include [Cacaphony](https://github.com/centromere/cacophony) test vectors.
|
||||||
|
### Changed
|
||||||
|
Updated `enacl` to version [1.1.1](https://github.com/jlouis/enacl/releases/tag/v1.1.1).
|
||||||
|
Fixed some imprecise type specifications.
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
## [1.0.1] - 2018-12-21
|
||||||
|
### Added
|
||||||
|
### Changed
|
||||||
|
Improved argument checks and error handling in handshake (in particular related to empty
|
||||||
|
hand shake messages).
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
## [1.0] - 2018-10-09
|
||||||
|
Initial version the following map describe what is supported:
|
||||||
|
```
|
||||||
|
#{ hs_pattern => [nn, kn, nk, kk, nx, kx, xn, in, xk, ik, xx, ix]
|
||||||
|
, hash => [blake2b, sha256, sha512]
|
||||||
|
, cipher => ['ChaChaPoly', 'AESGCM']
|
||||||
|
, dh => [dh25519] }
|
||||||
|
```
|
||||||
|
|
||||||
|
[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.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
|
||||||
@@ -39,3 +39,9 @@ Test
|
|||||||
----
|
----
|
||||||
|
|
||||||
$ rebar3 eunit
|
$ rebar3 eunit
|
||||||
|
|
||||||
|
Typecheck
|
||||||
|
---------
|
||||||
|
|
||||||
|
$ rebar3 dialyzer
|
||||||
|
$ elp --eqwalize-all --rebar
|
||||||
|
|||||||
+5
-2
@@ -1,11 +1,14 @@
|
|||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
{plugins, [rebar3_hex]}.
|
{plugins, [rebar3_hex]}.
|
||||||
{deps, [{enacl, "0.17.2"}]}.
|
|
||||||
|
|
||||||
{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,
|
||||||
locals_not_used,
|
locals_not_used,
|
||||||
deprecated_function_calls, deprecated_functions]}.
|
deprecated_function_calls, deprecated_functions]}.
|
||||||
|
|
||||||
|
{dialyzer, [{warnings, [unknown]}]}.
|
||||||
|
|||||||
+1
-6
@@ -1,6 +1 @@
|
|||||||
{"1.1.0",
|
[].
|
||||||
[{<<"enacl">>,{pkg,<<"enacl">>,<<"0.17.2">>},0}]}.
|
|
||||||
[
|
|
||||||
{pkg_hash,[
|
|
||||||
{<<"enacl">>, <<"4AD59142943E72D72C56E33C30DEDEF28ADD8EBEE79C51033562B0CB4B93EDE0">>}]}
|
|
||||||
].
|
|
||||||
|
|||||||
+2
-2
@@ -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.0.1"},
|
{vsn, "1.2.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
stdlib,
|
stdlib,
|
||||||
enacl
|
crypto
|
||||||
]},
|
]},
|
||||||
{env,[]},
|
{env,[]},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
|
|||||||
+160
-115
@@ -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,164 +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() :: #{ rx := enoise_cipher_state:state(),
|
-type split_state() :: enoise_hs_state:noise_split_state().
|
||||||
tx := enoise_cipher_state:state(),
|
|
||||||
hs_hash := binary() }.
|
|
||||||
%% 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()} | {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,38 +283,37 @@ 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,
|
||||||
state => {TcpSock, Active, <<>>} },
|
state => {TcpSock, Active, <<>>}},
|
||||||
case handshake(Options, Role, ComState) of
|
case handshake(Options, Role, ComState) of
|
||||||
{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
|
||||||
@@ -289,29 +323,35 @@ 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
|
||||||
Buf1 = <<Len:16, Rest/binary>> when byte_size(Rest) < Len ->
|
Buf1 = <<Len:16, Rest/binary>> when byte_size(Rest) < Len ->
|
||||||
gen_tcp_rcv_msg({TcpSock, true, Buf1}, Timeout);
|
gen_tcp_rcv_msg({TcpSock, true, Buf1}, Timeout);
|
||||||
@@ -323,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).
|
||||||
|
|||||||
@@ -50,11 +50,12 @@ set_nonce(CState = #noise_cs{}, Nonce) ->
|
|||||||
CState#noise_cs{ n = Nonce }.
|
CState#noise_cs{ n = Nonce }.
|
||||||
|
|
||||||
-spec encrypt_with_ad(CState :: state(), AD :: binary(), PlainText :: binary()) ->
|
-spec encrypt_with_ad(CState :: state(), AD :: binary(), PlainText :: binary()) ->
|
||||||
{ok, state(), binary()}.
|
{ok, state(), binary()} | {error, term()}.
|
||||||
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) ->
|
||||||
{ok, CState#noise_cs{ n = N+1 }, enoise_crypto:encrypt(Cipher, K, N, AD, PlainText)}.
|
CipherText = enoise_crypto:encrypt(Cipher, K, N, AD, PlainText),
|
||||||
|
{ok, CState#noise_cs{ n = N+1 }, CipherText}.
|
||||||
|
|
||||||
-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()}.
|
||||||
@@ -69,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) }.
|
||||||
|
|
||||||
|
|||||||
@@ -53,15 +53,19 @@ start_link(TcpSock, Rx, Tx, Owner, {Active0, Buf}) ->
|
|||||||
Err
|
Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec send(Noise :: pid(), Data :: binary()) -> ok | {error, term()}.
|
||||||
send(Noise, Data) ->
|
send(Noise, Data) ->
|
||||||
gen_server:call(Noise, {send, Data}).
|
gen_server:call(Noise, {send, Data}).
|
||||||
|
|
||||||
|
-spec set_active(Noise :: pid(), Active :: true | once) -> ok | {error, term()}.
|
||||||
set_active(Noise, Active) ->
|
set_active(Noise, Active) ->
|
||||||
gen_server:call(Noise, {active, self(), Active}).
|
gen_server:call(Noise, {active, self(), Active}).
|
||||||
|
|
||||||
|
-spec close(Noise :: pid()) -> ok | {error, term()}.
|
||||||
close(Noise) ->
|
close(Noise) ->
|
||||||
gen_server:call(Noise, close).
|
gen_server:call(Noise, close).
|
||||||
|
|
||||||
|
-spec controlling_process(Noise :: pid(), NewPid :: pid()) -> ok | {error, term()}.
|
||||||
controlling_process(Noise, NewPid) ->
|
controlling_process(Noise, NewPid) ->
|
||||||
gen_server:call(Noise, {controlling_process, self(), NewPid}, 100).
|
gen_server:call(Noise, {controlling_process, self(), NewPid}, 100).
|
||||||
|
|
||||||
|
|||||||
+35
-26
@@ -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,38 +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().
|
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>>.
|
||||||
enacl:aead_chacha20poly1305_encrypt(K, N, Ad, PlainText);
|
|
||||||
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) ->
|
|
||||||
enacl:aead_chacha20poly1305_decrypt(K, N, Ad, CipherText);
|
|
||||||
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
|
||||||
crypto:block_decrypt(aes_gcm, K, Nonce, {Ad, CipherText, MAC}).
|
error -> {error, decrypt_failed};
|
||||||
|
Data -> Data
|
||||||
|
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) ->
|
||||||
{ok, 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) ->
|
||||||
|
|||||||
+35
-23
@@ -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").
|
||||||
|
|
||||||
@@ -21,6 +20,13 @@
|
|||||||
-type noise_dh() :: dh25519 | dh448.
|
-type noise_dh() :: dh25519 | dh448.
|
||||||
-type noise_token() :: s | e | ee | ss | es | se.
|
-type noise_token() :: s | e | ee | ss | es | se.
|
||||||
-type keypair() :: enoise_keypair:keypair().
|
-type keypair() :: enoise_keypair:keypair().
|
||||||
|
-type noise_split_state() :: #{rx := enoise_cipher_state:state(),
|
||||||
|
tx := enoise_cipher_state:state(),
|
||||||
|
hs_hash := binary(),
|
||||||
|
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
|
||||||
@@ -32,13 +38,10 @@
|
|||||||
, msgs = [] :: [enoise_protocol:noise_msg()] }).
|
, msgs = [] :: [enoise_protocol:noise_msg()] }).
|
||||||
|
|
||||||
-opaque state() :: #noise_hs{}.
|
-opaque state() :: #noise_hs{}.
|
||||||
-export_type([noise_dh/0, noise_role/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),
|
||||||
@@ -48,13 +51,21 @@ 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));
|
|
||||||
({in, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RE))
|
|
||||||
end, HS, PreMsgs).
|
|
||||||
|
|
||||||
-spec finalize(HS :: state()) -> {ok, map()} | {error, term()}.
|
pre_mix([], HS) -> {ok, HS};
|
||||||
|
pre_mix([{out, [s]} | Msgs], HS = #noise_hs{ s = S }) when S /= undefined ->
|
||||||
|
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()}.
|
||||||
finalize(HS = #noise_hs{ msgs = [], ss = SS, role = Role }) ->
|
finalize(HS = #noise_hs{ msgs = [], ss = SS, role = Role }) ->
|
||||||
{C1, C2} = enoise_sym_state:split(SS),
|
{C1, C2} = enoise_sym_state:split(SS),
|
||||||
HSHash = enoise_sym_state:h(SS),
|
HSHash = enoise_sym_state:h(SS),
|
||||||
@@ -68,7 +79,7 @@ finalize(_) ->
|
|||||||
|
|
||||||
-spec next_message(HS :: state()) -> in | out | done.
|
-spec next_message(HS :: state()) -> in | out | done.
|
||||||
next_message(#noise_hs{ msgs = [{Dir, _} | _] }) -> Dir;
|
next_message(#noise_hs{ msgs = [{Dir, _} | _] }) -> Dir;
|
||||||
next_message(_) -> done.
|
next_message(#noise_hs{ }) -> done.
|
||||||
|
|
||||||
-spec write_message(HS :: state(), PayLoad :: binary()) -> {ok, state(), binary()}.
|
-spec write_message(HS :: state(), PayLoad :: binary()) -> {ok, state(), binary()}.
|
||||||
write_message(HS = #noise_hs{ msgs = [{out, Msg} | Msgs] }, PayLoad) ->
|
write_message(HS = #noise_hs{ msgs = [{out, Msg} | Msgs] }, PayLoad) ->
|
||||||
@@ -85,6 +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()) -> undefined | keypair().
|
||||||
remote_keys(#noise_hs{ rs = RS }) ->
|
remote_keys(#noise_hs{ rs = RS }) ->
|
||||||
RS.
|
RS.
|
||||||
|
|
||||||
|
|||||||
+12
-8
@@ -30,13 +30,15 @@
|
|||||||
%% @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'
|
||||||
%% it will be computed from the `Secret' (using the curve/algorithm
|
%% it will be computed from the `Secret' (using the curve/algorithm
|
||||||
%% indicated by `Type').
|
%% indicated by `Type').
|
||||||
-spec new(Type :: key_type(), Secret :: binary(), Public :: binary() | undefined) -> keypair().
|
-spec new(Type :: key_type(),
|
||||||
|
Secret :: binary() | undefined,
|
||||||
|
Public :: binary() | undefined) -> keypair().
|
||||||
new(Type, Secret, undefined) ->
|
new(Type, Secret, undefined) ->
|
||||||
new(Type, Secret, pubkey_from_secret(Type, Secret));
|
new(Type, Secret, pubkey_from_secret(Type, Secret));
|
||||||
new(Type, Secret, Public) ->
|
new(Type, Secret, Public) ->
|
||||||
@@ -67,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.
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -90,6 +90,7 @@ pre_msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
|
|||||||
{PreMsgs, _Msgs} = protocol(Pattern),
|
{PreMsgs, _Msgs} = protocol(Pattern),
|
||||||
role_adapt(Role, PreMsgs).
|
role_adapt(Role, PreMsgs).
|
||||||
|
|
||||||
|
-spec role_adapt(Role :: enoise_hs_state:noise_role(), [noise_msg()]) -> [noise_msg()].
|
||||||
role_adapt(initiator, Msgs) ->
|
role_adapt(initiator, Msgs) ->
|
||||||
Msgs;
|
Msgs;
|
||||||
role_adapt(responder, Msgs) ->
|
role_adapt(responder, Msgs) ->
|
||||||
@@ -136,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) ->
|
||||||
@@ -147,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".
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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() ->
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ noise_hs_test_() ->
|
|||||||
fun() -> test_utils:noise_test_vectors() end,
|
fun() -> test_utils:noise_test_vectors() end,
|
||||||
fun(_X) -> ok end,
|
fun(_X) -> ok end,
|
||||||
fun(Tests) ->
|
fun(Tests) ->
|
||||||
[ {maps:get(name, T), fun() -> noise_hs_test(T) end}
|
[ {maps:get(protocol_name, T), fun() -> noise_hs_test(T) end}
|
||||||
|| T <- test_utils:noise_test_filter(Tests) ]
|
|| T <- test_utils:noise_test_filter(Tests) ]
|
||||||
end
|
end
|
||||||
}.
|
}.
|
||||||
|
|
||||||
noise_hs_test(V = #{ name := Name }) ->
|
noise_hs_test(V = #{ protocol_name := Name }) ->
|
||||||
Protocol = enoise_protocol:from_name(Name),
|
Protocol = enoise_protocol:from_name(Name),
|
||||||
|
|
||||||
FixK = fun(undefined) -> undefined;
|
FixK = fun(undefined) -> undefined;
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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))).
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ noise_interactive_test_() ->
|
|||||||
fun() -> test_utils:noise_test_vectors() end,
|
fun() -> test_utils:noise_test_vectors() end,
|
||||||
fun(_X) -> ok end,
|
fun(_X) -> ok end,
|
||||||
fun(Tests) ->
|
fun(Tests) ->
|
||||||
[ {maps:get(name, T), fun() -> noise_interactive(T) end}
|
[ {maps:get(protocol_name, T), fun() -> noise_interactive(T) end}
|
||||||
|| T <- test_utils:noise_test_filter(Tests) ]
|
|| T <- test_utils:noise_test_filter(Tests) ]
|
||||||
end
|
end
|
||||||
}.
|
}.
|
||||||
|
|
||||||
noise_interactive(V = #{ name := Name }) ->
|
noise_interactive(V = #{ protocol_name := Name }) ->
|
||||||
Protocol = enoise_protocol:from_name(Name),
|
Protocol = enoise_protocol:from_name(Name),
|
||||||
|
|
||||||
FixK = fun(undefined) -> undefined;
|
FixK = fun(undefined) -> undefined;
|
||||||
@@ -41,10 +41,9 @@ noise_interactive(V = #{ 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!">>),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -114,9 +114,10 @@ parse_test_vectors(File) ->
|
|||||||
|
|
||||||
%% Only test supported configurations
|
%% Only test supported configurations
|
||||||
noise_test_filter(Tests0) ->
|
noise_test_filter(Tests0) ->
|
||||||
Tests1 = [ T || T = #{ name := Name } <- Tests0, supported(Name) ],
|
Tests1 = [ T || T = #{ protocol_name := Name } <- Tests0, supported(Name) ],
|
||||||
case length(Tests1) < length(Tests0) of
|
case length(Tests1) < length(Tests0) of
|
||||||
true -> ?debugFmt("WARNING: ~p test vectors are unsupported", [length(Tests0) - length(Tests1)]);
|
true -> ?debugFmt("WARNING: ~p test vectors out of ~p are unsupported",
|
||||||
|
[length(Tests0) - length(Tests1), length(Tests0)]);
|
||||||
false -> ok
|
false -> ok
|
||||||
end,
|
end,
|
||||||
Tests1.
|
Tests1.
|
||||||
|
|||||||
+29691
-14523
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user