summaryrefslogtreecommitdiff
path: root/lib/ssl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl')
-rw-r--r--lib/ssl/doc/src/ssl.xml10
-rw-r--r--lib/ssl/doc/src/ssl_app.xml19
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache_api.xml50
-rw-r--r--lib/ssl/src/Makefile7
-rw-r--r--lib/ssl/src/dtls_connection.erl45
-rw-r--r--lib/ssl/src/dtls_handshake.erl18
-rw-r--r--lib/ssl/src/dtls_listener_sup.erl31
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl9
-rw-r--r--lib/ssl/src/dtls_socket.erl33
-rw-r--r--lib/ssl/src/dtls_sup.erl76
-rw-r--r--lib/ssl/src/ssl.app.src7
-rw-r--r--lib/ssl/src/ssl.erl829
-rw-r--r--lib/ssl/src/ssl_admin_sup.erl13
-rw-r--r--lib/ssl/src/ssl_alert.erl83
-rw-r--r--lib/ssl/src/ssl_alert.hrl7
-rw-r--r--lib/ssl/src/ssl_app.erl3
-rw-r--r--lib/ssl/src/ssl_certificate.erl2
-rw-r--r--lib/ssl/src/ssl_cipher.erl66
-rw-r--r--lib/ssl/src/ssl_cipher.hrl10
-rw-r--r--lib/ssl/src/ssl_connection.erl132
-rw-r--r--lib/ssl/src/ssl_connection.hrl7
-rw-r--r--lib/ssl/src/ssl_connection_sup.erl48
-rw-r--r--lib/ssl/src/ssl_crl_cache.erl6
-rw-r--r--lib/ssl/src/ssl_crl_cache_api.erl13
-rw-r--r--lib/ssl/src/ssl_crl_hash_dir.erl77
-rw-r--r--lib/ssl/src/ssl_handshake.erl219
-rw-r--r--lib/ssl/src/ssl_internal.hrl159
-rw-r--r--lib/ssl/src/ssl_logger.erl103
-rw-r--r--lib/ssl/src/ssl_manager.erl42
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl5
-rw-r--r--lib/ssl/src/ssl_record.hrl1
-rw-r--r--lib/ssl/src/ssl_session.erl89
-rw-r--r--lib/ssl/src/tls_bloom_filter.erl113
-rw-r--r--lib/ssl/src/tls_client_ticket_store.erl355
-rw-r--r--lib/ssl/src/tls_connection.erl174
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl4
-rw-r--r--lib/ssl/src/tls_handshake.erl18
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl587
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl4
-rw-r--r--lib/ssl/src/tls_record.erl2
-rw-r--r--lib/ssl/src/tls_sender.erl25
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl391
-rw-r--r--lib/ssl/src/tls_server_session_ticket_sup.erl72
-rw-r--r--lib/ssl/src/tls_server_sup.erl77
-rw-r--r--lib/ssl/src/tls_socket.erl87
-rw-r--r--lib/ssl/src/tls_sup.erl76
-rw-r--r--lib/ssl/src/tls_v1.erl11
-rw-r--r--lib/ssl/test/Makefile4
-rw-r--r--lib/ssl/test/dtls_api_SUITE.erl131
-rw-r--r--lib/ssl/test/make_certs.erl84
-rw-r--r--lib/ssl/test/openssl_npn_SUITE.erl1
-rw-r--r--lib/ssl/test/openssl_session_SUITE.erl16
-rw-r--r--lib/ssl/test/openssl_tls_1_3_version_SUITE.erl12
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl4
-rw-r--r--lib/ssl/test/ssl_alert_SUITE.erl24
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl144
-rw-r--r--lib/ssl/test/ssl_crl_SUITE.erl19
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl1
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl7
-rw-r--r--lib/ssl/test/ssl_npn_SUITE.erl3
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl12
-rw-r--r--lib/ssl/test/ssl_packet_SUITE.erl71
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl8
-rw-r--r--lib/ssl/test/ssl_session_cache_SUITE.erl28
-rw-r--r--lib/ssl/test/ssl_session_ticket_SUITE.erl722
-rw-r--r--lib/ssl/test/ssl_test_lib.erl105
-rw-r--r--lib/ssl/test/tls_1_3_record_SUITE.erl470
-rw-r--r--lib/ssl/test/tls_1_3_version_SUITE.erl71
68 files changed, 4935 insertions, 1217 deletions
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 05590666da..0ad2d8da0b 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -655,16 +655,18 @@ fun(srp, Username :: string(), UserState :: term()) ->
<datatype>
<name name="log_alert"/>
- <desc><p>If set to <c>false</c>, error reports are not displayed.
+ <desc><p>If set to <c>false</c>, TLS/DTLS Alert reports are not displayed.
Deprecated in OTP 22, use {log_level, <seealso marker="#type-logging_level">logging_level()</seealso>} instead.</p>
</desc>
</datatype>
<datatype>
<name name="logging_level"/>
- <desc><p>Specifies the log level for TLS/DTLS. At verbosity level <c>notice</c> and above error reports are
- displayed in TLS/DTLS. The level <c>debug</c> triggers verbose logging of TLS/DTLS protocol
- messages.</p>
+ <desc><p>Specifies the log level for a TLS/DTLS
+ connection. Alerts are logged on <c>notice</c> level, which is
+ the default level. The level <c>debug</c> triggers verbose
+ logging of TLS/DTLS protocol messages. See also <seealso marker="ssl_app">ssl(6)</seealso>
+ </p>
</desc>
</datatype>
diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml
index b05caf44ea..129fc57862 100644
--- a/lib/ssl/doc/src/ssl_app.xml
+++ b/lib/ssl/doc/src/ssl_app.xml
@@ -67,14 +67,14 @@
<tag><c>protocol_version = </c><seealso marker="ssl#type-protocol">ssl:ssl_tls_protocol()</seealso><c><![CDATA[<optional>]]></c></tag>
<item><p>Protocol supported by started clients and
servers. If this option is not set, it defaults to all
- TLS protocols currently supported by the SSL application.
+ TLS protocols currently supported, more might be configurable, by the SSL application.
This option can be overridden by the version option
to <c>ssl:connect/[2,3]</c> and <c>ssl:listen/2</c>.</p></item>
<tag><c>dtls_protocol_version = </c><seealso marker="ssl#type-protocol">ssl:dtls_protocol()</seealso><c><![CDATA[<optional>]]></c></tag>
<item><p>Protocol supported by started clients and
servers. If this option is not set, it defaults to all
- DTLS protocols currently supported by the SSL application.
+ DTLS protocols currently supported, more might be configurable, by the SSL application.
This option can be overridden by the version option
to <c>ssl:connect/[2,3]</c> and <c>ssl:listen/2</c>.</p></item>
@@ -155,10 +155,17 @@
<section>
<title>ERROR LOGGER AND EVENT HANDLERS</title>
- <p>The SSL application uses the default <seealso
- marker="kernel:error_logger">OTP error logger</seealso> to log
- unexpected errors and TLS/DTLS alerts. The logging of TLS/DTLS alerts may be
- turned off with the <c>log_alert</c> option. </p>
+
+ <p>The SSL application uses <seealso
+ marker="kernel:logger">OTP logger</seealso>.
+ TLS/DTLS alerts are logged on notice level. Unexpected
+ errors are logged on error level. These log entries
+ will by default end up in the default Erlang log.
+ The option <c>log_level</c> may be used to in run-time to set
+ the log level of a specific TLS connection, which is
+ handy when you want to use level debug to inspect the
+ TLS handshake setup.
+ </p>
</section>
<section>
diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml
index 4cba4e1de1..9922cfb0e9 100644
--- a/lib/ssl/doc/src/ssl_crl_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml
@@ -56,19 +56,29 @@
</desc>
</datatype>
-
<datatype>
<name name="dist_point"/>
<desc>
<p>For description see <seealso
marker="public_key:public_key_records"> X509 certificates records</seealso></p>
</desc>
- </datatype>
+ </datatype>
+
+ <datatype>
+ <name name="logger_info"/>
+ <desc>
+ <p>Information for ssl applications use of <seealso
+ marker="kernel:logger"> Logger(3)</seealso></p>
+ </desc>
+ </datatype>
+
+
</datatypes>
<funcs>
- <func>
- <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL</name>
+ <func>
+ <name since="@maint@">fresh_crl(DistributionPoint, CRL) -> FreshCRL </name>
+ <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL | {LoggerInfo, FreshCRL}</name>
<fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
public_key:pkix_crls_validate/3 </fsummary>
<type>
@@ -77,14 +87,21 @@
marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
<v> FreshCRL = [<seealso
marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
+ <v> LoggerInfo = {logger, <seealso marker="#type-logger_info"> logger_info() </seealso>}} </v>
</type>
<desc>
<p> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
<seealso marker="public_key:public_key#pkix_crls_validate-3">public_key:pkix_crls_validate/3 </seealso> </p>
+
+ <p>It is possible to return logger info that will be used by the TLS connection
+ to produce log events.
+ </p>
</desc>
</func>
<func>
+ <name since="@maint@">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs |
+ {LoggerInfo, CRLs} </name>
<name since="OTP 19.0">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
<name since="OTP 18.0">lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
<fsummary> </fsummary>
@@ -94,7 +111,8 @@
marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso> </v>
<v> DbHandle = <seealso marker="#type-crl_cache_ref"> crl_cache_ref() </seealso></v>
<v> CRLs = [<seealso
- marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v>
+ marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>]</v>
+ <v> LoggerInfo = {logger, <seealso marker="#type-logger_info"> logger_info() </seealso>}} </v>
</type>
<desc> <p>Lookup the CRLs belonging to the distribution point <c> Distributionpoint</c>.
This function may choose to only look in the cache or to follow distribution point
@@ -111,19 +129,35 @@
compatibility, this is still supported: if there is no
<c>lookup/3</c> function in the callback module,
<c>lookup/2</c> is called instead.</p>
+
+ <p>It is possible to return logger info that will be used by the TLS connection
+ to produce log events.
+ </p>
</desc>
</func>
<func>
+ <name since="@maint@">select(Issuer, DbHandle) -> CRLs | {LoggerInfo, CRLs} </name>
<name since="OTP 18.0">select(Issuer, DbHandle) -> CRLs </name>
<fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary>
<type>
<v> Issuer = <seealso
- marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso></v>
+ marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso> | list() </v>
<v> DbHandle = <seealso marker="#type-crl_cache_ref"> cache_ref() </seealso></v>
- </type>
+ <v> LoggerInfo = {logger, <seealso marker="#type-logger_info"> logger_info() </seealso>} </v>
+ </type>
<desc>
- <p>Select the CRLs in the cache that are issued by <c>Issuer</c> </p>
+ <p>Select the CRLs in the cache that are issued by <c>Issuer</c> unless
+ the value is a list of so called general names, see <seealso
+ marker="public_key:public_key_records"> X509 certificates records</seealso>,
+ originating form <c>#'DistributionPoint'.cRLissuer</c> and
+ representing different mechanism to obtain the CRLs. The cache
+ callback needs to use the appropriate entry to retrive the CRLs or
+ return an empty list if it does not exist.
+ </p>
+
+ <p>It is possible to return logger info that will be used by the TLS connection
+ to produce log events.</p>
</desc>
</func>
</funcs>
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 8dc76f2638..e961f05b37 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -50,6 +50,7 @@ MODULES= \
dtls_listener_sup \
dtls_packet_demux \
dtls_record \
+ dtls_sup \
dtls_socket \
dtls_v1 \
inet_tls_dist \
@@ -83,6 +84,7 @@ MODULES= \
ssl_srp_primes \
ssl_sup \
ssl_v3 \
+ tls_bloom_filter \
tls_connection \
tls_connection_sup \
tls_connection_1_3 \
@@ -90,8 +92,13 @@ MODULES= \
tls_handshake_1_3 \
tls_record \
tls_record_1_3 \
+ tls_client_ticket_store \
tls_sender \
+ tls_server_session_ticket\
+ tls_server_session_ticket_sup\
+ tls_server_sup\
tls_socket \
+ tls_sup \
tls_v1
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index f8fd42e36d..a658fe0306 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -441,19 +441,19 @@ init({call, From}, {start, Timeout},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = CEnv,
ssl_options = #{versions := Versions} = SslOpts,
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificate = Cert} = NewSession,
connection_states = ConnectionStates0
} = State0) ->
+ Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ Session#session.session_id, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, Versions),
State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
{State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
+ session = Session,
start_or_recv_from = From},
next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
init({call, _} = Type, Event, #state{static_env = #static_env{role = server},
@@ -521,30 +521,24 @@ hello(internal, #client_hello{cookie = <<>>,
Actions);
hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
host = Host,
- port = Port,
- session_cache = Cache,
- session_cache_cb = CacheCb},
+ port = Port},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
connection_env = CEnv,
ssl_options = SslOpts,
- session = #session{own_certificate = OwnCert}
- = Session0,
+ session = #session{own_certificate = Cert, session_id = Id},
connection_states = ConnectionStates0
} = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
- SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert),
+ SslOpts, Id, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
State1 = prepare_flight(State0#state{handshake_env =
HsEnv#handshake_env{tls_handshake_history
= ssl_handshake:init_handshake_history()}}),
{State2, Actions} = send_handshake(Hello, State1),
- State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% Requested version
- session =
- Session0#session{session_id =
- Hello#client_hello.session_id}},
+ State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version} % RequestedVersion
+ },
next_event(?FUNCTION_NAME, no_record, State, Actions);
hello(internal, #client_hello{extensions = Extensions} = Hello,
#state{ssl_options = #{handshake := hello},
@@ -702,7 +696,7 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
session_cache = Cache,
session_cache_cb = CacheCb
},
- handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
connection_env = CEnv,
session = #session{own_certificate = Cert} = Session0,
ssl_options = #{versions := Versions} = SslOpts,
@@ -710,15 +704,15 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho
protocol_specific = PS
} = State0) ->
+ Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert),
+ Session#session.session_id, Renegotiation, Cert),
Version = Hello#client_hello.client_version,
HelloVersion = dtls_record:hello_version(Version, Versions),
State1 = prepare_flight(State0),
{State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
State = State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)},
- session = Session0#session{session_id
- = Hello#client_hello.session_id}},
+ session = Session},
next_event(hello, no_record, State, Actions);
connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) ->
@@ -1209,12 +1203,13 @@ is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) ->
is_ignore_alert(_) ->
false.
-log_ignore_alert(debug, StateName, Alert, Role) ->
- Txt = ssl_alert:alert_txt(Alert),
- ?LOG_ERROR("DTLS over UDP ~p: In state ~p ignored to send ALERT ~s as DoS-attack mitigation \n",
- [Role, StateName, Txt]);
-log_ignore_alert(_, _, _, _) ->
- ok.
+log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) ->
+ ssl_logger:log(info,
+ Level, #{alert => Alert,
+ alerter => ignored,
+ statename => StateName,
+ role => Role,
+ protocol => protocol_name()}, Location).
send_application_data(Data, From, _StateName,
#state{static_env = #static_env{socket = Socket,
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index b2a5fcfa66..cad31b23ee 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -30,7 +30,7 @@
-include("ssl_alert.hrl").
%% Handshake handling
--export([client_hello/8, client_hello/9, cookie/4, hello/4,
+-export([client_hello/7, client_hello/8, cookie/4, hello/4,
hello_verify_request/2]).
%% Handshake encoding
@@ -47,29 +47,29 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
- ssl_options(), integer(), atom(), boolean(), der_cert()) ->
+ ssl_options(), binary(), boolean(), der_cert()) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
%%--------------------------------------------------------------------
client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert) ->
+ Id, Renegotiation, OwnCert) ->
%% First client hello (two sent in DTLS ) uses empty Cookie
client_hello(Host, Port, <<>>, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert).
+ Id, Renegotiation, OwnCert).
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(),
- ssl_options(), integer(), atom(), boolean(), der_cert()) ->
+ ssl_options(), binary(),boolean(), der_cert()) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
%%--------------------------------------------------------------------
-client_hello(Host, Port, Cookie, ConnectionStates,
+client_hello(_Host, _Port, Cookie, ConnectionStates,
#{versions := Versions,
ciphers := UserSuites,
fallback := Fallback} = SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert) ->
+ Id, Renegotiation, _OwnCert) ->
Version = dtls_record:highest_protocol_version(Versions),
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = maps:get(security_parameters, Pending),
@@ -78,8 +78,8 @@ client_hello(Host, Port, Cookie, ConnectionStates,
Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites,
SslOpts, ConnectionStates,
- Renegotiation, undefined),
- Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
+ Renegotiation, undefined,
+ undefined),
#client_hello{session_id = Id,
client_version = Version,
diff --git a/lib/ssl/src/dtls_listener_sup.erl b/lib/ssl/src/dtls_listener_sup.erl
index dc30696a2c..2c43c215be 100644
--- a/lib/ssl/src/dtls_listener_sup.erl
+++ b/lib/ssl/src/dtls_listener_sup.erl
@@ -29,7 +29,9 @@
%% API
-export([start_link/0]).
--export([start_child/1]).
+-export([start_child/1,
+ lookup_listner/1,
+ register_listner/2]).
%% Supervisor callback
-export([init/1]).
@@ -43,10 +45,37 @@ start_link() ->
start_child(Args) ->
supervisor:start_child(?MODULE, Args).
+lookup_listner(0) ->
+ undefined;
+lookup_listner(Port) ->
+ try ets:lookup(dtls_listener_sup, Port) of
+ [{Port, {Owner, Handler}}] ->
+ case erlang:is_process_alive(Handler) of
+ true ->
+ case erlang:is_process_alive(Owner) of
+ true ->
+ {error, already_listening};
+ false ->
+ {ok, Handler}
+ end;
+ false ->
+ ets:delete(dtls_listener_sup, Port),
+ undefined
+ end;
+ [] ->
+ undefined
+ catch _:_ ->
+ undefined
+ end.
+
+register_listner(OwnerAndListner, Port) ->
+ ets:insert(dtls_listener_sup, {Port, OwnerAndListner}).
+
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
init(_O) ->
+ ets:new(dtls_listener_sup, [named_table, public]),
RestartStrategy = simple_one_for_one,
MaxR = 0,
MaxT = 3600,
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index 94b350eaa5..d3cbc364bf 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -28,7 +28,7 @@
%% API
-export([start_link/5, active_once/3, accept/2, sockname/1, close/1,
- get_all_opts/1, get_sock_opts/2, set_sock_opts/2]).
+ get_all_opts/1, set_all_opts/2, get_sock_opts/2, set_sock_opts/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -72,6 +72,8 @@ get_all_opts(PacketSocket) ->
call(PacketSocket, get_all_opts).
set_sock_opts(PacketSocket, Opts) ->
call(PacketSocket, {set_sock_opts, Opts}).
+set_all_opts(PacketSocket, Opts) ->
+ call(PacketSocket, {set_all_opts, Opts}).
%%%===================================================================
%%% gen_server callbacks
@@ -141,7 +143,10 @@ handle_call(get_all_opts, _, #state{dtls_options = DTLSOptions,
handle_call({set_sock_opts, {SocketOpts, NewEmOpts}}, _, #state{listener = Socket, emulated_options = EmOpts0} = State) ->
set_socket_opts(Socket, SocketOpts),
EmOpts = do_set_emulated_opts(NewEmOpts, EmOpts0),
- {reply, ok, State#state{emulated_options = EmOpts}}.
+ {reply, ok, State#state{emulated_options = EmOpts}};
+handle_call({set_all_opts, {SocketOpts, NewEmOpts, SslOpts}}, _, #state{listener = Socket} = State) ->
+ set_socket_opts(Socket, SocketOpts),
+ {reply, ok, State#state{emulated_options = NewEmOpts, dtls_options = SslOpts}}.
handle_cast({active_once, Client, Pid}, State0) ->
State = handle_active_once(Client, Pid, State0),
diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl
index b305d08f70..f7b1968064 100644
--- a/lib/ssl/src/dtls_socket.erl
+++ b/lib/ssl/src/dtls_socket.erl
@@ -31,23 +31,34 @@ send(Transport, {{IP,Port},Socket}, Data) ->
listen(Port, #config{transport_info = TransportInfo,
ssl = SslOpts,
- emulated = EmOpts,
+ emulated = EmOpts0,
inet_user = Options} = Config) ->
-
- case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}),
- Options ++ internal_inet_values(), SslOpts]) of
- {ok, Pid} ->
- Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}},
- check_active_n(EmOpts, Socket),
+ Result = case dtls_listener_sup:lookup_listner(Port) of
+ undefined ->
+ Result0 = {ok, Listner0} = dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts0, #socket_options{}),
+ Options ++ internal_inet_values(), SslOpts]),
+ dtls_listener_sup:register_listner({self(), Listner0}, Port),
+ Result0;
+ {ok, Listner0} = Result0 ->
+ dtls_packet_demux:set_all_opts(Listner0, {Options, emulated_socket_options(EmOpts0, #socket_options{}), SslOpts}),
+ dtls_listener_sup:register_listner({self(), Listner0}, Port),
+ Result0;
+ Result0 ->
+ Result0
+ end,
+ case Result of
+ {ok, Listner} ->
+ Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Listner, Port}}}},
+ check_active_n(EmOpts0, Socket),
{ok, Socket};
- Err = {error, _} ->
- Err
+ Err ->
+ Err
end.
accept(dtls, #config{transport_info = {Transport,_,_,_,_},
- connection_cb = ConnectionCb,
- dtls_handler = {Listner, _}}, _Timeout) ->
+ connection_cb = ConnectionCb,
+ dtls_handler = {Listner, _}}, _Timeout) ->
case dtls_packet_demux:accept(Listner, self()) of
{ok, Pid, Socket} ->
{ok, socket([Pid], Transport, {Listner, Socket}, ConnectionCb)};
diff --git a/lib/ssl/src/dtls_sup.erl b/lib/ssl/src/dtls_sup.erl
new file mode 100644
index 0000000000..2e72c10ba0
--- /dev/null
+++ b/lib/ssl/src/dtls_sup.erl
@@ -0,0 +1,76 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(dtls_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+ DTLSConnetionManager = dtls_connection_manager_child_spec(),
+ DTLSListeners = dtls_listeners_spec(),
+
+ {ok, {{one_for_one, 10, 3600}, [DTLSConnetionManager,
+ DTLSListeners
+ ]}}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+dtls_connection_manager_child_spec() ->
+ Name = dtls_connection,
+ StartFunc = {dtls_connection_sup, start_link, []},
+ Restart = permanent,
+
+ Shutdown = 4000,
+ Modules = [dtls_connection_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+dtls_listeners_spec() ->
+ Name = dtls_listener,
+ StartFunc = {dtls_listener_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index e7a4d73ec4..c45e6bcf9a 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -14,6 +14,10 @@
ssl_v3,
tls_connection_sup,
tls_sender,
+ tls_server_sup,
+ tls_server_session_ticket_sup,
+ tls_server_session_ticket,
+ tls_sup,
ssl_dh_groups,
%% DTLS
dtls_connection,
@@ -24,6 +28,7 @@
dtls_connection_sup,
dtls_packet_demux,
dtls_listener_sup,
+ dtls_sup,
%% API
ssl, %% Main API
ssl_session_cache_api,
@@ -37,6 +42,8 @@
ssl_srp_primes,
ssl_alert,
ssl_listen_tracker_sup, %% may be used by DTLS over SCTP
+ tls_bloom_filter,
+ tls_client_ticket_store,
%% Erlang Distribution over SSL/TLS
inet_tls_dist,
inet6_tls_dist,
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 0d6501b5ee..aae79d7625 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -92,13 +92,12 @@
connection_information/1,
connection_information/2]).
%% Misc
--export([handle_options/2,
+-export([handle_options/2,
+ handle_options/3,
tls_version/1,
- new_ssl_options/3,
suite_to_str/1,
suite_to_openssl_str/1,
- str_to_suite/1,
- options_to_map/1]).
+ str_to_suite/1]).
-deprecated({ssl_accept, 1, eventually}).
-deprecated({ssl_accept, 2, eventually}).
@@ -439,7 +438,6 @@
elliptic_curves => [public_key:oid()],
sni => hostname()}. % exported
%% -------------------------------------------------------------------------------------------------------
--define(SSL_OPTIONS, record_info(fields, ssl_options)).
%%%--------------------------------------------------------------------
%%% API
@@ -501,7 +499,8 @@ connect(Socket, SslOptions) when is_port(Socket) ->
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- CbInfo = handle_option(cb_info, SslOptions0, default_cb_info(tls)),
+ CbInfo = handle_option_cb_info(SslOptions0, tls),
+
Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
@@ -687,9 +686,10 @@ handshake(ListenSocket, SslOptions) when is_port(ListenSocket) ->
handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
(Timeout == infinity)->
handshake(Socket, Timeout);
-handshake(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts, Timeout) when
+handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
try
+ Tracker = proplists:get_value(option_tracker, Trackers),
{ok, EmOpts, _} = tls_socket:get_all_opts(Tracker),
ssl_connection:handshake(Socket, {SslOpts,
tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
@@ -707,7 +707,8 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
end;
handshake(Socket, SslOptions, Timeout) when is_port(Socket),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- CbInfo = handle_option(cb_info, SslOptions, default_cb_info(tls)),
+ CbInfo = handle_option_cb_info(SslOptions, tls),
+
Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
@@ -1461,6 +1462,7 @@ do_listen(Port, Config, dtls_connection) ->
dtls_socket:listen(Port, Config).
+
-spec handle_options([any()], client | server) -> {ok, #config{}};
([any()], ssl_options()) -> ssl_options().
@@ -1468,255 +1470,360 @@ handle_options(Opts, Role) ->
handle_options(Opts, Role, undefined).
-%% Handle ssl options at handshake_continue
-handle_options(Opts0, #{protocol := Protocol,
- cacerts := CaCerts0,
- cacertfile := CaCertFile0} = InheritedSslOpts, _) ->
- RecordCB = record_cb(Protocol),
- CaCerts = handle_option(cacerts, Opts0, CaCerts0),
- {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder,
- VerifyClientOnce} = handle_verify_options(Opts0, CaCerts),
- CaCertFile = case proplists:get_value(cacertfile, Opts0, CaCertFile0) of
- undefined ->
- CaCertDefault;
- CAFile ->
- CAFile
- end,
-
- NewVerifyOpts = InheritedSslOpts#{cacerts => CaCerts,
- cacertfile => CaCertFile,
- verify => Verify,
- verify_fun => VerifyFun,
- partial_chain => PartialChainHanlder,
- fail_if_no_peer_cert => FailIfNoPeerCert,
- verify_client_once => VerifyClientOnce},
- SslOpts1 = lists:foldl(fun(Key, PropList) ->
- proplists:delete(Key, PropList)
- end, Opts0, [cacerts, cacertfile, verify, verify_fun, partial_chain,
- fail_if_no_peer_cert, verify_client_once]),
- case handle_option(versions, SslOpts1, []) of
- [] ->
- new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB);
- Value ->
- Versions0 = [RecordCB:protocol_version(Vsn) || Vsn <- Value],
- Versions1 = lists:sort(fun RecordCB:is_higher/2, Versions0),
- new_ssl_options(proplists:delete(versions, SslOpts1),
- NewVerifyOpts#{versions => Versions1}, record_cb(Protocol))
- end;
-
+%% Handle ssl options at handshake, handshake_continue
+handle_options(Opts0, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
+ {SslOpts, _} = expand_options(Opts0, ?RULES),
+ process_options(SslOpts, InheritedSslOpts, #{role => Role,
+ rules => ?RULES});
%% Handle all options in listen, connect and handshake
handle_options(Opts0, Role, Host) ->
- Opts = proplists:expand([{binary, [{mode, binary}]},
- {list, [{mode, list}]}], Opts0),
- assert_proplist(Opts),
- RecordCb = record_cb(Opts),
- CaCerts = handle_option(cacerts, Opts, undefined),
-
- {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} =
- handle_verify_options(Opts, CaCerts),
-
- CertFile = handle_option(certfile, Opts, <<>>),
-
- [HighestVersion|_] = Versions =
- case handle_option(versions, Opts, []) of
- [] ->
- RecordCb:supported_protocol_versions();
- Vsns ->
- Versions0 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns],
- lists:sort(fun RecordCb:is_higher/2, Versions0)
- end,
-
- Protocol = handle_option(protocol, Opts, tls),
+ {SslOpts0, SockOpts} = expand_options(Opts0, ?RULES),
+
+ %% Ensure all options are evaluated at startup
+ SslOpts1 = add_missing_options(SslOpts0, ?RULES),
+ SslOpts = #{protocol := Protocol,
+ versions := Versions}
+ = process_options(SslOpts1,
+ #{},
+ #{role => Role,
+ host => Host,
+ rules => ?RULES}),
case Versions of
[{3, 0}] ->
- reject_alpn_next_prot_options(Opts);
+ reject_alpn_next_prot_options(SslOpts0);
_ ->
ok
end,
-
- SSLOptions0 = #ssl_options{
- versions = Versions,
- verify = validate_option(verify, Verify),
- verify_fun = VerifyFun,
- partial_chain = PartialChainHanlder,
- fail_if_no_peer_cert = FailIfNoPeerCert,
- verify_client_once = VerifyClientOnce,
- depth = handle_option(depth, Opts, 1),
- cert = handle_option(cert, Opts, undefined),
- certfile = CertFile,
- key = handle_option(key, Opts, undefined),
- keyfile = handle_option(keyfile, Opts, CertFile),
- password = handle_option(password, Opts, ""),
- cacerts = CaCerts,
- cacertfile = handle_option(cacertfile, Opts, CaCertDefault),
- dh = handle_option(dh, Opts, undefined),
- dhfile = handle_option(dhfile, Opts, undefined),
- user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined),
- psk_identity = handle_option(psk_identity, Opts, undefined),
- srp_identity = handle_option(srp_identity, Opts, undefined),
- ciphers = handle_option(ciphers, Opts, undefined, undefined, undefined, HighestVersion),
-
- eccs = handle_option(eccs, Opts, undefined, undefined, undefined, HighestVersion),
-
- supported_groups = handle_option(supported_groups, Opts, undefined, undefined, undefined,
- HighestVersion),
- signature_algs = handle_option(signature_algs, Opts, undefined, Role, undefined, HighestVersion),
- signature_algs_cert = handle_option(signature_algs_cert, Opts, undefined, undefined, undefined,
- HighestVersion),
- reuse_sessions = handle_option(reuse_sessions, Opts, undefined, Role),
-
- reuse_session = handle_option(reuse_session, Opts, undefined, Role),
-
- secure_renegotiate = handle_option(secure_renegotiate, Opts, true),
- client_renegotiation = handle_option(client_renegotiation, Opts, undefined, Role),
- renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
- hibernate_after = handle_option(hibernate_after, Opts, infinity),
- erl_dist = handle_option(erl_dist, Opts, false),
- alpn_advertised_protocols =
- handle_option(alpn_advertised_protocols, Opts, undefined),
- alpn_preferred_protocols =
- handle_option(alpn_preferred_protocols, Opts, undefined),
- next_protocols_advertised =
- handle_option(next_protocols_advertised, Opts, undefined),
- next_protocol_selector =
- handle_option(next_protocol_selector, Opts, undefined),
- server_name_indication =
- handle_option(server_name_indication, Opts, undefined, Role, Host),
- sni_hosts = handle_option(sni_hosts, Opts, []),
- sni_fun = handle_option(sni_fun, Opts, undefined),
- honor_cipher_order = handle_option(honor_cipher_order, Opts, undefined, Role),
- honor_ecc_order = handle_option(honor_ecc_order, Opts, undefined, Role),
- protocol = Protocol,
- padding_check = proplists:get_value(padding_check, Opts, true),
- beast_mitigation = handle_option(beast_mitigation, Opts, one_n_minus_one),
- fallback = handle_option(fallback, Opts, undefined, Role),
-
- crl_check = handle_option(crl_check, Opts, false),
- crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}),
- max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE),
- handshake = handle_option(handshake, Opts, full),
- customize_hostname_check = handle_option(customize_hostname_check, Opts, [])
- },
- LogLevel = handle_option(log_alert, Opts, true),
- SSLOptions1 = SSLOptions0#ssl_options{
- log_level = handle_option(log_level, Opts, LogLevel)
- },
-
- CbInfo = handle_option(cb_info, Opts, default_cb_info(Protocol)),
-
- SockOpts = lists:foldl(fun(Key, PropList) ->
- proplists:delete(Key, PropList)
- end, Opts, ?SSL_OPTIONS ++
- [ssl_imp, %% TODO: remove ssl_imp
- client_preferred_next_protocols, %% next_protocol_selector
- log_alert,
- cb_info]),
+ %% Handle special options
{Sock, Emulated} = emulated_options(Protocol, SockOpts),
- ConnetionCb = connection_cb(Opts),
- SSLOptions = options_to_map(SSLOptions1),
- {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = Sock,
- inet_user = Sock, transport_info = CbInfo, connection_cb = ConnetionCb
- }}.
-
-%% handle_option(OptionName, Opts, Default, Role, Role) ->
-%% handle_option(OptionName, Opts, Default);
-%% handle_option(_, _, undefined = Value, _, _) ->
-%% Value.
-
-handle_option(OptionName, Opts, Default) ->
- handle_option(OptionName, Opts, Default, undefined, undefined, undefined).
+ ConnetionCb = connection_cb(Protocol),
+ CbInfo = handle_option_cb_info(Opts0, Protocol),
+
+ {ok, #config{
+ ssl = SslOpts,
+ emulated = Emulated,
+ inet_ssl = Sock,
+ inet_user = Sock,
+ transport_info = CbInfo,
+ connection_cb = ConnetionCb
+ }}.
+
+
+%% process_options(SSLOptions, OptionsMap, Env) where
+%% SSLOptions is the following tuple:
+%% {InOptions, SkippedOptions, Counter}
%%
-handle_option(OptionName, Opts, Default, Role) ->
- handle_option(OptionName, Opts, Default, Role, undefined, undefined).
-%%
-handle_option(OptionName, Opts, Default, Role, Host) ->
- handle_option(OptionName, Opts, Default, Role, Host, undefined).
-%%
-handle_option(sni_fun, Opts, Default, _Role, _Host, _Version) ->
- OptFun = validate_option(sni_fun,
- proplists:get_value(sni_fun, Opts, Default)),
- OptHosts = proplists:get_value(sni_hosts, Opts, undefined),
- case {OptFun, OptHosts} of
- {Default, _} ->
- Default;
- {_, undefined} ->
- OptFun;
+%% The list of options is processed in multiple passes. When
+%% processing an option all dependencies must already be resolved.
+%% If there are unresolved dependencies the option will be
+%% skipped and processed in a subsequent pass.
+%% Counter is equal to the number of unprocessed options at
+%% the beginning of a pass. Its value must monotonically decrease
+%% after each successful pass.
+%% If the value of the counter is unchanged at the end of a pass,
+%% the processing stops due to faulty input data.
+process_options({[], [], _}, OptionsMap, _Env) ->
+ OptionsMap;
+process_options({[], [_|_] = Skipped, Counter}, OptionsMap, Env)
+ when length(Skipped) < Counter ->
+ %% Continue handling options if current pass was successful
+ process_options({Skipped, [], length(Skipped)}, OptionsMap, Env);
+process_options({[], [_|_], _Counter}, _OptionsMap, _Env) ->
+ throw({error, faulty_configuration});
+process_options({[{K0,V} = E|T], S, Counter}, OptionsMap0, Env) ->
+ K = maybe_map_key_internal(K0),
+ case check_dependencies(K, OptionsMap0, Env) of
+ true ->
+ OptionsMap = handle_option(K, V, OptionsMap0, Env),
+ process_options({T, S, Counter}, OptionsMap, Env);
+ false ->
+ %% Skip option for next pass
+ process_options({T, [E|S], Counter}, OptionsMap0, Env)
+ end.
+
+
+handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
+ verify := Verify,
+ verify_fun := VerifyFun} = OptionsMap, _Env)
+ when Verify =:= verify_none orelse
+ Verify =:= 0 ->
+ Value = validate_option(Option, ca_cert_default(verify_none, VerifyFun, CaCerts)),
+ OptionsMap#{Option => Value};
+handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts,
+ verify := Verify,
+ verify_fun := VerifyFun} = OptionsMap, _Env)
+ when Verify =:= verify_peer orelse
+ Verify =:= 1 orelse
+ Verify =:= 2 ->
+ Value = validate_option(Option, ca_cert_default(verify_peer, VerifyFun, CaCerts)),
+ OptionsMap#{Option => Value};
+handle_option(cacertfile = Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(ciphers = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := Rules}) ->
+ Value = handle_cipher_option(default_value(Option, Rules), HighestVersion),
+ OptionsMap#{Option => Value};
+handle_option(ciphers = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+ Value = handle_cipher_option(Value0, HighestVersion),
+ OptionsMap#{Option => Value};
+handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) ->
+ Value = default_option_role(server, true, Role),
+ OptionsMap#{Option => Value};
+handle_option(client_renegotiation = Option, Value0, OptionsMap, #{role := Role}) ->
+ assert_role(server_only, Role, Option, Value0),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
+ Value = handle_eccs_option(eccs(), HighestVersion),
+ OptionsMap#{Option => Value};
+handle_option(eccs = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+ Value = handle_eccs_option(Value0, HighestVersion),
+ OptionsMap#{Option => Value};
+handle_option(fallback = Option, unbound, OptionsMap, #{role := Role}) ->
+ Value = default_option_role(client, false, Role),
+ OptionsMap#{Option => Value};
+handle_option(fallback = Option, Value0, OptionsMap, #{role := Role}) ->
+ assert_role(client_only, Role, Option, Value0),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(honor_cipher_order = Option, unbound, OptionsMap, #{role := Role}) ->
+ Value = default_option_role(server, false, Role),
+ OptionsMap#{Option => Value};
+handle_option(honor_cipher_order = Option, Value0, OptionsMap, #{role := Role}) ->
+ assert_role(server_only, Role, Option, Value0),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(honor_ecc_order = Option, unbound, OptionsMap, #{role := Role}) ->
+ Value = default_option_role(server, false, Role),
+ OptionsMap#{Option => Value};
+handle_option(honor_ecc_order = Option, Value0, OptionsMap, #{role := Role}) ->
+ assert_role(server_only, Role, Option, Value0),
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(keyfile = Option, unbound, #{certfile := CertFile} = OptionsMap, _Env) ->
+ Value = validate_option(Option, CertFile),
+ OptionsMap#{Option => Value};
+handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = default_value(Option, Rules),
+ OptionsMap#{Option => Value};
+handle_option(next_protocol_selector = Option, Value0, OptionsMap, _Env) ->
+ Value = make_next_protocol_selector(
+ validate_option(client_preferred_next_protocols, Value0)),
+ OptionsMap#{Option => Value};
+handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
+ Value =
+ case Role of
+ client ->
+ undefined;
+ server ->
+ fun(_, _, _, _) -> true end
+ end,
+ OptionsMap#{Option => Value};
+handle_option(reuse_session = Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+%% TODO: validate based on role
+handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(reuse_sessions = Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(anti_replay = Option, Value0,
+ #{session_tickets := SessionTickets} = OptionsMap, #{rules := Rules}) ->
+ case SessionTickets of
+ stateless ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
_ ->
- throw({error, {conflict_options, [sni_fun, sni_hosts]}})
- end;
-handle_option(cb_info, Opts, Default, _Role, _Host, _Version) ->
- CbInfo = proplists:get_value(cb_info, Opts, Default),
- true = validate_option(cb_info, CbInfo),
- handle_cb_info(CbInfo, Default);
-handle_option(ciphers = Key, Opts, _Default, _Role, _Host, HighestVersion) ->
- handle_cipher_option(proplists:get_value(Key, Opts, []),
- HighestVersion);
-handle_option(eccs = Key, Opts, _Default, _Role, _Host, HighestVersion) ->
- handle_eccs_option(proplists:get_value(Key, Opts, eccs()),
- HighestVersion);
-handle_option(supported_groups = Key, Opts, _Default, _Role, _Host, HighestVersion) ->
- handle_supported_groups_option(proplists:get_value(Key, Opts, groups(default)),
- HighestVersion);
-handle_option(signature_algs = Key, Opts, _Default, Role, _Host, HighestVersion) ->
- handle_hashsigns_option(
- proplists:get_value(Key,
- Opts,
- default_option_role_sign_algs(server,
- tls_v1:default_signature_algs(HighestVersion),
- Role,
- HighestVersion)),
- tls_version(HighestVersion));
-handle_option(signature_algs_cert = Key, Opts, _Default, _Role, _Host, HighestVersion) ->
- handle_signature_algorithms_option(
- proplists:get_value(Key,
- Opts,
- undefined), %% Do not send by default
- tls_version(HighestVersion));
-handle_option(reuse_sessions = Key, Opts, _Default, client, _Host, _Version) ->
- Value = proplists:get_value(Key, Opts, true),
- validate_option(Key, Value),
- Value;
-handle_option(reuse_sessions = Key, Opts0, _Default, server, _Host, _Version) ->
- Opts = proplists:delete({Key, save}, Opts0),
- Value = proplists:get_value(Key, Opts, true),
- validate_option(Key, Value),
- Value;
-handle_option(reuse_session = Key, Opts, _Default, client, _Host, _Version) ->
- Value = proplists:get_value(Key, Opts, undefined),
- validate_option(Key, Value),
- Value;
-handle_option(reuse_session = Key, Opts, _Default, server, _Host, _Version) ->
- ReuseSessionFun = fun(_, _, _, _) -> true end,
- Value = proplists:get_value(Key, Opts, ReuseSessionFun),
- validate_option(Key, Value),
- Value;
-handle_option(fallback = Key, Opts, _Default, Role, _Host, _Version) ->
- Value = proplists:get_value(Key, Opts, default_option_role(client, false, Role)),
- assert_role(client_only, Role, Key, Value),
- validate_option(Key, Value);
-handle_option(client_renegotiation = Key, Opts, _Default, Role, _Host, _Version) ->
- Value = proplists:get_value(Key, Opts, default_option_role(server, true, Role)),
- assert_role(server_only, Role, Key, Value),
- validate_option(Key, Value);
-handle_option(honor_cipher_order = Key, Opts, _Default, Role, _Host, _Version) ->
- Value = proplists:get_value(Key, Opts, default_option_role(server, false, Role)),
- assert_role(server_only, Role, Key, Value),
- validate_option(Key, Value);
-handle_option(honor_ecc_order = Key, Opts, _Default, Role, _Host, _Version) ->
- Value = proplists:get_value(Key, Opts, default_option_role(server, false, Role)),
- assert_role(server_only, Role, Key, Value),
- validate_option(Key, Value);
-handle_option(next_protocol_selector = _Key, Opts, _Default, _Role, _Host, _Version) ->
- make_next_protocol_selector(
- handle_option(client_preferred_next_protocols, Opts, undefined, undefined, undefined, undefined));
-handle_option(server_name_indication = Key, Opts, _Default, Role, Host, _Version) ->
- Default = default_option_role(client, server_name_indication_default(Host), Role),
- validate_option(Key, proplists:get_value(Key, Opts, Default));
-handle_option(OptionName, Opts, Default, _Role, _Host, _Version) ->
- validate_option(OptionName,
- proplists:get_value(OptionName, Opts, Default)).
+ OptionsMap#{Option => default_value(Option, Rules)}
+end;
+handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host,
+ role := Role}) ->
+ Value = default_option_role(client, server_name_indication_default(Host), Role),
+ OptionsMap#{Option => Value};
+handle_option(server_name_indication = Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{role := Role}) ->
+ Value =
+ handle_hashsigns_option(
+ default_option_role_sign_algs(
+ server,
+ tls_v1:default_signature_algs(HighestVersion),
+ Role,
+ HighestVersion),
+ tls_version(HighestVersion)),
+ OptionsMap#{Option => Value};
+handle_option(signature_algs = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+ Value = handle_hashsigns_option(Value0, tls_version(HighestVersion)),
+ OptionsMap#{Option => Value};
+handle_option(signature_algs_cert = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+ %% Do not send by default
+ Value = handle_signature_algorithms_option(undefined, tls_version(HighestVersion)),
+ OptionsMap#{Option => Value};
+handle_option(signature_algs_cert = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+ Value = handle_signature_algorithms_option(Value0, tls_version(HighestVersion)),
+ OptionsMap#{Option => Value};
+handle_option(sni_fun = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = default_value(Option, Rules),
+ OptionsMap#{Option => Value};
+handle_option(sni_fun = Option, Value0, OptionsMap, _Env) ->
+ validate_option(Option, Value0),
+ OptHosts = maps:get(sni_hosts, OptionsMap, undefined),
+ Value =
+ case {Value0, OptHosts} of
+ {undefined, _} ->
+ Value0;
+ {_, []} ->
+ Value0;
+ _ ->
+ throw({error, {conflict_options, [sni_fun, sni_hosts]}})
+ end,
+ OptionsMap#{Option => Value};
+handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) ->
+ Value = handle_supported_groups_option(groups(default), HighestVersion),
+ OptionsMap#{Option => Value};
+handle_option(supported_groups = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) ->
+ Value = handle_supported_groups_option(Value0, HighestVersion),
+ OptionsMap#{Option => Value};
+handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) ->
+ handle_verify_option(default_value(Option, Rules), OptionsMap);
+handle_option(verify = _Option, Value, OptionsMap, _Env) ->
+ handle_verify_option(Value, OptionsMap);
+
+handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules})
+ when Verify =:= verify_none orelse
+ Verify =:= 0 ->
+ OptionsMap#{Option => default_value(Option, Rules)};
+handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env)
+ when Verify =:= verify_peer orelse
+ Verify =:= 1 orelse
+ Verify =:= 2 ->
+ OptionsMap#{Option => undefined};
+handle_option(verify_fun = Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value};
+handle_option(versions = Option, unbound, #{protocol := Protocol} = OptionsMap, _Env) ->
+ RecordCb = record_cb(Protocol),
+ Vsns0 = RecordCb:supported_protocol_versions(),
+ Value = lists:sort(fun RecordCb:is_higher/2, Vsns0),
+ OptionsMap#{Option => Value};
+handle_option(versions = Option, Vsns0, #{protocol := Protocol} = OptionsMap, _Env) ->
+ validate_option(versions, Vsns0),
+ RecordCb = record_cb(Protocol),
+ Vsns1 = [RecordCb:protocol_version(Vsn) || Vsn <- Vsns0],
+ Value = lists:sort(fun RecordCb:is_higher/2, Vsns1),
+ OptionsMap#{Option => Value};
+%% Special options
+handle_option(cb_info = Option, unbound, #{protocol := Protocol} = OptionsMap, _Env) ->
+ Default = default_cb_info(Protocol),
+ validate_option(Option, Default),
+ Value = handle_cb_info(Default),
+ OptionsMap#{Option => Value};
+handle_option(cb_info = Option, Value0, OptionsMap, _Env) ->
+ validate_option(Option, Value0),
+ Value = handle_cb_info(Value0),
+ OptionsMap#{Option => Value};
+%% Generic case
+handle_option(Option, unbound, OptionsMap, #{rules := Rules}) ->
+ Value = validate_option(Option, default_value(Option, Rules)),
+ OptionsMap#{Option => Value};
+handle_option(Option, Value0, OptionsMap, _Env) ->
+ Value = validate_option(Option, Value0),
+ OptionsMap#{Option => Value}.
+
+handle_option_cb_info(Options, Protocol) ->
+ Value = proplists:get_value(cb_info, Options, default_cb_info(Protocol)),
+ #{cb_info := CbInfo} = handle_option(cb_info, Value, #{protocol => Protocol}, #{}),
+ CbInfo.
+
+
+maybe_map_key_internal(client_preferred_next_protocols) ->
+ next_protocol_selector;
+maybe_map_key_internal(K) ->
+ K.
+
+
+maybe_map_key_external(next_protocol_selector) ->
+ client_preferred_next_protocols;
+maybe_map_key_external(K) ->
+ K.
+
+
+check_dependencies(K, OptionsMap, Env) ->
+ Rules = maps:get(rules, Env),
+ Deps = get_dependencies(K, Rules),
+ case Deps of
+ [] ->
+ true;
+ L ->
+ option_already_defined(K,OptionsMap) orelse
+ dependecies_already_defined(L, OptionsMap)
+ end.
+
+
+%% Handle options that are not present in the map
+get_dependencies(K, _) when K =:= cb_info orelse K =:= log_alert->
+ [];
+get_dependencies(K, Rules) ->
+ {_, Deps} = maps:get(K, Rules),
+ Deps.
+
+
+option_already_defined(K, Map) ->
+ maps:get(K, Map, unbound) =/= unbound.
+
+
+dependecies_already_defined(L, OptionsMap) ->
+ Fun = fun (E) -> option_already_defined(E, OptionsMap) end,
+ lists:all(Fun, L).
+
+
+expand_options(Opts0, Rules) ->
+ Opts1 = proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Opts0),
+ assert_proplist(Opts1),
+
+ %% Remove depricated ssl_imp option
+ Opts = proplists:delete(ssl_imp, Opts1),
+ AllOpts = maps:keys(Rules),
+ SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end,
+ Opts,
+ AllOpts ++
+ [ssl_imp, %% TODO: remove ssl_imp
+ cb_info,
+ client_preferred_next_protocols, %% next_protocol_selector
+ log_alert]), %% obsoleted by log_level
+
+ SslOpts = {Opts -- SockOpts, [], length(Opts -- SockOpts)},
+ {SslOpts, SockOpts}.
+
+
+add_missing_options({L0, S, _C}, Rules) ->
+ Fun = fun(K0, Acc) ->
+ K = maybe_map_key_external(K0),
+ case proplists:is_defined(K, Acc) of
+ true ->
+ Acc;
+ false ->
+ Default = unbound,
+ [{K, Default}|Acc]
+ end
+ end,
+ AllOpts = maps:keys(Rules),
+ L = lists:foldl(Fun, L0, AllOpts),
+ {L, S, length(L)}.
+
+
+default_value(Key, Rules) ->
+ {Default, _} = maps:get(Key, Rules, {undefined, []}),
+ Default.
assert_role(client_only, client, _, _) ->
@@ -1961,23 +2068,45 @@ validate_option(handshake, full = Value) ->
Value;
validate_option(customize_hostname_check, Value) when is_list(Value) ->
Value;
-validate_option(cb_info, {V1, V2, V3, V4}) when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4)
+validate_option(cb_info, {V1, V2, V3, V4} = Value) when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4)
->
- true;
-validate_option(cb_info, {V1, V2, V3, V4, V5}) when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4),
- is_atom(V5)
+ Value;
+validate_option(cb_info, {V1, V2, V3, V4, V5} = Value) when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4),
+ is_atom(V5)
->
- true;
-validate_option(cb_info, _) ->
- false;
+ Value;
+validate_option(use_ticket, Value) when is_list(Value) ->
+ Value;
+validate_option(session_tickets, Value) when Value =:= disabled orelse
+ Value =:= enabled orelse
+ Value =:= auto orelse
+ Value =:= stateless orelse
+ Value =:= stateful ->
+ Value;
+validate_option(anti_replay, '10k') ->
+ %% n = 10000
+ %% p = 0.030003564 (1 in 33)
+ %% m = 72985 (8.91KiB)
+ %% k = 5
+ {10, 5, 72985};
+validate_option(anti_replay, '100k') ->
+ %% n = 100000
+ %% p = 0.03000428 (1 in 33)
+ %% m = 729845 (89.09KiB)
+ %% k = 5
+ {10, 5, 729845};
+validate_option(anti_replay, Value) when (is_tuple(Value) andalso
+ tuple_size(Value) =:= 3) ->
+ Value;
validate_option(Opt, undefined = Value) ->
- case lists:member(Opt, ?SSL_OPTIONS) of
+ AllOpts = maps:keys(?RULES),
+ case lists:member(Opt, AllOpts) of
true ->
Value;
false ->
@@ -1986,9 +2115,9 @@ validate_option(Opt, undefined = Value) ->
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
-handle_cb_info({V1, V2, V3, V4}, {_,_,_,_,_}) ->
+handle_cb_info({V1, V2, V3, V4}) ->
{V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")};
-handle_cb_info(CbInfo, _) ->
+handle_cb_info(CbInfo) ->
CbInfo.
handle_hashsigns_option(Value, Version) when is_list(Value)
@@ -2275,159 +2404,22 @@ assert_proplist([inet6 | Rest]) ->
assert_proplist([Value | _]) ->
throw({option_not_a_key_value_tuple, Value}).
-new_ssl_options([], #{} = Opts, _) ->
- Opts;
-new_ssl_options([{verify_client_once, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{verify_client_once =>
- validate_option(verify_client_once, Value)}, RecordCB);
-new_ssl_options([{depth, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{depth => validate_option(depth, Value)}, RecordCB);
-new_ssl_options([{cert, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{cert => validate_option(cert, Value)}, RecordCB);
-new_ssl_options([{certfile, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{certfile => validate_option(certfile, Value)}, RecordCB);
-new_ssl_options([{key, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{key => validate_option(key, Value)}, RecordCB);
-new_ssl_options([{keyfile, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{keyfile => validate_option(keyfile, Value)}, RecordCB);
-new_ssl_options([{password, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{password => validate_option(password, Value)}, RecordCB);
-new_ssl_options([{dh, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{dh => validate_option(dh, Value)}, RecordCB);
-new_ssl_options([{dhfile, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{dhfile => validate_option(dhfile, Value)}, RecordCB);
-new_ssl_options([{user_lookup_fun, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{user_lookup_fun => validate_option(user_lookup_fun, Value)}, RecordCB);
-new_ssl_options([{psk_identity, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{psk_identity => validate_option(psk_identity, Value)}, RecordCB);
-new_ssl_options([{srp_identity, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{srp_identity => validate_option(srp_identity, Value)}, RecordCB);
-new_ssl_options([{ciphers, Value} | Rest], #{versions := Versions} = Opts, RecordCB) ->
- Ciphers = handle_cipher_option(Value, RecordCB:highest_protocol_version(Versions)),
- new_ssl_options(Rest, Opts#{ciphers => Ciphers}, RecordCB);
-new_ssl_options([{reuse_session, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{reuse_session => validate_option(reuse_session, Value)}, RecordCB);
-new_ssl_options([{reuse_sessions, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{reuse_sessions => validate_option(reuse_sessions, Value)}, RecordCB);
-new_ssl_options([{ssl_imp, _Value} | Rest], #{} = Opts, RecordCB) -> %% Not used backwards compatibility
- new_ssl_options(Rest, Opts, RecordCB);
-new_ssl_options([{renegotiate_at, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{renegotiate_at => validate_option(renegotiate_at, Value)}, RecordCB);
-new_ssl_options([{secure_renegotiate, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{secure_renegotiate => validate_option(secure_renegotiate, Value)}, RecordCB);
-new_ssl_options([{client_renegotiation, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{client_renegotiation => validate_option(client_renegotiation, Value)}, RecordCB);
-new_ssl_options([{hibernate_after, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{hibernate_after => validate_option(hibernate_after, Value)}, RecordCB);
-new_ssl_options([{alpn_advertised_protocols, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{alpn_advertised_protocols => validate_option(alpn_advertised_protocols, Value)},
- RecordCB);
-new_ssl_options([{alpn_preferred_protocols, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{alpn_preferred_protocols => validate_option(alpn_preferred_protocols, Value)},
- RecordCB);
-new_ssl_options([{next_protocols_advertised, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{next_protocols_advertised => validate_option(next_protocols_advertised, Value)},
- RecordCB);
-new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest,
- Opts#{next_protocol_selector =>
- make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))},
- RecordCB);
-new_ssl_options([{log_alert, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{log_level => validate_option(log_alert, Value)}, RecordCB);
-new_ssl_options([{log_level, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{log_level => validate_option(log_level, Value)}, RecordCB);
-new_ssl_options([{server_name_indication, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{server_name_indication => validate_option(server_name_indication, Value)}, RecordCB);
-new_ssl_options([{honor_cipher_order, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{honor_cipher_order => validate_option(honor_cipher_order, Value)}, RecordCB);
-new_ssl_options([{honor_ecc_order, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest, Opts#{honor_ecc_order => validate_option(honor_ecc_order, Value)}, RecordCB);
-new_ssl_options([{eccs, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest,
- Opts#{eccs => handle_eccs_option(Value, RecordCB:highest_protocol_version())
- },
- RecordCB);
-new_ssl_options([{supported_groups, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest,
- Opts#{supported_groups =>
- handle_supported_groups_option(Value, RecordCB:highest_protocol_version())
- },
- RecordCB);
-new_ssl_options([{signature_algs, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(Rest,
- Opts#{signature_algs =>
- handle_hashsigns_option(Value,
- tls_version(RecordCB:highest_protocol_version()))},
- RecordCB);
-new_ssl_options([{signature_algs_cert, Value} | Rest], #{} = Opts, RecordCB) ->
- new_ssl_options(
- Rest,
- Opts#{signature_algs_cert =>
- handle_signature_algorithms_option(
- Value,
- tls_version(RecordCB:highest_protocol_version()))},
- RecordCB);
-new_ssl_options([{protocol, dtls = Value} | Rest], #{} = Opts, dtls_record = RecordCB) ->
- new_ssl_options(Rest, Opts#{protocol => Value}, RecordCB);
-new_ssl_options([{protocol, tls = Value} | Rest], #{} = Opts, tls_record = RecordCB) ->
- new_ssl_options(Rest, Opts#{protocol => Value}, RecordCB);
-new_ssl_options([{Key, Value} | _Rest], #{}, _) ->
- throw({error, {options, {Key, Value}}}).
-
-
-handle_verify_options(Opts, CaCerts) ->
- DefaultVerifyNoneFun =
- {fun(_,{bad_cert, _}, UserState) ->
- {valid, UserState};
- (_,{extension, #'Extension'{critical = true}}, UserState) ->
- %% This extension is marked as critical, so
- %% certificate verification should fail if we don't
- %% understand the extension. However, this is
- %% `verify_none', so let's accept it anyway.
- {valid, UserState};
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, []},
- VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun),
-
- UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
- UserVerifyFun = handle_option(verify_fun, Opts, undefined),
-
- PartialChainHanlder = handle_option(partial_chain, Opts,
- fun(_) -> unknown_ca end),
-
- VerifyClientOnce = handle_option(verify_client_once, Opts, false),
-
- %% Handle 0, 1, 2 for backwards compatibility
- case proplists:get_value(verify, Opts, verify_none) of
- 0 ->
- {verify_none, false,
- ca_cert_default(verify_none, VerifyNoneFun, CaCerts),
- VerifyNoneFun, PartialChainHanlder, VerifyClientOnce};
- 1 ->
- {verify_peer, false,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts),
- UserVerifyFun, PartialChainHanlder, VerifyClientOnce};
- 2 ->
- {verify_peer, true,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts),
- UserVerifyFun, PartialChainHanlder, VerifyClientOnce};
- verify_none ->
- {verify_none, false,
- ca_cert_default(verify_none, VerifyNoneFun, CaCerts),
- VerifyNoneFun, PartialChainHanlder, VerifyClientOnce};
- verify_peer ->
- {verify_peer, UserFailIfNoPeerCert,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts),
- UserVerifyFun, PartialChainHanlder, VerifyClientOnce};
- Value ->
- throw({error, {options, {verify, Value}}})
- end.
+
+handle_verify_option(verify_none, #{fail_if_no_peer_cert := _FailIfNoPeerCert} = OptionsMap) ->
+ OptionsMap#{verify => verify_none,
+ fail_if_no_peer_cert => false};
+handle_verify_option(verify_peer, #{fail_if_no_peer_cert := FailIfNoPeerCert} = OptionsMap) ->
+ OptionsMap#{verify => verify_peer,
+ fail_if_no_peer_cert => FailIfNoPeerCert};
+%% Handle 0, 1, 2 for backwards compatibility
+handle_verify_option(0, OptionsMap) ->
+ handle_verify_option(verify_none, OptionsMap);
+handle_verify_option(1, OptionsMap) ->
+ handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => false});
+handle_verify_option(2, OptionsMap) ->
+ handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => true});
+handle_verify_option(Value, _) ->
+ throw({error, {options, {verify, Value}}}).
%% Added to handle default values for signature_algs in TLS 1.3
default_option_role_sign_algs(_, Value, _, Version) when Version >= {3,4} ->
@@ -2464,7 +2456,7 @@ server_name_indication_default(_) ->
undefined.
-reject_alpn_next_prot_options(Opts) ->
+reject_alpn_next_prot_options({Opts,_,_}) ->
AlpnNextOpts = [alpn_advertised_protocols,
alpn_preferred_protocols,
next_protocols_advertised,
@@ -2486,10 +2478,3 @@ add_filter(undefined, Filters) ->
Filters;
add_filter(Filter, Filters) ->
[Filter | Filters].
-
-%% Convert the record #ssl_options{} into a map for internal usage
-options_to_map(Options) ->
- Fields = record_info(fields, ssl_options),
- [_Tag| Values] = tuple_to_list(Options),
- L = lists:zip(Fields, Values),
- maps:from_list(L).
diff --git a/lib/ssl/src/ssl_admin_sup.erl b/lib/ssl/src/ssl_admin_sup.erl
index 9c96435753..01ddbb7686 100644
--- a/lib/ssl/src/ssl_admin_sup.erl
+++ b/lib/ssl/src/ssl_admin_sup.erl
@@ -46,7 +46,8 @@ start_link() ->
init([]) ->
PEMCache = pem_cache_child_spec(),
SessionCertManager = session_and_cert_manager_child_spec(),
- {ok, {{rest_for_one, 10, 3600}, [PEMCache, SessionCertManager]}}.
+ TicketStore = ticket_store_spec(),
+ {ok, {{rest_for_one, 10, 3600}, [PEMCache, SessionCertManager, TicketStore]}}.
manager_opts() ->
CbOpts = case application:get_env(ssl, session_cb) of
@@ -86,6 +87,16 @@ session_and_cert_manager_child_spec() ->
Type = worker,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
+ticket_store_spec() ->
+ Name = tls_client_ticket_store,
+ %% TODO do not hardcode storage size and lifetime
+ StartFunc = {tls_client_ticket_store, start_link, [20,10]},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_client_ticket_store],
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
session_cb_init_args() ->
case application:get_env(ssl, session_cb_init_args) of
{ok, Args} when is_list(Args) ->
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 2d57b72f7b..d4b0cb1f14 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -33,9 +33,8 @@
-include("ssl_internal.hrl").
-export([decode/1,
- own_alert_txt/1,
- alert_txt/1,
- alert_txt/4,
+ own_alert_format/4,
+ alert_format/4,
reason_code/4]).
%%====================================================================
@@ -62,53 +61,69 @@ decode(Bin) ->
reason_code(#alert{description = ?CLOSE_NOTIFY}, _, _, _) ->
closed;
reason_code(#alert{description = Description, role = Role} = Alert, Role, ProtocolName, StateName) ->
- Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, own_alert_txt(Alert))),
+ {PrefixFmt, PrefixArgs} = alert_prefix_format(ProtocolName, Role, StateName),
+ {Fmt, Args} = own_alert_format_depth(Alert),
+ Txt = lists:flatten(io_lib:format(PrefixFmt ++ Fmt, PrefixArgs ++ Args)),
{tls_alert, {description_atom(Description), Txt}};
reason_code(#alert{description = Description} = Alert, Role, ProtocolName, StateName) ->
- Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, alert_txt(Alert))),
+ {Fmt, Args} = alert_format(ProtocolName, Role, StateName, Alert),
+ Txt = lists:flatten(io_lib:format(Fmt, Args)),
{tls_alert, {description_atom(Description), Txt}}.
%%--------------------------------------------------------------------
--spec alert_txt(string(), server | client, StateNam::atom(), string()) -> string().
+-spec own_alert_format(string(), server | client, StateNam::atom(), #alert{}) -> {io:format(), list()}.
%%
%% Description: Generates alert text for log or string part of error return.
%%--------------------------------------------------------------------
-alert_txt(ProtocolName, Role, StateName, Txt) ->
- io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]).
+own_alert_format(ProtocolName, Role, StateName, Alert) ->
+ {PrfixFmt, PrefixArgs} = alert_prefix_format(ProtocolName, Role, StateName),
+ {Fmt, Args} = own_alert_format(Alert),
+ {PrfixFmt ++ Fmt, PrefixArgs ++ Args}.
%%--------------------------------------------------------------------
--spec own_alert_txt(#alert{}) -> string().
+-spec alert_format(string(), server | client, StateNam::atom(), #alert{}) -> {io:format(), list()}.
%%
-%% Description: Returns the error string for given alert generated
-%% by the erlang implementation.
-%%--------------------------------------------------------------------
-own_alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined, role = Role}) ->
- "at " ++ Mod ++ ":" ++ integer_to_list(Line) ++ " generated " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
- level_txt(Level) ++ description_txt(Description);
-own_alert_txt(#alert{reason = Reason} = Alert) ->
- BaseTxt = own_alert_txt(Alert#alert{reason = undefined}),
- FormatDepth = 9, % Some limit on printed representation of an error
- ReasonTxt = lists:flatten(io_lib:format("~P", [Reason, FormatDepth])),
- BaseTxt ++ " - " ++ ReasonTxt.
-
-%%--------------------------------------------------------------------
--spec alert_txt(#alert{}) -> string().
-%%
-%% Description: Returns the error string for given alert received from
-%% the peer.
+%% Description: Generates alert text for log or string part of error return.
%%--------------------------------------------------------------------
-alert_txt(#alert{level = Level, description = Description, reason = undefined, role = Role}) ->
- "received " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
- level_txt(Level) ++ description_txt(Description);
-alert_txt(#alert{reason = Reason} = Alert) ->
- BaseTxt = alert_txt(Alert#alert{reason = undefined}),
- FormatDepth = 9, % Some limit on printed representation of an error
- ReasonTxt = lists:flatten(io_lib:format("~P", [Reason, FormatDepth])),
- BaseTxt ++ " - " ++ ReasonTxt.
+alert_format(ProtocolName, Role, StateName, Alert) ->
+ {PrfixFmt, PrefixArgs} = alert_prefix_format(ProtocolName, Role, StateName),
+ {Fmt, Args} = alert_format(Alert),
+ {PrfixFmt ++ Fmt, PrefixArgs ++ Args}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+alert_prefix_format(ProtocolName, Role, StateName) ->
+ {"~s ~p: In state ~p", [ProtocolName, Role, StateName]}.
+
+own_alert_format(#alert{reason = Reason} = Alert) ->
+ Txt = own_alert_txt(Alert),
+ case Reason of
+ undefined ->
+ {" ~s\n", [Txt]};
+ Reason ->
+ {" ~s\n - ~p", [Txt, Reason]}
+ end.
+own_alert_format_depth(#alert{reason = Reason} = Alert) ->
+ Txt = own_alert_txt(Alert),
+ case Reason of
+ undefined ->
+ {" ~s\n", [Txt]};
+ Reason ->
+ {" ~s\n ~P", [Txt, Reason, ?DEPTH]}
+ end.
+
+own_alert_txt(#alert{level = Level, description = Description, where = #{line := Line, file := Mod}, role = Role}) ->
+ "at " ++ Mod ++ ":" ++ integer_to_list(Line) ++ " generated " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
+ level_txt(Level) ++ description_txt(Description).
+
+alert_format(Alert) ->
+ Txt = alert_txt(Alert),
+ {" ~s\n ", [Txt]}.
+
+alert_txt(#alert{level = Level, description = Description, role = Role}) ->
+ "received " ++ string:uppercase(atom_to_list(Role)) ++ " ALERT: " ++
+ level_txt(Level) ++ description_txt(Description).
%% It is very unlikely that an correct implementation will send more than one alert at the time
%% So it there is more than 10 warning alerts we consider it an error
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
index 9b2322da17..0edc4bf8e4 100644
--- a/lib/ssl/src/ssl_alert.hrl
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -26,6 +26,7 @@
-ifndef(ssl_alert).
-define(ssl_alert, true).
+-include_lib("kernel/include/logger.hrl").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Alert protocol - RFC 2246 section 7.2
@@ -113,8 +114,8 @@
-define(CERTIFICATE_REQUIRED, 116).
-define(NO_APPLICATION_PROTOCOL, 120).
--define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}).
--define(ALERT_REC(Level,Desc,Reason), #alert{level=Level,description=Desc,where={?FILE, ?LINE},reason=Reason}).
+-define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where= ?LOCATION}).
+-define(ALERT_REC(Level,Desc,Reason), #alert{level=Level,description=Desc,where=?LOCATION,reason=Reason}).
-define(MAX_ALERTS, 10).
@@ -122,7 +123,7 @@
-record(alert, {
level,
description,
- where = {?FILE, ?LINE},
+ where,
role,
reason
}).
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
index 9e6d676bef..840a075e84 100644
--- a/lib/ssl/src/ssl_app.erl
+++ b/lib/ssl/src/ssl_app.erl
@@ -45,7 +45,8 @@ start_logger() ->
Filter = {fun logger_filters:domain/2,{log,sub,[otp,ssl]}},
logger:add_handler(ssl_handler, logger_std_h, Config),
logger:add_handler_filter(ssl_handler, filter_non_ssl, Filter),
- logger:set_application_level(ssl, debug).
+ logger:set_module_level([ssl_logger],
+ debug).
%%
%% Description: Stop SSL logger
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 9997f5e0c8..104bbd65c8 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -103,7 +103,7 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) ->
certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert], []).
%%--------------------------------------------------------------------
--spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref(), [der_cert()]) ->
+-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}, [der_cert()]) ->
{error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}.
%%
%% Description: Create certificate chain with certs from
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index c16e2331ff..c97884ec08 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -48,7 +48,11 @@
key_material/1, signature_algorithm_to_scheme/1]).
%% RFC 8446 TLS 1.3
--export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]).
+-export([generate_client_shares/1,
+ generate_server_share/1,
+ add_zero_padding/2,
+ encrypt_ticket/3,
+ decrypt_ticket/3]).
-compile(inline).
@@ -1386,3 +1390,63 @@ add_zero_padding(Bin, PrimeSize)
Bin;
add_zero_padding(Bin, PrimeSize) ->
add_zero_padding(<<0, Bin/binary>>, PrimeSize).
+
+
+%% Functions for handling self-encrypted session tickets (TLS 1.3).
+%%
+encrypt_ticket(#stateless_ticket{
+ hash = Hash,
+ pre_shared_key = PSK,
+ ticket_age_add = TicketAgeAdd,
+ lifetime = Lifetime,
+ timestamp = Timestamp
+ }, Shard, IV) ->
+ Plaintext = <<(ssl_cipher:hash_algorithm(Hash)):8,PSK/binary,
+ ?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp)>>,
+ encrypt_ticket_data(Plaintext, Shard, IV).
+
+
+decrypt_ticket(CipherFragment, Shard, IV) ->
+ case decrypt_ticket_data(CipherFragment, Shard, IV) of
+ error ->
+ error;
+ Plaintext ->
+ <<?BYTE(HKDF),T/binary>> = Plaintext,
+ Hash = hash_algorithm(HKDF),
+ HashSize = hash_size(Hash),
+ <<PSK:HashSize/binary,?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp),_/binary>> = T,
+ #stateless_ticket{
+ hash = Hash,
+ pre_shared_key = PSK,
+ ticket_age_add = TicketAgeAdd,
+ lifetime = Lifetime,
+ timestamp = Timestamp
+ }
+ end.
+
+
+encrypt_ticket_data(Plaintext, Shard, IV) ->
+ AAD = additional_data(erlang:iolist_size(Plaintext) + 16), %% TagLen = 16
+ {OTP, Key} = make_otp_key(Shard),
+ {Content, CipherTag} = crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Plaintext, AAD, 16, true),
+ <<Content/binary,CipherTag/binary,OTP/binary>>.
+
+
+decrypt_ticket_data(CipherFragment, Shard, IV) ->
+ Size = byte_size(Shard),
+ AAD = additional_data(erlang:iolist_size(CipherFragment) - Size),
+ Len = byte_size(CipherFragment) - Size - 16,
+ <<Encrypted:Len/binary,CipherTag:16/binary,OTP:Size/binary>> = CipherFragment,
+ Key = crypto:exor(OTP, Shard),
+ crypto:crypto_one_time_aead(aes_256_gcm, Key, IV, Encrypted, AAD, CipherTag, false).
+
+
+additional_data(Length) ->
+ <<"ticket",?UINT16(Length)>>.
+
+
+make_otp_key(Shard) ->
+ Size = byte_size(Shard),
+ OTP = crypto:strong_rand_bytes(Size),
+ Key = crypto:exor(OTP, Shard),
+ {OTP, Key}.
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 0fa5f66c49..150d37ef18 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -27,6 +27,16 @@
-ifndef(ssl_cipher).
-define(ssl_cipher, true).
+%%% Session tickets %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record(stateless_ticket,
+ {
+ hash,
+ pre_shared_key,
+ ticket_age_add,
+ lifetime,
+ timestamp
+ }).
+
%%% SSL cipher protocol %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(CHANGE_CIPHER_SPEC_PROTO, 1). % _PROTO to not clash with
% SSL record protocol
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 6789c2c23f..9ef66bfb26 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -178,24 +178,24 @@ socket_control(Connection, Socket, Pid, Transport) ->
socket_control(Connection, Socket, Pid, Transport, undefined).
%--------------------------------------------------------------------
--spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), pid()| atom()) ->
+-spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%--------------------------------------------------------------------
socket_control(Connection, Socket, Pids, Transport, udp_listener) ->
%% dtls listener process must have the socket control
{ok, Connection:socket(Pids, Transport, Socket, undefined)};
-socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, ListenTracker) ->
+socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)};
+ {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
{error, Reason} ->
{error, Reason}
end;
-socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, ListenTracker) ->
+socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)};
+ {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
{error, Reason} ->
{error, Reason}
end.
@@ -350,29 +350,29 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
socket = Socket,
transport_cb = Transport,
protocol_cb = Connection,
- tracker = Tracker},
+ trackers = Trackers},
handshake_env = #handshake_env{renegotiation = {false, first}},
start_or_recv_from = StartFrom} = State) ->
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, StateName, Connection);
+ alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection);
handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
socket = Socket,
transport_cb = Transport,
protocol_cb = Connection,
- tracker = Tracker},
+ trackers = Trackers},
connection_env = #connection_env{user_application = {_Mon, Pid}},
socket_options = Opts,
start_or_recv_from = RecvFrom} = State) ->
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
#state{static_env = #static_env{role = Role,
socket = Socket,
host = Host,
port = Port,
- tracker = Tracker,
+ trackers = Trackers,
transport_cb = Transport,
protocol_cb = Connection},
connection_env = #connection_env{user_application = {_Mon, Pid}},
@@ -385,7 +385,7 @@ handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
log_alert(LogLevel, Role, Connection:protocol_name(),
StateName, Alert),
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
{stop, {shutdown, normal}, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
@@ -444,19 +444,22 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName,
%%====================================================================
%% Data handling
%%====================================================================
-passive_receive(State0 = #state{user_data_buffer = {_,BufferSize,_}}, StateName, Connection, StartTimerAction) ->
+passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
+ %% Assert! Erl distribution uses active sockets
+ connection_env = #connection_env{erl_dist_handle = undefined}}
+ = State0, StateName, Connection, StartTimerAction) ->
case BufferSize of
0 ->
Connection:next_event(StateName, no_record, State0, StartTimerAction);
_ ->
- case read_application_data(<<>>, State0) of
+ case read_application_data(State0, Front, BufferSize, Rear) of
{stop, _, _} = ShutdownError ->
ShutdownError;
{Record, State} ->
case State#state.start_or_recv_from of
undefined ->
%% Cancel recv timeout as data has been delivered
- Connection:next_event(StateName, Record, State,
+ Connection:next_event(StateName, Record, State,
[{{timeout, recv}, infinity, timeout}]);
_ ->
Connection:next_event(StateName, Record, State, StartTimerAction)
@@ -556,13 +559,13 @@ read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFr
socket = Socket,
protocol_cb = Connection,
transport_cb = Transport,
- tracker = Tracker},
+ trackers = Trackers},
connection_env =
#connection_env{user_application = {_Mon, Pid}}} = State,
Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]),
deliver_packet_error(
Connection:pids(State), Transport, Socket, SocketOpts0,
- Buffer, Pid, RecvFrom, Tracker, Connection),
+ Buffer, Pid, RecvFrom, Trackers, Connection),
{stop, {shutdown, normal}, State#state{socket_options = SocketOpts0,
bytes_to_read = BytesToRead,
start_or_recv_from = RecvFrom,
@@ -576,12 +579,12 @@ read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvF
socket = Socket,
protocol_cb = Connection,
transport_cb = Transport,
- tracker = Tracker},
+ trackers = Trackers},
connection_env =
#connection_env{user_application = {_Mon, Pid}}} = State,
SocketOpts =
deliver_app_data(
- Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Tracker, Connection),
+ Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection),
if
SocketOpts#socket_options.active =:= false ->
%% Passive mode, wait for active once or recv
@@ -821,7 +824,7 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout},
ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0, Connection) ->
try
- SslOpts = ssl:handle_options(Opts, OrigSSLOptions),
+ SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
State = ssl_config(SslOpts, Role, State0),
init({call, From}, {start, Timeout},
State#state{ssl_options = SslOpts,
@@ -870,7 +873,7 @@ user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
#state{static_env = #static_env{role = Role},
handshake_env = #handshake_env{hello = Hello},
ssl_options = Options0} = State0, _Connection) ->
- Options = ssl:handle_options(NewOptions, Options0#{handshake => full}),
+ Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}),
State = ssl_config(Options, Role, State0),
{next_state, hello, State#state{start_or_recv_from = From},
[{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]};
@@ -965,7 +968,7 @@ certify(internal, #certificate{asn1_certificates = []},
ssl_options = #{verify := verify_peer,
fail_if_no_peer_cert := true}} =
State, _) ->
- Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
+ Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided),
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
#state{static_env = #static_env{role = server},
@@ -1434,7 +1437,7 @@ handle_call({get_opts, OptTags}, From, _,
handle_call({set_opts, Opts0}, From, StateName,
#state{static_env = #static_env{socket = Socket,
transport_cb = Transport,
- tracker = Tracker},
+ trackers = Trackers},
connection_env =
#connection_env{user_application = {_Mon, Pid}},
socket_options = Opts1
@@ -1445,7 +1448,7 @@ handle_call({set_opts, Opts0}, From, StateName,
send_user(
Pid,
format_passive(
- Connection:pids(State0), Transport, Socket, Tracker, Connection));
+ Connection:pids(State0), Transport, Socket, Trackers, Connection));
_ ->
ok
end,
@@ -1488,19 +1491,20 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
socket = Socket,
transport_cb = Transport,
error_tag = ErrorTag,
- tracker = Tracker,
+ trackers = Trackers,
protocol_cb = Connection},
start_or_recv_from = StartFrom
} = State) when StateName =/= connection ->
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker,Socket,
+ alert_user(Pids, Transport, Trackers,Socket,
StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
{stop, {shutdown, normal}, State};
handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket,
- error_tag = ErrorTag}} = State) ->
- Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
- ?LOG_ERROR(Report),
+ error_tag = ErrorTag},
+ ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(info, Level, #{description => "Socket error",
+ reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
{stop, {shutdown,normal}, State};
@@ -1528,9 +1532,11 @@ handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_en
handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) ->
{next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}};
-handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) ->
- Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
- ?LOG_NOTICE(Report),
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag},
+ ssl_options = #{log_level := Level}} = State) ->
+ ssl_logger:log(notice, Level, #{description => "Unexpected INFO message",
+ reason => [{message, Msg}, {socket, Socket},
+ {error_tag, ErrorTag}]}, ?LOCATION),
{next_state, StateName, State}.
%%====================================================================
@@ -1616,7 +1622,8 @@ send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}}
Connection:send_alert(Alert, State).
connection_info(#state{static_env = #static_env{protocol_cb = Connection},
- handshake_env = #handshake_env{sni_hostname = SNIHostname},
+ handshake_env = #handshake_env{sni_hostname = SNIHostname,
+ resumption = Resumption},
session = #session{session_id = SessionId,
cipher_suite = CipherSuite, ecc = ECCCurve},
connection_env = #connection_env{negotiated_version = {_,_} = Version},
@@ -1633,6 +1640,7 @@ connection_info(#state{static_env = #static_env{protocol_cb = Connection},
end,
[{protocol, RecordCB:protocol_version(Version)},
{session_id, SessionId},
+ {session_resumption, Resumption},
{cipher_suite, ssl_cipher_format:suite_legacy(CipherSuiteDef)},
{selected_cipher_suite, CipherSuiteDef},
{sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts).
@@ -2863,12 +2871,12 @@ decode_packet(Type, Buffer, PacketOpts) ->
deliver_app_data(
CPids, Transport, Socket,
#socket_options{active=Active, packet=Type} = SOpts,
- Data, Pid, From, Tracker, Connection) ->
+ Data, Pid, From, Trackers, Connection) ->
%%
send_or_reply(
Active, Pid, From,
format_reply(
- CPids, Transport, Socket, SOpts, Data, Tracker, Connection)),
+ CPids, Transport, Socket, SOpts, Data, Trackers, Connection)),
SO =
case Data of
{P, _, _, _}
@@ -2889,7 +2897,7 @@ deliver_app_data(
send_user(
Pid,
format_passive(
- CPids, Transport, Socket, Tracker, Connection)),
+ CPids, Transport, Socket, Trackers, Connection)),
SO#socket_options{active=false};
N when is_integer(N) ->
SO#socket_options{active=N - 1};
@@ -2901,20 +2909,20 @@ format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packe
header = Header}, Data, _, _) ->
{ok, do_format_reply(Mode, Packet, Header, Data)};
format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
- header = Header}, Data, Tracker, Connection) ->
- {ssl, Connection:socket(CPids, Transport, Socket, Tracker),
+ header = Header}, Data, Trackers, Connection) ->
+ {ssl, Connection:socket(CPids, Transport, Socket, Trackers),
do_format_reply(Mode, Packet, Header, Data)}.
deliver_packet_error(CPids, Transport, Socket,
- SO= #socket_options{active = Active}, Data, Pid, From, Tracker, Connection) ->
+ SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) ->
send_or_reply(Active, Pid, From, format_packet_error(CPids,
- Transport, Socket, SO, Data, Tracker, Connection)).
+ Transport, Socket, SO, Data, Trackers, Connection)).
format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) ->
{error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode},
- Data, Tracker, Connection) ->
- {ssl_error, Connection:socket(CPids, Transport, Socket, Tracker),
+ Data, Trackers, Connection) ->
+ {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers),
{invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
@@ -2929,8 +2937,8 @@ do_format_reply(list, Packet, _, Data)
do_format_reply(list, _,_, Data) ->
binary_to_list(Data).
-format_passive(CPids, Transport, Socket, Tracker, Connection) ->
- {ssl_passive, Connection:socket(CPids, Transport, Socket, Tracker)}.
+format_passive(CPids, Transport, Socket, Trackers, Connection) ->
+ {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}.
header(0, <<>>) ->
<<>>;
@@ -2955,13 +2963,13 @@ send_user(Pid, Msg) ->
Pid ! Msg,
ok.
-alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
-alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection).
+alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
+alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection).
-alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection) ->
- alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
+alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
%% If there is an outstanding ssl_accept | recv
@@ -2969,24 +2977,28 @@ alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Conne
%% send the appropriate error message.
ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
+alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
closed ->
send_or_reply(Active, Pid, From,
- {ssl_closed, Connection:socket(Pids, Transport, Socket, Tracker)});
+ {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)});
ReasonCode ->
send_or_reply(Active, Pid, From,
- {ssl_error, Connection:socket(Pids, Transport, Socket, Tracker), ReasonCode})
+ {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode})
end.
log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
- Txt = ssl_alert:own_alert_txt(Alert),
- Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt),
- ssl_logger:notice(Level, Report);
-log_alert(Level, Role, ProtocolName, StateName, Alert) ->
- Txt = ssl_alert:alert_txt(Alert),
- Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt),
- ssl_logger:notice(Level, Report).
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => own}, Alert#alert.where);
+log_alert(Level, Role, ProtocolName, StateName, Alert) ->
+ ssl_logger:log(notice, Level, #{protocol => ProtocolName,
+ role => Role,
+ statename => StateName,
+ alert => Alert,
+ alerter => peer}, Alert#alert.where).
invalidate_session(client, Host, Port, Session) ->
ssl_manager:invalidate_session(Host, Port, Session);
@@ -3042,7 +3054,7 @@ update_ssl_options_from_sni(#{sni_fun := SNIFun,
undefined ->
undefined;
_ ->
- ssl:handle_options(SSLOption, OrigSSLOptions)
+ ssl:handle_options(SSLOption, server, OrigSSLOptions)
end.
new_emulated([], EmOpts) ->
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index be94fd05bb..284ded64d8 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -50,9 +50,10 @@
crl_db :: term(),
file_ref_db :: db_handle(),
cert_db_ref :: certdb_ref() | 'undefined',
- tracker :: pid() | 'undefined' %% Tracker process for listen socket
+ trackers :: [{atom(), pid()}] | 'undefined' %% Tracker process for listen socket
}).
+
-record(handshake_env, {
client_hello_version :: ssl_record:ssl_version() | 'undefined',
unprocessed_handshake_events = 0 :: integer(),
@@ -60,6 +61,7 @@
| 'undefined',
expecting_finished = false ::boolean(),
renegotiation :: undefined | {boolean(), From::term() | internal | peer},
+ resumption = false :: boolean(), %% TLS 1.3
allow_renegotiate = true ::boolean(),
%% Ext handling
hello, %%:: #client_hello{} | #server_hello{}
@@ -77,7 +79,8 @@
srp_params :: #srp_user{} | secret_printout() | 'undefined',
public_key_info :: ssl_handshake:public_key_info() | 'undefined',
premaster_secret :: binary() | secret_printout() | 'undefined',
- server_psk_identity :: binary() | 'undefined' % server psk identity hint
+ server_psk_identity :: binary() | 'undefined', % server psk identity hint
+ ticket_seed
}).
-record(connection_env, {
diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/ssl_connection_sup.erl
index 934dd39df5..d43254d23a 100644
--- a/lib/ssl/src/ssl_connection_sup.erl
+++ b/lib/ssl/src/ssl_connection_sup.erl
@@ -45,57 +45,31 @@ start_link() ->
init([]) ->
- TLSConnetionManager = tls_connection_manager_child_spec(),
- %% Handles emulated options so that they inherited by the accept
- %% socket, even when setopts is performed on the listen socket
- ListenOptionsTracker = listen_options_tracker_child_spec(),
-
- DTLSConnetionManager = dtls_connection_manager_child_spec(),
- DTLSListeners = dtls_listeners_spec(),
+ TLSSup = tls_sup_child_spec(),
+ DTLSSup = dtls_sup_child_spec(),
- {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
- ListenOptionsTracker,
- DTLSConnetionManager,
- DTLSListeners
- ]}}.
+ {ok, {{one_for_one, 10, 3600}, [TLSSup, DTLSSup]}}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_manager_child_spec() ->
- Name = tls_connection,
- StartFunc = {tls_connection_sup, start_link, []},
+tls_sup_child_spec() ->
+ Name = tls_sup,
+ StartFunc = {tls_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [tls_connection_sup],
+ Modules = [tls_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-dtls_connection_manager_child_spec() ->
- Name = dtls_connection,
- StartFunc = {dtls_connection_sup, start_link, []},
+dtls_sup_child_spec() ->
+ Name = dtls_sup,
+ StartFunc = {dtls_sup, start_link, []},
Restart = permanent,
Shutdown = 4000,
- Modules = [dtls_connection_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-listen_options_tracker_child_spec() ->
- Name = tls_socket,
- StartFunc = {ssl_listen_tracker_sup, start_link, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [tls_socket],
+ Modules = [dtls_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-dtls_listeners_spec() ->
- Name = dtls_listener,
- StartFunc = {dtls_listener_sup, start_link, []},
- Restart = permanent,
- Shutdown = 4000,
- Modules = [],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl
index 841620ce57..2dc538c251 100644
--- a/lib/ssl/src/ssl_crl_cache.erl
+++ b/lib/ssl/src/ssl_crl_cache.erl
@@ -46,6 +46,12 @@ lookup(#'DistributionPoint'{distributionPoint = {fullName, Names}},
lookup(_,_,_) ->
not_available.
+select(GenNames, CRLDbHandle) when is_list(GenNames) ->
+ lists:flatmap(fun({directoryName, Issuer}) ->
+ select(Issuer, CRLDbHandle);
+ (_) ->
+ []
+ end, GenNames);
select(Issuer, {{_Cache, Mapping},_}) ->
case ssl_pkix_db:lookup(Issuer, Mapping) of
undefined ->
diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl
index 8a750b3929..0c7842dc4b 100644
--- a/lib/ssl/src/ssl_crl_cache_api.erl
+++ b/lib/ssl/src/ssl_crl_cache_api.erl
@@ -23,13 +23,16 @@
-module(ssl_crl_cache_api).
-include_lib("public_key/include/public_key.hrl").
--export_type([dist_point/0, crl_cache_ref/0]).
+-export_type([dist_point/0, crl_cache_ref/0, logger_info/0]).
-type crl_cache_ref() :: any().
-type issuer_name() :: {rdnSequence,[#'AttributeTypeAndValue'{}]}.
-type dist_point() :: #'DistributionPoint'{}.
+-type logger_info() :: {logger:level(), Report::#{description => string(), reason => term()}, logger:metadata()}.
-
--callback lookup(dist_point(), issuer_name(), crl_cache_ref()) -> not_available | [public_key:der_encoded()].
--callback select(issuer_name(), crl_cache_ref()) -> [public_key:der_encoded()].
--callback fresh_crl(dist_point(), public_key:der_encoded()) -> public_key:der_encoded().
+-callback lookup(dist_point(), issuer_name(), crl_cache_ref()) -> not_available | [public_key:der_encoded()] |
+ {{logger, logger_info()}, [public_key:der_encoded()]}.
+-callback select(issuer_name() | list(), crl_cache_ref()) -> [public_key:der_encoded()] |
+ {logger, logger_info(), [public_key:der_encoded()]}.
+-callback fresh_crl(dist_point(), public_key:der_encoded()) -> public_key:der_encoded() |
+ {logger, logger_info(), public_key:der_encoded()}.
diff --git a/lib/ssl/src/ssl_crl_hash_dir.erl b/lib/ssl/src/ssl_crl_hash_dir.erl
index 9478ff9b78..635b82ed1e 100644
--- a/lib/ssl/src/ssl_crl_hash_dir.erl
+++ b/lib/ssl/src/ssl_crl_hash_dir.erl
@@ -27,72 +27,65 @@
-export([lookup/3, select/2, fresh_crl/2]).
lookup(#'DistributionPoint'{cRLIssuer = CRLIssuer} = DP, CertIssuer, CRLDbInfo) ->
- Issuer =
- case CRLIssuer of
- asn1_NOVALUE ->
- %% If the distribution point extension doesn't
- %% indicate a CRL issuer, use the certificate issuer.
- CertIssuer;
- _ ->
- CRLIssuer
- end,
- %% Find all CRLs for this issuer, and return those that match the
- %% given distribution point.
- AllCRLs = select(Issuer, CRLDbInfo),
- lists:filter(fun(DER) ->
- public_key:pkix_match_dist_point(DER, DP)
- end, AllCRLs).
+ case CRLIssuer of
+ asn1_NOVALUE ->
+ %% If the distribution point extension doesn't
+ %% indicate a CRL issuer, use the certificate issuer.
+ select(CertIssuer, CRLDbInfo);
+ _ ->
+ CRLs = select(CRLIssuer, CRLDbInfo),
+ lists:filter(fun(DER) ->
+ public_key:pkix_match_dist_point(DER, DP)
+ end, CRLs)
+ end.
fresh_crl(#'DistributionPoint'{}, CurrentCRL) ->
CurrentCRL.
-select(Issuer, {_DbHandle, [{dir, Dir}]}) ->
- case find_crls(Issuer, Dir) of
- [_|_] = DERs ->
- DERs;
- [] ->
- %% That's okay, just report that we didn't find any CRL.
- %% If the crl_check setting is best_effort, ssl_handshake
- %% is happy with that, but if it's true, this is an error.
- [];
+select({rdnSequence, _} = Issuer, DbHandle) ->
+ select([{directoryName, Issuer}], DbHandle);
+select([], _) ->
+ [];
+select([{directoryName, Issuer} | _], {_DbHandle, [{dir, Dir}]}) ->
+ case find_crls(public_key:pkix_normalize_name(Issuer), Dir) of
+ {#{reason := []}, DERs} ->
+ DERs;
+ {#{reason := [_|_]} = Report, DERs} ->
+ {logger, {notice, Report, ?LOCATION}, DERs};
{error, Error} ->
- ?LOG_ERROR(
- [{cannot_find_crl, Error},
- {dir, Dir},
- {module, ?MODULE},
- {line, ?LINE}]),
- []
- end.
+ {logger, {error, #{description => "CRL retrival",
+ reason => [{cannot_find_crl, Error},
+ {dir, Dir}]}, ?LOCATION}, []}
+ end;
+select([_ | Rest], CRLDbInfo) ->
+ select(Rest, CRLDbInfo).
find_crls(Issuer, Dir) ->
case filelib:is_dir(Dir) of
true ->
Hash = public_key:short_name_hash(Issuer),
- find_crls(Issuer, Hash, Dir, 0, []);
+ find_crls(Issuer, Hash, Dir, 0, [], #{description => "CRL file traversal",
+ reason => []});
false ->
{error, not_a_directory}
end.
-find_crls(Issuer, Hash, Dir, N, Acc) ->
+find_crls(Issuer, Hash, Dir, N, Acc, #{reason := Reason} = Report) ->
Filename = filename:join(Dir, Hash ++ ".r" ++ integer_to_list(N)),
case file:read_file(Filename) of
{error, enoent} ->
- Acc;
- {ok, Bin} ->
+ {Report, Acc};
+ {ok, Bin} ->
try maybe_parse_pem(Bin) of
DER when is_binary(DER) ->
%% Found one file. Let's see if there are more.
- find_crls(Issuer, Hash, Dir, N + 1, [DER] ++ Acc)
+ find_crls(Issuer, Hash, Dir, N + 1, [DER] ++ Acc, Report)
catch
error:Error ->
%% Something is wrong with the file. Report
%% it, and try the next one.
- ?LOG_ERROR(
- [{crl_parse_error, Error},
- {filename, Filename},
- {module, ?MODULE},
- {line, ?LINE}]),
- find_crls(Issuer, Hash, Dir, N + 1, Acc)
+ find_crls(Issuer, Hash, Dir, N + 1, Acc, Report#{reason => [{{crl_parse_error, Error},
+ {filename, Filename}} | Reason]})
end
end.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 1c8a2ca452..ecbe905d28 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -72,7 +72,7 @@
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
--export([client_hello_extensions/6,
+-export([client_hello_extensions/7,
handle_client_hello_extensions/9, %% Returns server hello extensions
handle_server_hello_extensions/9, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
@@ -82,7 +82,7 @@
-export([get_cert_params/1,
server_name/3,
- validation_fun_and_state/9,
+ validation_fun_and_state/10,
handle_path_validation_error/7]).
%%====================================================================
@@ -343,12 +343,13 @@ next_protocol(SelectedProtocol) ->
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
#{server_name_indication := ServerNameIndication,
- partial_chain := PartialChain,
- verify_fun := VerifyFun,
- customize_hostname_check := CustomizeHostnameCheck,
- crl_check := CrlCheck,
- depth := Depth} = Opts, CRLDbHandle, Role, Host) ->
-
+ partial_chain := PartialChain,
+ verify_fun := VerifyFun,
+ customize_hostname_check := CustomizeHostnameCheck,
+ crl_check := CrlCheck,
+ log_level := Level,
+ depth := Depth} = Opts, CRLDbHandle, Role, Host) ->
+
ServerName = server_name(ServerNameIndication, Host, Role),
[PeerCert | ChainCerts ] = ASN1Certs,
try
@@ -358,7 +359,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
ValidationFunAndState = validation_fun_and_state(VerifyFun, Role,
CertDbHandle, CertDbRef, ServerName,
CustomizeHostnameCheck,
- CrlCheck, CRLDbHandle, CertPath),
+ CrlCheck, CRLDbHandle, CertPath, Level),
Options = [{max_path_length, Depth},
{verify_fun, ValidationFunAndState}],
case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
@@ -369,7 +370,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
CertDbHandle, CertDbRef)
end
catch
- error:{badmatch,{error, {asn1, Asn1Reason}}} ->
+ error:{_,{error, {asn1, Asn1Reason}}} ->
%% ASN-1 decode of certificate somehow failed
?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason});
error:OtherReason ->
@@ -724,12 +725,16 @@ encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) ->
encode_extensions([#pre_shared_key_client_hello{
offered_psks = #offered_psks{
identities = Identities0,
- binders = Binders0} = PSKs} | Rest], Acc) ->
+ binders = Binders0} = _PSKs} | Rest], Acc) ->
Identities = encode_psk_identities(Identities0),
Binders = encode_psk_binders(Binders0),
Len = byte_size(Identities) + byte_size(Binders),
- encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT),
- ?UINT16(Len), Identities/binary, Binders/binary, Acc/binary>>);
+ %% The "pre_shared_key" extension MUST be the last extension in the
+ %% ClientHello (this facilitates implementation as described below).
+ %% Servers MUST check that it is the last extension and otherwise fail
+ %% the handshake with an "illegal_parameter" alert.
+ encode_extensions(Rest, <<Acc/binary,?UINT16(?PRE_SHARED_KEY_EXT),
+ ?UINT16(Len), Identities/binary, Binders/binary>>);
encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) ->
encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT),
?UINT16(2), ?UINT16(Identity), Acc/binary>>).
@@ -952,9 +957,9 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port,
Session, Version,
#{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = SslOpts,
Cache, CacheCb, Cert) ->
- {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId,
- SslOpts, Cert,
- Cache, CacheCb),
+ {SessionId, Resumed} = ssl_session:server_select_session(Version, Port, SuggestedSessionId,
+ SslOpts, Cert,
+ Cache, CacheCb),
case Resumed of
undefined ->
Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0),
@@ -1069,10 +1074,11 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) ->
%%====================================================================
%% Extensions handling
%%====================================================================
-client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare) ->
+client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare,
+ TicketData) ->
HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation),
HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts),
- maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare).
+ maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare, TicketData).
add_tls12_extensions(_Version,
@@ -1129,14 +1135,16 @@ maybe_add_tls13_extensions({3,4},
HelloExtensions0,
#{signature_algs_cert := SignatureSchemes,
versions := SupportedVersions},
- KeyShare) ->
- HelloExtensions =
+ KeyShare,
+ TicketData) ->
+ HelloExtensions1 =
HelloExtensions0#{client_hello_versions =>
#client_hello_versions{versions = SupportedVersions},
signature_algs_cert =>
signature_algs_cert(SignatureSchemes)},
- maybe_add_key_share(HelloExtensions, KeyShare);
-maybe_add_tls13_extensions(_, HelloExtensions, _, _) ->
+ HelloExtensions = maybe_add_key_share(HelloExtensions1, KeyShare),
+ maybe_add_pre_shared_key(HelloExtensions, TicketData);
+maybe_add_tls13_extensions(_, HelloExtensions, _, _, _) ->
HelloExtensions.
@@ -1181,6 +1189,43 @@ maybe_add_key_share(HelloExtensions, KeyShare) ->
HelloExtensions#{key_share => #key_share_client_hello{
client_shares = ClientShares}}.
+
+maybe_add_pre_shared_key(HelloExtensions, undefined) ->
+ HelloExtensions;
+maybe_add_pre_shared_key(HelloExtensions, TicketData) ->
+ {Identities, Binders} = get_identities_binders(TicketData),
+
+ %% A client MUST provide a "psk_key_exchange_modes" extension if it
+ %% offers a "pre_shared_key" extension.
+ HelloExtensions#{pre_shared_key =>
+ #pre_shared_key_client_hello{
+ offered_psks =
+ #offered_psks{
+ identities = Identities,
+ binders = Binders}},
+ psk_key_exchange_modes =>
+ #psk_key_exchange_modes{
+ ke_modes = [psk_ke, psk_dhe_ke]}}.
+
+
+get_identities_binders(TicketData) ->
+ get_identities_binders(TicketData, {[], []}, 0).
+%%
+get_identities_binders([], {Identities, Binders}, _) ->
+ {lists:reverse(Identities), lists:reverse(Binders)};
+get_identities_binders([{Key, _, Identity, _, _, HKDF}|T], {I0, B0}, N) ->
+ %% Use dummy binder for proper calculation of packet size when creating
+ %% the real binder value.
+ Binder = dummy_binder(HKDF),
+ %% Store ticket position in identities
+ tls_client_ticket_store:update_ticket(Key, N),
+ get_identities_binders(T, {[Identity|I0], [Binder|B0]}, N + 1).
+
+
+dummy_binder(HKDF) ->
+ binary:copy(<<0>>, ssl_cipher:hash_size(HKDF)).
+
+
add_server_share(server_hello, Extensions, KeyShare) ->
#key_share_server_hello{server_share = ServerShare0} = KeyShare,
%% Keep only public keys
@@ -1216,6 +1261,7 @@ kse_remove_private_key(#key_share_entry{
group = Group,
key_exchange = PublicKey}.
+
signature_algs_ext(undefined) ->
undefined;
signature_algs_ext(SignatureSchemes0) ->
@@ -1520,7 +1566,13 @@ extension_value(#key_share_server_hello{server_share = ServerShare}) ->
extension_value(#client_hello_versions{versions = Versions}) ->
Versions;
extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) ->
- SelectedVersion.
+ SelectedVersion;
+extension_value(#pre_shared_key_client_hello{offered_psks = PSKs}) ->
+ PSKs;
+extension_value(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
+ SelectedIdentity;
+extension_value(#psk_key_exchange_modes{ke_modes = Modes}) ->
+ Modes.
%%--------------------------------------------------------------------
@@ -1581,7 +1633,8 @@ certificate_authorities_from_db(_CertDbHandle, {extracted, CertDbData}) ->
%%-------------Handle handshake messages --------------------------------
validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
- ServerNameIndication, CustomizeHostCheck, CRLCheck, CRLDbHandle, CertPath) ->
+ ServerNameIndication, CustomizeHostCheck, CRLCheck,
+ CRLDbHandle, CertPath, LogLevel) ->
{fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) ->
case ssl_certificate:validate(OtpCert,
Extension,
@@ -1590,17 +1643,18 @@ validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef,
{valid, {NewSslState, UserState}};
{fail, Reason} ->
apply_user_fun(Fun, OtpCert, Reason, UserState,
- SslState, CertPath);
+ SslState, CertPath, LogLevel);
{unknown, _} ->
apply_user_fun(Fun, OtpCert,
- Extension, UserState, SslState, CertPath)
+ Extension, UserState, SslState, CertPath, LogLevel)
end;
(OtpCert, VerifyResult, {SslState, UserState}) ->
apply_user_fun(Fun, OtpCert, VerifyResult, UserState,
- SslState, CertPath)
+ SslState, CertPath, LogLevel)
end, {{Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}, UserState0}};
validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
- ServerNameIndication, CustomizeHostCheck, CRLCheck, CRLDbHandle, CertPath) ->
+ ServerNameIndication, CustomizeHostCheck, CRLCheck,
+ CRLDbHandle, CertPath, LogLevel) ->
{fun(OtpCert, {extension, _} = Extension, SslState) ->
ssl_certificate:validate(OtpCert,
Extension,
@@ -1608,7 +1662,7 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
(OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or
(VerifyResult == valid_peer) ->
case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
- CRLDbHandle, VerifyResult, CertPath) of
+ CRLDbHandle, VerifyResult, CertPath, LogLevel) of
valid ->
ssl_certificate:validate(OtpCert,
VerifyResult,
@@ -1623,21 +1677,21 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef,
end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}.
apply_user_fun(Fun, OtpCert, VerifyResult, UserState0,
- {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath) when
+ {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath, LogLevel) when
(VerifyResult == valid) or (VerifyResult == valid_peer) ->
case Fun(OtpCert, VerifyResult, UserState0) of
{Valid, UserState} when (Valid == valid) or (Valid == valid_peer) ->
case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef,
- CRLDbHandle, VerifyResult, CertPath) of
+ CRLDbHandle, VerifyResult, CertPath, LogLevel) of
valid ->
{Valid, {SslState, UserState}};
Result ->
- apply_user_fun(Fun, OtpCert, Result, UserState, SslState, CertPath)
+ apply_user_fun(Fun, OtpCert, Result, UserState, SslState, CertPath, LogLevel)
end;
{fail, _} = Fail ->
Fail
end;
-apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath) ->
+apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath, _LogLevel) ->
case Fun(OtpCert, ExtensionOrError, UserState0) of
{Valid, UserState} when (Valid == valid) or (Valid == valid_peer)->
{Valid, {SslState, UserState}};
@@ -1651,40 +1705,44 @@ handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain,
Opts, Options, CertDbHandle, CertsDbRef) ->
handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason);
handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0,
- #{partial_chain := PartialChain} = Opts, Options, CertDbHandle, CertsDbRef) ->
- case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef, Chain0) of
- {ok, _, [PeerCert | Chain] = OrdedChain} when Chain =/= Chain0 -> %% Chain appaears to be unorded
- {Trusted, Path} = ssl_certificate:trusted_cert_and_path(OrdedChain,
+ Opts, Options, CertDbHandle, CertsDbRef) ->
+ handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason);
+handle_path_validation_error(Reason, _, _, _, _,_, _) ->
+ path_validation_alert(Reason).
+
+handle_incomplete_chain(PeerCert, Chain0,
+ #{partial_chain := PartialChain} = Opts, Options, CertDbHandle, CertsDbRef, Reason) ->
+ case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of
+ {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
+ {Trusted, Path} = ssl_certificate:trusted_cert_and_path(Chain,
CertDbHandle, CertsDbRef,
PartialChain),
case public_key:pkix_path_validation(Trusted, Path, Options) of
{ok, {PublicKeyInfo,_}} ->
{PeerCert, PublicKeyInfo};
{error, PathError} ->
- handle_path_validation_error(PathError, PeerCert, Path,
- Opts, Options, CertDbHandle, CertsDbRef)
+ handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, PathError)
end;
_ ->
- path_validation_alert(Reason)
- end;
-handle_path_validation_error(Reason, _, _, _, _,_, _) ->
- path_validation_alert(Reason).
+ handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason)
+ end.
-handle_incomplete_chain(PeerCert, Chain0,
- #{partial_chain := PartialChain}, Options, CertDbHandle, CertsDbRef, PathError0) ->
- case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of
- {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found
+handle_unordered_chain(PeerCert, Chain0,
+ #{partial_chain := PartialChain}, Options, CertDbHandle, CertsDbRef, Reason) ->
+ {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, Chain0}),
+ case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, ExtractedCerts, Chain0) of
+ {ok, _, Chain} when Chain =/= Chain0 -> %% Chain appaears to be unordered
{Trusted, Path} = ssl_certificate:trusted_cert_and_path(Chain,
CertDbHandle, CertsDbRef,
PartialChain),
case public_key:pkix_path_validation(Trusted, Path, Options) of
- {ok, {PublicKeyInfo,_}} ->
- {PeerCert, PublicKeyInfo};
+ {ok, {PublicKeyInfo,_}} ->
+ {PeerCert, PublicKeyInfo};
{error, PathError} ->
- path_validation_alert(PathError)
+ path_validation_alert(PathError)
end;
_ ->
- path_validation_alert(PathError0)
+ path_validation_alert(Reason)
end.
path_validation_alert({bad_cert, cert_expired}) ->
@@ -1705,7 +1763,7 @@ path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) ->
path_validation_alert({bad_cert, selfsigned_peer}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
path_validation_alert({bad_cert, unknown_ca}) ->
- ?ALERT_REC(?FATAL, ?UNKNOWN_CA);
+ ?ALERT_REC(?FATAL, ?UNKNOWN_CA);
path_validation_alert(Reason) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason).
@@ -1741,29 +1799,38 @@ bad_key(#'RSAPrivateKey'{}) ->
bad_key(#'ECPrivateKey'{}) ->
unacceptable_ecdsa_key.
-crl_check(_, false, _,_,_, _, _) ->
+crl_check(_, false, _,_,_, _, _, _) ->
valid;
-crl_check(_, peer, _, _,_, valid, _) -> %% Do not check CAs with this option.
+crl_check(_, peer, _, _,_, valid, _, _) -> %% Do not check CAs with this option.
valid;
-crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath) ->
+crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _, CertPath, LogLevel) ->
Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) ->
ssl_crl:trusted_cert_and_path(CRL, Issuer, {CertPath,
DBInfo})
end, {CertDbHandle, CertDbRef}}},
- {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end},
+ {update_crl, fun(DP, CRL) ->
+ case Callback:fresh_crl(DP, CRL) of
+ {logger, LogInfo, Fresh} ->
+ handle_log(LogLevel, LogInfo),
+ Fresh;
+ Fresh ->
+ Fresh
+ end
+ end},
{undetermined_details, true}
],
- case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of
+ case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext, LogLevel) of
no_dps ->
crl_check_same_issuer(OtpCert, Check,
- dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer),
+ dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel),
Options);
DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed
%% but could not be retrived, will result in {bad_cert, revocation_status_undetermined}
case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of
{bad_cert, {revocation_status_undetermined, _}} ->
- crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback,
- CRLDbHandle, same_issuer), Options);
+ crl_check_same_issuer(OtpCert, Check,
+ dps_and_crls(OtpCert, Callback,
+ CRLDbHandle, same_issuer, LogLevel), Options);
Other ->
Other
end
@@ -1779,21 +1846,27 @@ crl_check_same_issuer(OtpCert, best_effort, Dps, Options) ->
crl_check_same_issuer(OtpCert, _, Dps, Options) ->
public_key:pkix_crls_validate(OtpCert, Dps, Options).
-dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) ->
+dps_and_crls(OtpCert, Callback, CRLDbHandle, ext, LogLevel) ->
case public_key:pkix_dist_points(OtpCert) of
[] ->
no_dps;
DistPoints ->
Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer,
- CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle),
+ CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle, LogLevel),
dps_and_crls(DistPoints, CRLs, [])
end;
-dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) ->
+dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel) ->
DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} =
public_key:pkix_dist_point(OtpCert),
CRLs = lists:flatmap(fun({directoryName, Issuer}) ->
- Callback:select(Issuer, CRLDbHandle);
+ case Callback:select(Issuer, CRLDbHandle) of
+ {logger, LogInfo, Return} ->
+ handle_log(LogLevel, LogInfo),
+ Return;
+ Return ->
+ Return
+ end;
(_) ->
[]
end, GenNames),
@@ -1805,9 +1878,9 @@ dps_and_crls([DP | Rest], CRLs, Acc) ->
DpCRL = [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs],
dps_and_crls(Rest, CRLs, DpCRL ++ Acc).
-distpoints_lookup([],_, _, _) ->
+distpoints_lookup([],_, _, _, _) ->
[];
-distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) ->
+distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle, LogLevel) ->
Result =
try Callback:lookup(DistPoint, Issuer, CRLDbHandle)
catch
@@ -1818,8 +1891,11 @@ distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) ->
end,
case Result of
not_available ->
- distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle);
- CRLs ->
+ distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle, LogLevel);
+ {logger, LogInfo, CRLs} ->
+ handle_log(LogLevel, LogInfo),
+ CRLs;
+ CRLs ->
CRLs
end.
@@ -2144,7 +2220,7 @@ encode_psk_identities([#psk_identity{
identity = Identity,
obfuscated_ticket_age = Age}|T], Acc) ->
IdLen = byte_size(Identity),
- encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,Age/binary>>).
+ encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,?UINT32(Age)>>).
encode_psk_binders(Binders) ->
@@ -2613,7 +2689,7 @@ decode_psk_identities(Identities) ->
%%
decode_psk_identities(<<>>, Acc) ->
lists:reverse(Acc);
-decode_psk_identities(<<?UINT16(Len), Identity:Len/binary, Age:4/binary, Rest/binary>>, Acc) ->
+decode_psk_identities(<<?UINT16(Len), Identity:Len/binary, ?UINT32(Age), Rest/binary>>, Acc) ->
decode_psk_identities(Rest, [#psk_identity{
identity = Identity,
obfuscated_ticket_age = Age}|Acc]).
@@ -3202,3 +3278,6 @@ empty_extensions(_, server_hello) ->
alpn => undefined,
next_protocol_negotiation => undefined,
ec_point_formats => undefined}.
+
+handle_log(Level, {LogLevel, ReportMap, Meta}) ->
+ ssl_logger:log(Level, LogLevel, ReportMap, Meta).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index fc9f16a189..ceca206605 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -60,6 +60,7 @@
-define(CDR_MAGIC, "GIOP").
-define(CDR_HDR_SIZE, 12).
-define(INTERNAL_ACTIVE_N, 100).
+-define(DEPTH, 20).
-define(DEFAULT_TIMEOUT, 5000).
-define(NO_DIST_POINT, "http://dummy/no_distribution_point").
@@ -108,76 +109,91 @@
-define('24H_in_msec', 86400000).
-define('24H_in_sec', 86400).
--record(ssl_options, {
- protocol :: tls | dtls | 'undefined',
- versions :: [ssl_record:ssl_version()] | 'undefined', %% ssl_record:atom_version() in API
- verify :: verify_none | verify_peer | 'undefined',
- verify_fun, %%:: fun(CertVerifyErrors::term()) -> boolean(),
- partial_chain :: fun() | 'undefined',
- fail_if_no_peer_cert :: boolean() | 'undefined',
- verify_client_once :: boolean() | 'undefined',
- %% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError}
- validate_extensions_fun,
- depth :: integer() | 'undefined',
- certfile :: binary() | 'undefined',
- cert :: public_key:der_encoded() | secret_printout() | 'undefined',
- keyfile :: binary() | 'undefined',
- key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo' | 'undefined',
- public_key:der_encoded()} | map() %%map() -> ssl:key() how to handle dialyzer?
- | secret_printout() | 'undefined',
- password :: string() | secret_printout() | 'undefined',
- cacerts :: [public_key:der_encoded()] | secret_printout() | 'undefined',
- cacertfile :: binary() | 'undefined',
- dh :: public_key:der_encoded() | secret_printout() | 'undefined',
- dhfile :: binary() | secret_printout() | 'undefined',
- user_lookup_fun, % server option, fun to lookup the user
- psk_identity :: binary() | secret_printout() | 'undefined',
- srp_identity, % client option {User, Password}
- ciphers, %
- %% Local policy for the server if it want's to reuse the session
- %% or not. Defaluts to allways returning true.
- %% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean()
- reuse_session :: fun() | binary() | undefined, %% Server side is a fun()
- %% If false sessions will never be reused, if true they
- %% will be reused if possible.
- reuse_sessions :: boolean() | save | 'undefined', %% Only client side can use value save
- renegotiate_at,
- secure_renegotiate,
- client_renegotiation,
- %% undefined if not hibernating, or number of ms of
- %% inactivity after which ssl_connection will go into
- %% hibernation
- hibernate_after :: timeout() | 'undefined',
- %% This option should only be set to true by inet_tls_dist
- erl_dist = false :: boolean(),
- alpn_advertised_protocols = undefined :: [binary()] | undefined,
- alpn_preferred_protocols = undefined :: [binary()] | undefined,
- next_protocols_advertised = undefined :: [binary()] | undefined,
- next_protocol_selector = undefined, %% fun([binary()]) -> binary())
- log_level = notice :: atom(),
- server_name_indication = undefined,
- sni_hosts :: [{inet:hostname(), [tuple()]}] | 'undefined',
- sni_fun :: function() | undefined,
- %% Should the server prefer its own cipher order over the one provided by
- %% the client?
- honor_cipher_order = false :: boolean(),
- padding_check = true :: boolean(),
- %%Should we use 1/n-1 or 0/n splitting to mitigate BEAST, or disable
- %%mitigation entirely?
- beast_mitigation = one_n_minus_one :: one_n_minus_one | zero_n | disabled,
- fallback = false :: boolean(),
- crl_check :: boolean() | peer | best_effort | 'undefined',
- crl_cache,
- signature_algs,
- signature_algs_cert,
- eccs,
- supported_groups, %% RFC 8422, RFC 8446
- honor_ecc_order :: boolean() | 'undefined',
- max_handshake_size :: integer() | 'undefined',
- handshake,
- customize_hostname_check
- %% ,
- %% save_session :: boolean()
+
+%% This map stores all supported options with default values and
+%% list of dependencies:
+%% #{<option> => {<default_value>, [<option>]},
+%% ...}
+-define(RULES,
+ #{
+ alpn_advertised_protocols => {undefined, [versions]},
+ alpn_preferred_protocols => {undefined, [versions]},
+ beast_mitigation => {one_n_minus_one, [versions]},
+ cacertfile => {undefined, [versions,
+ verify_fun,
+ cacerts]},
+ cacerts => {undefined, [versions]},
+ cert => {undefined, [versions]},
+ certfile => {<<>>, [versions]},
+ ciphers => {[], [versions]},
+ client_renegotiation => {undefined, [versions]},
+ crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]},
+ crl_check => {false, [versions]},
+ customize_hostname_check => {[], [versions]},
+ depth => {1, [versions]},
+ dh => {undefined, [versions]},
+ dhfile => {undefined, [versions]},
+ eccs => {undefined, [versions]},
+ erl_dist => {false, [versions]},
+ fail_if_no_peer_cert => {false, [versions]},
+ fallback => {false, [versions]},
+ handshake => {full, [versions]},
+ hibernate_after => {infinity, [versions]},
+ honor_cipher_order => {false, [versions]},
+ honor_ecc_order => {undefined, [versions]},
+ key => {undefined, [versions]},
+ keyfile => {undefined, [versions,
+ certfile]},
+ log_level => {notice, [versions]},
+ max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
+ next_protocol_selector => {undefined, [versions]},
+ next_protocols_advertised => {undefined, [versions]},
+ padding_check => {true, [versions]},
+ partial_chain => {fun(_) -> unknown_ca end, [versions]},
+ password => {"", [versions]},
+ protocol => {tls, []},
+ psk_identity => {undefined, [versions]},
+ renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]},
+ reuse_session => {undefined, [versions]},
+ reuse_sessions => {true, [versions]},
+ anti_replay => {undefined, [versions, session_tickets]},
+ secure_renegotiate => {true, [versions]},
+ server_name_indication => {undefined, [versions]},
+ session_tickets => {disabled, [versions]},
+ signature_algs => {undefined, [versions]},
+ signature_algs_cert => {undefined, [versions]},
+ sni_fun => {undefined, [versions,
+ sni_hosts]},
+ sni_hosts => {[], [versions]},
+ srp_identity => {undefined, [versions]},
+ supported_groups => {undefined, [versions]},
+ use_ticket => {undefined, [versions]},
+ user_lookup_fun => {undefined, [versions]},
+ validate_extensions_fun => {undefined, [versions]},
+ verify => {verify_none, [versions,
+ fail_if_no_peer_cert,
+ partial_chain,
+ verify_client_once]},
+ verify_client_once => {false, [versions]},
+ verify_fun =>
+ {
+ {fun(_,{bad_cert, _}, UserState) ->
+ {valid, UserState};
+ (_,{extension, #'Extension'{critical = true}}, UserState) ->
+ %% This extension is marked as critical, so
+ %% certificate verification should fail if we don't
+ %% understand the extension. However, this is
+ %% `verify_none', so let's accept it anyway.
+ {valid, UserState};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, []},
+ [versions, verify]},
+ versions => {[], [protocol]}
}).
-record(socket_options,
@@ -191,7 +207,8 @@
-record(config, {ssl, %% SSL parameters
inet_user, %% User set inet options
- emulated, %% Emulated option list or "inherit_tracker" pid
+ emulated, %% Emulated option list or
+ trackers,
dtls_handler,
inet_ssl, %% inet options for internal ssl socket
transport_info, %% Callback info
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index 514a4464bc..9ee0c23aaa 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -20,9 +20,10 @@
-module(ssl_logger).
--export([debug/4,
+-export([log/4,
+ debug/4,
format/2,
- notice/2]).
+ format/1]).
-define(DEC2HEX(X),
if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0;
@@ -31,6 +32,7 @@
-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))).
+-include("ssl_internal.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
@@ -40,31 +42,21 @@
-include_lib("kernel/include/logger.hrl").
%%-------------------------------------------------------------------------
-%% External API
+%% Internal API -- Stateful logging
%%-------------------------------------------------------------------------
-%% SSL log formatter
-format(#{level:= _Level, msg:= {report, Msg}, meta:= _Meta}, _Config0) ->
- #{direction := Direction,
- protocol := Protocol,
- message := Content} = Msg,
- case Protocol of
- 'record' ->
- BinMsg =
- case Content of
- #ssl_tls{} ->
- [tls_record:build_tls_record(Content)];
- _ when is_list(Content) ->
- lists:flatten(Content)
- end,
- format_tls_record(Direction, BinMsg);
- 'handshake' ->
- format_handshake(Direction, Content);
- _Other ->
- []
+log(Level, LogLevel, ReportMap, Meta) ->
+ case logger:compare_levels(LogLevel, Level) of
+ lt ->
+ logger:log(Level, ReportMap, Meta#{depth => ?DEPTH,
+ report_cb => fun ?MODULE:format/1});
+ eq ->
+ logger:log(Level, ReportMap, Meta#{depth => ?DEPTH,
+ report_cb => fun ?MODULE:format/1});
+ _ ->
+ ok
end.
-%% Stateful logging
debug(Level, Direction, Protocol, Message)
when (Direction =:= inbound orelse Direction =:= outbound) andalso
(Protocol =:= 'record' orelse Protocol =:= 'handshake') ->
@@ -83,17 +75,64 @@ debug(Level, Direction, Protocol, Message)
ok
end.
-%% Stateful logging
-notice(Level, Report) ->
- case logger:compare_levels(Level, notice) of
- lt ->
- ?LOG_NOTICE(Report);
- eq ->
- ?LOG_NOTICE(Report);
- _ ->
- ok
+%%-------------------------------------------------------------------------
+%% Report formatting CB
+%%-------------------------------------------------------------------------
+format(#{alert := Alert, alerter := own} = Report) ->
+ #{protocol := ProtocolName,
+ role := Role,
+ alert := Alert,
+ statename := StateName } = Report,
+ ssl_alert:own_alert_format(ProtocolName, Role, StateName, Alert);
+format(#{alert := Alert, alerter := peer} = Report) ->
+ #{protocol := ProtocolName,
+ role := Role,
+ alert := Alert,
+ statename := StateName } = Report,
+ ssl_alert:alert_format(ProtocolName, Role, StateName, Alert);
+format(#{alert := Alert, alerter := ignored} = Report) ->
+ #{protocol := ProtocolName,
+ role := Role,
+ alert := Alert,
+ statename := StateName} = Report,
+ %% Happens in DTLS
+ {Fmt, Args} = ssl_alert:own_alert_format(ProtocolName, Role, StateName, Alert),
+ {"~s " ++ Fmt, ["Ignored alert to mitigate DoS attacks", Args]};
+format(#{description := Desc} = Report) ->
+ #{reason := Reason} = Report,
+ {"~s11:~p"
+ "~n"
+ "~s11:~p"
+ "~n",
+ ["Description", Desc, "Reason", Reason]
+ }.
+
+%%-------------------------------------------------------------------------
+%% SSL log handler formatter
+%%-------------------------------------------------------------------------
+format(#{msg:= {report, Msg}}, _Config0) ->
+ #{direction := Direction,
+ protocol := Protocol,
+ message := Content} = Msg,
+ case Protocol of
+ 'record' ->
+ BinMsg =
+ case Content of
+ #ssl_tls{} ->
+ [tls_record:build_tls_record(Content)];
+ _ when is_list(Content) ->
+ lists:flatten(Content)
+ end,
+ format_tls_record(Direction, BinMsg);
+ 'handshake' ->
+ format_handshake(Direction, Content);
+ _Other ->
+ []
end.
+%%-------------------------------------------------------------------------
+%% Internal functions
+%%-------------------------------------------------------------------------
%%-------------------------------------------------------------------------
%% Handshake Protocol
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 456a560bf6..fd842fcec4 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -53,7 +53,6 @@
session_lifetime :: integer(),
certificate_db :: db_handle(),
session_validation_timer :: reference(),
- last_delay_timer = {undefined, undefined},%% Keep for testing purposes
session_cache_client_max :: integer(),
session_cache_server_max :: integer(),
session_server_invalidator :: undefined | pid(),
@@ -375,12 +374,6 @@ handle_info(validate_sessions, #state{session_cache_cb = CacheCb,
session_client_invalidator = CPid,
session_server_invalidator = SPid}};
-
-handle_info({delayed_clean_session, Key, Cache}, #state{session_cache_cb = CacheCb
- } = State) ->
- CacheCb:delete(Cache, Key),
- {noreply, State};
-
handle_info({clean_cert_db, Ref, File},
#state{certificate_db = [CertDb, {RefDb, FileMapDb} | _]} = State) ->
@@ -470,14 +463,6 @@ session_validation({{Port, _}, Session}, LifeTime) ->
validate_session(Port, Session, LifeTime),
LifeTime.
-delay_time() ->
- case application:get_env(ssl, session_delay_cleanup_time) of
- {ok, Time} when is_integer(Time) ->
- Time;
- _ ->
- ?CLEAN_SESSION_DB
- end.
-
max_session_cache_size(CacheType) ->
case application:get_env(ssl, CacheType) of
{ok, Size} when is_integer(Size) ->
@@ -486,36 +471,15 @@ max_session_cache_size(CacheType) ->
?DEFAULT_MAX_SESSION_CACHE
end.
-invalidate_session(Cache, CacheCb, Key, Session, State) ->
+invalidate_session(Cache, CacheCb, Key, _Session, State) ->
case CacheCb:lookup(Cache, Key) of
undefined -> %% Session is already invalidated
{noreply, State};
- #session{is_resumable = new} ->
+ #session{} ->
CacheCb:delete(Cache, Key),
- {noreply, State};
- _ ->
- delayed_invalidate_session(CacheCb, Cache, Key, Session, State)
+ {noreply, State}
end.
-delayed_invalidate_session(CacheCb, Cache, Key, Session,
- #state{last_delay_timer = LastTimer} = State) ->
- %% When a registered session is invalidated we need to
- %% wait a while before deleting it as there might be
- %% pending connections that rightfully needs to look up
- %% the session data but new connections should not get to
- %% use this session.
- CacheCb:update(Cache, Key, Session#session{is_resumable = false}),
- TRef =
- erlang:send_after(delay_time(), self(),
- {delayed_clean_session, Key, Cache}),
- {noreply, State#state{last_delay_timer =
- last_delay_timer(Key, TRef, LastTimer)}}.
-
-last_delay_timer({{_,_},_}, TRef, {LastServer, _}) ->
- {LastServer, TRef};
-last_delay_timer({_,_}, TRef, {_, LastClient}) ->
- {TRef, LastClient}.
-
%% If we cannot generate a not allready in use session ID in
%% ?GEN_UNIQUE_ID_MAX_TRIES we make the new session uncacheable The
%% value of ?GEN_UNIQUE_ID_MAX_TRIES is stolen from open SSL which
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
index dec48fa914..49db77ffd0 100644
--- a/lib/ssl/src/ssl_pkix_db.erl
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -310,9 +310,8 @@ decode_certs(Ref, Cert) ->
{decoded, {{Ref, SerialNumber, Issuer}, {Cert, ErlCert}}}
catch
error:_ ->
- Report = io_lib:format("SSL WARNING: Ignoring a CA cert as "
- "it could not be correctly decoded.~n", []),
- ?LOG_NOTICE(Report),
+ ?LOG_NOTICE("SSL WARNING: Ignoring a CA cert as "
+ "it could not be correctly decoded.~n"),
undefined
end.
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index 6d4d47cedb..e37e3f714f 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -66,6 +66,7 @@
hash_size, % unit 8
compression_algorithm, % unit 8
master_secret, % opaque 48
+ resumption_master_secret,
client_random, % opaque 32
server_random, % opaque 32
exportable % boolean
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index fd012acd5e..0f7ea0502b 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -30,7 +30,7 @@
-include("ssl_api.hrl").
%% Internal application API
--export([is_new/2, client_id/4, server_id/6, valid_session/2]).
+-export([is_new/2, client_select_session/4, server_select_session/7, valid_session/2]).
-type seconds() :: integer().
@@ -48,41 +48,38 @@ is_new(_ClientSuggestion, _ServerDecision) ->
true.
%%--------------------------------------------------------------------
--spec client_id({ssl:host(), inet:port_number(), ssl_options()}, db_handle(), atom(),
- undefined | binary()) -> binary().
+-spec client_select_session({ssl:host(), inet:port_number(), map()}, db_handle(), atom(),
+ #session{}) -> #session{}.
%%
%% Description: Should be called by the client side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
-client_id({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)->
- case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
- undefined ->
- <<>>;
- #session{} ->
- SessionId
- end;
-client_id(ClientInfo, Cache, CacheCb, OwnCert) ->
- case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
- no_session ->
- <<>>;
- SessionId ->
- SessionId
- end.
+client_select_session({_, _, #{versions := Versions,
+ protocol := Protocol}} = ClientInfo,
+ Cache, CacheCb, NewSession) ->
+
+ RecordCb = record_cb(Protocol),
+ Version = RecordCb:lowest_protocol_version(Versions),
+
+ case Version of
+ {3, N} when N >= 4 ->
+ NewSession#session{session_id = crypto:strong_rand_bytes(32)};
+ _ ->
+ do_client_select_session(ClientInfo, Cache, CacheCb, NewSession)
+ end.
--spec valid_session(#session{}, seconds() | {invalidate_before, integer()}) -> boolean().
+%%--------------------------------------------------------------------
+-spec server_select_session(ssl_record:ssl_version(), inet:port_number(), binary(), map(),
+ binary(),db_handle(), atom()) -> {binary(), #session{} | undefined}.
%%
-%% Description: Check that the session has not expired
+%% Description: Should be called by the server side to get an id
+%% for the client hello message.
%%--------------------------------------------------------------------
-valid_session(#session{time_stamp = TimeStamp}, {invalidate_before, Before}) ->
- TimeStamp > Before;
-valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
- Now = erlang:monotonic_time(),
- Lived = erlang:convert_time_unit(Now-TimeStamp, native, seconds),
- Lived < LifeTime.
-
-server_id(Port, <<>>, _SslOpts, _Cert, _, _) ->
+server_select_session({_, Minor}, Port, <<>>, _SslOpts, _Cert, _, _) when Minor >= 4 ->
{ssl_manager:new_session_id(Port), undefined};
-server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
+server_select_session(_, Port, <<>>, _SslOpts, _Cert, _, _) ->
+ {ssl_manager:new_session_id(Port), undefined};
+server_select_session(_, Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
LifeTime = case application:get_env(ssl, session_lifetime) of
{ok, Time} when is_integer(Time) -> Time;
_ -> ?'24H_in_sec'
@@ -96,11 +93,38 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
{ssl_manager:new_session_id(Port), undefined}
end.
+-spec valid_session(#session{}, seconds() | {invalidate_before, integer()}) -> boolean().
+%%
+%% Description: Check that the session has not expired
+%%--------------------------------------------------------------------
+valid_session(#session{time_stamp = TimeStamp}, {invalidate_before, Before}) ->
+ TimeStamp > Before;
+valid_session(#session{time_stamp = TimeStamp}, LifeTime) ->
+ Now = erlang:monotonic_time(),
+ Lived = erlang:convert_time_unit(Now-TimeStamp, native, seconds),
+ Lived < LifeTime.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+
+do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession) when is_binary(SessionId)->
+ case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
+ undefined ->
+ NewSession#session{session_id = <<>>};
+ #session{} = Session->
+ Session
+ end;
+do_client_select_session(ClientInfo,
+ Cache, CacheCb, #session{own_certificate = OwnCert} = NewSession) ->
+ case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
+ no_session ->
+ NewSession#session{session_id = <<>>};
+ Session ->
+ Session
+ end.
+
select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
- %% If reuse_sessions == true | save a new session should be created
+ %% If reuse_sessions == false | save a new session should be created
no_session;
select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
@@ -117,7 +141,7 @@ select_session(Sessions, #{ciphers := Ciphers}, OwnCert) ->
end,
case lists:dropwhile(IsNotResumable, Sessions) of
[] -> no_session;
- [Session | _] -> Session#session.session_id
+ [Session | _] -> Session
end.
is_resumable(_, _, #{reuse_sessions := false}, _, _, _, _) ->
@@ -154,3 +178,8 @@ reusable_options(#{fail_if_no_peer_cert := true,
(Session#session.peer_certificate =/= undefined);
reusable_options(_,_) ->
true.
+
+record_cb(tls) ->
+ tls_record;
+record_cb(dtls) ->
+ dtls_record.
diff --git a/lib/ssl/src/tls_bloom_filter.erl b/lib/ssl/src/tls_bloom_filter.erl
new file mode 100644
index 0000000000..5cc29b6048
--- /dev/null
+++ b/lib/ssl/src/tls_bloom_filter.erl
@@ -0,0 +1,113 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Bloom Filter implementation for anti-replay protection
+%% in TLS 1.3 (stateless tickets)
+%%----------------------------------------------------------------------
+
+-module(tls_bloom_filter).
+
+-export([add_elem/2,
+ contains/2,
+ new/2,
+ rotate/1]).
+
+%%--------------------------------------------------------------------
+%% API ---------------------------------------------------------------
+%%--------------------------------------------------------------------
+
+%% Create new Bloom Filter with k hashes, m bits in the filter
+new(K, M) ->
+ Size = round(math:ceil(M / 8)),
+ BitField = binary:copy(<<0>>, Size),
+ #{k => K,
+ m => M,
+ current => BitField,
+ old => BitField
+ }.
+
+
+%% Add new element to Bloom Filter
+add_elem(#{k := K,
+ m := M,
+ current := BitField0} = BloomFilter,
+ Elem) ->
+ Hash = hash(Elem, K, M),
+ BitField = set_bits(BitField0, Hash),
+ BloomFilter#{current => BitField}.
+
+
+%% Check if Bloom Filter contains element.
+contains(#{k := K,
+ m := M,
+ current := BFCurrent,
+ old := BFOld},
+ Elem) ->
+ Hash = hash(Elem, K, M),
+ lists:all(fun (Pos) -> bit_is_set(BFCurrent, Pos) end, Hash) orelse
+ lists:all(fun (Pos) -> bit_is_set(BFOld, Pos) end, Hash).
+
+
+rotate(#{m := M,
+ current := BFCurrent} = BloomFilter) ->
+ Size = round(math:ceil(M / 8)),
+ BFNew = binary:copy(<<0>>, Size),
+ BloomFilter#{current := BFNew,
+ old := BFCurrent}.
+
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+bit_is_set(<<1:1,_/bitstring>>, 0) ->
+ true;
+bit_is_set(BitField, N) ->
+ case BitField of
+ <<_:N,1:1,_/bitstring>> ->
+ true;
+ _ ->
+ false
+ end.
+
+
+set_bits(BitField, []) ->
+ BitField;
+set_bits(BitField, [H|T]) ->
+ set_bits(set_bit(BitField, H), T).
+
+
+set_bit(BitField, 0) ->
+ <<_:1,Rest/bitstring>> = BitField,
+ <<1:1,Rest/bitstring>>;
+set_bit(BitField, B) ->
+ <<Front:B,_:1,Rest/bitstring>> = BitField,
+ <<Front:B,1:1,Rest/bitstring>>.
+
+
+%% Kirsch-Mitzenmacher-Optimization
+hash(Elem, K, M) ->
+ hash(Elem, K, M, []).
+%%
+hash(_, 0, _, Acc) ->
+ Acc;
+hash(Elem, K, M, Acc) ->
+ H = (erlang:phash2({Elem, 0}, M) + (K - 1) * erlang:phash2({Elem, 1}, M)) rem M,
+ hash(Elem, K - 1, M, [H|Acc]).
diff --git a/lib/ssl/src/tls_client_ticket_store.erl b/lib/ssl/src/tls_client_ticket_store.erl
new file mode 100644
index 0000000000..6343b9cf0b
--- /dev/null
+++ b/lib/ssl/src/tls_client_ticket_store.erl
@@ -0,0 +1,355 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Handle client side TLS-1.3 session ticket storage
+%%----------------------------------------------------------------------
+
+-module(tls_client_ticket_store).
+-behaviour(gen_server).
+
+-include("tls_handshake_1_3.hrl").
+
+%% API
+-export([find_ticket/2,
+ get_tickets/2,
+ lock_tickets/2,
+ remove_tickets/1,
+ start_link/2,
+ store_ticket/4,
+ unlock_tickets/2,
+ update_ticket/2]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3, format_status/2]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {
+ db,
+ lifetime,
+ max
+ }).
+
+-record(data, {
+ pos = undefined,
+ hkdf,
+ sni,
+ psk,
+ timestamp,
+ ticket,
+ lock = undefined
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec start_link(integer(), integer()) -> {ok, Pid :: pid()} |
+ {error, Error :: {already_started, pid()}} |
+ {error, Error :: term()} |
+ ignore.
+start_link(Max, Lifetime) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [Max, Lifetime], []).
+
+find_ticket(Pid, HashAlgos) ->
+ %% TODO use also SNI when selecting tickets
+ gen_server:call(?MODULE, {find_ticket, Pid, HashAlgos}, infinity).
+
+get_tickets(Pid, Keys) ->
+ gen_server:call(?MODULE, {get_tickets, Pid, Keys}, infinity).
+
+lock_tickets(_, undefined) ->
+ ok;
+lock_tickets(Pid, Keys) ->
+ gen_server:call(?MODULE, {lock, Pid, Keys}, infinity).
+
+remove_tickets([]) ->
+ ok;
+remove_tickets(Keys) ->
+ gen_server:cast(?MODULE, {remove_tickets, Keys}).
+
+store_ticket(Ticket, HKDF, SNI, PSK) ->
+ gen_server:call(?MODULE, {store_ticket, Ticket, HKDF, SNI, PSK}, infinity).
+
+unlock_tickets(Pid, Keys) ->
+ gen_server:call(?MODULE, {unlock, Pid, Keys}, infinity).
+
+update_ticket(Key, Pos) ->
+ gen_server:call(?MODULE, {update_ticket, Key, Pos}, infinity).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+-spec init(Args :: term()) -> {ok, State :: term()}.
+
+init(Args) ->
+ process_flag(trap_exit, true),
+ State = inital_state(Args),
+ {ok, State}.
+
+-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
+ {reply, Reply :: term(), NewState :: term()} .
+handle_call({find_ticket, Pid, HashAlgos}, _From, State) ->
+ Key = find_ticket(State, Pid, HashAlgos),
+ {reply, Key, State};
+handle_call({get_tickets, Pid, Keys}, _From, State) ->
+ Data = get_tickets(State, Pid, Keys),
+ {reply, Data, State};
+handle_call({lock, Pid, Keys}, _From, State0) ->
+ State = lock_tickets(State0, Pid, Keys),
+ {reply, ok, State};
+handle_call({store_ticket, Ticket, HKDF, SNI, PSK}, _From, State0) ->
+ State = store_ticket(State0, Ticket, HKDF, SNI, PSK),
+ {reply, ok, State};
+handle_call({unlock, Pid, Keys}, _From, State0) ->
+ State = unlock_tickets(State0, Pid, Keys),
+ {reply, ok, State};
+handle_call({update_ticket, Key, Pos}, _From, State0) ->
+ State = update_ticket(State0, Key, Pos),
+ {reply, ok, State}.
+
+-spec handle_cast(Request :: term(), State :: term()) ->
+ {noreply, NewState :: term()}.
+handle_cast({remove_tickets, Key}, State0) ->
+ State = remove_tickets(State0, Key),
+ {noreply, State};
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+-spec handle_info(Info :: timeout() | term(), State :: term()) ->
+ {noreply, NewState :: term()}.
+handle_info(remove_invalid_tickets, State0) ->
+ State = remove_invalid_tickets(State0),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+-spec terminate(Reason :: normal | shutdown | {shutdown, term()} | term(),
+ State :: term()) -> any().
+terminate(_Reason, _State) ->
+ ok.
+
+-spec code_change(OldVsn :: term() | {down, term()},
+ State :: term(),
+ Extra :: term()) -> {ok, NewState :: term()} |
+ {error, Reason :: term()}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+-spec format_status(Opt :: normal | terminate,
+ Status :: list()) -> Status :: term().
+format_status(_Opt, Status) ->
+ Status.
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+inital_state([Max, Lifetime]) ->
+ erlang:send_after(Lifetime * 1000, self(), remove_invalid_tickets),
+ #state{db = gb_trees:empty(),
+ lifetime = Lifetime,
+ max = Max
+ }.
+
+
+find_ticket(_, _, []) ->
+ undefined;
+find_ticket(#state{db = Db,
+ lifetime = Lifetime} = State, Pid, [Hash|T]) ->
+ case iterate_tickets(gb_trees:iterator(Db), Pid, Hash, Lifetime) of
+ none ->
+ find_ticket(State, Pid, T);
+ Key ->
+ Key
+ end.
+
+
+iterate_tickets(Iter0, Pid, Hash, Lifetime) ->
+ case gb_trees:next(Iter0) of
+ {Key, #data{hkdf = Hash,
+ timestamp = Timestamp,
+ lock = Lock}, Iter} when Lock =:= undefined orelse
+ Lock =:= Pid ->
+ Age = erlang:system_time(seconds) - Timestamp,
+ if Age < Lifetime ->
+ Key;
+ true ->
+ iterate_tickets(Iter, Pid, Hash, Lifetime)
+ end;
+ {_, _, Iter} ->
+ iterate_tickets(Iter, Pid, Hash, Lifetime);
+ none ->
+ none
+ end.
+
+
+%% Get tickets that are not locked by another process
+get_tickets(State, Pid, Keys) ->
+ get_tickets(State, Pid, Keys, []).
+%%
+get_tickets(_, _, [], []) ->
+ undefined; %% No tickets found
+get_tickets(_, _, [], Acc) ->
+ Acc;
+get_tickets(#state{db = Db} = State, Pid, [Key|T], Acc) ->
+ try gb_trees:get(Key, Db) of
+ #data{pos = Pos,
+ hkdf = HKDF,
+ psk = PSK,
+ timestamp = Timestamp,
+ ticket = NewSessionTicket,
+ lock = Lock} when Lock =:= undefined orelse
+ Lock =:= Pid ->
+ #new_session_ticket{
+ ticket_lifetime = _LifeTime,
+ ticket_age_add = AgeAdd,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
+ extensions = _Extensions
+ } = NewSessionTicket,
+ TicketAge = erlang:system_time(seconds) - Timestamp,
+ ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
+ Identity = #psk_identity{
+ identity = Ticket,
+ obfuscated_ticket_age = ObfuscatedTicketAge},
+ get_tickets(State, Pid, T, [{Key, Pos, Identity, PSK, Nonce, HKDF}|Acc])
+ catch
+ _:_ ->
+ get_tickets(State, Pid, T, Acc)
+ end.
+
+%% The "obfuscated_ticket_age"
+%% field of each PskIdentity contains an obfuscated version of the
+%% ticket age formed by taking the age in milliseconds and adding the
+%% "ticket_age_add" value that was included with the ticket
+%% (see Section 4.6.1), modulo 2^32.
+obfuscate_ticket_age(TicketAge, AgeAdd) ->
+ (TicketAge + AgeAdd) rem round(math:pow(2,32)).
+
+
+remove_tickets(State, []) ->
+ State;
+remove_tickets(State0, [Key|T]) ->
+ remove_tickets(remove_ticket(State0, Key), T).
+
+
+remove_ticket(#state{db = Db0} = State, Key) ->
+ Db = gb_trees:delete_any(Key, Db0),
+ State#state{db = Db}.
+
+
+remove_invalid_tickets(#state{db = Db,
+ lifetime = Lifetime} = State0) ->
+ Keys = collect_invalid_tickets(gb_trees:iterator(Db), Lifetime),
+ State = remove_tickets(State0, Keys),
+ erlang:send_after(Lifetime * 1000, self(), remove_invalid_tickets),
+ State.
+
+
+collect_invalid_tickets(Iter, Lifetime) ->
+ collect_invalid_tickets(Iter, Lifetime, []).
+%%
+collect_invalid_tickets(Iter0, Lifetime, Acc) ->
+ case gb_trees:next(Iter0) of
+ {Key, #data{timestamp = Timestamp,
+ lock = undefined}, Iter} ->
+ Age = erlang:system_time(seconds) - Timestamp,
+ if Age < Lifetime ->
+ collect_invalid_tickets(Iter, Lifetime, Acc);
+ true ->
+ collect_invalid_tickets(Iter, Lifetime, [Key|Acc])
+ end;
+ {_, _, Iter} -> %% Skip locked tickets
+ collect_invalid_tickets(Iter, Lifetime, Acc);
+ none ->
+ Acc
+ end.
+
+
+store_ticket(#state{db = Db0, max = Max} = State, Ticket, HKDF, SNI, PSK) ->
+ Timestamp = erlang:system_time(seconds),
+ Size = gb_trees:size(Db0),
+ Db1 = if Size =:= Max ->
+ delete_oldest(Db0);
+ true ->
+ Db0
+ end,
+ Key = erlang:monotonic_time(),
+ Db = gb_trees:insert(Key,
+ #data{hkdf = HKDF,
+ sni = SNI,
+ psk = PSK,
+ timestamp = Timestamp,
+ ticket = Ticket},
+ Db1),
+ State#state{db = Db}.
+
+
+update_ticket(#state{db = Db0} = State, Key, Pos) ->
+ try gb_trees:get(Key, Db0) of
+ Value ->
+ Db = gb_trees:update(Key, Value#data{pos = Pos}, Db0),
+ State#state{db = Db}
+ catch
+ _:_ ->
+ State
+ end.
+
+
+delete_oldest(Db0) ->
+ try gb_trees:take_smallest(Db0) of
+ {_, _, Db} ->
+ Db
+ catch
+ _:_ ->
+ Db0
+ end.
+
+
+lock_tickets(State, Pid, Keys) ->
+ set_lock(State, Pid, Keys, lock).
+
+
+unlock_tickets(State, Pid, Keys) ->
+ set_lock(State, Pid, Keys, unlock).
+
+
+set_lock(State, _, [], _) ->
+ State;
+set_lock(#state{db = Db0} = State, Pid, [Key|T], Cmd) ->
+ try gb_trees:get(Key, Db0) of
+ Value ->
+ Db = gb_trees:update(Key, update_data_lock(Value, Pid, Cmd), Db0),
+ set_lock(State#state{db = Db}, Pid, T, Cmd)
+ catch
+ _:_ ->
+ set_lock(State, Pid, T, Cmd)
+ end.
+
+
+update_data_lock(Value, Pid, lock) ->
+ Value#data{lock = Pid};
+update_data_lock(#data{lock = Pid} = Value, Pid, unlock) ->
+ Value#data{lock = undefined};
+update_data_lock(Value, _, _) ->
+ Value.
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index cf104cd805..20224f4c65 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -98,28 +98,28 @@
%%====================================================================
%% Setup
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts,
User, {CbModule, _,_, _, _} = CbInfo,
Timeout) ->
try
{ok, Sender} = tls_sender:start(),
{ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
Error
end;
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Tracker} = Opts,
+start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts,
User, {CbModule, _,_, _, _} = CbInfo,
Timeout) ->
try
{ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
{ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Tracker),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers),
ssl_connection:handshake(SslSocket, Timeout)
catch
error:{badmatch, {error, _} = Error} ->
@@ -298,9 +298,12 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
StateName, #state{protocol_buffers =
#protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
connection_env = #connection_env{negotiated_version = Version},
+ static_env = #static_env{role = Role},
ssl_options = Options} = State0) ->
try
- EffectiveVersion = effective_version(Version, Options),
+ %% Calculate the effective version that should be used when decoding an incoming handshake
+ %% message.
+ EffectiveVersion = effective_version(Version, Options, Role),
{Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
State =
State0#state{protocol_buffers =
@@ -514,8 +517,8 @@ protocol_name() ->
%% Data handling
%%====================================================================
-socket(Pids, Transport, Socket, Tracker) ->
- tls_socket:socket(Pids, Transport, Socket, ?MODULE, Tracker).
+socket(Pids, Transport, Socket, Trackers) ->
+ tls_socket:socket(Pids, Transport, Socket, ?MODULE, Trackers).
setopts(Transport, Socket, Other) ->
tls_socket:setopts(Transport, Socket, Other).
@@ -543,26 +546,48 @@ init({call, From}, {start, Timeout},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
connection_env = CEnv,
ssl_options = #{log_level := LogLevel,
- versions := Versions} = SslOpts,
- session = #session{own_certificate = Cert} = Session0,
+ %% Use highest version in initial ClientHello.
+ %% Versions is a descending list of supported versions.
+ versions := [HelloVersion|_] = Versions,
+ session_tickets := SessionTickets} = SslOpts,
+ session = NewSession,
connection_states = ConnectionStates0
} = State0) ->
KeyShare = maybe_generate_client_shares(SslOpts),
+ Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession),
+ %% Update UseTicket in case of automatic session resumption
+ {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
+ TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert, KeyShare),
+ Session#session.session_id,
+ Renegotiation,
+ Session#session.own_certificate,
+ KeyShare,
+ TicketData),
- HelloVersion = tls_record:hello_version(Versions),
Handshake0 = ssl_handshake:init_handshake_history(),
+
+ %% Update pre_shared_key extension with binders (TLS 1.3)
+ Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion),
+
{BinMsg, ConnectionStates, Handshake} =
- encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0),
+ encode_handshake(Hello1, HelloVersion, ConnectionStates0, Handshake0),
+
tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1),
ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
- State = State0#state{connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
+ %% RequestedVersion is used as the legacy record protocol version and shall be
+ %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the
+ %% lowest supported protocol version.
+ %%
+ %% negotiated_version is also used by the TLS 1.3 state machine and is set after
+ %% ServerHello is processed.
+ RequestedVersion = tls_record:hello_version(Versions),
+ State = State1#state{connection_states = ConnectionStates,
+ connection_env = CEnv#connection_env{
+ negotiated_version = RequestedVersion},
+ session = Session,
handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
start_or_recv_from = From,
key_share = KeyShare},
@@ -599,7 +624,7 @@ hello(internal, #client_hello{extensions = Extensions} = Hello,
{next_state, user_hello, State#state{start_or_recv_from = undefined,
handshake_env = HsEnv#handshake_env{hello = Hello}},
[{reply, From, {ok, Extensions}}]};
-hello(internal, #server_hello{extensions = Extensions} = Hello,
+hello(internal, #server_hello{extensions = Extensions} = Hello,
#state{ssl_options = #{handshake := hello},
handshake_env = HsEnv,
start_or_recv_from = From} = State) ->
@@ -670,9 +695,12 @@ hello(internal, #server_hello{} = Hello,
ssl_connection:handle_session(Hello,
Version, NewId, ConnectionStates, ProtoExt, Protocol, State);
%% TLS 1.3
- {next_state, wait_sh} ->
+ {next_state, wait_sh, SelectedVersion} ->
%% Continue in TLS 1.3 'wait_sh' state
- {next_state, wait_sh, State, [{next_event, internal, Hello}]}
+ {next_state, wait_sh,
+ State#state{
+ connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
+ [{next_event, internal, Hello}]}
end;
hello(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
@@ -762,11 +790,14 @@ connection(internal, #hello_request{},
connection_states = ConnectionStates} = State0) ->
try tls_sender:peer_renegotiate(Pid) of
{ok, Write} ->
+ Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0),
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert, undefined),
- {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}}),
- next_event(hello, no_record, State#state{session = Session0#session{session_id
- = Hello#client_hello.session_id}}, Actions)
+ Session#session.session_id,
+ Renegotiation, Cert, undefined,
+ undefined),
+ {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write},
+ session = Session}),
+ next_event(hello, no_record, State, Actions)
catch
_:_ ->
{stop, {shutdown, sender_blocked}, State0}
@@ -774,19 +805,17 @@ connection(internal, #hello_request{},
connection(internal, #hello_request{},
#state{static_env = #static_env{role = client,
host = Host,
- port = Port,
- session_cache = Cache,
- session_cache_cb = CacheCb},
+ port = Port},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
- session = #session{own_certificate = Cert} = Session0,
+ session = #session{own_certificate = Cert},
ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
- Cache, CacheCb, Renegotiation, Cert, undefined),
+ <<>>, Renegotiation, Cert, undefined,
+ undefined),
{State, Actions} = send_handshake(Hello, State0),
- next_event(hello, no_record, State#state{session = Session0#session{session_id
- = Hello#client_hello.session_id}}, Actions);
+ next_event(hello, no_record, State, Actions);
connection(internal, #client_hello{} = Hello,
#state{static_env = #static_env{role = server},
handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv,
@@ -813,9 +842,9 @@ connection(internal, #client_hello{},
State = reinit_handshake_data(State0),
next_event(?FUNCTION_NAME, no_record, State);
-connection(internal, #new_session_ticket{}, State) ->
+connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
%% TLS 1.3
- %% Drop NewSessionTicket (currently not supported)
+ handle_new_session_ticket(NewSessionTicket, State),
next_event(?FUNCTION_NAME, no_record, State);
connection(Type, Event, State) ->
@@ -981,7 +1010,7 @@ code_change(_OldVsn, StateName, State, _) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
+initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
#{beast_mitigation := BeastMitigation,
erl_dist := IsErlDist,
@@ -1012,8 +1041,8 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
port = Port,
socket = Socket,
session_cache_cb = SessionCacheCb,
- tracker = Tracker
- },
+ trackers = Trackers
+ },
#state{
static_env = InitStatEnv,
handshake_env = #handshake_env{
@@ -1040,7 +1069,7 @@ initialize_tls_sender(#state{static_env = #static_env{
role = Role,
transport_cb = Transport,
socket = Socket,
- tracker = Tracker
+ trackers = Trackers
},
connection_env = #connection_env{negotiated_version = Version},
socket_options = SockOpts,
@@ -1052,7 +1081,7 @@ initialize_tls_sender(#state{static_env = #static_env{
role => Role,
socket => Socket,
socket_options => SockOpts,
- tracker => Tracker,
+ trackers => Trackers,
transport_cb => Transport,
negotiated_version => Version,
renegotiate_at => RenegotiateAt,
@@ -1065,12 +1094,19 @@ next_tls_record(Data, StateName,
tls_cipher_texts = CT0} = Buffers,
ssl_options = SslOpts} = State0) ->
Versions =
- %% TLS 1.3 Client/Server
- %% - Ignore TLSPlaintext.legacy_record_version
- %% - Verify that TLSCiphertext.legacy_record_version is set to 0x0303 for all records
- %% other than an initial ClientHello, where it MAY also be 0x0301.
+ %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all
+ %% record version are accepted when receiving initial ClientHello and
+ %% ServerHello. This can happen in state 'hello' in case of all TLS
+ %% versions and also in state 'start' when TLS 1.3 is negotiated.
+ %% After the version is negotiated all subsequent TLS records shall have
+ %% the proper legacy_record_version (= negotiated_version).
+ %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this
+ %% point it is the same as the negotiated protocol version.
+ %% TODO: Refactor state machine and introduce a record_protocol_version beside
+ %% the negotiated_version.
case StateName of
- hello ->
+ State when State =:= hello orelse
+ State =:= start ->
[tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS];
_ ->
State0#state.connection_env#connection_env.negotiated_version
@@ -1323,7 +1359,55 @@ choose_tls_version(_, _) ->
'tls_v1.2'.
-effective_version(undefined, #{versions := [Version|_]}) ->
+%% Special version handling for TLS 1.3 clients:
+%% In the shared state 'init' negotiated_version is set to requested version and
+%% that is expected by the legacy part of the state machine. However, in order to
+%% be able to process new TLS 1.3 extensions, the effective version shall be set
+%% {3,4}.
+%% When highest supported version is {3,4} the negotiated version is set to {3,3}.
+effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} ->
+ Version;
+%% Use highest supported version during startup (TLS server, all versions).
+effective_version(undefined, #{versions := [Version|_]}, _) ->
Version;
-effective_version(Version, _) ->
+%% Use negotiated version in all other cases.
+effective_version(Version, _, _) ->
Version.
+
+
+handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) ->
+ ok;
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets,
+ server_name_indication := SNI},
+ connection_env = #connection_env{user_application = {_, User}}})
+ when SessionTickets =:= enabled ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK);
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets,
+ server_name_indication := SNI}})
+ when SessionTickets =:= auto ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK).
+
+
+%% Send ticket data to user as opaque binary
+send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) ->
+ Timestamp = erlang:system_time(seconds),
+ TicketData = #{hkdf => HKDF,
+ sni => SNI,
+ psk => PSK,
+ timestamp => Timestamp,
+ ticket => NewSessionTicket},
+ User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}.
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 117e4f059d..6fb96b1f5a 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -128,7 +128,9 @@ start(internal, #client_hello{} = Hello, State0, _Module) ->
{State, start} ->
{next_state, start, State, []};
{State, negotiated} ->
- {next_state, negotiated, State, [{next_event, internal, start_handshake}]}
+ {next_state, negotiated, State, [{next_event, internal, {start_handshake, undefined}}]};
+ {State, negotiated, PSK} -> %% Session Resumption with PSK
+ {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
end;
start(internal, #server_hello{} = ServerHello, State0, _Module) ->
case tls_handshake_1_3:do_start(ServerHello, State0) of
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 203f89a0b8..474cbd621b 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -51,18 +51,18 @@
%%====================================================================
%%--------------------------------------------------------------------
-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(),
- ssl_options(), integer(), atom(), boolean(), der_cert(),
- #key_share_client_hello{} | undefined) ->
+ ssl_options(), binary(), boolean(), der_cert(),
+ #key_share_client_hello{} | undefined, tuple() | undefined) ->
#client_hello{}.
%%
%% Description: Creates a client hello message.
%%--------------------------------------------------------------------
-client_hello(Host, Port, ConnectionStates,
+client_hello(_Host, _Port, ConnectionStates,
#{versions := Versions,
ciphers := UserSuites,
fallback := Fallback
} = SslOpts,
- Cache, CacheCb, Renegotiation, OwnCert, KeyShare) ->
+ Id, Renegotiation, _OwnCert, KeyShare, TicketData) ->
Version = tls_record:highest_protocol_version(Versions),
%% In TLS 1.3, the client indicates its version preferences in the
@@ -83,9 +83,9 @@ client_hello(Host, Port, ConnectionStates,
AvailableCipherSuites,
SslOpts, ConnectionStates,
Renegotiation,
- KeyShare),
+ KeyShare,
+ TicketData),
CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback),
- Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
client_version = LegacyVersion,
cipher_suites = CipherSuites,
@@ -105,7 +105,7 @@ client_hello(Host, Port, ConnectionStates,
{tls_record:tls_version(), {resumed | new, #session{}},
ssl_record:connection_states(), binary() | undefined,
HelloExt::map(), {ssl:hash(), ssl:sign_algo()} |
- undefined} | {atom(), atom()} |#alert{}.
+ undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}.
%%
%% Description: Handles a received hello message
%%--------------------------------------------------------------------
@@ -175,9 +175,9 @@ hello(#server_hello{server_version = LegacyVersion,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt,
ConnectionStates0, Renegotiation);
- {3,4} ->
+ SelectedVersion ->
%% TLS 1.3
- {next_state, wait_sh}
+ {next_state, wait_sh, SelectedVersion}
end;
false ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 5f4c0b9a4a..119ed75d36 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -48,8 +48,13 @@
do_wait_finished/2,
do_wait_sh/2,
do_wait_ee/2,
- do_wait_cert_cr/2]).
+ do_wait_cert_cr/2,
+ get_ticket_data/3,
+ maybe_add_binders/3,
+ maybe_add_binders/4,
+ maybe_automatic_session_resumption/1]).
+-export([is_valid_binder/4]).
%% crypto:hash(sha256, "HelloRetryRequest").
-define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17,
@@ -61,10 +66,10 @@
%% Create handshake messages
%%====================================================================
-server_hello(MsgType, SessionId, KeyShare, ConnectionStates) ->
+server_hello(MsgType, SessionId, KeyShare, PSK, ConnectionStates) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates, read),
- Extensions = server_hello_extensions(MsgType, KeyShare),
+ Extensions = server_hello_extensions(MsgType, KeyShare, PSK),
#server_hello{server_version = {3,3}, %% legacy_version
cipher_suite = SecParams#security_parameters.cipher_suite,
compression_method = 0, %% legacy attribute
@@ -81,13 +86,19 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates) ->
%% extensions that were not first offered by the client in its
%% ClientHello, with the exception of optionally the "cookie" (see
%% Section 4.2.2) extension.
-server_hello_extensions(hello_retry_request = MsgType, KeyShare) ->
+server_hello_extensions(hello_retry_request = MsgType, KeyShare, _) ->
SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
Extensions = #{server_hello_selected_version => SupportedVersions},
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
-server_hello_extensions(MsgType, KeyShare) ->
+server_hello_extensions(MsgType, KeyShare, undefined) ->
SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
Extensions = #{server_hello_selected_version => SupportedVersions},
+ ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
+server_hello_extensions(MsgType, KeyShare, {SelectedIdentity, _}) ->
+ SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ PreSharedKey = #pre_shared_key_server_hello{selected_identity = SelectedIdentity},
+ Extensions = #{server_hello_selected_version => SupportedVersions,
+ pre_shared_key => PreSharedKey},
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare).
@@ -271,9 +282,11 @@ encode_handshake(#new_session_ticket{
ticket = Ticket,
extensions = Exts}) ->
TicketSize = byte_size(Ticket),
+ NonceSize = byte_size(Nonce),
BinExts = encode_extensions(Exts),
{?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
- ?BYTE(Nonce), ?UINT16(TicketSize), Ticket/binary,
+ ?BYTE(NonceSize), Nonce/binary,
+ ?UINT16(TicketSize), Ticket/binary,
BinExts/binary>>};
encode_handshake(#end_of_early_data{}) ->
{?END_OF_EARLY_DATA, <<>>};
@@ -352,6 +365,18 @@ decode_handshake(?KEY_UPDATE, <<?BYTE(Update)>>) ->
decode_handshake(Tag, HandshakeMsg) ->
ssl_handshake:decode_handshake({3,4}, Tag, HandshakeMsg).
+is_valid_binder(Binder, HHistory, PSK, Hash) ->
+ case HHistory of
+ [ClientHello2, HRR, MessageHash|_] ->
+ Truncated = truncate_client_hello(ClientHello2),
+ FinishedKey = calculate_finished_key(PSK, Hash),
+ Binder == calculate_binder(FinishedKey, Hash, [MessageHash, HRR, Truncated]);
+ [ClientHello1|_] ->
+ Truncated = truncate_client_hello(ClientHello1),
+ FinishedKey = calculate_finished_key(PSK, Hash),
+ Binder == calculate_binder(FinishedKey, Hash, Truncated)
+ end.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -515,6 +540,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
ClientShares0 = maps:get(key_share, Extensions, undefined),
ClientShares = get_key_shares(ClientShares0),
+ OfferedPSKs = get_offered_psks(Extensions),
+
ClientALPN0 = maps:get(alpn, Extensions, undefined),
ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
@@ -534,6 +561,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% and a signature algorithm/certificate pair to authenticate itself to
%% the client.
Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
+
Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)),
Maybe(validate_client_key_share(ClientGroups, ClientShares)),
@@ -569,10 +597,14 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% message if it is able to find an acceptable set of parameters but the
%% ClientHello does not contain sufficient information to proceed with
%% the handshake.
- Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId))
-
- %% TODO: session handling
-
+ case Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) of
+ {_, start} = NextStateTuple ->
+ NextStateTuple;
+ {_, negotiated} = NextStateTuple ->
+ %% Exclude any incompatible PSKs.
+ PSK = Maybe(handle_pre_shared_key(State1, OfferedPSKs, Cipher)),
+ Maybe(session_resumption(NextStateTuple, PSK))
+ end
catch
{Ref, {insufficient_security, no_suitable_groups}} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
@@ -590,20 +622,18 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% TLS Client
do_start(#server_hello{cipher_suite = SelectedCipherSuite,
session_id = SessionId,
- extensions = Extensions} = _ServerHello,
+ extensions = Extensions},
#state{static_env = #static_env{role = client,
host = Host,
port = Port,
transport_cb = Transport,
- socket = Socket,
- session_cache = Cache,
- session_cache_cb = CacheCb},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
- tls_handshake_history = _HHistory} = HsEnv,
- connection_env = CEnv,
+ socket = Socket},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ connection_env = #connection_env{negotiated_version = NegotiatedVersion},
ssl_options = #{ciphers := ClientCiphers,
supported_groups := ClientGroups0,
- versions := Versions,
+ use_ticket := UseTicket,
+ session_tickets := SessionTickets,
log_level := LogLevel} = SslOpts,
session = #session{own_certificate = Cert} = Session0,
connection_states = ConnectionStates0
@@ -632,10 +662,10 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
%% new KeyShareEntry for the group indicated in the selected_group field
%% of the triggering HelloRetryRequest.
ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
- Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- Cache, CacheCb, Renegotiation, Cert, ClientKeyShare),
-
- HelloVersion = tls_record:hello_version(Versions),
+ TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
+ Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
+ SessionId, Renegotiation, Cert, ClientKeyShare,
+ TicketData),
%% Update state
State1 = update_start_state(State0,
@@ -646,19 +676,21 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
%% Replace ClientHello1 with a special synthetic handshake message
State2 = replace_ch1_with_message_hash(State1),
- #state{handshake_env = #handshake_env{tls_handshake_history = HHistory}} = State2,
+ #state{handshake_env = #handshake_env{tls_handshake_history = HHistory0}} = State2,
+
+ %% Update pre_shared_key extension with binders (TLS 1.3)
+ Hello = tls_handshake_1_3:maybe_add_binders(Hello0, HHistory0, TicketData, NegotiatedVersion),
- {BinMsg, ConnectionStates, Handshake} =
- tls_connection:encode_handshake(Hello, HelloVersion, ConnectionStates0, HHistory),
+ {BinMsg, ConnectionStates, HHistory} =
+ tls_connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
tls_socket:send(Transport, Socket, BinMsg),
ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
State = State2#state{
connection_states = ConnectionStates,
- connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version
session = Session0#session{session_id = Hello#client_hello.session_id},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory},
key_share = ClientKeyShare},
{State, wait_sh}
@@ -669,36 +701,32 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite,
end.
-do_negotiated(start_handshake,
+do_negotiated({start_handshake, PSK0},
#state{connection_states = ConnectionStates0,
session = #session{session_id = SessionId,
- own_certificate = OwnCert,
ecc = SelectedGroup,
- sign_alg = SignatureScheme,
dh_public_value = ClientPublicKey},
ssl_options = #{} = SslOpts,
- key_share = KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- connection_env = #connection_env{private_key = CertPrivateKey},
- static_env = #static_env{
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
+ key_share = KeyShare} = State0) ->
ServerPrivateKey = get_server_private_key(KeyShare),
- {Ref,Maybe} = maybe(),
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{prf_algorithm = HKDF} = SecParamsR,
+
+ {Ref,Maybe} = maybe(),
try
%% Create server_hello
- %% Extensions: supported_versions, key_share, (pre_shared_key)
- ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0),
+ ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0),
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
+ PSK = get_pre_shared_key(PSK0, HKDF),
+
State2 =
- calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, State1),
+ calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup,
+ PSK, State1),
State3 = ssl_record:step_encryption_state(State2),
@@ -709,19 +737,13 @@ do_negotiated(start_handshake,
State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
%% Create and send CertificateRequest ({verify, verify_peer})
- {State5, NextState} = maybe_send_certificate_request(State4, SslOpts),
+ {State5, NextState} = maybe_send_certificate_request(State4, SslOpts, PSK0),
- %% Create Certificate
- Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server)),
+ %% Create and send Certificate (if PSK is undefined)
+ State6 = Maybe(maybe_send_certificate(State5, PSK0)),
- %% Encode Certificate
- State6 = tls_connection:queue_handshake(Certificate, State5),
-
- %% Create CertificateVerify
- CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme,
- State6, server)),
- %% Encode CertificateVerify
- State7 = tls_connection:queue_handshake(CertificateVerify, State6),
+ %% Create and send CertificateVerify (if PSK is undefined)
+ State7 = Maybe(maybe_send_certificate_verify(State6, PSK0)),
%% Create Finished
Finished = finished(State7),
@@ -779,17 +801,19 @@ do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
%% TLS Server
do_wait_finished(#finished{verify_data = VerifyData},
#state{static_env = #static_env{role = server}} = State0) ->
-
{Ref,Maybe} = maybe(),
try
Maybe(validate_client_finished(State0, VerifyData)),
State1 = calculate_traffic_secrets(State0),
+ State2 = maybe_calculate_resumption_master_secret(State1),
%% Configure traffic keys
- ssl_record:step_encryption_state(State1)
+ State3 = ssl_record:step_encryption_state(State2),
+ %% Send session ticket
+ maybe_send_session_ticket(State3, 3)
catch
{Ref, decrypt_error} ->
@@ -816,9 +840,10 @@ do_wait_finished(#finished{verify_data = _VerifyData},
{State3, _} = tls_connection:send_handshake_flight(State2),
State4 = calculate_traffic_secrets(State3),
+ State5 = maybe_calculate_resumption_master_secret(State4),
%% Configure traffic keys
- ssl_record:step_encryption_state(State4)
+ ssl_record:step_encryption_state(State5)
catch
{Ref, decrypt_error} ->
@@ -835,9 +860,13 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
extensions = Extensions} = ServerHello,
#state{key_share = ClientKeyShare0,
ssl_options = #{ciphers := ClientCiphers,
- supported_groups := ClientGroups0}} = State0) ->
+ supported_groups := ClientGroups0,
+ session_tickets := SessionTickets,
+ use_ticket := UseTicket}} = State0) ->
ClientGroups = get_supported_groups(ClientGroups0),
ServerKeyShare0 = maps:get(key_share, Extensions, undefined),
+ ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
+ SelectedIdentity = get_selected_identity(ServerPreSharedKey),
ClientKeyShare = get_key_shares(ClientKeyShare0),
{Ref,Maybe} = maybe(),
@@ -845,6 +874,8 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
%% Go to state 'start' if server replies with 'HelloRetryRequest'.
Maybe(maybe_hello_retry_request(ServerHello, State0)),
+ %% Resumption and PSK
+ State1 = handle_resumption(State0, SelectedIdentity),
ServerKeyShare = get_key_shares(ServerKeyShare0),
Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
@@ -856,18 +887,24 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
{_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare),
%% Update state
- State1 = update_start_state(State0,
+ State2 = update_start_state(State1,
#{cipher => SelectedCipherSuite,
key_share => ClientKeyShare0,
session_id => SessionId,
group => SelectedGroup,
peer_public_key => ServerPublicKey}),
- State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1),
+ #state{connection_states = ConnectionStates} = State2,
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
- State3 = ssl_record:step_encryption_state(State2),
+ PSK = Maybe(get_pre_shared_key(SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)),
+ State3 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup,
+ PSK, State2),
+ State4 = ssl_record:step_encryption_state(State3),
- {State3, wait_ee}
+ {State4, wait_ee}
catch
{Ref, {State, StateName, ServerHello}} ->
@@ -890,9 +927,12 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
ALPNProtocol = get_alpn(ALPNProtocol0),
- {Ref,_Maybe} = maybe(),
+ {Ref, Maybe} = maybe(),
try
+ %% Go to state 'wait_finished' if using PSK.
+ Maybe(maybe_resumption(State0)),
+
%% Update state
#state{handshake_env = HsEnv} = State0,
State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
@@ -908,7 +948,9 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
{Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
{Ref, {insufficient_security, no_suitable_public_key}} ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key);
+ {Ref, {State, StateName}} ->
+ {State, StateName}
end.
@@ -974,6 +1016,19 @@ maybe_hello_retry_request(_, _) ->
ok.
+maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) ->
+ {error, {State, wait_finished}};
+maybe_resumption(_) ->
+ ok.
+
+
+handle_resumption(State, undefined) ->
+ State;
+handle_resumption(#state{handshake_env = HSEnv0} = State, _) ->
+ HSEnv = HSEnv0#handshake_env{resumption = true},
+ State#state{handshake_env = HSEnv}.
+
+
maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) ->
{ok, State};
maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
@@ -1054,7 +1109,7 @@ compare_verify_data(_, _) ->
send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0,
no_suitable_key, KeyShare, SessionId) ->
- ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0),
+ ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0),
{State1, _} = tls_connection:send_handshake(ServerHello, State0),
%% Update handshake history
@@ -1065,16 +1120,72 @@ send_hello_retry_request(State0, _, _, _) ->
%% Suitable key found.
{ok, {State0, negotiated}}.
+session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, negotiated}, _) ->
+ {ok, {State, negotiated}};
+session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined)
+ when Tickets =/= disabled ->
+ {ok, {State, negotiated}};
+session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State0, negotiated}, PSK)
+ when Tickets =/= disabled ->
+ State = handle_resumption(State0, ok),
+ {ok, {State, negotiated, PSK}}.
-maybe_send_certificate_request(State, #{verify := verify_none}) ->
+
+%% Do not send CR during session resumption
+maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined ->
+ {State, wait_finished};
+maybe_send_certificate_request(State, #{verify := verify_none}, _) ->
{State, wait_finished};
maybe_send_certificate_request(State, #{verify := verify_peer,
signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert}) ->
+ signature_algs_cert := SignAlgsCert}, _) ->
CertificateRequest = certificate_request(SignAlgs, SignAlgsCert),
{tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
+maybe_send_certificate(State, PSK) when PSK =/= undefined ->
+ {ok, State};
+maybe_send_certificate(#state{session = #session{own_certificate = OwnCert},
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef}} = State, _) ->
+ case certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server) of
+ {ok, Certificate} ->
+ {ok, tls_connection:queue_handshake(Certificate, State)};
+ Error ->
+ Error
+ end.
+
+
+maybe_send_certificate_verify(State, PSK) when PSK =/= undefined ->
+ {ok, State};
+maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme},
+ connection_env = #connection_env{
+ private_key = CertPrivateKey}} = State, _) ->
+ case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
+ {ok, CertificateVerify} ->
+ {ok, tls_connection:queue_handshake(CertificateVerify, State)};
+ Error ->
+ Error
+ end.
+
+
+maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} = State, _) ->
+ %% Do nothing!
+ State;
+maybe_send_session_ticket(State, 0) ->
+ State;
+maybe_send_session_ticket(#state{connection_states = ConnectionStates,
+ static_env = #static_env{trackers = Trackers}} = State0, N) ->
+ Tracker = proplists:get_value(session_tickets_tracker, Trackers),
+ #{security_parameters := SecParamsR} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDF,
+ resumption_master_secret = RMS} = SecParamsR,
+ Ticket = tls_server_session_ticket:new(Tracker, HKDF, RMS),
+ {State, _} = tls_connection:send_handshake(Ticket, State0),
+ maybe_send_session_ticket(State, N - 1).
+
process_certificate_request(#certificate_request_1_3{},
#state{session = #session{own_certificate = undefined}} = State) ->
{ok, {State#state{client_certificate_requested = true}, wait_cert}};
@@ -1186,6 +1297,7 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
verify_fun := VerifyFun,
customize_hostname_check := CustomizeHostnameCheck,
crl_check := CrlCheck,
+ log_level := LogLevel,
depth := Depth} = SslOptions,
CRLDbHandle, Role, Host) ->
ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role),
@@ -1198,7 +1310,7 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
ssl_handshake:validation_fun_and_state(VerifyFun, Role,
CertDbHandle, CertDbRef, ServerName,
CustomizeHostnameCheck,
- CrlCheck, CRLDbHandle, CertPath),
+ CrlCheck, CRLDbHandle, CertPath, LogLevel),
Options = [{max_path_length, Depth},
{verify_fun, ValidationFunAndState}],
%% TODO: Validate if Certificate is using a supported signature algorithm
@@ -1271,7 +1383,7 @@ message_hash(ClientHello1, HKDFAlgo) ->
crypto:hash(HKDFAlgo, ClientHello1)].
-calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup,
+calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK,
#state{connection_states = ConnectionStates,
handshake_env =
#handshake_env{
@@ -1280,9 +1392,6 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup,
ssl_record:pending_connection_state(ConnectionStates, read),
#security_parameters{prf_algorithm = HKDFAlgo,
cipher_suite = CipherSuite} = SecParamsR,
-
- %% Calculate handshake_secret
- PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)),
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup),
@@ -1305,11 +1414,63 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup,
ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo),
WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo),
- update_pending_connection_states(State0, HandshakeSecret,
+ update_pending_connection_states(State0, HandshakeSecret, undefined,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey).
+%% Server
+get_pre_shared_key(undefined, HKDFAlgo) ->
+ binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo));
+get_pre_shared_key({_, PSK}, _) ->
+ PSK.
+%%
+%% Client
+%% Server initiates a full handshake
+get_pre_shared_key(_, _, HKDFAlgo, undefined) ->
+ {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
+%% Session resumption not configured
+get_pre_shared_key(undefined, _, HKDFAlgo, _) ->
+ {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
+get_pre_shared_key(_, undefined, HKDFAlgo, _) ->
+ {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
+%% Session resumption
+get_pre_shared_key(enabled = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
+ TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
+ case choose_psk(TicketData, SelectedIdentity) of
+ undefined -> %% full handshake, default PSK
+ {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
+ illegal_parameter ->
+ {error, illegal_parameter};
+ {_, PSK} ->
+ {ok, PSK}
+ end;
+get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
+ TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
+ case choose_psk(TicketData, SelectedIdentity) of
+ undefined -> %% full handshake, default PSK
+ tls_client_ticket_store:unlock_tickets(self(), UseTicket),
+ {ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
+ illegal_parameter ->
+ tls_client_ticket_store:unlock_tickets(self(), UseTicket),
+ {error, illegal_parameter};
+ {Key, PSK} ->
+ tls_client_ticket_store:remove_tickets([Key]), %% Remove single-use ticket
+ tls_client_ticket_store:unlock_tickets(self(), UseTicket -- [Key]),
+ {ok, PSK}
+ end.
+
+
+choose_psk(undefined, _) ->
+ undefined;
+choose_psk([], _) ->
+ illegal_parameter;
+choose_psk([{Key, SelectedIdentity, _, PSK, _, _}|_], SelectedIdentity) ->
+ {Key, PSK};
+choose_psk([_|T], SelectedIdentity) ->
+ choose_psk(T, SelectedIdentity).
+
+
calculate_traffic_secrets(#state{
static_env = #static_env{role = Role},
connection_states = ConnectionStates,
@@ -1339,7 +1500,7 @@ calculate_traffic_secrets(#state{
{ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientAppTrafficSecret0),
{WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerAppTrafficSecret0),
- update_pending_connection_states(State0, MasterSecret,
+ update_pending_connection_states(State0, MasterSecret, undefined,
ReadKey, ReadIV, undefined,
WriteKey, WriteIV, undefined).
@@ -1374,17 +1535,35 @@ calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
public_key:compute_key(Point, MyKey).
+maybe_calculate_resumption_master_secret(#state{ssl_options = #{session_tickets := disabled}} = State) ->
+ State;
+maybe_calculate_resumption_master_secret(#state{
+ ssl_options = #{session_tickets := SessionTickets},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = HHistory}} = State)
+ when SessionTickets =/= disabled ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{master_secret = MasterSecret,
+ prf_algorithm = HKDFAlgo} = SecParamsR,
+ {Messages0, _} = HHistory,
+ RMS = tls_v1:resumption_master_secret(HKDFAlgo, MasterSecret, lists:reverse(Messages0)),
+ update_resumption_master_secret(State, RMS).
+
+
update_pending_connection_states(#state{
static_env = #static_env{role = server},
connection_states =
CS = #{pending_read := PendingRead0,
pending_write := PendingWrite0}} = State,
- HandshakeSecret,
+ HandshakeSecret, ResumptionMasterSecret,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey) ->
- PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ResumptionMasterSecret,
ReadKey, ReadIV, ReadFinishedKey),
- PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, ResumptionMasterSecret,
WriteKey, WriteIV, WriteFinishedKey),
State#state{connection_states = CS#{pending_read => PendingRead,
pending_write => PendingWrite}};
@@ -1393,22 +1572,23 @@ update_pending_connection_states(#state{
connection_states =
CS = #{pending_read := PendingRead0,
pending_write := PendingWrite0}} = State,
- HandshakeSecret,
+ HandshakeSecret, ResumptionMasterSecret,
ReadKey, ReadIV, ReadFinishedKey,
WriteKey, WriteIV, WriteFinishedKey) ->
- PendingRead = update_connection_state(PendingRead0, HandshakeSecret,
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ResumptionMasterSecret,
WriteKey, WriteIV, WriteFinishedKey),
- PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret,
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, ResumptionMasterSecret,
ReadKey, ReadIV, ReadFinishedKey),
State#state{connection_states = CS#{pending_read => PendingRead,
pending_write => PendingWrite}}.
update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
- HandshakeSecret, Key, IV, FinishedKey) ->
+ HandshakeSecret, ResumptionMasterSecret, Key, IV, FinishedKey) ->
%% Store secret
SecurityParameters = SecurityParameters0#security_parameters{
- master_secret = HandshakeSecret},
+ master_secret = HandshakeSecret,
+ resumption_master_secret = ResumptionMasterSecret},
ConnectionState#{security_parameters => SecurityParameters,
cipher_state => cipher_init(Key, IV, FinishedKey)}.
@@ -1451,6 +1631,21 @@ update_start_state(#state{connection_states = ConnectionStates0,
connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
+update_resumption_master_secret(#state{connection_states = ConnectionStates0} = State,
+ ResumptionMasterSecret) ->
+ #{security_parameters := SecParamsR0} = PendingRead =
+ maps:get(pending_read, ConnectionStates0),
+ #{security_parameters := SecParamsW0} = PendingWrite =
+ maps:get(pending_write, ConnectionStates0),
+
+ SecParamsR = SecParamsR0#security_parameters{resumption_master_secret = ResumptionMasterSecret},
+ SecParamsW = SecParamsW0#security_parameters{resumption_master_secret = ResumptionMasterSecret},
+ ConnectionStates =
+ ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
+ pending_write => PendingWrite#{security_parameters => SecParamsW}},
+ State#state{connection_states = ConnectionStates}.
+
+
cipher_init(Key, IV, FinishedKey) ->
#cipher_state{key = Key,
iv = IV,
@@ -1882,6 +2077,42 @@ get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
get_key_shares(#key_share_server_hello{server_share = ServerShare}) ->
ServerShare.
+get_selected_identity(undefined) ->
+ undefined;
+get_selected_identity(#pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
+ SelectedIdentity.
+
+get_offered_psks(Extensions) ->
+ PSK = maps:get(pre_shared_key, Extensions, undefined),
+ case PSK of
+ undefined ->
+ undefined;
+ #pre_shared_key_client_hello{offered_psks = OfferedPSKs} ->
+ OfferedPSKs
+ end.
+
+
+%% Prior to accepting PSK key establishment, the server MUST validate
+%% the corresponding binder value (see Section 4.2.11.2 below). If this
+%% value is not present or does not validate, the server MUST abort the
+%% handshake. Servers SHOULD NOT attempt to validate multiple binders;
+%% rather, they SHOULD select a single PSK and validate solely the
+%% binder that corresponds to that PSK.
+%%
+%% If no acceptable PSKs are found, the server SHOULD perform a non-PSK
+%% handshake if possible.
+handle_pre_shared_key(_, undefined, _) ->
+ {ok, undefined};
+handle_pre_shared_key(#state{ssl_options = #{session_tickets := disabled}}, _, _) ->
+ {ok, undefined};
+handle_pre_shared_key(#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{tls_handshake_history = {HHistory, _}},
+ static_env = #static_env{trackers = Trackers}},
+ OfferedPreSharedKeys, Cipher) when Tickets =/= disabled ->
+ Tracker = proplists:get_value(session_tickets_tracker, Trackers),
+ #{prf := CipherHash} = ssl_cipher_format:suite_bin_to_map(Cipher),
+ tls_server_session_ticket:use(Tracker, OfferedPreSharedKeys, CipherHash, HHistory).
+
get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) ->
SelectedGroup.
@@ -1901,3 +2132,195 @@ maybe() ->
throw({Ref,Reason})
end,
{Ref,Ok}.
+
+
+%% If the handshake includes a HelloRetryRequest, the initial
+%% ClientHello and HelloRetryRequest are included in the transcript
+%% along with the new ClientHello. For instance, if the client sends
+%% ClientHello1, its binder will be computed over:
+%%
+%% Transcript-Hash(Truncate(ClientHello1))
+%%
+%% Where Truncate() removes the binders list from the ClientHello.
+%%
+%% If the server responds with a HelloRetryRequest and the client then
+%% sends ClientHello2, its binder will be computed over:
+%%
+%% Transcript-Hash(ClientHello1,
+%% HelloRetryRequest,
+%% Truncate(ClientHello2))
+%%
+%% The full ClientHello1/ClientHello2 is included in all other handshake
+%% hash computations. Note that in the first flight,
+%% Truncate(ClientHello1) is hashed directly, but in the second flight,
+%% ClientHello1 is hashed and then reinjected as a "message_hash"
+%% message, as described in Section 4.4.1.
+maybe_add_binders(Hello, undefined, _) ->
+ Hello;
+maybe_add_binders(Hello0, TicketData, Version) when Version =:= {3,4} ->
+ HelloBin0 = tls_handshake:encode_handshake(Hello0, Version),
+ HelloBin1 = iolist_to_binary(HelloBin0),
+ Truncated = truncate_client_hello(HelloBin1),
+ Binders = create_binders([Truncated], TicketData),
+ update_binders(Hello0, Binders);
+maybe_add_binders(Hello, _, Version) when Version =< {3,3} ->
+ Hello.
+%%
+%% HelloRetryRequest
+maybe_add_binders(Hello, _, undefined, _) ->
+ Hello;
+maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, Version) when Version =:= {3,4} ->
+ HelloBin0 = tls_handshake:encode_handshake(Hello0, Version),
+ HelloBin1 = iolist_to_binary(HelloBin0),
+ Truncated = truncate_client_hello(HelloBin1),
+ Binders = create_binders([MessageHash,HRR,Truncated], TicketData),
+ update_binders(Hello0, Binders);
+maybe_add_binders(Hello, _, _, Version) when Version =< {3,3} ->
+ Hello.
+
+
+create_binders(Context, TicketData) ->
+ create_binders(Context, TicketData, []).
+%%
+create_binders(_, [], Acc) ->
+ lists:reverse(Acc);
+create_binders(Context, [{_, _, _, PSK, _, HKDF}|T], Acc) ->
+ FinishedKey = calculate_finished_key(PSK, HKDF),
+ Binder = calculate_binder(FinishedKey, HKDF, Context),
+ create_binders(Context, T, [Binder|Acc]).
+
+
+%% Removes the binders list from the ClientHello.
+%% opaque PskBinderEntry<32..255>;
+%%
+%% struct {
+%% PskIdentity identities<7..2^16-1>;
+%% PskBinderEntry binders<33..2^16-1>;
+%% } OfferedPsks;
+truncate_client_hello(HelloBin0) ->
+ HelloBin1 = remove_binders(HelloBin0),
+ {Truncated, _} = split_binary(HelloBin1, size(HelloBin1) - 2),
+ Truncated.
+
+
+remove_binders(Binary0) ->
+ OrigSize = byte_size(Binary0),
+ HashSize256 = ssl_cipher:hash_size(sha256),
+ HashSize384 = ssl_cipher:hash_size(sha384),
+ HashSize512 = ssl_cipher:hash_size(sha512),
+
+ NewSize256 = OrigSize - HashSize256 - 1,
+ NewSize384 = OrigSize - HashSize384 - 1,
+ NewSize512 = OrigSize - HashSize512 - 1,
+ case Binary0 of
+ <<Binary:NewSize256/binary,?BYTE(HashSize256),_:HashSize256/binary>> ->
+ remove_binders(Binary);
+ <<Binary:NewSize384/binary,?BYTE(HashSize384),_:HashSize384/binary>> ->
+ remove_binders(Binary);
+ <<Binary:NewSize512/binary,?BYTE(HashSize512),_:HashSize512/binary>> ->
+ remove_binders(Binary);
+ Else ->
+ Else
+ end.
+
+
+%% The PskBinderEntry is computed in the same way as the Finished
+%% message (Section 4.4.4) but with the BaseKey being the binder_key
+%% derived via the key schedule from the corresponding PSK which is
+%% being offered (see Section 7.1).
+calculate_finished_key(PSK, HKDFAlgo) ->
+ EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
+ PRK = tls_v1:resumption_binder_key(HKDFAlgo, EarlySecret),
+ tls_v1:finished_key(PRK, HKDFAlgo).
+
+
+calculate_binder(BinderKey, HKDF, Truncated) ->
+ tls_v1:finished_verify_data(BinderKey, HKDF, [Truncated]).
+
+
+update_binders(#client_hello{extensions =
+ #{pre_shared_key := PreSharedKey0} = Extensions0} = Hello, Binders) ->
+ #pre_shared_key_client_hello{
+ offered_psks =
+ #offered_psks{identities = Identities}} = PreSharedKey0,
+
+ PreSharedKey =
+ #pre_shared_key_client_hello{
+ offered_psks =
+ #offered_psks{identities = Identities,
+ binders = Binders}},
+
+ Extensions = Extensions0#{pre_shared_key => PreSharedKey},
+ Hello#client_hello{extensions = Extensions}.
+
+%% Configure a suitable session ticket
+maybe_automatic_session_resumption(#state{
+ ssl_options = #{versions := [Version|_],
+ ciphers := UserSuites,
+ session_tickets := SessionTickets} = SslOpts0
+ } = State0)
+ when Version >= {3,4} andalso
+ SessionTickets =:= auto ->
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ HashAlgos = cipher_hash_algos(AvailableCipherSuites),
+ UseTicket = tls_client_ticket_store:find_ticket(self(), HashAlgos),
+ tls_client_ticket_store:lock_tickets(self(), [UseTicket]),
+ State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}},
+ {[UseTicket], State};
+maybe_automatic_session_resumption(#state{
+ ssl_options = #{use_ticket := UseTicket}
+ } = State) ->
+ {UseTicket, State}.
+
+
+cipher_hash_algos(Ciphers) ->
+ Fun = fun(Cipher) ->
+ #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher),
+ Hash
+ end,
+ lists:map(Fun, Ciphers).
+
+
+get_ticket_data(_, undefined, _) ->
+ undefined;
+get_ticket_data(_, _, undefined) ->
+ undefined;
+get_ticket_data(_, enabled, UseTicket) ->
+ process_user_tickets(UseTicket);
+get_ticket_data(Pid, auto, UseTicket) ->
+ tls_client_ticket_store:get_tickets(Pid, UseTicket).
+
+
+process_user_tickets(UseTicket) ->
+ process_user_tickets(UseTicket, [], 0).
+%%
+process_user_tickets([], Acc, _) ->
+ lists:reverse(Acc);
+process_user_tickets([H|T], Acc, N) ->
+ #{hkdf := HKDF,
+ sni := _SNI,
+ psk := PSK,
+ timestamp := Timestamp,
+ ticket := NewSessionTicket} = erlang:binary_to_term(H),
+ #new_session_ticket{
+ ticket_lifetime = _LifeTime,
+ ticket_age_add = AgeAdd,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
+ extensions = _Extensions
+ } = NewSessionTicket,
+ TicketAge = erlang:system_time(seconds) - Timestamp,
+ ObfuscatedTicketAge = obfuscate_ticket_age(TicketAge, AgeAdd),
+ Identity = #psk_identity{
+ identity = Ticket,
+ obfuscated_ticket_age = ObfuscatedTicketAge},
+ process_user_tickets(T, [{undefined, N, Identity, PSK, Nonce, HKDF}|Acc], N + 1).
+
+
+%% The "obfuscated_ticket_age"
+%% field of each PskIdentity contains an obfuscated version of the
+%% ticket age formed by taking the age in milliseconds and adding the
+%% "ticket_age_add" value that was included with the ticket
+%% (see Section 4.6.1), modulo 2^32.
+obfuscate_ticket_age(TicketAge, AgeAdd) ->
+ (TicketAge + AgeAdd) rem round(math:pow(2,32)).
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
index eb85f216c8..cb28ca78f6 100644
--- a/lib/ssl/src/tls_handshake_1_3.hrl
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -254,8 +254,10 @@
-type tls_handshake_1_3() :: #encrypted_extensions{} |
#certificate_request_1_3{} |
#certificate_1_3{} |
- #certificate_verify_1_3{}.
+ #certificate_verify_1_3{} |
+ #new_session_ticket{}.
-export_type([tls_handshake_1_3/0]).
+
-endif. % -ifdef(tls_handshake_1_3).
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index cfd75076b3..e8040b461d 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -391,7 +391,7 @@ is_acceptable_version(_,_) ->
-spec hello_version([tls_version()]) -> tls_version().
hello_version([Highest|_]) when Highest >= {3,3} ->
- Highest;
+ {3,3};
hello_version(Versions) ->
lowest_protocol_version(Versions).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index d0604565e3..50811ef3dd 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -43,7 +43,7 @@
role,
socket,
socket_options,
- tracker,
+ trackers,
transport_cb,
negotiated_version,
renegotiate_at,
@@ -200,7 +200,7 @@ init({call, From}, {Pid, #{current_write := WriteState,
role := Role,
socket := Socket,
socket_options := SockOpts,
- tracker := Tracker,
+ trackers := Trackers,
transport_cb := Transport,
negotiated_version := Version,
renegotiate_at := RenegotiateAt,
@@ -214,7 +214,7 @@ init({call, From}, {Pid, #{current_write := WriteState,
role = Role,
socket = Socket,
socket_options = SockOpts,
- tracker = Tracker,
+ trackers = Trackers,
transport_cb = Transport,
negotiated_version = Version,
renegotiate_at = RenegotiateAt,
@@ -255,8 +255,8 @@ connection({call, From}, dist_get_tls_socket,
#data{static = #static{transport_cb = Transport,
socket = Socket,
connection_pid = Pid,
- tracker = Tracker}} = StateData) ->
- TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Tracker),
+ trackers = Trackers}} = StateData) ->
+ TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Trackers),
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
connection({call, From}, {dist_handshake_complete, _Node, DHandle},
#data{static = #static{connection_pid = Pid} = Static} = StateData) ->
@@ -394,16 +394,13 @@ handle_common(
info, {'DOWN', Monitor, _, _, _},
#data{static = #static{connection_monitor = Monitor}} = StateData) ->
{stop, normal, StateData};
-handle_common(info, Msg, _) ->
- Report =
- io_lib:format("TLS sender: Got unexpected info: ~p ~n", [Msg]),
- error_logger:info_report(Report),
+handle_common(info, Msg, #data{static = #static{log_level = Level}}) ->
+ ssl_logger:log(info, Level, #{event => "TLS sender recived unexpected info",
+ reason => [{message, Msg}]}, ?LOCATION),
keep_state_and_data;
-handle_common(Type, Msg, _) ->
- Report =
- io_lib:format(
- "TLS sender: Got unexpected event: ~p ~n", [{Type,Msg}]),
- error_logger:error_report(Report),
+handle_common(Type, Msg, #data{static = #static{log_level = Level}}) ->
+ ssl_logger:log(error, Level, #{event => "TLS sender recived unexpected event",
+ reason => [{type, Type}, {message, Msg}]}, ?LOCATION),
keep_state_and_data.
send_tls_alert(#alert{} = Alert,
diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl
new file mode 100644
index 0000000000..e30205f4b8
--- /dev/null
+++ b/lib/ssl/src/tls_server_session_ticket.erl
@@ -0,0 +1,391 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Handle server side TLS-1.3 session ticket storage
+%%----------------------------------------------------------------------
+
+-module(tls_server_session_ticket).
+-behaviour(gen_server).
+
+-include("tls_handshake_1_3.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_alert.hrl").
+-include("ssl_cipher.hrl").
+
+%% API
+-export([start_link/3,
+ new/3,
+ use/4
+ ]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3, format_status/2]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {
+ stateless,
+ stateful,
+ nonce,
+ lifetime
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec start_link(atom(), integer(), tuple()) -> {ok, Pid :: pid()} |
+ {error, Error :: {already_started, pid()}} |
+ {error, Error :: term()} |
+ ignore.
+start_link(Mode, Lifetime, AntiReplay) ->
+ gen_server:start_link(?MODULE, [Mode, Lifetime, AntiReplay], []).
+
+new(Pid, Prf, MasterSecret) ->
+ gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret}, infinity).
+
+use(Pid, Identifiers, Prf, HandshakeHist) ->
+ gen_server:call(Pid, {use_ticket, Identifiers, Prf, HandshakeHist},
+ infinity).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+-spec init(Args :: term()) -> {ok, State :: term()}.
+init(Args) ->
+ process_flag(trap_exit, true),
+ State = inital_state(Args),
+ {ok, State}.
+
+-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
+ {reply, Reply :: term(), NewState :: term()} .
+handle_call({new_session_ticket, Prf, MasterSecret}, _From,
+ #state{nonce = Nonce,
+ lifetime = LifeTime,
+ stateful = #{}} = State0) ->
+ Id = stateful_psk_id(),
+ PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf),
+ SessionTicket = new_session_ticket(Id, Nonce, LifeTime),
+ State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0),
+ {reply, SessionTicket, State};
+handle_call({new_session_ticket, Prf, MasterSecret}, _From,
+ #state{nonce = Nonce,
+ stateless = #{}} = State) ->
+ BaseSessionTicket = new_session_ticket_base(State),
+ SessionTicket = generate_statless_ticket(BaseSessionTicket, Prf,
+ MasterSecret, State),
+ {reply, SessionTicket, State#state{nonce = Nonce+1}};
+handle_call({use_ticket, Identifiers, Prf, HandshakeHist}, _From,
+ #state{stateful = #{}} = State0) ->
+ {Result, State} = stateful_use(Identifiers, Prf,
+ HandshakeHist, State0),
+ {reply, Result, State};
+handle_call({use_ticket, Identifiers, Prf, HandshakeHist}, _From,
+ #state{stateless = #{}} = State0) ->
+ {Result, State} = stateless_use(Identifiers, Prf,
+ HandshakeHist, State0),
+ {reply, Result, State}.
+
+-spec handle_cast(Request :: term(), State :: term()) ->
+ {noreply, NewState :: term()}.
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+-spec handle_info(Info :: timeout() | term(), State :: term()) ->
+ {noreply, NewState :: term()}.
+handle_info(rotate_bloom_filters,
+ #state{stateless = #{bloom_filter := BloomFilter0,
+ window := Window} = Stateless} = State) ->
+ BloomFilter = tls_bloom_filter:rotate(BloomFilter0),
+ erlang:send_after(Window * 1000, self(), rotate_bloom_filters),
+ {noreply, State#state{stateless = Stateless#{bloom_filter => BloomFilter}}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+-spec terminate(Reason :: normal | shutdown | {shutdown, term()} | term(),
+ State :: term()) -> any().
+terminate(_Reason, _State) ->
+ ok.
+
+-spec code_change(OldVsn :: term() | {down, term()},
+ State :: term(),
+ Extra :: term()) -> {ok, NewState :: term()} |
+ {error, Reason :: term()}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+-spec format_status(Opt :: normal | terminate,
+ Status :: list()) -> Status :: term().
+format_status(_Opt, Status) ->
+ Status.
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+inital_state([stateless, Lifetime, undefined]) ->
+ #state{nonce = 0,
+ stateless = #{seed => {crypto:strong_rand_bytes(16),
+ crypto:strong_rand_bytes(32)},
+ window => undefined},
+ lifetime = Lifetime
+ };
+inital_state([stateless, Lifetime, {Window, K, M}]) ->
+ erlang:send_after(Window * 1000, self(), rotate_bloom_filters),
+ #state{nonce = 0,
+ stateless = #{bloom_filter => tls_bloom_filter:new(K, M),
+ seed => {crypto:strong_rand_bytes(16),
+ crypto:strong_rand_bytes(32)},
+ window => Window},
+ lifetime = Lifetime
+ };
+inital_state([stateful, Lifetime|_]) ->
+ %% statfeful servers replay
+ %% protection is that it saves
+ %% all valid tickets
+ #state{lifetime = Lifetime,
+ nonce = 0,
+ stateful = #{db => stateful_store(),
+ max => 1000,
+ ref_index => #{}
+ }
+ }.
+
+ticket_age_add() ->
+ MaxTicketAge = 7 * 24 * 3600,
+ IntMax = round(math:pow(2,32)) - 1,
+ MaxAgeAdd = IntMax - MaxTicketAge,
+ <<?UINT32(I)>> = crypto:strong_rand_bytes(4),
+ case I > MaxAgeAdd of
+ true ->
+ I - MaxTicketAge;
+ false ->
+ I
+ end.
+
+ticket_nonce(I) ->
+ <<?UINT64(I)>>.
+
+new_session_ticket_base(#state{nonce = Nonce,
+ lifetime = Lifetime}) ->
+ new_session_ticket(undefined, Nonce, Lifetime).
+
+new_session_ticket(Id, Nonce, Lifetime) ->
+ TicketAgeAdd = ticket_age_add(),
+ #new_session_ticket{
+ ticket = Id,
+ ticket_lifetime = Lifetime,
+ ticket_age_add = TicketAgeAdd,
+ ticket_nonce = ticket_nonce(Nonce),
+ extensions = #{}
+ }.
+
+
+validate_binder(Binder, HandshakeHist, PSK, Prf, AlertDetail) ->
+ case tls_handshake_1_3:is_valid_binder(Binder, HandshakeHist, PSK, Prf) of
+ true ->
+ true;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, AlertDetail)}
+ end.
+
+%%%===================================================================
+%%% Stateful store
+%%%===================================================================
+
+stateful_store() ->
+ gb_trees:empty().
+
+stateful_ticket_store(Ref, NewSessionTicket, Hash, Psk,
+ #state{nonce = Nonce,
+ stateful = #{db := Tree0,
+ max := Max,
+ ref_index := Index0} = Stateful}
+ = State0) ->
+ Id = erlang:monotonic_time(),
+ StatefulTicket = {NewSessionTicket, Hash, Psk},
+ case gb_trees:size(Tree0) of
+ Max ->
+ %% Trow away oldes ticket
+ {_, {#new_session_ticket{ticket = OldRef},_,_}, Tree1}
+ = gb_trees:take_smallest(Tree0),
+ Tree = gb_trees:insert(Id, StatefulTicket, Tree1),
+ Index = maps:without([OldRef], Index0),
+ State0#state{nonce = Nonce+1, stateful =
+ Stateful#{db => Tree,
+ ref_index => Index#{Ref => Id}}};
+ _ ->
+ Tree = gb_trees:insert(Id, StatefulTicket, Tree0),
+ State0#state{nonce = Nonce+1, stateful =
+ Stateful#{db => Tree,
+ ref_index => Index0#{Ref => Id}}}
+ end.
+
+stateful_use(#offered_psks{
+ identities = Identities,
+ binders = Binders
+ }, Prf, HandshakeHist, State) ->
+ stateful_use(Identities, Binders, Prf, HandshakeHist, 0, State).
+
+stateful_use([], [], _, _, _, State) ->
+ {{ok, undefined}, State};
+stateful_use([#psk_identity{identity = Ref} | Refs], [Binder | Binders],
+ Prf, HandshakeHist, Index,
+ #state{stateful = #{db := Tree0,
+ ref_index := RefIndex0} = Stateful} = State) ->
+ try maps:get(Ref, RefIndex0) of
+ Key ->
+ case stateful_usable_ticket(Key, Prf, Binder,
+ HandshakeHist, Tree0) of
+ true ->
+ RefIndex = maps:without([Ref], RefIndex0),
+ {{_,_, PSK}, Tree} = gb_trees:take(Key, Tree0),
+ {{ok, {Index, PSK}},
+ State#state{stateful = Stateful#{db => Tree,
+ ref_index => RefIndex}}};
+ false ->
+ stateful_use(Refs, Binders, Prf,
+ HandshakeHist, Index + 1, State);
+ {error, _} = Error ->
+ {Error, State}
+ end
+ catch
+ _:{badkey, Ref} ->
+ stateful_use(Refs, Binders, Prf, HandshakeHist, Index + 1, State)
+ end.
+
+stateful_usable_ticket(Key, Prf, Binder, HandshakeHist, Tree) ->
+ case gb_trees:lookup(Key, Tree) of
+ none ->
+ false;
+ {value, {NewSessionTicket, Prf, PSK}} ->
+ case stateful_living_ticket(Key, NewSessionTicket) of
+ true ->
+ validate_binder(Binder, HandshakeHist, PSK, Prf, stateful);
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end.
+
+stateful_living_ticket(TimeStamp,
+ #new_session_ticket{ticket_lifetime = LifeTime}) ->
+ Now = erlang:monotonic_time(),
+ Lived = erlang:convert_time_unit(Now-TimeStamp, native, seconds),
+ Lived < LifeTime.
+
+
+stateful_psk_id() ->
+ term_to_binary(make_ref()).
+
+%%%===================================================================
+%%% Stateless ticket
+%%%===================================================================
+generate_statless_ticket(#new_session_ticket{ticket_nonce = Nonce,
+ ticket_age_add = TicketAgeAdd,
+ ticket_lifetime = Lifetime}
+ = Ticket, Prf, MasterSecret,
+ #state{stateless = #{seed := {IV, Shard}}}) ->
+ PSK = tls_v1:pre_shared_key(MasterSecret, Nonce, Prf),
+ Timestamp = erlang:system_time(second),
+ Encrypted = ssl_cipher:encrypt_ticket(#stateless_ticket{
+ hash = Prf,
+ pre_shared_key = PSK,
+ ticket_age_add = TicketAgeAdd,
+ lifetime = Lifetime,
+ timestamp = Timestamp
+ }, Shard, IV),
+ Ticket#new_session_ticket{ticket = Encrypted}.
+
+stateless_use(#offered_psks{
+ identities = Identities,
+ binders = Binders
+ }, Prf, HandshakeHist, State) ->
+ stateless_use(Identities, Binders, Prf, HandshakeHist, 0, State).
+
+stateless_use([], [], _, _, _, State) ->
+ {{ok, undefined}, State};
+stateless_use([#psk_identity{identity = Encrypted,
+ obfuscated_ticket_age = ObfAge} | Ids],
+ [Binder | Binders], Prf, HandshakeHist, Index,
+ #state{stateless = #{seed := {IV, Shard},
+ window := Window}} = State) ->
+ case ssl_cipher:decrypt_ticket(Encrypted, Shard, IV) of
+ #stateless_ticket{hash = Prf,
+ pre_shared_key = PSK} = Ticket ->
+ case statless_usable_ticket(Ticket, ObfAge, Binder,
+ HandshakeHist, Window) of
+ true ->
+ stateless_anti_replay(Index, PSK, Binder, State);
+ false ->
+ stateless_use(Ids, Binders, Prf, HandshakeHist,
+ Index+1, State);
+ {error, _} = Error ->
+ {Error, State}
+ end;
+ _ ->
+ stateless_use(Ids, Binders, Prf, HandshakeHist, Index+1, State)
+ end.
+
+statless_usable_ticket(#stateless_ticket{hash = Prf,
+ ticket_age_add = TicketAgeAdd,
+ lifetime = Lifetime,
+ timestamp = Timestamp,
+ pre_shared_key = PSK}, ObfAge,
+ Binder, HandshakeHist, Window) ->
+ case stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime,
+ Timestamp, Window) of
+ true ->
+ validate_binder(Binder, HandshakeHist, PSK, Prf, stateless);
+ false ->
+ false
+ end.
+
+stateless_living_ticket(0, _, _, _, _) ->
+ true;
+stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) ->
+ ReportedAge = ObfAge - TicketAgeAdd,
+ RealAge = erlang:system_time(second) - Timestamp,
+ (ReportedAge =< Lifetime)
+ andalso (RealAge =< Lifetime)
+ andalso (in_window(RealAge, Window)).
+
+in_window(_, undefined) ->
+ true;
+in_window(Age, {Window, _, _}) ->
+ Age =< Window.
+
+stateless_anti_replay(Index, PSK, Binder,
+ #state{stateless = #{bloom_filter := BloomFilter0}
+ = Stateless} = State) ->
+ case tls_bloom_filter:contains(BloomFilter0, Binder) of
+ true ->
+ %%possible_replay
+ {{ok, undefined}, State};
+ false ->
+ BloomFilter = tls_bloom_filter:add_elem(BloomFilter0, Binder),
+ {{ok, {Index, PSK}},
+ State#state{stateless = Stateless#{bloom_filter => BloomFilter}}}
+ end;
+stateless_anti_replay(Index, PSK, _, State) ->
+ {{ok, {Index, PSK}}, State}.
diff --git a/lib/ssl/src/tls_server_session_ticket_sup.erl b/lib/ssl/src/tls_server_session_ticket_sup.erl
new file mode 100644
index 0000000000..739491cf1e
--- /dev/null
+++ b/lib/ssl/src/tls_server_session_ticket_sup.erl
@@ -0,0 +1,72 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2014-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Supervisor for a listen options tracker
+%%----------------------------------------------------------------------
+-module(tls_server_session_ticket_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0, start_link_dist/0]).
+-export([start_child/1, start_child_dist/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link() ->
+ supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []).
+
+start_link_dist() ->
+ supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []).
+
+start_child(Args) ->
+ supervisor:start_child(tracker_name(normal), Args).
+
+start_child_dist(Args) ->
+ supervisor:start_child(tracker_name(dist), Args).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_O) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 0,
+ MaxT = 3600,
+
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {tls_server_session_ticket, start_link, []},
+ Restart = temporary, % E.g. should not be restarted
+ Shutdown = 4000,
+ Modules = [tls_server_session_ticket],
+ Type = worker,
+
+ ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
+ {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
+
+tracker_name(normal) ->
+ ?MODULE;
+tracker_name(dist) ->
+ list_to_atom(atom_to_list(?MODULE) ++ "dist").
diff --git a/lib/ssl/src/tls_server_sup.erl b/lib/ssl/src/tls_server_sup.erl
new file mode 100644
index 0000000000..7834589206
--- /dev/null
+++ b/lib/ssl/src/tls_server_sup.erl
@@ -0,0 +1,77 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(tls_server_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+ ListenTracker = listen_options_tracker_child_spec(),
+ SessionTracker = tls_server_session_child_spec(),
+
+ {ok, {{one_for_all, 10, 3600}, [ListenTracker,
+ SessionTracker
+ ]}}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Handles emulated options so that they inherited by the accept
+%% socket, even when setopts is performed on the listen socket
+listen_options_tracker_child_spec() ->
+ Name = tls_socket,
+ StartFunc = {ssl_listen_tracker_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [ssl_listen_tracker_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+tls_server_session_child_spec() ->
+ Name = tls_server_session_ticket,
+ StartFunc = {tls_server_session_ticket_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_server_session_ticket_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index acd907905c..031665bfe8 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -24,14 +24,39 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
--export([send/3, listen/3, accept/3, socket/5, connect/4, upgrade/3,
- setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2]).
--export([split_options/1, get_socket_opts/3]).
--export([emulated_options/0, emulated_options/1, internal_inet_values/0, default_inet_values/0,
- init/1, start_link/3, terminate/2, inherit_tracker/3,
- emulated_socket_options/2, get_emulated_opts/1,
- set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2,
- handle_info/2, code_change/3]).
+-export([send/3,
+ listen/3,
+ accept/3,
+ socket/5,
+ connect/4,
+ upgrade/3,
+ setopts/3,
+ getopts/3,
+ getstat/3,
+ peername/2,
+ sockname/2,
+ port/2]).
+
+-export([split_options/1,
+ get_socket_opts/3]).
+
+-export([emulated_options/0,
+ emulated_options/1,
+ internal_inet_values/0,
+ default_inet_values/0,
+ init/1,
+ start_link/3,
+ terminate/2,
+ inherit_tracker/3,
+ emulated_socket_options/2,
+ get_emulated_opts/1,
+ set_emulated_opts/2,
+ get_all_opts/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ code_change/3]).
+
-export([update_active_n/2]).
-record(state, {
@@ -52,8 +77,11 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _},
case Transport:listen(Port, Options ++ internal_inet_values()) of
{ok, ListenSocket} ->
{ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts),
- Socket = #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}},
- check_active_n(EmOpts, Socket),
+ %% TODO not hard code
+ {ok, SessionHandler} = session_tickets_tracker(7200, SslOpts),
+ Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler}],
+ Socket = #sslsocket{pid = {ListenSocket, Config#config{trackers = Trackers}}},
+ check_active_n(EmOpts, Socket),
{ok, Socket};
Err = {error, _} ->
Err
@@ -62,17 +90,18 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _},
accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo,
connection_cb = ConnectionCb,
ssl = SslOpts,
- emulated = Tracker}, Timeout) ->
+ trackers = Trackers}, Timeout) ->
case Transport:accept(ListenSocket, Timeout) of
{ok, Socket} ->
- {ok, EmOpts} = get_emulated_opts(Tracker),
+ Tracker = proplists:get_value(option_tracker, Trackers),
+ {ok, EmOpts} = get_emulated_opts(Tracker),
{ok, Port} = tls_socket:port(Transport, Socket),
{ok, Sender} = tls_sender:start(),
ConnArgs = [server, Sender, "localhost", Port, Socket,
- {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo],
+ {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
case tls_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
- ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Tracker);
+ ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers);
{error, Reason} ->
{error, Reason}
end;
@@ -116,17 +145,19 @@ connect(Address, Port,
{error, {options, {socket_options, UserOpts}}}
end.
-socket(Pids, Transport, Socket, ConnectionCb, Tracker) ->
+socket(Pids, Transport, Socket, ConnectionCb, Trackers) ->
#sslsocket{pid = Pids,
%% "The name "fd" is keept for backwards compatibility
- fd = {Transport, Socket, ConnectionCb, Tracker}}.
-setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
+ fd = {Transport, Socket, ConnectionCb, Trackers}}.
+setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{trackers = Trackers}}}, Options) ->
+ Tracker = proplists:get_value(option_tracker, Trackers),
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
check_active_n(EmulatedOpts, Socket),
inet:setopts(ListenSocket, SockOpts);
setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_,_},
- emulated = Tracker}}}, Options) ->
+ trackers = Trackers}}}, Options) ->
+ Tracker = proplists:get_value(option_tracker, Trackers),
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
check_active_n(EmulatedOpts, Socket),
@@ -137,7 +168,8 @@ setopts(gen_tcp, Socket, Options) ->
setopts(Transport, Socket, Options) ->
Transport:setopts(Socket, Options).
-check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tracker}}}) ->
+check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{trackers = Trackers}}}) ->
+ Tracker = proplists:get_value(option_tracker, Trackers),
%% We check the resulting options to send an ssl_passive message if necessary.
case proplists:lookup(active, EmulatedOpts) of
%% The provided value is out of bound.
@@ -162,12 +194,14 @@ check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tr
ok
end.
-getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
+getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{trackers = Trackers}}}, Options) ->
+ Tracker = proplists:get_value(option_tracker, Trackers),
{SockOptNames, EmulatedOptNames} = split_options(Options),
EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames),
SocketOpts = get_socket_opts(ListenSocket, SockOptNames, inet),
{ok, EmulatedOpts ++ SocketOpts};
-getopts(Transport, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
+getopts(Transport, #sslsocket{pid = {ListenSocket, #config{trackers = Trackers}}}, Options) ->
+ Tracker = proplists:get_value(option_tracker, Trackers),
{SockOptNames, EmulatedOptNames} = split_options(Options),
EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames),
SocketOpts = get_socket_opts(ListenSocket, SockOptNames, Transport),
@@ -215,6 +249,17 @@ inherit_tracker(ListenSocket, EmOpts, #{erl_dist := false} = SslOpts) ->
inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) ->
ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]).
+session_tickets_tracker(_, #{erl_dist := false,
+ session_tickets := disabled}) ->
+ {ok, disabled};
+session_tickets_tracker(Lifetime, #{erl_dist := false,
+ session_tickets := Mode,
+ anti_replay := AntiReplay}) ->
+ tls_server_session_ticket_sup:start_child([Mode, Lifetime, AntiReplay]);
+session_tickets_tracker(Lifetime, #{erl_dist := true, session_tickets := Mode}) ->
+ tls_server_session_ticket_sup:start_child_dist([Mode, Lifetime]).
+
+
get_emulated_opts(TrackerPid) ->
call(TrackerPid, get_emulated_opts).
set_emulated_opts(TrackerPid, InetValues) ->
diff --git a/lib/ssl/src/tls_sup.erl b/lib/ssl/src/tls_sup.erl
new file mode 100644
index 0000000000..25c1db0272
--- /dev/null
+++ b/lib/ssl/src/tls_sup.erl
@@ -0,0 +1,76 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(tls_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+
+init([]) ->
+
+ TLSConnetionManager = tls_connection_manager_child_spec(),
+ ServerInstanceSup = server_instance_child_spec(),
+
+ {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager,
+ ServerInstanceSup
+ ]}}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+tls_connection_manager_child_spec() ->
+ Name = tls_connection,
+ StartFunc = {tls_connection_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_connection_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+server_instance_child_spec() ->
+ Name = tls_server_sup,
+ StartFunc = {tls_server_sup, start_link, []},
+ Restart = permanent,
+ Shutdown = 4000,
+ Modules = [tls_server_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index f7c8c770ae..381793c65d 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -44,7 +44,7 @@
client_application_traffic_secret_0/3, server_application_traffic_secret_0/3,
exporter_master_secret/3, resumption_master_secret/3,
update_traffic_secret/2, calculate_traffic_keys/3,
- transcript_hash/2, finished_key/2, finished_verify_data/3]).
+ transcript_hash/2, finished_key/2, finished_verify_data/3, pre_shared_key/3]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
@@ -393,6 +393,15 @@ finished_verify_data(FinishedKey, HKDFAlgo, Messages) ->
THash = tls_v1:transcript_hash(Context, HKDFAlgo),
tls_v1:hmac_hash(HKDFAlgo, FinishedKey, THash).
+-spec pre_shared_key(binary(), binary(), atom()) -> binary().
+pre_shared_key(RMS, Nonce, Algo) ->
+ %% The PSK associated with the ticket is computed as:
+ %%
+ %% HKDF-Expand-Label(resumption_master_secret,
+ %% "resumption", ticket_nonce, Hash.length)
+ ssl_cipher:hash_size(Algo),
+ hkdf_expand_label(RMS, <<"resumption">>, Nonce, ssl_cipher:hash_size(Algo), Algo).
+
%% The next-generation application_traffic_secret is computed as:
%%
%% application_traffic_secret_N+1 =
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index ec0addac59..cb52a6d3d5 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -43,7 +43,8 @@ MODULES = \
ssl_dist_test_lib \
ssl_api_SUITE\
tls_api_SUITE\
- ssl_basic_SUITE \
+ dtls_api_SUITE\
+ ssl_basic_SUITE \
ssl_bench_SUITE \
ssl_cipher_SUITE \
ssl_cipher_suite_SUITE \
@@ -71,6 +72,7 @@ MODULES = \
ssl_pem_cache_SUITE \
ssl_session_SUITE \
ssl_session_cache_SUITE \
+ ssl_session_ticket_SUITE \
openssl_session_SUITE \
ssl_ECC_SUITE \
ssl_ECC_openssl_SUITE \
diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl
new file mode 100644
index 0000000000..37130abcbf
--- /dev/null
+++ b/lib/ssl/test/dtls_api_SUITE.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(dtls_api_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+all() ->
+ [
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}
+ ].
+
+groups() ->
+ [
+ {'dtlsv1.2', [], api_tests()},
+ {'dtlsv1', [], api_tests()}
+ ].
+
+api_tests() ->
+ [
+ dtls_listen_owner_dies
+ ].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl_test_lib:make_rsa_cert(Config0)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:unload(ssl),
+ application:stop(crypto).
+
+
+init_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_tls_version(GroupName) of
+ true ->
+ case ssl_test_lib:sufficient_crypto_support(GroupName) of
+ true ->
+ ssl_test_lib:init_tls_version(GroupName, Config);
+ false ->
+ {skip, "Missing crypto support"}
+ end;
+ _ ->
+ ssl:start(),
+ Config
+ end.
+
+end_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_tls_version(GroupName) of
+ true ->
+ ssl_test_lib:clean_tls_version(Config);
+ false ->
+ Config
+ end.
+
+init_per_testcase(_TestCase, Config) ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap({seconds, 10}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+dtls_listen_owner_dies() ->
+ [{doc, "Test that you can start new DTLS 'listner' if old owner dies"}].
+
+dtls_listen_owner_dies(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Port = ssl_test_lib:inet_port(ServerNode),
+ Test = self(),
+ Pid = spawn(fun() -> {ok, _} =
+ ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
+ {error, _} = ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
+ Test ! {self(), listened}
+ end),
+ receive
+ {Pid, listened} ->
+ ok
+ end,
+ {ok, LSocket} = ssl:listen(Port, [{protocol, dtls} | ServerOpts]),
+ spawn(fun() ->
+ {ok, ASocket} = ssl:transport_accept(LSocket),
+ Result0 = ssl:handshake(ASocket),
+ receive
+ {ssl, Socket, "from client"} ->
+ ssl:send(Socket, "from server"),
+ ssl:close(Socket)
+ end
+ end),
+ {ok, Client} = ssl:connect(Hostname, Port, ClientOpts),
+
+ ssl:send(Client, "from client"),
+ receive
+ {ssl, Client, "from server"} ->
+ ssl:close(Client)
+ end.
+
+
diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl
index 76bf0fa895..70f718cb12 100644
--- a/lib/ssl/test/make_certs.erl
+++ b/lib/ssl/test/make_certs.erl
@@ -33,6 +33,7 @@
v2_crls = true,
ecc_certs = false,
issuing_distribution_point = false,
+ crldp_crlissuer = false,
crl_port = 8000,
openssl_cmd = "openssl",
hostname = "host.example.com"}).
@@ -66,6 +67,8 @@ make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) ->
make_config(T, C#config{ecc_certs = Bool});
make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) ->
make_config(T, C#config{issuing_distribution_point = Bool});
+make_config([{crldp_crlissuer, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{crldp_crlissuer = Bool});
make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) ->
make_config(T, C#config{openssl_cmd = Cmd});
make_config([{hostname, Hostname}|T], C) when is_list(Hostname) ->
@@ -485,6 +488,87 @@ ca_cnf(
ca_cnf(
Root,
#config{
+ crldp_crlissuer = true,
+ hostname = Hostname} = C) ->
+ ["# Purpose: Configuration for CAs.\n"
+ "\n"
+ "ROOTDIR = " ++ Root ++ "\n"
+ "default_ca = ca\n"
+ "\n"
+
+ "[ca]\n"
+ "dir = $ROOTDIR/", C#config.commonName, "\n"
+ "certs = $dir/certs\n"
+ "crl_dir = $dir/crl\n"
+ "database = $dir/index.txt\n"
+ "new_certs_dir = $dir/newcerts\n"
+ "certificate = $dir/cert.pem\n"
+ "serial = $dir/serial\n"
+ "crl = $dir/crl.pem\n",
+ ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls],
+ "private_key = $dir/private/key.pem\n"
+ "RANDFILE = $dir/private/RAND\n"
+ "\n"
+ "x509_extensions = user_cert\n",
+ ["crl_extensions = crl_ext\n" || C#config.v2_crls],
+ "unique_subject = no\n"
+ "default_days = 3600\n"
+ "default_md = sha1\n"
+ "preserve = no\n"
+ "policy = policy_match\n"
+ "\n"
+
+ "[policy_match]\n"
+ "commonName = supplied\n"
+ "organizationalUnitName = optional\n"
+ "organizationName = match\n"
+ "countryName = match\n"
+ "localityName = match\n"
+ "emailAddress = supplied\n"
+ "\n"
+
+ "[crl_ext]\n"
+ "authorityKeyIdentifier=keyid:always,issuer:always\n",
+
+ "[user_cert]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = DNS.1:" ++ Hostname ++ "\n"
+ "issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=crl_section\n"
+
+ "[crl_section]\n"
+ "fullname=URI:http://localhost/",C#config.commonName,"/crl.pem\n"
+ "CRLissuer=dirName:issuer_sect\n"
+
+ "[issuer_sect]\n"
+ "C=UK\n"
+ "O=Organisation\n"
+ "CN=Some Name\n"
+
+ "[user_cert_digital_signature_only]\n"
+ "basicConstraints = CA:false\n"
+ "keyUsage = digitalSignature\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid,issuer:always\n"
+ "subjectAltName = DNS.1:" ++ Hostname ++ "\n"
+ "issuerAltName = issuer:copy\n"
+ "\n"
+
+ "[ca_cert]\n"
+ "basicConstraints = critical,CA:true\n"
+ "keyUsage = cRLSign, keyCertSign\n"
+ "subjectKeyIdentifier = hash\n"
+ "authorityKeyIdentifier = keyid:always,issuer:always\n"
+ "subjectAltName = email:copy\n"
+ "issuerAltName = issuer:copy\n"
+ ];
+
+ca_cnf(
+ Root,
+ #config{
issuing_distribution_point = false,
hostname = Hostname
} = C) ->
diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl
index ed3d81eba7..7322e228bd 100644
--- a/lib/ssl/test/openssl_npn_SUITE.erl
+++ b/lib/ssl/test/openssl_npn_SUITE.erl
@@ -74,6 +74,7 @@ init_per_suite(Config0) ->
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
+ ssl:clear_pem_cache(),
ssl_test_lib:make_rsa_cert(Config0)
catch _:_ ->
{skip, "Crypto did not start"}
diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl
index 7c129633da..6f74408cb4 100644
--- a/lib/ssl/test/openssl_session_SUITE.erl
+++ b/lib/ssl/test/openssl_session_SUITE.erl
@@ -129,7 +129,21 @@ init_per_testcase(reuse_session_erlang_client, Config) ->
application:set_env(ssl, session_lifetime, ?EXPIRE),
ssl:start(),
Config;
-
+init_per_testcase(reuse_session_erlang_server, Config) ->
+ Version = ssl_test_lib:protocol_version(Config),
+ case ssl_test_lib:is_dtls_version(Version) of
+ true ->
+ case ssl_test_lib:openssl_sane_dtls_session_reuse() of
+ true ->
+ ct:timetrap({seconds, 10}),
+ Config;
+ false ->
+ {skip, "Broken OpenSSL DTLS session reuse"}
+ end;
+ false ->
+ ct:timetrap({seconds, 10}),
+ Config
+ end;
init_per_testcase(TestCase, Config) ->
ct:timetrap({seconds, 10}),
Config.
diff --git a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl
index 8a2692ec1d..443236f166 100644
--- a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl
@@ -51,7 +51,7 @@ cert_groups() ->
{group, ecdsa}].
tests() ->
- [tls13_client_tls12_server,
+ [%%tls13_client_tls12_server, %% Not testable with current openssl s_client
%%tls13_client_with_ext_tls12_server,
tls12_client_tls13_server].
@@ -127,6 +127,10 @@ end_per_group(GroupName, Config) ->
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
+%% openssl s_client cannot be configured to support both TLS 1.3 and TLS 1.2.
+%% In its ClientHello the supported_versions extension contains only one element
+%% [{3,4}] that the server does not accept if it is configured to not support
+%% TLS 1.3.
tls13_client_tls12_server() ->
[{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}].
@@ -159,11 +163,13 @@ tls13_client_tls12_server(Config) when is_list(Config) ->
%% ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
-
+
+
+%% TODO: wrong version of TLS is configured for the client
tls12_client_tls13_server() ->
[{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}].
-tls12_client_tls13_server(Config) when is_list(Config) ->
+tls12_client_tls13_server(Config) when is_list(Config) ->
ClientOpts = [{versions,
['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
ServerOpts = [{versions,
diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
index 2ceb540e15..9ae267a2d3 100644
--- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
@@ -863,9 +863,9 @@ psk_identities(N, Acc) ->
psk_identities(N - 1, [psk_identity()|Acc]).
psk_identity() ->
- Len = rand:uniform(32),
+ Len = 8 + rand:uniform(8),
Identity = crypto:strong_rand_bytes(Len),
- Age = crypto:strong_rand_bytes(4),
+ <<?UINT32(Age)>> = crypto:strong_rand_bytes(4),
#psk_identity{
identity = Identity,
obfuscated_ticket_age = Age}.
diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl
index cc0b636580..a5b9fe9b0e 100644
--- a/lib/ssl/test/ssl_alert_SUITE.erl
+++ b/lib/ssl/test/ssl_alert_SUITE.erl
@@ -49,7 +49,7 @@ end_per_testcase(_TestCase, Config) ->
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
alerts() ->
- [{doc, "Test ssl_alert:alert_txt/1"}].
+ [{doc, "Test ssl_alert formating code"}].
alerts(Config) when is_list(Config) ->
Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC,
?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE,
@@ -66,7 +66,9 @@ alerts(Config) when is_list(Config) ->
Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) |
[?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]],
lists:foreach(fun(Alert) ->
- try ssl_alert:alert_txt(Alert)
+ try
+ ssl_alert:reason_code(Alert, server, "TLS", cipher),
+ ssl_alert:reason_code(Alert, client, "TLS", hello)
catch
C:E:T ->
ct:fail({unexpected, {C, E, T}})
@@ -78,8 +80,9 @@ alert_details() ->
alert_details(Config) when is_list(Config) ->
Unique = make_ref(),
UniqueStr = lists:flatten(io_lib:format("~w", [Unique])),
- Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique),
- case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of
+ Alert = ?ALERT_REC(?WARNING, ?INTERNAL_ERROR, Unique),
+ {tls_alert, {_, Txt}} = ssl_alert:reason_code(Alert#alert{role=server}, server, "TLS", cipher),
+ case string:str(Txt, UniqueStr) of
0 ->
ct:fail(error_details_missing);
_ ->
@@ -90,11 +93,16 @@ alert_details(Config) when is_list(Config) ->
alert_details_not_too_big() ->
[{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}].
alert_details_not_too_big(Config) when is_list(Config) ->
- Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))),
- Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason),
- case length(ssl_alert:alert_txt(Alert)) < 1000 of
+ Reason = ssl:cipher_suites(all, 'tlsv1.2'),
+ ReasonText = lists:flatten(io_lib:format("~p", [Reason])),
+ Alert = ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason),
+ PrefixLen =
+ length("TLS server: In state cipher at ssl_handshake.erl:1710 generated SERVER ALERT: Fatal - Handshake Failure"),
+ {tls_alert, {_, Txt}} = ssl_alert:reason_code(Alert#alert{role=server, where = #{file => "ssl_handshake.erl",
+ line => 1710}}, server, "TLS", cipher),
+ case byte_size(term_to_binary(Txt)) < (byte_size(term_to_binary(ReasonText)) - PrefixLen) of
true ->
- ok;
+ ct:pal("~s", [Txt]);
false ->
ct:fail(ssl_alert_text_too_big)
end.
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index c0f23cce58..d2fe83a58e 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -44,17 +44,22 @@ all() ->
groups() ->
[
- {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order,
- new_options_in_handshake])
+ {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) --
+ [dh_params, honor_server_cipher_order, honor_client_cipher_order,
+ new_options_in_handshake, handshake_continue_tls13_client])
++ (since_1_2() -- [conf_signature_algs])},
{'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()},
{'tlsv1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3() ++ beast_mitigation_test()},
{'sslv3', [], (gen_api_tests() -- [new_options_in_handshake]) ++ beast_mitigation_test() ++ pre_1_3()},
- {'dtlsv1.2', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile,
- invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()},
- {'dtlsv1', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile,
- invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()}
+ {'dtlsv1.2', [], (gen_api_tests() --
+ [invalid_keyfile, invalid_certfile, invalid_cacertfile,
+ invalid_options, new_options_in_handshake]) ++
+ handshake_paus_tests() -- [handshake_continue_tls13_client] ++ pre_1_3()},
+ {'dtlsv1', [], (gen_api_tests() --
+ [invalid_keyfile, invalid_certfile, invalid_cacertfile,
+ invalid_options, new_options_in_handshake]) ++
+ handshake_paus_tests() -- [handshake_continue_tls13_client] ++ pre_1_3()}
].
since_1_2() ->
@@ -102,7 +107,9 @@ gen_api_tests() ->
invalid_cacertfile,
invalid_keyfile,
options_not_proplist,
- invalid_options
+ invalid_options,
+ cb_info,
+ log_alert
].
handshake_paus_tests() ->
@@ -110,7 +117,8 @@ handshake_paus_tests() ->
handshake_continue,
handshake_continue_timeout,
hello_client_cancel,
- hello_server_cancel
+ hello_server_cancel,
+ handshake_continue_tls13_client
].
%% Only relevant for SSL 3.0 and TLS 1.1
@@ -435,7 +443,8 @@ handshake_continue(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
- {options, ssl_test_lib:ssl_options([{reuseaddr, true},
+ {options, ssl_test_lib:ssl_options([{reuseaddr, true},
+ {log_level, debug},
{verify, verify_peer},
{handshake, hello} | ServerOpts
],
@@ -460,6 +469,69 @@ handshake_continue(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+%%--------------------------------------------------------------------
+handshake_continue_tls13_client() ->
+ [{doc, "Test API function ssl:handshake_continue/3 with fixed TLS 1.3 client"}].
+handshake_continue_tls13_client(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ssl_test_lib:ssl_options([{reuseaddr, true},
+ {log_level, debug},
+ {verify, verify_peer},
+ {handshake, hello} | ServerOpts
+ ],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ServerOpts)}
+ ]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ DummyTicket =
+ <<131,116,0,0,0,5,100,0,4,104,107,100,102,100,0,6,115,104,97,51,
+ 56,52,100,0,3,112,115,107,109,0,0,0,48,150,90,38,127,26,12,5,
+ 228,180,235,229,214,215,27,236,149,182,82,14,140,50,81,0,150,
+ 248,152,180,193,207,80,52,107,196,200,2,77,4,96,140,65,239,205,
+ 224,125,129,179,147,103,100,0,3,115,110,105,107,0,25,112,114,
+ 111,117,100,102,111,111,116,46,111,116,112,46,101,114,105,99,
+ 115,115,111,110,46,115,101,100,0,6,116,105,99,107,101,116,104,6,
+ 100,0,18,110,101,119,95,115,101,115,115,105,111,110,95,116,105,
+ 99,107,101,116,98,0,0,28,32,98,127,110,83,249,109,0,0,0,8,0,0,0,
+ 0,0,0,0,5,109,0,0,0,113,112,154,74,26,27,0,111,147,51,110,216,
+ 43,45,4,100,215,152,195,118,96,22,34,1,184,170,42,166,238,109,
+ 187,138,196,147,102,205,116,83,241,174,227,232,156,148,60,153,3,
+ 175,128,115,192,36,103,191,239,58,222,192,172,190,239,92,8,131,
+ 195,0,217,187,222,143,104,6,86,53,93,27,218,198,205,138,223,202,
+ 11,55,168,104,6,219,228,217,157,37,52,205,252,165,135,167,116,
+ 216,172,231,222,189,84,97,0,8,106,108,88,47,114,48,116,0,0,0,0,
+ 100,0,9,116,105,109,101,115,116,97,109,112,98,93,205,0,44>>,
+
+ %% Send dummy session ticket to trigger sending of pre_shared_key and
+ %% psk_key_exchange_modes extensions.
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ssl_test_lib:ssl_options([{handshake, hello},
+ {session_tickets, enabled},
+ {use_ticket, [DummyTicket]},
+ {versions, ['tlsv1.3',
+ 'tlsv1.2',
+ 'tlsv1.1',
+ 'tlsv1']},
+ {verify, verify_peer} | ClientOpts
+ ],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
%%------------------------------------------------------------------
handshake_continue_timeout() ->
@@ -1538,6 +1610,60 @@ default_reject_anonymous(Config) when is_list(Config) ->
ssl_test_lib:check_server_alert(Server, Client, insufficient_security).
%%-------------------------------------------------------------------
+cb_info() ->
+ [{doc,"Test that we can set cb_info."}].
+
+cb_info(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ Protocol = proplists:get_value(protocol, Config, tls),
+ CbInfo =
+ case Protocol of
+ tls ->
+ {cb_info, {gen_tcp, tcp, tcp_closed, tcp_error}};
+ dtls ->
+ {cb_info, {gen_udp, udp, udp_closed, udp_error}}
+ end,
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [CbInfo | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [CbInfo | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+%%-------------------------------------------------------------------
+log_alert() ->
+ [{doc,"Test that we can set log_alert."}].
+
+log_alert(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{log_alert, false} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{log_alert, false} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+
+%%-------------------------------------------------------------------
%% Note that these test only test that the options are valid to set. As application data
%% is a stream you can not test that the send acctually splits it up as when it arrives
%% again at the user layer it may be concatenated. But COVER can show that the split up
diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl
index b2fd3874a8..47d4b04d90 100644
--- a/lib/ssl/test/ssl_crl_SUITE.erl
+++ b/lib/ssl/test/ssl_crl_SUITE.erl
@@ -42,7 +42,8 @@ groups() ->
{check_true, [], [{group, v2_crl},
{group, v1_crl},
{group, idp_crl},
- {group, crl_hash_dir}]},
+ {group, crl_hash_dir},
+ {group, crl_verify_crldp_crlissuer}]},
{check_peer, [], [{group, v2_crl},
{group, v1_crl},
{group, idp_crl},
@@ -54,7 +55,8 @@ groups() ->
{v2_crl, [], basic_tests()},
{v1_crl, [], basic_tests()},
{idp_crl, [], basic_tests()},
- {crl_hash_dir, [], basic_tests() ++ crl_hash_dir_tests()}].
+ {crl_hash_dir, [], basic_tests() ++ crl_hash_dir_tests()},
+ {crl_verify_crldp_crlissuer, [], [crl_verify_valid]}].
basic_tests() ->
[crl_verify_valid, crl_verify_revoked, crl_verify_no_crl].
@@ -108,8 +110,8 @@ init_per_group(Group, Config0) ->
CertDir = filename:join(proplists:get_value(priv_dir, Config0), Group),
{CertOpts, Config} = init_certs(CertDir, Group, Config0),
{ok, _} = make_certs:all(DataDir, CertDir, CertOpts),
- CrlCacheOpts = case Group of
- crl_hash_dir ->
+ CrlCacheOpts = case need_hash_dir(Group) of
+ true ->
CrlDir = filename:join(CertDir, "crls"),
%% Copy CRLs to their hashed filenames.
%% Find the hashes with 'openssl crl -noout -hash -in crl.pem'.
@@ -462,8 +464,17 @@ is_idp(idp_crl) ->
is_idp(_) ->
false.
+need_hash_dir(crl_hash_dir) ->
+ true;
+need_hash_dir(crl_verify_crldp_crlissuer) ->
+ true;
+need_hash_dir(_) ->
+ false.
+
init_certs(_,v1_crl, Config) ->
{[{v2_crls, false}], Config};
+init_certs(_,crl_verify_crldp_crlissuer , Config) ->
+ {[{crldp_crlissuer, true}], Config};
init_certs(_, idp_crl, Config) ->
Port = proplists:get_value(httpd_port, Config),
{[{crl_port,Port},
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index 7cfb2ac0c5..be3d7c2dfe 100644
--- a/lib/ssl/test/ssl_dist_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_SUITE.erl
@@ -116,7 +116,6 @@ basic_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
-
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end),
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index e4a2ebb583..dd84d732c1 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -28,6 +28,7 @@
-include("ssl_alert.hrl").
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
-include("tls_handshake.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -106,7 +107,7 @@ decode_hello_handshake(_Config) ->
Version = {3, 0},
{Records, _Buffer} = tls_handshake:get_tls_handshake(Version, HelloPacket, <<>>,
- ssl:options_to_map(#ssl_options{})),
+ default_options_map()),
{Hello, _Data} = hd(Records),
Extensions = Hello#server_hello.extensions,
@@ -274,3 +275,7 @@ is_supported(Hash) ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
lists:member(Hash, Hashs).
+
+default_options_map() ->
+ Fun = fun (_Key, {Default, _}) -> Default end,
+ maps:map(Fun, ?RULES).
diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl
index 8a76a3a82b..1f794075c1 100644
--- a/lib/ssl/test/ssl_npn_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_SUITE.erl
@@ -68,7 +68,8 @@ init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
ok ->
- ssl_test_lib:clean_start(),
+ ssl_test_lib:clean_start(),
+ ssl:clear_pem_cache(),
Config = ssl_test_lib:make_rsa_cert(Config0),
ssl_test_lib:cert_options(Config)
catch _:_ ->
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index e098190ece..68c3962b2c 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -72,7 +72,7 @@ encode_and_decode_client_hello_test(Config) ->
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>,
- ssl:options_to_map(#ssl_options{})),
+ default_options_map()),
Extensions = DecodedHandshakeMessage#client_hello.extensions,
#{next_protocol_negotiation := undefined} = Extensions.
%%--------------------------------------------------------------------
@@ -81,7 +81,7 @@ encode_and_decode_npn_client_hello_test(Config) ->
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>,
- ssl:options_to_map(#ssl_options{})),
+ default_options_map()),
Extensions = DecodedHandshakeMessage#client_hello.extensions,
#{next_protocol_negotiation := #next_protocol_negotiation{extension_data = <<>>}} = Extensions.
%%--------------------------------------------------------------------
@@ -90,7 +90,7 @@ encode_and_decode_server_hello_test(Config) ->
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>,
- ssl:options_to_map(#ssl_options{})),
+ default_options_map()),
Extensions = DecodedHandshakeMessage#server_hello.extensions,
#{next_protocol_negotiation := undefined} = Extensions.
@@ -100,7 +100,7 @@ encode_and_decode_npn_server_hello_test(Config) ->
Version = ssl_test_lib:protocol_version(Config),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>,
- ssl:options_to_map(#ssl_options{})),
+ default_options_map()),
Extensions = DecodedHandshakeMessage#server_hello.extensions,
ct:log("~p ~n", [Extensions]),
#{next_protocol_negotiation := #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}} = Extensions.
@@ -153,3 +153,7 @@ create_connection_states() ->
current_read => #{secure_renegotiation => false
}
}.
+
+default_options_map() ->
+ Fun = fun (_Key, {Default, _}) -> Default end,
+ maps:map(Fun, ?RULES).
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index 6d26b2df33..b79a51f02a 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -860,11 +860,11 @@ server_http_decode(Socket, HttpResponse) ->
Other4 -> exit({?LINE, Other4})
end,
assert_packet_opt(Socket, http),
- ok = ssl:send(Socket, HttpResponse),
+ spawn(fun() -> ssl:send(Socket, HttpResponse) end),
ok.
client_http_decode(Socket, HttpRequest) ->
- ok = ssl:send(Socket, HttpRequest),
+ spawn(fun() -> ssl:send(Socket, HttpRequest) end),
receive
{ssl, Socket, {http_response, {1,1}, 200, "OK"}} -> ok;
Other1 -> exit({?LINE, Other1})
@@ -922,7 +922,7 @@ packet_http_decode_list(Config) when is_list(Config) ->
client_http_decode_list(Socket, HttpRequest) ->
- ok = ssl:send(Socket, HttpRequest),
+ spawn(fun() -> ssl:send(Socket, HttpRequest) end),
receive
{ssl, Socket, {http_response, {1,1}, 200, "OK"}} -> ok;
Other1 -> exit({?LINE, Other1})
@@ -1001,13 +1001,13 @@ server_http_bin_decode(Socket, HttpResponse, Count) when Count > 0 ->
Other4 -> exit({?LINE, Other4})
end,
assert_packet_opt(Socket, http_bin),
- ok = ssl:send(Socket, HttpResponse),
+ spawn(fun() -> ssl:send(Socket, HttpResponse) end),
server_http_bin_decode(Socket, HttpResponse, Count - 1);
server_http_bin_decode(_, _, _) ->
ok.
client_http_bin_decode(Socket, HttpRequest, Count) when Count > 0 ->
- ok = ssl:send(Socket, HttpRequest),
+ spawn(fun() -> ssl:send(Socket, HttpRequest) end),
receive
{ssl, Socket, {http_response, {1,1}, 200, <<"OK">>}} -> ok;
Other1 -> exit({?LINE, Other1})
@@ -1085,7 +1085,7 @@ server_http_decode_error(Socket, HttpResponse) ->
{ok, http_eoh} = ssl:recv(Socket, 0),
assert_packet_opt(Socket, http),
- ok = ssl:send(Socket, HttpResponse),
+ spawn(fun() -> ssl:send(Socket, HttpResponse) end),
ok.
%%--------------------------------------------------------------------
packet_httph_active() ->
@@ -2009,13 +2009,14 @@ packet(Config, Data, Send, Recv, Quantity, Packet, Active) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
{mfa, {?MODULE, Send ,[Data, Quantity]}},
- {options, [{packet, Packet} | ServerOpts]}]),
+ {options, [{packet, Packet}, {nodelay, true}| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
{mfa, {?MODULE, Recv, [Data, Quantity]}},
{options, [{active, Active},
+ {nodelay, true},
{packet, Packet} |
ClientOpts]}]),
@@ -2048,7 +2049,7 @@ passive_recv_packet(Socket, _, 0) ->
{other, Other, ssl:connection_information(Socket, [session_id, cipher_suite]), 0}
end;
passive_recv_packet(Socket, Data, N) ->
- case ssl:recv(Socket, 0) of
+ case ssl:recv(Socket, 0, 5000) of
{ok, Data} ->
passive_recv_packet(Socket, Data, N-1);
Other ->
@@ -2108,18 +2109,10 @@ active_once_packet(Socket,_, 0) ->
end;
active_once_packet(Socket, Data, N) ->
receive
- {ssl, Socket, Byte} when length(Byte) == 1 ->
- ssl:setopts(Socket, [{active, once}]),
- receive
- {ssl, Socket, _} ->
- ssl:setopts(Socket, [{active, once}]),
- active_once_packet(Socket, Data, N-1)
- end;
{ssl, Socket, Data} ->
- ok
- end,
- ssl:setopts(Socket, [{active, once}]),
- active_once_packet(Socket, Data, N-1).
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_packet(Socket, Data, N-1)
+ end.
active_raw(Socket, Data, N) ->
active_raw(Socket, (length(Data) * N)).
@@ -2140,11 +2133,6 @@ active_packet(Socket, _, 0) ->
end;
active_packet(Socket, Data, N) ->
receive
- {ssl, Socket, Byte} when length(Byte) == 1 ->
- receive
- {ssl, Socket, _} ->
- active_packet(Socket, Data, N -1)
- end;
{ssl, Socket, Data} ->
active_packet(Socket, Data, N -1);
Other ->
@@ -2164,7 +2152,8 @@ server_packet_decode(Socket, Packet) ->
{ssl, Socket, Packet} -> ok;
Other2 -> exit({?LINE, Other2})
end,
- ok = ssl:send(Socket, Packet).
+ spawn(fun() -> ssl:send(Socket, Packet) end),
+ ok.
client_packet_decode(Socket, Packet) when is_binary(Packet)->
<<P1:10/binary, P2/binary>> = Packet,
@@ -2173,14 +2162,12 @@ client_packet_decode(Socket, [Head | Tail] = Packet) ->
client_packet_decode(Socket, [Head], Tail, Packet).
client_packet_decode(Socket, P1, P2, Packet) ->
- ct:log("Packet: ~p ~n", [Packet]),
- ok = ssl:send(Socket, P1),
- ok = ssl:send(Socket, P2),
+ spawn(fun() -> ssl:send(Socket, P1), ssl:send(Socket, P2) end),
receive
{ssl, Socket, Packet} -> ok;
Other1 -> exit({?LINE, Other1})
end,
- ok = ssl:send(Socket, Packet),
+ spawn(fun() -> ssl:send(Socket, Packet) end),
receive
{ssl, Socket, Packet} -> ok;
Other2 -> exit({?LINE, Other2})
@@ -2193,10 +2180,11 @@ server_header_decode_active(Socket, Packet, Result) ->
{ssl, Socket, Other1} ->
check_header_result(Result, Other1)
end,
- ok = ssl:send(Socket, Packet).
+ spawn(fun() -> ssl:send(Socket, Packet) end),
+ ok.
client_header_decode_active(Socket, Packet, Result) ->
- ok = ssl:send(Socket, Packet),
+ spawn(fun() -> ssl:send(Socket, Packet) end),
receive
{ssl, Socket, Result} ->
ok;
@@ -2211,11 +2199,11 @@ server_header_decode_passive(Socket, Packet, Result) ->
{ok, Other} ->
check_header_result(Result, Other)
end,
- ok = ssl:send(Socket, Packet).
+ spawn(fun() -> ssl:send(Socket, Packet) end),
+ ok.
client_header_decode_passive(Socket, Packet, Result) ->
- ok = ssl:send(Socket, Packet),
-
+ spawn(fun() -> ssl:send(Socket, Packet) end),
case ssl:recv(Socket, 0) of
{ok, Result} ->
ok;
@@ -2253,7 +2241,8 @@ server_line_packet_decode(Socket, L1, L2, Packet) ->
{ssl, Socket, L2} -> ok;
Other2 -> exit({?LINE, Other2})
end,
- ok = ssl:send(Socket, Packet).
+ spawn(fun() -> ssl:send(Socket, Packet) end),
+ ok.
client_line_packet_decode(Socket, Packet) when is_binary(Packet)->
<<P1:10/binary, P2/binary>> = Packet,
@@ -2264,8 +2253,7 @@ client_line_packet_decode(Socket, [Head | Tail] = Packet) ->
client_line_packet_decode(Socket, [Head], Tail, L1 ++ "\n", L2 ++ "\n").
client_line_packet_decode(Socket, P1, P2, L1, L2) ->
- ok = ssl:send(Socket, P1),
- ok = ssl:send(Socket, P2),
+ spawn(fun() -> ssl:send(Socket, P1), ssl:send(Socket, P2) end),
receive
{ssl, Socket, L1} -> ok;
Other1 -> exit({?LINE, Other1})
@@ -2305,11 +2293,11 @@ client_reject_packet_opt(Config, PacketOpt) ->
send_switch_packet(SslSocket, Data, NextPacket) ->
- ssl:send(SslSocket, Data),
+ spawn(fun() -> ssl:send(SslSocket, Data) end),
receive
{ssl, SslSocket, "Hello World"} ->
ssl:setopts(SslSocket, [{packet, NextPacket}]),
- ssl:send(SslSocket, Data),
+ spawn(fun() -> ssl:send(SslSocket, Data) end),
receive
{ssl, SslSocket, "Hello World"} ->
ok
@@ -2318,10 +2306,11 @@ send_switch_packet(SslSocket, Data, NextPacket) ->
recv_switch_packet(SslSocket, Data, NextPacket) ->
receive
{ssl, SslSocket, "Hello World"} ->
- ssl:send(SslSocket, Data),
+ spawn(fun() -> ssl:send(SslSocket, Data) end),
ssl:setopts(SslSocket, [{packet, NextPacket}]),
receive
{ssl, SslSocket, "Hello World"} ->
- ssl:send(SslSocket, Data)
+ spawn(fun() -> ssl:send(SslSocket, Data) end),
+ ok
end
end.
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index 2d0ffd03d7..91043408b7 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -776,7 +776,7 @@ echo_recv(_Socket, 0) ->
ok;
echo_recv(Socket, Size) ->
{ok, Data} = ssl:recv(Socket, 0),
- ok = ssl:send(Socket, Data),
+ spawn(fun() -> ssl:send(Socket, Data) end),
echo_recv(Socket, Size - byte_size(Data)).
@@ -785,7 +785,7 @@ echo_recv_chunk(_Socket, _, 0) ->
ok;
echo_recv_chunk(Socket, ChunkSize, Size) ->
{ok, Data} = ssl:recv(Socket, ChunkSize),
- ok = ssl:send(Socket, Data),
+ spawn(fun() -> ssl:send(Socket, Data) end),
echo_recv_chunk(Socket, ChunkSize, Size - ChunkSize).
@@ -795,7 +795,7 @@ echo_active_once(_Socket, 0) ->
echo_active_once(Socket, Size) ->
receive
{ssl, Socket, Data} ->
- ok = ssl:send(Socket, Data),
+ spawn(fun() -> ssl:send(Socket, Data) end),
NewSize = Size - byte_size(Data),
ssl:setopts(Socket, [{active, once}]),
echo_active_once(Socket, NewSize)
@@ -807,7 +807,7 @@ echo_active(_Socket, 0) ->
echo_active(Socket, Size) ->
receive
{ssl, Socket, Data} ->
- ok = ssl:send(Socket, Data),
+ spawn(fun() -> ssl:send(Socket, Data) end),
echo_active(Socket, Size - byte_size(Data))
end.
diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl
index 553c2d247b..f3b1c38d2e 100644
--- a/lib/ssl/test/ssl_session_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -225,15 +225,9 @@ session_cleanup(Config) when is_list(Config) ->
%% Make sure session has expired and been cleaned up
check_timer(SessionTimer),
- ct:sleep(?DELAY *2), %% Delay time + some extra time
-
- {ServerDelayTimer, ClientDelayTimer} = get_delay_timers(),
-
- check_timer(ServerDelayTimer),
- check_timer(ClientDelayTimer),
-
+
ct:sleep(?SLEEP), %% Make sure clean has had time to run
-
+
undefined = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}),
undefined = ssl_session_cache:lookup(ServerCache, {Port, Id}),
@@ -524,24 +518,6 @@ check_timer(Timer) ->
ct:sleep(Int),
check_timer(Timer)
end.
-
-get_delay_timers() ->
- {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
- [_, _,_, _, Prop] = StatusInfo,
- State = ssl_test_lib:state(Prop),
- case element(8, State) of
- {undefined, undefined} ->
- ct:sleep(?SLEEP),
- get_delay_timers();
- {undefined, _} ->
- ct:sleep(?SLEEP),
- get_delay_timers();
- {_, undefined} ->
- ct:sleep(?SLEEP),
- get_delay_timers();
- DelayTimers ->
- DelayTimers
- end.
wait_for_server() ->
ct:sleep(100).
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
new file mode 100644
index 0000000000..83e8e3214d
--- /dev/null
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -0,0 +1,722 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssl_session_ticket_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("tls_handshake.hrl").
+
+-include_lib("common_test/include/ct.hrl").
+
+-define(SLEEP, 500).
+
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [
+ {group, 'tlsv1.3'}
+ ].
+
+groups() ->
+ [{'tlsv1.3', [], [{group, stateful}, {group, stateless}, {group, openssl_server}]},
+ {openssl_server, [], [erlang_client_openssl_server_basic,
+ erlang_client_openssl_server_hrr,
+ erlang_client_openssl_server_hrr_multiple_tickets
+ ]},
+ {stateful, [], session_tests()},
+ {stateless, [], session_tests()}].
+
+session_tests() ->
+ [erlang_client_erlang_server_basic,
+ openssl_client_erlang_server_basic,
+ erlang_client_erlang_server_hrr,
+ openssl_client_erlang_server_hrr,
+ erlang_client_erlang_server_multiple_tickets,
+ erlang_client_erlang_server_multiple_tickets_2hash].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl_test_lib:make_rsa_cert(Config0)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_group(stateful, Config) ->
+ [{server_ticket_mode, stateful} | proplists:delete(server_ticket_mode, Config)];
+init_per_group(stateless, Config) ->
+ [{server_ticket_mode, stateless} | proplists:delete(server_ticket_mode, Config)];
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:clean_tls_version(Config),
+ case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of
+ true ->
+ ssl_test_lib:init_tls_version(GroupName, Config);
+ _ ->
+ case ssl_test_lib:sufficient_crypto_support(GroupName) of
+ true ->
+ ssl:start(),
+ Config;
+ false ->
+ {skip, "Missing crypto support"}
+ end
+ end.
+
+end_per_group(GroupName, Config) ->
+ case ssl_test_lib:is_tls_version(GroupName) of
+ true ->
+ ssl_test_lib:clean_tls_version(Config);
+ false ->
+ Config
+ end.
+
+init_per_testcase(_, Config) ->
+ ssl:stop(),
+ application:load(ssl),
+ ssl:start(),
+ ct:timetrap({seconds, 15}),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+
+erlang_client_erlang_server_basic() ->
+ [{doc,"Test session resumption with session tickets (erlang client - erlang server)"}].
+erlang_client_erlang_server_basic(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Full handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+
+erlang_client_openssl_server_basic() ->
+ [{doc,"Test session resumption with session tickets (erlang client - openssl server)"}].
+erlang_client_openssl_server_basic(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Version = 'tlsv1.3',
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ CACertFile = proplists:get_value(cacertfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+
+ Exe = "openssl",
+ Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile, "-msg", "-debug"],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}},
+ {from, self()}, {options, ClientOpts}]),
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply]}},
+ {from, self()},
+ {options, ClientOpts}]),
+ process_flag(trap_exit, false),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Client1).
+
+
+openssl_client_erlang_server_basic() ->
+ [{doc,"Test session resumption with session tickets (openssl client - erlang server)"}].
+openssl_client_erlang_server_basic(Config) when is_list(Config) ->
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ TicketFile0 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket0"]),
+ TicketFile1 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket1"]),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ Data = "Hello world",
+
+ %% Configure session tickets
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+
+ Version = 'tlsv1.3',
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ Exe = "openssl",
+ Args0 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
+ ++ ":" ++ integer_to_list(Port0),
+ ssl_test_lib:version_flag(Version),
+ "-sess_out", TicketFile0],
+
+ OpenSslPort0 = ssl_test_lib:portable_open_port(Exe, Args0),
+
+ true = port_command(OpenSslPort0, Data),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ Args1 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
+ ++ ":" ++ integer_to_list(Port0),
+ ssl_test_lib:version_flag(Version),
+ "-sess_in", TicketFile0,
+ "-sess_out", TicketFile1],
+
+ OpenSslPort1 = ssl_test_lib:portable_open_port(Exe, Args1),
+
+ true = port_command(OpenSslPort1, Data),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close_port(OpenSslPort0),
+ ssl_test_lib:close_port(OpenSslPort1).
+
+
+erlang_client_erlang_server_hrr() ->
+ [{doc,"Test session resumption with session tickets and hello_retry_request (erlang client - erlang server)"}].
+erlang_client_erlang_server_hrr(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups,[secp256r1, x25519]}|ClientOpts0],
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups, [x448, x25519]}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+
+erlang_client_openssl_server_hrr() ->
+ [{doc,"Test session resumption with session tickets and hello_retry_request (erlang client - openssl server)"}].
+erlang_client_openssl_server_hrr(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Version = 'tlsv1.3',
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ CACertFile = proplists:get_value(cacertfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups,[secp256r1, x25519]}|ClientOpts0],
+
+ Exe = "openssl",
+ Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-cert", CertFile,
+ "-key", KeyFile,
+ "-CAfile", CACertFile,
+ "-groups", "X448:X25519",
+ "-msg", "-debug"],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply]}},
+ {from, self()}, {options, ClientOpts}]),
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply]}},
+ {from, self()},
+ {options, ClientOpts}]),
+ process_flag(trap_exit, false),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Client1).
+
+
+openssl_client_erlang_server_hrr() ->
+ [{doc,"Test session resumption with session tickets and hello_retry_request (openssl client - erlang server)"}].
+openssl_client_erlang_server_hrr(Config) when is_list(Config) ->
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ TicketFile0 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket0"]),
+ TicketFile1 = filename:join([proplists:get_value(priv_dir, Config), "session_ticket1"]),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ Data = "Hello world",
+
+ %% Configure session tickets
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups,[x448, x25519]}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+
+ Version = 'tlsv1.3',
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ Exe = "openssl",
+ Args0 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
+ ++ ":" ++ integer_to_list(Port0),
+ ssl_test_lib:version_flag(Version),
+ "-groups", "P-256:X25519",
+ "-sess_out", TicketFile0],
+
+ OpenSslPort0 = ssl_test_lib:portable_open_port(Exe, Args0),
+
+ true = port_command(OpenSslPort0, Data),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ Args1 = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname)
+ ++ ":" ++ integer_to_list(Port0),
+ ssl_test_lib:version_flag(Version),
+ "-groups", "P-256:X25519",
+ "-sess_in", TicketFile0,
+ "-sess_out", TicketFile1],
+
+ OpenSslPort1 = ssl_test_lib:portable_open_port(Exe, Args1),
+
+ true = port_command(OpenSslPort1, Data),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close_port(OpenSslPort0),
+ ssl_test_lib:close_port(OpenSslPort1).
+
+
+erlang_client_erlang_server_multiple_tickets() ->
+ [{doc,"Test session resumption with multiple session tickets (erlang client - erlang server)"}].
+erlang_client_erlang_server_multiple_tickets(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, enabled}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, wait_reply, {tickets, 3}]}},
+ {from, self()}, {options, ClientOpts}]),
+
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, wait_reply, no_tickets]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0}|ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server0, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client1).
+
+
+erlang_client_openssl_server_hrr_multiple_tickets() ->
+ [{doc,"Test session resumption with multiple session tickets and hello_retry_request (erlang client - openssl server)"}].
+erlang_client_openssl_server_hrr_multiple_tickets(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Version = 'tlsv1.3',
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ CACertFile = proplists:get_value(cacertfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, enabled}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']},
+ {supported_groups,[secp256r1, x25519]}|ClientOpts0],
+
+ Exe = "openssl",
+ Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-cert", CertFile,
+ "-key", KeyFile,
+ "-CAfile", CACertFile,
+ "-groups", "X448:X25519",
+ "-msg", "-debug"],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+
+ %% Store ticket from first connection
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, no_reply, {tickets, 2}]}},
+ {from, self()}, {options, ClientOpts}]),
+
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ %% Close previous connection as s_server can only handle one at a time
+ ssl_test_lib:close(Client0),
+
+ %% Use tickets
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true, no_reply, no_tickets]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0}|ClientOpts]}]),
+
+ process_flag(trap_exit, false),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Client1).
+
+
+erlang_client_erlang_server_multiple_tickets_2hash() ->
+ [{doc,"Test session resumption with multiple session tickets with 2 different hash algorithms (erlang client - erlang server)"}].
+erlang_client_erlang_server_multiple_tickets_2hash(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, enabled}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+ ServerOpts = [{session_tickets, ServerTicketMode}, {log_level, debug},
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ %% Get tickets using sha256
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, wait_reply, {tickets, 3}]}},
+ {from, self()},
+ {options, [{ciphers, [#{key_exchange => any,
+ cipher => aes_128_gcm,
+ mac => aead,
+ prf => sha256}]}| ClientOpts]}]),
+
+ Tickets0 = ssl_test_lib:check_tickets(Client0),
+
+ ct:pal("Received tickets: ~p~n", [Tickets0]),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}}},
+
+ ssl_test_lib:close(Client0),
+
+ %% Get tickets using sha384
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false, wait_reply, {tickets, 3}]}},
+ {from, self()},
+ {options, [{ciphers, [#{key_exchange => any,
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384}]}| ClientOpts]}]),
+
+ Tickets1 = ssl_test_lib:check_tickets(Client1),
+
+ ct:pal("Received tickets: ~p~n", [Tickets1]),
+
+ ssl_test_lib:check_result(Server0, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ ssl_test_lib:close(Client1),
+
+ %% Use tickets for handshake (server chooses TLS_AES_256_GCM_SHA384 cipher suite)
+ Client2 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()},
+ {options, [{use_ticket, Tickets0 ++ Tickets1}|ClientOpts]}]),
+ ssl_test_lib:check_result(Server0, ok, Client2, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [true]}}},
+
+ ssl_test_lib:close(Client2),
+
+ %% Use tickets for handshake (client chooses TLS_CHACHA20_POLY1305_SHA256 cipher suite)
+ Client3 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()},
+ {options, [{ciphers, [#{key_exchange => any,
+ cipher => chacha20_poly1305,
+ mac => aead,
+ prf => sha256}]},
+ {use_ticket, Tickets0 ++ Tickets1}|ClientOpts]}]),
+ ssl_test_lib:check_result(Server0, ok, Client3, ok),
+
+ Server0 ! {listen, {mfa, {ssl_test_lib,
+ verify_active_session_resumption,
+ [false]}}},
+
+ ssl_test_lib:close(Client3),
+
+ %% Use tickets (created using sha384) for handshake (client chooses
+ %% TLS_CHACHA20_POLY1305_SHA256 cipher suite).
+ %% Session resumption should fail as chosen cipher suite uses different hash algorithms
+ %% than those supplied by the selected tickets.
+ Client4 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [false]}},
+ {from, self()},
+ {options, [{ciphers, [#{key_exchange => any,
+ cipher => chacha20_poly1305,
+ mac => aead,
+ prf => sha256}]},
+ {use_ticket, Tickets1}|ClientOpts]}]),
+ ssl_test_lib:check_result(Server0, ok, Client4, ok),
+
+ ssl_test_lib:close(Client4),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0).
+
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index cb528f185a..6218857e05 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -120,9 +120,7 @@ do_run_server(ListenSocket, AcceptSocket, Opts) ->
ct:log("~p:~p~nServer closing ~p ~n", [?MODULE,?LINE, self()]),
Result = Transport:close(AcceptSocket),
Result1 = Transport:close(ListenSocket),
- ct:log("~p:~p~nResult ~p : ~p ~n", [?MODULE,?LINE, Result, Result1]);
- {ssl_closed, _} ->
- ok
+ ct:log("~p:~p~nResult ~p : ~p ~n", [?MODULE,?LINE, Result, Result1])
end.
%%% To enable to test with s_client -reconnect
@@ -537,13 +535,17 @@ check_server_alert(Pid, Alert) ->
check_server_txt(STxt),
ok;
{Pid, {error, closed}} ->
- ok
+ ok;
+ {Pid, {ok, _}} ->
+ ct:fail("Successful connection during negative test.")
end.
check_server_alert(Server, Client, Alert) ->
receive
{Server, {error, {tls_alert, {Alert, STxt}}}} ->
check_server_txt(STxt),
- check_client_alert(Client, Alert)
+ check_client_alert(Client, Alert);
+ {Server, {ok, _}} ->
+ ct:fail("Successful connection during negative test.")
end.
check_client_alert(Pid, Alert) ->
receive
@@ -554,7 +556,9 @@ check_client_alert(Pid, Alert) ->
check_client_txt(CTxt),
ok;
{Pid, {error, closed}} ->
- ok
+ ok;
+ {Pid, {ok, _}} ->
+ ct:fail("Successful connection during negative test.")
end.
check_client_alert(Server, Client, Alert) ->
receive
@@ -565,7 +569,9 @@ check_client_alert(Server, Client, Alert) ->
check_client_txt(CTxt),
ok;
{Client, {error, closed}} ->
- ok
+ ok;
+ {Client, {ok, _}} ->
+ ct:fail("Successful connection during negative test.")
end.
check_server_txt("TLS server" ++ _) ->
ok;
@@ -1832,6 +1838,7 @@ state([{data,[{"StateData", State}]} | _]) -> %% gen_fsm
state([_ | Rest]) ->
state(Rest).
+%% TODO: DTLS considered tls version in this use maybe rename
is_tls_version('dtlsv1.2') ->
true;
is_tls_version('dtlsv1') ->
@@ -1849,6 +1856,13 @@ is_tls_version('sslv3') ->
is_tls_version(_) ->
false.
+is_dtls_version('dtlsv1.2') ->
+ true;
+is_dtls_version('dtlsv1') ->
+ true;
+is_dtls_version(_) ->
+ false.
+
init_tls_version(Version, Config)
when Version == 'dtlsv1.2'; Version == 'dtlsv1' ->
ssl:stop(),
@@ -1936,6 +1950,69 @@ send_recv_result_active_once(Socket) ->
ssl:send(Socket, Data),
active_once_recv_list(Socket, length(Data)).
+verify_active_session_resumption(Socket, SessionResumption) ->
+ verify_active_session_resumption(Socket, SessionResumption, wait_reply, no_tickets).
+%%
+verify_active_session_resumption(Socket, SessionResumption, WaitReply) ->
+ verify_active_session_resumption(Socket, SessionResumption, WaitReply, no_tickets).
+%%
+verify_active_session_resumption(Socket, SessionResumption, WaitForReply, TicketOption) ->
+ case ssl:connection_information(Socket, [session_resumption]) of
+ {ok, [{session_resumption, SessionResumption}]} ->
+ Msg = boolean_to_log_msg(SessionResumption),
+ ct:log("~p:~p~nSession resumption verified! (expected ~p, got ~p)!",
+ [?MODULE, ?LINE, Msg, Msg]);
+ {ok, [{session_resumption, Got0}]} ->
+ Expected = boolean_to_log_msg(SessionResumption),
+ Got = boolean_to_log_msg(Got0),
+ ct:fail("~p:~p~nFailed to verify session resumption! (expected ~p, got ~p)",
+ [?MODULE, ?LINE, Expected, Got])
+ end,
+
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ case WaitForReply of
+ wait_reply ->
+ Data = active_recv(Socket, length(Data));
+ no_reply ->
+ ok;
+ Else1 ->
+ ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else1])
+ end,
+ case TicketOption of
+ {tickets, N} ->
+ receive_tickets(N);
+ no_tickets ->
+ ok;
+ Else2 ->
+ ct:fail("~p:~p~nFaulty parameter: ~p", [?MODULE, ?LINE, Else2])
+ end.
+
+boolean_to_log_msg(true) ->
+ "OK";
+boolean_to_log_msg(false) ->
+ "FAIL".
+
+receive_tickets(N) ->
+ receive_tickets(N, []).
+%%
+receive_tickets(0, Acc) ->
+ Acc;
+receive_tickets(N, Acc) ->
+ receive
+ {ssl, session_ticket, {_, Ticket}} ->
+ receive_tickets(N - 1, [Ticket|Acc])
+ end.
+
+check_tickets(Client) ->
+ receive
+ {Client, Tickets} ->
+ Tickets
+ after
+ 5000 ->
+ ct:fail("~p:~p~nNo tickets received!", [?MODULE, ?LINE])
+ end.
+
active_recv(Socket, N) ->
active_recv(Socket, N, []).
@@ -2219,7 +2296,7 @@ openssl_allows_client_renegotaite(Config) ->
case os:cmd("openssl version") of
"OpenSSL 1.1" ++ _ ->
{skip, "OpenSSL does not allow client renegotiation"};
- "LibreSSL 2" ++ _ ->
+ "LibreSSL" ++ _ ->
{skip, "LibreSSL does not allow client renegotiation"};
_ ->
Config
@@ -2795,6 +2872,18 @@ openssl_sane_dtls_alpn() ->
case os:cmd("openssl version") of
"OpenSSL 1.1.0g" ++ _ ->
false;
+ "OpenSSL 1.1.1 " ++ _ ->
+ false;
+ "OpenSSL 1.1.1a" ++ _ ->
+ false;
+ _->
+ openssl_sane_dtls()
+ end.
+
+openssl_sane_dtls_session_reuse() ->
+ case os:cmd("openssl version") of
+ "OpenSSL 1.1.1 " ++ _ ->
+ false;
"OpenSSL 1.1.1a" ++ _ ->
false;
_->
diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl
index 5df8853b1b..26d7694e16 100644
--- a/lib/ssl/test/tls_1_3_record_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_record_SUITE.erl
@@ -26,13 +26,15 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/tls_record.hrl").
-include_lib("ssl/src/tls_handshake.hrl").
+-include_lib("ssl/src/tls_handshake_1_3.hrl").
-include_lib("ssl/src/ssl_cipher.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
all() ->
[encode_decode,
finished_verify_data,
- '1_RTT_handshake'].
+ '1_RTT_handshake',
+ '0_RTT_handshake'].
init_per_suite(Config) ->
catch crypto:stop(),
@@ -79,7 +81,7 @@ encode_decode(_Config) ->
<<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
- 157>>},
+ 157>>}, undefined,
undefined,
<<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
@@ -105,7 +107,7 @@ encode_decode(_Config) ->
<<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
- 157>>},
+ 157>>}, undefined,
undefined,
<<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207,
147,61,168,145,177,118,160,153,33,53,48,108,191,174>>,
@@ -735,6 +737,22 @@ encode_decode(_Config) ->
SAPTrafficSecret =
tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF),
+ %% Resumption master secret from '0-RTT'
+ %%
+ %% {server} generate resumption secret "tls13 resumption":
+ %%
+ %% PRK (32 octets): 7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf
+ %% da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c
+ %%
+ RMS = hexstr2bin("7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf
+ da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c"),
+
+ %% Verify calculation of resumption master secret that is used to create
+ %% the pre shared key in '0-RTT'.
+ Temp = tls_v1:resumption_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF),
+ erlang:display({rms, RMS}),
+ erlang:display({new_rms, Temp}),
+
%% {server} derive secret "tls13 exp master":
%%
%% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47
@@ -783,7 +801,7 @@ encode_decode(_Config) ->
%% PRK = SAPTrafficsecret
%% key info = WriteKeyInfo
- %% iv info = WrtieIVInfo
+ %% iv info = WriteIVInfo
SWKey =
hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"),
@@ -815,7 +833,449 @@ encode_decode(_Config) ->
SRIV =
hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"),
- {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret).
+ {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret),
+
+ %% {client} calculate finished "tls13 finished":
+ %%
+ %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f
+ %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21
+ %%
+ %% hash (0 octets): (empty)
+ %%
+ %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65
+ %% 64 00
+ %%
+ %% expanded (32 octets): b8 0a d0 10 15 fb 2f 0b d6 5f f7 d4 da 5d
+ %% 6b f8 3f 84 82 1d 1f 87 fd c7 d3 c7 5b 5a 7b 42 d9 c4
+ %%
+ %% finished (32 octets): a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1
+ %% 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61
+
+ %% PRK = CHSTrafficsecret
+ %% info = FInfo
+ CFExpanded =
+ hexstr2bin("b8 0a d0 10 15 fb 2f 0b d6 5f f7 d4 da 5d
+ 6b f8 3f 84 82 1d 1f 87 fd c7 d3 c7 5b 5a 7b 42 d9 c4"),
+
+ CFinishedVerifyData =
+ hexstr2bin("a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1
+ 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"),
+
+ MessageHistory1 = [FinishedHSBin,
+ CertificateVerify,
+ Certificate,
+ EncryptedExtensions,
+ ServerHello,
+ ClientHello],
+
+ CFExpanded = tls_v1:finished_key(CHSTrafficSecret, HKDFAlgo),
+ CFinishedVerifyData = tls_v1:finished_verify_data(CFExpanded, HKDFAlgo, MessageHistory1),
+
+ %% {client} construct a Finished handshake message:
+ %%
+ %% Finished (36 octets): 14 00 00 20 a8 ec 43 6d 67 76 34 ae 52 5a
+ %% c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce
+ %% 61
+ CFinishedBin =
+ hexstr2bin("14 00 00 20 a8 ec 43 6d 67 76 34 ae 52 5a
+ c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce
+ 61"),
+
+ CFinished = #finished{verify_data = CFinishedVerifyData},
+
+ CFinishedIOList = tls_handshake:encode_handshake(CFinished, {3,4}),
+ CFinishedBin = iolist_to_binary(CFinishedIOList),
+
+ %% {client} derive write traffic keys for application data:
+ %%
+ %% PRK (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce 65 52
+ %% 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5
+ %%
+ %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00
+ %%
+ %% key expanded (16 octets): 17 42 2d da 59 6e d5 d9 ac d8 90 e3 c6
+ %% 3f 50 51
+ %%
+ %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00
+ %%
+ %% iv expanded (12 octets): 5b 78 92 3d ee 08 57 90 33 e5 23 d9
+
+ %% PRK = CAPTrafficsecret
+ %% key info = WriteKeyInfo
+ %% iv info = WriteIVInfo
+
+ CWKey =
+ hexstr2bin("17 42 2d da 59 6e d5 d9 ac d8 90 e3 c6 3f 50 51"),
+
+ CWIV =
+ hexstr2bin("5b 78 92 3d ee 08 57 90 33 e5 23 d9"),
+
+ {CWKey, CWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CAPTrafficSecret),
+
+ %% {client} derive secret "tls13 res master":
+ %%
+ %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47
+ %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
+ %%
+ %% hash (32 octets): 20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26
+ %% 84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d
+ %%
+ %% info (52 octets): 00 20 10 74 6c 73 31 33 20 72 65 73 20 6d 61 73
+ %% 74 65 72 20 20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26 84
+ %% 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d
+ %%
+ %% expanded (32 octets): 7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41
+ %% b0 bf da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c
+
+ %% PRK = MasterSecret
+
+ CRMHash = hexstr2bin("20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26
+ 84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d"),
+
+ CRMInfo = hexstr2bin(" 00 20 10 74 6c 73 31 33 20 72 65 73 20 6d 61 73
+ 74 65 72 20 20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26 84
+ 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d"),
+
+ ResumptionMasterSecret = hexstr2bin("7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41
+ b0 bf da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c"),
+
+ CHCF = <<ClientHello/binary,
+ ServerHello/binary,
+ EncryptedExtensions/binary,
+ Certificate/binary,
+ CertificateVerify/binary,
+ FinishedHSBin/binary,
+ CFinishedBin/binary>>,
+
+ MessageHistory3 = [ClientHello,
+ ServerHello,
+ EncryptedExtensions,
+ Certificate,
+ CertificateVerify,
+ FinishedHSBin,
+ CFinishedBin
+ ],
+
+ CRMHash = crypto:hash(HKDFAlgo, CHCF),
+
+ CRMInfo =
+ tls_v1:create_info(<<"res master">>, CRMHash, ssl_cipher:hash_size(HKDFAlgo)),
+
+ ResumptionMasterSecret =
+ tls_v1:resumption_master_secret(HKDFAlgo, {master_secret, MasterSecret}, MessageHistory3),
+ ok.
+
+%%--------------------------------------------------------------------
+'0_RTT_handshake'() ->
+ [{doc,"Test TLS 1.3 0-RTT Handshake"}].
+
+'0_RTT_handshake'(_Config) ->
+ HKDFAlgo = sha256,
+
+ %% {server} generate resumption secret "tls13 resumption":
+ %%
+ %% PRK (32 octets): 7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf
+ %% da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c
+ %%
+ %% hash (2 octets): 00 00
+ %%
+ %% info (22 octets): 00 20 10 74 6c 73 31 33 20 72 65 73 75 6d 70 74
+ %% 69 6f 6e 02 00 00
+ %%
+ %% expanded (32 octets): 4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c
+ %% a4 c5 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3
+ %%
+ %% {server} construct a NewSessionTicket handshake message:
+ %%
+ %% NewSessionTicket (205 octets): 04 00 00 c9 00 00 00 1e fa d6 aa
+ %% c5 02 00 00 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00 00 00
+ %% 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70 ad 3c
+ %% 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9 82 11
+ %% 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6 1d 28
+ %% 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0 37 25
+ %% a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5 90 6c
+ %% 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5 ae a6
+ %% 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d e6 50
+ %% 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 00 08 00 2a 00
+ %% 04 00 00 04 00
+ ResPRK =
+ hexstr2bin("7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf
+ da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c"),
+
+ _ResHash = hexstr2bin("00 00"),
+
+ _ResInfo = hexstr2bin("00 20 10 74 6c 73 31 33 20 72 65 73 75 6d 70 74
+ 69 6f 6e 02 00 00"),
+
+ ResExpanded =
+ hexstr2bin("4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c
+ a4 c5 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3"),
+
+ NewSessionTicket =
+ hexstr2bin("04 00 00 c9 00 00 00 1e fa d6 aa
+ c5 02 00 00 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00 00 00
+ 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70 ad 3c
+ 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9 82 11
+ 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6 1d 28
+ 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0 37 25
+ a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5 90 6c
+ 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5 ae a6
+ 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d e6 50
+ 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 00 08 00 2a 00
+ 04 00 00 04 00"),
+ <<?BYTE(NWT), ?UINT24(_), TicketBody/binary>> = NewSessionTicket,
+ #new_session_ticket{
+ ticket_lifetime = _LifeTime,
+ ticket_age_add = _AgeAdd,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
+ extensions = _Extensions
+ } = tls_handshake:decode_handshake({3,4}, NWT, TicketBody),
+
+ %% ResPRK = resumption master secret
+ ResExpanded = tls_v1:pre_shared_key(ResPRK, Nonce, HKDFAlgo),
+
+ %% {client} create an ephemeral x25519 key pair:
+ %%
+ %% private key (32 octets): bf f9 11 88 28 38 46 dd 6a 21 34 ef 71
+ %% 80 ca 2b 0b 14 fb 10 dc e7 07 b5 09 8c 0d dd c8 13 b2 df
+ %%
+ %% public key (32 octets): e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98 34
+ %% 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b
+ %%
+ %% {client} extract secret "early":
+ %%
+ %% salt: 0 (all zero octets)
+ %%
+ %% IKM (32 octets): 4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c a4 c5
+ %% 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3
+ %%
+ %% secret (32 octets): 9b 21 88 e9 b2 fc 6d 64 d7 1d c3 29 90 0e 20
+ %% bb 41 91 50 00 f6 78 aa 83 9c bb 79 7c b7 d8 33 2c
+ %%
+
+ PSK = hexstr2bin("4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c a4 c5
+ 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3"),
+ PSK = ResExpanded,
+ EarlySecret = hexstr2bin("9b 21 88 e9 b2 fc 6d 64 d7 1d c3 29 90 0e 20
+ bb 41 91 50 00 f6 78 aa 83 9c bb 79 7c b7 d8 33 2c"),
+
+ {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, PSK}),
+
+ %% {client} construct a ClientHello handshake message:
+ %%
+ %% ClientHello (477 octets): 01 00 01 fc 03 03 1b c3 ce b6 bb e3 9c
+ %% ff 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 d7 b4 bc 41 9d 78
+ %% 76 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00 00 0b
+ %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00
+ %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33
+ %% 00 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98
+ %% 34 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b 00 2a
+ %% 00 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03
+ %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06
+ %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 57 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9
+ %% 00 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00
+ %% 70 ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3
+ %% a9 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f
+ %% d6 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e
+ %% e0 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f
+ %% a5 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97
+ %% b5 ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f
+ %% 7d e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 fa d6
+ %% aa cb
+ %%
+ ClientHello =
+ hexstr2bin("01 00 01 fc 03 03 1b c3 ce b6 bb e3 9c
+ ff 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 d7 b4 bc 41 9d 78
+ 76 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00 00 0b
+ 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00
+ 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33
+ 00 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98
+ 34 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b 00 2a
+ 00 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03
+ 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06
+ 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 57 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9
+ 00 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00
+ 70 ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3
+ a9 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f
+ d6 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e
+ e0 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f
+ a5 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97
+ b5 ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f
+ 7d e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 fa d6
+ aa cb"),
+
+ %% {client} calculate PSK binder:
+ %%
+ %% ClientHello prefix (477 octets): 01 00 01 fc 03 03 1b c3 ce b6 bb
+ %% e3 9c ff 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 d7 b4 bc 41
+ %% 9d 78 76 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00
+ %% 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00
+ %% 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04
+ %% 00 33 00 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d 96 c9 9d a2
+ %% 66 98 34 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b
+ %% 00 2a 00 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03
+ %% 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05
+ %% 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 57
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 ee 5f f7 af
+ %% 4e c9 00 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf
+ %% 1b 00 70 ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60
+ %% 97 a3 a9 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61
+ %% be 7f d6 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4
+ %% d2 9e e0 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2
+ %% 67 7f a5 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb
+ %% f2 97 b5 ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41
+ %% ef 5f 7d e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57
+ %% fa d6 aa cb
+ %%
+ %% binder hash (32 octets): 63 22 4b 2e 45 73 f2 d3 45 4c a8 4b 9d
+ %% 00 9a 04 f6 be 9e 05 71 1a 83 96 47 3a ef a0 1e 92 4a 14
+ %%
+ %% PRK (32 octets): 69 fe 13 1a 3b ba d5 d6 3c 64 ee bc c3 0e 39 5b
+ %% 9d 81 07 72 6a 13 d0 74 e3 89 db c8 a4 e4 72 56
+ %%
+ %% hash (0 octets): (empty)
+ %%
+ %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65
+ %% 64 00
+ %%
+ %% expanded (32 octets): 55 88 67 3e 72 cb 59 c8 7d 22 0c af fe 94
+ %% f2 de a9 a3 b1 60 9f 7d 50 e9 0a 48 22 7d b9 ed 7e aa
+ %%
+ %% finished (32 octets): 3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e
+ %% f5 e8 8d ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d
+ BinderHash =
+ hexstr2bin("63 22 4b 2e 45 73 f2 d3 45 4c a8 4b 9d
+ 00 9a 04 f6 be 9e 05 71 1a 83 96 47 3a ef a0 1e 92 4a 14"),
+
+ %% Part of derive_secret/4
+ BinderHash = crypto:hash(HKDFAlgo, [ClientHello]),
+
+ PRK = hexstr2bin("69 fe 13 1a 3b ba d5 d6 3c 64 ee bc c3 0e 39 5b
+ 9d 81 07 72 6a 13 d0 74 e3 89 db c8 a4 e4 72 56"),
+
+ PRK = tls_v1:resumption_binder_key(HKDFAlgo, {early_secret, EarlySecret}),
+
+ Expanded =
+ hexstr2bin("55 88 67 3e 72 cb 59 c8 7d 22 0c af fe 94
+ f2 de a9 a3 b1 60 9f 7d 50 e9 0a 48 22 7d b9 ed 7e aa"),
+
+ Expanded = tls_v1:finished_key(PRK, HKDFAlgo),
+
+ Finished = hexstr2bin("3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e
+ f5 e8 8d ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d"),
+
+ Finished = tls_v1:finished_verify_data(Expanded, HKDFAlgo, [ClientHello]),
+
+ %% {client} send handshake record:
+ %%
+ %% payload (512 octets): 01 00 01 fc 03 03 1b c3 ce b6 bb e3 9c ff
+ %% 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 d7 b4 bc 41 9d 78 76
+ %% 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00 00 0b 00
+ %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12
+ %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33 00
+ %% 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98 34
+ %% 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b 00 2a 00
+ %% 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02
+ %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02
+ %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 57 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00
+ %% 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70
+ %% ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9
+ %% 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6
+ %% 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0
+ %% 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5
+ %% 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5
+ %% ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d
+ %% e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 fa d6 aa
+ %% cb 00 21 20 3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e f5 e8 8d
+ %% ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d
+ %%
+ %% complete record (517 octets): 16 03 01 02 00 01 00 01 fc 03 03 1b
+ %% c3 ce b6 bb e3 9c ff 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49
+ %% d7 b4 bc 41 9d 78 76 48 7d 95 00 00 06 13 01 13 03 13 02 01 00
+ %% 01 cd 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01
+ %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02
+ %% 01 03 01 04 00 33 00 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d
+ %% 96 c9 9d a2 66 98 34 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1
+ %% 8d 66 8f 0b 00 2a 00 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e
+ %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02
+ %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01
+ %% 00 15 00 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ %% 00 00 00 00 00 00 00 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59
+ %% ee 5f f7 af 4e c9 00 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb
+ %% 33 fa 90 bf 1b 00 70 ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc
+ %% 55 cd 22 60 97 a3 a9 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3
+ %% 6d 64 e8 61 be 7f d6 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66
+ %% 4d 4e 6d a4 d2 9e e0 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29
+ %% 51 3e 3d a2 67 7f a5 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72
+ %% 14 70 f9 fb f2 97 b5 ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6
+ %% 21 a7 91 41 ef 5f 7d e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93
+ %% 4a e4 d3 57 fa d6 aa cb 00 21 20 3a dd 4f b2 d8 fd f8 22 a0 ca
+ %% 3c f7 67 8e f5 e8 8d ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f
+ %% 9d
+ ClientHelloRecord =
+ hexstr2bin("01 00 01 fc 03 03 1b c3 ce b6 bb e3 9c ff
+ 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 d7 b4 bc 41 9d 78 76
+ 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00 00 0b 00
+ 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12
+ 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33 00
+ 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98 34
+ 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b 00 2a 00
+ 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02
+ 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02
+ 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 57 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00
+ 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70
+ ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9
+ 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6
+ 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0
+ 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5
+ 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5
+ ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d
+ e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 fa d6 aa
+ cb 00 21 20 3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e f5 e8 8d
+ ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d"),
+
+ <<?BYTE(CH), ?UINT24(_Length), ClientHelloBody/binary>> = ClientHelloRecord,
+ #client_hello{extensions = #{pre_shared_key := PreSharedKey}} =
+ tls_handshake:decode_handshake({3,4}, CH, ClientHelloBody),
+
+ #pre_shared_key_client_hello{
+ offered_psks = #offered_psks{
+ identities = [Identity],
+ binders = [_Binders]}} = PreSharedKey,
+
+ #psk_identity{
+ identity = Ticket} = Identity,
+
+ ok.
+
%%--------------------------------------------------------------------
finished_verify_data() ->
diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl
index f0b224d4e5..e0ac53e0f9 100644
--- a/lib/ssl/test/tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_version_SUITE.erl
@@ -50,8 +50,14 @@ cert_groups() ->
tests() ->
[tls13_client_tls12_server,
tls13_client_with_ext_tls12_server,
- tls12_client_tls13_server].
-
+ tls12_client_tls13_server,
+ tls_client_tls10_server,
+ tls_client_tls11_server,
+ tls_client_tls12_server,
+ tls10_client_tls_server,
+ tls11_client_tls_server,
+ tls12_client_tls_server].
+
init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
@@ -150,4 +156,63 @@ tls12_client_tls13_server(Config) when is_list(Config) ->
ServerOpts = [{versions,
['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
-
+
+tls_client_tls10_server() ->
+ [{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.0 server."}].
+tls_client_tls10_server(Config) when is_list(Config) ->
+ ClientOpts = [{versions,
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ServerOpts = [{versions,
+ ['tlsv1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+tls_client_tls11_server() ->
+ [{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.1 server."}].
+tls_client_tls11_server(Config) when is_list(Config) ->
+ ClientOpts = [{versions,
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ServerOpts = [{versions,
+ ['tlsv1.1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+tls_client_tls12_server() ->
+ [{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.2 server."}].
+tls_client_tls12_server(Config) when is_list(Config) ->
+ ClientOpts = [{versions,
+ ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ServerOpts = [{versions,
+ ['tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+tls10_client_tls_server() ->
+ [{doc,"Test that a TLS 1.0 client can connect to a TLS 1.0-1.3 server."}].
+tls10_client_tls_server(Config) when is_list(Config) ->
+ ClientOpts = [{versions,
+ ['tlsv1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ServerOpts = [{versions,
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+tls11_client_tls_server() ->
+ [{doc,"Test that a TLS 1.1 client can connect to a TLS 1.0-1.3 server."}].
+tls11_client_tls_server(Config) when is_list(Config) ->
+ ClientOpts = [{versions,
+ ['tlsv1.1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ServerOpts = [{versions,
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
+
+tls12_client_tls_server() ->
+ [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.0-1.3 server."}].
+tls12_client_tls_server(Config) when is_list(Config) ->
+ ClientOpts = [{versions,
+ ['tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)],
+ ServerOpts = [{versions,
+ ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} |
+ ssl_test_lib:ssl_options(server_cert_opts, Config)],
+ ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).