From 6de4d0bf71a6a652f6b15314eee01e591a77c9dc Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 21 Dec 2018 09:34:24 +0100 Subject: [PATCH 1/2] Add (and refactor) tests for bad data in handshake --- test/enoise_bad_data_tests.erl | 27 ++++++++++++ test/enoise_tests.erl | 51 +++------------------ test/enoise_utils.erl | 81 ++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 test/enoise_bad_data_tests.erl create mode 100644 test/enoise_utils.erl diff --git a/test/enoise_bad_data_tests.erl b/test/enoise_bad_data_tests.erl new file mode 100644 index 0000000..ee3e442 --- /dev/null +++ b/test/enoise_bad_data_tests.erl @@ -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). + diff --git a/test/enoise_tests.erl b/test/enoise_tests.erl index d68b65a..c8eb9c2 100644 --- a/test/enoise_tests.erl +++ b/test/enoise_tests.erl @@ -69,8 +69,6 @@ noise_interactive([#{ payload := PL0, ciphertext := CT0 } | Msgs], SendHS, RecvH ?assertEqual(HSHash, HSHash1), ?assertEqual(HSHash, HSHash2) end. - - noise_dh25519_test_() -> %% Test vectors from https://raw.githubusercontent.com/rweather/noise-c/master/tests/vector/noise-c-basic.txt {setup, @@ -85,8 +83,8 @@ noise_monitor_test_() -> {setup, fun() -> setup_dh25519() end, fun(_X) -> ok end, - fun({[T|Tests] = _Tests, SKP, CKP}) -> - [ {T, fun() -> noise_monitor_test(T, SKP, CKP) end} ] + fun({Tests, SKP, CKP}) -> + [ {T, fun() -> noise_monitor_test(T, SKP, CKP) end} || T <- Tests ] end }. @@ -104,7 +102,7 @@ setup_dh25519() -> noise_test(Conf, SKP, CKP) -> #{econn := EConn, echo_srv := EchoSrv} = noise_test_run(Conf, SKP, CKP), enoise:close(EConn), - echo_srv_stop(EchoSrv), + enoise_utils:echo_srv_stop(EchoSrv), ok. noise_test_run(Conf, SKP, CKP) -> @@ -151,11 +149,12 @@ noise_test_run_(Conf, SKP, CKP) -> Protocol = enoise_protocol:from_name(Conf), 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), - 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 = enoise:send(EConn, <<"Hello World!">>), @@ -176,7 +175,7 @@ noise_test_run_(Conf, SKP, CKP) -> noise_monitor_test(Conf, SKP, CKP) -> #{ econn := {enoise, EConnPid} , 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) catch error:normal -> @@ -185,42 +184,6 @@ noise_monitor_test(Conf, SKP, CKP) -> 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) %% client_test() -> %% TestProtocol = enoise_protocol:from_name("Noise_XK_25519_ChaChaPoly_BLAKE2b"), diff --git a/test/enoise_utils.erl b/test/enoise_utils.erl new file mode 100644 index 0000000..fe5377b --- /dev/null +++ b/test/enoise_utils.erl @@ -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). + From 1ac27a035a29a74a5c0227e43a8cd3dea6f5a9d6 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 21 Dec 2018 09:34:51 +0100 Subject: [PATCH 2/2] Make read_message/read_token more robust --- src/enoise_hs_state.erl | 42 ++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/enoise_hs_state.erl b/src/enoise_hs_state.erl index cba8f97..9a64acd 100644 --- a/src/enoise_hs_state.erl +++ b/src/enoise_hs_state.erl @@ -80,8 +80,10 @@ write_message(HS = #noise_hs{ msgs = [{out, Msg} | Msgs] }, PayLoad) -> -spec read_message(HS :: state(), Message :: binary()) -> {ok, state(), binary()} | {error, term()}. read_message(HS = #noise_hs{ msgs = [{in, Msg} | Msgs] }, Message) -> - {HS1, RestBuf1} = read_message(HS#noise_hs{ msgs = Msgs }, Msg, Message), - decrypt_and_hash(HS1, RestBuf1). + case read_message(HS#noise_hs{ msgs = Msgs }, Msg, Message) of + {ok, HS1, RestBuf1} -> decrypt_and_hash(HS1, RestBuf1); + Err = {error, _} -> Err + end. remote_keys(#noise_hs{ rs = RS }) -> RS. @@ -93,10 +95,12 @@ write_message(HS, [Token | Tokens], MsgBuf0) -> write_message(HS1, Tokens, <>). read_message(HS, [], Data) -> - {HS, Data}; + {ok, HS, Data}; read_message(HS, [Token | Tokens], Data0) -> - {HS1, Data1} = read_token(HS, Token, Data0), - read_message(HS1, Tokens, Data1). + case read_token(HS, Token, Data0) of + {ok, HS1, Data1} -> read_message(HS1, Tokens, Data1); + Err = {error, _} -> Err + end. write_token(HS = #noise_hs{ e = undefined }, e) -> E = new_key_pair(HS), @@ -115,21 +119,33 @@ write_token(HS, Token) -> read_token(HS = #noise_hs{ re = undefined, dh = DH }, e, Data0) -> DHLen = enoise_crypto:dhlen(DH), - <> = Data0, - RE = enoise_keypair:new(DH, REPub), - {mix_hash(HS#noise_hs{ re = RE }, REPub), Data1}; + case Data0 of + <> -> + RE = enoise_keypair:new(DH, REPub), + {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) -> DHLen = case has_key(HS) of true -> enoise_crypto:dhlen(DH) + 16; false -> enoise_crypto:dhlen(DH) end, - <> = Data0, - {ok, HS1, RSPub} = decrypt_and_hash(HS, Temp), - RS = enoise_keypair:new(DH, RSPub), - {HS1#noise_hs{ rs = RS }, Data1}; + case Data0 of + <> -> + case decrypt_and_hash(HS, Temp) of + {ok, HS1, RSPub} -> + RS = enoise_keypair:new(DH, RSPub), + {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) -> {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, rs = RS, role = initiator }, es) -> {E, RS};