Refactored + added some test vector support
This commit is contained in:
parent
bc8ebc7ec6
commit
b1d63ad3b9
@ -1,5 +1,3 @@
|
||||
-define(MAX_NONCE, 16#FFFFFFFFFFFFFFFF).
|
||||
-define(AD_LEN, 16).
|
||||
|
||||
-record(key_pair, { puk, pik }).
|
||||
-define(MAX_AD_LEN, 16).
|
||||
|
||||
|
@ -1,2 +1,5 @@
|
||||
{erl_opts, [debug_info]}.
|
||||
{deps, [{enacl, ".*", {git, "https://github.com/aeternity/enacl.git", {ref, "2d354f9"}}}]}.
|
||||
{deps, [{enacl, ".*", {git, "https://github.com/aeternity/enacl.git", {ref, "2f50ba6"}}}]}.
|
||||
|
||||
{profiles, [{test, [{deps, [{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}]}]}
|
||||
]}.
|
||||
|
@ -6,23 +6,50 @@
|
||||
|
||||
-include("enoise.hrl").
|
||||
|
||||
-export([decrypt/5, encrypt/5, rekey/2, hash/2, pad/3, hashlen/1, dhlen/1, new_key_pair/1, hmac/3, hkdf/3, dh/3]).
|
||||
-export([ decrypt/5
|
||||
, dh/3
|
||||
, dhlen/1
|
||||
, encrypt/5
|
||||
, hash/2
|
||||
, hashlen/1
|
||||
, hkdf/3
|
||||
, hmac/3
|
||||
, new_key_pair/1
|
||||
, pad/3
|
||||
, pub_key/1
|
||||
, rekey/2
|
||||
]).
|
||||
|
||||
-record(key_pair, { puk, pik }).
|
||||
|
||||
-opaque key_pair() :: #key_pair{}.
|
||||
|
||||
-export_type([key_pair/0]).
|
||||
|
||||
-define(MAC_LEN, 16).
|
||||
|
||||
-spec new_key_pair(Algo :: enoise_hs_state:noise_dh()) -> key_pair().
|
||||
new_key_pair(dh25519) ->
|
||||
KeyPair = enacl:crypto_sign_ed25519_keypair(),
|
||||
#key_pair{ puk = enacl:crypto_sign_ed25519_public_to_curve25519(maps:get(public, KeyPair))
|
||||
, pik = enacl:crypto_sign_ed25519_secret_to_curve25519(maps:get(secret, KeyPair)) }.
|
||||
|
||||
-spec dh(Algo :: enoise_hs_state:noise_dh(),
|
||||
PrivKey :: key_pair(), PubKey :: binary()) -> binary().
|
||||
dh(dh25519, KeyPair, PubKey) ->
|
||||
enacl:curve25519_scalarmult(KeyPair#key_pair.pik, PubKey).
|
||||
|
||||
-spec hmac(Hash :: enoise_sym_state:noise_hash(),
|
||||
Key :: binary(), Data :: binary()) -> binary().
|
||||
hmac(Hash, Key, Data) ->
|
||||
BLen = blocklen(blake2b),
|
||||
BLen = blocklen(Hash),
|
||||
Block1 = hmac_format_key(Hash, Key, 16#36, BLen),
|
||||
Hash1 = hash(Hash, <<Block1/binary, Data/binary>>),
|
||||
Block2 = hmac_format_key(Hash, Key, 16#5C, BLen),
|
||||
hash(Hash, <<Block2/binary, Hash1/binary>>).
|
||||
|
||||
-spec hkdf(Hash :: enoise_sym_state:noise_hash(),
|
||||
Key :: binary(), Data :: binary()) -> [binary()].
|
||||
hkdf(Hash, Key, Data) ->
|
||||
TempKey = hmac(Hash, Key, Data),
|
||||
Output1 = hmac(Hash, TempKey, <<1:8>>),
|
||||
@ -30,24 +57,47 @@ hkdf(Hash, Key, Data) ->
|
||||
Output3 = hmac(Hash, TempKey, <<Output2/binary, 3:8>>),
|
||||
[Output1, Output2, Output3].
|
||||
|
||||
-spec rekey(Cipher :: enoise_cipher_state:noise_cipher(),
|
||||
Key :: binary()) -> binary().
|
||||
rekey(Cipher, K) ->
|
||||
encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>).
|
||||
|
||||
-spec encrypt(Cipher :: enoise_cipher_state:noise_cipher(),
|
||||
Key :: binary(), Nonce :: non_neg_integer(),
|
||||
Ad :: binary(), PlainText :: binary()) ->
|
||||
binary() | {error, term()}.
|
||||
encrypt('ChaChaPoly', K, N, Ad, PlainText) ->
|
||||
enacl:aead_chacha20poly1305_encrypt(K, N, Ad, PlainText).
|
||||
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(),
|
||||
Key :: binary(), Nonce :: non_neg_integer(),
|
||||
AD :: binary(), CipherText :: binary()) ->
|
||||
binary() | {error, term()}.
|
||||
decrypt('ChaChaPoly', K, N, Ad, CipherText) ->
|
||||
enacl:aead_chacha20poly1305_decrypt(K, N, Ad, CipherText).
|
||||
enacl:aead_chacha20poly1305_decrypt(K, N, Ad, CipherText);
|
||||
decrypt('AESGCM', K, N, Ad, CipherText0) ->
|
||||
CTLen = byte_size(CipherText0) - ?MAC_LEN,
|
||||
<<CipherText:CTLen/binary, MAC:?MAC_LEN/binary>> = CipherText0,
|
||||
Nonce = <<0:32, N:64>>,
|
||||
crypto:block_decrypt(aes_gcm, K, Nonce, {Ad, CipherText, MAC}).
|
||||
|
||||
|
||||
-spec hash(Hash :: enoise_sym_state:noise_hash(), Data :: binary()) -> binary().
|
||||
hash(blake2b, Data) ->
|
||||
{ok, Hash} = enacl:generichash(64, Data), Hash;
|
||||
hash(sha256, Data) ->
|
||||
crypto:hash(sha256, Data);
|
||||
hash(sha512, Data) ->
|
||||
crypto:hash(sha512, Data);
|
||||
hash(Hash, _Data) ->
|
||||
error({hash_not_implemented_yet, Hash}).
|
||||
|
||||
-spec pad(Data :: binary(), MinSize :: non_neg_integer(),
|
||||
PadByte :: integer()) -> binary().
|
||||
pad(Data, MinSize, PadByte) ->
|
||||
case byte_size(Data) of
|
||||
N when N >= MinSize ->
|
||||
@ -57,16 +107,22 @@ pad(Data, MinSize, PadByte) ->
|
||||
<<Data/binary, PadData/binary>>
|
||||
end.
|
||||
|
||||
-spec pub_key(KeyPair :: key_pair()) -> binary().
|
||||
pub_key(#key_pair{ puk = PubKey }) -> PubKey.
|
||||
|
||||
-spec hashlen(Hash :: enoise_sym_state:noise_hash()) -> non_neg_integer().
|
||||
hashlen(sha256) -> 32;
|
||||
hashlen(sha512) -> 64;
|
||||
hashlen(blake2s) -> 32;
|
||||
hashlen(blake2b) -> 64.
|
||||
|
||||
-spec blocklen(Hash :: enoise_sym_state:noise_hash()) -> non_neg_integer().
|
||||
blocklen(sha256) -> 64;
|
||||
blocklen(sha512) -> 128;
|
||||
blocklen(blake2s) -> 64;
|
||||
blocklen(blake2b) -> 128.
|
||||
|
||||
-spec dhlen(DH :: enoise_hs_state:noise_dh()) -> non_neg_integer().
|
||||
dhlen(dh25519) -> 32;
|
||||
dhlen(dh448) -> 56.
|
||||
|
||||
|
@ -11,17 +11,22 @@
|
||||
-type noise_role() :: initiator | responder.
|
||||
-type noise_dh() :: dh25519 | dh448.
|
||||
-type noise_token() :: s | e | ee | ss | es | se.
|
||||
-type noise_msg() :: {in | out, [noise_token()]}.
|
||||
|
||||
-record(noise_hs, { ss :: enoise_sym_state:state()
|
||||
, s :: #key_pair{} | undefined
|
||||
, e :: #key_pair{} | undefined
|
||||
, s :: enoise_crypto:key_pair() | undefined
|
||||
, e :: enoise_crypto:key_pair() | undefined
|
||||
, rs :: binary() | undefined
|
||||
, re :: binary() | undefined
|
||||
, role = initiatior :: noise_role()
|
||||
, dh = dh25519 :: noise_dh()
|
||||
, msgs = [] :: [noise_msg()] }).
|
||||
, msgs = [] :: [enoise_protocol:noise_msg()] }).
|
||||
|
||||
-export_type([noise_dh/0, noise_role/0, noise_token/0]).
|
||||
|
||||
-spec init(Protocol :: string() | enoise_protocol:protocol(),
|
||||
Role :: noise_role(), Prologue :: binary(), Keys :: term()) -> #noise_hs{}.
|
||||
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}) ->
|
||||
SS0 = enoise_sym_state:init(Protocol),
|
||||
SS1 = enoise_sym_state:mix_hash(SS0, Prologue),
|
||||
@ -29,19 +34,20 @@ init(Protocol, Role, Prologue, {S, E, RS, RE}) ->
|
||||
, s = S, e = E, rs = RS, re = RE
|
||||
, role = Role
|
||||
, dh = enoise_protocol:dh(Protocol)
|
||||
, msgs = msgs(Role, enoise_protocol:pattern(Protocol)) },
|
||||
PreMsgs = pre_msgs(Role, enoise_protocol:pattern(Protocol)),
|
||||
lists:foldl(fun({out, [s]}, HS0) -> mix_hash(HS0, S#key_pair.puk);
|
||||
({out, [e]}, HS0) -> mix_hash(HS0, E#key_pair.puk);
|
||||
, msgs = enoise_protocol:msgs(Role, Protocol) },
|
||||
PreMsgs = enoise_protocol:pre_msgs(Role, Protocol),
|
||||
lists:foldl(fun({out, [s]}, HS0) -> mix_hash(HS0, enoise_crypto:pub_key(S));
|
||||
({out, [e]}, HS0) -> mix_hash(HS0, enoise_crypto:pub_key(E));
|
||||
({in, [s]}, HS0) -> mix_hash(HS0, RS);
|
||||
({in, [e]}, HS0) -> mix_hash(HS0, RE)
|
||||
end, HS, PreMsgs).
|
||||
|
||||
finalize(#noise_hs{ msgs = [], ss = SS, role = Role }) ->
|
||||
{C1, C2} = enoise_sym_state:split(SS),
|
||||
HSHash = enoise_sym_state:h(SS),
|
||||
case Role of
|
||||
initiator -> {ok, #{ tx => C1, rx => C2 }};
|
||||
responder -> {ok, #{ rx => C1, tx => C2 }}
|
||||
initiator -> {ok, #{ tx => C1, rx => C2, hs_hash => HSHash }};
|
||||
responder -> {ok, #{ rx => C1, tx => C2, hs_hash => HSHash }}
|
||||
end;
|
||||
finalize(_) ->
|
||||
error({bad_state, finalize}).
|
||||
@ -73,10 +79,15 @@ read_message(HS, [Token | Tokens], Data0) ->
|
||||
read_message(HS1, Tokens, Data1).
|
||||
|
||||
write_token(HS = #noise_hs{ e = undefined }, e) ->
|
||||
E = #key_pair{ puk = PubE } = new_key_pair(HS),
|
||||
E = new_key_pair(HS),
|
||||
PubE = enoise_crypto:pub_key(E),
|
||||
{mix_hash(HS#noise_hs{ e = E }, PubE), PubE};
|
||||
%% Should only apply during test - TODO: secure this
|
||||
write_token(HS = #noise_hs{ e = E }, e) ->
|
||||
PubE = enoise_crypto:pub_key(E),
|
||||
{mix_hash(HS, PubE), PubE};
|
||||
write_token(HS = #noise_hs{ s = S }, s) ->
|
||||
{ok, HS1, Msg} = encrypt_and_hash(HS, S#key_pair.puk),
|
||||
{ok, HS1, Msg} = encrypt_and_hash(HS, enoise_crypto:pub_key(S)),
|
||||
{HS1, Msg};
|
||||
write_token(HS, Token) ->
|
||||
{K1, K2} = dh_token(HS, Token),
|
||||
@ -134,21 +145,4 @@ decrypt_and_hash(HS = #noise_hs{ ss = SS0 }, CipherText) ->
|
||||
{ok, HS#noise_hs{ ss = SS1 }, PlainText}.
|
||||
|
||||
|
||||
msgs(Role, Protocol) ->
|
||||
{_Pre, Msgs} = protocol(Protocol),
|
||||
role_adapt(Role, Msgs).
|
||||
|
||||
pre_msgs(Role, Protocol) ->
|
||||
{PreMsgs, _Msgs} = protocol(Protocol),
|
||||
role_adapt(Role, PreMsgs).
|
||||
|
||||
role_adapt(initiator, Msgs) ->
|
||||
Msgs;
|
||||
role_adapt(responder, Msgs) ->
|
||||
Flip = fun({in, Msg}) -> {out, Msg}; ({out, Msg}) -> {in, Msg} end,
|
||||
lists:map(Flip, Msgs).
|
||||
|
||||
protocol(nn) ->
|
||||
{[], [{out, [e]}, {in, [e, ee]}]};
|
||||
protocol(xk) ->
|
||||
{[{in, [s]}], [{out, [e, es]}, {in, [e, ee]}, {out, [s, se]}]}.
|
||||
|
@ -8,10 +8,13 @@
|
||||
, dh/1
|
||||
, from_name/1
|
||||
, hash/1
|
||||
, msgs/2
|
||||
, pattern/1
|
||||
, pre_msgs/2
|
||||
, to_name/1]).
|
||||
|
||||
-compile(export_all).
|
||||
-type noise_pattern() :: nn | xk.
|
||||
-type noise_msg() :: {in | out, [enoise_hs_state:noise_token()]}.
|
||||
|
||||
-record(noise_protocol,
|
||||
{ hs_pattern = noiseNN :: noise_pattern()
|
||||
@ -22,7 +25,7 @@
|
||||
|
||||
-opaque protocol() :: #noise_protocol{}.
|
||||
|
||||
-export_type([noise_pattern/0, protocol/0]).
|
||||
-export_type([noise_msg/0, noise_pattern/0, protocol/0]).
|
||||
|
||||
-spec cipher(Protocol :: protocol()) -> enoise_cipher_state:noise_cipher().
|
||||
cipher(#noise_protocol{ cipher = Cipher }) ->
|
||||
@ -40,11 +43,100 @@ hash(#noise_protocol{ hash = Hash }) ->
|
||||
pattern(#noise_protocol{ hs_pattern = Pattern }) ->
|
||||
Pattern.
|
||||
|
||||
to_name(_Protocol) ->
|
||||
<<"Noise_XK_25519_ChaChaPoly_BLAKE2b">>.
|
||||
-spec to_name(Protocol :: protocol()) -> binary().
|
||||
to_name(Protocol = #noise_protocol{ hs_pattern = Pattern, dh = Dh
|
||||
, cipher = Cipher, hash = Hash }) ->
|
||||
case supported_pattern(Pattern) andalso supported_dh(Dh) andalso
|
||||
supported_cipher(Cipher) andalso supported_hash(Hash) of
|
||||
true -> to_name(Pattern, Dh, Cipher, Hash);
|
||||
false -> error({protocol_not_recognized, Protocol})
|
||||
end.
|
||||
|
||||
from_name("Noise_XK_25519_ChaChaPoly_Blake2b") ->
|
||||
#noise_protocol{ hs_pattern = xk, dh = dh25519, cipher = 'ChaChaPoly', hash = blake2b };
|
||||
from_name(Name) ->
|
||||
error({protocol_not_implemented_yet, Name}).
|
||||
-spec from_name(Name :: string() | binary()) -> protocol().
|
||||
from_name(Bin) when is_binary(Bin) -> from_name(binary_to_list(Bin));
|
||||
from_name(String) ->
|
||||
case string:lexemes(String, "_") of
|
||||
["Noise", PatStr, DhStr, CipStr, HashStr] ->
|
||||
Pattern = from_name_pattern(PatStr),
|
||||
Dh = from_name_dh(DhStr),
|
||||
Cipher = from_name_cipher(CipStr),
|
||||
Hash = from_name_hash(HashStr),
|
||||
case supported_pattern(Pattern) andalso supported_dh(Dh) andalso
|
||||
supported_cipher(Cipher) andalso supported_hash(Hash) of
|
||||
true -> #noise_protocol{ hs_pattern = Pattern, dh = Dh
|
||||
, cipher = Cipher, hash = Hash };
|
||||
false -> error({name_not_recognized, String})
|
||||
end;
|
||||
_ ->
|
||||
error({name_not_recognized, String})
|
||||
end.
|
||||
|
||||
-spec msgs(Role :: enoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
|
||||
msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
|
||||
{_Pre, Msgs} = protocol(Pattern),
|
||||
role_adapt(Role, Msgs).
|
||||
|
||||
-spec pre_msgs(Role :: enoise_hs_state:noise_role(), Protocol :: protocol()) -> [noise_msg()].
|
||||
pre_msgs(Role, #noise_protocol{ hs_pattern = Pattern }) ->
|
||||
{PreMsgs, _Msgs} = protocol(Pattern),
|
||||
role_adapt(Role, PreMsgs).
|
||||
|
||||
role_adapt(initiator, Msgs) ->
|
||||
Msgs;
|
||||
role_adapt(responder, Msgs) ->
|
||||
Flip = fun({in, Msg}) -> {out, Msg}; ({out, Msg}) -> {in, Msg} end,
|
||||
lists:map(Flip, Msgs).
|
||||
|
||||
protocol(nn) ->
|
||||
{[], [{out, [e]}, {in, [e, ee]}]};
|
||||
protocol(kn) ->
|
||||
{[{out, [s]}], [{out, [e]}, {in, [e, ee, se]}]};
|
||||
protocol(nk) ->
|
||||
{[{in, [s]}], [{out, [e, es]}, {in, [e, ee]}]};
|
||||
protocol(xk) ->
|
||||
{[{in, [s]}], [{out, [e, es]}, {in, [e, ee]}, {out, [s, se]}]}.
|
||||
|
||||
supported_hash(Hash) ->
|
||||
lists:member(Hash, [blake2b, sha256, sha512]).
|
||||
|
||||
supported_cipher(Cipher) ->
|
||||
lists:member(Cipher, ['ChaChaPoly', 'AESGCM']).
|
||||
|
||||
supported_dh(Dh) ->
|
||||
lists:member(Dh, [dh25519]).
|
||||
|
||||
supported_pattern(P) ->
|
||||
lists:member(P, [nn, kn, nk, xk]).
|
||||
|
||||
to_name(Pattern, Dh, Cipher, Hash) ->
|
||||
list_to_binary(lists:join("_", ["Noise", to_name_pattern(Pattern), to_name_dh(Dh),
|
||||
to_name_cipher(Cipher), to_name_hash(Hash)])).
|
||||
|
||||
to_name_pattern(Atom) ->
|
||||
[Simple | Rest] = string:lexemes(atom_to_list(Atom), "_"),
|
||||
string:uppercase(Simple) ++ lists:join("+", Rest).
|
||||
|
||||
from_name_pattern(String) ->
|
||||
[Init | Mod2] = string:lexemes(String, "+"),
|
||||
{Simple, Mod1} = lists:splitwith(fun(C) -> C >= $A andalso C =< $Z end, Init),
|
||||
list_to_atom(string:lowercase(Simple) ++
|
||||
case Mod1 of
|
||||
"" -> "";
|
||||
_ -> "_" ++ lists:join([Mod1 | Mod2], "_")
|
||||
end).
|
||||
|
||||
to_name_dh(dh25519) -> "25519";
|
||||
to_name_dh(dh448) -> "448".
|
||||
|
||||
from_name_dh(Dh) -> list_to_atom("dh" ++ Dh).
|
||||
|
||||
to_name_cipher(Cipher) -> atom_to_list(Cipher).
|
||||
|
||||
from_name_cipher(Cipher) -> list_to_atom(Cipher).
|
||||
|
||||
to_name_hash(sha256) -> "SHA256";
|
||||
to_name_hash(sha512) -> "SHA512";
|
||||
to_name_hash(blake2s) -> "BLAKE2s";
|
||||
to_name_hash(blake2b) -> "BLAKE2b".
|
||||
|
||||
from_name_hash(Hash) -> list_to_atom(string:lowercase(Hash)).
|
||||
|
@ -5,7 +5,7 @@
|
||||
-module(enoise_crypto_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("enoise.hrl").
|
||||
-record(key_pair, { puk, pik }).
|
||||
|
||||
curve25519_test() ->
|
||||
KeyPair1 = enoise_crypto:new_key_pair(dh25519),
|
||||
|
11
test/enoise_protocol_tests.erl
Normal file
11
test/enoise_protocol_tests.erl
Normal file
@ -0,0 +1,11 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(enoise_protocol_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
name_test() ->
|
||||
?assertMatch(<<"Noise_XK_25519_ChaChaPoly_SHA512">>,
|
||||
enoise_protocol:to_name(enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_SHA512"))).
|
@ -8,7 +8,7 @@
|
||||
-include("enoise.hrl").
|
||||
|
||||
noise_XK_25519_ChaChaPoly_Blake2b_test() ->
|
||||
Protocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_Blake2b"),
|
||||
Protocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
|
||||
|
||||
SSE0 = enoise_sym_state:init(Protocol),
|
||||
SSD0 = enoise_sym_state:init(Protocol),
|
||||
|
@ -5,13 +5,145 @@
|
||||
-module(enoise_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-record(key_pair, { puk, pik }).
|
||||
|
||||
-include("enoise.hrl").
|
||||
|
||||
%% connect_test() ->
|
||||
%% TestProtocol = #noise_protocol{ },
|
||||
%% {ok, EConn} = enoise:connect("localhost", 7890, [{noise, TestProtocol}, {pre_comm, <<0,0,0,0,2>>}], 1000),
|
||||
%% enoise:close(EConn).
|
||||
noise_test_() ->
|
||||
%% Test vector from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt
|
||||
{setup,
|
||||
fun() -> test_utils:noise_test_vectors() end,
|
||||
fun(_X) -> ok end,
|
||||
fun(Tests) ->
|
||||
[ {maps:get(name, T), fun() -> noise_test(T) end} || T <- noise_test_filter(Tests) ]
|
||||
end
|
||||
}.
|
||||
|
||||
noise_test_filter(Tests0) ->
|
||||
Tests1 = [ T || T = #{ name := Name } <- Tests0, supported(Name) ],
|
||||
case length(Tests1) < length(Tests0) of
|
||||
true -> ?debugFmt("WARNING: ~p test vectors are unsupported", [length(Tests0) - length(Tests1)]);
|
||||
false -> ok
|
||||
end,
|
||||
Tests1.
|
||||
|
||||
supported(Name) ->
|
||||
try enoise_protocol:from_name(Name), true
|
||||
catch _:_ -> false end.
|
||||
|
||||
noise_test(V = #{ name := Name }) ->
|
||||
%% ?debugFmt("~s", [Name]),
|
||||
Protocol = enoise_protocol:from_name(Name),
|
||||
|
||||
FixK = fun(undefined) -> undefined;
|
||||
(Bin) -> test_utils:hex_str_to_bin("0x" ++ binary_to_list(Bin)) end,
|
||||
|
||||
Init = #{ prologue => FixK(maps:get(init_prologue, V, <<>>))
|
||||
, e => FixK(maps:get(init_ephemeral, V, undefined))
|
||||
, s => FixK(maps:get(init_static, V, undefined))
|
||||
, rs => FixK(maps:get(init_remote_static, V, undefined)) },
|
||||
Resp = #{ prologue => FixK(maps:get(resp_prologue, V, <<>>))
|
||||
, e => FixK(maps:get(resp_ephemeral, V, undefined))
|
||||
, s => FixK(maps:get(resp_static, V, undefined))
|
||||
, rs => FixK(maps:get(resp_remote_static, V, undefined)) },
|
||||
Messages = maps:get(messages, V),
|
||||
HandshakeHash = maps:get(handshake_hash, V),
|
||||
|
||||
noise_test(Name, Protocol, Init, Resp, Messages, FixK(HandshakeHash)),
|
||||
|
||||
ok.
|
||||
|
||||
noise_test(_Name, Protocol, Init, Resp, Messages, HSHash) ->
|
||||
PubK = fun(undefined) -> undefined; (S) -> enacl:curve25519_scalarmult_base(S) end,
|
||||
HSInit = fun(P, R, #{ e := E, s := S, rs := RS, prologue := PL }) ->
|
||||
enoise_hs_state:init(P, R, PL, {#key_pair{ pik = S, puk = PubK(S) },
|
||||
#key_pair{ pik = E, puk = PubK(E) },
|
||||
RS, undefined})
|
||||
end,
|
||||
|
||||
InitHS = HSInit(Protocol, initiator, Init),
|
||||
RespHS = HSInit(Protocol, responder, Resp),
|
||||
|
||||
noise_test(Messages, InitHS, RespHS, HSHash),
|
||||
|
||||
ok.
|
||||
|
||||
noise_test([M = #{ payload := PL0, ciphertext := CT0 } | Msgs], SendHS, RecvHS, HSHash) ->
|
||||
PL = test_utils:hex_str_to_bin("0x" ++ binary_to_list(PL0)),
|
||||
CT = test_utils:hex_str_to_bin("0x" ++ binary_to_list(CT0)),
|
||||
case {enoise_hs_state:next_message(SendHS), enoise_hs_state:next_message(RecvHS)} of
|
||||
{out, in} ->
|
||||
{ok, SendHS1, Message} = enoise_hs_state:write_message(SendHS, PL),
|
||||
?assertEqual(CT, Message),
|
||||
{ok, RecvHS1, PL1} = enoise_hs_state:read_message(RecvHS, <<(byte_size(Message)):16, Message/binary>>),
|
||||
?assertEqual(PL, PL1),
|
||||
noise_test(Msgs, RecvHS1, SendHS1, HSHash);
|
||||
{done, done} ->
|
||||
{ok, #{ rx := RX1, tx := TX1, hs_hash := HSHash1 }} = enoise_hs_state:finalize(SendHS),
|
||||
{ok, #{ rx := RX2, tx := TX2, hs_hash := HSHash2 }} = enoise_hs_state:finalize(RecvHS),
|
||||
?assertEqual(RX1, TX2), ?assertEqual(RX2, TX1),
|
||||
?assertEqual(HSHash, HSHash1), ?assertEqual(HSHash, HSHash2),
|
||||
noise_test([M | Msgs], TX1, RX1);
|
||||
{Out, In} -> ?assertMatch({out, in}, {Out, In})
|
||||
end.
|
||||
|
||||
noise_test([], _, _) -> ok;
|
||||
noise_test([#{ payload := PL0, ciphertext := CT0 } | Msgs], CA, CB) ->
|
||||
PL = test_utils:hex_str_to_bin("0x" ++ binary_to_list(PL0)),
|
||||
CT = test_utils:hex_str_to_bin("0x" ++ binary_to_list(CT0)),
|
||||
{ok, CA1, CT1} = enoise_cipher_state:encrypt_with_ad(CA, <<>>, PL),
|
||||
?assertEqual(CT, CT1),
|
||||
{ok, CA2, PL1} = enoise_cipher_state:decrypt_with_ad(CA, <<>>, CT1),
|
||||
?assertEqual(CA1, CA2),
|
||||
?assertEqual(PL, PL1),
|
||||
noise_test(Msgs, CB, CA1).
|
||||
|
||||
%% Talks to local echo-server (noise-c)
|
||||
client_test() ->
|
||||
TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"),
|
||||
ClientPrivKey = <<64,168,119,119,151,194,94,141,86,245,144,220,78,53,243,231,168,216,66,199,49,148,202,117,98,40,61,109,170,37,133,122>>,
|
||||
ClientPubKey = <<115,39,86,77,44,85,192,176,202,11,4,6,194,144,127,123, 34,67,62,180,190,232,251,5,216,168,192,190,134,65,13,64>>,
|
||||
ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
|
||||
|
||||
{ok, TcpSock} = gen_tcp:connect("localhost", 7890, [{active, true}, binary, {reuseaddr, true}], 1000),
|
||||
gen_tcp:send(TcpSock, <<0,8,0,0,3>>), %% "Noise_XK_25519_ChaChaPoly_Blake2b"
|
||||
|
||||
Opts = [ {noise, TestProtocol}
|
||||
, {s, #key_pair{ pik = ClientPrivKey, puk = ClientPubKey }}
|
||||
, {rs, ServerPubKey}
|
||||
, {prologue, <<0,8,0,0,3>>}],
|
||||
|
||||
{ok, EConn} = enoise:connect(TcpSock, Opts),
|
||||
EConn1 = enoise:send(EConn, <<"ok\n">>),
|
||||
{EConn2, <<"ok\n">>} = enoise:recv(EConn1),
|
||||
enoise:close(EConn2).
|
||||
|
||||
|
||||
%% Expects a call-in from a local echo-client (noise-c)
|
||||
%% server_test_() ->
|
||||
%% {timeout, 20, fun() ->
|
||||
%% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_Blake2b"),
|
||||
|
||||
%% ServerPrivKey = <<200,81,196,192,228,196,182,200,181,83,169,255,242,54,99,113,8,49,129,92,225,220,99,50,93,96,253,250,116,196,137,103>>,
|
||||
%% ServerPubKey = <<112,91,141,253,183,66,217,102,211,40,13,249,238,51,77,114,163,159,32,1,162,219,76,106,89,164,34,71,149,2,103,59>>,
|
||||
|
||||
%% Opts = [ {noise, TestProtocol}
|
||||
%% , {s, #key_pair{ pik = ServerPrivKey, puk = ServerPubKey }}
|
||||
%% , {prologue, <<0,8,0,0,3>>}],
|
||||
|
||||
%% {ok, LSock} = gen_tcp:listen(7891, [{reuseaddr, true}, binary]),
|
||||
|
||||
%% {ok, TcpSock} = gen_tcp:accept(LSock, 10000),
|
||||
|
||||
%% receive {tcp, TcpSock, <<0,8,0,0,3>>} -> ok
|
||||
%% after 1000 -> error(timeout) end,
|
||||
|
||||
%% {ok, EConn} = enoise:accept(TcpSock, Opts),
|
||||
|
||||
%% {EConn1, Msg} = enoise:recv(EConn),
|
||||
%% EConn2 = enoise:send(EConn1, Msg),
|
||||
|
||||
%% enoise:close(EConn2)
|
||||
%% end}.
|
||||
|
||||
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(test_utils).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
@ -101,5 +102,13 @@ blake2b_hkdf_data() ->
|
||||
"CBB42F57D8B1B63B4C9EA64B0493E82A6F6D3A7037C33212EF6E4F56E321D4D9")
|
||||
}].
|
||||
|
||||
noise_test_vectors() ->
|
||||
parse_test_vectors("test/test_vectors.txt").
|
||||
|
||||
hex_str_to_bin("0x" ++ Rest) -> << <<(list_to_integer([C], 16)):4>> || C <- Rest >>.
|
||||
|
||||
parse_test_vectors(File) ->
|
||||
{ok, Bin} = file:read_file(File),
|
||||
#{ vectors := Vectors } = jsx:decode(Bin, [{labels, atom}, return_maps]),
|
||||
Vectors.
|
||||
|
||||
|
19684
test/test_vectors.txt
Normal file
19684
test/test_vectors.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user