From 4b568c6f180af5648218db95e331a39b90b8609a Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Sat, 30 Mar 2019 16:58:02 +0100 Subject: Use Erlang cookie as shared secret * Remove all configuration possibilities, so use the cookie as secret * Clean up error handling to make the module a more complete dist module * Change the init message to use length fields instead of zero termination * Remove the dependency towards modern crypto so it should run on maint --- lib/ssl/test/inet_crypto_dist.erl | 609 +++++++++++++++++++--------------- lib/ssl/test/ssl_dist_bench_SUITE.erl | 14 +- 2 files changed, 345 insertions(+), 278 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 5aafaac983..2cf9e3569e 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -30,13 +30,15 @@ -define(FAMILY, inet). -define(PROTOCOL, inet_crypto_dist_v1). --define(DEFAULT_BLOCK_CRYPTO, aes_128_gcm). --define(DEFAULT_HASH_ALGORITHM, sha256). --define(DEFAULT_REKEY_INTERVAL, 32768). +-define(HASH_ALGORITHM, sha256). +-define(BLOCK_CRYPTO, aes_gcm). +-define(IV_LEN, 12). +-define(KEY_LEN, 16). +-define(TAG_LEN, 16). +-define(REKEY_INTERVAL, 32768). -export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). --export([is_supported/0]). %% Generalized dist API, for sibling IPv6 module inet6_crypto_dist -export([gen_listen/2, gen_accept/2, gen_accept_connection/6, @@ -52,20 +54,6 @@ -include_lib("kernel/include/dist.hrl"). -include_lib("kernel/include/dist_util.hrl"). -%% Test if crypto has got enough capabilities for this module to run -%% -is_supported() -> - try {crypto:cipher_info(?DEFAULT_BLOCK_CRYPTO), - crypto:hash_info(?DEFAULT_HASH_ALGORITHM)} - of - {#{block_size := _, iv_length := _, key_length := _}, - #{size := _}} -> - true - catch - error:undef -> - false - end. - %% ------------------------------------------------------------------------- %% Erlang distribution plugin structure explained to myself %% ------- @@ -217,24 +205,23 @@ gen_accept(Listen, Driver) -> %% %% Spawn Acceptor process %% - Config = config(), monitor_dist_proc( spawn_opt( fun () -> - accept_loop(Listen, Driver, NetKernel, Config) + accept_loop(Listen, Driver, NetKernel) end, [link, {priority, max}])). -accept_loop(Listen, Driver, NetKernel, Config) -> - case Driver:accept(Listen) of +accept_loop(Listen, Driver, NetKernel) -> + case Driver:accept(trace(Listen)) of {ok, Socket} -> wait_for_code_server(), Timeout = net_kernel:connecttime(), - DistCtrl = start_dist_ctrl(Socket, Config, Timeout), + DistCtrl = start_dist_ctrl(trace(Socket), Timeout), %% DistCtrl is a "socket" NetKernel ! - {accept, - self(), DistCtrl, Driver:family(), ?DIST_PROTO}, + trace({accept, + self(), DistCtrl, Driver:family(), ?DIST_PROTO}), receive {NetKernel, controller, Controller} -> call_dist_ctrl(DistCtrl, {controller, Controller, self()}), @@ -242,7 +229,7 @@ accept_loop(Listen, Driver, NetKernel, Config) -> {NetKernel, unsupported_protocol} -> exit(unsupported_protocol) end, - accept_loop(Listen, Driver, NetKernel, Config); + accept_loop(Listen, Driver, NetKernel); AcceptError -> exit({accept, AcceptError}) end. @@ -292,12 +279,13 @@ gen_accept_connection( fun() -> do_accept( Acceptor, DistCtrl, - MyNode, Allowed, SetupTime, Driver, NetKernel) + trace(MyNode), Allowed, SetupTime, Driver, NetKernel) end, [link, {priority, max}])). do_accept( Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver, NetKernel) -> + %% receive {Acceptor, controller, Socket} -> Timer = dist_util:start_timer(SetupTime), @@ -337,40 +325,42 @@ gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, Driver) -> -spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()). setup_fun( Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) -> + %% fun() -> do_setup( - Node, Type, MyNode, LongOrShortNames, SetupTime, + trace(Node), Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) end. -spec do_setup(_,_,_,_,_,_,_) -> no_return(). do_setup( Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) -> + %% {Name, Address} = split_node(Driver, Node, LongOrShortNames), ErlEpmd = net_kernel:epmd_module(), {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver), Timer = trace(dist_util:start_timer(SetupTime)), case ARMod:ARFun(Name, Address, Driver:family()) of - {ok, Ip, TcpPort, Version} -> - do_setup_connect( - Node, Type, MyNode, Timer, Driver, NetKernel, - Ip, TcpPort, Version); + {ok, Ip, TcpPort, Version} -> + do_setup_connect( + Node, Type, MyNode, Timer, Driver, NetKernel, + Ip, TcpPort, Version); {ok, Ip} -> case ErlEpmd:port_please(Name, Ip) of {port, TcpPort, Version} -> do_setup_connect( Node, Type, MyNode, Timer, Driver, NetKernel, - Ip, TcpPort, Version); + Ip, TcpPort, trace(Version)); Other -> - ?shutdown2( - Node, - trace( - {port_please_failed, ErlEpmd, Name, Ip, Other})) + _ = trace( + {ErlEpmd, port_please, [Name, Ip], Other}), + ?shutdown(Node) end; Other -> - ?shutdown2( - Node, - trace({getaddr_failed, Driver, Address, Other})) + _ = trace( + {ARMod, ARFun, [Name, Address, Driver:family()], + Other}), + ?shutdown(Node) end. -spec do_setup_connect(_,_,_,_,_,_,_,_,_) -> no_return(). @@ -385,9 +375,12 @@ do_setup_connect( [binary, {active, false}, {packet, 2}, {nodelay, true}])), case Driver:connect(Ip, TcpPort, ConnectOpts) of {ok, Socket} -> - Config = config(), DistCtrl = - start_dist_ctrl(Socket, Config, net_kernel:connecttime()), + try start_dist_ctrl(Socket, net_kernel:connecttime()) + catch error : {dist_ctrl, _} = DistCtrlError -> + _ = trace(DistCtrlError), + ?shutdown(Node) + end, %% DistCtrl is a "socket" HSData = hs_data_common( @@ -401,8 +394,10 @@ do_setup_connect( request_type = Type}, dist_util:handshake_we_started(trace(HSData_1)); ConnectError -> - ?shutdown2(Node, - trace({connect_failed, Ip, TcpPort, ConnectError})) + _ = trace( + {Driver, connect, [Ip, TcpPort, ConnectOpts], + ConnectError}), + ?shutdown(Node) end. %% ------------------------------------------------------------------------- @@ -412,7 +407,7 @@ close(Socket) -> gen_close(Socket, ?DRIVER). gen_close(Socket, Driver) -> - trace(Driver:close(Socket)). + Driver:close(trace(Socket)). %% ------------------------------------------------------------------------- @@ -428,17 +423,23 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) -> socket = DistCtrl, timer = Timer, %% - f_send = + f_send = % -> ok | {error, closed}=>?shutdown() fun (S, Packet) when S =:= DistCtrl -> - call_dist_ctrl(S, {send, Packet}) + try call_dist_ctrl(S, {send, Packet}) + catch error : {dist_ctrl, Reason} -> + _ = trace(Reason), + {error, closed} + end end, - f_recv = + f_recv = % -> {ok, List} | Other=>?shutdown() fun (S, 0, infinity) when S =:= DistCtrl -> - case call_dist_ctrl(S, recv) of + try call_dist_ctrl(S, recv) of {ok, Bin} when is_binary(Bin) -> {ok, binary_to_list(Bin)}; Error -> Error + catch error : {dist_ctrl, Reason} -> + {error, trace(Reason)} end end, f_setopts_pre_nodeup = @@ -453,9 +454,9 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) -> fun (S) when S =:= DistCtrl -> {ok, S} %% DistCtrl is the distribution port end, - f_address = + f_address = % -> #net_address{} | ?shutdown() fun (S, Node) when S =:= DistCtrl -> - case call_dist_ctrl(S, peername) of + try call_dist_ctrl(S, peername) of {ok, Address} -> case dist_util:split_node(Node) of {node, _, Host} -> @@ -465,13 +466,23 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) -> protocol = ?DIST_PROTO, family = Family}; _ -> - {error, no_node} - end + ?shutdown(Node) + end; + Error -> + _ = trace(Error), + ?shutdown(Node) + catch error : {dist_ctrl, Reason} -> + _ = trace(Reason), + ?shutdown(Node) end end, - f_handshake_complete = - fun (S, _Node, DistHandle) when S =:= DistCtrl -> - call_dist_ctrl(S, {handshake_complete, DistHandle}) + f_handshake_complete = % -> ok | ?shutdown() + fun (S, Node, DistHandle) when S =:= DistCtrl -> + try call_dist_ctrl(S, {handshake_complete, DistHandle}) + catch error : {dist_ctrl, Reason} -> + _ = trace(Reason), + ?shutdown(Node) + end end, %% %% mf_tick/1, mf_getstat/1, mf_setopts/2 and mf_getopts/2 @@ -481,7 +492,7 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) -> fun (S) when S =:= DistCtrl -> S ! dist_tick end, - mf_getstat = + mf_getstat = % -> {ok, RecvCnt, SendCnt, SendPend} | Other=>ignore_it fun (S) when S =:= DistCtrl -> case inet:getstat(Socket, [recv_cnt, send_cnt, send_pend]) @@ -489,7 +500,7 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) -> {ok, Stat} -> split_stat(Stat, 0, 0, 0); Error -> - Error + trace(Error) end end, mf_setopts = @@ -596,25 +607,6 @@ nodelay() -> {nodelay, true} end. -config() -> - case init:get_argument(?DIST_NAME) of - error -> - error({missing_argument, ?DIST_NAME}); - {ok, [[String]]} -> - {ok, Tokens, _} = erl_scan:string(String ++ "."), - case erl_parse:parse_term(Tokens) of - {ok, #{secret := Secret} = Config} - when is_binary(Secret); is_list(Secret) -> - Config; - {ok, #{} = Config} -> - error({missing_secret, [{?DIST_NAME,Config}]}); - _ -> - error({bad_argument_value, [{?DIST_NAME,String}]}) - end; - {ok, Value} -> - error({malformed_argument, [{?DIST_NAME,Value}]}) - end. - %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% The DistCtrl process(es). @@ -625,19 +617,13 @@ config() -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% XXX Missing to "productified": -%%% * Cryptoanalysis by experts -%%% * Proof of usefulness -%%% * Unifying exit reasons using a death_row() function -%%% * Verification (and rejection) of other end's crypto parameters -%%% * OTP:ification (proc_lib?) -%%% * An application to belong to (crypto|kernel?) -%%% * Secret on file (cookie as default?), parameter handling -%%% * Restart and/or code reload policy +%%% * Cryptoanalysis by experts, e.g Forward Secrecy is missing, right? +%%% * Is it useful? +%%% * An application to belong to (kernel) +%%% * Restart and/or code reload policy (not needed in kernel) -%% Debug client and server -test_config() -> - #{secret => <<"Secret Cluster Password 123456">>}. +%% Debug client and server test_server() -> {ok, Listen} = gen_tcp:listen(0, [{packet, 2}, {active, false}, binary]), @@ -653,12 +639,12 @@ test_client(Port) -> test(Socket). test(Socket) -> - start_dist_ctrl(Socket, test_config(), 10000). + start_dist_ctrl(Socket, 10000). %% ------------------------------------------------------------------------- -start_dist_ctrl(Socket, Config, Timeout) -> - Protocol = ?PROTOCOL, +start_dist_ctrl(Socket, Timeout) -> + Secret = atom_to_binary(auth:get_cookie(), latin1), Controller = self(), Server = monitor_dist_proc( @@ -667,7 +653,7 @@ start_dist_ctrl(Socket, Config, Timeout) -> receive {?MODULE, From, start} -> {SendParams, RecvParams} = - init(Socket, Config, Protocol), + init(Socket, Secret), reply(From, self()), handshake(SendParams, 0, RecvParams, 0, Controller) end @@ -691,12 +677,13 @@ call_dist_ctrl(Server, Msg, Timeout) -> erlang:demonitor(Ref, [flush]), Res; {'DOWN', Ref, process, Server, Reason} -> - exit({?PROTOCOL, Reason}) - after Timeout -> - exit(Server, timeout), + error({dist_ctrl, Reason}) + after Timeout -> % Timeout < infinity is only used by start_dist_ctrl/2 receive {'DOWN', Ref, process, Server, _} -> - exit({?PROTOCOL, timeout}) + receive {Ref, _} -> ok after 0 -> ok end, + error({dist_ctrl, timeout}) + %% Server will be killed by link end end. @@ -717,10 +704,11 @@ reply({Ref, Pid}, Msg) -> key, tag_len}). --define(TCP_ACTIVE, 64). +-define(TCP_ACTIVE, 16). -define(CHUNK_SIZE, (65536 - 512)). -%% The start chunk starts with zeros, so it seems logical to not have -%% a chunk type with value 0 + +%% The start chunk starts with zeros, so it seems logical to +%% not have a chunk type with value 0 -define(HANDSHAKE_CHUNK, 1). -define(DATA_CHUNK, 2). -define(TICK_CHUNK, 3). @@ -759,85 +747,49 @@ reply({Ref, Pid}, Msg) -> %% in the local log, but all the other end sees is a closed connection. %% ------------------------------------------------------------------------- -init(Socket, Config, Protocol) -> - Secret = maps:get(secret, Config), - HashAlgorithm = - maps:get(hash_algorithm, Config, ?DEFAULT_HASH_ALGORITHM), - BlockCrypto = - maps:get(block_crypto, Config, ?DEFAULT_BLOCK_CRYPTO), - RekeyInterval = - maps:get(rekey_interval, Config, ?DEFAULT_REKEY_INTERVAL), - %% - SendParams = - init_params( - Socket, Protocol, HashAlgorithm, BlockCrypto, RekeyInterval), - send_init(SendParams, Secret). - -send_init( - #params{ - protocol = Protocol, - socket = Socket, - block_crypto = BlockCrypto, - iv = IVLen, - key = KeyLen, - hash_algorithm = HashAlgorithm} = SendParams, - Secret) -> - %% - ProtocolString = atom_to_binary(Protocol, utf8), - BlockCryptoString = atom_to_binary(BlockCrypto, utf8), - HashAlgorithmString = atom_to_binary(HashAlgorithm, utf8), - SendHeader = - <>, - <> = IV_KeySalt = - crypto:strong_rand_bytes(IVLen + KeyLen), - InitPacket = [SendHeader, IV_KeySalt], - ok = gen_tcp:send(Socket, InitPacket), - recv_init(SendParams#params{iv = IV, key = KeySalt}, Secret, SendHeader). +init(Socket, Secret) -> + {SendParams, Header, Data} = init_params(Socket), + ok = gen_tcp:send(Socket, [Header, Data]), + init( + SendParams, Secret, + [<<(byte_size(Header) + byte_size(Data)):32>>, Header]). -recv_init( +init( #params{ socket = Socket, hash_algorithm = SendHashAlgorithm, - key = SendKeySalt} = SendParams, Secret, SendHeader) -> + key = {SendKeySalt, SendOtherSalt}} = SendParams, Secret, SendAAD) -> %% - {ok, InitPacket} = gen_tcp:recv(Socket, 0), - [ProtocolString, Rest_1] = binary:split(InitPacket, <<0>>), - Protocol = binary_to_existing_atom(ProtocolString, utf8), - case Protocol of - ?PROTOCOL -> - [HashAlgorithmString, Rest_2] = binary:split(Rest_1, <<0>>), - HashAlgorithm = binary_to_existing_atom(HashAlgorithmString, utf8), - [BlockCryptoString, Rest_3] = binary:split(Rest_2, <<0>>), - BlockCrypto = binary_to_existing_atom(BlockCryptoString, utf8), - #params{ - hash_algorithm = RecvHashAlgorithm, - iv = RecvIVLen, - key = RecvKeyLen} = RecvParams = - init_params( - Socket, Protocol, HashAlgorithm, BlockCrypto, undefined), - <> = Rest_3, + {ok, InitMsg} = gen_tcp:recv(Socket, 0), + try + init_params(Socket, InitMsg) + of + {#params{ + hash_algorithm = RecvHashAlgorithm, + key = {RecvKeySalt, RecvOtherSalt}} = RecvParams, + RecvHeader} -> + RecvAAD = [<<(byte_size(InitMsg)):32>>, RecvHeader], SendKey = - hash_key(SendHashAlgorithm, SendKeySalt, [RecvKeySalt, Secret]), + hash_key( + SendHashAlgorithm, SendKeySalt, [RecvOtherSalt, Secret]), RecvKey = - hash_key(RecvHashAlgorithm, RecvKeySalt, [SendKeySalt, Secret]), + hash_key( + RecvHashAlgorithm, RecvKeySalt, [SendOtherSalt, Secret]), SendParams_1 = SendParams#params{key = SendKey}, - RecvParams_1 = RecvParams#params{iv = RecvIV, key = RecvKey}, - RecvHeaderLen = byte_size(InitPacket) - RecvIVLen - RecvKeyLen, - <> = InitPacket, - send_start(SendParams_1, SendHeader), - RecvRekeyInterval = recv_start(RecvParams_1, RecvHeader), - {SendParams_1, - RecvParams_1#params{rekey_interval = RecvRekeyInterval}} + RecvParams_1 = RecvParams#params{key = RecvKey}, + send_start(SendParams_1, SendAAD), + recv_start(RecvParams_1, RecvAAD), + {SendParams_1, RecvParams_1} + catch + error : Reason -> + _ = trace(Reason), + exit(connection_closed) end. send_start( #params{ socket = Socket, block_crypto = BlockCrypto, - rekey_interval= RekeyInterval, iv = IV, key = Key, tag_len = TagLen}, AAD) -> @@ -845,9 +797,7 @@ send_start( KeyLen = byte_size(Key), Zeros = binary:copy(<<0>>, KeyLen), {Ciphertext, CipherTag} = - crypto:block_encrypt( - crypto_cipher_name(BlockCrypto), - Key, IV, {AAD, [Zeros, <>], TagLen}), + crypto:block_encrypt(BlockCrypto, Key, IV, {AAD, Zeros, TagLen}), ok = gen_tcp:send(Socket, [Ciphertext, CipherTag]). recv_start( @@ -857,51 +807,100 @@ recv_start( iv = IV, key = Key, tag_len = TagLen}, AAD) -> + %% {ok, Packet} = gen_tcp:recv(Socket, 0), KeyLen = byte_size(Key), - PacketLen = KeyLen + 4, + PacketLen = KeyLen, <> = Packet, Zeros = binary:copy(<<0>>, KeyLen), case crypto:block_decrypt( - crypto_cipher_name(BlockCrypto), - Key, IV, {AAD, Ciphertext, CipherTag}) + BlockCrypto, Key, IV, {AAD, Ciphertext, CipherTag}) of - <> - when 1 =< RekeyInterval -> - RekeyInterval; + Zeros -> + ok; _ -> - error(decrypt_error) + _ = trace(decrypt_error), + exit(connection_closed) end. -init_params(Socket, Protocol, HashAlgorithm, BlockCrypto, RekeyInterval) -> - #{block_size := 1, - iv_length := IVLen, - key_length := KeyLen} = crypto:cipher_info(BlockCrypto), - case crypto:hash_info(HashAlgorithm) of - #{size := HashSize} when HashSize >= KeyLen -> - #params{ - socket = Socket, - protocol = Protocol, - hash_algorithm = HashAlgorithm, - block_crypto = BlockCrypto, - rekey_interval = RekeyInterval, - iv = IVLen, - key = KeyLen, - tag_len = 16} - end. -crypto_cipher_name(BlockCrypto) -> - case BlockCrypto of - aes_128_gcm -> aes_gcm; - aes_192_gcm -> aes_gcm; - aes_256_gcm -> aes_gcm + +init_params(Socket) -> + Protocol = ?PROTOCOL, + HashAlgorithm = ?HASH_ALGORITHM, + BlockCrypto = ?BLOCK_CRYPTO, + IVLen = ?IV_LEN, + KeyLen = ?KEY_LEN, + TagLen = ?TAG_LEN, + RekeyInterval = ?REKEY_INTERVAL, + %% + ProtocolBin = atom_to_binary(Protocol, utf8), + HashAlgorithmBin = atom_to_binary(HashAlgorithm, utf8), + BlockCryptoBin = atom_to_binary(BlockCrypto, utf8), + Header = + <<(byte_size(ProtocolBin)), ProtocolBin/binary, + (byte_size(HashAlgorithmBin)), HashAlgorithmBin/binary, + (byte_size(BlockCryptoBin)), BlockCryptoBin/binary, + KeyLen, TagLen>>, + <> = + Data = crypto:strong_rand_bytes(IVLen + KeyLen + KeyLen), + {#params{ + socket = Socket, + protocol = Protocol, + hash_algorithm = HashAlgorithm, + block_crypto = BlockCrypto, + iv = IV, + key = {KeySalt, OtherSalt}, + tag_len = TagLen, + rekey_interval = RekeyInterval}, Header, Data}. + +init_params(Socket, Msg) -> + Protocol = ?PROTOCOL, + ProtocolBin = atom_to_binary(Protocol, utf8), + ProtocolBinLen = byte_size(ProtocolBin), + case Msg of + <> -> + HashAlgorithm = ?HASH_ALGORITHM, + BlockCrypto = ?BLOCK_CRYPTO, + HashAlgorithmBin = atom_to_binary(HashAlgorithm, utf8), + HashAlgorithmBinLen = byte_size(HashAlgorithmBin), + BlockCryptoBin = atom_to_binary(BlockCrypto, utf8), + BlockCryptoBinLen = byte_size(BlockCryptoBin), + case Rest_1 of + <> -> + IVLen = ?IV_LEN, KeyLen = ?KEY_LEN, + TagLen = ?TAG_LEN, RekeyInterval = ?REKEY_INTERVAL, + <> = Rest_2, + HeaderLen = byte_size(Msg) - (byte_size(Rest_2) - 2), + <> = Msg, + {#params{ + socket = Socket, + protocol = Protocol, + hash_algorithm = HashAlgorithm, + block_crypto = BlockCrypto, + iv = IV, + key = {KeySalt, OtherSalt}, + tag_len = TagLen, + rekey_interval = RekeyInterval}, + Header} + end end. -hash_key(HashAlgorithm, KeySalt, OtherSalt) -> + + +hash_key(HashAlgorithm, KeySalt, KeyData) -> KeyLen = byte_size(KeySalt), <> = - crypto:hash(HashAlgorithm, [KeySalt, OtherSalt]), + crypto:hash(HashAlgorithm, [KeySalt, KeyData]), Key. %% ------------------------------------------------------------------------- @@ -918,7 +917,6 @@ handshake( reply(From, Result), handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller_1); {?MODULE, From, {handshake_complete, DistHandle}} -> - reply(From, ok), InputHandler = monitor_dist_proc( spawn_opt( @@ -945,25 +943,38 @@ handshake( ok = gen_tcp:controlling_process(Socket, InputHandler), ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler), InputHandler ! DistHandle, + crypto:rand_seed_alg(crypto_cache), + reply(From, ok), process_flag(priority, normal), erlang:dist_ctrl_get_data_notification(DistHandle), - crypto:rand_seed_alg(crypto_cache), output_handler( SendParams#params{dist_handle = DistHandle}, SendSeq); %% {?MODULE, From, {send, Data}} -> - {SendParams_1, SendSeq_1} = + case encrypt_and_send_chunk( - SendParams, SendSeq, [?HANDSHAKE_CHUNK, Data]), - reply(From, ok), - handshake( - SendParams_1, SendSeq_1, RecvParams, RecvSeq, Controller); + SendParams, SendSeq, [?HANDSHAKE_CHUNK, Data]) + of + {SendParams_1, SendSeq_1, ok} -> + reply(From, ok), + handshake( + SendParams_1, SendSeq_1, RecvParams, RecvSeq, + Controller); + {_, _, Error} -> + reply(From, {error, closed}), + death_row({send, trace(Error)}) + end; {?MODULE, From, recv} -> - {RecvParams_1, RecvSeq_1, Reply} = - recv_and_decrypt_chunk(RecvParams, RecvSeq), - reply(From, Reply), - handshake( - SendParams, SendSeq, RecvParams_1, RecvSeq_1, Controller); + case recv_and_decrypt_chunk(RecvParams, RecvSeq) of + {RecvParams_1, RecvSeq_1, {ok, _} = Reply} -> + reply(From, Reply), + handshake( + SendParams, SendSeq, RecvParams_1, RecvSeq_1, + Controller); + {_, _, Error} -> + reply(From, Error), + death_row({recv, trace(Error)}) + end; {?MODULE, From, peername} -> reply(From, inet:peername(Socket)), handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller); @@ -978,10 +989,12 @@ recv_and_decrypt_chunk(#params{socket = Socket} = RecvParams, RecvSeq) -> case decrypt_chunk(RecvParams, RecvSeq, Chunk) of <> -> {RecvParams, RecvSeq + 1, {ok, Cleartext}}; + OtherChunk when is_binary(OtherChunk) -> + {RecvParams, RecvSeq + 1, {error, decrypt_error}}; #params{} = RecvParams_1 -> recv_and_decrypt_chunk(RecvParams_1, 0); - _ -> - error(decrypt_error) + error -> + {RecvParams, RecvSeq, {error, decrypt_error}} end; Error -> {RecvParams, RecvSeq, Error} @@ -1001,8 +1014,9 @@ output_handler(Params, Seq) -> output_handler_data(Params, Seq); dist_tick -> output_handler_tick(Params, Seq); - _Other -> + Other -> %% Ignore + _ = trace(Other), output_handler(Params, Seq) end end. @@ -1015,14 +1029,15 @@ output_handler_data(Params, Seq) -> output_handler_data(Params, Seq); dist_tick -> output_handler_data(Params, Seq); - _Other -> + Other -> %% Ignore + _ = trace(Other), output_handler_data(Params, Seq) end after 0 -> DistHandle = Params#params.dist_handle, Q = get_data(DistHandle, empty_q()), - {Params_1, Seq_1} = output_handler_send(Params, Seq, Q, true), + {Params_1, Seq_1} = output_handler_send(Params, Seq, Q), erlang:dist_ctrl_get_data_notification(DistHandle), output_handler(Params_1, Seq_1) end. @@ -1035,39 +1050,56 @@ output_handler_tick(Params, Seq) -> output_handler_data(Params, Seq); dist_tick -> output_handler_tick(Params, Seq); - _Other -> + Other -> %% Ignore + _ = trace(Other), output_handler_tick(Params, Seq) end after 0 -> - TickSize = 8 + rand:uniform(56), + TickSize = 7 + rand:uniform(56), TickData = binary:copy(<<0>>, TickSize), - {Params_1, Seq_1} = - encrypt_and_send_chunk(Params, Seq, [?TICK_CHUNK, TickData]), - output_handler(Params_1, Seq_1) + case + encrypt_and_send_chunk(Params, Seq, [?TICK_CHUNK, TickData]) + of + {Params_1, Seq_1, ok} -> + output_handler(Params_1, Seq_1); + {_, _, Error} -> + _ = trace(Error), + death_row() + end end. -output_handler_send( - #params{dist_handle = DistHandle} = Params, Seq, {_, Size, _} = Q, Retry) -> - %% +output_handler_send(Params, Seq, {_, Size, _} = Q) -> if ?CHUNK_SIZE < Size -> - {Cleartext, Q_1} = deq_iovec(?CHUNK_SIZE, Q), - {Params_1, Seq_1} = - encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext]), - output_handler_send(Params_1, Seq_1, Q_1, Retry); - Retry -> - Q_1 = get_data(DistHandle, Q), - output_handler_send(Params, Seq, Q_1, false); + output_handler_send(Params, Seq, Q, ?CHUNK_SIZE); true -> - {Cleartext, _} = deq_iovec(Size, Q), - encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext]) + case get_data(Params#params.dist_handle, Q) of + {_, 0, _} -> + {Params, Seq}; + {_, Size, _} = Q_1 -> % Got no more + output_handler_send(Params, Seq, Q_1, Size); + Q_1 -> + output_handler_send(Params, Seq, Q_1) + end + end. + +output_handler_send(Params, Seq, Q, Size) -> + {Cleartext, Q_1} = deq_iovec(Size, Q), + case + encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext]) + of + {Params_1, Seq_1, ok} -> + output_handler_send(Params_1, Seq_1, Q_1); + {_, _, Error} -> + _ = trace(Error), + death_row() end. %% ------------------------------------------------------------------------- %% Input handler process %% -%% Here is T 0 or infinity to steer if we should try to receive +%% Here is T = 0|infinity to steer if we should try to receive %% more data or not; start with infinity, and when we get some %% data try with 0 to see if more is waiting @@ -1086,11 +1118,12 @@ input_handler(#params{socket = Socket} = Params, Seq, Q, T) -> end, input_handler(Params, Seq, Q_1, infinity); {tcp, Socket, Chunk} -> - input_chunk(Params, Seq, Q, Chunk); + input_chunk(Params, Seq, Q, T, Chunk); {tcp_closed, Socket} -> - error(connection_closed); - _Other -> + exit(connection_closed); + Other -> %% Ignore... + _ = trace(Other), input_handler(Params, Seq, Q, T) end after T -> @@ -1098,16 +1131,20 @@ input_handler(#params{socket = Socket} = Params, Seq, Q, T) -> input_handler(Params, Seq, Q_1, infinity) end. -input_chunk(Params, Seq, Q, Chunk) -> +input_chunk(Params, Seq, Q, T, Chunk) -> case decrypt_chunk(Params, Seq, Chunk) of <> -> input_handler(Params, Seq + 1, enq_binary(Cleartext, Q), 0); <> -> - input_handler(Params, Seq + 1, Q, 0); + input_handler(Params, Seq + 1, Q, T); + OtherChunk when is_binary(OtherChunk) -> + _ = trace(invalid_chunk), + exit(connection_closed); #params{} = Params_1 -> - input_handler(Params_1, 0, Q, 0); - _ -> - error(decrypt_error) + input_handler(Params_1, 0, Q, T); + error -> + _ = trace(decrypt_error), + exit(connection_closed) end. %% ------------------------------------------------------------------------- @@ -1206,14 +1243,22 @@ encrypt_and_send_chunk( IVLen = byte_size(IV), Chunk = <> = crypto:strong_rand_bytes(IVLen + KeyLen), - ok = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, Chunk])), - Key_1 = hash_key(HashAlgorithm, Key, KeySalt), - Params_1 = Params#params{key = Key_1, iv = IV_1}, - ok = gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), - {Params_1, 1}; + case + gen_tcp:send( + Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, Chunk])) + of + ok -> + Key_1 = hash_key(HashAlgorithm, Key, KeySalt), + Params_1 = Params#params{key = Key_1, iv = IV_1}, + Result = + gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), + {Params_1, 1, Result}; + SendError -> + {Params, Seq + 1, SendError} + end; encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) -> - ok = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)), - {Params, Seq + 1}. + Result = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)), + {Params, Seq + 1, Result}. encrypt_chunk( #params{ @@ -1223,8 +1268,7 @@ encrypt_chunk( ChunkLen = iolist_size(Cleartext) + TagLen, AAD = <>, {Ciphertext, CipherTag} = - crypto:block_encrypt( - crypto_cipher_name(BlockCrypto), Key, IV, {AAD, Cleartext, TagLen}), + crypto:block_encrypt(BlockCrypto, Key, IV, {AAD, Cleartext, TagLen}), Chunk = [Ciphertext,CipherTag], Chunk. @@ -1234,28 +1278,50 @@ decrypt_chunk( iv = IV, key = Key, tag_len = TagLen} = Params, Seq, Chunk) -> %% ChunkLen = byte_size(Chunk), - true = TagLen =< ChunkLen, % Assert - AAD = <>, - CiphertextLen = ChunkLen - TagLen, - <> = Chunk, - block_decrypt( - Params, Seq, crypto_cipher_name(BlockCrypto), - Key, IV, {AAD, Ciphertext, CipherTag}). + if + ChunkLen < TagLen -> + error; + true -> + AAD = <>, + CiphertextLen = ChunkLen - TagLen, + case Chunk of + <> -> + block_decrypt( + Params, Seq, BlockCrypto, Key, IV, + {AAD, Ciphertext, CipherTag}); + _ -> + error + end + end. block_decrypt( - #params{rekey_interval = Seq} = Params, Seq, CipherName, Key, IV, Data) -> + #params{rekey_interval = RekeyInterval} = Params, + Seq, CipherName, Key, IV, Data) -> %% - KeyLen = byte_size(Key), - IVLen = byte_size(IV), case crypto:block_decrypt(CipherName, Key, IV, Data) of - <> -> - Key_1 = hash_key(Params#params.hash_algorithm, Key, KeySalt), - Params#params{iv = IV_1, key = Key_1}; - _ -> - error(decrypt_error) - end; -block_decrypt(_Params, _Seq, CipherName, Key, IV, Data) -> - crypto:block_decrypt(CipherName, Key, IV, Data). + <> -> + KeyLen = byte_size(Key), + IVLen = byte_size(IV), + case Rest of + <> -> + Key_1 = + hash_key( + Params#params.hash_algorithm, Key, KeySalt), + Params#params{iv = IV_1, key = Key_1}; + _ -> + error + end; + Chunk when is_binary(Chunk) -> + case Seq of + RekeyInterval -> + error; + _ -> + Chunk + end; + error -> + error + end. %% ------------------------------------------------------------------------- %% Queue of binaries i.e an iovec queue @@ -1289,6 +1355,13 @@ deq_iovec(GetSize, [Bin|Front], Size, Rear, Acc) -> %% ------------------------------------------------------------------------- +death_row() -> death_row(connection_closed). +%% +death_row(normal) -> death_row(connection_closed); +death_row(Reason) -> receive after 5000 -> exit(Reason) end. + +%% ------------------------------------------------------------------------- + %% Trace point trace(Term) -> Term. @@ -1316,7 +1389,7 @@ dbg() -> dbg:stop(), dbg:tracer(), dbg:p(all, c), - dbg:tpl(?MODULE, cx), + dbg:tpl(?MODULE, trace, cx), dbg:tpl(erlang, dist_ctrl_get_data_notification, cx), dbg:tpl(erlang, dist_ctrl_get_data, cx), dbg:tpl(erlang, dist_ctrl_put_data, cx), diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl index 1fea6f6f72..67944c74d2 100644 --- a/lib/ssl/test/ssl_dist_bench_SUITE.erl +++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl @@ -169,16 +169,10 @@ end_per_suite(Config) -> init_per_group(ssl, Config) -> [{ssl_dist, true}, {ssl_dist_prefix, "SSL"}|Config]; init_per_group(crypto, Config) -> - case inet_crypto_dist:is_supported() of - true -> - [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"}, - {ssl_dist_args, - "-proto_dist inet_crypto " - "-inet_crypto '#{secret => \"123456\"}'"} - |Config]; - false -> - {skip, "Not supported on this OTP version"} - end; + [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"}, + {ssl_dist_args, + "-proto_dist inet_crypto"} + |Config]; init_per_group(plain, Config) -> [{ssl_dist, false}, {ssl_dist_prefix, "Plain"}|Config]; init_per_group(_GroupName, Config) -> -- cgit v1.2.3 From a3640345c9ee0a66df13d5cb47313adeb70d937e Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 9 Apr 2019 16:20:24 +0200 Subject: Use incrementing IV --- lib/ssl/test/inet_crypto_dist.erl | 54 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 2cf9e3569e..20819015d1 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -655,7 +655,7 @@ start_dist_ctrl(Socket, Timeout) -> {SendParams, RecvParams} = init(Socket, Secret), reply(From, self()), - handshake(SendParams, 0, RecvParams, 0, Controller) + handshake(SendParams, 1, RecvParams, 1, Controller) end end, [link, @@ -790,21 +790,22 @@ send_start( #params{ socket = Socket, block_crypto = BlockCrypto, - iv = IV, + iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, AAD) -> %% KeyLen = byte_size(Key), Zeros = binary:copy(<<0>>, KeyLen), + IVBin = <>, {Ciphertext, CipherTag} = - crypto:block_encrypt(BlockCrypto, Key, IV, {AAD, Zeros, TagLen}), + crypto:block_encrypt(BlockCrypto, Key, IVBin, {AAD, Zeros, TagLen}), ok = gen_tcp:send(Socket, [Ciphertext, CipherTag]). recv_start( #params{ socket = Socket, block_crypto = BlockCrypto, - iv = IV, + iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, AAD) -> %% @@ -812,10 +813,11 @@ recv_start( KeyLen = byte_size(Key), PacketLen = KeyLen, <> = Packet, + IVBin = <>, Zeros = binary:copy(<<0>>, KeyLen), case crypto:block_decrypt( - BlockCrypto, Key, IV, {AAD, Ciphertext, CipherTag}) + BlockCrypto, Key, IVBin, {AAD, Ciphertext, CipherTag}) of Zeros -> ok; @@ -830,7 +832,7 @@ init_params(Socket) -> Protocol = ?PROTOCOL, HashAlgorithm = ?HASH_ALGORITHM, BlockCrypto = ?BLOCK_CRYPTO, - IVLen = ?IV_LEN, + IVSaltLen = ?IV_LEN - 6, KeyLen = ?KEY_LEN, TagLen = ?TAG_LEN, RekeyInterval = ?REKEY_INTERVAL, @@ -843,14 +845,15 @@ init_params(Socket) -> (byte_size(HashAlgorithmBin)), HashAlgorithmBin/binary, (byte_size(BlockCryptoBin)), BlockCryptoBin/binary, KeyLen, TagLen>>, - <> = - Data = crypto:strong_rand_bytes(IVLen + KeyLen + KeyLen), + <> = + Data = crypto:strong_rand_bytes(KeyLen + KeyLen + IVSaltLen + 6), {#params{ socket = Socket, protocol = Protocol, hash_algorithm = HashAlgorithm, block_crypto = BlockCrypto, - iv = IV, + iv = {IVSalt, IVNo}, key = {KeySalt, OtherSalt}, tag_len = TagLen, rekey_interval = RekeyInterval}, Header, Data}. @@ -874,12 +877,12 @@ init_params(Socket, Msg) -> BlockCryptoBinLen, BlockCryptoBin:BlockCryptoBinLen/binary, Rest_2/binary>> -> - IVLen = ?IV_LEN, KeyLen = ?KEY_LEN, + IVSaltLen = ?IV_LEN - 6, KeyLen = ?KEY_LEN, TagLen = ?TAG_LEN, RekeyInterval = ?REKEY_INTERVAL, <> = Rest_2, + IVSalt:IVSaltLen/binary, IVNo:48>> = Rest_2, HeaderLen = byte_size(Msg) - (byte_size(Rest_2) - 2), <> = Msg, {#params{ @@ -887,7 +890,7 @@ init_params(Socket, Msg) -> protocol = Protocol, hash_algorithm = HashAlgorithm, block_crypto = BlockCrypto, - iv = IV, + iv = {IVSalt, IVNo}, key = {KeySalt, OtherSalt}, tag_len = TagLen, rekey_interval = RekeyInterval}, @@ -1236,20 +1239,20 @@ deliver_data(DistHandle, Front, Size, Rear, Bin) -> encrypt_and_send_chunk( #params{ socket = Socket, rekey_interval = Seq, - key = Key, iv = IV, hash_algorithm = HashAlgorithm} = Params, + key = Key, iv = {IVSalt, _}, hash_algorithm = HashAlgorithm} = Params, Seq, Cleartext) -> %% KeyLen = byte_size(Key), - IVLen = byte_size(IV), - Chunk = <> = - crypto:strong_rand_bytes(IVLen + KeyLen), + IVSaltLen = byte_size(IVSalt), + Chunk = <> = + crypto:strong_rand_bytes(KeyLen + IVSaltLen + 6), case gen_tcp:send( Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, Chunk])) of ok -> Key_1 = hash_key(HashAlgorithm, Key, KeySalt), - Params_1 = Params#params{key = Key_1, iv = IV_1}, + Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}}, Result = gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), {Params_1, 1, Result}; @@ -1263,19 +1266,20 @@ encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) -> encrypt_chunk( #params{ block_crypto = BlockCrypto, - iv = IV, key = Key, tag_len = TagLen}, Seq, Cleartext) -> + iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, Seq, Cleartext) -> %% ChunkLen = iolist_size(Cleartext) + TagLen, AAD = <>, + IVBin = <>, {Ciphertext, CipherTag} = - crypto:block_encrypt(BlockCrypto, Key, IV, {AAD, Cleartext, TagLen}), + crypto:block_encrypt(BlockCrypto, Key, IVBin, {AAD, Cleartext, TagLen}), Chunk = [Ciphertext,CipherTag], Chunk. decrypt_chunk( #params{ block_crypto = BlockCrypto, - iv = IV, key = Key, tag_len = TagLen} = Params, Seq, Chunk) -> + iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) -> %% ChunkLen = byte_size(Chunk), if @@ -1283,12 +1287,13 @@ decrypt_chunk( error; true -> AAD = <>, + IVBin = <>, CiphertextLen = ChunkLen - TagLen, case Chunk of <> -> block_decrypt( - Params, Seq, BlockCrypto, Key, IV, + Params, Seq, BlockCrypto, Key, IVBin, {AAD, Ciphertext, CipherTag}); _ -> error @@ -1302,19 +1307,20 @@ block_decrypt( case crypto:block_decrypt(CipherName, Key, IV, Data) of <> -> KeyLen = byte_size(Key), - IVLen = byte_size(IV), + IVSaltLen = byte_size(IV) - 6, case Rest of - <> -> + <> -> Key_1 = hash_key( Params#params.hash_algorithm, Key, KeySalt), - Params#params{iv = IV_1, key = Key_1}; + Params#params{iv = {IVSalt, IVNo}, key = Key_1}; _ -> error end; Chunk when is_binary(Chunk) -> case Seq of RekeyInterval -> + %% This was one chunk too many without rekeying error; _ -> Chunk -- cgit v1.2.3 From cd685023c1ae84b3361979851d725e0cc1c4bdb0 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Mon, 15 Apr 2019 17:30:39 +0200 Subject: Implement some kind of PEKE to get forward secrecy --- lib/ssl/test/inet_crypto_dist.erl | 375 +++++++++++++++++++------------------- 1 file changed, 190 insertions(+), 185 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 20819015d1..6d8a8b7d09 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -29,14 +29,6 @@ -define(DRIVER, inet_tcp). -define(FAMILY, inet). --define(PROTOCOL, inet_crypto_dist_v1). --define(HASH_ALGORITHM, sha256). --define(BLOCK_CRYPTO, aes_gcm). --define(IV_LEN, 12). --define(KEY_LEN, 16). --define(TAG_LEN, 16). --define(REKEY_INTERVAL, 32768). - -export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). @@ -54,6 +46,33 @@ -include_lib("kernel/include/dist.hrl"). -include_lib("kernel/include/dist_util.hrl"). +%% ------------------------------------------------------------------------- + +-record(params, + {socket, + dist_handle, + public_key_type, + public_key_params, + hmac_algorithm, + aead_cipher, + iv, + key, + tag_len, + rekey_interval + }). + +current_params(Socket) -> + #params{ + socket = Socket, + public_key_type = ecdh, + public_key_params = brainpoolP384t1, + hmac_algorithm = sha256, + aead_cipher = aes_gcm, + iv = 12, + key = 16, + tag_len = 16, + rekey_interval = 32768}. + %% ------------------------------------------------------------------------- %% Erlang distribution plugin structure explained to myself %% ------- @@ -68,7 +87,7 @@ %% is not one or two processes, but one port - a gen_tcp socket %% %% When the VM is started with the argument "-proto_dist inet_crypto" -%% net_kernel registers the module inet_crypto_dist as distribution +%% net_kernel registers the module inet_crypto_dist acli,oams distribution %% module. net_kernel calls listen/1 to create a listen socket %% and then accept/1 with the listen socket as argument to spawn %% the Acceptor process, which is linked to net_kernel. Apparently @@ -617,10 +636,13 @@ nodelay() -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% XXX Missing to "productified": -%%% * Cryptoanalysis by experts, e.g Forward Secrecy is missing, right? +%%% * Cryptoanalysis by experts, this is crypto amateur work. %%% * Is it useful? %%% * An application to belong to (kernel) %%% * Restart and/or code reload policy (not needed in kernel) +%%% * Fitting into the epmd/Erlang distro protocol version framework +%%% (something needs to be created for multiple protocols, epmd, +%%% multiple address families, etc) %% Debug client and server @@ -693,17 +715,6 @@ reply({Ref, Pid}, Msg) -> %% ------------------------------------------------------------------------- --record(params, - {protocol, % Encryption protocol tag - socket, - dist_handle, - hash_algorithm, - block_crypto, - rekey_interval, - iv, - key, - tag_len}). - -define(TCP_ACTIVE, 16). -define(CHUNK_SIZE, (65536 - 512)). @@ -727,20 +738,34 @@ reply({Ref, Pid}, Msg) -> %% and waits for the other end's start message. So if the send %% blocks we have a deadlock. %% -%% The init message is unencrypted and contains the block cipher and hash -%% algorithms the sender will use, the IV and a key salt. Both sides' -%% key salt is used with the mutual secret as input to the hash algorithm -%% to create different encryption/decryption keys for both directions. +%% The init + start sequence tries to implement Password Encrypted +%% Key Exchange using an ephemeral public/private keypair and the +%% shared secret (the Cookie) to create session encryption keys +%% that can not be re-created if the shared secret is compromized, +%% which should create forward secrecy. +%% +%% All exchanged messages uses {packet, 2} i.e 16 bit size header. +%% +%% The init message contains a random number and encrypted: a public key +%% and two random numbers. The encryption is done with Key and IV hashed +%% from the unencrypted random number and the shared secret. +%% +%% The exchanged epemeral public/private keypairs are used +%% to create a shared key that is hashed with one of the encrypted +%% random numbers from each side to create Key and IV for the session. %% -%% The start message is the first encrypted message and contains just -%% encrypted zeros the width of the key, with the header of the init -%% message as AAD data. Successfully decrypting this message -%% verifies that we have an encrypted channel. +%% The start message contains the two encrypted random numbers +%% this time encrypted with the session keys for verification +%% by the other side, plus the rekey interval. The rekey interval +%% is just there to get an early check for if the other side's +%% maximum rekey interal is acceptable, it is just an embryo +%% of some better check. Any side may rekey earlier but if the +%% rekey interval is exceeded the connection fails. %% %% Subsequent encrypted messages has the sequence number and the length -%% of the message as AAD data. These messages has got a message type -%% differentiating data from ticks. Ticks have a random size in an -%% attempt to make them less obvious to spot. +%% of the message as AAD data, and an incrementing IV. These messages +%% has got a message type that differentes data from ticks and rekeys. +%% Ticks have a random size in an attempt to make them less obvious to spot. %% %% The only reaction to errors is to crash noisily wich will bring %% down the connection and hopefully produce something useful @@ -748,163 +773,140 @@ reply({Ref, Pid}, Msg) -> %% ------------------------------------------------------------------------- init(Socket, Secret) -> - {SendParams, Header, Data} = init_params(Socket), - ok = gen_tcp:send(Socket, [Header, Data]), - init( - SendParams, Secret, - [<<(byte_size(Header) + byte_size(Data)):32>>, Header]). + Params = current_params(Socket), + {PrivKey, R2, R3, Msg} = init_msg(Params, Secret), + ok = gen_tcp:send(Socket, Msg), + init(Params, Secret, PrivKey, R2, R3). init( - #params{ - socket = Socket, - hash_algorithm = SendHashAlgorithm, - key = {SendKeySalt, SendOtherSalt}} = SendParams, Secret, SendAAD) -> + #params{socket = Socket, iv = IVLen} = Params, Secret, PrivKey, R2, R3) -> %% {ok, InitMsg} = gen_tcp:recv(Socket, 0), + IVSaltLen = IVLen - 6, try - init_params(Socket, InitMsg) - of - {#params{ - hash_algorithm = RecvHashAlgorithm, - key = {RecvKeySalt, RecvOtherSalt}} = RecvParams, - RecvHeader} -> - RecvAAD = [<<(byte_size(InitMsg)):32>>, RecvHeader], - SendKey = - hash_key( - SendHashAlgorithm, SendKeySalt, [RecvOtherSalt, Secret]), - RecvKey = - hash_key( - RecvHashAlgorithm, RecvKeySalt, [SendOtherSalt, Secret]), - SendParams_1 = SendParams#params{key = SendKey}, - RecvParams_1 = RecvParams#params{key = RecvKey}, - send_start(SendParams_1, SendAAD), - recv_start(RecvParams_1, RecvAAD), - {SendParams_1, RecvParams_1} + case init_msg(Params, Secret, PrivKey, R2, R3, InitMsg) of + {#params{iv = <>} = + SendParams, + RecvParams, SendStartMsg} -> + ok = gen_tcp:send(Socket, SendStartMsg), + {ok, RecvStartMsg} = gen_tcp:recv(Socket, 0), + #params{ + iv = <>} = + RecvParams_1 = + start_msg(RecvParams, R2, R3, RecvStartMsg), + {SendParams#params{iv = {IV2ASalt, IV2ANo}}, + RecvParams_1#params{iv = {IV2BSalt, IV2BNo}}} + end catch - error : Reason -> - _ = trace(Reason), + error : Reason : Stacktrace-> + _ = trace({Reason, Stacktrace}), exit(connection_closed) end. -send_start( + + +init_msg( #params{ - socket = Socket, - block_crypto = BlockCrypto, - iv = {IVSalt, IVNo}, - key = Key, - tag_len = TagLen}, AAD) -> + public_key_type = PublicKeyType, + public_key_params = PublicKeyParams, + hmac_algorithm = HmacAlgo, + aead_cipher = AeadCipher, + key = KeyLen, + iv = IVLen, + tag_len = TagLen}, Secret) -> %% - KeyLen = byte_size(Key), - Zeros = binary:copy(<<0>>, KeyLen), - IVBin = <>, - {Ciphertext, CipherTag} = - crypto:block_encrypt(BlockCrypto, Key, IVBin, {AAD, Zeros, TagLen}), - ok = gen_tcp:send(Socket, [Ciphertext, CipherTag]). - -recv_start( + {PubKeyA, PrivKey} = crypto:generate_key(PublicKeyType, PublicKeyParams), + RLen = KeyLen + IVLen, + <> = + crypto:strong_rand_bytes(3 * RLen), + {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen), + Plaintext = [R2A, R3A, PubKeyA], + MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext), + AAD = [<>, R1A], + {Ciphertext, Tag} = + crypto:block_encrypt(AeadCipher, Key1A, IV1A, {AAD, Plaintext, TagLen}), + Msg = [R1A, Tag, Ciphertext], + {PrivKey, R2A, R3A, Msg}. +%% +init_msg( #params{ - socket = Socket, - block_crypto = BlockCrypto, - iv = {IVSalt, IVNo}, - key = Key, - tag_len = TagLen}, AAD) -> + public_key_type = PublicKeyType, + public_key_params = PublicKeyParams, + hmac_algorithm = HmacAlgo, + aead_cipher = AeadCipher, + key = KeyLen, + iv = IVLen, + tag_len = TagLen, + rekey_interval = RekeyInterval} = Params, Secret, PrivKey, R2A, R3A, Msg) -> %% - {ok, Packet} = gen_tcp:recv(Socket, 0), - KeyLen = byte_size(Key), - PacketLen = KeyLen, - <> = Packet, - IVBin = <>, - Zeros = binary:copy(<<0>>, KeyLen), - case - crypto:block_decrypt( - BlockCrypto, Key, IVBin, {AAD, Ciphertext, CipherTag}) - of - Zeros -> - ok; - _ -> - _ = trace(decrypt_error), - exit(connection_closed) + RLen = KeyLen + IVLen, + case Msg of + <> -> + {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen), + MsgLen = byte_size(Msg), + AAD = [<>, R1B], + case + crypto:block_decrypt( + AeadCipher, Key1B, IV1B, {AAD, Ciphertext, Tag}) + of + <> -> + SharedSecret = + crypto:compute_key( + PublicKeyType, PubKeyB, PrivKey, PublicKeyParams), + %% + {Key2A, IV2A} = + hmac_key_iv( + HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen), + SendParams = Params#params{key = Key2A, iv = IV2A}, + %% + StartCleartext = [R2B, R3B, <>], + StartMsgLen = TagLen + iolist_size(StartCleartext), + StartAAD = <>, + {StartCiphertext, StartTag} = + crypto:block_encrypt( + AeadCipher, Key2A, IV2A, + {StartAAD, StartCleartext, TagLen}), + StartMsg = [StartTag, StartCiphertext], + %% + {Key2B, IV2B} = + hmac_key_iv( + HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen), + RecvParams = Params#params{key = Key2B, iv = IV2B}, + %% + {SendParams, RecvParams, StartMsg} + end end. - - -init_params(Socket) -> - Protocol = ?PROTOCOL, - HashAlgorithm = ?HASH_ALGORITHM, - BlockCrypto = ?BLOCK_CRYPTO, - IVSaltLen = ?IV_LEN - 6, - KeyLen = ?KEY_LEN, - TagLen = ?TAG_LEN, - RekeyInterval = ?REKEY_INTERVAL, +start_msg( + #params{ + aead_cipher = AeadCipher, + key = Key2B, + iv = IV2B, + tag_len = TagLen, + rekey_interval = RekeyIntervalA} = RecvParams, R2A, R3A, Msg) -> %% - ProtocolBin = atom_to_binary(Protocol, utf8), - HashAlgorithmBin = atom_to_binary(HashAlgorithm, utf8), - BlockCryptoBin = atom_to_binary(BlockCrypto, utf8), - Header = - <<(byte_size(ProtocolBin)), ProtocolBin/binary, - (byte_size(HashAlgorithmBin)), HashAlgorithmBin/binary, - (byte_size(BlockCryptoBin)), BlockCryptoBin/binary, - KeyLen, TagLen>>, - <> = - Data = crypto:strong_rand_bytes(KeyLen + KeyLen + IVSaltLen + 6), - {#params{ - socket = Socket, - protocol = Protocol, - hash_algorithm = HashAlgorithm, - block_crypto = BlockCrypto, - iv = {IVSalt, IVNo}, - key = {KeySalt, OtherSalt}, - tag_len = TagLen, - rekey_interval = RekeyInterval}, Header, Data}. - -init_params(Socket, Msg) -> - Protocol = ?PROTOCOL, - ProtocolBin = atom_to_binary(Protocol, utf8), - ProtocolBinLen = byte_size(ProtocolBin), case Msg of - <> -> - HashAlgorithm = ?HASH_ALGORITHM, - BlockCrypto = ?BLOCK_CRYPTO, - HashAlgorithmBin = atom_to_binary(HashAlgorithm, utf8), - HashAlgorithmBinLen = byte_size(HashAlgorithmBin), - BlockCryptoBin = atom_to_binary(BlockCrypto, utf8), - BlockCryptoBinLen = byte_size(BlockCryptoBin), - case Rest_1 of - <> -> - IVSaltLen = ?IV_LEN - 6, KeyLen = ?KEY_LEN, - TagLen = ?TAG_LEN, RekeyInterval = ?REKEY_INTERVAL, - <> = Rest_2, - HeaderLen = byte_size(Msg) - (byte_size(Rest_2) - 2), - <> = Msg, - {#params{ - socket = Socket, - protocol = Protocol, - hash_algorithm = HashAlgorithm, - block_crypto = BlockCrypto, - iv = {IVSalt, IVNo}, - key = {KeySalt, OtherSalt}, - tag_len = TagLen, - rekey_interval = RekeyInterval}, - Header} + <> -> + KeyLen = byte_size(Key2B), + IVLen = byte_size(IV2B), + RLen = KeyLen + IVLen, + MsgLen = byte_size(Msg), + AAD = <>, + case + crypto:block_decrypt( + AeadCipher, Key2B, IV2B, {AAD, Ciphertext, Tag}) + of + <> + when RekeyIntervalA =< (RekeyIntervalB bsl 2), + RekeyIntervalB =< (RekeyIntervalA bsl 2) -> + RecvParams#params{rekey_interval = RekeyIntervalB} end end. - - -hash_key(HashAlgorithm, KeySalt, KeyData) -> - KeyLen = byte_size(KeySalt), - <> = - crypto:hash(HashAlgorithm, [KeySalt, KeyData]), - Key. +hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) -> + <> = + crypto:hmac(HmacAlgo, MacKey, Data, KeyLen + IVLen), + {Key, IV}. %% ------------------------------------------------------------------------- %% net_kernel distribution handshake in progress @@ -1239,19 +1241,19 @@ deliver_data(DistHandle, Front, Size, Rear, Bin) -> encrypt_and_send_chunk( #params{ socket = Socket, rekey_interval = Seq, - key = Key, iv = {IVSalt, _}, hash_algorithm = HashAlgorithm} = Params, + key = Key, iv = {IVSalt, _}, hmac_algorithm = HmacAlgo} = Params, Seq, Cleartext) -> %% KeyLen = byte_size(Key), IVSaltLen = byte_size(IVSalt), - Chunk = <> = - crypto:strong_rand_bytes(KeyLen + IVSaltLen + 6), + R = crypto:strong_rand_bytes(KeyLen + IVSaltLen + 6), case gen_tcp:send( - Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, Chunk])) + Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, R])) of ok -> - Key_1 = hash_key(HashAlgorithm, Key, KeySalt), + {Key_1, <>} = + hmac_key_iv(HmacAlgo, Key, R, KeyLen, IVSaltLen + 6), Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}}, Result = gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), @@ -1265,20 +1267,20 @@ encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) -> encrypt_chunk( #params{ - block_crypto = BlockCrypto, + aead_cipher = AeadCipher, iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, Seq, Cleartext) -> %% ChunkLen = iolist_size(Cleartext) + TagLen, AAD = <>, IVBin = <>, {Ciphertext, CipherTag} = - crypto:block_encrypt(BlockCrypto, Key, IVBin, {AAD, Cleartext, TagLen}), + crypto:block_encrypt(AeadCipher, Key, IVBin, {AAD, Cleartext, TagLen}), Chunk = [Ciphertext,CipherTag], Chunk. decrypt_chunk( #params{ - block_crypto = BlockCrypto, + aead_cipher = AeadCipher, iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) -> %% ChunkLen = byte_size(Chunk), @@ -1293,7 +1295,7 @@ decrypt_chunk( <> -> block_decrypt( - Params, Seq, BlockCrypto, Key, IVBin, + Params, Seq, AeadCipher, Key, IVBin, {AAD, Ciphertext, CipherTag}); _ -> error @@ -1302,17 +1304,20 @@ decrypt_chunk( block_decrypt( #params{rekey_interval = RekeyInterval} = Params, - Seq, CipherName, Key, IV, Data) -> + Seq, AeadCipher, Key, IV, Data) -> %% - case crypto:block_decrypt(CipherName, Key, IV, Data) of + case crypto:block_decrypt(AeadCipher, Key, IV, Data) of <> -> KeyLen = byte_size(Key), - IVSaltLen = byte_size(IV) - 6, + IVLen = byte_size(IV), + IVSaltLen = IVLen - 6, + RLen = KeyLen + IVLen, case Rest of - <> -> - Key_1 = - hash_key( - Params#params.hash_algorithm, Key, KeySalt), + <> -> + {Key_1, <>} = + hmac_key_iv( + Params#params.hmac_algorithm, + Key, R, KeyLen, IVLen), Params#params{iv = {IVSalt, IVNo}, key = Key_1}; _ -> error -- cgit v1.2.3 From 03961a5520b43e7c2a70f0e2089302dd78fd6d9c Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 18 Apr 2019 14:39:56 +0200 Subject: Keep the keypair for the node's lifetime --- lib/ssl/test/inet_crypto_dist.erl | 125 ++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 39 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 6d8a8b7d09..4574e1e6c3 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -51,27 +51,69 @@ -record(params, {socket, dist_handle, - public_key_type, - public_key_params, - hmac_algorithm, - aead_cipher, - iv, - key, - tag_len, - rekey_interval + hmac_algorithm = sha256, + aead_cipher = aes_gcm, + iv = 12, + key = 16, + tag_len = 16, + rekey_interval = 32768 }). -current_params(Socket) -> - #params{ - socket = Socket, - public_key_type = ecdh, - public_key_params = brainpoolP384t1, - hmac_algorithm = sha256, - aead_cipher = aes_gcm, - iv = 12, - key = 16, - tag_len = 16, - rekey_interval = 32768}. +-record(public_key_pair, + {type = ecdh, + params = brainpoolP384t1, + public, + private}). + +params(Socket) -> + #params{socket = Socket}. + + +%% ------------------------------------------------------------------------- +%% Keep the same public/private key pair during the node's lifetime +%% in the process state of a process linked to the acceptor process. +%% Create it the first time it is needed. +%% + +start_public_key_pair() -> + spawn_link( + fun () -> + register(?MODULE, self()), + public_key_pair(#public_key_pair{}) + end). + +public_key_pair(PKP) -> + receive + {Pid, Tag, public_key_pair} -> + case PKP of + #public_key_pair{ + type = Type, + params = Params, + public = undefined} -> + %% + {Public, Private} = crypto:generate_key(Type, Params), + PKP_1 = + PKP#public_key_pair{ + public = Public, private = Private}, + Pid ! {Tag, PKP_1}, + public_key_pair(PKP_1); + #public_key_pair{} -> + Pid ! {Tag, PKP}, + public_key_pair(PKP) + end + end. + +public_key_pair() -> + Pid = whereis(?MODULE), + Ref = erlang:monitor(process, Pid), + Pid ! {self(), Ref, public_key_pair}, + receive + {Ref, PKP} -> + erlang:demonitor(Ref, [flush]), + PKP; + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + end. %% ------------------------------------------------------------------------- %% Erlang distribution plugin structure explained to myself @@ -227,6 +269,7 @@ gen_accept(Listen, Driver) -> monitor_dist_proc( spawn_opt( fun () -> + start_public_key_pair(), accept_loop(Listen, Driver, NetKernel) end, [link, {priority, max}])). @@ -642,7 +685,7 @@ nodelay() -> %%% * Restart and/or code reload policy (not needed in kernel) %%% * Fitting into the epmd/Erlang distro protocol version framework %%% (something needs to be created for multiple protocols, epmd, -%%% multiple address families, etc) +%%% multiple address families, fallback to previous version, etc) %% Debug client and server @@ -739,19 +782,21 @@ reply({Ref, Pid}, Msg) -> %% blocks we have a deadlock. %% %% The init + start sequence tries to implement Password Encrypted -%% Key Exchange using an ephemeral public/private keypair and the +%% Key Exchange using a node public/private keypair and the %% shared secret (the Cookie) to create session encryption keys %% that can not be re-created if the shared secret is compromized, -%% which should create forward secrecy. +%% which should create forward secrecy. You need both nodes' +%% keypairs and the shared secret to decrypt the traffic +%% between the nodes. %% %% All exchanged messages uses {packet, 2} i.e 16 bit size header. %% -%% The init message contains a random number and encrypted: a public key +%% The init message contains a random number and encrypted: the public key %% and two random numbers. The encryption is done with Key and IV hashed %% from the unencrypted random number and the shared secret. %% -%% The exchanged epemeral public/private keypairs are used -%% to create a shared key that is hashed with one of the encrypted +%% The other node's public key is used with the own node's private +%% key to create a shared key that is hashed with one of the encrypted %% random numbers from each side to create Key and IV for the session. %% %% The start message contains the two encrypted random numbers @@ -773,18 +818,19 @@ reply({Ref, Pid}, Msg) -> %% ------------------------------------------------------------------------- init(Socket, Secret) -> - Params = current_params(Socket), - {PrivKey, R2, R3, Msg} = init_msg(Params, Secret), + #public_key_pair{public = PubKey} = PKP = public_key_pair(), + Params = params(Socket), + {R2, R3, Msg} = init_msg(Params, PubKey, Secret), ok = gen_tcp:send(Socket, Msg), - init(Params, Secret, PrivKey, R2, R3). + init_recv(Params, Secret, PKP, R2, R3). -init( - #params{socket = Socket, iv = IVLen} = Params, Secret, PrivKey, R2, R3) -> +init_recv( + #params{socket = Socket, iv = IVLen} = Params, Secret, PKP, R2, R3) -> %% {ok, InitMsg} = gen_tcp:recv(Socket, 0), IVSaltLen = IVLen - 6, try - case init_msg(Params, Secret, PrivKey, R2, R3, InitMsg) of + case init_msg(Params, Secret, PKP, R2, R3, InitMsg) of {#params{iv = <>} = SendParams, RecvParams, SendStartMsg} -> @@ -807,15 +853,12 @@ init( init_msg( #params{ - public_key_type = PublicKeyType, - public_key_params = PublicKeyParams, hmac_algorithm = HmacAlgo, aead_cipher = AeadCipher, key = KeyLen, iv = IVLen, - tag_len = TagLen}, Secret) -> + tag_len = TagLen}, PubKeyA, Secret) -> %% - {PubKeyA, PrivKey} = crypto:generate_key(PublicKeyType, PublicKeyParams), RLen = KeyLen + IVLen, <> = crypto:strong_rand_bytes(3 * RLen), @@ -826,18 +869,22 @@ init_msg( {Ciphertext, Tag} = crypto:block_encrypt(AeadCipher, Key1A, IV1A, {AAD, Plaintext, TagLen}), Msg = [R1A, Tag, Ciphertext], - {PrivKey, R2A, R3A, Msg}. + {R2A, R3A, Msg}. %% init_msg( #params{ - public_key_type = PublicKeyType, - public_key_params = PublicKeyParams, hmac_algorithm = HmacAlgo, aead_cipher = AeadCipher, key = KeyLen, iv = IVLen, tag_len = TagLen, - rekey_interval = RekeyInterval} = Params, Secret, PrivKey, R2A, R3A, Msg) -> + rekey_interval = RekeyInterval} = Params, + Secret, + #public_key_pair{ + type = PublicKeyType, + params = PublicKeyParams, + private = PrivKey}, + R2A, R3A, Msg) -> %% RLen = KeyLen + IVLen, case Msg of -- cgit v1.2.3 From fe26eb291ac275568f82fa8bb1b24f54391bfda6 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 3 May 2019 15:07:39 +0200 Subject: Set socket buffer sizes to avoid handshake deadlock --- lib/ssl/test/inet_crypto_dist.erl | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 4574e1e6c3..443397cabf 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -46,6 +46,9 @@ -include_lib("kernel/include/dist.hrl"). -include_lib("kernel/include/dist_util.hrl"). +-define(PACKET_SIZE, 65536). +-define(BUFFER_SIZE, (?PACKET_SIZE bsl 4)). + %% ------------------------------------------------------------------------- -record(params, @@ -61,7 +64,14 @@ -record(public_key_pair, {type = ecdh, - params = brainpoolP384t1, + %% The curve choice greatly affects setup time, + %% we really want an Edwards curve but that would + %% require a very new openssl version. + %% Twisted brainpool curves (*t1) are faster than + %% non-twisted (*r1), 256 is much faster than 384, + %% and so on... +%%% params = brainpoolP384t1, + params = brainpoolP256t1, public, private}). @@ -208,6 +218,12 @@ public_key_pair() -> %% terminate with reason 'normal'. %% ------------------------------------------------------------------------- +-compile({inline, [socket_options/0]}). +socket_options() -> + [binary, {active, false}, {packet, 2}, {nodelay, true}, + {sndbuf, ?BUFFER_SIZE}, {recbuf, ?BUFFER_SIZE}, + {buffer, ?BUFFER_SIZE}]. + %% ------------------------------------------------------------------------- %% select/1 is called by net_kernel to ask if this distribution protocol %% is willing to handle Node @@ -244,7 +260,7 @@ listen(Name) -> gen_listen(Name, Driver) -> case inet_tcp_dist:gen_listen(Driver, Name) of {ok, {Socket, Address, Creation}} -> - inet:setopts(Socket, [binary, {nodelay, true}]), + inet:setopts(Socket, socket_options()), {ok, {Socket, Address#net_address{protocol = ?DIST_PROTO}, Creation}}; Other -> @@ -431,10 +447,7 @@ do_setup_connect( Node, Type, MyNode, Timer, Driver, NetKernel, Ip, TcpPort, Version) -> dist_util:reset_timer(Timer), - ConnectOpts = - trace( - connect_options( - [binary, {active, false}, {packet, 2}, {nodelay, true}])), + ConnectOpts = trace(connect_options(socket_options())), case Driver:connect(Ip, TcpPort, ConnectOpts) of {ok, Socket} -> DistCtrl = @@ -691,16 +704,14 @@ nodelay() -> %% Debug client and server test_server() -> - {ok, Listen} = gen_tcp:listen(0, [{packet, 2}, {active, false}, binary]), + {ok, Listen} = gen_tcp:listen(0, socket_options()), {ok, Port} = inet:port(Listen), io:format(?MODULE_STRING":test_client(~w).~n", [Port]), {ok, Socket} = gen_tcp:accept(Listen), test(Socket). test_client(Port) -> - {ok, Socket} = - gen_tcp:connect( - localhost, Port, [{packet, 2}, {active, false}, binary]), + {ok, Socket} = gen_tcp:connect(localhost, Port, socket_options()), test(Socket). test(Socket) -> @@ -759,7 +770,7 @@ reply({Ref, Pid}, Msg) -> %% ------------------------------------------------------------------------- -define(TCP_ACTIVE, 16). --define(CHUNK_SIZE, (65536 - 512)). +-define(CHUNK_SIZE, (?PACKET_SIZE - 512)). %% The start chunk starts with zeros, so it seems logical to %% not have a chunk type with value 0 -- cgit v1.2.3 From 22034cd51e28a09610827f9c1ed7a2defec1b1cd Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 3 May 2019 16:26:26 +0200 Subject: Cycle the keypair by time and count --- lib/ssl/test/inet_crypto_dist.erl | 51 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 443397cabf..e25cfee4b9 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -80,37 +80,56 @@ params(Socket) -> %% ------------------------------------------------------------------------- -%% Keep the same public/private key pair during the node's lifetime -%% in the process state of a process linked to the acceptor process. +%% Keep the node's public/private key pair in the process state +%% of a process linked to the acceptor process. %% Create it the first time it is needed. %% +-define(KEYPAIR_LIFETIME, 3600000). +-define(KEYPAIR_LIFECOUNT, 256). + start_public_key_pair() -> spawn_link( fun () -> register(?MODULE, self()), - public_key_pair(#public_key_pair{}) + public_key_pair_loop() end). -public_key_pair(PKP) -> +public_key_pair_loop() -> + public_key_pair_loop(undefined, undefined, undefined). +%% +public_key_pair_loop(_PKP, Timer, 0) -> + case erlang:cancel_timer(Timer) of + false -> + receive + {timeout, Timer, _} -> ok + end; + _RemainingTime -> + ok + end, + public_key_pair_loop(); +public_key_pair_loop(PKP, Timer, Count) -> receive {Pid, Tag, public_key_pair} -> case PKP of - #public_key_pair{ - type = Type, - params = Params, - public = undefined} -> - %% - {Public, Private} = crypto:generate_key(Type, Params), + undefined -> + #public_key_pair{type = Type, params = Params} = + #public_key_pair{}, + {Public, Private} = + crypto:generate_key(Type, Params), PKP_1 = - PKP#public_key_pair{ - public = Public, private = Private}, + #public_key_pair{public = Public, private = Private}, Pid ! {Tag, PKP_1}, - public_key_pair(PKP_1); + public_key_pair_loop( + PKP_1, + erlang:start_timer(?KEYPAIR_LIFETIME, self(), renew), + ?KEYPAIR_LIFECOUNT); #public_key_pair{} -> Pid ! {Tag, PKP}, - public_key_pair(PKP) - end + public_key_pair_loop(PKP, Timer, Count - 1) + end; + {timeout, Timer, renew} when is_reference(Timer) -> + public_key_pair_loop() end. public_key_pair() -> @@ -772,8 +791,6 @@ reply({Ref, Pid}, Msg) -> -define(TCP_ACTIVE, 16). -define(CHUNK_SIZE, (?PACKET_SIZE - 512)). -%% The start chunk starts with zeros, so it seems logical to -%% not have a chunk type with value 0 -define(HANDSHAKE_CHUNK, 1). -define(DATA_CHUNK, 2). -define(TICK_CHUNK, 3). -- cgit v1.2.3 From 53c821c51f62e8ad249f37f8f66f73f19ba71c17 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Fri, 3 May 2019 16:59:26 +0200 Subject: Use shared secret to rekey --- lib/ssl/test/inet_crypto_dist.erl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index e25cfee4b9..9261a1aa3b 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -56,6 +56,7 @@ dist_handle, hmac_algorithm = sha256, aead_cipher = aes_gcm, + shared_secret, iv = 12, key = 16, tag_len = 16, @@ -932,7 +933,10 @@ init_msg( {Key2A, IV2A} = hmac_key_iv( HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen), - SendParams = Params#params{key = Key2A, iv = IV2A}, + SendParams = + Params#params{ + shared_secret = SharedSecret, + key = Key2A, iv = IV2A}, %% StartCleartext = [R2B, R3B, <>], StartMsgLen = TagLen + iolist_size(StartCleartext), @@ -946,7 +950,10 @@ init_msg( {Key2B, IV2B} = hmac_key_iv( HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen), - RecvParams = Params#params{key = Key2B, iv = IV2B}, + RecvParams = + Params#params{ + shared_secret = SharedSecret, + key = Key2B, iv = IV2B}, %% {SendParams, RecvParams, StartMsg} end @@ -1316,6 +1323,7 @@ deliver_data(DistHandle, Front, Size, Rear, Bin) -> encrypt_and_send_chunk( #params{ socket = Socket, rekey_interval = Seq, + shared_secret = SharedSecret, key = Key, iv = {IVSalt, _}, hmac_algorithm = HmacAlgo} = Params, Seq, Cleartext) -> %% @@ -1328,7 +1336,8 @@ encrypt_and_send_chunk( of ok -> {Key_1, <>} = - hmac_key_iv(HmacAlgo, Key, R, KeyLen, IVSaltLen + 6), + hmac_key_iv( + HmacAlgo, SharedSecret, R, KeyLen, IVSaltLen + 6), Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}}, Result = gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), @@ -1378,7 +1387,9 @@ decrypt_chunk( end. block_decrypt( - #params{rekey_interval = RekeyInterval} = Params, + #params{ + shared_secret = SharedSecret, + rekey_interval = RekeyInterval} = Params, Seq, AeadCipher, Key, IV, Data) -> %% case crypto:block_decrypt(AeadCipher, Key, IV, Data) of @@ -1392,7 +1403,7 @@ block_decrypt( {Key_1, <>} = hmac_key_iv( Params#params.hmac_algorithm, - Key, R, KeyLen, IVLen), + SharedSecret, R, KeyLen, IVLen), Params#params{iv = {IVSalt, IVNo}, key = Key_1}; _ -> error -- cgit v1.2.3 From acaab6a1dc9aee21f43574a787a9242b718847d6 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Sun, 5 May 2019 15:01:14 +0200 Subject: Rekey also the shared secret --- lib/ssl/test/inet_crypto_dist.erl | 242 +++++++++++++++++++++++--------------- 1 file changed, 145 insertions(+), 97 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl index 9261a1aa3b..63c19d9438 100644 --- a/lib/ssl/test/inet_crypto_dist.erl +++ b/lib/ssl/test/inet_crypto_dist.erl @@ -56,14 +56,18 @@ dist_handle, hmac_algorithm = sha256, aead_cipher = aes_gcm, - shared_secret, + rekey_key, iv = 12, key = 16, tag_len = 16, - rekey_interval = 32768 + rekey_interval = 262144 }). --record(public_key_pair, +params(Socket) -> + #params{socket = Socket}. + + +-record(key_pair, {type = ecdh, %% The curve choice greatly affects setup time, %% we really want an Edwards curve but that would @@ -76,30 +80,67 @@ public, private}). -params(Socket) -> - #params{socket = Socket}. +-define(KEY_PAIR_LIFE_TIME, 3600000). % 1 hour +-define(KEY_PAIR_LIFE_COUNT, 256). % Number of connection setups %% ------------------------------------------------------------------------- %% Keep the node's public/private key pair in the process state -%% of a process linked to the acceptor process. -%% Create it the first time it is needed. +%% of a key pair server linked to the acceptor process. +%% Create the key pair the first time it is needed +%% so crypto gets time to start first. %% --define(KEYPAIR_LIFETIME, 3600000). --define(KEYPAIR_LIFECOUNT, 256). - -start_public_key_pair() -> - spawn_link( - fun () -> - register(?MODULE, self()), - public_key_pair_loop() - end). +start_key_pair_server() -> + monitor_dist_proc( + spawn_link( + fun () -> + register(?MODULE, self()), + key_pair_server() + end)). -public_key_pair_loop() -> - public_key_pair_loop(undefined, undefined, undefined). +key_pair_server() -> + key_pair_server(undefined, undefined, undefined). %% -public_key_pair_loop(_PKP, Timer, 0) -> +key_pair_server(KeyPair) -> + key_pair_server( + KeyPair, + erlang:start_timer(?KEY_PAIR_LIFE_TIME, self(), discard), + ?KEY_PAIR_LIFE_COUNT). +%% +key_pair_server(_KeyPair, Timer, 0) -> + cancel_timer(Timer), + key_pair_server(); +key_pair_server(KeyPair, Timer, Count) -> + receive + {Pid, Tag, get_key_pair} -> + case KeyPair of + undefined -> + KeyPair_1 = generate_key_pair(), + Pid ! {Tag, KeyPair_1}, + key_pair_server(KeyPair_1); + #key_pair{} -> + Pid ! {Tag, KeyPair}, + key_pair_server(KeyPair, Timer, Count - 1) + end; + {Pid, Tag, get_new_key_pair} -> + cancel_timer(Timer), + KeyPair_1 = generate_key_pair(), + Pid ! {Tag, KeyPair_1}, + key_pair_server(KeyPair_1); + {timeout, Timer, discard} when is_reference(Timer) -> + key_pair_server() + end. + +generate_key_pair() -> + #key_pair{type = Type, params = Params} = #key_pair{}, + {Public, Private} = + crypto:generate_key(Type, Params), + #key_pair{public = Public, private = Private}. + +cancel_timer(undefined) -> + ok; +cancel_timer(Timer) -> case erlang:cancel_timer(Timer) of false -> receive @@ -107,44 +148,34 @@ public_key_pair_loop(_PKP, Timer, 0) -> end; _RemainingTime -> ok - end, - public_key_pair_loop(); -public_key_pair_loop(PKP, Timer, Count) -> - receive - {Pid, Tag, public_key_pair} -> - case PKP of - undefined -> - #public_key_pair{type = Type, params = Params} = - #public_key_pair{}, - {Public, Private} = - crypto:generate_key(Type, Params), - PKP_1 = - #public_key_pair{public = Public, private = Private}, - Pid ! {Tag, PKP_1}, - public_key_pair_loop( - PKP_1, - erlang:start_timer(?KEYPAIR_LIFETIME, self(), renew), - ?KEYPAIR_LIFECOUNT); - #public_key_pair{} -> - Pid ! {Tag, PKP}, - public_key_pair_loop(PKP, Timer, Count - 1) - end; - {timeout, Timer, renew} when is_reference(Timer) -> - public_key_pair_loop() end. -public_key_pair() -> +get_key_pair() -> + call_key_pair_server(get_key_pair). + +get_new_key_pair() -> + call_key_pair_server(get_new_key_pair). + +call_key_pair_server(Request) -> Pid = whereis(?MODULE), Ref = erlang:monitor(process, Pid), - Pid ! {self(), Ref, public_key_pair}, + Pid ! {self(), Ref, Request}, receive - {Ref, PKP} -> + {Ref, Reply} -> erlang:demonitor(Ref, [flush]), - PKP; + Reply; {'DOWN', Ref, process, Pid, Reason} -> error(Reason) end. +compute_shared_secret( + #key_pair{ + type = PublicKeyType, + params = PublicKeyParams, + private = PrivKey}, PubKey) -> + %% + crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams). + %% ------------------------------------------------------------------------- %% Erlang distribution plugin structure explained to myself %% ------- @@ -305,7 +336,7 @@ gen_accept(Listen, Driver) -> monitor_dist_proc( spawn_opt( fun () -> - start_public_key_pair(), + start_key_pair_server(), accept_loop(Listen, Driver, NetKernel) end, [link, {priority, max}])). @@ -713,7 +744,8 @@ nodelay() -> %%% XXX Missing to "productified": %%% * Cryptoanalysis by experts, this is crypto amateur work. -%%% * Is it useful? +%%% * Is it useful over inet_tls_dist; i.e to not have to bother +%%% with certificates but instead manage a secret cluster cookie? %%% * An application to belong to (kernel) %%% * Restart and/or code reload policy (not needed in kernel) %%% * Fitting into the epmd/Erlang distro protocol version framework @@ -811,11 +843,11 @@ reply({Ref, Pid}, Msg) -> %% blocks we have a deadlock. %% %% The init + start sequence tries to implement Password Encrypted -%% Key Exchange using a node public/private keypair and the +%% Key Exchange using a node public/private key pair and the %% shared secret (the Cookie) to create session encryption keys %% that can not be re-created if the shared secret is compromized, %% which should create forward secrecy. You need both nodes' -%% keypairs and the shared secret to decrypt the traffic +%% key pairs and the shared secret to decrypt the traffic %% between the nodes. %% %% All exchanged messages uses {packet, 2} i.e 16 bit size header. @@ -838,28 +870,39 @@ reply({Ref, Pid}, Msg) -> %% %% Subsequent encrypted messages has the sequence number and the length %% of the message as AAD data, and an incrementing IV. These messages -%% has got a message type that differentes data from ticks and rekeys. +%% has got a message type that differentiates data from ticks and rekeys. %% Ticks have a random size in an attempt to make them less obvious to spot. %% -%% The only reaction to errors is to crash noisily wich will bring +%% Rekeying is done by the sender that creates a new key pair and +%% a new shared secret from the other end's public key and with +%% this and the current key and iv hashes a new key and iv. +%% The new public key is sent to the other end that uses it +%% and its old private key to create the same new shared +%% secret and from that a new key and iv. +%% So the receiver keeps its private key, and the sender keeps +%% the receivers public key for the connection's life time. +%% While the sender generates a new key pair at every rekey, +%% which changes the shared secret at every rekey. +%% +%% The only reaction to errors is to crash noisily (?) wich will bring %% down the connection and hopefully produce something useful %% in the local log, but all the other end sees is a closed connection. %% ------------------------------------------------------------------------- init(Socket, Secret) -> - #public_key_pair{public = PubKey} = PKP = public_key_pair(), + #key_pair{public = PubKey} = KeyPair = get_key_pair(), Params = params(Socket), {R2, R3, Msg} = init_msg(Params, PubKey, Secret), ok = gen_tcp:send(Socket, Msg), - init_recv(Params, Secret, PKP, R2, R3). + init_recv(Params, Secret, KeyPair, R2, R3). init_recv( - #params{socket = Socket, iv = IVLen} = Params, Secret, PKP, R2, R3) -> + #params{socket = Socket, iv = IVLen} = Params, Secret, KeyPair, R2, R3) -> %% {ok, InitMsg} = gen_tcp:recv(Socket, 0), IVSaltLen = IVLen - 6, try - case init_msg(Params, Secret, PKP, R2, R3, InitMsg) of + case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of {#params{iv = <>} = SendParams, RecvParams, SendStartMsg} -> @@ -908,12 +951,7 @@ init_msg( iv = IVLen, tag_len = TagLen, rekey_interval = RekeyInterval} = Params, - Secret, - #public_key_pair{ - type = PublicKeyType, - params = PublicKeyParams, - private = PrivKey}, - R2A, R3A, Msg) -> + Secret, KeyPair, R2A, R3A, Msg) -> %% RLen = KeyLen + IVLen, case Msg of @@ -926,16 +964,14 @@ init_msg( AeadCipher, Key1B, IV1B, {AAD, Ciphertext, Tag}) of <> -> - SharedSecret = - crypto:compute_key( - PublicKeyType, PubKeyB, PrivKey, PublicKeyParams), + SharedSecret = compute_shared_secret(KeyPair, PubKeyB), %% {Key2A, IV2A} = hmac_key_iv( HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen), SendParams = Params#params{ - shared_secret = SharedSecret, + rekey_key = PubKeyB, key = Key2A, iv = IV2A}, %% StartCleartext = [R2B, R3B, <>], @@ -952,7 +988,7 @@ init_msg( HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen), RecvParams = Params#params{ - shared_secret = SharedSecret, + rekey_key = KeyPair, key = Key2B, iv = IV2B}, %% {SendParams, RecvParams, StartMsg} @@ -1322,22 +1358,28 @@ deliver_data(DistHandle, Front, Size, Rear, Bin) -> encrypt_and_send_chunk( #params{ - socket = Socket, rekey_interval = Seq, - shared_secret = SharedSecret, - key = Key, iv = {IVSalt, _}, hmac_algorithm = HmacAlgo} = Params, + socket = Socket, + rekey_interval = Seq, + rekey_key = PubKeyB, + key = Key, + iv = {IVSalt, IVNo}, + hmac_algorithm = HmacAlgo} = Params, Seq, Cleartext) -> %% KeyLen = byte_size(Key), IVSaltLen = byte_size(IVSalt), - R = crypto:strong_rand_bytes(KeyLen + IVSaltLen + 6), + #key_pair{public = PubKeyA} = KeyPair = get_new_key_pair(), case gen_tcp:send( - Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, R])) + Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, PubKeyA])) of ok -> + SharedSecret = compute_shared_secret(KeyPair, PubKeyB), + IV = <<(IVNo + Seq):48>>, {Key_1, <>} = hmac_key_iv( - HmacAlgo, SharedSecret, R, KeyLen, IVSaltLen + 6), + HmacAlgo, SharedSecret, [Key, IVSalt, IV], + KeyLen, IVSaltLen + 6), Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}}, Result = gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)), @@ -1388,22 +1430,23 @@ decrypt_chunk( block_decrypt( #params{ - shared_secret = SharedSecret, + rekey_key = #key_pair{public = PubKeyA} = KeyPair, rekey_interval = RekeyInterval} = Params, Seq, AeadCipher, Key, IV, Data) -> %% case crypto:block_decrypt(AeadCipher, Key, IV, Data) of <> -> - KeyLen = byte_size(Key), - IVLen = byte_size(IV), - IVSaltLen = IVLen - 6, - RLen = KeyLen + IVLen, + PubKeyLen = byte_size(PubKeyA), case Rest of - <> -> + <> -> + SharedSecret = compute_shared_secret(KeyPair, PubKeyB), + KeyLen = byte_size(Key), + IVLen = byte_size(IV), + IVSaltLen = IVLen - 6, {Key_1, <>} = hmac_key_iv( Params#params.hmac_algorithm, - SharedSecret, R, KeyLen, IVLen), + SharedSecret, [Key, IV], KeyLen, IVLen), Params#params{iv = {IVSalt, IVNo}, key = Key_1}; _ -> error @@ -1463,24 +1506,29 @@ death_row(Reason) -> receive after 5000 -> exit(Reason) end. trace(Term) -> Term. %% Keep an eye on this Pid (debug) +-ifndef(undefined). +monitor_dist_proc(Pid) -> + Pid. +-else. monitor_dist_proc(Pid) -> -%%% spawn( -%%% fun () -> -%%% MRef = erlang:monitor(process, Pid), -%%% receive -%%% {'DOWN', MRef, _, _, normal} -> -%%% error_logger:error_report( -%%% [dist_proc_died, -%%% {reason, normal}, -%%% {pid, Pid}]); -%%% {'DOWN', MRef, _, _, Reason} -> -%%% error_logger:info_report( -%%% [dist_proc_died, -%%% {reason, Reason}, -%%% {pid, Pid}]) -%%% end -%%% end), + spawn( + fun () -> + MRef = erlang:monitor(process, Pid), + receive + {'DOWN', MRef, _, _, normal} -> + error_logger:error_report( + [dist_proc_died, + {reason, normal}, + {pid, Pid}]); + {'DOWN', MRef, _, _, Reason} -> + error_logger:info_report( + [dist_proc_died, + {reason, Reason}, + {pid, Pid}]) + end + end), Pid. +-endif. dbg() -> dbg:stop(), -- cgit v1.2.3