Skip to content

Commit

Permalink
Merge pull request #2988 from peterdmv/ssl/early-data-server/OTP-17042
Browse files Browse the repository at this point in the history
Implement handling of early data in ssl server
  • Loading branch information
peterdmv authored Jan 29, 2021
2 parents 462f800 + dcea6f7 commit aece091
Show file tree
Hide file tree
Showing 21 changed files with 1,198 additions and 265 deletions.
10 changes: 10 additions & 0 deletions lib/ssl/doc/src/ssl_app.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@
</p>
</item>

<tag><c><![CDATA[server_session_ticket_max_early_data = integer() <optional>]]></c></tag>
<item>
<p>
Sets the maximum size of the early data that the server accepts and also configures
its NewSessionTicket messages to include this same size limit in their
early_data_indication extension.
Defaults to 16384. Size limit is enforced by both client and server.
</p>
</item>

<tag><c><![CDATA[client_session_ticket_lifetime = integer() <optional>]]></c></tag>
<item>
<p>
Expand Down
18 changes: 9 additions & 9 deletions lib/ssl/doc/src/standards_compliance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
<list type="bulleted">
<item>PSK and session resumption is supported (stateful and stateless tickets)</item>
<item>Anti-replay protection using Bloom-filters with stateless tickets</item>
<item>Early data and 0-RTT is supported on the client side</item>
<item>Early data and 0-RTT is supported</item>
<item>Key and Initialization Vector Update is supported</item>
</list>
<p>For more detailed information see the
Expand Down Expand Up @@ -526,8 +526,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
Expand Down Expand Up @@ -1141,8 +1141,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"><em></em></cell>
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>23.3</em></cell>
</row>

<row>
Expand Down Expand Up @@ -1341,8 +1341,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">early_data (RFC8446)</cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
Expand Down Expand Up @@ -1673,8 +1673,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
<cell align="left" valign="middle"><em>NC</em></cell>
<cell align="left" valign="middle"><em></em></cell>
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>23.3</em></cell>
</row>

<row>
Expand Down
38 changes: 27 additions & 11 deletions lib/ssl/doc/src/using_ssl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -579,21 +579,37 @@ ok
replays causing resource limit exhaustion and other similar problems.</p>
<p>An example of sending early data with automatic and manual session ticket handling:</p>
<warning>
<p>The Early Data feature is implemented only on the client side in this version of OTP.
<p>The Early Data feature is experimental in this version of OTP.
</p>
</warning>

<p><em>Server side (openssl s_server):</em></p>
<p><em>Server (with NSS key logging)</em></p>
<code type="none">
openssl s_server -accept 11029 \
-tls1_3 -verify 2 \
-cert certs/server.pem \
-CAfile certs/ca.pem \
-key certs/server.key \
-keylogfile keylog \
-msg -debug \
-early_data \
-no_anti_replay
early_data_server() ->
application:load(ssl),
{ok, _} = application:ensure_all_started(ssl),
Port = 11029,
LOpts = [{certfile, ?SERVER_CERT},
{keyfile, ?SERVER_KEY},
{reuseaddr, true},
{versions, ['tlsv1.2','tlsv1.3']},
{session_tickets, stateless},
{early_data, enabled},
{keep_secrets, true} %% Enable NSS key log (debug option)
],
{ok, LSock} = ssl:listen(Port, LOpts),
%% Accept first connection
{ok, CSock0} = ssl:transport_accept(LSock),
{ok, _} = ssl:handshake(CSock0),
%% Accept second connection
{ok, CSock1} = ssl:transport_accept(LSock),
{ok, Sock} = ssl:handshake(CSock1),
Sock.
</code>
<p><em>Exporting the secrets (optional)</em></p>
<code type="none">
{ok, [{keylog, KeylogItems}]} = ssl:connection_information(Sock, [keylog]).
file:write_file("key.log", [[KeylogItem,$\n] || KeylogItem &lt;- KeylogItems]).
</code>
<p><em>Client (automatic ticket handling):</em></p>
<code type="erl">
Expand Down
22 changes: 0 additions & 22 deletions lib/ssl/src/ssl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1783,21 +1783,6 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
handle_option(max_early_data = Option, unbound, OptionsMap, #{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
OptionsMap#{Option => Value};
handle_option(max_early_data = Option, Value0, #{early_data := EarlyData,
session_tickets := SessionTickets,
versions := Versions} = OptionsMap,
#{role := Role}) ->
assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
assert_role(server_only, Role, Option, Value0),
assert_option_dependency(Option, session_tickets, [SessionTickets],
[stateful, stateless]),
assert_option_dependency(Option, early_data, [EarlyData],
[enabled]),
Value = validate_option(Option, Value0),
OptionsMap#{Option => Value};
handle_option(next_protocols_advertised = Option, unbound, OptionsMap,
#{rules := Rules}) ->
Value = validate_option(Option, default_value(Option, Rules)),
Expand Down Expand Up @@ -2332,13 +2317,6 @@ validate_option(log_level, Value, _)
Value =:= info orelse
Value =:= debug) ->
Value;
validate_option(max_early_data, Value, _)
when is_integer(Value),
Value >= 0 ->
Value;
validate_option(max_early_data = Option, Value, _) when Value =/= undefined ->
throw({error,
{options, type, {Option, {Value, not_integer}}}});
%% RFC 6066, Section 4
validate_option(max_fragment_length, I, _)
when I == ?MAX_FRAGMENT_LENGTH_BYTES_1;
Expand Down
31 changes: 30 additions & 1 deletion lib/ssl/src/ssl_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
-include("ssl_connection.hrl").
-include_lib("public_key/include/public_key.hrl").

-export([init/2]).
-export([get_max_early_data_size/0,
get_ticket_lifetime/0,
get_ticket_store_size/0,
init/2]).

init(#{erl_dist := ErlDist,
key := Key,
Expand Down Expand Up @@ -176,3 +179,29 @@ init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
_:Reason ->
file_error(DHParamFile, {dhfile, Reason})
end.

get_ticket_lifetime() ->
case application:get_env(ssl, server_session_ticket_lifetime) of
{ok, Seconds} when is_integer(Seconds) andalso
Seconds =< 604800 -> %% MUST be less than 7 days
Seconds;
_ ->
7200 %% Default 2 hours
end.

get_ticket_store_size() ->
case application:get_env(ssl, server_session_ticket_store_size) of
{ok, Size} when is_integer(Size) ->
Size;
_ ->
1000
end.

get_max_early_data_size() ->
case application:get_env(ssl, server_session_ticket_max_early_data) of
{ok, Size} when is_integer(Size) ->
Size;
_ ->
?DEFAULT_MAX_EARLY_DATA_SIZE
end.

61 changes: 46 additions & 15 deletions lib/ssl/src/ssl_gen_statem.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1434,11 +1434,17 @@ read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvF
State#state{
user_data_buffer = {Front,BufferSize,Rear},
start_or_recv_from = undefined,
bytes_to_read = undefined,
bytes_to_read = undefined,
socket_options = SocketOpts
}};
true -> %% Try to deliver more data
read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined)
%% Process early data if it is accepted.
case (State#state.handshake_env)#handshake_env.early_data_accepted of
false ->
read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined);
true ->
read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, undefined)
end
end.


Expand Down Expand Up @@ -1795,28 +1801,45 @@ security_info(#state{connection_states = ConnectionStates,
#security_parameters{client_random = ClientRand,
server_random = ServerRand,
master_secret = MasterSecret,
application_traffic_secret = AppTrafSecretRead}} = ReadState,
application_traffic_secret = AppTrafSecretRead,
client_early_data_secret = ServerEarlyData
}} = ReadState,
BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
if KeepSecrets =/= true ->
BaseSecurityInfo;
true ->
#{security_parameters :=
#security_parameters{application_traffic_secret = AppTrafSecretWrite}} =
#security_parameters{application_traffic_secret = AppTrafSecretWrite,
client_early_data_secret = ClientEarlyData
}} =
ssl_record:current_connection_state(ConnectionStates, write),
BaseSecurityInfo ++
if Role == server ->
[{server_traffic_secret_0, AppTrafSecretWrite}, {client_traffic_secret_0, AppTrafSecretRead}];
true ->
[{client_traffic_secret_0, AppTrafSecretWrite}, {server_traffic_secret_0, AppTrafSecretRead}]
end ++
if Role == server ->
if ServerEarlyData =/= undefined ->
[{server_traffic_secret_0, AppTrafSecretWrite},
{client_traffic_secret_0, AppTrafSecretRead},
{client_early_data_secret, ServerEarlyData}];
true ->
[{server_traffic_secret_0, AppTrafSecretWrite},
{client_traffic_secret_0, AppTrafSecretRead}]
end;
true ->
if ClientEarlyData =/= undefined ->
[{client_traffic_secret_0, AppTrafSecretWrite},
{server_traffic_secret_0, AppTrafSecretRead},
{client_early_data_secret, ClientEarlyData}];
true ->
[{client_traffic_secret_0, AppTrafSecretWrite},
{server_traffic_secret_0, AppTrafSecretRead}]
end
end ++
case ReadState of
#{client_handshake_traffic_secret := ClientHSTrafficSecret,
server_handshake_traffic_secret := ServerHSTrafficSecret} ->
[{client_handshake_traffic_secret, ClientHSTrafficSecret},
{server_handshake_traffic_secret, ServerHSTrafficSecret}];
_ ->
[]
end
end ++ BaseSecurityInfo
end.

record_cb(tls) ->
Expand Down Expand Up @@ -1994,10 +2017,18 @@ maybe_add_keylog({_, 'tlsv1.3'}, Info) ->
ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf),
ClientHSecret = keylog_secret(ClientHSecretBin, Prf),
ServerHSecret = keylog_secret(ServerHSecretBin, Prf),
Keylog = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
Keylog0 = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret,
io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret,
io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0,
io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0],
Keylog = case lists:keyfind(client_early_data_secret, 1, Info) of
{client_early_data_secret, EarlySecret} ->
ClientEarlySecret = keylog_secret(EarlySecret, Prf),
[io_lib:format("CLIENT_EARLY_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientEarlySecret
| Keylog0];
_ ->
Keylog0
end,
Info ++ [{keylog,Keylog}]
catch
_Cxx:_Exx ->
Expand Down
5 changes: 2 additions & 3 deletions lib/ssl/src/ssl_internal.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@
%% 2^24.5 * 2^14 = 2^38.5
-define(KEY_USAGE_LIMIT_AES_GCM, 388736063997).

-define(DEFAULT_MAX_EARLY_DATA_SIZE, 16384).

%% This map stores all supported options with default values and
%% list of dependencies:
%% #{<option> => {<default_value>, [<option>]},
Expand Down Expand Up @@ -158,9 +160,6 @@
log_level => {notice, [versions]},
max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
middlebox_comp_mode => {true, [versions]},
max_early_data => {undefined, [versions,
early_data,
session_tickets]},
max_fragment_length => {undefined, [versions]},
next_protocol_selector => {undefined, [versions]},
next_protocols_advertised => {undefined, [versions]},
Expand Down
27 changes: 11 additions & 16 deletions lib/ssl/src/ssl_record.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
set_client_verify_data/3,
set_server_verify_data/3,
set_max_fragment_length/2,
empty_connection_state/2, initial_connection_state/2, record_protocol_role/1,
empty_connection_state/2,
empty_connection_state/3,
record_protocol_role/1,
step_encryption_state/1,
step_encryption_state_read/1,
step_encryption_state_write/1]).
Expand Down Expand Up @@ -458,6 +460,10 @@ nonce_seed(_,_, CipherState) ->
%%--------------------------------------------------------------------

empty_connection_state(ConnectionEnd, BeastMitigation) ->
MaxEarlyDataSize = ssl_config:get_max_early_data_size(),
empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize).
%%
empty_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
SecParams = empty_security_params(ConnectionEnd),
#{security_parameters => SecParams,
beast_mitigation => BeastMitigation,
Expand All @@ -467,7 +473,10 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
secure_renegotiation => undefined,
client_verify_data => undefined,
server_verify_data => undefined,
max_fragment_length => undefined
max_early_data_size => MaxEarlyDataSize,
max_fragment_length => undefined,
trial_decryption => false,
early_data_limit => false
}.

empty_security_params(ConnectionEnd = ?CLIENT) ->
Expand All @@ -494,20 +503,6 @@ record_protocol_role(client) ->
record_protocol_role(server) ->
?SERVER.

initial_connection_state(ConnectionEnd, BeastMitigation) ->
#{security_parameters =>
initial_security_params(ConnectionEnd),
sequence_number => 0,
beast_mitigation => BeastMitigation,
compression_state => undefined,
cipher_state => undefined,
mac_secret => undefined,
secure_renegotiation => undefined,
client_verify_data => undefined,
server_verify_data => undefined,
max_fragment_length => undefined
}.

initial_security_params(ConnectionEnd) ->
SecParams = #security_parameters{connection_end = ConnectionEnd,
compression_algorithm = ?NULL},
Expand Down
1 change: 1 addition & 0 deletions lib/ssl/src/ssl_record.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
master_secret, % opaque 48
resumption_master_secret,
application_traffic_secret,
client_early_data_secret,
client_random, % opaque 32
server_random, % opaque 32
exportable % boolean
Expand Down
Loading

0 comments on commit aece091

Please sign in to comment.