aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2019-05-08 11:47:38 +0200
committerRaimo Niskanen <[email protected]>2019-05-08 11:47:38 +0200
commitb4037b963258b45b62a3bf3dbbdaf3ef7fd06c9c (patch)
tree47f5116a23434eeb4a3c5b1a070ad8c4700cd560 /lib
parenta85ee28f2b72a72da63ada2561e2dd53afdbf6d9 (diff)
parentacaab6a1dc9aee21f43574a787a9242b718847d6 (diff)
downloadotp-b4037b963258b45b62a3bf3dbbdaf3ef7fd06c9c.tar.gz
otp-b4037b963258b45b62a3bf3dbbdaf3ef7fd06c9c.tar.bz2
otp-b4037b963258b45b62a3bf3dbbdaf3ef7fd06c9c.zip
Merge branch 'raimo/inet_crypto_dist'
* raimo/inet_crypto_dist: Rekey also the shared secret Use shared secret to rekey Cycle the keypair by time and count Set socket buffer sizes to avoid handshake deadlock Keep the keypair for the node's lifetime Implement some kind of PEKE to get forward secrecy Use incrementing IV Use Erlang cookie as shared secret
Diffstat (limited to 'lib')
-rw-r--r--lib/ssl/test/inet_crypto_dist.erl940
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl14
2 files changed, 583 insertions, 371 deletions
diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl
index 5aafaac983..63c19d9438 100644
--- a/lib/ssl/test/inet_crypto_dist.erl
+++ b/lib/ssl/test/inet_crypto_dist.erl
@@ -29,14 +29,8 @@
-define(DRIVER, inet_tcp).
-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).
-
-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 +46,136 @@
-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
+-define(PACKET_SIZE, 65536).
+-define(BUFFER_SIZE, (?PACKET_SIZE bsl 4)).
+
+%% -------------------------------------------------------------------------
+
+-record(params,
+ {socket,
+ dist_handle,
+ hmac_algorithm = sha256,
+ aead_cipher = aes_gcm,
+ rekey_key,
+ iv = 12,
+ key = 16,
+ tag_len = 16,
+ rekey_interval = 262144
+ }).
+
+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
+ %% 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}).
+
+-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 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.
%%
-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
+
+start_key_pair_server() ->
+ monitor_dist_proc(
+ spawn_link(
+ fun () ->
+ register(?MODULE, self()),
+ key_pair_server()
+ end)).
+
+key_pair_server() ->
+ key_pair_server(undefined, undefined, undefined).
+%%
+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
+ {timeout, Timer, _} -> ok
+ end;
+ _RemainingTime ->
+ ok
+ end.
+
+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, Request},
+ receive
+ {Ref, Reply} ->
+ erlang:demonitor(Ref, [flush]),
+ 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
%% -------
@@ -80,7 +190,7 @@ is_supported() ->
%% 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
@@ -159,6 +269,12 @@ is_supported() ->
%% 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
@@ -195,7 +311,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 ->
@@ -217,24 +333,24 @@ gen_accept(Listen, Driver) ->
%%
%% Spawn Acceptor process
%%
- Config = config(),
monitor_dist_proc(
spawn_opt(
fun () ->
- accept_loop(Listen, Driver, NetKernel, Config)
+ start_key_pair_server(),
+ 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 +358,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 +408,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 +454,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().
@@ -379,15 +498,15 @@ 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} ->
- 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 +520,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 +533,7 @@ close(Socket) ->
gen_close(Socket, ?DRIVER).
gen_close(Socket, Driver) ->
- trace(Driver:close(Socket)).
+ Driver:close(trace(Socket)).
%% -------------------------------------------------------------------------
@@ -428,17 +549,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 +580,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 +592,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 +618,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 +626,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 +733,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,40 +743,36 @@ 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, this is crypto amateur work.
+%%% * 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
+%%% (something needs to be created for multiple protocols, epmd,
+%%% multiple address families, fallback to previous version, etc)
-%% 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]),
+ {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) ->
- 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,9 +781,9 @@ 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)
+ handshake(SendParams, 1, RecvParams, 1, Controller)
end
end,
[link,
@@ -691,12 +805,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.
@@ -706,21 +821,9 @@ 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, 64).
--define(CHUNK_SIZE, (65536 - 512)).
-%% The start chunk starts with zeros, so it seems logical to not have
-%% a chunk type with value 0
+-define(TCP_ACTIVE, 16).
+-define(CHUNK_SIZE, (?PACKET_SIZE - 512)).
+
-define(HANDSHAKE_CHUNK, 1).
-define(DATA_CHUNK, 2).
-define(TICK_CHUNK, 3).
@@ -739,170 +842,189 @@ 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 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'
+%% 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.
+%%
+%% 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 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 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 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, 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),
+init(Socket, Secret) ->
+ #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, KeyPair, R2, R3).
+
+init_recv(
+ #params{socket = Socket, iv = IVLen} = Params, Secret, KeyPair, R2, R3) ->
%%
- SendParams =
- init_params(
- Socket, Protocol, HashAlgorithm, BlockCrypto, RekeyInterval),
- send_init(SendParams, Secret).
+ {ok, InitMsg} = gen_tcp:recv(Socket, 0),
+ IVSaltLen = IVLen - 6,
+ try
+ case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of
+ {#params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>>} =
+ SendParams,
+ RecvParams, SendStartMsg} ->
+ ok = gen_tcp:send(Socket, SendStartMsg),
+ {ok, RecvStartMsg} = gen_tcp:recv(Socket, 0),
+ #params{
+ iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>>} =
+ RecvParams_1 =
+ start_msg(RecvParams, R2, R3, RecvStartMsg),
+ {SendParams#params{iv = {IV2ASalt, IV2ANo}},
+ RecvParams_1#params{iv = {IV2BSalt, IV2BNo}}}
+ end
+ catch
+ error : Reason : Stacktrace->
+ _ = trace({Reason, Stacktrace}),
+ exit(connection_closed)
+ end.
-send_init(
+
+
+init_msg(
#params{
- protocol = Protocol,
- socket = Socket,
- block_crypto = BlockCrypto,
- iv = IVLen,
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
key = KeyLen,
- hash_algorithm = HashAlgorithm} = SendParams,
- Secret) ->
+ iv = IVLen,
+ tag_len = TagLen}, PubKeyA, Secret) ->
%%
- ProtocolString = atom_to_binary(Protocol, utf8),
- BlockCryptoString = atom_to_binary(BlockCrypto, utf8),
- HashAlgorithmString = atom_to_binary(HashAlgorithm, utf8),
- SendHeader =
- <<ProtocolString/binary, 0,
- HashAlgorithmString/binary, 0,
- BlockCryptoString/binary, 0>>,
- <<IV:IVLen/binary, KeySalt:KeyLen/binary>> = 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).
-
-recv_init(
+ RLen = KeyLen + IVLen,
+ <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> =
+ 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 = [<<MsgLen:32>>, R1A],
+ {Ciphertext, Tag} =
+ crypto:block_encrypt(AeadCipher, Key1A, IV1A, {AAD, Plaintext, TagLen}),
+ Msg = [R1A, Tag, Ciphertext],
+ {R2A, R3A, Msg}.
+%%
+init_msg(
#params{
- socket = Socket,
- hash_algorithm = SendHashAlgorithm,
- key = SendKeySalt} = SendParams, Secret, SendHeader) ->
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
+ key = KeyLen,
+ iv = IVLen,
+ tag_len = TagLen,
+ rekey_interval = RekeyInterval} = Params,
+ Secret, KeyPair, R2A, R3A, Msg) ->
%%
- {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),
- <<RecvIV:RecvIVLen/binary,
- RecvKeySalt:RecvKeyLen/binary>> = Rest_3,
- SendKey =
- hash_key(SendHashAlgorithm, SendKeySalt, [RecvKeySalt, Secret]),
- RecvKey =
- hash_key(RecvHashAlgorithm, RecvKeySalt, [SendKeySalt, Secret]),
- SendParams_1 = SendParams#params{key = SendKey},
- RecvParams_1 = RecvParams#params{iv = RecvIV, key = RecvKey},
- RecvHeaderLen = byte_size(InitPacket) - RecvIVLen - RecvKeyLen,
- <<RecvHeader:RecvHeaderLen/binary, _/binary>> = InitPacket,
- send_start(SendParams_1, SendHeader),
- RecvRekeyInterval = recv_start(RecvParams_1, RecvHeader),
- {SendParams_1,
- RecvParams_1#params{rekey_interval = RecvRekeyInterval}}
+ RLen = KeyLen + IVLen,
+ case Msg of
+ <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> ->
+ {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen),
+ MsgLen = byte_size(Msg),
+ AAD = [<<MsgLen:32>>, R1B],
+ case
+ crypto:block_decrypt(
+ AeadCipher, Key1B, IV1B, {AAD, Ciphertext, Tag})
+ of
+ <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ %%
+ {Key2A, IV2A} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen),
+ SendParams =
+ Params#params{
+ rekey_key = PubKeyB,
+ key = Key2A, iv = IV2A},
+ %%
+ StartCleartext = [R2B, R3B, <<RekeyInterval:32>>],
+ StartMsgLen = TagLen + iolist_size(StartCleartext),
+ StartAAD = <<StartMsgLen:32>>,
+ {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{
+ rekey_key = KeyPair,
+ key = Key2B, iv = IV2B},
+ %%
+ {SendParams, RecvParams, StartMsg}
+ end
end.
-send_start(
+start_msg(
#params{
- socket = Socket,
- block_crypto = BlockCrypto,
- rekey_interval= RekeyInterval,
- iv = IV,
- key = Key,
- tag_len = TagLen}, AAD) ->
+ aead_cipher = AeadCipher,
+ key = Key2B,
+ iv = IV2B,
+ tag_len = TagLen,
+ rekey_interval = RekeyIntervalA} = RecvParams, R2A, R3A, Msg) ->
%%
- KeyLen = byte_size(Key),
- Zeros = binary:copy(<<0>>, KeyLen),
- {Ciphertext, CipherTag} =
- crypto:block_encrypt(
- crypto_cipher_name(BlockCrypto),
- Key, IV, {AAD, [Zeros, <<RekeyInterval:32>>], TagLen}),
- ok = gen_tcp:send(Socket, [Ciphertext, CipherTag]).
-
-recv_start(
- #params{
- socket = Socket,
- block_crypto = BlockCrypto,
- iv = IV,
- key = Key,
- tag_len = TagLen}, AAD) ->
- {ok, Packet} = gen_tcp:recv(Socket, 0),
- KeyLen = byte_size(Key),
- PacketLen = KeyLen + 4,
- <<Ciphertext:PacketLen/binary, CipherTag:TagLen/binary>> = Packet,
- Zeros = binary:copy(<<0>>, KeyLen),
- case
- crypto:block_decrypt(
- crypto_cipher_name(BlockCrypto),
- Key, IV, {AAD, Ciphertext, CipherTag})
- of
- <<Zeros:KeyLen/binary, RekeyInterval:32>>
- when 1 =< RekeyInterval ->
- RekeyInterval;
- _ ->
- error(decrypt_error)
- 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
+ case Msg of
+ <<Tag:TagLen/binary, Ciphertext/binary>> ->
+ KeyLen = byte_size(Key2B),
+ IVLen = byte_size(IV2B),
+ RLen = KeyLen + IVLen,
+ MsgLen = byte_size(Msg),
+ AAD = <<MsgLen:32>>,
+ case
+ crypto:block_decrypt(
+ AeadCipher, Key2B, IV2B, {AAD, Ciphertext, Tag})
+ of
+ <<R2A:RLen/binary, R3A:RLen/binary, RekeyIntervalB:32>>
+ when RekeyIntervalA =< (RekeyIntervalB bsl 2),
+ RekeyIntervalB =< (RekeyIntervalA bsl 2) ->
+ RecvParams#params{rekey_interval = RekeyIntervalB}
+ end
end.
-hash_key(HashAlgorithm, KeySalt, OtherSalt) ->
- KeyLen = byte_size(KeySalt),
- <<Key:KeyLen/binary, _/binary>> =
- crypto:hash(HashAlgorithm, [KeySalt, OtherSalt]),
- Key.
+hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) ->
+ <<Key:KeyLen/binary, IV:IVLen/binary>> =
+ crypto:hmac(HmacAlgo, MacKey, Data, KeyLen + IVLen),
+ {Key, IV}.
%% -------------------------------------------------------------------------
%% net_kernel distribution handshake in progress
@@ -918,7 +1040,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 +1066,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 +1112,12 @@ recv_and_decrypt_chunk(#params{socket = Socket} = RecvParams, RecvSeq) ->
case decrypt_chunk(RecvParams, RecvSeq, Chunk) of
<<?HANDSHAKE_CHUNK, Cleartext/binary>> ->
{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 +1137,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 +1152,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 +1173,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 +1241,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 +1254,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
<<?DATA_CHUNK, Cleartext/binary>> ->
input_handler(Params, Seq + 1, enq_binary(Cleartext, Q), 0);
<<?TICK_CHUNK, _/binary>> ->
- 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.
%% -------------------------------------------------------------------------
@@ -1198,64 +1358,110 @@ 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,
+ socket = Socket,
+ rekey_interval = Seq,
+ rekey_key = PubKeyB,
+ key = Key,
+ iv = {IVSalt, IVNo},
+ hmac_algorithm = HmacAlgo} = Params,
Seq, Cleartext) ->
%%
KeyLen = byte_size(Key),
- IVLen = byte_size(IV),
- Chunk = <<IV_1:IVLen/binary, KeySalt:KeyLen/binary>> =
- 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};
+ IVSaltLen = byte_size(IVSalt),
+ #key_pair{public = PubKeyA} = KeyPair = get_new_key_pair(),
+ case
+ gen_tcp:send(
+ Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, PubKeyA]))
+ of
+ ok ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ IV = <<(IVNo + Seq):48>>,
+ {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
+ hmac_key_iv(
+ 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)),
+ {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{
- block_crypto = BlockCrypto,
- iv = IV, key = Key, tag_len = TagLen}, Seq, Cleartext) ->
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, Seq, Cleartext) ->
%%
ChunkLen = iolist_size(Cleartext) + TagLen,
AAD = <<Seq:32, ChunkLen:32>>,
+ IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
{Ciphertext, CipherTag} =
- crypto:block_encrypt(
- crypto_cipher_name(BlockCrypto), Key, IV, {AAD, Cleartext, TagLen}),
+ crypto:block_encrypt(AeadCipher, 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) ->
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) ->
%%
ChunkLen = byte_size(Chunk),
- true = TagLen =< ChunkLen, % Assert
- AAD = <<Seq:32, ChunkLen:32>>,
- CiphertextLen = ChunkLen - TagLen,
- <<Ciphertext:CiphertextLen/binary, CipherTag:TagLen/binary>> = Chunk,
- block_decrypt(
- Params, Seq, crypto_cipher_name(BlockCrypto),
- Key, IV, {AAD, Ciphertext, CipherTag}).
+ if
+ ChunkLen < TagLen ->
+ error;
+ true ->
+ AAD = <<Seq:32, ChunkLen:32>>,
+ IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
+ CiphertextLen = ChunkLen - TagLen,
+ case Chunk of
+ <<Ciphertext:CiphertextLen/binary,
+ CipherTag:TagLen/binary>> ->
+ block_decrypt(
+ Params, Seq, AeadCipher, Key, IVBin,
+ {AAD, Ciphertext, CipherTag});
+ _ ->
+ error
+ end
+ end.
block_decrypt(
- #params{rekey_interval = Seq} = Params, Seq, CipherName, Key, IV, Data) ->
+ #params{
+ rekey_key = #key_pair{public = PubKeyA} = KeyPair,
+ rekey_interval = RekeyInterval} = Params,
+ Seq, AeadCipher, Key, IV, Data) ->
%%
- KeyLen = byte_size(Key),
- IVLen = byte_size(IV),
- case crypto:block_decrypt(CipherName, Key, IV, Data) of
- <<?REKEY_CHUNK, IV_1:IVLen/binary, KeySalt:KeyLen/binary>> ->
- 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).
+ case crypto:block_decrypt(AeadCipher, Key, IV, Data) of
+ <<?REKEY_CHUNK, Rest/binary>> ->
+ PubKeyLen = byte_size(PubKeyA),
+ case Rest of
+ <<PubKeyB:PubKeyLen/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ KeyLen = byte_size(Key),
+ IVLen = byte_size(IV),
+ IVSaltLen = IVLen - 6,
+ {Key_1, <<IVSalt:IVSaltLen/binary, IVNo:48>>} =
+ hmac_key_iv(
+ Params#params.hmac_algorithm,
+ SharedSecret, [Key, IV], KeyLen, IVLen),
+ 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
+ end;
+ error ->
+ error
+ end.
%% -------------------------------------------------------------------------
%% Queue of binaries i.e an iovec queue
@@ -1289,34 +1495,46 @@ 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.
%% 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(),
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) ->