From 866b4421b36f465aedf94456088bccce8bea0638 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 7 Mar 2018 14:29:37 +0100 Subject: [PATCH] Add tests at gen_tcp-level Move data structure tests to enoise_hs_state_tests --- test/enoise_hs_state_tests.erl | 85 ++++++++++++++++ test/enoise_tests.erl | 176 +++++++++++++++------------------ test/test_utils.erl | 12 +++ 3 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 test/enoise_hs_state_tests.erl diff --git a/test/enoise_hs_state_tests.erl b/test/enoise_hs_state_tests.erl new file mode 100644 index 0000000..3fe006d --- /dev/null +++ b/test/enoise_hs_state_tests.erl @@ -0,0 +1,85 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%%------------------------------------------------------------------- + +-module(enoise_hs_state_tests). + +-include_lib("eunit/include/eunit.hrl"). +-record(key_pair, { puk, pik }). + +noise_hs_test_() -> + %% Test vectors 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_hs_test(T) end} + || T <- test_utils:noise_test_filter(Tests) ] + end + }. + +noise_hs_test(V = #{ name := 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, Message), + ?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). diff --git a/test/enoise_tests.erl b/test/enoise_tests.erl index 339d253..225c158 100644 --- a/test/enoise_tests.erl +++ b/test/enoise_tests.erl @@ -5,120 +5,108 @@ -module(enoise_tests). -include_lib("eunit/include/eunit.hrl"). --record(key_pair, { puk, pik }). - -noise_test_() -> - %% Test vector from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt +noise_dh25519_test_() -> + %% Test vectors from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt {setup, - fun() -> test_utils:noise_test_vectors() end, + fun() -> setup_dh25519() end, fun(_X) -> ok end, - fun(Tests) -> - [ {maps:get(name, T), fun() -> noise_test(T) end} || T <- noise_test_filter(Tests) ] + fun({Tests, SKP, CKP}) -> + [ {T, fun() -> noise_test(T, SKP, CKP) end} || T <- 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. +setup_dh25519() -> + %% Generate a static key-pair for Client and Server + SrvKeyPair = enoise_crypto:new_key_pair(dh25519), + CliKeyPair = enoise_crypto:new_key_pair(dh25519), -supported(Name) -> - try enoise_protocol:from_name(Name), true - catch _:_ -> false end. + #{ hs_pattern := Ps, hash := Hs, cipher := Cs } = enoise_protocol:supported(), + Configurations = [ enoise_protocol:to_name(P, dh25519, C, H) + || P <- Ps, C <- Cs, H <- Hs ], + %% Configurations = [ enoise_protocol:to_name(xk, dh25519, 'ChaChaPoly', blake2b) ], + {Configurations, SrvKeyPair, CliKeyPair}. -noise_test(V = #{ name := Name }) -> - %% ?debugFmt("~s", [Name]), - Protocol = enoise_protocol:from_name(Name), +noise_test(Conf, SKP, CKP) -> + Protocol = enoise_protocol:from_name(Conf), + Port = 4556, - FixK = fun(undefined) -> undefined; - (Bin) -> test_utils:hex_str_to_bin("0x" ++ binary_to_list(Bin)) end, + EchoSrv = echo_srv_start(Port, Protocol, SKP, enoise_crypto:pub_key(CKP)), - 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), + {ok, TcpSock} = gen_tcp:connect("localhost", Port, [{active, false}, binary, {reuseaddr, true}], 100), - noise_test(Name, Protocol, Init, Resp, Messages, FixK(HandshakeHash)), + Opts = [{noise, Protocol}, {s, CKP}] ++ [{rs, enoise_crypto:pub_key(SKP)} || need_rs(initiator, Conf) ], + {ok, EConn} = enoise:connect(TcpSock, Opts), + + ok = enoise:send(EConn, <<"Hello World!">>), + {ok, <<"Hello World!">>} = enoise:recv(EConn, 12, 100), + + ok = enoise:send(EConn, <<"Goodbye!">>), + timer:sleep(10), + {ok, <<"Goodbye!">>} = enoise:recv(EConn, 0, 100), + + enoise:close(EConn), + echo_srv_stop(EchoSrv), + ok. + +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. -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, +echo_srv_stop(Pid) -> + erlang:exit(Pid, kill). - 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, Message), - ?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). +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) -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>>, +%% 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, false}, binary, {reuseaddr, true}], 1000), - gen_tcp:send(TcpSock, <<0,8,0,0,3>>), %% "Noise_XK_25519_ChaChaPoly_Blake2b" +%% {ok, TcpSock} = gen_tcp:connect("localhost", 7890, [{active, false}, 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>>}], +%% Opts = [ {noise, TestProtocol} +%% , {s, #key_pair{ pik = ClientPrivKey, puk = ClientPubKey }} +%% , {rs, ServerPubKey} +%% , {prologue, <<0,8,0,0,3>>}], - {ok, EConn} = enoise:connect(TcpSock, Opts), - ok = enoise:send(EConn, <<"ok\n">>), - %% receive - %% {noise, EConn, <<"ok\n">>} -> ok - %% after 1000 -> error(timeout) end, - {ok, <<"ok\n">>} = enoise:recv(EConn, 3, 1000), - enoise:close(EConn). +%% {ok, EConn} = enoise:connect(TcpSock, Opts), +%% ok = enoise:send(EConn, <<"ok\n">>), +%% %% receive +%% %% {noise, EConn, <<"ok\n">>} -> ok +%% %% after 1000 -> error(timeout) end, +%% {ok, <<"ok\n">>} = enoise:recv(EConn, 3, 1000), +%% enoise:close(EConn). %% Expects a call-in from a local echo-client (noise-c) diff --git a/test/test_utils.erl b/test/test_utils.erl index 85ee4f9..e333bbb 100644 --- a/test/test_utils.erl +++ b/test/test_utils.erl @@ -112,3 +112,15 @@ parse_test_vectors(File) -> #{ vectors := Vectors } = jsx:decode(Bin, [{labels, atom}, return_maps]), Vectors. +%% Only test supported configurations +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.