Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8acbce9269 | |||
| be39bbc464 | |||
| dd94b371e6 | |||
| 11ca32c72f | |||
| 71300ba5b6 | |||
| ffde489e53 | |||
| 991d7390ea | |||
| 83fa0d5a00 | |||
| 336b331b8a | |||
| fc4a41f13d | |||
| 3819ba5c0f | |||
| 98d18bcaa5 | |||
| a3e803fc1a | |||
| 1e6ee6703f | |||
| 7c7ad54a6a | |||
| c06bbae07d | |||
| 29420d6d63 | |||
| 1ac27a035a | |||
| 6de4d0bf71 |
@@ -0,0 +1,48 @@
|
|||||||
|
# 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
|
||||||
|
### Changed
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
## [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
|
||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
{plugins, [rebar3_hex]}.
|
{plugins, [rebar3_hex]}.
|
||||||
{deps, [{enacl, "0.17.2"}]}.
|
{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"}}}]}]}
|
||||||
]}.
|
]}.
|
||||||
@@ -9,3 +9,4 @@
|
|||||||
locals_not_used,
|
locals_not_used,
|
||||||
deprecated_function_calls, deprecated_functions]}.
|
deprecated_function_calls, deprecated_functions]}.
|
||||||
|
|
||||||
|
{dialyzer, [{warnings, [unknown]}]}.
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{"1.1.0",
|
{"1.2.0",
|
||||||
[{<<"enacl">>,{pkg,<<"enacl">>,<<"0.17.2">>},0}]}.
|
[{<<"enacl">>,{pkg,<<"enacl">>,<<"1.1.1">>},0}]}.
|
||||||
[
|
[
|
||||||
{pkg_hash,[
|
{pkg_hash,[
|
||||||
{<<"enacl">>, <<"4AD59142943E72D72C56E33C30DEDEF28ADD8EBEE79C51033562B0CB4B93EDE0">>}]}
|
{<<"enacl">>, <<"F65DC64D9BFF2D8A534CB77AEF14DA5E7A2FA148987D87856F79A4745C9C2627">>}]}
|
||||||
].
|
].
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{application, enoise,
|
{application, enoise,
|
||||||
[{description, "An Erlang implementation of the Noise protocol"},
|
[{description, "An Erlang implementation of the Noise protocol"},
|
||||||
{vsn, "1.0.0"},
|
{vsn, "1.2.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
|
|||||||
+3
-5
@@ -64,11 +64,9 @@ binary().
|
|||||||
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 noise_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.
|
||||||
@@ -140,7 +138,7 @@ connect(TcpSock, Options) ->
|
|||||||
%% @end
|
%% @end
|
||||||
-spec accept(TcpSock :: gen_tcp:socket(),
|
-spec accept(TcpSock :: gen_tcp:socket(),
|
||||||
Options :: noise_options()) ->
|
Options :: noise_options()) ->
|
||||||
{ok, noise_socket()} | {error, term()}.
|
{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).
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,16 @@ 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)}.
|
case enoise_crypto:encrypt(Cipher, K, N, AD, PlainText) of
|
||||||
|
Encrypted when is_binary(Encrypted) ->
|
||||||
|
{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()}.
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|
||||||
|
|||||||
+15
-6
@@ -55,7 +55,11 @@ hkdf(Hash, Key, Data) ->
|
|||||||
[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 = enacl:aead_chacha20poly1305_ietf_KEYBYTES(),
|
||||||
|
<<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)>>).
|
||||||
|
|
||||||
@@ -64,10 +68,11 @@ rekey(Cipher, K) ->
|
|||||||
Ad :: binary(), PlainText :: binary()) ->
|
Ad :: binary(), PlainText :: binary()) ->
|
||||||
binary() | {error, term()}.
|
binary() | {error, term()}.
|
||||||
encrypt('ChaChaPoly', K, N, Ad, PlainText) ->
|
encrypt('ChaChaPoly', K, N, Ad, PlainText) ->
|
||||||
enacl:aead_chacha20poly1305_encrypt(K, N, Ad, PlainText);
|
Nonce = <<0:32, N:64/little-unsigned-integer>>,
|
||||||
|
enacl:aead_chacha20poly1305_ietf_encrypt(PlainText, Ad, Nonce, K);
|
||||||
encrypt('AESGCM', K, N, Ad, PlainText) ->
|
encrypt('AESGCM', K, N, Ad, PlainText) ->
|
||||||
Nonce = <<0:32, N:64>>,
|
Nonce = <<0:32, N:64>>,
|
||||||
{CipherText, CipherTag} = crypto:block_encrypt(aes_gcm, K, Nonce, {Ad, PlainText}),
|
{CipherText, CipherTag} = crypto:crypto_one_time_aead(aes_256_gcm, K, Nonce, PlainText, Ad, true),
|
||||||
<<CipherText/binary, CipherTag/binary>>.
|
<<CipherText/binary, CipherTag/binary>>.
|
||||||
|
|
||||||
-spec decrypt(Cipher ::enoise_cipher_state:noise_cipher(),
|
-spec decrypt(Cipher ::enoise_cipher_state:noise_cipher(),
|
||||||
@@ -75,17 +80,21 @@ encrypt('AESGCM', K, N, Ad, PlainText) ->
|
|||||||
AD :: binary(), CipherText :: binary()) ->
|
AD :: binary(), CipherText :: binary()) ->
|
||||||
binary() | {error, term()}.
|
binary() | {error, term()}.
|
||||||
decrypt('ChaChaPoly', K, N, Ad, CipherText) ->
|
decrypt('ChaChaPoly', K, N, Ad, CipherText) ->
|
||||||
enacl:aead_chacha20poly1305_decrypt(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) ->
|
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,
|
<<CipherText:CTLen/binary, MAC:?MAC_LEN/binary>> = CipherText0,
|
||||||
Nonce = <<0:32, N:64>>,
|
Nonce = <<0:32, N:64>>,
|
||||||
crypto:block_decrypt(aes_gcm, K, Nonce, {Ad, CipherText, MAC}).
|
case crypto:crypto_one_time_aead(aes_256_gcm, K, Nonce, CipherText, Ad, MAC, false) of
|
||||||
|
error -> {error, decrypt_failed};
|
||||||
|
Data -> Data
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary().
|
-spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary().
|
||||||
hash(blake2b, Data) ->
|
hash(blake2b, Data) ->
|
||||||
{ok, Hash} = enacl:generichash(64, Data), Hash;
|
Hash = enacl:generichash(64, Data), Hash;
|
||||||
hash(sha256, Data) ->
|
hash(sha256, Data) ->
|
||||||
crypto:hash(sha256, Data);
|
crypto:hash(sha256, Data);
|
||||||
hash(sha512, Data) ->
|
hash(sha512, Data) ->
|
||||||
|
|||||||
+36
-14
@@ -21,6 +21,11 @@
|
|||||||
-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() }.
|
||||||
|
|
||||||
|
|
||||||
-record(noise_hs, { ss :: enoise_sym_state:state()
|
-record(noise_hs, { ss :: enoise_sym_state:state()
|
||||||
, s :: keypair() | undefined
|
, s :: keypair() | undefined
|
||||||
@@ -32,7 +37,7 @@
|
|||||||
, 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 :: string() | enoise_protocol:protocol(),
|
||||||
Role :: noise_role(), Prologue :: binary(),
|
Role :: noise_role(), Prologue :: binary(),
|
||||||
@@ -54,7 +59,7 @@ init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
|
|||||||
({in, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RE))
|
({in, [e]}, HS0) -> mix_hash(HS0, enoise_keypair:pubkey(RE))
|
||||||
end, HS, PreMsgs).
|
end, HS, PreMsgs).
|
||||||
|
|
||||||
-spec finalize(HS :: state()) -> {ok, map()} | {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 }) ->
|
||||||
{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 +73,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) ->
|
||||||
@@ -80,9 +85,12 @@ write_message(HS = #noise_hs{ msgs = [{out, Msg} | Msgs] }, PayLoad) ->
|
|||||||
-spec read_message(HS :: state(), Message :: binary()) ->
|
-spec read_message(HS :: state(), Message :: binary()) ->
|
||||||
{ok, state(), binary()} | {error, term()}.
|
{ok, state(), binary()} | {error, term()}.
|
||||||
read_message(HS = #noise_hs{ msgs = [{in, Msg} | Msgs] }, Message) ->
|
read_message(HS = #noise_hs{ msgs = [{in, Msg} | Msgs] }, Message) ->
|
||||||
{HS1, RestBuf1} = read_message(HS#noise_hs{ msgs = Msgs }, Msg, Message),
|
case read_message(HS#noise_hs{ msgs = Msgs }, Msg, Message) of
|
||||||
decrypt_and_hash(HS1, RestBuf1).
|
{ok, HS1, RestBuf1} -> decrypt_and_hash(HS1, RestBuf1);
|
||||||
|
Err = {error, _} -> Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec remote_keys(HS :: state()) -> keypair().
|
||||||
remote_keys(#noise_hs{ rs = RS }) ->
|
remote_keys(#noise_hs{ rs = RS }) ->
|
||||||
RS.
|
RS.
|
||||||
|
|
||||||
@@ -93,10 +101,12 @@ write_message(HS, [Token | Tokens], MsgBuf0) ->
|
|||||||
write_message(HS1, Tokens, <<MsgBuf0/binary, MsgBuf1/binary>>).
|
write_message(HS1, Tokens, <<MsgBuf0/binary, MsgBuf1/binary>>).
|
||||||
|
|
||||||
read_message(HS, [], Data) ->
|
read_message(HS, [], Data) ->
|
||||||
{HS, Data};
|
{ok, HS, Data};
|
||||||
read_message(HS, [Token | Tokens], Data0) ->
|
read_message(HS, [Token | Tokens], Data0) ->
|
||||||
{HS1, Data1} = read_token(HS, Token, Data0),
|
case read_token(HS, Token, Data0) of
|
||||||
read_message(HS1, Tokens, Data1).
|
{ok, HS1, Data1} -> read_message(HS1, Tokens, Data1);
|
||||||
|
Err = {error, _} -> Err
|
||||||
|
end.
|
||||||
|
|
||||||
write_token(HS = #noise_hs{ e = undefined }, e) ->
|
write_token(HS = #noise_hs{ e = undefined }, e) ->
|
||||||
E = new_key_pair(HS),
|
E = new_key_pair(HS),
|
||||||
@@ -115,21 +125,33 @@ write_token(HS, Token) ->
|
|||||||
|
|
||||||
read_token(HS = #noise_hs{ re = undefined, dh = DH }, e, Data0) ->
|
read_token(HS = #noise_hs{ re = undefined, dh = DH }, e, Data0) ->
|
||||||
DHLen = enoise_crypto:dhlen(DH),
|
DHLen = enoise_crypto:dhlen(DH),
|
||||||
<<REPub:DHLen/binary, Data1/binary>> = Data0,
|
case Data0 of
|
||||||
|
<<REPub:DHLen/binary, Data1/binary>> ->
|
||||||
RE = enoise_keypair:new(DH, REPub),
|
RE = enoise_keypair:new(DH, REPub),
|
||||||
{mix_hash(HS#noise_hs{ re = RE }, REPub), Data1};
|
{ok, mix_hash(HS#noise_hs{ re = RE }, REPub), Data1};
|
||||||
|
_ ->
|
||||||
|
{error, {bad_data, {failed_to_read_token, e, DHLen}}}
|
||||||
|
end;
|
||||||
read_token(HS = #noise_hs{ rs = undefined, dh = DH }, s, Data0) ->
|
read_token(HS = #noise_hs{ rs = undefined, dh = DH }, s, Data0) ->
|
||||||
DHLen = case has_key(HS) of
|
DHLen = case has_key(HS) of
|
||||||
true -> enoise_crypto:dhlen(DH) + 16;
|
true -> enoise_crypto:dhlen(DH) + 16;
|
||||||
false -> enoise_crypto:dhlen(DH)
|
false -> enoise_crypto:dhlen(DH)
|
||||||
end,
|
end,
|
||||||
<<Temp:DHLen/binary, Data1/binary>> = Data0,
|
case Data0 of
|
||||||
{ok, HS1, RSPub} = decrypt_and_hash(HS, Temp),
|
<<Temp:DHLen/binary, Data1/binary>> ->
|
||||||
|
case decrypt_and_hash(HS, Temp) of
|
||||||
|
{ok, HS1, RSPub} ->
|
||||||
RS = enoise_keypair:new(DH, RSPub),
|
RS = enoise_keypair:new(DH, RSPub),
|
||||||
{HS1#noise_hs{ rs = RS }, Data1};
|
{ok, HS1#noise_hs{ rs = RS }, Data1};
|
||||||
|
Err = {error, _} ->
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{error, {bad_data, {failed_to_read_token, s, DHLen}}}
|
||||||
|
end;
|
||||||
read_token(HS, Token, Data) ->
|
read_token(HS, Token, Data) ->
|
||||||
{K1, K2} = dh_token(HS, Token),
|
{K1, K2} = dh_token(HS, Token),
|
||||||
{mix_key(HS, dh(HS, K1, K2)), Data}.
|
{ok, mix_key(HS, dh(HS, K1, K2)), Data}.
|
||||||
|
|
||||||
dh_token(#noise_hs{ e = E, re = RE } , ee) -> {E, RE};
|
dh_token(#noise_hs{ e = E, re = RE } , ee) -> {E, RE};
|
||||||
dh_token(#noise_hs{ e = E, rs = RS, role = initiator }, es) -> {E, RS};
|
dh_token(#noise_hs{ e = E, rs = RS, role = initiator }, es) -> {E, RS};
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ new(Type) ->
|
|||||||
%% @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) ->
|
||||||
|
|||||||
@@ -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) ->
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(enoise_bad_data_tests).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
bad_data_hs_1_test() ->
|
||||||
|
SrvKeyPair = enoise_keypair:new(dh25519),
|
||||||
|
Proto = enoise_protocol:to_name(xk, dh25519, 'ChaChaPoly', blake2b),
|
||||||
|
Opts = [{echos, 1}, {reply, self()}],
|
||||||
|
Srv = enoise_utils:echo_srv_start(4567, Proto, SrvKeyPair, Opts),
|
||||||
|
|
||||||
|
bad_client(4567),
|
||||||
|
|
||||||
|
SrvRes =
|
||||||
|
receive {Srv, server_result, Res0} -> Res0
|
||||||
|
after 500 -> timeout end,
|
||||||
|
?assertMatch({error, {bad_data, _}}, SrvRes),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
bad_client(Port) ->
|
||||||
|
{ok, Sock} = gen_tcp:connect("localhost", Port, [binary, {reuseaddr, true}], 100),
|
||||||
|
gen_tcp:send(Sock, <<0:256/unit:8>>),
|
||||||
|
timer:sleep(100),
|
||||||
|
gen_tcp:close(Sock).
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|||||||
+9
-46
@@ -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;
|
||||||
@@ -69,8 +69,6 @@ noise_interactive([#{ payload := PL0, ciphertext := CT0 } | Msgs], SendHS, RecvH
|
|||||||
?assertEqual(HSHash, HSHash1), ?assertEqual(HSHash, HSHash2)
|
?assertEqual(HSHash, HSHash1), ?assertEqual(HSHash, HSHash2)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
noise_dh25519_test_() ->
|
noise_dh25519_test_() ->
|
||||||
%% Test vectors from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt
|
%% Test vectors from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt
|
||||||
{setup,
|
{setup,
|
||||||
@@ -85,8 +83,8 @@ noise_monitor_test_() ->
|
|||||||
{setup,
|
{setup,
|
||||||
fun() -> setup_dh25519() end,
|
fun() -> setup_dh25519() end,
|
||||||
fun(_X) -> ok end,
|
fun(_X) -> ok end,
|
||||||
fun({[T|Tests] = _Tests, SKP, CKP}) ->
|
fun({Tests, SKP, CKP}) ->
|
||||||
[ {T, fun() -> noise_monitor_test(T, SKP, CKP) end} ]
|
[ {T, fun() -> noise_monitor_test(T, SKP, CKP) end} || T <- Tests ]
|
||||||
end
|
end
|
||||||
}.
|
}.
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ setup_dh25519() ->
|
|||||||
noise_test(Conf, SKP, CKP) ->
|
noise_test(Conf, SKP, CKP) ->
|
||||||
#{econn := EConn, echo_srv := EchoSrv} = noise_test_run(Conf, SKP, CKP),
|
#{econn := EConn, echo_srv := EchoSrv} = noise_test_run(Conf, SKP, CKP),
|
||||||
enoise:close(EConn),
|
enoise:close(EConn),
|
||||||
echo_srv_stop(EchoSrv),
|
enoise_utils:echo_srv_stop(EchoSrv),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
noise_test_run(Conf, SKP, CKP) ->
|
noise_test_run(Conf, SKP, CKP) ->
|
||||||
@@ -151,11 +149,12 @@ noise_test_run_(Conf, SKP, CKP) ->
|
|||||||
Protocol = enoise_protocol:from_name(Conf),
|
Protocol = enoise_protocol:from_name(Conf),
|
||||||
Port = 4556,
|
Port = 4556,
|
||||||
|
|
||||||
EchoSrv = echo_srv_start(Port, Protocol, SKP, CKP),
|
SrvOpts = [{echos, 2}, {cpub, CKP}],
|
||||||
|
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} || need_rs(initiator, Conf) ],
|
Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, 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!">>),
|
||||||
@@ -176,7 +175,7 @@ noise_test_run_(Conf, SKP, CKP) ->
|
|||||||
noise_monitor_test(Conf, SKP, CKP) ->
|
noise_monitor_test(Conf, SKP, CKP) ->
|
||||||
#{ econn := {enoise, EConnPid}
|
#{ econn := {enoise, EConnPid}
|
||||||
, proxy := Proxy
|
, proxy := Proxy
|
||||||
, tcp_sock := TcpSock } = noise_test_run(Conf, SKP, CKP),
|
, tcp_sock := _TcpSock } = noise_test_run(Conf, SKP, CKP),
|
||||||
try proxy_exec(Proxy, fun() -> exit(normal) end)
|
try proxy_exec(Proxy, fun() -> exit(normal) end)
|
||||||
catch
|
catch
|
||||||
error:normal ->
|
error:normal ->
|
||||||
@@ -185,42 +184,6 @@ noise_monitor_test(Conf, SKP, CKP) ->
|
|||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
echo_srv_start(Port, Protocol, SKP, CPub) ->
|
|
||||||
Pid = spawn(fun() -> echo_srv(Port, Protocol, SKP, CPub) end),
|
|
||||||
timer:sleep(10),
|
|
||||||
Pid.
|
|
||||||
|
|
||||||
echo_srv(Port, Protocol, SKP, CPub) ->
|
|
||||||
TcpOpts = [{active, true}, binary, {reuseaddr, true}],
|
|
||||||
|
|
||||||
{ok, LSock} = gen_tcp:listen(Port, TcpOpts),
|
|
||||||
{ok, TcpSock} = gen_tcp:accept(LSock, 500),
|
|
||||||
|
|
||||||
Opts = [{noise, Protocol}, {s, SKP}] ++ [{rs, CPub} || need_rs(responder, Protocol)],
|
|
||||||
{ok, EConn, _} = enoise:accept(TcpSock, Opts),
|
|
||||||
|
|
||||||
gen_tcp:close(LSock),
|
|
||||||
|
|
||||||
%% {ok, Msg} = enoise:recv(EConn, 0, 100),
|
|
||||||
Msg0 = receive {noise, EConn, Data0} -> Data0
|
|
||||||
after 200 -> error(timeout) end,
|
|
||||||
ok = enoise:send(EConn, Msg0),
|
|
||||||
|
|
||||||
%% {ok, Msg} = enoise:recv(EConn, 0, 100),
|
|
||||||
Msg1 = receive {noise, EConn, Data1} -> Data1
|
|
||||||
after 200 -> error(timeout) end,
|
|
||||||
ok = enoise:send(EConn, Msg1),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
echo_srv_stop(Pid) ->
|
|
||||||
erlang:exit(Pid, kill).
|
|
||||||
|
|
||||||
need_rs(Role, Conf) when is_binary(Conf) -> need_rs(Role, enoise_protocol:from_name(Conf));
|
|
||||||
need_rs(Role, Protocol) ->
|
|
||||||
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
|
|
||||||
lists:member({in, [s]}, PreMsgs).
|
|
||||||
|
|
||||||
%% Talks to local echo-server (noise-c)
|
%% Talks to local echo-server (noise-c)
|
||||||
%% client_test() ->
|
%% client_test() ->
|
||||||
%% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
|
%% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(enoise_utils).
|
||||||
|
|
||||||
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
|
echo_srv_start(Port, Protocol, SKP, Opts) ->
|
||||||
|
Pid = spawn(fun() -> echo_srv(Port, Protocol, SKP, Opts) end),
|
||||||
|
timer:sleep(10),
|
||||||
|
Pid.
|
||||||
|
|
||||||
|
echo_srv_stop(Pid) ->
|
||||||
|
erlang:exit(Pid, kill).
|
||||||
|
|
||||||
|
echo_srv(Port, Protocol, SKP, SrvOpts) ->
|
||||||
|
TcpOpts = [{active, true}, binary, {reuseaddr, true}],
|
||||||
|
|
||||||
|
{ok, LSock} = gen_tcp:listen(Port, TcpOpts),
|
||||||
|
{ok, TcpSock} = gen_tcp:accept(LSock, 500),
|
||||||
|
|
||||||
|
Opts = [{noise, Protocol}, {s, SKP}] ++
|
||||||
|
[{rs, proplists:get_value(cpub, SrvOpts)} || need_rs(responder, Protocol)],
|
||||||
|
|
||||||
|
AcceptRes =
|
||||||
|
try
|
||||||
|
enoise:accept(TcpSock, Opts)
|
||||||
|
catch _:R -> gen_tcp:close(TcpSock), {error, {R, erlang:get_stacktrace()}} end,
|
||||||
|
|
||||||
|
gen_tcp:close(LSock),
|
||||||
|
|
||||||
|
case AcceptRes of
|
||||||
|
{ok, EConn, _} -> echo_srv_loop(EConn, SrvOpts);
|
||||||
|
Err = {error, _} -> srv_reply(Err, SrvOpts)
|
||||||
|
end.
|
||||||
|
|
||||||
|
echo_srv_loop(EConn, SrvOpts) ->
|
||||||
|
|
||||||
|
Recv =
|
||||||
|
case proplists:get_value(mode, SrvOpts, passive) of
|
||||||
|
passive ->
|
||||||
|
fun() ->
|
||||||
|
receive {noise, EConn, Data} -> Data
|
||||||
|
after 200 -> error(timeout) end
|
||||||
|
end;
|
||||||
|
active ->
|
||||||
|
fun() ->
|
||||||
|
{ok, Msg} = enoise:recv(EConn, 0, 100),
|
||||||
|
Msg
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
Echos = proplists:get_value(echos, SrvOpts, 2),
|
||||||
|
Res =
|
||||||
|
try
|
||||||
|
[ begin
|
||||||
|
Msg = Recv(),
|
||||||
|
ok = enoise:send(EConn, Msg)
|
||||||
|
end || _ <- lists:seq(1, Echos) ],
|
||||||
|
ok
|
||||||
|
catch _:R -> {error, R} end,
|
||||||
|
|
||||||
|
srv_reply(Res, SrvOpts),
|
||||||
|
|
||||||
|
enoise:close(EConn),
|
||||||
|
|
||||||
|
Res.
|
||||||
|
|
||||||
|
srv_reply(Reply, SrvOpts) ->
|
||||||
|
case proplists:get_value(reply, SrvOpts, undefined) of
|
||||||
|
undefined -> ok;
|
||||||
|
Pid -> Pid ! {self(), server_result, Reply}
|
||||||
|
end.
|
||||||
|
|
||||||
|
need_rs(Role, Conf) when is_binary(Conf) ->
|
||||||
|
need_rs(Role, enoise_protocol:from_name(Conf));
|
||||||
|
need_rs(Role, Protocol) ->
|
||||||
|
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
|
||||||
|
lists:member({in, [s]}, PreMsgs).
|
||||||
|
|
||||||
+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