summaryrefslogtreecommitdiff
path: root/lib/ssl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssl')
-rw-r--r--lib/ssl/Makefile4
-rw-r--r--lib/ssl/doc/src/notes.xml24
-rw-r--r--lib/ssl/doc/src/ssl.xml142
-rw-r--r--lib/ssl/doc/src/standards_compliance.xml52
-rw-r--r--lib/ssl/doc/src/using_ssl.xml2
-rw-r--r--lib/ssl/src/.gitignore1
-rw-r--r--lib/ssl/src/Makefile7
-rw-r--r--lib/ssl/src/dtls_connection.erl83
-rw-r--r--lib/ssl/src/dtls_gen_connection.erl13
-rw-r--r--lib/ssl/src/dtls_handshake.erl42
-rw-r--r--lib/ssl/src/dtls_handshake.hrl10
-rw-r--r--lib/ssl/src/dtls_record.erl120
-rw-r--r--lib/ssl/src/dtls_v1.erl48
-rw-r--r--lib/ssl/src/inet6_tls_dist.erl20
-rw-r--r--lib/ssl/src/inet_tls_dist.erl825
-rw-r--r--lib/ssl/src/ssl.app.src9
-rw-r--r--lib/ssl/src/ssl.erl2377
-rw-r--r--lib/ssl/src/ssl_certificate.erl72
-rw-r--r--lib/ssl/src/ssl_cipher.erl315
-rw-r--r--lib/ssl/src/ssl_cipher.hrl3
-rw-r--r--lib/ssl/src/ssl_config.erl220
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl311
-rw-r--r--lib/ssl/src/ssl_handshake.erl641
-rw-r--r--lib/ssl/src/ssl_handshake.hrl22
-rw-r--r--lib/ssl/src/ssl_internal.hrl118
-rw-r--r--lib/ssl/src/ssl_logger.erl17
-rw-r--r--lib/ssl/src/ssl_record.erl11
-rw-r--r--lib/ssl/src/ssl_record.hrl34
-rw-r--r--lib/ssl/src/ssl_session.erl26
-rw-r--r--lib/ssl/src/ssl_trace.erl512
-rw-r--r--lib/ssl/src/tls_client_connection_1_3.erl1007
-rw-r--r--lib/ssl/src/tls_connection.erl57
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl740
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl81
-rw-r--r--lib/ssl/src/tls_dyn_connection_sup.erl9
-rw-r--r--lib/ssl/src/tls_gen_connection.erl30
-rw-r--r--lib/ssl/src/tls_gen_connection_1_3.erl382
-rw-r--r--lib/ssl/src/tls_handshake.erl74
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl1852
-rw-r--r--lib/ssl/src/tls_record.erl219
-rw-r--r--lib/ssl/src/tls_record.hrl4
-rw-r--r--lib/ssl/src/tls_record_1_3.erl19
-rw-r--r--lib/ssl/src/tls_record_1_3.hrl6
-rw-r--r--lib/ssl/src/tls_sender.erl80
-rw-r--r--lib/ssl/src/tls_server_connection_1_3.erl914
-rw-r--r--lib/ssl/src/tls_server_session_ticket.erl241
-rw-r--r--lib/ssl/src/tls_socket.erl51
-rw-r--r--lib/ssl/src/tls_v1.erl389
-rw-r--r--lib/ssl/test/Makefile11
-rw-r--r--lib/ssl/test/cryptcookie.erl747
-rw-r--r--lib/ssl/test/dist_cryptcookie.erl595
-rw-r--r--lib/ssl/test/dtls_api_SUITE.erl6
-rw-r--r--lib/ssl/test/inet_crypto_dist.erl1746
-rw-r--r--lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl230
-rw-r--r--lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl276
-rw-r--r--lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl200
-rw-r--r--lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl241
-rw-r--r--lib/ssl/test/openssl_alpn_SUITE.erl6
-rw-r--r--lib/ssl/test/openssl_cipher_suite_SUITE.erl22
-rw-r--r--lib/ssl/test/openssl_client_cert_SUITE.erl130
-rw-r--r--lib/ssl/test/openssl_mfl_SUITE.erl10
-rw-r--r--lib/ssl/test/openssl_npn_SUITE.erl22
-rw-r--r--lib/ssl/test/openssl_ocsp_SUITE.erl118
-rw-r--r--lib/ssl/test/openssl_renegotiate_SUITE.erl2
-rw-r--r--lib/ssl/test/openssl_server_cert_SUITE.erl82
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_chain.erl211
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl87
-rw-r--r--lib/ssl/test/ssl_ECC_SUITE.erl35
-rw-r--r--lib/ssl/test/ssl_api_SUITE.erl1504
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl345
-rw-r--r--lib/ssl/test/ssl_bench_test_lib.erl37
-rw-r--r--lib/ssl/test/ssl_cert_SUITE.erl183
-rw-r--r--lib/ssl/test/ssl_cert_tests.erl37
-rw-r--r--lib/ssl/test/ssl_cipher_SUITE.erl43
-rw-r--r--lib/ssl/test/ssl_cipher_suite_SUITE.erl98
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl426
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl876
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl27
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl85
-rw-r--r--lib/ssl/test/ssl_key_update_SUITE.erl2
-rw-r--r--lib/ssl/test/ssl_mfl_SUITE.erl10
-rw-r--r--lib/ssl/test/ssl_npn_SUITE.erl12
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl9
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl30
-rw-r--r--lib/ssl/test/ssl_reject_SUITE.erl25
-rw-r--r--lib/ssl/test/ssl_renegotiate_SUITE.erl27
-rw-r--r--lib/ssl/test/ssl_session_SUITE.erl6
-rw-r--r--lib/ssl/test/ssl_session_ticket_SUITE.erl172
-rw-r--r--lib/ssl/test/ssl_sni_SUITE.erl64
-rw-r--r--lib/ssl/test/ssl_test_lib.erl738
-rw-r--r--lib/ssl/test/ssl_test_lib.hrl22
-rw-r--r--lib/ssl/test/ssl_trace_SUITE.erl493
-rw-r--r--lib/ssl/test/ssl_use_srtp_SUITE.erl176
-rw-r--r--lib/ssl/test/tls_1_3_record_SUITE.erl18
-rw-r--r--lib/ssl/test/tls_1_3_version_SUITE.erl29
-rw-r--r--lib/ssl/test/tls_api_SUITE.erl23
-rw-r--r--lib/ssl/test/tls_server_session_ticket_SUITE.erl99
-rw-r--r--lib/ssl/vsn.mk2
98 files changed, 13699 insertions, 8932 deletions
diff --git a/lib/ssl/Makefile b/lib/ssl/Makefile
index 5cc111ec14..40f97eff5c 100644
--- a/lib/ssl/Makefile
+++ b/lib/ssl/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2021. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. 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.
@@ -40,4 +40,6 @@ include $(ERL_TOP)/make/otp_subdir.mk
DIA_PLT_APPS=crypto runtime_tools inets public_key
+TEST_NEEDS_RELEASE=true
+
include $(ERL_TOP)/make/app_targets.mk
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 6ff15d429a..911055d742 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,30 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 10.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, ssl:connection_information/2 returns
+ correct keylog data after TLS1.3 key update.</p>
+ <p>
+ Own Id: OTP-18489</p>
+ </item>
+ <item>
+ <p>
+ Client signature algorithm list input order is now
+ honored again , it was accidently reversed by a previous
+ fix.</p>
+ <p>
+ Own Id: OTP-18550</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 10.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 803c0f789a..3574ae91ac 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1999</year><year>2022</year>
+ <year>1999</year><year>2023</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -229,6 +229,10 @@
</datatype>
<datatype>
+ <name name="legacy_named_curve"/>
+ </datatype>
+
+ <datatype>
<name name="psk_identity"/>
</datatype>
@@ -475,14 +479,10 @@
{sha256, ecdsa},
{sha256, rsa},
{sha224, ecdsa},
-{sha224, rsa},
-%% SHA
-{sha, ecdsa},
-{sha, rsa},
-{sha, dsa}
+{sha224, rsa}
]</code>
-<p>Support for {md5, rsa} was removed from the the TLS-1.2 default in ssl-8.0 (OTP-22) </p>
+<p>Support for {md5, rsa} was removed from the the TLS-1.2 default in ssl-8.0 (OTP-22) and support for SHA1 {sha, _} was removed in ssl-11.0 (OTP-26) </p>
<p><c> rsa_pss_schemes =</c></p>
<code>
@@ -500,8 +500,6 @@ rsa_pss_rsae_sha256]
rsa_pkcs1_sha512, %% Corresponds to {sha512, rsa}
rsa_pkcs1_sha384, %% Corresponds to {sha384, rsa}
rsa_pkcs1_sha256, %% Corresponds to {sha256, rsa}
-ecdsa_sha1, %% Corresponds to {sha, ecdsa}
-rsa_pkcs1_sha1 %% Corresponds to {sha, rsa}
]
</code>
@@ -520,7 +518,7 @@ ecdsa_secp256r1_sha256] ++
rsa_pss_schemes()
</code>
- <p>EDDSA was made highest priority in ssl-11.0 (OTP-25) </p>
+ <p>EDDSA was made highest priority in ssl-10.8 (OTP-25) </p>
<p>TLS-1.3 default is</p>
<code>Default_TLS_13_Schemes ++ Legacy_TLS_13_Schemes </code>
@@ -1160,7 +1158,10 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<name name="customize_hostname_check"/>
<desc>
<p> Customizes the hostname verification of the peer certificate, as different protocols that use
- TLS such as HTTP or LDAP may want to do it differently, for possible options see
+ TLS such as HTTP or LDAP may want to do it differently. For example the get standard HTTPS handling
+ provide the already implememnted fun from the public_key application for HTTPS.
+ <c>{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}</c>
+ For futher description of customize options see
<seemfa marker="public_key:public_key#pkix_verify_hostname/3">public_key:pkix_verify_hostname/3</seemfa> </p>
</desc>
</datatype>
@@ -1231,6 +1232,34 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</desc>
</datatype>
+ <datatype>
+ <name name="use_srtp"/>
+ <desc>
+ <p>Configures the <c>use_srtp</c> DTLS hello extension.</p>
+ <p>In order to negotiate the use of SRTP data protection, clients
+ include an extension of type "use_srtp" in the DTLS extended client
+ hello. This extension MUST only be used when the data being
+ transported is RTP or RTCP.</p>
+ <p>The value is a map with a mandatory <c>protection_profiles</c> and
+ an optional <c>mki</c> parameters.</p>
+ <p><c>protection_profiles</c> configures the list of the client's acceptable
+ SRTP Protection Profiles. Each profile is a 2-byte binary. Example:
+ <c>#{protection_profiles =&gt; [&lt;&lt;0,2&gt;&gt;, &lt;&lt;0,5&gt;&gt;]}</c></p>
+ <p><c>mki</c> configures the SRTP Master Key Identifier chosen by the client.</p>
+ <p>The srtp_mki field contains the value of the SRTP MKI which is associated
+ with the SRTP master keys derived from this handshake. Each SRTP
+ session MUST have exactly one master key that is used to protect
+ packets at any given time. The client MUST choose the MKI value so
+ that it is distinct from the last MKI value that was used, and it
+ SHOULD make these values unique for the duration of the TLS session.</p>
+ <note><p>This extension MUST only be used with DTLS, and not with TLS.</p></note>
+ <note><p>OTP does not handle SRTP, so an external implementations of SRTP
+ encoder/decoder and a packet demultiplexer are needed to make use
+ of the <c>use_srtp</c> extension. See also
+ <seetype marker="#transport_option">cb_info</seetype> option.</p></note>
+ </desc>
+ </datatype>
+
<!-- <datatype> -->
<!-- <name name="ocsp_stapling"/> -->
<!-- <desc><p>If true, OCSP stapling will be enabled, an extension of type "status_request" will be -->
@@ -1472,15 +1501,34 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<datatype>
<name name="server_session_tickets"/>
<desc>
- <p>Configures the session ticket functionality. Allowed values are <c>disabled</c>,
- <c>stateful</c> and <c>stateless</c>.</p>
- <p>If it is set to <c>stateful</c> or
- <c>stateless</c>, session resumption with pre-shared keys is enabled and the server will
- send stateful or stateless session tickets to the client after successful connections.</p>
+ <p>Configures the session ticket functionality. Allowed values are <c>disabled</c>,
+ <c>stateful</c>, <c>stateless</c>, <c>stateful_with_cert</c>, <c>stateless_with_cert</c>.</p>
+ <p>If it is not set to <c>disabled</c>,
+ session resumption with pre-shared keys is enabled and the server will
+ send stateful or stateless session tickets to the client after successful connections.</p>
+
+ <note><p>
+ Pre-shared key session ticket resumption does not include any certificate exchange,
+ hence the function <seemfa marker="ssl:ssl#peercert/1">ssl:peercert/1</seemfa> will not
+ be able to return the peer certificate as it is only communicated in the initial handshake.
+ The server options <c>stateful_with_cert</c> or <c>stateless_with_cert</c> may be used
+ to make a server associate the client certificate from the original handshake
+ with the tickets it issues.
+ </p></note>
+
<p>A stateful session ticket is a database reference to internal state information.
A stateless session ticket is a self-encrypted binary that contains both cryptographic keying
material and state data.
</p>
+
+ <warning><p>
+ If it is set to <c>stateful_with_cert</c> the client certificate
+ is stored with the internal state information, increasing memory consumption.
+ If it is set to <c>stateless_with_cert</c> the client certificate is
+ encoded in the self-encrypted binary that is sent to the client,
+ increasing the payload size.
+ </p></warning>
+
<note><p>This option is supported by TLS 1.3 and above. See also
<seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">
SSL's Users Guide, Session Tickets and Session Resumption in TLS 1.3</seeguide>
@@ -1489,6 +1537,26 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</datatype>
<datatype>
+ <name name="stateless_tickets_seed"/>
+ <desc>
+ <p>Configures the seed used for the encryption of stateless session tickets.
+ Allowed values are any randomly generated <c>binary()</c>. If this option is not
+ configured, an encryption seed will be randomly generated.</p>
+
+ <warning><p>Reusing the ticket encryption seed between multiple server
+ instances enables stateless session tickets to work across multiple server
+ instances, but it breaks anti-replay protection across instances.</p>
+
+ <p>Inaccurate time synchronization between server instances can also
+ affect session ticket freshness checks, potentially causing false negatives as
+ well as false positives.</p></warning>
+
+ <note><p>This option is supported by TLS 1.3 and above and only with stateless
+ session tickets.</p></note>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="anti_replay"/>
<desc>
<p>Configures the server's built-in anti replay feature based on Bloom filters.</p>
@@ -1496,7 +1564,7 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<p>Allowed values are the pre-defined <c>'10k'</c>, <c>'100k'</c> or a custom 3-tuple that
defines the properties of the bloom filters: <c>{WindowSize, HashFunctions, Bits}</c>.
<c>WindowSize</c> is the number of seconds after the current Bloom filter is rotated
- and also the window size used for freshness checks. <c>HashFunctions</c> is the number
+ and also the window size used for freshness checks of ClientHello. <c>HashFunctions</c> is the number
hash functions and <c>Bits</c> is the number of bits in the bit vector.
<c>'10k'</c> and <c>'100k'</c> are simple defaults with the following properties:</p>
<list type="bulleted">
@@ -1544,6 +1612,40 @@ fun(srp, Username :: binary(), UserState :: term()) ->
</datatype>
<datatype>
+ <name name="use_srtp"/>
+ <desc>
+ <p>Configures the <c>use_srtp</c> DTLS hello extension.</p>
+ <p>Servers that receive an extended hello containing a "use_srtp"
+ extension can agree to use SRTP by including an extension of type
+ "use_srtp", with the chosen protection profile in the extended server
+ hello. This extension MUST only be used when the data being
+ transported is RTP or RTCP.</p>
+ <p>The value is a map with a mandatory <c>protection_profiles</c> and
+ an optional <c>mki</c> parameters.</p>
+ <list type="bulleted">
+ <item><p><c>protection_profiles</c> configures the list of the server's chosen
+ SRTP Protection Profile as a list of a single 2-byte binary. Example:
+ <c>#{protection_profiles =&gt; [&lt;&lt;0,5&gt;&gt;]}</c></p></item>
+ <item><p><c>mki</c> configures the server's SRTP Master Key Identifier.</p>
+ <p>Upon receipt of a "use_srtp" extension containing a "srtp_mki" field,
+ the server MUST either (assuming it accepts the extension at all):</p>
+ <list type="bulleted">
+ <item><p>include a matching "srtp_mki" value in its "use_srtp" extension
+ to indicate that it will make use of the MKI, or</p></item>
+ <item><p>return an empty "srtp_mki" value to indicate that it cannot
+ make use of the MKI (default).</p></item>
+ </list>
+ </item>
+ </list>
+ <note><p>This extension MUST only be used with DTLS, and not with TLS.</p></note>
+ <note><p>OTP does not handle SRTP, so an external implementations of SRTP
+ encoder/decoder and a packet demultiplexer are needed to make use
+ of the <c>use_srtp</c> extension. See also
+ <seetype marker="#transport_option">cb_info</seetype> option.</p></note>
+ </desc>
+ </datatype>
+
+ <datatype>
<name name="connection_info"/>
</datatype>
@@ -2019,6 +2121,12 @@ fun(srp, Username :: binary(), UserState :: term()) ->
<c>{error, renegotiation_rejected}</c> indicating that the peer
refused to go through with the renegotiation, but the connection
is still active using the previously negotiated session.</p>
+ <p>TLS-1.3 has removed the renegotiate feature of earlier TLS versions
+ and instead adds a new feature called key update that replaces the most
+ important part of renegotiate, that is the refreshing of session keys.
+ This is triggered automatically after reaching a plaintext limit and
+ can be configured by option <seetype marker="ssl:ssl#key_update_at">key_update_at</seetype>.
+ </p>
</desc>
</func>
diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml
index 0311174978..725c219f13 100644
--- a/lib/ssl/doc/src/standards_compliance.xml
+++ b/lib/ssl/doc/src/standards_compliance.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2015</year>
- <year>2022</year>
+ <year>2023</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -327,8 +327,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">use_srtp (RFC5764)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -466,8 +466,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">use_srtp (RFC5764)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -594,6 +594,12 @@
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>22.1</em></cell>
</row>
+ <row>
+ <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">use_srtp (RFC5764)</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
+ </row>
<row>
<cell align="left" valign="middle"></cell>
@@ -625,6 +631,12 @@
<cell align="left" valign="middle"><em>C</em></cell>
<cell align="left" valign="middle"><em>22</em></cell>
</row>
+ <row>
+ <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle">use_srtp (RFC5764)</cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">26.0</cell>
+ </row>
<row>
<cell align="left" valign="middle">
@@ -910,14 +922,14 @@
</url>
</cell>
<cell align="left" valign="middle"><em>Client</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">24.3</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"><em></em></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle"><em>24.3</em></cell>
</row>
<row>
@@ -1254,8 +1266,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">application_layer_protocol_negotiation (RFC7301)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">23.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1277,13 +1289,6 @@
</row>
<row>
<cell align="left" valign="middle"></cell>
- <cell align="left" valign="middle">supported_versions (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
- </row>
-
- <row>
- <cell align="left" valign="middle"></cell>
<cell align="left" valign="middle"><em>Server</em></cell>
<cell align="left" valign="middle"><em>PC</em></cell>
<cell align="left" valign="middle"><em>22</em></cell>
@@ -1321,8 +1326,8 @@
<row>
<cell align="left" valign="middle"></cell>
<cell align="left" valign="middle">application_layer_protocol_negotiation (RFC7301)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
+ <cell align="left" valign="middle"><em>C</em></cell>
+ <cell align="left" valign="middle">23.0</cell>
</row>
<row>
<cell align="left" valign="middle"></cell>
@@ -1343,13 +1348,6 @@
<cell align="left" valign="middle"><em>23.3</em></cell>
</row>
<row>
- <cell align="left" valign="middle"></cell>
- <cell align="left" valign="middle">supported_versions (RFC8446)</cell>
- <cell align="left" valign="middle"><em>NC</em></cell>
- <cell align="left" valign="middle"></cell>
- </row>
-
- <row>
<cell align="left" valign="middle">
<url href="https://tools.ietf.org/html/rfc8446#section-4.3.2">
4.3.2. Certificate Request
diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml
index 318e367245..148c7d8dfc 100644
--- a/lib/ssl/doc/src/using_ssl.xml
+++ b/lib/ssl/doc/src/using_ssl.xml
@@ -776,7 +776,7 @@ ssl:connect("localhost", 9999, [{verify, verify_peer},
less than ticket lifetime.</p></item>
<item><p>Actual ticket age shall be less than the ticket lifetime (stateless session
tickets contain the servers timestamp when the ticket was issued).</p></item>
- <item><p>Ticket shall be used within specified time window (freshness checks).</p></item>
+ <item><p>ClientHello created with the ticket shall be sent relatively recently (freshness checks).</p></item>
<item><p>If all above checks passed both <em>current</em> and <em>old</em> Bloom filters
are checked to detect if binder was already seen. Being a probabilistic data structure,
false positives can occur and they trigger a full handshake.</p></item>
diff --git a/lib/ssl/src/.gitignore b/lib/ssl/src/.gitignore
new file mode 100644
index 0000000000..3f9cfafd33
--- /dev/null
+++ b/lib/ssl/src/.gitignore
@@ -0,0 +1 @@
+deps
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index 789bed5c3f..6395834fe9 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -39,6 +39,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN)
# ----------------------------------------------------
BEHAVIOUR_MODULES= \
+ ssl_trace \
ssl_crl_cache_api \
ssl_session_cache_api
@@ -94,8 +95,10 @@ MODULES= \
tls_dtls_connection \
tls_connection \
tls_connection_sup \
- tls_connection_1_3 \
- tls_gen_connection \
+ tls_server_connection_1_3 \
+ tls_client_connection_1_3 \
+ tls_gen_connection_1_3 \
+ tls_gen_connection \
tls_handshake \
tls_handshake_1_3 \
tls_record \
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 08229d8bb5..899e7d3305 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -88,7 +88,7 @@
%% | Abbrev Flight 1 to Abbrev Flight 2 part 1
%% |
%% New session | Resumed session
-%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED
%%
%% <- Possibly Receive -- | |
%% OCSP Stapel ------> | Send/ Recv Flight 5 |
@@ -155,17 +155,20 @@
code_change/4,
format_status/2]).
+%% Tracing
+-export([handle_trace/3]).
+
%%====================================================================
%% Internal application API
-%%====================================================================
+%%====================================================================
%%====================================================================
%% Setup
-%%====================================================================
+%%====================================================================
init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
process_flag(trap_exit, true),
State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
try
- State = ssl_gen_statem:ssl_config(State0#state.ssl_options,
+ State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options,
Role, State0),
gen_statem:enter_loop(?MODULE, [], initial_hello, State)
catch
@@ -175,8 +178,8 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
gen_statem:enter_loop(?MODULE, [], config_error, EState)
end.
%%====================================================================
-%% Handshake
-%%====================================================================
+%% Handshake
+%%====================================================================
renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
@@ -191,7 +194,7 @@ renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -
dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions).
%%--------------------------------------------------------------------
-%% State functions
+%% State functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
-spec initial_hello(gen_statem:event_type(),
@@ -199,7 +202,7 @@ renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -
gen_statem:state_function_result().
%%--------------------------------------------------------------------
initial_hello(enter, _, State) ->
- {keep_state, State};
+ {keep_state, State};
initial_hello({call, From}, {start, Timeout},
#state{static_env = #static_env{host = Host,
port = Port,
@@ -297,34 +300,32 @@ hello(internal, #client_hello{cookie = <<>>,
catch throw:#alert{} = Alert ->
alert_or_reset_connection(Alert, ?FUNCTION_NAME, State0)
end;
-hello(internal, #hello_verify_request{cookie = Cookie},
+hello(internal, #hello_verify_request{cookie = Cookie},
#state{static_env = #static_env{role = client,
host = Host,
port = Port},
handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
ocsp_stapling_state = OcspState0} = HsEnv,
connection_env = CEnv,
- ssl_options = #{ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt} = SslOpts,
+ ssl_options = SslOpts,
session = #session{session_id = Id},
connection_states = ConnectionStates0,
protocol_specific = PS
} = State0) ->
- OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ OcspNonce = tls_handshake:ocsp_nonce(SslOpts),
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
SslOpts, Id, Renegotiation, OcspNonce),
Version = Hello#client_hello.client_version,
- State1 = prepare_flight(State0#state{handshake_env =
- HsEnv#handshake_env{tls_handshake_history
- = ssl_handshake:init_handshake_history(),
- ocsp_stapling_state =
- OcspState0#{ocsp_nonce => OcspNonce}}}),
-
- {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
-
- State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, % RequestedVersion
- protocol_specific = PS#{current_cookie_secret => Cookie}
- },
+ State1 =
+ prepare_flight(
+ State0#state{handshake_env =
+ HsEnv#handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}}),
+ {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1),
+ State = State2#state{connection_env =
+ CEnv#connection_env{negotiated_version = Version}, % RequestedVersion
+ protocol_specific = PS#{current_cookie_secret => Cookie}},
dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions);
hello(internal, #client_hello{extensions = Extensions} = Hello,
#state{handshake_env = #handshake_env{continue_status = pause},
@@ -372,11 +373,11 @@ hello(internal, #server_hello{} = Hello,
try
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} =
dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId),
- tls_dtls_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol,
- State#state{handshake_env =
- HsEnv#handshake_env{
- ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
+ tls_dtls_connection:handle_session(
+ Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol,
+ State#state{handshake_env =
+ HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}})
catch throw:#alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
end;
@@ -478,10 +479,7 @@ wait_cert_verify(info, Event, State) ->
wait_cert_verify(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
wait_cert_verify(Type, Event, State) ->
- try tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State)
- catch throw:#alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State)
- end.
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec cipher(gen_statem:event_type(), term(), #state{}) ->
@@ -505,7 +503,7 @@ cipher(internal = Type, #finished{} = Event, #state{connection_states = Connecti
cipher(state_timeout, Event, State) ->
handle_state_timeout(Event, ?FUNCTION_NAME, State);
cipher(Type, Event, State) ->
- gen_handshake(?FUNCTION_NAME, Type, Event, State).
+ gen_handshake(?FUNCTION_NAME, Type, Event, State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
@@ -654,7 +652,7 @@ format_status(Type, Data) ->
%%% Internal functions
%%--------------------------------------------------------------------
initial_state(Role, Host, Port, Socket,
- {#{client_renegotiation := ClientRenegotiation} = SSLOptions, SocketOptions, Trackers}, User,
+ {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
put(log_level, maps:get(log_level, SSLOptions)),
BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
@@ -681,13 +679,11 @@ initial_state(Role, Host, Port, Socket,
handshake_env = #handshake_env{
tls_handshake_history = ssl_handshake:init_handshake_history(),
renegotiation = {false, first},
- allow_renegotiate = ClientRenegotiation
+ allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined)
},
connection_env = #connection_env{user_application = {Monitor, User}},
socket_options = SocketOptions,
- %% We do not want to save the password in the state so that
- %% could be written in the clear into error logs.
- ssl_options = SSLOptions#{password => undefined},
+ ssl_options = SSLOptions,
session = #session{is_resumable = false},
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
@@ -762,6 +758,8 @@ alert_or_reset_connection(Alert, StateName, #state{connection_states = Cs} = Sta
{next_state, connection, NewState}
end.
+gen_handshake(_, {call, _From}, {application_data, _Data}, _State) ->
+ {keep_state_and_data, [postpone]};
gen_handshake(StateName, Type, Event, State) ->
try tls_dtls_connection:StateName(Type, Event, State)
catch
@@ -877,3 +875,12 @@ is_time_to_renegotiate(N, M) when N < M->
is_time_to_renegotiate(_,_) ->
true.
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(hbn,
+ {call, {?MODULE, connection,
+ [_Type = info, Event, _State]}},
+ Stack) ->
+ {io_lib:format("Type = info Event = ~W ", [Event, 10]), Stack}.
diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl
index c075fa5879..446a065ac3 100644
--- a/lib/ssl/src/dtls_gen_connection.erl
+++ b/lib/ssl/src/dtls_gen_connection.erl
@@ -82,10 +82,10 @@
%%====================================================================
%% Setup
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts,
+start_fsm(Role, Host, Port, Socket, {_,_, Tracker} = Opts,
User, {CbModule, _, _, _, _} = CbInfo,
- Timeout) ->
- try
+ Timeout) ->
+ try
{ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
{ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker),
@@ -545,10 +545,9 @@ handle_info({CloseTag, Socket}, StateName,
%% with widespread implementation practice.
case (Active == false) andalso (CTs =/= []) of
false ->
- case Version of
- {254, N} when N =< 253 ->
+ if (?DTLS_GTE(Version, ?DTLS_1_2)) ->
ok;
- _ ->
+ true ->
%% As invalidate_sessions here causes performance issues,
%% we will conform to the widespread implementation
%% practice and go against the spec
@@ -634,7 +633,7 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{
ssl_options = SslOpts} = State0) ->
case dtls_record:get_dtls_records(Data,
{DataTag, StateName, Version,
- [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
+ [dtls_record:protocol_version_name(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]},
Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 10258dcc50..85faad11b6 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -118,11 +118,12 @@ hello(#client_hello{client_version = ClientVersion} = Hello,
Version = ssl_handshake:select_version(dtls_record, ClientVersion, Versions),
handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation).
-cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor},
+cookie(Key, Address, Port, #client_hello{client_version = Version,
random = Random,
session_id = SessionId,
cipher_suites = CipherSuites,
compression_methods = CompressionMethods}) ->
+ {Major, Minor} = Version,
CookieData = [address_to_bin(Address, Port),
<<?BYTE(Major), ?BYTE(Minor)>>,
Random, SessionId, CipherSuites, CompressionMethods],
@@ -189,7 +190,7 @@ handle_client_hello(Version,
TLSVersion = dtls_v1:corresponding_tls_version(Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, TLSVersion),
- ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, TLSVersion, ECCOrder),
+ ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite,
own_certificates = [OwnCert |_]} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
@@ -219,32 +220,35 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) ->
{Session, ConnectionStates, Protocol, ServerHelloExt} =
ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites,
- HelloExt, dtls_v1:corresponding_tls_version(Version),
- SslOpts, Session0,
+ HelloExt,
+ dtls_v1:corresponding_tls_version(Version),
+ SslOpts, Session0,
ConnectionStates0, Renegotiation,
Session0#session.is_resumable),
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}.
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
- Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) ->
+ Compression, HelloExt, SslOpt, ConnectionStates0,
+ Renegotiation, IsNew) ->
{ConnectionStates, ProtoExt, Protocol, OcspState} =
- ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt,
- dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation, IsNew),
+ ssl_handshake:handle_server_hello_extensions(
+ dtls_record, Random, CipherSuite, Compression, HelloExt,
+ dtls_v1:corresponding_tls_version(Version), SslOpt, ConnectionStates0,
+ Renegotiation, IsNew),
{Version, SessionId, ConnectionStates, ProtoExt, Protocol, OcspState}.
%%--------------------------------------------------------------------
-enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
+enc_handshake(#hello_verify_request{protocol_version = Version,
cookie = Cookie}, _Version) ->
CookieLength = byte_size(Cookie),
+ {Major,Minor} = Version,
{?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor),
?BYTE(CookieLength),
Cookie:CookieLength/binary>>};
enc_handshake(#hello_request{}, _Version) ->
{?HELLO_REQUEST, <<>>};
-enc_handshake(#client_hello{client_version = {Major, Minor},
+enc_handshake(#client_hello{client_version = ClientVersion,
random = Random,
session_id = SessionID,
cookie = Cookie,
@@ -257,8 +261,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor},
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions,
- dtls_v1:corresponding_tls_version({Major, Minor})),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ {Major,Minor} = ClientVersion,
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
@@ -362,8 +366,8 @@ decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?UINT24(_), ?UINT16(_),
?BYTE(Major), ?BYTE(Minor),
?BYTE(CookieLength),
Cookie:CookieLength/binary>>) ->
- #hello_verify_request{protocol_version = {Major, Minor},
- cookie = Cookie};
+ #hello_verify_request{protocol_version = {Major,Minor},
+ cookie = Cookie};
decode_handshake(Version, Tag, <<?UINT24(_), ?UINT16(_),
?UINT24(_), ?UINT24(_), Msg/binary>>) ->
%% DTLS specifics stripped
@@ -388,9 +392,9 @@ decode_handshake_fragments(<<?BYTE(Type), ?UINT24(Length),
reassemble(Version, #handshake_fragment{message_seq = Seq} = Fragment,
#protocol_buffers{dtls_handshake_next_seq = Seq,
- dtls_handshake_next_fragments = Fragments0,
- dtls_handshake_later_fragments = LaterFragments0} =
- Buffers0)->
+ dtls_handshake_next_fragments = Fragments0,
+ dtls_handshake_later_fragments = LaterFragments0} =
+ Buffers0)->
case reassemble_fragments(Fragment, Fragments0) of
{more_data, Fragments} ->
{more_data, Buffers0#protocol_buffers{dtls_handshake_next_fragments = Fragments}};
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index 2c89ce15d5..ea7a1d72a0 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -29,9 +29,10 @@
-include("tls_handshake.hrl"). %% Common TLS and DTLS records and Constantes
-include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes
-include("ssl_api.hrl").
+-include("ssl_record.hrl").
-define(HELLO_VERIFY_REQUEST, 3).
--define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}).
+-define(HELLO_VERIFY_REQUEST_VERSION, ?DTLS_1_0).
-record(hello_verify_request, {
protocol_version,
@@ -48,10 +49,9 @@
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% RFC 7764 Datagram Transport Layer Security (DTLS) Extension to Establish Keys
+%% RFC 5764 Datagram Transport Layer Security (DTLS) Extension to Establish Keys
%% for the Secure Real-time Transport Protocol (SRTP)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Not supported
--define(USE_SRTP, 14).
+%% Defined in ssl_handshake.hrl because extension parsing code is in ssl_handshake.erl
-endif. % -ifdef(dtls_handshake).
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index dadb16d250..c0030fe1dc 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -43,7 +43,7 @@
-export([decode_cipher_text/2]).
%% Protocol version handling
--export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2,
+-export([protocol_version/1, protocol_version_name/1, lowest_protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, highest_protocol_version/2,
is_higher/2, supported_protocol_versions/0,
is_acceptable_version/2, hello_version/2]).
@@ -141,14 +141,14 @@ set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch
%%--------------------------------------------------------------------
-spec init_connection_state_seq(ssl_record:ssl_version(), ssl_record:connection_states()) ->
- ssl_record:connection_state().
+ ssl_record:connection_state().
%%
%% Description: Copy the read sequence number to the write sequence number
%% This is only valid for DTLS in the first client_hello
%%--------------------------------------------------------------------
-init_connection_state_seq({254, _},
+init_connection_state_seq(Version,
#{current_read := #{epoch := 0, sequence_number := Seq},
- current_write := #{epoch := 0} = Write} = ConnnectionStates0) ->
+ current_write := #{epoch := 0} = Write} = ConnnectionStates0) when ?DTLS_1_X(Version)->
ConnnectionStates0#{current_write => Write#{sequence_number => Seq}};
init_connection_state_seq(_, ConnnectionStates) ->
ConnnectionStates.
@@ -263,85 +263,83 @@ decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) ->
%% Protocol version handling
%%====================================================================
+
+%%--------------------------------------------------------------------
+-spec protocol_version_name(dtls_atom_version()) -> ssl_record:ssl_version().
+%%
+%% Description: Creates a protocol version record from a version atom
+%% or vice versa.
%%--------------------------------------------------------------------
--spec protocol_version(dtls_atom_version() | ssl_record:ssl_version()) ->
- ssl_record:ssl_version() | dtls_atom_version().
+
+protocol_version_name('dtlsv1.2') ->
+ ?DTLS_1_2;
+protocol_version_name(dtlsv1) ->
+ ?DTLS_1_0.
+
+%%--------------------------------------------------------------------
+-spec protocol_version(ssl_record:ssl_version()) -> dtls_atom_version().
+
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
%%--------------------------------------------------------------------
-protocol_version('dtlsv1.2') ->
- {254, 253};
-protocol_version(dtlsv1) ->
- {254, 255};
-protocol_version({254, 253}) ->
+
+protocol_version(?DTLS_1_2) ->
'dtlsv1.2';
-protocol_version({254, 255}) ->
+protocol_version(?DTLS_1_0) ->
dtlsv1.
%%--------------------------------------------------------------------
-spec lowest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
-lowest_protocol_version(Version = {M, N}, {M, O}) when N > O ->
- Version;
-lowest_protocol_version({M, _}, Version = {M, _}) ->
- Version;
-lowest_protocol_version(Version = {M,_}, {N, _}) when M > N ->
- Version;
-lowest_protocol_version(_,Version) ->
- Version.
+lowest_protocol_version(Version1, Version2) when ?DTLS_LT(Version1, Version2) ->
+ Version1;
+lowest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec lowest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
-lowest_protocol_version([]) ->
- lowest_protocol_version();
lowest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- lowest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun lowest_protocol_version/2).
%%--------------------------------------------------------------------
-spec highest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
-highest_protocol_version([]) ->
- highest_protocol_version();
highest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- highest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun highest_protocol_version/2).
+
+
+check_protocol_version([], Fun) -> check_protocol_version(supported_protocol_versions(), Fun);
+check_protocol_version([Ver | Versions], Fun) -> lists:foldl(Fun, Ver, Versions).
%%--------------------------------------------------------------------
-spec highest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
-highest_protocol_version(Version = {M, N}, {M, O}) when N < O ->
- Version;
-highest_protocol_version({M, _},
- Version = {M, _}) ->
- Version;
-highest_protocol_version(Version = {M,_},
- {N, _}) when M < N ->
- Version;
-highest_protocol_version(_,Version) ->
- Version.
+
+highest_protocol_version(Version1, Version2) when ?DTLS_GT(Version1, Version2) ->
+ Version1;
+highest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec is_higher(V1 :: ssl_record:ssl_version(), V2::ssl_record:ssl_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
-is_higher({M, N}, {M, O}) when N < O ->
- true;
-is_higher({M, _}, {N, _}) when M < N ->
+is_higher(V1, V2) when ?DTLS_GT(V1, V2) ->
true;
is_higher(_, _) ->
false.
+
%%--------------------------------------------------------------------
-spec supported_protocol_versions() -> [ssl_record:ssl_version()].
%%
@@ -349,7 +347,7 @@ is_higher(_, _) ->
%%--------------------------------------------------------------------
supported_protocol_versions() ->
Fun = fun(Version) ->
- protocol_version(Version)
+ protocol_version_name(Version)
end,
case application:get_env(ssl, dtls_protocol_version) of
undefined ->
@@ -397,7 +395,7 @@ is_acceptable_version(Version, Versions) ->
-spec hello_version(ssl_record:ssl_version(), [ssl_record:ssl_version()]) -> ssl_record:ssl_version().
hello_version(Version, Versions) ->
case dtls_v1:corresponding_tls_version(Version) of
- TLSVersion when TLSVersion >= {3, 3} ->
+ TLSVersion when ?TLS_GTE(TLSVersion, ?TLS_1_2) ->
Version;
_ ->
lowest_protocol_version(Versions)
@@ -433,10 +431,11 @@ get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo,
orelse ((StateName == abbreviated) andalso (DataTag == udp)))
andalso ((Type == ?HANDSHAKE) orelse (Type == ?ALERT)) ->
ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]),
- Acc = [#ssl_tls{type = Type, version = {MajVer, MinVer},
+ Version = {MajVer,MinVer},
+ Acc = [#ssl_tls{type = Type, version = Version,
epoch = Epoch, sequence_number = SequenceNumber,
fragment = Data} | Acc0],
- case is_acceptable_version({MajVer, MinVer}, Versions) of
+ case is_acceptable_version(Version, Versions) of
true ->
get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
false ->
@@ -452,13 +451,14 @@ get_dtls_records_aux({_, _, Version, Versions} = Vinfo,
(Type == ?ALERT) orelse
(Type == ?CHANGE_CIPHER_SPEC) ->
ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]),
- Acc = [#ssl_tls{type = Type, version = {MajVer,MinVer},
+ Version1 = {MajVer,MinVer},
+ Acc = [#ssl_tls{type = Type, version = Version,
epoch = Epoch, sequence_number = SequenceNumber,
fragment = Data} | Acc0],
- if {MajVer, MinVer} =:= Version ->
+ if Version1 =:= Version ->
get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
Type == ?HANDSHAKE ->
- case is_acceptable_version({MajVer, MinVer}, Versions) of
+ case is_acceptable_version(Version1, Versions) of
true ->
get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel);
false ->
@@ -529,9 +529,10 @@ update_replay_window(SequenceNumber,
%%--------------------------------------------------------------------
-encode_dtls_cipher_text(Type, {MajVer, MinVer}, Fragment,
+encode_dtls_cipher_text(Type, Version, Fragment,
#{epoch := Epoch, sequence_number := Seq} = WriteState) ->
Length = erlang:iolist_size(Fragment),
+ {MajVer,MinVer} = Version,
{[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch),
?UINT48(Seq), ?UINT16(Length)>>, Fragment],
WriteState#{sequence_number => Seq + 1}}.
@@ -632,32 +633,19 @@ calc_mac_hash(Type, Version, #{mac_secret := MacSecret,
mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type,
Length, Fragment).
-mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) ->
+mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) ->
+ {Major,Minor} = Version,
Value = [<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type),
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
Fragment],
dtls_v1:hmac_hash(MacAlg, MacSecret, Value).
-start_additional_data(Type, {MajVer, MinVer}, Epoch, SeqNo) ->
+start_additional_data(Type, Version, Epoch, SeqNo) ->
+ {MajVer,MinVer} = Version,
<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
%%--------------------------------------------------------------------
-lowest_list_protocol_version(Ver, []) ->
- Ver;
-lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
-
-highest_list_protocol_version(Ver, []) ->
- Ver;
-highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
-
-highest_protocol_version() ->
- highest_protocol_version(supported_protocol_versions()).
-
-lowest_protocol_version() ->
- lowest_protocol_version(supported_protocol_versions()).
sufficient_dtlsv1_2_crypto_support() ->
CryptoSupport = crypto:supports(),
diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl
index 3ac6d8226e..851de78963 100644
--- a/lib/ssl/src/dtls_v1.erl
+++ b/lib/ssl/src/dtls_v1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -20,6 +20,7 @@
-module(dtls_v1).
-include("ssl_cipher.hrl").
+-include("ssl_record.hrl").
-export([suites/1,
all_suites/1,
@@ -35,13 +36,13 @@
-define(COOKIE_BASE_TIMEOUT, 30000).
--spec suites(Minor:: 253|255) -> [ssl_cipher_format:cipher_suite()].
+-spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
-suites(Minor) ->
+suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
- tls_v1:suites(corresponding_minor_tls_version(Minor))).
+ tls_v1:suites(corresponding_tls_version(Version))).
all_suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
@@ -54,27 +55,30 @@ anonymous_suites(Version) ->
end,
ssl_cipher:anonymous_suites(corresponding_tls_version(Version))).
-exclusive_suites(Minor) ->
+exclusive_suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
- tls_v1:exclusive_suites(corresponding_minor_tls_version(Minor))).
+ tls_v1:exclusive_suites(corresponding_tls_version(Version))).
-exclusive_anonymous_suites(Minor) ->
+exclusive_anonymous_suites(Version) ->
lists:filter(fun(Cipher) ->
is_acceptable_cipher(ssl_cipher_format:suite_bin_to_map(Cipher))
end,
- tls_v1:exclusive_anonymous_suites(corresponding_minor_tls_version(Minor))).
+ tls_v1:exclusive_anonymous_suites(corresponding_tls_version(Version))).
hmac_hash(MacAlg, MacSecret, Value) ->
tls_v1:hmac_hash(MacAlg, MacSecret, Value).
-ecc_curves({_Major, Minor}) ->
- tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)).
+ecc_curves(Version) ->
+ tls_v1:ecc_curves(corresponding_tls_version(Version)).
-corresponding_tls_version({254, Minor}) ->
- {3, corresponding_minor_tls_version(Minor)}.
+
+corresponding_tls_version(?DTLS_1_0) ->
+ ?TLS_1_1;
+corresponding_tls_version(?DTLS_1_2) ->
+ ?TLS_1_2.
cookie_secret() ->
crypto:strong_rand_bytes(32).
@@ -82,18 +86,12 @@ cookie_secret() ->
cookie_timeout() ->
%% Cookie will live for two timeouts periods
round(rand:uniform() * ?COOKIE_BASE_TIMEOUT/2).
-
-corresponding_minor_tls_version(255) ->
- 2;
-corresponding_minor_tls_version(253) ->
- 3.
-
-corresponding_dtls_version({3, Minor}) ->
- {254, corresponding_minor_dtls_version(Minor)}.
-
-corresponding_minor_dtls_version(2) ->
- 255;
-corresponding_minor_dtls_version(3) ->
- 253.
+
+
+corresponding_dtls_version(?TLS_1_1) ->
+ ?DTLS_1_0;
+corresponding_dtls_version(?TLS_1_2) ->
+ ?DTLS_1_2.
+
is_acceptable_cipher(Suite) ->
not ssl_cipher:is_stream_ciphersuite(Suite).
diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl
index 5ca0cd6904..0e51f9a190 100644
--- a/lib/ssl/src/inet6_tls_dist.erl
+++ b/lib/ssl/src/inet6_tls_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. 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.
@@ -25,26 +25,30 @@
-export([listen/2, accept/1, accept_connection/5,
setup/5, close/1, select/1, address/0]).
+-define(FAMILY, inet6).
+
childspecs() ->
inet_tls_dist:childspecs().
select(Node) ->
- inet_tls_dist:gen_select(inet6_tcp, Node).
+ inet_tls_dist:fam_select(?FAMILY, Node).
address() ->
- inet_tls_dist:gen_address(inet6_tcp).
+ inet_tls_dist:fam_address(?FAMILY).
listen(Name, Host) ->
- inet_tls_dist:gen_listen(inet6_tcp, Name, Host).
+ inet_tls_dist:fam_listen(?FAMILY, Name, Host).
accept(Listen) ->
- inet_tls_dist:gen_accept(inet6_tcp, Listen).
+ inet_tls_dist:fam_accept(?FAMILY, Listen).
accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
- inet_tls_dist:gen_accept_connection(inet6_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime).
+ inet_tls_dist:fam_accept_connection(
+ ?FAMILY, AcceptPid, Socket, MyNode, Allowed, SetupTime).
setup(Node, Type, MyNode, LongOrShortNames,SetupTime) ->
- inet_tls_dist:gen_setup(inet6_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime).
+ inet_tls_dist:fam_setup(
+ ?FAMILY, Node, Type, MyNode, LongOrShortNames,SetupTime).
close(Socket) ->
- inet_tls_dist:gen_close(inet6_tcp, Socket).
+ inet_tls_dist:close(Socket).
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 9118fb59f6..c93bb27596 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -20,19 +20,24 @@
%%
-module(inet_tls_dist).
+-feature(maybe_expr, enable).
-export([childspecs/0]).
--export([listen/2, accept/1, accept_connection/5,
- setup/5, close/1, select/1, address/0, is_node_name/1]).
+-export([select/1, address/0, is_node_name/1,
+ listen/2, accept/1, accept_connection/5,
+ setup/5, close/1]).
%% Generalized dist API
--export([gen_listen/3, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_close/2, gen_select/2, gen_address/1]).
-
--export([nodelay/0]).
+-export([fam_select/2, fam_address/1, fam_listen/3, fam_accept/2,
+ fam_accept_connection/6, fam_setup/6]).
-export([verify_client/3, cert_nodes/1]).
+%% kTLS helpers
+-export([inet_ktls_setopt/3, inet_ktls_getopt/3,
+ set_ktls/1, set_ktls_ulp/2, set_ktls_cipher/5,
+ ktls_os/0, ktls_opt_ulp/1, ktls_opt_cipher/6]).
+
-export([dbg/0]). % Debug
-include_lib("kernel/include/net_address.hrl").
@@ -41,37 +46,68 @@
-include_lib("public_key/include/public_key.hrl").
-include("ssl_api.hrl").
+-include("ssl_cipher.hrl").
+-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
-include_lib("kernel/include/logger.hrl").
+-define(FAMILY, inet).
+-define(DRIVER, inet_tcp). % Implies ?FAMILY = inet through inet_drv.c
+-define(PROTOCOL, tls).
+
%% -------------------------------------------------------------------------
childspecs() ->
{ok, [{ssl_dist_sup,{ssl_dist_sup, start_link, []},
permanent, infinity, supervisor, [ssl_dist_sup]}]}.
+%% -------------------------------------------------------------------------
+%% Select this protocol based on node name
select(Node) ->
- gen_select(inet_tcp, Node).
-
-gen_select(Driver, Node) ->
- inet_tcp_dist:gen_select(Driver, Node).
-
-%% ------------------------------------------------------------
-%% Get the address family that this distribution uses
-%% ------------------------------------------------------------
+ fam_select(?FAMILY, Node).
+fam_select(Family, Node) ->
+ inet_tcp_dist:fam_select(Family, Node).
+%% -------------------------------------------------------------------------
+%% Get the #net_address this distribution uses
address() ->
- gen_address(inet_tcp).
-gen_address(Driver) ->
- inet_tcp_dist:gen_address(Driver).
-
+ fam_address(?FAMILY).
+fam_address(Family) ->
+ NetAddress = inet_tcp_dist:fam_address(Family),
+ NetAddress#net_address{ protocol = ?PROTOCOL }.
%% -------------------------------------------------------------------------
-
+%% Is this one really needed??
is_node_name(Node) ->
dist_util:is_node_name(Node).
-
%% -------------------------------------------------------------------------
-hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
+hs_data_inet_tcp(Driver, Socket) ->
+ Family = Driver:family(),
+ {ok, Peername} =
+ maybe
+ {error, einval} ?= inet:peername(Socket),
+ ?shutdown({Driver, closed})
+ end,
+ (inet_tcp_dist:gen_hs_data(Driver, Socket))
+ #hs_data{
+ f_address =
+ fun(_, Node) ->
+ {node, _, Host} = dist_util:split_node(Node),
+ #net_address{
+ address = Peername,
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family
+ }
+ end}.
+
+hs_data_ssl(Family, #sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
+ {ok, Address} =
+ maybe
+ {error, einval} ?= ssl:peername(SslSocket),
+ ?shutdown({sslsocket, closed})
+ end,
#hs_data{
+ socket = DistCtrl,
f_send =
fun (_Ctrl, Packet) ->
f_send(SslSocket, Packet)
@@ -95,7 +131,7 @@ hs_data_common(#sslsocket{pid = [_, DistCtrl|_]} = SslSocket) ->
end,
f_address =
fun (Ctrl, Node) when Ctrl == DistCtrl ->
- f_address(SslSocket, Node)
+ f_address(Family, Address, Node)
end,
mf_tick =
fun (Ctrl) when Ctrl == DistCtrl ->
@@ -133,22 +169,21 @@ f_setopts_pre_nodeup(_SslSocket) ->
ok.
f_setopts_post_nodeup(SslSocket) ->
- ssl:setopts(SslSocket, [nodelay()]).
+ ssl:setopts(SslSocket, [inet_tcp_dist:nodelay()]).
f_getll(DistCtrl) ->
{ok, DistCtrl}.
-f_address(SslSocket, Node) ->
- case ssl:peername(SslSocket) of
- {ok, Address} ->
- case dist_util:split_node(Node) of
- {node,_,Host} ->
- #net_address{
- address=Address, host=Host,
- protocol=tls, family=inet};
- _ ->
- {error, no_node}
- end
+f_address(Family, Address, Node) ->
+ case dist_util:split_node(Node) of
+ {node,_,Host} ->
+ #net_address{
+ address = Address,
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family};
+ _ ->
+ {error, no_node}
end.
mf_tick(DistCtrl) ->
@@ -194,52 +229,79 @@ split_stat([], R, W, P) ->
%% -------------------------------------------------------------------------
listen(Name, Host) ->
- gen_listen(inet_tcp, Name, Host).
-
-gen_listen(Driver, Name, Host) ->
- case inet_tcp_dist:gen_listen(Driver, Name, Host) of
- {ok, {Socket, Address, Creation}} ->
- inet:setopts(Socket, [{packet, 4}, {nodelay, true}]),
- {ok, {Socket, Address#net_address{protocol=tls}, Creation}};
- Other ->
- Other
+ fam_listen(?FAMILY, Name, Host).
+
+fam_listen(Family, Name, Host) ->
+ ForcedOptions =
+ [Family, {active, false}, {packet, 4}, {nodelay, true}],
+ ListenFun =
+ fun (First, Last, ListenOptions) ->
+ listen_loop(
+ First, Last,
+ inet_tcp_dist:merge_options(ListenOptions, ForcedOptions))
+ end,
+ maybe
+ %%
+ {ok, {ListenSocket, Address, Creation}} ?=
+ inet_tcp_dist:fam_listen(Family, Name, Host, ListenFun),
+ NetAddress =
+ #net_address{
+ host = Host,
+ protocol = ?PROTOCOL,
+ family = Family,
+ address = Address},
+ {ok, {ListenSocket, NetAddress, Creation}}
end.
+listen_loop(First, Last, ListenOptions) when First =< Last ->
+ case gen_tcp:listen(First, ListenOptions) of
+ {error, eaddrinuse} ->
+ listen_loop(First + 1, Last, ListenOptions);
+ Result ->
+ Result
+ end;
+listen_loop(_, _, _) ->
+ {error, eaddrinuse}.
+
%% -------------------------------------------------------------------------
-accept(Listen) ->
- gen_accept(inet_tcp, Listen).
+accept(ListenSocket) ->
+ fam_accept(?FAMILY, ListenSocket).
-gen_accept(Driver, Listen) ->
- Kernel = self(),
+fam_accept(Family, ListenSocket) ->
+ NetKernel = self(),
monitor_pid(
spawn_opt(
fun () ->
- process_flag(trap_exit, true),
- LOpts = application:get_env(kernel, inet_dist_listen_options, []),
- MaxPending =
- case lists:keyfind(backlog, 1, LOpts) of
- {backlog, Backlog} -> Backlog;
- false -> 128
- end,
- DLK = {Driver, Listen, Kernel},
- accept_loop(DLK, spawn_accept(DLK), MaxPending, #{})
+ process_flag(trap_exit, true),
+ MaxPending = erlang:system_info(schedulers_online),
+ Continue = make_ref(),
+ FLNC = {Family, ListenSocket, NetKernel, Continue},
+ Pending = #{},
+ accept_loop(
+ FLNC, Continue, spawn_accept(FLNC), MaxPending,
+ Pending)
end,
- [link, {priority, max}])).
+ dist_util:net_ticker_spawn_options())).
%% Concurrent accept loop will spawn a new HandshakePid when
%% there is no HandshakePid already running, and Pending map is
%% smaller than MaxPending
-accept_loop(DLK, undefined, MaxPending, Pending) when map_size(Pending) < MaxPending ->
- accept_loop(DLK, spawn_accept(DLK), MaxPending, Pending);
-accept_loop({_, _, NetKernelPid} = DLK, HandshakePid, MaxPending, Pending) ->
+accept_loop(FLNC, Continue, undefined, MaxPending, Pending)
+ when map_size(Pending) < MaxPending ->
+ accept_loop(FLNC, Continue, spawn_accept(FLNC), MaxPending, Pending);
+accept_loop({_, _, NetKernelPid, _} = FLNC, Continue, HandshakePid, MaxPending, Pending) ->
receive
- {continue, HandshakePid} when is_pid(HandshakePid) ->
- accept_loop(DLK, undefined, MaxPending, Pending#{HandshakePid => true});
+ {Continue, HandshakePid} when is_pid(HandshakePid) ->
+ accept_loop(
+ FLNC, Continue, undefined, MaxPending,
+ Pending#{HandshakePid => true});
{'EXIT', Pid, Reason} when is_map_key(Pid, Pending) ->
Reason =/= normal andalso
?LOG_ERROR("TLS distribution handshake failed: ~p~n", [Reason]),
- accept_loop(DLK, HandshakePid, MaxPending, maps:remove(Pid, Pending));
+ accept_loop(
+ FLNC, Continue, HandshakePid, MaxPending,
+ maps:remove(Pid, Pending));
{'EXIT', HandshakePid, Reason} when is_pid(HandshakePid) ->
%% HandshakePid crashed before turning into Pending, which means
%% error happened in accept. Need to restart the listener.
@@ -248,20 +310,21 @@ accept_loop({_, _, NetKernelPid} = DLK, HandshakePid, MaxPending, Pending) ->
%% Since we're trapping exits, need to manually propagate this signal
exit(Reason);
Unexpected ->
- ?LOG_WARNING("TLS distribution: unexpected message: ~p~n" ,[Unexpected]),
- accept_loop(DLK, HandshakePid, MaxPending, Pending)
+ ?LOG_WARNING(
+ "TLS distribution: unexpected message: ~p~n", [Unexpected]),
+ accept_loop(FLNC, Continue, HandshakePid, MaxPending, Pending)
end.
-spawn_accept({Driver, Listen, Kernel}) ->
+spawn_accept({Family, ListenSocket, NetKernel, Continue}) ->
AcceptLoop = self(),
spawn_link(
fun () ->
- case Driver:accept(Listen) of
+ case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
- AcceptLoop ! {continue, self()},
- case check_ip(Driver, Socket) of
+ AcceptLoop ! {Continue, self()},
+ case check_ip(Socket) of
true ->
- accept_one(Driver, Kernel, Socket);
+ accept_one(Family, Socket, NetKernel);
{false,IP} ->
?LOG_ERROR(
"** Connection attempt from "
@@ -273,33 +336,37 @@ spawn_accept({Driver, Listen, Kernel}) ->
end
end).
-accept_one(Driver, Kernel, Socket) ->
+accept_one(Family, Socket, NetKernel) ->
Opts = setup_verify_client(Socket, get_ssl_options(server)),
- wait_for_code_server(),
+ KTLS = proplists:get_value(ktls, Opts, false),
case
ssl:handshake(
Socket,
trace([{active, false},{packet, 4}|Opts]),
net_kernel:connecttime())
of
- {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} ->
- trace(
- Kernel !
- {accept, self(), DistCtrl,
- Driver:family(), tls}),
- receive
- {Kernel, controller, Pid} ->
- case ssl:controlling_process(SslSocket, Pid) of
+ {ok, SslSocket} ->
+ Receiver = hd(SslSocket#sslsocket.pid),
+ case KTLS of
+ true ->
+ {ok, KtlsInfo} = ssl_gen_statem:ktls_handover(Receiver),
+ case inet_set_ktls(KtlsInfo) of
ok ->
- trace(Pid ! {self(), controller});
- Error ->
- trace(Pid ! {self(), exit}),
+ accept_one(
+ Family, maps:get(socket, KtlsInfo), NetKernel,
+ fun gen_tcp:controlling_process/2);
+ {error, KtlsReason} ->
?LOG_ERROR(
- "Cannot control TLS distribution connection: ~p~n",
- [Error])
+ [{slogan, set_ktls_failed},
+ {reason, KtlsReason},
+ {pid, self()}]),
+ close(Socket),
+ trace({ktls_error, KtlsReason})
end;
- {Kernel, unsupported_protocol} ->
- trace(unsupported_protocol)
+ false ->
+ accept_one(
+ Family, SslSocket, NetKernel,
+ fun ssl:controlling_process/2)
end;
{error, {options, _}} = Error ->
%% Bad options: that's probably our fault.
@@ -307,12 +374,31 @@ accept_one(Driver, Kernel, Socket) ->
?LOG_ERROR(
"Cannot accept TLS distribution connection: ~s~n",
[ssl:format_error(Error)]),
- gen_tcp:close(Socket),
+ close(Socket),
trace(Error);
Other ->
- gen_tcp:close(Socket),
+ close(Socket),
trace(Other)
end.
+%%
+accept_one(
+ Family, DistSocket, NetKernel, ControllingProcessFun) ->
+ trace(NetKernel ! {accept, self(), DistSocket, Family, ?PROTOCOL}),
+ receive
+ {NetKernel, controller, Pid} ->
+ case ControllingProcessFun(DistSocket, Pid) of
+ ok ->
+ trace(Pid ! {self(), controller});
+ {error, Reason} ->
+ trace(Pid ! {self(), exit}),
+ ?LOG_ERROR(
+ [{slogan, controlling_process_failed},
+ {reason, Reason},
+ {pid, self()}])
+ end;
+ {NetKernel, unsupported_protocol} ->
+ trace(unsupported_protocol)
+ end.
%% {verify_fun,{fun ?MODULE:verify_client/3,_}} is used
@@ -398,72 +484,50 @@ verify_client(PeerCert, valid_peer, {AllowedHosts,PeerIP} = S) ->
end.
-wait_for_code_server() ->
- %% This is an ugly hack. Upgrading a socket to TLS requires the
- %% crypto module to be loaded. Loading the crypto module triggers
- %% its on_load function, which calls code:priv_dir/1 to find the
- %% directory where its NIF library is. However, distribution is
- %% started earlier than the code server, so the code server is not
- %% necessarily started yet, and code:priv_dir/1 might fail because
- %% of that, if we receive an incoming connection on the
- %% distribution port early enough.
- %%
- %% If the on_load function of a module fails, the module is
- %% unloaded, and the function call that triggered loading it fails
- %% with 'undef', which is rather confusing.
- %%
- %% Thus, the accept process will terminate, and be
- %% restarted by ssl_dist_sup. However, it won't have any memory
- %% of being asked by net_kernel to listen for incoming
- %% connections. Hence, the node will believe that it's open for
- %% distribution, but it actually isn't.
- %%
- %% So let's avoid that by waiting for the code server to start.
- case whereis(code_server) of
- undefined ->
- timer:sleep(10),
- wait_for_code_server();
- Pid when is_pid(Pid) ->
- ok
- end.
-
%% -------------------------------------------------------------------------
-accept_connection(AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
- gen_accept_connection(
- inet_tcp, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime).
+accept_connection(AcceptPid, DistSocket, MyNode, Allowed, SetupTime) ->
+ fam_accept_connection(
+ ?FAMILY, AcceptPid, DistSocket, MyNode, Allowed, SetupTime).
-gen_accept_connection(
- Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
+fam_accept_connection(
+ Family, AcceptPid, DistSocket, MyNode, Allowed, SetupTime) ->
Kernel = self(),
monitor_pid(
spawn_opt(
fun() ->
do_accept(
- Driver, AcceptPid, DistCtrl,
+ Family, AcceptPid, DistSocket,
MyNode, Allowed, SetupTime, Kernel)
end,
dist_util:net_ticker_spawn_options())).
do_accept(
- _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) ->
+ Family, AcceptPid, DistSocket, MyNode, Allowed, SetupTime, Kernel) ->
MRef = erlang:monitor(process, AcceptPid),
receive
{AcceptPid, controller} ->
erlang:demonitor(MRef, [flush]),
- {ok, SslSocket} = tls_sender:dist_tls_socket(DistCtrl),
- Timer = dist_util:start_timer(SetupTime),
- NewAllowed = allowed_nodes(SslSocket, Allowed),
- HSData0 = hs_data_common(SslSocket),
+ Timer = dist_util:start_timer(SetupTime),
+ {HSData0, NewAllowed} =
+ case DistSocket of
+ SslSocket = #sslsocket{pid = [_Receiver, Sender| _]} ->
+ link(Sender),
+ {hs_data_ssl(Family, SslSocket),
+ allowed_nodes(SslSocket, Allowed)};
+ PortSocket when is_port(DistSocket) ->
+ %%% XXX Breaking abstraction barrier
+ Driver = erlang:port_get_data(PortSocket),
+ {hs_data_inet_tcp(Driver, PortSocket),
+ Allowed}
+ end,
HSData =
HSData0#hs_data{
kernel_pid = Kernel,
this_node = MyNode,
- socket = DistCtrl,
timer = Timer,
this_flags = 0,
allowed = NewAllowed},
- link(DistCtrl),
dist_util:handshake_other_started(trace(HSData));
{AcceptPid, exit} ->
%% this can happen when connection was initiated, but dropped
@@ -535,138 +599,147 @@ allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) ->
allowed_nodes(PeerCert, Allowed, PeerIP)
end.
+
+%% -------------------------------------------------------------------------
+
setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime).
+ fam_setup(?FAMILY, Node, Type, MyNode, LongOrShortNames, SetupTime).
-gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- Kernel = self(),
+fam_setup(Family, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+ NetKernel = self(),
monitor_pid(
- spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime),
- dist_util:net_ticker_spawn_options())).
+ spawn_opt(
+ setup_fun(
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime,
+ NetKernel),
+ dist_util:net_ticker_spawn_options())).
-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
-setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
+setup_fun(
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime, NetKernel) ->
fun() ->
do_setup(
- Driver, Kernel, Node, Type,
- MyNode, LongOrShortNames, SetupTime)
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime,
+ NetKernel)
end.
-
-spec do_setup(_,_,_,_,_,_,_) -> no_return().
-do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- {Name, Address} = split_node(Driver, Node, LongOrShortNames),
- ErlEpmd = net_kernel:epmd_module(),
- {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver),
+do_setup(
+ Family, Node, Type, MyNode, LongOrShortNames, SetupTime, NetKernel) ->
Timer = trace(dist_util:start_timer(SetupTime)),
- case ARMod:ARFun(Name,Address,Driver:family()) of
- {ok, Ip, TcpPort, Version} ->
- do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer);
- {ok, Ip} ->
- case ErlEpmd:port_please(Name, Ip) of
- {port, TcpPort, Version} ->
- do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer);
- Other ->
- ?shutdown2(
- Node,
- trace(
- {port_please_failed, ErlEpmd, Name, Ip, Other}))
- end;
- Other ->
- ?shutdown2(
- Node,
- trace({getaddr_failed, Driver, Address, Other}))
- end.
-
--spec do_setup_connect(_,_,_,_,_,_,_,_,_,_) -> no_return().
-
-do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) ->
- Opts = trace(connect_options(get_ssl_options(client))),
+ ParseAddress = fun (A) -> inet:parse_strict_address(A, Family) end,
+ {#net_address{
+ host = Host,
+ address = {Ip, PortNum}},
+ ConnectOptions,
+ Version} =
+ trace(inet_tcp_dist:fam_setup(
+ Family, Node, LongOrShortNames, ParseAddress)),
+ Opts =
+ inet_tcp_dist:merge_options(
+ inet_tcp_dist:merge_options(
+ ConnectOptions,
+ get_ssl_options(client)),
+ [Family, binary, {active, false}, {packet, 4}, {nodelay, true}],
+ [{server_name_indication, Host}]),
+ KTLS = proplists:get_value(ktls, Opts, false),
dist_util:reset_timer(Timer),
- case ssl:connect(
- Ip, TcpPort,
- [binary, {active, false}, {packet, 4}, {server_name_indication, Address},
- Driver:family(), {nodelay, true}] ++ Opts,
- net_kernel:connecttime()) of
- {ok, #sslsocket{pid = [_, DistCtrl| _]} = SslSocket} ->
- _ = monitor_pid(DistCtrl),
- ok = ssl:controlling_process(SslSocket, self()),
- HSData0 = hs_data_common(SslSocket),
+ maybe
+ {ok, #sslsocket{pid = [Receiver, Sender| _]} = SslSocket} ?=
+ ssl:connect(Ip, PortNum, Opts, net_kernel:connecttime()),
HSData =
- HSData0#hs_data{
- kernel_pid = Kernel,
- other_node = Node,
- this_node = MyNode,
- socket = DistCtrl,
- timer = Timer,
- this_flags = 0,
- other_version = Version,
- request_type = Type},
- link(DistCtrl),
- dist_util:handshake_we_started(trace(HSData));
- Other ->
- %% Other Node may have closed since
- %% port_please !
- ?shutdown2(
- Node,
- trace(
- {ssl_connect_failed, Ip, TcpPort, Other}))
+ case KTLS of
+ true ->
+ {ok, KtlsInfo} =
+ ssl_gen_statem:ktls_handover(Receiver),
+ Socket = maps:get(socket, KtlsInfo),
+ case inet_set_ktls(KtlsInfo) of
+ ok when is_port(Socket) ->
+ %% XXX Breaking abstraction barrier
+ Driver = erlang:port_get_data(Socket),
+ hs_data_inet_tcp(Driver, Socket);
+ {error, KtlsReason} ->
+ ?shutdown2(
+ Node,
+ trace({set_ktls_failed, KtlsReason}))
+ end;
+ false ->
+ _ = monitor_pid(Sender),
+ ok = ssl:controlling_process(SslSocket, self()),
+ link(Sender),
+ hs_data_ssl(Family, SslSocket)
+ end
+ #hs_data{
+ kernel_pid = NetKernel,
+ other_node = Node,
+ this_node = MyNode,
+ timer = Timer,
+ this_flags = 0,
+ other_version = Version,
+ request_type = Type},
+ dist_util:handshake_we_started(trace(HSData))
+ else
+ Other ->
+ %% Other Node may have closed since
+ %% port_please !
+ ?shutdown2(
+ Node,
+ trace({ssl_connect_failed, Ip, PortNum, Other}))
end.
-close(Socket) ->
- gen_close(inet, Socket).
-
-gen_close(Driver, Socket) ->
- trace(Driver:close(Socket)).
+close(Socket) ->
+ gen_tcp:close(Socket).
-%% ------------------------------------------------------------
-%% Determine if EPMD module supports address resolving. Default
-%% is to use inet_tcp:getaddr/2.
-%% ------------------------------------------------------------
-get_address_resolver(EpmdModule, _Driver) ->
- case erlang:function_exported(EpmdModule, address_please, 3) of
- true -> {EpmdModule, address_please};
- _ -> {erl_epmd, address_please}
- end.
%% ------------------------------------------------------------
%% Do only accept new connection attempts from nodes at our
%% own LAN, if the check_ip environment parameter is true.
%% ------------------------------------------------------------
-check_ip(Driver, Socket) ->
+check_ip(Socket) ->
case application:get_env(check_ip) of
{ok, true} ->
- case get_ifs(Socket) of
- {ok, IFs, IP} ->
- check_ip(Driver, IFs, IP);
- Other ->
- ?shutdown2(
- no_node, trace({check_ip_failed, Socket, Other}))
- end;
+ maybe
+ {ok, {IP, _}} ?= inet:sockname(Socket),
+ ok ?= if is_tuple(IP) -> ok;
+ true -> {error, {no_ip_address, IP}}
+ end,
+ {ok, Ifaddrs} ?= inet:getifaddrs(),
+ {ok, Netmask} ?= find_netmask(IP, Ifaddrs),
+ {ok, {PeerIP, _}} ?= inet:sockname(Socket),
+ ok ?= if is_tuple(PeerIP) -> ok;
+ true -> {error, {no_ip_address, PeerIP}}
+ end,
+ mask(IP, Netmask) =:= mask(PeerIP, Netmask)
+ orelse {false, PeerIP}
+ else
+ Other ->
+ exit({check_ip, Other})
+ end;
_ ->
true
end.
-check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) ->
- case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of
- {M, M} -> true;
- _ -> check_ip(IFs, PeerIP)
- end;
-check_ip(_Driver, [], PeerIP) ->
- {false, PeerIP}.
-
-get_ifs(Socket) ->
- case inet:peername(Socket) of
- {ok, {IP, _}} ->
- %% XXX this is seriously broken for IPv6
- case inet:getif(Socket) of
- {ok, IFs} -> {ok, IFs, IP};
- Error -> Error
- end;
- Error ->
- Error
- end.
+find_netmask(IP, [{_Name,Items} | Ifaddrs]) ->
+ find_netmask(IP, Ifaddrs, Items);
+find_netmask(_, []) ->
+ {error, no_netmask}.
+%%
+find_netmask(IP, _Ifaddrs, [{addr, IP}, {netmask, Netmask} | _]) ->
+ {ok, Netmask};
+find_netmask(IP, Ifaddrs, [_ | Items]) ->
+ find_netmask(IP, Ifaddrs, Items);
+find_netmask(IP, Ifaddrs, []) ->
+ find_netmask(IP, Ifaddrs).
+
+mask(Addr, Mask) ->
+ list_to_tuple(mask(Addr, Mask, 1)).
+%%
+mask(Addr, Mask, N) when N =< tuple_size(Addr) ->
+ [element(N, Addr) band element(N, Mask) | mask(Addr, Mask, N + 1)];
+mask(_, _, _) ->
+ [].
+
%% Look in Extensions, in all subjectAltName:s
@@ -744,90 +817,32 @@ parse_rdn([_|Rdn]) ->
parse_rdn(Rdn).
-%% If Node is illegal terminate the connection setup!!
-split_node(Driver, Node, LongOrShortNames) ->
- case dist_util:split_node(Node) of
- {node, Name, Host} ->
- check_node(Driver, Node, Name, Host, LongOrShortNames);
- {host, _} ->
- ?LOG_ERROR(
- "** Nodename ~p illegal, no '@' character **~n",
- [Node]),
- ?shutdown2(Node, trace({illegal_node_n@me, Node}));
- _ ->
- ?LOG_ERROR(
- "** Nodename ~p illegal **~n", [Node]),
- ?shutdown2(Node, trace({illegal_node_name, Node}))
- end.
-
-check_node(Driver, Node, Name, Host, LongOrShortNames) ->
- case string:split(Host, ".", all) of
- [_] when LongOrShortNames =:= longnames ->
- case Driver:parse_address(Host) of
- {ok, _} ->
- {Name, Host};
- _ ->
- ?LOG_ERROR(
- "** System running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_longnames, Host}))
- end;
- [_,_|_] when LongOrShortNames =:= shortnames ->
- ?LOG_ERROR(
- "** System NOT running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_shortnames, Host}));
- _ ->
- {Name, Host}
- end.
-
%% -------------------------------------------------------------------------
-
-connect_options(Opts) ->
- case application:get_env(kernel, inet_dist_connect_options) of
- {ok,ConnectOpts} ->
- lists:ukeysort(1, ConnectOpts ++ Opts);
- _ ->
- Opts
- end.
-
-%% we may not always want the nodelay behaviour
-%% for performance reasons
-nodelay() ->
- case application:get_env(kernel, dist_nodelay) of
- undefined ->
- {nodelay, true};
- {ok, true} ->
- {nodelay, true};
- {ok, false} ->
- {nodelay, false};
- _ ->
- {nodelay, true}
- end.
-
-
get_ssl_options(Type) ->
- try ets:lookup(ssl_dist_opts, Type) of
- [{Type, Opts0}] ->
- [{erl_dist, true} | dist_defaults(Opts0)];
- _ ->
- get_ssl_dist_arguments(Type)
- catch
- error:badarg ->
- get_ssl_dist_arguments(Type)
- end.
-
-get_ssl_dist_arguments(Type) ->
- case init:get_argument(ssl_dist_opt) of
- {ok, Args} ->
- [{erl_dist, true} | dist_defaults(ssl_options(Type, lists:append(Args)))];
- _ ->
- [{erl_dist, true}]
- end.
+ [{erl_dist, true} |
+ case
+ case init:get_argument(ssl_dist_opt) of
+ {ok, Args} ->
+ ssl_options(Type, lists:append(Args));
+ _ ->
+ []
+ end
+ ++
+ try ets:lookup(ssl_dist_opts, Type) of
+ [{Type, Opts0}] ->
+ Opts0;
+ _ ->
+ []
+ catch
+ error:badarg ->
+ []
+ end
+ of
+ [] ->
+ [];
+ Opts1 ->
+ dist_defaults(Opts1)
+ end].
dist_defaults(Opts) ->
case proplists:get_value(versions, Opts, undefined) of
@@ -874,7 +889,13 @@ ssl_option(client, Opt) ->
"secure_renegotiate" -> fun atomize/1;
"depth" -> fun erlang:list_to_integer/1;
"hibernate_after" -> fun erlang:list_to_integer/1;
- "ciphers" -> fun listify/1;
+ "ciphers" ->
+ %% Allows just one cipher, for now (could be , separated)
+ fun (Val) -> [listify(Val)] end;
+ "versions" ->
+ %% Allows just one version, for now (could be , separated)
+ fun (Val) -> [atomize(Val)] end;
+ "ktls" -> fun atomize/1;
_ -> error
end.
@@ -900,6 +921,174 @@ verify_fun(Value) ->
error(malformed_ssl_dist_opt, [Value])
end.
+
+inet_set_ktls(
+ #{ socket := Socket, socket_options := SocketOptions } = KtlsInfo) ->
+ %%
+ maybe
+ ok ?=
+ set_ktls(
+ KtlsInfo
+ #{ setopt_fun => fun ?MODULE:inet_ktls_setopt/3,
+ getopt_fun => fun ?MODULE:inet_ktls_getopt/3 }),
+ %%
+ #socket_options{
+ mode = _Mode,
+ packet = Packet,
+ packet_size = PacketSize,
+ header = Header,
+ active = Active
+ } = SocketOptions,
+ case
+ inet:setopts(
+ Socket,
+ [list, {packet, Packet}, {packet_size, PacketSize},
+ {header, Header}, {active, Active}])
+ of
+ ok ->
+ ok;
+ {error, SetoptError} ->
+ {error, {ktls_setopt_failed, SetoptError}}
+ end
+ end.
+
+inet_ktls_setopt(Socket, {Level, Opt}, Value)
+ when is_integer(Level), is_integer(Opt), is_binary(Value) ->
+ inet:setopts(Socket, [{raw, Level, Opt, Value}]).
+
+inet_ktls_getopt(Socket, {Level, Opt}, Size)
+ when is_integer(Level), is_integer(Opt), is_integer(Size) ->
+ case inet:getopts(Socket, [{raw, Level, Opt, Size}]) of
+ {ok, [{raw, Level, Opt, Value}]} ->
+ {ok, Value};
+ {ok, _} = Error ->
+ {error, Error};
+ {error, _} = Error ->
+ Error
+ end.
+
+
+set_ktls(KtlsInfo) ->
+ maybe
+ {ok, OS} ?= ktls_os(),
+ ok ?= set_ktls_ulp(KtlsInfo, OS),
+ #{ write_state := WriteState,
+ write_seq := WriteSeq,
+ read_state := ReadState,
+ read_seq := ReadSeq } = KtlsInfo,
+ ok ?= set_ktls_cipher(KtlsInfo, OS, WriteState, WriteSeq, tx),
+ set_ktls_cipher(KtlsInfo, OS, ReadState, ReadSeq, rx)
+ end.
+
+set_ktls_ulp(
+ #{ socket := Socket,
+ setopt_fun := SetoptFun,
+ getopt_fun := GetoptFun },
+ OS) ->
+ %%
+ {Option, Value} = ktls_opt_ulp(OS),
+ Size = byte_size(Value),
+ _ = SetoptFun(Socket, Option, Value),
+ %%
+ %% Check if kernel module loaded,
+ %% i.e if getopts Level, Opt returns Value
+ %%
+ case GetoptFun(Socket, Option, Size + 1) of
+ {ok, <<Value:Size/binary, 0>>} ->
+ ok;
+ Other ->
+ {error, {ktls_set_ulp_failed, Option, Value, Other}}
+ end.
+
+%% Set kTLS cipher
+%%
+set_ktls_cipher(
+ _KtlsInfo =
+ #{ tls_version := TLS_version,
+ cipher_suite := CipherSuite,
+ %%
+ socket := Socket,
+ setopt_fun := SetoptFun,
+ getopt_fun := GetoptFun },
+ OS, CipherState, CipherSeq, TxRx) ->
+ maybe
+ {ok, {Option, Value}} ?=
+ ktls_opt_cipher(
+ OS, TLS_version, CipherSuite, CipherState, CipherSeq, TxRx),
+ _ = SetoptFun(Socket, Option, Value),
+ case TxRx of
+ tx ->
+ Size = byte_size(Value),
+ case GetoptFun(Socket, Option, Size) of
+ {ok, Value} ->
+ ok;
+ Other ->
+ {error, {ktls_set_cipher_failed, Other}}
+ end;
+ rx ->
+ ok
+ end
+ end.
+
+ktls_os() ->
+ OS = {os:type(), os:version()},
+ case OS of
+ {{unix,linux}, OsVersion} when {5,2,0} =< OsVersion ->
+ {ok, OS};
+ _ ->
+ {error, {ktls_notsup, {os,OS}}}
+ end.
+
+ktls_opt_ulp(_OS) ->
+ %%
+ %% See https://www.kernel.org/doc/html/latest/networking/tls.html
+ %% and include/netinet/tcp.h
+ %%
+ SOL_TCP = 6, TCP_ULP = 31,
+ KtlsMod = <<"tls">>,
+ {{SOL_TCP,TCP_ULP}, KtlsMod}.
+
+ktls_opt_cipher(
+ _OS,
+ _TLS_version = ?TLS_1_3, % 'tlsv1.3'
+ _CipherSpec = ?TLS_AES_256_GCM_SHA384,
+ #cipher_state{
+ key = <<Key:32/bytes>>,
+ iv = <<Salt:4/bytes, IV:8/bytes>> },
+ CipherSeq,
+ TxRx) when is_integer(CipherSeq) ->
+ %%
+ %% See include/linux/tls.h
+ %%
+ TLS_1_3_VERSION_MAJOR = 3,
+ TLS_1_3_VERSION_MINOR = 4,
+ TLS_1_3_VERSION =
+ (TLS_1_3_VERSION_MAJOR bsl 8) bor TLS_1_3_VERSION_MINOR,
+ TLS_CIPHER_AES_GCM_256 = 52,
+ SOL_TLS = 282,
+ TLS_TX = 1,
+ TLS_RX = 2,
+ Value =
+ <<TLS_1_3_VERSION:16/native,
+ TLS_CIPHER_AES_GCM_256:16/native,
+ IV/bytes, Key/bytes,
+ Salt/bytes, CipherSeq:64/native>>,
+ %%
+ SOL_TLS = 282,
+ TLS_TX = 1,
+ TLS_RX = 2,
+ TLS_TxRx =
+ case TxRx of
+ tx -> TLS_TX;
+ rx -> TLS_RX
+ end,
+ {ok, {{SOL_TLS,TLS_TxRx}, Value}};
+ktls_opt_cipher(
+ _OS, TLS_version, CipherSpec, _CipherState, _CipherSeq, _TxRx) ->
+ {error,
+ {ktls_notsup, {cipher, TLS_version, CipherSpec, _CipherState}}}.
+
+
%% -------------------------------------------------------------------------
%% Trace point
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index b5cb6b5d91..abc5d278a8 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -4,7 +4,9 @@
{modules, [
%% TLS/SSL
tls_connection,
- tls_connection_1_3,
+ tls_client_connection_1_3,
+ tls_server_connection_1_3,
+ tls_gen_connection_1_3,
tls_handshake,
tls_handshake_1_3,
tls_record,
@@ -75,6 +77,7 @@
ssl_crl_hash_dir,
%% Logging
ssl_logger,
+ ssl_trace,
%% App structure
ssl_app,
ssl_sup,
@@ -85,6 +88,6 @@
{applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}},
- {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-8.4",
- "erts-10.0","crypto-5.0", "inets-5.10.7",
+ {runtime_dependencies, ["stdlib-4.1","public_key-1.11.3","kernel-@OTP-18235@",
+ "erts-@OTP-18248@","crypto-5.0", "inets-5.10.7",
"runtime_tools-1.15.1"]}]}.
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index ad5028655d..c96173e98b 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. 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.
@@ -94,12 +94,14 @@
connection_information/1,
connection_information/2]).
%% Misc
--export([handle_options/2,
- handle_options/3,
- tls_version/1,
+-export([handle_options/3,
+ update_options/3,
+ tls_version/1,
suite_to_str/1,
suite_to_openssl_str/1,
str_to_suite/1]).
+%% Tracing
+-export([handle_trace/3]).
-removed({ssl_accept, '_',
"use ssl_handshake/1,2,3 instead"}).
@@ -178,8 +180,7 @@
des_cbc |
'3des_ede_cbc'.
--type hash() :: sha |
- sha2() |
+-type hash() :: sha2() |
legacy_hash(). % exported
-type sha2() :: sha224 |
@@ -187,7 +188,7 @@
sha384 |
sha512.
--type legacy_hash() :: md5.
+-type legacy_hash() :: sha | md5.
-type sign_algo() :: rsa | dsa | ecdsa | eddsa. % exported
@@ -245,7 +246,9 @@
brainpoolP256r1 |
secp256k1 |
secp256r1 |
- sect239k1 |
+ legacy_named_curve(). % exported
+
+-type legacy_named_curve() :: sect239k1 |
sect233k1 |
sect233r1 |
secp224k1 |
@@ -259,9 +262,9 @@
sect163r2 |
secp160k1 |
secp160r1 |
- secp160r2. % exported
+ secp160r2.
--type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
+-type group() :: x25519 | x448 | secp256r1 | secp384r1 | secp521r1 | ffdhe2048 |
ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported
-type srp_param_type() :: srp_1024 |
@@ -386,7 +389,7 @@
-type log_alert() :: boolean().
-type logging_level() :: logger:level() | none | all.
-type client_session_tickets() :: disabled | manual | auto.
--type server_session_tickets() :: disabled | stateful | stateless.
+-type server_session_tickets() :: disabled | stateful | stateless | stateful_with_cert | stateless_with_cert.
-type session_tickets() :: client_session_tickets() | server_session_tickets().
-type key_update_at() :: pos_integer().
-type bloom_filter_window_size() :: integer().
@@ -400,6 +403,7 @@
-type middlebox_comp_mode() :: boolean().
-type client_early_data() :: binary().
-type server_early_data() :: disabled | enabled.
+-type use_srtp() :: #{protection_profiles := [binary()], mki => binary()}.
-type spawn_opts() :: [erlang:spawn_opt_option()].
%% -------------------------------------------------------------------------------------------------------
@@ -421,7 +425,8 @@
{certificate_authorities, client_certificate_authorities()} |
{session_tickets, client_session_tickets()} |
{use_ticket, use_ticket()} |
- {early_data, client_early_data()}.
+ {early_data, client_early_data()} |
+ {use_srtp, use_srtp()}.
%% {ocsp_stapling, ocsp_stapling()} |
%% {ocsp_responder_certs, ocsp_responder_certs()} |
%% {ocsp_nonce, ocsp_nonce()}.
@@ -470,9 +475,11 @@
{honor_ecc_order, honor_ecc_order()} |
{client_renegotiation, client_renegotiation()}|
{session_tickets, server_session_tickets()} |
+ {stateless_tickets_seed, stateless_tickets_seed()} |
{anti_replay, anti_replay()} |
{cookie, cookie()} |
- {early_data, server_early_data()}.
+ {early_data, server_early_data()} |
+ {use_srtp, use_srtp()}.
-type server_cacerts() :: [public_key:der_encoded()] | [public_key:combined_cert()].
-type server_cafile() :: file:filename().
@@ -486,10 +493,11 @@
-type server_reuse_session() :: fun().
-type server_reuse_sessions() :: boolean().
-type sni_hosts() :: [{hostname(), [server_option() | common_option()]}].
--type sni_fun() :: fun().
+-type sni_fun() :: fun((string()) -> [] | undefined).
-type honor_cipher_order() :: boolean().
-type honor_ecc_order() :: boolean().
-type client_renegotiation() :: boolean().
+-type stateless_tickets_seed() :: binary().
-type cookie() :: boolean().
-type server_certificate_authorities() :: boolean().
%% -------------------------------------------------------------------------------------------------------
@@ -593,11 +601,11 @@ connect(Socket, SslOptions) ->
connect(Socket, SslOptions0, Timeout) when is_list(SslOptions0) andalso
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- CbInfo = handle_option_cb_info(SslOptions0, tls),
- Transport = element(1, CbInfo),
- try handle_options(Transport, Socket, SslOptions0, client, undefined) of
- {ok, Config} ->
- tls_socket:upgrade(Socket, Config, Timeout)
+ try
+ CbInfo = handle_option_cb_info(SslOptions0, tls),
+ Transport = element(1, CbInfo),
+ {ok, Config} = handle_options(Transport, Socket, SslOptions0, client, undefined),
+ tls_socket:upgrade(Socket, Config, Timeout)
catch
_:{error, Reason} ->
{error, Reason}
@@ -642,7 +650,7 @@ listen(_Port, []) ->
{error, nooptions};
listen(Port, Options0) ->
try
- {ok, Config} = handle_options(Options0, server),
+ {ok, Config} = handle_options(Options0, server, undefined),
do_listen(Port, Config, Config#config.connection_cb)
catch
Error = {error, _} ->
@@ -729,7 +737,7 @@ handshake(ListenSocket, SslOptions) ->
Reason :: closed | timeout | {options, any()} | error_alert().
handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or
- (Timeout == infinity)->
+ (Timeout == infinity)->
handshake(Socket, Timeout);
handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)->
@@ -751,19 +759,20 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
Error = {error, _Reason} -> Error
end;
handshake(Socket, SslOptions, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- CbInfo = handle_option_cb_info(SslOptions, tls),
- Transport = element(1, CbInfo),
- ConnetionCb = connection_cb(SslOptions),
- try handle_options(Transport, Socket, SslOptions, server, undefined) of
- {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
- ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
- {ok, Port} = tls_socket:port(Transport, Socket),
- {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts),
- ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
- {SslOpts,
- tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
- [{session_id_tracker, SessionIdHandle}]},
- self(), CbInfo, Timeout)
+ try
+ CbInfo = handle_option_cb_info(SslOptions, tls),
+ Transport = element(1, CbInfo),
+ ConnetionCb = connection_cb(SslOptions),
+ {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} =
+ handle_options(Transport, Socket, SslOptions, server, undefined),
+ ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()),
+ {ok, Port} = tls_socket:port(Transport, Socket),
+ {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts),
+ ssl_gen_statem:handshake(ConnetionCb, Port, Socket,
+ {SslOpts,
+ tls_socket:emulated_socket_options(EmOpts, #socket_options{}),
+ [{session_id_tracker, SessionIdHandle}]},
+ self(), CbInfo, Timeout)
catch
Error = {error, _Reason} -> Error
end.
@@ -1001,7 +1010,7 @@ negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
%%--------------------------------------------------------------------
-spec cipher_suites(Description, Version) -> ciphers() when
Description :: default | all | exclusive | anonymous | exclusive_anonymous,
- Version :: protocol_version().
+ Version :: protocol_version() | ssl_record:ssl_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
@@ -1010,17 +1019,17 @@ cipher_suites(Description, Version) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1 ->
- cipher_suites(Description, tls_record:protocol_version(Version));
+ cipher_suites(Description, tls_record:protocol_version_name(Version));
cipher_suites(Description, Version) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Description, dtls_record:protocol_version(Version));
+ cipher_suites(Description, dtls_record:protocol_version_name(Version));
cipher_suites(Description, Version) ->
[ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)].
%%--------------------------------------------------------------------
-spec cipher_suites(Description, Version, rfc | openssl) -> [string()] when
Description :: default | all | exclusive | anonymous,
- Version :: protocol_version().
+ Version :: protocol_version() | ssl_record:ssl_version().
%% Description: Returns all default and all supported cipher suites for a
%% TLS/DTLS version
@@ -1029,10 +1038,10 @@ cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'tlsv1.1';
Version == tlsv1 ->
- cipher_suites(Description, tls_record:protocol_version(Version), StringType);
+ cipher_suites(Description, tls_record:protocol_version_name(Version), StringType);
cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2';
Version == 'dtlsv1'->
- cipher_suites(Description, dtls_record:protocol_version(Version), StringType);
+ cipher_suites(Description, dtls_record:protocol_version_name(Version), StringType);
cipher_suites(Description, Version, rfc) ->
[ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
|| Suite <- supported_suites(Description, Version)];
@@ -1134,14 +1143,14 @@ eccs_filter_supported(Curves) ->
%% Description: returns all supported groups (TLS 1.3 and later)
%%--------------------------------------------------------------------
groups() ->
- tls_v1:groups(4).
+ tls_v1:groups().
%%--------------------------------------------------------------------
-spec groups(default) -> [group()].
%% Description: returns the default groups (TLS 1.3 and later)
%%--------------------------------------------------------------------
groups(default) ->
- tls_v1:default_groups(4).
+ tls_v1:default_groups().
%%--------------------------------------------------------------------
-spec getopts(SslSocket, OptionNames) ->
@@ -1328,8 +1337,8 @@ versions() ->
SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)],
SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)],
- AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version(Vsn))],
- AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version(Vsn))],
+ AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version_name(Vsn))],
+ AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version_name(Vsn))],
[{ssl_app, ?VSN},
{supported, SupportedTLSVsns},
@@ -1346,13 +1355,18 @@ versions() ->
%%
%% Description: Initiates a renegotiation.
%%--------------------------------------------------------------------
-renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid),
+renegotiate(#sslsocket{pid = [Pid, Sender |_]} = Socket) when is_pid(Pid),
is_pid(Sender) ->
- case tls_sender:renegotiate(Sender) of
- {ok, Write} ->
- tls_dtls_connection:renegotiation(Pid, Write);
- Error ->
- Error
+ case ssl:connection_information(Socket, [protocol]) of
+ {ok, [{protocol, 'tlsv1.3'}]} ->
+ {error, notsup};
+ _ ->
+ case tls_sender:renegotiate(Sender) of
+ {ok, Write} ->
+ tls_dtls_connection:renegotiation(Pid, Write);
+ Error ->
+ Error
+ end
end;
renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) ->
tls_dtls_connection:renegotiation(Pid);
@@ -1378,7 +1392,7 @@ update_keys(#sslsocket{pid = [Pid, Sender |_]}, Type0) when is_pid(Pid) andalso
read_write ->
update_requested
end,
- tls_connection_1_3:send_key_update(Sender, Type);
+ tls_gen_connection_1_3:send_key_update(Sender, Type);
update_keys(_, Type) ->
{error, {illegal_parameter, Type}}.
@@ -1418,9 +1432,9 @@ format_error({error, Reason}) ->
format_error(Reason) ->
do_format_error(Reason).
-tls_version({3, _} = Version) ->
+tls_version(Version) when ?TLS_1_X(Version) ->
Version;
-tls_version({254, _} = Version) ->
+tls_version(Version) when ?DTLS_1_X(Version) ->
dtls_v1:corresponding_tls_version(Version).
%%--------------------------------------------------------------------
@@ -1470,57 +1484,132 @@ str_to_suite(CipherSuiteName) ->
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
-supported_suites(exclusive, {3,Minor}) ->
- tls_v1:exclusive_suites(Minor);
-supported_suites(exclusive, {254, Minor}) ->
- dtls_v1:exclusive_suites(Minor);
+supported_suites(exclusive, Version) when ?TLS_1_X(Version) ->
+ tls_v1:exclusive_suites(Version);
+supported_suites(exclusive, Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:exclusive_suites(Version);
supported_suites(default, Version) ->
ssl_cipher:suites(Version);
supported_suites(all, Version) ->
ssl_cipher:all_suites(Version);
supported_suites(anonymous, Version) ->
ssl_cipher:anonymous_suites(Version);
-supported_suites(exclusive_anonymous, {3, Minor}) ->
- tls_v1:exclusive_anonymous_suites(Minor);
-supported_suites(exclusive_anonymous, {254, Minor}) ->
- dtls_v1:exclusive_anonymous_suites(Minor).
+supported_suites(exclusive_anonymous, Version) when ?TLS_1_X(Version) ->
+ tls_v1:exclusive_anonymous_suites(Version);
+supported_suites(exclusive_anonymous, Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:exclusive_anonymous_suites(Version).
do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_gen_connection) ->
tls_socket:listen(Transport, Port, Config);
do_listen(Port, Config, dtls_gen_connection) ->
dtls_socket:listen(Port, Config).
-
--spec handle_options([any()], client | server) -> {ok, #config{}};
- ([any()], ssl_options()) -> ssl_options().
-handle_options(Opts, Role) ->
- handle_options(undefined, undefined, Opts, Role, undefined).
-
-handle_options(Opts, Role, InheritedSslOpts) ->
- handle_options(undefined, undefined, Opts, Role, InheritedSslOpts).
+ssl_options() ->
+ [
+ alpn_advertised_protocols, alpn_preferred_protocols,
+ anti_replay,
+ beast_mitigation,
+ cacertfile, cacerts,
+ cert, certs_keys,certfile,
+ certificate_authorities,
+ ciphers,
+ client_renegotiation,
+ cookie,
+ crl_cache, crl_check,
+ customize_hostname_check,
+ depth,
+ dh, dhfile,
+
+ early_data,
+ eccs,
+ erl_dist,
+ fail_if_no_peer_cert,
+ fallback,
+ handshake,
+ hibernate_after,
+ honor_cipher_order, honor_ecc_order,
+ keep_secrets,
+ key, keyfile,
+ key_update_at,
+ ktls,
+
+ log_level,
+ max_handshake_size,
+ middlebox_comp_mode,
+ max_fragment_length,
+ next_protocol_selector, next_protocols_advertised,
+ ocsp_stapling, ocsp_responder_certs, ocsp_nonce,
+ padding_check,
+ partial_chain,
+ password,
+ protocol,
+ psk_identity,
+ receiver_spawn_opts,
+ renegotiate_at,
+ reuse_session, reuse_sessions,
+
+ secure_renegotiate,
+ sender_spawn_opts,
+ server_name_indication,
+ session_tickets,
+ stateless_tickets_seed,
+ signature_algs, signature_algs_cert,
+ sni_fun,
+ sni_hosts,
+ srp_identity,
+ supported_groups,
+ use_ticket,
+ use_srtp,
+ user_lookup_fun,
+ verify, verify_fun,
+ versions
+ ].
%% 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});
+-spec update_options([any()], client | server, map()) -> map().
+update_options(Opts, Role, InheritedSslOpts) when is_map(InheritedSslOpts) ->
+ {UserSslOpts, _} = split_options(Opts, ssl_options()),
+ process_options(UserSslOpts, InheritedSslOpts, #{role => Role}).
+
+process_options(UserSslOpts, SslOpts0, Env) ->
+ %% Reverse option list so we get the last set option if set twice,
+ %% users depend on it.
+ UserSslOptsMap = proplists:to_map(lists:reverse(UserSslOpts)),
+ SslOpts1 = opt_protocol_versions(UserSslOptsMap, SslOpts0, Env),
+ SslOpts2 = opt_verification(UserSslOptsMap, SslOpts1, Env),
+ SslOpts3 = opt_certs(UserSslOptsMap, SslOpts2, Env),
+ SslOpts4 = opt_tickets(UserSslOptsMap, SslOpts3, Env),
+ SslOpts5 = opt_ocsp(UserSslOptsMap, SslOpts4, Env),
+ SslOpts6 = opt_sni(UserSslOptsMap, SslOpts5, Env),
+ SslOpts7 = opt_signature_algs(UserSslOptsMap, SslOpts6, Env),
+ SslOpts8 = opt_alpn(UserSslOptsMap, SslOpts7, Env),
+ SslOpts9 = opt_mitigation(UserSslOptsMap, SslOpts8, Env),
+ SslOpts10 = opt_server(UserSslOptsMap, SslOpts9, Env),
+ SslOpts11 = opt_client(UserSslOptsMap, SslOpts10, Env),
+ SslOpts12 = opt_renegotiate(UserSslOptsMap, SslOpts11, Env),
+ SslOpts13 = opt_reuse_sessions(UserSslOptsMap, SslOpts12, Env),
+ SslOpts14 = opt_identity(UserSslOptsMap, SslOpts13, Env),
+ SslOpts15 = opt_supported_groups(UserSslOptsMap, SslOpts14, Env),
+ SslOpts16 = opt_crl(UserSslOptsMap, SslOpts15, Env),
+ SslOpts17 = opt_handshake(UserSslOptsMap, SslOpts16, Env),
+ SslOpts18 = opt_use_srtp(UserSslOptsMap, SslOpts17, Env),
+ SslOpts = opt_process(UserSslOptsMap, SslOpts18, Env),
+ SslOpts.
+
+-spec handle_options([any()], client | server, undefined|host()) -> {ok, #config{}}.
+handle_options(Opts, Role, Host) ->
+ handle_options(undefined, undefined, Opts, Role, Host).
+
%% Handle all options in listen, connect and handshake
handle_options(Transport, Socket, Opts0, Role, Host) ->
- {SslOpts0, SockOpts0} = expand_options(Opts0, ?RULES),
-
- %% Ensure all options are evaluated at startup
- SslOpts1 = add_missing_options(SslOpts0, ?RULES),
- SslOpts2 = #{protocol := Protocol}
- = process_options(SslOpts1,
- #{},
- #{role => Role,
- host => Host,
- rules => ?RULES}),
-
- maybe_client_warn_no_verify(SslOpts2, Role),
- SslOpts = maps:without([warn_verify_none], SslOpts2),
+ {UserSslOptsList, SockOpts0} = split_options(Opts0, ssl_options()),
+
+ Env = #{role => Role, host => Host},
+ SslOpts = process_options(UserSslOptsList, #{}, Env),
+
%% Handle special options
+ #{protocol := Protocol} = SslOpts,
{Sock, Emulated} = emulated_options(Transport, Socket, Protocol, SockOpts0),
ConnetionCb = connection_cb(Protocol),
CbInfo = handle_option_cb_info(Opts0, Protocol),
@@ -1535,811 +1624,367 @@ handle_options(Transport, Socket, Opts0, Role, Host) ->
}}.
-%% process_options(SSLOptions, OptionsMap, Env) where
-%% SSLOptions is the following tuple:
-%% {InOptions, SkippedOptions, Counter}
-%%
-%% 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.
+opt_protocol_versions(UserOpts, Opts, Env) ->
+ {_, PRC} = get_opt_of(protocol, [tls, dtls], tls, UserOpts, Opts),
+
+ LogLevels = [none, all, emergency, alert, critical, error,
+ warning, notice, info, debug],
-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,
- versions := Versions} = OptionsMap, #{rules := Rules}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]),
- case SessionTickets of
- stateless ->
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
- _ ->
- OptionsMap#{Option => default_value(Option, Rules)}
- end;
-handle_option(beast_mitigation = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(beast_mitigation = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-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 := Versions} = OptionsMap, #{rules := Rules}) ->
- Value = handle_cipher_option(default_value(Option, Rules), Versions),
- OptionsMap#{Option => Value};
-handle_option(ciphers = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- Value = handle_cipher_option(Value0, Versions),
- 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,
- #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_role(server_only, Role, Option, Value0),
- assert_option_dependency(Option, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(early_data = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets,
- versions := Versions} = OptionsMap,
- #{role := server = Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_option_dependency(Option, session_tickets, [SessionTickets],
- [stateful, stateless]),
- Value = validate_option(Option, Value0, Role),
- OptionsMap#{Option => Value};
-handle_option(early_data = Option, Value0, #{session_tickets := SessionTickets,
- use_ticket := UseTicket,
- versions := Versions} = OptionsMap,
- #{role := client = Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_option_dependency(Option, session_tickets, [SessionTickets],
- [manual, auto]),
- case UseTicket of
- undefined when SessionTickets =/= auto ->
- throw({error, {options, dependency, {Option, use_ticket}}});
- _ ->
- ok
- end,
- Value = validate_option(Option, Value0, Role),
- 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(certificate_authorities = Option, unbound, OptionsMap, #{role := server}) ->
- OptionsMap#{Option => true};
-handle_option(certificate_authorities = Option, unbound, OptionsMap, #{role := client}) ->
- OptionsMap#{Option => false};
-handle_option(certificate_authorities = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(cookie = Option, unbound, OptionsMap, #{role := Role}) ->
- Value = default_option_role(server, true, Role),
- OptionsMap#{Option => Value};
-handle_option(cookie = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- assert_role(server_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(key_update_at = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(log_level = Option, unbound, OptionsMap, _Env) ->
DefaultLevel = case logger:get_module_level(?MODULE) of
[] -> notice;
[{ssl,Level}] -> Level
end,
- Value = validate_option(Option, DefaultLevel),
- OptionsMap#{Option => Value};
-handle_option(next_protocols_advertised = Option, unbound, OptionsMap,
- #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(next_protocols_advertised = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(next_protocols_advertised, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- 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,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(client_preferred_next_protocols, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = make_next_protocol_selector(
- validate_option(client_preferred_next_protocols, Value0)),
- OptionsMap#{Option => Value};
-handle_option(padding_check = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(padding_check = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(password = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{password => Value};
-handle_option(password = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{password => Value};
-handle_option(certs_keys, unbound, OptionsMap, _Env) ->
- OptionsMap;
-handle_option(certs_keys = Option, Value0, OptionsMap, _Env) ->
- Value = validate_option(Option, Value0),
- OptionsMap#{certs_keys => Value};
-handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(psk_identity = Option, Value0, #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(secure_renegotiate = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(secure_renegotiate= Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(secure_renegotiate, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) ->
- Value =
- case Role of
- client ->
- undefined;
- server ->
- fun(_, _, _, _) -> true end
+
+ {_, LL} = get_opt_of(log_level, LogLevels, DefaultLevel, UserOpts, Opts),
+
+ Opts1 = set_opt_bool(keep_secrets, false, UserOpts, Opts),
+
+ {DistW, Dist} = get_opt_bool(erl_dist, false, UserOpts, Opts1),
+ option_incompatible(PRC =:= dtls andalso Dist, [{protocol, PRC}, {erl_dist, Dist}]),
+ Opts2 = set_opt_new(DistW, erl_dist, false, Dist, Opts1),
+
+ {KtlsW, Ktls} = get_opt_bool(ktls, false, UserOpts, Opts1),
+ option_incompatible(PRC =:= dtls andalso Ktls, [{protocol, PRC}, {ktls, Ktls}]),
+ Opts3 = set_opt_new(KtlsW, ktls, false, Ktls, Opts2),
+
+ opt_versions(UserOpts, Opts3#{protocol => PRC, log_level => LL}, Env).
+
+opt_versions(UserOpts, #{protocol := Protocol} = Opts, _Env) ->
+ Versions = case get_opt(versions, unbound, UserOpts, Opts) of
+ {default, unbound} -> default_versions(Protocol);
+ {new, Vs} -> validate_versions(Protocol, Vs);
+ {old, Vs} -> Vs
+ end,
+
+ {Where, MCM} = get_opt_bool(middlebox_comp_mode, true, UserOpts, Opts),
+ assert_version_dep(Where =:= new, middlebox_comp_mode, Versions, ['tlsv1.3']),
+ Opts1 = set_opt_new(Where, middlebox_comp_mode, true, MCM, Opts),
+ Opts1#{versions => Versions}.
+
+default_versions(tls) ->
+ Vsns0 = tls_record:supported_protocol_versions(),
+ lists:sort(fun tls_record:is_higher/2, Vsns0);
+default_versions(dtls) ->
+ Vsns0 = dtls_record:supported_protocol_versions(),
+ lists:sort(fun dtls_record:is_higher/2, Vsns0).
+
+validate_versions(tls, Vsns0) ->
+ Validate =
+ fun(Version) ->
+ try tls_record:sufficient_crypto_support(Version) of
+ true -> tls_record:protocol_version_name(Version);
+ false -> option_error(insufficient_crypto_support,
+ {Version, {versions, Vsns0}})
+ catch error:function_clause ->
+ option_error(Version, {versions, Vsns0})
+ end
end,
- OptionsMap#{Option => Value};
-handle_option(reuse_session = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(reuse_session, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- 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,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(reuse_sessions, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-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(session_tickets = Option, unbound, OptionsMap, #{role := Role,
- rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules), Role),
- OptionsMap#{Option => Value};
-handle_option(session_tickets = Option, Value0, #{versions := Versions} = OptionsMap, #{role := Role}) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0, Role),
- OptionsMap#{Option => Value};
-handle_option(signature_algs = Option, unbound, #{versions := [HighestVersion | _] = Versions} = OptionsMap, #{role := Role}) ->
- Value =
- handle_hashsigns_option(
- default_option_role_sign_algs(
- server,
- tls_v1:default_signature_algs(Versions),
- 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]}})
+ Vsns = [Validate(V) || V <- Vsns0],
+ tls_validate_version_gap(Vsns0),
+ option_error([] =:= Vsns, versions, Vsns0),
+ lists:sort(fun tls_record:is_higher/2, Vsns);
+validate_versions(dtls, Vsns0) ->
+ Validate =
+ fun(Version) ->
+ try tls_record:sufficient_crypto_support(
+ dtls_v1:corresponding_tls_version(
+ dtls_record:protocol_version_name(Version))) of
+ true -> dtls_record:protocol_version_name(Version);
+ false-> option_error(insufficient_crypto_support,
+ {Version, {versions, Vsns0}})
+ catch error:function_clause ->
+ option_error(Version, {versions, Vsns0})
+ end
end,
- OptionsMap#{Option => Value};
-handle_option(srp_identity = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(srp_identity = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(srp_identity, versions, Versions,
- ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- 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|_] = Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = handle_supported_groups_option(Value0, HighestVersion),
- OptionsMap#{Option => Value};
-handle_option(use_ticket = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(use_ticket = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1.3']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(user_lookup_fun = Option, unbound, OptionsMap, #{rules := Rules}) ->
- Value = validate_option(Option, default_value(Option, Rules)),
- OptionsMap#{Option => Value};
-handle_option(user_lookup_fun = Option, Value0,
- #{versions := Versions} = OptionsMap, _Env) ->
- assert_option_dependency(Option, versions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
- Value = validate_option(Option, Value0),
- OptionsMap#{Option => Value};
-handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) ->
- handle_verify_option(default_value(Option, Rules), OptionsMap#{warn_verify_none => true});
-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 ->
- OptionsMap#{Option => default_value(Option, Rules)};
-handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env)
- when Verify =:= verify_peer ->
- 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)
+ Vsns = [Validate(V) || V <- Vsns0],
+ option_error([] =:= Vsns, versions, Vsns0),
+ lists:sort(fun dtls_record:is_higher/2, Vsns).
+
+opt_verification(UserOpts, Opts0, #{role := Role} = Env) ->
+ {Verify, Opts1} =
+ case get_opt_of(verify, [verify_none, verify_peer], default_verify(Role), UserOpts, Opts0) of
+ {old, Val} ->
+ {Val, Opts0};
+ {_, verify_none} ->
+ {verify_none, Opts0#{verify => verify_none, verify_fun => {none_verify_fun(), []}}};
+ {_, verify_peer} ->
+ %% If 'verify' is changed from verify_none to verify_peer, (via update_options/3)
+ %% the 'verify_fun' must also be changed to undefined.
+ %% i.e remove verify_none fun
+ Temp = Opts0#{verify => verify_peer, verify_fun => undefined},
+ {verify_peer, maps:remove(fail_if_no_peer_cert, Temp)}
+ end,
+ Opts2 = opt_cacerts(UserOpts, Opts1, Env),
+ {_, PartialChain} = get_opt_fun(partial_chain, 1, fun(_) -> unknown_ca end, UserOpts, Opts2),
+
+ DefFailNoPeer = Role =:= server andalso Verify =:= verify_peer,
+ {_, FailNoPeerCert} = get_opt_bool(fail_if_no_peer_cert, DefFailNoPeer, UserOpts, Opts2),
+ assert_server_only(Role, FailNoPeerCert, fail_if_no_peer_cert),
+ option_incompatible(FailNoPeerCert andalso Verify =:= verify_none,
+ [{verify, verify_none}, {fail_if_no_peer_cert, true}]),
+
+ Opts = set_opt_int(depth, 0, 255, ?DEFAULT_DEPTH, UserOpts, Opts2),
+
+ case Role of
+ client ->
+ opt_verify_fun(UserOpts, Opts#{partial_chain => PartialChain},
+ Env);
+ server ->
+ opt_verify_fun(UserOpts, Opts#{partial_chain => PartialChain,
+ fail_if_no_peer_cert => FailNoPeerCert},
+ Env)
end.
+default_verify(client) ->
+ %% Server authenication is by default requiered
+ verify_peer;
+default_verify(server) ->
+ %% Client certification is an optional part of the protocol
+ verify_none.
+
+opt_verify_fun(UserOpts, Opts, _Env) ->
+ %%DefVerifyNoneFun = {default_verify_fun(), []},
+ VerifyFun = case get_opt(verify_fun, undefined, UserOpts, Opts) of
+ {_, {F,_} = FA} when is_function(F, 3); is_function(F, 4) ->
+ FA;
+ {_, UserFun} when is_function(UserFun, 1) ->
+ {convert_verify_fun(), UserFun};
+ {_, undefined} ->
+ undefined;
+ {_, Value} ->
+ option_error(verify_fun, Value)
+ end,
+ Opts#{verify_fun => VerifyFun}.
+
+none_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.
+
+convert_verify_fun() ->
+ fun(_,{bad_cert, _} = Reason, OldFun) ->
+ case OldFun([Reason]) of
+ true -> {valid, OldFun};
+ false -> {fail, Reason}
+ end;
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ 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).
+opt_certs(UserOpts, #{log_level := LogLevel} = Opts0, Env) ->
+ case get_opt_list(certs_keys, [], UserOpts, Opts0) of
+ {Where, []} when Where =/= new ->
+ opt_old_certs(UserOpts, #{}, Opts0, Env);
+ {old, [CertKey]} ->
+ opt_old_certs(UserOpts, CertKey, Opts0, Env);
+ {Where, CKs} when is_list(CKs) ->
+ warn_override(Where, UserOpts, certs_keys, [cert,certfile,key,keyfile,password], LogLevel),
+ Opts0#{certs_keys => [check_cert_key(CK, #{}, LogLevel) || CK <- CKs]}
+ end.
+opt_old_certs(UserOpts, CertKeys, #{log_level := LogLevel}=SSLOpts, _Env) ->
+ CK = check_cert_key(UserOpts, CertKeys, LogLevel),
+ case maps:keys(CK) =:= [] of
+ true ->
+ SSLOpts#{certs_keys => []};
+ false ->
+ SSLOpts#{certs_keys => [CK]}
+ end.
-expand_options(Opts0, Rules) ->
- Opts1 = proplists:expand([{binary, [{mode, binary}]},
- {list, [{mode, list}]}], Opts0),
- Opts2 = handle_option_format(Opts1, []),
+check_cert_key(UserOpts, CertKeys, LogLevel) ->
+ CertKeys0 = case get_opt(cert, undefined, UserOpts, CertKeys) of
+ {Where, Cert} when is_binary(Cert) ->
+ warn_override(Where, UserOpts, cert, [certfile], LogLevel),
+ CertKeys#{cert => [Cert]};
+ {Where, [C0|_] = Certs} when is_binary(C0) ->
+ warn_override(Where, UserOpts, cert, [certfile], LogLevel),
+ CertKeys#{cert => Certs};
+ {new, Err0} ->
+ option_error(cert, Err0);
+ {_, undefined} ->
+ case get_opt_file(certfile, unbound, UserOpts, CertKeys) of
+ {default, unbound} -> CertKeys;
+ {_, CertFile} -> CertKeys#{certfile => CertFile}
+ end
+ end,
- %% Remove deprecated ssl_imp option
- Opts = proplists:delete(ssl_imp, Opts2),
- 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
-
- SslOpts0 = Opts -- SockOpts,
- SslOpts = {SslOpts0, [], length(SslOpts0)},
- {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)}.
+ CertKeys1 = case get_opt(key, undefined, UserOpts, CertKeys) of
+ {_, undefined} ->
+ case get_opt_file(keyfile, <<>>, UserOpts, CertKeys) of
+ {new, KeyFile} ->
+ CertKeys0#{keyfile => KeyFile};
+ {_, <<>>} ->
+ case maps:get(certfile, CertKeys0, unbound) of
+ unbound -> CertKeys0;
+ CF -> CertKeys0#{keyfile => CF}
+ end;
+ {old, _} ->
+ CertKeys0
+ end;
+ {_, {KF, K0} = Key}
+ when is_binary(K0), KF =:= rsa; KF =:= dsa;
+ KF == 'RSAPrivateKey'; KF == 'DSAPrivateKey';
+ KF == 'ECPrivateKey'; KF == 'PrivateKeyInfo' ->
+ CertKeys0#{key => Key};
+ {_, #{engine := _, key_id := _, algorithm := _} = Key} ->
+ CertKeys0#{key => Key};
+ {new, Err1} ->
+ option_error(key, Err1)
+ end,
-default_value(Key, Rules) ->
- {Default, _} = maps:get(Key, Rules, {undefined, []}),
- Default.
+ CertKeys2 = case get_opt(password, unbound, UserOpts,CertKeys) of
+ {default, _} -> CertKeys1;
+ {_, Pwd} when is_binary(Pwd); is_list(Pwd) ->
+ CertKeys1#{password => fun() -> Pwd end};
+ {_, Pwd} when is_function(Pwd, 0) ->
+ CertKeys1#{password => Pwd};
+ {_, Err2} ->
+ option_error(password, Err2)
+ end,
+ CertKeys2.
+
+opt_cacerts(UserOpts, #{verify := Verify, log_level := LogLevel, versions := Versions} = Opts,
+ #{role := Role}) ->
+ {_, CaCerts} = get_opt_list(cacerts, undefined, UserOpts, Opts),
+
+ CaCertFile = case get_opt_file(cacertfile, <<>>, UserOpts, Opts) of
+ {Where1, _FileName} when CaCerts =/= undefined ->
+ warn_override(Where1, UserOpts, cacerts, [cacertfile], LogLevel),
+ <<>>;
+ {new, FileName} -> unambiguous_path(FileName);
+ {_, FileName} -> FileName
+ end,
+ option_incompatible(CaCertFile =:= <<>> andalso CaCerts =:= undefined andalso Verify =:= verify_peer,
+ [{verify, verify_peer}, {cacerts, undefined}]),
+
+ {Where2, CA} = get_opt_bool(certificate_authorities, Role =:= server, UserOpts, Opts),
+ assert_version_dep(Where2 =:= new, certificate_authorities, Versions, ['tlsv1.3']),
+
+ Opts1 = set_opt_new(new, cacertfile, <<>>, CaCertFile, Opts),
+ Opts2 = set_opt_new(Where2, certificate_authorities, Role =:= server, CA, Opts1),
+ Opts2#{cacerts => CaCerts}.
+
+opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {_, SessionTickets} = get_opt_of(session_tickets, [disabled,manual,auto], disabled, UserOpts, Opts),
+ assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']),
+
+ {_, UseTicket} = get_opt_list(use_ticket, undefined, UserOpts, Opts),
+ option_error(UseTicket =:= [], use_ticket, UseTicket),
+ option_incompatible(UseTicket =/= undefined andalso SessionTickets =/= manual,
+ [{use_ticket, UseTicket}, {session_tickets, SessionTickets}]),
+
+ {_, EarlyData} = get_opt_bin(early_data, undefined, UserOpts, Opts),
+ option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= disabled,
+ [early_data, {session_tickets, disabled}]),
+ option_incompatible(is_binary(EarlyData) andalso SessionTickets =:= manual andalso UseTicket =:= undefined,
+ [early_data, {session_tickets, manual}, {use_ticket, undefined}]),
+
+ assert_server_only(anti_replay, UserOpts),
+ assert_server_only(stateless_tickets_seed, UserOpts),
+ Opts#{session_tickets => SessionTickets, use_ticket => UseTicket, early_data => EarlyData};
+opt_tickets(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
+ {_, SessionTickets} =
+ get_opt_of(session_tickets,
+ [disabled, stateful, stateless, stateful_with_cert, stateless_with_cert],
+ disabled,
+ UserOpts,
+ Opts),
+ assert_version_dep(SessionTickets =/= disabled, session_tickets, Versions, ['tlsv1.3']),
+
+ {_, EarlyData} = get_opt_of(early_data, [enabled, disabled], disabled, UserOpts, Opts),
+ option_incompatible(SessionTickets =:= disabled andalso EarlyData =:= enabled,
+ [early_data, {session_tickets, disabled}]),
+
+ Stateless = lists:member(SessionTickets, [stateless, stateless_with_cert]),
+
+ AntiReplay =
+ case get_opt(anti_replay, undefined, UserOpts, Opts) of
+ {_, undefined} -> undefined;
+ {_,AR} when not Stateless ->
+ option_incompatible([{anti_replay, AR}, {session_tickets, SessionTickets}]);
+ {_,'10k'} -> {10, 5, 72985}; %% n = 10000 p = 0.030003564 (1 in 33) m = 72985 (8.91KiB) k = 5
+ {_,'100k'} -> {10, 5, 729845}; %% n = 10000 p = 0.03000428 (1 in 33) m = 729845 (89.09KiB) k = 5
+ {_, {_,_,_} = AR} -> AR;
+ {_, AR} -> option_error(anti_replay, AR)
+ end,
+ {_, STS} = get_opt_bin(stateless_tickets_seed, undefined, UserOpts, Opts),
+ option_incompatible(STS =/= undefined andalso not Stateless,
+ [stateless_tickets_seed, {session_tickets, SessionTickets}]),
-assert_role(client_only, client, _, _) ->
- ok;
-assert_role(server_only, server, _, _) ->
- ok;
-assert_role(client_only, _, _, undefined) ->
- ok;
-assert_role(server_only, _, _, undefined) ->
- ok;
-assert_role(Type, _, Key, _) ->
- throw({error, {option, Type, Key}}).
+ assert_client_only(use_ticket, UserOpts),
+ Opts#{session_tickets => SessionTickets, early_data => EarlyData,
+ anti_replay => AntiReplay, stateless_tickets_seed => STS}.
-assert_option_dependency(Option, OptionDep, Values0, AllowedValues) ->
- case is_dtls_configured(Values0) of
+opt_ocsp(UserOpts, #{versions := _Versions} = Opts, #{role := Role}) ->
+ {Stapling, SMap} =
+ case get_opt(ocsp_stapling, ?DEFAULT_OCSP_STAPLING, UserOpts, Opts) of
+ {old, Map} when is_map(Map) -> {true, Map};
+ {_, Bool} when is_boolean(Bool) -> {Bool, #{}};
+ {_, Value} -> option_error(ocsp_stapling, Value)
+ end,
+ assert_client_only(Role, Stapling, ocsp_stapling),
+ {_, Nonce} = get_opt_bool(ocsp_nonce, ?DEFAULT_OCSP_NONCE, UserOpts, SMap),
+ option_incompatible(Stapling =:= false andalso Nonce =:= false,
+ [{ocsp_nonce, false}, {ocsp_stapling, false}]),
+ {_, ORC} = get_opt_list(ocsp_responder_certs, ?DEFAULT_OCSP_RESPONDER_CERTS,
+ UserOpts, SMap),
+ CheckBinary = fun(Cert) when is_binary(Cert) -> ok;
+ (_Cert) -> option_error(ocsp_responder_certs, ORC)
+ end,
+ [CheckBinary(C) || C <- ORC],
+ option_incompatible(Stapling =:= false andalso ORC =/= [],
+ [ocsp_responder_certs, {ocsp_stapling, false}]),
+ case Stapling of
true ->
- %% TODO: Check option dependency for DTLS
- ok;
+ Opts#{ocsp_stapling =>
+ #{ocsp_nonce => Nonce,
+ ocsp_responder_certs => ORC}};
false ->
- %% special handling for version
- Values =
- case OptionDep of
- versions ->
- lists:map(fun tls_record:protocol_version/1, Values0);
- _ ->
- Values0
- end,
- Set1 = sets:from_list(Values),
- Set2 = sets:from_list(AllowedValues),
- case sets:size(sets:intersection(Set1, Set2)) > 0 of
- true ->
- ok;
- false ->
- throw({error, {options, dependency,
- {Option, {OptionDep, AllowedValues}}}})
- end
+ Opts
end.
-is_dtls_configured(Versions) ->
- Fun = fun (Version) when Version =:= {254, 253} orelse
- Version =:= {254, 255} ->
- true;
- (_) ->
- false
- end,
- lists:any(Fun, Versions).
-
-validate_option(Option, Value) ->
- validate_option(Option, Value, undefined).
-%%
-validate_option(Opt, Value, _)
- when Opt =:= alpn_advertised_protocols orelse
- Opt =:= alpn_preferred_protocols,
- is_list(Value) ->
- validate_binary_list(Opt, Value),
- Value;
-validate_option(Opt, Value, _)
- when Opt =:= alpn_advertised_protocols orelse
- Opt =:= alpn_preferred_protocols,
- Value =:= undefined ->
- undefined;
-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(beast_mitigation, Value, _)
- when Value == one_n_minus_one orelse
- Value == zero_n orelse
- Value == disabled ->
- Value;
-%% certfile must be present in some cases otherwise it can be set
-%% to the empty string.
-validate_option(cacertfile, undefined, _) ->
- <<>>;
-validate_option(cacertfile, Value, _)
- when is_binary(Value) ->
- unambiguous_path(Value);
-validate_option(cacertfile, Value, _)
- when is_list(Value), Value =/= ""->
- binary_filename(unambiguous_path(Value));
-validate_option(cacerts, Value, _)
- when Value == undefined;
- is_list(Value) ->
- Value;
-validate_option(cb_info, {V1, V2, V3, V4} = Value, _)
- when is_atom(V1),
- is_atom(V2),
- is_atom(V3),
- is_atom(V4) ->
- 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) ->
- Value;
-validate_option(cert, Value, _) when Value == undefined;
- is_list(Value)->
- Value;
-validate_option(cert, Value, _) when Value == undefined;
- is_binary(Value)->
- [Value];
-validate_option(certificate_authorities, Value, _) when is_boolean(Value)->
- Value;
-validate_option(certfile, undefined = Value, _) ->
- Value;
-validate_option(certfile, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(certfile, Value, _)
- when is_list(Value) ->
- binary_filename(Value);
-validate_option(client_preferred_next_protocols, {Precedence, PreferredProtocols}, _)
- when is_list(PreferredProtocols) ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- {Precedence, PreferredProtocols, ?NO_PROTOCOL};
-validate_option(client_preferred_next_protocols,
- {Precedence, PreferredProtocols, Default} = Value, _)
- when is_list(PreferredProtocols), is_binary(Default),
- byte_size(Default) > 0, byte_size(Default) < 256 ->
- validate_binary_list(client_preferred_next_protocols, PreferredProtocols),
- validate_npn_ordering(Precedence),
- Value;
-validate_option(client_preferred_next_protocols, undefined, _) ->
- undefined;
-validate_option(client_renegotiation, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(cookie, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(crl_cache, {Cb, {_Handle, Options}} = Value, _)
- when is_atom(Cb) and is_list(Options) ->
- Value;
-validate_option(crl_check, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(crl_check, Value, _)
- when (Value == best_effort) or
- (Value == peer) ->
- Value;
-validate_option(customize_hostname_check, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(depth, Value, _)
- when is_integer(Value),
- Value >= 0, Value =< 255->
- Value;
-validate_option(dh, Value, _)
- when Value == undefined;
- is_binary(Value) ->
- Value;
-validate_option(dhfile, undefined = Value, _) ->
- Value;
-validate_option(dhfile, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(dhfile, Value, _)
- when is_list(Value), Value =/= "" ->
- binary_filename(Value);
-validate_option(early_data, Value, server)
- when Value =:= disabled orelse
- Value =:= enabled ->
- Value;
-validate_option(early_data = Option, Value, server) ->
- throw({error,
- {options, role, {Option, {Value, {server, [disabled, enabled]}}}}});
-validate_option(early_data, Value, client)
- when is_binary(Value) ->
- Value;
-validate_option(early_data = Option, Value, client) ->
- throw({error,
- {options, type, {Option, {Value, not_binary}}}});
-validate_option(erl_dist, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(fail_if_no_peer_cert, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(fallback, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(handshake, hello = Value, _) ->
- Value;
-validate_option(handshake, full = Value, _) ->
- Value;
-validate_option(hibernate_after, undefined, _) -> %% Backwards compatibility
- infinity;
-validate_option(hibernate_after, infinity, _) ->
- infinity;
-validate_option(hibernate_after, Value, _)
- when is_integer(Value), Value >= 0 ->
- Value;
-validate_option(honor_cipher_order, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(honor_ecc_order, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(keep_secrets, Value, _) when is_boolean(Value) ->
- Value;
-validate_option(key, undefined, _) ->
- undefined;
-validate_option(key, {KeyType, Value}, _)
- when is_binary(Value),
- KeyType == rsa; %% Backwards compatibility
- KeyType == dsa; %% Backwards compatibility
- KeyType == 'RSAPrivateKey';
- KeyType == 'DSAPrivateKey';
- KeyType == 'ECPrivateKey';
- KeyType == 'PrivateKeyInfo' ->
- {KeyType, Value};
-validate_option(key, #{algorithm := _} = Value, _) ->
- Value;
-validate_option(keyfile, undefined, _) ->
- <<>>;
-validate_option(keyfile, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(keyfile, Value, _)
- when is_list(Value), Value =/= "" ->
- binary_filename(Value);
-validate_option(key_update_at, Value, _)
- when is_integer(Value) andalso
- Value > 0 ->
- Value;
-validate_option(log_level, Value, _) when
- is_atom(Value) andalso
- (Value =:= none orelse
- Value =:= all orelse
- Value =:= emergency orelse
- Value =:= alert orelse
- Value =:= critical orelse
- Value =:= error orelse
- Value =:= warning orelse
- Value =:= notice orelse
- Value =:= info orelse
- Value =:= debug) ->
- Value;
-%% RFC 6066, Section 4
-validate_option(max_fragment_length, I, _)
- when I == ?MAX_FRAGMENT_LENGTH_BYTES_1;
- I == ?MAX_FRAGMENT_LENGTH_BYTES_2;
- I == ?MAX_FRAGMENT_LENGTH_BYTES_3;
- I == ?MAX_FRAGMENT_LENGTH_BYTES_4 ->
- I;
-validate_option(max_fragment_length, undefined, _) ->
- undefined;
-validate_option(max_handshake_size, Value, _)
- when is_integer(Value) andalso
- Value =< ?MAX_UNIT24 ->
- Value;
-validate_option(middlebox_comp_mode, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(next_protocols_advertised, Value, _) when is_list(Value) ->
- validate_binary_list(next_protocols_advertised, Value),
- Value;
-validate_option(next_protocols_advertised, undefined, _) ->
- undefined;
-validate_option(ocsp_nonce, Value, _)
- when Value =:= true orelse
- Value =:= false ->
- Value;
-%% The OCSP responders' certificates can be given as a suggestion and
-%% will be used to verify the OCSP response.
-validate_option(ocsp_responder_certs, Value, _)
- when is_list(Value) ->
- [public_key:pkix_decode_cert(CertDer, plain) || CertDer <- Value,
- is_binary(CertDer)];
-validate_option(ocsp_stapling, Value, _)
- when Value =:= true orelse
- Value =:= false ->
- Value;
-validate_option(padding_check, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(partial_chain, Value, _)
- when is_function(Value) ->
- Value;
-validate_option(password, Value, _)
- when is_list(Value); is_binary(Value) ->
- Value;
-validate_option(password, Value, _)
- when is_function(Value, 0) ->
- Value;
-validate_option(certs_keys, Value, _) when is_list(Value) ->
- Value;
-validate_option(protocol, Value = tls, _) ->
- Value;
-validate_option(protocol, Value = dtls, _) ->
- Value;
-validate_option(psk_identity, undefined, _) ->
- undefined;
-validate_option(psk_identity, Identity, _)
- when is_list(Identity), Identity =/= "", length(Identity) =< 65535 ->
- binary_filename(Identity);
-validate_option(receiver_spawn_opts, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(renegotiate_at, Value, _) when is_integer(Value) ->
- erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
-validate_option(reuse_session, undefined, _) ->
- undefined;
-validate_option(reuse_session, Value, _)
- when is_function(Value) ->
- Value;
-validate_option(reuse_session, Value, _)
- when is_binary(Value) ->
- Value;
-validate_option(reuse_session, {Id, Data} = Value, _)
- when is_binary(Id) andalso
- is_binary(Data) ->
- Value;
-validate_option(reuse_sessions, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(reuse_sessions, save = Value, _) ->
- Value;
-validate_option(secure_renegotiate, Value, _)
- when is_boolean(Value) ->
- Value;
-validate_option(sender_spawn_opts, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(server_name_indication, Value, _)
- when is_list(Value) ->
+opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := server}) ->
+ {_, SniHosts} = get_opt_list(sni_hosts, [], UserOpts, Opts),
+ %% Postpone option checking until all other options are checked FIXME
+ Check = fun({[_|_], SO}) when is_list(SO) ->
+ case proplists:get_value(sni_hosts, SO, undefined) of
+ undefined -> ok;
+ Recursive -> option_error(sni_hosts, Recursive)
+ end;
+ (HostOpts) -> option_error(sni_hosts, HostOpts)
+ end,
+ [Check(E) || E <- SniHosts],
+
+ {Where, SniFun0} = get_opt_fun(sni_fun, 1, undefined, UserOpts, Opts),
+
+ option_incompatible(is_function(SniFun0) andalso SniHosts =/= [] andalso Where =:= new,
+ [sni_fun, sni_hosts]),
+ assert_client_only(server_name_indication, UserOpts),
+
+ SniFun = case SniFun0 =:= undefined of
+ true -> fun(Host) -> proplists:get_value(Host, SniHosts) end;
+ false -> SniFun0
+ end,
+
+ Opts#{sni_fun => SniFun};
+opt_sni(UserOpts, #{versions := _Versions} = Opts, #{role := client} = Env) ->
%% RFC 6066, Section 3: Currently, the only server names supported are
%% DNS hostnames
%% case inet_parse:domain(Value) of
@@ -2351,195 +1996,572 @@ validate_option(server_name_indication, Value, _)
%%
%% But the definition seems very diffuse, so let all strings through
%% and leave it up to public_key to decide...
- Value;
-validate_option(server_name_indication, undefined, _) ->
- undefined;
-validate_option(server_name_indication, disable, _) ->
- disable;
-validate_option(session_tickets, Value, server)
- when Value =:= disabled orelse
- Value =:= stateful orelse
- Value =:= stateless ->
- Value;
-validate_option(session_tickets, Value, server) ->
- throw({error,
- {options, role,
- {session_tickets,
- {Value, {server, [disabled, stateful, stateless]}}}}});
-validate_option(session_tickets, Value, client)
- when Value =:= disabled orelse
- Value =:= manual orelse
- Value =:= auto ->
- Value;
-validate_option(session_tickets, Value, client) ->
- throw({error,
- {options, role,
- {session_tickets,
- {Value, {client, [disabled, manual, auto]}}}}});
-validate_option(sni_fun, undefined, _) ->
- undefined;
-validate_option(sni_fun, Fun, _)
- when is_function(Fun) ->
- Fun;
-validate_option(sni_hosts, [], _) ->
- [];
-validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail], _)
- when is_list(Hostname) ->
- RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined),
- case RecursiveSNIOptions of
- undefined ->
- [{Hostname, validate_options(SSLOptions)} |
- validate_option(sni_hosts, Tail)];
- _ ->
- throw({error, {options, {sni_hosts, RecursiveSNIOptions}}})
- end;
-validate_option(srp_identity, undefined, _) ->
- undefined;
-validate_option(srp_identity, {Username, Password}, _)
- when is_list(Username),
- is_list(Password), Username =/= "",
- length(Username) =< 255 ->
- {unicode:characters_to_binary(Username),
- unicode:characters_to_binary(Password)};
-validate_option(user_lookup_fun, undefined, _) ->
- undefined;
-validate_option(user_lookup_fun, {Fun, _} = Value, _)
- when is_function(Fun, 3) ->
- Value;
-validate_option(use_ticket, Value, _)
- when is_list(Value) ->
- Value;
-validate_option(verify, Value, _)
- when Value == verify_none; Value == verify_peer ->
- Value;
-validate_option(verify_fun, undefined, _) ->
- undefined;
-%% Backwards compatibility
-validate_option(verify_fun, Fun, _) when is_function(Fun) ->
- {fun(_,{bad_cert, _} = Reason, OldFun) ->
- case OldFun([Reason]) of
- true ->
- {valid, OldFun};
- false ->
- {fail, Reason}
- end;
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, Fun};
-validate_option(verify_fun, {Fun, _} = Value, _) when is_function(Fun) ->
- Value;
-validate_option(versions, Versions, _) ->
- validate_versions(Versions, Versions);
-validate_option(Opt, undefined = Value, _) ->
- AllOpts = maps:keys(?RULES),
- case lists:member(Opt, AllOpts) of
- true ->
- Value;
- false ->
- throw({error, {options, {Opt, Value}}})
+ SNI = case get_opt(server_name_indication, unbound, UserOpts, Opts) of
+ {_, unbound} -> server_name_indication_default(maps:get(host, Env, undefined));
+ {_, [_|_] = SN} -> SN;
+ {_, disable} -> disable;
+ {_, SN} -> option_error(server_name_indication, SN)
+ end,
+ assert_server_only(sni_fun, UserOpts),
+ assert_server_only(sni_hosts, UserOpts),
+ Opts#{server_name_indication => SNI}.
+
+server_name_indication_default(Host) when is_list(Host) ->
+ %% SNI should not contain a trailing dot that a hostname may
+ string:strip(Host, right, $.);
+server_name_indication_default(_) ->
+ undefined.
+
+opt_signature_algs(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ [TlsVersion|_] = TlsVsns = [tls_version(V) || V <- Versions],
+ SA = case get_opt_list(signature_algs, undefined, UserOpts, Opts) of
+ {default, undefined} when ?TLS_GTE(TlsVersion, ?TLS_1_2) ->
+ DefAlgs = tls_v1:default_signature_algs(TlsVsns),
+ handle_hashsigns_option(DefAlgs, TlsVersion);
+ {new, Algs} ->
+ assert_version_dep(signature_algs, Versions, ['tlsv1.2', 'tlsv1.3']),
+ SA0 = handle_hashsigns_option(Algs, TlsVersion),
+ option_error(SA0 =:= [], no_supported_algorithms, {signature_algs, Algs}),
+ SA0;
+ {_, Algs} ->
+ Algs
+ end,
+ SAC = case get_opt_list(signature_algs_cert, undefined, UserOpts, Opts) of
+ {new, Schemes} ->
+ %% Do not send by default
+ assert_version_dep(signature_algs_cert, Versions, ['tlsv1.2', 'tlsv1.3']),
+ SAC0 = handle_signature_algorithms_option(Schemes, TlsVersion),
+ option_error(SAC0 =:= [], no_supported_signature_schemes, {signature_algs_cert, Schemes}),
+ SAC0;
+ {_, Schemes} ->
+ Schemes
+ end,
+ Opts#{signature_algs => SA, signature_algs_cert => SAC}.
+
+opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
+ {_, APP} = get_opt_list(alpn_preferred_protocols, undefined, UserOpts, Opts),
+ validate_protocols(is_list(APP), alpn_preferred_protocols, APP),
+
+ {Where, NPA} = get_opt_list(next_protocols_advertised, undefined, UserOpts, Opts),
+ validate_protocols(is_list(NPA), next_protocols_advertised, NPA),
+ assert_version_dep(is_list(NPA), next_protocols_advertised, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ assert_client_only(alpn_advertised_protocols, UserOpts),
+ assert_client_only(client_preferred_next_protocols, UserOpts),
+
+ Opts1 = set_opt_new(Where, next_protocols_advertised, undefined, NPA, Opts),
+ Opts1#{alpn_preferred_protocols => APP};
+opt_alpn(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {_, AAP} = get_opt_list(alpn_advertised_protocols, undefined, UserOpts, Opts),
+ validate_protocols(is_list(AAP), alpn_advertised_protocols, AAP),
+
+ {Where, NPS} = case get_opt(client_preferred_next_protocols, undefined, UserOpts, Opts) of
+ {new, CPNP} ->
+ assert_version_dep(client_preferred_next_protocols,
+ Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ {new, make_next_protocol_selector(CPNP)};
+ CPNP ->
+ CPNP
+ end,
+
+ validate_protocols(is_list(NPS), client_preferred_next_protocols, NPS),
+
+ assert_server_only(alpn_preferred_protocols, UserOpts),
+ assert_server_only(next_protocols_advertised, UserOpts),
+
+ Opts1 = set_opt_new(Where, next_protocol_selector, undefined, NPS, Opts),
+ Opts1#{alpn_advertised_protocols => AAP}.
+
+validate_protocols(false, _Opt, _List) -> ok;
+validate_protocols(true, Opt, List) ->
+ Check = fun(Bin) ->
+ IsOK = is_binary(Bin) andalso byte_size(Bin) > 0 andalso byte_size(Bin) < 256,
+ option_error(not IsOK, Opt, {invalid_protocol, Bin})
+ end,
+ lists:foreach(Check, List).
+
+opt_mitigation(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ DefBeast = case ?TLS_GT(lists:last(Versions), ?TLS_1_0) of
+ true -> disabled;
+ false -> one_n_minus_one
+ end,
+ {Where1, BM} = get_opt_of(beast_mitigation, [disabled, one_n_minus_one, zero_n], DefBeast, UserOpts, Opts),
+ assert_version_dep(Where1 =:= new, beast_mitigation, Versions, ['tlsv1']),
+
+ {Where2, PC} = get_opt_bool(padding_check, true, UserOpts, Opts),
+ assert_version_dep(Where2 =:= new, padding_check, Versions, ['tlsv1']),
+
+ %% Use 'new' we need to check for non default 'one_n_minus_one'
+ Opts1 = set_opt_new(new, beast_mitigation, disabled, BM, Opts),
+ set_opt_new(Where2, padding_check, true, PC, Opts1).
+
+opt_server(UserOpts, #{versions := Versions, log_level := LogLevel} = Opts, #{role := server}) ->
+ {_, ECC} = get_opt_bool(honor_ecc_order, false, UserOpts, Opts),
+
+ {_, Cipher} = get_opt_bool(honor_cipher_order, false, UserOpts, Opts),
+
+ {Where1, Cookie} = get_opt_bool(cookie, true, UserOpts, Opts),
+ assert_version_dep(Where1 =:= new, cookie, Versions, ['tlsv1.3']),
+
+ {Where2, ReNeg} = get_opt_bool(client_renegotiation, true, UserOpts, Opts),
+ assert_version_dep(Where2 =:= new, client_renegotiation, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ Opts1 = case get_opt(dh, undefined, UserOpts, Opts) of
+ {Where, DH} when is_binary(DH) ->
+ warn_override(Where, UserOpts, dh, [dhfile], LogLevel),
+ Opts#{dh => DH};
+ {new, DH} ->
+ option_error(dh, DH);
+ {_, undefined} ->
+ case get_opt_file(dhfile, unbound, UserOpts, Opts) of
+ {default, unbound} -> Opts;
+ {_, DHFile} -> Opts#{dhfile => DHFile}
+ end
+ end,
+
+ Opts1#{honor_ecc_order => ECC, honor_cipher_order => Cipher,
+ cookie => Cookie, client_renegotiation => ReNeg};
+opt_server(UserOpts, Opts, #{role := client}) ->
+ assert_server_only(honor_ecc_order, UserOpts),
+ assert_server_only(honor_cipher_order, UserOpts),
+ assert_server_only(cookie, UserOpts),
+ assert_server_only(client_renegotiation, UserOpts),
+ assert_server_only(dh, UserOpts),
+ assert_server_only(dhfile, UserOpts),
+ Opts.
+
+opt_client(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {Where, FB} = get_opt_bool(fallback, false, UserOpts, Opts),
+ assert_version_dep(Where =:= new, fallback, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ {_, CHC} = get_opt_list(customize_hostname_check, [], UserOpts, Opts),
+
+ ValidMFL = [undefined, ?MAX_FRAGMENT_LENGTH_BYTES_1, ?MAX_FRAGMENT_LENGTH_BYTES_2, %% RFC 6066, Section 4
+ ?MAX_FRAGMENT_LENGTH_BYTES_3, ?MAX_FRAGMENT_LENGTH_BYTES_4],
+ {_, MFL} = get_opt_of(max_fragment_length, ValidMFL, undefined, UserOpts, Opts),
+
+ Opts#{fallback => FB, customize_hostname_check => CHC, max_fragment_length => MFL};
+opt_client(UserOpts, Opts, #{role := server}) ->
+ assert_client_only(fallback, UserOpts),
+ assert_client_only(customize_hostname_check, UserOpts),
+ assert_client_only(max_fragment_length, UserOpts),
+ Opts#{customize_hostname_check => []}.
+
+opt_renegotiate(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ {Where1, KUA} = get_opt_pos_int(key_update_at, ?KEY_USAGE_LIMIT_AES_GCM, UserOpts, Opts),
+ assert_version_dep(Where1 =:= new, key_update_at, Versions, ['tlsv1.3']),
+
+ %% Undocumented, old ?
+ {_, RA0} = get_opt_pos_int(renegotiate_at, ?DEFAULT_RENEGOTIATE_AT, UserOpts, Opts),
+ RA = min(RA0, ?DEFAULT_RENEGOTIATE_AT), %% Override users choice without notifying ??
+
+ {Where3, SR} = get_opt_bool(secure_renegotiate, true, UserOpts, Opts),
+ assert_version_dep(Where3 =:= new, secure_renegotiate, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+
+ Opts#{secure_renegotiate => SR, key_update_at => KUA, renegotiate_at => RA}.
+
+opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := client}) ->
+ {Where1, RUSS} = get_opt_of(reuse_sessions, [true, false, save], true, UserOpts, Opts),
+
+ {Where2, RS} = RST = get_opt(reuse_session, undefined, UserOpts, Opts),
+ case RST of
+ {new, Bin} when is_binary(Bin) -> ok;
+ {new, {B1,B2}} when is_binary(B1), is_binary(B2) -> ok;
+ {new, Bad} -> option_error(reuse_session, Bad);
+ {_, _} -> ok
+ end,
+
+ assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ Opts#{reuse_sessions => RUSS, reuse_session => RS};
+opt_reuse_sessions(UserOpts, #{versions := Versions} = Opts, #{role := server}) ->
+ {Where1, RUSS} = get_opt_bool(reuse_sessions, true, UserOpts, Opts),
+
+ DefRS = fun(_, _, _, _) -> true end,
+ {Where2, RS} = get_opt_fun(reuse_session, 4, DefRS, UserOpts, Opts),
+
+ assert_version_dep(Where1 =:= new, reuse_sessions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ assert_version_dep(Where2 =:= new, reuse_session, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ Opts#{reuse_sessions => RUSS, reuse_session => RS}.
+
+opt_identity(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ PSK = case get_opt_list(psk_identity, undefined, UserOpts, Opts) of
+ {new, PSK0} ->
+ PSK1 = unicode:characters_to_binary(PSK0),
+ PSKSize = byte_size(PSK1),
+ assert_version_dep(psk_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ option_error(not (0 < PSKSize andalso PSKSize < 65536),
+ psk_identity, {psk_identity, PSK0}),
+ PSK1;
+ {_, PSK0} ->
+ PSK0
+ end,
+
+ SRP = case get_opt(srp_identity, undefined, UserOpts, Opts) of
+ {new, {S1, S2}} when is_list(S1), is_list(S2) ->
+ User = unicode:characters_to_binary(S1),
+ UserSize = byte_size(User),
+ assert_version_dep(srp_identity, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ option_error(not (0 < UserSize andalso UserSize < 65536),
+ srp_identity, {srp_identity, PSK0}),
+ {User, unicode:characters_to_binary(S2)};
+ {new, Err} ->
+ option_error(srp_identity, Err);
+ {_, SRP0} ->
+ SRP0
+ end,
+
+ ULF = case get_opt(user_lookup_fun, undefined, UserOpts, Opts) of
+ {new, {Fun, _} = ULF0} when is_function(Fun, 3) ->
+ assert_version_dep(user_lookup_fun, Versions, ['tlsv1','tlsv1.1','tlsv1.2']),
+ ULF0;
+ {new, ULF0} ->
+ option_error(user_lookup_fun, ULF0);
+ {_, ULF0} ->
+ ULF0
+ end,
+
+ Opts#{psk_identity => PSK, srp_identity => SRP, user_lookup_fun => ULF}.
+
+opt_supported_groups(UserOpts, #{versions := Versions} = Opts, _Env) ->
+ [TlsVersion|_] = TlsVsns = [tls_version(V) || V <- Versions],
+ SG = case get_opt_list(supported_groups, undefined, UserOpts, Opts) of
+ {default, undefined} ->
+ handle_supported_groups_option(groups(default), TlsVersion);
+ {new, SG0} ->
+ assert_version_dep(supported_groups, TlsVsns, ['tlsv1.3']),
+ handle_supported_groups_option(SG0, TlsVersion);
+ {old, SG0} ->
+ SG0
+ end,
+
+ CPHS = case get_opt_list(ciphers, [], UserOpts, Opts) of
+ {old, CPS0} -> CPS0;
+ {_, CPS0} -> handle_cipher_option(CPS0, Versions)
+ end,
+
+ ECCS = case get_opt_list(eccs, undefined, UserOpts, Opts) of
+ {old, ECCS0} -> ECCS0;
+ {default, _} -> handle_eccs_option(tls_v1:ecc_curves(all), TlsVersion);
+ {new, ECCS0} -> handle_eccs_option(ECCS0, TlsVersion)
+ end,
+
+ Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG}.
+
+opt_crl(UserOpts, Opts, _Env) ->
+ {_, Check} = get_opt_of(crl_check, [best_effort, peer, true, false], false, UserOpts, Opts),
+ Cache = case get_opt(crl_cache, {ssl_crl_cache, {internal, []}}, UserOpts, Opts) of
+ {_, {Cb, {_Handle, Options}} = Value} when is_atom(Cb), is_list(Options) ->
+ Value;
+ {_, Err} ->
+ option_error(crl_cache, Err)
+ end,
+ Opts#{crl_check => Check, crl_cache => Cache}.
+
+opt_handshake(UserOpts, Opts, _Env) ->
+ {_, HS} = get_opt_of(handshake, [hello, full], full, UserOpts, Opts),
+
+ {_, MHSS} = get_opt_int(max_handshake_size, 1, ?MAX_UNIT24, ?DEFAULT_MAX_HANDSHAKE_SIZE,
+ UserOpts, Opts),
+
+ Opts#{handshake => HS, max_handshake_size => MHSS}.
+
+opt_use_srtp(UserOpts, #{protocol := Protocol} = Opts, _Env) ->
+ UseSRTP = case get_opt_map(use_srtp, undefined, UserOpts, Opts) of
+ {old, UseSRTP0} ->
+ UseSRTP0;
+ {default, undefined} ->
+ undefined;
+ {new, UseSRTP1} ->
+ assert_protocol_dep(use_srtp, Protocol, [dtls]),
+ validate_use_srtp(UseSRTP1)
+ end,
+ case UseSRTP of
+ #{} -> Opts#{use_srtp => UseSRTP};
+ _ -> Opts
+ end.
+
+validate_use_srtp(#{protection_profiles := [_|_] = PPs} = UseSRTP) ->
+ case maps:keys(UseSRTP) -- [protection_profiles, mki] of
+ [] -> ok;
+ Extra -> option_error(use_srtp, {unknown_parameters, Extra})
+ end,
+ IsValidProfile = fun(<<_, _>>) -> true; (_) -> false end,
+ case lists:all(IsValidProfile, PPs) of
+ true -> ok;
+ false -> option_error(use_srtp, {invalid_protection_profiles, PPs})
+ end,
+ case UseSRTP of
+ #{mki := MKI} when not is_binary(MKI) ->
+ option_error(use_srtp, {invalid_mki, MKI});
+ #{mki := _} ->
+ UseSRTP;
+ #{} ->
+ UseSRTP#{mki => <<>>}
end;
-validate_option(Opt, Value, _) ->
- throw({error, {options, {Opt, Value}}}).
+
+validate_use_srtp(#{} = UseSRTP) ->
+ option_error(use_srtp, {no_protection_profiles, UseSRTP}).
+
+
+opt_process(UserOpts, Opts0, _Env) ->
+ Opts1 = set_opt_list(receiver_spawn_opts, [], UserOpts, Opts0),
+ Opts2 = set_opt_list(sender_spawn_opts, [], UserOpts, Opts1),
+ %% {_, SSO} = get_opt_list(sender_spawn_opts, [], UserOpts, Opts),
+ %% Opts = Opts1#{receiver_spawn_opts => RSO, sender_spawn_opts => SSO},
+ set_opt_int(hibernate_after, 0, infinity, infinity, UserOpts, Opts2).
+
+%%%%
+
+get_opt(Opt, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, unbound) of
+ unbound ->
+ case maps:get(maybe_map_key_internal(Opt), Opts, unbound) of
+ unbound -> %% Uses default value
+ {default, Default};
+ Value -> %% Uses already set value (merge)
+ {old, Value}
+ end;
+ Value -> %% Uses new user option
+ {new, Value}
+ end.
+
+get_opt_of(Opt, Valid, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Value} = Res ->
+ case lists:member(Value, Valid) of
+ true -> Res;
+ false -> option_error(Opt, Value)
+ end;
+ Res ->
+ Res
+ end.
+
+get_opt_bool(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {_, Value} = Res when is_boolean(Value) -> Res;
+ {_, Value} -> option_error(Opt, Value)
+ end.
+
+get_opt_pos_int(Opt, Default, UserOpts, Opts) ->
+ get_opt_int(Opt, 1, infinity, Default, UserOpts, Opts).
+
+get_opt_int(Opt, Min, Max, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {_, Value} = Res when is_integer(Value), Min =< Value, Value =< Max ->
+ Res;
+ {_, Value} = Res when Value =:= infinity, Max =:= infinity ->
+ Res;
+ {_, Value} ->
+ option_error(Opt, Value)
+ end.
+
+get_opt_fun(Opt, Arity, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {_, Fun} = Res when is_function(Fun, Arity) -> Res;
+ {new, Err} -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+get_opt_list(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Err} when not is_list(Err) -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+get_opt_bin(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Err} when not is_binary(Err) -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+get_opt_file(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, File} -> {new, validate_filename(File, Opt)};
+ Res -> Res
+ end.
+
+set_opt_bool(Opt, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, Default) of
+ Default -> Opts;
+ Value when is_boolean(Value) -> Opts#{Opt => Value};
+ Value -> option_error(Opt, Value)
+ end.
+
+get_opt_map(Opt, Default, UserOpts, Opts) ->
+ case get_opt(Opt, Default, UserOpts, Opts) of
+ {new, Err} when not is_map(Err) -> option_error(Opt, Err);
+ Res -> Res
+ end.
+
+set_opt_int(Opt, Min, Max, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, Default) of
+ Default ->
+ Opts;
+ Value when is_integer(Value), Min =< Value, Value =< Max ->
+ Opts#{Opt => Value};
+ Value when Value =:= infinity, Max =:= infinity ->
+ Opts#{Opt => Value};
+ Value ->
+ option_error(Opt, Value)
+ end.
+
+set_opt_list(Opt, Default, UserOpts, Opts) ->
+ case maps:get(Opt, UserOpts, []) of
+ Default ->
+ Opts;
+ List when is_list(List) ->
+ Opts#{Opt => List};
+ Value ->
+ option_error(Opt, Value)
+ end.
+
+set_opt_new(new, Opt, Default, Value, Opts)
+ when Default =/= Value ->
+ Opts#{Opt => Value};
+set_opt_new(_, _, _, _, Opts) ->
+ Opts.
+
+%%%%
+
+default_cb_info(tls) ->
+ {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
+default_cb_info(dtls) ->
+ {gen_udp, udp, udp_closed, udp_error, udp_passive}.
handle_cb_info({V1, V2, V3, V4}) ->
{V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")};
+handle_cb_info(CbInfo) when tuple_size(CbInfo) =:= 5 ->
+ CbInfo;
handle_cb_info(CbInfo) ->
- CbInfo.
+ option_error(cb_info, CbInfo).
-handle_hashsigns_option(Value, Version) when is_list(Value)
- andalso Version >= {3, 4} ->
- case tls_v1:signature_schemes(Version, Value) of
- [] ->
- throw({error, {options,
- no_supported_signature_schemes,
- {signature_algs, Value}}});
- _ ->
- Value
- end;
-handle_hashsigns_option(Value, Version) when is_list(Value)
- andalso Version =:= {3, 3} ->
- case tls_v1:signature_algs(Version, Value) of
- [] ->
- throw({error, {options, no_supported_algorithms, {signature_algs, Value}}});
- _ ->
- Value
- end;
-handle_hashsigns_option(_, Version) when Version =:= {3, 3} ->
- handle_hashsigns_option(tls_v1:default_signature_algs([Version]), Version);
-handle_hashsigns_option(_, _Version) ->
- undefined.
+handle_option_cb_info(Options, Protocol) ->
+ CbInfo = proplists:get_value(cb_info, Options, default_cb_info(Protocol)),
+ handle_cb_info(CbInfo).
-handle_signature_algorithms_option(Value, Version) when is_list(Value)
- andalso Version >= {3, 3} ->
- case tls_v1:signature_schemes(Version, Value) of
- [] ->
- throw({error, {options,
- no_supported_signature_schemes,
- {signature_algs_cert, Value}}});
- _ ->
- Value
- end;
-handle_signature_algorithms_option(_, _Version) ->
- undefined.
+maybe_map_key_internal(client_preferred_next_protocols) ->
+ next_protocol_selector;
+maybe_map_key_internal(K) ->
+ K.
+
+split_options(Opts0, AllOptions) ->
+ Opts1 = proplists:expand([{binary, [{mode, binary}]},
+ {list, [{mode, list}]}], Opts0),
+ Opts2 = handle_option_format(Opts1, []),
+ %% Remove deprecated ssl_imp option
+ Opts = proplists:delete(ssl_imp, Opts2),
-validate_options([]) ->
- [];
-validate_options([{Opt, Value} | Tail]) ->
- [{Opt, validate_option(Opt, Value)} | validate_options(Tail)].
+ DeleteUserOpts = fun(Key, PropList) -> proplists:delete(Key, PropList) end,
+ AllOpts = [cb_info, client_preferred_next_protocols] ++ AllOptions,
+ SockOpts = lists:foldl(DeleteUserOpts, Opts, AllOpts),
+ {Opts -- SockOpts, SockOpts}.
+
+assert_server_only(Option, Opts) ->
+ Value = maps:get(Option, Opts, undefined),
+ role_error(Value =/= undefined, server_only, Option).
+assert_client_only(Option, Opts) ->
+ Value = maps:get(Option, Opts, undefined),
+ role_error(Value =/= undefined, client_only, Option).
+
+assert_server_only(client, Bool, Option) ->
+ role_error(Bool, server_only, Option);
+assert_server_only(_, _, _) ->
+ ok.
+assert_client_only(server, Bool, Option) ->
+ role_error(Bool, client_only, Option);
+assert_client_only(_, _, _) ->
+ ok.
-validate_npn_ordering(client) ->
+role_error(false, _ErrorDesc, _Option) ->
ok;
-validate_npn_ordering(server) ->
- ok;
-validate_npn_ordering(Value) ->
- throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}).
-
-validate_binary_list(Opt, List) ->
- lists:foreach(
- fun(Bin) when is_binary(Bin),
- byte_size(Bin) > 0,
- byte_size(Bin) < 256 ->
- ok;
- (Bin) ->
- throw({error, {options, {Opt, {invalid_protocol, Bin}}}})
- end, List).
-validate_versions([], Versions) ->
- Versions;
-validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
- Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1 ->
- case tls_record:sufficient_crypto_support(Version) of
- true ->
- tls_validate_versions(Rest, Versions);
- false ->
- throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}})
- end;
-validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
- Version == 'dtlsv1.2'->
- DTLSVer = dtls_record:protocol_version(Version),
- case tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(DTLSVer)) of
- true ->
- dtls_validate_versions(Rest, Versions);
+role_error(true, ErrorDesc, Option)
+ when ErrorDesc =:= client_only; ErrorDesc =:= server_only ->
+ throw_error({option, ErrorDesc, Option}).
+
+option_incompatible(false, _Options) -> ok;
+option_incompatible(true, Options) -> option_incompatible(Options).
+
+-spec option_incompatible(_) -> no_return().
+option_incompatible(Options) ->
+ throw_error({options, incompatible, Options}).
+
+option_error(false, _, _What) -> true;
+option_error(true, Tag, What) -> option_error(Tag,What).
+
+-spec option_error(_,_) -> no_return().
+option_error(Tag, What) ->
+ throw_error({options, {Tag, What}}).
+
+-spec throw_error(_) -> no_return().
+throw_error(Err) ->
+ throw({error, Err}).
+
+assert_protocol_dep(Option, Protocol, AllowedProtos) ->
+ case lists:member(Protocol, AllowedProtos) of
+ true -> ok;
+ false -> option_incompatible([Option, {protocol, Protocol}])
+ end.
+
+assert_version_dep(Option, Vsns, AllowedVsn) ->
+ assert_version_dep(true, Option, Vsns, AllowedVsn).
+
+assert_version_dep(false, _, _, _) -> true;
+assert_version_dep(true, Option, SSLVsns, AllowedVsn) ->
+ case is_dtls_configured(SSLVsns) of
+ true -> %% TODO: Check option dependency for DTLS
+ true;
false ->
- throw({error, {options, {insufficient_crypto_support, {Version, {versions, Versions}}}}})
- end;
-validate_versions([Version| _], Versions) ->
- throw({error, {options, {Version, {versions, Versions}}}}).
-
-tls_validate_versions([], Versions) ->
- tls_validate_version_gap(Versions);
-tls_validate_versions([Version | Rest], Versions) when Version == 'tlsv1.3';
- Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == tlsv1 ->
- tls_validate_versions(Rest, Versions);
-tls_validate_versions([Version| _], Versions) ->
- throw({error, {options, {Version, {versions, Versions}}}}).
+ APIVsns = lists:map(fun tls_record:protocol_version/1, SSLVsns),
+ Set1 = sets:from_list(APIVsns),
+ Set2 = sets:from_list(AllowedVsn),
+ case sets:size(sets:intersection(Set1, Set2)) > 0 of
+ true -> ok;
+ false -> option_incompatible([Option, {versions, APIVsns}])
+ end
+ end.
+
+warn_override(new, UserOpts, NewOpt, OldOpts, LogLevel) ->
+ Check = fun(Key) -> maps:is_key(Key,UserOpts) end,
+ case lists:filter(Check, OldOpts) of
+ [] -> ok;
+ Ignored ->
+ Desc = lists:flatten(io_lib:format("Options ~w are ignored", [Ignored])),
+ Reas = lists:flatten(io_lib:format("Option ~w is set", [NewOpt])),
+ ssl_logger:log(notice, LogLevel, #{description => Desc, reason => Reas}, ?LOCATION)
+ end;
+warn_override(_, _UserOpts, _NewOpt, _OldOpts, _LogLevel) ->
+ ok.
+
+is_dtls_configured(Versions) ->
+ lists:any(fun (Ver) -> ?DTLS_1_X(Ver) end, Versions).
+
+handle_hashsigns_option(Value, Version) ->
+ try
+ if ?TLS_GTE(Version, ?TLS_1_3) ->
+ tls_v1:signature_schemes(Version, Value);
+ (Version =:= ?TLS_1_2) ->
+ tls_v1:signature_algs(Version, Value);
+ true ->
+ undefined
+ end
+ catch error:function_clause ->
+ option_error(signature_algs, Value)
+ end.
+
+handle_signature_algorithms_option(Value, Version) ->
+ try tls_v1:signature_schemes(Version, Value)
+ catch error:function_clause ->
+ option_error(signature_algs_cert, Value)
+ end.
+
+validate_filename(FN, _Option) when is_binary(FN), FN =/= <<>> ->
+ FN;
+validate_filename([_|_] = FN, _Option) ->
+ Enc = file:native_name_encoding(),
+ unicode:characters_to_binary(FN, unicode, Enc);
+validate_filename(FN, Option) ->
+ option_error(Option, FN).
%% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported
%% as that configuration can trigger the built in version downgrade protection
@@ -2556,25 +2578,7 @@ tls_validate_version_gap(Versions) ->
_ ->
Versions
end.
-dtls_validate_versions([], Versions) ->
- Versions;
-dtls_validate_versions([Version | Rest], Versions) when Version == 'dtlsv1';
- Version == 'dtlsv1.2'->
- dtls_validate_versions(Rest, Versions);
-dtls_validate_versions([Ver| _], Versions) ->
- throw({error, {options, {Ver, {versions, Versions}}}}).
-
-%% The option cacerts overrides cacertfile
-ca_cert_default(_,_, [_|_]) ->
- undefined;
-ca_cert_default(verify_none, _, _) ->
- undefined;
-ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) ->
- undefined;
-%% Server that wants to verify_peer and has no verify_fun must have
-%% some trusted certs.
-ca_cert_default(verify_peer, undefined, _) ->
- "".
+
emulated_options(undefined, undefined, Protocol, Opts) ->
case Protocol of
tls ->
@@ -2594,16 +2598,16 @@ handle_cipher_option(Value, Versions) when is_list(Value) ->
Suites
catch
exit:_ ->
- throw({error, {options, {ciphers, Value}}});
+ option_error(ciphers, Value);
error:_->
- throw({error, {options, {ciphers, Value}}})
+ option_error(ciphers, Value)
end.
-binary_cipher_suites([{3,4} = Version], []) ->
+binary_cipher_suites([?TLS_1_3], []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration TLS-1.3
%% only mode.
- default_binary_suites(exclusive, Version);
+ default_binary_suites(exclusive, ?TLS_1_3);
binary_cipher_suites([Version| _], []) ->
%% Defaults to all supported suites that does
%% not require explicit configuration
@@ -2633,15 +2637,15 @@ binary_cipher_suites(Versions, Ciphers0) ->
Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")],
binary_cipher_suites(Versions, Ciphers).
-default_binary_suites(exclusive, {_, Minor}) ->
- ssl_cipher:filter_suites(tls_v1:exclusive_suites(Minor));
+default_binary_suites(exclusive, Version) ->
+ ssl_cipher:filter_suites(tls_v1:exclusive_suites(Version));
default_binary_suites(default, Version) ->
ssl_cipher:filter_suites(ssl_cipher:suites(Version)).
-all_suites([{3, 4 = Minor}]) ->
- tls_v1:exclusive_suites(Minor);
-all_suites([{3, 4} = Version0, Version1 |_]) ->
- all_suites([Version0]) ++
+all_suites([?TLS_1_3]) ->
+ tls_v1:exclusive_suites(?TLS_1_3);
+all_suites([?TLS_1_3, Version1 |_]) ->
+ all_suites([?TLS_1_3]) ++
ssl_cipher:all_suites(Version1) ++
ssl_cipher:anonymous_suites(Version1);
all_suites([Version|_]) ->
@@ -2669,22 +2673,26 @@ tuple_to_map_mac(chacha20_poly1305, _) ->
tuple_to_map_mac(_, MAC) ->
MAC.
-handle_eccs_option(Value, Version) when is_list(Value) ->
- {_Major, Minor} = tls_version(Version),
- try tls_v1:ecc_curves(Minor, Value) of
- Curves -> #elliptic_curves{elliptic_curve_list = Curves}
+%% TODO: remove Version
+handle_eccs_option(Value, _Version0) when is_list(Value) ->
+ try tls_v1:ecc_curves(Value) of
+ Curves ->
+ option_error(Curves =:= [], eccs, none_valid),
+ #elliptic_curves{elliptic_curve_list = Curves}
catch
- exit:_ -> throw({error, {options, {eccs, Value}}});
- error:_ -> throw({error, {options, {eccs, Value}}})
+ exit:_ -> option_error(eccs, Value);
+ error:_ -> option_error(eccs, Value)
end.
-handle_supported_groups_option(Value, Version) when is_list(Value) ->
- {_Major, Minor} = tls_version(Version),
- try tls_v1:groups(Minor, Value) of
- Groups -> #supported_groups{supported_groups = Groups}
+%% TODO: remove Version
+handle_supported_groups_option(Value, _Version0) when is_list(Value) ->
+ try tls_v1:groups(Value) of
+ Groups ->
+ option_error(Groups =:= [], supported_groups, none_valid),
+ #supported_groups{supported_groups = Groups}
catch
- exit:_ -> throw({error, {options, {supported_groups, Value}}});
- error:_ -> throw({error, {options, {supported_groups, Value}}})
+ exit:_ -> option_error(supported_groups, Value);
+ error:_ -> option_error(supported_groups, Value)
end.
@@ -2698,9 +2706,10 @@ handle_supported_groups_option(Value, Version) when is_list(Value) ->
| InetError
| OtherReason) -> string()
when
- FileType :: cacertfile | certfile | keyfile | dhfile,
- OtherReason :: term(),
- InetError :: inet:posix() | system_limit.
+ FileType :: cacertfile | certfile | keyfile | dhfile,
+ OtherReason :: term(),
+ Error :: term(),
+ InetError :: inet:posix() | system_limit.
do_format_error(Reason) when is_list(Reason) ->
Reason;
@@ -2734,7 +2743,7 @@ unexpected_format(Error) ->
file_error_format({error, Error})->
case file:format_error(Error) of
- "unknown POSIX error" ->
+ "unknown POSIX error" ++ _ ->
"decoding error";
Str ->
Str
@@ -2751,42 +2760,37 @@ file_desc(keyfile) ->
file_desc(dhfile) ->
"Invalid DH params file ".
-detect(_Pred, []) ->
- undefined;
-detect(Pred, [H|T]) ->
- case Pred(H) of
- true ->
- H;
- _ ->
- detect(Pred, T)
- end.
-
make_next_protocol_selector(undefined) ->
undefined;
-make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) ->
- fun(AdvertisedProtocols) ->
- case detect(fun(PreferredProtocol) ->
- lists:member(PreferredProtocol, AdvertisedProtocols)
- end, AllProtocols) of
- undefined ->
- DefaultProtocol;
- PreferredProtocol ->
- PreferredProtocol
- end
+make_next_protocol_selector({Precedence, PrefProtcol} = V) ->
+ option_error(not is_list(PrefProtcol), client_preferred_next_protocols, V),
+ make_next_protocol_selector({Precedence, PrefProtcol, ?NO_PROTOCOL});
+make_next_protocol_selector({Precedence, AllProtocols, DefP} = V) ->
+ option_error(not is_list(AllProtocols), client_preferred_next_protocols, V),
+ option_error(not (is_binary(DefP) andalso byte_size(DefP) < 256), client_preferred_next_protocols, V),
+ validate_protocols(true, client_preferred_next_protocols, AllProtocols),
+ case Precedence of
+ client ->
+ fun(Advertised) ->
+ Search = fun(P) -> lists:member(P, Advertised) end,
+ case lists:search(Search, AllProtocols) of
+ false -> DefP;
+ {value, Preferred} -> Preferred
+ end
+ end;
+ server ->
+ fun(Advertised) ->
+ Search = fun(P) -> lists:member(P, AllProtocols) end,
+ case lists:search(Search, Advertised) of
+ false -> DefP;
+ {value, Preferred} -> Preferred
+ end
+ end;
+ Value ->
+ option_error(client_preferred_next_protocols, {invalid_precedence, Value})
end;
-
-make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) ->
- fun(AdvertisedProtocols) ->
- case detect(fun(PreferredProtocol) ->
- lists:member(PreferredProtocol, AllProtocols)
- end,
- AdvertisedProtocols) of
- undefined ->
- DefaultProtocol;
- PreferredProtocol ->
- PreferredProtocol
- end
- end.
+make_next_protocol_selector(What) ->
+ option_error(client_preferred_next_protocols, What).
connection_cb(tls) ->
tls_gen_connection;
@@ -2795,16 +2799,6 @@ connection_cb(dtls) ->
connection_cb(Opts) ->
connection_cb(proplists:get_value(protocol, Opts, tls)).
-record_cb(tls) ->
- tls_record;
-record_cb(dtls) ->
- dtls_record;
-record_cb(Opts) ->
- record_cb(proplists:get_value(protocol, Opts, tls)).
-
-binary_filename(FileName) ->
- Enc = file:native_name_encoding(),
- unicode:characters_to_binary(FileName, unicode, Enc).
%% Assert that basic options are on the format {Key, Value}
%% with a few exceptions and phase out log_alert
@@ -2812,12 +2806,12 @@ handle_option_format([], Acc) ->
lists:reverse(Acc);
handle_option_format([{log_alert, Bool} | Rest], Acc) when is_boolean(Bool) ->
case proplists:get_value(log_level, Acc ++ Rest, undefined) of
- undefined ->
+ undefined ->
handle_option_format(Rest, [{log_level,
map_log_level(Bool)} | Acc]);
- _ ->
+ _ ->
handle_option_format(Rest, Acc)
- end;
+ end;
handle_option_format([{Key,_} = Opt | Rest], Acc) when is_atom(Key) ->
handle_option_format(Rest, [Opt | Acc]);
%% Handle exceptions
@@ -2828,53 +2822,13 @@ handle_option_format([inet = Opt | Rest], Acc) ->
handle_option_format([inet6 = Opt | Rest], Acc) ->
handle_option_format(Rest, [Opt | Acc]);
handle_option_format([Value | _], _) ->
- throw({option_not_a_key_value_tuple, Value}).
+ option_error(option_not_a_key_value_tuple, Value).
map_log_level(true) ->
notice;
map_log_level(false) ->
none.
-handle_verify_option(verify_none, #{fail_if_no_peer_cert := false} = OptionsMap) ->
- OptionsMap#{verify => verify_none};
-handle_verify_option(verify_none, #{fail_if_no_peer_cert := true}) ->
- throw({error, {options, incompatible,
- {verify, verify_none},
- {fail_if_no_peer_cert, true}}});
-%% The option 'verify' is simulated by the configured 'verify_fun' that is mostly
-%% hidden from the end user. When 'verify' is set to verify_none, the option
-%% 'verify_fun' is also set to a default verify-none-verify_fun when processing
-%% the configuration. If 'verify' is later changed from verify_none to verify_peer,
-%% the 'verify_fun' must also be changed to undefined. When 'verify_fun' is set to
-%% undefined, public_key's default verify_fun will be used that performs a full
-%% verification.
-handle_verify_option(verify_peer, #{verify := verify_none} = OptionsMap) ->
- OptionsMap#{verify => verify_peer,
- verify_fun => undefined};
-handle_verify_option(verify_peer, OptionsMap) ->
- OptionsMap#{verify => verify_peer};
-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} ->
- Value;
-default_option_role_sign_algs(Role, Value, Role, _) ->
- Value;
-default_option_role_sign_algs(_, _, _, _) ->
- undefined.
-
-default_option_role(Role, Value, Role) ->
- Value;
-default_option_role(_,_,_) ->
- undefined.
-
-
-default_cb_info(tls) ->
- {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
-default_cb_info(dtls) ->
- {gen_udp, udp, udp_closed, udp_error, udp_passive}.
-
include_security_info([]) ->
false;
include_security_info([Item | Items]) ->
@@ -2885,39 +2839,46 @@ include_security_info([Item | Items]) ->
include_security_info(Items)
end.
-server_name_indication_default(Host) when is_list(Host) ->
- %% SNI should not contain a trailing dot that a hostname may
- string:strip(Host, right, $.);
-server_name_indication_default(_) ->
- undefined.
add_filter(undefined, Filters) ->
Filters;
add_filter(Filter, Filters) ->
[Filter | Filters].
-maybe_client_warn_no_verify(#{verify := verify_none,
- warn_verify_none := true,
- log_level := LogLevel}, client) ->
- ssl_logger:log(warning, LogLevel,
- #{description => "Server authenticity is not verified since certificate path validation is not enabled",
- reason => "The option {verify, verify_peer} and one of the options 'cacertfile' or "
- "'cacerts' are required to enable this."}, ?LOCATION);
-maybe_client_warn_no_verify(_,_) ->
- %% Warning not needed. Note client certificate validation is optional in TLS
- ok.
-
unambiguous_path(Value) ->
AbsName = filename:absname(Value),
- case file:read_link(AbsName) of
- {ok, PathWithNoLink} ->
- case filename:pathtype(PathWithNoLink) of
- relative ->
- Dirname = filename:dirname(AbsName),
- filename:join([Dirname, PathWithNoLink]);
- _ ->
- PathWithNoLink
- end;
- _ ->
- AbsName
- end.
+ UP = case file:read_link(AbsName) of
+ {ok, PathWithNoLink} ->
+ case filename:pathtype(PathWithNoLink) of
+ relative ->
+ Dirname = filename:dirname(AbsName),
+ filename:join([Dirname, PathWithNoLink]);
+ _ ->
+ PathWithNoLink
+ end;
+ _ ->
+ AbsName
+ end,
+ validate_filename(UP, cacertfile).
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp, {call, {?MODULE, opt_ocsp, [UserOpts | _]}}, Stack) ->
+ {format_ocsp_params(UserOpts), Stack};
+handle_trace(csp, {return_from, {?MODULE, opt_ocsp, 3}, Return}, Stack) ->
+ {format_ocsp_params(Return), Stack};
+handle_trace(rle, {call, {?MODULE, listen, Args}}, Stack0) ->
+ Role = server,
+ {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]};
+handle_trace(rle, {call, {?MODULE, connect, Args}}, Stack0) ->
+ Role = client,
+ {io_lib:format("(*~w) Args = ~W", [Role, Args, 10]), [{role, Role} | Stack0]}.
+
+format_ocsp_params(Map) ->
+ Stapling = maps:get(ocsp_stapling, Map, '?'),
+ Nonce = maps:get(ocsp_nonce, Map, '?'),
+ Certs = maps:get(ocsp_responder_certs, Map, '?'),
+ io_lib:format("Stapling = ~W Nonce = ~W Certs = ~W",
+ [Stapling, 5, Nonce, 5, Certs, 5]).
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 2e2b43f564..ec1816fdde 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022 All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023 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.
@@ -65,6 +65,7 @@
-include("ssl_handshake.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
+-include("ssl_record.hrl").
-include_lib("public_key/include/public_key.hrl").
-export([trusted_cert_and_paths/4,
@@ -85,6 +86,9 @@
available_cert_key_pairs/2
]).
+%% Tracing
+-export([handle_trace/3]).
+
%%====================================================================
%% Internal application API
%%====================================================================
@@ -342,13 +346,14 @@ available_cert_key_pairs(CertKeyGroups) ->
%% Create the prioritized list of cert key pairs that
%% are availble for use in the negotiated version
-available_cert_key_pairs(CertKeyGroups, {3, 4}) ->
+available_cert_key_pairs(CertKeyGroups, ?TLS_1_3) ->
RevAlgos = [rsa, rsa_pss_pss, ecdsa, eddsa],
cert_key_group_to_list(RevAlgos, CertKeyGroups, []);
-available_cert_key_pairs(CertKeyGroups, {3, 3}) ->
+available_cert_key_pairs(CertKeyGroups, ?TLS_1_2) ->
RevAlgos = [dsa, rsa, rsa_pss_pss, ecdsa],
cert_key_group_to_list(RevAlgos, CertKeyGroups, []);
-available_cert_key_pairs(CertKeyGroups, {3, N}) when N < 3->
+available_cert_key_pairs(CertKeyGroups, Version)
+ when ?TLS_LT(Version, ?TLS_1_2) ->
RevAlgos = [dsa, rsa, ecdsa],
cert_key_group_to_list(RevAlgos, CertKeyGroups, []).
@@ -578,10 +583,12 @@ verify_cert_extensions(Cert, UserState, [], _) ->
{valid, UserState#{issuer => Cert}};
verify_cert_extensions(Cert, #{ocsp_responder_certs := ResponderCerts,
ocsp_state := OscpState,
- issuer := Issuer} = UserState,
- [#certificate_status{response = OcspResponsDer} | Exts], Context) ->
+ issuer := Issuer} = UserState,
+ [#certificate_status{response = OcspResponsDer} | Exts],
+ Context) ->
#{ocsp_nonce := Nonce} = OscpState,
- case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer, ResponderCerts, Nonce) of
+ case public_key:pkix_ocsp_validate(Cert, Issuer, OcspResponsDer,
+ ResponderCerts, Nonce) of
valid ->
verify_cert_extensions(Cert, UserState, Exts, Context);
{bad_cert, _} = Status ->
@@ -591,21 +598,22 @@ verify_cert_extensions(Cert, UserState, [_|Exts], Context) ->
%% Skip unknown extensions!
verify_cert_extensions(Cert, UserState, Exts, Context).
-verify_sign(_, #{version := {_, Minor}}) when Minor < 3 ->
+verify_sign(_, #{version := Version})
+ when ?TLS_LT(Version, ?TLS_1_2) ->
%% This verification is not applicable pre TLS-1.2
true;
-verify_sign(Cert, #{version := {3, 3},
+verify_sign(Cert, #{version := ?TLS_1_2,
signature_algs := SignAlgs,
signature_algs_cert := undefined}) ->
is_supported_signature_algorithm_1_2(Cert, SignAlgs);
-verify_sign(Cert, #{version := {3, 3},
+verify_sign(Cert, #{version := ?TLS_1_2,
signature_algs_cert := SignAlgs}) ->
is_supported_signature_algorithm_1_2(Cert, SignAlgs);
-verify_sign(Cert, #{version := {3, 4},
+verify_sign(Cert, #{version := ?TLS_1_3,
signature_algs := SignAlgs,
signature_algs_cert := undefined}) ->
is_supported_signature_algorithm_1_3(Cert, SignAlgs);
-verify_sign(Cert, #{version := {3, 4},
+verify_sign(Cert, #{version := ?TLS_1_3,
signature_algs_cert := SignAlgs}) ->
is_supported_signature_algorithm_1_3(Cert, SignAlgs).
@@ -620,7 +628,7 @@ is_supported_signature_algorithm_1_2(#'OTPCertificate'{signatureAlgorithm =
is_supported_signature_algorithm_1_2(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) ->
Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
{Hash, Sign, _ } = ssl_cipher:scheme_to_components(Scheme),
- ssl_cipher:is_supported_sign({pre_1_3_hash(Hash), pre_1_3_sign(Sign)}, ssl_cipher:signature_schemes_1_2(SignAlgs)).
+ ssl_cipher:is_supported_sign({Hash, pre_1_3_sign(Sign)}, ssl_cipher:signature_schemes_1_2(SignAlgs)).
is_supported_signature_algorithm_1_3(#'OTPCertificate'{signatureAlgorithm = SignAlg}, SignAlgs) ->
Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
ssl_cipher:is_supported_sign(Scheme, SignAlgs).
@@ -629,10 +637,6 @@ pre_1_3_sign(rsa_pkcs1) ->
rsa;
pre_1_3_sign(Other) ->
Other.
-pre_1_3_hash(sha1) ->
- sha;
-pre_1_3_hash(Hash) ->
- Hash.
paths(Chain, CertDbHandle) ->
paths(Chain, Chain, CertDbHandle, []).
@@ -825,3 +829,37 @@ cert_issuers(OTPCerts) ->
cert_auth_member(ChainSubjects, CertAuths) ->
CommonAuthorities = sets:intersection(sets:from_list(ChainSubjects), sets:from_list(CertAuths)),
not sets:is_empty(CommonAuthorities).
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(crt,
+ {call, {?MODULE, validate, [Cert, StatusOrExt| _]}}, Stack) ->
+ {io_lib:format("[~W] StatusOrExt = ~W", [Cert, 3, StatusOrExt, 10]), Stack};
+ %% {io_lib:format("(~s) StatusOrExt = ~W",
+ %% [ssl_test_lib:format_cert(Cert), StatusOrExt, 10]), Stack};
+handle_trace(crt, {call, {?MODULE, verify_cert_extensions,
+ [Cert,
+ _UserState,
+ [], _Context]}}, Stack) ->
+ {io_lib:format(" no more extensions [~W]", [Cert, 3]), Stack};
+ %% {io_lib:format(" no more extensions (~s)", [ssl_test_lib:format_cert(Cert)]), Stack};
+handle_trace(crt, {call, {?MODULE, verify_cert_extensions,
+ [Cert,
+ #{ocsp_responder_certs := _ResponderCerts,
+ ocsp_state := OcspState,
+ issuer := Issuer} = _UserState,
+ [#certificate_status{response = OcspResponsDer} |
+ _Exts], _Context]}}, Stack) ->
+ {io_lib:format("#2 OcspState = ~W Issuer = [~W] OcspResponsDer = ~W [~W]",
+ [OcspState, 10, Issuer, 3, OcspResponsDer, 2, Cert, 3]),
+ Stack};
+ %% {io_lib:format("#2 OcspState = ~W Issuer = (~s) OcspResponsDer = ~W (~s)",
+ %% [OcspState, 10, ssl_test_lib:format_cert(Issuer),
+ %% OcspResponsDer, 2, ssl_test_lib:format_cert(Cert)]),
+handle_trace(crt, {return_from,
+ {ssl_certificate, verify_cert_extensions, 4},
+ {valid, #{issuer := Issuer}}}, Stack) ->
+ {io_lib:format(" extensions valid Issuer = ~W", [Issuer, 3]), Stack}.
+ %% {io_lib:format(" extensions valid Issuer = ~s", [ssl_test_lib:format_cert(Issuer)]), Stack}.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 6e952a4584..8d982f7fa2 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -214,16 +214,15 @@ build_cipher_block(BlockSz, Mac, Fragment) ->
[Fragment, Mac, padding_with_len(TotSz, BlockSz)].
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
- Mac, Fragment, {3, N})
- when N == 0; N == 1 ->
+ Mac, Fragment, ?TLS_1_0) ->
L = build_cipher_block(BlockSz, Mac, Fragment),
T = Fun(Key, IV, L),
NextIV = next_iv(T, IV),
{T, CS0#cipher_state{iv=NextIV}};
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS0,
- Mac, Fragment, {3, N})
- when N == 2; N == 3; N == 4 ->
+ Mac, Fragment, Version)
+ when ?TLS_GT(Version, ?TLS_1_0)->
IV_Size = byte_size(IV),
<<NextIV:IV_Size/binary, IV_Cache/binary>> =
case IV_Cache0 of
@@ -321,45 +320,42 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
%%
%% Description: Returns a list of supported cipher suites.
%%--------------------------------------------------------------------
-suites({3, Minor}) ->
- tls_v1:suites(Minor);
-suites({_, Minor}) ->
- dtls_v1:suites(Minor).
-all_suites({3, 4} = Version) ->
- suites(Version)
- ++ tls_v1:psk_suites({3,3})
- ++ tls_v1:srp_suites({3,3})
- ++ tls_v1:rsa_suites({3,3})
- ++ tls_v1:des_suites({3,3})
- ++ tls_v1:rc4_suites({3,3});
-all_suites({3, _} = Version) ->
- suites(Version)
- ++ tls_v1:psk_suites(Version)
- ++ tls_v1:srp_suites(Version)
- ++ tls_v1:rsa_suites(Version)
- ++ tls_v1:des_suites(Version)
- ++ tls_v1:rc4_suites(Version);
+suites(Version) when ?TLS_1_X(Version) ->
+ tls_v1:suites(Version);
+suites(Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:suites(Version).
+all_suites(?TLS_1_3 = Version) ->
+ suites(Version) ++ tls_legacy_suites(?TLS_1_2);
+all_suites(Version) when ?TLS_1_X(Version) ->
+ suites(Version) ++ tls_legacy_suites(Version);
all_suites(Version) ->
dtls_v1:all_suites(Version).
+tls_legacy_suites(Version) ->
+ Tests = [fun tls_v1:psk_suites/1,
+ fun tls_v1:srp_suites/1,
+ fun tls_v1:rsa_suites/1,
+ fun tls_v1:des_suites/1,
+ fun tls_v1:rc4_suites/1],
+ lists:flatmap(fun (Fun) -> Fun(Version) end, Tests).
+
%%--------------------------------------------------------------------
--spec anonymous_suites(ssl_record:ssl_version() | integer()) ->
- [ssl_cipher_format:cipher_suite()].
+-spec anonymous_suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous cipher suites, only supported
%% if explicitly set by user. Intended only for testing.
%%--------------------------------------------------------------------
-anonymous_suites({3, N}) ->
- anonymous_suites(N);
-anonymous_suites({254, _} = Version) ->
- dtls_v1:anonymous_suites(Version);
-
-anonymous_suites(1 = N) ->
- tls_v1:exclusive_anonymous_suites(N);
-anonymous_suites(4 = N) ->
- tls_v1:exclusive_anonymous_suites(N);
-anonymous_suites(N) when N > 1->
- tls_v1:exclusive_anonymous_suites(N) ++ anonymous_suites(N-1).
+
+anonymous_suites(Version) when ?TLS_1_X(Version) ->
+ SuitesToTest = anonymous_suite_to_test(Version),
+ lists:flatmap(fun tls_v1:exclusive_anonymous_suites/1, SuitesToTest);
+anonymous_suites(Version) when ?DTLS_1_X(Version) ->
+ dtls_v1:anonymous_suites(Version).
+
+anonymous_suite_to_test(?TLS_1_0) -> [?TLS_1_0];
+anonymous_suite_to_test(?TLS_1_1) -> [?TLS_1_1, ?TLS_1_0];
+anonymous_suite_to_test(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+anonymous_suite_to_test(?TLS_1_3) -> [?TLS_1_3].
%%--------------------------------------------------------------------
-spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()],
@@ -393,11 +389,11 @@ filter(DerCert, Ciphers0, Version) ->
%% Description: Filter suites using supplied filter funs
%%-------------------------------------------------------------------
filter_suites(Suites, Filters) ->
- ApplyFilters = fun(Suite) ->
- filter_suite(Suite, Filters)
- end,
- lists:filter(ApplyFilters, Suites).
-
+ Fn = fun (Suite) when is_map_key(key_exchange, Suite) -> Suite;
+ (Suite) -> ssl_cipher_format:suite_bin_to_map(Suite)
+ end,
+ lists:filter(fun(Suite) -> filter_suite(Fn(Suite), Filters) end, Suites).
+
filter_suite(#{key_exchange := KeyExchange,
cipher := Cipher,
mac := Hash,
@@ -406,12 +402,10 @@ filter_suite(#{key_exchange := KeyExchange,
cipher_filters := CipherFilters,
mac_filters := HashFilters,
prf_filters := PrfFilters}) ->
- all_filters(KeyExchange, KeyFilters) andalso
- all_filters(Cipher, CipherFilters) andalso
- all_filters(Hash, HashFilters) andalso
- all_filters(Prf, PrfFilters);
-filter_suite(Suite, Filters) ->
- filter_suite(ssl_cipher_format:suite_bin_to_map(Suite), Filters).
+ KeyPairs = [{KeyExchange, KeyFilters}, {Cipher, CipherFilters},
+ {Hash, HashFilters}, {Prf, PrfFilters}],
+ lists:all(fun all_filters/1, KeyPairs).
+
%%--------------------------------------------------------------------
-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) ->
@@ -423,15 +417,9 @@ filter_suites(Suites) ->
Filters = crypto_support_filters(),
filter_suites(Suites, Filters).
-all_filters(_, []) ->
- true;
-all_filters(Value, [Filter| Rest]) ->
- case Filter(Value) of
- true ->
- all_filters(Value, Rest);
- false ->
- false
- end.
+all_filters({Value, Filters}) ->
+ lists:all(fun (FilterFn) -> FilterFn(Value) end, Filters).
+
crypto_support_filters() ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
@@ -568,20 +556,17 @@ hash_size(sha384) ->
hash_size(sha512) ->
64.
-is_supported_sign({Hash, rsa} = SignAlgo, HashSigns) -> %% PRE TLS-1.3
- lists:member(SignAlgo, HashSigns) orelse
- lists:member({Hash, rsa_pss_rsae}, HashSigns);
-is_supported_sign(rsa_pkcs1_sha256 = SignAlgo, HashSigns) -> %% TLS-1.3 legacy
- lists:member(SignAlgo, HashSigns) orelse
- lists:member(rsa_pss_rsae_sha256, HashSigns);
-is_supported_sign(rsa_pkcs1_sha384 = SignAlgo, HashSigns) -> %% TLS-1.3 legacy
- lists:member(SignAlgo, HashSigns) orelse
- lists:member(rsa_pss_rsae_sha384, HashSigns);
-is_supported_sign(rsa_pkcs1_sha512 = SignAlgo, HashSigns) -> %% TLS-1.3 legacy
- lists:member(SignAlgo, HashSigns) orelse
- lists:member(rsa_pss_rsae_sha512, HashSigns);
-is_supported_sign(SignAlgo, HashSigns) -> %% PRE TLS-1.3 SignAlgo::tuple() TLS-1.3 SignAlgo::atom()
- lists:member(SignAlgo, HashSigns).
+is_supported_sign(SignAlgo, HashSigns) ->
+ lists:any(fun (SignAlgo0) -> lists:member(SignAlgo0, HashSigns) end,
+ [SignAlgo, supported_signalgo(SignAlgo)]).
+
+supported_signalgo({Hash, rsa}) -> {Hash,rsa_pss_rsae}; %% PRE TLS-1.3 %% PRE TLS-1.3
+supported_signalgo(rsa_pkcs1_sha256) -> rsa_pss_rsae_sha256; %% TLS-1.3 legacy
+supported_signalgo(rsa_pkcs1_sha384) -> rsa_pss_rsae_sha384; %% TLS-1.3 legacy
+supported_signalgo(rsa_pkcs1_sha512) -> rsa_pss_rsae_sha512; %% TLS-1.3 legacy
+supported_signalgo(_) -> skip_test. %% made up atom, PRE TLS-1.3 SignAlgo::tuple() TLS-1.3 SignAlgo::atom()
+
+
signature_scheme(rsa_pkcs1_sha256) -> ?RSA_PKCS1_SHA256;
signature_scheme(rsa_pkcs1_sha384) -> ?RSA_PKCS1_SHA384;
@@ -687,8 +672,8 @@ scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
_Length, _Fragment) ->
<<>>;
-mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
- when N =:= 1; N =:= 2; N =:= 3; N =:= 4 ->
+mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment)
+ when ?TLS_LTE(Version, ?TLS_1_2), Version =/= ?SSL_3_0 ->
tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
Length, Fragment).
@@ -828,9 +813,9 @@ block_size(Cipher) when Cipher == aes_128_cbc;
Cipher == chacha20_poly1305 ->
16.
-prf_algorithm(default_prf, {3, N}) when N >= 3 ->
+prf_algorithm(default_prf, ?TLS_1_2) ->
?SHA256;
-prf_algorithm(default_prf, {3, _}) ->
+prf_algorithm(default_prf, Version) when ?TLS_1_X(Version) ->
?MD5SHA;
prf_algorithm(Algo, _) ->
hash_algorithm(Algo).
@@ -933,8 +918,7 @@ signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS'
%% We return the original (possibly invalid) PadLength in any case.
%% An invalid PadLength will be caught by is_correct_padding/2
%%
-generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
- when N == 0; N == 1 ->
+generic_block_cipher_from_bin(?TLS_1_0, T, IV, HashSize)->
Sz1 = byte_size(T) - 1,
<<_:Sz1/binary, ?BYTE(PadLength0)>> = T,
PadLength = if
@@ -948,8 +932,8 @@ generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
padding=Padding, padding_length=PadLength0,
next_iv = IV};
-generic_block_cipher_from_bin({3, N}, T, IV, HashSize)
- when N == 2; N == 3; N == 4 ->
+generic_block_cipher_from_bin(Version, T, IV, HashSize)
+ when Version == ?TLS_1_1; Version == ?TLS_1_2 ->
Sz1 = byte_size(T) - 1,
<<_:Sz1/binary, ?BYTE(PadLength)>> = T,
IVLength = byte_size(IV),
@@ -968,14 +952,14 @@ generic_stream_cipher_from_bin(T, HashSz) ->
mac=Mac}.
is_correct_padding(#generic_block_cipher{padding_length = Len,
- padding = Padding}, {3, 0}, _) ->
+ padding = Padding}, ?SSL_3_0, _) ->
Len == byte_size(Padding); %% Only length check is done in SSL 3.0 spec
%% For interoperability reasons it is possible to disable
-%% the padding check when using TLS 1.0, as it is not strictly required
+%% the padding check when using TLS 1.0 (mimicking SSL-3.0), as it is not strictly required
%% in the spec (only recommended), however this makes TLS 1.0 vunrable to the Poodle attack
%% so by default this clause will not match
-is_correct_padding(GenBlockCipher, {3, 1}, false) ->
- is_correct_padding(GenBlockCipher, {3, 0}, false);
+is_correct_padding(GenBlockCipher, ?TLS_1_0, false) ->
+ is_correct_padding(GenBlockCipher, ?SSL_3_0, false);
%% Padding must be checked in TLS 1.1 and after
is_correct_padding(#generic_block_cipher{padding_length = Len,
padding = Padding}, _, _) ->
@@ -1053,14 +1037,14 @@ filter_suites_pubkey(ecdsa, Ciphers, _, OtpCert) ->
ec_ecdhe_suites(Ciphers)),
filter_keyuse_suites(keyAgreement, Uses, CiphersSuites, ec_ecdh_suites(Ciphers)).
-filter_suites_signature(_, Ciphers, {3, N}) when N >= 3 ->
+filter_suites_signature(_, Ciphers, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
Ciphers;
filter_suites_signature(rsa, Ciphers, Version) ->
- (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version);
+ (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers);
filter_suites_signature(dsa, Ciphers, Version) ->
(Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- rsa_signed_suites(Ciphers, Version);
filter_suites_signature(ecdsa, Ciphers, Version) ->
- (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version).
+ (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers).
%% From RFC 5246 - Section 7.4.2. Server Certificate
@@ -1079,7 +1063,7 @@ filter_suites_signature(ecdsa, Ciphers, Version) ->
%% extension. The names DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are
%% historical.
%% Note: DH_DSS and DH_RSA is not supported
-rsa_signed({3,N}) when N >= 3 ->
+rsa_signed(?TLS_1_2) ->
fun(rsa) -> true;
(dhe_rsa) -> true;
(ecdhe_rsa) -> true;
@@ -1098,11 +1082,9 @@ rsa_signed(_) ->
end.
%% Cert should be signed by RSA
rsa_signed_suites(Ciphers, Version) ->
- filter_suites(Ciphers, #{key_exchange_filters => [rsa_signed(Version)],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
-ecdsa_signed({3,N}) when N >= 3 ->
+ filter_kex(Ciphers, rsa_signed(Version)).
+
+ecdsa_signed(Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
fun(ecdhe_ecdsa) -> true;
(_) -> false
end;
@@ -1114,10 +1096,7 @@ ecdsa_signed(_) ->
%% Cert should be signed by ECDSA
ecdsa_signed_suites(Ciphers, Version) ->
- filter_suites(Ciphers, #{key_exchange_filters => [ecdsa_signed(Version)],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, ecdsa_signed(Version)).
rsa_keyed(dhe_rsa) ->
true;
@@ -1134,98 +1113,67 @@ rsa_keyed(_) ->
%% Certs key is an RSA key
rsa_keyed_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> rsa_keyed(Kex) end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun rsa_keyed/1).
%% RSA Certs key can be used for encipherment
rsa_suites_encipher(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(rsa) -> true;
- (rsa_psk) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun(rsa) -> true;
+ (rsa_psk) -> true;
+ (_) -> false
+ end).
-dss_keyed(dhe_dss) ->
- true;
-dss_keyed(spr_dss) ->
- true;
-dss_keyed(_) ->
- false.
%% Cert should be have DSS key (DSA)
dss_keyed_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> dss_keyed(Kex) end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun (dhe_dss) -> true;
+ (spr_dss) -> true;
+ (_) -> false
+ end).
%% Cert should be signed by DSS (DSA)
-dsa_signed_suites(Ciphers, Version) ->
- filter_suites(Ciphers, #{key_exchange_filters => [dsa_signed(Version)],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
-dsa_signed(_) ->
- fun(dhe_dss) -> true;
- (_) -> false
- end.
+dsa_signed_suites(Ciphers) ->
+ filter_kex(Ciphers, fun(dhe_dss) -> true;
+ (_) -> false
+ end).
dss_dhe_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_dss) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
-
-ec_keyed(ecdh_ecdsa) ->
- true;
-ec_keyed(ecdh_rsa) ->
- true;
-ec_keyed(ecdhe_ecdsa) ->
- true;
-ec_keyed(_) ->
- false.
-
+ filter_kex(Ciphers, fun(dhe_dss) -> true;
+ (_) -> false
+ end).
%% Certs key is an ECC key
ec_keyed_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> ec_keyed(Kex) end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun (ecdh_ecdsa) -> true;
+ (ecdh_rsa) -> true;
+ (ecdhe_ecdsa) -> true;
+ (_) -> false
+ end).
%% EC Certs key usage keyAgreement
ec_ecdh_suites(Ciphers)->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun(ecdh_ecdsa) -> true;
+ (_) -> false
+ end).
%% EC Certs key usage digitalSignature
ec_ecdhe_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdhe_ecdsa) -> true;
- (ecdhe_rsa) -> true;
- (_) -> false
- end],
- cipher_filters => [],
- mac_filters => [],
- prf_filters => []}).
+ filter_kex(Ciphers, fun(ecdhe_ecdsa) -> true;
+ (ecdhe_rsa) -> true;
+ (_) -> false
+ end).
%% RSA Certs key usage digitalSignature
rsa_ecdhe_dhe_suites(Ciphers) ->
- filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_rsa) -> true;
- (ecdhe_rsa) -> true;
- (_) -> false
- end],
+ filter_kex(Ciphers, fun(dhe_rsa) -> true;
+ (ecdhe_rsa) -> true;
+ (_) -> false
+ end).
+
+filter_kex(Ciphers, Fn) ->
+ filter_suites(Ciphers, #{key_exchange_filters => [Fn],
cipher_filters => [],
mac_filters => [],
prf_filters => []}).
+
key_uses(OtpCert) ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions,
@@ -1256,21 +1204,12 @@ generate_server_share(Group) ->
key_exchange = Key
}}.
-generate_client_shares([]) ->
- #key_share_client_hello{client_shares = []};
generate_client_shares(Groups) ->
- generate_client_shares(Groups, []).
-%%
-generate_client_shares([], Acc) ->
- #key_share_client_hello{client_shares = lists:reverse(Acc)};
-generate_client_shares([Group|Groups], Acc) ->
- Key = generate_key_exchange(Group),
- KeyShareEntry = #key_share_entry{
- group = Group,
- key_exchange = Key
- },
- generate_client_shares(Groups, [KeyShareEntry|Acc]).
-
+ KeyShareEntry = fun (Group) ->
+ #key_share_entry{group = Group, key_exchange = generate_key_exchange(Group)}
+ end,
+ ClientShares = lists:map(KeyShareEntry, Groups),
+ #key_share_client_hello{client_shares = ClientShares}.
generate_key_exchange(secp256r1) ->
public_key:generate_key({namedCurve, secp256r1});
@@ -1308,10 +1247,22 @@ encrypt_ticket(#stateless_ticket{
pre_shared_key = PSK,
ticket_age_add = TicketAgeAdd,
lifetime = Lifetime,
- timestamp = Timestamp
+ timestamp = Timestamp,
+ certificate = Certificate
}, Shard, IV) ->
- Plaintext = <<(ssl_cipher:hash_algorithm(Hash)):8,PSK/binary,
+ Plaintext1 = <<(ssl_cipher:hash_algorithm(Hash)):8,PSK/binary,
?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp)>>,
+ CertificateLength = case Certificate of
+ undefined -> 0;
+ _ -> byte_size(Certificate)
+ end,
+ Plaintext = case CertificateLength of
+ 0 ->
+ <<Plaintext1/binary,?UINT16(0)>>;
+ _ ->
+ <<Plaintext1/binary,?UINT16(CertificateLength),
+ Certificate/binary>>
+ end,
encrypt_ticket_data(Plaintext, Shard, IV).
@@ -1323,13 +1274,19 @@ decrypt_ticket(CipherFragment, Shard, IV) ->
<<?BYTE(HKDF),T/binary>> = Plaintext,
Hash = hash_algorithm(HKDF),
HashSize = hash_size(Hash),
- <<PSK:HashSize/binary,?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp),_/binary>> = T,
+ <<PSK:HashSize/binary,?UINT64(TicketAgeAdd),?UINT32(Lifetime),?UINT32(Timestamp),
+ ?UINT16(CertificateLength),Certificate1:CertificateLength/binary,_/binary>> = T,
+ Certificate = case CertificateLength of
+ 0 -> undefined;
+ _ -> Certificate1
+ end,
#stateless_ticket{
hash = Hash,
pre_shared_key = PSK,
ticket_age_add = TicketAgeAdd,
lifetime = Lifetime,
- timestamp = Timestamp
+ timestamp = Timestamp,
+ certificate = Certificate
}
end.
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 77305c1e77..cf5bd06a35 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -34,7 +34,8 @@
pre_shared_key,
ticket_age_add,
lifetime,
- timestamp
+ timestamp,
+ certificate
}).
%%% SSL cipher protocol %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl
index 46578c05a5..761a4f4315 100644
--- a/lib/ssl/src/ssl_config.erl
+++ b/lib/ssl/src/ssl_config.erl
@@ -40,40 +40,24 @@
%%====================================================================
%% Internal application API
%%====================================================================
-init(#{erl_dist := ErlDist,
- dh := DH,
- dhfile := DHFile} = SslOpts, Role) ->
-
- init_manager_name(ErlDist),
+init(SslOpts, Role) ->
+ init_manager_name(maps:get(erl_dist, SslOpts, false)),
#{pem_cache := PemCache} = Config = init_cacerts(SslOpts, Role),
- DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
-
+ DHParams = init_diffie_hellman(PemCache, SslOpts, Role),
CertKeyAlts = init_certs_keys(SslOpts, Role, PemCache),
-
{ok, Config#{cert_key_alts => CertKeyAlts, dh_params => DHParams}}.
init_certs_keys(#{certs_keys := CertsKeys}, Role, PemCache) ->
- Pairs = lists:map(fun(CertKey) -> cert_key_pair(CertKey, Role, PemCache) end, CertsKeys),
+ Pairs = lists:map(fun(CertKey) -> init_cert_key_pair(CertKey, Role, PemCache) end, CertsKeys),
CertKeyGroups = group_pairs(Pairs),
- prioritize_groups(CertKeyGroups);
-init_certs_keys(SslOpts, Role, PemCache) ->
- KeyPair = init_cert_key_pair(SslOpts, Role, PemCache),
- group_pairs([KeyPair]).
-
-init_cert_key_pair(#{key := Key,
- keyfile := KeyFile,
- password := Password} = Opts, Role, PemCache) ->
- {ok, Certs} = init_certificates(Opts, PemCache, Role),
- PrivateKey =
- init_private_key(PemCache, Key, KeyFile, Password, Role),
- #{private_key => PrivateKey, certs => Certs}.
-
-cert_key_pair(CertKey, Role, PemCache) ->
- CertKeyPairConf = cert_conf(key_conf(CertKey)),
- init_cert_key_pair(CertKeyPairConf, Role, PemCache).
+ prioritize_groups(CertKeyGroups).
+init_cert_key_pair(CertKey, Role, PemCache) ->
+ Certs = init_certificates(CertKey, PemCache, Role),
+ PrivateKey = init_private_key(maps:get(key, CertKey, undefined), CertKey, PemCache),
+ #{private_key => PrivateKey, certs => Certs}.
-group_pairs([#{certs := [[]]}]) ->
+group_pairs([#{certs := []}]) ->
#{eddsa => [],
ecdsa => [],
rsa_pss_pss => [],
@@ -87,8 +71,7 @@ group_pairs(Pairs) ->
rsa => [],
dsa => []
}).
-group_pairs([], Group) ->
- Group;
+
group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed25519'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) ->
group_pairs(Rest, Group#{eddsa => [Pair | EDDSA]});
group_pairs([#{private_key := #'ECPrivateKey'{parameters = {namedCurve, ?'id-Ed448'}}} = Pair | Rest], #{eddsa := EDDSA} = Group) ->
@@ -106,7 +89,10 @@ group_pairs([#{private_key := #{algorithm := dss, engine := _}} = Pair | Rest],
group_pairs(Rest, Group#{dsa => [Pair | Pairs]});
group_pairs([#{private_key := #{algorithm := Alg, engine := _}} = Pair | Rest], Group) ->
Pairs = maps:get(Alg, Group),
- group_pairs(Rest, Group#{Alg => [Pair | Pairs]}).
+ group_pairs(Rest, Group#{Alg => [Pair | Pairs]});
+group_pairs([], Group) ->
+ Group.
+
prioritize_groups(#{eddsa := EDDSA,
ecdsa := ECDSA,
@@ -178,25 +164,6 @@ prio_dsa(DSA) ->
end,
lists:sort(Order, DSA).
-key_conf(#{key := _} = Conf) ->
- Conf#{certfile => <<>>,
- keyfile => <<>>,
- password => undefined};
-key_conf(#{keyfile := _} = Conf) ->
- case maps:get(password, Conf, undefined) of
- undefined ->
- Conf#{key => undefined,
- password => undefined};
- _ ->
- Conf#{key => undefined}
- end.
-
-cert_conf(#{cert := Bin} = Conf) when is_binary(Bin)->
- Conf#{cert => [Bin]};
-cert_conf(#{cert := _} = Conf) ->
- Conf#{certfile => <<>>};
-cert_conf(#{certfile := _} = Conf) ->
- Conf#{cert => undefined}.
pre_1_3_session_opts(Role) ->
{Cb, InitArgs} = session_cb_opts(Role),
@@ -261,17 +228,13 @@ init_manager_name(true) ->
put(ssl_manager, ssl_manager:name(dist)),
put(ssl_pem_cache, ssl_pem_cache:name(dist)).
-init_cacerts(#{cacerts := CaCerts,
- cacertfile := CACertFile,
- crl_cache := CRLCache
- }, Role) ->
+init_cacerts(#{cacerts := CaCerts, crl_cache := CRLCache} = Opts, Role) ->
+ CACertFile = maps:get(cacertfile, Opts, <<>>),
{ok, Config} =
- try
+ try
Certs = case CaCerts of
- undefined ->
- CACertFile;
- _ ->
- {der, CaCerts}
+ undefined -> CACertFile;
+ _ -> {der, CaCerts}
end,
{ok,_} = ssl_manager:connection_init(Certs, Role, CRLCache)
catch
@@ -280,65 +243,56 @@ init_cacerts(#{cacerts := CaCerts,
end,
Config.
-init_certificates(#{certfile := CertFile,
- cert := OwnCerts}, PemCache, Role) ->
- init_certificates(OwnCerts, PemCache, CertFile, Role).
-
-init_certificates(undefined, _, <<>>, _) ->
- {ok, [[]]};
-init_certificates(undefined, PemCache, CertFile, client) ->
- try
- %% OwnCert | [OwnCert | Chain]
- OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, OwnCerts}
- catch _Error:_Reason ->
- {ok, [[]]}
- end;
-init_certificates(undefined, PemCache, CertFile, server) ->
- try
- %% OwnCert | [OwnCert | Chain]
- OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache),
- {ok, OwnCerts}
+init_certificates(CertKey, PemCache, Role) ->
+ case maps:get(cert, CertKey, undefined) of
+ undefined ->
+ init_certificate_file(maps:get(certfile, CertKey, <<>>), PemCache, Role);
+ Bin when is_binary(Bin) ->
+ [Bin];
+ Certs when is_list(Certs) ->
+ Certs
+ end.
+
+init_certificate_file(<<>>, _PemCache, _Role) ->
+ [];
+init_certificate_file(CertFile, PemCache, Role) ->
+ try %% OwnCert | [OwnCert | Chain]
+ ssl_certificate:file_to_certificats(CertFile, PemCache)
catch
- _:Reason ->
- file_error(CertFile, {certfile, Reason})
- end;
-init_certificates(OwnCerts, _, _, _) when is_binary(OwnCerts)->
- {ok, [OwnCerts]};
-init_certificates(OwnCerts, _, _, _) ->
- {ok, OwnCerts}.
-
-init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
- Alg == rsa;
- Alg == dss ->
+ _Error:_Reason when Role =:= client ->
+ [];
+ _Error:Reason ->
+ file_error(CertFile, {certfile, Reason})
+ end.
+
+init_private_key(#{algorithm := Alg} = Key, _, _PemCache)
+ when Alg =:= ecdsa; Alg =:= rsa; Alg =:= dss ->
case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of
- true ->
- Key;
- false ->
- throw({key, {invalid_key_id, Key}})
- end;
-init_private_key(_, undefined, <<>>, _Password, _Client) ->
- #{};
-init_private_key(DbHandle, undefined, KeyFile, Password, _) ->
- try
- {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
- [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
- PKey =:= 'RSAPrivateKey' orelse
- PKey =:= 'DSAPrivateKey' orelse
- PKey =:= 'ECPrivateKey' orelse
- PKey =:= 'PrivateKeyInfo'
- ],
- private_key(public_key:pem_entry_decode(PemEntry, Password))
- catch
- _:Reason ->
- file_error(KeyFile, {keyfile, Reason})
+ true -> Key;
+ false -> throw({key, {invalid_key_id, Key}})
end;
-
-init_private_key(_,{Asn1Type, PrivateKey},_,_,_) ->
- private_key(init_private_key(Asn1Type, PrivateKey)).
-
-init_private_key(Asn1Type, PrivateKey) ->
- public_key:der_decode(Asn1Type, PrivateKey).
+init_private_key({Asn1Type, PrivateKey},_,_) ->
+ private_key(public_key:der_decode(Asn1Type, PrivateKey));
+init_private_key(undefined, CertKey, DbHandle) ->
+ case maps:get(keyfile, CertKey, undefined) of
+ undefined ->
+ #{};
+ KeyFile ->
+ Password = maps:get(password, CertKey, undefined),
+ try
+ {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
+ [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
+ PKey =:= 'RSAPrivateKey' orelse
+ PKey =:= 'DSAPrivateKey' orelse
+ PKey =:= 'ECPrivateKey' orelse
+ PKey =:= 'PrivateKeyInfo'
+ ],
+ private_key(public_key:pem_entry_decode(PemEntry, Password))
+ catch
+ _:Reason ->
+ file_error(KeyFile, {keyfile, Reason})
+ end
+ end.
private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
#'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'},
@@ -370,27 +324,35 @@ file_error(File, Throw) ->
throw(Throw)
end.
-init_diffie_hellman(_,Params, _,_) when is_binary(Params)->
- public_key:der_decode('DHParameter', Params);
-init_diffie_hellman(_,_,_, client) ->
+init_diffie_hellman(_, _, client) ->
undefined;
-init_diffie_hellman(_,_,undefined, _) ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS;
-init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
+init_diffie_hellman(DbHandle, Opts, server) ->
+ case maps:get(dh, Opts, undefined) of
+ Bin when is_binary(Bin) ->
+ public_key:der_decode('DHParameter', Bin);
+ _ ->
+ case maps:get(dh, Opts, undefined) of
+ undefined ->
+ ?DEFAULT_DIFFIE_HELLMAN_PARAMS;
+ DHParamFile ->
+ dh_file(DbHandle, DHParamFile)
+ end
+ end.
+
+dh_file(DbHandle, DHParamFile) ->
try
- {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle),
- case [Entry || Entry = {'DHParameter', _ , _} <- List] of
- [Entry] ->
- public_key:pem_entry_decode(Entry);
- [] ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS
- end
+ {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle),
+ case [Entry || Entry = {'DHParameter', _ , _} <- List] of
+ [Entry] ->
+ public_key:pem_entry_decode(Entry);
+ [] ->
+ ?DEFAULT_DIFFIE_HELLMAN_PARAMS
+ end
catch
- _:Reason ->
- file_error(DHParamFile, {dhfile, Reason})
+ _:Reason ->
+ file_error(DHParamFile, {dhfile, Reason})
end.
-
session_cb_init_args(client) ->
case application:get_env(ssl, client_session_cb_init_args) of
undefined ->
diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl
index 62c09c60ae..12a4721a09 100644
--- a/lib/ssl/src/ssl_gen_statem.erl
+++ b/lib/ssl/src/ssl_gen_statem.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -38,7 +38,8 @@
init/1]).
%% TLS connection setup
--export([ssl_config/3,
+-export([init_ssl_config/3,
+ ssl_config/3,
connect/8,
handshake/7,
handshake/2,
@@ -60,7 +61,8 @@
set_opts/2,
peer_certificate/1,
negotiated_protocol/1,
- connection_information/2
+ connection_information/2,
+ ktls_handover/1
]).
%% Erlang Distribution export
@@ -96,6 +98,9 @@
%% Log handling
-export([format_status/2]).
+%% Tracing
+-export([handle_trace/3]).
+
%%--------------------------------------------------------------------
%%% Initial Erlang process setup
%%--------------------------------------------------------------------
@@ -106,7 +111,8 @@
%% Description: Creates a process which calls Module:init/1 to
%% choose appropriat gen_statem and initialize.
%%--------------------------------------------------------------------
-start_link(Role, Sender, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) ->
+start_link(Role, Sender, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo) ->
+ ReceiverOpts = maps:get(receiver_spawn_opts, SslOpts, []),
Opts = [link | proplists:delete(link, ReceiverOpts)],
Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]], Opts),
{ok, Pid}.
@@ -118,7 +124,8 @@ start_link(Role, Sender, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverO
%% Description: Creates a gen_statem process which calls Module:init/1 to
%% initialize.
%%--------------------------------------------------------------------
-start_link(Role, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _, _} = Options, User, CbInfo) ->
+start_link(Role, Host, Port, Socket, {SslOpts, _, _} = Options, User, CbInfo) ->
+ ReceiverOpts = maps:get(receiver_spawn_opts, SslOpts, []),
Opts = [link | proplists:delete(link, ReceiverOpts)],
Pid = proc_lib:spawn_opt(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]], Opts),
{ok, Pid}.
@@ -128,30 +135,51 @@ start_link(Role, Host, Port, Socket, {#{receiver_spawn_opts := ReceiverOpts}, _,
-spec init(list()) -> no_return().
%% Description: Initialization
%%--------------------------------------------------------------------
-init([_Role, _Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+init([Role, _Sender, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
process_flag(trap_exit, true),
- case ErlDist of
+ case maps:get(erl_dist, TLSOpts, false) of
true ->
process_flag(priority, max);
_ ->
ok
end,
- ConnectionFsm = tls_connection_fsm(TLSOpts),
- ConnectionFsm:init(InitArgs);
-init([_Role, _Host, _Port, _Socket, {TLSOpts, _, _}, _User, _CbInfo] = InitArgs) ->
+ case {Role, TLSOpts} of
+ {?CLIENT_ROLE, #{versions := [?TLS_1_3]}} ->
+ tls_client_connection_1_3:init(InitArgs);
+ {?SERVER_ROLE, #{versions := [?TLS_1_3]}} ->
+ tls_server_connection_1_3:init(InitArgs);
+ {_,_} ->
+ tls_connection:init(InitArgs)
+ end;
+init([_Role, _Host, _Port, _Socket, _TLSOpts, _User, _CbInfo] = InitArgs) ->
process_flag(trap_exit, true),
- ConnectionFsm = dtls_connection_fsm(TLSOpts),
- ConnectionFsm:init(InitArgs).
+ dtls_connection:init(InitArgs).
%%====================================================================
%% TLS connection setup
%%====================================================================
%%--------------------------------------------------------------------
+-spec init_ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
+%%--------------------------------------------------------------------
+init_ssl_config(Opts, Role, #state{ssl_options = #{handshake := Handshake},
+ handshake_env = HsEnv} = State0) ->
+ ContinueStatus = case Handshake of
+ hello ->
+ %% Will pause handshake after hello message to
+ %% enable user to react to hello extensions
+ pause;
+ full ->
+ Handshake
+ end,
+ ssl_config(Opts, Role,
+ State0#state{handshake_env =
+ HsEnv#handshake_env{continue_status = ContinueStatus}}).
+
+%%--------------------------------------------------------------------
-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}.
%%--------------------------------------------------------------------
ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
- ssl_options = #{handshake := Handshake},
handshake_env = HsEnv,
connection_env = CEnv} = State0) ->
{ok, #{cert_db_ref := Ref,
@@ -165,15 +193,6 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
TimeStamp = erlang:monotonic_time(),
Session = State0#state.session,
- ContinueStatus = case Handshake of
- hello ->
- %% Will pause handshake after hello message to
- %% enable user to react to hello extensions
- pause;
- full ->
- Handshake
- end,
-
State0#state{session = Session#session{time_stamp = TimeStamp},
static_env = InitStatEnv0#static_env{
file_ref_db = FileRefHandle,
@@ -182,8 +201,8 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
crl_db = CRLDbHandle,
session_cache = CacheHandle
},
- handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams,
- continue_status = ContinueStatus},
+ handshake_env =
+ HsEnv#handshake_env{diffie_hellman_params = DHParams},
connection_env = CEnv#connection_env{cert_key_alts = CertKeyAlts},
ssl_options = Opts}.
@@ -292,21 +311,23 @@ socket_control(Connection, Socket, Pid, Transport) ->
-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom(), [pid()] | atom()) ->
{ok, #sslsocket{}} | {error, reason()}.
%%--------------------------------------------------------------------
-socket_control(dtls_gen_connection = Connection, Socket, Pids, Transport, udp_listener) ->
+socket_control(dtls_gen_connection, Socket, Pids, Transport, udp_listener) ->
%% dtls listener process must have the socket control
- {ok, Connection:socket(Pids, Transport, Socket, undefined)};
+ {ok, dtls_gen_connection:socket(Pids, Transport, Socket, undefined)};
-socket_control(tls_gen_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
+socket_control(tls_gen_connection, Socket, [Pid|_] = Pids, Transport, Trackers) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, Socket, Trackers)};
+ {ok, tls_gen_connection:socket(Pids, Transport, Socket, Trackers)};
{error, Reason} ->
{error, Reason}
end;
-socket_control(dtls_gen_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) ->
+socket_control(dtls_gen_connection, {PeerAddrPort, Socket},
+ [Pid|_] = Pids, Transport, Trackers) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)};
+ {ok, dtls_gen_connection:socket(Pids, Transport, {PeerAddrPort, Socket},
+ Trackers)};
{error, Reason} ->
{error, Reason}
end.
@@ -420,6 +441,14 @@ peer_certificate(ConnectionPid) ->
negotiated_protocol(ConnectionPid) ->
call(ConnectionPid, negotiated_protocol).
+%%--------------------------------------------------------------------
+-spec ktls_handover(pid()) -> {ok, map()} | {error, reason()}.
+%%
+%% Description: Returns the negotiated protocol
+%%--------------------------------------------------------------------
+ktls_handover(ConnectionPid) ->
+ call(ConnectionPid, ktls_handover).
+
dist_handshake_complete(ConnectionPid, DHandle) ->
gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}).
@@ -460,8 +489,6 @@ initial_hello({call, From}, {start, Timeout},
%% Versions is a descending list of supported versions.
versions := [HelloVersion|_] = Versions,
session_tickets := SessionTickets,
- ocsp_stapling := OcspStaplingOpt,
- ocsp_nonce := OcspNonceOpt,
early_data := EarlyData} = SslOpts,
session = Session,
connection_states = ConnectionStates0
@@ -471,9 +498,9 @@ initial_hello({call, From}, {start, Timeout},
%% Update UseTicket in case of automatic session resumption. The automatic ticket handling
%% also takes it into account if the ticket is suitable for sending early data not exceeding
%% the max_early_data_size or if it can only be used for session resumption.
- {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0),
+ {UseTicket, State1} = tls_client_connection_1_3:maybe_automatic_session_resumption(State0),
TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
- OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt),
+ OcspNonce = tls_handshake:ocsp_nonce(SslOpts),
Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Session#session.session_id,
Renegotiation,
@@ -507,21 +534,24 @@ initial_hello({call, From}, {start, Timeout},
%% ServerHello is processed.
RequestedVersion = tls_record:hello_version(Versions),
- {Ref,Maybe} = tls_handshake_1_3:maybe(),
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
try
%% Send Early Data
- State4 = Maybe(tls_handshake_1_3:maybe_send_early_data(State3)),
+ State4 = Maybe(tls_client_connection_1_3:maybe_send_early_data(State3)),
{#state{handshake_env = HsEnv1} = State5, _} =
Connection:send_handshake_flight(State4),
+ OcspStaplingKeyPresent = maps:is_key(ocsp_stapling, SslOpts),
State = State5#state{
connection_env = CEnv#connection_env{
negotiated_version = RequestedVersion},
session = Session,
- handshake_env = HsEnv1#handshake_env{
- ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce,
- ocsp_stapling => OcspStaplingOpt}},
+ handshake_env =
+ HsEnv1#handshake_env{
+ ocsp_stapling_state =
+ OcspState0#{ocsp_nonce => OcspNonce,
+ ocsp_stapling => OcspStaplingKeyPresent}},
start_or_recv_from = From,
key_share = KeyShare},
NextState = next_statem_state(Versions, Role),
@@ -534,17 +564,17 @@ initial_hello({call, From}, {start, Timeout},
initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{role = Role,
protocol_cb = Connection},
ssl_options = #{versions := Versions}} = State0) ->
-
- NextState = next_statem_state(Versions, Role),
+
+ NextState = next_statem_state(Versions, Role),
Connection:next_event(NextState, no_record, State0#state{start_or_recv_from = From},
[{{timeout, handshake}, Timeout, close}]);
-
+
initial_hello({call, From}, {start, {Opts, EmOpts}, Timeout},
#state{static_env = #static_env{role = Role},
ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0) ->
try
- SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions),
+ SslOpts = ssl:update_options(Opts, Role, OrigSSLOptions),
State = ssl_config(SslOpts, Role, State0),
initial_hello({call, From}, {start, Timeout},
State#state{ssl_options = SslOpts,
@@ -646,6 +676,45 @@ connection({call, From},
{error, timeout} ->
{stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
end;
+connection({call, From}, ktls_handover, #state{
+ static_env = #static_env{
+ transport_cb = Transport,
+ socket = Socket
+ },
+ connection_env = #connection_env{
+ user_application = {_Mon, Pid},
+ negotiated_version = TlsVersion
+ },
+ ssl_options = #{ktls := true},
+ socket_options = SocketOpts,
+ connection_states = #{
+ current_write := #{
+ security_parameters := #security_parameters{cipher_suite = CipherSuite},
+ cipher_state := WriteState,
+ sequence_number := WriteSeq
+ },
+ current_read := #{
+ cipher_state := ReadState,
+ sequence_number := ReadSeq
+ }
+ }
+}) ->
+ Reply = case Transport:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, #{
+ socket => Socket,
+ tls_version => TlsVersion,
+ cipher_suite => CipherSuite,
+ socket_options => SocketOpts,
+ write_state => WriteState,
+ write_seq => WriteSeq,
+ read_state => ReadState,
+ read_seq => ReadSeq
+ }};
+ {error, Reason} ->
+ {error, Reason}
+ end,
+ {stop_and_reply, {shutdown, ktls}, [{reply, From, Reply}]};
connection({call, From}, Msg, State) ->
handle_call(Msg, From, ?FUNCTION_NAME, State);
connection(cast, {dist_handshake_complete, DHandle},
@@ -953,8 +1022,9 @@ passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear},
%%====================================================================
hibernate_after(connection = StateName,
- #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ #state{ssl_options= SslOpts} = State,
Actions) ->
+ HibernateAfter = maps:get(hibernate_after, SslOpts, infinity),
{next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
@@ -1006,26 +1076,8 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
Pids = Connection:pids(State),
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,
- trackers = Trackers,
- transport_cb = Transport,
- protocol_cb = Connection},
- connection_env = #connection_env{user_application = {_Mon, Pid}},
- ssl_options = #{log_level := LogLevel},
- start_or_recv_from = From,
- session = Session,
- socket_options = Opts} = State) ->
- invalidate_session(Role, Host, Port, Session),
- Alert = Alert0#alert{role = opposite_role(Role)},
- log_alert(LogLevel, Role, Connection:protocol_name(),
- StateName, Alert),
- Pids = Connection:pids(State),
- alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
- {stop, {shutdown, normal}, State};
+handle_alert(#alert{level = ?FATAL} = Alert, StateName, State) ->
+ handle_fatal_alert(Alert, StateName, State);
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
downgrade= StateName, State) ->
{next_state, StateName, State, [{next_event, internal, Alert}]};
@@ -1092,27 +1144,61 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert,
%% Go back to connection!
State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
Connection:next_event(connection, no_record, State);
+handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = #{log_level := LogLevel}} = State) when StateName =/= connection ->
+ log_alert(LogLevel, Role,
+ Connection:protocol_name(), StateName,
+ Alert#alert{role = opposite_role(Role)}),
+ %% Wait for close alert that should follow or handshake timeout
+ Connection:next_event(StateName, no_record, State);
%% Gracefully log and ignore all other warning alerts pre TLS-1.3
handle_alert(#alert{level = ?WARNING} = Alert, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
connection_env = #connection_env{negotiated_version = Version},
- ssl_options = #{log_level := LogLevel}} = State) when Version < {3,4} ->
+ ssl_options = #{log_level := LogLevel}} = State) when ?TLS_LT(Version, ?TLS_1_3) ->
log_alert(LogLevel, Role,
Connection:protocol_name(), StateName,
Alert#alert{role = opposite_role(Role)}),
Connection:next_event(StateName, no_record, State);
-handle_alert(Alert0, StateName, State) ->
+handle_alert(Alert, StateName, State) ->
%% In TLS-1.3 all error alerts are fatal not matter of legacy level
- handle_alert(Alert0#alert{level = ?FATAL}, StateName, State).
+ %% but keep the level for the log so that users looking at what is
+ %% sent and what is logged are not confused! Or if some one sends
+ %% user cancel alert in connection which is inappropriate!
+ handle_fatal_alert(Alert, StateName, State).
+
+handle_fatal_alert(Alert0, StateName,
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ trackers = Trackers,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ connection_env = #connection_env{user_application = {_Mon, Pid}},
+ ssl_options = #{log_level := LogLevel},
+ start_or_recv_from = From,
+ session = Session,
+ socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ Alert = Alert0#alert{role = opposite_role(Role)},
+ log_alert(LogLevel, Role, Connection:protocol_name(),
+ StateName, Alert),
+ Pids = Connection:pids(State),
+ alert_user(Pids, Transport, Trackers, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
+ {stop, {shutdown, normal}, State}.
-handle_trusted_certs_db(#state{ssl_options =
- #{cacertfile := <<>>, cacerts := []}}) ->
+handle_trusted_certs_db(#state{ssl_options =#{cacerts := []} = Opts})
+ when not is_map_key(cacertfile, Opts) ->
%% No trusted certs specified
ok;
handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
cert_db = CertDb},
- ssl_options = #{cacertfile := <<>>}}) when CertDb =/= undefined ->
+ ssl_options = Opts})
+ when CertDb =/= undefined, not is_map_key(cacertfile, Opts) ->
%% Certs provided as DER directly can not be shared
%% with other connections and it is safe to delete them when the connection ends.
ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
@@ -1130,9 +1216,9 @@ handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
ok
end.
-maybe_invalidate_session({3, 4},_, _, _, _, _) ->
+maybe_invalidate_session(?TLS_1_3,_, _, _, _, _) ->
ok;
-maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 ->
+maybe_invalidate_session(Version, Type, Role, Host, Port, Session) when ?TLS_LT(Version, ?TLS_1_3) ->
maybe_invalidate_session(Type, Role, Host, Port, Session).
maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
@@ -1140,6 +1226,9 @@ maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) ->
maybe_invalidate_session(_, _, _, _, _) ->
ok.
+terminate({shutdown, ktls}, connection, State) ->
+ %% Socket shall not be closed as it should be returned to user
+ handle_trusted_certs_db(State);
terminate({shutdown, downgrade}, downgrade, State) ->
%% Socket shall not be closed as it should be returned to user
handle_trusted_certs_db(State);
@@ -1191,10 +1280,9 @@ format_status(normal, [_, StateName, State]) ->
[{data, [{"State", {StateName, State}}]}];
format_status(terminate, [_, StateName, State]) ->
SslOptions = (State#state.ssl_options),
- NewOptions = SslOptions#{password => ?SECRET_PRINTOUT,
- cert => ?SECRET_PRINTOUT,
+ NewOptions = SslOptions#{
+ certs_keys => ?SECRET_PRINTOUT,
cacerts => ?SECRET_PRINTOUT,
- key => ?SECRET_PRINTOUT,
dh => ?SECRET_PRINTOUT,
psk_identity => ?SECRET_PRINTOUT,
srp_identity => ?SECRET_PRINTOUT},
@@ -1210,24 +1298,16 @@ format_status(terminate, [_, StateName, State]) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-tls_connection_fsm(#{versions := [{3,4}]}) ->
- tls_connection_1_3;
-tls_connection_fsm(_) ->
- tls_connection.
-
-dtls_connection_fsm(_) ->
- dtls_connection.
-
next_statem_state([Version], client) ->
case ssl:tls_version(Version) of
- {3,4} ->
+ ?TLS_1_3 ->
wait_sh;
_ ->
hello
end;
next_statem_state([Version], server) ->
case ssl:tls_version(Version) of
- {3,4} ->
+ ?TLS_1_3 ->
start;
_ ->
hello
@@ -1276,11 +1356,8 @@ is_sni_value(Hostname) ->
true
end.
-is_hostname_recognized(#{sni_fun := undefined,
- sni_hosts := SNIHosts}, Hostname) ->
- proplists:is_defined(Hostname, SNIHosts);
-is_hostname_recognized(_, _) ->
- true.
+is_hostname_recognized(#{sni_fun := Fun}, Hostname) ->
+ Fun(Hostname) =:= undefined.
handle_sni_hostname(Hostname,
#state{static_env = #static_env{role = Role} = InitStatEnv0,
@@ -1290,7 +1367,7 @@ handle_sni_hostname(Hostname,
NewOptions = update_ssl_options_from_sni(Opts, Hostname),
case NewOptions of
undefined ->
- case maps:get(server_name_indication, Opts) of
+ case maps:get(server_name_indication, Opts, undefined) of
disable when Role == client->
State0;
_ ->
@@ -1320,23 +1397,14 @@ handle_sni_hostname(Hostname,
}
end.
-update_ssl_options_from_sni(#{sni_fun := SNIFun,
- sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) ->
- SSLOptions =
- case SNIFun of
- undefined ->
- proplists:get_value(SNIHostname,
- SNIHosts);
- SNIFun ->
- SNIFun(SNIHostname)
- end,
- case SSLOptions of
+update_ssl_options_from_sni(#{sni_fun := SNIFun} = OrigSSLOptions, SNIHostname) ->
+ case SNIFun(SNIHostname) of
undefined ->
undefined;
- _ ->
+ SSLOptions ->
VersionsOpt = proplists:get_value(versions, SSLOptions, []),
FallBackOptions = filter_for_versions(VersionsOpt, OrigSSLOptions),
- ssl:handle_options(SSLOptions, server, FallBackOptions)
+ ssl:update_options(SSLOptions, server, FallBackOptions)
end.
filter_for_versions([], OrigSSLOptions) ->
@@ -1897,7 +1965,7 @@ connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname
security_info(#state{connection_states = ConnectionStates,
static_env = #static_env{role = Role},
- ssl_options = #{keep_secrets := KeepSecrets},
+ ssl_options = Opts,
protocol_specific = ProtocolSpecific}) ->
ReadState = ssl_record:current_connection_state(ConnectionStates, read),
#{security_parameters :=
@@ -1908,6 +1976,8 @@ security_info(#state{connection_states = ConnectionStates,
client_early_data_secret = ServerEarlyData
}} = ReadState,
BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}],
+
+ KeepSecrets = maps:get(keep_secrets, Opts, false),
if KeepSecrets =/= true ->
BaseSecurityInfo;
true ->
@@ -2175,12 +2245,37 @@ keylog_secret(SecretBin, sha384) ->
keylog_secret(SecretBin, sha512) ->
io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]).
-maybe_generate_client_shares(#{versions := [Version|_],
+maybe_generate_client_shares(#{versions := [?TLS_1_3|_],
supported_groups :=
#supported_groups{
- supported_groups = [Group|_]}})
- when Version =:= {3,4} ->
+ supported_groups = [Group|_]}}) ->
%% Generate only key_share entry for the most preferred group
ssl_cipher:generate_client_shares([Group]);
maybe_generate_client_shares(_) ->
undefined.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(api,
+ {call, {?MODULE, connect, [Connection | _]}}, Stack0) ->
+ {io_lib:format("Connection = ~w", [Connection]), Stack0};
+handle_trace(rle,
+ {call, {?MODULE, init, Args = [[Role | _]]}}, Stack0) ->
+ {io_lib:format("(*~w) Args = ~W", [Role, Args, 3]), [{role, Role} | Stack0]};
+handle_trace(hbn,
+ {call, {?MODULE, hibernate_after,
+ [_StateName = connection, State, Actions]}},
+ Stack) ->
+ #state{ssl_options= #{hibernate_after := HibernateAfter}} = State,
+ {io_lib:format("* * * maybe hibernating in ~w ms * * * Actions = ~W ",
+ [HibernateAfter, Actions, 10]), Stack};
+handle_trace(hbn,
+ {return_from, {?MODULE, hibernate_after, 3},
+ {Cmd, Arg,_State, Actions}},
+ Stack) ->
+ {io_lib:format("Cmd = ~w Arg = ~w Actions = ~W", [Cmd, Arg, Actions, 10]), Stack};
+handle_trace(hbn,
+ {call, {?MODULE, handle_common_event, [timeout, hibernate, connection | _]}}, Stack) ->
+ {io_lib:format("* * * hibernating * * *", []), Stack}.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 56a1ca81b9..fb30372999 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -23,6 +23,7 @@
%%----------------------------------------------------------------------
-module(ssl_handshake).
+-feature(maybe_expr,enable).
-include("ssl_handshake.hrl").
-include("ssl_record.hrl").
@@ -59,7 +60,7 @@
]).
%% Encode
--export([encode_handshake/2, encode_hello_extensions/2, encode_extensions/1, encode_extensions/2,
+-export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2,
encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]).
%% Decode
-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3,
@@ -69,13 +70,13 @@
%% Cipher suites handling
-export([available_suites/2, available_signature_algs/2, available_signature_algs/3,
- cipher_suites/3, prf/6, select_session/9, supported_ecc/1,
+ cipher_suites/3, prf/6, select_session/9,
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%% Extensions handling
-export([client_hello_extensions/10,
handle_client_hello_extensions/10, %% Returns server hello extensions
- handle_server_hello_extensions/10, select_curve/3, select_curve/4,
+ handle_server_hello_extensions/10, select_curve/2, select_curve/3,
select_hashsign/4, select_hashsign/5,
select_hashsign_algs/3, empty_extensions/2, add_server_share/3,
add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1
@@ -84,11 +85,12 @@
-export([get_cert_params/1,
select_own_cert/1,
server_name/3,
- path_validate/9,
path_validation/10,
validation_fun_and_state/4,
path_validation_alert/1]).
+%% Tracing
+-export([handle_trace/3]).
%%====================================================================
%% Create handshake messages
%%====================================================================
@@ -337,10 +339,9 @@ next_protocol(SelectedProtocol) ->
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
- #{server_name_indication := ServerNameIndication,
- partial_chain := PartialChain} = SSlOptions,
+ #{partial_chain := PartialChain} = SSlOptions,
CRLDbHandle, Role, Host, Version, CertExt) ->
- ServerName = server_name(ServerNameIndication, Host, Role),
+ ServerName = server_name(SSlOptions, Host, Role),
[PeerCert | _ChainCerts ] = ASN1Certs,
try
PathsAndAnchors =
@@ -391,26 +392,29 @@ verify_signature(_, Msg, {HashAlgo, SignAlgo}, Signature,
SignAlgo == rsa_pss_pss ->
Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
public_key:verify(Msg, HashAlgo, Signature, PubKey, Options);
-verify_signature({3, Minor}, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams})
- when Minor >= 3 ->
+verify_signature(Version, Msg, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams})
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
Options = verify_options(SignAlgo, HashAlgo, PubKeyParams),
public_key:verify(Msg, HashAlgo, Signature, PubKey, Options);
-verify_signature({3, Minor}, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when Minor =< 2 ->
+verify_signature(Version, {digest, Digest}, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams})
+ when ?TLS_LTE(Version, ?TLS_1_1) ->
case public_key:decrypt_public(Signature, PubKey,
[{rsa_pad, rsa_pkcs1_padding}]) of
Digest -> true;
_ -> false
end;
-verify_signature({3, 4}, Msg, {_, eddsa}, Signature, {?'id-Ed25519', PubKey, PubKeyParams}) ->
+verify_signature(?TLS_1_3, Msg, {_, eddsa}, Signature, {?'id-Ed25519', PubKey, PubKeyParams}) ->
public_key:verify(Msg, none, Signature, {PubKey, PubKeyParams});
-verify_signature({3, 4}, Msg, {_, eddsa}, Signature, {?'id-Ed448', PubKey, PubKeyParams}) ->
+verify_signature(?TLS_1_3, Msg, {_, eddsa}, Signature, {?'id-Ed448', PubKey, PubKeyParams}) ->
public_key:verify(Msg, none, Signature, {PubKey, PubKeyParams});
verify_signature(_, Msg, {HashAlgo, _SignAlg}, Signature,
{?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
public_key:verify(Msg, HashAlgo, Signature, {PublicKey, PublicKeyParams});
-verify_signature({3, Minor}, _Msg, {_HashAlgo, anon}, _Signature, _) when Minor =< 3 ->
+verify_signature(Version, _Msg, {_HashAlgo, anon}, _Signature, _)
+ when ?TLS_1_X(Version), ?TLS_LTE(Version, ?TLS_1_2) ->
true;
-verify_signature({3, Minor}, Msg, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when Minor =< 3->
+verify_signature(Version, Msg, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams})
+ when ?TLS_1_X(Version), ?TLS_LTE(Version, ?TLS_1_2) ->
public_key:verify(Msg, HashAlgo, Signature, {PublicKey, PublicKeyParams}).
%%--------------------------------------------------------------------
@@ -521,17 +525,13 @@ select_version(RecordCB, ClientVersion, Versions) ->
%% Called by TLS 1.2/1.3 Server when "supported_versions" is present
%% in ClientHello.
%% Input lists are ordered (highest first)
-select_supported_version([], _ServerVersions) ->
- undefined;
-select_supported_version([ClientVersion|T], ServerVersions) ->
- case lists:member(ClientVersion, ServerVersions) of
- true ->
- ClientVersion;
- false ->
- select_supported_version(T, ServerVersions)
+select_supported_version(ClientVersions, ServerVersions) ->
+ Fn = fun (ClientVersion) -> lists:member(ClientVersion, ServerVersions) end,
+ case lists:search(Fn, ClientVersions) of
+ {value, ClientVersion} -> ClientVersion;
+ false -> undefined
end.
-
%%====================================================================
%% Encode handshake
%%====================================================================
@@ -540,14 +540,15 @@ encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version)
PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32),
{?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary,
?BYTE(PaddingLength), 0:(PaddingLength * 8)>>};
-encode_handshake(#server_hello{server_version = {Major, Minor} = Version,
+encode_handshake(#server_hello{server_version = ServerVersion,
random = Random,
session_id = Session_ID,
cipher_suite = CipherSuite,
compression_method = Comp_method,
extensions = Extensions}, _Version) ->
SID_length = byte_size(Session_ID),
- ExtensionsBin = encode_hello_extensions(Extensions, Version),
+ {Major,Minor} = ServerVersion,
+ ExtensionsBin = encode_hello_extensions(Extensions),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>};
@@ -564,7 +565,7 @@ encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign,
encode_handshake(#certificate_request{certificate_types = CertTypes,
hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos},
certificate_authorities = CertAuths},
- {3,3}) ->
+ ?TLS_1_2) ->
HashSigns = << <<(ssl_cipher:signature_scheme(SignatureScheme)):16 >> ||
SignatureScheme <- HashSignAlgos >>,
EncCertAuths = encode_cert_auths(CertAuths),
@@ -588,17 +589,15 @@ encode_handshake(#certificate_request{certificate_types = CertTypes,
};
encode_handshake(#server_hello_done{}, _Version) ->
{?SERVER_HELLO_DONE, <<>>};
-encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, Version) ->
- {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys, Version)};
+encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, _Version) ->
+ {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys)};
encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) ->
EncSig = enc_sign(HashSign, BinSig, Version),
{?CERTIFICATE_VERIFY, EncSig};
encode_handshake(#finished{verify_data = VerifyData}, _Version) ->
{?FINISHED, VerifyData}.
-encode_hello_extensions(_, {3, 0}) ->
- <<>>;
-encode_hello_extensions(Extensions, _) ->
+encode_hello_extensions(Extensions) ->
encode_extensions(hello_extensions_list(Extensions), <<>>).
encode_extensions(Exts) ->
@@ -695,6 +694,15 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
?BYTE(?SNI_NAMETYPE_HOST_NAME),
?UINT16(HostLen), HostnameBin/binary,
Acc/binary>>);
+encode_extensions([#use_srtp{protection_profiles = Profiles, mki = MKI} | Rest], Acc) ->
+ ProfilesBin = iolist_to_binary(Profiles),
+ ProfilesLength = byte_size(ProfilesBin),
+ MKILength = byte_size(MKI),
+ ExtLength = ProfilesLength + 2 + MKILength + 1,
+ encode_extensions(Rest, <<?UINT16(?USE_SRTP_EXT), ?UINT16(ExtLength),
+ ?UINT16(ProfilesLength), ProfilesBin/binary,
+ ?BYTE(MKILength), MKI/binary,
+ Acc/binary>>);
encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) ->
ExtLength = 1,
encode_extensions(Rest, <<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(ExtLength), ?BYTE(MaxFragEnum),
@@ -823,14 +831,12 @@ encode_protocols_advertised_on_server(Protocols) ->
extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}.
encode_cert_auths(Auths) ->
- encode_cert_auths(Auths, []).
-
-encode_cert_auths([], Acc) ->
- list_to_binary(lists:reverse(Acc));
-encode_cert_auths([Auth | Auths], Acc) ->
- DNEncodedBin = public_key:pkix_encode('Name', Auth, otp),
- DNEncodedLen = byte_size(DNEncodedBin),
- encode_cert_auths(Auths, [<<?UINT16(DNEncodedLen), DNEncodedBin/binary>> | Acc]).
+ DNEncode = fun (Auth) ->
+ DNEncodedBin = public_key:pkix_encode('Name', Auth, otp),
+ DNEncodedLen = byte_size(DNEncodedBin),
+ <<?UINT16(DNEncodedLen), DNEncodedBin/binary>>
+ end,
+ list_to_binary(lists:map(DNEncode, Auths)).
%%====================================================================
%% Decode handshake
@@ -879,7 +885,7 @@ decode_handshake(_Version, ?CERTIFICATE_STATUS, <<?BYTE(?CERTIFICATE_STATUS_TYPE
response = ASN1OcspResponse};
decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) ->
#server_key_exchange{exchange_keys = Keys};
-decode_handshake({3, 3} = Version, ?CERTIFICATE_REQUEST,
+decode_handshake(?TLS_1_2 = Version, ?CERTIFICATE_REQUEST,
<<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
?UINT16(HashSignsLen), HashSigns:HashSignsLen/binary,
?UINT16(CertAuthsLen), EncCertAuths:CertAuthsLen/binary>>) ->
@@ -894,9 +900,8 @@ decode_handshake(_Version, ?CERTIFICATE_REQUEST,
certificate_authorities = decode_cert_auths(EncCertAuths, [])};
decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) ->
#server_hello_done{};
-decode_handshake({Major, Minor}, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen),
- Signature:SignLen/binary>>)
- when Major == 3, Minor >= 3 ->
+decode_handshake(?TLS_1_2, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen),
+ Signature:SignLen/binary>>) ->
#certificate_verify{hashsign_algorithm = dec_hashsign(HashSign), signature = Signature};
decode_handshake(_Version, ?CERTIFICATE_VERIFY,<<?UINT16(SignLen), Signature:SignLen/binary>>)->
#certificate_verify{signature = Signature};
@@ -1000,16 +1005,15 @@ available_suites(ServerCert, UserSuites, Version, undefined, Curve) ->
filter_unavailable_ecc_suites(Curve, Suites);
available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) ->
Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve),
- filter_hashsigns(Suites, [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- Suites], HashSigns,
- Version, []).
+ filter_hashsigns(Suites, [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- Suites], HashSigns, Version).
available_signature_algs(undefined, _) ->
undefined;
-available_signature_algs(SupportedHashSigns, Version) when Version >= {3, 3} ->
+available_signature_algs(SupportedHashSigns, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
case contains_scheme(SupportedHashSigns) of
true ->
case Version of
- {3,3} ->
+ ?TLS_1_2 ->
#hash_sign_algos{hash_sign_algos = ssl_cipher:signature_schemes_1_2(SupportedHashSigns)};
_ ->
#signature_algorithms{signature_scheme_list = SupportedHashSigns}
@@ -1021,12 +1025,12 @@ available_signature_algs(_, _) ->
undefined.
available_signature_algs(undefined, SupportedHashSigns, Version) when
- Version >= {3,3} ->
+ ?TLS_GTE(Version, ?TLS_1_2) ->
SupportedHashSigns;
available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns0,
- Version) when Version >= {3,3} ->
+ Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
SupportedHashSigns =
- case (Version == {3,3}) andalso contains_scheme(SupportedHashSigns0) of
+ case (Version == ?TLS_1_2) andalso contains_scheme(SupportedHashSigns0) of
true ->
ssl_cipher:signature_schemes_1_2(SupportedHashSigns0);
false ->
@@ -1037,18 +1041,15 @@ available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, Su
available_signature_algs(_, _, _) ->
undefined.
-contains_scheme([]) ->
- false;
-contains_scheme([Scheme | _]) when is_atom(Scheme) ->
- true;
-contains_scheme([_| Rest]) ->
- contains_scheme(Rest).
+contains_scheme(Schemes) ->
+ lists:any(fun erlang:is_atom/1, Schemes).
cipher_suites(Suites, Renegotiation, true) ->
%% TLS_FALLBACK_SCSV should be placed last -RFC7507
cipher_suites(Suites, Renegotiation) ++ [?TLS_FALLBACK_SCSV];
cipher_suites(Suites, Renegotiation, false) ->
cipher_suites(Suites, Renegotiation).
+
cipher_suites(Suites, false) ->
[?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites];
cipher_suites(Suites, true) ->
@@ -1059,7 +1060,8 @@ cipher_suites(Suites, true) ->
%%
%% Description: use the TLS PRF to generate key material
%%--------------------------------------------------------------------
-prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) ->
+prf(Version, PRFAlgo, Secret, Label, Seed, WantedLength)
+ when ?TLS_1_X(Version)->
{ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}.
select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyAlts) ->
@@ -1121,8 +1123,9 @@ server_select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, cert
end
end.
-is_acceptable_cert(Cert, HashSigns, {Major, Minor}) when Major == 3,
- Minor >= 3 ->
+is_acceptable_cert(Cert, HashSigns, Version)
+ when ?TLS_1_X(Version),
+ ?TLS_GTE(Version, ?TLS_1_2) ->
{SignAlgo0, Param, _, _, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0, Param),
is_acceptable_hash_sign(SignAlgo, HashSigns);
@@ -1130,12 +1133,6 @@ is_acceptable_cert(_,_,_) ->
%% Not negotiable pre TLS-1.2. So if cert is available for version it is acceptable
true.
-supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) ->
- Curves = tls_v1:ecc_curves(Minor),
- #elliptic_curves{elliptic_curve_list = Curves};
-supported_ecc(_) ->
- #elliptic_curves{elliptic_curve_list = []}.
-
premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) ->
try
public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params)
@@ -1242,12 +1239,11 @@ client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renego
add_tls12_extensions(_Version,
#{alpn_advertised_protocols := AlpnAdvertisedProtocols,
- next_protocol_selector := NextProtocolSelector,
- server_name_indication := ServerNameIndication,
- max_fragment_length := MaxFragmentLength} = SslOpts,
+ max_fragment_length := MaxFragmentLength} = SslOpts,
ConnectionStates,
Renegotiation) ->
SRP = srp_user(SslOpts),
+ NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined),
#{renegotiation_info => renegotiation_info(tls_record, client,
ConnectionStates, Renegotiation),
srp => SRP,
@@ -1255,12 +1251,13 @@ add_tls12_extensions(_Version,
next_protocol_negotiation =>
encode_client_protocol_negotiation(NextProtocolSelector,
Renegotiation),
- sni => sni(ServerNameIndication),
+ sni => sni(SslOpts),
+ use_srtp => use_srtp_ext(SslOpts),
max_frag_enum => max_frag_enum(MaxFragmentLength)
}.
-add_common_extensions({3,4},
+add_common_extensions(?TLS_1_3,
HelloExtensions,
_CipherSuites,
#{eccs := SupportedECCs,
@@ -1297,10 +1294,9 @@ add_common_extensions(Version,
signature_algs_cert =>
signature_algs_cert(SignatureCertSchemes)}.
-maybe_add_tls13_extensions({3,4},
+maybe_add_tls13_extensions(?TLS_1_3,
HelloExtensions0,
- #{versions := SupportedVersions,
- certificate_authorities := Bool},
+ #{versions := SupportedVersions} = Opts,
KeyShare,
TicketData, CertDbHandle, CertDbRef) ->
HelloExtensions1 =
@@ -1308,18 +1304,15 @@ maybe_add_tls13_extensions({3,4},
#client_hello_versions{versions = SupportedVersions}},
HelloExtensions2 = maybe_add_key_share(HelloExtensions1, KeyShare),
HelloExtensions = maybe_add_pre_shared_key(HelloExtensions2, TicketData),
- maybe_add_certificate_auths(HelloExtensions, CertDbHandle, CertDbRef, Bool);
+ AddCA = maps:get(certificate_authorities, Opts, false),
+ maybe_add_certificate_auths(HelloExtensions, CertDbHandle, CertDbRef, AddCA);
maybe_add_tls13_extensions(_, HelloExtensions, _, _, _, _,_) ->
HelloExtensions.
-maybe_add_certificate_status_request(
- _Version, #{ocsp_stapling := false}, _OcspNonce, HelloExtensions) ->
- HelloExtensions;
-maybe_add_certificate_status_request(
- _Version, #{ocsp_stapling := true,
- ocsp_responder_certs := OcspResponderCerts},
- OcspNonce, HelloExtensions) ->
+maybe_add_certificate_status_request(_Version, #{ocsp_stapling := OcspStapling},
+ OcspNonce, HelloExtensions) ->
+ OcspResponderCerts = maps:get(ocsp_responder_certs, OcspStapling),
OcspResponderList = get_ocsp_responder_list(OcspResponderCerts),
OcspRequestExtns = public_key:ocsp_extensions(OcspNonce),
Req = #ocsp_status_request{responder_id_list = OcspResponderList,
@@ -1328,16 +1321,13 @@ maybe_add_certificate_status_request(
status_type = ?CERTIFICATE_STATUS_TYPE_OCSP,
request = Req
},
- HelloExtensions#{status_request => CertStatusReqExtn}.
+ HelloExtensions#{status_request => CertStatusReqExtn};
+maybe_add_certificate_status_request(_Version, _SslOpts, _OcspNonce,
+ HelloExtensions) ->
+ HelloExtensions.
get_ocsp_responder_list(ResponderCerts) ->
- get_ocsp_responder_list(ResponderCerts, []).
-
-get_ocsp_responder_list([], Acc) ->
- Acc;
-get_ocsp_responder_list([ResponderCert | T], Acc) ->
- get_ocsp_responder_list(
- T, [public_key:ocsp_responder_id(ResponderCert) | Acc]).
+ lists:map(fun public_key:ocsp_responder_id/1, ResponderCerts).
%% TODO: Add support for PSK key establishment
@@ -1441,7 +1431,7 @@ add_alpn(Extensions, ALPN0) ->
Extensions#{alpn => ALPN}.
add_selected_version(Extensions) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ SupportedVersions = #server_hello_selected_version{selected_version = ?TLS_1_3},
Extensions#{server_hello_selected_version => SupportedVersions}.
kse_remove_private_key(#key_share_entry{
@@ -1472,6 +1462,13 @@ signature_algs_cert(undefined) ->
signature_algs_cert(SignatureSchemes) ->
#signature_algorithms_cert{signature_scheme_list = SignatureSchemes}.
+
+use_srtp_ext(#{use_srtp := #{protection_profiles := Profiles, mki := MKI}}) ->
+ #use_srtp{protection_profiles = Profiles, mki = MKI};
+use_srtp_ext(#{}) ->
+ undefined.
+
+
handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
Exts, Version,
#{secure_renegotiate := SecureRenegotation,
@@ -1498,6 +1495,7 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
ConnectionStates, Renegotiation),
ec_point_formats => server_ecc_extension(Version,
maps:get(ec_point_formats, Exts, undefined)),
+ use_srtp => use_srtp_ext(Opts),
max_frag_enum => ServerMaxFragEnum
},
@@ -1519,9 +1517,8 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
Exts, Version,
- #{secure_renegotiate := SecureRenegotation,
- next_protocol_selector := NextProtoSelector,
- ocsp_stapling := Stapling},
+ #{secure_renegotiate := SecureRenegotation} =
+ SslOpts,
ConnectionStates0, Renegotiation, IsNew) ->
ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version,
maps:get(renegotiation_info, Exts, undefined), Random,
@@ -1544,7 +1541,7 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
ok
end,
- case handle_ocsp_extension(Stapling, Exts) of
+ case handle_ocsp_extension(SslOpts, Exts) of
#alert{} = Alert ->
Alert;
OcspState ->
@@ -1560,7 +1557,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
{ConnectionStates, alpn, undefined, OcspState};
undefined ->
NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
- Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
+ NextProtocolSelector = maps:get(next_protocol_selector, SslOpts, undefined),
+ Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtocolSelector, Renegotiation),
{ConnectionStates, npn, Protocol, OcspState};
{error, Reason} ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
@@ -1571,12 +1569,11 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
end
end.
-select_curve(Client, Server, Version) ->
- select_curve(Client, Server, Version, false).
+select_curve(Client, Server) ->
+ select_curve(Client, Server, false).
select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves},
#elliptic_curves{elliptic_curve_list = ServerCurves},
- _,
ServerOrder) ->
case ServerOrder of
false ->
@@ -1584,25 +1581,25 @@ select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves},
true ->
select_shared_curve(ServerCurves, ClientCurves)
end;
-select_curve(undefined, _, {_,Minor}, _) ->
+select_curve(undefined, _, _) ->
%% Client did not send ECC extension use default curve if
%% ECC cipher is negotiated
- case tls_v1:ecc_curves(Minor, [secp256r1]) of
+ case tls_v1:ecc_curves([secp256r1]) of
[] ->
%% Curve not supported by cryptolib ECC algorithms can not be negotiated
no_curve;
[CurveOid] ->
{namedCurve, CurveOid}
end;
-select_curve({supported_groups, Groups}, Server,{_, Minor} = Version, HonorServerOrder) ->
+select_curve({supported_groups, Groups}, Server, HonorServerOrder) ->
%% TLS-1.3 hello but lesser version chosen
TLSCommonCurves = [secp256r1,secp384r1,secp521r1],
Curves = [tls_v1:enum_to_oid(tls_v1:group_to_enum(Name)) || Name <- Groups, lists:member(Name, TLSCommonCurves)],
- case tls_v1:ecc_curves(Minor, Curves) of
+ case tls_v1:ecc_curves(Curves) of
[] ->
- select_curve(undefined, Server, Version, HonorServerOrder);
+ select_curve(undefined, Server, HonorServerOrder);
[_|_] = ClientCurves ->
- select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, Server, Version, HonorServerOrder)
+ select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, Server, HonorServerOrder)
end.
%%--------------------------------------------------------------------
@@ -1623,12 +1620,12 @@ select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon;
%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have
%% negotiated a lower version.
select_hashsign({ClientHashSigns, ClientSignatureSchemes},
- Cert, KeyExAlgo, undefined, {3, 3} = Version) ->
+ Cert, KeyExAlgo, undefined, ?TLS_1_2 = Version) ->
select_hashsign({ClientHashSigns, ClientSignatureSchemes}, Cert, KeyExAlgo,
tls_v1:default_signature_algs([Version]), Version);
select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns},
ClientSignatureSchemes0},
- Cert, KeyExAlgo, SupportedHashSigns, {3, 3}) ->
+ Cert, KeyExAlgo, SupportedHashSigns, ?TLS_1_2) ->
ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0),
{SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert),
SignAlgo = sign_algo(SignAlgo0, Param),
@@ -1685,7 +1682,7 @@ select_hashsign(#certificate_request{
certificate_types = Types},
Cert,
SupportedHashSigns,
- {3, 3}) ->
+ ?TLS_1_2) ->
{SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert),
SignAlgo = {_, KeyType} = sign_algo(SignAlgo0, Param),
PublicKeyAlgo = ssl_certificate:public_key_type(PublicKeyAlgo0),
@@ -1877,9 +1874,9 @@ get_signature_scheme(#signature_algorithms_cert{
%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
%%--------------------------------------------------------------------
-select_hashsign_algs(HashSign, _, {3, 3}) when HashSign =/= undefined ->
+select_hashsign_algs(HashSign, _, ?TLS_1_2) when HashSign =/= undefined ->
HashSign;
-select_hashsign_algs(undefined, ?rsaEncryption, {3,3}) ->
+select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_2) ->
{sha, rsa};
select_hashsign_algs(undefined,?'id-ecPublicKey', _) ->
{sha, ecdsa};
@@ -1897,6 +1894,8 @@ extension_value(undefined) ->
undefined;
extension_value(#sni{hostname = HostName}) ->
HostName;
+extension_value(#use_srtp{protection_profiles = ProtectionProfiles, mki = MKI}) ->
+ #{protection_profiles => ProtectionProfiles, mki => MKI};
extension_value(#ec_point_formats{ec_point_format_list = List}) ->
List;
extension_value(#elliptic_curves{elliptic_curve_list = List}) ->
@@ -1936,21 +1935,21 @@ extension_value(#psk_key_exchange_modes{ke_modes = Modes}) ->
extension_value(#cookie{cookie = Cookie}) ->
Cookie.
-handle_ocsp_extension(true = Stapling, Extensions) ->
+handle_ocsp_extension(#{ocsp_stapling := _OcspStapling}, Extensions) ->
case maps:get(status_request, Extensions, false) of
undefined -> %% status_request in server hello is empty
- #{ocsp_stapling => Stapling,
+ #{ocsp_stapling => true,
ocsp_expect => staple};
false -> %% status_request is missing (not negotiated)
- #{ocsp_stapling => Stapling,
+ #{ocsp_stapling => true,
ocsp_expect => no_staple};
_Else ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, status_request_not_empty)
end;
-handle_ocsp_extension(false = Stapling, Extensions) ->
+handle_ocsp_extension(_SslOpts, Extensions) ->
case maps:get(status_request, Extensions, false) of
false -> %% status_request is missing (not negotiated)
- #{ocsp_stapling => Stapling,
+ #{ocsp_stapling => false,
ocsp_expect => no_staple};
_Else -> %% unsolicited status_request
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_status_request)
@@ -1973,7 +1972,7 @@ int_to_bin(I) ->
%% The end-entity certificate provided by the client MUST contain a
%% key that is compatible with certificate_types.
-certificate_types(Version) when Version =< {3,3} ->
+certificate_types(Version) when ?TLS_LTE(Version, ?TLS_1_2) ->
ECDSA = supported_cert_type_or_empty(ecdsa, ?ECDSA_SIGN),
RSA = supported_cert_type_or_empty(rsa, ?RSA_SIGN),
DSS = supported_cert_type_or_empty(dss, ?DSS_SIGN),
@@ -2103,8 +2102,7 @@ path_validation_alert({bad_cert, unknown_critical_extension}) ->
path_validation_alert({bad_cert, {revoked, _}}) ->
?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED);
path_validation_alert({bad_cert, {revocation_status_undetermined, Details}}) ->
- Alert = ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE),
- Alert#alert{reason = Details};
+ ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, Details);
path_validation_alert({bad_cert, selfsigned_peer}) ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);
path_validation_alert({bad_cert, unknown_ca}) ->
@@ -2121,21 +2119,21 @@ digitally_signed(Version, Msg, HashAlgo, PrivateKey, SignAlgo) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey)))
end.
-do_digitally_signed({3, Minor}, Msg, HashAlgo, {#'RSAPrivateKey'{} = Key,
- #'RSASSA-PSS-params'{}}, SignAlgo) when Minor >= 3 ->
+do_digitally_signed(Version, Msg, HashAlgo, {#'RSAPrivateKey'{} = Key,
+ #'RSASSA-PSS-params'{}}, SignAlgo) when ?TLS_GTE(Version, ?TLS_1_2) ->
Options = signature_options(SignAlgo, HashAlgo),
public_key:sign(Msg, HashAlgo, Key, Options);
-do_digitally_signed({3, Minor}, {digest, Digest}, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when Minor =< 2 ->
+do_digitally_signed(Version, {digest, Digest}, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when ?TLS_LTE(Version, ?TLS_1_1) ->
public_key:encrypt_private(Digest, Key,
[{rsa_pad, rsa_pkcs1_padding}]);
-do_digitally_signed({3, Minor}, {digest, Digest}, _,
- #{algorithm := rsa} = Engine, rsa) when Minor =< 2->
+do_digitally_signed(Version, {digest, Digest}, _,
+ #{algorithm := rsa} = Engine, rsa) when ?TLS_LTE(Version, ?TLS_1_1) ->
crypto:private_encrypt(rsa, Digest, maps:remove(algorithm, Engine),
rsa_pkcs1_padding);
do_digitally_signed(_, Msg, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) ->
Options = signature_options(SignAlgo, HashAlgo),
crypto:sign(Alg, HashAlgo, Msg, maps:remove(algorithm, Engine), Options);
-do_digitally_signed({3, Minor}, {digest, _} = Msg , HashAlgo, Key, _) when Minor =< 2 ->
+do_digitally_signed(Version, {digest, _} = Msg , HashAlgo, Key, _) when ?TLS_LTE(Version,?TLS_1_1) ->
public_key:sign(Msg, HashAlgo, Key);
do_digitally_signed(_, Msg, HashAlgo, Key, SignAlgo) ->
Options = signature_options(SignAlgo, HashAlgo),
@@ -2172,21 +2170,25 @@ bad_key(#{algorithm := rsa}) ->
bad_key(#{algorithm := ecdsa}) ->
unacceptable_ecdsa_key.
-cert_status_check(_, #{ocsp_state := #{ocsp_stapling := true,
- ocsp_expect := stapled}}, _VerifyResult, _, _) ->
- valid; %% OCSP staple will now be checked by ssl_certifcate:verify_cert_extensions/2 in ssl_certifcate:validate
-cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := false}} = SslState, VerifyResult, CertPath, LogLevel) ->
+cert_status_check(_,
+ #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := stapled}},
+ _VerifyResult, _, _) ->
+ %% OCSP staple will now be checked by
+ %% ssl_certificate:verify_cert_extensions/2 in ssl_certificate:validate
+ valid;
+cert_status_check(OtpCert,
+ #{ocsp_state := #{ocsp_stapling := false}} = SslState,
+ VerifyResult, CertPath, LogLevel) ->
maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
-cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true,
- ocsp_expect := undetermined}},
+cert_status_check(_OtpCert,
+ #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := undetermined}},
_VerifyResult, _CertPath, _LogLevel) ->
{bad_cert, {revocation_status_undetermined, not_stapled}};
-cert_status_check(OtpCert, #{ocsp_state := #{ocsp_stapling := best_effort, %% TODO support this ?
- ocsp_expect := undetermined}} = SslState,
- VerifyResult, CertPath, LogLevel) ->
- maybe_check_crl(OtpCert, SslState, VerifyResult, CertPath, LogLevel);
-cert_status_check(_OtpCert, #{ocsp_state := #{ocsp_stapling := true,
- ocsp_expect := no_staple}},
+cert_status_check(_OtpCert,
+ #{ocsp_state := #{ocsp_stapling := true,
+ ocsp_expect := no_staple}},
_VerifyResult, _CertPath, _LogLevel) ->
{bad_cert, {revocation_status_undetermined, not_stapled}}.
@@ -2242,12 +2244,12 @@ crl_check_same_issuer(OtpCert, _, Dps, Options) ->
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, LogLevel),
- dps_and_crls(DistPoints, CRLs, [])
+ [] ->
+ no_dps;
+ DistPoints ->
+ Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer,
+ CRLs = distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle, LogLevel),
+ [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || DP <- DistPoints, CRL <- CRLs]
end;
dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel) ->
@@ -2266,12 +2268,7 @@ dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer, LogLevel) ->
end, GenNames),
[{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs].
-dps_and_crls([], _, Acc) ->
- Acc;
-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([DistPoint | Rest], Issuer, Callback, CRLDbHandle, LogLevel) ->
@@ -2305,10 +2302,12 @@ encrypted_premaster_secret(Secret, RSAPublicKey) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed))
end.
-calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) ->
- tls_v1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)).
-calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) ->
- tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)).
+-spec calc_certificate_verify(ssl_record:ssl_version(), md5sha | ssl:hash(), _, [binary()]) -> binary().
+calc_certificate_verify(Version, HashAlgo, _MasterSecret, Handshake) when ?TLS_1_X(Version) ->
+ tls_v1:certificate_verify(HashAlgo, lists:reverse(Handshake)).
+
+calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) when ?TLS_1_X(Version) ->
+ tls_v1:finished(Role, Version, PrfAlgo, MasterSecret, lists:reverse(Handshake)).
master_secret(Version, MasterSecret,
#security_parameters{
@@ -2336,11 +2335,12 @@ master_secret(Version, MasterSecret,
{MasterSecret,
ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState,
ServerCipherState, Role)}.
-setup_keys({3,N}, PrfAlgo, MasterSecret,
- ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) ->
- tls_v1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+setup_keys(Version, PrfAlgo, MasterSecret,
+ ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) when ?TLS_1_X(Version)->
+ tls_v1:setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
KML, IVS).
-calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) ->
+calc_master_secret(Version, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom)
+ when ?TLS_LT(Version, ?TLS_1_3) ->
tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom).
%% Update pending connection states with parameters exchanged via
@@ -2464,50 +2464,48 @@ encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}
<<?UINT16(NLen), N/binary, ?UINT16(GLen), G/binary,
?BYTE(SLen), S/binary, ?UINT16(BLen), B/binary>>.
-encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) ->
- PKEPMS;
-encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) ->
+encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}) ->
PKEPMSLen = byte_size(PKEPMS),
<<?UINT16(PKEPMSLen), PKEPMS/binary>>;
-encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}, _) ->
+encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}) ->
Len = byte_size(DHPublic),
<<?UINT16(Len), DHPublic/binary>>;
-encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}, _) ->
+encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}) ->
Len = byte_size(DHPublic),
<<?BYTE(Len), DHPublic/binary>>;
-encode_client_key(#client_psk_identity{identity = undefined}, _) ->
+encode_client_key(#client_psk_identity{identity = undefined}) ->
Id = <<"psk_identity">>,
Len = byte_size(Id),
<<?UINT16(Len), Id/binary>>;
-encode_client_key(#client_psk_identity{identity = Id}, _) ->
+encode_client_key(#client_psk_identity{identity = Id}) ->
Len = byte_size(Id),
<<?UINT16(Len), Id/binary>>;
-encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}, Version) ->
- encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}, Version);
-encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, _) ->
+encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}) ->
+ encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>});
+encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}) ->
Len = byte_size(Id),
DHLen = byte_size(DHPublic),
<<?UINT16(Len), Id/binary, ?UINT16(DHLen), DHPublic/binary>>;
-encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}, Version) ->
- encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>}, Version);
-encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}, _) ->
+encode_client_key(Identity = #client_ecdhe_psk_identity{identity = undefined}) ->
+ encode_client_key(Identity#client_ecdhe_psk_identity{identity = <<"psk_identity">>});
+encode_client_key(#client_ecdhe_psk_identity{identity = Id, dh_public = DHPublic}) ->
Len = byte_size(Id),
DHLen = byte_size(DHPublic),
<<?UINT16(Len), Id/binary, ?BYTE(DHLen), DHPublic/binary>>;
-encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}, Version) ->
- encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version);
-encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) ->
- EncPMS = encode_client_key(ExchangeKeys, Version),
+encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}) ->
+ encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>});
+encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}) ->
+ EncPMS = encode_client_key(ExchangeKeys),
Len = byte_size(Id),
<<?UINT16(Len), Id/binary, EncPMS/binary>>;
-encode_client_key(#client_srp_public{srp_a = A}, _) ->
+encode_client_key(#client_srp_public{srp_a = A}) ->
Len = byte_size(A),
<<?UINT16(Len), A/binary>>.
enc_sign({_, anon}, _Sign, _Version) ->
<<>>;
-enc_sign({HashAlg, SignAlg}, Signature, _Version = {Major, Minor})
- when Major == 3, Minor >= 3->
+enc_sign({HashAlg, SignAlg}, Signature, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2)->
SignLen = byte_size(Signature),
HashSign = enc_hashsign(HashAlg, SignAlg),
<<HashSign/binary, ?UINT16(SignLen), Signature/binary>>;
@@ -2561,61 +2559,32 @@ encode_alpn(Protocols, _) ->
encode_versions(Versions) ->
- encode_versions(lists:reverse(Versions), <<>>).
-%%
-encode_versions([], Acc) ->
- Acc;
-encode_versions([{M,N}|T], Acc) ->
- encode_versions(T, <<?BYTE(M),?BYTE(N),Acc/binary>>).
+ << <<?BYTE(M),?BYTE(N)>> || {M,N} <- Versions>>.
encode_client_shares(ClientShares) ->
- encode_client_shares(ClientShares, <<>>).
-%%
-encode_client_shares([], Acc) ->
- Acc;
-encode_client_shares([KeyShareEntry0|T], Acc) ->
- KeyShareEntry = encode_key_share_entry(KeyShareEntry0),
- encode_client_shares(T, <<Acc/binary,KeyShareEntry/binary>>).
+ << << (encode_key_share_entry(KeyShareEntry0))/binary >> || KeyShareEntry0 <- ClientShares >>.
-encode_key_share_entry(#key_share_entry{
- group = Group,
- key_exchange = KeyExchange}) ->
+encode_key_share_entry(#key_share_entry{group = Group,
+ key_exchange = KeyExchange}) ->
Len = byte_size(KeyExchange),
<<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>.
encode_psk_key_exchange_modes(KEModes) ->
- encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>).
-%%
-encode_psk_key_exchange_modes([], Acc) ->
- Acc;
-encode_psk_key_exchange_modes([psk_ke|T], Acc) ->
- encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_KE),Acc/binary>>);
-encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) ->
- encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_DHE_KE),Acc/binary>>).
-
+ << <<?BYTE((choose_psk_key(PskKey)))>> || PskKey <- KEModes>>.
+%
+choose_psk_key(psk_ke) -> ?PSK_KE;
+choose_psk_key(psk_dhe_ke) -> ?PSK_DHE_KE.
encode_psk_identities(Identities) ->
- encode_psk_identities(Identities, <<>>).
-%%
-encode_psk_identities([], Acc) ->
- Len = byte_size(Acc),
- <<?UINT16(Len), Acc/binary>>;
-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,?UINT32(Age)>>).
-
+ Result = << << ?UINT16((byte_size(Identity))), Identity/binary,?UINT32(Age) >>
+ || #psk_identity{ identity = Identity, obfuscated_ticket_age = Age} <- Identities >>,
+ Len = byte_size(Result),
+ <<?UINT16(Len), Result/binary>>.
encode_psk_binders(Binders) ->
- encode_psk_binders(Binders, <<>>).
-%%
-encode_psk_binders([], Acc) ->
- Len = byte_size(Acc),
- <<?UINT16(Len), Acc/binary>>;
-encode_psk_binders([Binder|T], Acc) ->
- Len = byte_size(Binder),
- encode_psk_binders(T, <<Acc/binary,?BYTE(Len),Binder/binary>>).
+ Result = << << ?BYTE((byte_size(Binder))),Binder/binary >> || Binder <- Binders >>,
+ Len = byte_size(Result),
+ <<?UINT16(Len), Result/binary>>.
hello_extensions_list(HelloExtensions) ->
@@ -2699,8 +2668,6 @@ dec_server_key(<<?UINT16(NLen), N:NLen/binary,
dec_server_key(_, KeyExchange, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})).
-dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) ->
- #encrypted_premaster_secret{premaster_secret = PKEPMS};
dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) ->
#encrypted_premaster_secret{premaster_secret = PKEPMS};
dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
@@ -2724,10 +2691,6 @@ dec_client_key(<<?UINT16(Len), Id:Len/binary,
?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>,
?KEY_EXCHANGE_EC_DIFFIE_HELLMAN_PSK, _) ->
#client_ecdhe_psk_identity{identity = Id, dh_public = DH_Y};
-dec_client_key(<<?UINT16(Len), Id:Len/binary, PKEPMS/binary>>,
- ?KEY_EXCHANGE_RSA_PSK, {3, 0}) ->
- #client_rsa_psk_identity{identity = Id,
- exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}};
dec_client_key(<<?UINT16(Len), Id:Len/binary, ?UINT16(_), PKEPMS/binary>>,
?KEY_EXCHANGE_RSA_PSK, _) ->
#client_rsa_psk_identity{identity = Id,
@@ -2741,27 +2704,27 @@ dec_server_key_params(Len, Keys, Version) ->
dec_server_key_signature(Params, Signature, Version).
dec_server_key_signature(Params, <<?BYTE(8), ?BYTE(SignAlgo),
- ?UINT16(0)>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(0)>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
<<?UINT16(Scheme0)>> = <<?BYTE(8), ?BYTE(SignAlgo)>>,
Scheme = ssl_cipher:signature_scheme(Scheme0),
{Hash, Sign, _} = ssl_cipher:scheme_to_components(Scheme),
{Params, {Hash, Sign}, <<>>};
dec_server_key_signature(Params, <<?BYTE(8), ?BYTE(SignAlgo),
- ?UINT16(Len), Signature:Len/binary>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(Len), Signature:Len/binary>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
<<?UINT16(Scheme0)>> = <<?BYTE(8), ?BYTE(SignAlgo)>>,
Scheme = ssl_cipher:signature_scheme(Scheme0),
{Hash, Sign, _} = ssl_cipher:scheme_to_components(Scheme),
{Params, {Hash, Sign}, Signature};
dec_server_key_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo),
- ?UINT16(0)>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(0)>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)},
{Params, HashSign, <<>>};
dec_server_key_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo),
- ?UINT16(Len), Signature:Len/binary>>, {Major, Minor})
- when Major == 3, Minor >= 3 ->
+ ?UINT16(Len), Signature:Len/binary>>, Version)
+ when ?TLS_GTE(Version, ?TLS_1_2) ->
HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)},
{Params, HashSign, Signature};
dec_server_key_signature(Params, <<>>, _) ->
@@ -2847,7 +2810,7 @@ decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version < {3,3} ->
+ when ?TLS_LT(Version, ?TLS_1_2) ->
SignAlgoListLen = Len - 2,
<<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData,
HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} ||
@@ -2857,8 +2820,7 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
#hash_sign_algos{hash_sign_algos =
HashSignAlgos}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,3} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_2=Version, MessageType, Acc) ->
SignSchemeListLen = Len - 2,
<<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
HashSigns = decode_sign_alg(Version, SignSchemeList),
@@ -2867,8 +2829,7 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
#hash_sign_algos{
hash_sign_algos = HashSigns}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,4} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_3=Version, MessageType, Acc) ->
SignSchemeListLen = Len - 2,
<<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
SignSchemes = decode_sign_alg(Version, SignSchemeList),
@@ -2878,8 +2839,7 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
signature_scheme_list = SignSchemes}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,4} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_3=Version, MessageType, Acc) ->
SignSchemeListLen = Len - 2,
<<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData,
%% Ignore unknown signature algorithms
@@ -2918,9 +2878,19 @@ decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len),
#signature_algorithms_cert{
signature_scheme_list = SignSchemes}});
+decode_extensions(<<?UINT16(?USE_SRTP_EXT), ?UINT16(Len),
+ ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) ->
+ <<?UINT16(ProfilesLen), ProfilesBin:ProfilesLen/binary, ?BYTE(MKILen), MKI:MKILen/binary>> = ExtData,
+ Profiles = [P || <<P:2/binary>> <= ProfilesBin],
+ decode_extensions(Rest, Version, MessageType,
+ Acc#{use_srtp =>
+ #use_srtp{
+ protection_profiles = Profiles,
+ mki = MKI}});
+
decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version < {3,4} ->
+ when ?TLS_LT(Version, ?TLS_1_3) ->
<<?UINT16(_), EllipticCurveList/binary>> = ExtData,
%% Ignore unknown curves
Pick = fun(Enum) ->
@@ -2938,8 +2908,7 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
EllipticCurves}});
decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
- ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc)
- when Version =:= {3,4} ->
+ ExtData:Len/binary, Rest/binary>>, ?TLS_1_3=Version, MessageType, Acc) ->
<<?UINT16(_), GroupList/binary>> = ExtData,
%% Ignore unknown curves
Pick = fun(Enum) ->
@@ -2993,8 +2962,7 @@ decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
when Len =:= 2, SelectedVersion =:= 16#0304 ->
decode_extensions(Rest, Version, MessageType,
Acc#{server_hello_selected_version =>
- #server_hello_selected_version{selected_version =
- {3,4}}});
+ #server_hello_selected_version{selected_version = ?TLS_1_3}});
decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>,
@@ -3113,7 +3081,7 @@ decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>
decode_extensions(_, _, _, Acc) ->
Acc.
-decode_sign_alg({3,3}, SignSchemeList) ->
+decode_sign_alg(?TLS_1_2, SignSchemeList) ->
%% Ignore unknown signature algorithms
Fun = fun(Elem) ->
case ssl_cipher:signature_scheme(Elem) of
@@ -3138,7 +3106,7 @@ decode_sign_alg({3,3}, SignSchemeList) ->
end,
lists:filtermap(Fun, [SignScheme ||
<<?UINT16(SignScheme)>> <= SignSchemeList]);
-decode_sign_alg({3,4}, SignSchemeList) ->
+decode_sign_alg(?TLS_1_3, SignSchemeList) ->
%% Ignore unknown signature algorithms
Fun = fun(Elem) ->
case ssl_cipher:signature_scheme(Elem) of
@@ -3152,7 +3120,7 @@ decode_sign_alg({3,4}, SignSchemeList) ->
<<?UINT16(SignScheme)>> <= SignSchemeList]).
dec_hashsign(Value) ->
- [HashSign] = decode_sign_alg({3,3}, Value),
+ [HashSign] = decode_sign_alg(?TLS_1_2, Value),
HashSign.
@@ -3308,14 +3276,11 @@ select_cipher_suite(CipherSuites, Suites, false) ->
select_cipher_suite(CipherSuites, Suites, true) ->
select_cipher_suite(Suites, CipherSuites).
-select_cipher_suite([], _) ->
- no_suite;
-select_cipher_suite([Suite | ClientSuites], SupportedSuites) ->
- case is_member(Suite, SupportedSuites) of
- true ->
- Suite;
- false ->
- select_cipher_suite(ClientSuites, SupportedSuites)
+select_cipher_suite(ClientSuites, SupportedSuites) ->
+ F = fun(Suite) -> is_member(Suite, SupportedSuites) end,
+ case lists:search(F, ClientSuites) of
+ {value, Suite} -> Suite;
+ false -> no_suite
end.
is_member(Suite, SupportedSuites) ->
@@ -3350,25 +3315,41 @@ handle_psk_identity(_PSKIdentity, LookupFun)
handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
Fun(psk, PSKIdentity, UserState).
-filter_hashsigns([], [], _, _, Acc) ->
- lists:reverse(Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when KeyExchange == dhe_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
- do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Version, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when KeyExchange == rsa;
+
+filter_hashsigns(Suites, Algos, HashSigns, Version) ->
+ %% HashSigns, and Version never change
+ ZipperF = fun (Suite, #{key_exchange := KeyExchange}) -> {Suite, KeyExchange} end,
+ SuiteAlgoPairs = lists:zipwith(ZipperF, Suites, Algos),
+ FilterHashSign = fun ({Suite, Kex}) ->
+ maybe true ?= filter_hashsigns_helper(Kex, HashSigns, Version),
+ {true, Suite}
+ end
+ end,
+ lists:filtermap(FilterHashSign, SuiteAlgoPairs).
+
+filter_hashsigns_helper(KeyExchange, HashSigns, _Version)
+ when KeyExchange == dhe_ecdsa;
+ KeyExchange == ecdhe_ecdsa ->
+ lists:keymember(ecdsa, 2, HashSigns);
+filter_hashsigns_helper(KeyExchange, HashSigns, ?TLS_1_2) when KeyExchange == rsa;
+ KeyExchange == dhe_rsa;
+ KeyExchange == ecdhe_rsa;
+ KeyExchange == srp_rsa;
+ KeyExchange == rsa_psk ->
+ lists:any(fun (H) -> lists:keymember(H, 2, HashSigns) end,
+ [rsa, rsa_pss_rsae, rsa_pss_pss]);
+filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when KeyExchange == rsa;
KeyExchange == dhe_rsa;
KeyExchange == ecdhe_rsa;
KeyExchange == srp_rsa;
KeyExchange == rsa_psk ->
- do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Version, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when
+ lists:keymember(rsa, 2, HashSigns);
+filter_hashsigns_helper(KeyExchange, HashSigns, _Version) when
KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
- do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Version, Acc);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when
+ KeyExchange == srp_dss ->
+ lists:keymember(dsa, 2, HashSigns);
+
+filter_hashsigns_helper(KeyExchange, _HashSigns, _Version) when
KeyExchange == dh_dss;
KeyExchange == dh_rsa;
KeyExchange == dh_ecdsa;
@@ -3377,9 +3358,8 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has
%% Fixed DH certificates MAY be signed with any hash/signature
%% algorithm pair appearing in the hash_sign extension. The names
%% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
-filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version,
- Acc) when
+ true;
+filter_hashsigns_helper(KeyExchange, _HashSigns, _Version) when
KeyExchange == dh_anon;
KeyExchange == ecdh_anon;
KeyExchange == srp_anon;
@@ -3387,24 +3367,7 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has
KeyExchange == dhe_psk;
KeyExchange == ecdhe_psk ->
%% In this case hashsigns is not used as the kexchange is anonaymous
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]).
-
-do_filter_hashsigns(rsa = SignAlgo, Suite, Suites, Algos, HashSigns, {3,3} = Version, Acc) ->
- case (lists:keymember(SignAlgo, 2, HashSigns) orelse
- lists:keymember(rsa_pss_rsae, 2, HashSigns) orelse
- lists:keymember(rsa_pss_pss, 2, HashSigns)) of
- true ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
- false ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, Acc)
- end;
-do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Version, Acc) ->
- case lists:keymember(SignAlgo, 2, HashSigns) of
- true ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]);
- false ->
- filter_hashsigns(Suites, Algos, HashSigns, Version, Acc)
- end.
+ true.
filter_unavailable_ecc_suites(no_curve, Suites) ->
ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true;
@@ -3476,10 +3439,9 @@ handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-
handle_next_protocol_on_server(undefined, _Renegotiation, _SslOpts) ->
undefined;
-
handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>},
- false, #{next_protocols_advertised := Protocols}) ->
- Protocols;
+ false, SslOpts) ->
+ maps:get(next_protocols_advertised, SslOpts, undefined);
handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension).
@@ -3564,10 +3526,13 @@ sign_type(ecdsa) ->
server_name(_, _, server) ->
undefined; %% Not interesting to check your own name.
-server_name(undefined, Host, client) ->
- {fallback, Host}; %% Fallback to Host argument to connect
-server_name(SNI, _, client) ->
- SNI. %% If Server Name Indication is available
+server_name(SSLOpts, Host, client) ->
+ case maps:get(server_name_indication, SSLOpts, undefined) of
+ undefined ->
+ {fallback, Host}; %% Fallback to Host argument to connect
+ SNI ->
+ SNI %% If Server Name Indication is available
+ end.
client_ecc_extensions(SupportedECCs) ->
CryptoSupport = proplists:get_value(public_keys, crypto:supports()),
@@ -3599,39 +3564,25 @@ handle_ecc_point_fmt_extension(undefined) ->
handle_ecc_point_fmt_extension(_) ->
#ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}.
-advertises_ec_ciphers([]) ->
- false;
-advertises_ec_ciphers([#{key_exchange := ecdh_ecdsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdhe_ecdsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdh_rsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdhe_rsa} | _]) ->
- true;
-advertises_ec_ciphers([#{key_exchange := ecdh_anon} | _]) ->
- true;
-advertises_ec_ciphers([{ecdhe_psk, _,_,_} | _]) ->
- true;
-advertises_ec_ciphers([_| Rest]) ->
- advertises_ec_ciphers(Rest).
+advertises_ec_ciphers(ListKex) ->
+ KeyExchanges = [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon],
+ F = fun (#{key_exchange := Kex}) -> lists:member(Kex, KeyExchanges);
+ ({ecdhe_psk, _,_,_}) -> true
+ end,
+ lists:any(F, ListKex).
-select_shared_curve([], _) ->
- no_curve;
-select_shared_curve([Curve | Rest], Curves) ->
- case lists:member(Curve, Curves) of
- true ->
- {namedCurve, Curve};
- false ->
- select_shared_curve(Rest, Curves)
+select_shared_curve(SharedCurves, Curves) ->
+ case lists:search(fun (Curve) -> lists:member(Curve, Curves) end, SharedCurves) of
+ {value, SharedCurve} -> {namedCurve, SharedCurve};
+ false -> no_curve
end.
-sni(undefined) ->
- undefined;
-sni(disable) ->
- undefined;
-sni(Hostname) ->
- #sni{hostname = Hostname}.
+sni(SslOpts) ->
+ case maps:get(server_name_indication, SslOpts, undefined) of
+ undefined -> undefined;
+ disable -> undefined;
+ Hostname -> #sni{hostname = Hostname}
+ end.
%% convert max_fragment_length (in bytes) to the RFC 6066 ENUM
max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) ->
@@ -3759,14 +3710,14 @@ cert_curve(Cert, ECCCurve0, CipherSuite) ->
empty_extensions() ->
#{}.
-empty_extensions({3,4}, client_hello) ->
+empty_extensions(?TLS_1_3, client_hello) ->
#{
sni => undefined,
%% max_frag_enum => undefined,
%% status_request => undefined,
elliptic_curves => undefined,
signature_algs => undefined,
- %% use_srtp => undefined,
+ use_srtp => undefined,
%% heartbeat => undefined,
alpn => undefined,
%% signed_cert_timestamp => undefined,
@@ -3783,8 +3734,8 @@ empty_extensions({3,4}, client_hello) ->
%% post_handshake_auth => undefined,
signature_algs_cert => undefined
};
-empty_extensions({3, 3}, client_hello) ->
- Ext = empty_extensions({3,2}, client_hello),
+empty_extensions(?TLS_1_2, client_hello) ->
+ Ext = empty_extensions(?TLS_1_1, client_hello),
Ext#{signature_algs => undefined};
empty_extensions(_, client_hello) ->
#{renegotiation_info => undefined,
@@ -3794,12 +3745,12 @@ empty_extensions(_, client_hello) ->
ec_point_formats => undefined,
elliptic_curves => undefined,
sni => undefined};
-empty_extensions({3,4}, server_hello) ->
+empty_extensions(?TLS_1_3, server_hello) ->
#{server_hello_selected_version => undefined,
key_share => undefined,
pre_shared_key => undefined
};
-empty_extensions({3,4}, hello_retry_request) ->
+empty_extensions(?TLS_1_3, hello_retry_request) ->
#{server_hello_selected_version => undefined,
key_share => undefined,
pre_shared_key => undefined, %% TODO remove!
@@ -3857,8 +3808,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
#{verify_fun := VerifyFun,
customize_hostname_check := CustomizeHostnameCheck,
crl_check := CrlCheck,
- log_level := Level,
- depth := Depth} = Opts,
+ log_level := Level} = Opts,
#{cert_ext := CertExt,
ocsp_responder_certs := OcspResponderCerts,
ocsp_state := OcspState}) ->
@@ -3881,7 +3831,7 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
ocsp_responder_certs => OcspResponderCerts,
ocsp_state => OcspState},
Path, Level),
- Options = [{max_path_length, Depth},
+ Options = [{max_path_length, maps:get(depth, Opts, ?DEFAULT_DEPTH)},
{verify_fun, ValidationFunAndState}],
public_key:pkix_path_validation(TrustedCert, Path, Options).
@@ -3890,7 +3840,20 @@ error_to_propagate({error, {bad_cert, root_cert_expired}} = Error, _) ->
error_to_propagate(_, Error) ->
Error.
-path_validation_cb({3,4}) ->
+path_validation_cb(?TLS_1_3) ->
tls_handshake_1_3;
path_validation_cb(_) ->
?MODULE.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, maybe_add_certificate_status_request,
+ [_Version, SslOpts,
+ _OcspNonce, _HelloExtensions]}},
+ Stack) ->
+ OcspStapling = maps:get(ocsp_stapling, SslOpts, false),
+ {io_lib:format("#1 ADD crt status request / OcspStapling option = ~W",
+ [OcspStapling, 10]), Stack}.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 6dd47019f4..ada0c774d5 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -83,9 +83,14 @@
-define(CERTIFICATE_VERIFY, 15).
-define(CLIENT_KEY_EXCHANGE, 16).
-define(FINISHED, 20).
-
-define(MAX_UNIT24, 8388607).
--define(DEFAULT_MAX_HANDSHAKE_SIZE, (256*1024)).
+
+%% Usually the biggest handshake message will be the message conveying the
+%% certificate chain. This size should be sufficient for usual certificate
+%% chains, certificates without special extensions have a typical size of
+%% 1-2kB. By dividing the old default value by 2 we still have a slightly
+%% bigger margin than OpenSSL
+-define(DEFAULT_MAX_HANDSHAKE_SIZE, ((256*1024) div 2)).
-record(random, {
gmt_unix_time, % uint32
@@ -371,6 +376,17 @@
-define(ECPOINT_ANSIX962_COMPRESSED_CHAR2, 2).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RFC 5764 section 4 Datagram Transport Layer Security (DTLS) Extensions
+%% for SRTP (Secure Real-time Transport Protocol) Key Establishment
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(USE_SRTP_EXT, 14).
+
+-record(use_srtp, {
+ protection_profiles,
+ mki
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ECC RFC 4492 Handshake Messages, Section 5
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index cdb3154cb6..f98be277bf 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -26,6 +26,9 @@
-include_lib("kernel/include/logger.hrl").
-include_lib("public_key/include/public_key.hrl").
+-define(CLIENT_ROLE, client).
+-define(SERVER_ROLE, server).
+
-define(SECRET_PRINTOUT, "***").
-type reason() :: any().
@@ -118,109 +121,6 @@
-define(DEFAULT_MAX_EARLY_DATA_SIZE, 16384).
-%% 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]},
- anti_replay => {undefined, [versions, session_tickets]},
- beast_mitigation => {one_n_minus_one, [versions]},
- cacertfile => {undefined, [versions,
- verify_fun,
- cacerts]},
- cacerts => {undefined, [versions]},
- cert => {undefined, [versions]},
- certs_keys => {undefined, [versions]},
- certfile => {<<>>, [versions]},
- certificate_authorities => {false, [versions]},
- ciphers => {[], [versions]},
- client_renegotiation => {undefined, [versions]},
- cookie => {true, [versions]},
- crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]},
- crl_check => {false, [versions]},
- customize_hostname_check => {[], [versions]},
- depth => {10, [versions]},
- dh => {undefined, [versions]},
- dhfile => {undefined, [versions]},
- early_data => {undefined, [versions,
- session_tickets,
- use_ticket]},
- 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]},
- keep_secrets => {false, [versions]},
- key => {undefined, [versions]},
- keyfile => {undefined, [versions,
- certfile]},
- key_update_at => {?KEY_USAGE_LIMIT_AES_GCM, [versions]},
- log_level => {notice, [versions]},
- max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]},
- middlebox_comp_mode => {true, [versions]},
- max_fragment_length => {undefined, [versions]},
- next_protocol_selector => {undefined, [versions]},
- next_protocols_advertised => {undefined, [versions]},
- %% If enable OCSP stapling
- ocsp_stapling => {false, [versions]},
- %% Optional arg, if give suggestion of OCSP responders
- ocsp_responder_certs => {[], [versions,
- ocsp_stapling]},
- %% Optional arg, if add nonce extension in request
- ocsp_nonce => {true, [versions,
- ocsp_stapling]},
- padding_check => {true, [versions]},
- partial_chain => {fun(_) -> unknown_ca end, [versions]},
- password => {"", [versions]},
- protocol => {tls, []},
- psk_identity => {undefined, [versions]},
- receiver_spawn_opts => {[], [versions]},
- renegotiate_at => {?DEFAULT_RENEGOTIATE_AT, [versions]},
- reuse_session => {undefined, [versions]},
- reuse_sessions => {true, [versions]},
- secure_renegotiate => {true, [versions]},
- sender_spawn_opts => {[], [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]},
- verify => {verify_none, [versions,
- fail_if_no_peer_cert,
- partial_chain]},
- 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]}
- }).
-
-define('TLS-1_3_ONLY_OPTIONS', [anti_replay,
certificate_authorities,
cookie,
@@ -300,10 +200,8 @@
max_size %% max early data size allowed by this ticket
}).
-
+-define(DEFAULT_DEPTH, 10).
+-define(DEFAULT_OCSP_STAPLING, false).
+-define(DEFAULT_OCSP_NONCE, true).
+-define(DEFAULT_OCSP_RESPONDER_CERTS, []).
-endif. % -ifdef(ssl_internal).
-
-
-
-
-
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index 8777233b81..e9131b28a5 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2023. 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.
@@ -290,19 +290,20 @@ get_server_version(Version, Extensions) ->
Version
end.
-version({3,4}) ->
+-spec version(ssl_record:ssl_version()) -> string().
+version(?TLS_1_3) ->
"TLS 1.3";
-version({3,3}) ->
+version(?TLS_1_2) ->
"TLS 1.2";
-version({3,2}) ->
+version(?TLS_1_1) ->
"TLS 1.1";
-version({3,1}) ->
+version(?TLS_1_0) ->
"TLS 1.0";
-version({3,0}) ->
+version(?SSL_3_0) ->
"SSL 3.0";
-version({254,253}) ->
+version(?DTLS_1_2) ->
"DTLS 1.2";
-version({254,255}) ->
+version(?DTLS_1_0) ->
"DTLS 1.0";
version({M,N}) ->
io_lib:format("TLS/DTLS [0x0~B0~B]", [M,N]).
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index b7b68edd82..9daee92c5b 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -55,14 +55,13 @@
-export([cipher/4, cipher/5, decipher/4,
cipher_aead/4, cipher_aead/5, decipher_aead/5,
is_correct_mac/2, nonce_seed/3]).
--define(TLS_1_3, {3, 4}).
-export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]).
--type ssl_version() :: {integer(), integer()}.
--type ssl_atom_version() :: tls_record:tls_atom_version().
+-type ssl_version() :: {non_neg_integer(), non_neg_integer()}.
+-type ssl_atom_version() :: tls_record:tls_atom_version().
-type connection_states() :: map(). %% Map
--type connection_state() :: map(). %% Map
+-type connection_state() :: map(). %% Map
%%====================================================================
%% Connection state handling
@@ -496,7 +495,7 @@ init_security_parameters(?SERVER, Version) ->
#security_parameters{connection_end = ?SERVER,
server_random = make_random(Version)}.
-make_random({_Major, _Minor} = Version) when Version >= ?TLS_1_3 ->
+make_random(Version) when ?TLS_GTE(Version, ?TLS_1_3) ->
ssl_cipher:random_bytes(32);
make_random(_Version) ->
Secs_since_1970 = calendar:datetime_to_gregorian_seconds(
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index e39ca92bc2..c58a931ab5 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -163,9 +163,6 @@
%% minor % unit 8
%% }).
--define(LOWEST_MAJOR_SUPPORTED_VERSION, 3).
-
-
-record(generic_stream_cipher, {
content, % opaque content[TLSCompressed.length];
mac % opaque MAC[CipherSpec.hash_size];
@@ -180,4 +177,33 @@
next_iv % opaque IV[SecurityParameters.record_iv_length];
}).
+-define(PROTOCOL_TO_BINARY_VERSION(Version), (Version)).
+-define(BINARY_PROTOCOL_TO_INTERNAL_REPRESENTATION(Version), (Version)).
+
+-define(TLS_1_X(Version), (element(1,Version) == 3)).
+-define(DTLS_1_X(Version), (element(1,Version) == 254)).
+
+-define(TLS_GTE(Version1, Version2), (Version1 >= Version2)).
+-define(TLS_GT(Version1, Version2), (Version1 > Version2)).
+-define(TLS_LTE(Version1, Version2), (Version1 =< Version2)).
+-define(TLS_LT(Version1, Version2), (Version1 < Version2)).
+
+-define(DTLS_GTE(Version1, Version2), (Version1 =< Version2)).
+-define(DTLS_GT(Version1, Version2), (Version1 < Version2)).
+-define(DTLS_LTE(Version1, Version2), (Version >= Version2)).
+-define(DTLS_LT(Version1, Version2), (Version1 > Version2)).
+
+%% Atoms used to refer to protocols
+
+-define(TLS_1_3, {3,4}).
+-define(TLS_1_2, {3,3}).
+-define(TLS_1_1, {3,2}).
+-define(TLS_1_0, {3,1}).
+
+-define(DTLS_1_2, {254,253}).
+-define(DTLS_1_0, {254,255}).
+
+-define(SSL_3_0, {3,0}).
+-define(SSL_2_0, {2,0}).
+
-endif. % -ifdef(ssl_record).
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index 3999b2fc0e..721a9ef4d5 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -28,6 +28,7 @@
-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
+-include("ssl_record.hrl").
%% Internal application API
-export([is_new/2,
@@ -39,17 +40,19 @@
-type seconds() :: integer().
%%--------------------------------------------------------------------
--spec legacy_session_id() -> ssl:session_id().
+-spec legacy_session_id(map()) -> ssl:session_id().
%%
%% Description: TLS-1.3 deprecates the session id but has a dummy
%% value for it for protocol backwards-compatibility reasons.
%% If now lower versions are configured this function can be called
%% for a dummy value.
%%--------------------------------------------------------------------
-legacy_session_id(#{middlebox_comp_mode := true}) ->
- legacy_session_id();
-legacy_session_id(_) ->
- ?EMPTY_ID.
+legacy_session_id(Opts) ->
+ case maps:get(middlebox_comp_mode, Opts, true) of
+ true -> legacy_session_id();
+ false -> ?EMPTY_ID
+ end.
+
%%--------------------------------------------------------------------
-spec is_new(ssl:session_id() | #session{}, ssl:session_id()) -> boolean().
%%
@@ -87,7 +90,7 @@ client_select_session({_, _, #{versions := Versions,
HVersion = RecordCb:highest_protocol_version(Versions),
case LVersion of
- {3, 4} ->
+ ?TLS_1_3 ->
%% Session reuse is not supported, do pure legacy
%% middlebox comp mode negotiation, by providing either
%% empty session id (no middle box) or random id (middle
@@ -239,7 +242,12 @@ record_cb(dtls) ->
legacy_session_id() ->
crypto:strong_rand_bytes(32).
-maybe_handle_middlebox({3, 4}, #session{session_id = ?EMPTY_ID} = Session, #{middlebox_comp_mode := true})->
- Session#session{session_id = legacy_session_id()};
+maybe_handle_middlebox(?TLS_1_3, #session{session_id = ?EMPTY_ID} = Session, Options)->
+ case maps:get(middlebox_comp_mode, Options,true) of
+ true ->
+ Session#session{session_id = legacy_session_id()};
+ false ->
+ Session
+ end;
maybe_handle_middlebox(_, Session, _) ->
Session.
diff --git a/lib/ssl/src/ssl_trace.erl b/lib/ssl/src/ssl_trace.erl
new file mode 100644
index 0000000000..c8ac32712e
--- /dev/null
+++ b/lib/ssl/src/ssl_trace.erl
@@ -0,0 +1,512 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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:
+%%% This module implements support for using the Erlang trace in a simple way
+%%% for ssl tracing.
+%%%
+%%% Begin the session with ssl_trace:start(). This will do a dbg:start()
+%%% if needed and then dbg:p/2 to set some flags.
+%%%
+%%% Next select trace profiles to activate: for example plain text
+%%% printouts of messages sent or received. This is switched on and off with
+%%% ssl_trace:on(TraceProfile(s)) and ssl_trace:off(TraceProfile(s)).
+%%% For example:
+%%%
+%%% ssl_trace:on(rle) -- switch on printing role traces
+%%% ssl_trace:on([api, rle]) -- switch on printing role and api traces
+%%% ssl_trace:on() -- switch on all ssl trace profiles
+%%%
+%%% To switch, use the off/0 or off/1 function in the same way, for example:
+%%%
+%%% ssl_trace:off(api) -- switch off api tracing, keep all other
+%%% ssl_trace:off() -- switch off all ssl tracing
+%%%
+%%% Present the trace result with some other method than the default
+%%% io:format/2:
+%%% ssl_trace:start(fun(Format,Args) ->
+%%% my_special( io_lib:format(Format,Args) )
+%%% end)
+%%% Write traces to text file with budget of 1000 trace entries:
+%%% ssl_trace:start(IoFmt, [file, {budget, 1000}])
+%%%
+-module(ssl_trace).
+
+-export([start/0, start/1, start/2, stop/0, on/0, on/1, off/0, off/1, is_on/0,
+ is_off/0, write/2]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
+%% Internal apply_after:
+-export([ets_delete/2]).
+%% Test purpose
+-export([trace_profiles/0]).
+
+-behaviour(gen_server).
+-define(SERVER, ?MODULE).
+-define(CALL_TIMEOUT, 15000). % 3x the default
+-define(TRACE_BUDGET, 10000).
+-define(TRACE_FILE, "ssl_trace.txt").
+
+-record(state, {
+ file = undefined,
+ types_on = [],
+ io_device = undefined,
+ write_fun
+ }).
+
+%%%----------------------------------------------------------------
+start() -> start(fun io:format/2).
+
+start(file) ->
+ start(fun io:format/2, [file]);
+start(IoFmtFun) when is_function(IoFmtFun,2) ; is_function(IoFmtFun,3) ->
+ start(IoFmtFun, []).
+
+start(IoFmtFun, TraceOpts) when is_function(IoFmtFun,2);
+ is_function(IoFmtFun,3);
+ is_list(TraceOpts) ->
+ WriteFun = fun(F,A,S) -> IoFmtFun(F,A), S end,
+ {ok, Pid} = gen_server:start({local,?SERVER}, ?MODULE,
+ [{write_fun, WriteFun}, TraceOpts], []),
+ true = is_process_alive(Pid),
+ catch dbg:start(),
+ start_tracer(IoFmtFun, TraceOpts),
+ dbg:p(all, [timestamp, c]),
+ {ok, get_all_trace_profiles()}.
+
+stop() ->
+ try
+ dbg:stop(),
+ ok = gen_server:call(?SERVER, file_close, ?CALL_TIMEOUT),
+ gen_server:stop(?SERVER)
+ catch
+ _:_ -> ok
+ end.
+
+on() ->
+ on(get_all_trace_profiles()).
+
+on(Type) ->
+ switch(on, Type).
+
+off() ->
+ off(get_all_trace_profiles()).
+
+off(Type) ->
+ switch(off, Type).
+
+is_on() ->
+ gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT).
+
+is_off() ->
+ get_all_trace_profiles() -- is_on().
+
+write(Fmt, Args) ->
+ gen_server:call(?SERVER, {write, Fmt, Args}, ?CALL_TIMEOUT).
+
+%%%----------------------------------------------------------------
+init(Args) ->
+ try
+ ets:new(?MODULE, [public, named_table])
+ catch
+ exit:badarg ->
+ ok
+ end,
+ {ok, #state{write_fun = proplists:get_value(write_fun, Args)}}.
+
+handle_call({switch,on,Profiles}, _From, State) ->
+ [enable_profile(P) || P <- Profiles],
+ NowOn = lists:usort(Profiles ++ State#state.types_on),
+ {reply, {ok,NowOn}, State#state{types_on = NowOn}};
+handle_call({switch,off,Profiles}, _From, State) ->
+ StillOn = State#state.types_on -- Profiles,
+ [disable_profile(P) || P <- Profiles],
+ {reply, {ok,StillOn}, State#state{types_on = StillOn}};
+handle_call(get_on, _From, State) ->
+ {reply, State#state.types_on, State};
+handle_call({file_open, File}, _From, State) ->
+ {ok, IODevice} = file:open(File, [write]),
+ {reply, {ok, IODevice}, State#state{io_device = IODevice}};
+handle_call(file_close, _From, #state{io_device = IODevice} = State) ->
+ case is_pid(IODevice) of
+ true ->
+ ok = file:close(IODevice);
+ _ ->
+ ok
+ end,
+ {reply, ok, State#state{io_device = undefined}};
+handle_call({write, Fmt, Args}, _From, State) ->
+ #state{io_device = IODevice, write_fun = WriteFun0} = State,
+ WriteFun = get_write_fun(IODevice, WriteFun0),
+ WriteFun(Fmt, Args, processed),
+ {reply, ok, State};
+handle_call(C, _From, State) ->
+ io:format('*** Unknown call: ~p~n',[C]),
+ {reply, {error,{unknown_call,C}}, State}.
+
+handle_cast({new_proc,Pid}, State) ->
+ monitor(process, Pid),
+ {noreply, State};
+handle_cast(C, State) ->
+ io:format('*** Unknown cast: ~p~n',[C]),
+ {noreply, State}.
+
+handle_info({'DOWN', _MonitorRef, process, Pid, _Info}, State) ->
+ %% Universal real-time synchronization (there might be dbg msgs in the queue to the tracer):
+ timer:apply_after(20000, ?MODULE, ets_delete, [?MODULE, Pid]),
+ {noreply, State};
+handle_info(C, State) ->
+ io:format('*** Unknown info: ~p~n',[C]),
+ {noreply, State}.
+
+%%%----------------------------------------------------------------
+get_proc_stack(Pid) when is_pid(Pid) ->
+ try ets:lookup_element(?MODULE, Pid, 2)
+ catch
+ error:badarg ->
+ %% Non-existing item
+ new_proc(Pid),
+ ets:insert(?MODULE, {Pid,[]}),
+ []
+ end.
+
+new_proc(Pid) when is_pid(Pid) ->
+ gen_server:cast(?SERVER, {new_proc,Pid}).
+
+put_proc_stack(Pid, Stack) when is_pid(Pid),
+ is_list(Stack) ->
+ ets:insert(?MODULE, {Pid, Stack}).
+
+ets_delete(Tab, Key) ->
+ catch ets:delete(Tab, Key).
+
+start_tracer(WriteFun, TraceOpts) when is_function(WriteFun,2) ->
+ start_tracer(fun(F,A,S) -> WriteFun(F,A), S end, TraceOpts);
+start_tracer(WriteFun, TraceOpts) when is_function(WriteFun,3) ->
+ Acc0 = [{budget, proplists:get_value(budget, TraceOpts, ?TRACE_BUDGET)}],
+ Acc1 = case lists:member(file, TraceOpts) of
+ true ->
+ TraceFile =
+ case init:get_argument(ssl_trace_file) of
+ {ok, [[Path]]} -> Path;
+ _ -> ?TRACE_FILE
+ end,
+ [{file, TraceFile} | Acc0];
+ _ ->
+ Acc0
+ end,
+ start_dbg_tracer(WriteFun, Acc1).
+
+start_dbg_tracer(WriteFun, InitHandlerAcc0) when is_function(WriteFun, 3) ->
+ Handler =
+ fun(Arg, Acc0) ->
+ try_handle_trace(gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT),
+ Arg, WriteFun,
+ Acc0)
+ end,
+ InitHandlerAcc1 =
+ case proplists:get_value(file, InitHandlerAcc0) of
+ undefined ->
+ InitHandlerAcc0;
+ File ->
+ {ok, IODevice} = gen_server:call(?SERVER, {file_open, File}, ?CALL_TIMEOUT),
+ [{io_device, IODevice} | InitHandlerAcc0]
+ end,
+ dbg:tracer(process, {Handler,InitHandlerAcc1}).
+
+try_handle_trace(ProfilesOn, Arg, WriteFun0, HandlerAcc) ->
+ IODevice = proplists:get_value(io_device, HandlerAcc),
+ WriteFun = get_write_fun(IODevice, WriteFun0),
+ Budget0 = proplists:get_value(budget, HandlerAcc, 0),
+ Timestamp = trace_ts(Arg),
+ Pid = trace_pid(Arg),
+ TraceInfo = trace_info(Arg),
+ Module = trace_module(TraceInfo),
+ ProcessStack = get_proc_stack(Pid),
+ Role = proplists:get_value(role, ProcessStack, '?'),
+ Budget1 =
+ lists:foldl(
+ fun(Profile, BAcc) ->
+ case BAcc > 1 of
+ true ->
+ try
+ Module:handle_trace(Profile, TraceInfo, ProcessStack)
+ of
+ {skip, NewProcessStack} ->
+ %% Don't try to process this later
+ put_proc_stack(Pid, NewProcessStack),
+ reduce_budget(BAcc, WriteFun);
+ {Txt, NewProcessStack} when is_list(Txt) ->
+ put_proc_stack(Pid, NewProcessStack),
+ write_txt(WriteFun, Timestamp, Pid,
+ common_prefix(TraceInfo, Role,
+ Profile) ++ Txt),
+ reduce_budget(BAcc, WriteFun)
+ catch
+ _:_ ->
+ %% not processed by custom handler
+ BAcc
+ end;
+ _ ->
+ BAcc
+ end
+ end, Budget0, ProfilesOn),
+ %% generate default trace if was not processed by any custom handler
+ Budget2 =
+ case (Budget1 == Budget0 andalso Budget0 > 0) of
+ true ->
+ WriteFun("~.100s ~W~n",
+ [io_lib:format("~s ~p ~s ",
+ [lists:flatten(Timestamp),Pid,
+ common_prefix(TraceInfo, Role,
+ " ")]),
+ TraceInfo, 7], processed),
+ reduce_budget(Budget0, WriteFun);
+ _ ->
+ Budget1
+ end,
+ [{budget, Budget2} | proplists:delete(budget, HandlerAcc)].
+
+get_write_fun(IODevice, WriteFun0) ->
+ case is_pid(IODevice) of
+ true ->
+ fun(Format, Args, Return) ->
+ ok = io:format(IODevice, Format, Args),
+ Return
+ end;
+ false ->
+ WriteFun0
+ end.
+
+reduce_budget(B, _) when B > 1 ->
+ B - 1;
+reduce_budget(_, WriteFun) ->
+ case get(no_budget_msg_written) of
+ undefined ->
+ WriteFun("No more trace budget!~n", [], processed),
+ put(no_budget_msg_written, true);
+ _ ->
+ ok
+ end,
+ 0.
+
+write_txt(WriteFun, Timestamp, Pid, Txt) when is_list(Txt) ->
+ WriteFun("~s ~p ~ts~n", [Timestamp, Pid, Txt], processed).
+
+get_all_trace_profiles() ->
+ Unsorted = [Profile ||
+ {Profile, _TraceOn, _TraceOff, _TracedFuns}
+ <- trace_profiles()],
+ lists:usort(Unsorted).
+
+switch(X, Profile) when is_atom(Profile); is_tuple(Profile) ->
+ switch(X, [Profile]);
+switch(X, Profiles) when is_list(Profiles) ->
+ case whereis(?SERVER) of
+ undefined ->
+ start();
+ _ ->
+ ok
+ end,
+ case unknown_types(Profiles, get_all_trace_profiles(), []) of
+ [] ->
+ gen_server:call(?SERVER, {switch,X,Profiles}, ?CALL_TIMEOUT);
+ L ->
+ {error, {unknown, L}}
+ end.
+
+unknown_types([], _AllProfiles, Acc) -> Acc;
+unknown_types([Profile | Tail], AllProfiles, Acc)
+ when is_atom(Profile) ->
+ case lists:member(Profile, AllProfiles) of
+ false -> unknown_types(Tail, AllProfiles, [Profile | Acc]);
+ _ -> unknown_types(Tail, AllProfiles, Acc)
+ end;
+unknown_types([ModProfile = {_Mod, Profile} | Tail], AllProfiles, Acc)
+ when is_tuple(ModProfile) ->
+ unknown_types([Profile | Tail], AllProfiles, Acc).
+
+%%%----------------------------------------------------------------
+%%% Format of trace messages are described in reference manual for erlang:trace/4
+%%% {call,MFA}
+%%% {return_from,{M,F,N},Result}
+%%% {send,Msg,To}
+%%% {'receive',Msg}
+
+%% Pick 2nd element, the Pid
+trace_pid(T) when element(1,T)==trace
+ ; element(1,T)==trace_ts ->
+ element(2,T).
+
+%% Pick last element, the Time Stamp, and format it
+trace_ts(T) when element(1,T)==trace_ts ->
+ ts( element(tuple_size(T), T) ).
+
+ts({_,_,Usec}=Now) when is_integer(Usec) ->
+ {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now),
+ io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]);
+ts(_) ->
+ "-".
+
+%% Make a tuple of all elements but the 1st, 2nd and last
+trace_info(T) ->
+ case tuple_to_list(T) of
+ [trace,_Pid | Info] -> list_to_tuple(Info);
+ [trace_ts,_Pid | InfoTS] -> list_to_tuple(
+ lists:droplast(InfoTS))
+ end.
+
+trace_module(Info) ->
+ {Module, _, _} = element(2, Info),
+ Module.
+
+common_prefix({call, {M, F, Args}}, Role, Profile) ->
+ [io_lib:format("~s (~w) -> ~w:~w/~w ",
+ [Profile, Role, M, F, length(Args)])];
+common_prefix({return_from, {M, F, Arity}, _Return}, Role, Profile) ->
+ [io_lib:format("~s (~w) <- ~w:~w/~w returned ",
+ [Profile, Role, M, F, Arity])];
+common_prefix({exception_from, {M, F, Arity}, Reason}, Role, Profile) ->
+ [io_lib:format("~s (~w) exception_from ~w:~w/~w ~w",
+ [Profile, Role, M, F, Arity, Reason])];
+common_prefix(_E, _Role, _Profile) ->
+ [].
+
+enable_profile(Profile) when is_atom(Profile) ->
+ [enable_profile({M, Profile}) || M <- modules(Profile)];
+enable_profile({Module, Profile}) when is_atom(Module); is_atom(Profile) ->
+ {Profile, TraceOn, _, AllFuns} = profile(Profile),
+ Funs = proplists:get_value(Module, AllFuns),
+ process_profile(Module, TraceOn, Funs).
+
+disable_profile(Profile) when is_atom(Profile) ->
+ [disable_profile({M, Profile}) || M <- modules(Profile)];
+disable_profile({Module, Profile}) when is_atom(Module); is_atom(Profile) ->
+ {Profile, _, TraceOff, AllFuns} = profile(Profile),
+ Funs = proplists:get_value(Module, AllFuns),
+ process_profile(Module, TraceOff, Funs).
+
+process_profile(Module, Action, Funs) when is_atom(Module) ->
+ [Action(Module, F, A) || {F, A} <- Funs].
+
+profile(P) ->
+ lists:keyfind(P, 1, trace_profiles()).
+
+modules(P) ->
+ {_, _, _, Funs} = profile(P),
+ proplists:get_keys(Funs).
+
+trace_profiles() ->
+ [{api,
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{ssl,
+ [{listen,2}, {connect,3}, {handshake,2}, {close, 1}]},
+ {ssl_gen_statem,
+ [{initial_hello,3}, {connect, 8}, {close, 2}, {terminate_alert, 1}]},
+ {tls_gen_connection,
+ [{start_connection_tree, 5}, {socket_control, 6}]}
+ ]},
+ {csp, %% OCSP
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{ssl_handshake, [{maybe_add_certificate_status_request, 4},
+ {client_hello_extensions, 10}, {cert_status_check, 5},
+ {get_ocsp_responder_list, 1}, {handle_ocsp_extension, 2},
+ {path_validation, 10},
+ {handle_server_hello_extensions, 10},
+ {handle_client_hello_extensions, 10},
+ {cert_status_check, 5}]},
+ {public_key, [{ocsp_extensions, 1}, {pkix_ocsp_validate, 5},
+ {ocsp_responder_id, 1}, {otp_cert, 1}]},
+ {pubkey_ocsp, [{find_responder_cert, 2}, {do_verify_ocsp_signature, 4},
+ {verify_ocsp_response, 3}, {verify_ocsp_nonce, 2},
+ {verify_ocsp_signature, 5}, {do_verify_ocsp_response, 3},
+ {is_responder, 2}, {find_single_response, 3},
+ {ocsp_status, 1}, {match_single_response, 4}]},
+ {ssl, [{opt_ocsp, 3}]},
+ {ssl_certificate, [{verify_cert_extensions, 4}]},
+ {ssl_test_lib, [{init_openssl_server, 3}, {openssl_server_loop, 3}]},
+ {tls_connection, [{wait_ocsp_stapling, 3}]},
+ {dtls_connection, [{initial_hello, 3}, {hello, 3}, {connection, 3}]},
+ {tls_dtls_connection, [{wait_ocsp_stapling, 3}, {certify, 3}]},
+ {tls_handshake, [{ocsp_nonce, 1}, {ocsp_expect, 1}, {client_hello, 11}]},
+ {dtls_handshake, [{client_hello, 8}]}]},
+ {crt, %% certificates
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{public_key, [{pkix_path_validation, 3}, {path_validation, 2},
+ {pkix_decode_cert, 2}]},
+ {ssl_certificate, [{validate, 3}, {trusted_cert_and_paths, 4},
+ {certificate_chain, 3}, {certificate_chain, 5},
+ {issuer, 1}]},
+ {ssl_cipher, [{filter, 3}]},
+ {ssl_gen_statem, [{initial_hello, 3}]},
+ {ssl_handshake, [{path_validate, 11}, {path_validation, 10},
+ {select_hashsign, 5}, {get_cert_params, 1},
+ {cert_curve, 3},
+ {maybe_check_hostname, 3}, {maybe_check_hostname, 3}]},
+ {ssl_pkix_db, [{decode_cert, 2}]},
+ {tls_handshake_1_3, [{path_validation, 10}]},
+ {tls_server_connection_1_3, [{init,1}]},
+ {tls_client_connection_1_3, [{init,1}]},
+ {tls_connection, [{init,1}]},
+ {dtls_connection, [{init,1}]}]},
+ {kdt, %% key update
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{tls_gen_connection_1_3, [{handle_key_update, 2}]},
+ {tls_sender, [{init, 3}, {time_to_rekey, 6},
+ {send_post_handshake_data, 4}]},
+ {tls_v1, [{update_traffic_secret, 2}]}]},
+ {rle, %% role
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{ssl, [{listen,2}, {connect,3}]},
+ {ssl_gen_statem, [{init, 1}]},
+ {tls_server_session_ticket, [{init,1}]},
+ {tls_sender, [{init, 3}]}]},
+ {ssn, %% session
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{tls_server_session_ticket,
+ [{handle_call,3}, {handle_cast,2}, {handle_info,2},
+ {terminate,2}, {start_link,7},
+ {init,1}, {initial_state,1}, {validate_binder,5}, {stateful_store,0},
+ {stateful_ticket_store,6}, {stateful_use,4}, {stateful_use,6},
+ {stateful_usable_ticket,5}, {stateful_living_ticket,2},
+ {stateful_psk_ticket_id,1}, {generate_stateless_ticket,5}, {stateless_use,6},
+ {stateless_usable_ticket,5}, {stateless_living_ticket,5}, {in_window,2},
+ {stateless_anti_replay,5}]},
+ {tls_handshake_1_3,
+ [{get_ticket_data,3}]}]},
+ {hbn, %% hibernate
+ fun(M, F, A) -> dbg:tpl(M, F, A, x) end,
+ fun(M, F, A) -> dbg:ctpl(M, F, A) end,
+ [{tls_sender,
+ [{connection, 3}, {hibernate_after, 3}]},
+ {dtls_connection,
+ [{connection,3},
+ {gen_info, 3}]},
+ {dtls_gen_connection,
+ [{handle_info,3}]},
+ {ssl_gen_statem,
+ [{hibernate_after, 3}, {handle_common_event, 4}]}]}].
diff --git a/lib/ssl/src/tls_client_connection_1_3.erl b/lib/ssl/src/tls_client_connection_1_3.erl
new file mode 100644
index 0000000000..d5742ea390
--- /dev/null
+++ b/lib/ssl/src/tls_client_connection_1_3.erl
@@ -0,0 +1,1007 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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: TLS-1.3 FSM (client side)
+%%----------------------------------------------------------------------
+%% INITIAL_HELLO
+%% Client send
+%% first ClientHello
+%% | ---> CONFIG_ERROR
+%% | Send error to user
+%% | and shutdown
+%% |
+%% V
+%% RFC 8446
+%% A.1. Client
+%%
+%% START <----+
+%% Send ClientHello | | Recv HelloRetryRequest
+%% [K_send = early data] | |
+%% v |
+%% / WAIT_SH ----+
+%% | | Recv ServerHello
+%% | | K_recv = handshake
+%% Can | V
+%% send | WAIT_EE
+%% early | | Recv EncryptedExtensions
+%% data | +--------+--------+
+%% | Using | | Using certificate
+%% | PSK | v
+%% | | WAIT_CERT_CR
+%% | | Recv | | Recv CertificateRequest
+%% | | Certificate | v
+%% | | | WAIT_CERT
+%% | | | | Recv Certificate
+%% | | v v
+%% | | WAIT_CV
+%% | | | Recv CertificateVerify
+%% | +> WAIT_FINISHED <+
+%% | | Recv Finished
+%% \ | [Send EndOfEarlyData]
+%% | K_send = handshake
+%% | [Send Certificate [+ CertificateVerify]]
+%% Can send | Send Finished
+%% app data --> | K_send = K_recv = application
+%% after here v
+%% CONNECTED
+%%
+
+-module(tls_client_connection_1_3).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-include("ssl_alert.hrl").
+-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+-behaviour(gen_statem).
+
+%% gen_statem callbacks
+-export([init/1,
+ callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
+
+%% gen_statem state functions
+-export([config_error/3,
+ initial_hello/3,
+ user_hello/3,
+ start/3,
+ wait_sh/3,
+ hello_middlebox_assert/3,
+ hello_retry_middlebox_assert/3,
+ wait_ee/3,
+ wait_cert_cr/3,
+ wait_cert/3,
+ wait_cv/3,
+ wait_finished/3,
+ connection/3,
+ downgrade/3
+ ]).
+
+%% Internal API
+-export([maybe_send_early_data/1,
+ maybe_automatic_session_resumption/1
+ ]).
+
+%%--------------------------------------------------------------------
+%% gen_statem callbacks
+%%--------------------------------------------------------------------
+callback_mode() ->
+ [state_functions, state_enter].
+
+init([?CLIENT_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
+ State0 = #state{protocol_specific = Map} =
+ tls_gen_connection_1_3:initial_state(?CLIENT_ROLE, Sender,
+ Host, Port, Socket,
+ Options, User, CbInfo),
+ try
+ State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options,
+ ?CLIENT_ROLE, State0),
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
+ catch throw:Error ->
+ EState = State0#state{protocol_specific = Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
+ end.
+
+terminate({shutdown, {sender_died, Reason}}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined);
+terminate(Reason, StateName, State) ->
+ ssl_gen_statem:terminate(Reason, StateName, State).
+
+format_status(Type, Data) ->
+ ssl_gen_statem:format_status(Type, Data).
+
+code_change(_OldVsn, StateName, State, _) ->
+ {ok, StateName, State}.
+
+%--------------------------------------------------------------------
+%% state functions
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello(enter, _, State) ->
+ {keep_state, State};
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error(enter, _, State) ->
+ {keep_state, State};
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello(enter, _, State) ->
+ {keep_state, State};
+user_hello({call, From}, cancel, State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED,
+ user_canceled),
+ ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{handshake_env = #handshake_env{continue_status = pause} = HSEnv,
+ ssl_options = Options0} = State0) ->
+ try ssl:update_options(NewOptions, ?CLIENT_ROLE, Options0) of
+ Options ->
+ State = ssl_gen_statem:ssl_config(Options, ?CLIENT_ROLE, State0),
+ {next_state, wait_sh, State#state{start_or_recv_from = From,
+ handshake_env =
+ HSEnv#handshake_env{continue_status
+ = continue}
+ },
+ [{{timeout, handshake}, Timeout, close}]}
+ catch
+ throw:{error, Reason} ->
+ gen_statem:reply(From, {error, Reason}),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0)
+ end;
+user_hello(Type, Msg, State) ->
+ tls_gen_connection_1_3:user_hello(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec start(gen_statem:event_type(),
+ #server_hello{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+start(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+start(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+start(internal,
+ #server_hello{extensions =
+ #{server_hello_selected_version :=
+ #server_hello_selected_version{selected_version
+ = Version}}}
+ = ServerHello,
+ #state{ssl_options = #{handshake := full,
+ versions := SupportedVersions}} = State) ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State);
+ false ->
+ ssl_gen_statem:handle_own_alert(
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
+ end;
+start(internal, #server_hello{extensions =
+ #{server_hello_selected_version :=
+ #server_hello_selected_version{
+ selected_version = Version}}
+ = Extensions},
+ #state{ssl_options = #{versions := SupportedVersions},
+ start_or_recv_from = From,
+ handshake_env = #handshake_env{continue_status = pause}}
+ = State) ->
+ case tls_record:is_acceptable_version(Version, SupportedVersions) of
+ true ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined},
+ [{postpone, true},
+ {reply, From, {ok, Extensions}}]};
+ false ->
+ ssl_gen_statem:handle_own_alert(
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
+ end;
+start(internal, #server_hello{} = ServerHello,
+ #state{handshake_env =
+ #handshake_env{continue_status = continue}} = State) ->
+ handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State);
+start(internal, #server_hello{}, State0) ->
+ %% Missing mandantory TLS-1.3 extensions,
+ %%so it is a previous version hello.
+ ssl_gen_statem:handle_own_alert(
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
+start(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+start(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_sh(gen_statem:event_type(),
+ #server_hello{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_sh(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_sh(internal, #server_hello{extensions = Extensions},
+ #state{handshake_env = #handshake_env{continue_status = pause},
+ start_or_recv_from = From} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined},
+ [{postpone, true},{reply, From, {ok, Extensions}}]};
+wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello,
+ #state{session = #session{session_id = ?EMPTY_ID},
+ ssl_options = #{middlebox_comp_mode := false}} = State0) ->
+ case handle_server_hello(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
+ {State1, start, ServerHello} ->
+ %% hello_retry_request: go to start
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {State1, wait_ee} ->
+ tls_gen_connection:next_event(wait_ee, no_record, State1)
+ end;
+wait_sh(internal, #server_hello{} = Hello,
+ #state{protocol_specific = PS,
+ ssl_options = SSLOpts} = State0)
+ when not is_map_key(middlebox_comp_mode, SSLOpts) ->
+ IsRetry = maps:get(hello_retry, PS, false),
+ case handle_server_hello(Hello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
+ {State1 = #state{}, start, ServerHello} ->
+ %% hello_retry_request
+ {next_state, start, State1, [{next_event, internal, ServerHello}]};
+ {State1, wait_ee} when IsRetry == true ->
+ tls_gen_connection:next_event(wait_ee, no_record, State1);
+ {State1, wait_ee} when IsRetry == false ->
+ tls_gen_connection:next_event(hello_middlebox_assert,
+ no_record, State1)
+ end;
+wait_sh(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_sh(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec hello_middlebox_assert(gen_statem:event_type(),
+ #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello_middlebox_assert(enter, _, State) ->
+ {keep_state, State};
+hello_middlebox_assert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(wait_ee, no_record, State);
+hello_middlebox_assert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+hello_middlebox_assert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec hello_retry_middlebox_assert(gen_statem:event_type(),
+ #server_hello{} | #change_cipher_spec{}
+ | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+hello_retry_middlebox_assert(enter, _, State) ->
+ {keep_state, State};
+hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) ->
+ tls_gen_connection:next_event(wait_sh, no_record, State);
+hello_retry_middlebox_assert(internal, #server_hello{}, State) ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, [postpone]);
+hello_retry_middlebox_assert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+hello_retry_middlebox_assert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_ee(gen_statem:event_type(),
+ #encrypted_extensions{} | #change_cipher_spec{}
+ | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_ee(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_ee(internal, #encrypted_extensions{extensions = Extensions}, State0) ->
+ case handle_encrypted_extensions(Extensions, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0);
+ {State, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State)
+ end;
+wait_ee(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_ee(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert_cr(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert_cr(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
+ case handle_certificate(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State);
+ {State1, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest,
+ State0) ->
+ case handle_certificate_request(CertificateRequest, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State0);
+ {State1, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State1)
+ end;
+wait_cert_cr(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert_cr(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cert(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cv(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cv(internal,
+ #certificate_verify_1_3{} = CertificateVerify, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ {State, NextState}
+ = Maybe(tls_handshake_1_3:verify_certificate_verify(State0,
+ CertificateVerify)),
+ tls_gen_connection:next_event(NextState, no_record, State)
+ catch
+ {Ref, {#alert{} = Alert, AState}} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cv, AState)
+ end;
+wait_cv(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cv(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_finished(gen_statem:event_type(),
+ #finished{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_finished(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
+ ?FUNCTION_NAME, State);
+wait_finished(internal,
+ #finished{verify_data = VerifyData},
+ #state{static_env = #static_env{protocol_cb = Connection}}
+ = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:validate_finished(State0, VerifyData)),
+ %% D.4. Middlebox Compatibility Mode
+ State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0,
+ first),
+ %% Signal change of cipher
+ State2 = maybe_send_end_of_early_data(State1),
+ %% Maybe send Certificate + CertificateVerify
+ State3 = Maybe(maybe_queue_cert_cert_cv(State2)),
+ Finished = tls_handshake_1_3:finished(State3),
+ %% Encode Finished
+ State4 = Connection:queue_handshake(Finished, State3),
+ %% Send first flight
+ {State5, _} = Connection:send_handshake_flight(State4),
+ State6 = tls_handshake_1_3:calculate_traffic_secrets(State5),
+ State7 =
+ tls_handshake_1_3:maybe_calculate_resumption_master_secret(State6),
+ State8 = tls_handshake_1_3:forget_master_secret(State7),
+ %% Configure traffic keys
+ State9 = ssl_record:step_encryption_state(State8),
+ {Record, State} = ssl_gen_statem:prepare_connection(State9,
+ tls_gen_connection),
+ tls_gen_connection:next_event(connection, Record, State,
+ [{{timeout, handshake}, cancel}])
+ catch
+ {Ref, #alert{} = Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0)
+ end;
+wait_finished(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_finished(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection(Type, Msg, State) ->
+ tls_gen_connection_1_3:connection(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec downgrade(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+downgrade(Type, Msg, State) ->
+ tls_gen_connection_1_3:downgrade(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State0) ->
+ case do_handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello,
+ State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, start, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end.
+
+do_handle_exlusive_1_3_hello_or_hello_retry_request(
+ #server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions},
+ #state{static_env = #static_env{host = Host,
+ port = Port,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
+ ocsp_stapling_state = OcspState},
+ connection_env = #connection_env{negotiated_version =
+ NegotiatedVersion},
+ protocol_specific = PS,
+ ssl_options = #{ciphers := ClientCiphers,
+ supported_groups := ClientGroups0,
+ use_ticket := UseTicket,
+ session_tickets := SessionTickets,
+ log_level := LogLevel} = SslOpts,
+ session = Session0,
+ connection_states = ConnectionStates0
+ } = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ClientGroups =
+ Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
+ Cookie = maps:get(cookie, Extensions, undefined),
+
+ KeyShare = maps:get(key_share, Extensions, undefined),
+ SelectedGroup = server_group(KeyShare),
+
+ %% Upon receipt of this extension in a HelloRetryRequest, the client
+ %% MUST verify that (1) the selected_group field corresponds to a group
+ %% which was provided in the "supported_groups" extension in the
+ %% original ClientHello and (2) the selected_group field does not
+ %% correspond to a group which was provided in the "key_share" extension
+ %% in the original ClientHello. If either of these checks fails, then
+ %% the client MUST abort the handshake with an "illegal_parameter"
+ %% alert.
+ case KeyShare of
+ #key_share_hello_retry_request{} ->
+ Maybe(validate_selected_group(SelectedGroup, ClientGroups));
+ _ ->
+ ok
+ end,
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+
+ %% Otherwise, when sending the new ClientHello, the client MUST
+ %% replace the original "key_share" extension with one containing only a
+ %% new KeyShareEntry for the group indicated in the selected_group field
+ %% of the triggering HelloRetryRequest.
+ ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
+ TicketData =
+ tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
+ OcspNonce = maps:get(ocsp_nonce, OcspState, undefined),
+ Hello0 = tls_handshake:client_hello(Host, Port,
+ ConnectionStates0, SslOpts,
+ SessionId, Renegotiation,
+ ClientKeyShare,
+ TicketData, OcspNonce,
+ CertDbHandle, CertDbRef),
+ %% Echo cookie received in HelloRetryrequest
+ Hello1 = tls_handshake_1_3:maybe_add_cookie_extension(Cookie, Hello0),
+
+ %% Update state
+ State1 =
+ tls_handshake_1_3:update_start_state(State0,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare,
+ session_id => SessionId,
+ group => SelectedGroup}),
+
+ %% Replace ClientHello1 with a special synthetic handshake message
+ State2 = tls_handshake_1_3:replace_ch1_with_message_hash(State1),
+ #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(Hello1, HHistory0,
+ TicketData, NegotiatedVersion),
+
+ {BinMsg0, ConnectionStates, HHistory} =
+ Connection:encode_handshake(Hello,
+ NegotiatedVersion,
+ ConnectionStates0,
+ HHistory0),
+
+ %% D.4. Middlebox Compatibility Mode
+ {#state{handshake_env = HsEnv} = State3, BinMsg} =
+ tls_gen_connection_1_3:maybe_prepend_change_cipher_spec(State2,
+ BinMsg0),
+
+ tls_socket:send(Transport, Socket, BinMsg),
+ ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
+
+ State = State3#state{
+ connection_states = ConnectionStates,
+ session = Session0#session{session_id =
+ Hello#client_hello.session_id},
+ handshake_env =
+ HsEnv#handshake_env{tls_handshake_history = HHistory},
+ key_share = ClientKeyShare},
+
+ %% If it is a hello_retry and middlebox mode is
+ %% used assert the change_cipher_spec message
+ %% that the server should send next
+ case (maps:get(hello_retry, PS, false)) andalso
+ (maps:get(middlebox_comp_mode, SslOpts, true))
+ of
+ true ->
+ {State, hello_retry_middlebox_assert};
+ false ->
+ {State, wait_sh}
+ end
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert
+ end.
+
+handle_server_hello(#server_hello{cipher_suite = SelectedCipherSuite,
+ session_id = SessionId,
+ extensions = Extensions} = ServerHello,
+ #state{key_share = ClientKeyShare,
+ ssl_options = #{ciphers := ClientCiphers,
+ supported_groups := ClientGroups0,
+ session_tickets := SessionTickets,
+ use_ticket := UseTicket}} = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ClientGroups =
+ Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
+ ServerKeyShare = server_share(maps:get(key_share, Extensions)),
+ ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),
+
+ %% Go to state 'start' if server replies with 'HelloRetryRequest'.
+ Maybe(tls_handshake_1_3:maybe_hello_retry_request(ServerHello, State0)),
+
+ %% Resumption and PSK
+ State1 = tls_gen_connection_1_3:handle_resumption(State0,
+ ServerPreSharedKey),
+
+ Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
+ Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),
+
+ %% Get server public key
+ #key_share_entry{group = SelectedGroup,
+ key_exchange = ServerPublicKey} = ServerKeyShare,
+
+ ClientPrivateKey =
+ client_private_key(SelectedGroup,
+ ClientKeyShare#key_share_client_hello.client_shares),
+ %% Update state
+ State2 = tls_handshake_1_3:update_start_state(State1,
+ #{cipher => SelectedCipherSuite,
+ key_share => ClientKeyShare,
+ session_id => SessionId,
+ group => SelectedGroup,
+ peer_public_key => ServerPublicKey}),
+
+ #state{connection_states = ConnectionStates} = State2,
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ PSK = Maybe(tls_handshake_1_3:get_pre_shared_key(SessionTickets,
+ UseTicket,
+ HKDFAlgo,
+ ServerPreSharedKey)),
+ State3 =
+ tls_handshake_1_3:calculate_handshake_secrets(ServerPublicKey,
+ ClientPrivateKey,
+ SelectedGroup,
+ PSK, State2),
+ State4 = ssl_record:step_encryption_state_read(State3),
+ {State4, wait_ee}
+ catch
+ {Ref, {State, StateName, ServerHello}} ->
+ {State, StateName, ServerHello};
+ {Ref, #alert{} = Alert} ->
+ Alert
+ end.
+
+handle_encrypted_extensions(Extensions, State0) ->
+ {Ref, Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
+ ALPNProtocol = decode_alpn(ALPNProtocol0),
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
+
+ %% RFC 6066: handle received/expected maximum fragment length
+ Maybe(maybe_max_fragment_length(Extensions, State0)),
+
+ %% Check if early_data is accepted/rejected
+ State1 = maybe_check_early_data_indication(EarlyDataIndication, State0),
+
+ %% Go to state 'wait_finished' if using PSK.
+ Maybe(maybe_resumption(State1)),
+
+ %% Update state
+ #state{handshake_env = HsEnv} = State1,
+ State2 = State1#state{handshake_env =
+ HsEnv#handshake_env{alpn = ALPNProtocol}},
+ {State2, wait_cert_cr}
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert;
+ {Ref, {_, _} = Next} ->
+ Next
+ end.
+
+handle_certificate(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:process_certificate(Certificate, State0))
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end.
+handle_certificate_request(#certificate_request_1_3{} =
+ CertificateRequest, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:process_certificate_request(
+ CertificateRequest, State0))
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0}
+ end.
+
+maybe_send_early_data(#state{
+ handshake_env =
+ #handshake_env{tls_handshake_history = {Hist, _}},
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ session_tickets := SessionTickets,
+ early_data := EarlyData} = _SslOpts0
+ } = State0) when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined ->
+ %% D.4. Middlebox Compatibility Mode
+ State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0, last),
+ %% Early traffic secret
+ EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData),
+ case tls_handshake_1_3:get_pre_shared_key_early_data(SessionTickets,
+ UseTicket) of
+ {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize ->
+ State2 =
+ tls_handshake_1_3:calculate_client_early_traffic_secret(Hist,
+ PSK,
+ Cipher,
+ HKDF,
+ State1),
+ %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
+ State3 = ssl_record:step_encryption_state_write(State2),
+ {ok, tls_handshake_1_3:encode_early_data(Cipher, State3)};
+ {ok, {_, _, _, MaxSize}} ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
+ {too_much_early_data, {max, MaxSize}})};
+ {error, Alert} ->
+ {error, Alert}
+ end;
+maybe_send_early_data(State) ->
+ {ok, State}.
+
+maybe_send_end_of_early_data(
+ #state{
+ handshake_env = #handshake_env{early_data_accepted = true},
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State0) when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined ->
+ %% EndOfEarlydata is encrypted with the 0-RTT traffic keys
+ State1 = Connection:queue_handshake(#end_of_early_data{}, State0),
+ %% Use handshake keys after EndOfEarlyData is sent
+ ssl_record:step_encryption_state_write(State1);
+maybe_send_end_of_early_data(State) ->
+ State.
+
+%% Configure a suitable session ticket
+maybe_automatic_session_resumption(#state{ssl_options =
+ #{versions := [Version|_],
+ ciphers := UserSuites,
+ early_data := EarlyData,
+ session_tickets :=
+ SessionTickets,
+ server_name_indication := SNI}
+ = SslOpts0
+ } = State0)
+ when ?TLS_GTE(Version, ?TLS_1_3) andalso
+ SessionTickets =:= auto ->
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ HashAlgos = cipher_hash_algos(AvailableCipherSuites),
+ Ciphers = tls_handshake_1_3:ciphers_for_early_data(AvailableCipherSuites),
+ %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where
+ %% Ticket0 satisfies requirements for early_data and session
+ %% resumption while Ticket2 can only be used for session
+ %% resumption.
+ EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData),
+ KeyPair =
+ tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos,
+ SNI, EarlyDataSize),
+ UseTicket = tls_handshake_1_3:choose_ticket(KeyPair, EarlyData),
+ 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}.
+
+maybe_resumption(#state{handshake_env =
+ #handshake_env{resumption = true}} = State) ->
+ {error, {State, wait_finished}};
+maybe_resumption(_) ->
+ ok.
+
+server_group(undefined) ->
+ undefined;
+server_group(#key_share_server_hello{server_share = #key_share_entry{group = Group}}) ->
+ Group;
+server_group(#key_share_hello_retry_request{selected_group = Group}) ->
+ Group.
+
+server_share(#key_share_server_hello{server_share = Share}) ->
+ Share;
+server_share(#key_share_hello_retry_request{selected_group = Share}) ->
+ Share.
+
+client_private_key(Group, ClientShares) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, #key_share_entry{key_exchange =
+ ClientPrivateKey = #'ECPrivateKey'{}}} ->
+ ClientPrivateKey;
+ {value, #key_share_entry{key_exchange = {_, ClientPrivateKey}}} ->
+ ClientPrivateKey;
+ false ->
+ no_suitable_key
+ end.
+
+maybe_check_early_data_indication(EarlyDataIndication,
+ #state{
+ handshake_env = HsEnv,
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData}
+ } = State)
+ when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined andalso
+ EarlyDataIndication =/= undefined ->
+ signal_user_early_data(State, accepted),
+ State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}};
+maybe_check_early_data_indication(EarlyDataIndication,
+ #state{
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [?TLS_1_3|_],
+ use_ticket := UseTicket,
+ early_data := EarlyData}
+ = _SslOpts0
+ } = State)
+ when UseTicket =/= [undefined] andalso
+ EarlyData =/= undefined andalso
+ EarlyDataIndication =:= undefined ->
+ signal_user_early_data(State, rejected),
+ %% Use handshake keys if early_data is rejected.
+ ssl_record:step_encryption_state_write(State);
+maybe_check_early_data_indication(_, State) ->
+ %% Use handshake keys if there is no early_data.
+ ssl_record:step_encryption_state_write(State).
+
+signal_user_early_data(#state{
+ connection_env =
+ #connection_env{
+ user_application = {_, User}},
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ trackers = Trackers}} = State,
+ Result) ->
+ CPids = Connection:pids(State),
+ SslSocket = Connection:socket(CPids, Transport, Socket, Trackers),
+ User ! {ssl, SslSocket, {early_data, Result}}.
+
+maybe_max_fragment_length(Extensions, State) ->
+ ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
+ ClientMaxFragEnum = ssl_handshake:max_frag_enum(
+ maps:get(max_fragment_length,
+ State#state.ssl_options, undefined)),
+ if ServerMaxFragEnum == ClientMaxFragEnum ->
+ ok;
+ true ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
+
+cipher_hash_algos(Ciphers) ->
+ Fun = fun(Cipher) ->
+ #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher),
+ Hash
+ end,
+ lists:map(Fun, Ciphers).
+
+maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested}
+ = State) ->
+ {ok, State};
+maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
+ session = #session{session_id = _SessionId,
+ own_certificates = OwnCerts},
+ ssl_options = #{} = _SslOpts,
+ key_share = _KeyShare,
+ handshake_env =
+ #handshake_env{tls_handshake_history =
+ _HHistory0},
+ static_env = #static_env{
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ %% Create Certificate
+ Certificate = Maybe(tls_handshake_1_3:certificate(OwnCerts,
+ CertDbHandle,
+ CertDbRef, <<>>,
+ client)),
+
+ %% Encode Certificate
+ State1 = Connection:queue_handshake(Certificate, State0),
+ %% Maybe create and queue CertificateVerify
+ State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
+ {ok, State}
+ catch
+ {Ref, #alert{} = Alert} ->
+ {error, Alert}
+ end.
+
+%% Clients MUST send this message whenever authenticating via a certificate
+%% (i.e., when the Certificate message is non-empty).
+maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
+ {ok, State};
+maybe_queue_cert_verify(_Certificate,
+ #state{connection_states = _ConnectionStates0,
+ session = #session{sign_alg = SignatureScheme,
+ private_key = CertPrivateKey},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ CertificateVerify =
+ Maybe(tls_handshake_1_3:certificate_verify(CertPrivateKey,
+ SignatureScheme,
+ State, client)),
+ {ok, Connection:queue_handshake(CertificateVerify, State)}
+ catch
+ {Ref, #alert{} = Alert} ->
+ {error, Alert}
+ end.
+
+decode_alpn(undefined) ->
+ undefined;
+decode_alpn(Encoded) ->
+ [Decoded] = ssl_handshake:decode_alpn(Encoded),
+ Decoded.
+
+%% Verify that selected group is offered by the client.
+validate_server_key_share([], _) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+validate_server_key_share([Group |_ClientGroups], #key_share_entry{group = Group}) ->
+ ok;
+validate_server_key_share([_|ClientGroups], #key_share_entry{} = ServerKeyShare) ->
+ validate_server_key_share(ClientGroups, ServerKeyShare).
+
+
+validate_selected_group(SelectedGroup, [SelectedGroup|_]) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
+ "Selected group sent by the server shall not correspond to a group"
+ " which was provided in the key_share extension")};
+validate_selected_group(SelectedGroup, ClientGroups) ->
+ case lists:member(SelectedGroup, ClientGroups) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
+ "Selected group sent by the server shall correspond to a group"
+ " which was provided in the supported_groups extension")}
+ end.
+
+%% RFC 8446 4.1.3 ServerHello
+%% A client which receives a cipher suite that was not offered MUST abort the
+%% handshake with an "illegal_parameter" alert.
+validate_cipher_suite(Cipher, ClientCiphers) ->
+ case lists:member(Cipher, ClientCiphers) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end.
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 6cc9e21cfb..83ebdbd167 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -71,10 +71,10 @@
%% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1
%% |
%% New session | Resumed session
-%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED
+%% WAIT_OCSP_STAPLING CERTIFY <----------------------------------> ABBREVIATED
%% WAIT_CERT_VERIFY
%% <- Possibly Receive -- | |
-%% OCSP Stapel/CertVerify -> | Flight 3 part 1 |
+%% OCSP Staple/CertVerify -> | Flight 3 part 1 |
%% | |
%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3
%% CIPHER |
@@ -135,7 +135,9 @@
terminate/3,
code_change/4,
format_status/2]).
-
+
+%% Tracing
+-export([handle_trace/3]).
%%====================================================================
%% Internal application API
%%====================================================================
@@ -147,7 +149,7 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
},
connection_env = #connection_env{cert_key_alts = CertKeyAlts},
ssl_options = SslOptions,
- session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
+ session = Session0} = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, Role, State0),
State = case Role of
client ->
CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts),
@@ -232,7 +234,7 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
case choose_tls_fsm(SslOpts, Hello) of
tls_1_3_fsm ->
{next_state, start, State1,
- [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]};
+ [{change_callback_module, tls_server_connection_1_3}, {next_event, internal, Hello}]};
tls_1_0_to_1_2_fsm ->
{ServerHelloExt, Type, State} = handle_client_hello(Hello, State1),
{next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}
@@ -254,18 +256,23 @@ hello(internal, #server_hello{} = Hello,
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of
%% Legacy TLS 1.2 and older
{Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} ->
- tls_dtls_connection:handle_session(Hello,
- Version, NewId, ConnectionStates, ProtoExt, Protocol,
- State#state{
- handshake_env = HsEnv#handshake_env{
- ocsp_stapling_state = maps:merge(OcspState0,OcspState)}});
+ tls_dtls_connection:handle_session(
+ Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol,
+ State#state{
+ handshake_env =
+ HsEnv#handshake_env{
+ ocsp_stapling_state = maps:merge(OcspState0,OcspState)}});
%% TLS 1.3
{next_state, wait_sh, SelectedVersion, OcspState} ->
%% Continue in TLS 1.3 'wait_sh' state
{next_state, wait_sh,
- State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state = maps:merge(OcspState0,OcspState)},
- connection_env = CEnv#connection_env{negotiated_version = SelectedVersion}},
- [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state =
+ maps:merge(OcspState0, OcspState)},
+ connection_env =
+ CEnv#connection_env{negotiated_version = SelectedVersion}},
+ [{change_callback_module, tls_client_connection_1_3},
+ {next_event, internal, Hello}]}
end
catch throw:#alert{} = Alert ->
ssl_gen_statem:handle_own_alert(Alert, hello, State)
@@ -476,10 +483,8 @@ code_change(_OldVsn, StateName, State, _) ->
initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
{CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
put(log_level, maps:get(log_level, SSLOptions)),
- #{erl_dist := IsErlDist,
- %% Use highest supported version for client/server random nonce generation
- versions := [Version|_],
- client_renegotiation := ClientRenegotiation} = SSLOptions,
+ %% Use highest supported version for client/server random nonce generation
+ #{versions := [Version|_]} = SSLOptions,
BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled),
ConnectionStates = tls_record:init_connection_states(Role,
Version,
@@ -505,7 +510,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
handshake_env = #handshake_env{
tls_handshake_history = ssl_handshake:init_handshake_history(),
renegotiation = {false, first},
- allow_renegotiate = ClientRenegotiation
+ allow_renegotiate = maps:get(client_renegotiation, SSLOptions, undefined)
},
connection_env = #connection_env{user_application = {UserMonitor, User}},
socket_options = SocketOptions,
@@ -517,7 +522,8 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
start_or_recv_from = undefined,
flight_buffer = [],
protocol_specific = #{sender => Sender,
- active_n => ssl_config:get_internal_active_n(IsErlDist),
+ active_n => ssl_config:get_internal_active_n(
+ maps:get(erl_dist, SSLOptions, false)),
active_n_toggle => true
}
}.
@@ -591,10 +597,19 @@ choose_tls_fsm(#{versions := Versions},
}
}) ->
case ssl_handshake:select_supported_version(ClientVersions, Versions) of
- {3,4} ->
+ ?TLS_1_3 ->
tls_1_3_fsm;
_Else ->
tls_1_0_to_1_2_fsm
end;
choose_tls_fsm(_, _) ->
tls_1_0_to_1_2_fsm.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, wait_ocsp_stapling,
+ [Type, Event|_]}}, Stack) ->
+ {io_lib:format("Type = ~w Event = ~W", [Type, Event, 10]), Stack}.
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
deleted file mode 100644
index 8041e48eb0..0000000000
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ /dev/null
@@ -1,740 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2023. 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: TLS-1.3 FSM
-%%----------------------------------------------------------------------
-%% INITIAL_HELLO
-%% Client send
-%% first ClientHello
-%% | ---> CONFIG_ERROR
-%% | Send error to user
-%% | and shutdown
-%% |
-%% V
-%% RFC 8446
-%% A.1. Client
-%%
-%% START <----+
-%% Send ClientHello | | Recv HelloRetryRequest
-%% [K_send = early data] | |
-%% v |
-%% / WAIT_SH ----+
-%% | | Recv ServerHello
-%% | | K_recv = handshake
-%% Can | V
-%% send | WAIT_EE
-%% early | | Recv EncryptedExtensions
-%% data | +--------+--------+
-%% | Using | | Using certificate
-%% | PSK | v
-%% | | WAIT_CERT_CR
-%% | | Recv | | Recv CertificateRequest
-%% | | Certificate | v
-%% | | | WAIT_CERT
-%% | | | | Recv Certificate
-%% | | v v
-%% | | WAIT_CV
-%% | | | Recv CertificateVerify
-%% | +> WAIT_FINISHED <+
-%% | | Recv Finished
-%% \ | [Send EndOfEarlyData]
-%% | K_send = handshake
-%% | [Send Certificate [+ CertificateVerify]]
-%% Can send | Send Finished
-%% app data --> | K_send = K_recv = application
-%% after here v
-%% CONNECTED
-%%
-%% A.2. Server
-%%
-%% START <-----+
-%% Recv ClientHello | | Send HelloRetryRequest
-%% v |
-%% RECVD_CH ----+
-%% | Select parameters
-%% v
-%% NEGOTIATED
-%% | Send ServerHello
-%% | K_send = handshake
-%% | Send EncryptedExtensions
-%% | [Send CertificateRequest]
-%% Can send | [Send Certificate + CertificateVerify]
-%% app data | Send Finished
-%% after --> | K_send = application
-%% here +--------+--------+
-%% No 0-RTT | | 0-RTT
-%% | |
-%% K_recv = handshake | | K_recv = early data
-%% [Skip decrypt errors] | +------> WAIT_EOED -+
-%% | | Recv | | Recv EndOfEarlyData
-%% | | early data | | K_recv = handshake
-%% | +------------+ |
-%% | |
-%% +> WAIT_FLIGHT2 <--------+
-%% |
-%% +--------+--------+
-%% No auth | | Client auth
-%% | |
-%% | v
-%% | WAIT_CERT
-%% | Recv | | Recv Certificate
-%% | empty | v
-%% | Certificate | WAIT_CV
-%% | | | Recv
-%% | v | CertificateVerify
-%% +-> WAIT_FINISHED <---+
-%% | Recv Finished
-%% | K_recv = application
-%% v
-%% CONNECTED
-
--module(tls_connection_1_3).
-
--include("ssl_alert.hrl").
--include("ssl_connection.hrl").
--include("tls_connection.hrl").
--include("tls_handshake.hrl").
--include("tls_handshake_1_3.hrl").
-
--behaviour(gen_statem).
-
-%% gen_statem callbacks
--export([init/1, callback_mode/0, terminate/3, code_change/4, format_status/2]).
-
-%% gen_statem state functions
--export([initial_hello/3,
- config_error/3,
- user_hello/3,
- start/3,
- hello_middlebox_assert/3,
- hello_retry_middlebox_assert/3,
- negotiated/3,
- wait_cert/3,
- wait_cv/3,
- wait_finished/3,
- wait_sh/3,
- wait_ee/3,
- wait_cert_cr/3,
- wait_eoed/3,
- connection/3,
- downgrade/3
- ]).
-
-%% Internal API
--export([setopts/3,
- getopts/3,
- send_key_update/2,
- update_cipher_key/2]).
-
-%%====================================================================
-%% Internal API
-%%====================================================================
-
-setopts(Transport, Socket, Other) ->
- tls_socket:setopts(Transport, Socket, Other).
-
-getopts(Transport, Socket, Tag) ->
- tls_socket:getopts(Transport, Socket, Tag).
-
-send_key_update(Sender, Type) ->
- KeyUpdate = tls_handshake_1_3:key_update(Type),
- tls_sender:send_post_handshake(Sender, KeyUpdate).
-
-update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
- CS = update_cipher_key(ConnStateName, CS0),
- State0#state{connection_states = CS};
-update_cipher_key(ConnStateName, CS0) ->
- #{security_parameters := SecParams0,
- cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
- HKDF = SecParams0#security_parameters.prf_algorithm,
- CipherSuite = SecParams0#security_parameters.cipher_suite,
- ApplicationTrafficSecret0 = SecParams0#security_parameters.application_traffic_secret,
- ApplicationTrafficSecret = tls_v1:update_traffic_secret(HKDF, ApplicationTrafficSecret0),
-
- %% Calculate traffic keys
- KeyLength = tls_v1:key_length(CipherSuite),
- {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, KeyLength, ApplicationTrafficSecret),
-
- SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
- CipherState = CipherState0#cipher_state{key = Key, iv = IV},
- ConnState = ConnState0#{security_parameters => SecParams,
- cipher_state => CipherState,
- sequence_number => 0},
- CS0#{ConnStateName => ConnState}.
-
-%--------------------------------------------------------------------
-%% gen_statem callbacks
-%%--------------------------------------------------------------------
-callback_mode() ->
- [state_functions, state_enter].
-
-init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
- State0 = #state{protocol_specific = Map} = initial_state(Role, Sender,
- Host, Port, Socket, Options, User, CbInfo),
- try
- State = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0),
- tls_gen_connection:initialize_tls_sender(State),
- gen_statem:enter_loop(?MODULE, [], initial_hello, State)
- catch throw:Error ->
- EState = State0#state{protocol_specific = Map#{error => Error}},
- gen_statem:enter_loop(?MODULE, [], config_error, EState)
- end.
-
-terminate({shutdown, {sender_died, Reason}}, _StateName,
- #state{static_env = #static_env{socket = Socket,
- transport_cb = Transport}}
- = State) ->
- ssl_gen_statem:handle_trusted_certs_db(State),
- tls_gen_connection:close(Reason, Socket, Transport, undefined);
-terminate(Reason, StateName, State) ->
- ssl_gen_statem:terminate(Reason, StateName, State).
-
-format_status(Type, Data) ->
- ssl_gen_statem:format_status(Type, Data).
-
-code_change(_OldVsn, StateName, State, _) ->
- {ok, StateName, State}.
-
-%--------------------------------------------------------------------
-%% state callbacks
-%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
--spec initial_hello(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-initial_hello(enter, _, State) ->
- {keep_state, State};
-initial_hello(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-%%--------------------------------------------------------------------
--spec config_error(gen_statem:event_type(),
- {start, timeout()} | term(), #state{}) ->
- gen_statem:state_function_result().
-%%--------------------------------------------------------------------
-config_error(enter, _, State) ->
- {keep_state, State};
-config_error(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-user_hello(enter, _, State) ->
- {keep_state, State};
-user_hello({call, From}, cancel, State) ->
- gen_statem:reply(From, ok),
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
- ?FUNCTION_NAME, State);
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = client = Role},
- handshake_env = HSEnv,
- ssl_options = Options0} = State0) ->
- Options = ssl:handle_options(NewOptions, Role, Options0),
- State = ssl_gen_statem:ssl_config(Options, Role, State0),
- {next_state, wait_sh, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}},
- [{{timeout, handshake}, Timeout, close}]};
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
- #state{static_env = #static_env{role = server = Role},
- handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv,
- ssl_options = Options0} = State0) ->
- Options = #{versions := Versions} = ssl:handle_options(NewOptions, Role, Options0),
- State = ssl_gen_statem:ssl_config(Options, Role, State0),
- case ssl_handshake:select_supported_version(ClientVersions, Versions) of
- {3,4} ->
- {next_state, start, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}},
- [{{timeout, handshake}, Timeout, close}]};
- undefined ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State);
- _Else ->
- {next_state, hello, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}},
- [{change_callback_module, tls_connection},
- {{timeout, handshake}, Timeout, close}]}
- end;
-user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
- ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
-user_hello(_, _, _) ->
- {keep_state_and_data, [postpone]}.
-
-start(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-start(internal = Type, #change_cipher_spec{} = Msg,
- #state{static_env = #static_env{role = server},
- handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) ->
- case ssl_handshake:init_handshake_history() of
- Hist -> %% First message must always be client hello
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
- _ ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State)
- end;
-start(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-start(internal, #client_hello{extensions = #{client_hello_versions :=
- #client_hello_versions{versions = ClientVersions}
- }} = Hello,
- #state{ssl_options = #{handshake := full}} = State) ->
- case tls_record:is_acceptable_version({3,4}, ClientVersions) of
- true ->
- do_server_start(Hello, State);
- false ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
- end;
-start(internal, #client_hello{extensions = #{client_hello_versions :=
- #client_hello_versions{versions = ClientVersions}
- }= Extensions},
- #state{start_or_recv_from = From,
- handshake_env = #handshake_env{continue_status = pause} = HSEnv} = State) ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined, handshake_env = HSEnv#handshake_env{continue_status = {pause, ClientVersions}}},
- [{postpone, true}, {reply, From, {ok, Extensions}}]};
-start(internal, #client_hello{} = Hello,
- #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
- do_server_start(Hello, State);
-start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello.
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
-start(internal, #server_hello{extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}}} = ServerHello,
- #state{ssl_options = #{handshake := full,
- versions := SupportedVersions}} = State) ->
- case tls_record:is_acceptable_version(Version, SupportedVersions) of
- true ->
- do_client_start(ServerHello, State);
- false ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
- end;
-start(internal, #server_hello{extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}}
- = Extensions},
- #state{ssl_options = #{versions := SupportedVersions},
- start_or_recv_from = From,
- handshake_env = #handshake_env{continue_status = pause}}
- = State) ->
- case tls_record:is_acceptable_version(Version, SupportedVersions) of
- true ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined}, [{postpone, true}, {reply, From, {ok, Extensions}}]};
- false ->
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State)
- end;
-start(internal, #server_hello{} = ServerHello,
- #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
- do_client_start(ServerHello, State);
-start(internal, #server_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions, so it is a previous version hello.
- ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
-start(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-start(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-negotiated(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-negotiated(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-negotiated(internal, Message, State0) ->
- case tls_handshake_1_3:do_negotiated(Message, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, negotiated, State0);
- {State, NextState} ->
- {next_state, NextState, State, []}
- end;
-negotiated(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State).
-
-wait_cert(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_cert(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_cert(internal,
- #certificate_1_3{} = Certificate, State0) ->
- case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cert, State);
- {State, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State)
- end;
-wait_cert(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_cert(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_cv(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_cv(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_cv(internal,
- #certificate_verify_1_3{} = CertificateVerify, State0) ->
- case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cv, State);
- {State, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State)
- end;
-wait_cv(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_cv(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_finished(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_finished(internal,
- #finished{} = Finished, State0) ->
- case tls_handshake_1_3:do_wait_finished(Finished, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, finished, State0);
- State1 ->
- {Record, State} = ssl_gen_statem:prepare_connection(State1, tls_gen_connection),
- tls_gen_connection:next_event(connection, Record, State,
- [{{timeout, handshake}, cancel}])
- end;
-wait_finished(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_finished(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_sh(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_sh(internal, #server_hello{extensions = Extensions},
- #state{handshake_env = #handshake_env{continue_status = pause},
- start_or_recv_from = From} = State) ->
- {next_state, user_hello,
- State#state{start_or_recv_from = undefined}, [{postpone, true},{reply, From, {ok, Extensions}}]};
-wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello, #state{session = #session{session_id = ?EMPTY_ID},
- ssl_options = #{middlebox_comp_mode := false}} = State0) ->
- case tls_handshake_1_3:do_wait_sh(Hello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
- {State1, start, ServerHello} ->
- %% hello_retry_request: go to start
- {next_state, start, State1, [{next_event, internal, ServerHello}]};
- {State1, wait_ee} ->
- tls_gen_connection:next_event(wait_ee, no_record, State1)
- end;
-wait_sh(internal, #server_hello{} = Hello,
- #state{protocol_specific = PS, ssl_options = #{middlebox_comp_mode := true}} = State0) ->
- IsRetry = maps:get(hello_retry, PS, false),
- case tls_handshake_1_3:do_wait_sh(Hello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_sh, State0);
- {State1 = #state{}, start, ServerHello} ->
- %% hello_retry_request: go to start
- {next_state, start, State1, [{next_event, internal, ServerHello}]};
- {State1, wait_ee} when IsRetry == true ->
- tls_gen_connection:next_event(wait_ee, no_record, State1);
- {State1, wait_ee} when IsRetry == false ->
- tls_gen_connection:next_event(hello_middlebox_assert, no_record, State1)
- end;
-wait_sh(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_sh(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-hello_middlebox_assert(enter, _, State) ->
- {keep_state, State};
-hello_middlebox_assert(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(wait_ee, no_record, State);
-hello_middlebox_assert(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-hello_middlebox_assert(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-hello_retry_middlebox_assert(enter, _, State) ->
- {keep_state, State};
-hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) ->
- tls_gen_connection:next_event(wait_sh, no_record, State);
-hello_retry_middlebox_assert(internal, #server_hello{}, State) ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, [postpone]);
-hello_retry_middlebox_assert(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-hello_retry_middlebox_assert(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_ee(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_ee(internal, #encrypted_extensions{} = EE, State0) ->
- case tls_handshake_1_3:do_wait_ee(EE, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_ee, State0);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_ee(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_ee(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_cert_cr(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg,
- State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
- case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0) ->
- case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, wait_cert_cr, State0);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_cert_cr(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_cert_cr(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-wait_eoed(enter, _, State0) ->
- State = handle_middlebox(State0),
- {next_state, ?FUNCTION_NAME, State,[]};
-wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) ->
- handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
-wait_eoed(internal, #end_of_early_data{} = EOED, State0) ->
- case tls_handshake_1_3:do_wait_eoed(EOED, State0) of
- {#alert{} = Alert, State} ->
- ssl_gen_statem:handle_own_alert(Alert, wait_eoed, State);
- {State1, NextState} ->
- tls_gen_connection:next_event(NextState, no_record, State1)
- end;
-wait_eoed(info, Msg, State) ->
- tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
-wait_eoed(Type, Msg, State) ->
- ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
-
-connection(enter, _, State) ->
- {keep_state, State};
-connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
- handle_new_session_ticket(NewSessionTicket, State),
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
-
-connection(internal, #key_update{} = KeyUpdate, State0) ->
- case handle_key_update(KeyUpdate, State0) of
- {ok, State} ->
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
- {error, State, Alert} ->
- ssl_gen_statem:handle_own_alert(Alert, connection, State),
- tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State)
- end;
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
- ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
-connection({call, From}, negotiated_protocol,
- #state{handshake_env = #handshake_env{alpn = SelectedProtocol,
- negotiated_protocol = undefined}} = State) ->
- ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
- [{reply, From, {ok, SelectedProtocol}}]);
-connection(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-downgrade(enter, _, State) ->
- {keep_state, State};
-downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) ->
- _ = handle_new_session_ticket(NewSessionTicket, State),
- {next_state, ?FUNCTION_NAME, State};
-downgrade(Type, Event, State) ->
- ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
-
-%--------------------------------------------------------------------
-%% internal functions
-%%--------------------------------------------------------------------
-
-do_server_start(ClientHello, State0) ->
- case tls_handshake_1_3:do_start(ClientHello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, start, State0);
- {State, start} ->
- {next_state, start, State, []};
- {State, negotiated} ->
- {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.
-
-do_client_start(ServerHello, State0) ->
- case tls_handshake_1_3:do_start(ServerHello, State0) of
- #alert{} = Alert ->
- ssl_gen_statem:handle_own_alert(Alert, start, State0);
- {State, NextState} ->
- {next_state, NextState, State, []}
- end.
-
-initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User,
- {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
- put(log_level, maps:get(log_level, SSLOptions)),
- #{erl_dist := IsErlDist,
- %% Use highest supported version for client/server random nonce generation
- versions := [Version|_],
- client_renegotiation := ClientRenegotiation} = SSLOptions,
- MaxEarlyDataSize = init_max_early_data_size(Role),
- ConnectionStates = tls_record:init_connection_states(Role,
- Version,
- disabled,
- MaxEarlyDataSize),
- UserMonitor = erlang:monitor(process, User),
- InitStatEnv = #static_env{
- role = Role,
- transport_cb = CbModule,
- protocol_cb = tls_gen_connection,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- passive_tag = PassiveTag,
- host = Host,
- port = Port,
- socket = Socket,
- trackers = Trackers
- },
- #state{
- static_env = InitStatEnv,
- handshake_env = #handshake_env{
- tls_handshake_history = ssl_handshake:init_handshake_history(),
- renegotiation = {false, first},
- allow_renegotiate = ClientRenegotiation
- },
- connection_env = #connection_env{user_application = {UserMonitor, User}},
- socket_options = SocketOptions,
- ssl_options = SSLOptions,
- session = #session{is_resumable = false,
- session_id = ssl_session:legacy_session_id(SSLOptions)},
- connection_states = ConnectionStates,
- protocol_buffers = #protocol_buffers{},
- user_data_buffer = {[],0,[]},
- start_or_recv_from = undefined,
- flight_buffer = [],
- protocol_specific = #{sender => Sender,
- active_n => internal_active_n(IsErlDist),
- active_n_toggle => true
- }
- }.
-
-internal_active_n(true) ->
- %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N
- %% In most cases distribution connections are established all at
- %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for
- %% all connections. Which creates a wave of "passive" messages, leading
- %% to significant bump of memory & scheduler utilisation. Starting with
- %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the
- %% spike.
- erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1;
-internal_active_n(false) ->
- case application:get_env(ssl, internal_active_n) of
- {ok, N} when is_integer(N) ->
- N;
- _ ->
- ?INTERNAL_ACTIVE_N
- end.
-
-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 =:= manual ->
- #{security_parameters := SecParams} =
- ssl_record:current_connection_state(ConnectionStates, read),
- CipherSuite = SecParams#security_parameters.cipher_suite,
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- 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, {Cipher, 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),
- CipherSuite = SecParams#security_parameters.cipher_suite,
- #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
- 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, {Cipher, HKDF}, SNI, PSK).
-
-send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) ->
- Timestamp = erlang:system_time(millisecond),
- TicketData = #{cipher_suite => CipherSuite,
- sni => SNI,
- psk => PSK,
- timestamp => Timestamp,
- ticket => NewSessionTicket},
- User ! {ssl, session_ticket, TicketData}.
-
-handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
- %% Update read key in connection
- {ok, update_cipher_key(current_read, State0)};
-handle_key_update(#key_update{request_update = update_requested},
- #state{protocol_specific = #{sender := Sender}} = State0) ->
- %% Update read key in connection
- State1 = update_cipher_key(current_read, State0),
- %% Send key_update and update sender's write key
- case send_key_update(Sender, update_not_requested) of
- ok ->
- {ok, State1};
- {error, Reason} ->
- {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
- end.
-
-init_max_early_data_size(client) ->
- %% Disable trial decryption on the client side
- %% Servers do trial decryption of max_early_data bytes of plain text.
- %% Setting it to 0 means that a decryption error will result in an Alert.
- 0;
-init_max_early_data_size(server) ->
- ssl_config:get_max_early_data_size().
-
-handle_middlebox(#state{protocol_specific = PS} = State0) ->
- %% Always be prepared to ignore one change cipher spec
- %% for maximum interopablility, even if middlebox mode
- %% is not enabled.
- State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}.
-
-
-handle_change_cipher_spec(Type, Msg, StateName, #state{protocol_specific = PS0} = State) ->
- case maps:get(change_cipher_spec, PS0) of
- ignore ->
- PS = PS0#{change_cipher_spec => fail},
- tls_gen_connection:next_event(StateName, no_record,
- State#state{protocol_specific = PS});
- fail ->
- ssl_gen_statem:handle_common_event(Type, Msg, StateName, State)
- end.
diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl
index 6882e8af34..c2edbffe30 100644
--- a/lib/ssl/src/tls_dtls_connection.erl
+++ b/lib/ssl/src/tls_dtls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -63,6 +63,9 @@
downgrade/3,
gen_handshake/4]).
+%% Tracing
+-export([handle_trace/3]).
+
%%--------------------------------------------------------------------
-spec internal_renegotiation(pid(), ssl_record:connection_states()) ->
ok.
@@ -171,12 +174,18 @@ user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
#state{static_env = #static_env{role = Role},
handshake_env = HSEnv,
ssl_options = Options0} = State0) ->
- Options = ssl:handle_options(NewOptions, Role, Options0),
- State = ssl_gen_statem:ssl_config(Options, Role, State0),
- {next_state, hello, State#state{start_or_recv_from = From,
- handshake_env = HSEnv#handshake_env{continue_status = continue}
- },
- [{{timeout, handshake}, Timeout, close}]};
+ try ssl:update_options(NewOptions, Role, Options0) of
+ Options ->
+ State = ssl_gen_statem:ssl_config(Options, Role, State0),
+ {next_state, hello, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}
+ },
+ [{{timeout, handshake}, Timeout, close}]}
+ catch
+ throw:{error, Reason} ->
+ gen_statem:reply(From, {error, Reason}),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0)
+ end;
user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
user_hello(_, _, _) ->
@@ -252,9 +261,10 @@ abbreviated(internal,
handshake_env = HsEnv} = State) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
- ConnectionStates1,
- handshake_env = HsEnv#handshake_env{expecting_finished = true}});
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{connection_states =
+ ConnectionStates1,
+ handshake_env = HsEnv#handshake_env{expecting_finished = true}});
abbreviated(info, Msg, State) ->
handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(internal, #hello_request{}, _) ->
@@ -275,24 +285,28 @@ wait_ocsp_stapling(internal, #certificate{},
%% Receive OCSP staple message
wait_ocsp_stapling(internal, #certificate_status{} = CertStatus,
#state{static_env = #static_env{protocol_cb = _Connection},
- handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State) ->
- {next_state, certify, State#state{handshake_env = HsEnv#handshake_env{ocsp_stapling_state =
- OcspState#{ocsp_expect => stapled,
- ocsp_response => CertStatus}}}};
+ handshake_env =
+ #handshake_env{ocsp_stapling_state = OcspState} = HsEnv} = State) ->
+ {next_state, certify,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => stapled,
+ ocsp_response => CertStatus}}}};
%% Server did not send OCSP staple message
-wait_ocsp_stapling(internal, Msg, #state{static_env = #static_env{protocol_cb = _Connection},
- handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState} = HsEnv} = State)
+wait_ocsp_stapling(internal, Msg,
+ #state{static_env = #static_env{protocol_cb = _Connection},
+ handshake_env = #handshake_env{
+ ocsp_stapling_state = OcspState} = HsEnv} = State)
when is_record(Msg, server_key_exchange) orelse
is_record(Msg, hello_request) orelse
is_record(Msg, certificate_request) orelse
is_record(Msg, server_hello_done) orelse
is_record(Msg, client_key_exchange) ->
- {next_state, certify, State#state{handshake_env =
- HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}},
+ {next_state, certify,
+ State#state{handshake_env =
+ HsEnv#handshake_env{ocsp_stapling_state =
+ OcspState#{ocsp_expect => undetermined}}},
[{postpone, true}]};
-
wait_ocsp_stapling(internal, #hello_request{}, _) ->
keep_state_and_data;
wait_ocsp_stapling(Type, Event, State) ->
@@ -1633,8 +1647,9 @@ handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
throw(Alert)
end.
-make_premaster_secret({MajVer, MinVer}, rsa) ->
+make_premaster_secret(Version, rsa) ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
+ {MajVer,MinVer} = Version,
<<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
make_premaster_secret(_, _) ->
undefined.
@@ -1663,23 +1678,21 @@ handle_sni_extension(#state{static_env =
throw(Alert)
end.
-ensure_tls({254, _} = Version) ->
+ensure_tls(Version) when ?DTLS_1_X(Version) ->
dtls_v1:corresponding_tls_version(Version);
ensure_tls(Version) ->
Version.
-ocsp_info(#{ocsp_expect := stapled,
- ocsp_response := CertStatus} = OcspState,
- #{ocsp_responder_certs := OcspResponderCerts}, PeerCert) ->
+ocsp_info(#{ocsp_expect := stapled, ocsp_response := CertStatus} = OcspState,
+ #{ocsp_stapling := OcspStapling} = _SslOpts, PeerCert) ->
+ #{ocsp_responder_certs := OcspResponderCerts} = OcspStapling,
#{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]},
ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState
- };
+ ocsp_state => OcspState};
ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) ->
#{cert_ext => #{public_key:pkix_subject_id(PeerCert) => []},
ocsp_responder_certs => [],
- ocsp_state => OcspState
- }.
+ ocsp_state => OcspState}.
select_client_cert_key_pair(Session0,_,
[#{private_key := NoKey, certs := [[]] = NoCerts}],
@@ -1724,3 +1737,11 @@ default_cert_key_pair_return(undefined, Session) ->
Session;
default_cert_key_pair_return(Default, _) ->
Default.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(csp,
+ {call, {?MODULE, wait_ocsp_stapling, [Type, Msg | _]}}, Stack) ->
+ {io_lib:format("Type = ~w Msg = ~W", [Type, Msg, 10]), Stack}.
diff --git a/lib/ssl/src/tls_dyn_connection_sup.erl b/lib/ssl/src/tls_dyn_connection_sup.erl
index cb0576cbd6..5905889225 100644
--- a/lib/ssl/src/tls_dyn_connection_sup.erl
+++ b/lib/ssl/src/tls_dyn_connection_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. 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.
@@ -74,5 +74,10 @@ receiver(Args) ->
type => worker,
significant => true,
start => {ssl_gen_statem, start_link, Args},
- modules => [ssl_gen_statem, tls_connection, tls_connection_1_3]
+ modules => [ssl_gen_statem,
+ tls_connection,
+ tls_gen_connection,
+ tls_client_connection_1_3,
+ tls_server_connection_1_3,
+ tls_gen_connection_1_3]
}.
diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
index 940666f104..76e7bc334e 100644
--- a/lib/ssl/src/tls_gen_connection.erl
+++ b/lib/ssl/src/tls_gen_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. 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.
@@ -33,6 +33,7 @@
-include("ssl_alert.hrl").
-include("ssl_api.hrl").
-include("ssl_internal.hrl").
+-include("tls_record_1_3.hrl").
%% Setup
-export([start_fsm/8,
@@ -77,9 +78,11 @@
%% Setup
%%====================================================================
start_fsm(Role, Host, Port, Socket,
- {#{erl_dist := ErlDist, sender_spawn_opts := SenderSpawnOpts}, _, Trackers} = Opts,
+ {SSLOpts, _, Trackers} = Opts,
User, {CbModule, _, _, _, _} = CbInfo,
Timeout) ->
+ ErlDist = maps:get(erl_dist, SSLOpts, false),
+ SenderSpawnOpts = maps:get(sender_spawn_opts, SSLOpts, []),
SenderOptions = handle_sender_options(ErlDist, SenderSpawnOpts),
Starter = start_connection_tree(User, ErlDist, SenderOptions,
Role, [Host, Port, Socket, Opts, User, CbInfo]),
@@ -149,16 +152,16 @@ initialize_tls_sender(#state{static_env = #static_env{
socket_options = SockOpts,
ssl_options = #{renegotiate_at := RenegotiateAt,
key_update_at := KeyUpdateAt,
- erl_dist := ErlDist,
- log_level := LogLevel,
- hibernate_after := HibernateAfter},
+ log_level := LogLevel
+ } = SSLOpts,
connection_states = #{current_write := ConnectionWriteState},
protocol_specific = #{sender := Sender}}) ->
+ HibernateAfter = maps:get(hibernate_after, SSLOpts, infinity),
Init = #{current_write => ConnectionWriteState,
role => Role,
socket => Socket,
socket_options => SockOpts,
- erl_dist => ErlDist,
+ erl_dist => maps:get(erl_dist, SSLOpts, false),
trackers => Trackers,
transport_cb => Transport,
negotiated_version => Version,
@@ -354,6 +357,11 @@ handle_info({CloseTag, Socket}, StateName,
%% is called after all data has been deliver.
{next_state, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}, []}
end;
+handle_info({ssl_tls, Port, Type, {Major, Minor}, Data}, StateName,
+ #state{static_env = #static_env{data_tag = Protocol},
+ ssl_options = #{ktls := true}} = State0) ->
+ Len = byte_size(Data),
+ handle_info({Protocol, Port, <<Type, Major, Minor, Len:16, Data/binary>>}, StateName, State0);
handle_info(Msg, StateName, State) ->
ssl_gen_statem:handle_info(Msg, StateName, State).
@@ -631,7 +639,7 @@ next_tls_record(Data, StateName,
%% This does not allow SSL-3.0 connections, that we do not support
%% or interfere with TLS-1.3 extensions to handle version negotiation.
AllHelloVersions = [ 'sslv3' | ?ALL_AVAILABLE_VERSIONS],
- [tls_record:protocol_version(Vsn) || Vsn <- AllHelloVersions];
+ [tls_record:protocol_version_name(Vsn) || Vsn <- AllHelloVersions];
_ ->
State0#state.connection_env#connection_env.negotiated_version
end,
@@ -673,6 +681,8 @@ next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []
next_record(_, State) ->
{no_record, State}.
+flow_ctrl(#state{ssl_options = #{ktls := true}} = State) ->
+ {no_record, State};
%%% bytes_to_read equals the integer Length arg of ssl:recv
%%% the actual value is only relevant for packet = raw | 0
%%% bytes_to_read = undefined means no recv call is ongoing
@@ -732,7 +742,7 @@ activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n :
next_record(State, CipherTexts, ConnectionStates, Check) ->
next_record(State, CipherTexts, ConnectionStates, Check, [], false).
%%
-next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State,
+next_record(#state{connection_env = #connection_env{negotiated_version = ?TLS_1_3 = Version}} = State,
[CT|CipherTexts], ConnectionStates0, Check, Acc, IsEarlyData) ->
case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
{Record = #ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} ->
@@ -822,7 +832,7 @@ next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, Connec
%% field in TLS-1.3 client hello). The versions are instead negotiated with an hello extension. When
%% decoding the server_hello messages we want to go through TLS-1.3 decode functions to be able
%% to handle TLS-1.3 extensions if TLS-1.3 will be the negotiated version.
-handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, hello) ->
+handle_unnegotiated_version(?LEGACY_VERSION , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, Buffer, client, hello) ->
%% The effective version for decoding the server hello message should be the TLS-1.3. Possible coalesced TLS-1.2
%% server handshake messages should be decoded with the negotiated version in later state.
<<_:8, ?UINT24(Length), _/binary>> = Data,
@@ -830,7 +840,7 @@ handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Option
{HSPacket, <<>> = NewHsBuffer} = tls_handshake:get_tls_handshakes(Version, FirstPacket, Buffer, Options),
{HSPacket, NewHsBuffer, RecordRest};
%% TLS-1.3 RetryRequest
-handle_unnegotiated_version({3,3} , #{versions := [{3,4} = Version |_]} = Options, Data, Buffer, client, wait_sh) ->
+handle_unnegotiated_version(?TLS_1_2 , #{versions := [?TLS_1_3 = Version |_]} = Options, Data, Buffer, client, wait_sh) ->
tls_handshake:get_tls_handshakes(Version, Data, Buffer, Options);
%% When the `negotiated_version` variable is not yet set use the highest supported version.
handle_unnegotiated_version(undefined, #{versions := [Version|_]} = Options, Data, Buff, _, _) ->
diff --git a/lib/ssl/src/tls_gen_connection_1_3.erl b/lib/ssl/src/tls_gen_connection_1_3.erl
new file mode 100644
index 0000000000..eec4aa324e
--- /dev/null
+++ b/lib/ssl/src/tls_gen_connection_1_3.erl
@@ -0,0 +1,382 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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_gen_connection_1_3).
+
+-include("ssl_alert.hrl").
+-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+%% Internal API
+
+
+% gen_statem state help functions
+-export([initial_state/8,
+ user_hello/3,
+ wait_cert/3,
+ wait_cv/3,
+ connection/3,
+ downgrade/3
+ ]).
+
+-export([maybe_queue_change_cipher_spec/2,
+ maybe_prepend_change_cipher_spec/2,
+ maybe_append_change_cipher_spec/2,
+ handle_change_cipher_spec/4,
+ handle_middlebox/1,
+ handle_resumption/2,
+ send_key_update/2,
+ update_cipher_key/2,
+ do_maybe/0]).
+
+%%--------------------------------------------------------------------
+%% Internal API
+%%--------------------------------------------------------------------
+initial_state(Role, Sender, Host, Port, Socket,
+ {SSLOptions, SocketOptions, Trackers}, User,
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
+ %% Use highest supported version for client/server random nonce generation
+ #{versions := [Version|_]} = SSLOptions,
+ MaxEarlyDataSize = init_max_early_data_size(Role),
+ ConnectionStates = tls_record:init_connection_states(Role,
+ Version,
+ disabled,
+ MaxEarlyDataSize),
+ UserMonitor = erlang:monitor(process, User),
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = tls_gen_connection,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ passive_tag = PassiveTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ trackers = Trackers
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first}
+ },
+ connection_env = #connection_env{user_application = {UserMonitor, User}},
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = false,
+ session_id =
+ ssl_session:legacy_session_id(SSLOptions)},
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_data_buffer = {[],0,[]},
+ start_or_recv_from = undefined,
+ flight_buffer = [],
+ protocol_specific = #{sender => Sender,
+ active_n => internal_active_n(SSLOptions, Socket),
+ active_n_toggle => true
+ }
+ }.
+
+user_hello(info, {'DOWN', _, _, _, _} = Event, State) ->
+ ssl_gen_statem:handle_info(Event, ?FUNCTION_NAME, State);
+user_hello(_, _, _) ->
+ {keep_state_and_data, [postpone]}.
+
+wait_cert(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cert(internal = Type, #change_cipher_spec{} = Msg,
+ #state{session = #session{session_id = Id}} = State)
+ when Id =/= ?EMPTY_ID ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_cert(internal,
+ #certificate_1_3{} = Certificate, State0) ->
+ case do_wait_cert(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cert, State);
+ {State, NextState} ->
+ tls_gen_connection:next_event(NextState, no_record, State)
+ end;
+wait_cert(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cert(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+wait_cv(enter, _, State0) ->
+ State = handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_cv(internal = Type, #change_cipher_spec{} = Msg,
+ #state{session = #session{session_id = Id}} = State)
+ when Id =/= ?EMPTY_ID ->
+ handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_cv(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_cv(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+connection(enter, _, State) ->
+ {keep_state, State};
+connection(internal, #new_session_ticket{} = NewSessionTicket, State) ->
+ handle_new_session_ticket(NewSessionTicket, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+
+connection(internal, #key_update{} = KeyUpdate, State0) ->
+ case handle_key_update(KeyUpdate, State0) of
+ {ok, State} ->
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State);
+ {error, State, Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, connection, State),
+ tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State)
+ end;
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env = #handshake_env{alpn = undefined}} = State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]);
+connection({call, From}, negotiated_protocol,
+ #state{handshake_env =
+ #handshake_env{alpn = SelectedProtocol,
+ negotiated_protocol = undefined}} =
+ State) ->
+ ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State,
+ [{reply, From, {ok, SelectedProtocol}}]);
+connection(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+downgrade(enter, _, State) ->
+ {keep_state, State};
+downgrade(internal, #new_session_ticket{} = NewSessionTicket, State) ->
+ _ = handle_new_session_ticket(NewSessionTicket, State),
+ {next_state, ?FUNCTION_NAME, State};
+downgrade(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%% Description: Enqueues a change_cipher_spec record as the first/last
+%% message of the current flight buffer
+maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0,
+ first) ->
+ {State, FlightBuffer} =
+ maybe_prepend_change_cipher_spec(State0, FlightBuffer0),
+ State#state{flight_buffer = FlightBuffer};
+maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0,
+ last) ->
+ {State, FlightBuffer} = maybe_append_change_cipher_spec(State0,
+ FlightBuffer0),
+ State#state{flight_buffer = FlightBuffer}.
+
+handle_change_cipher_spec(Type, Msg, StateName,
+ #state{protocol_specific = PS0} = State) ->
+ case maps:get(change_cipher_spec, PS0) of
+ ignore ->
+ PS = PS0#{change_cipher_spec => fail},
+ tls_gen_connection:next_event(StateName, no_record,
+ State#state{protocol_specific = PS});
+ fail ->
+ ssl_gen_statem:handle_common_event(Type, Msg, StateName, State)
+ end.
+
+handle_middlebox(#state{protocol_specific = PS} = State0) ->
+ %% Always be prepared to ignore one change cipher spec
+ %% for maximum interopablility, even if middlebox mode
+ %% is not enabled.
+ State0#state{protocol_specific = PS#{change_cipher_spec => ignore}}.
+
+handle_resumption(State, undefined) ->
+ State;
+handle_resumption(#state{handshake_env = HSEnv0} = State, _) ->
+ HSEnv = HSEnv0#handshake_env{resumption = true},
+ State#state{handshake_env = HSEnv}.
+
+do_maybe() ->
+ Ref = erlang:make_ref(),
+ Ok = fun(ok) -> ok;
+ ({ok,R}) -> R;
+ ({error,Reason}) ->
+ throw({Ref,Reason})
+ end,
+ {Ref,Ok}.
+
+%% Take care of including a change_cipher_spec message in the
+%% correct place if middlebox mod is used. From RFC: 8446 "D.4.
+%% Middlebox Compatibility Mode If not offering early data, the
+%% client sends a dummy change_cipher_spec record (see the third
+%% paragraph of Section 5) immediately before its second flight.
+%% This may either be before its second ClientHello or before its
+%% encrypted handshake flight. If offering early data, the
+%% record is placed immediately after the first ClientHello."
+maybe_prepend_change_cipher_spec(#state{
+ session = #session{session_id = Id},
+ handshake_env =
+ #handshake_env{
+ change_cipher_spec_sent = false}
+ = HSEnv}
+ = State, Bin) when Id =/= ?EMPTY_ID ->
+ CCSBin = tls_handshake_1_3:create_change_cipher_spec(State),
+ {State#state{handshake_env =
+ HSEnv#handshake_env{change_cipher_spec_sent = true}},
+ [CCSBin|Bin]};
+maybe_prepend_change_cipher_spec(State, Bin) ->
+ {State, Bin}.
+
+maybe_append_change_cipher_spec(#state{
+ session = #session{session_id = Id},
+ handshake_env =
+ #handshake_env{
+ change_cipher_spec_sent = false}
+ = HSEnv}
+ = State, Bin) when Id =/= ?EMPTY_ID ->
+ CCSBin = tls_handshake_1_3:create_change_cipher_spec(State),
+ {State#state{handshake_env =
+ HSEnv#handshake_env{change_cipher_spec_sent = true}},
+ Bin ++ [CCSBin]};
+maybe_append_change_cipher_spec(State, Bin) ->
+ {State, Bin}.
+
+send_key_update(Sender, Type) ->
+ KeyUpdate = tls_handshake_1_3:key_update(Type),
+ tls_sender:send_post_handshake(Sender, KeyUpdate).
+
+update_cipher_key(ConnStateName, #state{connection_states = CS0} = State0) ->
+ CS = update_cipher_key(ConnStateName, CS0),
+ State0#state{connection_states = CS};
+update_cipher_key(ConnStateName, CS0) ->
+ #{security_parameters := SecParams0,
+ cipher_state := CipherState0} = ConnState0 = maps:get(ConnStateName, CS0),
+ HKDF = SecParams0#security_parameters.prf_algorithm,
+ CipherSuite = SecParams0#security_parameters.cipher_suite,
+ ApplicationTrafficSecret0 =
+ SecParams0#security_parameters.application_traffic_secret,
+ ApplicationTrafficSecret =
+ tls_v1:update_traffic_secret(HKDF,
+ ApplicationTrafficSecret0),
+
+ %% Calculate traffic keys
+ KeyLength = tls_v1:key_length(CipherSuite),
+ {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, KeyLength,
+ ApplicationTrafficSecret),
+
+ SecParams = SecParams0#security_parameters{application_traffic_secret = ApplicationTrafficSecret},
+ CipherState = CipherState0#cipher_state{key = Key, iv = IV},
+ ConnState = ConnState0#{security_parameters => SecParams,
+ cipher_state => CipherState,
+ sequence_number => 0},
+ CS0#{ConnStateName => ConnState}.
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+
+do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:process_certificate(Certificate, State0))
+ catch
+ {Ref, #alert{} = Alert} ->
+ {Alert, State0};
+ {Ref, {#alert{} = Alert, State}} ->
+ {Alert, State}
+ end.
+
+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} = SslOpts,
+ connection_env = #connection_env{user_application = {_, User}}})
+ when SessionTickets =:= manual ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ CipherSuite = SecParams#security_parameters.cipher_suite,
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ SNI = maps:get(server_name_indication, SslOpts, undefined),
+ send_ticket_data(User, NewSessionTicket, {Cipher, HKDF}, SNI, PSK);
+handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce}
+ = NewSessionTicket,
+ #state{connection_states = ConnectionStates,
+ ssl_options = #{session_tickets := SessionTickets} = SslOpts})
+ when SessionTickets =:= auto ->
+ #{security_parameters := SecParams} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ CipherSuite = SecParams#security_parameters.cipher_suite,
+ #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite),
+ HKDF = SecParams#security_parameters.prf_algorithm,
+ RMS = SecParams#security_parameters.resumption_master_secret,
+ PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF),
+ SNI = maps:get(server_name_indication, SslOpts, undefined),
+ tls_client_ticket_store:store_ticket(NewSessionTicket, {Cipher, HKDF}, SNI, PSK).
+
+send_ticket_data(User, NewSessionTicket, CipherSuite, SNI, PSK) ->
+ Timestamp = erlang:system_time(millisecond),
+ TicketData = #{cipher_suite => CipherSuite,
+ sni => SNI,
+ psk => PSK,
+ timestamp => Timestamp,
+ ticket => NewSessionTicket},
+ User ! {ssl, session_ticket, TicketData}.
+
+handle_key_update(#key_update{request_update = update_not_requested}, State0) ->
+ %% Update read key in connection
+ {ok, update_cipher_key(current_read, State0)};
+handle_key_update(#key_update{request_update = update_requested},
+ #state{protocol_specific = #{sender := Sender}} = State0) ->
+ %% Update read key in connection
+ State1 = update_cipher_key(current_read, State0),
+ %% Send key_update and update sender's write key
+ case send_key_update(Sender, update_not_requested) of
+ ok ->
+ {ok, State1};
+ {error, Reason} ->
+ {error, State1, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason)}
+ end.
+
+init_max_early_data_size(client) ->
+ %% Disable trial decryption on the client side
+ %% Servers do trial decryption of max_early_data bytes of plain text.
+ %% Setting it to 0 means that a decryption error will result in an Alert.
+ 0;
+init_max_early_data_size(server) ->
+ ssl_config:get_max_early_data_size().
+
+internal_active_n(#{ktls := true}, Socket) ->
+ inet:setopts(Socket, [{packet, ssl_tls}]),
+ 1;
+internal_active_n(#{erl_dist := true}, _) ->
+ %% Start with a random number between 1 and ?INTERNAL_ACTIVE_N
+ %% In most cases distribution connections are established all at
+ %% the same time, and flow control engages with ?INTERNAL_ACTIVE_N for
+ %% all connections. Which creates a wave of "passive" messages, leading
+ %% to significant bump of memory & scheduler utilisation. Starting with
+ %% a random number between 1 and ?INTERNAL_ACTIVE_N helps to spread the
+ %% spike.
+ erlang:system_time() rem ?INTERNAL_ACTIVE_N + 1;
+internal_active_n(_,_) ->
+ case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end.
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 7704ff7b6b..ec53b65959 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -45,7 +45,7 @@
-export([get_tls_handshakes/4, decode_handshake/3]).
%% Handshake helper
--export([ocsp_nonce/2]).
+-export([ocsp_nonce/1]).
-type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake().
@@ -74,15 +74,15 @@ client_hello(_Host, _Port, ConnectionStates,
%% legacy_version field MUST be set to 0x0303, which is the version
%% number for TLS 1.2.
LegacyVersion =
- case tls_record:is_higher(Version, {3,2}) of
+ case tls_record:is_higher(Version, ?TLS_1_1) of
true ->
- {3,3};
+ ?TLS_1_2;
false ->
Version
end,
- #{security_parameters := SecParams} =
+ #{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates, read),
- AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
Extensions = ssl_handshake:client_hello_extensions(Version,
AvailableCipherSuites,
SslOpts,
@@ -157,24 +157,26 @@ hello(#server_hello{server_version = LegacyVersion,
cipher_suite = CipherSuite,
compression_method = Compression,
session_id = SessionId,
- extensions = #{server_hello_selected_version :=
- #server_hello_selected_version{selected_version = Version}} = HelloExt},
- #{versions := SupportedVersions,
- ocsp_stapling := Stapling} = SslOpt,
+ extensions =
+ #{server_hello_selected_version :=
+ #server_hello_selected_version{
+ selected_version = Version}} = HelloExt},
+ #{versions := SupportedVersions} = SslOpt,
ConnectionStates0, Renegotiation, OldId) ->
+ Stapling = maps:get(ocsp_stapling, SslOpt, ?DEFAULT_OCSP_STAPLING),
%% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension
%% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version
%% number for TLS 1.2.
%% The "supported_versions" extension is supported from TLS 1.2.
- case LegacyVersion > {3,3} orelse
- LegacyVersion =:= {3,3} andalso Version < {3,3} of
+ case ?TLS_LT(LegacyVersion, ?TLS_1_2) orelse
+ LegacyVersion =:= ?TLS_1_2 andalso ?TLS_LT(Version, ?TLS_1_2) of
true ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER));
false ->
case tls_record:is_acceptable_version(Version, SupportedVersions) of
true ->
case Version of
- {3,3} ->
+ ?TLS_1_2 ->
IsNew = ssl_session:is_new(OldId, SessionId),
%% TLS 1.2 ServerHello with "supported_versions" (special case)
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
@@ -182,8 +184,9 @@ hello(#server_hello{server_version = LegacyVersion,
ConnectionStates0, Renegotiation, IsNew);
SelectedVersion ->
%% TLS 1.3
- {next_state, wait_sh, SelectedVersion, #{ocsp_stapling => Stapling,
- ocsp_expect => ocsp_expect(Stapling)}}
+ {next_state, wait_sh, SelectedVersion,
+ #{ocsp_stapling => Stapling,
+ ocsp_expect => ocsp_expect(Stapling)}}
end;
false ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER))
@@ -305,14 +308,17 @@ get_tls_handshakes(Version, Data, Buffer, Options) ->
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec ocsp_nonce(boolean(), boolean()) -> binary() | undefined.
+-spec ocsp_nonce(map()) -> binary() | undefined.
%%
%% Description: Get an OCSP nonce
%%--------------------------------------------------------------------
-ocsp_nonce(true, true) ->
- public_key:der_encode('Nonce', crypto:strong_rand_bytes(8));
-ocsp_nonce(_OcspNonceOpt, _OcspStaplingOpt) ->
- undefined.
+ocsp_nonce(SslOpts) ->
+ case maps:get(ocsp_stapling, SslOpts, disabled) of
+ #{ocsp_nonce := true} ->
+ public_key:der_encode('Nonce', crypto:strong_rand_bytes(8));
+ _ ->
+ undefined
+ end.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -336,7 +342,7 @@ handle_client_hello(Version,
ClientSignatureSchemes = get_signature_ext(signature_algs_cert, HelloExt, Version),
AvailableHashSigns = ssl_handshake:available_signature_algs(
ClientHashSigns, SupportedHashSigns, Version),
- ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, Version, ECCOrder),
+ ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder),
{Type, #session{cipher_suite = CipherSuite,
own_certificates = [OwnCert |_]} = Session1}
= ssl_handshake:select_session(SugesstedId, CipherSuites,
@@ -403,9 +409,9 @@ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) -
end.
%%--------------------------------------------------------------------
-enc_handshake(#hello_request{}, {3, N}) when N < 4 ->
+enc_handshake(#hello_request{}, Version) when ?TLS_LT(Version, ?TLS_1_3)->
{?HELLO_REQUEST, <<>>};
-enc_handshake(#client_hello{client_version = {Major, Minor} = Version,
+enc_handshake(#client_hello{client_version = ServerVersion,
random = Random,
session_id = SessionID,
cipher_suites = CipherSuites,
@@ -416,35 +422,37 @@ enc_handshake(#client_hello{client_version = {Major, Minor} = Version,
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
- ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, Version),
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
+ {Major,Minor} = ServerVersion,
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
?UINT16(CsLength), BinCipherSuites/binary,
?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
-enc_handshake(HandshakeMsg, {3, 4}) ->
+enc_handshake(HandshakeMsg, ?TLS_1_3) ->
tls_handshake_1_3:encode_handshake(HandshakeMsg);
enc_handshake(HandshakeMsg, Version) ->
ssl_handshake:encode_handshake(HandshakeMsg, Version).
%%--------------------------------------------------------------------
get_tls_handshakes_aux(Version, <<?BYTE(Type), ?UINT24(Length),
- Body:Length/binary,Rest/binary>>,
+ Body:Length/binary,Rest/binary>>,
#{log_level := LogLevel} = Opts, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
try decode_handshake(Version, Type, Body) of
- Handshake ->
+ Handshake ->
ssl_logger:debug(LogLevel, inbound, 'handshake', Handshake),
- get_tls_handshakes_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
+ get_tls_handshakes_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc])
catch
- error:Reason:ST ->
+ error:Reason:ST ->
?SSL_LOG(info, handshake_error, [{reason,Reason}, {stacktrace, ST}]),
- throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, handshake_decode_error))
+ throw(?ALERT_REC(?FATAL, ?DECODE_ERROR, handshake_decode_error))
end;
get_tls_handshakes_aux(_Version, Data, _, Acc) ->
{lists:reverse(Acc), Data}.
-decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
+decode_handshake(Version, ?HELLO_REQUEST, <<>>)
+ when ?TLS_LT(Version, ?TLS_1_3) ->
#hello_request{};
decode_handshake(Version, ?CLIENT_HELLO,
<<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
@@ -463,7 +471,7 @@ decode_handshake(Version, ?CLIENT_HELLO,
compression_methods = erlang:binary_to_list(Comp_methods),
extensions = DecodedExtensions
};
-decode_handshake({3, 4}, Tag, Msg) ->
+decode_handshake(?TLS_1_3, Tag, Msg) ->
tls_handshake_1_3:decode_handshake(Tag, Msg);
decode_handshake(Version, Tag, Msg) ->
ssl_handshake:decode_handshake(Version, Tag, Msg).
@@ -474,7 +482,7 @@ ocsp_expect(true) ->
ocsp_expect(_) ->
no_staple.
-get_signature_ext(Ext, HelloExt, {3,3}) ->
+get_signature_ext(Ext, HelloExt, ?TLS_1_2) ->
case maps:get(Ext, HelloExt, undefined) of
%% Signature algorithms was not sent
undefined ->
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 750a79887f..0861db4607 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -38,31 +38,58 @@
-export([encode_handshake/1, decode_handshake/2]).
%% Create handshake messages
--export([certificate/5,
- certificate_verify/4,
- encrypted_extensions/1,
- key_update/1]).
-
--export([do_start/2,
- do_negotiated/2,
- do_wait_cert/2,
- do_wait_cv/2,
- do_wait_finished/2,
- do_wait_sh/2,
- do_wait_ee/2,
- do_wait_cert_cr/2,
- do_wait_eoed/2,
- early_data_size/1,
- get_ticket_data/3,
+-export([server_hello/5,
+ maybe_add_cookie_extension/2,
maybe_add_binders/3,
maybe_add_binders/4,
maybe_add_early_data_indication/3,
- maybe_automatic_session_resumption/1,
- maybe_send_early_data/1]).
+ supported_groups_from_extensions/1,
+ maybe_hello_retry_request/2,
+ certificate/5,
+ certificate_verify/4,
+ certificate_request/5,
+ encrypted_extensions/1,
+ key_update/1,
+ finished/1,
+ create_change_cipher_spec/1
+ ]).
+
+%% Handle handshake messages
+-export([process_certificate_request/2,
+ process_certificate/2,
+ calculate_handshake_secrets/5,
+ verify_certificate_verify/2,
+ validate_finished/2,
+ maybe_calculate_resumption_master_secret/1,
+ replace_ch1_with_message_hash/1,
+ select_common_groups/2,
+ verify_signature_algorithm/2,
+ forget_master_secret/1,
+ set_client_random/2,
+ handle_pre_shared_key/3,
+ update_start_state/2,
+ get_signature_scheme_list/1,
+ get_certificate_authorities/1,
+ get_certificate_params/1,
+ select_sign_algo/5
+ ]).
+
+-export([early_data_size/1,
+ get_pre_shared_key/2,
+ get_pre_shared_key/4,
+ get_pre_shared_key_early_data/2,
+ get_supported_groups/1,
+ calculate_traffic_secrets/1,
+ calculate_client_early_traffic_secret/5,
+ calculate_client_early_traffic_secret/2,
+ encode_early_data/2,
+ get_ticket_data/3,
+ ciphers_for_early_data/1,
+ choose_ticket/2]).
-export([get_max_early_data/1,
is_valid_binder/4,
- maybe/0,
+ check_cert_sign_algo/4,
path_validation/10]).
%% crypto:hash(sha256, "HelloRetryRequest").
@@ -79,7 +106,7 @@ server_hello(MsgType, SessionId, KeyShare, PSK, ConnectionStates) ->
#{security_parameters := SecParams} =
ssl_record:pending_connection_state(ConnectionStates, read),
Extensions = server_hello_extensions(MsgType, KeyShare, PSK),
- #server_hello{server_version = {3,3}, %% legacy_version
+ #server_hello{server_version = ?LEGACY_VERSION, %% legacy_version
cipher_suite = SecParams#security_parameters.cipher_suite,
compression_method = 0, %% legacy attribute
random = server_hello_random(MsgType, SecParams),
@@ -96,19 +123,20 @@ server_hello(MsgType, SessionId, KeyShare, PSK, ConnectionStates) ->
%% ClientHello, with the exception of optionally the "cookie" (see
%% Section 4.2.2) extension.
server_hello_extensions(hello_retry_request = MsgType, KeyShare, _) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
- Extensions = #{server_hello_selected_version => SupportedVersions},
+ Extensions = server_hello_extensions_versions(),
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
server_hello_extensions(MsgType, KeyShare, undefined) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
- Extensions = #{server_hello_selected_version => SupportedVersions},
+ Extensions = server_hello_extensions_versions(),
ssl_handshake:add_server_share(MsgType, Extensions, KeyShare);
server_hello_extensions(MsgType, KeyShare, {SelectedIdentity, _}) ->
- SupportedVersions = #server_hello_selected_version{selected_version = {3,4}},
+ Extensions = server_hello_extensions_versions(),
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).
+ ssl_handshake:add_server_share(MsgType, Extensions#{pre_shared_key => PreSharedKey}, KeyShare).
+
+server_hello_extensions_versions() ->
+ SupportedVersions = #server_hello_selected_version{selected_version = ?TLS_1_3},
+ #{server_hello_selected_version => SupportedVersions}.
+
server_hello_random(server_hello, #security_parameters{server_random = Random}) ->
@@ -150,29 +178,9 @@ maybe_add_cookie_extension(undefined, ClientHello) ->
ClientHello;
maybe_add_cookie_extension(Cookie,
#client_hello{extensions = Extensions0} = ClientHello) ->
- Extensions = Extensions0#{cookie => #cookie{cookie = Cookie}},
+ Extensions = Extensions0#{cookie => Cookie},
ClientHello#client_hello{extensions = Extensions}.
-validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) ->
- ok;
-validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) ->
- ok;
-validate_cookie(Cookie0, #state{ssl_options = #{cookie := true},
- handshake_env =
- #handshake_env{
- tls_handshake_history =
- {[_CH2,_HRR,MessageHash|_], _},
- cookie_iv_shard = {IV, Shard}}}) ->
- Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV),
- case Cookie =:= iolist_to_binary(MessageHash) of
- true ->
- ok;
- false ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
- end;
-validate_cookie(_,_) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}.
-
encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
E0 = #{},
E1 = case HandshakeEnv#handshake_env.alpn of
@@ -203,7 +211,6 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
extensions = E
}.
-
certificate_request(SignAlgs0, SignAlgsCert0, CertDbHandle, CertDbRef, CertAuthBool) ->
%% Input arguments contain TLS 1.2 algorithms due to backward compatibility
%% reasons. These {Hash, Algo} tuples must be filtered before creating the
@@ -237,14 +244,14 @@ add_signature_algorithms_cert(Extensions, SignAlgsCert) ->
filter_tls13_algs(undefined) -> undefined;
filter_tls13_algs(Algo) ->
- lists:foldl(fun(Atom, Acc) when is_atom(Atom) ->
+ lists:foldl(fun(Atom, Acc) when is_atom(Atom) ->
[Atom | Acc];
({sha512, rsa}, Acc) ->
[rsa_pkcs1_sha512 | Acc];
({sha384, rsa}, Acc) ->
[rsa_pkcs1_sha384 | Acc];
({sha256, rsa}, Acc) ->
- [rsa_pkcs1_sha256 | Acc];
+ [rsa_pkcs1_sha256 | Acc];
({sha, rsa}, Acc) ->
[rsa_pkcs1_sha1 | Acc];
({sha, ecdsa}, Acc) ->
@@ -337,6 +344,22 @@ certificate_verify(PrivateKey, SignatureScheme,
{error, Alert}
end.
+%% For reasons of backward compatibility with middleboxes (see
+%% Appendix D.4), the HelloRetryRequest message uses the same structure
+%% as the ServerHello, but with Random set to the special value of the
+%% SHA-256 of "HelloRetryRequest":
+%%
+%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
+%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
+%%
+%% Upon receiving a message with type server_hello, implementations MUST
+%% first examine the Random value and, if it matches this value, process
+%% it as described in Section 4.1.4).
+maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello,
+ #state{protocol_specific = PS} = State0) ->
+ {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}};
+maybe_hello_retry_request(_, _) ->
+ ok.
finished(#state{connection_states = ConnectionStates,
handshake_env =
@@ -357,19 +380,167 @@ finished(#state{connection_states = ConnectionStates,
key_update(Type) ->
#key_update{request_update = Type}.
+create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
+ %% Dummy connection_states with NULL cipher
+ ConnectionStates =
+ #{current_write =>
+ #{compression_state => undefined,
+ cipher_state => undefined,
+ sequence_number => 1,
+ security_parameters =>
+ #security_parameters{
+ bulk_cipher_algorithm = 0,
+ compression_algorithm = ?NULL,
+ mac_algorithm = ?NULL
+ },
+ mac_secret => undefined}},
+ {BinChangeCipher, _} =
+ tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates),
+ ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
+ [BinChangeCipher].
+
+%%====================================================================
+%% Handle handshake messages
+%%====================================================================
+
+process_certificate_request(#certificate_request_1_3{
+ extensions = Extensions},
+ #state{ssl_options = #{signature_algs := ClientSignAlgs},
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts,
+ negotiated_version = Version},
+ static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef},
+ session = Session0} =
+ State) ->
+ ServerSignAlgs = get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ServerSignAlgsCert = get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+ CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
+
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version),
+ Session = select_client_cert_key_pair(Session0, CertKeyPairs,
+ ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs,
+ CertDbHandle, CertDbRef, CertAuths, undefined),
+ {ok, {State#state{client_certificate_status = requested, session = Session}, wait_cert}}.
+
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #{fail_if_no_peer_cert := false}} = State) ->
+ {ok, {State, wait_finished}};
+process_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #{fail_if_no_peer_cert := true}} = State0) ->
+ %% At this point the client believes that the connection is up and starts using
+ %% its traffic secrets. In order to be able send an proper Alert to the client
+ %% the server should also change its connection state and use the traffic
+ %% secrets.
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}};
+process_certificate(#certificate_1_3{certificate_list = CertEntries},
+ #state{ssl_options = SslOptions,
+ static_env =
+ #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbHandle},
+ handshake_env =
+ #handshake_env{
+ ocsp_stapling_state = OcspState}} = State0) ->
+ case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
+ SslOptions, CRLDbHandle, Role, Host, OcspState) of
+ #alert{} = Alert ->
+ State = update_encryption_state(Role, State0),
+ {error, {Alert, State}};
+ {PeerCert, PublicKeyInfo} ->
+ State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
+ {ok, {State, wait_cv}}
+ end.
+
+verify_certificate_verify(#state{static_env = #static_env{role = Role},
+ connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ public_key_info = PublicKeyInfo,
+ tls_handshake_history = HHistory}} = State0,
+ #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature}) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ {HashAlgo, SignAlg, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ Messages = get_handshake_context_cv(HHistory),
+
+ Context = lists:reverse(Messages),
+
+ %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+
+ ContextString = peer_context_string(Role),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of
+ {ok, true} ->
+ {ok, {State0, wait_finished}};
+ {ok, false} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
+ "Failed to verify CertificateVerify"), State}};
+ {error, #alert{} = Alert} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {Alert, State}}
+ end.
+
+%% Recipients of Finished messages MUST verify that the contents are
+%% correct and if incorrect MUST terminate the connection with a
+%% "decrypt_error" alert.
+validate_finished(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history = {Messages0, _}}}, VerifyData) ->
+ #{security_parameters := SecParamsR,
+ cipher_state := #cipher_state{finished_key = FinishedKey}} =
+ ssl_record:current_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ %% Drop the peer's finished message, it is not part of the handshake context
+ %% when the client/server calculates its finished message.
+ [_|Messages] = Messages0,
+
+ ControlData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
+ compare_verify_data(ControlData, VerifyData).
+
+
+compare_verify_data(Data, Data) ->
+ ok;
+compare_verify_data(_, _) ->
+ {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}.
%%====================================================================
%% Encode handshake
%%====================================================================
encode_handshake(#certificate_request_1_3{
- certificate_request_context = Context,
+ certificate_request_context = Context,
extensions = Exts})->
EncContext = encode_cert_req_context(Context),
BinExts = encode_extensions(Exts),
{?CERTIFICATE_REQUEST, <<EncContext/binary, BinExts/binary>>};
encode_handshake(#certificate_1_3{
- certificate_request_context = Context,
+ certificate_request_context = Context,
certificate_list = Entries}) ->
EncContext = encode_cert_req_context(Context),
EncEntries = encode_cert_entries(Entries),
@@ -381,12 +552,12 @@ encode_handshake(#certificate_verify_1_3{
EncSign = encode_signature(Signature),
{?CERTIFICATE_VERIFY, <<EncAlgo/binary, EncSign/binary>>};
encode_handshake(#encrypted_extensions{extensions = Exts})->
- {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)};
+ {?ENCRYPTED_EXTENSIONS, encode_extensions(Exts)};
encode_handshake(#new_session_ticket{
- ticket_lifetime = LifeTime,
- ticket_age_add = Age,
- ticket_nonce = Nonce,
- ticket = Ticket,
+ ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
extensions = Exts}) ->
TicketSize = byte_size(Ticket),
NonceSize = byte_size(Nonce),
@@ -401,7 +572,27 @@ encode_handshake(#key_update{request_update = Update}) ->
EncUpdate = encode_key_update(Update),
{?KEY_UPDATE, <<EncUpdate/binary>>};
encode_handshake(HandshakeMsg) ->
- ssl_handshake:encode_handshake(HandshakeMsg, {3,4}).
+ ssl_handshake:encode_handshake(HandshakeMsg, ?TLS_1_3).
+
+encode_early_data(Cipher,
+ #state{
+ flight_buffer = Flight0,
+ protocol_specific = #{sender := _Sender},
+ ssl_options = #{versions := [Version|_],
+ early_data := EarlyData} = _SslOpts0
+ } = State0) ->
+ #state{connection_states =
+ #{current_write :=
+ #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0,
+ BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher),
+ SecurityParameters = SecurityParameters0#security_parameters{
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo},
+ Write = Write0#{security_parameters => SecurityParameters},
+ ConnectionStates1 = ConnectionStates0#{current_write => Write},
+ {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1),
+ State0#state{connection_states = ConnectionStates,
+ flight_buffer = Flight0 ++ [BinEarlyData]}.
%%====================================================================
@@ -414,7 +605,7 @@ decode_handshake(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>)
when Random =:= ?HELLO_RETRY_REQUEST_RANDOM ->
- HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, {3,4}, {Major, Minor},
+ HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, ?TLS_1_3, {Major, Minor},
hello_retry_request),
#server_hello{
server_version = {Major,Minor},
@@ -436,14 +627,14 @@ decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(CSize), Context:CSize/binary,
extensions = Exts};
decode_handshake(?CERTIFICATE, <<?BYTE(0), ?UINT24(Size), Certs:Size/binary>>) ->
CertList = decode_cert_entries(Certs),
- #certificate_1_3{
+ #certificate_1_3{
certificate_request_context = <<>>,
certificate_list = CertList
};
decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary,
?UINT24(Size), Certs:Size/binary>>) ->
CertList = decode_cert_entries(Certs),
- #certificate_1_3{
+ #certificate_1_3{
certificate_request_context = Context,
certificate_list = CertList
};
@@ -461,17 +652,17 @@ decode_handshake(?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age),
?UINT16(TicketSize), Ticket:TicketSize/binary,
?UINT16(BinExtSize), BinExts:BinExtSize/binary>>) ->
Exts = decode_extensions(BinExts, encrypted_extensions),
- #new_session_ticket{ticket_lifetime = LifeTime,
- ticket_age_add = Age,
- ticket_nonce = Nonce,
- ticket = Ticket,
+ #new_session_ticket{ticket_lifetime = LifeTime,
+ ticket_age_add = Age,
+ ticket_nonce = Nonce,
+ ticket = Ticket,
extensions = Exts};
decode_handshake(?END_OF_EARLY_DATA, _) ->
#end_of_early_data{};
decode_handshake(?KEY_UPDATE, <<?BYTE(Update)>>) ->
#key_update{request_update = decode_key_update(Update)};
decode_handshake(Tag, HandshakeMsg) ->
- ssl_handshake:decode_handshake({3,4}, Tag, HandshakeMsg).
+ ssl_handshake:decode_handshake(?TLS_1_3, Tag, HandshakeMsg).
is_valid_binder(Binder, HHistory, PSK, Hash) ->
case HHistory of
@@ -537,36 +728,27 @@ decode_key_update(N) ->
throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {request_update,N})).
decode_cert_entries(Entries) ->
- decode_cert_entries(Entries, []).
-
-decode_cert_entries(<<>>, Acc) ->
- lists:reverse(Acc);
-decode_cert_entries(<<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary,
- Rest/binary>>, Acc) ->
- Exts = decode_extensions(BinExts, certificate_request),
- decode_cert_entries(Rest, [#certificate_entry{data = Data,
- extensions = Exts} | Acc]).
+ [ #certificate_entry{data = Data, extensions = decode_extensions(BinExts, certificate_request)}
+ || <<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary>> <= Entries ].
encode_extensions(Exts)->
ssl_handshake:encode_extensions(extensions_list(Exts)).
decode_extensions(Exts, MessageType) ->
- ssl_handshake:decode_extensions(Exts, {3,4}, MessageType).
+ ssl_handshake:decode_extensions(Exts, ?TLS_1_3, MessageType).
extensions_list(Extensions) ->
[Ext || {_, Ext} <- maps:to_list(Extensions)].
-
%% TODO: add extensions!
chain_to_cert_list(L) ->
chain_to_cert_list(L, []).
-%%
+
chain_to_cert_list([], Acc) ->
lists:reverse(Acc);
chain_to_cert_list([H|T], Acc) ->
chain_to_cert_list(T, [certificate_entry(H)|Acc]).
-
certificate_entry(DER) ->
#certificate_entry{
data = DER,
@@ -592,7 +774,7 @@ certificate_entry(DER) ->
sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
Content = build_content(Context, THash),
try
- {ok, ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo)}
+ {ok, ssl_handshake:digitally_signed(?TLS_1_3, Content, HashAlgo, PrivateKey, SignAlgo)}
catch throw:Alert ->
{error, Alert}
end.
@@ -600,7 +782,7 @@ sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) ->
verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) ->
Content = build_content(Context, THash),
- try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
+ try ssl_handshake:verify_signature(?TLS_1_3, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of
Result ->
{ok, Result}
catch
@@ -614,895 +796,6 @@ build_content(Context, THash) ->
<<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
-%%====================================================================
-%% Handle handshake messages
-%%====================================================================
-
-
-%% TLS Server
-do_start(#client_hello{cipher_suites = ClientCiphers,
- session_id = SessionId,
- extensions = Extensions} = Hello,
- #state{ssl_options = #{ciphers := ServerCiphers,
- signature_algs := ServerSignAlgs,
- supported_groups := ServerGroups0,
- alpn_preferred_protocols := ALPNPreferredProtocols,
- keep_secrets := KeepSecrets,
- honor_cipher_order := HonorCipherOrder,
- early_data := EarlyDataEnabled}} = State0) ->
- SNI = maps:get(sni, Extensions, undefined),
- EarlyDataIndication = maps:get(early_data, Extensions, undefined),
- {Ref,Maybe} = maybe(),
- try
- ClientGroups0 = Maybe(supported_groups_from_extensions(Extensions)),
- ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
- ServerGroups = Maybe(get_supported_groups(ServerGroups0)),
-
- 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),
-
- ClientSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ClientSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
- CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
- #state{connection_states = ConnectionStates0,
- session = Session0,
- connection_env = #connection_env{cert_key_alts = CertKeyAlts}} = State1 =
- Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
-
- Maybe(validate_cookie(Cookie, State1)),
-
- %% Handle ALPN extension if ALPN is configured
- ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
-
- %% If the server does not select a PSK, then the server independently selects a
- %% cipher suite, an (EC)DHE group and key share for key establishment,
- %% 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)),
- CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, {3,4}),
- #session{own_certificates = [Cert|_]} = Session =
- Maybe(select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs,
- ClientSignAlgsCert, CertAuths, State0,
- undefined)),
- {PublicKeyAlgo, _, _, RSAKeySize, Curve} = get_certificate_params(Cert),
-
- %% Select signature algorithm (used in CertificateVerify message).
- SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs, Curve)),
-
- %% Select client public key. If no public key found in ClientShares or
- %% ClientShares is empty, trigger HelloRetryRequest as we were able
- %% to find an acceptable set of parameters but the ClientHello does not
- %% contain sufficient information.
- {Group, ClientPubKey} = get_client_public_key(Groups, ClientShares),
-
- %% Generate server_share
- KeyShare = ssl_cipher:generate_server_share(Group),
-
- State2 = case maps:get(max_frag_enum, Extensions, undefined) of
- MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
- ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
- HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
- State1#state{handshake_env = HsEnv1,
- session = Session,
- connection_states = ConnectionStates1};
- _ ->
- State1#state{session = Session}
- end,
-
- State3 = if KeepSecrets =:= true ->
- set_client_random(State2, Hello#client_hello.random);
- true ->
- State2
- end,
-
- State4 = update_start_state(State3,
- #{cipher => Cipher,
- key_share => KeyShare,
- session_id => SessionId,
- group => Group,
- sign_alg => SelectedSignAlg,
- peer_public_key => ClientPubKey,
- alpn => ALPNProtocol}),
-
- %% 4.1.4. Hello Retry Request
- %%
- %% The server will send this message in response to a ClientHello
- %% 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.
- case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of
- {_, start} = NextStateTuple ->
- NextStateTuple;
- {State5, negotiated} ->
- %% Determine if early data is accepted
- State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication),
- %% Exclude any incompatible PSKs.
- PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)),
- Maybe(session_resumption({State, negotiated}, PSK))
- end
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end;
-%% TLS Client
-do_start(#server_hello{cipher_suite = SelectedCipherSuite,
- session_id = SessionId,
- extensions = Extensions},
- #state{static_env = #static_env{role = client,
- host = Host,
- port = Port,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- protocol_cb = Connection,
- transport_cb = Transport,
- socket = Socket},
- handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
- ocsp_stapling_state = OcspState},
- connection_env = #connection_env{negotiated_version = NegotiatedVersion},
- protocol_specific = PS,
- ssl_options = #{ciphers := ClientCiphers,
- supported_groups := ClientGroups0,
- use_ticket := UseTicket,
- session_tickets := SessionTickets,
- log_level := LogLevel} = SslOpts,
- session = Session0,
- connection_states = ConnectionStates0
- } = State0) ->
- {Ref,Maybe} = maybe(),
- try
- ClientGroups = Maybe(get_supported_groups(ClientGroups0)),
- CookieExt = maps:get(cookie, Extensions, undefined),
- Cookie = get_cookie(CookieExt),
-
- ServerKeyShare = maps:get(key_share, Extensions, undefined),
- SelectedGroup = get_selected_group(ServerKeyShare),
-
- %% Upon receipt of this extension in a HelloRetryRequest, the client
- %% MUST verify that (1) the selected_group field corresponds to a group
- %% which was provided in the "supported_groups" extension in the
- %% original ClientHello and (2) the selected_group field does not
- %% correspond to a group which was provided in the "key_share" extension
- %% in the original ClientHello. If either of these checks fails, then
- %% the client MUST abort the handshake with an "illegal_parameter"
- %% alert.
- Maybe(validate_selected_group(SelectedGroup, ClientGroups)),
-
- Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
-
- %% Otherwise, when sending the new ClientHello, the client MUST
- %% replace the original "key_share" extension with one containing only a
- %% new KeyShareEntry for the group indicated in the selected_group field
- %% of the triggering HelloRetryRequest.
- ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
- TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- OcspNonce = maps:get(ocsp_nonce, OcspState, undefined),
- Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
- SessionId, Renegotiation, ClientKeyShare,
- TicketData, OcspNonce, CertDbHandle, CertDbRef),
- %% Echo cookie received in HelloRetryrequest
- Hello1 = maybe_add_cookie_extension(Cookie, Hello0),
-
- %% Update state
- State1 = update_start_state(State0,
- #{cipher => SelectedCipherSuite,
- key_share => ClientKeyShare,
- session_id => SessionId,
- group => SelectedGroup}),
-
- %% Replace ClientHello1 with a special synthetic handshake message
- State2 = replace_ch1_with_message_hash(State1),
- #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(Hello1, HHistory0, TicketData, NegotiatedVersion),
-
- {BinMsg0, ConnectionStates, HHistory} =
- Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0),
-
- %% D.4. Middlebox Compatibility Mode
- {#state{handshake_env = HsEnv} = State3, BinMsg} =
- maybe_prepend_change_cipher_spec(State2, BinMsg0),
-
- tls_socket:send(Transport, Socket, BinMsg),
- ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
- ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),
-
- State = State3#state{
- connection_states = ConnectionStates,
- session = Session0#session{session_id = Hello#client_hello.session_id},
- handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory},
- key_share = ClientKeyShare},
-
- %% If it is a hello_retry and middlebox mode is
- %% used assert the change_cipher_spec message
- %% that the server should send next
- case (maps:get(hello_retry, PS, false)) andalso
- (maps:get(middlebox_comp_mode, SslOpts, true))
- of
- true ->
- {State, hello_retry_middlebox_assert};
- false ->
- {State, wait_sh}
- end
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-do_negotiated({start_handshake, PSK0},
- #state{connection_states = ConnectionStates0,
- handshake_env =
- #handshake_env{
- early_data_accepted = EarlyDataAccepted},
- static_env = #static_env{protocol_cb = Connection},
- session = #session{session_id = SessionId,
- ecc = SelectedGroup,
- dh_public_value = ClientPublicKey},
- ssl_options = #{} = SslOpts,
- key_share = KeyShare} = State0) ->
- ServerPrivateKey = get_server_private_key(KeyShare),
-
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates0, read),
- #security_parameters{prf_algorithm = HKDF} = SecParamsR,
-
- {Ref,Maybe} = maybe(),
- try
- %% Create server_hello
- ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0),
- State1 = Connection:queue_handshake(ServerHello, State0),
- %% D.4. Middlebox Compatibility Mode
- State2 = maybe_queue_change_cipher_spec(State1, last),
-
- PSK = get_pre_shared_key(PSK0, HKDF),
-
- State3 =
- calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup,
- PSK, State2),
-
- %% Step only write state if early_data is accepted
- State4 =
- case EarlyDataAccepted of
- true ->
- ssl_record:step_encryption_state_write(State3);
- false ->
- %% Read state is overwritten when handshake secrets are set.
- %% Trial_decryption and early_data_accepted must be set here!
- update_current_read(
- ssl_record:step_encryption_state(State3),
- true, %% trial_decryption
- false %% early_data_accepted
- )
-
- end,
-
- %% Create EncryptedExtensions
- EncryptedExtensions = encrypted_extensions(State4),
-
- %% Encode EncryptedExtensions
- State5 = Connection:queue_handshake(EncryptedExtensions, State4),
-
- %% Create and send CertificateRequest ({verify, verify_peer})
- {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0),
-
- %% Create and send Certificate (if PSK is undefined)
- State7 = Maybe(maybe_send_certificate(State6, PSK0)),
-
- %% Create and send CertificateVerify (if PSK is undefined)
- State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)),
-
- %% Create Finished
- Finished = finished(State8),
-
- %% Encode Finished
- State9 = Connection:queue_handshake(Finished, State8),
-
- %% Send first flight
- {State, _} = Connection:send_handshake_flight(State9),
-
- {State, NextState}
-
- catch
- {Ref, #alert{} = Alert} ->
- Alert;
- error:badarg=Reason:ST ->
- ?SSL_LOG(debug, crypto_error, [{reason, Reason}, {stacktrace, ST}]),
- ?ALERT_REC(?ILLEGAL_PARAMETER, illegal_parameter_to_compute_key)
- end.
-
-
-do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
- {Ref,Maybe} = maybe(),
- try
- Maybe(process_certificate(Certificate, State0))
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0};
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end.
-
-
-do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, #state{static_env = #static_env{role = Role}} = State0) ->
- {Ref,Maybe} = maybe(),
- try
- State1 = case Role of
- server ->
- Maybe(verify_signature_algorithm(State0, CertificateVerify));
- client ->
- State0
- end,
- Maybe(verify_certificate_verify(State1, CertificateVerify))
- catch
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end.
-
-%% TLS Server
-do_wait_finished(#finished{verify_data = VerifyData},
- #state{static_env = #static_env{role = server}} = State0) ->
- {Ref,Maybe} = maybe(),
-
- try
- Maybe(validate_finished(State0, VerifyData)),
-
- State1 = calculate_traffic_secrets(State0),
- State2 = maybe_calculate_resumption_master_secret(State1),
- State3 = forget_master_secret(State2),
-
- %% Configure traffic keys
- State4 = ssl_record:step_encryption_state(State3),
-
- %% Send session ticket
- maybe_send_session_ticket(State4)
-
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end;
-%% TLS Client
-do_wait_finished(#finished{verify_data = VerifyData},
- #state{static_env = #static_env{role = client,
- protocol_cb = Connection}} = State0) ->
-
- {Ref,Maybe} = maybe(),
-
- try
- Maybe(validate_finished(State0, VerifyData)),
- %% D.4. Middlebox Compatibility Mode
- State1 = maybe_queue_change_cipher_spec(State0, first),
- %% Signal change of cipher
- State2 = maybe_send_end_of_early_data(State1),
- %% Maybe send Certificate + CertificateVerify
- State3 = Maybe(maybe_queue_cert_cert_cv(State2)),
- Finished = finished(State3),
- %% Encode Finished
- State4 = Connection:queue_handshake(Finished, State3),
- %% Send first flight
- {State5, _} = Connection:send_handshake_flight(State4),
- State6 = calculate_traffic_secrets(State5),
- State7 = maybe_calculate_resumption_master_secret(State6),
- State8 = forget_master_secret(State7),
- %% Configure traffic keys
- ssl_record:step_encryption_state(State8)
- catch
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-
-do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite,
- session_id = SessionId,
- extensions = Extensions} = ServerHello,
- #state{key_share = ClientKeyShare0,
- ssl_options = #{ciphers := ClientCiphers,
- supported_groups := ClientGroups0,
- session_tickets := SessionTickets,
- use_ticket := UseTicket}} = State0) ->
-
- {Ref,Maybe} = maybe(),
- try
- ClientGroups = Maybe(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),
-
- %% 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)),
- Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),
-
- %% Get server public key
- {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare),
-
- {_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare),
-
- %% Update state
- State2 = update_start_state(State1,
- #{cipher => SelectedCipherSuite,
- key_share => ClientKeyShare0,
- session_id => SessionId,
- group => SelectedGroup,
- peer_public_key => ServerPublicKey}),
-
- #state{connection_states = ConnectionStates} = State2,
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
-
- 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),
- State4 = ssl_record:step_encryption_state_read(State3),
- {State4, wait_ee}
-
- catch
- {Ref, {State, StateName, ServerHello}} ->
- {State, StateName, ServerHello};
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-
-do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) ->
-
- ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
- ALPNProtocol = get_alpn(ALPNProtocol0),
- EarlyDataIndication = maps:get(early_data, Extensions, undefined),
-
- {Ref, Maybe} = maybe(),
-
- try
- %% RFC 6066: handle received/expected maximum fragment length
- Maybe(maybe_max_fragment_length(Extensions, State0)),
-
- %% Check if early_data is accepted/rejected
- State1 = maybe_check_early_data_indication(EarlyDataIndication, State0),
-
- %% Go to state 'wait_finished' if using PSK.
- Maybe(maybe_resumption(State1)),
-
- %% Update state
- #state{handshake_env = HsEnv} = State1,
- State2 = State1#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}},
-
- {State2, wait_cert_cr}
- catch
- {Ref, {State, StateName}} ->
- {State, StateName};
- {Ref, #alert{} = Alert} ->
- Alert
- end.
-
-
-do_wait_cert_cr(#certificate_1_3{} = Certificate, State0) ->
- {Ref,Maybe} = maybe(),
- try
- Maybe(process_certificate(Certificate, State0))
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0};
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end;
-do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) ->
- {Ref,Maybe} = maybe(),
- try
- Maybe(process_certificate_request(CertificateRequest, State0))
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0}
- end.
-
-
-do_wait_eoed(#end_of_early_data{}, State0) ->
- {Ref,_Maybe} = maybe(),
- try
- %% Step read state to enable reading handshake messages from the client.
- %% Write state is already stepped in state 'negotiated'.
- State1 = ssl_record:step_encryption_state_read(State0),
-
- %% Early data has been received, no more early data is expected.
- HsEnv = (State1#state.handshake_env)#handshake_env{early_data_accepted = false},
- State2 = State1#state{handshake_env = HsEnv},
- {State2, wait_finished}
- catch
- {Ref, #alert{} = Alert} ->
- {Alert, State0};
- {Ref, {#alert{} = Alert, State}} ->
- {Alert, State}
- end.
-
-
-%% For reasons of backward compatibility with middleboxes (see
-%% Appendix D.4), the HelloRetryRequest message uses the same structure
-%% as the ServerHello, but with Random set to the special value of the
-%% SHA-256 of "HelloRetryRequest":
-%%
-%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91
-%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C
-%%
-%% Upon receiving a message with type server_hello, implementations MUST
-%% first examine the Random value and, if it matches this value, process
-%% it as described in Section 4.1.4).
-maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello,
- #state{protocol_specific = PS} = State0) ->
- {error, {State0#state{protocol_specific = PS#{hello_retry => true}}, start, ServerHello}};
-maybe_hello_retry_request(_, _) ->
- ok.
-
-maybe_max_fragment_length(Extensions, State) ->
- ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
- ClientMaxFragEnum = ssl_handshake:max_frag_enum(
- maps:get(max_fragment_length, State#state.ssl_options, undefined)),
- if ServerMaxFragEnum == ClientMaxFragEnum ->
- ok;
- true ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
- end.
-
-
-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}.
-
-%% @doc Enqueues a change_cipher_spec record as the first/last message of
-%% the current flight buffer
-%% @end
-maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, first) ->
- {State, FlightBuffer} = maybe_prepend_change_cipher_spec(State0, FlightBuffer0),
- State#state{flight_buffer = FlightBuffer};
-maybe_queue_change_cipher_spec(#state{flight_buffer = FlightBuffer0} = State0, last) ->
- {State, FlightBuffer} = maybe_append_change_cipher_spec(State0, FlightBuffer0),
- State#state{flight_buffer = FlightBuffer}.
-
-%% @doc Prepends a change_cipher_spec record to the input binary
-%%
-%% It can only prepend the change_cipher_spec record only once in
-%% order to accurately emulate a legacy TLS 1.2 connection.
-%%
-%% D.4. Middlebox Compatibility Mode
-%% If not offering early data, the client sends a dummy
-%% change_cipher_spec record (see the third paragraph of Section 5)
-%% immediately before its second flight. This may either be before
-%% its second ClientHello or before its encrypted handshake flight.
-%% If offering early data, the record is placed immediately after the
-%% first ClientHello.
-%% @end
-maybe_prepend_change_cipher_spec(#state{
- session = #session{session_id = Id},
- handshake_env =
- #handshake_env{
- change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID ->
- CCSBin = create_change_cipher_spec(State),
- {State#state{handshake_env =
- HSEnv#handshake_env{change_cipher_spec_sent = true}},
- [CCSBin|Bin]};
-maybe_prepend_change_cipher_spec(State, Bin) ->
- {State, Bin}.
-
-%% @doc Appends a change_cipher_spec record to the input binary
-%% @end
-maybe_append_change_cipher_spec(#state{
- session = #session{session_id = Id},
- handshake_env =
- #handshake_env{
- change_cipher_spec_sent = false} = HSEnv} = State, Bin) when Id =/= ?EMPTY_ID ->
- CCSBin = create_change_cipher_spec(State),
- {State#state{handshake_env =
- HSEnv#handshake_env{change_cipher_spec_sent = true}},
- Bin ++ [CCSBin]};
-maybe_append_change_cipher_spec(State, Bin) ->
- {State, Bin}.
-
-maybe_queue_cert_cert_cv(#state{client_certificate_status = not_requested} = State) ->
- {ok, State};
-maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
- session = #session{session_id = _SessionId,
- own_certificates = OwnCerts},
- ssl_options = #{} = _SslOpts,
- key_share = _KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- static_env = #static_env{
- role = client,
- protocol_cb = Connection,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
- {Ref,Maybe} = maybe(),
- try
- %% Create Certificate
- Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)),
-
- %% Encode Certificate
- State1 = Connection:queue_handshake(Certificate, State0),
- %% Maybe create and queue CertificateVerify
- State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
- {ok, State}
- catch
- {Ref, #alert{} = Alert} ->
- {error, Alert}
- end.
-
-
-%% Clients MUST send this message whenever authenticating via a certificate
-%% (i.e., when the Certificate message is non-empty).
-maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
- {ok, State};
-maybe_queue_cert_verify(_Certificate,
- #state{connection_states = _ConnectionStates0,
- session = #session{sign_alg = SignatureScheme,
- private_key = CertPrivateKey},
- static_env = #static_env{role = client,
- protocol_cb = Connection}
- } = State) ->
- {Ref,Maybe} = maybe(),
- try
- CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)),
- {ok, Connection:queue_handshake(CertificateVerify, State)}
- catch
- {Ref, #alert{} = Alert} ->
- {error, Alert}
- end.
-
-
-%% Recipients of Finished messages MUST verify that the contents are
-%% correct and if incorrect MUST terminate the connection with a
-%% "decrypt_error" alert.
-validate_finished(#state{connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- tls_handshake_history = {Messages0, _}}}, VerifyData) ->
- #{security_parameters := SecParamsR,
- cipher_state := #cipher_state{finished_key = FinishedKey}} =
- ssl_record:current_connection_state(ConnectionStates, read),
- #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
-
- %% Drop the peer's finished message, it is not part of the handshake context
- %% when the client/server calculates its finished message.
- [_|Messages] = Messages0,
-
- ControlData = tls_v1:finished_verify_data(FinishedKey, HKDFAlgo, Messages),
- compare_verify_data(ControlData, VerifyData).
-
-
-compare_verify_data(Data, Data) ->
- ok;
-compare_verify_data(_, _) ->
- {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}.
-
-
-send_hello_retry_request(#state{connection_states = ConnectionStates0,
- static_env = #static_env{protocol_cb = Connection}} = State0,
- no_suitable_key, KeyShare, SessionId) ->
- ServerHello0 = server_hello(hello_retry_request, SessionId, KeyShare, undefined, ConnectionStates0),
- {State1, ServerHello} = maybe_add_cookie_extension(State0, ServerHello0),
-
- State2 = Connection:queue_handshake(ServerHello, State1),
- %% D.4. Middlebox Compatibility Mode
- State3 = maybe_queue_change_cipher_spec(State2, last),
- {State4, _} = Connection:send_handshake_flight(State3),
-
- %% Update handshake history
- State5 = replace_ch1_with_message_hash(State4),
-
- {ok, {State5, start}};
-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},
- handshake_env = #handshake_env{
- early_data_accepted = false}} = State0, negotiated}, PSK)
- when Tickets =/= disabled ->
- State = handle_resumption(State0, ok),
- {ok, {State, negotiated, PSK}};
-session_resumption({#state{ssl_options = #{session_tickets := Tickets},
- handshake_env = #handshake_env{
- early_data_accepted = true}} = State0, negotiated}, PSK0)
- when Tickets =/= disabled ->
- State1 = handle_resumption(State0, ok),
- %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed.
- {_ , PSK} = PSK0,
- State2 = calculate_client_early_traffic_secret(State1, PSK),
- %% Set 0-RTT traffic keys for reading early_data
- State3 = ssl_record:step_encryption_state_read(State2),
- State = update_current_read(State3, true, true),
- {ok, {State, negotiated, PSK0}}.
-
-%% Session resumption with early_data
-maybe_send_certificate_request(#state{
- handshake_env =
- #handshake_env{
- early_data_accepted = true}} = State,
- _, PSK) when PSK =/= undefined ->
- %% Go wait for End of Early Data
- {State, wait_eoed};
-%% 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{static_env = #static_env{protocol_cb = Connection,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef}} = State,
- #{verify := verify_peer,
- signature_algs := SignAlgs,
- signature_algs_cert := SignAlgsCert,
- certificate_authorities := CertAuthBool}, _) ->
- CertificateRequest = certificate_request(SignAlgs, SignAlgsCert, CertDbHandle, CertDbRef, CertAuthBool),
- {Connection:queue_handshake(CertificateRequest, State), wait_cert}.
-
-maybe_send_certificate(State, PSK) when PSK =/= undefined ->
- {ok, State};
-maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts},
- static_env = #static_env{
- protocol_cb = Connection,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef}} = State, _) ->
- case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of
- {ok, Certificate} ->
- {ok, 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,
- private_key = CertPrivateKey},
- static_env = #static_env{protocol_cb = Connection}
- } = State, _) ->
- case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
- {ok, CertificateVerify} ->
- {ok, Connection:queue_handshake(CertificateVerify, State)};
- Error ->
- Error
- end.
-
-
-maybe_send_session_ticket(State) ->
- Number = case application:get_env(ssl, server_session_tickets_amount) of
- {ok, Size} when is_integer(Size) andalso
- Size > 0 ->
- Size;
- _ ->
- 3
- end,
- maybe_send_session_ticket(State, Number).
-%%
-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,
- protocol_cb = Connection}
-
- } = 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, _} = Connection:send_handshake(Ticket, State0),
- maybe_send_session_ticket(State, N - 1).
-
-create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) ->
- %% Dummy connection_states with NULL cipher
- ConnectionStates =
- #{current_write =>
- #{compression_state => undefined,
- cipher_state => undefined,
- sequence_number => 1,
- security_parameters =>
- #security_parameters{
- bulk_cipher_algorithm = 0,
- compression_algorithm = ?NULL,
- mac_algorithm = ?NULL
- },
- mac_secret => undefined}},
- {BinChangeCipher, _} =
- tls_record:encode_change_cipher_spec(?LEGACY_VERSION, ConnectionStates),
- ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher),
- [BinChangeCipher].
-
-process_certificate_request(#certificate_request_1_3{
- extensions = Extensions},
- #state{ssl_options = #{signature_algs := ClientSignAlgs},
- connection_env = #connection_env{cert_key_alts = CertKeyAlts,
- negotiated_version = Version},
- static_env = #static_env{cert_db = CertDbHandle, cert_db_ref = CertDbRef},
- session = Session0} =
- State) ->
- ServerSignAlgs = get_signature_scheme_list(
- maps:get(signature_algs, Extensions, undefined)),
- ServerSignAlgsCert = get_signature_scheme_list(
- maps:get(signature_algs_cert, Extensions, undefined)),
- CertAuths = get_certificate_authorities(maps:get(certificate_authorities, Extensions, undefined)),
-
- CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, Version),
- Session = select_client_cert_key_pair(Session0, CertKeyPairs,
- ServerSignAlgs, ServerSignAlgsCert, filter_tls13_algs(ClientSignAlgs),
- CertDbHandle, CertDbRef, CertAuths, undefined),
- {ok, {State#state{client_certificate_status = requested, session = Session}, wait_cert}}.
-
-process_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #{fail_if_no_peer_cert := false}} = State) ->
- {ok, {State, wait_finished}};
-process_certificate(#certificate_1_3{
- certificate_request_context = <<>>,
- certificate_list = []},
- #state{ssl_options =
- #{fail_if_no_peer_cert := true}} = State0) ->
- %% At this point the client believes that the connection is up and starts using
- %% its traffic secrets. In order to be able send an proper Alert to the client
- %% the server should also change its connection state and use the traffic
- %% secrets.
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}};
-process_certificate(#certificate_1_3{certificate_list = CertEntries},
- #state{ssl_options = SslOptions,
- static_env =
- #static_env{
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbHandle},
- handshake_env = #handshake_env{
- ocsp_stapling_state = OcspState}} = State0) ->
- case validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
- SslOptions, CRLDbHandle, Role, Host, OcspState) of
- #alert{} = Alert ->
- State = update_encryption_state(Role, State0),
- {error, {Alert, State}};
- {PeerCert, PublicKeyInfo} ->
- State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
- {ok, {State, wait_cv}}
- end.
-
%% Sets correct encryption state when sending Alerts in shared states that use different secrets.
%% - If client: use handshake secrets.
%% - If server: use traffic secrets as by this time the client's state machine
@@ -1515,29 +808,33 @@ update_encryption_state(client, State) ->
validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef,
- #{ocsp_responder_certs := OcspResponderCerts
- } = SslOptions, CRLDbHandle, Role, Host, OcspState0) ->
+ SslOptions, CRLDbHandle, Role, Host, OcspState0) ->
{Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0),
-
+ OcspResponderCerts =
+ case maps:get(ocsp_stapling, SslOptions, disabled) of
+ #{ocsp_responder_certs := V} ->
+ V;
+ disabled ->
+ ?DEFAULT_OCSP_RESPONDER_CERTS
+ end,
ssl_handshake:certify(#certificate{asn1_certificates = Certs}, CertDbHandle, CertDbRef,
- SslOptions, CRLDbHandle, Role, Host, {3,4},
+ SslOptions, CRLDbHandle, Role, Host, ?TLS_1_3,
#{cert_ext => CertExt,
ocsp_state => OcspState,
ocsp_responder_certs => OcspResponderCerts}).
-
store_peer_cert(#state{session = Session,
handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
State#state{session = Session#session{peer_certificate = PeerCert},
handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}.
-
split_cert_entries(CertEntries, OcspState) ->
split_cert_entries(CertEntries, OcspState, [], #{}).
+
split_cert_entries([], OcspState, Chain, Ext) ->
{lists:reverse(Chain), Ext, OcspState};
-split_cert_entries([#certificate_entry{data = DerCert,
- extensions = Extensions0} | CertEntries], OcspState0, Chain, Ext) ->
+split_cert_entries([#certificate_entry{data = DerCert, extensions = Extensions0} | CertEntries],
+ OcspState0, Chain, Ext) ->
Id = public_key:pkix_subject_id(DerCert),
Extensions = [ExtValue || {_, ExtValue} <- maps:to_list(Extensions0)],
OcspState = case maps:get(status_request, Extensions0, undefined) of
@@ -1548,7 +845,6 @@ split_cert_entries([#certificate_entry{data = DerCert,
end,
split_cert_entries(CertEntries, OcspState, [DerCert | Chain], Ext#{Id => Extensions}).
-
%% 4.4.1. The Transcript Hash
%%
%% As an exception to this general rule, when the server responds to a
@@ -1644,7 +940,7 @@ calculate_client_early_traffic_secret(#state{connection_states = ConnectionState
calculate_client_early_traffic_secret(
ClientHello, PSK, Cipher, HKDFAlgo,
#state{connection_states = ConnectionStates,
- ssl_options = #{keep_secrets := KeepSecrets},
+ ssl_options = Opts,
static_env = #static_env{role = Role}} = State0) ->
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
ClientEarlyTrafficSecret =
@@ -1657,7 +953,7 @@ calculate_client_early_traffic_secret(
case Role of
client ->
PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write),
- PendingWrite1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
+ PendingWrite1 = maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
PendingWrite0),
PendingWrite = update_connection_state(PendingWrite1, undefined, undefined,
undefined,
@@ -1665,7 +961,7 @@ calculate_client_early_traffic_secret(
State0#state{connection_states = ConnectionStates#{pending_write => PendingWrite}};
server ->
PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
- PendingRead1 = maybe_store_early_data_secret(KeepSecrets, ClientEarlyTrafficSecret,
+ PendingRead1 = maybe_store_early_data_secret(Opts, ClientEarlyTrafficSecret,
PendingRead0),
PendingRead = update_connection_state(PendingRead1, undefined, undefined,
undefined,
@@ -1673,20 +969,20 @@ calculate_client_early_traffic_secret(
State0#state{connection_states = ConnectionStates#{pending_read => PendingRead}}
end.
-update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataExpected) ->
- Read0 = ssl_record:current_connection_state(CS, read),
- Read = Read0#{trial_decryption => TrialDecryption,
- early_data_accepted => EarlyDataExpected},
- State#state{connection_states = CS#{current_read => Read}}.
-maybe_store_early_data_secret(true, EarlySecret, State) ->
+
+maybe_store_early_data_secret(#{keep_secrets := true}, EarlySecret, State) ->
#{security_parameters := SecParams0} = State,
SecParams = SecParams0#security_parameters{client_early_data_secret = EarlySecret},
State#{security_parameters := SecParams};
-maybe_store_early_data_secret(false, _, State) ->
+maybe_store_early_data_secret(_, _, State) ->
State.
%% Server
+%% get_pre_shared_key(undefined, HKDFAlgo) ->
+%% binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo));
+%% get_pre_shared_key(#pre_shared_key_server_hello{selected_identity = PSK}, _) ->
+%% PSK.
get_pre_shared_key(undefined, HKDFAlgo) ->
binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo));
get_pre_shared_key({_, PSK}, _) ->
@@ -1702,9 +998,9 @@ get_pre_shared_key(undefined, _, HKDFAlgo, _) ->
get_pre_shared_key(_, undefined, HKDFAlgo, _) ->
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
%% Session resumption
-get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
+get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, ServerPSK) ->
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- case choose_psk(TicketData, SelectedIdentity) of
+ case choose_psk(TicketData, ServerPSK) of
undefined -> %% full handshake, default PSK
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
illegal_parameter ->
@@ -1712,9 +1008,9 @@ get_pre_shared_key(manual = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentit
{_, PSK, _, _, _} ->
{ok, PSK}
end;
-get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity) ->
+get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, ServerPSK) ->
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- case choose_psk(TicketData, SelectedIdentity) of
+ case choose_psk(TicketData, ServerPSK) of
undefined -> %% full handshake, default PSK
tls_client_ticket_store:unlock_tickets(self(), UseTicket),
{ok, binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo))};
@@ -1730,7 +1026,7 @@ get_pre_shared_key(auto = SessionTickets, UseTicket, HKDFAlgo, SelectedIdentity)
%% Early Data
get_pre_shared_key_early_data(SessionTickets, UseTicket) ->
TicketData = get_ticket_data(self(), SessionTickets, UseTicket),
- case choose_psk(TicketData, 0) of
+ case choose_psk(TicketData, #pre_shared_key_server_hello{selected_identity = 0}) of
undefined -> %% Should not happen
{error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
illegal_parameter ->
@@ -1739,6 +1035,11 @@ get_pre_shared_key_early_data(SessionTickets, UseTicket) ->
{ok, {PSK, Cipher, HKDF, MaxSize}}
end.
+get_supported_groups(undefined = Groups) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
+get_supported_groups(#supported_groups{supported_groups = Groups}) ->
+ {ok, Groups}.
+
choose_psk(undefined, _) ->
undefined;
choose_psk([], _) ->
@@ -1748,7 +1049,7 @@ choose_psk([#ticket_data{
pos = SelectedIdentity,
psk = PSK,
cipher_suite = {Cipher, HKDF},
- max_size = MaxSize}|_], SelectedIdentity) ->
+ max_size = MaxSize}|_], #pre_shared_key_server_hello{selected_identity = SelectedIdentity}) ->
{Key, PSK, Cipher, HKDF, MaxSize};
choose_psk([_|T], SelectedIdentity) ->
choose_psk(T, SelectedIdentity).
@@ -1789,16 +1090,7 @@ calculate_traffic_secrets(#state{
WriteKey, WriteIV, undefined).
-get_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
- get_private_key(ServerShare).
-get_private_key(#key_share_entry{
- key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
- PrivateKey;
-get_private_key(#key_share_entry{
- key_exchange =
- {_, PrivateKey}}) ->
- PrivateKey.
%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
@@ -1967,7 +1259,7 @@ update_start_state(#state{connection_states = ConnectionStates0,
sign_alg = SelectedSignAlg,
dh_public_value = PeerPublicKey,
cipher_suite = Cipher},
- connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
+ connection_env = CEnv#connection_env{negotiated_version = ?TLS_1_3}}.
update_resumption_master_secret(#state{connection_states = ConnectionStates0} = State,
@@ -2096,46 +1388,7 @@ maybe_update_selected_sign_alg(State, _, _) ->
State.
-verify_certificate_verify(#state{static_env = #static_env{role = Role},
- connection_states = ConnectionStates,
- handshake_env =
- #handshake_env{
- public_key_info = PublicKeyInfo,
- tls_handshake_history = HHistory}} = State0,
- #certificate_verify_1_3{
- algorithm = SignatureScheme,
- signature = Signature}) ->
- #{security_parameters := SecParamsR} =
- ssl_record:pending_connection_state(ConnectionStates, write),
- #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
-
- {HashAlgo, SignAlg, _} =
- ssl_cipher:scheme_to_components(SignatureScheme),
-
- Messages = get_handshake_context_cv(HHistory),
- Context = lists:reverse(Messages),
-
- %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
- THash = tls_v1:transcript_hash(Context, HKDFAlgo),
-
- ContextString = peer_context_string(Role),
-
- %% Digital signatures use the hash function defined by the selected signature
- %% scheme.
- case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of
- {ok, true} ->
- {ok, {State0, wait_finished}};
- {ok, false} ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
- "Failed to verify CertificateVerify"), State}};
- {error, #alert{} = Alert} ->
- State1 = calculate_traffic_secrets(State0),
- State = ssl_record:step_encryption_state(State1),
- {error, {Alert, State}}
- end.
context_string(server) ->
@@ -2166,146 +1419,6 @@ select_common_groups(ServerGroups, ClientGroups) ->
{ok, L}
end.
-
-%% RFC 8446 - 4.2.8. Key Share
-%% This vector MAY be empty if the client is requesting a
-%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
-%% group offered in the "supported_groups" extension and MUST appear in
-%% the same order. However, the values MAY be a non-contiguous subset
-%% of the "supported_groups" extension and MAY omit the most preferred
-%% groups.
-%%
-%% Clients can offer as many KeyShareEntry values as the number of
-%% supported groups it is offering, each representing a single set of
-%% key exchange parameters.
-%%
-%% Clients MUST NOT offer multiple KeyShareEntry values
-%% for the same group. Clients MUST NOT offer any KeyShareEntry values
-%% for groups not listed in the client's "supported_groups" extension.
-%% Servers MAY check for violations of these rules and abort the
-%% handshake with an "illegal_parameter" alert if one is violated.
-validate_client_key_share(_ ,[]) ->
- ok;
-validate_client_key_share([], _) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
-validate_client_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) ->
- validate_client_key_share(ClientGroups, ClientShares);
-validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) ->
- validate_client_key_share(ClientGroups, ClientShares).
-
-
-%% Verify that selected group is offered by the client.
-validate_server_key_share([], _) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
-validate_server_key_share([G|_ClientGroups], {_, G, _}) ->
- ok;
-validate_server_key_share([_|ClientGroups], {_, _, _} = ServerKeyShare) ->
- validate_server_key_share(ClientGroups, ServerKeyShare).
-
-
-validate_selected_group(SelectedGroup, [SelectedGroup|_]) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
- "Selected group sent by the server shall not correspond to a group"
- " which was provided in the key_share extension")};
-validate_selected_group(SelectedGroup, ClientGroups) ->
- case lists:member(SelectedGroup, ClientGroups) of
- true ->
- ok;
- false ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
- "Selected group sent by the server shall correspond to a group"
- " which was provided in the supported_groups extension")}
- end.
-
-
-get_client_public_key([Group|_] = Groups, ClientShares) ->
- get_client_public_key(Groups, ClientShares, Group).
-%%
-get_client_public_key(_, [], PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_public_key([], _, PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_public_key([Group|Groups], ClientShares, PreferredGroup) ->
- case lists:keysearch(Group, 2, ClientShares) of
- {value, {_, _, ClientPublicKey}} ->
- {Group, ClientPublicKey};
- false ->
- get_client_public_key(Groups, ClientShares, PreferredGroup)
- end.
-
-get_client_private_key([Group|_] = Groups, ClientShares) ->
- get_client_private_key(Groups, ClientShares, Group).
-%%
-get_client_private_key(_, [], PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_private_key([], _, PreferredGroup) ->
- {PreferredGroup, no_suitable_key};
-get_client_private_key([Group|Groups], ClientShares, PreferredGroup) ->
- case lists:keysearch(Group, 2, ClientShares) of
- {value, {_, _, {_, ClientPrivateKey}}} ->
- {Group, ClientPrivateKey};
- {value, {_, _, #'ECPrivateKey'{} = ClientPrivateKey}} ->
- {Group, ClientPrivateKey};
- false ->
- get_client_private_key(Groups, ClientShares, PreferredGroup)
- end.
-
-
-get_server_public_key({key_share_entry, Group, PublicKey}) ->
- {Group, PublicKey}.
-
-
-%% RFC 7301 - Application-Layer Protocol Negotiation Extension
-%% It is expected that a server will have a list of protocols that it
-%% supports, in preference order, and will only select a protocol if the
-%% client supports it. In that case, the server SHOULD select the most
-%% highly preferred protocol that it supports and that is also
-%% advertised by the client. In the event that the server supports no
-%% protocols that the client advertises, then the server SHALL respond
-%% with a fatal "no_application_protocol" alert.
-handle_alpn(undefined, _) ->
- {ok, undefined};
-handle_alpn([], _) ->
- {error, ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)};
-handle_alpn([_|_], undefined) ->
- {ok, undefined};
-handle_alpn([ServerProtocol|T], ClientProtocols) ->
- case lists:member(ServerProtocol, ClientProtocols) of
- true ->
- {ok, ServerProtocol};
- false ->
- handle_alpn(T, ClientProtocols)
- end.
-
-
-select_cipher_suite(_, [], _) ->
- {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher)};
-%% If honor_cipher_order is set to true, use the server's preference for
-%% cipher suite selection.
-select_cipher_suite(true, ClientCiphers, ServerCiphers) ->
- select_cipher_suite(false, ServerCiphers, ClientCiphers);
-select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) ->
- case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso
- lists:member(Cipher, ServerCiphers) of
- true ->
- {ok, Cipher};
- false ->
- select_cipher_suite(false, ClientCiphers, ServerCiphers)
- end.
-
-
-%% RFC 8446 4.1.3 ServerHello
-%% A client which receives a cipher suite that was not offered MUST abort the
-%% handshake with an "illegal_parameter" alert.
-validate_cipher_suite(Cipher, ClientCiphers) ->
- case lists:member(Cipher, ClientCiphers) of
- true ->
- ok;
- false ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
- end.
-
-
%% RFC 8446 (TLS 1.3)
%% TLS 1.3 provides two extensions for indicating which signature
%% algorithms may be used in digital signatures. The
@@ -2474,36 +1587,6 @@ get_certificate_authorities(#certificate_authorities{authorities = Auths}) ->
get_certificate_authorities(undefined) ->
[].
-get_supported_groups(undefined = Groups) ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})};
-get_supported_groups(#supported_groups{supported_groups = Groups}) ->
- {ok, Groups}.
-
-get_key_shares(undefined) ->
- [];
-get_key_shares(#key_share_client_hello{client_shares = ClientShares}) ->
- ClientShares;
-get_key_shares(#key_share_server_hello{server_share = ServerShare}) ->
- ServerShare.
-
-get_cookie(undefined) ->
- undefined;
-get_cookie(#cookie{cookie = Cookie}) ->
- Cookie.
-
-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
@@ -2521,33 +1604,13 @@ 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 ->
+ static_env = #static_env{trackers = Trackers}},
+ #pre_shared_key_client_hello{offered_psks =
+ 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.
-
-get_alpn(ALPNProtocol0) ->
- case ssl_handshake:decode_alpn(ALPNProtocol0) of
- undefined ->
- undefined;
- [ALPNProtocol] ->
- ALPNProtocol
- end.
-
-maybe() ->
- Ref = erlang:make_ref(),
- Ok = fun(ok) -> ok;
- ({ok,R}) -> R;
- ({error,Reason}) ->
- 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
@@ -2571,25 +1634,25 @@ maybe() ->
%% message, as described in Section 4.4.1.
maybe_add_binders(Hello, undefined, _) ->
Hello;
-maybe_add_binders(Hello0, TicketData, Version) when Version =:= {3,4} ->
+maybe_add_binders(Hello0, TicketData, ?TLS_1_3=Version) ->
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} ->
+maybe_add_binders(Hello, _, Version) when ?TLS_LTE(Version, ?TLS_1_2) ->
Hello.
%%
%% HelloRetryRequest
maybe_add_binders(Hello, _, undefined, _) ->
Hello;
-maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, Version) when Version =:= {3,4} ->
+maybe_add_binders(Hello0, {[HRR,MessageHash|_], _}, TicketData, ?TLS_1_3=Version) ->
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} ->
+maybe_add_binders(Hello, _, _, Version) when ?TLS_LTE(Version, ?TLS_1_2) ->
Hello.
create_binders(Context, TicketData) ->
@@ -2615,7 +1678,7 @@ truncate_client_hello(HelloBin0) ->
<<?BYTE(Type), ?UINT24(_Length), Body/binary>> = HelloBin0,
CH0 = #client_hello{
extensions = #{pre_shared_key := PSK0} = Extensions0} =
- tls_handshake:decode_handshake({3,4}, Type, Body),
+ tls_handshake:decode_handshake(?TLS_1_3, Type, Body),
#pre_shared_key_client_hello{offered_psks = OfferedPsks0} = PSK0,
OfferedPsks = OfferedPsks0#offered_psks{binders = []},
PSK = PSK0#pre_shared_key_client_hello{offered_psks = OfferedPsks},
@@ -2628,8 +1691,8 @@ truncate_client_hello(HelloBin0) ->
%% The original length of the binders can still be determined by
%% re-encoding the original ClientHello and using its size as reference
%% when we subtract the size of the truncated binary.
- TruncatedSize = iolist_size(tls_handshake:encode_handshake(CH, {3,4})),
- RefSize = iolist_size(tls_handshake:encode_handshake(CH0, {3,4})),
+ TruncatedSize = iolist_size(tls_handshake:encode_handshake(CH, ?TLS_1_3)),
+ RefSize = iolist_size(tls_handshake:encode_handshake(CH0, ?TLS_1_3)),
BindersSize = RefSize - TruncatedSize,
%% Return the truncated ClientHello by cutting of the binders from the original
@@ -2640,9 +1703,8 @@ truncate_client_hello(HelloBin0) ->
maybe_add_early_data_indication(#client_hello{
extensions = Extensions0} = ClientHello,
EarlyData,
- Version)
- when Version =:= {3,4} andalso
- is_binary(EarlyData) andalso
+ ?TLS_1_3)
+ when is_binary(EarlyData) andalso
byte_size(EarlyData) > 0 ->
Extensions = Extensions0#{early_data =>
#early_data_indication{}},
@@ -2650,6 +1712,17 @@ maybe_add_early_data_indication(#client_hello{
maybe_add_early_data_indication(ClientHello, _, _) ->
ClientHello.
+supported_groups_from_extensions(Extensions) ->
+ case maps:get(elliptic_curves, Extensions, undefined) of
+ #supported_groups{} = Groups->
+ {ok, Groups};
+ %% We do not support legacy for TLS-1.2 in TLS-1.3
+ #elliptic_curves{} ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+ undefined ->
+ {ok, undefined}
+ 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
@@ -2679,32 +1752,6 @@ update_binders(#client_hello{extensions =
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,
- early_data := EarlyData,
- session_tickets := SessionTickets,
- server_name_indication := SNI} = SslOpts0
- } = State0)
- when Version >= {3,4} andalso
- SessionTickets =:= auto ->
- AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
- HashAlgos = cipher_hash_algos(AvailableCipherSuites),
- Ciphers = ciphers_for_early_data(AvailableCipherSuites),
- %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where Ticket0 satisfies
- %% requirements for early_data and session resumption while Ticket2 can only
- %% be used for session resumption.
- EarlyDataSize = early_data_size(EarlyData),
- KeyPair = tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos, SNI, EarlyDataSize),
- UseTicket = choose_ticket(KeyPair, EarlyData),
- 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}.
early_data_size(undefined) ->
undefined;
@@ -2728,138 +1775,17 @@ choose_ticket(_, _) ->
%% here prevents session resumption instead.
undefined.
-maybe_send_early_data(#state{
- handshake_env = #handshake_env{tls_handshake_history = {Hist, _}},
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- session_tickets := SessionTickets,
- early_data := EarlyData} = _SslOpts0
- } = State0) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined ->
- %% D.4. Middlebox Compatibility Mode
- State1 = maybe_queue_change_cipher_spec(State0, last),
- %% Early traffic secret
- EarlyDataSize = early_data_size(EarlyData),
- case get_pre_shared_key_early_data(SessionTickets, UseTicket) of
- {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize ->
- State2 = calculate_client_early_traffic_secret(Hist, PSK, Cipher, HKDF, State1),
- %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
- State3 = ssl_record:step_encryption_state_write(State2),
- {ok, encode_early_data(Cipher, State3)};
- {ok, {_, _, _, MaxSize}} ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {too_much_early_data, {max, MaxSize}})};
- {error, Alert} ->
- {error, Alert}
- end;
-maybe_send_early_data(State) ->
- {ok, State}.
-
-encode_early_data(Cipher,
- #state{
- flight_buffer = Flight0,
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- early_data := EarlyData} = _SslOpts0
- } = State0) ->
- #state{connection_states =
- #{current_write :=
- #{security_parameters := SecurityParameters0} = Write0} = ConnectionStates0} = State0,
- BulkCipherAlgo = ssl_cipher:bulk_cipher_algorithm(Cipher),
- SecurityParameters = SecurityParameters0#security_parameters{
- cipher_type = ?AEAD,
- bulk_cipher_algorithm = BulkCipherAlgo},
- Write = Write0#{security_parameters => SecurityParameters},
- ConnectionStates1 = ConnectionStates0#{current_write => Write},
- {BinEarlyData, ConnectionStates} = tls_record:encode_data([EarlyData], Version, ConnectionStates1),
- State0#state{connection_states = ConnectionStates,
- flight_buffer = Flight0 ++ [BinEarlyData]}.
-
-maybe_send_end_of_early_data(
- #state{
- handshake_env = #handshake_env{early_data_accepted = true},
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- early_data := EarlyData},
- static_env = #static_env{protocol_cb = Connection}
- } = State0) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined ->
- %% EndOfEarlydata is encrypted with the 0-RTT traffic keys
- State1 = Connection:queue_handshake(#end_of_early_data{}, State0),
- %% Use handshake keys after EndOfEarlyData is sent
- ssl_record:step_encryption_state_write(State1);
-maybe_send_end_of_early_data(State) ->
- State.
-
-maybe_check_early_data_indication(EarlyDataIndication,
- #state{
- handshake_env = HsEnv,
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- early_data := EarlyData}
- } = State) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined andalso
- EarlyDataIndication =/= undefined ->
- signal_user_early_data(State, accepted),
- State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}};
-maybe_check_early_data_indication(EarlyDataIndication,
- #state{
- protocol_specific = #{sender := _Sender},
- ssl_options = #{versions := [Version|_],
- use_ticket := UseTicket,
- early_data := EarlyData} = _SslOpts0
- } = State) when Version =:= {3,4} andalso
- UseTicket =/= [undefined] andalso
- EarlyData =/= undefined andalso
- EarlyDataIndication =:= undefined ->
- signal_user_early_data(State, rejected),
- %% Use handshake keys if early_data is rejected.
- ssl_record:step_encryption_state_write(State);
-maybe_check_early_data_indication(_, State) ->
- %% Use handshake keys if there is no early_data.
- ssl_record:step_encryption_state_write(State).
-
-signal_user_early_data(#state{
- connection_env =
- #connection_env{
- user_application = {_, User}},
- static_env =
- #static_env{
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- trackers = Trackers}} = State,
- Result) ->
- CPids = Connection:pids(State),
- SslSocket = Connection:socket(CPids, Transport, Socket, Trackers),
- User ! {ssl, SslSocket, {early_data, Result}}.
-
-handle_early_data(State, enabled, #early_data_indication{}) ->
- %% Accept early data
- HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true},
- State#state{handshake_env = HsEnv};
-handle_early_data(State, _, _) ->
- State.
-
-cipher_hash_algos(Ciphers) ->
- Fun = fun(Cipher) ->
- #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher),
- Hash
- end,
- lists:map(Fun, Ciphers).
-
ciphers_for_early_data(CipherSuites0) ->
- %% Use only supported TLS 1.3 cipher suites
- Supported = lists:filter(fun(CipherSuite) ->
- lists:member(CipherSuite, tls_v1:exclusive_suites(4)) end,
- CipherSuites0),
%% Return supported block cipher algorithms
- lists:map(fun(#{cipher := Cipher}) -> Cipher end,
- lists:map(fun ssl_cipher_format:suite_bin_to_map/1, Supported)).
+ lists:filtermap(fun ciphers_for_early_data0/1, CipherSuites0).
+
+ciphers_for_early_data0(CipherSuite) ->
+ %% Use only supported TLS 1.3 cipher suites
+ case lists:member(CipherSuite, tls_v1:exclusive_suites(?TLS_1_3)) of
+ true -> {true, maps:get(cipher, ssl_cipher_format:suite_bin_to_map(CipherSuite))};
+ false -> false
+ end.
+
get_ticket_data(_, undefined, _) ->
undefined;
@@ -2938,102 +1864,34 @@ path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CR
crl_check := CrlCheck,
log_level := LogLevel,
signature_algs := SignAlgos,
- signature_algs_cert := SignAlgosCert,
- depth := Depth},
+ signature_algs_cert := SignAlgosCert} = Opts,
#{cert_ext := CertExt,
ocsp_responder_certs := OcspResponderCerts,
ocsp_state := OcspState}) ->
- ValidationFunAndState =
- ssl_handshake:validation_fun_and_state(VerifyFun, #{role => Role,
- certdb => CertDbHandle,
- certdb_ref => CertDbRef,
- server_name => ServerName,
- customize_hostname_check =>
- CustomizeHostnameCheck,
- crl_check => CrlCheck,
- crl_db => CRLDbHandle,
- signature_algs => filter_tls13_algs(SignAlgos),
- signature_algs_cert =>
- filter_tls13_algs(SignAlgosCert),
- version => Version,
- issuer => TrustedCert,
- cert_ext => CertExt,
- ocsp_responder_certs => OcspResponderCerts,
- ocsp_state => OcspState
- },
+ ValidationFunAndState =
+ ssl_handshake:validation_fun_and_state(VerifyFun,
+ #{role => Role,
+ certdb => CertDbHandle,
+ certdb_ref => CertDbRef,
+ server_name => ServerName,
+ customize_hostname_check =>
+ CustomizeHostnameCheck,
+ crl_check => CrlCheck,
+ crl_db => CRLDbHandle,
+ signature_algs => filter_tls13_algs(SignAlgos),
+ signature_algs_cert =>
+ filter_tls13_algs(SignAlgosCert),
+ version => Version,
+ issuer => TrustedCert,
+ cert_ext => CertExt,
+ ocsp_responder_certs => OcspResponderCerts,
+ ocsp_state => OcspState
+ },
Path, LogLevel),
- Options = [{max_path_length, Depth},
+ Options = [{max_path_length, maps:get(depth, Opts, ?DEFAULT_DEPTH)},
{verify_fun, ValidationFunAndState}],
public_key:pkix_path_validation(TrustedCert, Path, Options).
-supported_groups_from_extensions(Extensions) ->
- case maps:get(elliptic_curves, Extensions, undefined) of
- #supported_groups{} = Groups->
- {ok, Groups};
- %% We do not support legacy for TLS-1.2 in TLS-1.3
- #elliptic_curves{} ->
- {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
- undefined ->
- {ok, undefined}
- end.
-
-select_server_cert_key_pair(_,[], _,_,_,_, #session{}=Session) ->
- %% Conformant Cert-Key pair with advertised signature algorithm is
- %% selected.
- {ok, Session};
-select_server_cert_key_pair(_,[], _,_,_,_, {fallback, #session{}=Session}) ->
- %% Use fallback Cert-Key pair as no conformant pair to the advertised
- %% signature algorithms was found.
- {ok, Session};
-select_server_cert_key_pair(_,[], _,_,_,_, undefined) ->
- {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_supply_acceptable_cert)};
-select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
- ClientSignAlgs, ClientSignAlgsCert, CertAuths,
- #state{static_env = #static_env{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef} = State},
- Default0) ->
- {_, SignAlgo, SignHash, _, _} = get_certificate_params(Cert),
- %% TODO: We do validate the signature algorithm and signature hash but we could also check
- %% if the signing cert has a key on a curve supported by the client for ECDSA/EDDSA certs
- case check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert) of
- ok ->
- case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
- {ok, EncodeChain} -> %% Chain fullfills certificate_authorities extension
- {ok, Session#session{own_certificates = EncodeChain, private_key = Key}};
- {error, EncodeChain, not_in_auth_domain} ->
- %% If this is the first chain to fulfill the signing requirement, use it as default,
- %% if not later alternative also fulfills certificate_authorities extension
- Default = Session#session{own_certificates = EncodeChain, private_key = Key},
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
- CertAuths, State, default_or_fallback(Default0, Default))
- end;
- _ ->
- %% If the server cannot produce a certificate chain that is signed only
- %% via the indicated supported algorithms, then it SHOULD continue the
- %% handshake by sending the client a certificate chain of its choice
- case SignHash of
- sha ->
- %% According to "Server Certificate Selection - RFC 8446"
- %% Never send cert using sha1 unless client allows it
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
- CertAuths, State, Default0);
- _ ->
- %% If there does not exist a default or fallback from previous alternatives
- %% use this alternative as fallback.
- Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}},
- select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
- CertAuths, State,
- default_or_fallback(Default0, Fallback))
- end
- end.
-
-default_or_fallback(undefined, DefaultOrFallback) ->
- DefaultOrFallback;
-default_or_fallback({fallback, _}, #session{} = Default) ->
- Default;
-default_or_fallback(Default, _) ->
- Default.
-
select_client_cert_key_pair(Session0,
[#{private_key := NoKey, certs := [[]] = NoCerts}],
_,_,_,_,_,_, _) ->
@@ -3048,11 +1906,11 @@ select_client_cert_key_pair(Session, [],_,_,_,_,_,_, undefined) ->
select_client_cert_key_pair(_,[],_,_,_,_,_,_, #session{} = Plausible) ->
%% If we do not find an alternative chain with a cert signed in auth_domain,
%% but have a single cert without chain certs it might be verifiable by
- %% a server that has the means to recreate the chain
+ %% a server that has the means to recreate the chain
Plausible;
select_client_cert_key_pair(Session0, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
- ServerSignAlgs, ServerSignAlgsCert,
- ClientSignAlgs, CertDbHandle, CertDbRef,
+ ServerSignAlgs, ServerSignAlgsCert,
+ ClientSignAlgs, CertDbHandle, CertDbRef,
CertAuths, Plausible0) ->
{PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize, Curve} = get_certificate_params(Cert),
case select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs, Curve) of
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index ff23b2a99b..01f85624bf 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -49,7 +49,7 @@
-export([build_tls_record/1]).
%% Protocol version handling
--export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2,
+-export([protocol_version/1, protocol_version_name/1, lowest_protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, highest_protocol_version/2,
is_higher/2, supported_protocol_versions/0, sufficient_crypto_support/1,
is_acceptable_version/1, is_acceptable_version/2, hello_version/1]).
@@ -130,7 +130,7 @@ get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, SslOpts) -
%
%% Description: Encodes a handshake message to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_handshake(Frag, {3, 4}, ConnectionStates) ->
+encode_handshake(Frag, ?TLS_1_3, ConnectionStates) ->
tls_record_1_3:encode_handshake(Frag, ConnectionStates);
encode_handshake(Frag, Version,
#{current_write :=
@@ -158,7 +158,7 @@ encode_handshake(Frag, Version,
%%
%% Description: Encodes an alert message to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_alert_record(Alert, {3, 4}, ConnectionStates) ->
+encode_alert_record(Alert, ?TLS_1_3, ConnectionStates) ->
tls_record_1_3:encode_alert_record(Alert, ConnectionStates);
encode_alert_record(#alert{level = Level, description = Description},
Version, ConnectionStates) ->
@@ -180,7 +180,7 @@ encode_change_cipher_spec(Version, ConnectionStates) ->
%%
%% Description: Encodes data to send on the ssl-socket.
%%--------------------------------------------------------------------
-encode_data(Data, {3, 4}, ConnectionStates) ->
+encode_data(Data, ?TLS_1_3, ConnectionStates) ->
tls_record_1_3:encode_data(Data, ConnectionStates);
encode_data(Data, Version,
#{current_write := #{beast_mitigation := BeastMitigation,
@@ -207,7 +207,7 @@ encode_data(Data, Version,
%%
%% Description: Decode cipher text
%%--------------------------------------------------------------------
-decode_cipher_text({3,4}, CipherTextRecord, ConnectionStates, _) ->
+decode_cipher_text(?TLS_1_3, CipherTextRecord, ConnectionStates, _) ->
tls_record_1_3:decode_cipher_text(CipherTextRecord, ConnectionStates);
decode_cipher_text(_, CipherTextRecord,
#{current_read :=
@@ -219,7 +219,8 @@ decode_cipher_text(_, CipherTextRecord,
}
} = ConnectionStates0, _) ->
SeqBin = <<?UINT64(Seq)>>,
- #ssl_tls{type = Type, version = {MajVer,MinVer} = Version, fragment = Fragment} = CipherTextRecord,
+ #ssl_tls{type = Type, version = Version, fragment = Fragment} = CipherTextRecord,
+ {MajVer,MinVer} = Version,
StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>,
CipherS = ssl_record:nonce_seed(BulkCipherAlgo, SeqBin, CipherS0),
case ssl_record:decipher_aead(
@@ -272,100 +273,92 @@ decode_cipher_text(_, #ssl_tls{version = Version,
%%====================================================================
%%--------------------------------------------------------------------
--spec protocol_version(tls_atom_version() | tls_version()) ->
- tls_version() | tls_atom_version().
+-spec protocol_version_name(tls_atom_version()) -> tls_version().
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
%%--------------------------------------------------------------------
-protocol_version('tlsv1.3') ->
- {3, 4};
-protocol_version('tlsv1.2') ->
- {3, 3};
-protocol_version('tlsv1.1') ->
- {3, 2};
-protocol_version(tlsv1) ->
- {3, 1};
-protocol_version(sslv3) ->
- {3, 0};
-protocol_version(sslv2) -> %% Backwards compatibility
- {2, 0};
-protocol_version({3, 4}) ->
+protocol_version_name('tlsv1.3') ->
+ ?TLS_1_3;
+protocol_version_name('tlsv1.2') ->
+ ?TLS_1_2;
+protocol_version_name('tlsv1.1') ->
+ ?TLS_1_1;
+protocol_version_name(tlsv1) ->
+ ?TLS_1_0;
+protocol_version_name(sslv3) ->
+ ?SSL_3_0;
+protocol_version_name(sslv2) -> %% Backwards compatibility
+ ?SSL_2_0.
+
+%%--------------------------------------------------------------------
+-spec protocol_version(tls_version()) -> tls_atom_version().
+%%
+%% Description: Creates a protocol version record from a version atom
+%% or vice versa.
+%%--------------------------------------------------------------------
+
+protocol_version(?TLS_1_3) ->
'tlsv1.3';
-protocol_version({3, 3}) ->
+protocol_version(?TLS_1_2) ->
'tlsv1.2';
-protocol_version({3, 2}) ->
+protocol_version(?TLS_1_1) ->
'tlsv1.1';
-protocol_version({3, 1}) ->
+protocol_version(?TLS_1_0) ->
tlsv1;
-protocol_version({3, 0}) ->
+protocol_version(?SSL_3_0) ->
sslv3.
%%--------------------------------------------------------------------
-spec lowest_protocol_version(tls_version(), tls_version()) -> tls_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
-lowest_protocol_version(Version = {M, N}, {M, O}) when N < O ->
- Version;
-lowest_protocol_version({M, _},
- Version = {M, _}) ->
- Version;
-lowest_protocol_version(Version = {M,_},
- {N, _}) when M < N ->
- Version;
-lowest_protocol_version(_,Version) ->
- Version.
+lowest_protocol_version(Version1, Version2) when ?TLS_LT(Version1, Version2) ->
+ Version1;
+lowest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec lowest_protocol_version([tls_version()]) -> tls_version().
%%
%% Description: Lowest protocol version present in a list
%%--------------------------------------------------------------------
-lowest_protocol_version([]) ->
- lowest_protocol_version();
lowest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- lowest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun lowest_protocol_version/2).
%%--------------------------------------------------------------------
-spec highest_protocol_version([tls_version()]) -> tls_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
-highest_protocol_version([]) ->
- highest_protocol_version();
highest_protocol_version(Versions) ->
- [Ver | Vers] = Versions,
- highest_list_protocol_version(Ver, Vers).
+ check_protocol_version(Versions, fun highest_protocol_version/2).
+
+
+check_protocol_version([], Fun) -> check_protocol_version(supported_protocol_versions(), Fun);
+check_protocol_version([Ver | Versions], Fun) -> lists:foldl(Fun, Ver, Versions).
%%--------------------------------------------------------------------
-spec highest_protocol_version(tls_version(), tls_version()) -> tls_version().
%%
%% Description: Highest protocol version of two given versions
%%--------------------------------------------------------------------
-highest_protocol_version(Version = {M, N}, {M, O}) when N > O ->
- Version;
-highest_protocol_version({M, _},
- Version = {M, _}) ->
- Version;
-highest_protocol_version(Version = {M,_},
- {N, _}) when M > N ->
- Version;
-highest_protocol_version(_,Version) ->
- Version.
+highest_protocol_version(Version1, Version2) when ?TLS_GT(Version1, Version2) ->
+ Version1;
+highest_protocol_version(_, Version2) ->
+ Version2.
%%--------------------------------------------------------------------
-spec is_higher(V1 :: tls_version(), V2::tls_version()) -> boolean().
%%
%% Description: Is V1 > V2
%%--------------------------------------------------------------------
-is_higher({M, N}, {M, O}) when N > O ->
+is_higher(V1, V2) when ?TLS_GT(V1, V2) ->
true;
-is_higher({M, _}, {N, _}) when M > N ->
- true;
is_higher(_, _) ->
false.
+
%%--------------------------------------------------------------------
-spec supported_protocol_versions() -> [tls_version()].
%%
@@ -373,7 +366,7 @@ is_higher(_, _) ->
%%--------------------------------------------------------------------
supported_protocol_versions() ->
Fun = fun(Version) ->
- protocol_version(Version)
+ protocol_version_name(Version)
end,
case application:get_env(ssl, protocol_version) of
undefined ->
@@ -399,8 +392,6 @@ supported_protocol_versions([_|_] = Vsns) ->
sufficient_crypto_support(Version) ->
sufficient_crypto_support(crypto:supports(), Version).
-sufficient_crypto_support(CryptoSupport, {_,_} = Version) ->
- sufficient_crypto_support(CryptoSupport, protocol_version(Version));
sufficient_crypto_support(CryptoSupport, Version) when Version == 'tlsv1';
Version == 'tlsv1.1' ->
Hashes = proplists:get_value(hashs, CryptoSupport),
@@ -453,28 +444,28 @@ sufficient_crypto_support(CryptoSupport, 'tlsv1.3') ->
%% {public_keys, eddsa}, %% TODO
{curves, secp256r1}, %% key exchange with secp256r1
{curves, x25519}], %% key exchange with X25519
- lists:all(Fun, L).
+ lists:all(Fun, L);
+sufficient_crypto_support(CryptoSupport, Version) ->
+ sufficient_crypto_support(CryptoSupport, protocol_version(Version)).
+
is_algorithm_supported(CryptoSupport, Group, Algorithm) ->
proplists:get_bool(Algorithm, proplists:get_value(Group, CryptoSupport)).
-spec is_acceptable_version(tls_version()) -> boolean().
-is_acceptable_version({N,_})
- when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION ->
+is_acceptable_version(Version)
+ when ?TLS_1_X(Version) ->
true;
is_acceptable_version(_) ->
false.
-spec is_acceptable_version(tls_version(), Supported :: [tls_version()]) -> boolean().
-is_acceptable_version({N,_} = Version, Versions)
- when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION ->
- lists:member(Version, Versions);
-is_acceptable_version(_,_) ->
- false.
+is_acceptable_version(Version, Versions) ->
+ ?TLS_1_X(Version) andalso lists:member(Version, Versions).
-spec hello_version([tls_version()]) -> tls_version().
-hello_version([Highest|_]) when Highest >= {3,3} ->
- {3,3};
+hello_version([Highest|_]) when ?TLS_GTE(Highest, ?TLS_1_2) ->
+ ?TLS_1_2;
hello_version(Versions) ->
lowest_protocol_version(Versions).
@@ -505,8 +496,9 @@ initial_connection_state(ConnectionEnd, BeastMitigation, MaxEarlyDataSize) ->
}.
%% Used by logging to recreate the received bytes
-build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fragment}) ->
+build_tls_record(#ssl_tls{type = Type, version = Version, fragment = Fragment}) ->
Length = byte_size(Fragment),
+ {MajVer, MinVer} = Version,
<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>.
@@ -520,10 +512,13 @@ decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, undefine
if
5 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0),
- validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ %% TODO: convert the MajVer and MinVer to corresponding macro
+ Version = {MajVer,MinVer},
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
3 =< Size ->
{<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0),
- validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ Version = {MajVer,MinVer},
+ validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, undefined);
1 =< Size ->
{<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0),
validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, undefined, undefined);
@@ -534,10 +529,12 @@ decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, un
if
4 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0),
- validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length);
+ Version = {MajVer,MinVer},
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
2 =< Size ->
{<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0),
- validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined);
+ Version = {MajVer,MinVer},
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, undefined);
true ->
validate_tls_record_version(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, undefined, undefined)
end;
@@ -552,38 +549,36 @@ decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, Ve
decode_tls_records(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length).
+
+%% TODO: separate validation logic from modification of the record
validate_tls_records_type(_Versions, Q, _MaxFragLen, _SslOpts, Acc, undefined, _Version, _Length) ->
{lists:reverse(Acc),
{undefined, Q}};
-validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
- if
- ?KNOWN_RECORD_TYPE(Type) ->
- validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- true ->
- %% Not ?KNOWN_RECORD_TYPE(Type)
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type})
- end.
+validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) when ?KNOWN_RECORD_TYPE(Type) ->
+ validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
+validate_tls_records_type(_Versions, _Q, _MaxFragLen, _SslOpts, _Acc, Type, _Version, _Length) ->
+ %% Not ?KNOWN_RECORD_TYPE(Type)
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type}).
+
validate_tls_record_version(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, undefined, _Length) ->
{lists:reverse(Acc),
{#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}};
-validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
- case Versions of
- _ when is_list(Versions) ->
- case is_acceptable_version(Version, Versions) of
- true ->
- validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
- end;
- {3, 4} when Version =:= {3, 3} ->
- validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- Version ->
- %% Exact version match
+validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) when is_list(Versions) ->
+ case is_acceptable_version(Version, Versions) of
+ true ->
validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
- _ ->
+ false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version})
- end.
+ end;
+validate_tls_record_version(?TLS_1_3=Versions, Q, MaxFragLen, SslOpts, Acc, Type, ?TLS_1_2=Version, Length) ->
+ validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
+validate_tls_record_version(Version, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) ->
+ %% Exact version match
+ validate_tls_record_length(Version, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length);
+validate_tls_record_version(_Versions, _Q, _MaxFragLen, _SslOpts, _Acc, _Type, Version, _Length) ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version}).
+
validate_tls_record_length(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, Version, undefined) ->
{lists:reverse(Acc),
@@ -713,20 +708,20 @@ encode_fragments(Type, Version, [Text|Data],
CipherHeader = <<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>,
encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1,
[[CipherHeader, CipherFragment] | CipherFragments]).
+
+
%%--------------------------------------------------------------------
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 ciphers are
%% not vulnerable to this attack.
-split_iovec(Data, Version, BCA, one_n_minus_one, MaxLength)
- when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
+split_iovec(Data, ?TLS_1_0, BCA, one_n_minus_one, MaxLength)
+ when BCA =/= ?RC4 ->
{Part, RestData} = split_iovec(Data, 1, []),
[Part|split_iovec(RestData, MaxLength)];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_iovec(Data, Version, BCA, zero_n, MaxLength)
- when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse
- {3, 0} == Version) ->
+split_iovec(Data, ?TLS_1_0, BCA, zero_n, MaxLength)
+ when BCA =/= ?RC4 ->
{Part, RestData} = split_iovec(Data, 0, []),
[Part|split_iovec(RestData, MaxLength)];
split_iovec(Data, _Version, _BCA, _BeatMitigation, MaxLength) ->
@@ -747,23 +742,9 @@ split_iovec([], _SplitSize, Acc) ->
{lists:reverse(Acc),[]}.
%%--------------------------------------------------------------------
-lowest_list_protocol_version(Ver, []) ->
- Ver;
-lowest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest).
-
-highest_list_protocol_version(Ver, []) ->
- Ver;
-highest_list_protocol_version(Ver1, [Ver2 | Rest]) ->
- highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest).
-
-highest_protocol_version() ->
- highest_protocol_version(supported_protocol_versions()).
-lowest_protocol_version() ->
- lowest_protocol_version(supported_protocol_versions()).
-max_len([{3,4}|_])->
+max_len([?TLS_1_3|_])->
?TLS13_MAX_CIPHER_TEXT_LENGTH;
max_len(_) ->
?MAX_CIPHER_TEXT_LENGTH.
diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl
index e446e481a7..d2e6ad262f 100644
--- a/lib/ssl/src/tls_record.hrl
+++ b/lib/ssl/src/tls_record.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2023. 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.
@@ -32,7 +32,7 @@
%% Used to handle tls_plain_text, tls_compressed and tls_cipher_text
-record(ssl_tls, {
type,
- version,
+ version :: tls_record:tls_version() | undefined,
fragment,
early_data = false % TLS-1.3
}).
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index 440d9e0998..abd67774ce 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -171,7 +171,7 @@ decode_cipher_text(#ssl_tls{type = ?ALERT,
fragment = <<?FATAL,?ILLEGAL_PARAMETER>>},
ConnectionStates0) ->
{#ssl_tls{type = ?ALERT,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = <<?FATAL,?ILLEGAL_PARAMETER>>}, ConnectionStates0};
%% TLS 1.3 server can receive a User Cancelled Alert when handshake is
%% paused and then cancelled on the client side.
@@ -180,7 +180,7 @@ decode_cipher_text(#ssl_tls{type = ?ALERT,
fragment = <<?FATAL,?USER_CANCELED>>},
ConnectionStates0) ->
{#ssl_tls{type = ?ALERT,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = <<?FATAL,?USER_CANCELED>>}, ConnectionStates0};
%% RFC8446 - TLS 1.3
%% D.4. Middlebox Compatibility Mode
@@ -195,7 +195,7 @@ decode_cipher_text(#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
fragment = <<1>>},
ConnectionStates0) ->
{#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = <<1>>}, ConnectionStates0};
decode_cipher_text(#ssl_tls{type = Type,
version = ?LEGACY_VERSION,
@@ -206,7 +206,7 @@ decode_cipher_text(#ssl_tls{type = Type,
cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
}} = ConnnectionStates0) ->
{#ssl_tls{type = Type,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = CipherFragment}, ConnnectionStates0};
decode_cipher_text(#ssl_tls{type = Type}, _) ->
%% Version mismatch is already asserted
@@ -288,7 +288,7 @@ encode_plain_text(#inner_plaintext{
Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen),
%% 23 (application_data) for outward compatibility
#tls_cipher_text{opaque_type = ?OPAQUE_TYPE,
- legacy_version = {3,3},
+ legacy_version = ?LEGACY_VERSION,
encoded_record = Encoded};
encode_plain_text(#inner_plaintext{
content = Data,
@@ -301,7 +301,7 @@ encode_plain_text(#inner_plaintext{
%% When record protection has not yet been engaged, TLSPlaintext
%% structures are written directly onto the wire.
#tls_cipher_text{opaque_type = Type,
- legacy_version = {3,3},
+ legacy_version = ?TLS_1_2,
encoded_record = Data}.
additional_data(Length) ->
@@ -330,10 +330,11 @@ cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
<<Content/binary, CipherTag/binary>>.
encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
- legacy_version = {MajVer, MinVer},
+ legacy_version = Version,
encoded_record = Encoded},
#{sequence_number := Seq} = Write) ->
Length = erlang:iolist_size(Encoded),
+ {MajVer,MinVer} = Version,
{[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded],
Write#{sequence_number => Seq +1}}.
@@ -375,7 +376,7 @@ decode_inner_plaintext(PlainText) ->
Type =:= ?HANDSHAKE orelse
Type =:= ?ALERT ->
#ssl_tls{type = Type,
- version = {3,4}, %% Internally use real version
+ version = ?TLS_1_3, %% Internally use real version
fragment = init_binary(PlainText)};
_Else ->
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert)
diff --git a/lib/ssl/src/tls_record_1_3.hrl b/lib/ssl/src/tls_record_1_3.hrl
index c6214a5de3..c2624e8fde 100644
--- a/lib/ssl/src/tls_record_1_3.hrl
+++ b/lib/ssl/src/tls_record_1_3.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. 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.
@@ -43,7 +43,7 @@
%% } ContentType;
-define(INVALID, 0).
--define(LEGACY_VERSION, {3,3}).
+-define(LEGACY_VERSION, ?TLS_1_2).
-define(OPAQUE_TYPE, 23).
-record(inner_plaintext, {
@@ -55,7 +55,7 @@
%% decrypted version will still use #ssl_tls for code reuse purposes
%% with real values for content type and version
opaque_type = ?OPAQUE_TYPE,
- legacy_version = ?LEGACY_VERSION,
+ legacy_version = ?LEGACY_VERSION :: ssl_record:ssl_version(),
encoded_record
}).
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index b531b51819..455684bd87 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. 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.
@@ -54,6 +54,8 @@
connection/3,
handshake/3,
death_row/3]).
+%% Tracing
+-export([handle_trace/3]).
-record(static,
{connection_pid,
@@ -566,14 +568,14 @@ send_post_handshake_data(Handshake, From, StateName,
maybe_update_cipher_key(#data{connection_states = ConnectionStates0,
static = Static0} = StateData, #key_update{}) ->
- ConnectionStates = tls_connection_1_3:update_cipher_key(current_write, ConnectionStates0),
+ ConnectionStates = tls_gen_connection_1_3:update_cipher_key(current_write, ConnectionStates0),
Static = Static0#static{bytes_sent = 0},
StateData#data{connection_states = ConnectionStates,
static = Static};
maybe_update_cipher_key(StateData, _) ->
StateData.
-update_bytes_sent(Version, StateData, _) when Version < {3,4} ->
+update_bytes_sent(Version, StateData, _) when ?TLS_LT(Version, ?TLS_1_3) ->
StateData;
%% Count bytes sent in TLS 1.3 for AES-GCM
update_bytes_sent(_, #data{static = #static{key_update_at = seq_num_wrap}} = StateData, _) ->
@@ -586,20 +588,12 @@ update_bytes_sent(_, #data{static = #static{bytes_sent = Sent} = Static} = State
%% approximately 2^-57 for Authenticated Encryption (AE) security. For
%% ChaCha20/Poly1305, the record sequence number would wrap before the
%% safety limit is reached.
-key_update_at(Version, #{security_parameters :=
+key_update_at(?TLS_1_3, #{security_parameters :=
#security_parameters{
- bulk_cipher_algorithm = CipherAlgo}}, KeyUpdateAt)
- when Version >= {3,4} ->
- case CipherAlgo of
- ?AES_GCM ->
- KeyUpdateAt;
- ?CHACHA20_POLY1305 ->
- seq_num_wrap;
- ?AES_CCM ->
- KeyUpdateAt;
- ?AES_CCM_8 ->
- KeyUpdateAt
- end;
+ bulk_cipher_algorithm = ?CHACHA20_POLY1305}}, _KeyUpdateAt) ->
+ seq_num_wrap;
+key_update_at(?TLS_1_3, _, KeyUpdateAt) ->
+ KeyUpdateAt;
key_update_at(_, _, KeyUpdateAt) ->
KeyUpdateAt.
@@ -622,11 +616,11 @@ set_opts(SocketOptions, [{packet, N}]) ->
time_to_rekey(Version, _Data,
#{current_write := #{sequence_number := ?MAX_SEQUENCE_NUMBER}},
- _, _, _) when Version >= {3,4} ->
+ _, _, _) when ?TLS_GTE(Version, ?TLS_1_3) ->
key_update;
-time_to_rekey(Version, _Data, _, _, seq_num_wrap, _) when Version >= {3,4} ->
+time_to_rekey(Version, _Data, _, _, seq_num_wrap, _) when ?TLS_GTE(Version, ?TLS_1_3) ->
false;
-time_to_rekey(Version, Data, _, _, KeyUpdateAt, BytesSent) when Version >= {3,4} ->
+time_to_rekey(Version, Data, _, _, KeyUpdateAt, BytesSent) when ?TLS_GTE(Version, ?TLS_1_3) ->
DataSize = iolist_size(Data),
case (BytesSent + DataSize) > KeyUpdateAt of
true ->
@@ -719,3 +713,51 @@ hibernate_after(connection = StateName,
{next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]};
hibernate_after(StateName, State, Actions) ->
{next_state, StateName, State, Actions}.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(kdt,
+ {call, {?MODULE, time_to_rekey,
+ [_Version, Data, Map, _RenegotiateAt,
+ KeyUpdateAt, BytesSent]}}, Stack) ->
+ #{current_write := #{sequence_number := Sn}} = Map,
+ DataSize = iolist_size(Data),
+ {io_lib:format("~w) (BytesSent:~w + DataSize:~w) > KeyUpdateAt:~w",
+ [Sn, BytesSent, DataSize, KeyUpdateAt]), Stack};
+handle_trace(kdt,
+ {call, {?MODULE, send_post_handshake_data,
+ [{key_update, update_requested}|_]}}, Stack) ->
+ {io_lib:format("KeyUpdate procedure 1/4 - update_requested sent", []), Stack};
+handle_trace(kdt,
+ {call, {?MODULE, send_post_handshake_data,
+ [{key_update, update_not_requested}|_]}}, Stack) ->
+ {io_lib:format("KeyUpdate procedure 3/4 - update_not_requested sent", []), Stack};
+handle_trace(hbn,
+ {call, {?MODULE, connection,
+ [timeout, hibernate | _]}}, Stack) ->
+ {io_lib:format("* * * hibernating * * *", []), Stack};
+handle_trace(hbn,
+ {call, {?MODULE, hibernate_after,
+ [_StateName = connection, State, Actions]}},
+ Stack) ->
+ #data{static=#static{hibernate_after = HibernateAfter}} = State,
+ {io_lib:format("* * * maybe hibernating in ~w ms * * * Actions = ~W ",
+ [HibernateAfter, Actions, 10]), Stack};
+handle_trace(hbn,
+ {return_from, {?MODULE, hibernate_after, 3},
+ {Cmd, Arg,_State, Actions}},
+ Stack) ->
+ {io_lib:format("Cmd = ~w Arg = ~w Actions = ~W", [Cmd, Arg, Actions, 10]), Stack};
+handle_trace(rle,
+ {call, {?MODULE, init, [Type, Opts, _StateData]}}, Stack0) ->
+ {Pid, #{role := Role,
+ socket := _Socket,
+ key_update_at := KeyUpdateAt,
+ erl_dist := IsErlDist,
+ trackers := Trackers,
+ negotiated_version := _Version}} = Opts,
+ {io_lib:format("(*~w) Type = ~w Pid = ~w Trackers = ~w Dist = ~w KeyUpdateAt = ~w",
+ [Role, Type, Pid, Trackers, IsErlDist, KeyUpdateAt]),
+ [{role, Role} | Stack0]}.
diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl
new file mode 100644
index 0000000000..f00cf12d74
--- /dev/null
+++ b/lib/ssl/src/tls_server_connection_1_3.erl
@@ -0,0 +1,914 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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: TLS-1.3 FSM (server side)
+%%----------------------------------------------------------------------
+%% A.2. Server
+%%
+%% START <-----+
+%% Recv ClientHello | | Send HelloRetryRequest
+%% v |
+%% RECVD_CH ----+
+%% | Select parameters
+%% v
+%% NEGOTIATED
+%% | Send ServerHello
+%% | K_send = handshake
+%% | Send EncryptedExtensions
+%% | [Send CertificateRequest]
+%% Can send | [Send Certificate + CertificateVerify]
+%% app data | Send Finished
+%% after --> | K_send = application
+%% here +--------+--------+
+%% No 0-RTT | | 0-RTT
+%% | |
+%% K_recv = handshake | | K_recv = early data
+%% [Skip decrypt errors] | +------> WAIT_EOED -+
+%% | | Recv | | Recv EndOfEarlyData
+%% | | early data | | K_recv = handshake
+%% | +------------+ |
+%% | |
+%% +> WAIT_FLIGHT2 <--------+
+%% |
+%% +--------+--------+
+%% No auth | | Client auth
+%% | |
+%% | v
+%% | WAIT_CERT
+%% | Recv | | Recv Certificate
+%% | empty | v
+%% | Certificate | WAIT_CV
+%% | | | Recv
+%% | v | CertificateVerify
+%% +-> WAIT_FINISHED <---+
+%% | Recv Finished
+%% | K_recv = application
+%% v
+%% CONNECTED
+
+
+-module(tls_server_connection_1_3).
+
+-include_lib("public_key/include/public_key.hrl").
+
+-include("ssl_alert.hrl").
+-include("ssl_connection.hrl").
+-include("tls_connection.hrl").
+-include("tls_handshake.hrl").
+-include("tls_handshake_1_3.hrl").
+
+-behaviour(gen_statem).
+
+%% gen_statem callbacks
+-export([init/1,
+ callback_mode/0,
+ terminate/3,
+ code_change/4,
+ format_status/2]).
+
+%% gen_statem state functions
+-export([config_error/3,
+ initial_hello/3,
+ user_hello/3,
+ start/3,
+ negotiated/3,
+ wait_cert/3,
+ wait_cv/3,
+ wait_finished/3,
+ wait_eoed/3,
+ connection/3,
+ downgrade/3
+ ]).
+
+%--------------------------------------------------------------------
+%% gen_statem callbacks
+%%--------------------------------------------------------------------
+callback_mode() ->
+ [state_functions, state_enter].
+
+init([?SERVER_ROLE, Sender, Host, Port, Socket, Options, User, CbInfo]) ->
+ State0 = #state{protocol_specific = Map} =
+ tls_gen_connection_1_3:initial_state(?SERVER_ROLE, Sender,
+ Host, Port, Socket, Options, User, CbInfo),
+ try
+ State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options, ?SERVER_ROLE, State0),
+ tls_gen_connection:initialize_tls_sender(State),
+ gen_statem:enter_loop(?MODULE, [], initial_hello, State)
+ catch throw:Error ->
+ EState = State0#state{protocol_specific = Map#{error => Error}},
+ gen_statem:enter_loop(?MODULE, [], config_error, EState)
+ end.
+
+terminate({shutdown, {sender_died, Reason}}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_gen_statem:handle_trusted_certs_db(State),
+ tls_gen_connection:close(Reason, Socket, Transport, undefined);
+terminate(Reason, StateName, State) ->
+ ssl_gen_statem:terminate(Reason, StateName, State).
+
+format_status(Type, Data) ->
+ ssl_gen_statem:format_status(Type, Data).
+
+code_change(_OldVsn, StateName, State, _) ->
+ {ok, StateName, State}.
+
+%--------------------------------------------------------------------
+%% state functions
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+-spec initial_hello(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+initial_hello(enter, _, State) ->
+ {keep_state, State};
+initial_hello(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec config_error(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+config_error(enter, _, State) ->
+ {keep_state, State};
+config_error(Type, Event, State) ->
+ ssl_gen_statem:?FUNCTION_NAME(Type, Event, State).
+
+%%--------------------------------------------------------------------
+-spec user_hello(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+user_hello(enter, _, State) ->
+ {keep_state, State};
+user_hello({call, From}, cancel, State) ->
+ gen_statem:reply(From, ok),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
+ ?FUNCTION_NAME, State);
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{handshake_env = #handshake_env{continue_status = {pause, ClientVersions}} = HSEnv,
+ ssl_options = Options0} = State0) ->
+ try ssl:update_options(NewOptions, ?SERVER_ROLE, Options0) of
+ Options = #{versions := Versions} ->
+ State = ssl_gen_statem:ssl_config(Options, ?SERVER_ROLE, State0),
+ case ssl_handshake:select_supported_version(ClientVersions, Versions) of
+ ?TLS_1_3 ->
+ {next_state, start, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{{timeout, handshake}, Timeout, close}]};
+ undefined ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State);
+ _Else ->
+ {next_state, hello, State#state{start_or_recv_from = From,
+ handshake_env = HSEnv#handshake_env{continue_status = continue}},
+ [{change_callback_module, tls_connection},
+ {{timeout, handshake}, Timeout, close}]}
+ end
+ catch
+ throw:{error, Reason} ->
+ gen_statem:reply(From, {error, Reason}),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), ?FUNCTION_NAME, State0)
+ end;
+user_hello(Type, Msg, State) ->
+ tls_gen_connection_1_3:user_hello(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec start(gen_statem:event_type(),
+ #client_hello{} | #change_cipher_spec{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+start(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+start(internal = Type, #change_cipher_spec{} = Msg,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist}} = State) ->
+ case ssl_handshake:init_handshake_history() of
+ Hist -> %% First message must always be client hello
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State);
+ _ ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State)
+ end;
+start(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+start(internal, #client_hello{extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }} = Hello,
+ #state{ssl_options = #{handshake := full}} = State) ->
+ case tls_record:is_acceptable_version(?TLS_1_3, ClientVersions) of
+ true ->
+ handle_client_hello(Hello, State);
+ false ->
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION),
+ ?FUNCTION_NAME, State)
+ end;
+start(internal, #client_hello{extensions = #{client_hello_versions :=
+ #client_hello_versions{versions = ClientVersions}
+ }= Extensions},
+ #state{start_or_recv_from = From,
+ handshake_env = #handshake_env{continue_status = pause} = HSEnv} = State) ->
+ {next_state, user_hello,
+ State#state{start_or_recv_from = undefined,
+ handshake_env = HSEnv#handshake_env{continue_status = {pause, ClientVersions}}},
+ [{postpone, true}, {reply, From, {ok, Extensions}}]};
+start(internal, #client_hello{} = Hello,
+ #state{handshake_env = #handshake_env{continue_status = continue}} = State) ->
+ handle_client_hello(Hello, State);
+start(internal, #client_hello{}, State0) -> %% Missing mandantory TLS-1.3 extensions,
+ %% so it is a previous version hello.
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?FUNCTION_NAME, State0);
+start(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+start(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec negotiated(gen_statem:event_type(),
+ {start_handshake, #pre_shared_key_server_hello{} | undefined} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+negotiated(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+negotiated(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+negotiated(internal, {start_handshake, _} = Message, State0) ->
+ case send_hello_flight(Message, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, negotiated, State0);
+ {State, NextState} ->
+ {next_state, NextState, State, []}
+ end;
+negotiated(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+negotiated(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cert(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cert(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cert(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_cv(gen_statem:event_type(),
+ {start, timeout()} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_cv(internal,
+ #certificate_verify_1_3{} = CertificateVerify, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ State1 = Maybe(tls_handshake_1_3:verify_signature_algorithm(State0,
+ CertificateVerify)),
+ {State, NextState} =
+ Maybe(tls_handshake_1_3:verify_certificate_verify(State1, CertificateVerify)),
+ tls_gen_connection:next_event(NextState, no_record, State)
+ catch
+ {Ref, {#alert{} = Alert, AState}} ->
+ ssl_gen_statem:handle_own_alert(Alert, wait_cv, AState)
+ end;
+wait_cv(Type, Msg, State) ->
+ tls_gen_connection_1_3:wait_cv(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec wait_finished(gen_statem:event_type(),
+ #finished{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_finished(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_finished(internal,
+ #finished{verify_data = VerifyData}, State0) ->
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ Maybe(tls_handshake_1_3:validate_finished(State0, VerifyData)),
+
+ State1 = tls_handshake_1_3:calculate_traffic_secrets(State0),
+ State2 = tls_handshake_1_3:maybe_calculate_resumption_master_secret(State1),
+ State3 = tls_handshake_1_3:forget_master_secret(State2),
+
+ %% Configure traffic keys
+ State4 = ssl_record:step_encryption_state(State3),
+
+ State5 = maybe_send_session_ticket(State4),
+
+ {Record, State} = ssl_gen_statem:prepare_connection(State5, tls_gen_connection),
+ tls_gen_connection:next_event(connection, Record, State,
+ [{{timeout, handshake}, cancel}])
+ catch
+ {Ref, #alert{} = Alert} ->
+ ssl_gen_statem:handle_own_alert(Alert, ?FUNCTION_NAME, State0)
+ end;
+wait_finished(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_finished(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec wait_eoed(gen_statem:event_type(),
+ #end_of_early_data{} | term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+wait_eoed(enter, _, State0) ->
+ State = tls_gen_connection_1_3:handle_middlebox(State0),
+ {next_state, ?FUNCTION_NAME, State,[]};
+wait_eoed(internal = Type, #change_cipher_spec{} = Msg, State) ->
+ tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg, ?FUNCTION_NAME, State);
+wait_eoed(internal, #end_of_early_data{}, #state{handshake_env = HsEnv0} = State0) ->
+ try
+ State = ssl_record:step_encryption_state_read(State0),
+ HsEnv = HsEnv0#handshake_env{early_data_accepted = false},
+ tls_gen_connection:next_event(wait_finished, no_record, State#state{handshake_env = HsEnv})
+ catch
+ error:Reason:ST ->
+ ?SSL_LOG(info, internal_error, [{error, Reason}, {stacktrace, ST}]),
+ ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason),
+ wait_eoed, State0)
+ end;
+wait_eoed(info, Msg, State) ->
+ tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State);
+wait_eoed(Type, Msg, State) ->
+ ssl_gen_statem:handle_common_event(Type, Msg, ?FUNCTION_NAME, State).
+
+%%--------------------------------------------------------------------
+-spec connection(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+connection(Type, Msg, State) ->
+ tls_gen_connection_1_3:connection(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+-spec downgrade(gen_statem:event_type(),
+ term(), #state{}) ->
+ gen_statem:state_function_result().
+%%--------------------------------------------------------------------
+downgrade(Type, Msg, State) ->
+ tls_gen_connection_1_3:downgrade(Type, Msg, State).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
+handle_client_hello(ClientHello, State0) ->
+ case do_handle_client_hello(ClientHello, State0) of
+ #alert{} = Alert ->
+ ssl_gen_statem:handle_own_alert(Alert, start, State0);
+ {State, start} ->
+ {next_state, start, State, []};
+ {State, negotiated, PSK} -> %% Session Resumption with PSK i PSK =/= undefined
+ {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]}
+ end.
+
+do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
+ session_id = SessionId,
+ extensions = Extensions} = Hello,
+ #state{ssl_options = #{ciphers := ServerCiphers,
+ signature_algs := ServerSignAlgs,
+ supported_groups := ServerGroups0,
+ alpn_preferred_protocols := ALPNPreferredProtocols,
+ honor_cipher_order := HonorCipherOrder,
+ early_data := EarlyDataEnabled} = Opts} = State0) ->
+ SNI = maps:get(sni, Extensions, undefined),
+ EarlyDataIndication = maps:get(early_data, Extensions, undefined),
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ ClientGroups0 = Maybe(tls_handshake_1_3:supported_groups_from_extensions(Extensions)),
+ ClientGroups = Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
+ ServerGroups = Maybe(tls_handshake_1_3:get_supported_groups(ServerGroups0)),
+
+ ClientShares = maps:get(key_share, Extensions, []),
+ OfferedPSKs = maps:get(pre_shared_key, Extensions, undefined),
+
+ ClientALPN0 = maps:get(alpn, Extensions, undefined),
+ ClientALPN = ssl_handshake:decode_alpn(ClientALPN0),
+
+ ClientSignAlgs = tls_handshake_1_3:get_signature_scheme_list(
+ maps:get(signature_algs, Extensions, undefined)),
+ ClientSignAlgsCert = tls_handshake_1_3:get_signature_scheme_list(
+ maps:get(signature_algs_cert, Extensions, undefined)),
+ CertAuths = tls_handshake_1_3:get_certificate_authorities(maps:get(certificate_authorities,
+ Extensions, undefined)),
+ Cookie = maps:get(cookie, Extensions, undefined),
+
+ #state{connection_states = ConnectionStates0,
+ session = Session0,
+ connection_env = #connection_env{cert_key_alts = CertKeyAlts}} = State1 =
+ Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)),
+
+ Maybe(validate_cookie(Cookie, State1)),
+
+ %% Handle ALPN extension if ALPN is configured
+ ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)),
+
+ %% If the server does not select a PSK, then the server independently selects a
+ %% cipher suite, an (EC)DHE group and key share for key establishment,
+ %% and a signature algorithm/certificate pair to authenticate itself to
+ %% the client.
+ Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)),
+ Groups = Maybe(tls_handshake_1_3:select_common_groups(ServerGroups, ClientGroups)),
+ Maybe(validate_client_key_share(ClientGroups,
+ ClientShares#key_share_client_hello.client_shares)),
+ CertKeyPairs = ssl_certificate:available_cert_key_pairs(CertKeyAlts, ?TLS_1_3),
+ #session{own_certificates = [Cert|_]} = Session =
+ Maybe(select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs,
+ ClientSignAlgsCert, CertAuths, State0,
+ undefined)),
+ {PublicKeyAlgo, _, _, RSAKeySize, Curve} = tls_handshake_1_3:get_certificate_params(Cert),
+
+ %% Select signature algorithm (used in CertificateVerify message).
+ SelectedSignAlg = Maybe(tls_handshake_1_3:select_sign_algo(PublicKeyAlgo,
+ RSAKeySize, ClientSignAlgs,
+ ServerSignAlgs, Curve)),
+
+ %% Select client public key. If no public key found in ClientShares or
+ %% ClientShares is empty, trigger HelloRetryRequest as we were able
+ %% to find an acceptable set of parameters but the ClientHello does not
+ %% contain sufficient information.
+ {Group, ClientPubKey} = select_client_public_key(Groups, ClientShares),
+
+ %% Generate server_share
+ KeyShare = ssl_cipher:generate_server_share(Group),
+
+ State2 = case maps:get(max_frag_enum, Extensions, undefined) of
+ MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
+ ConnectionStates1 =
+ ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+ HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum},
+ State1#state{handshake_env = HsEnv1,
+ session = Session,
+ connection_states = ConnectionStates1};
+ _ ->
+ State1#state{session = Session}
+ end,
+
+ State3 = case maps:get(keep_secrets, Opts, false) of
+ true -> tls_handshake_1_3:set_client_random(State2, Hello#client_hello.random);
+ false -> State2
+ end,
+
+ State4 = tls_handshake_1_3:update_start_state(State3,
+ #{cipher => Cipher,
+ key_share => KeyShare,
+ session_id => SessionId,
+ group => Group,
+ sign_alg => SelectedSignAlg,
+ peer_public_key => ClientPubKey,
+ alpn => ALPNProtocol}),
+
+ %% 4.1.4. Hello Retry Request
+ %%
+ %% The server will send this message in response to a ClientHello
+ %% 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.
+ case Maybe(send_hello_retry_request(State4, ClientPubKey, KeyShare, SessionId)) of
+ {_, start} = NextStateTuple ->
+ NextStateTuple;
+ {State5, negotiated} ->
+ %% Determine if early data is accepted
+ State = handle_early_data(State5, EarlyDataEnabled, EarlyDataIndication),
+ %% Exclude any incompatible PSKs.
+ PSK = Maybe(tls_handshake_1_3:handle_pre_shared_key(State, OfferedPSKs, Cipher)),
+ Maybe(session_resumption({State, negotiated}, PSK))
+ end
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert
+ end.
+
+send_hello_flight({start_handshake, PSK0},
+ #state{connection_states = ConnectionStates0,
+ handshake_env =
+ #handshake_env{
+ early_data_accepted = EarlyDataAccepted},
+ static_env = #static_env{protocol_cb = Connection},
+ session = #session{session_id = SessionId,
+ ecc = SelectedGroup,
+ dh_public_value = ClientPublicKey},
+ ssl_options = #{} = SslOpts,
+ key_share = KeyShare} = State0) ->
+ ServerPrivateKey = select_server_private_key(KeyShare),
+
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ #security_parameters{prf_algorithm = HKDF} = SecParamsR,
+
+ {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
+ try
+ %% Create server_hello
+ ServerHello = tls_handshake_1_3:server_hello(server_hello, SessionId,
+ KeyShare, PSK0, ConnectionStates0),
+ State1 = Connection:queue_handshake(ServerHello, State0),
+ %% D.4. Middlebox Compatibility Mode
+ State2 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State1, last),
+
+ PSK = tls_handshake_1_3:get_pre_shared_key(PSK0, HKDF),
+
+ State3 =
+ tls_handshake_1_3:calculate_handshake_secrets(ClientPublicKey,
+ ServerPrivateKey, SelectedGroup,
+ PSK, State2),
+ %% Step only write state if early_data is accepted
+ State4 =
+ case EarlyDataAccepted of
+ true ->
+ ssl_record:step_encryption_state_write(State3);
+ false ->
+ %% Read state is overwritten when handshake secrets are set.
+ %% Trial_decryption and early_data_accepted must be set here!
+ update_current_read(
+ ssl_record:step_encryption_state(State3),
+ true, %% trial_decryption
+ false %% early_data_accepted
+ )
+
+ end,
+
+ %% Create EncryptedExtensions
+ EncryptedExtensions = tls_handshake_1_3:encrypted_extensions(State4),
+
+ %% Encode EncryptedExtensions
+ State5 = Connection:queue_handshake(EncryptedExtensions, State4),
+
+ %% Create and send CertificateRequest ({verify, verify_peer})
+ {State6, NextState} = maybe_send_certificate_request(State5, SslOpts, PSK0),
+
+ %% Create and send Certificate (if PSK is undefined)
+ State7 = Maybe(maybe_send_certificate(State6, PSK0)),
+
+ %% Create and send CertificateVerify (if PSK is undefined)
+ State8 = Maybe(maybe_send_certificate_verify(State7, PSK0)),
+
+ %% Create Finished
+ Finished = tls_handshake_1_3:finished(State8),
+
+ %% Encode Finished
+ State9 = Connection:queue_handshake(Finished, State8),
+
+ %% Send first flight
+ {State, _} = Connection:send_handshake_flight(State9),
+
+ {State, NextState}
+
+ catch
+ {Ref, #alert{} = Alert} ->
+ Alert;
+ error:badarg ->
+ ?ALERT_REC(?ILLEGAL_PARAMETER, illegal_parameter_to_compute_key)
+ end.
+
+validate_cookie(_Cookie, #state{ssl_options = #{cookie := false}}) ->
+ ok;
+validate_cookie(undefined, #state{ssl_options = #{cookie := true}}) ->
+ ok;
+validate_cookie(#cookie{cookie = Cookie0}, #state{ssl_options = #{cookie := true},
+ handshake_env =
+ #handshake_env{
+ tls_handshake_history =
+ {[_CH2,_HRR,MessageHash|_], _},
+ cookie_iv_shard = {IV, Shard}}}) ->
+ Cookie = ssl_cipher:decrypt_data(<<"cookie">>, Cookie0, Shard, IV),
+ case Cookie =:= iolist_to_binary(MessageHash) of
+ true ->
+ ok;
+ false ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
+ end;
+validate_cookie(_,_) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}.
+
+session_resumption({#state{ssl_options = #{session_tickets := disabled}} = State, negotiated}, _) ->
+ {ok, {State, negotiated, undefined}}; % Resumption prohibited
+session_resumption({#state{ssl_options = #{session_tickets := Tickets}} = State, negotiated}, undefined = PSK)
+ when Tickets =/= disabled ->
+ {ok, {State, negotiated, PSK}}; % No resumption
+session_resumption({#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{
+ early_data_accepted = false}} = State0, negotiated}, PSKInfo)
+ when Tickets =/= disabled -> % Resumption but early data prohibited
+ State1 = tls_gen_connection_1_3:handle_resumption(State0, ok),
+ {Index, PSK, PeerCert} = PSKInfo,
+ State = maybe_store_peer_cert(State1, PeerCert),
+ State = maybe_store_peer_cert(State1, PeerCert),
+ {ok, {State, negotiated, {Index, PSK}}};
+session_resumption({#state{ssl_options = #{session_tickets := Tickets},
+ handshake_env = #handshake_env{
+ early_data_accepted = true}} = State0, negotiated}, PSKInfo)
+ when Tickets =/= disabled -> % Resumption with early data allowed
+ State1 = tls_gen_connection_1_3:handle_resumption(State0, ok),
+ %% TODO Refactor PSK-tuple {Index, PSK}, index might not be needed.
+ {Index, PSK, PeerCert} = PSKInfo,
+ State2 = tls_handshake_1_3:calculate_client_early_traffic_secret(State1, PSK),
+ %% Set 0-RTT traffic keys for reading early_data
+ State3 = ssl_record:step_encryption_state_read(State2),
+ State4 = maybe_store_peer_cert(State3, PeerCert),
+ State = update_current_read(State4, true, true),
+ {ok, {State, negotiated, {Index, PSK}}}.
+
+maybe_store_peer_cert(State, undefined) ->
+ State;
+maybe_store_peer_cert(#state{session = Session} = State, PeerCert) ->
+ State#state{session = Session#session{peer_certificate = PeerCert}}.
+
+maybe_send_session_ticket(State) ->
+ Number = case application:get_env(ssl, server_session_tickets_amount) of
+ {ok, Size} when is_integer(Size) andalso
+ Size > 0 ->
+ Size;
+ _ ->
+ 3
+ end,
+ maybe_send_session_ticket(State, Number).
+
+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,
+ protocol_cb = Connection}
+ } = 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 = new_session_ticket(Tracker, HKDF, RMS, State0),
+ {State, _} = Connection:send_handshake(Ticket, State0),
+ maybe_send_session_ticket(State, N - 1).
+
+new_session_ticket(Tracker, HKDF, RMS, #state{ssl_options = #{session_tickets := stateful_with_cert},
+ session = #session{peer_certificate = PeerCert}}) ->
+ tls_server_session_ticket:new(Tracker, HKDF, RMS, PeerCert);
+new_session_ticket(Tracker, HKDF, RMS, #state{ssl_options = #{session_tickets := stateless_with_cert},
+ session = #session{peer_certificate = PeerCert}}) ->
+ tls_server_session_ticket:new(Tracker, HKDF, RMS, PeerCert);
+new_session_ticket(Tracker, HKDF, RMS, _) ->
+ tls_server_session_ticket:new(Tracker, HKDF, RMS, undefined).
+
+select_server_cert_key_pair(_,[], _,_,_,_, #session{}=Session) ->
+ %% Conformant Cert-Key pair with advertised signature algorithm is
+ %% selected.
+ {ok, Session};
+select_server_cert_key_pair(_,[], _,_,_,_, {fallback, #session{}=Session}) ->
+ %% Use fallback Cert-Key pair as no conformant pair to the advertised
+ %% signature algorithms was found.
+ {ok, Session};
+select_server_cert_key_pair(_,[], _,_,_,_, undefined) ->
+ {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unable_to_supply_acceptable_cert)};
+select_server_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest],
+ ClientSignAlgs, ClientSignAlgsCert, CertAuths,
+ #state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef} = State},
+ Default0) ->
+ {_, SignAlgo, SignHash, _, _} = tls_handshake_1_3:get_certificate_params(Cert),
+ %% TODO: We do validate the signature algorithm and signature hash
+ %% but we could also check if the signing cert has a key on a
+ %% curve supported by the client for ECDSA/EDDSA certs
+ case tls_handshake_1_3:check_cert_sign_algo(SignAlgo, SignHash,
+ ClientSignAlgs, ClientSignAlgsCert) of
+ ok ->
+ case ssl_certificate:handle_cert_auths(Certs, CertAuths, CertDbHandle, CertDbRef) of
+ {ok, EncodeChain} -> %% Chain fullfills certificate_authorities extension
+ {ok, Session#session{own_certificates = EncodeChain, private_key = Key}};
+ {error, EncodeChain, not_in_auth_domain} ->
+ %% If this is the first chain to fulfill the
+ %% signing requirement, use it as default, if not
+ %% later alternative also fulfills
+ %% certificate_authorities extension
+ Default = Session#session{own_certificates = EncodeChain, private_key = Key},
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State,
+ default_or_fallback(Default0, Default))
+ end;
+ _ ->
+ %% If the server cannot produce a certificate chain that
+ %% is signed only via the indicated supported algorithms,
+ %% then it SHOULD continue the handshake by sending the
+ %% client a certificate chain of its choice
+ case SignHash of
+ sha ->
+ %% According to "Server Certificate Selection -
+ %% RFC 8446" Never send cert using sha1 unless
+ %% client allows it
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State, Default0);
+ _ ->
+ %% If there does not exist a default or fallback
+ %% from previous alternatives use this alternative
+ %% as fallback.
+ Fallback = {fallback, Session#session{own_certificates = Certs, private_key = Key}},
+ select_server_cert_key_pair(Session, Rest, ClientSignAlgs, ClientSignAlgsCert,
+ CertAuths, State,
+ default_or_fallback(Default0, Fallback))
+ end
+ end.
+
+default_or_fallback(undefined, DefaultOrFallback) ->
+ DefaultOrFallback;
+default_or_fallback({fallback, _}, #session{} = Default) ->
+ Default;
+default_or_fallback(Default, _) ->
+ Default.
+
+select_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
+ select_private_key(ServerShare).
+
+select_private_key(#key_share_entry{
+ key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
+ PrivateKey;
+select_private_key(#key_share_entry{
+ key_exchange =
+ {_, PrivateKey}}) ->
+ PrivateKey.
+
+
+select_client_public_key([Group|_] = Groups, ClientShares) ->
+ select_client_public_key(Groups, ClientShares, Group).
+
+select_client_public_key([], _, PreferredGroup) ->
+ {PreferredGroup, no_suitable_key};
+select_client_public_key([Group|Groups],
+ #key_share_client_hello{client_shares = ClientShares} = KeyS,
+ PreferredGroup) ->
+ case lists:keysearch(Group, 2, ClientShares) of
+ {value, #key_share_entry{key_exchange = ClientPublicKey}} ->
+ {Group, ClientPublicKey};
+ false ->
+ select_client_public_key(Groups, KeyS, PreferredGroup)
+ end.
+
+send_hello_retry_request(#state{connection_states = ConnectionStates0,
+ static_env = #static_env{protocol_cb = Connection}} = State0,
+ no_suitable_key, KeyShare, SessionId) ->
+ ServerHello0 = tls_handshake_1_3:server_hello(hello_retry_request, SessionId,
+ KeyShare, undefined, ConnectionStates0),
+ {State1, ServerHello} = tls_handshake_1_3:maybe_add_cookie_extension(State0, ServerHello0),
+
+ State2 = Connection:queue_handshake(ServerHello, State1),
+ %% D.4. Middlebox Compatibility Mode
+ State3 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State2, last),
+ {State4, _} = Connection:send_handshake_flight(State3),
+
+ %% Update handshake history
+ State5 = tls_handshake_1_3:replace_ch1_with_message_hash(State4),
+
+ {ok, {State5, start}};
+send_hello_retry_request(State0, _, _, _) ->
+ %% Suitable key found.
+ {ok, {State0, negotiated}}.
+
+update_current_read(#state{connection_states = CS} = State, TrialDecryption, EarlyDataExpected) ->
+ Read0 = ssl_record:current_connection_state(CS, read),
+ Read = Read0#{trial_decryption => TrialDecryption,
+ early_data_accepted => EarlyDataExpected},
+ State#state{connection_states = CS#{current_read => Read}}.
+
+handle_early_data(State, enabled, #early_data_indication{}) ->
+ %% Accept early data
+ HsEnv = (State#state.handshake_env)#handshake_env{early_data_accepted = true},
+ State#state{handshake_env = HsEnv};
+handle_early_data(State, _, _) ->
+ State.
+
+%% Session resumption with early_data
+maybe_send_certificate_request(#state{
+ handshake_env =
+ #handshake_env{
+ early_data_accepted = true}} = State,
+ _, PSK) when PSK =/= undefined ->
+ %% Go wait for End of Early Data
+ {State, wait_eoed};
+%% 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{static_env = #static_env{protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef}} = State,
+ #{verify := verify_peer,
+ signature_algs := SignAlgs,
+ signature_algs_cert := SignAlgsCert} = Opts, _) ->
+ AddCertAuth = maps:get(certificate_authorities, Opts, true),
+ CertificateRequest = tls_handshake_1_3:certificate_request(SignAlgs, SignAlgsCert, CertDbHandle,
+ CertDbRef, AddCertAuth),
+ {Connection:queue_handshake(CertificateRequest, State), wait_cert}.
+
+maybe_send_certificate(State, PSK) when PSK =/= undefined ->
+ {ok, State};
+maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts},
+ static_env = #static_env{
+ protocol_cb = Connection,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef}} = State, _) ->
+ case tls_handshake_1_3:certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of
+ {ok, Certificate} ->
+ {ok, 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,
+ private_key = CertPrivateKey},
+ static_env = #static_env{protocol_cb = Connection}
+ } = State, _) ->
+ case tls_handshake_1_3:certificate_verify(CertPrivateKey, SignatureScheme, State, server) of
+ {ok, CertificateVerify} ->
+ {ok, Connection:queue_handshake(CertificateVerify, State)};
+ Error ->
+ Error
+ end.
+
+%% RFC 8446 - 4.2.8. Key Share
+%% This vector MAY be empty if the client is requesting a
+%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
+%% group offered in the "supported_groups" extension and MUST appear in
+%% the same order. However, the values MAY be a non-contiguous subset
+%% of the "supported_groups" extension and MAY omit the most preferred
+%% groups.
+%%
+%% Clients can offer as many KeyShareEntry values as the number of
+%% supported groups it is offering, each representing a single set of
+%% key exchange parameters.
+%%
+%% Clients MUST NOT offer multiple KeyShareEntry values
+%% for the same group. Clients MUST NOT offer any KeyShareEntry values
+%% for groups not listed in the client's "supported_groups" extension.
+%% Servers MAY check for violations of these rules and abort the
+%% handshake with an "illegal_parameter" alert if one is violated.
+validate_client_key_share(_ ,[]) ->
+ ok;
+validate_client_key_share([], _) ->
+ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
+validate_client_key_share([Group | ClientGroups], [#key_share_entry{group = Group} | ClientShares]) ->
+ validate_client_key_share(ClientGroups, ClientShares);
+validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) ->
+ validate_client_key_share(ClientGroups, ClientShares).
+
+select_cipher_suite(_, [], _) ->
+ {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher)};
+%% If honor_cipher_order is set to true, use the server's preference for
+%% cipher suite selection.
+select_cipher_suite(true, ClientCiphers, ServerCiphers) ->
+ select_cipher_suite(false, ServerCiphers, ClientCiphers);
+select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) ->
+ case lists:member(Cipher, tls_v1:exclusive_suites(?TLS_1_3)) andalso
+ lists:member(Cipher, ServerCiphers) of
+ true ->
+ {ok, Cipher};
+ false ->
+ select_cipher_suite(false, ClientCiphers, ServerCiphers)
+ end.
+
+%% RFC 7301 - Application-Layer Protocol Negotiation Extension
+%% It is expected that a server will have a list of protocols that it
+%% supports, in preference order, and will only select a protocol if the
+%% client supports it. In that case, the server SHOULD select the most
+%% highly preferred protocol that it supports and that is also
+%% advertised by the client. In the event that the server supports no
+%% protocols that the client advertises, then the server SHALL respond
+%% with a fatal "no_application_protocol" alert.
+handle_alpn(undefined, _) ->
+ {ok, undefined};
+handle_alpn([], _) ->
+ {error, ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)};
+handle_alpn([_|_], undefined) ->
+ {ok, undefined};
+handle_alpn([ServerProtocol|T], ClientProtocols) ->
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true ->
+ {ok, ServerProtocol};
+ false ->
+ handle_alpn(T, ClientProtocols)
+ end.
diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl
index f6b91404fb..a2e5c327a0 100644
--- a/lib/ssl/src/tls_server_session_ticket.erl
+++ b/lib/ssl/src/tls_server_session_ticket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -31,8 +31,8 @@
-include("ssl_cipher.hrl").
%% API
--export([start_link/6,
- new/3,
+-export([start_link/7,
+ new/4,
use/4
]).
@@ -40,6 +40,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, format_status/2]).
+%% Tracing
+-export([handle_trace/3]).
+
-define(SERVER, ?MODULE).
-record(state, {
@@ -54,15 +57,24 @@
%%%===================================================================
%%% API
%%%===================================================================
--spec start_link(term(), atom(), integer(), integer(), integer(), tuple()) -> {ok, Pid :: pid()} |
+-spec start_link(term(), Mode, integer(), integer(), integer(), tuple(), Seed) ->
+ {ok, Pid :: pid()} |
{error, Error :: {already_started, pid()}} |
{error, Error :: term()} |
- ignore.
-start_link(Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay) ->
- gen_server:start_link(?MODULE, [Listener, Mode, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay], []).
-
-new(Pid, Prf, MasterSecret) ->
- gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret}, infinity).
+ ignore
+ when Mode :: stateful | stateless | stateful_with_cert | stateless_with_cert,
+ Seed :: undefined | binary().
+start_link(Listener, Mode1, Lifetime, TicketStoreSize, MaxEarlyDataSize, AntiReplay, Seed) ->
+ Mode = case Mode1 of
+ stateful_with_cert -> stateful;
+ stateless_with_cert -> stateless;
+ _ -> Mode1
+ end,
+ gen_server:start_link(?MODULE, [Listener, Mode, Lifetime, TicketStoreSize,
+ MaxEarlyDataSize, AntiReplay, Seed], []).
+
+new(Pid, Prf, MasterSecret, PeerCert) ->
+ gen_server:call(Pid, {new_session_ticket, Prf, MasterSecret, PeerCert}, infinity).
use(Pid, Identifiers, Prf, HandshakeHist) ->
gen_server:call(Pid, {use_ticket, Identifiers, Prf, HandshakeHist},
@@ -76,12 +88,12 @@ use(Pid, Identifiers, Prf, HandshakeHist) ->
init([Listener | Args]) ->
process_flag(trap_exit, true),
Monitor = inet:monitor(Listener),
- State = inital_state(Args),
+ State = initial_state(Args),
{ok, State#state{listen_monitor = Monitor}}.
-spec handle_call(Request :: term(), From :: {pid(), term()}, State :: term()) ->
{reply, Reply :: term(), NewState :: term()} .
-handle_call({new_session_ticket, Prf, MasterSecret}, _From,
+handle_call({new_session_ticket, Prf, MasterSecret, PeerCert}, _From,
#state{nonce = Nonce,
lifetime = LifeTime,
max_early_data_size = MaxEarlyDataSize,
@@ -89,14 +101,14 @@ handle_call({new_session_ticket, Prf, MasterSecret}, _From,
Id = stateful_psk_ticket_id(IdGen),
PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf),
SessionTicket = new_session_ticket(Id, Nonce, LifeTime, MaxEarlyDataSize),
- State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0),
+ State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, PeerCert, State0),
{reply, SessionTicket, State};
-handle_call({new_session_ticket, Prf, MasterSecret}, _From,
+handle_call({new_session_ticket, Prf, MasterSecret, PeerCert}, _From,
#state{nonce = Nonce,
stateless = #{}} = State) ->
BaseSessionTicket = new_session_ticket_base(State),
SessionTicket = generate_stateless_ticket(BaseSessionTicket, Prf,
- MasterSecret, State),
+ MasterSecret, PeerCert, State),
{reply, SessionTicket, State#state{nonce = Nonce+1}};
handle_call({use_ticket, Identifiers, Prf, HandshakeHist}, _From,
#state{stateful = #{}} = State0) ->
@@ -118,10 +130,13 @@ handle_cast(_Request, State) ->
{noreply, NewState :: term()}.
handle_info(rotate_bloom_filters,
#state{stateless = #{bloom_filter := BloomFilter0,
+ warm_up_windows_remaining := WarmUp0,
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}}};
+ WarmUp = max(WarmUp0 - 1, 0),
+ {noreply, State#state{stateless = Stateless#{bloom_filter => BloomFilter,
+ warm_up_windows_remaining => WarmUp}}};
handle_info({'DOWN', Monitor, _, _, _}, #state{listen_monitor = Monitor} = State) ->
{stop, normal, State};
handle_info(_Info, State) ->
@@ -144,29 +159,28 @@ code_change(_OldVsn, State, _Extra) ->
Status :: list()) -> Status :: term().
format_status(_Opt, Status) ->
Status.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
-
-inital_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined]) ->
+initial_state([stateless, Lifetime, _, MaxEarlyDataSize, undefined, Seed]) ->
#state{nonce = 0,
- stateless = #{seed => {crypto:strong_rand_bytes(16),
- crypto:strong_rand_bytes(32)},
+ stateless = #{seed => stateless_seed(Seed),
window => undefined},
lifetime = Lifetime,
max_early_data_size = MaxEarlyDataSize
};
-inital_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}]) ->
+initial_state([stateless, Lifetime, _, MaxEarlyDataSize, {Window, K, M}, Seed]) ->
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)},
+ warm_up_windows_remaining => warm_up_windows(Seed),
+ seed => stateless_seed(Seed),
window => Window},
lifetime = Lifetime,
max_early_data_size = MaxEarlyDataSize
};
-inital_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) ->
+initial_state([stateful, Lifetime, TicketStoreSize, MaxEarlyDataSize|_]) ->
%% statfeful servers replay
%% protection is that it saves
%% all valid tickets
@@ -229,18 +243,18 @@ validate_binder(Binder, HandshakeHist, PSK, Prf, AlertDetail) ->
stateful_store() ->
gb_trees:empty().
-stateful_ticket_store(Ref, NewSessionTicket, Hash, Psk,
+stateful_ticket_store(Ref, NewSessionTicket, Hash, Psk, PeerCert,
#state{nonce = Nonce,
stateful = #{db := Tree0,
max := Max,
ref_index := Index0} = Stateful}
= State0) ->
Id = {erlang:monotonic_time(), erlang:unique_integer([monotonic])},
- StatefulTicket = {NewSessionTicket, Hash, Psk},
+ StatefulTicket = {NewSessionTicket, Hash, Psk, PeerCert},
case gb_trees:size(Tree0) of
Max ->
%% Trow away oldest ticket
- {_, {#new_session_ticket{ticket = OldRef},_,_}, Tree1}
+ {_, {#new_session_ticket{ticket = OldRef},_,_,_}, Tree1}
= gb_trees:take_smallest(Tree0),
Tree = gb_trees:insert(Id, StatefulTicket, Tree1),
Index = maps:without([OldRef], Index0),
@@ -272,8 +286,8 @@ stateful_use([#psk_identity{identity = Ref} | Refs], [Binder | Binders],
HandshakeHist, Tree0) of
true ->
RefIndex = maps:without([Ref], RefIndex0),
- {{_,_, PSK}, Tree} = gb_trees:take(Key, Tree0),
- {{ok, {Index, PSK}},
+ {{_,_, PSK, PeerCert}, Tree} = gb_trees:take(Key, Tree0),
+ {{ok, {Index, PSK, PeerCert}},
State#state{stateful = Stateful#{db => Tree,
ref_index => RefIndex}}};
false ->
@@ -291,7 +305,7 @@ stateful_usable_ticket(Key, Prf, Binder, HandshakeHist, Tree) ->
case gb_trees:lookup(Key, Tree) of
none ->
false;
- {value, {NewSessionTicket, Prf, PSK}} ->
+ {value, {NewSessionTicket, Prf, PSK, _PeerCert}} ->
case stateful_living_ticket(Key, NewSessionTicket) of
true ->
validate_binder(Binder, HandshakeHist, PSK, Prf, stateful);
@@ -323,7 +337,7 @@ stateful_psk_ticket_id(Key) ->
generate_stateless_ticket(#new_session_ticket{ticket_nonce = Nonce,
ticket_age_add = TicketAgeAdd,
ticket_lifetime = Lifetime}
- = Ticket, Prf, MasterSecret,
+ = Ticket, Prf, MasterSecret, PeerCert,
#state{stateless = #{seed := {IV, Shard}}}) ->
PSK = tls_v1:pre_shared_key(MasterSecret, Nonce, Prf),
Timestamp = erlang:system_time(second),
@@ -332,7 +346,8 @@ generate_stateless_ticket(#new_session_ticket{ticket_nonce = Nonce,
pre_shared_key = PSK,
ticket_age_add = TicketAgeAdd,
lifetime = Lifetime,
- timestamp = Timestamp
+ timestamp = Timestamp,
+ certificate = PeerCert
}, Shard, IV),
Ticket#new_session_ticket{ticket = Encrypted}.
@@ -351,11 +366,12 @@ stateless_use([#psk_identity{identity = Encrypted,
window := Window}} = State) ->
case ssl_cipher:decrypt_ticket(Encrypted, Shard, IV) of
#stateless_ticket{hash = Prf,
- pre_shared_key = PSK} = Ticket ->
+ pre_shared_key = PSK,
+ certificate = PeerCert} = Ticket ->
case stateless_usable_ticket(Ticket, ObfAge, Binder,
HandshakeHist, Window) of
true ->
- stateless_anti_replay(Index, PSK, Binder, State);
+ stateless_anti_replay(Index, PSK, Binder, PeerCert, State);
false ->
stateless_use(Ids, Binders, Prf, HandshakeHist,
Index+1, State);
@@ -382,19 +398,62 @@ stateless_usable_ticket(#stateless_ticket{hash = Prf,
stateless_living_ticket(0, _, _, _, _) ->
true;
+%% If `anti_replay` is not enabled, then a ticket is considered to be living
+%% if it has not exceeded its lifetime.
+%%
+%% If `anti_replay` is enabled, we must additionally perform a freshness check
+%% as is outlined in section 8.3 Freshness Checks - RFC 8446
stateless_living_ticket(ObfAge, TicketAgeAdd, Lifetime, Timestamp, Window) ->
- ReportedAge = ObfAge - TicketAgeAdd,
+ %% RealAge is the server's view of the age of the ticket in seconds.
RealAge = erlang:system_time(second) - Timestamp,
+
+ %% ReportedAge is the client's view of the age of the ticket in milliseconds.
+ ReportedAge = ObfAge - TicketAgeAdd,
+
+ %% DeltaAge is the difference of the client's view of the age of the ticket
+ %% and the server's view of the age of the ticket in seconds.
+ DeltaAge = abs(RealAge - (ReportedAge / 1000)),
+
+ %% We ensure that both the client's view of the age of the ticket and the
+ %% server's view of the age of the ticket do not exceed the lifetime specified.
(ReportedAge =< Lifetime * 1000)
andalso (RealAge =< Lifetime)
- andalso (in_window(RealAge, Window)).
+ andalso (in_window(DeltaAge, Window)).
in_window(_, undefined) ->
true;
+%% RFC 8446 - section 8.2 Client Hello Recording
+%% describes an anti-replay implementation that can use bounded memory
+%% by storing a unique value from a ClientHello (in our case the PSK binder)
+%% withing a given time window.
+%%
+%% In order implement this, when a ClientHello is received, the server
+%% must ensure that a ClientHello has been sent relatively recently.
+%% We do this by ensuring that the client and server view of the age
+%% of the ticket is not larger than our recording window.
+%%
+%% In the case of an attempted replay attack, there are 2 possible
+%% outcomes:
+%% - A ClientHello is replayed within the recording window
+%% * The ticket looks valid, `in_window` returns true
+%% so we proceed to check the unique value
+%% * The unique value (PSK Binder) is stored in the bloom filter
+%% and we reject the ticket.
+%%
+%% - A ClientHello is replayed outside the recording window
+%% * We reject the ticket as `in_window` returns false.
in_window(Age, Window) when is_integer(Window) ->
Age =< Window.
-stateless_anti_replay(Index, PSK, Binder,
+stateless_anti_replay(_Index, _PSK, _Binder, _PeerCert,
+ #state{stateless = #{warm_up_windows_remaining := WarmUpRemaining}
+ } = State) when WarmUpRemaining > 0 ->
+ %% Reject all tickets during the warm-up period:
+ %% RFC 8446 8.2 Client Hello Recording
+ %% "When implementations are freshly started, they SHOULD reject 0-RTT as
+ %% long as any portion of their recording window overlaps the startup time."
+ {{ok, undefined}, State};
+stateless_anti_replay(Index, PSK, Binder, PeerCert,
#state{stateless = #{bloom_filter := BloomFilter0}
= Stateless} = State) ->
case tls_bloom_filter:contains(BloomFilter0, Binder) of
@@ -403,8 +462,110 @@ stateless_anti_replay(Index, PSK, Binder,
{{ok, undefined}, State};
false ->
BloomFilter = tls_bloom_filter:add_elem(BloomFilter0, Binder),
- {{ok, {Index, PSK}},
+ {{ok, {Index, PSK, PeerCert}},
State#state{stateless = Stateless#{bloom_filter => BloomFilter}}}
end;
-stateless_anti_replay(Index, PSK, _, State) ->
- {{ok, {Index, PSK}}, State}.
+stateless_anti_replay(Index, PSK, _Binder, PeerCert, State) ->
+ {{ok, {Index, PSK, PeerCert}}, State}.
+
+-spec stateless_seed(Seed :: undefined | binary()) ->
+ {IV :: binary(), Shard :: binary()}.
+stateless_seed(undefined) ->
+ {crypto:strong_rand_bytes(16), crypto:strong_rand_bytes(32)};
+stateless_seed(Seed) ->
+ <<IV:16/binary, Shard:32/binary, _/binary>> = crypto:hash(sha512, Seed),
+ {IV, Shard}.
+
+-spec warm_up_windows(Seed :: undefined | binary()) -> 0 | 2.
+warm_up_windows(undefined) ->
+ 0;
+warm_up_windows(_) ->
+ %% When the encryption seed is specified, "warm up" the bloom filter for
+ %% 2*WindowSize to ensure tickets from a previous instance of the server
+ %% (before a restart) cannot be reused, if the ticket encryption seed is reused.
+ 2.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(rle, {call, {?MODULE, init, [[ListenSocket, Mode, Lifetime,
+ StoreSize | _T]]}}, Stack) ->
+ {io_lib:format("(*server) ([ListenSocket = ~w Mode = ~w Lifetime = ~w "
+ "StoreSize = ~w, ...])",
+ [ListenSocket, Mode, Lifetime, StoreSize]),
+ [{role, server} | Stack]};
+handle_trace(ssn,
+ {call, {?MODULE, terminate, [Reason, _State]}}, Stack) ->
+ {io_lib:format("(Reason ~w)", [Reason]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, handle_call,
+ [CallTuple, _From, _State]}}, Stack) ->
+ {io_lib:format("(Call = ~w)", [element(1, CallTuple)]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE,handle_call,
+ [{Call = use_ticket,
+ {offered_psks,
+ [{psk_identity, PskIdentity, _ObfAge}],
+ [Binder]},
+ _Prf,
+ _HandshakeHist}, _From, _State]}}, Stack) ->
+ {io_lib:format("(Call = ~w PskIdentity = ~W Binder = ~W)",
+ [Call, PskIdentity, 5, Binder, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, validate_binder,
+ [Binder, _HandshakeHist, PSK, _Prf, _AlertDetail]}}, Stack) ->
+ {io_lib:format("(Binder = ~W PSK = ~W)", [Binder, 5, PSK, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, initial_state,
+ [[Mode, _Lifetime, _StoreSize,_MaxEarlyDataSize,
+ Window, Seed]]}}, Stack) ->
+ {io_lib:format("(Mode = ~w Window = ~w Seed = ~W)", [Mode, Window, Seed, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, generate_stateless_ticket,
+ [_BaseTicket, _Prf, MasterSecret, _State]}}, Stack) ->
+ {io_lib:format("(MasterSecret = ~W)", [MasterSecret, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, stateless_use,
+ [[{psk_identity, Encrypted, _ObfAge} | _],
+ [Binder | _],
+ _Prf, _HandshakeHist, _Index, _State]}}, Stack) ->
+ {io_lib:format("(Encrypted = ~W Binder = ~W)", [Encrypted, 5, Binder, 5]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, in_window, [RealAge, Window]}}, Stack) ->
+ {io_lib:format("(RealAge = ~w Window = ~w)",
+ [RealAge, Window]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, stateless_usable_ticket,
+ [{stateless_ticket, _Prf, _PreSharedKey, _TicketAgeAdd,
+ _Lifetime, _Timestamp}, _ObfAge, Binder,
+ _HandshakeHist, Window]}}, Stack) ->
+ {io_lib:format("(Binder = ~W Window = ~w)", [Binder, 5, Window]), Stack};
+handle_trace(ssn,
+ {call, {?MODULE, stateless_anti_replay,
+ [_Index, PSK, _Binder, _State]}}, Stack) ->
+ {io_lib:format("(PSK = ~W)", [PSK, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, stateless_use, 6},
+ {{ok, {_Index, PSK}}, _State}}, Stack) ->
+ {io_lib:format("PSK = ~W", [PSK, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, generate_stateless_ticket, 4},
+ {new_session_ticket, _LifeTime, _AgeAdd, _Nonce, Ticket,
+ _Extensions}}, Stack) ->
+ {io_lib:format("Ticket = ~W", [Ticket, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, initial_state, 1},
+ {state, _Stateless = #{seed := {IV, Shard}, window := Window},
+ _Stateful = undefined, _Nonce, _Lifetime ,
+ _MaxEarlyDataSize, ListenMonitor}}, Stack) ->
+ {io_lib:format("IV = ~W Shard = ~W Window = ~w ListenMonitor = ~w",
+ [IV, 5, Shard, 5, Window, ListenMonitor]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, stateless_anti_replay, 4},
+ {{ok, {_Index, PSK}}, _State}}, Stack) ->
+ {io_lib:format("ticket OK ~W", [PSK, 5]), Stack};
+handle_trace(ssn,
+ {return_from, {?MODULE, stateless_anti_replay, 4},
+ Return}, Stack) ->
+ {io_lib:format("ticket REJECTED ~W", [Return, 5]), Stack}.
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index 779043f1de..fd919f7356 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2023. 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.
@@ -23,6 +23,7 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
+-include("ssl_record.hrl").
-export([send/3,
listen/3,
@@ -250,46 +251,49 @@ internal_inet_values() ->
default_inet_values() ->
[{packet_size, 0}, {packet,0}, {header, 0}, {active, true}, {mode, list}].
-inherit_tracker(ListenSocket, EmOpts, #{erl_dist := false} = SslOpts) ->
- ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts, SslOpts]);
inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) ->
- ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]).
+ ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]);
+inherit_tracker(ListenSocket, EmOpts, SslOpts) ->
+ ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts, SslOpts]).
-session_tickets_tracker(_,_, _, _, #{erl_dist := false,
- session_tickets := disabled}) ->
- {ok, disabled};
-session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize,
- #{erl_dist := false,
- session_tickets := Mode,
- anti_replay := AntiReplay}) ->
- tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime,
- TicketStoreSize, MaxEarlyDataSize, AntiReplay]);
session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize,
#{erl_dist := true,
session_tickets := Mode,
- anti_replay := AntiReplay}) ->
+ anti_replay := AntiReplay,
+ stateless_tickets_seed := Seed}) ->
SupName = tls_server_session_ticket_sup:sup_name(dist),
Children = supervisor:count_children(SupName),
Workers = proplists:get_value(workers, Children),
case Workers of
0 ->
tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime,
- TicketStoreSize, MaxEarlyDataSize, AntiReplay]);
+ TicketStoreSize, MaxEarlyDataSize,
+ AntiReplay, Seed]);
1 ->
[{_,Child,_, _}] = supervisor:which_children(SupName),
{ok, Child}
- end.
-session_id_tracker(_, #{versions := [{3,4}]}) ->
+ end;
+session_tickets_tracker(_,_, _, _, #{session_tickets := disabled}) ->
+ {ok, disabled};
+session_tickets_tracker(ListenSocket, Lifetime, TicketStoreSize, MaxEarlyDataSize,
+ #{session_tickets := Mode,
+ anti_replay := AntiReplay,
+ stateless_tickets_seed := Seed}) ->
+ tls_server_session_ticket_sup:start_child([ListenSocket, Mode, Lifetime,
+ TicketStoreSize, MaxEarlyDataSize,
+ AntiReplay, Seed]).
+
+session_id_tracker(_, #{versions := [?TLS_1_3]}) ->
{ok, not_relevant};
%% Regardless of the option reuse_sessions we need the session_id_tracker
%% to generate session ids, but no sessions will be stored unless
%% reuse_sessions = true.
-session_id_tracker(ssl_unknown_listener, #{erl_dist := false}) ->
- ssl_upgrade_server_session_cache_sup:start_child(normal);
-session_id_tracker(ListenSocket, #{erl_dist := false}) ->
- ssl_server_session_cache_sup:start_child(ListenSocket);
session_id_tracker(_, #{erl_dist := true}) ->
- ssl_upgrade_server_session_cache_sup:start_child(dist).
+ ssl_upgrade_server_session_cache_sup:start_child(dist);
+session_id_tracker(ssl_unknown_listener, _) ->
+ ssl_upgrade_server_session_cache_sup:start_child(normal);
+session_id_tracker(ListenSocket, _) ->
+ ssl_server_session_cache_sup:start_child(ListenSocket).
get_emulated_opts(TrackerPid) ->
call(TrackerPid, get_emulated_opts).
@@ -391,9 +395,10 @@ code_change(_OldVsn, State, _Extra) ->
call(Pid, Msg) ->
gen_server:call(Pid, Msg, infinity).
-start_tls_server_connection(#{sender_spawn_opts := SenderOpts} = SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) ->
+start_tls_server_connection(SslOpts, ConnectionCb, Transport, Port, Socket, EmOpts, Trackers, CbInfo) ->
try
{ok, DynSup} = tls_connection_sup:start_child([]),
+ SenderOpts = maps:get(sender_spawn_opts, SslOpts, []),
{ok, Sender} = tls_dyn_connection_sup:start_child(DynSup, sender, [[{spawn_opt, SenderOpts}]]),
ConnArgs = [server, Sender, "localhost", Port, Socket,
{SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo],
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index dd891967bc..7404365520 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -29,13 +29,13 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
--export([master_secret/4,
- finished/5,
- certificate_verify/3,
- mac_hash/7,
+-export([master_secret/4,
+ finished/5,
+ certificate_verify/2,
+ mac_hash/7,
hmac_hash/3,
- setup_keys/8,
- suites/1,
+ setup_keys/8,
+ suites/1,
exclusive_suites/1,
exclusive_anonymous_suites/1,
psk_suites/1,
@@ -44,52 +44,54 @@
srp_suites/1,
srp_suites_anon/1,
srp_exclusive/1,
- rc4_suites/1,
+ rc4_suites/1,
rc4_exclusive/1,
des_suites/1,
des_exclusive/1,
rsa_suites/1,
rsa_exclusive/1,
prf/5,
- ecc_curves/1,
- ecc_curves/2,
- oid_to_enum/1,
- enum_to_oid/1,
- default_signature_algs/1,
+ ecc_curves/1,
+ oid_to_enum/1,
+ enum_to_oid/1,
+ default_signature_algs/1,
signature_algs/2,
signature_schemes/2,
rsa_schemes/0,
- groups/1,
- groups/2,
- group_to_enum/1,
- enum_to_group/1,
- default_groups/1]).
-
--export([derive_secret/4,
- hkdf_expand_label/5,
- hkdf_extract/3,
+ groups/0,
+ groups/1,
+ group_to_enum/1,
+ enum_to_group/1,
+ default_groups/0]).
+
+-export([derive_secret/4,
+ hkdf_expand_label/5,
+ hkdf_extract/3,
hkdf_expand/4,
key_length/1,
- key_schedule/3,
- key_schedule/4,
+ key_schedule/3,
+ key_schedule/4,
create_info/3,
- external_binder_key/2,
+ external_binder_key/2,
resumption_binder_key/2,
- client_early_traffic_secret/3,
+ client_early_traffic_secret/3,
early_exporter_master_secret/3,
- client_handshake_traffic_secret/3,
+ client_handshake_traffic_secret/3,
server_handshake_traffic_secret/3,
- client_application_traffic_secret_0/3,
+ client_application_traffic_secret_0/3,
server_application_traffic_secret_0/3,
- exporter_master_secret/3,
+ exporter_master_secret/3,
resumption_master_secret/3,
- update_traffic_secret/2,
+ 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]).
+%% Tracing
+-export([handle_trace/3]).
+
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 |
@@ -115,7 +117,7 @@ derive_secret(Secret, Label, Messages, Algo) ->
Hash, ssl_cipher:hash_size(Algo), Algo).
-spec hkdf_expand_label(Secret::binary(), Label0::binary(),
- Context::binary(), Length::integer(),
+ Context::binary(), Length::integer(),
Algo::ssl:hash()) -> KeyingMaterial::binary().
hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
HkdfLabel = create_info(Label0, Context, Length),
@@ -140,14 +142,14 @@ create_info(Label0, Context0, Length) ->
-spec hkdf_extract(MacAlg::ssl:hash(), Salt::binary(),
KeyingMaterial::binary()) -> PseudoRandKey::binary().
-hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
+hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
hmac_hash(MacAlg, Salt, KeyingMaterial).
-spec hkdf_expand(PseudoRandKey::binary(), ContextInfo::binary(),
Length::integer(), Algo::ssl:hash()) -> KeyingMaterial::binary().
-
-hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
+
+hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)),
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>).
@@ -170,10 +172,15 @@ master_secret(PrfAlgo, PreMasterSecret, ClientRandom, ServerRandom) ->
[ClientRandom, ServerRandom], 48).
%% TLS 1.0 -1.2 ---------------------------------------------------
--spec finished(client | server, integer(), integer(), binary(), [binary()]) -> binary().
+-spec finished(Role, Version, PrfAlgo, MasterSecret, Handshake) -> binary() when
+ Role :: client | server,
+ Version :: ssl_record:ssl_version(),
+ PrfAlgo :: integer(),
+ MasterSecret :: binary(),
+ Handshake :: [binary()].
%% TLS 1.0 -1.1 ---------------------------------------------------
finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
- when Version == 1; Version == 2; PrfAlgo == ?MD5SHA ->
+ when Version == ?TLS_1_0; Version == ?TLS_1_1; PrfAlgo == ?MD5SHA ->
%% RFC 2246 & 4346 - 7.4.9. Finished
%% struct {
%% opaque verify_data[12];
@@ -188,8 +195,7 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
%% TLS 1.0 -1.1 ---------------------------------------------------
%% TLS 1.2 ---------------------------------------------------
-finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
- when Version == 3 ->
+finished(Role, ?TLS_1_2, PrfAlgo, MasterSecret, Handshake) ->
%% RFC 5246 - 7.4.9. Finished
%% struct {
%% opaque verify_data[12];
@@ -203,30 +209,29 @@ finished(Role, Version, PrfAlgo, MasterSecret, Handshake)
%% TODO 1.3 finished
--spec certificate_verify(md5sha | sha, integer(), [binary()]) -> binary().
-
+-spec certificate_verify(HashAlgo, Handshake) -> binary() when
+ HashAlgo :: md5sha | ssl:hash(),
+ Handshake :: [binary()].
%% TLS 1.0 -1.1 ---------------------------------------------------
-certificate_verify(md5sha, _Version, Handshake) ->
+certificate_verify(md5sha, Handshake) ->
MD5 = crypto:hash(md5, Handshake),
SHA = crypto:hash(sha, Handshake),
{digest, <<MD5/binary, SHA/binary>>};
%% TLS 1.0 -1.1 ---------------------------------------------------
%% TLS 1.2 ---------------------------------------------------
-certificate_verify(_HashAlgo, _Version, Handshake) ->
+certificate_verify(_HashAlgo, Handshake) ->
%% crypto:hash(HashAlgo, Handshake).
%% Optimization: Let crypto calculate the hash in sign/verify call
Handshake.
%% TLS 1.2 ---------------------------------------------------
-
--spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(),
+-spec setup_keys(ssl_record:ssl_version(), integer(), binary(), binary(), binary(), integer(),
integer(), integer()) -> {binary(), binary(), binary(),
binary(), binary(), binary()}.
%% TLS v1.0 ---------------------------------------------------
-setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
- KeyMatLen, IVSize)
- when Version == 1 ->
+setup_keys(?TLS_1_0, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
%% RFC 2246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -251,9 +256,8 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
%% TLS v1.0 ---------------------------------------------------
%% TLS v1.1 ---------------------------------------------------
-setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
- KeyMatLen, IVSize)
- when Version == 2 ->
+setup_keys(?TLS_1_1, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
%% RFC 4346 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -279,9 +283,8 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize
%% TLS v1.1 ---------------------------------------------------
%% TLS v1.2 ---------------------------------------------------
-setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
- KeyMatLen, IVSize)
- when Version == 3; Version == 4 ->
+setup_keys(?TLS_1_2, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
+ KeyMatLen, IVSize) ->
%% RFC 5246 - 6.3. Key calculation
%% key_block = PRF(SecurityParameters.master_secret,
%% "key expansion",
@@ -492,12 +495,12 @@ key_length(CipherSuite) ->
-spec mac_hash(integer() | atom(), binary(), integer(), integer(), tls_record:tls_version(),
integer(), binary()) -> binary().
-mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
- Length, Fragment) ->
+mac_hash(Method, Mac_write_secret, Seq_num, Type, Version,Length, Fragment) ->
%% RFC 2246 & 4346 - 6.2.3.1.
%% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type +
%% TLSCompressed.version + TLSCompressed.length +
%% TLSCompressed.fragment));
+ {Major,Minor} = Version,
Mac = hmac_hash(Method, Mac_write_secret,
[<<?UINT64(Seq_num), ?BYTE(Type),
?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
@@ -505,19 +508,19 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
Mac.
%% TLS 1.0 -1.2 ---------------------------------------------------
-%% TODO 1.3 same as above?
+-spec suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
--spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()].
+suites(Version) when ?TLS_1_X(Version) ->
+ lists:flatmap(fun exclusive_suites/1, suites_to_test(Version)).
-suites(Minor) when Minor == 1; Minor == 2 ->
- exclusive_suites(1);
-suites(3) ->
- exclusive_suites(3) ++ suites(2);
+suites_to_test(?TLS_1_0) -> [?TLS_1_0];
+suites_to_test(?TLS_1_1) -> [?TLS_1_0];
+suites_to_test(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_0];
+suites_to_test(?TLS_1_3) -> [?TLS_1_3, ?TLS_1_2, ?TLS_1_0].
-suites(4) ->
- exclusive_suites(4) ++ suites(3).
+-spec exclusive_suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
-exclusive_suites(4) ->
+exclusive_suites(?TLS_1_3) ->
[?TLS_AES_256_GCM_SHA384,
?TLS_AES_128_GCM_SHA256,
@@ -526,7 +529,7 @@ exclusive_suites(4) ->
?TLS_AES_128_CCM_SHA256,
?TLS_AES_128_CCM_8_SHA256
];
-exclusive_suites(3) ->
+exclusive_suites(?TLS_1_2) ->
[?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
@@ -565,12 +568,12 @@ exclusive_suites(3) ->
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
-
+
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
-
+
?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
-
+
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
@@ -580,13 +583,13 @@ exclusive_suites(3) ->
%% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
%% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256
];
-exclusive_suites(2) ->
+exclusive_suites(?TLS_1_1) ->
[];
-exclusive_suites(1) ->
+exclusive_suites(?TLS_1_0) ->
[
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
-
+
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
@@ -601,17 +604,18 @@ exclusive_suites(1) ->
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA
].
-
+
%%--------------------------------------------------------------------
--spec exclusive_anonymous_suites(Minor:: integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec exclusive_anonymous_suites(ssl_record:ssl_version()) ->
+ [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous cipher suites introduced
%% in Version, only supported if explicitly set by user.
%%--------------------------------------------------------------------
-exclusive_anonymous_suites(4) ->
+exclusive_anonymous_suites(?TLS_1_3) ->
[];
-exclusive_anonymous_suites(3 = N) ->
- psk_anon_exclusive(N) ++
+exclusive_anonymous_suites(?TLS_1_2=Version) ->
+ psk_anon_exclusive(Version) ++
[?TLS_DH_anon_WITH_AES_128_GCM_SHA256,
?TLS_DH_anon_WITH_AES_256_GCM_SHA384,
?TLS_DH_anon_WITH_AES_128_CBC_SHA256,
@@ -622,35 +626,34 @@ exclusive_anonymous_suites(3 = N) ->
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
?TLS_DH_anon_WITH_RC4_128_MD5];
-exclusive_anonymous_suites(2 = N) ->
- psk_anon_exclusive(N) ++
+exclusive_anonymous_suites(?TLS_1_1=Version) ->
+ psk_anon_exclusive(Version) ++
[?TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
?TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
-
+
?TLS_DH_anon_WITH_DES_CBC_SHA,
?TLS_DH_anon_WITH_RC4_128_MD5];
-exclusive_anonymous_suites(N = 1) ->
- psk_anon_exclusive(N) ++
+exclusive_anonymous_suites(?TLS_1_0=Version) ->
+ psk_anon_exclusive(Version) ++
[?TLS_DH_anon_WITH_RC4_128_MD5,
?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
?TLS_DH_anon_WITH_DES_CBC_SHA
- ] ++ srp_suites_anon({3,1}).
+ ] ++ srp_suites_anon(Version).
%%--------------------------------------------------------------------
--spec psk_suites(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec psk_suites(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the PSK cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-psk_suites({3, 3}) ->
- psk_exclusive(3);
-psk_suites({3, N}) ->
- psk_exclusive(N).
-
-psk_exclusive(3) ->
- psk_exclusive(1) -- [?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA];
-psk_exclusive(1) ->
+psk_suites(Version) when ?TLS_1_X(Version) ->
+ psk_exclusive(Version).
+
+-spec psk_exclusive(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
+psk_exclusive(?TLS_1_2) ->
+ psk_exclusive(?TLS_1_0) -- [?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA];
+psk_exclusive(?TLS_1_0) ->
[
?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
@@ -664,15 +667,17 @@ psk_exclusive(_) ->
[].
%%--------------------------------------------------------------------
--spec psk_suites_anon(ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec psk_suites_anon(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the anonymous PSK cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-psk_suites_anon({3, _}) ->
- psk_anon_exclusive(3) ++ psk_anon_exclusive(1).
+psk_suites_anon(Version) when ?TLS_1_X(Version) ->
+ psk_anon_exclusive(?TLS_1_2) ++ psk_anon_exclusive(?TLS_1_0).
-psk_anon_exclusive(3) ->
+-spec psk_anon_exclusive(ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
+
+psk_anon_exclusive(?TLS_1_2) ->
[
?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384,
?TLS_PSK_WITH_AES_256_GCM_SHA384,
@@ -692,7 +697,7 @@ psk_anon_exclusive(3) ->
?TLS_PSK_WITH_AES_128_CCM,
?TLS_PSK_WITH_AES_128_CCM_8
];
-psk_anon_exclusive(1) ->
+psk_anon_exclusive(?TLS_1_0) ->
[
?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
@@ -720,15 +725,19 @@ psk_anon_exclusive(_) ->
%% Description: Returns a list of the SRP cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites({3, 3}) ->
- srp_exclusive(1) -- [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
- ];
-srp_suites({3, N}) when N == 1;
- N == 2 ->
- srp_exclusive(1).
-
-srp_exclusive(1) ->
+srp_suites(?TLS_1_2) ->
+ srp_exclusive(?TLS_1_0) -- [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
+ ];
+srp_suites(?TLS_1_1) ->
+ srp_exclusive(?TLS_1_0);
+srp_suites(?TLS_1_0) ->
+ srp_exclusive(?TLS_1_0).
+
+
+-spec srp_exclusive(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()].
+
+srp_exclusive(?TLS_1_0) ->
[?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
@@ -745,30 +754,36 @@ srp_exclusive(_) ->
%% Description: Returns a list of the SRP anonymous cipher suites, only supported
%% if explicitly set by user.
%%--------------------------------------------------------------------
-srp_suites_anon({3, 3}) ->
- srp_exclusive_anon(1) -- [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA];
-srp_suites_anon({3, N}) when N == 1;
- N == 2 ->
- srp_exclusive_anon(1).
-srp_exclusive_anon(1) ->
+srp_suites_anon(?TLS_1_2) ->
+ srp_exclusive_anon(?TLS_1_0) -- [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA];
+srp_suites_anon(?TLS_1_1) ->
+ srp_exclusive_anon(?TLS_1_0);
+srp_suites_anon(?TLS_1_0) ->
+ srp_exclusive_anon(?TLS_1_0).
+
+
+srp_exclusive_anon(?TLS_1_0) ->
[?TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
?TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA
].
%%--------------------------------------------------------------------
--spec rc4_suites(Version::ssl_record:ssl_version() | integer()) ->
- [ssl_cipher_format:cipher_suite()].
+-spec rc4_suites(Version::ssl_record:ssl_version()) ->
+ [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the RSA|(ECDH/RSA)| (ECDH/ECDSA)
%% with RC4 cipher suites, only supported if explicitly set by user.
%% Are not considered secure any more. Other RC4 suites already
%% belonged to the user configured only category.
%%--------------------------------------------------------------------
-rc4_suites({3, _}) ->
- rc4_exclusive(1).
+rc4_suites(Version) when ?TLS_1_X(Version) ->
+ rc4_exclusive(?TLS_1_0).
+
+-spec rc4_exclusive(Version::ssl_record:ssl_version()) ->
+ [ssl_cipher_format:cipher_suite()].
-rc4_exclusive(1) ->
+rc4_exclusive(?TLS_1_0) ->
[?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
?TLS_ECDHE_RSA_WITH_RC4_128_SHA,
?TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
@@ -785,10 +800,10 @@ rc4_exclusive(_) ->
%% with DES cipher, only supported if explicitly set by user.
%% Are not considered secure any more.
%%--------------------------------------------------------------------
-des_suites({3, _}) ->
- des_exclusive(1).
+des_suites(Version) when ?TLS_1_X(Version) ->
+ des_exclusive(?TLS_1_0).
-des_exclusive(1)->
+des_exclusive(?TLS_1_0)->
[?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
@@ -800,27 +815,28 @@ des_exclusive(1)->
des_exclusive(_) ->
[].
%%--------------------------------------------------------------------
--spec rsa_suites(Version::ssl_record:ssl_version() | integer()) -> [ssl_cipher_format:cipher_suite()].
+-spec rsa_suites(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
%%
%% Description: Returns a list of the RSA key exchange
%% cipher suites, only supported if explicitly set by user.
%% Are not considered secure any more.
%%--------------------------------------------------------------------
-rsa_suites({3, 3}) ->
- rsa_exclusive(3) ++ rsa_exclusive(1);
-rsa_suites({3, 2}) ->
- rsa_exclusive(1);
-rsa_suites({3, 1}) ->
- rsa_exclusive(1).
-
-rsa_exclusive(3) ->
+rsa_suites(Version) when ?TLS_1_X(Version) ->
+ lists:flatmap(fun rsa_exclusive/1, rsa_suites_to_test(Version)).
+
+rsa_suites_to_test(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_0];
+rsa_suites_to_test(?TLS_1_1) -> [?TLS_1_0];
+rsa_suites_to_test(?TLS_1_0) -> [?TLS_1_0].
+
+-spec rsa_exclusive(Version::ssl_record:ssl_version()) -> [ssl_cipher_format:cipher_suite()].
+rsa_exclusive(?TLS_1_2) ->
[
?TLS_RSA_WITH_AES_256_GCM_SHA384,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
?TLS_RSA_WITH_AES_128_GCM_SHA256,
?TLS_RSA_WITH_AES_128_CBC_SHA256
];
-rsa_exclusive(1) ->
+rsa_exclusive(?TLS_1_0) ->
[?TLS_RSA_WITH_AES_256_CBC_SHA,
?TLS_RSA_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
@@ -828,15 +844,15 @@ rsa_exclusive(1) ->
rsa_exclusive(_) ->
[].
-signature_algs({3, 4}, HashSigns) ->
- signature_algs({3, 3}, HashSigns);
-signature_algs({3, 3}, HashSigns) ->
+signature_algs(?TLS_1_3, HashSigns) ->
+ signature_algs(?TLS_1_2, HashSigns);
+signature_algs(?TLS_1_2, HashSigns) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
Schemes = rsa_schemes(),
Supported = lists:foldl(fun({Hash, dsa = Sign} = Alg, Acc) ->
- case proplists:get_bool(dss, PubKeys)
+ case proplists:get_bool(dss, PubKeys)
andalso proplists:get_bool(Hash, Hashes)
andalso is_pair(Hash, Sign, Hashes)
of
@@ -845,9 +861,9 @@ signature_algs({3, 3}, HashSigns) ->
false ->
Acc
end;
- ({Hash, Sign} = Alg, Acc) ->
- case proplists:get_bool(Sign, PubKeys)
- andalso proplists:get_bool(Hash, Hashes)
+ ({Hash, Sign} = Alg, Acc) ->
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
andalso is_pair(Hash, Sign, Hashes)
of
true ->
@@ -858,7 +874,7 @@ signature_algs({3, 3}, HashSigns) ->
(Alg, Acc) when is_atom(Alg) ->
case lists:member(Alg, Schemes) of
true ->
- [NewAlg] = signature_schemes({3,4}, [Alg]),
+ [NewAlg] = signature_schemes(?TLS_1_3, [Alg]),
[NewAlg| Acc];
false ->
Acc
@@ -866,11 +882,11 @@ signature_algs({3, 3}, HashSigns) ->
end, [], HashSigns),
lists:reverse(Supported).
-default_signature_algs([{3, 4} = Version]) ->
- default_signature_schemes(Version) ++ legacy_signature_schemes(Version);
-default_signature_algs([{3, 4}, {3,3} | _]) ->
- default_signature_schemes({3,4}) ++ default_pre_1_3_signature_algs_only();
-default_signature_algs([{3, 3} = Version |_]) ->
+default_signature_algs([?TLS_1_3]) ->
+ default_signature_schemes(?TLS_1_3) ++ legacy_signature_schemes(?TLS_1_3);
+default_signature_algs([?TLS_1_3, ?TLS_1_2 | _]) ->
+ default_signature_schemes(?TLS_1_3) ++ default_pre_1_3_signature_algs_only();
+default_signature_algs([?TLS_1_2 = Version |_]) ->
Default = [%% SHA2 ++ PSS
{sha512, ecdsa},
rsa_pss_pss_sha512,
@@ -885,11 +901,8 @@ default_signature_algs([{3, 3} = Version |_]) ->
rsa_pss_rsae_sha256,
{sha256, rsa},
{sha224, ecdsa},
- {sha224, rsa},
- %% SHA
- {sha, ecdsa},
- {sha, rsa},
- {sha, dsa}],
+ {sha224, rsa}
+ ],
signature_algs(Version, Default);
default_signature_algs(_) ->
undefined.
@@ -903,16 +916,12 @@ default_pre_1_3_signature_algs_only() ->
{sha256, ecdsa},
{sha256, rsa},
{sha224, ecdsa},
- {sha224, rsa},
- %% SHA
- {sha, ecdsa},
- {sha, rsa},
- {sha, dsa}],
- signature_algs({3,3}, Default).
-
+ {sha224, rsa}
+ ],
+ signature_algs(?TLS_1_2, Default).
signature_schemes(Version, [_|_] =SignatureSchemes) when is_tuple(Version)
- andalso Version >= {3, 3} ->
+ andalso ?TLS_GTE(Version, ?TLS_1_2) ->
CryptoSupports = crypto:supports(),
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
@@ -995,12 +1004,10 @@ legacy_signature_schemes(Version) ->
%% MAY appear in "signature_algorithms" and
%% "signature_algorithms_cert" for backward compatibility with
%% TLS 1.2.
- LegacySchemes =
+ LegacySchemes =
[rsa_pkcs1_sha512,
rsa_pkcs1_sha384,
- rsa_pkcs1_sha256,
- ecdsa_sha1,
- rsa_pkcs1_sha1],
+ rsa_pkcs1_sha256],
signature_schemes(Version, LegacySchemes).
rsa_schemes() ->
@@ -1037,7 +1044,7 @@ hmac_hash(?NULL, _, _) ->
hmac_hash(Alg, Key, Value) ->
crypto:mac(hmac, mac_algo(Alg), Key, Value).
-mac_algo(Alg) when is_atom(Alg) ->
+mac_algo(Alg) when is_atom(Alg) ->
Alg;
mac_algo(?MD5) -> md5;
mac_algo(?SHA) -> sha;
@@ -1127,22 +1134,23 @@ is_pair(Hash, rsa, Hashs) ->
is_pair(_,_,_) ->
false.
+%% Should we create a new function for ecc_curves(Version)
+%% and another explicit one for ecc_curve_named([TLSCurves])?
%% list ECC curves in preferred order
--spec ecc_curves(1..3 | all) -> [named_curve()].
+-spec ecc_curves(TLS | DTLS | all) -> [named_curve()] when
+ TLS :: ?TLS_1_0 | ?TLS_1_1 | ?TLS_1_2,
+ DTLS :: ?DTLS_1_0 | ?DTLS_1_2;
+ ([named_curve()]) -> [named_curve()].
ecc_curves(all) ->
[sect571r1,sect571k1,secp521r1,brainpoolP512r1,
sect409k1,sect409r1,brainpoolP384r1,secp384r1,
- sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1,
- sect239k1,sect233k1,sect233r1,secp224k1,secp224r1,
- sect193r1,sect193r2,secp192k1,secp192r1,sect163k1,
- sect163r1,sect163r2,secp160k1,secp160r1,secp160r2];
+ sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1];
-ecc_curves(Minor) ->
+ecc_curves(Version) when is_tuple(Version) ->
TLSCurves = ecc_curves(all),
- ecc_curves(Minor, TLSCurves).
+ ecc_curves(TLSCurves);
--spec ecc_curves(1..3, [named_curve()]) -> [named_curve()].
-ecc_curves(_Minor, TLSCurves) ->
+ecc_curves(TLSCurves) when is_list(TLSCurves) ->
CryptoCurves = crypto:ec_curves(),
lists:foldr(fun(Curve, Curves) ->
case proplists:get_bool(Curve, CryptoCurves) of
@@ -1151,7 +1159,11 @@ ecc_curves(_Minor, TLSCurves) ->
end
end, [], TLSCurves).
--spec groups(4 | all | default) -> [group()].
+groups() ->
+ TLSGroups = groups(all),
+ groups(TLSGroups).
+
+-spec groups(all | default | TLSGroups :: list()) -> [group()].
groups(all) ->
[x25519,
x448,
@@ -1168,18 +1180,13 @@ groups(default) ->
x448,
secp256r1,
secp384r1];
-groups(Minor) ->
- TLSGroups = groups(all),
- groups(Minor, TLSGroups).
-%%
--spec groups(4, [group()]) -> [group()].
-groups(_Minor, TLSGroups) ->
+groups(TLSGroups) when is_list(TLSGroups) ->
CryptoGroups = supported_groups(),
lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
-default_groups(Minor) ->
+default_groups() ->
TLSGroups = groups(default),
- groups(Minor, TLSGroups).
+ groups(TLSGroups).
supported_groups() ->
%% TODO: Add new function to crypto?
@@ -1273,3 +1280,23 @@ enum_to_oid(29) -> ?'id-X25519';
enum_to_oid(30) -> ?'id-X448';
enum_to_oid(_) ->
undefined.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+handle_trace(kdt,
+ {call, {?MODULE, update_traffic_secret,
+ [_HKDF, ApplicationTrafficSecret0]}},
+ Stack) ->
+ ATS0 = string:sub_string(
+ binary:bin_to_list(
+ binary:encode_hex(ApplicationTrafficSecret0)), 1, 5) ++ "...",
+ {io_lib:format("ApplicationTrafficSecret0 = \"~s\"", [ATS0]), Stack};
+handle_trace(kdt,
+ {return_from, {?MODULE, update_traffic_secret, 2},
+ Return}, Stack) ->
+ ATS = string:sub_string(
+ binary:bin_to_list(
+ binary:encode_hex(Return)), 1, 5) ++ "...",
+ {io_lib:format("ApplicationTrafficSecret = \"~s\"", [ATS]), Stack}.
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index ac1318bd14..d8bb5b5913 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2022. All Rights Reserved.
+# Copyright Ericsson AB 1999-2023. 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.
@@ -56,6 +56,7 @@ MODULES = \
openssl_sni_SUITE\
ssl_mfl_SUITE\
openssl_mfl_SUITE\
+ ssl_use_srtp_SUITE\
ssl_reject_SUITE\
ssl_renegotiate_SUITE\
openssl_renegotiate_SUITE\
@@ -79,6 +80,7 @@ MODULES = \
ssl_session_cache_SUITE \
ssl_session_cache_api_SUITE\ \
ssl_session_ticket_SUITE \
+ ssl_trace_SUITE \
openssl_session_ticket_SUITE \
openssl_session_SUITE \
ssl_ECC_SUITE \
@@ -94,7 +96,12 @@ MODULES = \
ssl_socket_SUITE\
make_certs \
x509_test \
- inet_crypto_dist \
+ cryptcookie \
+ dist_cryptcookie \
+ inet_epmd_cryptcookie_inet_ktls \
+ inet_epmd_dist_cryptcookie_inet \
+ inet_epmd_dist_cryptcookie_socket \
+ inet_epmd_cryptcookie_socket_ktls \
openssl_ocsp_SUITE \
tls_server_session_ticket_SUITE \
tls_client_ticket_store_SUITE
diff --git a/lib/ssl/test/cryptcookie.erl b/lib/ssl/test/cryptcookie.erl
new file mode 100644
index 0000000000..19a26651ac
--- /dev/null
+++ b/lib/ssl/test/cryptcookie.erl
@@ -0,0 +1,747 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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 for an encrypted Erlang stream based only on
+%% the cookie as a shared secret
+%%
+-module(cryptcookie).
+-feature(maybe_expr, enable).
+
+-export([supported/0, start_keypair_server/0, init/1, init/2,
+ encrypt_and_send_chunk/4, recv_and_decrypt_chunk/2]).
+
+%% For kTLS integration
+-export([ktls_info/1, ktls_info/0]).
+-include_lib("ssl/src/ssl_cipher.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
+
+
+-define(PROTOCOL, (?MODULE)).
+
+%% -------------------------------------------------------------------------
+%% The curve choice greatly affects setup time,
+%% we really want an Edwards curve but that would
+%% require a very new openssl version.
+%%
+%% Twisted brainpool curves (*t1) are faster than
+%% non-twisted (*r1), 256 is much faster than 384,
+%% and so on...
+%%
+%%% -define(CURVE, brainpoolP384t1).
+%%% -define(CURVE, brainpoolP256t1).
+-define(CURVE, secp256r1). % Portability
+-define(CIPHER, aes_256_gcm). % kTLS compatible
+-define(HMAC, sha384).
+
+%% kTLS integration
+-define(TLS_VERSION, ?TLS_1_3).
+-define(CIPHER_SUITE, (?TLS_AES_256_GCM_SHA384)).
+
+
+supported() ->
+ maybe
+ true ?= crypto_supports(curves, ?CURVE),
+ true ?= crypto_supports(ciphers, ?CIPHER),
+ true ?= crypto_supports(macs, hmac),
+ true ?= crypto_supports(hashs, ?HMAC),
+ ok
+ end.
+
+crypto_supports(Tag, Item) ->
+ lists:member(Item, crypto:supports(Tag))
+ orelse "Crypto does not support "
+ ++ atom_to_list(Tag) ++ ": " ++ atom_to_list(Item).
+
+%% -------------------------------------------------------------------------
+
+-define(PACKET_SIZE, (1 bsl 16)). % 2 byte size header
+%% Plenty of room for AEAD tag and chunk type
+-define(CHUNK_SIZE, (?PACKET_SIZE - 256)).
+
+-record(params,
+ {hmac_algorithm = ?HMAC,
+ aead_cipher = ?CIPHER,
+ iv,
+ key,
+ tag_len = 16,
+ rekey_count = 256 * 1024,
+ rekey_time = 2 * 3600, % (seconds): 2 hours
+ rekey_timestamp,
+ rekey_key
+ }).
+
+params() ->
+ #{ iv_length := IVLen, key_length := KeyLen } =
+ crypto:cipher_info(?CIPHER),
+ #params{ iv = IVLen, key = KeyLen, rekey_timestamp = timestamp() }.
+
+
+-record(keypair,
+ {type = ecdh,
+ params = ?CURVE,
+ public,
+ private,
+ life_count = 256, % Number of connection setups
+ life_time = 3600, % 1 hour
+ life_timestamp
+ }).
+
+%% -------------------------------------------------------------------------
+%% Keep the node's public/private key pair in the process state
+%% of a key pair server linked to the net_kernel process.
+%% Create the key pair the first time it is needed
+%% so crypto gets time to start first.
+%%
+
+start_keypair_server() ->
+ Parent = self(),
+ Ref = make_ref(),
+ _ =
+ spawn_link(
+ fun () ->
+ try register(?MODULE, self()) of
+ true ->
+ Parent ! Ref,
+ keypair_server()
+ catch error : badarg ->
+ %% Already started - simply exit
+ %% and let the other run
+ Parent ! Ref,
+ ok
+ end
+ end),
+ receive Ref ->
+ ?PROTOCOL
+ end.
+
+keypair_server() ->
+ keypair_server(undefined, 1).
+%%
+keypair_server(KeyPair) ->
+ keypair_server(KeyPair, KeyPair#keypair.life_count).
+%%
+keypair_server(_KeyPair, 0) ->
+ keypair_server();
+keypair_server(KeyPair, Count) ->
+ receive
+ {RefAlias, get_keypair} when is_reference(RefAlias) ->
+ case KeyPair of
+ undefined ->
+ KeyPair_1 = generate_keypair(),
+ RefAlias ! {RefAlias, KeyPair_1},
+ keypair_server(KeyPair_1);
+ #keypair{} ->
+ RefAlias ! {RefAlias, KeyPair},
+ keypair_server(KeyPair, Count - 1)
+ end;
+ {RefAlias, get_new_keypair} ->
+ KeyPair_1 = generate_keypair(),
+ RefAlias ! {RefAlias, KeyPair_1},
+ keypair_server(KeyPair_1)
+ end.
+
+call_keypair_server(Request) ->
+ Pid = whereis(?MODULE),
+ RefAlias = erlang:monitor(process, Pid, [{alias, reply_demonitor}]),
+ Pid ! {RefAlias, Request},
+ receive
+ {RefAlias, Reply} ->
+ Reply;
+ {'DOWN', RefAlias, process, Pid, Reason} ->
+ error({keypair_server, Reason})
+ end.
+
+generate_keypair() ->
+ #keypair{ type = Type, params = Params } = #keypair{},
+ {Public, Private} = crypto:generate_key(Type, Params),
+ #keypair{
+ public = Public, private = Private,
+ life_timestamp = timestamp() }.
+
+
+get_keypair() ->
+ call_keypair_server(?FUNCTION_NAME).
+
+get_new_keypair() ->
+ call_keypair_server(?FUNCTION_NAME).
+
+compute_shared_secret(
+ #keypair{
+ type = PublicKeyType,
+ params = PublicKeyParams,
+ private = PrivKey }, PubKey) ->
+ %%
+ crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams).
+
+%% -------------------------------------------------------------------------
+
+-define(DATA_CHUNK, 2).
+-define(TICK_CHUNK, 3).
+-define(REKEY_CHUNK, 4).
+
+%% -------------------------------------------------------------------------
+%% Crypto strategy
+%% -------
+%% The crypto strategy is as simple as possible to get an encrypted
+%% connection as benchmark reference. It is geared around AEAD
+%% ciphers in particular AES-GCM.
+%%
+%% The init message and the start message must fit in the TCP buffers
+%% since both sides start with sending the init message, waits
+%% for the other end's init message, sends the start message
+%% and waits for the other end's start message. So if the send
+%% blocks we have a deadlock.
+%%
+%% The init + start sequence tries to implement Password Encrypted
+%% Key Exchange using a node public/private key pair and the
+%% shared secret (the Cookie) to create session encryption keys
+%% that can not be re-created if the shared secret is compromized,
+%% which should create forward secrecy. You need both nodes'
+%% key pairs and the shared secret to decrypt the traffic
+%% between the nodes.
+%%
+%% All exchanged messages uses {packet, 2} i.e 16 bit size header.
+%%
+%% The init message contains a random number and encrypted: the public key
+%% and two random numbers. The encryption is done with Key and IV hashed
+%% from the unencrypted random number and the shared secret.
+%%
+%% The other node's public key is used with the own node's private
+%% key to create a shared key that is hashed with one of the encrypted
+%% random numbers from each side to create Key and IV for the session.
+%%
+%% The start message contains the two encrypted random numbers
+%% this time encrypted with the session keys for verification
+%% by the other side, plus the rekey count. The rekey count
+%% is just there to get an early check for if the other side's
+%% maximum rekey count is acceptable, it is just an embryo
+%% of some better check. Any side may rekey earlier but if the
+%% rekey count is exceeded the connection fails. Rekey is also
+%% triggered by a timer.
+%%
+%% Subsequent encrypted messages has the sequence number and the length
+%% of the message as AAD data, and an incrementing IV. These messages
+%% has got a message type that differentiates data from ticks and rekeys.
+%% Ticks have a random size in an attempt to make them less obvious to spot.
+%%
+%% Rekeying is done by the sender that creates a new key pair and
+%% a new shared secret from the other end's public key and with
+%% this and the current key and iv hashes a new key and iv.
+%% The new public key is sent to the other end that uses it
+%% and its old private key to create the same new shared
+%% secret and from that a new key and iv.
+%% So the receiver keeps its private key, and the sender keeps
+%% the receivers public key for the connection's life time.
+%% While the sender generates a new key pair at every rekey,
+%% which changes the shared secret at every rekey.
+%%
+%% The only reaction to errors is to crash noisily (?) which will bring
+%% down the connection and hopefully produce something useful
+%% in the local log, but all the other end sees is a closed connection.
+%% -------------------------------------------------------------------------
+
+
+%% -------------------------------------------------------------------------
+%% Initialize encryption on Stream; initial handshake
+%%
+%% init(Stream, Secret) ->
+%% {NewStream, ChunkSize, [RecvSeq|RecvParams], [SendSeq|SendParams]}.
+
+init(Stream) ->
+ Secret = atom_to_binary(auth:get_cookie(), latin1),
+ init(Stream, Secret).
+
+init(Stream = {_, OutStream, _}, Secret) ->
+ #keypair{ public = PubKey } = KeyPair = get_keypair(),
+ Params = params(),
+ {R2, R3, Msg} = init_msg(Params, Secret, PubKey),
+ OutStream_1 = init_send_block(OutStream, Msg, iolist_size(Msg)),
+ Stream_1 = setelement(2, Stream, OutStream_1),
+ init_recv(Stream_1, Params, Secret, KeyPair, R2, R3).
+
+init_recv(
+ {InStream, OutStream, ControllingProcessFun},
+ Params = #params{ iv = IVLen },
+ Secret, KeyPair, R2, R3) ->
+ %%
+ [InitMsg | InStream_1] = init_recv_block(InStream),
+ IVSaltLen = IVLen - 6,
+ try
+ case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of
+ {SendParams =
+ #params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>> },
+ RecvParams,
+ SendStartMsg} ->
+ OutStream_1 =
+ init_send_block(
+ OutStream, SendStartMsg, iolist_size(SendStartMsg)),
+ [RecvStartMsg | InStream_2] = init_recv_block(InStream_1),
+ RecvParams_1 =
+ #params{
+ iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>> } =
+ start_msg(RecvParams, R2, R3, RecvStartMsg),
+ Stream_2 = {InStream_2, OutStream_1, ControllingProcessFun},
+ RecvSeqParams =
+ [0 | RecvParams_1#params{ iv = {IV2BSalt, IV2BNo} }],
+ SendSeqParams =
+ [0 | SendParams#params{ iv = {IV2ASalt, IV2ANo} }],
+ CipherState = {RecvSeqParams, SendSeqParams},
+ {Stream_2, ?CHUNK_SIZE, CipherState}
+ end
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_report(
+ [init_recv_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ _ = trace({Reason, Stacktrace}),
+ exit(connection_closed)
+ end.
+
+init_msg(
+ #params{
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
+ key = KeyLen,
+ iv = IVLen,
+ tag_len = TagLen }, Secret, PubKeyA) ->
+ %%
+ RLen = KeyLen + IVLen,
+ <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> =
+ crypto:strong_rand_bytes(3 * RLen),
+ {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen),
+ Plaintext = [R2A, R3A, PubKeyA],
+ MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext),
+ AAD = [<<MsgLen:32>>, R1A],
+ {Ciphertext, Tag} =
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key1A, IV1A, Plaintext, AAD, TagLen, true),
+ Msg = [R1A, Tag, Ciphertext],
+ {R2A, R3A, Msg}.
+%%
+init_msg(
+ #params{
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
+ key = KeyLen,
+ iv = IVLen,
+ tag_len = TagLen,
+ rekey_count = RekeyCount } = Params,
+ Secret, KeyPair, R2A, R3A, Msg) ->
+ %%
+ RLen = KeyLen + IVLen,
+ case Msg of
+ <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> ->
+ {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen),
+ MsgLen = byte_size(Msg),
+ AAD = [<<MsgLen:32>>, R1B],
+ case
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key1B, IV1B, Ciphertext, AAD, Tag, false)
+ of
+ <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ %%
+ {Key2A, IV2A} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen),
+ SendParams =
+ Params#params{
+ rekey_key = PubKeyB,
+ key = Key2A, iv = IV2A },
+ %%
+ StartCleartext = [R2B, R3B, <<RekeyCount:32>>],
+ StartMsgLen = TagLen + iolist_size(StartCleartext),
+ StartAAD = <<StartMsgLen:32>>,
+ {StartCiphertext, StartTag} =
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key2A, IV2A,
+ StartCleartext, StartAAD, TagLen, true),
+ StartMsg = [StartTag, StartCiphertext],
+ %%
+ {Key2B, IV2B} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen),
+ RecvParams =
+ Params#params{
+ rekey_key = KeyPair,
+ key = Key2B, iv = IV2B },
+ %%
+ {SendParams, RecvParams, StartMsg}
+ end
+ end.
+
+start_msg(
+ #params{
+ aead_cipher = AeadCipher,
+ key = Key2B,
+ iv = IV2B,
+ tag_len = TagLen,
+ rekey_count = RekeyCountA } = RecvParams, R2A, R3A, Msg) ->
+ %%
+ case Msg of
+ <<Tag:TagLen/binary, Ciphertext/binary>> ->
+ KeyLen = byte_size(Key2B),
+ IVLen = byte_size(IV2B),
+ RLen = KeyLen + IVLen,
+ MsgLen = byte_size(Msg),
+ AAD = <<MsgLen:32>>,
+ case
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key2B, IV2B, Ciphertext, AAD, Tag, false)
+ of
+ <<R2A:RLen/binary, R3A:RLen/binary, RekeyCountB:32>>
+ when RekeyCountA =< (RekeyCountB bsl 2),
+ RekeyCountB =< (RekeyCountA bsl 2) ->
+ RecvParams#params{ rekey_count = RekeyCountB }
+ end
+ end.
+
+hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) ->
+ <<Key:KeyLen/binary, IV:IVLen/binary>> =
+ crypto:macN(hmac, HmacAlgo, MacKey, Data, KeyLen + IVLen),
+ {Key, IV}.
+
+
+init_send_block(OutStream, Chunk, Size) ->
+ OutStream_1 = send_block(OutStream, Chunk, Size),
+ if
+ hd(OutStream_1) =:= closed ->
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, closed}]),
+ _ = trace({?FUNCTION_NAME, closed}),
+ exit(connection_closed);
+ true ->
+ OutStream_1
+ end.
+
+init_recv_block(InStream) ->
+ Result = [Data | _] = recv_block(InStream),
+ if
+ Data =:= closed ->
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, closed}]),
+ _ = trace({?FUNCTION_NAME, closed}),
+ exit(connection_closed);
+ true ->
+ Result
+ end.
+
+
+%% -------------------------------------------------------------------------
+encrypt_and_send_chunk(
+ OutStream,
+ [Seq |
+ Params =
+ #params{ rekey_count = RekeyCount,
+ rekey_time = RekeyTime,
+ rekey_timestamp = RekeyTimestamp }],
+ Chunk, Size) ->
+ %%
+ Timestamp = timestamp(),
+ if
+ RekeyCount =< Seq;
+ RekeyTimestamp + RekeyTime =< Timestamp ->
+ {OutStream_1, Params_1} =
+ encrypt_and_send_rekey_chunk(
+ OutStream, Seq, Params, Timestamp),
+ if
+ hd(OutStream_1) =:= closed ->
+ {OutStream_1, [0 | Params_1]};
+ true ->
+ {encrypt_and_send_chunk(
+ OutStream_1, 0, Params_1, Chunk, Size),
+ [1 | Params_1]}
+ end;
+ true ->
+ {encrypt_and_send_chunk(OutStream, Seq, Params, Chunk, Size),
+ [Seq + 1 | Params]}
+ end.
+
+encrypt_and_send_chunk(OutStream, Seq, Params, Chunk, 0) -> % Tick
+ <<>> = Chunk, % ASSERT
+ %% A ticks are sent as a somewhat random size block
+ %% to make it less obvious to spot
+ <<S:8>> = crypto:strong_rand_bytes(1),
+ TickSize = 8 + (S band 63),
+ TickData = crypto:strong_rand_bytes(TickSize),
+ encrypt_and_send_block(
+ OutStream, Seq, Params, [?TICK_CHUNK, TickData], 1 + TickSize);
+encrypt_and_send_chunk(OutStream, Seq, Params, Chunk, Size) ->
+ encrypt_and_send_block(
+ OutStream, Seq, Params, [?DATA_CHUNK, Chunk], 1 + Size).
+
+encrypt_and_send_rekey_chunk(
+ OutStream, Seq,
+ Params =
+ #params{
+ rekey_key = PubKeyB,
+ key = Key,
+ iv = {IVSalt, IVNo},
+ hmac_algorithm = HmacAlgo },
+ Timestamp) ->
+ %%
+ KeyLen = byte_size(Key),
+ IVSaltLen = byte_size(IVSalt),
+ #keypair{ public = PubKeyA } = KeyPair = get_new_keypair(),
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ IV = <<(IVNo + Seq):48>>,
+ {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [Key, IVSalt, IV],
+ KeyLen, IVSaltLen + 6),
+ %%
+ {encrypt_and_send_block(
+ OutStream, Seq, Params,
+ [?REKEY_CHUNK, PubKeyA], 1 + byte_size(PubKeyA)),
+ Params#params{
+ key = Key_1, iv = {IVSalt_1, IVNo_1},
+ rekey_timestamp = Timestamp }}.
+
+encrypt_and_send_block(OutStream, Seq, Params, Block, Size) ->
+ {EncryptedBlock, EncryptedSize} =
+ encrypt_block(Seq, Params, Block, Size),
+ send_block(OutStream, EncryptedBlock, EncryptedSize).
+
+encrypt_block(
+ Seq,
+ #params{
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen },
+ Block, Size) ->
+ %%
+ EncryptedSize = Size + TagLen,
+ AAD = <<Seq:32, EncryptedSize:32>>,
+ IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
+ {Ciphertext, CipherTag} =
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key, IVBin, Block, AAD, TagLen, true),
+ EncryptedBlock = [Ciphertext, CipherTag],
+ {EncryptedBlock, EncryptedSize}.
+
+%% Send packet=2
+%%
+send_block(OutStream, Block, Size) ->
+ Msg =
+ if
+ is_binary(Block) -> [<<Size:16>>, Block];
+ is_list(Block) -> [<<Size:16>> | Block]
+ end,
+ (hd(OutStream))(OutStream, Msg).
+
+
+%% -------------------------------------------------------------------------
+
+recv_and_decrypt_chunk(InStream, SeqParams = [Seq | Params]) ->
+ case recv_block(InStream) of
+ Result = [closed | _] ->
+ {Result, SeqParams};
+ [Ciphertext | InStream_1] ->
+ case decrypt_block(Seq, Params, Ciphertext) of
+ <<?DATA_CHUNK, DataChunk/binary>> ->
+ {[DataChunk | InStream_1], [Seq + 1 | Params]};
+ <<?TICK_CHUNK, _/binary>> ->
+ {[<<>> | InStream_1], [Seq + 1 | Params]};
+ <<?REKEY_CHUNK, RekeyChunk>> ->
+ case decrypt_rekey(Params, RekeyChunk) of
+ Params_1 = #params{} ->
+ recv_and_decrypt_chunk(
+ InStream_1, [0 | Params_1]);
+ Problem when is_atom(Problem) ->
+ error_report(
+ [?FUNCTION_NAME, {reason, Problem}]),
+ {[closed | InStream_1], [Seq + 1 | Params]}
+ end;
+ <<_UnknownChunk/binary>> ->
+ error_report([?FUNCTION_NAME, {reason, unknown_chunk}]),
+ {[closed | InStream_1], [Seq + 1 | Params]};
+ Problem when is_atom(Problem) ->
+ error_report([?FUNCTION_NAME, {reason, Problem}]),
+ {[closed | InStream_1], [Seq + 1 | Params]}
+ end
+ end.
+
+%% Non-optimized receive packet=2
+%%
+recv_block(InStream) ->
+ Result_1 = [Data | InStream_1] = (hd(InStream))(InStream, 2),
+ if
+ Data =:= closed ->
+ Result_1;
+ is_binary(Data) ->
+ recv_block(InStream_1, Data);
+ is_list(Data) ->
+ recv_block(InStream_1, iolist_to_binary(Data))
+ end.
+%%
+recv_block(InStream, <<0:16>>) ->
+ [<<>> | InStream];
+recv_block(InStream, <<Size:16>>) ->
+ case (hd(InStream))(InStream, Size) of
+ [Data | InStream_1] when is_list(Data) ->
+ [iolist_to_binary(Data) | InStream_1];
+ Result = [Data | _]
+ when is_binary(Data);
+ Data =:= closed ->
+ Result
+ end.
+
+decrypt_block(
+ Seq, #params{ rekey_count = RekeyCount }, _Ciphertext)
+ when RekeyCount =:= Seq ->
+ %% This was one chunk too many without rekeying
+ rekey_overdue;
+decrypt_block(
+ Seq,
+ #params{
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen },
+ Ciphertext) ->
+ %%
+ CiphertextSize = byte_size(Ciphertext),
+ if
+ CiphertextSize < TagLen ->
+ decrypt_short_block;
+ true ->
+ AAD = <<Seq:32, CiphertextSize:32>>,
+ IV = <<IVSalt/binary, (IVNo + Seq):48>>,
+ Size = CiphertextSize - TagLen,
+ <<EncryptedBlock:Size/binary, CipherTag:TagLen/binary>> =
+ Ciphertext,
+ case
+ crypto:crypto_one_time_aead(
+ AeadCipher, Key, IV, EncryptedBlock, AAD, CipherTag,
+ false)
+ of
+ Block when is_binary(Block) ->
+ Block;
+ error ->
+ decrypt_error
+ end
+ end.
+
+decrypt_rekey(
+ Params =
+ #params{
+ iv = IV,
+ key = Key,
+ rekey_key = #keypair{public = PubKeyA} = KeyPair,
+ hmac_algorithm = HmacAlgorithm},
+ RekeyChunk) ->
+ %%
+ PubKeyLen = byte_size(PubKeyA),
+ case RekeyChunk of
+ <<PubKeyB:PubKeyLen/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ KeyLen = byte_size(Key),
+ IVLen = byte_size(IV),
+ IVSaltLen = IVLen - 6,
+ {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
+ hmac_key_iv(
+ HmacAlgorithm, SharedSecret, [Key, IV], KeyLen, IVLen),
+ Params#params{
+ iv = {IVSalt_1, IVNo_1},
+ key = Key_1 };
+ _ ->
+ decrypt_bad_rekey_chunk
+ end.
+
+
+%% -------------------------------------------------------------------------
+ktls_info(
+ {[RecvSeq |
+ #params{
+ iv = {RecvSalt, RecvIV},
+ key = RecvKey,
+ aead_cipher = ?CIPHER } = _RecvParams],
+ [SendSeq |
+ #params{
+ iv = {SendSalt, SendIV},
+ key = SendKey,
+ aead_cipher = ?CIPHER } = _SendParams]}) ->
+ %%
+ RecvState =
+ #cipher_state{
+ key = <<RecvKey/bytes>>,
+ iv = <<RecvSalt/bytes, (RecvIV + RecvSeq):48>> },
+ SendState =
+ #cipher_state{
+ key = <<SendKey/bytes>>,
+ iv = <<SendSalt/bytes, (SendIV + SendSeq):48>> },
+ #{ tls_version => ?TLS_VERSION,
+ cipher_suite => ?CIPHER_SUITE,
+ read_state => RecvState,
+ read_seq => RecvSeq,
+ write_state => SendState,
+ write_seq => SendSeq }.
+
+%% Dummy cipher parameters to use when checking if kTLS is supported.
+%% Completely random to avoid accidentally creating an unsafe connection.
+%%
+ktls_info() ->
+ #params{ iv = IVLen, key = KeyLen } = params(),
+ <<RecvSeq:48, RecvKey:KeyLen/bytes, RecvIV:IVLen/bytes>> =
+ crypto:strong_rand_bytes(6 + KeyLen + IVLen),
+ <<SendSeq:48, SendKey:KeyLen/bytes, SendIV:IVLen/bytes>> =
+ crypto:strong_rand_bytes(6 + KeyLen + IVLen),
+ RecvState = #cipher_state{ key = RecvKey, iv = RecvIV },
+ SendState = #cipher_state{ key = SendKey, iv = SendIV },
+ #{ tls_version => ?TLS_VERSION,
+ cipher_suite => ?CIPHER_SUITE,
+ read_state => RecvState,
+ read_seq => RecvSeq,
+ write_state => SendState,
+ write_seq => SendSeq }.
+
+
+%% -------------------------------------------------------------------------
+-ifdef(undefined).
+-define(RECORD_TO_MAP(Name),
+ record_to_map(Record) when element(1, (Record)) =:= (Name) ->
+ record_to_map(record_info(fields, Name), Record, 2, #{})).
+?RECORD_TO_MAP(params).
+%%
+record_to_map([Field | Fields], Record, Index, Map) ->
+ record_to_map(
+ Fields, Record, Index + 1,
+ Map#{ Field => element(Index, Record) });
+record_to_map([], _Record, _Index, Map) ->
+ Map.
+-endif.
+
+timestamp() ->
+ erlang:monotonic_time(second).
+
+
+error_report(Report) ->
+ error_logger:error_report(Report).
+
+-ifdef(undefined).
+info_report(Report) ->
+ error_logger:info_report(Report).
+-endif.
+
+%% Trace point
+trace(Term) -> Term.
diff --git a/lib/ssl/test/dist_cryptcookie.erl b/lib/ssl/test/dist_cryptcookie.erl
new file mode 100644
index 0000000000..703d828606
--- /dev/null
+++ b/lib/ssl/test/dist_cryptcookie.erl
@@ -0,0 +1,595 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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 for encrypted Erlang protocol - a minimal encrypted
+%% distribution protocol based on only a shared secret
+%% and the crypto application, over a Stream
+%%
+-module(dist_cryptcookie).
+-feature(maybe_expr, enable).
+
+%% inet_epmd_* API
+-export([protocol/0,
+ start_dist_ctrl/1,
+ controlling_process/2,
+ hs_data/2]).
+
+-include_lib("kernel/include/dist_util.hrl").
+
+%% ------------------------------------------------------------
+protocol() ->
+ cryptcookie:start_keypair_server().
+
+%% ------------------------------------------------------------
+start_dist_ctrl({Stream, ChunkSize, CipherState}) ->
+ ControllingProcess = self(),
+ Tag = make_ref(),
+ Pid =
+ spawn_opt(
+ fun () ->
+ receive
+ {Tag, Alias, {start, Stream_2}} ->
+ reply(Alias, trace(started)),
+ handshake(
+ Stream_2, Tag, ControllingProcess, ChunkSize,
+ CipherState)
+ end
+ end,
+ [link,
+ {priority, max},
+ {message_queue_data, off_heap},
+ {fullsweep_after, 0}]),
+ ControllingProcessFun = element(3, Stream),
+ Stream_1 = ControllingProcessFun(Stream, Pid),
+ DistCtrlHandle = {Pid, Tag},
+ started = call(DistCtrlHandle, {start, Stream_1}),
+ DistCtrlHandle.
+
+
+call({Pid, Tag}, Request) ->
+ Ref = Alias = monitor(process, Pid, [{alias, reply_demonitor}]),
+ Pid ! {Tag, Alias, Request},
+ receive
+ {Alias, Response} ->
+ Response;
+ {'DOWN', Ref, process, Pid, Reason} ->
+ error({dist_ctrl, Reason})
+ end.
+
+reply(Alias, Response) when is_reference(Alias) ->
+ Alias ! {Alias, Response},
+ ok.
+
+%% ------------------------------------------------------------
+controlling_process(DistCtrlHandle, Pid) ->
+ call(DistCtrlHandle, {controlling_process, Pid}),
+ DistCtrlHandle.
+
+%% ------------------------------------------------------------
+hs_data(NetAddress, {DistCtrl, _} = DistCtrlHandle) ->
+ #hs_data{
+ socket = DistCtrlHandle,
+ f_send =
+ fun (S, Packet) when S =:= DistCtrlHandle ->
+ call(S, {send, Packet})
+ end,
+ f_recv =
+ fun (S, 0, infinity) when S =:= DistCtrlHandle ->
+ call(S, recv)
+ end,
+ f_setopts_pre_nodeup = f_ok(DistCtrlHandle),
+ f_setopts_post_nodeup = f_ok(DistCtrlHandle),
+ f_address =
+ fun (S, Node) when S =:= DistCtrlHandle ->
+ inet_epmd_dist:f_address(NetAddress, Node)
+ end,
+ f_getll =
+ fun (S) when S =:= DistCtrlHandle ->
+ {ok, DistCtrl}
+ end,
+ f_handshake_complete =
+ fun (S, _Node, DistHandle) when S =:= DistCtrlHandle ->
+ call(S, {handshake_complete, DistHandle})
+ end,
+ %%
+ %%
+ mf_tick =
+ fun (S) when S =:= DistCtrlHandle ->
+ DistCtrl ! dist_tick
+ end }.
+
+f_ok(DistCtrlHandle) ->
+ fun (S) when S =:= DistCtrlHandle -> ok end.
+
+
+%% -------------------------------------------------------------------------
+%% net_kernel distribution handshake in progress
+%%
+
+handshake(
+ Stream, Tag, ControllingProcess, ChunkSize,
+ {DecryptState, EncryptState} = _CipherState) ->
+ handshake(
+ Stream, Tag, ControllingProcess,
+ ChunkSize, DecryptState, EncryptState).
+%%
+handshake(
+ Stream = {InStream, OutStream, ControllingProcessFun},
+ Tag, ControllingProcess, ChunkSize, DecryptState, EncryptState) ->
+ receive
+ {Tag, From, {controlling_process, NewControllingProcess}} ->
+ link(NewControllingProcess),
+ unlink(ControllingProcess),
+ reply(From, ok),
+ handshake(
+ Stream, Tag, NewControllingProcess,
+ ChunkSize, DecryptState, EncryptState);
+ {Tag, From, {handshake_complete, DistHandle}} ->
+ InputHandler =
+ spawn_opt(
+ fun () ->
+ link(ControllingProcess),
+ receive
+ {Tag, dist_handle, DistHandle, InStream_2} ->
+ input_handler_start(
+ InStream_2, DecryptState, DistHandle)
+ end
+ end,
+ [link,
+ {priority, normal},
+ {message_queue_data, off_heap},
+ {fullsweep_after, 0}]),
+ _ = monitor(process, InputHandler), % For the benchmark test
+ {InStream_1, OutStream_1, _} =
+ ControllingProcessFun(Stream, InputHandler),
+ false = erlang:dist_ctrl_set_opt(DistHandle, get_size, true),
+ ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler),
+ InputHandler ! {Tag, dist_handle, DistHandle, InStream_1},
+ reply(From, ok),
+ process_flag(priority, normal),
+ output_handler_start(
+ OutStream_1, EncryptState, ChunkSize, DistHandle);
+ %%
+ {Tag, From, {send, Data}} ->
+ {OutStream_1, EncryptState_1} =
+ cryptcookie:encrypt_and_send_chunk(
+ OutStream, EncryptState, Data, iolist_size(Data)),
+ if
+ hd(OutStream_1) =:= closed ->
+ reply(From, {error, closed}),
+ death_row({send, trace(closed)});
+ true ->
+ reply(From, ok),
+ handshake(
+ setelement(2, Stream, OutStream_1),
+ Tag, ControllingProcess,
+ ChunkSize, DecryptState, EncryptState_1)
+ end;
+ {Tag, From, recv} ->
+ {[Data | InStream_1], DecryptState_1} =
+ cryptcookie:recv_and_decrypt_chunk(InStream, DecryptState),
+ if
+ Data =:= closed ->
+ reply(From, {error, Data}),
+ death_row({recv, trace(Data)});
+ true ->
+ reply(From, {ok, binary_to_list(Data)}),
+ handshake(
+ setelement(1, Stream, InStream_1),
+ Tag, ControllingProcess,
+ ChunkSize, DecryptState_1, EncryptState)
+ end;
+ %%
+ Alien ->
+ _ = trace(Alien),
+ handshake(
+ Stream, Tag, ControllingProcess,
+ ChunkSize, DecryptState, EncryptState)
+ end.
+
+
+%% -------------------------------------------------------------------------
+%% Output handler process
+%%
+%% Await an event about what to do; fetch dist data from the VM,
+%% or send a dist tick.
+%%
+%% In case we are overloaded we could get many accumulated
+%% dist_tick messages; make sure to flush all of them
+%% before proceeding with what to do. But, do not use selective
+%% receive since that does not perform well when there are
+%% many messages in the process mailbox.
+
+%% Entry function
+output_handler_start(OutStream, EncryptState, ChunkSize, DistHandle) ->
+ try
+ erlang:dist_ctrl_get_data_notification(DistHandle),
+ output_handler(OutStream, EncryptState, [ChunkSize|DistHandle])
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_report(
+ [output_handler_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+%% Loop top
+%%
+%% Awaiting outbound data or tick
+%%
+output_handler(OutStream, EncryptState, CS_DH) ->
+ receive
+ Msg ->
+ case Msg of
+ dist_data ->
+ output_handler_data(
+ OutStream, EncryptState, CS_DH);
+ dist_tick ->
+ output_handler_tick(
+ OutStream, EncryptState, CS_DH);
+ _ ->
+ %% Ignore
+ _ = trace(Msg),
+ output_handler(OutStream, EncryptState, CS_DH)
+ end
+ end.
+
+%% We have received at least one dist_tick but no dist_data message
+%%
+output_handler_tick(OutStream, EncryptState, CS_DH) ->
+ receive
+ Msg ->
+ case Msg of
+ dist_data ->
+ output_handler_data(
+ OutStream, EncryptState, CS_DH);
+ dist_tick ->
+ %% Consume all dist_tick messages
+ %%
+ output_handler_tick(OutStream, EncryptState, CS_DH);
+ _ ->
+ %% Ignore
+ _ = trace(Msg),
+ output_handler_tick(OutStream, EncryptState, CS_DH)
+ end
+ after 0 ->
+ {OutStream_1, EncryptState_1} =
+ cryptcookie:encrypt_and_send_chunk(
+ OutStream, EncryptState, <<>>, 0),
+ if
+ hd(OutStream_1) =:= closed ->
+ death_row({send_tick, trace(closed)});
+ true ->
+ output_handler(OutStream_1, EncryptState_1, CS_DH)
+ end
+ end.
+
+%% We have received a dist_data notification
+%%
+output_handler_data(OutStream, EncryptState, CS_DH) ->
+ {OutStream_1, EncryptState_1} =
+ output_handler_xfer(OutStream, EncryptState, CS_DH, [], 0, []),
+ erlang:dist_ctrl_get_data_notification(tl(CS_DH)),
+ output_handler(OutStream_1, EncryptState_1, CS_DH).
+
+%% Get outbound data from VM; encrypt and send,
+%% until the VM has no more
+%%
+%% Front,Size,Rear is an Okasaki queue of binaries with total byte Size
+%%
+output_handler_xfer(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear)
+ when hd(CS_DH) =< Size ->
+ %%
+ %% We have a full chunk or more
+ %% -> collect one chunk or less and send
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear);
+output_handler_xfer(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear) ->
+ %% when Size < hd(CS_DH) ->
+ %%
+ %% We do not have a full chunk -> try to fetch more from VM
+ case erlang:dist_ctrl_get_data(tl(CS_DH)) of
+ none ->
+ if
+ Size =:= 0 ->
+ %% No more data from VM, nothing buffered
+ %% -> done, for now
+ {OutStream, EncryptState};
+ true ->
+ %% The VM had no more -> send what we have
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear)
+ end;
+ {Len,Iov} ->
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH,
+ Front, Size + 4 + Len, [<<Len:32>>|Rear],
+ Iov)
+ end.
+
+%% Enqueue VM data while splitting large binaries into
+%% chunk size; hd(CS_DH)
+%%
+output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, []) ->
+ output_handler_xfer(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear);
+output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, [Bin|Iov]) ->
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, Iov, Bin).
+%%
+output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, Iov, Bin) ->
+ BinSize = byte_size(Bin),
+ ChunkSize = hd(CS_DH),
+ if
+ BinSize =< ChunkSize ->
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, [Bin|Rear],
+ Iov);
+ true ->
+ <<Bin1:ChunkSize/binary, Bin2/binary>> = Bin,
+ output_handler_enq(
+ OutStream, EncryptState, CS_DH, Front, Size, [Bin1|Rear],
+ Iov, Bin2)
+ end.
+
+%% Collect small binaries into chunks of at most
+%% chunk size; hd(CS_DH)
+%%
+output_handler_collect(OutStream, EncryptState, CS_DH, [], Zero, []) ->
+ 0 = Zero, % ASSERT
+ %% No more enqueued -> try to get more form VM
+ output_handler_xfer(OutStream, EncryptState, CS_DH, [], Zero, []);
+output_handler_collect(OutStream, EncryptState, CS_DH, Front, Size, Rear) ->
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, [], 0).
+%%
+output_handler_collect(
+ OutStream, EncryptState, CS_DH, [], Zero, [], Acc, DataSize) ->
+ 0 = Zero, % ASSERT
+ output_handler_chunk(
+ OutStream, EncryptState, CS_DH, [], Zero, [], Acc, DataSize);
+output_handler_collect(
+ OutStream, EncryptState, CS_DH, [], Size, Rear, Acc, DataSize) ->
+ %% Okasaki queue transfer Rear -> Front
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, lists:reverse(Rear), Size, [],
+ Acc, DataSize);
+output_handler_collect(
+ OutStream, EncryptState, CS_DH, [Bin|Iov] = Front, Size, Rear,
+ Acc, DataSize) ->
+ ChunkSize = hd(CS_DH),
+ BinSize = byte_size(Bin),
+ DataSize_1 = DataSize + BinSize,
+ if
+ ChunkSize < DataSize_1 ->
+ %% Bin does not fit in chunk -> send Acc
+ output_handler_chunk(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear,
+ Acc, DataSize);
+ DataSize_1 < ChunkSize ->
+ %% Chunk not full yet -> try to accumulate more
+ output_handler_collect(
+ OutStream, EncryptState, CS_DH, Iov, Size - BinSize, Rear,
+ [Bin|Acc], DataSize_1);
+ true -> % DataSize_1 == ChunkSize ->
+ %% Optimize one iteration; Bin fits exactly
+ %% -> accumulate and send
+ output_handler_chunk(
+ OutStream, EncryptState, CS_DH, Iov, Size - BinSize, Rear,
+ [Bin|Acc], DataSize_1)
+ end.
+
+%% Encrypt and send a chunk
+%%
+output_handler_chunk(
+ OutStream, EncryptState, CS_DH, Front, Size, Rear, Acc, DataSize) ->
+ Data = lists:reverse(Acc),
+ {OutStream_1, EncryptState_1} =
+ cryptcookie:encrypt_and_send_chunk(
+ OutStream, EncryptState, Data, DataSize),
+ if
+ hd(OutStream_1) =:= closed ->
+ death_row({send_chunk, trace(closed)});
+ true ->
+ output_handler_collect(
+ OutStream_1, EncryptState_1, CS_DH, Front, Size, Rear)
+ end.
+
+
+%% -------------------------------------------------------------------------
+%% Input handler process
+%%
+
+%% Entry function
+input_handler_start(InStream, DecryptState, DistHandle) ->
+ try
+ input_handler(InStream, DecryptState, DistHandle)
+ catch
+ Class : Reason : Stacktrace when Class =:= error ->
+ error_report(
+ [input_handler_exception,
+ {class, Class},
+ {reason, Reason},
+ {stacktrace, Stacktrace}]),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+%% Loop top
+input_handler(InStream, DecryptState, DistHandle) ->
+ %% Shortcut into the loop
+ {InStream_1, DecryptState_1, Chunk} =
+ input_chunk(InStream, DecryptState),
+ input_handler(
+ InStream_1, DecryptState_1, DistHandle, Chunk, [], byte_size(Chunk)).
+%%
+input_handler(InStream, DecryptState, DistHandle, First, Buffer, Size) ->
+ %% Size is size of First + Buffer
+ case First of
+ <<Packet1Size:32, Packet1:Packet1Size/binary,
+ Packet2Size:32, Packet2:Packet2Size/binary, Rest/binary>> ->
+ erlang:dist_ctrl_put_data(DistHandle, Packet1),
+ erlang:dist_ctrl_put_data(DistHandle, Packet2),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ Rest, Buffer, Size - (8 + Packet1Size + Packet2Size));
+ <<PacketSize:32, Packet:PacketSize/binary, Rest/binary>> ->
+ erlang:dist_ctrl_put_data(DistHandle, Packet),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ Rest, Buffer, Size - (4 + PacketSize));
+ <<PacketSize:32, PacketStart/binary>> ->
+ %% Partial packet in First
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ PacketStart, Buffer, Size - 4, PacketSize);
+ Tick = <<>> ->
+ erlang:dist_ctrl_put_data(DistHandle, Tick),
+ if
+ Buffer =:= [] ->
+ Size = 0, % ASSERT
+ input_handler(InStream, DecryptState, DistHandle);
+ true ->
+ [First_1 | Buffer_1] = lists:reverse(Buffer),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ First_1, Buffer_1, Size)
+ end;
+ <<Bin/binary>> ->
+ %% Partial header in First
+ if
+ 4 =< Size ->
+ %% Complete header in First + Buffer
+ {First_1, Buffer_1, PacketSize} =
+ input_get_packet_size(Bin, lists:reverse(Buffer)),
+ input_handler(
+ InStream, DecryptState, DistHandle,
+ First_1, Buffer_1, Size - 4, PacketSize);
+ true ->
+ %% Incomplete header received so far
+ {InStream_1, DecryptState_1, Chunk} =
+ input_chunk(InStream, DecryptState),
+ input_handler(
+ InStream_1, DecryptState_1, DistHandle,
+ Bin, [Chunk|Buffer], Size + byte_size(Chunk))
+ end
+ end.
+%%
+input_handler(
+ InStream, DecryptState, DistHandle,
+ PacketStart, Buffer, Size, PacketSize) ->
+ %%
+ %% Size is size of PacketStart + Buffer
+ RestSize = Size - PacketSize,
+ if
+ RestSize < 0 ->
+ %% Incomplete packet received so far
+ {InStream_1, DecryptState_1, Chunk} =
+ input_chunk(InStream, DecryptState),
+ input_handler(
+ InStream_1, DecryptState_1, DistHandle,
+ PacketStart, [Chunk|Buffer], Size + byte_size(Chunk),
+ PacketSize);
+ 0 < RestSize, Buffer =:= [] ->
+ %% Rest data in PacketStart
+ <<Packet:PacketSize/binary, Rest/binary>> = PacketStart,
+ erlang:dist_ctrl_put_data(DistHandle, Packet),
+ input_handler(
+ InStream, DecryptState, DistHandle, Rest, [], RestSize);
+ Buffer =:= [] -> % RestSize == 0, Size == 0
+ %% No rest data
+ erlang:dist_ctrl_put_data(DistHandle, PacketStart),
+ input_handler(InStream, DecryptState, DistHandle);
+ true ->
+ %% Split packet from rest data
+ LastBin = hd(Buffer),
+ <<PacketLast:(byte_size(LastBin) - RestSize)/binary,
+ Rest/binary>> = LastBin,
+ Packet = [PacketStart|lists:reverse(tl(Buffer), PacketLast)],
+ erlang:dist_ctrl_put_data(DistHandle, Packet),
+ input_handler(
+ InStream, DecryptState, DistHandle, Rest, [], RestSize)
+ end.
+
+input_get_packet_size(First, [Bin|Buffer]) ->
+ MissingSize = 4 - byte_size(First),
+ if
+ MissingSize =< byte_size(Bin) ->
+ <<Last:MissingSize/binary, Rest/binary>> = Bin,
+ <<PacketSize:32>> = <<First/binary, Last/binary>>,
+ {Rest, lists:reverse(Buffer), PacketSize};
+ true ->
+ input_get_packet_size(<<First/binary, Bin/binary>>, Buffer)
+ end.
+
+input_chunk(InStream, DecryptState) ->
+ {[Chunk | InStream_1], DecryptState_1} =
+ cryptcookie:recv_and_decrypt_chunk(InStream, DecryptState),
+ if
+ is_atom(Chunk) ->
+ _ = Chunk =:= closed
+ orelse
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, Chunk}]),
+ _ = trace({?FUNCTION_NAME, Chunk}),
+ exit(connection_closed);
+ is_binary(Chunk) ->
+ {InStream_1, DecryptState_1, Chunk}
+ end.
+
+
+%% -------------------------------------------------------------------------
+
+%% Wait for getting killed by process link,
+%% and if that does not happen - drop dead
+
+death_row(Reason) ->
+ receive
+ after 5000 ->
+ death_row_timeout(Reason)
+ end.
+
+death_row_timeout(Reason) ->
+ error_report(
+ [?FUNCTION_NAME,
+ {reason, Reason},
+ {pid, self()}]),
+ exit(Reason).
+
+%% -------------------------------------------------------------------------
+
+error_report(Report) ->
+ error_logger:error_report(Report).
+
+-ifdef(undefined).
+info_report(Report) ->
+ error_logger:info_report(Report).
+-endif.
+
+%% Trace point
+trace(Term) -> Term.
diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl
index 611c552858..35e19d86f2 100644
--- a/lib/ssl/test/dtls_api_SUITE.erl
+++ b/lib/ssl/test/dtls_api_SUITE.erl
@@ -191,7 +191,7 @@ dtls_listen_reopen() ->
[{doc, "Test that you close a DTLS 'listner' socket and open a new one for the same port"}].
dtls_listen_reopen(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -356,7 +356,7 @@ client_restarts() ->
[{doc, "Test re-connection "}].
client_restarts(Config) ->
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -435,7 +435,7 @@ client_restarts_multiple_acceptors(Config) ->
%% closed.
%% Then do a new openssl connect with the same client port.
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl
deleted file mode 100644
index 902bb5c252..0000000000
--- a/lib/ssl/test/inet_crypto_dist.erl
+++ /dev/null
@@ -1,1746 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2019-2022. 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 for encrypted Erlang protocol - a minimal encrypted
-%% distribution protocol based on only a shared secret
-%% and the crypto application
-%%
--module(inet_crypto_dist).
--define(DIST_NAME, inet_crypto).
--define(DIST_PROTO, crypto).
--define(DRIVER, inet_tcp).
--define(FAMILY, inet).
-
--export([supported/0, listen/1, accept/1, accept_connection/5,
- setup/5, close/1, select/1, is_node_name/1]).
-
-%% Generalized dist API, for sibling IPv6 module inet6_crypto_dist
--export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
- gen_setup/6, gen_close/2, gen_select/2]).
-
--export([nodelay/0]).
-
-%% Debug
-%%%-compile(export_all).
--export([dbg/0, test_server/0, test_client/1]).
-
--include_lib("kernel/include/net_address.hrl").
--include_lib("kernel/include/dist.hrl").
--include_lib("kernel/include/dist_util.hrl").
-
--define(PACKET_SIZE, 65536).
--define(BUFFER_SIZE, (?PACKET_SIZE bsl 4)).
-
-%% -------------------------------------------------------------------------
-
-%% The curve choice greatly affects setup time,
-%% we really want an Edwards curve but that would
-%% require a very new openssl version.
-%% Twisted brainpool curves (*t1) are faster than
-%% non-twisted (*r1), 256 is much faster than 384,
-%% and so on...
-%%% -define(CURVE, brainpoolP384t1).
-%%% -define(CURVE, brainpoolP256t1).
--define(CURVE, secp256r1).
--define(CIPHER, aes_gcm).
--define(HMAC, sha256).
-
--record(params,
- {socket,
- dist_handle,
- hmac_algorithm = ?HMAC,
- aead_cipher = ?CIPHER,
- rekey_key,
- iv = 12,
- key = 16,
- tag_len = 16,
- rekey_count = 262144,
- rekey_time = 7200000, % 2 hours
- rekey_msg
- }).
-
-params(Socket) ->
- #params{socket = Socket}.
-
-
--record(key_pair,
- {type = ecdh,
- params = ?CURVE,
- public,
- private,
- life_time = 3600000, % 1 hour
- life_count = 256 % Number of connection setups
- }).
-
-supported() ->
- Curve = lists:member(?CURVE, crypto:supports(curves)),
- Cipher = lists:member(?CIPHER, crypto:supports(ciphers)),
- Hmac =
- lists:member(hmac, crypto:supports(macs)) andalso
- lists:member(?HMAC, crypto:supports(hashs)),
- if
- not Curve ->
- "curve " ++ atom_to_list(?CURVE);
- not Cipher ->
- "cipher " ++ atom_to_list(?CIPHER);
- not Hmac ->
- "HMAC " ++ atom_to_list(?HMAC);
- true ->
- ok
- end.
-
-%% -------------------------------------------------------------------------
-%% Keep the node's public/private key pair in the process state
-%% of a key pair server linked to the acceptor process.
-%% Create the key pair the first time it is needed
-%% so crypto gets time to start first.
-%%
-
-start_key_pair_server() ->
- monitor_dist_proc(
- key_pair_server,
- spawn_link(
- fun () ->
- register(?MODULE, self()),
- key_pair_server()
- end)).
-
-key_pair_server() ->
- key_pair_server(undefined, undefined, undefined).
-%%
-key_pair_server(
- #key_pair{life_time = LifeTime, life_count = LifeCount} = KeyPair) ->
- %% Presuming: 1 < LifeCount
- Timer =
- case LifeCount of
- 1 ->
- undefined;
- _ ->
- erlang:start_timer(LifeTime, self(), discard)
- end,
- key_pair_server(KeyPair, Timer, LifeCount - 1).
-%%
-key_pair_server(_KeyPair, Timer, 0) ->
- cancel_timer(Timer),
- key_pair_server();
-key_pair_server(KeyPair, Timer, Count) ->
- receive
- {Pid, Tag, get_key_pair} ->
- case KeyPair of
- undefined ->
- KeyPair_1 = generate_key_pair(),
- Pid ! {Tag, KeyPair_1},
- key_pair_server(KeyPair_1);
- #key_pair{} ->
- Pid ! {Tag, KeyPair},
- key_pair_server(KeyPair, Timer, Count - 1)
- end;
- {Pid, Tag, get_new_key_pair} ->
- cancel_timer(Timer),
- KeyPair_1 = generate_key_pair(),
- Pid ! {Tag, KeyPair_1},
- key_pair_server(KeyPair_1);
- {timeout, Timer, discard} when is_reference(Timer) ->
- key_pair_server()
- end.
-
-generate_key_pair() ->
- #key_pair{type = Type, params = Params} = #key_pair{},
- {Public, Private} =
- crypto:generate_key(Type, Params),
- #key_pair{public = Public, private = Private}.
-
-
-cancel_timer(undefined) ->
- ok;
-cancel_timer(Timer) ->
- erlang_cancel_timer(Timer).
-
-start_rekey_timer(Time) ->
- Timer = erlang:start_timer(Time, self(), rekey_time),
- {timeout, Timer, rekey_time}.
-
-cancel_rekey_timer({timeout, Timer, rekey_time}) ->
- erlang_cancel_timer(Timer).
-
-erlang_cancel_timer(Timer) ->
- case erlang:cancel_timer(Timer) of
- false ->
- receive
- {timeout, Timer, _} -> ok
- end;
- _RemainingTime ->
- ok
- end.
-
-get_key_pair() ->
- call_key_pair_server(get_key_pair).
-
-get_new_key_pair() ->
- call_key_pair_server(get_new_key_pair).
-
-call_key_pair_server(Request) ->
- Pid = whereis(?MODULE),
- Ref = erlang:monitor(process, Pid),
- Pid ! {self(), Ref, Request},
- receive
- {Ref, Reply} ->
- erlang:demonitor(Ref, [flush]),
- Reply;
- {'DOWN', Ref, process, Pid, Reason} ->
- error(Reason)
- end.
-
-compute_shared_secret(
- #key_pair{
- type = PublicKeyType,
- params = PublicKeyParams,
- private = PrivKey}, PubKey) ->
- %%
- crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams).
-
-%% -------------------------------------------------------------------------
-%% Erlang distribution plugin structure explained to myself
-%% -------
-%% These are the processes involved in the distribution:
-%% * net_kernel
-%% * The Acceptor
-%% * The Controller | Handshaker | Ticker
-%% * The DistCtrl process that may be split into:
-%% + The Output controller
-%% + The Input controller
-%% For the regular inet_tcp_dist distribution module, DistCtrl
-%% is not one or two processes, but one port - a gen_tcp socket
-%%
-%% When the VM is started with the argument "-proto_dist inet_crypto"
-%% net_kernel registers the module inet_crypto_dist acli,oams distribution
-%% module. net_kernel calls listen/1 to create a listen socket
-%% and then accept/1 with the listen socket as argument to spawn
-%% the Acceptor process, which is linked to net_kernel. Apparently
-%% the listen socket is owned by net_kernel - I wonder if it could
-%% be owned by the Acceptor process instead...
-%%
-%% The Acceptor process calls blocking accept on the listen socket
-%% and when an incoming socket is returned it spawns the DistCtrl
-%% process a linked to the Acceptor. The ownership of the accepted
-%% socket is transferred to the DistCtrl process.
-%% A message is sent to net_kernel to inform it that an incoming
-%% connection has appeared and the Acceptor awaits a reply from net_kernel.
-%%
-%% net_kernel then calls accept_connection/5 to spawn the Controller |
-%% Handshaker | Ticker process that is linked to net_kernel.
-%% The Controller then awaits a message from the Acceptor process.
-%%
-%% When net_kernel has spawned the Controller it replies with a message
-%% to the Acceptor that then calls DistCtrl to changes its links
-%% so DistCtrl ends up linked to the Controller and not to the Acceptor.
-%% The Acceptor then sends a message to the Controller. The Controller
-%% then changes role into the Handshaker creates a #hs_data{} record
-%% and calls dist_util:handshake_other_started/1. After this
-%% the Acceptor goes back into a blocking accept on the listen socket.
-%%
-%% For the regular distribution inet_tcp_dist DistCtrl is a gen_tcp socket
-%% and when it is a process it also acts as a socket. The #hs_data{}
-%% record used by dist_util presents a set of funs that are used
-%% by dist_util to perform the distribution handshake. These funs
-%% make sure to transfer the handshake messages through the DistCtrl
-%% "socket".
-%%
-%% When the handshake is finished a fun for this purpose in #hs_data{}
-%% is called, which tells DistCtrl that it does not need to be prepared
-%% for any more #hs_data{} handshake calls. The DistCtrl process in this
-%% module then spawns the Input controller process that gets ownership
-%% of the connection's gen_tcp socket and changes into {active, N} mode
-%% so now it gets all incoming traffic and delivers that to the VM.
-%% The original DistCtrl process changes role into the Output controller
-%% process and starts asking the VM for outbound messages and transfers
-%% them on the connection socket.
-%%
-%% The Handshaker now changes into the Ticker role, and uses only two
-%% functions in the #hs_data{} record; one to get socket statistics
-%% and one to send a tick. None of these may block for any reason
-%% in particular not for a congested socket since that would destroy
-%% connection supervision.
-%%
-%%
-%% For an connection net_kernel calls setup/5 which spawns the
-%% Controller process as linked to net_kernel. This Controller process
-%% connects to the other node's listen socket and when that is successful
-%% spawns the DistCtrl process as linked to the controller and transfers
-%% socket ownership to it.
-%%
-%% Then the Controller creates the #hs_data{} record and calls
-%% dist_util:handshake_we_started/1 which changes the process role
-%% into Handshaker.
-%%
-%% When the distribution handshake is finished the procedure is just
-%% as for an incoming connection above.
-%%
-%%
-%% To sum it up.
-%%
-%% There is an Acceptor process that is linked to net_kernel and
-%% informs it when new connections arrive.
-%%
-%% net_kernel spawns Controllers for incoming and for outgoing connections.
-%% these Controllers use the DistCtrl processes to do distribution
-%% handshake and after that becomes Tickers that supervise the connection.
-%%
-%% The Controller | Handshaker | Ticker is linked to net_kernel, and to
-%% DistCtrl, one or both. If any of these connection processes would die
-%% all others should be killed by the links. Therefore none of them may
-%% terminate with reason 'normal'.
-%% -------------------------------------------------------------------------
-
--compile({inline, [socket_options/0]}).
-socket_options() ->
- [binary, {active, false}, {packet, 2}, {nodelay, true},
- {sndbuf, ?BUFFER_SIZE}, {recbuf, ?BUFFER_SIZE},
- {buffer, ?BUFFER_SIZE}].
-
-%% -------------------------------------------------------------------------
-%% select/1 is called by net_kernel to ask if this distribution protocol
-%% is willing to handle Node
-%%
-
-select(Node) ->
- gen_select(Node, ?DRIVER).
-
-gen_select(Node, Driver) ->
- case dist_util:split_node(Node) of
- {node, _, Host} ->
- case Driver:getaddr(Host) of
- {ok, _} -> true;
- _ -> false
- end;
- _ ->
- false
- end.
-
-%% -------------------------------------------------------------------------
-
-is_node_name(Node) ->
- dist_util:is_node_name(Node).
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel to create a listen socket for this
-%% distribution protocol. This listen socket is used by
-%% the Acceptor process.
-%%
-
-listen(Name) ->
- gen_listen(Name, ?DRIVER).
-
-gen_listen(Name, Driver) ->
- {ok, Host} = inet:gethostname(),
- case inet_tcp_dist:gen_listen(Driver, Name, Host) of
- {ok, {Socket, Address, Creation}} ->
- inet:setopts(Socket, socket_options()),
- {ok,
- {Socket, Address#net_address{protocol = ?DIST_PROTO}, Creation}};
- Other ->
- Other
- end.
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel to spawn the Acceptor process that awaits
-%% new connection in a blocking accept and informs net_kernel
-%% when a new connection has appeared, and starts the DistCtrl
-%% "socket" process for the connection.
-%%
-
-accept(Listen) ->
- gen_accept(Listen, ?DRIVER).
-
-gen_accept(Listen, Driver) ->
- NetKernel = self(),
- %%
- %% Spawn Acceptor process
- %%
- monitor_dist_proc(
- acceptor,
- spawn_opt(
- fun () ->
- start_key_pair_server(),
- accept_loop(Listen, Driver, NetKernel)
- end,
- [link, {priority, max}])).
-
-accept_loop(Listen, Driver, NetKernel) ->
- case Driver:accept(trace(Listen)) of
- {ok, Socket} ->
- wait_for_code_server(),
- Timeout = net_kernel:connecttime(),
- DistCtrl = start_dist_ctrl(trace(Socket), Timeout),
- %% DistCtrl is a "socket"
- NetKernel !
- trace({accept,
- self(), DistCtrl, Driver:family(), ?DIST_PROTO}),
- receive
- {NetKernel, controller, Controller} ->
- call_dist_ctrl(DistCtrl, {controller, Controller, self()}),
- Controller ! {self(), controller, Socket};
- {NetKernel, unsupported_protocol} ->
- exit(unsupported_protocol)
- end,
- accept_loop(Listen, Driver, NetKernel);
- AcceptError ->
- exit({accept, AcceptError})
- end.
-
-wait_for_code_server() ->
- %% This is an ugly hack. Starting encryption on a connection
- %% requires the crypto module to be loaded. Loading the crypto
- %% module triggers its on_load function, which calls
- %% code:priv_dir/1 to find the directory where its NIF library is.
- %% However, distribution is started earlier than the code server,
- %% so the code server is not necessarily started yet, and
- %% code:priv_dir/1 might fail because of that, if we receive
- %% an incoming connection on the distribution port early enough.
- %%
- %% If the on_load function of a module fails, the module is
- %% unloaded, and the function call that triggered loading it fails
- %% with 'undef', which is rather confusing.
- %%
- %% So let's avoid that by waiting for the code server to start.
- %%
- case whereis(code_server) of
- undefined ->
- timer:sleep(10),
- wait_for_code_server();
- Pid when is_pid(Pid) ->
- ok
- end.
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel when a new connection has appeared, to spawn
-%% a Controller process that performs the handshake with the new node,
-%% and then becomes the Ticker connection supervisor.
-%% -------------------------------------------------------------------------
-
-accept_connection(Acceptor, DistCtrl, MyNode, Allowed, SetupTime) ->
- gen_accept_connection(
- Acceptor, DistCtrl, MyNode, Allowed, SetupTime, ?DRIVER).
-
-gen_accept_connection(
- Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver) ->
- NetKernel = self(),
- %%
- %% Spawn Controller/handshaker/ticker process
- %%
- monitor_dist_proc(
- accept_controller,
- spawn_opt(
- fun() ->
- do_accept(
- Acceptor, DistCtrl,
- trace(MyNode), Allowed, SetupTime, Driver, NetKernel)
- end,
- [link, {priority, max}])).
-
-do_accept(
- Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver, NetKernel) ->
- %%
- receive
- {Acceptor, controller, Socket} ->
- Timer = dist_util:start_timer(SetupTime),
- HSData =
- hs_data_common(
- NetKernel, MyNode, DistCtrl, Timer,
- Socket, Driver:family()),
- HSData_1 =
- HSData#hs_data{
- this_node = MyNode,
- this_flags = 0,
- allowed = Allowed},
- dist_util:handshake_other_started(trace(HSData_1))
- end.
-
-%% -------------------------------------------------------------------------
-%% Called by net_kernel to spawn a Controller process that sets up
-%% a new connection to another Erlang node, performs the handshake
-%% with the other it, and then becomes the Ticker process
-%% that supervises the connection.
-%% -------------------------------------------------------------------------
-
-setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
- gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, ?DRIVER).
-
-gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, Driver) ->
- NetKernel = self(),
- %%
- %% Spawn Controller/handshaker/ticker process
- %%
- monitor_dist_proc(
- setup_controller,
- spawn_opt(
- setup_fun(
- Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel),
- [link, {priority, max}])).
-
--spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
-setup_fun(
- Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) ->
- %%
- fun() ->
- do_setup(
- trace(Node), Type, MyNode, LongOrShortNames, SetupTime,
- Driver, NetKernel)
- end.
-
--spec do_setup(_,_,_,_,_,_,_) -> no_return().
-do_setup(
- Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) ->
- %%
- {Name, Address} = split_node(Driver, Node, LongOrShortNames),
- ErlEpmd = net_kernel:epmd_module(),
- {ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver),
- Timer = trace(dist_util:start_timer(SetupTime)),
- case ARMod:ARFun(Name, Address, Driver:family()) of
- {ok, Ip, TcpPort, Version} ->
- do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, Version);
- {ok, Ip} ->
- case ErlEpmd:port_please(Name, Ip) of
- {port, TcpPort, Version} ->
- do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, trace(Version));
- Other ->
- _ = trace(
- {ErlEpmd, port_please, [Name, Ip], Other}),
- ?shutdown(Node)
- end;
- Other ->
- _ = trace(
- {ARMod, ARFun, [Name, Address, Driver:family()],
- Other}),
- ?shutdown(Node)
- end.
-
--spec do_setup_connect(_,_,_,_,_,_,_,_,_) -> no_return().
-
-do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, Version) ->
- dist_util:reset_timer(Timer),
- ConnectOpts = trace(connect_options(socket_options())),
- case Driver:connect(Ip, TcpPort, ConnectOpts) of
- {ok, Socket} ->
- DistCtrl =
- try start_dist_ctrl(Socket, net_kernel:connecttime())
- catch error : {dist_ctrl, _} = DistCtrlError ->
- _ = trace(DistCtrlError),
- ?shutdown(Node)
- end,
- %% DistCtrl is a "socket"
- HSData =
- hs_data_common(
- NetKernel, MyNode, DistCtrl, Timer,
- Socket, Driver:family()),
- HSData_1 =
- HSData#hs_data{
- other_node = Node,
- this_flags = 0,
- other_version = Version,
- request_type = Type},
- dist_util:handshake_we_started(trace(HSData_1));
- ConnectError ->
- _ = trace(
- {Driver, connect, [Ip, TcpPort, ConnectOpts],
- ConnectError}),
- ?shutdown(Node)
- end.
-
-%% -------------------------------------------------------------------------
-%% close/1 is only called by net_kernel on the socket returned by listen/1.
-
-close(Socket) ->
- gen_close(Socket, ?DRIVER).
-
-gen_close(Socket, Driver) ->
- Driver:close(trace(Socket)).
-
-%% -------------------------------------------------------------------------
-
-
-hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
- %% Field 'socket' below is set to DistCtrl, which makes
- %% the distribution handshake process (ticker) call
- %% the funs below with DistCtrl as the S argument.
- %% So, S =:= DistCtrl below...
- #hs_data{
- kernel_pid = NetKernel,
- this_node = MyNode,
- socket = DistCtrl,
- timer = Timer,
- %%
- f_send = % -> ok | {error, closed}=>?shutdown()
- fun (S, Packet) when S =:= DistCtrl ->
- try call_dist_ctrl(S, {send, Packet})
- catch error : {dist_ctrl, Reason} ->
- _ = trace(Reason),
- {error, closed}
- end
- end,
- f_recv = % -> {ok, List} | Other=>?shutdown()
- fun (S, 0, infinity) when S =:= DistCtrl ->
- try call_dist_ctrl(S, recv) of
- {ok, Bin} when is_binary(Bin) ->
- {ok, binary_to_list(Bin)};
- Error ->
- Error
- catch error : {dist_ctrl, Reason} ->
- {error, trace(Reason)}
- end
- end,
- f_setopts_pre_nodeup =
- fun (S) when S =:= DistCtrl ->
- ok
- end,
- f_setopts_post_nodeup =
- fun (S) when S =:= DistCtrl ->
- ok
- end,
- f_getll =
- fun (S) when S =:= DistCtrl ->
- {ok, S} %% DistCtrl is the distribution port
- end,
- f_address = % -> #net_address{} | ?shutdown()
- fun (S, Node) when S =:= DistCtrl ->
- try call_dist_ctrl(S, peername) of
- {ok, Address} ->
- case dist_util:split_node(Node) of
- {node, _, Host} ->
- #net_address{
- address = Address,
- host = Host,
- protocol = ?DIST_PROTO,
- family = Family};
- _ ->
- ?shutdown(Node)
- end;
- Error ->
- _ = trace(Error),
- ?shutdown(Node)
- catch error : {dist_ctrl, Reason} ->
- _ = trace(Reason),
- ?shutdown(Node)
- end
- end,
- f_handshake_complete = % -> ok | ?shutdown()
- fun (S, Node, DistHandle) when S =:= DistCtrl ->
- try call_dist_ctrl(S, {handshake_complete, DistHandle})
- catch error : {dist_ctrl, Reason} ->
- _ = trace(Reason),
- ?shutdown(Node)
- end
- end,
- %%
- %% mf_tick/1, mf_getstat/1, mf_setopts/2 and mf_getopts/2
- %% are called by the ticker any time after f_handshake_complete/3
- %% so they may not block the caller even for congested socket
- mf_tick =
- fun (S) when S =:= DistCtrl ->
- S ! dist_tick
- end,
- mf_getstat = % -> {ok, RecvCnt, SendCnt, SendPend} | Other=>ignore_it
- fun (S) when S =:= DistCtrl ->
- case
- inet:getstat(Socket, [recv_cnt, send_cnt, send_pend])
- of
- {ok, Stat} ->
- split_stat(Stat, 0, 0, 0);
- Error ->
- trace(Error)
- end
- end,
- mf_setopts =
- fun (S, Opts) when S =:= DistCtrl ->
- inet:setopts(Socket, setopts_filter(Opts))
- end,
- mf_getopts =
- fun (S, Opts) when S =:= DistCtrl ->
- inet:getopts(Socket, Opts)
- end}.
-
-setopts_filter(Opts) ->
- [Opt ||
- Opt <- Opts,
- case Opt of
- {K, _} when K =:= active; K =:= deliver; K =:= packet -> false;
- K when K =:= list; K =:= binary -> false;
- K when K =:= inet; K =:= inet6 -> false;
- _ -> true
- end].
-
-split_stat([{recv_cnt, R}|Stat], _, W, P) ->
- split_stat(Stat, R, W, P);
-split_stat([{send_cnt, W}|Stat], R, _, P) ->
- split_stat(Stat, R, W, P);
-split_stat([{send_pend, P}|Stat], R, W, _) ->
- split_stat(Stat, R, W, P);
-split_stat([], R, W, P) ->
- {ok, R, W, P}.
-
-%% ------------------------------------------------------------
-%% Determine if EPMD module supports address resolving. Default
-%% is to use inet_tcp:getaddr/2.
-%% ------------------------------------------------------------
-get_address_resolver(EpmdModule, _Driver) ->
- case erlang:function_exported(EpmdModule, address_please, 3) of
- true -> {EpmdModule, address_please};
- _ -> {erl_epmd, address_please}
- end.
-
-
-%% If Node is illegal terminate the connection setup!!
-split_node(Driver, Node, LongOrShortNames) ->
- case dist_util:split_node(Node) of
- {node, Name, Host} ->
- check_node(Driver, Node, Name, Host, LongOrShortNames);
- {host, _} ->
- error_logger:error_msg(
- "** Nodename ~p illegal, no '@' character **~n",
- [Node]),
- ?shutdown2(Node, trace({illegal_node_n@me, Node}));
- _ ->
- error_logger:error_msg(
- "** Nodename ~p illegal **~n", [Node]),
- ?shutdown2(Node, trace({illegal_node_name, Node}))
- end.
-
-check_node(Driver, Node, Name, Host, LongOrShortNames) ->
- case string:split(Host, ".", all) of
- [_] when LongOrShortNames =:= longnames ->
- case Driver:parse_address(Host) of
- {ok, _} ->
- {Name, Host};
- _ ->
- error_logger:error_msg(
- "** System running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_longnames, Host}))
- end;
- [_, _|_] when LongOrShortNames =:= shortnames ->
- error_logger:error_msg(
- "** System NOT running to use "
- "fully qualified hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown2(Node, trace({not_shortnames, Host}));
- _ ->
- {Name, Host}
- end.
-
-%% -------------------------------------------------------------------------
-
-connect_options(Opts) ->
- case application:get_env(kernel, inet_dist_connect_options) of
- {ok, ConnectOpts} ->
- Opts ++ setopts_filter(ConnectOpts);
- _ ->
- Opts
- end.
-
-%% we may not always want the nodelay behaviour
-%% for performance reasons
-nodelay() ->
- case application:get_env(kernel, dist_nodelay) of
- undefined ->
- {nodelay, true};
- {ok, true} ->
- {nodelay, true};
- {ok, false} ->
- {nodelay, false};
- _ ->
- {nodelay, true}
- end.
-
-%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
-%% The DistCtrl process(es).
-%%
-%% At net_kernel handshake_complete spawns off the input controller that
-%% takes over the socket ownership, and itself becomes the output controller
-%%
-%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%% XXX Missing to "productified":
-%%% * Cryptoanalysis by experts, this is crypto amateur work.
-%%% * Is it useful over inet_tls_dist; i.e to not have to bother
-%%% with certificates but instead manage a secret cluster cookie?
-%%% * An application to belong to (kernel)
-%%% * Restart and/or code reload policy (not needed in kernel)
-%%% * Fitting into the epmd/Erlang distro protocol version framework
-%%% (something needs to be created for multiple protocols, epmd,
-%%% multiple address families, fallback to previous version, etc)
-
-
-%% Debug client and server
-
-test_server() ->
- {ok, Listen} = gen_tcp:listen(0, socket_options()),
- {ok, Port} = inet:port(Listen),
- io:format(?MODULE_STRING":test_client(~w).~n", [Port]),
- {ok, Socket} = gen_tcp:accept(Listen),
- test(Socket).
-
-test_client(Port) ->
- {ok, Socket} = gen_tcp:connect(localhost, Port, socket_options()),
- test(Socket).
-
-test(Socket) ->
- start_dist_ctrl(Socket, 10000).
-
-%% -------------------------------------------------------------------------
-
-start_dist_ctrl(Socket, Timeout) ->
- Secret = atom_to_binary(auth:get_cookie(), latin1),
- Controller = self(),
- Server =
- monitor_dist_proc(
- output_handler,
- spawn_opt(
- fun () ->
- receive
- {?MODULE, From, start} ->
- {SendParams, RecvParams} =
- init(Socket, Secret),
- reply(From, self()),
- handshake(SendParams, 1, RecvParams, 1, Controller)
- end
- end,
- [link,
- {priority, max},
- {message_queue_data, off_heap},
- {fullsweep_after, 0}])),
- ok = gen_tcp:controlling_process(Socket, Server),
- call_dist_ctrl(Server, start, Timeout).
-
-
-call_dist_ctrl(Server, Msg) ->
- call_dist_ctrl(Server, Msg, infinity).
-%%
-call_dist_ctrl(Server, Msg, Timeout) ->
- Ref = erlang:monitor(process, Server),
- Server ! {?MODULE, {Ref, self()}, Msg},
- receive
- {Ref, Res} ->
- erlang:demonitor(Ref, [flush]),
- Res;
- {'DOWN', Ref, process, Server, Reason} ->
- error({dist_ctrl, Reason})
- after Timeout -> % Timeout < infinity is only used by start_dist_ctrl/2
- receive
- {'DOWN', Ref, process, Server, _} ->
- receive {Ref, _} -> ok after 0 -> ok end,
- error({dist_ctrl, timeout})
- %% Server will be killed by link
- end
- end.
-
-reply({Ref, Pid}, Msg) ->
- Pid ! {Ref, Msg},
- ok.
-
-%% -------------------------------------------------------------------------
-
--define(TCP_ACTIVE, 16).
--define(CHUNK_SIZE, (?PACKET_SIZE - 512)).
-
--define(HANDSHAKE_CHUNK, 1).
--define(DATA_CHUNK, 2).
--define(TICK_CHUNK, 3).
--define(REKEY_CHUNK, 4).
-
-%% -------------------------------------------------------------------------
-%% Crypto strategy
-%% -------
-%% The crypto strategy is as simple as possible to get an encrypted
-%% connection as benchmark reference. It is geared around AEAD
-%% ciphers in particular AES-GCM.
-%%
-%% The init message and the start message must fit in the TCP buffers
-%% since both sides start with sending the init message, waits
-%% for the other end's init message, sends the start message
-%% and waits for the other end's start message. So if the send
-%% blocks we have a deadlock.
-%%
-%% The init + start sequence tries to implement Password Encrypted
-%% Key Exchange using a node public/private key pair and the
-%% shared secret (the Cookie) to create session encryption keys
-%% that can not be re-created if the shared secret is compromized,
-%% which should create forward secrecy. You need both nodes'
-%% key pairs and the shared secret to decrypt the traffic
-%% between the nodes.
-%%
-%% All exchanged messages uses {packet, 2} i.e 16 bit size header.
-%%
-%% The init message contains a random number and encrypted: the public key
-%% and two random numbers. The encryption is done with Key and IV hashed
-%% from the unencrypted random number and the shared secret.
-%%
-%% The other node's public key is used with the own node's private
-%% key to create a shared key that is hashed with one of the encrypted
-%% random numbers from each side to create Key and IV for the session.
-%%
-%% The start message contains the two encrypted random numbers
-%% this time encrypted with the session keys for verification
-%% by the other side, plus the rekey count. The rekey count
-%% is just there to get an early check for if the other side's
-%% maximum rekey count is acceptable, it is just an embryo
-%% of some better check. Any side may rekey earlier but if the
-%% rekey count is exceeded the connection fails. Rekey is also
-%% triggered by a timer.
-%%
-%% Subsequent encrypted messages has the sequence number and the length
-%% of the message as AAD data, and an incrementing IV. These messages
-%% has got a message type that differentiates data from ticks and rekeys.
-%% Ticks have a random size in an attempt to make them less obvious to spot.
-%%
-%% Rekeying is done by the sender that creates a new key pair and
-%% a new shared secret from the other end's public key and with
-%% this and the current key and iv hashes a new key and iv.
-%% The new public key is sent to the other end that uses it
-%% and its old private key to create the same new shared
-%% secret and from that a new key and iv.
-%% So the receiver keeps its private key, and the sender keeps
-%% the receivers public key for the connection's life time.
-%% While the sender generates a new key pair at every rekey,
-%% which changes the shared secret at every rekey.
-%%
-%% The only reaction to errors is to crash noisily (?) which will bring
-%% down the connection and hopefully produce something useful
-%% in the local log, but all the other end sees is a closed connection.
-%% -------------------------------------------------------------------------
-
-init(Socket, Secret) ->
- #key_pair{public = PubKey} = KeyPair = get_key_pair(),
- Params = params(Socket),
- {R2, R3, Msg} = init_msg(Params, PubKey, Secret),
- ok = gen_tcp:send(Socket, Msg),
- init_recv(Params, Secret, KeyPair, R2, R3).
-
-init_recv(
- #params{socket = Socket, iv = IVLen} = Params, Secret, KeyPair, R2, R3) ->
- %%
- {ok, InitMsg} = gen_tcp:recv(Socket, 0),
- IVSaltLen = IVLen - 6,
- try
- case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of
- {#params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>>} =
- SendParams,
- RecvParams, SendStartMsg} ->
- ok = gen_tcp:send(Socket, SendStartMsg),
- {ok, RecvStartMsg} = gen_tcp:recv(Socket, 0),
- #params{
- iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>>} =
- RecvParams_1 =
- start_msg(RecvParams, R2, R3, RecvStartMsg),
- {SendParams#params{iv = {IV2ASalt, IV2ANo}},
- RecvParams_1#params{iv = {IV2BSalt, IV2BNo}}}
- end
- catch
- Class : Reason : Stacktrace when Class =:= error ->
- error_logger:info_report(
- [init_recv_exception,
- {class, Class},
- {reason, Reason},
- {stacktrace, Stacktrace}]),
- _ = trace({Reason, Stacktrace}),
- exit(connection_closed)
- end.
-
-
-
-init_msg(
- #params{
- hmac_algorithm = HmacAlgo,
- aead_cipher = AeadCipher,
- key = KeyLen,
- iv = IVLen,
- tag_len = TagLen}, PubKeyA, Secret) ->
- %%
- RLen = KeyLen + IVLen,
- <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> =
- crypto:strong_rand_bytes(3 * RLen),
- {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen),
- Plaintext = [R2A, R3A, PubKeyA],
- MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext),
- AAD = [<<MsgLen:32>>, R1A],
- {Ciphertext, Tag} =
- crypto:crypto_one_time_aead(
- AeadCipher, Key1A, IV1A, Plaintext, AAD, TagLen, true),
- Msg = [R1A, Tag, Ciphertext],
- {R2A, R3A, Msg}.
-%%
-init_msg(
- #params{
- hmac_algorithm = HmacAlgo,
- aead_cipher = AeadCipher,
- key = KeyLen,
- iv = IVLen,
- tag_len = TagLen,
- rekey_count = RekeyCount} = Params,
- Secret, KeyPair, R2A, R3A, Msg) ->
- %%
- RLen = KeyLen + IVLen,
- case Msg of
- <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> ->
- {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen),
- MsgLen = byte_size(Msg),
- AAD = [<<MsgLen:32>>, R1B],
- case
- crypto:crypto_one_time_aead(
- AeadCipher, Key1B, IV1B, Ciphertext, AAD, Tag, false)
- of
- <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> ->
- SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
- %%
- {Key2A, IV2A} =
- hmac_key_iv(
- HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen),
- SendParams =
- Params#params{
- rekey_key = PubKeyB,
- key = Key2A, iv = IV2A},
- %%
- StartCleartext = [R2B, R3B, <<RekeyCount:32>>],
- StartMsgLen = TagLen + iolist_size(StartCleartext),
- StartAAD = <<StartMsgLen:32>>,
- {StartCiphertext, StartTag} =
- crypto:crypto_one_time_aead(
- AeadCipher, Key2A, IV2A,
- StartCleartext, StartAAD, TagLen, true),
- StartMsg = [StartTag, StartCiphertext],
- %%
- {Key2B, IV2B} =
- hmac_key_iv(
- HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen),
- RecvParams =
- Params#params{
- rekey_key = KeyPair,
- key = Key2B, iv = IV2B},
- %%
- {SendParams, RecvParams, StartMsg}
- end
- end.
-
-start_msg(
- #params{
- aead_cipher = AeadCipher,
- key = Key2B,
- iv = IV2B,
- tag_len = TagLen,
- rekey_count = RekeyCountA} = RecvParams, R2A, R3A, Msg) ->
- %%
- case Msg of
- <<Tag:TagLen/binary, Ciphertext/binary>> ->
- KeyLen = byte_size(Key2B),
- IVLen = byte_size(IV2B),
- RLen = KeyLen + IVLen,
- MsgLen = byte_size(Msg),
- AAD = <<MsgLen:32>>,
- case
- crypto:crypto_one_time_aead(
- AeadCipher, Key2B, IV2B, Ciphertext, AAD, Tag, false)
- of
- <<R2A:RLen/binary, R3A:RLen/binary, RekeyCountB:32>>
- when RekeyCountA =< (RekeyCountB bsl 2),
- RekeyCountB =< (RekeyCountA bsl 2) ->
- RecvParams#params{rekey_count = RekeyCountB}
- end
- end.
-
-hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) ->
- <<Key:KeyLen/binary, IV:IVLen/binary>> =
- crypto:macN(hmac, HmacAlgo, MacKey, Data, KeyLen + IVLen),
- {Key, IV}.
-
-%% -------------------------------------------------------------------------
-%% net_kernel distribution handshake in progress
-%%
-
-handshake(
- SendParams, SendSeq,
- #params{socket = Socket} = RecvParams, RecvSeq, Controller) ->
- receive
- {?MODULE, From, {controller, Controller_1, Parent}} ->
- Result = link(Controller_1),
- true = unlink(Parent),
- reply(From, Result),
- handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller_1);
- {?MODULE, From, {handshake_complete, DistHandle}} ->
- InputHandler =
- monitor_dist_proc(
- input_handler,
- spawn_opt(
- fun () ->
- link(Controller),
- receive
- DistHandle ->
- input_handler(
- RecvParams, RecvSeq, DistHandle)
- end
- end,
- [link,
- {priority, normal},
- {message_queue_data, off_heap},
- {fullsweep_after, 0}])),
- _ = monitor(process, InputHandler), % For the benchmark test
- ok = gen_tcp:controlling_process(Socket, InputHandler),
- false = erlang:dist_ctrl_set_opt(DistHandle, get_size, true),
- ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler),
- InputHandler ! DistHandle,
- reply(From, ok),
- process_flag(priority, normal),
- output_handler(SendParams, SendSeq, DistHandle);
- %%
- {?MODULE, From, {send, Data}} ->
- {SendParams_1, SendSeq_1, Result} =
- encrypt_and_send_chunk(
- SendParams, SendSeq,
- [?HANDSHAKE_CHUNK, Data], 1 + iolist_size(Data)),
- if
- Result =:= ok ->
- reply(From, ok),
- handshake(
- SendParams_1, SendSeq_1, RecvParams, RecvSeq,
- Controller);
- true ->
- reply(From, {error, closed}),
- death_row({send, trace(Result)})
- end;
- {?MODULE, From, recv} ->
- {RecvParams_1, RecvSeq_1, Result} =
- recv_and_decrypt_chunk(RecvParams, RecvSeq),
- case Result of
- {ok, _} ->
- reply(From, Result),
- handshake(
- SendParams, SendSeq, RecvParams_1, RecvSeq_1,
- Controller);
- {error, _} ->
- reply(From, Result),
- death_row({recv, trace(Result)})
- end;
- {?MODULE, From, peername} ->
- reply(From, inet:peername(Socket)),
- handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller);
- %%
- _Alien ->
- handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller)
- end.
-
-recv_and_decrypt_chunk(#params{socket = Socket} = RecvParams, RecvSeq) ->
- case gen_tcp:recv(Socket, 0) of
- {ok, Chunk} ->
- case decrypt_chunk(RecvParams, RecvSeq, Chunk) of
- <<?HANDSHAKE_CHUNK, Cleartext/binary>> ->
- {RecvParams, RecvSeq + 1, {ok, Cleartext}};
- UnknownChunk when is_binary(UnknownChunk) ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,unknown_chunk}]),
- {RecvParams, RecvSeq + 1, {error, unknown_chunk}};
- #params{} = RecvParams_1 ->
- recv_and_decrypt_chunk(RecvParams_1, 0);
- error ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,decrypt_error}]),
- {RecvParams, RecvSeq, {error, decrypt_error}}
- end;
- Error ->
- {RecvParams, RecvSeq, Error}
- end.
-
-%% -------------------------------------------------------------------------
-%% Output handler process
-%%
-%% Await an event about what to do; fetch dist data from the VM,
-%% send a dist tick, or rekey outbound encryption parameters.
-%%
-%% In case we are overloaded and could get many accumulated
-%% dist_data or dist_tick messages; make sure to flush all of them
-%% before proceeding with what to do. But, do not use selective
-%% receive since that does not perform well when there are
-%% many messages in the process mailbox.
-
-%% Entry function
-output_handler(Params, Seq, DistHandle) ->
- try
- _ = crypto:rand_seed_alg(crypto_cache),
- erlang:dist_ctrl_get_data_notification(DistHandle),
- output_handler(
- Params#params{
- dist_handle = DistHandle,
- rekey_msg = start_rekey_timer(Params#params.rekey_time)},
- Seq)
- catch
- Class : Reason : Stacktrace ->
- error_logger:info_report(
- [output_handler_exception,
- {class, Class},
- {reason, Reason},
- {stacktrace, Stacktrace}]),
- erlang:raise(Class, Reason, Stacktrace)
- end.
-
-%% Loop top
-%%
-%% State: lurking until any interesting message
-output_handler(Params, Seq) ->
- receive
- Msg ->
- case Msg of
- dist_data ->
- output_handler_data(Params, Seq);
- dist_tick ->
- output_handler_tick(Params, Seq);
- _ when Msg =:= Params#params.rekey_msg ->
- output_handler_rekey(Params, Seq);
- _ ->
- %% Ignore
- _ = trace(Msg),
- output_handler(Params, Seq)
- end
- end.
-
-%% State: we have received at least one dist_data message
-output_handler_data(Params, Seq) ->
- receive
- Msg ->
- case Msg of
- dist_data ->
- output_handler_data(Params, Seq);
- dist_tick ->
- output_handler_data(Params, Seq);
- _ when Msg =:= Params#params.rekey_msg ->
- output_handler_rekey(Params, Seq);
- _ ->
- %% Ignore
- _ = trace(Msg),
- output_handler_data(Params, Seq)
- end
- after 0 ->
- {Params_1, Seq_1} = output_handler_xfer(Params, Seq),
- erlang:dist_ctrl_get_data_notification(Params#params.dist_handle),
- output_handler(Params_1, Seq_1)
- end.
-
-%% State: we have received at least one dist_tick but no dist_data message
-output_handler_tick(Params, Seq) ->
- receive
- Msg ->
- case Msg of
- dist_data ->
- output_handler_data(Params, Seq);
- dist_tick ->
- output_handler_tick(Params, Seq);
- _ when Msg =:= Params#params.rekey_msg ->
- output_handler_rekey(Params, Seq);
- _ ->
- %% Ignore
- _ = trace(Msg),
- output_handler_tick(Params, Seq)
- end
- after 0 ->
- TickSize = 7 + rand:uniform(56),
- TickData = binary:copy(<<0>>, TickSize),
- {Params_1, Seq_1, Result} =
- encrypt_and_send_chunk(
- Params, Seq, [?TICK_CHUNK, TickData], 1 + TickSize),
- if
- Result =:= ok ->
- output_handler(Params_1, Seq_1);
- true ->
- death_row({send_tick, trace(Result)})
- end
- end.
-
-output_handler_rekey(Params, Seq) ->
- case encrypt_and_send_rekey_chunk(Params, Seq) of
- #params{} = Params_1 ->
- output_handler(Params_1, 0);
- SendError ->
- death_row({send_rekey, trace(SendError)})
- end.
-
-
-%% Get outbound data from VM; encrypt and send,
-%% until the VM has no more
-%%
-output_handler_xfer(Params, Seq) ->
- output_handler_xfer(Params, Seq, [], 0, []).
-%%
-%% Front,Size,Rear is an Okasaki queue of binaries with total byte Size
-%%
-output_handler_xfer(Params, Seq, Front, Size, Rear)
- when ?CHUNK_SIZE =< Size ->
- %%
- %% We have a full chunk or more
- %% -> collect one chunk or less and send
- output_handler_collect(Params, Seq, Front, Size, Rear);
-output_handler_xfer(Params, Seq, Front, Size, Rear) ->
- %% when Size < ?CHUNK_SIZE ->
- %%
- %% We do not have a full chunk -> try to fetch more from VM
- case erlang:dist_ctrl_get_data(Params#params.dist_handle) of
- none ->
- if
- Size =:= 0 ->
- %% No more data from VM, nothing buffered
- %% -> go back to lurking
- {Params, Seq};
- true ->
- %% The VM had no more -> send what we have
- output_handler_collect(Params, Seq, Front, Size, Rear)
- end;
- {Len,Iov} ->
- output_handler_enq(
- Params, Seq, Front, Size + 4 + Len, [<<Len:32>>|Rear], Iov)
- end.
-
-%% Enqueue VM data while splitting large binaries into ?CHUNK_SIZE
-%%
-output_handler_enq(Params, Seq, Front, Size, Rear, []) ->
- output_handler_xfer(Params, Seq, Front, Size, Rear);
-output_handler_enq(Params, Seq, Front, Size, Rear, [Bin|Iov]) ->
- output_handler_enq(Params, Seq, Front, Size, Rear, Iov, Bin).
-%%
-output_handler_enq(Params, Seq, Front, Size, Rear, Iov, Bin) ->
- BinSize = byte_size(Bin),
- if
- BinSize =< ?CHUNK_SIZE ->
- output_handler_enq(
- Params, Seq, Front, Size, [Bin|Rear], Iov);
- true ->
- <<Bin1:?CHUNK_SIZE/binary, Bin2/binary>> = Bin,
- output_handler_enq(
- Params, Seq, Front, Size, [Bin1|Rear], Iov, Bin2)
- end.
-
-%% Collect small binaries into chunks of at most ?CHUNK_SIZE
-%%
-output_handler_collect(Params, Seq, [], Zero, []) ->
- 0 = Zero, % Assert
- %% No more enqueued -> try to get more form VM
- output_handler_xfer(Params, Seq);
-output_handler_collect(Params, Seq, Front, Size, Rear) ->
- output_handler_collect(Params, Seq, Front, Size, Rear, [], 0).
-%%
-output_handler_collect(Params, Seq, [], Zero, [], Acc, DataSize) ->
- 0 = Zero, % Assert
- output_handler_chunk(Params, Seq, [], Zero, [], Acc, DataSize);
-output_handler_collect(Params, Seq, [], Size, Rear, Acc, DataSize) ->
- %% Okasaki queue transfer Rear -> Front
- output_handler_collect(
- Params, Seq, lists:reverse(Rear), Size, [], Acc, DataSize);
-output_handler_collect(
- Params, Seq, [Bin|Iov] = Front, Size, Rear, Acc, DataSize) ->
- BinSize = byte_size(Bin),
- DataSize_1 = DataSize + BinSize,
- if
- ?CHUNK_SIZE < DataSize_1 ->
- %% Bin does not fit in chunk -> send Acc
- output_handler_chunk(
- Params, Seq, Front, Size, Rear, Acc, DataSize);
- DataSize_1 < ?CHUNK_SIZE ->
- %% Chunk not full yet -> try to accumulate more
- output_handler_collect(
- Params, Seq, Iov, Size - BinSize, Rear, [Bin|Acc], DataSize_1);
- true -> % DataSize_1 == ?CHUNK_SIZE ->
- %% Optimize one iteration; Bin fits exactly -> accumulate and send
- output_handler_chunk(
- Params, Seq, Iov, Size - BinSize, Rear, [Bin|Acc], DataSize_1)
- end.
-
-%% Encrypt and send a chunk
-%%
-output_handler_chunk(Params, Seq, Front, Size, Rear, Acc, DataSize) ->
- Data = lists:reverse(Acc),
- {Params_1, Seq_1, Result} =
- encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK|Data], 1 + DataSize),
- if
- Result =:= ok ->
- %% Try to collect another chunk
- output_handler_collect(Params_1, Seq_1, Front, Size, Rear);
- true ->
- death_row({send_chunk, trace(Result)})
- end.
-
-%% -------------------------------------------------------------------------
-%% Input handler process
-%%
-
-%% Entry function
-input_handler(#params{socket = Socket} = Params, Seq, DistHandle) ->
- try
- ok =
- inet:setopts(
- Socket, [{active, ?TCP_ACTIVE}, nodelay()]),
- input_handler(
- Params#params{dist_handle = DistHandle},
- Seq)
- catch
- Class : Reason : Stacktrace ->
- error_logger:info_report(
- [input_handler_exception,
- {class, Class},
- {reason, Reason},
- {stacktrace, Stacktrace}]),
- erlang:raise(Class, Reason, Stacktrace)
- end.
-
-%% Loop top
-input_handler(Params, Seq) ->
- %% Shortcut into the loop
- {Params_1, Seq_1, Data} = input_data(Params, Seq),
- input_handler(Params_1, Seq_1, Data, [], byte_size(Data)).
-%%
-input_handler(Params, Seq, First, Buffer, Size) ->
- %% Size is size of First + Buffer
- case First of
- <<Packet1Size:32, Packet1:Packet1Size/binary,
- Packet2Size:32, Packet2:Packet2Size/binary, Rest/binary>> ->
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet1),
- erlang:dist_ctrl_put_data(DistHandle, Packet2),
- input_handler(
- Params, Seq, Rest,
- Buffer, Size - (8 + Packet1Size + Packet2Size));
- <<PacketSize:32, Packet:PacketSize/binary, Rest/binary>> ->
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet),
- input_handler(
- Params, Seq, Rest, Buffer, Size - (4 + PacketSize));
- <<PacketSize:32, PacketStart/binary>> ->
- %% Partial packet in First
- input_handler(
- Params, Seq, PacketStart, Buffer, Size - 4, PacketSize);
- <<Bin/binary>> ->
- %% Partial header in First
- if
- 4 =< Size ->
- %% Complete header in First + Buffer
- {First_1, Buffer_1, PacketSize} =
- input_get_packet_size(Bin, lists:reverse(Buffer)),
- input_handler(
- Params, Seq, First_1, Buffer_1, Size - 4, PacketSize);
- true ->
- %% Incomplete header received so far
- {Params_1, Seq_1, More} = input_data(Params, Seq),
- input_handler(
- Params_1, Seq_1, Bin,
- [More|Buffer], Size + byte_size(More))
- end
- end.
-%%
-input_handler(Params, Seq, PacketStart, Buffer, Size, PacketSize) ->
- %% Size is size of PacketStart + Buffer
- RestSize = Size - PacketSize,
- if
- RestSize < 0 ->
- %% Incomplete packet received so far
- {Params_1, Seq_1, More} = input_data(Params, Seq),
- input_handler(
- Params_1, Seq_1, PacketStart,
- [More|Buffer], Size + byte_size(More), PacketSize);
- 0 < RestSize, Buffer =:= [] ->
- %% Rest data in PacketStart
- <<Packet:PacketSize/binary, Rest/binary>> = PacketStart,
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet),
- input_handler(Params, Seq, Rest, [], RestSize);
- Buffer =:= [] -> % RestSize == 0
- %% No rest data
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, PacketStart),
- input_handler(Params, Seq);
- true ->
- %% Split packet from rest data
- LastBin = hd(Buffer),
- <<PacketLast:(byte_size(LastBin) - RestSize)/binary,
- Rest/binary>> = LastBin,
- Packet = [PacketStart|lists:reverse(tl(Buffer), PacketLast)],
- DistHandle = Params#params.dist_handle,
- erlang:dist_ctrl_put_data(DistHandle, Packet),
- input_handler(Params, Seq, Rest, [], RestSize)
- end.
-
-input_get_packet_size(First, [Bin|Buffer]) ->
- MissingSize = 4 - byte_size(First),
- if
- MissingSize =< byte_size(Bin) ->
- <<Last:MissingSize/binary, Rest/binary>> = Bin,
- <<PacketSize:32>> = <<First/binary, Last/binary>>,
- {Rest, lists:reverse(Buffer), PacketSize};
- true ->
- input_get_packet_size(<<First/binary, Bin/binary>>, Buffer)
- end.
-
-input_data(Params, Seq) ->
- receive Msg -> input_data(Params, Seq, Msg) end.
-%%
-input_data(#params{socket = Socket} = Params, Seq, Msg) ->
- case Msg of
- {tcp_passive, Socket} ->
- ok = inet:setopts(Socket, [{active, ?TCP_ACTIVE}]),
- input_data(Params, Seq);
- {tcp, Socket, Ciphertext} ->
- case decrypt_chunk(Params, Seq, Ciphertext) of
- <<?DATA_CHUNK, Chunk/binary>> ->
- {Params, Seq + 1, Chunk};
- <<?TICK_CHUNK, _Dummy/binary>> ->
- input_data(Params, Seq + 1);
- <<UnknownChunk/binary>> ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason, unknown_chunk}]),
- _ = trace(UnknownChunk),
- exit(connection_closed);
- #params{} = Params_1 ->
- input_data(Params_1, 0);
- error ->
- _ = trace(decrypt_error),
- exit(connection_closed)
- end;
- {tcp_closed = Reason, Socket} ->
- error_logger:info_report(
- [?FUNCTION_NAME,
- {reason, Reason}]),
- exit(connection_closed);
- Other ->
- %% Ignore...
- _ = trace(Other),
- input_data(Params, Seq)
- end.
-
-%% -------------------------------------------------------------------------
-%% Encryption and decryption helpers
-
-encrypt_and_send_chunk(
- #params{
- socket = Socket, rekey_count = RekeyCount, rekey_msg = RekeyMsg} = Params,
- Seq, Cleartext, Size) when Seq =:= RekeyCount ->
- %%
- cancel_rekey_timer(RekeyMsg),
- case encrypt_and_send_rekey_chunk(Params, Seq) of
- #params{} = Params_1 ->
- Result =
- gen_tcp:send(
- Socket, encrypt_chunk(Params, 0, Cleartext, Size)),
- {Params_1, 1, Result};
- SendError ->
- {Params, Seq + 1, SendError}
- end;
-encrypt_and_send_chunk(
- #params{socket = Socket} = Params, Seq, Cleartext, Size) ->
- Result =
- gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext, Size)),
- {Params, Seq + 1, Result}.
-
-encrypt_and_send_rekey_chunk(
- #params{
- socket = Socket,
- rekey_key = PubKeyB,
- key = Key,
- iv = {IVSalt, IVNo},
- hmac_algorithm = HmacAlgo} = Params,
- Seq) ->
- %%
- KeyLen = byte_size(Key),
- IVSaltLen = byte_size(IVSalt),
- #key_pair{public = PubKeyA} = KeyPair = get_new_key_pair(),
- case
- gen_tcp:send(
- Socket,
- encrypt_chunk(
- Params, Seq, [?REKEY_CHUNK, PubKeyA], 1 + byte_size(PubKeyA)))
- of
- ok ->
- SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
- IV = <<(IVNo + Seq):48>>,
- {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
- hmac_key_iv(
- HmacAlgo, SharedSecret, [Key, IVSalt, IV],
- KeyLen, IVSaltLen + 6),
- Params#params{
- key = Key_1, iv = {IVSalt_1, IVNo_1},
- rekey_msg = start_rekey_timer(Params#params.rekey_time)};
- SendError ->
- SendError
- end.
-
-encrypt_chunk(
- #params{
- aead_cipher = AeadCipher,
- iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen},
- Seq, Cleartext, Size) ->
- %%
- ChunkLen = Size + TagLen,
- AAD = <<Seq:32, ChunkLen:32>>,
- IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
- {Ciphertext, CipherTag} =
- crypto:crypto_one_time_aead(
- AeadCipher, Key, IVBin, Cleartext, AAD, TagLen, true),
- Chunk = [Ciphertext,CipherTag],
- Chunk.
-
-decrypt_chunk(
- #params{
- aead_cipher = AeadCipher,
- iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) ->
- %%
- ChunkLen = byte_size(Chunk),
- if
- ChunkLen < TagLen ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,short_chunk}]),
- error;
- true ->
- AAD = <<Seq:32, ChunkLen:32>>,
- IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
- CiphertextLen = ChunkLen - TagLen,
- <<Ciphertext:CiphertextLen/binary,
- CipherTag:TagLen/binary>> = Chunk,
- block_decrypt(
- Params, Seq, AeadCipher, Key, IVBin,
- Ciphertext, AAD, CipherTag)
- end.
-
-block_decrypt(
- #params{
- rekey_key = #key_pair{public = PubKeyA} = KeyPair,
- rekey_count = RekeyCount} = Params,
- Seq, AeadCipher, Key, IV, Ciphertext, AAD, CipherTag) ->
- case
- crypto:crypto_one_time_aead(
- AeadCipher, Key, IV, Ciphertext, AAD, CipherTag, false)
- of
- <<?REKEY_CHUNK, Chunk/binary>> ->
- PubKeyLen = byte_size(PubKeyA),
- case Chunk of
- <<PubKeyB:PubKeyLen/binary>> ->
- SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
- KeyLen = byte_size(Key),
- IVLen = byte_size(IV),
- IVSaltLen = IVLen - 6,
- {Key_1, <<IVSalt:IVSaltLen/binary, IVNo:48>>} =
- hmac_key_iv(
- Params#params.hmac_algorithm,
- SharedSecret, [Key, IV], KeyLen, IVLen),
- Params#params{iv = {IVSalt, IVNo}, key = Key_1};
- _ ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,bad_rekey_chunk}]),
- error
- end;
- Chunk when is_binary(Chunk) ->
- case Seq of
- RekeyCount ->
- %% This was one chunk too many without rekeying
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,rekey_overdue}]),
- error;
- _ ->
- Chunk
- end;
- error ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason,decrypt_error}]),
- error
- end.
-
-%% -------------------------------------------------------------------------
-
-%% Wait for getting killed by process link,
-%% and if that does not happen - drop dead
-
-death_row(Reason) ->
- error_logger:info_report(
- [?FUNCTION_NAME,
- {reason, Reason},
- {pid, self()}]),
- receive
- after 5000 ->
- death_row_timeout(Reason)
- end.
-
-death_row_timeout(Reason) ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason, Reason},
- {pid, self()}]),
- exit(Reason).
-
-%% -------------------------------------------------------------------------
-
-%% Trace point
-trace(Term) -> Term.
-
-%% Keep an eye on this Pid (debug)
--ifdef(undefined).
-monitor_dist_proc(_Tag, Pid) ->
- Pid.
--else.
-monitor_dist_proc(Tag, Pid) ->
- spawn(
- fun () ->
- MRef = erlang:monitor(process, Pid),
- error_logger:info_report(
- [?FUNCTION_NAME,
- {type, Tag},
- {pid, Pid}]),
- receive
- {'DOWN', MRef, _, _, normal} ->
- error_logger:error_report(
- [?FUNCTION_NAME,
- {reason, normal},
- {pid, Pid}]);
- {'DOWN', MRef, _, _, Reason} ->
- error_logger:info_report(
- [?FUNCTION_NAME,
- {reason, Reason},
- {pid, Pid}])
- end
- end),
- Pid.
--endif.
-
-dbg() ->
- dbg:stop(),
- dbg:tracer(),
- dbg:p(all, c),
- dbg:tpl(?MODULE, trace, cx),
- dbg:tpl(erlang, dist_ctrl_get_data_notification, cx),
- dbg:tpl(erlang, dist_ctrl_get_data, cx),
- dbg:tpl(erlang, dist_ctrl_put_data, cx),
- ok.
diff --git a/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl b/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl
new file mode 100644
index 0000000000..d350cc8139
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_cryptcookie_inet_ktls.erl
@@ -0,0 +1,230 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. 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%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Plug-in module for inet_epmd distribution
+%% with cryptcookie over inet_tcp with kTLS offloading
+%%
+-module(inet_epmd_cryptcookie_inet_ktls).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+-define(DRIVER, inet_tcp).
+
+%% ------------------------------------------------------------
+net_address() ->
+ Family = ?DRIVER:family(),
+ Protocol = cryptcookie:start_keypair_server(),
+ #net_address{
+ protocol = Protocol,
+ family = Family }.
+
+%% ------------------------------------------------------------
+listen_open(_NetAddress, Options) ->
+ {ok,
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ [])}.
+
+%% ------------------------------------------------------------
+listen_port(_NetAddress, Port, ListenOptions) ->
+ maybe
+ {ok, ListenSocket} ?=
+ ?DRIVER:listen(Port, ListenOptions),
+ {ok, Address} ?=
+ inet:sockname(ListenSocket),
+ {ok, {ListenSocket, Address}}
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ ?DRIVER:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:accept(ListenSocket),
+ {ok, {Ip, _}} ?=
+ inet:sockname(Socket),
+ {ok, {PeerIp, _} = PeerAddress} ?=
+ inet:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ inet_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?= inet_tls_dist:set_ktls(KtlsInfo),
+ ok ?= inet:setopts(Socket, [{packet, 2}, {mode, list}]),
+ {Socket, PeerAddress}
+ else
+ {error, Reason} ->
+ exit({accept, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, Socket) ->
+ ok = ?DRIVER:controlling_process(Socket, Controller),
+ Socket.
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, Socket) ->
+ inet_epmd_dist:hs_data(NetAddress, Socket).
+
+%% ------------------------------------------------------------
+connect(NetAddress, _Timer, Options) ->
+ ConnectOptions =
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ []),
+ #net_address{ address = {Ip, Port} } = NetAddress,
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:connect(Ip, Port, ConnectOptions),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ inet_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?= inet_tls_dist:set_ktls(KtlsInfo),
+ ok ?= inet:setopts(Socket, [{packet, 2}, {mode, list}]),
+ inet_epmd_dist:hs_data(NetAddress, Socket)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ ?DRIVER:recv(Socket, 0, 0);
+ true ->
+ ?DRIVER:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, closed} ->
+ [closed | InStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream = [_ | Socket], Data) ->
+ case ?DRIVER:send(Socket, Data) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case ?DRIVER:controlling_process(Socket, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ maybe
+ ok ?= cryptcookie:supported(),
+ %%
+ {ok, Listen} = ?DRIVER:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} =
+ ?DRIVER:connect({127,0,0,1}, Port, [{active, false}]),
+ try
+ inet_tls_dist:set_ktls(
+ inet_ktls_info(Client, cryptcookie:ktls_info()))
+ after
+ _ = ?DRIVER:close(Client),
+ _ = ?DRIVER:close(Listen)
+ end
+ end.
+
+
+inet_ktls_info(Socket, KtlsInfo) ->
+ KtlsInfo
+ #{ socket => Socket,
+ setopt_fun => fun inet_tls_dist:inet_ktls_setopt/3,
+ getopt_fun => fun inet_tls_dist:inet_ktls_getopt/3 }.
diff --git a/lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl b/lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl
new file mode 100644
index 0000000000..bab43ea331
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_cryptcookie_socket_ktls.erl
@@ -0,0 +1,276 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. 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%
+%%
+%% -------------------------------------------------------------------------
+%%
+%% Plug-in module for inet_epmd distribution
+%% with cryptcookie over inet_tcp with kTLS offloading
+%%
+-module(inet_epmd_cryptcookie_socket_ktls).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+
+%% ------------------------------------------------------------
+net_address() ->
+ #net_address{
+ protocol = dist_cryptcookie:protocol(),
+ family = ?FAMILY }.
+
+%% ------------------------------------------------------------
+listen_open(#net_address{ family = Family}, ListenOptions) ->
+ maybe
+ Key = backlog,
+ Default = 128,
+ Backlog = proplists:get_value(Key, ListenOptions, Default),
+ {ok, ListenSocket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ ListenSocket,
+ inet_epmd_dist:merge_options(
+ ListenOptions, [inet_epmd_dist:nodelay()], [])),
+ {ok, {ListenSocket, Backlog}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+setopts(Socket, Options) ->
+ gen_tcp_socket:socket_setopts(Socket, Options).
+
+%% ------------------------------------------------------------
+listen_port(
+ #net_address{ family = Family }, Port, {ListenSocket, Backlog}) ->
+ maybe
+ Sockaddr =
+ #{family => Family,
+ addr => any,
+ port => Port},
+ ok ?=
+ socket:bind(ListenSocket, Sockaddr),
+ ok ?=
+ socket:listen(ListenSocket, Backlog),
+ {ok, #{ addr := Ip, port := ListenPort}} ?=
+ socket:sockname(ListenSocket),
+ {ok, {ListenSocket, {Ip, ListenPort}}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ socket:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ socket:accept(ListenSocket),
+ {ok, #{ addr := Ip }} ?=
+ socket:sockname(Socket),
+ {ok, #{ addr := PeerIp, port := PeerPort }} ?=
+ socket:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ socket_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?=
+ inet_tls_dist:set_ktls(KtlsInfo),
+ {Socket, {PeerIp, PeerPort}}
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, Socket) ->
+ maybe
+ ok ?=
+ socket:setopt(Socket, {otp,controlling_process}, Controller),
+ Socket
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, Socket) ->
+ inet_epmd_socket:start_dist_ctrl(NetAddress, Socket).
+
+%% ------------------------------------------------------------
+connect(
+ #net_address{ address = {Ip, Port}, family = Family } = NetAddress,
+ _Timer, ConnectOptions) ->
+ maybe
+ {ok, Socket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ Socket,
+ inet_epmd_dist:merge_options(
+ ConnectOptions, [inet_epmd_dist:nodelay()], [])),
+ ConnectAddress =
+ #{ family => Family,
+ addr => Ip,
+ port => Port },
+ ok ?=
+ socket:connect(Socket, ConnectAddress),
+ Stream = stream(Socket),
+ {_Stream_1, _, CipherState} = cryptcookie:init(Stream),
+ KtlsInfo =
+ socket_ktls_info(Socket, cryptcookie:ktls_info(CipherState)),
+ ok ?=
+ inet_tls_dist:set_ktls(KtlsInfo),
+ inet_epmd_socket:start_dist_ctrl(NetAddress, Socket)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ socket:recv(Socket, 0, 0);
+ true ->
+ socket:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, {Reason, _Data}} ->
+ stream_recv_error(InStream, Reason);
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, Reason} ->
+ stream_recv_error(InStream, Reason)
+ end.
+
+stream_recv_error(InStream, Reason) ->
+ if
+ Reason =:= closed;
+ Reason =:= econnreset ->
+ [closed | InStream];
+ true ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream, Bin) when is_binary(Bin) ->
+ stream_send(OutStream, [Bin]);
+stream_send(OutStream = [_ | Socket], Data) ->
+ case socket:sendmsg(Socket, #{ iov => Data }) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case socket:setopt(Socket, {otp,controlling_process}, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ maybe
+ ok ?= inet_epmd_socket:supported(),
+ ok ?= cryptcookie:supported(),
+ %%
+ {ok, Listen} = socket:open(?FAMILY, stream),
+ ok = socket:bind(Listen, loopback),
+ ok = socket:listen(Listen),
+ {ok, Addr} = socket:sockname(Listen),
+ {ok, Client} = socket:open(?FAMILY, stream),
+ ok = socket:connect(Client, Addr),
+ try
+ inet_tls_dist:set_ktls(
+ socket_ktls_info(Client, cryptcookie:ktls_info()))
+ after
+ _ = socket:close(Client),
+ _ = socket:close(Listen)
+ end
+ end.
+
+
+socket_ktls_info(Socket, KtlsInfo) ->
+ KtlsInfo
+ #{ socket => Socket,
+ setopt_fun => fun socket:setopt_native/3,
+ getopt_fun => fun socket:getopt_native/3 }.
diff --git a/lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl b/lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl
new file mode 100644
index 0000000000..26fa27d404
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_dist_cryptcookie_inet.erl
@@ -0,0 +1,200 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. 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 for dist_cryptcookie over inet_tcp
+%%
+-module(inet_epmd_dist_cryptcookie_inet).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+-define(DRIVER, inet_tcp).
+
+%% ------------------------------------------------------------
+net_address() ->
+ Family = ?DRIVER:family(),
+ #net_address{
+ protocol = dist_cryptcookie:protocol(),
+ family = Family }.
+
+%% ------------------------------------------------------------
+listen_open(_NetAddress, Options) ->
+ {ok,
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ [])}.
+
+%% ------------------------------------------------------------
+listen_port(_NetAddress, Port, ListenOptions) ->
+ maybe
+ {ok, ListenSocket} ?=
+ ?DRIVER:listen(Port, ListenOptions),
+ {ok, Address} ?=
+ inet:sockname(ListenSocket),
+ {ok, {ListenSocket, Address}}
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ ?DRIVER:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:accept(ListenSocket),
+ {ok, {Ip, _}} ?=
+ inet:sockname(Socket),
+ {ok, {PeerIp, _} = PeerAddress} ?=
+ inet:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ {DistCtrlHandle, PeerAddress}
+ else
+ {error, Reason} ->
+ exit({accept, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, DistCtrlHandle) ->
+ dist_cryptcookie:controlling_process(DistCtrlHandle, Controller).
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, DistCtrlHandle) ->
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle).
+
+%% ------------------------------------------------------------
+connect(NetAddress, _Timer, Options) ->
+ ConnectOptions =
+ inet_epmd_dist:merge_options(
+ Options,
+ [{active, false}, {mode, binary}, {packet, 0},
+ inet_epmd_dist:nodelay()],
+ []),
+ #net_address{ address = {Ip, Port} } = NetAddress,
+ maybe
+ {ok, Socket} ?=
+ ?DRIVER:connect(Ip, Port, ConnectOptions),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ ?DRIVER:recv(Socket, 0, 0);
+ true ->
+ ?DRIVER:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, closed} ->
+ [closed | InStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream = [_ | Socket], Data) ->
+ case ?DRIVER:send(Socket, Data) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case ?DRIVER:controlling_process(Socket, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ cryptcookie:supported().
diff --git a/lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl b/lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl
new file mode 100644
index 0000000000..fb817060bd
--- /dev/null
+++ b/lib/ssl/test/inet_epmd_dist_cryptcookie_socket.erl
@@ -0,0 +1,241 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2023. 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 for dist_cryptcookie over socket
+%%
+-module(inet_epmd_dist_cryptcookie_socket).
+-feature(maybe_expr, enable).
+
+%% DistMod API
+-export([net_address/0, listen_open/2, listen_port/3, listen_close/1,
+ accept_open/2, accept_controller/3, accepted/3,
+ connect/3]).
+
+-export([supported/0]).
+
+%% Socket I/O Stream internal exports (export entry fun()s)
+-export([stream_recv/2, stream_send/2,
+ stream_controlling_process/2]).
+
+-include_lib("kernel/include/net_address.hrl").
+-include_lib("kernel/include/dist.hrl").
+-include_lib("kernel/include/dist_util.hrl").
+
+-define(FAMILY, inet).
+
+%% ------------------------------------------------------------
+net_address() ->
+ #net_address{
+ protocol = dist_cryptcookie:protocol(),
+ family = ?FAMILY }.
+
+%% ------------------------------------------------------------
+listen_open(#net_address{ family = Family}, ListenOptions) ->
+ maybe
+ Key = backlog,
+ Default = 128,
+ Backlog = proplists:get_value(Key, ListenOptions, Default),
+ {ok, ListenSocket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ ListenSocket,
+ inet_epmd_dist:merge_options(
+ ListenOptions, [inet_epmd_dist:nodelay()], [])),
+ {ok, {ListenSocket, Backlog}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+setopts(Socket, Options) ->
+ gen_tcp_socket:socket_setopts(Socket, Options).
+
+%% ------------------------------------------------------------
+listen_port(
+ #net_address{ family = Family }, Port, {ListenSocket, Backlog}) ->
+ maybe
+ Sockaddr =
+ #{family => Family,
+ addr => any,
+ port => Port},
+ ok ?=
+ socket:bind(ListenSocket, Sockaddr),
+ ok ?=
+ socket:listen(ListenSocket, Backlog),
+ {ok, #{ addr := Ip, port := ListenPort}} ?=
+ socket:sockname(ListenSocket),
+ {ok, {ListenSocket, {Ip, ListenPort}}}
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+listen_close(ListenSocket) ->
+ socket:close(ListenSocket).
+
+%% ------------------------------------------------------------
+accept_open(_NetAddress, ListenSocket) ->
+ maybe
+ {ok, Socket} ?=
+ socket:accept(ListenSocket),
+ {ok, #{ addr := Ip }} ?=
+ socket:sockname(Socket),
+ {ok, #{ addr := PeerIp, port := PeerPort }} ?=
+ socket:peername(Socket),
+ inet_epmd_dist:check_ip(Ip, PeerIp),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ {DistCtrlHandle, {PeerIp, PeerPort}}
+ else
+ {error, Reason} ->
+ exit({?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+accept_controller(_NetAddress, Controller, DistCtrlHandle) ->
+ dist_cryptcookie:controlling_process(DistCtrlHandle, Controller).
+
+%% ------------------------------------------------------------
+accepted(NetAddress, _Timer, DistCtrlHandle) ->
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle).
+
+%% ------------------------------------------------------------
+connect(
+ #net_address{ address = {Ip, Port}, family = Family } = NetAddress,
+ _Timer, ConnectOptions) ->
+ maybe
+ {ok, Socket} ?=
+ socket:open(Family, stream),
+ ok ?=
+ setopts(
+ Socket,
+ inet_epmd_dist:merge_options(
+ ConnectOptions, [inet_epmd_dist:nodelay()], [])),
+ ConnectAddress =
+ #{ family => Family,
+ addr => Ip,
+ port => Port },
+ ok ?=
+ socket:connect(Socket, ConnectAddress),
+ Stream = stream(Socket),
+ CryptcookieInit = cryptcookie:init(Stream),
+ DistCtrlHandle = dist_cryptcookie:start_dist_ctrl(CryptcookieInit),
+ dist_cryptcookie:hs_data(NetAddress, DistCtrlHandle)
+ else
+ {error, _} = Error ->
+ Error
+ end.
+
+%% ------------------------------------------------------------
+%% A socket as an I/O Stream
+%%
+%% Stream :: {InStream, OutStream, ControllingProcessFun}.
+%%
+%% InStream :: [InFun | InState].
+%% InFun :: fun (InStream, Size) ->
+%% [Data | NewInStream] |
+%% [closed | DebugTerm]
+%% NewInStream :: InStream
+%% %% If Size == 0 and there is no pending input data;
+%% %% return immediately with empty Data,
+%% %% otherwise wait for Size bytes of data
+%% %% or any amount of data > 0
+%%
+%% OutStream :: [OutFun | OutState]
+%% OutFun :: fun (OutStream, Data) ->
+%% NewOutStream |
+%% [closed | DebugTerm]
+%% NewOutStream :: OutStream
+%%
+%% Data :: binary() or list(binary())
+%%
+%% ControllingProcessFun :: fun (Stream, pid()) -> NewStream
+%%
+%% NewSTream :: Stream
+
+stream(Socket) ->
+ {stream_in(Socket), stream_out(Socket),
+ fun ?MODULE:stream_controlling_process/2}.
+
+stream_in(Socket) ->
+ [fun ?MODULE:stream_recv/2 | Socket].
+
+stream_recv(InStream = [_ | Socket], Size) ->
+ case
+ if
+ Size =:= 0 ->
+ socket:recv(Socket, 0, 0);
+ true ->
+ socket:recv(Socket, Size, infinity)
+ end
+ of
+ {ok, Data} ->
+ [Data | InStream];
+ {error, {Reason, _Data}} ->
+ stream_recv_error(InStream, Reason);
+ {error, timeout} ->
+ [<<>> | InStream];
+ {error, Reason} ->
+ stream_recv_error(InStream, Reason)
+ end.
+
+stream_recv_error(InStream, Reason) ->
+ if
+ Reason =:= closed;
+ Reason =:= econnreset ->
+ [closed | InStream];
+ true ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+stream_out(Socket) ->
+ [fun ?MODULE:stream_send/2 | Socket].
+
+stream_send(OutStream, Bin) when is_binary(Bin) ->
+ stream_send(OutStream, [Bin]);
+stream_send(OutStream = [_ | Socket], Data) ->
+ case socket:sendmsg(Socket, #{ iov => Data }) of
+ ok ->
+ OutStream;
+ {error, closed} ->
+ [closed | OutStream];
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason, [OutStream, Data]})
+ end.
+
+stream_controlling_process(Stream = {_, [_ | Socket], _}, Pid) ->
+ %%
+ case socket:setopt(Socket, {otp,controlling_process}, Pid) of
+ ok ->
+ Stream;
+ {error, Reason} ->
+ erlang:error({?MODULE, ?FUNCTION_NAME, Reason})
+ end.
+
+%% ------------------------------------------------------------
+supported() ->
+ maybe
+ ok ?= inet_epmd_socket:supported(),
+ cryptcookie:supported()
+ end.
diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl
index 2836e9a0a7..1d0bc82c4e 100644
--- a/lib/ssl/test/openssl_alpn_SUITE.erl
+++ b/lib/ssl/test/openssl_alpn_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -194,7 +194,7 @@ erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) ->
erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) ->
ClientOpts = proplists:get_value(client_rsa_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Protocol = <<"spdy/2">>,
Server = ssl_test_lib:start_server(erlang, [{from, self()}],
[{server_opts, [{alpn_preferred_protocols,
@@ -414,7 +414,7 @@ erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) ->
[{client_opts,
[{alpn_advertised_protocols, [AlpnProtocol]},
{client_preferred_next_protocols,
- {client, [<<"spdy/3">>, <<"http/1.1">>]}}]} | ClientOpts] ++ Config),
+ {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts]}] ++ Config),
case ssl:negotiated_protocol(CSocket) of
{ok, AlpnProtocol} ->
ok;
diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
index 8724595724..fce70645d3 100644
--- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -527,21 +527,25 @@ init_certs(rsa_psk, Config) ->
{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
init_certs(rsa, Config) ->
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ SigAlgs = ssl_test_lib:sig_algs(rsa, Version),
Ext = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
{ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
[[ssl_test_lib:digest()],[ssl_test_lib:digest()],
[ssl_test_lib:digest(), {extensions, Ext}]]}
],
Config, "_peer_keyEncipherment"),
- [{tls_config, #{server_config => ServerOpts,
- client_config => ClientOpts}} |
+ [{tls_config, #{server_config => SigAlgs ++ ServerOpts,
+ client_config => SigAlgs ++ ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(dhe_dss, Config) ->
- {ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ SigAlgs = ssl_test_lib:sig_algs(dsa, Version),
+ {ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
{client_chain, ssl_test_lib:default_cert_chain_conf()}],
- Config, ""),
- [{tls_config, #{server_config => ServerOpts,
- client_config => ClientOpts}} |
+ Config, ""),
+ [{tls_config, #{server_config => SigAlgs ++ ServerOpts,
+ client_config => SigAlgs ++ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(srp_dss, Config) ->
{ClientOpts, ServerOpts} = ssl_test_lib:make_dsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
@@ -954,10 +958,6 @@ test_ciphers(Kex, Cipher, Version) ->
end, Ciphers).
-openssl_suitestr_to_map(OpenSSLSuiteStrs) ->
- [ssl_cipher_format:suite_openssl_str_to_map(SuiteStr) || SuiteStr <- OpenSSLSuiteStrs].
-
-
supported_cipher(Cipher, CipherStr) ->
SupCrypto = proplists:get_value(ciphers, crypto:supports()),
SupOpenssl = [OCipher || OCipher <- ssl_test_lib:openssl_ciphers(), string:find(OCipher, CipherStr) =/= nomatch],
diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl
index 018b49e0b7..36b098bd49 100644
--- a/lib/ssl/test/openssl_client_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_client_cert_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -71,7 +71,7 @@
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-all() ->
+all() ->
[
{group, openssl_client}
].
@@ -99,7 +99,7 @@ groups() ->
].
protocol_groups() ->
- case ssl_test_lib:openssl_sane_dtls() of
+ case ssl_test_lib:openssl_sane_dtls() of
true ->
[{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
@@ -113,7 +113,7 @@ protocol_groups() ->
{group, 'tlsv1.1'},
{group, 'tlsv1'}
]
- end.
+ end.
pre_tls_1_3_protocol_groups() ->
[{group, rsa},
@@ -168,34 +168,34 @@ init_per_group(Group, Config0) when Group == rsa;
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
true;
- (ecdhe_rsa) ->
+ (ecdhe_rsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, rsa} |
- lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
end;
-init_per_group(Alg, Config) when
+init_per_group(Alg, Config) when
Alg == rsa_pss_rsae;
Alg == rsa_pss_pss;
Alg == rsa_pss_rsae_1_3;
Alg == rsa_pss_pss_1_3 ->
Supports = crypto:supports(),
RSAOpts = proplists:get_value(rsa_opts, Supports),
-
- case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
- andalso lists:member(rsa_pss_saltlen, RSAOpts)
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
andalso lists:member(rsa_mgf1_md, RSAOpts)
andalso ssl_test_lib:is_sane_oppenssl_pss(rsa_alg(Alg))
of
@@ -214,9 +214,9 @@ init_per_group(Alg, Config) when
init_per_group(Group, Config0) when Group == ecdsa;
Group == ecdsa_1_3 ->
PKAlg = crypto:supports(public_keys),
- case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg))
- andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
@@ -224,20 +224,20 @@ init_per_group(Group, Config0) when Group == ecdsa;
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
true;
- (ecdhe_ecdsa) ->
+ (ecdhe_ecdsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
[] ->
@@ -277,42 +277,46 @@ init_per_group(eddsa_1_3, Config0) ->
end;
init_per_group(Group, Config0) when Group == dsa ->
PKAlg = crypto:supports(public_keys),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
- andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of
+ NVersion = ssl_test_lib:n_version(proplists:get_value(version, Config0)),
+ SigAlgs = ssl_test_lib:sig_algs(dsa, NVersion),
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg)
+ andalso (ssl_test_lib:openssl_dsa_suites() =/= [])
+ andalso (ssl_test_lib:check_sane_openssl_dsa(Config0))
+ of
true ->
- Config = ssl_test_lib:make_dsa_cert(Config0),
- COpts = proplists:get_value(client_dsa_opts, Config),
- SOpts = proplists:get_value(server_dsa_opts, Config),
+ Config = ssl_test_lib:make_dsa_cert(Config0),
+ COpts = SigAlgs ++ proplists:get_value(client_dsa_opts, Config),
+ SOpts = SigAlgs ++ proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
- (dhe_dss) ->
+ (dhe_dss) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, dsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, [{ciphers, Ciphers} | SOpts]} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
end;
false ->
{skip, "Missing DSS crypto support"}
- end;
+ end;
init_per_group(GroupName, Config) ->
ssl_test_lib:init_per_group_openssl(GroupName, Config).
end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
-init_per_testcase(TestCase, Config) when
+init_per_testcase(TestCase, Config) when
TestCase == client_auth_empty_cert_accepted;
TestCase == client_auth_empty_cert_rejected ->
Version = ssl_test_lib:protocol_version(Config),
@@ -323,7 +327,7 @@ init_per_testcase(TestCase, Config) when
%% instead of sending EMPTY cert message in SSL-3.0 so empty cert test are not
%% relevant
{skip, openssl_behaves_differently};
- _ ->
+ _ ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 30}),
Config
@@ -333,7 +337,7 @@ init_per_testcase(_TestCase, Config) ->
ct:timetrap({seconds, 30}),
Config.
-end_per_testcase(_TestCase, Config) ->
+end_per_testcase(_TestCase, Config) ->
Config.
%%--------------------------------------------------------------------
@@ -366,10 +370,15 @@ client_auth_use_partial_chain() ->
[{doc, "Server does not trust an intermediat CA and fails the connetion as ROOT has expired"}].
client_auth_use_partial_chain(Config) when is_list(Config) ->
Prop = proplists:get_value(tc_group_properties, Config),
- DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)),
+ Group = proplists:get_value(name, Prop),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ Alg = proplists:get_value(cert_key_alg, Config),
+ DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(Group),
+ Ciphers = appropriate_ciphers(Group, Version),
+
{Year, Month, Day} = date(),
#{client_config := ClientOpts0,
- server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(proplists:get_value(cert_key_alg, Config),
+ server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(Alg,
[{client_chain,
[[{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
@@ -391,7 +400,7 @@ client_auth_use_partial_chain(Config) when is_list(Config) ->
end
end,
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, {partial_chain, PartialChain} |
- ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:ssl_options(extra_server, [{ciphers, Ciphers} | ServerOpts0], Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
%% Have to use partial chain functionality on side running Erlang (we are not testing OpenSSL features)
@@ -399,10 +408,15 @@ client_auth_do_not_use_partial_chain() ->
ssl_cert_tests:client_auth_do_not_use_partial_chain().
client_auth_do_not_use_partial_chain(Config) when is_list(Config) ->
Prop = proplists:get_value(tc_group_properties, Config),
- DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)),
+ Group = proplists:get_value(name, Prop),
+ DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(Group),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ Alg = proplists:get_value(cert_key_alg, Config),
+ Ciphers = appropriate_ciphers(Group, Version),
+
{Year, Month, Day} = date(),
#{client_config := ClientOpts0,
- server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(proplists:get_value(cert_key_alg, Config),
+ server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(Alg,
[{client_chain,
[[{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
@@ -415,7 +429,7 @@ client_auth_do_not_use_partial_chain(Config) when is_list(Config) ->
end,
ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config),
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, {partial_chain, PartialChain} |
- ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:ssl_options(extra_server, [{ciphers, Ciphers} | ServerOpts0], Config)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_expired).
%%--------------------------------------------------------------------
@@ -423,24 +437,29 @@ client_auth_do_not_use_partial_chain(Config) when is_list(Config) ->
client_auth_partial_chain_fun_fail() ->
ssl_cert_tests:client_auth_partial_chain_fun_fail().
client_auth_partial_chain_fun_fail(Config) when is_list(Config) ->
- Prop = proplists:get_value(tc_group_properties, Config),
- DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(proplists:get_value(name, Prop)),
+ Prop = proplists:get_value(tc_group_properties, Config),
+ Group = proplists:get_value(name, Prop),
+ DefaultCertConf = ssl_test_lib:default_ecc_cert_chain_conf(Group),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ Alg = proplists:get_value(cert_key_alg, Config),
+ Ciphers = appropriate_ciphers(Group, Version),
+
{Year, Month, Day} = date(),
#{client_config := ClientOpts0,
- server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(proplists:get_value(cert_key_alg, Config),
+ server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_pem(Alg,
[{client_chain,
[[{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}],
[],
[]
]},
- {server_chain, DefaultCertConf}], Config, "do_not_use_partial_chain"),
+ {server_chain, DefaultCertConf}], Config, "partial_chain_fun_fail"),
PartialChain = fun(_CertChain) ->
error(crash_on_purpose)
end,
ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config),
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, {partial_chain, PartialChain} |
- ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:ssl_options(extra_server, [{ciphers, Ciphers} | ServerOpts0], Config)],
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_expired).
%%--------------------------------------------------------------------
@@ -509,3 +528,8 @@ openssl_sig_algs(rsa_pss_pss_1_3) ->
[{sigalgs, "rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_pss_sha256"}];
openssl_sig_algs(rsa_pss_rsae_1_3) ->
[{sigalgs,"rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_rsae_sha256"}].
+
+appropriate_ciphers(dsa, Version) ->
+ ssl:cipher_suites(all, Version);
+appropriate_ciphers(_, Version) ->
+ ssl:cipher_suites(default, Version).
diff --git a/lib/ssl/test/openssl_mfl_SUITE.erl b/lib/ssl/test/openssl_mfl_SUITE.erl
index 54a6788966..1acd18e422 100644
--- a/lib/ssl/test/openssl_mfl_SUITE.erl
+++ b/lib/ssl/test/openssl_mfl_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. 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.
@@ -111,7 +111,7 @@ openssl_client(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
reuse_session_erlang_server(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
Protocol = proplists:get_value(protocol, ServerOpts, tls),
{_, ServerNode, _} = ssl_test_lib:run_where(Config),
MFL = 512,
@@ -135,7 +135,7 @@ reuse_session_erlang_server(Config) when is_list(Config) ->
reuse_session_erlang_client(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = proplists:get_value(server_rsa_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
Protocol = proplists:get_value(protocol, ClientOpts0, tls),
@@ -180,7 +180,7 @@ reuse_session_erlang_client(Config) when is_list(Config) ->
openssl_client(MFL, Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = proplists:get_value(client_rsa_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
Protocol = proplists:get_value(protocol, ServerOpts, tls),
{_, ServerNode, _} = ssl_test_lib:run_where(Config),
@@ -204,7 +204,7 @@ openssl_client(MFL, Config) ->
%%--------------------------------------------------------------------------------
openssl_server(MFL, Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = proplists:get_value(server_rsa_opts, Config),
Protocol = proplists:get_value(protocol, ClientOpts, tls),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl
index ccfd6e45c0..fb8aee6ef7 100644
--- a/lib/ssl/test/openssl_npn_SUITE.erl
+++ b/lib/ssl/test/openssl_npn_SUITE.erl
@@ -37,6 +37,7 @@
%% Test cases
-export([erlang_client_openssl_server_npn/0,
erlang_client_openssl_server_npn/1,
+ erlang_server_openssl_client_npn/0,
erlang_server_openssl_client_npn/1,
erlang_server_openssl_client_npn_only_client/1,
erlang_server_openssl_client_npn_only_server/1,
@@ -225,8 +226,8 @@ erlang_server_openssl_client_npn(Config) when is_list(Config) ->
%%--------------------------------------------------------------------------
-erlang_server_openssl_client_npn_renegotiate() ->
- [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}].
+%% erlang_server_openssl_client_npn_renegotiate() ->
+%% [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}].
erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) ->
ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
@@ -317,17 +318,18 @@ erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
Server =
ssl_test_lib:start_server(erlang, [{from, self()}],
- [{server_opts, [{client_preferred_next_protocols,
- {client, [<<"spdy/2">>], <<"http/1.1">>}
- } | ServerOpts]} | Config]),
+ [{server_opts,
+ [{next_protocols_advertised,
+ [<<"spdy/2">>, <<"http/1.1">>]}
+ | ServerOpts]} | Config]),
Port = ssl_test_lib:inet_port(Server),
- {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
- {options, ClientOpts},
+ {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port},
+ {options, ClientOpts},
return_port], Config),
-
+
Server ! get_socket,
- SSocket =
- receive
+ SSocket =
+ receive
{Server, {socket, Socket}} ->
Socket
end,
diff --git a/lib/ssl/test/openssl_ocsp_SUITE.erl b/lib/ssl/test/openssl_ocsp_SUITE.erl
index 800ce3ce78..045915cc84 100644
--- a/lib/ssl/test/openssl_ocsp_SUITE.erl
+++ b/lib/ssl/test/openssl_ocsp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2023. 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.
@@ -35,17 +35,18 @@
end_per_testcase/2]).
%% Testcases
--export([ocsp_stapling_basic/0,ocsp_stapling_basic/1,
- ocsp_stapling_with_nonce/0, ocsp_stapling_with_nonce/1,
- ocsp_stapling_with_responder_cert/0,ocsp_stapling_with_responder_cert/1,
- ocsp_stapling_revoked/0, ocsp_stapling_revoked/1,
- ocsp_stapling_undetermined/0, ocsp_stapling_undetermined/1,
- ocsp_stapling_no_staple/0, ocsp_stapling_no_staple/1
+-export([stapling_basic/0, stapling_basic/1,
+ stapling_with_nonce/0, stapling_with_nonce/1,
+ stapling_with_responder_cert/0, stapling_with_responder_cert/1,
+ stapling_revoked/0, stapling_revoked/1,
+ stapling_undetermined/0, stapling_undetermined/1,
+ stapling_no_staple/0, stapling_no_staple/1
]).
%% spawn export
--export([ocsp_responder_init/3]).
-
+-export([ocsp_responder_init/4]).
+-define(OCSP_RESPONDER_LOG, "ocsp_resp_log.txt").
+-define(DEBUG, false).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -61,17 +62,18 @@ groups() ->
{'dtlsv1.2', [], ocsp_tests()}].
ocsp_tests() ->
- [ocsp_stapling_basic,
- ocsp_stapling_with_nonce,
- ocsp_stapling_with_responder_cert,
- ocsp_stapling_revoked,
- ocsp_stapling_undetermined,
- ocsp_stapling_no_staple
+ [stapling_basic,
+ stapling_with_nonce,
+ stapling_with_responder_cert,
+ stapling_revoked,
+ stapling_undetermined,
+ stapling_no_staple
].
%%--------------------------------------------------------------------
init_per_suite(Config0) ->
- Config = ssl_test_lib:init_per_suite(Config0, openssl),
+ Config = lists:merge([{debug, ?DEBUG}],
+ ssl_test_lib:init_per_suite(Config0, openssl)),
case ssl_test_lib:openssl_ocsp_support(Config) of
true ->
do_init_per_suite(Config);
@@ -87,7 +89,7 @@ do_init_per_suite(Config) ->
{ok, _} = make_certs:all(DataDir, PrivDir),
ResponderPort = get_free_port(),
- Pid = start_ocsp_responder(ResponderPort, PrivDir),
+ Pid = start_ocsp_responder(ResponderPort, PrivDir, ?config(debug, Config)),
NewConfig =
lists:merge(
@@ -97,10 +99,10 @@ do_init_per_suite(Config) ->
ssl_test_lib:cert_options(NewConfig).
-
end_per_suite(Config) ->
ResponderPid = proplists:get_value(responder_pid, Config),
ssl_test_lib:close(ResponderPid),
+ [ssl_test_lib:ct_pal_file(?OCSP_RESPONDER_LOG) || ?config(debug, Config)],
ssl_test_lib:end_per_suite(Config).
%%--------------------------------------------------------------------
@@ -122,31 +124,30 @@ end_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-ocsp_stapling_basic() ->
+stapling_basic() ->
[{doc, "Verify OCSP stapling works without nonce and responder certs."}].
-ocsp_stapling_basic(Config)
+stapling_basic(Config)
when is_list(Config) ->
- ocsp_stapling_helper(Config, [{ocsp_nonce, false}]).
+ stapling_helper(Config, [{ocsp_nonce, false}]).
-ocsp_stapling_with_nonce() ->
+stapling_with_nonce() ->
[{doc, "Verify OCSP stapling works with nonce."}].
-ocsp_stapling_with_nonce(Config)
+stapling_with_nonce(Config)
when is_list(Config) ->
- ocsp_stapling_helper(Config, [{ocsp_nonce, true}]).
+ stapling_helper(Config, [{ocsp_nonce, true}]).
-ocsp_stapling_with_responder_cert() ->
+stapling_with_responder_cert() ->
[{doc, "Verify OCSP stapling works with nonce and responder certs."}].
-ocsp_stapling_with_responder_cert(Config)
+stapling_with_responder_cert(Config)
when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
{ok, ResponderCert} =
file:read_file(filename:join(PrivDir, "b.server/cert.pem")),
[{'Certificate', Der, _IsEncrypted}] =
public_key:pem_decode(ResponderCert),
- ocsp_stapling_helper(Config, [{ocsp_nonce, true},
- {ocsp_responder_certs, [Der]}]).
+ stapling_helper(Config, [{ocsp_nonce, true}, {ocsp_responder_certs, [Der]}]).
-ocsp_stapling_helper(Config, Opts) ->
+stapling_helper(Config, Opts) ->
PrivDir = proplists:get_value(priv_dir, Config),
CACertsFile = filename:join(PrivDir, "a.server/cacerts.pem"),
Data = "ping", %% 4 bytes
@@ -159,7 +160,8 @@ ocsp_stapling_helper(Config, Opts) ->
ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer},
{cacertfile, CACertsFile},
{server_name_indication, disable},
- {ocsp_stapling, true}] ++ Opts, Config),
+ {ocsp_stapling, true}] ++ Opts,
+ Config),
Client = ssl_test_lib:start_client(erlang,
[{port, Port},
{options, ClientOpts}], Config),
@@ -169,29 +171,29 @@ ocsp_stapling_helper(Config, Opts) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
-ocsp_stapling_revoked() ->
+stapling_revoked() ->
[{doc, "Verify OCSP stapling works with revoked certificate."}].
-ocsp_stapling_revoked(Config)
+stapling_revoked(Config)
when is_list(Config) ->
- ocsp_stapling_negative_helper(Config, "revoked/cacerts.pem",
+ stapling_negative_helper(Config, "revoked/cacerts.pem",
openssl_ocsp_revoked, certificate_revoked).
-ocsp_stapling_undetermined() ->
+stapling_undetermined() ->
[{doc, "Verify OCSP stapling works with certificate with undetermined status."}].
-ocsp_stapling_undetermined(Config)
+stapling_undetermined(Config)
when is_list(Config) ->
- ocsp_stapling_negative_helper(Config, "undetermined/cacerts.pem",
+ stapling_negative_helper(Config, "undetermined/cacerts.pem",
openssl_ocsp_undetermined, bad_certificate).
-ocsp_stapling_no_staple() ->
+stapling_no_staple() ->
[{doc, "Verify OCSP stapling works with a missing OCSP response."}].
-ocsp_stapling_no_staple(Config)
+stapling_no_staple(Config)
when is_list(Config) ->
%% Start a server that will not include an OCSP response.
- ocsp_stapling_negative_helper(Config, "a.server/cacerts.pem",
+ stapling_negative_helper(Config, "a.server/cacerts.pem",
openssl, bad_certificate).
-ocsp_stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError) ->
+stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError) ->
PrivDir = proplists:get_value(priv_dir, Config),
CACertsFile = filename:join(PrivDir, CACertsPath),
GroupName = undefined,
@@ -214,12 +216,13 @@ ocsp_stapling_negative_helper(Config, CACertsPath, ServerVariant, ExpectedError)
ssl_test_lib:check_client_alert(Client, ExpectedError).
%%--------------------------------------------------------------------
-%% Intrernal functions -----------------------------------------------
+%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-start_ocsp_responder(ResponderPort, PrivDir) ->
+start_ocsp_responder(ResponderPort, PrivDir, Debug) ->
Starter = self(),
- Pid = erlang:spawn_link(
- ?MODULE, ocsp_responder_init, [ResponderPort, PrivDir, Starter]),
+ Pid = erlang:spawn(
+ ?MODULE, ocsp_responder_init,
+ [ResponderPort, PrivDir, Starter, Debug]),
receive
{started, Pid} ->
Pid;
@@ -227,31 +230,40 @@ start_ocsp_responder(ResponderPort, PrivDir) ->
throw({unable_to_start_ocsp_service, Reason})
end.
-ocsp_responder_init(ResponderPort, PrivDir, Starter) ->
+ocsp_responder_init(ResponderPort, PrivDir, Starter, Debug) ->
Index = filename:join(PrivDir, "otpCA/index.txt"),
CACerts = filename:join(PrivDir, "b.server/cacerts.pem"),
Cert = filename:join(PrivDir, "b.server/cert.pem"),
Key = filename:join(PrivDir, "b.server/key.pem"),
-
+ DebugArgs = case Debug of
+ true -> ["-text", "-out", ?OCSP_RESPONDER_LOG];
+ _ -> []
+ end,
Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
- "-rkey", Key, "-port", erlang:integer_to_list(ResponderPort)],
+ "-rkey", Key, "-port", erlang:integer_to_list(ResponderPort)] ++
+ DebugArgs,
process_flag(trap_exit, true),
Port = ssl_test_lib:portable_open_port("openssl", Args),
+ ?CT_LOG("OCSP responder: Started Port = ~p", [Port]),
ocsp_responder_loop(Port, {new, Starter}).
ocsp_responder_loop(Port, {Status, Starter} = State) ->
receive
- {_Port, closed} ->
- ?LOG("Port Closed"),
+ close ->
+ ?CT_LOG("OCSP responder: received close", []),
+ ok;
+ {Port, closed} ->
+ ?CT_LOG("OCSP responder: Port = ~p Closed", [Port]),
ok;
- {'EXIT', _Port, Reason} ->
- ?LOG("Port Closed ~p",[Reason]),
+ {'EXIT', Sender, _Reason} ->
+ ?CT_LOG("OCSP responder: Sender = ~p Closed",[Sender]),
ok;
- {Port, {data, _Msg}} when Status == new ->
+ {Port, {data, Msg}} when Status == new ->
+ ?CT_LOG("OCSP responder: Msg = ~p", [Msg]),
Starter ! {started, self()},
ocsp_responder_loop(Port, {started, undefined});
{Port, {data, Msg}} ->
- ?PAL("Responder Msg ~p",[Msg]),
+ ?CT_LOG("OCSP responder: Responder Msg = ~p",[Msg]),
ocsp_responder_loop(Port, State)
after 1000 ->
case Status of
diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl
index 01b5a7a8a9..5102730396 100644
--- a/lib/ssl/test/openssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl
@@ -230,7 +230,7 @@ erlang_server_openssl_client_nowrap_seqnum() ->
erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{_, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl
index 7f7a9b739e..057d80b6f3 100644
--- a/lib/ssl/test/openssl_server_cert_SUITE.erl
+++ b/lib/ssl/test/openssl_server_cert_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -74,7 +74,7 @@
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-all() ->
+all() ->
[
{group, openssl_server}].
@@ -103,7 +103,7 @@ groups() ->
].
protocol_groups() ->
- case ssl_test_lib:openssl_sane_dtls() of
+ case ssl_test_lib:openssl_sane_dtls() of
true ->
[{group, 'tlsv1.3'},
{group, 'tlsv1.2'},
@@ -117,7 +117,7 @@ protocol_groups() ->
{group, 'tlsv1.1'},
{group, 'tlsv1'}
]
- end.
+ end.
pre_tls_1_3_protocol_groups() ->
[{group, rsa},
@@ -156,27 +156,27 @@ end_per_suite(Config) ->
init_per_group(openssl_server, Config0) ->
Config = proplists:delete(server_type, proplists:delete(client_type, Config0)),
- [{client_type, erlang}, {server_type, openssl} | Config];
+ [{client_type, erlang}, {server_type, openssl} | Config];
init_per_group(rsa = Group, Config0) ->
Config = ssl_test_lib:make_rsa_cert(Config0),
COpts = proplists:get_value(client_rsa_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
%% Make sure _rsa* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) ->
true;
- (ecdhe_rsa) ->
+ (ecdhe_rsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, rsa} |
- lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
@@ -203,9 +203,9 @@ init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
Alg == rsa_pss_pss ->
Supports = crypto:supports(),
RSAOpts = proplists:get_value(rsa_opts, Supports),
-
- case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
- andalso lists:member(rsa_pss_saltlen, RSAOpts)
+
+ case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
+ andalso lists:member(rsa_pss_saltlen, RSAOpts)
andalso lists:member(rsa_mgf1_md, RSAOpts)
andalso ssl_test_lib:is_sane_oppenssl_pss(Alg)
of
@@ -223,9 +223,9 @@ init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
end;
init_per_group(ecdsa = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
- case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg))
- andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
@@ -233,20 +233,20 @@ init_per_group(ecdsa = Group, Config0) ->
SOpts = proplists:get_value(server_ecdsa_opts, Config),
%% Make sure ecdh* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) ->
true;
- (ecdhe_ecdsa) ->
+ (ecdhe_ecdsa) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
[] ->
@@ -258,8 +258,8 @@ init_per_group(ecdsa = Group, Config0) ->
init_per_group(ecdsa_1_3 = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse
- lists:member(dh, PKAlg))
- andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
+ lists:member(dh, PKAlg))
+ andalso (ssl_test_lib:openssl_ecdsa_suites() =/= [])
of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
@@ -311,35 +311,37 @@ init_per_group(eddsa_1_3, Config0) ->
end;
init_per_group(dsa = Group, Config0) ->
PKAlg = crypto:supports(public_keys),
- case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) andalso
- (ssl_test_lib:openssl_dsa_suites() =/= []) of
+ case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) andalso
+ (ssl_test_lib:openssl_dsa_suites() =/= [])
+ andalso (ssl_test_lib:check_sane_openssl_dsa(Config0))
+ of
true ->
- Config = ssl_test_lib:make_dsa_cert(Config0),
+ Config = ssl_test_lib:make_dsa_cert(Config0),
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
%% Make sure dhe_dss* suite is chosen by ssl_test_lib:start_server
Version = ssl_test_lib:protocol_version(Config),
- Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
+ Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) ->
true;
- (dhe_dss) ->
+ (dhe_dss) ->
true;
(_) ->
- false
- end, Version),
+ false
+ end, Version),
case Ciphers of
[_|_] ->
[{cert_key_alg, dsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, [{ciphers, Ciphers} | COpts]},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, [{ciphers, Ciphers} | COpts] ++ ssl_test_lib:sig_algs(dsa, Version)},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
[] ->
{skip, {no_sup, Group, Version}}
end;
false ->
{skip, "Missing DSS crypto support"}
- end;
+ end;
init_per_group(GroupName, Config) ->
case ssl_test_lib:is_protocol_version(GroupName) of
true ->
@@ -361,7 +363,7 @@ init_per_testcase(_TestCase, Config) ->
ct:timetrap({seconds, 30}),
Config.
-end_per_testcase(_TestCase, Config) ->
+end_per_testcase(_TestCase, Config) ->
Config.
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/property_test/ssl_eqc_chain.erl b/lib/ssl/test/property_test/ssl_eqc_chain.erl
index 8ac8446cab..26bba1fc4a 100644
--- a/lib/ssl/test/property_test/ssl_eqc_chain.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_chain.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2018-2021. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -58,12 +58,14 @@
%%--------------------------------------------------------------------
prop_tls_unordered_path(PrivDir) ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_options(Version, PrivDir)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
_ ->
true
@@ -75,16 +77,18 @@ prop_tls_unordered_path(PrivDir) ->
prop_tls_extraneous_path(PrivDir) ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extraneous_options(Version, PrivDir)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
- of
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
+ of
_ ->
- true
- catch
+ true
+ catch
_:_ ->
false
end
@@ -92,15 +96,17 @@ prop_tls_extraneous_path(PrivDir) ->
prop_tls_extraneous_paths() ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extra_extraneous_options(Version)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
- _ ->
- true
+ _ ->
+ true
catch
_:_ ->
false
@@ -109,15 +115,17 @@ prop_tls_extraneous_paths() ->
prop_tls_extraneous_and_unordered_path() ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_extraneous_options(Version)),
- try
+ try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions, [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
- _ ->
- true
+ _ ->
+ true
catch
_:_ ->
false
@@ -128,13 +136,16 @@ prop_client_cert_auth() ->
?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), client_cert_auth_opts(Version)),
try
[TLSVersion] = proplists:get_value(versions, ClientOptions),
- ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang},
- {client_type, erlang},
- {version, TLSVersion}
- ])
+ SigAlgs = signature_algs(TLSVersion),
+ ssl_test_lib:basic_test(SigAlgs ++ ClientOptions,
+ SigAlgs ++ ServerOptions,
+ [{server_type, erlang},
+ {client_type, erlang},
+ {version, TLSVersion}
+ ])
of
- _ ->
- true
+ _ ->
+ true
catch
_:_ ->
false
@@ -146,24 +157,24 @@ prop_client_cert_auth() ->
%%--------------------------------------------------------------------
tls_version() ->
Versions = [Version || Version <- ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1', 'dtlsv1.2', 'dtlsv1'],
- ssl_test_lib:sufficient_crypto_support(Version)
+ ssl_test_lib:sufficient_crypto_support(Version)
],
oneof(Versions).
key_alg(Version) when Version == 'tlsv1.3';
Version == 'tlsv1.2';
Version == 'dtlsv1.2'->
- oneof([rsa, ecdsa]);
+ oneof([rsa, ecdsa]);
key_alg(_) ->
oneof([rsa]).
server_options('tlsv1.3') ->
- [{verify, verify_peer},
- {fail_if_no_peer_cert, true},
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
{reuseaddr, true}];
server_options(_) ->
- [{verify, verify_peer},
- {fail_if_no_peer_cert, true},
+ [{verify, verify_peer},
+ {fail_if_no_peer_cert, true},
{reuse_sessions, false},
{reuseaddr, true}].
@@ -184,28 +195,28 @@ unordered_der_cert_chain_opts(Version, Alg) ->
client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
peer => peer_key(Alg)},
- client_chain => #{root => root_key(Alg),
+ client_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
- peer => peer_key(Alg)}}),
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ClientConf)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ServerConf)]}.
+ peer => peer_key(Alg)}}),
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_der_conf(ServerConf)]}.
unordered_pem_cert_chain_opts(Version, Alg, PrivDir) ->
Index = integer_to_list(erlang:unique_integer()),
DerConfig = public_key:pkix_test_data(#{server_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
peer => peer_key(Alg)},
- client_chain => #{root => root_key(Alg),
+ client_chain => #{root => root_key(Alg),
intermediates => intermediates(Alg, 4),
- peer => peer_key(Alg)}}),
+ peer => peer_key(Alg)}}),
ClientBase = filename:join(PrivDir, "client_prop_test" ++ Index),
- SeverBase = filename:join(PrivDir, "server_prop_test" ++ Index),
+ SeverBase = filename:join(PrivDir, "server_prop_test" ++ Index),
PemConfig = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase),
ClientConf = proplists:get_value(client_config, PemConfig),
ServerConf = proplists:get_value(server_config, PemConfig),
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ClientConf)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ServerConf)]}.
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ClientConf)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} | unordered_pem_conf(ServerConf)]}.
unordered_der_conf(Config) ->
Cert = proplists:get_value(cert, Config),
@@ -253,101 +264,101 @@ client_cert_auth_opts(Version) ->
?LET({SAlg, CAlg}, {key_alg(Version), key_alg(Version)}, der_cert_chains(Version, CAlg,SAlg)).
extraneous_der_cert_chain_opts(Version, Alg) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)}}),
-
+
{ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
{ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
-
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ClientChain, ServerRoot, [OrgSRoot], ClientConf0)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ServerChain, ClientRoot, [OrgCRoot], ServerConf0)]}.
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot, [OrgSRoot], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot, [OrgCRoot], ServerConf0)]}.
extraneous_pem_cert_chain_opts(Version, Alg, PrivDir) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 1),
peer => peer_key(ecdsa)}}),
{ClientChain, ClientRoot} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 1),
{ServerChain, ServerRoot} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 1),
-
+
%% Only use files in final step
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_pem_conf(ClientChain, ServerRoot, OrgSRoot, ClientConf0, PrivDir)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_pem_conf(ServerChain, ClientRoot, OrgCRoot, ServerConf0, PrivDir)]}.
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ClientChain, ServerRoot, OrgSRoot, ClientConf0, PrivDir)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_pem_conf(ServerChain, ClientRoot, OrgCRoot, ServerConf0, PrivDir)]}.
extra_extraneous_der_cert_chain_opts(Version, Alg) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)}}),
-
-
+
+
{ClientChain0, ClientRoot0} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
{ServerChain0, ServerRoot0} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
-
+
{ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
{ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
-
+
{ClientChain, ServerChain} = create_extraneous_chains(ClientChain0, ClientChain1,
ServerChain0, ServerChain1),
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
der_extraneous_and_unorder_chain(Version, Alg) ->
- #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
- #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
+ #{cert := OrgSRoot} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(Alg)),
+ #{cert := OrgCRoot} = CRoot = public_key:pkix_test_root_cert("OTP test client ROOT", root_key(Alg)),
#{server_config := ServerConf0,
- client_config := ClientConf0} =
+ client_config := ClientConf0} =
public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)},
- client_chain => #{root => CRoot,
+ client_chain => #{root => CRoot,
intermediates => intermediates(Alg, 3),
peer => peer_key(ecdsa)}}),
{ClientChain0, ClientRoot0} = chain_and_root(ClientConf0),
{ServerChain0, ServerRoot0} = chain_and_root(ServerConf0),
-
+
{ClientChain1, ClientRoot1} = extraneous_chain_and_root(ClientConf0, "OTP test client ROOT", 2),
{ServerChain1, ServerRoot1} = extraneous_chain_and_root(ServerConf0, "OTP test server ROOT", 2),
-
+
{ClientChain, ServerChain} = create_extraneous_and_unorded(ClientChain0, ClientChain1,
ServerChain0, ServerChain1),
-
- {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
- server_options(Version) ++ [protocol(Version), {versions, [Version]} |
- extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
+
+ {client_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ClientChain, ServerRoot1, [OrgSRoot, ServerRoot0], ClientConf0)],
+ server_options(Version) ++ [protocol(Version), {versions, [Version]} |
+ extraneous_der_conf(ServerChain, ClientRoot1, [OrgCRoot, ClientRoot0], ServerConf0)]}.
der_cert_chains(Version, CAlg, SAlg) ->
SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", root_key(SAlg)),
@@ -384,7 +395,7 @@ extraneous_der_conf(Chain, NewRoot, OrgRoots,Config0) ->
CaCerts = proplists:get_value(cacerts, Config0),
Config1 = [{cert, Chain} | proplists:delete(cert, Config0)],
[{cacerts, [NewRoot | CaCerts -- OrgRoots]} | proplists:delete(cacerts, Config1)].
-
+
extraneous_pem_conf(Chain, NewRoot, OldRoot, Config0, PrivDir) ->
Int = erlang:unique_integer(),
FileName = filename:join(PrivDir, "prop_test" ++ integer_to_list(Int)),
@@ -409,7 +420,7 @@ create_extraneous_chains([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OC
create_extraneous_and_unorded([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT],
[Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) ->
{[Client, OCCA0, CCA2, OCCA2, OCROOT, OCCA1], [Server, OSCA0, SCA2, OSCA2, OSROOT, OSCA1]}.
-
+
root_key(ecdsa) ->
[{key,{namedCurve, ?secp256r1}}]; %% Use a curve that will be default supported in all TLS versions
root_key(rsa) ->
@@ -436,8 +447,16 @@ hardcode_rsa_keys([Head | Tail], N, Acc) ->
new_intermediat(CA0, Key) ->
OTPCert = public_key:pkix_decode_cert(CA0, otp),
- TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
+ TBSCert = OTPCert#'OTPCertificate'.tbsCertificate,
Num = TBSCert#'OTPTBSCertificate'.serialNumber,
- public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key).
+ public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key).
+
+signature_algs('tlsv1.3') ->
+ [ssl_test_lib:all_sig_algs()];
+signature_algs(Version) when Version == 'tlsv1.2';
+ Version == 'dtlsv1.2' ->
+ [ssl_test_lib:all_1_2_sig_algs()];
+signature_algs(_) ->
+ [].
diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
index 6d9d84e6ae..8f5aaedd1c 100644
--- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2023. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -57,11 +57,8 @@
-include_lib("ssl/src/ssl_handshake.hrl").
-include_lib("ssl/src/ssl_alert.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
--define('TLS_v1.3', {3,4}).
--define('TLS_v1.2', {3,3}).
--define('TLS_v1.1', {3,2}).
--define('TLS_v1', {3,1}).
%%--------------------------------------------------------------------
%% Properties --------------------------------------------------------
@@ -87,7 +84,7 @@ prop_tls_hs_encode_decode() ->
%% Message Generators -----------------------------------------------
%%--------------------------------------------------------------------
-tls_msg(?'TLS_v1.3'= Version) ->
+tls_msg(?TLS_1_3= Version) ->
oneof([client_hello(Version),
server_hello(Version),
%%new_session_ticket()
@@ -116,9 +113,9 @@ tls_msg(Version) ->
%%
%% Shared messages
%%
-client_hello(?'TLS_v1.3' = Version) ->
+client_hello(?TLS_1_3 = Version) ->
#client_hello{session_id = session_id(),
- client_version = ?'TLS_v1.2',
+ client_version = ?TLS_1_2,
cipher_suites = cipher_suites(Version),
compression_methods = compressions(Version),
random = client_random(Version),
@@ -133,8 +130,8 @@ client_hello(Version) ->
extensions = client_hello_extensions(Version)
}.
-server_hello(?'TLS_v1.3' = Version) ->
- #server_hello{server_version = ?'TLS_v1.2',
+server_hello(?TLS_1_3 = Version) ->
+ #server_hello{server_version = ?TLS_1_2,
session_id = session_id(),
random = server_random(Version),
cipher_suite = cipher_suite(Version),
@@ -184,7 +181,7 @@ finished() ->
%%
encrypted_extensions() ->
- ?LET(Exts, extensions(?'TLS_v1.3', encrypted_extensions),
+ ?LET(Exts, extensions(?TLS_1_3, encrypted_extensions),
#encrypted_extensions{extensions = Exts}).
@@ -197,7 +194,7 @@ key_update() ->
%%--------------------------------------------------------------------
tls_version() ->
- oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']).
+ oneof([?TLS_1_3, ?TLS_1_2, ?TLS_1_1, ?TLS_1_0]).
cipher_suite(Version) ->
oneof(cipher_suites(Version)).
@@ -290,14 +287,14 @@ pre_shared_keyextension() ->
%% | | |
%% | signature_algorithms_cert (RFC 8446) | CH, CR |
%% +--------------------------------------------------+-------------+
-extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
+extensions(?TLS_1_3 = Version, MsgType = client_hello) ->
?LET({
ServerName,
%% MaxFragmentLength,
%% StatusRequest,
SupportedGroups,
SignatureAlgorithms,
- %% UseSrtp,
+ UseSrtp,
%% Heartbeat,
ALPN,
%% SignedCertTimestamp,
@@ -320,7 +317,7 @@ extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
%% oneof([status_request(), undefined]),
oneof([supported_groups(Version), undefined]),
oneof([signature_algs(Version), undefined]),
- %% oneof([use_srtp(), undefined]),
+ oneof([use_srtp(), undefined]),
%% oneof([heartbeat(), undefined]),
oneof([alpn(), undefined]),
%% oneof([signed_cert_timestamp(), undefined]),
@@ -348,7 +345,7 @@ extensions(?'TLS_v1.3' = Version, MsgType = client_hello) ->
%% status_request => StatusRequest,
elliptic_curves => SupportedGroups,
signature_algs => SignatureAlgorithms,
- %% use_srtp => UseSrtp,
+ use_srtp => UseSrtp,
%% heartbeat => Heartbeat,
alpn => ALPN,
%% signed_cert_timestamp => SignedCertTimestamp,
@@ -398,7 +395,7 @@ extensions(Version, client_hello) ->
srp => SRP
%% renegotiation_info => RenegotiationInfo
}));
-extensions(?'TLS_v1.3' = Version, MsgType = server_hello) ->
+extensions(?TLS_1_3 = Version, MsgType = server_hello) ->
?LET({
KeyShare,
PreSharedKey,
@@ -443,7 +440,7 @@ extensions(Version, server_hello) ->
next_protocol_negotiation => NextP
%% renegotiation_info => RenegotiationInfo
}));
-extensions(?'TLS_v1.3' = Version, encrypted_extensions) ->
+extensions(?TLS_1_3 = Version, encrypted_extensions) ->
?LET({
ServerName,
%% MaxFragmentLength,
@@ -551,25 +548,25 @@ signature() ->
76,105,212,176,25,6,148,49,194,106,253,241,212,200,
37,154,227,53,49,216,72,82,163>>.
-client_hello_versions(?'TLS_v1.3') ->
+client_hello_versions(?TLS_1_3) ->
?LET(SupportedVersions,
- oneof([[{3,4}],
+ oneof([[?TLS_1_3],
%% This list breaks the property but can be used for negative tests
- %% [{3,3},{3,4}],
- [{3,4},{3,3}],
- [{3,4},{3,3},{3,2},{3,1},{3,0}]
+ %% [?TLS_1_2,?TLS_1_3],
+ [?TLS_1_3,?TLS_1_2],
+ [?TLS_1_3,?TLS_1_2,?TLS_1_1,?TLS_1_0,?SSL_3_0]
]),
#client_hello_versions{versions = SupportedVersions});
client_hello_versions(_) ->
?LET(SupportedVersions,
- oneof([[{3,3}],
- [{3,3},{3,2}],
- [{3,3},{3,2},{3,1},{3,0}]
+ oneof([[?TLS_1_2],
+ [?TLS_1_2,?TLS_1_1],
+ [?TLS_1_2,?TLS_1_1,?TLS_1_0,?SSL_3_0]
]),
#client_hello_versions{versions = SupportedVersions}).
server_hello_selected_version() ->
- #server_hello_selected_version{selected_version = {3,4}}.
+ #server_hello_selected_version{selected_version = ?TLS_1_3}.
request_update() ->
oneof([update_not_requested, update_requested]).
@@ -626,11 +623,11 @@ cert_conf()->
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}).
cert_auths() ->
- certificate_authorities(?'TLS_v1.3').
+ certificate_authorities(?TLS_1_3).
certificate_request_1_3() ->
#certificate_request_1_3{certificate_request_context = <<>>,
- extensions = #{certificate_authorities => certificate_authorities(?'TLS_v1.3')}
+ extensions = #{certificate_authorities => certificate_authorities(?TLS_1_3)}
}.
certificate_request(Version) ->
#certificate_request{certificate_types = certificate_types(Version),
@@ -640,17 +637,17 @@ certificate_request(Version) ->
certificate_types(_) ->
iolist_to_binary([<<?BYTE(?ECDSA_SIGN)>>, <<?BYTE(?RSA_SIGN)>>, <<?BYTE(?DSS_SIGN)>>]).
-signature_algs({3,4}) ->
+signature_algs(?TLS_1_3) ->
?LET(Algs, signature_algorithms(),
Algs);
-signature_algs({3,3} = Version) ->
+signature_algs(?TLS_1_2 = Version) ->
#hash_sign_algos{hash_sign_algos = hash_alg_list(Version)};
-signature_algs(Version) when Version < {3,3} ->
+signature_algs(Version) when ?TLS_LT(Version, ?TLS_1_2) ->
undefined.
-hashsign_algorithms({_, N} = Version) when N >= 3 ->
+hashsign_algorithms(Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
#hash_sign_algos{hash_sign_algos = hash_alg_list(Version)};
hashsign_algorithms(_) ->
undefined.
@@ -666,9 +663,9 @@ hash_alg(Version) ->
{hash_algorithm(Version, Alg), Alg}
).
-hash_algorithm(?'TLS_v1.3', _) ->
+hash_algorithm(?TLS_1_3, _) ->
oneof([sha, sha224, sha256, sha384, sha512]);
-hash_algorithm(?'TLS_v1.2', rsa) ->
+hash_algorithm(?TLS_1_2, rsa) ->
oneof([sha, sha224, sha256, sha384, sha512]);
hash_algorithm(_, rsa) ->
oneof([md5, sha, sha224, sha256, sha384, sha512]);
@@ -677,13 +674,19 @@ hash_algorithm(_, ecdsa) ->
hash_algorithm(_, dsa) ->
sha.
-sign_algorithm(?'TLS_v1.3') ->
+sign_algorithm(?TLS_1_3) ->
oneof([rsa, ecdsa]);
sign_algorithm(_) ->
oneof([rsa, dsa, ecdsa]).
-certificate_authorities(?'TLS_v1.3') ->
- Auths = certificate_authorities(?'TLS_v1.2'),
+use_srtp() ->
+ FullProfiles = [<<0,1>>, <<0,2>>, <<0,5>>],
+ NullProfiles = [<<0,5>>],
+ ?LET(PP, oneof([FullProfiles, NullProfiles]), #use_srtp{protection_profiles = PP, mki = <<>>}).
+
+certificate_authorities(?TLS_1_3) ->
+ Auths = certificate_authorities(?TLS_1_2),
+
#certificate_authorities{authorities = Auths};
certificate_authorities(_) ->
#{server_config := ServerConf} = cert_conf(),
@@ -712,13 +715,13 @@ ec_point_formats() ->
ec_point_format_list() ->
[?ECPOINT_UNCOMPRESSED].
-elliptic_curves({_, Minor}) when Minor < 4 ->
- Curves = tls_v1:ecc_curves(Minor),
+elliptic_curves(Version) when ?TLS_LT(Version, ?TLS_1_3) ->
+ Curves = tls_v1:ecc_curves(Version),
#elliptic_curves{elliptic_curve_list = Curves}.
%% RFC 8446 (TLS 1.3) renamed the "elliptic_curve" extension.
-supported_groups({_, Minor}) when Minor >= 4 ->
- SupportedGroups = tls_v1:groups(Minor),
+supported_groups(Version) when ?TLS_GTE(Version, ?TLS_1_3) ->
+ SupportedGroups = tls_v1:groups(),
#supported_groups{supported_groups = SupportedGroups}.
diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl
index 4b43954139..379cc6371b 100644
--- a/lib/ssl/test/ssl_ECC_SUITE.erl
+++ b/lib/ssl/test/ssl_ECC_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -24,6 +24,7 @@
-behaviour(ct_suite).
+-include_lib("ssl/src/ssl_record.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -180,7 +181,7 @@ client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) ->
ecc_default_order(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -195,7 +196,7 @@ ecc_default_order(Config) ->
ecc_default_order_custom_curves(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -210,7 +211,7 @@ ecc_default_order_custom_curves(Config) ->
ecc_client_order(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -225,7 +226,7 @@ ecc_client_order(Config) ->
ecc_client_order_custom_curves(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa,
@@ -245,12 +246,14 @@ ecc_unknown_curve(Config) ->
ecdhe_ecdsa, ecdhe_ecdsa, Config),
COpts = ssl_test_lib:ssl_options(COpts0, Config),
SOpts = ssl_test_lib:ssl_options(SOpts0, Config),
- ECCOpts = [{eccs, ['123_fake_curve']}],
- ssl_test_lib:ecc_test_error(COpts, SOpts, [], ECCOpts, Config).
+ ECCALL = ssl:eccs(),
+ SECCOpts = [{eccs, [hd(ECCALL)]}],
+ CECCOpts = [{eccs, tl(ECCALL)}],
+ ssl_test_lib:ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config).
client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdh_rsa, ecdhe_ecdsa, Config),
@@ -264,7 +267,7 @@ client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdh_rsa, ecdhe_rsa, Config),
@@ -279,7 +282,7 @@ client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) ->
client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_ecdsa, Config),
@@ -293,7 +296,7 @@ client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) ->
client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_rsa, Config),
@@ -307,7 +310,7 @@ client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) ->
end.
client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, [[], [], [{extensions, Ext}]]},
{client_chain, Default}],
@@ -325,7 +328,7 @@ client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) ->
client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa, Config),
@@ -339,7 +342,7 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) ->
client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_rsa, Config),
@@ -353,7 +356,7 @@ client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) ->
client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_ecdsa, ecdhe_ecdsa, Config),
@@ -367,7 +370,7 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) ->
client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom(Config) ->
Default = ssl_test_lib:default_cert_chain_conf(),
- DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(1))),
+ DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(?TLS_1_0))),
{COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default},
{client_chain, Default}],
ecdhe_rsa, ecdhe_ecdsa, Config),
diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl
index fc56323afd..215097bd4d 100644
--- a/lib/ssl/test/ssl_api_SUITE.erl
+++ b/lib/ssl/test/ssl_api_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -25,7 +25,9 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("ssl/src/ssl_api.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -133,6 +135,7 @@
options_not_proplist/1,
invalid_options/0,
invalid_options/1,
+ options_whitebox/0, options_whitebox/1,
cb_info/0,
cb_info/1,
log_alert/0,
@@ -175,14 +178,12 @@
server_options_negative_version_gap/1,
server_options_negative_dependency_role/0,
server_options_negative_dependency_role/1,
+ server_options_negative_stateless_tickets_seed/0,
+ server_options_negative_stateless_tickets_seed/1,
invalid_options_tls13/0,
invalid_options_tls13/1,
cookie/0,
cookie/1,
- warn_verify_none/0,
- warn_verify_none/1,
- suppress_warn_verify_none/0,
- suppress_warn_verify_none/1,
check_random_nonce/0,
check_random_nonce/1,
cipher_listing/0,
@@ -231,7 +232,7 @@ all() ->
{group, 'tlsv1'},
{group, 'dtlsv1.2'},
{group, 'dtlsv1'}
- ].
+ ] ++ simple_api_tests().
groups() ->
[
@@ -247,15 +248,9 @@ groups() ->
{'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()},
- {'dtlsv1.2', [], (gen_api_tests() --
- [invalid_keyfile, invalid_certfile, invalid_cacertfile,
- invalid_options, new_options_in_handshake,
- hibernate_server]) ++
+ {'dtlsv1.2', [], gen_api_tests() -- [new_options_in_handshake, hibernate_server] ++
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,
- hibernate_server]) ++
+ {'dtlsv1', [], gen_api_tests() -- [new_options_in_handshake, hibernate_server] ++
handshake_paus_tests() -- [handshake_continue_tls13_client] ++ pre_1_3()}
].
@@ -273,6 +268,17 @@ pre_1_3() ->
prf
].
+simple_api_tests() ->
+ [
+ invalid_keyfile,
+ invalid_certfile,
+ invalid_cacertfile,
+ invalid_options,
+ options_not_proplist,
+ options_whitebox
+ ].
+
+
gen_api_tests() ->
[
peercert,
@@ -283,6 +289,7 @@ gen_api_tests() ->
secret_connection_info,
keylog_connection_info,
versions,
+ new_options_in_handshake,
active_n,
dh_params,
hibernate_client,
@@ -307,18 +314,10 @@ gen_api_tests() ->
honor_client_cipher_order,
ipv6,
der_input,
- new_options_in_handshake,
max_handshake_size,
- invalid_certfile,
- invalid_cacertfile,
- invalid_keyfile,
- options_not_proplist,
- invalid_options,
cb_info,
log_alert,
getstat,
- warn_verify_none,
- suppress_warn_verify_none,
check_random_nonce,
cipher_listing
].
@@ -356,6 +355,7 @@ tls13_group() ->
server_options_negative_early_data,
server_options_negative_version_gap,
server_options_negative_dependency_role,
+ server_options_negative_stateless_tickets_seed,
invalid_options_tls13,
cookie
].
@@ -470,7 +470,7 @@ end_per_testcase(_TestCase, Config) ->
peercert() ->
[{doc,"Test API function peercert/1"}].
peercert(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -504,8 +504,8 @@ peercert(Config) when is_list(Config) ->
peercert_with_client_cert() ->
[{doc,"Test API function peercert/1"}].
peercert_with_client_cert(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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
@@ -561,52 +561,65 @@ select_sha1_cert() ->
select_sha1_cert(Config) when is_list(Config) ->
Version = ssl_test_lib:protocol_version(Config),
- TestConfRSA = public_key:pkix_test_data(#{server_chain => #{root => [{digest, sha},{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(3)}]
- },
- client_chain => #{root => [{digest, sha},{key, ssl_test_lib:hardcode_rsa_key(3)}],
- intermediates => [[{digest, sha},{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
-
- TestConfECDSA = public_key:pkix_test_data(#{server_chain => #{root => [{digest, sha},{key, {namedCurve, secp256r1}}],
- intermediates => [[{digest, sha}, {key,{namedCurve, secp256r1} }]],
- peer => [{digest, sha}, {key,{namedCurve, secp256r1}}]
- },
- client_chain => #{root => [{digest, sha},{key, {namedCurve, secp256r1}}],
- intermediates => [[{digest, sha},{key, {namedCurve, secp256r1}}]],
- peer => [{digest, sha}, {key, {namedCurve, secp256r1}}]}}),
- case (Version == 'tlsv1.3') orelse (Version == 'tlsv1.2') of
- true ->
- test_sha1_cert_conf(Version, TestConfRSA, TestConfECDSA, Config);
- false ->
- test_sha1_cert_conf(Version, TestConfRSA, undefined, Config)
- end.
+ TestConfRSA =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{digest, sha}, {key, ssl_test_lib:hardcode_rsa_key(3)}]
+ },
+ client_chain =>
+ #{root => [{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(3)}],
+ intermediates => [[{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{digest, sha},
+ {key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
+
+ TestConfECDSA =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => [{digest, sha},
+ {key, {namedCurve, secp256r1}}],
+ intermediates =>
+ [[{digest, sha},
+ {key,{namedCurve, secp256r1}}]],
+ peer => [{digest, sha},
+ {key,{namedCurve, secp256r1}}]
+ },
+ client_chain =>
+ #{root => [{digest, sha},
+ {key, {namedCurve, secp256r1}}],
+ intermediates => [[{digest, sha},
+ {key, {namedCurve, secp256r1}}]],
+ peer => [{digest, sha},
+ {key, {namedCurve, secp256r1}}]}}),
+ test_sha1_cert_conf(Version, TestConfRSA, TestConfECDSA, Config).
%%--------------------------------------------------------------------
connection_information() ->
[{doc,"Test the API function ssl:connection_information/1"}].
-connection_information(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+connection_information(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_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()},
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
{mfa, {?MODULE, connection_information_result, []}},
{options, ServerOpts}]),
-
+
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
- {from, self()},
+ {from, self()},
{mfa, {?MODULE, connection_information_result, []}},
{options, ClientOpts}]),
-
+
ct:log("Testcase ~p, Client ~p Server ~p ~n",
[self(), Client, Server]),
-
+
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -632,7 +645,7 @@ do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) ->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
SOpts = [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
- COpts = [{srp_identity, {"Test-User", "secret"}}],
+ COpts = [{verify, verify_none}, {srp_identity, {"Test-User", "secret"}}],
ServerOpts = ssl_test_lib:ssl_options(SOpts, Config),
ClientOpts = ssl_test_lib:ssl_options(COpts, Config),
@@ -665,7 +678,7 @@ do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) ->
secret_connection_info() ->
[{doc,"Test the API function ssl:connection_information/2"}].
secret_connection_info(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -697,7 +710,7 @@ keylog_connection_info(Config) when is_list(Config) ->
keylog_connection_info(Config, true),
keylog_connection_info(Config, false).
keylog_connection_info(Config, KeepSecrets) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -759,7 +772,7 @@ dh_params() ->
[{doc,"Test to specify DH-params file in server."}].
dh_params(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
DataDir = proplists:get_value(data_dir, Config),
DHParamFile = filename:join(DataDir, "dHParam.pem"),
@@ -788,7 +801,7 @@ dh_params(Config) when is_list(Config) ->
conf_signature_algs() ->
[{doc,"Test to set the signature_algs option on both client and server"}].
conf_signature_algs(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -820,7 +833,7 @@ no_common_signature_algs() ->
[{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}].
no_common_signature_algs(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -843,12 +856,13 @@ no_common_signature_algs(Config) when is_list(Config) ->
handshake_continue() ->
[{doc, "Test API function ssl:handshake_continue/3"}].
handshake_continue(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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {host, Hostname},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
{options, ssl_test_lib:ssl_options([{reuseaddr, true},
@@ -867,10 +881,10 @@ handshake_continue(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
{options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts
+ {verify, verify_none} | ClientOpts
],
Config)},
- {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
+ {continue_options, [{verify, verify_peer} | ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -881,7 +895,7 @@ handshake_continue(Config) when is_list(Config) ->
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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'),
@@ -953,7 +967,7 @@ handshake_continue_tls13_client(Config) when is_list(Config) ->
handshake_continue_timeout() ->
[{doc, "Test API function ssl:handshake_continue/3 with short timeout"}].
handshake_continue_timeout(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -961,9 +975,9 @@ handshake_continue_timeout(Config) when is_list(Config) ->
{from, self()},
{timeout, 1},
{options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello},
- {verify, verify_peer} | ServerOpts],
+ {verify, verify_none} | ServerOpts],
Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}
+ {continue_options, [{verify, verify_peer} | ServerOpts]}
]),
Port = ssl_test_lib:inet_port(Server),
@@ -981,7 +995,7 @@ handshake_continue_change_verify() ->
[{doc, "Test API function ssl:handshake_continue with updated verify option. "
"Use a verification that will fail to make sure verification is run"}].
handshake_continue_change_verify(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1001,7 +1015,8 @@ handshake_continue_change_verify(Config) when is_list(Config) ->
{from, self()},
{options, ssl_test_lib:ssl_options(
[{handshake, hello},
- {server_name_indication, "foobar"}
+ {server_name_indication, "foobar"},
+ {verify, verify_none}
| ClientOpts], Config)},
{continue_options, [{verify, verify_peer} | ClientOpts]}]),
ssl_test_lib:check_client_alert(Client, handshake_failure).
@@ -1010,16 +1025,17 @@ handshake_continue_change_verify(Config) when is_list(Config) ->
hello_client_cancel() ->
[{doc, "Test API function ssl:handshake_cancel/1 on the client side"}].
hello_client_cancel(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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {host, Hostname},
{from, self()},
{options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ServerOpts], Config)},
- {continue_options, proplists:delete(reuseaddr, ServerOpts)}]),
+ {verify, verify_none} | ServerOpts], Config)},
+ {continue_options, [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
@@ -1028,16 +1044,16 @@ hello_client_cancel(Config) when is_list(Config) ->
ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {options, ssl_test_lib:ssl_options([{handshake, hello},
- {verify, verify_peer} | ClientOpts], Config)},
+ {options, ssl_test_lib:ssl_options([{handshake, hello}
+ | ClientOpts], Config)},
{continue_options, cancel}]),
ssl_test_lib:check_server_alert(Server, user_canceled).
%%--------------------------------------------------------------------
hello_server_cancel() ->
[{doc, "Test API function ssl:handshake_cancel/1 on the server side"}].
hello_server_cancel(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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1074,7 +1090,7 @@ versions_option_based_on_sni() ->
[{doc,"Test that SNI versions option is selected over default versions"}].
versions_option_based_on_sni(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
TestVersion = ssl_test_lib:protocol_version(Config),
{Version, Versions} = test_versions_for_option_based_on_sni(TestVersion),
@@ -1112,9 +1128,10 @@ active_n() ->
[{doc,"Test {active,N} option"}].
active_n(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Port = ssl_test_lib:inet_port(node()),
+ Host = net_adm:localhost(),
N = 3,
LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])),
[{active,N}] = ok(ssl:getopts(LS, [active])),
@@ -1128,7 +1145,7 @@ active_n(Config) when is_list(Config) ->
ssl:controlling_process(S, Self),
Self ! {server, S}
end),
- C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])),
+ C = ok(ssl:connect(Host, Port, [{active,N}|ClientOpts])),
[{active,N}] = ok(ssl:getopts(C, [active])),
S = receive
{server, S0} -> S0
@@ -1196,7 +1213,7 @@ hibernate_client() ->
"option hibernate_after indeed hibernates after inactivity"}].
hibernate_client(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
StartServerOpts = [return_socket, {node, ServerNode},
@@ -1217,7 +1234,7 @@ hibernate_server() ->
"Note: for DTLS test will not be stable, because cookie secret refresh "
"mechanism might disturb hibernation of a server process."}].
hibernate_server(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
StartServerOpts = [return_socket, {node, ServerNode}, {port, 0},
@@ -1303,8 +1320,8 @@ peername() ->
[{doc,"Test API function peername/1"}].
peername(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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server(
@@ -1344,7 +1361,7 @@ recv_active() ->
[{doc,"Test recv on active socket"}].
recv_active(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1370,7 +1387,7 @@ recv_active_once() ->
[{doc,"Test recv on active (once) socket"}].
recv_active_once(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1396,7 +1413,7 @@ recv_active_n() ->
[{doc,"Test recv on active (n) socket"}].
recv_active_n(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -1422,7 +1439,7 @@ recv_timeout() ->
recv_timeout(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1448,7 +1465,7 @@ recv_timeout(Config) ->
recv_close() ->
[{doc,"Special case of call error handling"}].
recv_close(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1472,7 +1489,7 @@ recv_no_active_msg() ->
[{doc,"If we have a passive socket and do not call recv and peer closes we should no get"
"receive an active message"}].
recv_no_active_msg(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1494,7 +1511,7 @@ controlling_process() ->
[{doc,"Test API function controlling_process/2"}].
controlling_process(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
ClientMsg = "Server hello",
@@ -1531,7 +1548,7 @@ controlling_process(Config) when is_list(Config) ->
controller_dies() ->
[{doc,"Test that the socket is closed after controlling process dies"}].
controller_dies(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
ClientMsg = "Hello server",
@@ -1622,7 +1639,7 @@ controlling_process_transport_accept_socket() ->
[{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}].
controlling_process_transport_accept_socket(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server_transport_control([{node, ServerNode},
@@ -1642,7 +1659,7 @@ controlling_process_transport_accept_socket(Config) when is_list(Config) ->
close_with_timeout() ->
[{doc,"Test normal (not downgrade) ssl:close/2"}].
close_with_timeout(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1670,7 +1687,7 @@ close_in_error_state(Config) when is_list(Config) ->
_ = spawn(?MODULE, run_error_server_close, [[self() | ServerOpts]]),
receive
{_Pid, Port} ->
- spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
+ spawn_link(?MODULE, run_client_error, [[Port, [{verify, verify_none} | ClientOpts]]])
end,
receive
ok ->
@@ -1689,7 +1706,7 @@ call_in_error_state(Config) when is_list(Config) ->
Pid = spawn(?MODULE, run_error_server, [[self() | ServerOpts]]),
receive
{Pid, Port} ->
- spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
+ spawn_link(?MODULE, run_client_error, [[Port, [{verify, verify_none} | ClientOpts]]])
end,
receive
{error, closed} ->
@@ -1723,7 +1740,7 @@ abuse_transport_accept_socket() ->
[{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}].
abuse_transport_accept_socket(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode},
@@ -1745,7 +1762,7 @@ abuse_transport_accept_socket(Config) when is_list(Config) ->
invalid_keyfile() ->
[{doc,"Test what happens with an invalid key file"}].
invalid_keyfile(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
BadKeyFile = filename:join([proplists:get_value(priv_dir, Config),
"badkey.pem"]),
@@ -1830,7 +1847,7 @@ ipv6(Config) when is_list(Config) ->
case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of
true ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} =
ssl_test_lib:run_where(Config, ipv6),
@@ -1877,14 +1894,13 @@ der_input(Config) when is_list(Config) ->
SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} |
SeverVerifyOpts]),
- ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} |
ClientVerifyOpts]),
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true},
{dh, DHParams},
{cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}],
- ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true},
- {dh, DHParams},
+ ClientOpts = [{verify, verify_peer},
{cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}],
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -1909,8 +1925,7 @@ der_input(Config) when is_list(Config) ->
{cert, ServerCert}, {key, ServerKey},
{cacerts, [ #cert{der=Der, otp=public_key:pkix_decode_cert(Der, otp)}
|| Der <- ServerCaCerts]}],
- ClientOpts1 = [{verify, verify_peer}, {fail_if_no_peer_cert, true},
- {dh, DHParams},
+ ClientOpts1 = [{verify, verify_peer},
{cert, ClientCert}, {key, ClientKey},
{cacerts, [ #cert{der=Der, otp=public_key:pkix_decode_cert(Der, otp)}
|| Der <- ClientCaCerts]}],
@@ -1938,7 +1953,7 @@ invalid_certfile() ->
[{doc,"Test what happens with an invalid cert file"}].
invalid_certfile(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
BadCertFile = filename:join([proplists:get_value(priv_dir, Config),
"badcert.pem"]),
@@ -1970,7 +1985,7 @@ invalid_cacertfile() ->
[{doc,"Test what happens with an invalid cacert file"}].
invalid_cacertfile(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
BadCACertFile = filename:join([proplists:get_value(priv_dir, Config),
"badcacert.pem"]),
@@ -2022,7 +2037,7 @@ invalid_cacertfile(Config) when is_list(Config) ->
new_options_in_handshake() ->
[{doc,"Test that you can set ssl options in handshake/3 and not only in tcp upgrade"}].
new_options_in_handshake(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Version = ssl_test_lib:protocol_version(Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2069,7 +2084,7 @@ max_handshake_size() ->
[{doc,"Test that we can set max_handshake_size to max value."}].
max_handshake_size(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2095,7 +2110,7 @@ options_not_proplist() ->
options_not_proplist(Config) when is_list(Config) ->
BadOption = {client_preferred_next_protocols,
client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>},
- {option_not_a_key_value_tuple, BadOption} =
+ {error, {options, {option_not_a_key_value_tuple, BadOption}}} =
ssl:connect("twitter.com", 443, [binary, {active, false},
BadOption]).
@@ -2122,7 +2137,6 @@ invalid_options(Config) when is_list(Config) ->
TestOpts =
[{versions, [sslv2, sslv3]},
- {verify, 4},
{verify_fun, function},
{fail_if_no_peer_cert, 0},
{depth, four},
@@ -2130,7 +2144,6 @@ invalid_options(Config) when is_list(Config) ->
{keyfile,'key.pem' },
{password, foo},
{cacertfile, ""},
- {dhfile,'dh.pem' },
{ciphers, [{foo, bar, sha, ignore}]},
{reuse_session, foo},
{reuse_sessions, 0},
@@ -2143,31 +2156,19 @@ invalid_options(Config) when is_list(Config) ->
{key, 'key.pem' }],
TestOpts2 =
- [{[{anti_replay, '10k'}],
- %% anti_replay is a server only option but tested with client
- %% for simplicity
- {options,dependency,{anti_replay,{versions,['tlsv1.3']}}}},
- {[{cookie, false}],
- {options,dependency,{cookie,{versions,['tlsv1.3']}}}},
- {[{supported_groups, []}],
- {options,dependency,{supported_groups,{versions,['tlsv1.3']}}}},
- {[{use_ticket, [<<1,2,3,4>>]}],
- {options,dependency,{use_ticket,{versions,['tlsv1.3']}}}},
- {[{verify, verify_none}, {fail_if_no_peer_cert, true}],
- {options, incompatible,
- {verify, verify_none},
- {fail_if_no_peer_cert, true}}}],
+ [{[{supported_groups, []}, {versions, [tlsv1]}],
+ {options,incompatible,[supported_groups,{versions,['tlsv1']}]}}],
[begin
Server =
ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, [TestOpt | ServerOpts]}]),
+ {options, ServerOpts ++ [TestOpt]}]),
%% Will never reach a point where port is used.
Client =
ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0},
{host, Hostname}, {from, self()},
- {options, [TestOpt | ClientOpts]}]),
+ {options, ClientOpts ++ [TestOpt]}]),
Check(Client, Server, TestOpt),
ok
end || TestOpt <- TestOpts],
@@ -2178,6 +2179,871 @@ invalid_options(Config) when is_list(Config) ->
end || {TestOpt, ErrorMsg} <- TestOpts2],
ok.
+options_whitebox() ->
+ [{doc,"Whitebox tests of option handling"}].
+
+
+customize_defaults(Opts, Role, Host) ->
+ %% In many options test scenarios we do not care about verifcation options
+ %% but the client now requiers verification options by default.
+ ClientIgnorDef = case proplists:get_value(verify, Opts, undefined) of
+ undefined when Role == client ->
+ [{verify, verify_none}];
+ _ ->
+ []
+ end,
+ case proplists:get_value(protocol, Opts, tls) of
+ dtls ->
+ {ok, #config{ssl=DOpts}} = ssl:handle_options([{verify, verify_none}, {protocol, dtls}], Role, Host),
+ {DOpts, ClientIgnorDef ++ Opts};
+ tls ->
+ {ok, #config{ssl=DOpts}} = ssl:handle_options([{verify, verify_none}], Role, Host),
+ case proplists:get_value(versions, Opts) of
+ undefined ->
+ {DOpts, ClientIgnorDef ++ [{versions, ['tlsv1.2','tlsv1.3']}|Opts]};
+ _ ->
+ {DOpts, ClientIgnorDef ++ Opts}
+ end;
+ _ ->
+ {ok, #config{ssl=DOpts}} = ssl:handle_options(ClientIgnorDef, Role, Host),
+ {DOpts, ClientIgnorDef ++ Opts}
+ end.
+
+-define(OK(EXP, Opts, Role), ?OK(EXP,Opts, Role, [])).
+-define(OK(EXP, Opts, Role, ShouldBeMissing),
+ fun() ->
+ Host = "dummy.host.org",
+ {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host),
+ try ssl:handle_options(__Opts, Role, Host) of
+ {ok, #config{ssl=EXP = __ALL}} ->
+ ShouldBeMissing = ShouldBeMissing -- maps:keys(__ALL);
+ Other ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, Other})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ C:Other:ST ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, C, Other,ST})
+ end,
+ try ssl:update_options(__Opts, Role, __DefOpts) of
+ EXP = __ALL2 ->
+ ShouldBeMissing = ShouldBeMissing -- maps:keys(__ALL2);
+ Other2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~w,~w, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected2, Other2})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ C2:Other2:ST2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~p,~p, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected, C2, Other2, ST2})
+ end
+ end()).
+
+-define(ERR(EXP, Opts, Role),
+ fun() ->
+ Host = "dummy.host.org",
+ {__DefOpts, __Opts} = customize_defaults(Opts, Role, Host),
+ try ssl:handle_options(__Opts, Role, Host) of
+ Other ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, Other})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ throw:{error, {options, EXP}} -> ok;
+ throw:{error, EXP} -> ok;
+ C:Other:ST ->
+ ct:pal("ssl:handle_options(~0p,~0p,~0p).",[__Opts,Role,Host]),
+ error({unexpected, C, Other,ST})
+ end,
+ try ssl:update_options(__Opts, Role, __DefOpts) of
+ Other2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~p,~p, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected, Other2})
+ catch
+ throw:{error,{options,{insufficient_crypto_support,{'tlsv1.3',_}}}} -> ignored;
+ throw:{error, {options, EXP}} -> ok;
+ throw:{error, EXP} -> ok;
+ C2:Other2:ST2 ->
+ ct:pal("{ok,Cfg} = ssl:handle_options([],~p,~p),"
+ "ssl:update_options(~p,~p, element(2,Cfg)).",
+ [Role,Host,__Opts,Role]),
+ error({unexpected, C2, Other2,ST2})
+ end
+ end()).
+
+options_whitebox(Config) when is_list(Config) ->
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ true = is_binary(Cert),
+ ?OK(#{verify := verify_peer}, %% Check Option order last wins
+ [{verify, verify_none}, {verify,verify_peer}, {cacerts, [Cert]}], client),
+ options_protocol(Config),
+ options_version(Config),
+ options_alpn(Config),
+ options_anti_replay(Config),
+ options_beast_mitigation(Config),
+ options_cacerts(Config),
+ options_cert(Config),
+ options_certificate_authorities(Config),
+ options_ciphers(Config),
+ options_client_renegotiation(Config),
+ options_cookie(Config),
+ options_crl(Config),
+ options_hostname_check(Config),
+ options_dh(Config),
+ options_early_data(Config),
+ options_eccs(Config),
+ options_verify(Config),
+ options_fallback(Config),
+ options_handshake(Config),
+ options_process(Config),
+ options_honor(Config),
+ options_debug(Config),
+ options_renegotiate(Config),
+ options_middlebox(Config),
+ options_frag_len(Config),
+ options_oscp(Config),
+ options_padding(Config),
+ options_identity(Config),
+ options_reuse_session(Config),
+ options_sni(Config),
+ options_sign_alg(Config),
+ options_supported_groups(Config),
+ options_use_srtp(Config),
+ ok.
+
+options_protocol(_Config) ->
+ ?OK(#{protocol := tls}, [], client),
+ ?OK(#{protocol := tls}, [{protocol, tls}], client),
+ ?OK(#{protocol := dtls}, [{protocol, dtls}], client),
+
+ %% Errors
+ ?ERR({protocol, foo}, [{protocol, 'foo'}], client),
+
+ begin %% erl_dist
+ ?OK(#{}, [], client, [erl_dist]),
+ ?OK(#{}, [{erl_dist, false}], client, [erl_dist]),
+ ?OK(#{erl_dist := true}, [{erl_dist, true}], client),
+ ?OK(#{}, [], client, [ktls]),
+ ?OK(#{}, [{ktls, false}], client, [ktls]),
+ ?OK(#{ktls := true}, [{ktls, true}], client)
+ end,
+ ok.
+
+options_version(_Config) ->
+ ?OK(#{versions := [_|_]}, [], client), %% Hmm some machines still default only ?TLS_1_2
+ ?OK(#{versions := [?DTLS_1_2]}, [{protocol, dtls}], client),
+
+ ?OK(#{versions := [?TLS_1_3,?TLS_1_2,?TLS_1_1,?TLS_1_0]},
+ [{versions, ['tlsv1','tlsv1.1','tlsv1.2','tlsv1.3']}],
+ client),
+ ?OK(#{versions := [?TLS_1_3]}, [{versions, ['tlsv1.3']}], client),
+
+ ?OK(#{versions := [?DTLS_1_2,?DTLS_1_0]},
+ [{protocol, dtls}, {versions, ['dtlsv1', 'dtlsv1.2']}], client),
+ ?OK(#{versions := [?DTLS_1_2]}, [{protocol, dtls}, {versions, ['dtlsv1.2']}], client),
+
+
+ %% Errors
+ ?ERR({versions, []}, [{versions, []}], client),
+ ?ERR({versions, []}, [{protocol, dtls}, {versions, []}], client),
+
+ ?ERR({'tlsv1.4',{versions, ['tlsv1.4']}},
+ [{versions, ['tlsv1.4']}], client),
+ ?ERR({'dtlsv1', {versions, _}},
+ [{versions, ['dtlsv1', 'dtlsv1.2']}], client),
+
+ ?ERR({'tlsv1', {versions, _}},
+ [{protocol, dtls}, {versions, ['tlsv1','tlsv1.1','tlsv1.2','tlsv1.3']}],
+ client),
+ ?ERR({'dtlsv1.3',{versions, ['dtlsv1.3']}},
+ [{protocol, dtls}, {versions, ['dtlsv1.3']}],
+ client),
+ ?ERR({options,missing_version,{'tlsv1.2',_}},
+ [{versions, ['tlsv1.1','tlsv1.3']}],
+ client),
+ ok.
+
+options_alpn(_Config) -> %% alpn & next_protocols
+ Http = <<"HTTP/2">>,
+ ?OK(#{alpn_advertised_protocols := undefined}, [], client,
+ [alpn_preferred_protocols, next_protocol_selector, next_protocols_advertised]),
+ ?OK(#{alpn_preferred_protocols := undefined}, [], server,
+ [alpn_advertised_protocols, next_protocol_selector, next_protocols_advertised]),
+
+ ?OK(#{alpn_preferred_protocols := [Http]}, [{alpn_preferred_protocols, [Http]}],
+ server, [alpn_advertised_protocols, next_protocol_selector, next_protocols_advertised]),
+ ?OK(#{alpn_advertised_protocols := [Http]}, [{alpn_advertised_protocols, [Http]}],
+ client, [alpn_preferred_protocols, next_protocol_selector, next_protocols_advertised]),
+
+ %% Note names have been swapped in client/server variants
+
+ ?OK(#{alpn_preferred_protocols := undefined, next_protocols_advertised := [Http]},
+ [{next_protocols_advertised, [Http]}], server, [alpn_advertised_protocols, next_protocol_selector]),
+ ?OK(#{alpn_advertised_protocols := undefined, next_protocol_selector := _},
+ [{client_preferred_next_protocols, {server,[Http], Http}}],
+ client, [alpn_preferred_protocols, next_protocols_advertised]),
+
+ %% Errors
+ ?ERR({alpn_preferred_protocols, {invalid_protocol, <<>>}}, [{alpn_preferred_protocols, [Http, <<>>]}], server),
+ ?ERR({alpn_advertised_protocols, Http}, [{alpn_advertised_protocols, Http}], client),
+ ?ERR({alpn_preferred_protocols, undefined}, [{alpn_preferred_protocols, undefined}], server),
+ ?ERR({alpn_advertised_protocols, undefined}, [{alpn_advertised_protocols, undefined}], client),
+ ?ERR({option, server_only, alpn_preferred_protocols}, [{alpn_preferred_protocols, [Http]}], client),
+ ?ERR({option, client_only, alpn_advertised_protocols}, [{alpn_advertised_protocols, [Http]}], server),
+ ?ERR({option, server_only, next_protocols_advertised}, [{next_protocols_advertised, [Http]}], client),
+ ?ERR({option, client_only, client_preferred_next_protocols}, [{client_preferred_next_protocols, [Http]}], server),
+ ok.
+
+options_anti_replay(_Config) ->
+ ?OK(#{anti_replay := undefined}, [], server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, '10k'}, {session_tickets, stateless}],
+ server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, {42,4711,21}}, {session_tickets, stateless}],
+ server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, '10k'}, {session_tickets, stateless_with_cert}],
+ server),
+ ?OK(#{anti_replay := {_,_,_}},
+ [{anti_replay, {42,4711,21}}, {session_tickets, stateless_with_cert}],
+ server),
+
+
+ %% Errors
+ ?ERR({option, server_only, anti_replay},
+ [{anti_replay, '10k'}, {session_tickets, manual}],
+ client),
+ ?ERR({options, incompatible, [{anti_replay, _}, {session_tickets, disabled}]},
+ [{anti_replay, '10k'}],
+ server),
+ ?ERR({options,incompatible, [session_tickets,{versions,['tlsv1']}]},
+ [{anti_replay, '10k'}, {session_tickets, stateless}, {versions, ['tlsv1']}],
+ server),
+ ?ERR({anti_replay, '1k'},
+ [{anti_replay, '1k'}, {session_tickets, stateless}],
+ server),
+ ?ERR({anti_replay, _},
+ [{anti_replay, {1,1,1,1}}, {session_tickets, stateless}],
+ server),
+ ?ERR({options,incompatible, [session_tickets,{versions,['tlsv1']}]},
+ [{anti_replay, '10k'}, {session_tickets, stateless_with_cert}, {versions, ['tlsv1']}],
+ server),
+ ?ERR({anti_replay, '1k'},
+ [{anti_replay, '1k'}, {session_tickets, stateless_with_cert}],
+ server),
+ ?ERR({anti_replay, _},
+ [{anti_replay, {1,1,1,1}}, {session_tickets, stateless_with_cert}],
+ server),
+ ok.
+
+options_beast_mitigation(_Config) -> %% Beast mitigation TLS-1.0 option only
+ ?OK(#{beast_mitigation := one_n_minus_one}, [{versions, [tlsv1,'tlsv1.1']}], client),
+ ?OK(#{}, [{versions, ['tlsv1.1']}], client, [beast_mitigation]),
+ ?OK(#{}, [{beast_mitigation, disabled}, {versions, [tlsv1]}], client,
+ [beast_mitigation]),
+ ?OK(#{beast_mitigation := zero_n},
+ [{beast_mitigation, zero_n}, {versions, [tlsv1]}], client),
+
+ %% Errors
+ ?ERR({beast_mitigation, enabled},
+ [{beast_mitigation, enabled}, {versions, [tlsv1]}], client),
+ ?ERR({options, incompatible, [beast_mitigation, {versions, _}]},
+ [{beast_mitigation, disabled}], client),
+ ok.
+
+options_cacerts(Config) -> %% cacert[s]file
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ File = case os:type() of
+ {win32, _} -> <<"c:/tmp/foo">>;
+ _ -> <<"/tmp/foo">>
+ end,
+ ?OK(#{cacerts := undefined}, [], client, [cacertfile]),
+ ?OK(#{cacerts := undefined, cacertfile := File},
+ [{cacertfile, File}], client),
+ ?OK(#{cacerts := [Cert]}, [{cacerts, [Cert]}, {verify, verify_peer}],
+ client, [cacertfile]),
+ ?OK(#{cacerts := [#cert{}]}, [{cacerts, [#cert{der=Cert, otp=dummy}]}],
+ client, [cacertfile]),
+ ?OK(#{cacerts := [Cert]}, [{cacerts, [Cert]}, {cacertfile, "/tmp/foo"}],
+ client, [cacertfile]),
+
+ %% Errors
+ ?ERR({options, incompatible, _}, [{verify, verify_peer}], server),
+ ?ERR({cacerts, Cert}, [{cacerts, Cert}], client),
+ ?ERR({cacertfile, cert}, [{cacertfile, cert}], client),
+
+ ?OK(#{}, [], client, [depth]),
+ ?OK(#{depth := 5}, [{depth, 5}], client),
+ %% Error
+ ?ERR({depth, 256}, [{depth, 256}], client),
+ ?ERR({depth, not_an_int}, [{depth, not_an_int}], client),
+ ok.
+
+options_cert(Config) -> %% cert[file] cert_keys keys password
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ Old = [cert, certfile, key, keyfile, password],
+ ?OK(#{certs_keys := []}, [], client, Old),
+ ?OK(#{certs_keys := [#{cert := [Cert]}]}, [{cert,Cert}], client, Old),
+ ?OK(#{certs_keys := [#{cert := [Cert]}]}, [{cert,[Cert]}], client, Old),
+ ?OK(#{certs_keys := [#{certfile := <<"/tmp/foo">>, keyfile := <<"/tmp/foo">>}]},
+ [{certfile, <<"/tmp/foo">>}], client, Old),
+
+ ?OK(#{certs_keys := [#{}]}, [{certs_keys, [#{}]}], client),
+
+ ?OK(#{certs_keys := [#{key := {rsa, <<>>}}]},
+ [{key, {rsa, <<>>}}], client, Old),
+ ?OK(#{certs_keys := [#{key := #{}}]},
+ [{key, #{engine => foo, algorithm => foo, key_id => foo}}], client, Old),
+
+ ?OK(#{certs_keys := [#{password := _}]}, [{password, "foobar"}], client, Old),
+ ?OK(#{certs_keys := [#{password := _}]}, [{password, <<"foobar">>}], client, Old),
+ Pwd = fun() -> "foobar" end,
+ ?OK(#{certs_keys := [#{password := Pwd}]}, [{password, Pwd}], client, Old),
+
+ ?OK(#{certs_keys := [#{certfile := <<"/tmp/foo">>, keyfile := <<"/tmp/baz">>}]},
+ [{certfile, <<"/tmp/foo">>}, {keyfile, "/tmp/baz"}], client, Old),
+
+ ?OK(#{certs_keys := [#{}]},
+ [{cert, Cert}, {certfile, "/tmp/foo"}, {certs_keys, [#{}]}],
+ client, Old),
+
+ %% Errors
+ ?ERR({cert, #{}}, [{cert, #{}}], client),
+ ?ERR({certfile, cert}, [{certfile, cert}], client),
+ ?ERR({certs_keys, #{}}, [{certs_keys, #{}}], client),
+ ?ERR({keyfile, #{}}, [{keyfile, #{}}], client),
+ ?ERR({key, <<>>}, [{key, <<>>}], client),
+ ?ERR({password, _}, [{password, fun(Arg) -> Arg end}], client),
+ ok.
+
+options_certificate_authorities(_Config) ->
+ ?OK(#{}, [], client, [certificate_authorities]),
+ ?OK(#{}, [], server, [certificate_authorities]),
+ ?OK(#{certificate_authorities := true}, [{certificate_authorities, true}], client),
+ ?OK(#{}, [{certificate_authorities, false}], client, [certificate_authorities]),
+ ?OK(#{certificate_authorities := false}, [{certificate_authorities, false}], server),
+ ?OK(#{}, [{certificate_authorities, true}], server, [certificate_authorities]),
+
+ %% Errors
+ ?ERR({certificate_authorities, []},
+ [{certificate_authorities, []}], client),
+ ?ERR({options, incompatible, [certificate_authorities, {versions, _}]},
+ [{certificate_authorities, true}, {versions, ['tlsv1.2']}],
+ client),
+ ok.
+
+options_ciphers(_Config) ->
+ CipherSuite = ssl_test_lib:ecdh_dh_anonymous_suites(?TLS_1_2),
+ ?OK(#{ciphers := [_|_]}, [], client),
+ ?OK(#{ciphers := [_|_]}, [{ciphers, CipherSuite}], client),
+ ?OK(#{ciphers := [_|_]}, [{ciphers, "RC4-SHA:RC4-MD5"}], client),
+ ?OK(#{ciphers := [_|_]}, [{ciphers, ["RC4-SHA", "RC4-MD5"]}], client),
+
+ %% FIXME extend this
+ ok.
+
+options_client_renegotiation(_Config) ->
+ ?OK(#{client_renegotiation := true}, [], server),
+ ?OK(#{client_renegotiation := false}, [{client_renegotiation, false}], server),
+
+ %% Errors
+ ?ERR({client_renegotiation, []}, [{client_renegotiation, []}], server),
+ ?ERR({option, server_only, client_renegotiation}, [{client_renegotiation, true}], client),
+ ?ERR({options, incompatible, [client_renegotiation, {versions, _}]},
+ [{client_renegotiation, true}, {versions, ['tlsv1.3']}],
+ server),
+ ok.
+
+
+options_cookie(_Config) ->
+ ?OK(#{cookie := true}, [], server),
+ ?OK(#{cookie := false}, [{cookie, false}], server),
+
+ %% Errors
+ ?ERR({cookie, []}, [{cookie, []}], server),
+ ?ERR({option, server_only, cookie}, [{cookie, true}], client),
+ ?ERR({options, incompatible, [cookie, {versions, _}]},
+ [{cookie, true}, {versions, ['tlsv1.2']}], server),
+ ok.
+
+options_crl(_Config) ->
+ ?OK(#{crl_cache := {ssl_crl_cache, _}, crl_check := false}, [], server),
+ ?OK(#{crl_cache := {ssl_crl_cache, _}, crl_check := true}, [{crl_check, true}], server),
+ ?OK(#{crl_cache := {ssl_crl_hash_dir, {_,_}}, crl_check := true},
+ [{crl_check, true}, {crl_cache, {ssl_crl_hash_dir, {internal, [{dir, "/tmp"}]}}}],
+ server),
+ ?OK(#{crl_cache := {ssl_crl_cache, {_,_}}, crl_check := true},
+ [{crl_check, true}, {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}],
+ server),
+ %% Errors
+ ?ERR({crl_check, foo}, [{crl_check, foo}], server),
+ ?ERR({crl_cache, {a,b,c}}, [{crl_cache, {a,b,c}}], server),
+ ok.
+
+options_hostname_check(_Config) ->
+ ?OK(#{customize_hostname_check := []}, [], client),
+ ?OK(#{customize_hostname_check := [{match_fun, _}]},
+ [{customize_hostname_check, [{match_fun, fun() -> ok end}]}],
+ client),
+ %% Error
+ ?ERR({customize_hostname_check, _}, [{customize_hostname_check, {match_fun, pb_fun}}], client),
+ ok.
+
+options_dh(_Config) -> %% dh dhfile
+ ?OK(#{}, [], server, [dh, dhfile]),
+ ?OK(#{dh := <<>>}, [{dh, <<>>}], server, [dhfile]),
+ ?OK(#{dhfile := <<"/tmp/foo">>}, [{dhfile, <<"/tmp/foo">>}], server, [dh]),
+ ?OK(#{dh := <<>>}, [{dh, <<>>}, {dhfile, <<"/tmp/foo">>}], server, [dhfile]),
+
+ %% Should be an error
+ ?OK(#{dhfile := <<"/tmp/foo">>}, %% Not available in 1.3
+ [{dhfile, <<"/tmp/foo">>}, {versions, ['tlsv1.3']}], server, [dh]),
+
+ %% Error
+ ?ERR({dh, not_a_bin}, [{dh, not_a_bin}], server),
+ ?ERR({dhfile, not_a_filename}, [{dhfile, not_a_filename}], server),
+ ?ERR({option, server_only, dhfile}, [{dhfile, "file"}], client),
+ ?ERR({option, server_only, dh}, [{dh, <<"DER">>}], client),
+ ok.
+
+options_early_data(_Config) -> %% early_data, session_tickets and use_ticket
+ ?OK(#{early_data := undefined, session_tickets := disabled},
+ [], client),
+ ?OK(#{early_data := disabled, session_tickets := disabled, stateless_tickets_seed := undefined},
+ [], server),
+
+ ?OK(#{early_data := <<>>, session_tickets := auto},
+ [{early_data, <<>>}, {session_tickets, auto}], client),
+ ?OK(#{early_data := <<>>, session_tickets := manual, use_ticket := [<<1>>]},
+ [{early_data, <<>>}, {session_tickets, manual}, {use_ticket, [<<1>>]}],
+ client),
+
+ ?OK(#{early_data := enabled, stateless_tickets_seed := <<"foo">>},
+ [{early_data, enabled}, {session_tickets, stateless}, {stateless_tickets_seed, <<"foo">>}], server),
+ ?OK(#{early_data := enabled, stateless_tickets_seed := <<"foo">>},
+ [{early_data, enabled}, {session_tickets, stateless_with_cert}, {stateless_tickets_seed, <<"foo">>}], server),
+
+ ?OK(#{early_data := disabled}, [{early_data, disabled}], server),
+
+ %% Errors
+ ?ERR({option, client_only, use_ticket}, [{use_ticket, []}], server),
+ ?ERR({options, incompatible, _}, [{use_ticket, [<<>>]}], client),
+
+ ?ERR({options, {session_tickets, foo}}, [{session_tickets, foo}], server),
+ ?ERR({options, {session_tickets, manual}}, [{session_tickets, manual}], server),
+ ?ERR({options, {session_tickets, stateful}}, [{session_tickets, stateful}], client),
+ ?ERR({options, incompatible, [session_tickets, {versions, _}]},
+ [{session_tickets, stateful}, {versions, ['tlsv1.2']}], server),
+ ?ERR({options, incompatible, [session_tickets, {versions, _}]},
+ [{session_tickets, stateful_with_cert}, {versions, ['tlsv1.2']}], server),
+
+ ?ERR({use_ticket, foo},
+ [{use_ticket, foo}, {session_tickets, manual}], client),
+
+ ?ERR({early_data,undefined}, [{early_data, undefined}], client),
+ ?ERR({options, incompatible, [early_data, {session_tickets, _}]},
+ [{early_data, <<>>}], client),
+ ?ERR({options, {early_data, enabled}},
+ [{early_data, enabled}, {session_tickets, auto}], client),
+ ?ERR({options, incompatible, [early_data, _, {use_ticket, _}]},
+ [{early_data, <<>>}, {session_tickets, manual}], client),
+
+ ?ERR({options, incompatible, [early_data, {session_tickets, _}]},
+ [{early_data, enabled}], server),
+ ?ERR({options, {early_data, <<>>}},
+ [{early_data, <<>>}, {session_tickets, stateless}], server),
+ ?ERR({options, {early_data, <<>>}},
+ [{early_data, <<>>}, {session_tickets, stateless_with_cert}], server),
+
+ ?ERR({options, incompatible, [stateless_tickets_seed, {session_tickets, stateful}]},
+ [{stateless_tickets_seed, <<"foo">>}, {session_tickets, stateful}],
+ server),
+ ?ERR({options, incompatible, [stateless_tickets_seed, {session_tickets, stateful_with_cert}]},
+ [{stateless_tickets_seed, <<"foo">>}, {session_tickets, stateful_with_cert}],
+ server),
+ ?ERR({stateless_tickets_seed, foo},
+ [{stateless_tickets_seed, foo}, {session_tickets, stateless}],
+ server),
+ ?ERR({stateless_tickets_seed, foo},
+ [{stateless_tickets_seed, foo}, {session_tickets, stateless_with_cert}],
+ server),
+ ?ERR({option, server_only, stateless_tickets_seed},
+ [{stateless_tickets_seed, <<"foo">>}], client),
+ ok.
+
+options_eccs(_Config) ->
+ Curves = tl(ssl:eccs()),
+ ?OK(#{eccs := {elliptic_curves, [_|_]}}, [], client),
+ ?OK(#{eccs := {elliptic_curves, [_|_]}}, [{eccs, Curves}], client),
+
+ %% Errors
+ ?ERR({eccs, not_a_list}, [{eccs, not_a_list}], client),
+ ?ERR({eccs, none_valid}, [{eccs, []}], client),
+ ?ERR({eccs, none_valid}, [{eccs, [foo]}], client),
+ ok.
+
+options_verify(Config) -> %% fail_if_no_peer_cert, verify, verify_fun, partial_chain
+ Cert = proplists:get_value(cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ {ok, #config{ssl = DefOpts = #{verify_fun := {DefVerify,_}}}} = ssl:handle_options([{verify, verify_none}], client, "dummy.host.org"),
+
+ ?OK(#{fail_if_no_peer_cert := false, verify := verify_none, verify_fun := {DefVerify, []}, partial_chain := _},
+ [], server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := undefined, partial_chain := _},
+ [{fail_if_no_peer_cert, true}, {verify, verify_peer}, {cacerts, [Cert]}],
+ server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := undefined, partial_chain := _},
+ [{verify, verify_peer}, {cacerts, [Cert]}], server),
+
+ NewF3 = fun(_,_,_) -> ok end,
+ NewF4 = fun(_,_,_,_) -> ok end,
+ ?OK(#{}, [], client, [fail_if_no_peer_cert]),
+ ?OK(#{verify := verify_none, verify_fun := {NewF3, foo}, partial_chain := _},
+ [{verify_fun, {NewF3, foo}}], client),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := {NewF3, foo}, partial_chain := _},
+ [{verify_fun, {NewF3, foo}}, {verify, verify_peer}, {cacerts, [Cert]}],
+ server),
+ ?OK(#{fail_if_no_peer_cert := true, verify := verify_peer, verify_fun := {NewF4, foo}, partial_chain := _},
+ [{verify_fun, {NewF4, foo}}, {verify, verify_peer}, {cacerts, [Cert]}],
+ server),
+
+
+ %% check verify_fun in update_options case
+ #{verify_fun := undefined} = ssl:update_options([{verify, verify_peer}, {cacerts, [Cert]}], client, DefOpts),
+ #{verify_fun := {NewF3, bar}} = ssl:update_options([{verify, verify_peer}, {cacerts, [Cert]},
+ {verify_fun, {NewF3, bar}}],
+ client, DefOpts),
+
+ %% Errors
+ ?ERR({partial_chain, undefined}, [{partial_chain, undefined}], client),
+ ?ERR({options, incompatible, [{verify, verify_none}, {fail_if_no_peer_cert, true}]},
+ [{fail_if_no_peer_cert, true}], server),
+ ?ERR({options, incompatible, [{verify, _}, {cacerts, undefined}]}, [{verify, verify_peer}], client),
+ ?ERR({option, server_only, fail_if_no_peer_cert},
+ [{fail_if_no_peer_cert, true}, {verify, verify_peer}, {cacerts, [Cert]}],
+ client),
+ ?ERR({verify, verify}, [{verify, verify}], client),
+ ?ERR({options, incompatible, [{verify, _}, {cacerts, undefined}]}, [{verify, verify_peer}], server),
+ ?ERR({partial_chain, not_a_fun}, [{partial_chain, not_a_fun}], client),
+ ?ERR({verify_fun, not_a_fun}, [{verify_fun, not_a_fun}], client),
+ ok.
+
+options_fallback(_Config) ->
+ ?OK(#{fallback := false}, [], client),
+ ?OK(#{fallback := true}, [{fallback, true}], client),
+
+ %% Errors
+ ?ERR({option, client_only, fallback}, [{fallback, true}], server),
+ ?ERR({fallback, []}, [{fallback, []}], client),
+ ?ERR({options, incompatible, [fallback, {versions, _}]},
+ [{fallback, true}, {versions, ['tlsv1.3']}], client),
+ ok.
+
+options_handshake(_Config) -> %% handshake
+ ?OK(#{handshake := full, max_handshake_size := 131072},
+ [], client),
+ ?OK(#{handshake := hello, max_handshake_size := 123800},
+ [{handshake, hello}, {max_handshake_size, 123800}], client),
+
+
+ %% Errors
+ ?ERR({handshake, []}, [{handshake, []}], server),
+ ?ERR({max_handshake_size, 8388608}, [{max_handshake_size, 8388608}], server),
+ ?ERR({max_handshake_size, -1}, [{handshake, hello}, {max_handshake_size, -1}], client),
+ ok.
+
+options_process(_Config) -> % hibernate_after, spawn_opts
+ ?OK(#{}, [], client, [hibernate_after, receiver_spawn_opts, sender_spawn_opts]),
+ ?OK(#{hibernate_after := 10000,
+ receiver_spawn_opts := [{fullsweep_after, 500}],
+ sender_spawn_opts := [{fullsweep_after, 500}]},
+ [{hibernate_after, 10000},
+ {receiver_spawn_opts,[{fullsweep_after, 500}]},
+ {sender_spawn_opts, [{fullsweep_after, 500}]}],
+ client),
+ %% Errors
+ ?ERR({hibernate_after, -1}, [{hibernate_after, -1}], server),
+ ?ERR({receiver_spawn_opts, not_a_list}, [{receiver_spawn_opts, not_a_list}], server),
+ ?ERR({sender_spawn_opts, not_a_list}, [{sender_spawn_opts, not_a_list}], server),
+ ok.
+
+options_honor(_Config) -> %% honor_cipher_order & honor_ecc_order
+ ?OK(#{honor_cipher_order := false, honor_ecc_order := false},
+ [], server),
+ ?OK(#{honor_cipher_order := true, honor_ecc_order := true},
+ [{honor_cipher_order, true}, {honor_ecc_order, true}],
+ server),
+ %% Errors
+ ?ERR({option, server_only, honor_cipher_order},
+ [{honor_cipher_order, true}], client),
+ ?ERR({option, server_only, honor_ecc_order},
+ [{honor_ecc_order, true}], client),
+ ?ERR({honor_ecc_order, foo}, [{honor_ecc_order, foo}], server),
+ ok.
+
+options_debug(_Config) -> %% debug log_level keep_secrets
+ ?OK(#{log_level := notice}, [], server, [keep_secrets]),
+ ?OK(#{log_level := debug, keep_secrets := true},
+ [{log_level, debug}, {keep_secrets, true}], server),
+ ?OK(#{log_level := info},
+ [{log_level, info}, {keep_secrets, false}], server, [keep_secrets]),
+
+ %% Errors
+ ?ERR({log_level, foo}, [{log_level, foo}], server),
+ ?ERR({keep_secrets, foo}, [{keep_secrets, foo}], server),
+ ok.
+
+options_renegotiate(_Config) -> %% key_update_at renegotiate_at secure_renegotiate
+ ?OK(#{key_update_at := ?KEY_USAGE_LIMIT_AES_GCM, renegotiate_at := 268435456, secure_renegotiate := true},
+ [], server),
+ ?OK(#{key_update_at := 123456, renegotiate_at := 64000, secure_renegotiate := false},
+ [{key_update_at, 123456}, {renegotiate_at, 64000}, {secure_renegotiate, false}],
+ server),
+
+ %% Errors
+ ?ERR({options, incompatible, [key_update_at, {versions, _}]},
+ [{key_update_at, 123456}, {versions, ['tlsv1.2']}], server),
+ ?ERR({options, incompatible, [secure_renegotiate, {versions, _}]},
+ [{secure_renegotiate, true}, {versions, ['tlsv1.3']}], server),
+
+ ?ERR({key_update_at, -1}, [{key_update_at, -1}], server),
+ ?ERR({renegotiate_at, not_a_int}, [{renegotiate_at, not_a_int}], server),
+ ?ERR({renegotiate_at, -1}, [{renegotiate_at, -1}], server),
+ ok.
+
+options_middlebox(_Config) -> %% middlebox_comp_mode
+ ?OK(#{}, [], client, [middlebox_comp_mode]),
+ ?OK(#{}, [{middlebox_comp_mode, true}], client, [middlebox_comp_mode]),
+ ?OK(#{middlebox_comp_mode := false}, [{middlebox_comp_mode, false}], client),
+
+ %% Errors
+ ?ERR({middlebox_comp_mode, foo}, [{middlebox_comp_mode, foo}], server),
+ ?ERR({options, incompatible, [middlebox_comp_mode, {versions, _}]},
+ [{middlebox_comp_mode, false}, {versions, ['tlsv1.2']}], server),
+ ok.
+
+options_frag_len(_Config) -> %% max_fragment_length
+ ?OK(#{max_fragment_length := undefined}, [], client),
+ ?OK(#{max_fragment_length := 2048}, [{max_fragment_length, 2048}], client),
+
+ %% Errors
+ ?ERR({option, client_only, max_fragment_length},
+ [{max_fragment_length, 2048}], server),
+ ?ERR({max_fragment_length,2000}, [{max_fragment_length, 2000}], client),
+ ok.
+
+options_oscp(Config) ->
+ Cert = proplists:get_value(
+ cert, ssl_test_lib:ssl_options(server_rsa_der_opts, Config)),
+ NoOcspOption = [ocsp_stapling, ocsp_nonce, ocsp_responder_certs],
+
+ ?OK(#{}, [], client, NoOcspOption),
+ ?OK(#{}, [{ocsp_stapling, false}], client, NoOcspOption),
+ ?OK(#{ocsp_stapling := #{ocsp_nonce := true, ocsp_responder_certs := []}},
+ [{ocsp_stapling, true}], client, [ocsp_nonce, ocsp_responder_certs]),
+ ?OK(#{ocsp_stapling :=
+ #{ocsp_nonce := false, ocsp_responder_certs := [_, _]}},
+ [{ocsp_stapling, true}, {ocsp_nonce, false},
+ {ocsp_responder_certs, [Cert,Cert]}],
+ client, [ocsp_nonce, ocsp_responder_certs]),
+ %% Errors
+ ?ERR({ocsp_stapling, foo}, [{ocsp_stapling, 'foo'}], client),
+ ?ERR({ocsp_nonce, foo}, [{ocsp_nonce, 'foo'}], client),
+ ?ERR({ocsp_responder_certs, foo}, [{ocsp_responder_certs, 'foo'}], client),
+ ?ERR({options, incompatible, [{ocsp_nonce, false}, {ocsp_stapling, false}]},
+ [{ocsp_nonce, false}], client),
+ ?ERR({options, incompatible, [ocsp_responder_certs, {ocsp_stapling, false}]},
+ [{ocsp_responder_certs, [Cert]}], server),
+ ?ERR({ocsp_responder_certs, [_]},
+ [{ocsp_stapling, true}, {ocsp_responder_certs, ['NOT A BINARY']}],
+ client),
+ ok.
+
+options_padding(_Config) ->
+ ?OK(#{}, [], server, [padding_check]),
+ ?OK(#{padding_check := false}, [{padding_check, false}, {versions, [tlsv1]}], server),
+ %% Errors
+ ?ERR({padding_check, foo}, [{padding_check, foo}, {versions, [tlsv1]}], server),
+ ?ERR({options, incompatible, [padding_check, {versions, ['tlsv1.3','tlsv1.2']}]},
+ [{padding_check, false}], server),
+ ok.
+
+options_identity(_Config) -> %% psk_identity srp_identity and user_lookup_fun
+ ?OK(#{psk_identity := undefined, srp_identity := undefined, user_lookup_fun := undefined},
+ [], client),
+ ?OK(#{psk_identity := <<"foobar">>, srp_identity := undefined, user_lookup_fun := undefined},
+ [{psk_identity, "foobar"}], server),
+ ?OK(#{psk_identity := undefined, srp_identity := {<<"user">>, <<"pwd">>}, user_lookup_fun := undefined},
+ [{srp_identity, {"user", "pwd"}}], client),
+ ?OK(#{psk_identity := undefined, srp_identity := undefined, user_lookup_fun := {_, args}},
+ [{user_lookup_fun, {fun(_,_,_) -> ok end, args}}], client),
+
+ %% Should fail client option only
+ ?OK(#{psk_identity := undefined, srp_identity := {<<"user">>, <<"pwd">>}, user_lookup_fun := undefined},
+ [{srp_identity, {"user", "pwd"}}], server),
+
+ %% Errors
+ ?ERR({srp_identity, {_,_}}, %% FIXME doesn't like binary strings
+ [{srp_identity, {<<"user">>, <<"pwd">>}}], client),
+ ?ERR({psk_identity, _}, %% FIXME doesn't like binary strings
+ [{psk_identity, <<"user">>}], client),
+ ?ERR({srp_identity, _}, [{srp_identity, "user"}], client),
+ ?ERR({user_lookup_fun, _},
+ [{user_lookup_fun, {fun(_,_) -> ok end, args}}],
+ client),
+
+ ?ERR({options, incompatible, [psk_identity, _]},
+ [{psk_identity, "foobar"},{versions, ['tlsv1.3']}],
+ server),
+ ?ERR({options, incompatible, [srp_identity, _]},
+ [{srp_identity, {"user", "pwd"}},{versions, ['tlsv1.3']}],
+ client),
+ ?ERR({options, incompatible, [user_lookup_fun, _]},
+ [{user_lookup_fun, {fun(_,_,_) -> ok end, args}}, {versions, ['tlsv1.3']}],
+ client),
+ ok.
+
+options_reuse_session(_Config) ->
+ ?OK(#{reuse_session := undefined, reuse_sessions := true}, [], client),
+ ?OK(#{reuse_session := _, reuse_sessions := true}, [], server),
+
+ ?OK(#{reuse_session := <<>>, reuse_sessions := save},
+ [{reuse_session, <<>>}, {reuse_sessions, save}], client),
+ ?OK(#{reuse_session := {<<>>, <<>>}, reuse_sessions := false},
+ [{reuse_session, {<<>>, <<>>}}, {reuse_sessions, false}], client),
+
+ RS_F = fun(_,_,_,_) -> ok end,
+ ?OK(#{reuse_session := RS_F, reuse_sessions := false},
+ [{reuse_session, RS_F}, {reuse_sessions, false}],
+ server),
+
+ %% Errors
+ ?ERR({options, incompatible, [reuse_session, _]},
+ [{reuse_session, RS_F}, {versions, ['tlsv1.3']}],
+ server),
+ ?ERR({options, incompatible, [reuse_sessions, _]},
+ [{reuse_sessions, true}, {versions, ['tlsv1.3']}],
+ server),
+ ?ERR({reuse_session, RS_F},
+ [{reuse_session, RS_F},{reuse_sessions, false}],
+ client),
+ ?ERR({reuse_sessions, foo}, [{reuse_sessions, foo}], server),
+ ?ERR({reuse_session, foo}, [{reuse_session, foo}], server),
+ ?ERR({reuse_sessions, save}, [{reuse_sessions, save}], server),
+ ?ERR({reuse_session, <<>>}, [{reuse_session, <<>>}], server),
+
+ ok.
+
+options_sni(_Config) -> %% server_name_indication
+ ?OK(#{server_name_indication := "dummy.host.org"}, [], client),
+ ?OK(#{}, [], server, [server_name_indication]),
+ ?OK(#{server_name_indication := disable}, [{server_name_indication, disable}], client),
+ ?OK(#{server_name_indication := "dummy.org"}, [{server_name_indication, "dummy.org"}], client),
+
+ ?OK(#{sni_fun := _}, [], server, [sni_hosts]),
+
+ ?OK(#{sni_fun := _}, [{sni_hosts, [{"a", []}]}], server, [sni_hosts]),
+ SNI_F = fun(_) -> sni end,
+ ?OK(#{sni_fun := SNI_F}, [{sni_fun, SNI_F}], server, [sni_hosts]),
+
+ %% Errors
+ ?ERR({option, client_only, server_name_indication},
+ [{server_name_indication, "dummy.org"}], server),
+ ?ERR({option, server_only, sni_hosts}, [{sni_hosts, [{"a", []}]}], client),
+ ?ERR({option, server_only, sni_fun}, [{sni_fun, SNI_F}], client),
+
+ ?ERR({server_name_indication, foo}, [{server_name_indication, foo}], client),
+ ?ERR({sni_hosts, foo}, [{sni_hosts, foo}], server),
+ ?ERR({sni_fun, foo}, [{sni_fun, foo}], server),
+
+ ?ERR({options, incompatible, [sni_fun, sni_hosts]},
+ [{sni_fun, SNI_F}, {sni_hosts, [{"a", []}]}], server),
+ ok.
+
+options_sign_alg(_Config) -> %% signature_algs[_cert]
+ ?OK(#{signature_algs := [_|_], signature_algs_cert := undefined},
+ [], client),
+ ?OK(#{signature_algs := [rsa_pss_rsae_sha512,{sha512,rsa}], signature_algs_cert := undefined},
+ [{signature_algs, [rsa_pss_rsae_sha512,{sha512,rsa}]}], client),
+ ?OK(#{signature_algs := [_|_], signature_algs_cert := [eddsa_ed25519, rsa_pss_rsae_sha512]},
+ [{signature_algs_cert, [eddsa_ed25519, rsa_pss_rsae_sha512]}],
+ client),
+
+ %% Errors
+ ?ERR({signature_algs, not_a_list}, [{signature_algs, not_a_list}], client),
+ ?ERR({signature_algs_cert, not_a_list}, [{signature_algs_cert, not_a_list}], client),
+ ?ERR({signature_algs, [foobar]}, [{signature_algs, [foobar]}], client),
+ ?ERR({signature_algs_cert, [foobar]}, [{signature_algs_cert, [foobar]}], client),
+ ?ERR({options, incompatible, [signature_algs_cert, {versions, _}]},
+ [{signature_algs_cert, [eddsa_ed25519, rsa_pss_rsae_sha512]}, {versions, ['tlsv1.1']}],
+ client),
+ ?ERR({options, incompatible, [signature_algs_cert, {versions, _}]},
+ [{signature_algs_cert, [eddsa_ed25519, rsa_pss_rsae_sha512]}, {versions, ['tlsv1.1']}],
+ server),
+ ?ERR({options, {no_supported_algorithms, {signature_algs,[]}}},
+ [{signature_algs, []}], client),
+ ?ERR({options, {no_supported_signature_schemes, {signature_algs_cert,[]}}},
+ [{signature_algs_cert, []}],
+ client),
+ ok.
+
+options_supported_groups(_Config) ->
+ %% FIXME group() type doesn't cover the values below
+ ?OK(#{supported_groups := {supported_groups, [x25519,x448,secp256r1,secp384r1]}},
+ [], client),
+ ?OK(#{supported_groups := {supported_groups, [secp521r1, ffdhe2048]}},
+ [{supported_groups, [secp521r1, ffdhe2048]}], client),
+
+ %% ERRORs
+ ?ERR({{'tlvs1.2'},{versions,[{'tlvs1.2'}]}},
+ [{supported_groups, []}, {versions, [{'tlvs1.2'}]}], client),
+ ?ERR({supported_groups, not_a_list}, [{supported_groups, not_a_list}], client),
+ ?ERR({supported_groups, none_valid}, [{supported_groups, []}], client),
+ ?ERR({supported_groups, none_valid}, [{supported_groups, [foo]}], client),
+ ok.
+
+options_use_srtp(_Config) ->
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>, <<0,5>>], mki := <<>>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>, <<0,5>>]}}], client),
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>], mki := <<>>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>]}}], server),
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>, <<0,5>>], mki := <<"123">>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>, <<0,5>>], mki => <<"123">>}}], client),
+ ?OK(#{use_srtp := #{protection_profiles := [<<0,2>>], mki := <<"123">>}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>], mki => <<"123">>}}], server),
+
+ %% invalid parameters
+ ?ERR({options, {use_srtp, {no_protection_profiles, _}}},
+ [{protocol, dtls},
+ {use_srtp, #{}}], client),
+ ?ERR({options, {use_srtp, {no_protection_profiles, _}}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => []}}], client),
+ ?ERR({options, {use_srtp, {invalid_protection_profiles, _}}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<"bad">>]}}], client),
+ ?ERR({options, {use_srtp, {unknown_parameters,[whatever]}}},
+ [{protocol, dtls},
+ {use_srtp, #{protection_profiles => [<<0,2>>], whatever => xxx}}], client),
+
+ %% use_srtp is DTLS-only extension, by default setting its options should raise an error
+ ?ERR({options, incompatible, _},
+ [{use_srtp, #{protection_profiles => [<<0,2>>, <<0,5>>]}}], client),
+ ?ERR({options, incompatible, _},
+ [{use_srtp, #{protection_profiles => [<<0,2>>]}}], server),
+ ok.
+
%%-------------------------------------------------------------------
default_reject_anonymous()->
@@ -2209,7 +3075,7 @@ 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),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
Protocol = proplists:get_value(protocol, Config, tls),
CbInfo =
@@ -2240,7 +3106,7 @@ log_alert() ->
" that has replaced this option"}].
log_alert(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2360,8 +3226,8 @@ client_options_negative_dependency_version() ->
client_options_negative_dependency_version(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.1', 'tlsv1.2']},
{session_tickets, manual}],
- {options,dependency,
- {session_tickets,{versions,['tlsv1.3']}}}).
+ {options,incompatible,
+ [session_tickets,{versions,['tlsv1.2', 'tlsv1.1']}]}).
%%--------------------------------------------------------------------
client_options_negative_dependency_stateless() ->
@@ -2370,8 +3236,7 @@ client_options_negative_dependency_stateless(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{anti_replay, '10k'},
{session_tickets, manual}],
- {options,dependency,
- {anti_replay,{session_tickets,[stateless]}}}).
+ {option, server_only, anti_replay}).
%%--------------------------------------------------------------------
@@ -2380,45 +3245,37 @@ client_options_negative_dependency_role() ->
client_options_negative_dependency_role(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, stateless}],
- {options,role,
- {session_tickets,{stateless,{client,[disabled,manual,auto]}}}}).
+ {options,{session_tickets,stateless}}).
%%--------------------------------------------------------------------
client_options_negative_early_data() ->
[{doc,"Test client option early_data."}].
client_options_negative_early_data(Config) when is_list(Config) ->
start_client_negative(Config, [{versions, ['tlsv1.2']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{versions,['tlsv1.3']}}}),
+ {session_tickets, manual},
+ {early_data, <<"test">>}],
+ {options,incompatible,
+ [session_tickets,{versions,['tlsv1.2']}]}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[manual,auto]}}}),
+ {early_data, <<"test">>}],
+ {options,incompatible,
+ [early_data,{session_tickets,disabled}]}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, stateful},
- {early_data, "test"}],
- {options,role,
- {session_tickets,
- {stateful,{client,[disabled,manual,auto]}}}}),
- start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {session_tickets, disabled},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[manual,auto]}}}),
+ {early_data, <<"test">>}],
+ {options, {session_tickets, stateful}}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
- {early_data, "test"}],
- {options,dependency,
- {early_data, use_ticket}}),
+ {early_data, <<"test">>}],
+ {options,incompatible,
+ [early_data, {session_tickets, manual}, {use_ticket, undefined}]}),
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
{use_ticket, [<<"ticket">>]},
{early_data, "test"}],
- {options, type,
- {early_data, {"test", not_binary}}}),
+ {options, {early_data, "test"}}),
%% All options are ok but there is no server
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
@@ -2426,11 +3283,6 @@ client_options_negative_early_data(Config) when is_list(Config) ->
{early_data, <<"test">>}],
econnrefused),
- start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {session_tickets, auto},
- {early_data, "test"}],
- {options, type,
- {early_data, {"test", not_binary}}}),
%% All options are ok but there is no server
start_client_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, auto},
@@ -2442,31 +3294,25 @@ server_options_negative_early_data() ->
[{doc,"Test server option early_data."}].
server_options_negative_early_data(Config) when is_list(Config) ->
start_server_negative(Config, [{versions, ['tlsv1.2']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{versions,['tlsv1.3']}}}),
+ {early_data, enabled},
+ {session_tickets, stateful}
+ ],
+ {options,incompatible,
+ [session_tickets,{versions,['tlsv1.2']}]}),
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[stateful,stateless]}}}),
+ {early_data, enabled}],
+ {options,incompatible,
+ [early_data,{session_tickets,disabled}]}),
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual},
- {early_data, "test"}],
- {options,role,
- {session_tickets,
- {manual,{server,[disabled,stateful,stateless]}}}}),
- start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
- {session_tickets, disabled},
- {early_data, "test"}],
- {options,dependency,
- {early_data,{session_tickets,[stateful,stateless]}}}),
+ {early_data, enabled}],
+ {options, {session_tickets, manual}}),
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, stateful},
{early_data, "test"}],
- {options,role,
- {early_data,{"test",{server,[disabled,enabled]}}}}).
+ {options, {early_data, "test"}}).
%%--------------------------------------------------------------------
server_options_negative_version_gap() ->
@@ -2482,8 +3328,36 @@ server_options_negative_dependency_role() ->
server_options_negative_dependency_role(Config) when is_list(Config) ->
start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
{session_tickets, manual}],
- {options,role,
- {session_tickets,{manual,{server,[disabled,stateful,stateless]}}}}).
+ {options,{session_tickets,manual}}).
+
+%%--------------------------------------------------------------------
+server_options_negative_stateless_tickets_seed() ->
+ [{doc, "Test server option stateless_tickets_seed"}].
+server_options_negative_stateless_tickets_seed(Config) ->
+ Seed = crypto:strong_rand_bytes(32),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {stateless_tickets_seed, Seed}],
+ {options, incompatible,
+ [stateless_tickets_seed, {session_tickets, disabled}]}),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, disabled},
+ {stateless_tickets_seed, Seed}],
+ {options, incompatible,
+ [stateless_tickets_seed, {session_tickets, disabled}]}),
+
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, stateful},
+ {stateless_tickets_seed, Seed}],
+ {options, incompatible,
+ [stateless_tickets_seed, {session_tickets, stateful}]}),
+
+ InvalidSeed1 = 12345,
+ start_server_negative(Config, [{versions, ['tlsv1.2', 'tlsv1.3']},
+ {session_tickets, stateless},
+ {stateless_tickets_seed, InvalidSeed1}],
+ {options, {stateless_tickets_seed, InvalidSeed1}}).
%%--------------------------------------------------------------------
honor_server_cipher_order_tls13() ->
@@ -2541,27 +3415,24 @@ invalid_options_tls13() ->
invalid_options_tls13(Config) when is_list(Config) ->
TestOpts =
[{{beast_mitigation, one_n_minus_one},
- {options, dependency,
- {beast_mitigation,{versions,[tlsv1]}}},
+ {options, incompatible,
+ [beast_mitigation,{versions,['tlsv1.3']}]},
common},
{{next_protocols_advertised, [<<"http/1.1">>]},
- {options, dependency,
- {next_protocols_advertised,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible,
+ [next_protocols_advertised, {versions,['tlsv1.3']}]},
server},
{{client_preferred_next_protocols,
{client, [<<"http/1.1">>]}},
- {options, dependency,
- {client_preferred_next_protocols,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible,
+ [client_preferred_next_protocols, {versions,['tlsv1.3']}]},
client},
{{client_renegotiation, false},
- {options, dependency,
- {client_renegotiation,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible,
+ [client_renegotiation, {versions,['tlsv1.3']}]},
server
},
@@ -2571,49 +3442,36 @@ invalid_options_tls13(Config) when is_list(Config) ->
},
{{padding_check, false},
- {options, dependency,
- {padding_check,{versions,[tlsv1]}}},
+ {options, incompatible, [padding_check,{versions,['tlsv1.3']}]},
common},
{{psk_identity, "Test-User"},
- {options, dependency,
- {psk_identity,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [psk_identity,{versions,['tlsv1.3']}]},
common},
{{user_lookup_fun,
{fun ssl_test_lib:user_lookup/3, <<1,2,3>>}},
- {options, dependency,
- {user_lookup_fun,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [user_lookup_fun,{versions,['tlsv1.3']}]},
common},
{{reuse_session, fun(_,_,_,_) -> false end},
- {options, dependency,
- {reuse_session,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [reuse_session, {versions,['tlsv1.3']}]},
server},
{{reuse_session, <<1,2,3,4>>},
- {options, dependency,
- {reuse_session,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [reuse_session, {versions,['tlsv1.3']}]},
client},
{{reuse_sessions, true},
- {options, dependency,
- {reuse_sessions,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [reuse_sessions, {versions,['tlsv1.3']}]},
common},
{{secure_renegotiate, false},
- {options, dependency,
- {secure_renegotiate,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {options, incompatible, [secure_renegotiate, {versions,['tlsv1.3']}]},
common},
- {{srp_identity, false},
- {options, dependency,
- {srp_identity,
- {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}},
+ {{srp_identity, {"user", "passwd"}},
+ {options, incompatible, [srp_identity, {versions,['tlsv1.3']}]},
client}
],
@@ -2644,65 +3502,6 @@ cookie(Config) when is_list(Config) ->
cookie_extension(Config, true),
cookie_extension(Config, false).
-warn_verify_none() ->
- [{doc, "Test that verify_none default generates warning."}].
-warn_verify_none(Config) when is_list(Config)->
- ok = logger:add_handler(?MODULE,?MODULE,#{config=>self()}),
- 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()},
- {options, ServerOpts},
- {mfa, {ssl_test_lib, no_result, []}}]),
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {from, self()},
- {host, Hostname},
- {options, ClientOpts},
- {mfa, {ssl_test_lib, no_result, []}}]),
- receive
- warning_generated ->
- ok = logger:remove_handler(?MODULE)
- after 500 ->
- ok = logger:remove_handler(?MODULE),
- ct:fail(no_warning)
- end,
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Server).
-
-suppress_warn_verify_none() ->
- [{doc, "Test that explicit verify_none suppresses warning."}].
-suppress_warn_verify_none(Config) when is_list(Config)->
- ok = logger:add_handler(?MODULE,?MODULE,#{config=>self()}),
-
- 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()},
- {options, ServerOpts},
- {mfa, {ssl_test_lib, no_result, []}}]),
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {from, self()},
- {host, Hostname},
- {options, [{verify, verify_none} | ClientOpts]},
- {mfa, {ssl_test_lib, no_result, []}}]),
- receive
- warning_generated ->
- ok = logger:remove_handler(?MODULE),
- ct:fail(warning)
- after 500 ->
- ok = logger:remove_handler(?MODULE)
- end,
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Server).
-
%%--------------------------------------------------------------------
check_random_nonce() ->
[{doc,"Test random nonce - expecting 32B random for TLS1.3 and 4B UTC "
@@ -3058,13 +3857,10 @@ active_n_common(S, N) ->
ok({ok,V}) -> V.
repeat(N, Fun) ->
- repeat(N, N, Fun).
+ Repeat = fun F(Arg) when is_integer(Arg), Arg > 0 -> Fun(N - Arg), F(Arg - 1);
+ F(_) -> ok end,
+ Repeat(N).
-repeat(N, T, Fun) when is_integer(N), N > 0 ->
- Fun(T-N),
- repeat(N-1, T, Fun);
-repeat(_, _, _) ->
- ok.
get_close(Pid, Where) ->
receive
@@ -3224,55 +4020,69 @@ log(#{msg:={report,_Report}},#{config:=Pid}) ->
log(_,_) ->
ok.
-length_exclusive({3,_} = Version) ->
- length(exclusive_default_up_to_version(Version, [])) +
- length(exclusive_non_default_up_to_version(Version, []));
-length_exclusive({254,_} = Version) ->
- length(dtls_exclusive_default_up_to_version(Version, [])) +
- length(dtls_exclusive_non_default_up_to_version(Version, [])).
+length_exclusive(Version) when ?TLS_1_X(Version) ->
+ length(exclusive_default_up_to_version(Version)) +
+ length(exclusive_non_default_up_to_version(Version));
+length_exclusive(Version) when ?DTLS_1_X(Version)->
+ length(dtls_exclusive_default_up_to_version(Version)) +
+ length(dtls_exclusive_non_default_up_to_version(Version)).
length_all(Version) ->
length(ssl:cipher_suites(all, Version)).
-exclusive_default_up_to_version({3, 1} = Version, Acc) ->
- ssl:cipher_suites(exclusive, Version) ++ Acc;
-exclusive_default_up_to_version({3, Minor} = Version, Acc) when Minor =< 4 ->
- Suites = ssl:cipher_suites(exclusive, Version),
- exclusive_default_up_to_version({3, Minor-1}, Suites ++ Acc).
-
-dtls_exclusive_default_up_to_version({254, 255} = Version, Acc) ->
- ssl:cipher_suites(exclusive, Version) ++ Acc;
-dtls_exclusive_default_up_to_version({254, 253} = Version, Acc) ->
- Suites = ssl:cipher_suites(exclusive, Version),
- dtls_exclusive_default_up_to_version({254, 255}, Suites ++ Acc).
-
-exclusive_non_default_up_to_version({3, 1} = Version, Acc) ->
- exclusive_non_default_version(Version) ++ Acc;
-exclusive_non_default_up_to_version({3, 4}, Acc) ->
- exclusive_non_default_up_to_version({3, 3}, Acc);
-exclusive_non_default_up_to_version({3, Minor} = Version, Acc) when Minor =< 3 ->
- Suites = exclusive_non_default_version(Version),
- exclusive_non_default_up_to_version({3, Minor-1}, Suites ++ Acc).
-
-dtls_exclusive_non_default_up_to_version({254, 255} = Version, Acc) ->
- dtls_exclusive_non_default_version(Version) ++ Acc;
-dtls_exclusive_non_default_up_to_version({254, 253} = Version, Acc) ->
- Suites = dtls_exclusive_non_default_version(Version),
- dtls_exclusive_non_default_up_to_version({254, 255}, Suites ++ Acc).
-
-exclusive_non_default_version({_, Minor}) ->
- tls_v1:psk_exclusive(Minor) ++
- tls_v1:srp_exclusive(Minor) ++
- tls_v1:rsa_exclusive(Minor) ++
- tls_v1:des_exclusive(Minor) ++
- tls_v1:rc4_exclusive(Minor).
+exclusive_default_up_to_version(Version) ->
+ lists:flatmap(fun (Vsn) -> ssl:cipher_suites(exclusive, Vsn) end
+ , exclusive_default_up_to_version_helper(Version)).
+
+exclusive_default_up_to_version_helper(?TLS_1_3) -> [?TLS_1_3, ?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_default_up_to_version_helper(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_default_up_to_version_helper(?TLS_1_1) -> [?TLS_1_1, ?TLS_1_0];
+exclusive_default_up_to_version_helper(?TLS_1_0) -> [?TLS_1_0].
+
+
+
+dtls_exclusive_default_up_to_version(Version) ->
+ lists:flatmap( fun (Vsn) -> ssl:cipher_suites(exclusive, Vsn) end
+ , dtls_exclusive_default_up_to_version_helper(Version)).
+
+dtls_exclusive_default_up_to_version_helper(?DTLS_1_2) -> [?DTLS_1_0, ?DTLS_1_2];
+dtls_exclusive_default_up_to_version_helper(?DTLS_1_0) -> [?DTLS_1_0].
+
+
+
+exclusive_non_default_up_to_version(Version) ->
+ lists:flatmap(fun exclusive_non_default_version/1
+ , exclusive_non_default_up_to_version_helper(Version)).
+
+exclusive_non_default_up_to_version_helper(?TLS_1_3) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_non_default_up_to_version_helper(?TLS_1_2) -> [?TLS_1_2, ?TLS_1_1, ?TLS_1_0];
+exclusive_non_default_up_to_version_helper(?TLS_1_1) -> [?TLS_1_1, ?TLS_1_0];
+exclusive_non_default_up_to_version_helper(?TLS_1_0) -> [?TLS_1_0].
+
+
+dtls_exclusive_non_default_up_to_version(Version) ->
+ lists:flatmap( fun dtls_exclusive_non_default_version/1
+ , dtls_exclusive_non_default_up_to_version_helper(Version)).
+
+dtls_exclusive_non_default_up_to_version_helper(?DTLS_1_2) -> [?DTLS_1_0, ?DTLS_1_2];
+dtls_exclusive_non_default_up_to_version_helper(?DTLS_1_0) -> [?DTLS_1_0].
+
+
+exclusive_non_default_version(Version) ->
+ Ls = [ fun tls_v1:psk_exclusive/1
+ , fun tls_v1:srp_exclusive/1
+ , fun tls_v1:rsa_exclusive/1
+ , fun tls_v1:des_exclusive/1
+ , fun tls_v1:rc4_exclusive/1],
+ lists:flatmap(fun(Fn) -> Fn(Version) end, Ls).
dtls_exclusive_non_default_version(DTLSVersion) ->
- {_,Minor} = ssl:tls_version(DTLSVersion),
- tls_v1:psk_exclusive(Minor) ++
- tls_v1:srp_exclusive(Minor) ++
- tls_v1:rsa_exclusive(Minor) ++
- tls_v1:des_exclusive(Minor).
+ Version = ssl:tls_version(DTLSVersion),
+ Fns = [ fun tls_v1:psk_exclusive/1
+ , fun tls_v1:srp_exclusive/1
+ , fun tls_v1:rsa_exclusive/1
+ , fun tls_v1:des_exclusive/1],
+ lists:flatmap(fun (Fn) -> Fn(Version) end, Fns).
selected_peer(ExpectedClient,
ExpectedServer, ClientOpts, ServerOpts, Config) ->
@@ -3342,21 +4152,36 @@ test_config('tlsv1.2', _) ->
{SDSACert, SDSAKey, SDSACACerts} = get_single_options(cert, key, cacerts, SDSAOpts),
{CDSACert, CDSAKey, CDSACACerts} = get_single_options(cert, key, cacerts, CDSAOpts),
+ AllSigAlgs1_2 = ssl_test_lib:all_1_2_sig_algs(),
- [{#{server_config => [{certs_keys, [#{cert => SDSACert, key => SDSAKey}, #{cert => SRSACert, key => SRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.3', 'tlsv1.2']},
+ [{#{server_config => [{certs_keys,
+ [#{cert => SDSACert, key => SDSAKey},
+ #{cert => SRSACert, key => SRSAKey}]},
+ {verify, verify_peer},
+ {versions, ['tlsv1.3', 'tlsv1.2']},
{cacerts, SRSACACerts ++ SDSACACerts}],
- client_config => [{certs_keys, [#{cert => CDSACert, key => CDSAKey}, #{cert => CRSACert, key => CRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.3', 'tlsv1.2']},
+ client_config => [{certs_keys,
+ [#{cert => CDSACert, key => CDSAKey},
+ #{cert => CRSACert, key => CRSAKey}]},
+ {verify, verify_peer},
+ {versions, ['tlsv1.3', 'tlsv1.2']},
{cacerts, CRSACACerts ++ CDSACACerts}]
- }, {client_peer, SRSACert}, {server_peer, CRSACert}},
- {#{server_config => [{certs_keys, [#{cert => SDSACert, key => SDSAKey}, #{cert => SRSACert, key => SRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.2']},
+ },
+ {client_peer, SRSACert}, {server_peer, CRSACert}},
+ {#{server_config => [{certs_keys, [#{cert => SDSACert, key => SDSAKey},
+ #{cert => SRSACert, key => SRSAKey}]},
+ {verify, verify_peer},
+ AllSigAlgs1_2,
+ {versions, ['tlsv1.2']},
{cacerts, SRSACACerts ++ SDSACACerts}],
- client_config => [{certs_keys, [#{cert => CDSACert, key => CDSAKey}, #{cert => CRSACert, key => CRSAKey}]},
- {verify, verify_peer}, {versions, ['tlsv1.2']},
+ client_config => [{certs_keys, [#{cert => CDSACert, key => CDSAKey},
+ #{cert => CRSACert, key => CRSAKey}]},
+ {verify, verify_peer},
+ {versions, ['tlsv1.2']},
+ AllSigAlgs1_2,
{cacerts, CRSACACerts ++ CDSACACerts}]
- }, {client_peer, SDSACert}, {server_peer, CDSACert}}];
+ },
+ {client_peer, SDSACert}, {server_peer, CDSACert}}];
test_config('dtlsv1.2', Config) ->
#{server_config := SRSAPSSOpts,
client_config := CRSAPSSOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_pss_pss, [], Config, "dtls_pss_pss_conf"),
@@ -3376,6 +4201,7 @@ test_config('dtlsv1.2', Config) ->
client_config => [{certs_keys, [#{certfile => CRSAPSSRSAECert, keyfile => CRSAPSSRSAEKey},
#{certfile => CRSAPSSCert, keyfile => CRSAPSSKey}]},
{verify, verify_peer},
+ {signature_algs, [rsa_pss_pss_sha256]},
{cacertfile, CRSAPSSCACerts}]
},
{client_peer, pem_to_der_cert(SRSAPSSCert)}, {server_peer, pem_to_der_cert(CRSAPSSCert)}},
@@ -3460,7 +4286,8 @@ pem_to_der_cert(Pem) ->
test_sha1_cert_conf('tlsv1.3'= Version, RSA, ECDSA, Config) ->
run_sha1_cert_conf(Version, ECDSA, Config, ecdsa_sha1),
run_sha1_cert_conf(Version, RSA, Config, rsa_pkcs1_sha1);
-test_sha1_cert_conf('tlsv1.2'= Version, RSA, ECDSA, Config) ->
+test_sha1_cert_conf(Version, RSA, ECDSA, Config) when Version == 'tlsv1.2';
+ Version == 'dtlsv1.2' ->
run_sha1_cert_conf(Version, RSA, Config, {sha, rsa}),
run_sha1_cert_conf(Version, ECDSA, Config, {sha, ecdsa});
test_sha1_cert_conf(Version,RSA,_,Config) ->
@@ -3488,7 +4315,8 @@ run_sha1_cert_conf('tlsv1.3', #{client_config := ClientOpts, server_config := Se
ssl_test_lib:basic_test([{verify, verify_peer}, {signature_algs, IncludeLegacyAlg} | ClientOpts],
[{signature_algs, IncludeLegacyAlg} | ServerOpts], Config);
-run_sha1_cert_conf('tlsv1.2', #{client_config := ClientOpts, server_config := ServerOpts}, Config, LegacyAlg) ->
+run_sha1_cert_conf(Version, #{client_config := ClientOpts, server_config := ServerOpts}, Config, LegacyAlg) when Version == 'tlsv1.2';
+ Version == 'dtlsv1.2' ->
SigAlgs = [%% SHA2
{sha512, ecdsa},
{sha512, rsa},
@@ -3501,5 +4329,9 @@ run_sha1_cert_conf('tlsv1.2', #{client_config := ClientOpts, server_config := Se
IncludeLegacyAlg = SigAlgs ++ [LegacyAlg],
ssl_test_lib:basic_test( [{verify, verify_peer}, {signature_algs, IncludeLegacyAlg} | ClientOpts],
[{signature_algs, IncludeLegacyAlg} | ServerOpts], Config);
-run_sha1_cert_conf(_, #{client_config := ClientOpts, server_config := ServerOpts}, Config, _) ->
- ssl_test_lib:basic_test([{verify, verify_peer} | ClientOpts], ServerOpts, Config).
+run_sha1_cert_conf(_, #{client_config := ClientOpts, server_config := ServerOpts}, Config, LegacyAlg) ->
+ NVersion = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ SigOpts = ssl_test_lib:sig_algs(LegacyAlg, NVersion),
+ ssl_test_lib:basic_test([{verify, verify_peer} | ClientOpts] ++ SigOpts, ServerOpts, Config).
+
+
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index db306419aa..52242d8728 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -483,7 +483,8 @@ fake_root(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
TBS = OTPCert#'OTPCertificate'.tbsCertificate,
@@ -495,29 +496,42 @@ fake_root(Config) when is_list(Config) ->
AuthExt,
false}]),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
- {extensions, [AuthKeyExt]}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
- {extensions, [AuthKeyExt]}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
fake_root_no_intermediate() ->
[{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
@@ -528,7 +542,8 @@ fake_root_no_intermediate(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
@@ -541,28 +556,38 @@ fake_root_no_intermediate(Config) when is_list(Config) ->
AuthExt,
false}]),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
- {extensions, [AuthKeyExt]}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
- {extensions, [AuthKeyExt]}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256},
+ {extensions, [AuthKeyExt]}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, bad_certificate, bad_certificate).
fake_root_legacy() ->
[{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
@@ -572,34 +597,44 @@ fake_root_legacy(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
TBS = OTPCert#'OTPCertificate'.tbsCertificate,
FakeCert = public_key:pkix_sign(TBS, FakeKey),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
-
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256} ],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
fake_root_no_intermediate_legacy() ->
[{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}].
@@ -609,7 +644,8 @@ fake_root_no_intermediate_legacy(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
- {extensions, Ext}]),
+ {digest, sha256},
+ {extensions, Ext}]),
FakeKey = ssl_test_lib:hardcode_rsa_key(1),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
@@ -617,26 +653,35 @@ fake_root_no_intermediate_legacy(Config) when is_list(Config) ->
FakeCert = public_key:pkix_sign(TBS, FakeKey),
#{server_config := ServerConf,
- client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := FakeServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => #{cert => FakeCert, key => FakeKey},
- intermediates => [],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
- test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
+ client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
+ ),
+ #{server_config := FakeServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => #{cert => FakeCert, key => FakeKey},
+ intermediates => [],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}
+ ],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}),
+ test_fake_root(Hostname, ServerNode, ClientNode, ServerConf,
+ ClientConf, FakeCert, FakeServerConf, unknown_ca, unknown_ca).
fake_intermediate_cert() ->
[{doc,"Test that we can not use a fake intermediat cert claiming to be signed by a trusted ROOT but is not."}].
@@ -646,32 +691,48 @@ fake_intermediate_cert(Config) when is_list(Config) ->
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = #{cert := Cert,
key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {digest, sha256},
{extensions, Ext}]),
OtherSROOT = #{cert := OtherSCert,
- key := OtherSKey} = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
- {extensions, Ext}]),
+ key := OtherSKey} =
+ public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256},
+ {extensions, Ext}]),
OtherCROOT = #{cert := OtherCCert,
- key := _OtherCKey} = public_key:pkix_test_root_cert("OTHER Client ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(1)},
- {extensions, Ext}]),
- #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := OtherServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => OtherSROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
- client_chain =>
- #{root => OtherCROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
+ key := _OtherCKey} =
+ public_key:pkix_test_root_cert("OTHER Client ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256},
+ {extensions, Ext}]),
+ #{client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ #{server_config := OtherServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherSROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}]},
+ client_chain =>
+ #{root => OtherCROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
OTPCert = public_key:pkix_decode_cert(Cert, otp),
TBS = OTPCert#'OTPCertificate'.tbsCertificate,
TBSExt = TBS#'OTPTBSCertificate'.extensions,
@@ -711,32 +772,44 @@ incomplete_chain_length(Config) when is_list(Config)->
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]),
ROOT = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)},
+ {digest, sha256},
{extensions, Ext}]),
OtherROOT = public_key:pkix_test_root_cert("OTHER SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(3)},
{extensions, Ext}]),
- #{client_config := ClientConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => ROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}]},
- client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
- #{server_config := ServerConf} = public_key:pkix_test_data(#{server_chain =>
- #{root => OtherROOT,
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
- [{key, ssl_test_lib:hardcode_rsa_key(3)}]
- ],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]},
+ #{client_config := ClientConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => ROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(4)}, {digest, sha256}]},
+ client_chain =>
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256} ],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
+
+ #{server_config := ServerConf} =
+ public_key:pkix_test_data(#{server_chain =>
+ #{root => OtherROOT,
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256} ],
+ [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256} ]
+ ],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}]},
client_chain =>
- #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(3)}]}}
- ),
-
+ #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)},
+ {digest, sha256}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)},
+ {digest, sha256}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(3)},
+ {digest, sha256}]}}
+ ),
VerifyFun = {fun(_,{bad_cert, unknown_ca}, UserState) ->
%% accept this error to provoke the
diff --git a/lib/ssl/test/ssl_bench_test_lib.erl b/lib/ssl/test/ssl_bench_test_lib.erl
index 648b42fb03..474a69fe17 100644
--- a/lib/ssl/test/ssl_bench_test_lib.erl
+++ b/lib/ssl/test/ssl_bench_test_lib.erl
@@ -71,43 +71,6 @@ find_executable(Prog) ->
P -> P
end.
--ifdef(undefined).
-setup(Name) ->
- Host = case os:getenv(?remote_host) of
- false ->
- {ok, This} = inet:gethostname(),
- This;
- RemHost ->
- RemHost
- end,
- Node = list_to_atom(atom_to_list(Name) ++ "@" ++ Host),
- SlaveArgs = case init:get_argument(pa) of
- {ok, PaPaths} ->
- lists:append([" -pa " ++ P || [P] <- PaPaths]);
- _ -> []
- end,
- %% ct:pal("Slave args: ~p~n",[SlaveArgs]),
- Prog =
- case os:find_executable("erl") of
- false -> "erl";
- P -> P
- end,
- ct:pal("Prog = ~p~n", [Prog]),
-
- case net_adm:ping(Node) of
- pong -> ok;
- pang ->
- {ok, Node} =
- slave:start(Host, Name, SlaveArgs, no_link, Prog)
- end,
- Path = code:get_path(),
- true = rpc:call(Node, code, set_path, [Path]),
- ok = rpc:call(Node, ?MODULE, setup_server, [node()]),
- ct:pal("Client (~p) using ~ts~n",[node(), code:which(ssl)]),
- (Node =:= node()) andalso restrict_schedulers(client),
- Node.
--endif.
-
setup_server(ClientNode) ->
(ClientNode =:= node()) andalso restrict_schedulers(server),
ct:pal("Server (~p) using ~ts~n",[node(), code:which(ssl)]),
diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl
index 315c0e20b1..28f8d06aa7 100644
--- a/lib/ssl/test/ssl_cert_SUITE.erl
+++ b/lib/ssl/test/ssl_cert_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -25,6 +25,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -66,6 +67,8 @@
missing_root_cert_auth_user_verify_fun_accept/1,
missing_root_cert_auth_user_verify_fun_reject/0,
missing_root_cert_auth_user_verify_fun_reject/1,
+ missing_root_cert_auth_user_old_verify_fun_accept/0,
+ missing_root_cert_auth_user_old_verify_fun_accept/1,
verify_fun_always_run_client/0,
verify_fun_always_run_client/1,
verify_fun_always_run_server/0,
@@ -235,6 +238,7 @@ all_version_tests() ->
missing_root_cert_auth,
missing_root_cert_auth_user_verify_fun_accept,
missing_root_cert_auth_user_verify_fun_reject,
+ missing_root_cert_auth_user_old_verify_fun_accept,
verify_fun_always_run_client,
verify_fun_always_run_server,
incomplete_chain_auth,
@@ -270,11 +274,11 @@ init_per_group(GroupName, Config) ->
case ssl_test_lib:is_protocol_version(GroupName) of
true ->
ssl_test_lib:clean_start(),
- ssl_test_lib:init_per_group(GroupName,
+ ssl_test_lib:init_per_group(GroupName,
[{client_type, erlang},
{server_type, erlang},
{version, GroupName} | Config]);
- false ->
+ false ->
do_init_per_group(GroupName, Config)
end.
@@ -282,18 +286,22 @@ do_init_per_group(Group, Config0) when Group == rsa;
Group == rsa_1_3 ->
Config1 = ssl_test_lib:make_rsa_cert(Config0),
Config = ssl_test_lib:make_rsa_1024_cert(Config1),
- COpts = proplists:get_value(client_rsa_opts, Config),
+ COpts = proplists:get_value(client_rsa_verify_opts, Config),
SOpts = proplists:get_value(server_rsa_opts, Config),
- [{cert_key_alg, rsa} |
- lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ Version = proplists:get_value(version, Config),
+ [{cert_key_alg, rsa},
+ {extra_client, ssl_test_lib:sig_algs(rsa, Version)},
+ {extra_server, ssl_test_lib:sig_algs(rsa, Version)} |
+ lists:delete(cert_key_alg,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
do_init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
Alg == rsa_pss_pss ->
Supports = crypto:supports(),
RSAOpts = proplists:get_value(rsa_opts, Supports),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
case lists:member(rsa_pkcs1_pss_padding, RSAOpts)
andalso lists:member(rsa_pss_saltlen, RSAOpts)
@@ -302,8 +310,8 @@ do_init_per_group(Alg, Config) when Alg == rsa_pss_rsae;
#{client_config := COpts,
server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_alg(Alg), [], Config, ""),
[{cert_key_alg, Alg},
- {extra_client, sig_algs(Alg)},
- {extra_server, sig_algs(Alg)} |
+ {extra_client, ssl_test_lib:sig_algs(Alg, Version)},
+ {extra_server, ssl_test_lib:sig_algs(Alg, Version)} |
lists:delete(cert_key_alg,
[{client_cert_opts, COpts},
{server_cert_opts, SOpts} |
@@ -340,13 +348,13 @@ do_init_per_group(Group, Config0) when Group == ecdsa;
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
true ->
Config = ssl_test_lib:make_ecdsa_cert(Config0),
- COpts = proplists:get_value(client_ecdsa_opts, Config),
+ COpts = proplists:get_value(client_ecdsa_verify_opts, Config),
SOpts = proplists:get_value(server_ecdsa_opts, Config),
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
false ->
@@ -377,18 +385,23 @@ do_init_per_group(eddsa_1_3, Config0) ->
false ->
{skip, "Missing EC crypto support"}
end;
-do_init_per_group(dsa, Config0) ->
+do_init_per_group(dsa = Alg, Config0) ->
PKAlg = crypto:supports(public_keys),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config0)),
case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of
true ->
Config = ssl_test_lib:make_dsa_cert(Config0),
COpts = proplists:get_value(client_dsa_opts, Config),
SOpts = proplists:get_value(server_dsa_opts, Config),
- [{cert_key_alg, dsa} |
+ [{cert_key_alg, dsa},
+ {extra_client, ssl_test_lib:sig_algs(Alg, Version) ++
+ [{ciphers, ssl_test_lib:dsa_suites(Version)}]},
+ {extra_server, ssl_test_lib:sig_algs(Alg, Version) ++
+ [{ciphers, ssl_test_lib:dsa_suites(Version)}]} |
lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
- lists:delete(server_cert_opts,
+ [{client_cert_opts, COpts},
+ {server_cert_opts, SOpts} |
+ lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))])];
false ->
{skip, "Missing DSS crypto support"}
@@ -400,11 +413,11 @@ end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
init_per_testcase(signature_algorithms_bad_curve_secp256r1, Config) ->
- init_rsa_ecdsa_opts(Config, secp256r1);
+ init_ecdsa_opts(Config, secp256r1);
init_per_testcase(signature_algorithms_bad_curve_secp384r1, Config) ->
- init_rsa_ecdsa_opts(Config, secp384r1);
+ init_ecdsa_opts(Config, secp384r1);
init_per_testcase(signature_algorithms_bad_curve_secp521r1, Config) ->
- init_rsa_ecdsa_opts(Config, secp521r1);
+ init_ecdsa_opts(Config, secp521r1);
init_per_testcase(_TestCase, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
ct:timetrap({seconds, 10}),
@@ -413,17 +426,18 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
-init_rsa_ecdsa_opts(Config0, Curve) ->
+init_ecdsa_opts(Config0, Curve) ->
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config0)),
PKAlg = crypto:supports(public_keys),
case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of
true ->
Config = ssl_test_lib:make_rsa_ecdsa_cert(Config0, Curve),
- COpts = proplists:get_value(client_rsa_ecdsa_opts, Config),
- SOpts = proplists:get_value(server_rsa_ecdsa_opts, Config),
+ COpts = proplists:get_value(client_ecdsa_verify_opts, Config),
+ SOpts = proplists:get_value(server_ecdsa_opts, Config),
[{cert_key_alg, ecdsa} |
lists:delete(cert_key_alg,
- [{client_cert_opts, COpts},
- {server_cert_opts, SOpts} |
+ [{client_cert_opts, ssl_test_lib:sig_algs(ecdsa, Version) ++ COpts},
+ {server_cert_opts, ssl_test_lib:sig_algs(ecdsa, Version) ++ SOpts} |
lists:delete(server_cert_opts,
lists:delete(client_cert_opts, Config))]
)];
@@ -500,13 +514,15 @@ missing_root_cert_auth() ->
missing_root_cert_auth(Config) when is_list(Config) ->
ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)),
{ClientNode, ServerNode, _} = ssl_test_lib:run_where(Config),
- Version = proplists:get_value(version, Config),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer}
+ {options, no_reuse(Version) ++ [{verify, verify_peer}
| ServerOpts]}]),
- ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}),
+ Error = {error, {options, incompatible,
+ [{verify,verify_peer},{cacerts,undefined}]}},
+ ssl_test_lib:check_result(Server, Error),
ClientOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)),
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0},
@@ -514,7 +530,7 @@ missing_root_cert_auth(Config) when is_list(Config) ->
{options, [{verify, verify_peer}
| ClientOpts]}]),
- ssl_test_lib:check_result(Client, {error, {options, {cacertfile, ""}}}).
+ ssl_test_lib:check_result(Client, Error).
%%--------------------------------------------------------------------
missing_root_cert_auth_user_verify_fun_accept() ->
@@ -523,6 +539,7 @@ missing_root_cert_auth_user_verify_fun_accept() ->
missing_root_cert_auth_user_verify_fun_accept(Config) ->
ServerOpts = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ ClientCaCerts = public_key:cacerts_get(),
FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) ->
{valid, UserState};
(_,{bad_cert, _} = Reason, _) ->
@@ -534,16 +551,19 @@ missing_root_cert_auth_user_verify_fun_accept(Config) ->
(_, valid_peer, UserState) ->
{valid, UserState}
end, []},
- ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
- {verify_fun, FunAndState}], Config),
+ ClientOpts = ssl_test_lib:ssl_options(extra_client,
+ [{verify, verify_peer}, {verify_fun, FunAndState},
+ {cacerts, ClientCaCerts}],
+ Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
-missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept() ->
+missing_root_cert_auth_user_old_verify_fun_accept() ->
[{doc, "Test old style verify fun"}].
-missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) ->
+missing_root_cert_auth_user_old_verify_fun_accept(Config) ->
ServerOpts = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ ClientCaCerts = public_key:cacerts_get(),
AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc;
(Other, Acc) -> [Other | Acc]
end,
@@ -554,8 +574,10 @@ missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) ->
[_|_] -> false
end
end,
- ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
- {verify_fun, VerifyFun}], Config),
+ ClientOpts = ssl_test_lib:ssl_options(extra_client,
+ [{verify, verify_peer},
+ {verify_fun, VerifyFun},
+ {cacerts, ClientCaCerts}], Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
@@ -565,6 +587,7 @@ missing_root_cert_auth_user_verify_fun_reject() ->
missing_root_cert_auth_user_verify_fun_reject(Config) ->
ServerOpts = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
+ ClientCaCerts = public_key:cacerts_get(),
FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _UserState) ->
{fail, Reason};
(_,{bad_cert, _} = Reason, _) ->
@@ -576,8 +599,11 @@ missing_root_cert_auth_user_verify_fun_reject(Config) ->
(_, valid_peer, UserState) ->
{valid, UserState}
end, []},
- ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
- {verify_fun, FunAndState}], Config),
+ ClientOpts = ssl_test_lib:ssl_options(extra_client,
+ [{verify, verify_peer},
+ {verify_fun, FunAndState},
+ {cacerts, ClientCaCerts}],
+ Config),
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca).
@@ -641,7 +667,7 @@ verify_fun_always_run_client(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib,
no_result, []}},
- {options, no_reuse(n_version(Version)) ++ ServerOpts}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
%% If user verify fun is called correctly we fail the connection.
@@ -705,7 +731,7 @@ verify_fun_always_run_server(Config) when is_list(Config) ->
{mfa, {ssl_test_lib,
no_result, []}},
{options,
- no_reuse(n_version(Version)) ++ [{verify, verify_peer},
+ no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer},
{verify_fun, FunAndState} |
ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
@@ -754,7 +780,7 @@ critical_extension_auth(Config) when is_list(Config) ->
[{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error(
[{node, ClientNode}, {port, Port},
@@ -786,7 +812,7 @@ critical_extension_client_auth(Config) when is_list(Config) ->
[{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, no_result, []}},
- {options, no_reuse(n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error(
[{node, ClientNode}, {port, Port},
@@ -841,7 +867,7 @@ extended_key_usage_auth(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, no_reuse(n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_none} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -876,7 +902,7 @@ extended_key_usage_client_auth(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, no_reuse(n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ [{verify, verify_peer} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -913,7 +939,7 @@ cert_expired(Config) when is_list(Config) ->
Version = proplists:get_value(version, Config),
Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {options, no_reuse(n_version(Version)) ++ ServerOpts}]),
+ {options, no_reuse(ssl_test_lib:n_version(Version)) ++ ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -975,34 +1001,35 @@ key_auth_ext_sign_only(Config) when is_list(Config) ->
Version = proplists:get_value(version, Config),
ClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config)],
ServerOpts = [{verify, verify_peer}, {ciphers,
- ssl_test_lib:rsa_non_signed_suites(n_version(Version))}
- | ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
+ ssl_test_lib:rsa_non_signed_suites(ssl_test_lib:n_version(Version))}
+ | ssl_test_lib:ssl_options(extra_server, ServerOpts0, Config)],
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
longer_chain() ->
[{doc,"Test depth option"}].
-longer_chain(Config) when is_list(Config) ->
+longer_chain(Config) when is_list(Config) ->
#{server_config := ServerOpts0,
- client_config := ClientOpts0} =
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
- intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
[{key, ssl_test_lib:hardcode_rsa_key(3)}],
[{key, ssl_test_lib:hardcode_rsa_key(4)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(5)}]},
- client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}],
+ client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
- peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
[ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerOpts0),
ClientCas = proplists:get_value(cacerts, ClientOpts0),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
ServerOpts = ssl_test_lib:ssl_options(extra_server, [{verify, verify_peer}, {cacerts, [ServerRoot]} |
- proplists:delete(cacerts, ServerOpts0)], Config),
+ proplists:delete(cacerts, ServerOpts0)] ++ ssl_test_lib:sig_algs(rsa, Version), Config),
ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer},
{depth, 5},
- {cacerts, ServerCas ++ ClientCas} |
- proplists:delete(cacerts, ClientOpts0)], Config),
+ {cacerts, ServerCas ++ ClientCas} |
+ proplists:delete(cacerts, ClientOpts0)]++ ssl_test_lib:sig_algs(rsa, Version) , Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
cross_signed_chain() ->
@@ -1031,26 +1058,28 @@ cross_signed_chain(Config)
ServerCas0 = proplists:get_value(cacerts, ServerOpts0),
ClientCas0 = proplists:get_value(cacerts, ClientOpts0),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
{[Peer,CI1,CI2,CROld], CROld} = chain_and_root(ClientOpts0),
{[_Peer,CI1New,CI2New,CRNew], CRNew} = chain_and_root(ClientOptsNew),
ServerCas = [CRNew|ServerCas0 -- [CROld]],
ServerOpts = ssl_test_lib:ssl_options(extra_server, [{verify, verify_peer} |
- lists:keyreplace(cacerts, 1, ServerOpts0, {cacerts, ServerCas})],
+ lists:keyreplace(cacerts, 1, ServerOpts0, {cacerts, ServerCas})]
+ ++ ssl_test_lib:sig_algs(rsa, Version),
Config),
ClientOpts = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer} |
lists:keyreplace(cacerts, 1,
lists:keyreplace(cert, 1, ClientOpts0,
{cert, [Peer,CI1New,CI2New,CI1,CI2,CRNew,CROld]}),
- {cacerts, ClientCas0})],
+ {cacerts, ClientCas0})] ++ ssl_test_lib:sig_algs(rsa, Version),
Config),
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config),
ClientOpts2 = ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer} |
lists:keyreplace(cacerts, 1,
lists:keyreplace(cert, 1, ClientOpts0,
{cert, [Peer,CI1,CI1New,CI2,CI2New,CROld,CRNew]}),
- {cacerts, ClientCas0})],
+ {cacerts, ClientCas0})] ++ ssl_test_lib:sig_algs(rsa, Version),
Config),
ssl_test_lib:basic_test(ClientOpts2, ServerOpts, Config),
ok.
@@ -1072,12 +1101,15 @@ expired_root_with_cross_signed_root(Config) when is_list(Config) ->
#{cert := Root} = SRoot = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, Key1},
{validity, {{Year-2, Month, Day},
{Year-1, Month, Day}}}]),
- #{server_config := ServerOpts, client_config := ClientOpts} =
+ #{server_config := ServerOpts0, client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain => #{root => SRoot,
intermediates => [[{key, Key2}], [{key, Key3}]],
peer => [{key, Key4}]},
client_chain => #{root => [{key, Key5}],
peer => [{key, Key6}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
SCert = proplists:get_value(cert, ServerOpts),
SCerts = proplists:get_value(cacerts, ServerOpts),
@@ -1190,7 +1222,7 @@ hello_retry_client_auth(Config) ->
{supported_groups, [secp256r1, x25519]}|ClientOpts0],
ServerOpts = [{verify, verify_peer},
{fail_if_no_peer_cert, true} | ServerOpts1],
-
+
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
hello_retry_client_auth_empty_cert_accepted() ->
@@ -1277,12 +1309,14 @@ signature_algorithms_bad_curve_secp521r1(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
%% Set versions
- ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0],
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {signature_algs, [ecdsa_secp512r1_sha256,
+ {sha256,rsa}]}
+ | ServerOpts0],
ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']},
{signature_algs, [ecdsa_secp256r1_sha256,
ecdsa_secp384r1_sha384,
{sha256,rsa}]}|ClientOpts0],
-
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security).
%%--------------------------------------------------------------------
@@ -1337,17 +1371,6 @@ server_certificate_authorities_disabled(Config) ->
%%--------------------------------------------------------------------
%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-n_version(Version) when
- Version == 'tlsv1.3';
- Version == 'tlsv1.2';
- Version == 'tlsv1.1';
- Version == 'tlsv1';
- Version == 'sslv3' ->
- tls_record:protocol_version(Version);
-n_version(Version) when Version == 'dtlsv1.2';
- Version == 'dtlsv1' ->
- dtls_record:protocol_version(Version).
-
rsa_alg(rsa_pss_rsae_1_3) ->
rsa_pss_rsae;
rsa_alg(rsa_pss_pss_1_3) ->
@@ -1355,7 +1378,7 @@ rsa_alg(rsa_pss_pss_1_3) ->
rsa_alg(Atom) ->
Atom.
-no_reuse({3, N}) when N >= 4 ->
+no_reuse(?TLS_1_3) ->
[];
no_reuse(_) ->
[{reuse_sessions, false}].
@@ -1366,11 +1389,3 @@ chain_and_root(Config) ->
{ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, [], encoded),
{Chain, Root}.
-sig_algs(rsa_pss_pss) ->
- [{signature_algs, [rsa_pss_pss_sha512,
- rsa_pss_pss_sha384,
- rsa_pss_pss_sha256]}];
-sig_algs(rsa_pss_rsae) ->
- [{signature_algs, [rsa_pss_rsae_sha512,
- rsa_pss_rsae_sha384,
- rsa_pss_rsae_sha256]}].
diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl
index b6fb9f4724..a551025ea5 100644
--- a/lib/ssl/test/ssl_cert_tests.erl
+++ b/lib/ssl/test/ssl_cert_tests.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -102,9 +102,10 @@ client_auth_empty_cert_accepted() ->
[{doc,"Client sends empty cert chain as no cert is configured and server allows it"}].
client_auth_empty_cert_accepted(Config) ->
- ClientOpts = proplists:delete(keyfile,
- proplists:delete(certfile,
- ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config))),
+ ClientOpts = [{verify, verify_peer} |
+ proplists:delete(keyfile,
+ proplists:delete(certfile,
+ ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)))],
ServerOpts0 = ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config),
ServerOpts = [{verify, verify_peer},
{fail_if_no_peer_cert, false} | ServerOpts0],
@@ -115,8 +116,8 @@ client_auth_empty_cert_rejected() ->
client_auth_empty_cert_rejected(Config) ->
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
- | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
- ClientOpts0 = ssl_test_lib:ssl_options(extra_client, [], Config),
+ | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
+ ClientOpts0 = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_client, [], Config)],
%% Delete Client Cert and Key
ClientOpts1 = proplists:delete(certfile, ClientOpts0),
ClientOpts = proplists:delete(keyfile, ClientOpts1),
@@ -140,11 +141,11 @@ client_auth_no_suitable_chain(Config) when is_list(Config) ->
client_chain => #{root => CRoot,
intermediates => [[]],
peer => []}}),
- ClientOpts = ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config),
+ ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_client, ClientOpts0, Config)],
ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}
- | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
+ | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
Version = proplists:get_value(version, Config),
-
+
case Version of
'tlsv1.3' ->
ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required);
@@ -274,10 +275,14 @@ client_auth_seelfsigned_peer(Config) when is_list(Config) ->
#{cert := Cert,
key := Key} = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, ssl_test_lib:hardcode_rsa_key(6)},
{extensions, Ext}]),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
DerKey = public_key:der_encode('RSAPrivateKey', Key),
- ssl_test_lib:basic_alert(ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer}, {cacerts , [Cert]}], Config),
+ ssl_test_lib:basic_alert(ssl_test_lib:ssl_options(extra_client, [{verify, verify_peer}, {cacerts , [Cert]}] ++
+ ssl_test_lib:sig_algs(rsa, Version), Config),
ssl_test_lib:ssl_options(extra_server, [{cert, Cert},
- {key, {'RSAPrivateKey', DerKey}}], Config), Config, bad_certificate).
+ {key, {'RSAPrivateKey', DerKey}}] ++
+ ssl_test_lib:sig_algs(rsa, Version), Config),
+ Config, bad_certificate).
%%--------------------------------------------------------------------
missing_root_cert_no_auth() ->
[{doc,"Test that the client succeeds if the ROOT CA is unknown in verify_none mode"}].
@@ -285,12 +290,12 @@ missing_root_cert_no_auth() ->
missing_root_cert_no_auth(Config) ->
ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config)],
ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(extra_server, server_cert_opts, Config)],
-
+
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
invalid_signature_client() ->
- [{doc,"Test server with invalid signature"}].
+ [{doc,"Test that server detects invalid client signature"}].
invalid_signature_client(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config),
@@ -314,7 +319,7 @@ invalid_signature_client(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
invalid_signature_server() ->
- [{doc,"Test client with invalid signature"}].
+ [{doc,"Test that client detects invalid server signature"}].
invalid_signature_server(Config) when is_list(Config) ->
ClientOpts0 = ssl_test_lib:ssl_options(extra_client, client_cert_opts, Config),
@@ -462,8 +467,8 @@ test_ciphers(_, 'tlsv1.3' = Version) ->
end, Ciphers);
test_ciphers(_, Version) when Version == 'dtlsv1';
Version == 'dtlsv1.2' ->
- {_, Minor} = dtls_record:protocol_version(Version),
- Ciphers = [ssl_cipher_format:suite_bin_to_map(Bin) || Bin <- dtls_v1:suites(Minor)],
+ NVersion = dtls_record:protocol_version_name(Version),
+ Ciphers = [ssl_cipher_format:suite_bin_to_map(Bin) || Bin <- dtls_v1:suites(NVersion)],
ct:log("Version ~p Testing ~p~n", [Version, Ciphers]),
OpenSSLCiphers = openssl_ciphers(),
ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]),
diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl
index 442cc5f790..687bbd6f58 100644
--- a/lib/ssl/test/ssl_cipher_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_SUITE.erl
@@ -25,6 +25,7 @@
-include_lib("common_test/include/ct.hrl").
-include("tls_record.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_record.hrl").
%% Callback functions
-export([all/0,
@@ -97,10 +98,9 @@ aes_decipher_good() ->
aes_decipher_good(Config) when is_list(Config) ->
HashSz = 32,
CipherState = correct_cipher_state(),
- decipher_check_good(HashSz, CipherState, {3,0}),
- decipher_check_good(HashSz, CipherState, {3,1}),
- decipher_check_good(HashSz, CipherState, {3,2}),
- decipher_check_good(HashSz, CipherState, {3,3}).
+ decipher_check_good(HashSz, CipherState, ?TLS_1_0),
+ decipher_check_good(HashSz, CipherState, ?TLS_1_1),
+ decipher_check_good(HashSz, CipherState, ?TLS_1_2).
%%--------------------------------------------------------------------
aes_decipher_fail() ->
@@ -109,19 +109,17 @@ aes_decipher_fail() ->
aes_decipher_fail(Config) when is_list(Config) ->
HashSz = 32,
CipherState = incorrect_cipher_state(),
- decipher_check_fail(HashSz, CipherState, {3,0}),
- decipher_check_fail(HashSz, CipherState, {3,1}),
- decipher_check_fail(HashSz, CipherState, {3,2}),
- decipher_check_fail(HashSz, CipherState, {3,3}).
+ decipher_check_fail(HashSz, CipherState, ?TLS_1_0),
+ decipher_check_fail(HashSz, CipherState, ?TLS_1_1),
+ decipher_check_fail(HashSz, CipherState, ?TLS_1_2).
%%--------------------------------------------------------------------
padding_test(Config) when is_list(Config) ->
HashSz = 16,
CipherState = correct_cipher_state(),
- pad_test(HashSz, CipherState, {3,0}),
- pad_test(HashSz, CipherState, {3,1}),
- pad_test(HashSz, CipherState, {3,2}),
- pad_test(HashSz, CipherState, {3,3}).
+ pad_test(HashSz, CipherState, ?TLS_1_0),
+ pad_test(HashSz, CipherState, ?TLS_1_1),
+ pad_test(HashSz, CipherState, ?TLS_1_2).
%%--------------------------------------------------------------------
sign_algorithms(Config) when is_list(Config) ->
@@ -140,21 +138,14 @@ decipher_check_fail(HashSz, CipherState, Version) ->
true = {Content, Mac, #cipher_state{iv = NextIV}} =/=
ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, aes_fragment(Version), Version, true).
-pad_test(HashSz, CipherState, {3,0} = Version) ->
- %% 3.0 does not have padding test
- {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
- {Content, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, true),
- {Content, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, false);
-pad_test(HashSz, CipherState, {3,1} = Version) ->
+pad_test(HashSz, CipherState, ?TLS_1_0 = Version) ->
%% 3.1 should have padding test, but may be disabled
{Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
BadCont = badpad_content(Content),
{Content, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}) , {3,1}, false),
+ ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment(?TLS_1_0) , ?TLS_1_0, false),
{BadCont, Mac, #cipher_state{iv = NextIV}} =
- ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}), {3,1}, true);
+ ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment(?TLS_1_0), ?TLS_1_0, true);
pad_test(HashSz, CipherState, Version) ->
%% 3.2 and 3.3 must have padding test
{Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
@@ -164,7 +155,7 @@ pad_test(HashSz, CipherState, Version) ->
{BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES_CBC, HashSz, CipherState,
badpad_aes_fragment(Version), Version, true).
-aes_fragment({3,N}) when N == 0; N == 1->
+aes_fragment(?TLS_1_0) ->
<<197,9,6,109,242,87,80,154,85,250,110,81,119,95,65,185,53,206,216,153,246,169,
119,177,178,238,248,174,253,220,242,81,33,0,177,251,91,44,247,53,183,198,165,
63,20,194,159,107>>;
@@ -175,7 +166,7 @@ aes_fragment(_) ->
198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>.
-badpad_aes_fragment({3,N}) when N == 0; N == 1 ->
+badpad_aes_fragment(?TLS_1_0) ->
<<186,139,125,10,118,21,26,248,120,108,193,104,87,118,145,79,225,55,228,10,105,
30,190,37,1,88,139,243,210,99,65,41>>;
badpad_aes_fragment(_) ->
@@ -183,7 +174,7 @@ badpad_aes_fragment(_) ->
94,121,137,117,157,109,99,113,61,190,138,131,229,201,120,142,179,172,48,77,
234,19,240,33,38,91,93>>.
-content_nextiv_mac({3,N}) when N == 0; N == 1 ->
+content_nextiv_mac(?TLS_1_0) ->
{<<"HELLO\n">>,
<<72,196,247,97,62,213,222,109,210,204,217,186,172,184, 197,148>>,
<<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>};
@@ -192,7 +183,7 @@ content_nextiv_mac(_) ->
<<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>,
<<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>}.
-badpad_content_nextiv_mac({3,N}) when N == 0; N == 1 ->
+badpad_content_nextiv_mac(?TLS_1_0) ->
{<<"HELLO\n">>,
<<225,55,228,10,105,30,190,37,1,88,139,243,210,99,65,41>>,
<<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>
diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
index 0ddbf59e56..04425bbb1e 100644
--- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -530,57 +530,75 @@ end_per_testcase(_TestCase, Config) ->
init_certs(srp_rsa, Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
- [{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts],
- client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} |
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
+ [{tls_config, #{server_config =>
+ [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts],
+ client_config =>
+ [{srp_identity, {"Test-User", "secret"}}, {verify, verify_none} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
init_certs(srp_anon, Config) ->
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
- client_config => [{srp_identity, {"Test-User", "secret"}}]}} |
+ client_config => [{srp_identity, {"Test-User", "secret"}}, {verify, verify_none}]}} |
proplists:delete(tls_config, Config)];
init_certs(rsa_psk, Config) ->
ClientExt = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
- {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
- [[],[],[{extensions, ClientExt}]]}],
+ {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
+ [[],[],[{extensions, ClientExt}]]}],
Config, "_peer_keyEncipherment"),
PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>,
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ServerOpts],
client_config => [{psk_identity, "Test-User"},
- {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}} | ClientOpts]}} |
+ {user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}},
+ {verify, verify_none} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
init_certs(rsa, Config) ->
ClientExt = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]),
- {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
- [[],[],[{extensions, ClientExt}]]}],
+ {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
+ [[],[],[{extensions, ClientExt}]]}],
Config, "_peer_keyEncipherment"),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(ecdhe_1_3_rsa_cert, Config) ->
ClientExt = x509_test:extensions([{key_usage, [digitalSignature]}]),
- {ClientOpts, ServerOpts} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
- [[],[],[{extensions, ClientExt}]]}],
+ {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain,
+ [[],[],[{extensions, ClientExt}]]}],
Config, "_peer_rsa_digitalsign"),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(dhe_dss, Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
- CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf),
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
init_certs(srp_dss, Config) ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(dsa, dsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(dsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}} | ServerOpts],
client_config => [{srp_identity, {"Test-User", "secret"}} | ClientOpts]}} |
proplists:delete(tls_config, Config)];
@@ -588,9 +606,12 @@ init_certs(GroupName, Config) when GroupName == dhe_rsa;
GroupName == ecdhe_rsa ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
@@ -598,9 +619,12 @@ init_certs(GroupName, Config) when GroupName == dhe_ecdsa;
GroupName == ecdhe_ecdsa ->
DefConf = ssl_test_lib:default_cert_chain_conf(),
CertChainConf = ssl_test_lib:gen_conf(ecdsa, ecdsa, DefConf, DefConf),
- #{server_config := ServerOpts,
- client_config := ClientOpts}
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0}
= public_key:pkix_test_data(CertChainConf),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerOpts = ssl_test_lib:sig_algs(ecdsa, Version) ++ ServerOpts0,
+ ClientOpts = ssl_test_lib:sig_algs(ecdsa, Version) ++ ClientOpts0,
[{tls_config, #{server_config => ServerOpts,
client_config => ClientOpts}} |
proplists:delete(tls_config, Config)];
@@ -609,17 +633,17 @@ init_certs(GroupName, Config) when GroupName == psk;
GroupName == ecdhe_psk ->
PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>,
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}],
- client_config => [{psk_identity, "Test-User"},
+ client_config => [{verify, verify_none}, {psk_identity, "Test-User"},
{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, PskSharedSecret}}]}} |
- proplists:delete(tls_config, Config)];
-init_certs(srp, Config) ->
+ proplists:delete(tls_config, Config)];
+init_certs(srp, Config) ->
[{tls_config, #{server_config => [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}],
- client_config => [{srp_identity, {"Test-User", "secret"}}]}} |
+ client_config => [{verify, verify_none},{srp_identity, {"Test-User", "secret"}}]}} |
proplists:delete(tls_config, Config)];
-init_certs(_GroupName, Config) ->
+init_certs(_GroupName, Config) ->
%% Anonymous does not need certs
- [{tls_config, #{server_config => [],
- client_config => []}} |
+ [{tls_config, #{server_config => [{verify, verify_none}],
+ client_config => [{verify, verify_none}]}} |
proplists:delete(tls_config, Config)].
%%--------------------------------------------------------------------
@@ -978,14 +1002,14 @@ cipher_suite_test(ErlangCipherSuite, Version, Config) ->
test_ciphers(Kex, Cipher, Version) ->
- ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
- [{key_exchange,
- fun(Kex0) when Kex0 == Kex -> true;
- (_) -> false
- end},
- {cipher,
- fun(Cipher0) when Cipher0 == Cipher -> true;
- (_) -> false
+ ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version),
+ [{key_exchange,
+ fun(Kex0) when Kex0 == Kex -> true;
+ (_) -> false
+ end},
+ {cipher,
+ fun(Cipher0) when Cipher0 == Cipher -> true;
+ (_) -> false
end}]).
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index c6154855e5..0194ff4dce 100644
--- a/lib/ssl/test/ssl_dist_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2023. 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.
@@ -19,6 +19,7 @@
%%
-module(ssl_dist_SUITE).
+-feature(maybe_expr, enable).
-behaviour(ct_suite).
@@ -38,6 +39,12 @@
%% Test cases
-export([basic/0,
basic/1,
+ embedded/0,
+ embedded/1,
+ ktls_encrypt_decrypt/0,
+ ktls_encrypt_decrypt/1,
+ ktls_verify/0,
+ ktls_verify/1,
monitor_nodes/1,
payload/0,
payload/1,
@@ -107,6 +114,9 @@ start_ssl_node_name(Name, Args) ->
%%--------------------------------------------------------------------
all() ->
[basic,
+ embedded,
+ ktls_encrypt_decrypt,
+ ktls_verify,
monitor_nodes,
payload,
dist_port_overload,
@@ -143,31 +153,46 @@ init_per_suite(Config0) ->
end_per_suite(_Config) ->
application:stop(crypto).
-init_per_testcase(plain_verify_options = Case, Config) when is_list(Config) ->
- SslFlags = setup_tls_opts(Config),
- Flags = case os:getenv("ERL_FLAGS") of
- false ->
- os:putenv("ERL_FLAGS", SslFlags),
- "";
- OldFlags ->
- os:putenv("ERL_FLAGS", OldFlags ++ " " ++ SslFlags),
- OldFlags
- end,
- common_init(Case, [{old_flags, Flags} | Config]);
-init_per_testcase(Case, Config) when is_list(Config) ->
+init_per_testcase(Case, Config) ->
+ try init_per_tc(Case, Config)
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end.
+
+init_per_tc(embedded, Config) ->
+ LibDir = code:lib_dir(),
+ case
+ lists:all(
+ fun ({App,_,VSN}) ->
+ filelib:is_dir(
+ filename:join(
+ LibDir, atom_to_list(App)++"-"++VSN))
+ end, application_controller:which_applications())
+ of
+ false ->
+ {skip, "Must be run from a real Erlang installation"};
+ true ->
+ Config
+ end;
+init_per_tc(Case, Config)
+ when Case =:= ktls_verify, is_list(Config) ->
+ case ktls_encrypt_decrypt(false) of
+ ok ->
+ common_init(Case, Config);
+ Skip ->
+ Skip
+ end;
+%%
+init_per_tc(Case, Config) when is_list(Config) ->
common_init(Case, Config).
common_init(Case, Config) ->
ct:timetrap({seconds, ?DEFAULT_TIMETRAP_SECS}),
[{testcase, Case}|Config].
-end_per_testcase(Case, Config) when is_list(Config) ->
- Flags = proplists:get_value(old_flags, Config),
- catch os:putenv("ERL_FLAGS", Flags),
- common_end(Case, Config).
-
-common_end(_, _Config) ->
+end_per_testcase(_, _Config) ->
ok.
%%--------------------------------------------------------------------
@@ -179,6 +204,183 @@ basic() ->
basic(Config) when is_list(Config) ->
gen_dist_test(basic_test, Config).
+embedded() ->
+ [{doc,"Test that two nodes can connect via ssl distribution in embedded mode"}].
+embedded(Config) when is_list(Config) ->
+ ReleaseDir = filename:join(proplists:get_value(priv_dir,Config), "embedded"),
+ EbinDir = filename:join(ReleaseDir,"ebin/"),
+
+ %% Create an application for the test modules
+ Modules = [ssl_dist_test_lib, ?MODULE],
+ App = {application, tls_test,
+ [{description, "Erlang/OTP SSL test application"},
+ {vsn, "1.0"},
+ {modules, Modules},
+ {registered,[]},
+ {applications, [kernel, stdlib]}]},
+ ok = filelib:ensure_path(EbinDir),
+ [{ok,_} = file:copy(code:which(Mod), filename:join(EbinDir, atom_to_list(Mod)++".beam"))
+ || Mod <- Modules],
+ ok = file:write_file(filename:join(EbinDir,"tls_test.app"),
+ io_lib:format("~p.",[App])),
+
+ %% Create a release that we can boot from
+ Rel = {release, {"tls","1.0"}, {erts, get_app_vsn(erts)},
+ [{tls_test, "1.0"},
+ {kernel, get_app_vsn(kernel)},
+ {stdlib, get_app_vsn(stdlib)},
+ {public_key, get_app_vsn(public_key)},
+ {asn1, get_app_vsn(asn1)},
+ {sasl, get_app_vsn(sasl)},
+ {crypto, get_app_vsn(crypto)},
+ {ssl, get_app_vsn(ssl)}]},
+ TlsRel = filename:join(ReleaseDir, "tls"),
+ ok = file:write_file(TlsRel ++ ".rel", io_lib:format("~p.",[Rel])),
+ code:add_patha(EbinDir),
+ ok = systools:make_script(TlsRel),
+ ok = systools:script2boot(TlsRel),
+
+ %% Start two nodes in embedded mode and make sure they can connect
+ %% There used to be a bug here where crypto was not loaded early enough
+ %% for the distributed connection to work.
+ NodeConfig = [{app_opts, "-boot "++TlsRel++" -mode embedded -pa "++EbinDir++" "} | Config],
+ Node1 = peer:random_name(),
+ Node2 = peer:random_name(),
+
+ NH1 = start_ssl_node([{node_name,Node1}|NodeConfig]),
+ %% The second node does `sync_nodes_mandatory` with the first in order for
+ %% a connection to be established very early in the boot sequence
+ ok = file:write_file(
+ filename:join(ReleaseDir,"node2.config"),
+ io_lib:format(
+ "~p.",[[{kernel,
+ [{sync_nodes_timeout,infinity},
+ {sync_nodes_mandatory,
+ [list_to_atom(Node1++"@"++inet_db:gethostname())]}]}]])),
+ NH2 = start_ssl_node([{node_name,Node2}|NodeConfig],
+ " -config " ++ filename:join(ReleaseDir,"node2")),
+
+ try
+ basic_test(NH1, NH2, Config)
+ catch
+ _:Reason ->
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2),
+ ct:fail(Reason)
+ end,
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2),
+ success(Config).
+
+%%--------------------------------------------------------------------
+ktls_encrypt_decrypt() ->
+ [{doc,"Test that kTLS encryption offloading works"}].
+ktls_encrypt_decrypt(Config) when is_list(Config) ->
+ ktls_encrypt_decrypt(true);
+%%
+%% ktls_encrypt_decrypt(false) is used by init_per_tc(ktls_verify, _)
+%%
+ktls_encrypt_decrypt(Test) when is_boolean(Test) ->
+ %%
+ %% We need a connected socket
+ {ok, Listen} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} =
+ gen_tcp:connect({127,0,0,1}, Port, [{active, false}]),
+ {ok, Server} = gen_tcp:accept(Listen),
+ try
+ maybe
+ {ok, OS} ?= ssl_test_lib:ktls_os(),
+ ok ?= ssl_test_lib:ktls_set_ulp(Client, OS),
+ ok ?= ssl_test_lib:ktls_set_cipher(Client, OS, tx, 11),
+ case Test of
+ false ->
+ ok;
+ true ->
+ ktls_encrypt_decrypt(Client, Server)
+ end
+ else
+ {error, Reason} ->
+ {skip, Reason}
+ end
+ after
+ _ = gen_tcp:close(Server),
+ _ = gen_tcp:close(Client),
+ _ = gen_tcp:close(Listen)
+ end.
+
+ktls_encrypt_decrypt(Client, Server) ->
+ %%
+ %% Test to transfer encrypted data,
+ %% and also to not activate RX encryption and transfer data.
+ %%
+ Data = "The quick brown fox jumps over a lazy dog 0123456789",
+ %%
+ %% Send encrypted from Client before Server has activated decryption
+ ok = gen_tcp:send(Client, Data),
+ receive after 500 -> ok end, % Give time for data to arrive
+ %%
+ %% Activate Server TX encryption
+ {ok, OS} = ssl_test_lib:ktls_os(),
+ ok = ssl_test_lib:ktls_set_ulp(Server, OS),
+ ok = ssl_test_lib:ktls_set_cipher(Server, OS, tx, 17),
+ %% Send encrypted from Server
+ ok = gen_tcp:send(Server, Data),
+ %% Receive encrypted data without decryption
+ case gen_tcp:recv(Client, 0, 1000) of
+ {ok, Data} ->
+ ct:fail(recv_cleartext_data);
+ {ok, RandomData} when length(Data) < length(RandomData) ->
+ ct:log("Received ~p", [RandomData]),
+ %% A TLS block should be longer than Data
+ ok
+ end,
+ %% Finally, activate Server decryption
+ ok = ssl_test_lib:ktls_set_cipher(Server, OS, rx, 11),
+ %% Receive and decrypt the data that was first sent
+ {ok, Data} = gen_tcp:recv(Server, 0, 1000),
+ ok.
+
+%%--------------------------------------------------------------------
+ktls_verify() ->
+ [{doc,
+ "Test that two nodes can connect via ssl distribution over kTLS"}].
+ktls_verify(Config) ->
+ KTLSOpts = "-ssl_dist_opt "
+ "client_versions tlsv1.3 "
+ "server_versions tlsv1.3 "
+ "client_ciphers TLS_AES_256_GCM_SHA384 "
+ "server_ciphers TLS_AES_256_GCM_SHA384 "
+ "client_ktls true "
+ "server_ktls true ",
+ KTLSConfig = [{tls_verify_opts, KTLSOpts} | Config],
+ gen_dist_test(
+ fun (NH1, NH2) ->
+ basic_test(NH1, NH2, KTLSConfig),
+ 0 = ktls_count_tls_dist(NH1),
+ 0 = ktls_count_tls_dist(NH2),
+ ok
+ end, KTLSConfig).
+
+%% Verify that kTLS was activated (whitebox verification);
+%% check that a specific supervisor has no child supervisor
+%% which indicates that ssl_gen_statem:ktls_handover/1 has succeeded
+%%
+ktls_count_tls_dist(Node) ->
+ Key = supervisors,
+ case
+ lists:keyfind(
+ Key, 1,
+ apply_on_ssl_node(
+ Node, supervisor, count_children,
+ [tls_dist_connection_sup]))
+ of
+ {Key, N} ->
+ N;
+ false ->
+ 0
+ end.
+
%%--------------------------------------------------------------------
%% Test net_kernel:monitor_nodes with nodedown_reason (OTP-17838)
monitor_nodes(Config) when is_list(Config) ->
@@ -194,16 +396,20 @@ payload(Config) when is_list(Config) ->
dist_port_overload() ->
[{doc, "Test that TLS distribution connections can be accepted concurrently"}].
dist_port_overload(Config) when is_list(Config) ->
+ (RequiredConcurrency = 2) =< erlang:system_info(schedulers_online)
+ orelse
+ throw({skip, "Not enough schedulers online"}),
+ %%
%% Start a node, and get the port number it's listening on.
#node_handle{nodename = NodeName} = NH1 = start_ssl_node(Config),
[Name, Host] = string:lexemes(atom_to_list(NodeName), "@"),
{ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0),
{Name, Port} = lists:keyfind(Name, 1, NodesPorts),
- %% Run 4 connections concurrently. When TLS handshake is not concurrent,
- %% and with default net_setuptime of 7 seconds, only one connection per 7
- %% seconds is closed from server side. With concurrent accept, all 7 will
- %% be dropped in 7 seconds
- RequiredConcurrency = 4,
+ %% Run RequiredConcurrency connections concurrently.
+ %% When TLS handshake is not concurrent,
+ %% and with default net_setuptime of 7 seconds,
+ %% only one connection per 7 seconds is closed from server side.
+ %% With concurrent accept they will be closed in parallel.
Started = [connect(self(), Host, Port) || _ <- lists:seq(1, RequiredConcurrency)],
%% give 10 seconds (more than 7, less than 2x7 seconds)
Responded = barrier(RequiredConcurrency, [], erlang:system_time(millisecond) + 10000),
@@ -259,11 +465,15 @@ plain_options(Config) when is_list(Config) ->
plain_verify_options() ->
[{doc,"Test specifying tls options including certificate verification options"}].
plain_verify_options(Config) when is_list(Config) ->
- TLSOpts = "-ssl_dist_opt server_secure_renegotiate true "
- "client_secure_renegotiate true "
- "server_hibernate_after 500 client_hibernate_after 500"
- "server_reuse_sessions true client_reuse_sessions true "
- "server_depth 1 client_depth 1 ",
+ TLSOpts = "-ssl_dist_opt "
+ "server_secure_renegotiate true "
+ "client_secure_renegotiate true "
+ "server_hibernate_after 500 "
+ "client_hibernate_after 500 "
+ "server_reuse_sessions true "
+ "client_reuse_sessions true "
+ "server_depth 1 "
+ "client_depth 1 ",
gen_dist_test(plain_verify_options_test, [{tls_verify_opts, TLSOpts} | Config]).
%%--------------------------------------------------------------------
@@ -454,16 +664,20 @@ address_please(_, _, _) ->
gen_dist_test(Test, Config) ->
NH1 = start_ssl_node(Config),
NH2 = start_ssl_node(Config),
- try
- ?MODULE:Test(NH1, NH2, Config)
+ try
+ if
+ is_atom(Test) ->
+ ?MODULE:Test(NH1, NH2, Config);
+ is_function(Test, 2) ->
+ Test(NH1, NH2)
+ end
catch
- _:Reason ->
- stop_ssl_node(NH1),
- stop_ssl_node(NH2),
- ct:fail(Reason)
+ Class:Reason:Stacktrace ->
+ ct:fail({Class,Reason,Stacktrace})
+ after
+ stop_ssl_node(NH1),
+ stop_ssl_node(NH2)
end,
- stop_ssl_node(NH1),
- stop_ssl_node(NH2),
success(Config).
%% ssl_node side api
@@ -486,6 +700,7 @@ try_setting_priority(TestFun, Config) ->
{error,_} ->
{skip, "Can not set priority on socket"}
end.
+
basic_test(NH1, NH2, _) ->
Node1 = NH1#node_handle.nodename,
Node2 = NH2#node_handle.nodename,
@@ -493,6 +708,8 @@ basic_test(NH1, NH2, _) ->
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end),
+ verify_tls(NH1, NH2),
+
%% The test_server node has the same cookie as the ssl nodes
%% but it should not be able to communicate with the ssl nodes
%% via the erlang distribution.
@@ -582,6 +799,8 @@ payload_test(NH1, NH2, _) ->
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
+ verify_tls(NH1, NH2),
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end),
@@ -618,6 +837,8 @@ plain_options_test(NH1, NH2, _) ->
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
+ verify_tls(NH1, NH2),
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
@@ -627,6 +848,8 @@ plain_verify_options_test(NH1, NH2, _) ->
pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end),
+ verify_tls(NH1, NH2),
+
[Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end),
[Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end).
@@ -758,38 +981,52 @@ start_ssl_node(Config, XArgs) ->
App = proplists:get_value(app_opts, Config),
SSLOpts = setup_tls_opts(Config),
start_ssl_node_name(
- Name, App ++ " " ++ SSLOpts ++ XArgs).
+ Name, App ++ " " ++ SSLOpts ++ " " ++ XArgs).
mk_node_name(Config) ->
- N = erlang:unique_integer([positive]),
- Case = proplists:get_value(testcase, Config),
- Hostname =
- case proplists:get_value(hostname, Config) of
- undefined -> "";
- Host -> "@" ++ Host
- end,
- atom_to_list(?MODULE)
- ++ "_"
- ++ atom_to_list(Case)
- ++ "_"
- ++ integer_to_list(N) ++ Hostname.
-
+ case proplists:get_value(node_name, Config) of
+ undefined ->
+ N = erlang:unique_integer([positive]),
+ Case = proplists:get_value(testcase, Config),
+ Hostname =
+ case proplists:get_value(hostname, Config) of
+ undefined -> "";
+ Host -> "@" ++ Host
+ end,
+ atom_to_list(?MODULE)
+ ++ "_"
+ ++ atom_to_list(Case)
+ ++ "_"
+ ++ integer_to_list(N) ++ Hostname;
+ Name ->
+ Name
+ end.
setup_certs(Config) ->
+ {ok,Host} = inet:gethostname(),
+ Extensions =
+ {extensions,
+ [#'Extension'{
+ extnID = ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, Host}],
+ critical = false}]},
PrivDir = proplists:get_value(priv_dir, Config),
- DerConfig = public_key:pkix_test_data(#{server_chain => #{root => rsa_root_key(1),
- intermediates => [rsa_intermediate(2)],
- peer => rsa_peer_key(3)},
- client_chain => #{root => rsa_root_key(1),
- intermediates => [rsa_intermediate(5)],
- peer => rsa_peer_key(6)}}),
+ DerConfig =
+ public_key:pkix_test_data(
+ #{server_chain =>
+ #{root => [rsa_root_key(1), {digest,sha256}],
+ intermediates => [rsa_intermediate_conf(2)],
+ peer => [rsa_peer_key(3), {digest, sha256}, Extensions]},
+ client_chain =>
+ #{root => [rsa_root_key(1), {digest, sha256}],
+ intermediates => [rsa_intermediate_conf(5)],
+ peer => [rsa_peer_key(6), {digest, sha256}, Extensions]}}),
ClientBase = filename:join([PrivDir, "rsa"]),
- SeverBase = filename:join([PrivDir, "rsa"]),
-
- _ = x509_test:gen_pem_config_files(DerConfig, ClientBase, SeverBase).
-
-setup_tls_opts(Config) ->
+ ServerBase = filename:join([PrivDir, "rsa"]),
+ _ = x509_test:gen_pem_config_files(DerConfig, ClientBase, ServerBase).
+
+setup_tls_opts(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
SC = filename:join([PrivDir, "rsa_server_cert.pem"]),
SK = filename:join([PrivDir, "rsa_server_key.pem"]),
@@ -801,7 +1038,7 @@ setup_tls_opts(Config) ->
case proplists:get_value(tls_only_basic_opts, Config, []) of
[_|_] = BasicOpts -> %% No verify but server still need to have cert
"-proto_dist inet_tls " ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
- ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ BasicOpts;
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ " -ssl_dist_opt client_verify verify_none " ++ BasicOpts;
[] -> %% Verify
TlsVerifyOpts = proplists:get_value(tls_verify_opts, Config, []),
case TlsVerifyOpts of
@@ -819,7 +1056,7 @@ setup_tls_opts(Config) ->
++ TlsVerifyOpts;
_ -> %% No verify, no extra opts
"-proto_dist inet_tls " ++ "-ssl_dist_opt server_certfile " ++ SC ++ " "
- ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " "
+ ++ "-ssl_dist_opt server_keyfile " ++ SK ++ " " ++ "-ssl_dist_opt client_verify verify_none "
end
end.
@@ -842,9 +1079,10 @@ add_ssl_opts_config(Config) ->
KrnlDir = filename:join([LibDir, "kernel-" ++ KRNL_VSN]),
{ok, _} = file:read_file_info(StdlDir),
{ok, _} = file:read_file_info(KrnlDir),
- SSL_VSN = vsn(ssl),
- VSN_CRYPTO = vsn(crypto),
- VSN_PKEY = vsn(public_key),
+ SSL_VSN = get_app_vsn(ssl),
+ VSN_CRYPTO = get_app_vsn(crypto),
+ VSN_ASN1 = get_app_vsn(asn1),
+ VSN_PKEY = get_app_vsn(public_key),
SslDir = filename:join([LibDir, "ssl-" ++ SSL_VSN]),
{ok, _} = file:read_file_info(SslDir),
@@ -858,6 +1096,7 @@ add_ssl_opts_config(Config) ->
" [{kernel, \"~s\"},~n"
" {stdlib, \"~s\"},~n"
" {crypto, \"~s\"},~n"
+ " {asn1, \"~s\"},~n"
" {public_key, \"~s\"},~n"
" {ssl, \"~s\"}]}.~n",
[case catch erlang:system_info(otp_release) of
@@ -868,13 +1107,23 @@ add_ssl_opts_config(Config) ->
KRNL_VSN,
STDL_VSN,
VSN_CRYPTO,
+ VSN_ASN1,
VSN_PKEY,
SSL_VSN]),
ok = file:close(RelFile),
- ok = systools:make_script(Script, []),
+ ct:pal("Bootscript: ~p", [Script]),
+ case systools:make_script(Script, []) of
+ ok ->
+ ok;
+ NotOk ->
+ ct:pal("Bootscript problem: ~p", [NotOk]),
+ erlang:error(NotOk)
+ end,
[{app_opts, "-boot " ++ Script} | Config]
catch
- _:_ ->
+ Class : Reason : Stacktrace ->
+ ct:pal("Exception while generating bootscript:~n~p",
+ [{Class, Reason, Stacktrace}]),
[{app_opts, "-pa \"" ++ filename:dirname(code:which(ssl))++"\""}
| add_comment_config(
"Bootscript wasn't used since the test wasn't run on an "
@@ -896,19 +1145,12 @@ success(Config) ->
_ -> ok
end.
-vsn(App) ->
- application:start(App),
- try
- {value,
- {ssl,
- _,
- VSN}} = lists:keysearch(App,
- 1,
- application:which_applications()),
- VSN
- after
- application:stop(ssl)
- end.
+get_app_vsn(erts) ->
+ erlang:system_info(version);
+get_app_vsn(App) ->
+ application:load(App),
+ {ok, AppKeys} = application:get_all_key(App),
+ proplists:get_value(vsn, AppKeys).
verify_fail_always(_Certificate, _Event, _State) ->
%% Create an ETS table, to record the fact that the verify function ran.
@@ -940,6 +1182,18 @@ verify_pass_always(_Certificate, _Event, State) ->
receive go_ahead -> ok end,
{valid, State}.
+verify_tls(NH1, NH2) ->
+ %% Verify that distribution protocol between nodes is TLS
+ Node1 = NH1#node_handle.nodename,
+ Node2 = NH2#node_handle.nodename,
+ {ok,NodeInfo2} = apply_on_ssl_node(NH1, net_kernel, node_info, [Node2]),
+ {ok,NodeInfo1} = apply_on_ssl_node(NH2, net_kernel, node_info, [Node1]),
+ {address,#net_address{protocol = tls}} =
+ lists:keyfind(address, 1, NodeInfo1),
+ {address,#net_address{protocol = tls}} =
+ lists:keyfind(address, 1, NodeInfo2),
+ ok.
+
localhost_ip(InetVer) ->
{ok, Addr} = inet:getaddr(net_adm:localhost(), InetVer),
Addr.
@@ -963,14 +1217,14 @@ inet_ver() ->
rsa_root_key(N) ->
%% As rsa keygen is not guaranteed to be fast
- [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+ {key, ssl_test_lib:hardcode_rsa_key(N)}.
rsa_peer_key(N) ->
%% As rsa keygen is not guaranteed to be fast
- [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+ {key, ssl_test_lib:hardcode_rsa_key(N)}.
-rsa_intermediate(N) ->
- [{key, ssl_test_lib:hardcode_rsa_key(N)}].
+rsa_intermediate_conf(N) ->
+ [{key, ssl_test_lib:hardcode_rsa_key(N)}, {digest, sha256}].
maybe_quote_tuple_list(String) ->
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index b556701869..989007bec0 100644
--- a/lib/ssl/test/ssl_dist_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. 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.
@@ -18,6 +18,7 @@
%% %CopyrightEnd%
%%
-module(ssl_dist_bench_SUITE).
+-feature(maybe_expr, enable).
-behaviour(ct_suite).
@@ -33,8 +34,10 @@
%% Test cases
-export(
[setup/1,
+ parallel_setup/1,
roundtrip/1,
sched_utilization/1,
+ mean_load_cpu_margin/1,
throughput_0/1,
throughput_64/1,
throughput_1024/1,
@@ -45,27 +48,44 @@
throughput_1048576/1]).
%% Debug
--export([payload/1, roundtrip_runner/3, setup_runner/3, throughput_runner/4]).
+-export([payload/1, roundtrip_runner/3, setup_runner/3, throughput_runner/4,
+ mem/0]).
%%%-------------------------------------------------------------------
suite() -> [{ct_hooks, [{ts_install_cth, [{nodenames, 2}]}]}].
all() ->
- [{group, ssl},
- {group, crypto},
- {group, plain}].
+ [{group, smoketest}].
groups() ->
- [{benchmark, all()},
+ [{smoketest, protocols()},
+ {benchmark, protocols()},
+ %%
+ %% protocols()
+ {ssl, ssl_backends()},
+ {cryptcookie, cryptcookie_backends()},
+ {plain, categories()},
+ {socket, categories()},
+ %%
+ %% ssl_backends()
+ {tls, categories()},
+ {ktls, categories()},
%%
- {ssl, all_groups()},
- {crypto, all_groups()},
- {plain, all_groups()},
+ %% cryptcookie_backends()
+ {dist_cryptcookie_socket, categories()},
+ {cryptcookie_socket_ktls, categories()},
+ {dist_cryptcookie_inet, categories()},
+ {cryptcookie_inet_ktls, categories()},
%%
- {setup, [{repeat, 1}], [setup]},
+ %% categories()
+ {setup, [{repeat, 1}],
+ [setup,
+ parallel_setup]},
{roundtrip, [{repeat, 1}], [roundtrip]},
- {sched_utilization,[{repeat, 1}], [sched_utilization]},
+ {sched_utilization,[{repeat, 1}],
+ [sched_utilization,
+ mean_load_cpu_margin]},
{throughput, [{repeat, 1}],
[throughput_0,
throughput_64,
@@ -76,7 +96,23 @@ groups() ->
throughput_262144,
throughput_1048576]}].
-all_groups() ->
+protocols() ->
+ [{group, ssl},
+ {group, cryptcookie},
+ {group, plain},
+ {group, socket}].
+
+ssl_backends() ->
+ [{group, tls},
+ {group, ktls}].
+
+cryptcookie_backends() ->
+ [{group, dist_cryptcookie_socket},
+ {group, cryptcookie_socket_ktls},
+ {group, dist_cryptcookie_inet},
+ {group, cryptcookie_inet_ktls}].
+
+categories() ->
[{group, setup},
{group, roundtrip},
{group, throughput},
@@ -84,31 +120,37 @@ all_groups() ->
].
init_per_suite(Config) ->
- Digest = sha1,
+ Digest = sha256,
ECCurve = secp521r1,
- TLSVersion = 'tlsv1.2',
+%%% TLSVersion = 'tlsv1.2',
+%%% TLSCipher =
+%%% #{key_exchange => ecdhe_ecdsa,
+%%% cipher => aes_128_cbc,
+%%% mac => sha256,
+%%% prf => sha256},
+ TLSVersion = 'tlsv1.3',
TLSCipher =
#{key_exchange => ecdhe_ecdsa,
- cipher => aes_128_cbc,
- mac => sha256,
- prf => sha256},
+ cipher => aes_256_gcm,
+ mac => aead,
+ prf => sha384},
%%
Node = node(),
- Skipped = make_ref(),
+ Skip = make_ref(),
try
Node =/= nonode@nohost orelse
- throw({Skipped,"Node not distributed"}),
+ throw({Skip,"Node not distributed"}),
verify_node_src_addr(),
{supported, SSLVersions} =
lists:keyfind(supported, 1, ssl:versions()),
lists:member(TLSVersion, SSLVersions) orelse
throw(
- {Skipped,
+ {Skip,
"SSL does not support " ++ term_to_string(TLSVersion)}),
- lists:member(ECCurve, ssl:eccs(TLSVersion)) orelse
- throw(
- {Skipped,
- "SSL does not support " ++ term_to_string(ECCurve)}),
+%%% lists:member(ECCurve, ssl:eccs(TLSVersion)) orelse
+%%% throw(
+%%% {Skip,
+%%% "SSL does not support " ++ term_to_string(ECCurve)}),
TLSCipherKeys = maps:keys(TLSCipher),
lists:any(
fun (Cipher) ->
@@ -116,25 +158,11 @@ init_per_suite(Config) ->
end,
ssl:cipher_suites(default, TLSVersion)) orelse
throw(
- {Skipped,
+ {Skip,
"SSL does not support " ++ term_to_string(TLSCipher)}),
%%
%%
%%
- PrivDir = proplists:get_value(priv_dir, Config),
- [_, HostA] = split_node(Node),
- NodeAName = ?MODULE_STRING ++ "_node_a",
- NodeAString = NodeAName ++ "@" ++ HostA,
- NodeAConfFile = filename:join(PrivDir, NodeAString ++ ".conf"),
- NodeA = list_to_atom(NodeAString),
- %%
- ServerNode = ssl_bench_test_lib:setup(dist_server),
- [_, HostB] = split_node(ServerNode),
- NodeBName = ?MODULE_STRING ++ "_node_b",
- NodeBString = NodeBName ++ "@" ++ HostB,
- NodeBConfFile = filename:join(PrivDir, NodeBString ++ ".conf"),
- NodeB = list_to_atom(NodeBString),
- %%
CertOptions =
[{digest, Digest},
{key, {namedCurve, ECCurve}}],
@@ -152,56 +180,153 @@ init_per_suite(Config) ->
| SSLConf],
ClientConf = SSLConf,
%%
+ PrivDir = proplists:get_value(priv_dir, Config),
+ %%
+ ServerNode = ssl_bench_test_lib:setup(dist_server),
+ [_, ServerHost] = split_node(ServerNode),
+ ServerName = ?MODULE_STRING ++ "_server",
+ ServerString = ServerName ++ "@" ++ ServerHost,
+ ServerConfFile = filename:join(PrivDir, ServerString ++ ".conf"),
+ Server = list_to_atom(ServerString),
+ %%
write_node_conf(
- NodeAConfFile, NodeA, ServerConf, ClientConf,
- CertOptions, RootCert),
- write_node_conf(
- NodeBConfFile, NodeB, ServerConf, ClientConf,
+ ServerConfFile, Server, ServerConf, ClientConf,
CertOptions, RootCert),
%%
- [{node_a_name, NodeAName},
- {node_a, NodeA},
- {node_a_dist_args,
+ Schedulers =
+ erpc:call(ServerNode, erlang, system_info, [schedulers]),
+ [_, ClientHost] = split_node(Node),
+ [{server_node, ServerNode},
+ {server_name, ServerName},
+ {server, Server},
+ {server_dist_args,
"-proto_dist inet_tls "
- "-ssl_dist_optfile " ++ NodeAConfFile ++ " "},
- {node_b_name, NodeBName},
- {node_b, NodeB},
- {node_b_dist_args,
- "-proto_dist inet_tls "
- "-ssl_dist_optfile " ++ NodeBConfFile ++ " "},
- {server_node, ServerNode}
- |Config]
+ "-ssl_dist_optfile " ++ ServerConfFile ++ " "},
+ {clients, Schedulers} |
+ init_client_node(
+ ClientHost, Schedulers, PrivDir, ServerConf, ClientConf,
+ CertOptions, RootCert, Config)]
catch
- throw : {Skipped, Reason} ->
- {skipped, Reason};
+ throw : {Skip, Reason} ->
+ {skip, Reason};
Class : Reason : Stacktrace ->
- {failed, {Class, Reason, Stacktrace}}
+ {fail, {Class, Reason, Stacktrace}}
end.
+init_client_node(
+ _ClientHost, 0, _PrivDir, _ServerConf, _ClientConf,
+ _CertOptions, _RootCert, Config) ->
+ Config;
+init_client_node(
+ ClientHost, N, PrivDir, ServerConf, ClientConf,
+ CertOptions, RootCert, Config) ->
+ ClientName = ?MODULE_STRING ++ "_client_" ++ integer_to_list(N),
+ ClientString = ClientName ++ "@" ++ ClientHost,
+ ClientConfFile = filename:join(PrivDir, ClientString ++ ".conf"),
+ Client = list_to_atom(ClientString),
+ %%
+ write_node_conf(
+ ClientConfFile, Client, ServerConf, ClientConf,
+ CertOptions, RootCert),
+ init_client_node(
+ ClientHost, N - 1, PrivDir, ServerConf, ClientConf,
+ CertOptions, RootCert,
+ [{{client_name, N}, ClientName},
+ {{client, N}, Client},
+ {{client_dist_args, N},
+ "-proto_dist inet_tls "
+ "-ssl_dist_optfile " ++ ClientConfFile ++ " "} | Config]).
+
end_per_suite(Config) ->
ServerNode = proplists:get_value(server_node, Config),
ssl_bench_test_lib:cleanup(ServerNode).
+init_per_group(benchmark, Config) ->
+ [{effort,10}|Config];
+%%
init_per_group(ssl, Config) ->
[{ssl_dist, true}, {ssl_dist_prefix, "SSL"}|Config];
-init_per_group(crypto, Config) ->
- try inet_crypto_dist:supported() of
+init_per_group(dist_cryptcookie_socket, Config) ->
+ try inet_epmd_dist_cryptcookie_socket:supported() of
+ ok ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Socket"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd dist_cryptcookie_socket"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+init_per_group(cryptcookie_socket_ktls, Config) ->
+ try inet_epmd_cryptcookie_socket_ktls:supported() of
+ ok ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Socket-kTLS"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd cryptcookie_socket_ktls"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+init_per_group(dist_cryptcookie_inet, Config) ->
+ try inet_epmd_dist_cryptcookie_inet:supported() of
ok ->
- [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"},
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Inet"},
{ssl_dist_args,
- "-proto_dist inet_crypto"}
- |Config];
+ "-proto_dist inet_epmd -inet_epmd dist_cryptcookie_inet"}
+ | Config];
Problem ->
- {skipped,
- "Crypto does not support " ++ Problem}
+ {skip, Problem}
catch
Class : Reason : Stacktrace ->
- {failed, {Class, Reason, Stacktrace}}
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+init_per_group(cryptcookie_inet_ktls, Config) ->
+ try inet_epmd_cryptcookie_inet_ktls:supported() of
+ ok ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto-Inet-kTLS"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd cryptcookie_inet_ktls"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
end;
init_per_group(plain, Config) ->
[{ssl_dist, false}, {ssl_dist_prefix, "Plain"}|Config];
-init_per_group(benchmark, Config) ->
- [{effort,10}|Config];
+%%
+init_per_group(socket, Config) ->
+ try inet_epmd_socket:supported() of
+ ok ->
+ [{ssl_dist, false},
+ {ssl_dist_prefix, "Socket"},
+ {ssl_dist_args,
+ "-proto_dist inet_epmd -inet_epmd socket"}
+ | Config];
+ Problem ->
+ {skip, Problem}
+ catch
+ Class : Reason : Stacktrace ->
+ {fail, {Class, Reason, Stacktrace}}
+ end;
+%%
+init_per_group(ktls, Config) ->
+ case ktls_supported() of
+ ok ->
+ [{ktls, true},
+ {ssl_dist_prefix,
+ proplists:get_value(ssl_dist_prefix, Config) ++ "-kTLS"}
+ | proplists:delete(ssl_dist_prefix, Config)];
+ {error, Reason} ->
+ {skip, Reason}
+ end;
+%%
init_per_group(_GroupName, Config) ->
Config.
@@ -227,6 +352,24 @@ init_per_testcase(Func, Conf) ->
end_per_testcase(_Func, _Conf) ->
ok.
+
+ktls_supported() ->
+ {ok, Listen} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(Listen),
+ {ok, Client} =
+ gen_tcp:connect({127,0,0,1}, Port, [{active, false}]),
+ try
+ maybe
+ {ok, OS} ?= ssl_test_lib:ktls_os(),
+ ok ?= ssl_test_lib:ktls_set_ulp(Client, OS),
+ ssl_test_lib:ktls_set_cipher(Client, OS, tx, 1)
+ end
+ after
+ _ = gen_tcp:close(Client),
+ _ = gen_tcp:close(Listen)
+ end.
+
+
%%%-------------------------------------------------------------------
%%% CommonTest API helpers
@@ -297,18 +440,31 @@ setup(Config) ->
run_nodepair_test(fun setup/6, Config).
setup(A, B, Prefix, Effort, HA, HB) ->
- Rounds = 5 * Effort,
+ Rounds = 100 * Effort,
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ pong = ssl_apply(HA, net_adm, ping, [B]),
+ _ = ssl_apply(HA, fun () -> set_cpu_affinity(client) end),
+ {Log, Before, After} =
+ ssl_apply(HB, fun () -> set_cpu_affinity(server) end),
+ ct:pal("Server CPU affinity: ~w -> ~w~n~s", [Before, After, Log]),
+ MemStart = mem_start(HA, HB),
+ ChildCountResult =
+ ssl_dist_test_lib:apply_on_ssl_node(
+ HA, supervisor, count_children, [tls_dist_connection_sup]),
+ ct:log("TLS Connection Child Count Result: ~p", [ChildCountResult]),
{SetupTime, CycleTime} =
ssl_apply(HA, fun () -> setup_runner(A, B, Rounds) end),
ok = ssl_apply(HB, fun () -> setup_wait_nodedown(A, 10000) end),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
%% [] = ssl_apply(HA, erlang, nodes, []),
%% [] = ssl_apply(HB, erlang, nodes, []),
SetupSpeed = round((Rounds*1000000*1000) / SetupTime),
CycleSpeed = round((Rounds*1000000*1000) / CycleTime),
+ _ = report(Prefix++" Setup Mem A", MemA, "KByte"),
+ _ = report(Prefix++" Setup Mem B", MemB, "KByte"),
_ = report(Prefix++" Setup", SetupSpeed, "setups/1000s"),
- report(Prefix++" Setup Cycle", CycleSpeed, "cycles/1000s").
+ report(Prefix++" Setup Cycle", CycleSpeed, "cycles/1000s " ++ MemSuffix).
%% Runs on node A against rex in node B
setup_runner(A, B, Rounds) ->
@@ -320,7 +476,14 @@ setup_loop(_A, _B, T, 0) ->
T;
setup_loop(A, B, T, N) ->
StartTime = start_time(),
- [N,A] = [N|rpc:block_call(B, erlang, nodes, [])],
+ try erpc:call(B, net_adm, ping, [A]) of
+ pong -> ok;
+ Other ->
+ error({N,Other})
+ catch
+ Class : Reason : Stacktrace ->
+ erlang:raise(Class, {N,Reason}, Stacktrace)
+ end,
Time = elapsed_time(StartTime),
[N,B] = [N|erlang:nodes()],
Mref = erlang:monitor(process, {rex,B}),
@@ -348,6 +511,145 @@ setup_wait_nodedown(A, Time) ->
end.
+set_cpu_affinity(client) ->
+ set_cpu_affinity(1);
+set_cpu_affinity(server) ->
+ set_cpu_affinity(2);
+set_cpu_affinity(Index) when is_integer(Index) ->
+ case erlang:system_info(cpu_topology) of
+ undefined ->
+ {"", undefined, undefined};
+ CpuTopology ->
+ Log = taskset(element(Index, split_cpus(CpuTopology))),
+ %% Update Schedulers
+ _ = erlang:system_info(update_cpu_info),
+ Schedulers = erlang:system_info(logical_processors_available),
+ {Log,
+ erlang:system_flag(schedulers_online, Schedulers),
+ Schedulers}
+ end.
+
+taskset(LogicalProcessors) ->
+ os:cmd(
+ "taskset -c -p " ++
+ lists:flatten(
+ lists:join(
+ ",",
+ [integer_to_list(Id) || Id <- LogicalProcessors]),
+ " ") ++ os:getpid()).
+
+split_cpus([{_Tag, List}]) ->
+ split_cpus(List);
+split_cpus(List = [_ | _]) ->
+ {A, B} = lists:split(length(List) bsr 1, List),
+ {logical_processors(A), logical_processors(B)}.
+
+logical_processors([{_Tag, {logical, Id}} | Items]) ->
+ [Id | logical_processors(Items)];
+logical_processors([{_Tag, List} | Items]) ->
+ logical_processors(List) ++ logical_processors(Items);
+logical_processors([]) ->
+ [].
+
+
+%%----------------
+%% Parallel setup
+
+parallel_setup(Config) ->
+ Clients = proplists:get_value(clients, Config),
+ parallel_setup(Config, Clients, Clients, []).
+
+parallel_setup(Config, Clients, I, HNs) when 0 < I ->
+ Key = {client, I},
+ Node = proplists:get_value(Key, Config),
+ Handle = start_ssl_node(Key, Config),
+ _ = ssl_apply(Handle, fun () -> set_cpu_affinity(client) end),
+ try
+ parallel_setup(Config, Clients, I - 1, [{Handle, Node} | HNs])
+ after
+ stop_ssl_node(Key, Handle, Config)
+ end;
+parallel_setup(Config, Clients, _0, HNs) ->
+ Key = server,
+ ServerNode = proplists:get_value(Key, Config),
+ ServerHandle = start_ssl_node(Key, Config, 0),
+ Effort = proplists:get_value(effort, Config, 1),
+ TotalRounds = 1000 * Effort,
+ Rounds = round(TotalRounds / Clients),
+ try
+ {Log, Before, After} =
+ ssl_apply(ServerHandle, fun () -> set_cpu_affinity(server) end),
+ ct:pal("Server CPU affinity: ~w -> ~w~n~s", [Before, After, Log]),
+ ServerMemBefore =
+ ssl_apply(ServerHandle, fun mem/0),
+ parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore,
+ [parallel_setup_runner(Handle, Node, ServerNode, Rounds)
+ || {Handle, Node} <- HNs])
+ after
+ stop_ssl_node(Key, ServerHandle, Config)
+ end.
+
+parallel_setup_runner(Handle, Node, ServerNode, Rounds) ->
+ Collector = self(),
+ Tag = make_ref(),
+ _ =
+ spawn_link(
+ fun () ->
+ Collector !
+ {Tag,
+ try
+ MemBefore =
+ ssl_apply(Handle, fun mem/0),
+ Result =
+ ssl_apply(
+ Handle, ?MODULE, setup_runner,
+ [Node, ServerNode, Rounds]),
+ MemAfter =
+ ssl_apply(Handle, fun mem/0),
+ {MemBefore, Result, MemAfter}
+ catch Class : Reason : Stacktrace ->
+ {Class, Reason, Stacktrace}
+ end}
+ end),
+ Tag.
+
+parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, Tags) ->
+ parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, Tags,
+ 0, 0, 0).
+%%
+parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, [Tag | Tags],
+ SetupTime, CycleTime, Mem) ->
+ receive
+ {Tag, {Mem1, {ST, CT}, Mem2}}
+ when is_integer(ST), is_integer(CT) ->
+ parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, Tags,
+ SetupTime + ST, CycleTime + CT, Mem + Mem2 - Mem1);
+ {Tag, Error} ->
+ exit(Error)
+ end;
+parallel_setup_result(
+ Config, TotalRounds, ServerHandle, ServerMemBefore, [],
+ SetupTime, CycleTime, Mem) ->
+ ServerMemAfter =
+ ssl_apply(ServerHandle, fun mem/0),
+ ServerMem = ServerMemAfter - ServerMemBefore,
+ Clients = proplists:get_value(clients, Config),
+ Prefix = proplists:get_value(ssl_dist_prefix, Config),
+ SetupSpeed = 1000 * round(TotalRounds / (SetupTime/1000000)),
+ CycleSpeed = 1000 * round(TotalRounds / (CycleTime/1000000)),
+ {MemC, MemS, MemSuffix} = mem_result({Mem / Clients, ServerMem}),
+ _ = report(Prefix++" Parallel Setup Mem Clients", MemC, "KByte"),
+ _ = report(Prefix++" Parallel Setup Mem Server", MemS, "KByte"),
+ _ = report(Prefix++" Parallel Setup", SetupSpeed, "setups/1000s"),
+ report(
+ Prefix++" Parallel Setup Cycle", CycleSpeed, "cycles/1000s "
+ ++ MemSuffix).
+
%%----------------
%% Roundtrip speed
@@ -358,28 +660,33 @@ roundtrip(A, B, Prefix, Effort, HA, HB) ->
Rounds = 4000 * Effort,
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
ok = ssl_apply(HA, net_kernel, allow, [[B]]),
ok = ssl_apply(HB, net_kernel, allow, [[A]]),
Time = ssl_apply(HA, fun () -> roundtrip_runner(A, B, Rounds) end),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
Speed = round((Rounds*1000000) / Time),
- report(Prefix++" Roundtrip", Speed, "pings/s").
+ _ = report(Prefix++" Roundtrip Mem A", MemA, "KByte"),
+ _ = report(Prefix++" Roundtrip Mem B", MemB, "KByte"),
+ report(Prefix++" Roundtrip", Speed, "pings/s " ++ MemSuffix).
%% Runs on node A and spawns a server on node B
roundtrip_runner(A, B, Rounds) ->
ClientPid = self(),
- [A] = rpc:call(B, erlang, nodes, []),
+ [A] = erpc:call(B, erlang, nodes, []),
ServerPid =
erlang:spawn(
B,
- fun () -> roundtrip_server(ClientPid, Rounds) end),
+ fun () ->
+ roundtrip_server(ClientPid, Rounds)
+ end),
ServerMon = erlang:monitor(process, ServerPid),
- microseconds(
- roundtrip_client(ServerPid, ServerMon, start_time(), Rounds)).
+ roundtrip_client(ServerPid, ServerMon, start_time(), Rounds).
roundtrip_server(_Pid, 0) ->
- ok;
+ exit(ok);
roundtrip_server(Pid, N) ->
receive
N ->
@@ -390,7 +697,7 @@ roundtrip_server(Pid, N) ->
roundtrip_client(_Pid, Mon, StartTime, 0) ->
Time = elapsed_time(StartTime),
receive
- {'DOWN', Mon, _, _, normal} ->
+ {'DOWN', Mon, _, _, ok} ->
Time;
{'DOWN', Mon, _, _, Other} ->
exit(Other)
@@ -416,6 +723,7 @@ sched_utilization(A, B, Prefix, Effort, HA, HB, Config) ->
SSL = proplists:get_value(ssl_dist, Config),
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
PidA = ssl_apply(HA, os, getpid, []),
PidB = ssl_apply(HB, os, getpid, []),
ct:pal("Starting scheduler utilization run effort ~w:~n"
@@ -435,6 +743,7 @@ sched_utilization(A, B, Prefix, Effort, HA, HB, Config) ->
ct:log("Got ~p busy_dist_port msgs",[tail(BusyDistPortMsgs)]),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
ct:log("Microstate accounting for node ~w:", [A]),
msacc:print(ClientMsacc),
ct:log("Microstate accounting for node ~w:", [B]),
@@ -459,26 +768,30 @@ sched_utilization(A, B, Prefix, Effort, HA, HB, Config) ->
ct:log("Stray Msgs: ~p", [BusyDistPortMsgs]),
" ???"
end,
+ _ = report(Prefix++" Sched Utilization Client Mem", MemA, "KByte"),
+ _ = report(Prefix++" Sched Utilization Server Mem", MemB, "KByte"),
{comment, ClientComment} =
report(Prefix ++ " Sched Utilization Client" ++ Verdict,
- SchedUtilClient, "/100 %" ++ Verdict),
+ SchedUtilClient, " %" ++ Verdict),
{comment, ServerComment} =
report(Prefix++" Sched Utilization Server" ++ Verdict,
- SchedUtilServer, "/100 %" ++ Verdict),
- {comment, "Client " ++ ClientComment ++ ", Server " ++ ServerComment}.
+ SchedUtilServer, " %" ++ Verdict),
+ {comment,
+ "Client " ++ ClientComment ++ ", Server " ++ ServerComment ++
+ " " ++ MemSuffix}.
%% Runs on node A and spawns a server on node B
%% We want to avoid getting busy_dist_port as it hides the true SU usage
%% of the receiver and sender.
sched_util_runner(A, B, Effort, true, Config) ->
- sched_util_runner(A, B, Effort, 250, Config);
+ sched_util_runner(A, B, Effort, 100, Config);
sched_util_runner(A, B, Effort, false, Config) ->
- sched_util_runner(A, B, Effort, 250, Config);
+ sched_util_runner(A, B, Effort, 100, Config);
sched_util_runner(A, B, Effort, Senders, Config) ->
process_flag(trap_exit, true),
- Payload = payload(5),
+ Payload = payload(100),
Time = 1000 * Effort,
- [A] = rpc:call(B, erlang, nodes, []),
+ [A] = erpc:call(B, erlang, nodes, []),
ServerPids =
[erlang:spawn_link(
B, fun () -> throughput_server() end)
@@ -514,8 +827,9 @@ sched_util_runner(A, B, Effort, Senders, Config) ->
end
end),
erlang:system_monitor(self(),[busy_dist_port]),
- %% We spawn 250 senders which should mean that we
- %% have a load of 25 msgs/msec
+ %% We spawn 100 senders that send a message every 10 ms
+ %% which should produce a load of 10000 msgs/s with
+ %% payload 100 bytes each -> 1 MByte/s
_Clients =
[spawn_link(
fun() ->
@@ -594,6 +908,146 @@ throughput_client(Pid, Payload) ->
receive after 10 -> throughput_client(Pid, Payload) end.
%%-----------------
+%% Mean load CPU margin
+%%
+%% Start pairs of processes with the client on node A
+%% and the server on node B. The clients sends requests
+%% with random interval and payload and the servers reply
+%% immediately.
+%%
+%% Also, besides each server there is a compute process
+%% that does CPU work with low process priority and we measure
+%% how much such work that gets done.
+
+mean_load_cpu_margin(Config) ->
+ run_nodepair_test(fun run_mlcm/6, Config).
+
+-define(MLCM_NO, 100).
+
+run_mlcm(A, B, Prefix, Effort, HA, HB) ->
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
+ pong = ssl_apply(HB, net_adm, ping, [A]),
+ Count = ssl_apply(HA, fun () -> mlcm(B, Effort) end),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
+ _ = report(Prefix++" CPU Margin Mem A", MemA, "KByte"),
+ _ = report(Prefix++" CPU Margin Mem B", MemB, "KByte"),
+ report(
+ Prefix++" CPU Margin",
+ round(Count/?MLCM_NO/Effort),
+ "stones " ++ MemSuffix).
+
+mlcm(Node, Effort) ->
+ Payloads = mlcm_payloads(),
+ Clients =
+ [mlcm_client_start(Node, Payloads) || _ <- lists:seq(1, ?MLCM_NO)],
+ receive after 1000 * Effort -> ok end,
+ [Alias ! {Alias,stop} || {_Monitor, Alias} <- Clients],
+ Counts =
+ [receive
+ {'DOWN',Monitor,_,_,{Alias, Count}} ->
+ Count;
+ {'DOWN',Monitor,_,_,Reason} ->
+ exit(Reason)
+ end || {Monitor, Alias} <- Clients],
+ lists:sum(Counts).
+
+mlcm_payloads() ->
+ Bin = list_to_binary([rand:uniform(256) - 1 || _ <- lists:seq(1, 512)]),
+ lists:foldl(
+ fun (N, Payloads) ->
+ Payloads#{N => binary:copy(Bin, N)}
+ end, #{}, lists:seq(0, 255)).
+
+%%-------
+
+mlcm_client_start(Node, Payloads) ->
+ Parent = self(),
+ StartRef = make_ref(),
+ {_,Monitor} =
+ spawn_monitor(
+ fun () ->
+ Alias = alias(),
+ Parent ! {StartRef, Alias},
+ Server = mlcm_server_start(Node, Alias),
+ mlcm_client(Alias, Server, Payloads, 0)
+ end),
+ receive
+ {StartRef, Alias} ->
+ {Monitor, Alias};
+ {'DOWN',Monitor,_,_,Reason} ->
+ exit(Reason)
+ end.
+
+mlcm_client(Alias, Server, Payloads, Seq) ->
+ {Time, Index} = mlcm_rand(),
+ Payload = maps:get(Index, Payloads),
+ receive after Time -> ok end,
+ Server ! {Alias, Seq, Payload},
+ receive
+ {Alias, Seq, Pl} when byte_size(Pl) =:= byte_size(Payload) ->
+ mlcm_client(Alias, Server, Payloads, Seq + 1);
+ {Alias, stop} = Msg ->
+ Server ! Msg,
+ receive after infinity -> ok end
+ end.
+
+%% Approximate normal distribution Index with an average of 6 uniform bytes
+%% and use the 7:th byte for uniform Time
+mlcm_rand() ->
+ mlcm_rand(6, rand:uniform(1 bsl (1+6)*8) - 1, 0).
+%%
+mlcm_rand(0, X, I) ->
+ Time = X + 1, % 1..256
+ Index = abs((I - 3*256) div 3), % 0..255 upper half or normal distribution
+ {Time, Index};
+mlcm_rand(N, X, I) ->
+ mlcm_rand(N - 1, X bsr 8, I + (X band 255)).
+
+%%-------
+
+mlcm_server_start(Node, Alias) ->
+ spawn_link(
+ Node,
+ fun () ->
+ Compute = mlcm_compute_start(Alias),
+ mlcm_server(Alias, 0, Compute)
+ end).
+
+mlcm_server(Alias, Seq, Compute) ->
+ receive
+ {Alias, Seq, _Payload} = Msg ->
+ Alias ! Msg,
+ mlcm_server(Alias, Seq + 1, Compute);
+ {Alias, stop} = Msg ->
+ Compute ! Msg,
+ receive after infinity -> om end
+ end.
+
+%%-------
+
+mlcm_compute_start(Alias) ->
+ spawn_opt(
+ fun () ->
+ rand:seed(exro928ss),
+ mlcm_compute(Alias, 0, 0)
+ end,
+ [link, {priority,low}]).
+
+mlcm_compute(Alias, State, Count) ->
+ receive {Alias, stop} -> exit({Alias, Count})
+ after 0 -> ok
+ end,
+ mlcm_compute(
+ Alias,
+ %% CPU payload
+ (State +
+ lists:sum([rand:uniform(1 bsl 48) || _ <- lists:seq(1, 999)]))
+ div 1000,
+ Count + 1).
+
+%%-----------------
%% Throughput speed
throughput_0(Config) ->
@@ -647,8 +1101,8 @@ throughput_1048576(Config) ->
throughput(A, B, Prefix, HA, HB, Packets, Size) ->
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
+ MemStart = mem_start(HA, HB),
#{time := Time,
- client_dist_stats := ClientDistStats,
client_msacc_stats := ClientMsaccStats,
client_prof := ClientProf,
server_msacc_stats := ServerMsaccStats,
@@ -658,15 +1112,19 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) ->
ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
+ {MemA, MemB, MemSuffix} = mem_stop(HA, HB, MemStart),
ClientMsaccStats =:= undefined orelse
msacc:print(ClientMsaccStats),
- io:format("ClientDistStats: ~p~n", [ClientDistStats]),
Overhead =
50 % Distribution protocol headers (empirical) (TLS+=54)
+ byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead
Bytes = Packets * (Size + Overhead),
io:format("~w bytes, ~.4g s~n", [Bytes,Time/1000000]),
SizeString = integer_to_list(Size),
+ _ = report(
+ Prefix++" Throughput_" ++ SizeString ++ " Mem A", MemA, "KByte"),
+ _ = report(
+ Prefix++" Throughput_" ++ SizeString ++ " Mem B", MemB, "KByte"),
ClientMsaccStats =:= undefined orelse
report(
Prefix ++ " Sender_RelativeCoreLoad_" ++ SizeString,
@@ -687,12 +1145,13 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) ->
io:format("******* Server GC Before:~n~p~n", [Server_GC_Before]),
io:format("******* Server GC After:~n~p~n", [Server_GC_After]),
Speed = round((Bytes * 1000000) / (1024 * Time)),
- report(Prefix ++ " Throughput_" ++ SizeString, Speed, "kB/s").
+ report(
+ Prefix ++ " Throughput_" ++ SizeString, Speed, "kB/s " ++ MemSuffix).
%% Runs on node A and spawns a server on node B
throughput_runner(A, B, Rounds, Size) ->
Payload = payload(Size),
- [A] = rpc:call(B, erlang, nodes, []),
+ [A] = erpc:call(B, erlang, nodes, []),
ClientPid = self(),
ServerPid =
erlang:spawn_opt(
@@ -721,74 +1180,10 @@ throughput_runner(A, B, Rounds, Size) ->
undefined
end,
Prof = prof_end(),
- [{_Node,Socket}] = dig_dist_node_sockets(),
- DistStats = inet:getstat(Socket),
Result#{time := microseconds(Time),
- client_dist_stats => DistStats,
client_msacc_stats => MsaccStats,
client_prof => Prof}.
-dig_dist_node_sockets() ->
- DistCtrl2Node =
- maps:from_list(
- [{DistCtrl, Node}
- || {Node, DistCtrl}
- <- erlang:system_info(dist_ctrl), is_pid(DistCtrl)]),
- TlsDistConnSup = whereis(tls_dist_connection_sup),
- InetCryptoDist = whereis(inet_crypto_dist),
- [NodeSocket
- || {_, Socket} = NodeSocket
- <- erlang:system_info(dist_ctrl), is_port(Socket)]
- ++
- if
- TlsDistConnSup =/= undefined ->
- [case ConnSpec of
- {undefined, ConnSup, supervisor, _} ->
- [{receiver, ReceiverPid, worker, _},
- {sender, SenderPid, worker, _}] =
- lists:sort(supervisor:which_children(ConnSup)),
- {links,ReceiverLinks} =
- process_info(ReceiverPid, links),
- [Socket] = [S || S <- ReceiverLinks, is_port(S)],
- {maps:get(SenderPid, DistCtrl2Node), Socket}
- end
- || ConnSpec <- supervisor:which_children(TlsDistConnSup)];
- InetCryptoDist =/= undefined ->
- [begin
- {monitors,[{process,InputHandler}]} =
- erlang:process_info(DistCtrl, monitors),
- {links,InputHandlerLinks} =
- erlang:process_info(InputHandler, links),
- [Socket] =
- [S || S <- InputHandlerLinks, is_port(S)],
- {Node, Socket}
- end
- || {DistCtrl, Node} <- maps:to_list(DistCtrl2Node)];
- true ->
- []
- end.
-
--ifdef(undefined).
-dig_dist_node_sockets() ->
- [case DistCtrl of
- {_Node,Socket} = NodeSocket when is_port(Socket) ->
- NodeSocket;
- {Node,DistCtrlPid} when is_pid(DistCtrlPid) ->
- [{links,DistCtrlLinks}] = process_info(DistCtrlPid, [links]),
- case [S || S <- DistCtrlLinks, is_port(S)] of
- [Socket] ->
- {Node,Socket};
- [] ->
- [{monitors,[{process,DistSenderPid}]}] =
- process_info(DistCtrlPid, [monitors]),
- [{links,DistSenderLinks}] =
- process_info(DistSenderPid, [links]),
- [Socket] = [S || S <- DistSenderLinks, is_port(S)],
- {Node,Socket}
- end
- end || DistCtrl <- erlang:system_info(dist_ctrl)].
--endif.
-
throughput_server(Pid, N) ->
GC_Before = get_server_gc_info(),
%% dbg:tracer(port, dbg:trace_port(file, "throughput_server_gc.log")),
@@ -917,17 +1312,19 @@ prof_print([]) ->
%%% Test cases helpers
run_nodepair_test(TestFun, Config) ->
- A = proplists:get_value(node_a, Config),
- B = proplists:get_value(node_b, Config),
+ A = proplists:get_value({client,1}, Config),
+ B = proplists:get_value(server, Config),
Prefix = proplists:get_value(ssl_dist_prefix, Config),
Effort = proplists:get_value(effort, Config, 1),
- HA = start_ssl_node_a(Config),
- HB = start_ssl_node_b(Config),
- try TestFun(A, B, Prefix, Effort, HA, HB)
+ HA = start_ssl_node({client,1}, Config),
+ try
+ HB = start_ssl_node(server, Config),
+ try TestFun(A, B, Prefix, Effort, HA, HB)
+ after
+ stop_ssl_node(server, HB, Config)
+ end
after
- stop_ssl_node_a(HA),
- stop_ssl_node_b(HB, Config),
- ok
+ stop_ssl_node({client,1}, HA, Config)
end.
ssl_apply(Handle, M, F, Args) ->
@@ -946,33 +1343,39 @@ ssl_apply(Handle, Fun) ->
Result
end.
-start_ssl_node_a(Config) ->
- Name = proplists:get_value(node_a_name, Config),
- Args = get_node_args(node_a_dist_args, Config),
+start_ssl_node(Spec, Config) ->
+ start_ssl_node(Spec, Config, 0).
+%%
+start_ssl_node({client, N}, Config, Verbose) ->
+ Name = proplists:get_value({client_name, N}, Config),
+ Args = get_node_args({client_dist_args, N}, Config),
Pa = filename:dirname(code:which(?MODULE)),
ssl_dist_test_lib:start_ssl_node(
- Name, "-pa " ++ Pa ++ " " ++ Args).
-
-start_ssl_node_b(Config) ->
- Name = proplists:get_value(node_b_name, Config),
- Args = get_node_args(node_b_dist_args, Config),
+ Name, "-pa " ++ Pa ++ " +Muacul 0 " ++ Args, Verbose);
+start_ssl_node(server, Config, Verbose) ->
+ Name = proplists:get_value(server_name, Config),
+ Args = get_node_args(server_dist_args, Config),
Pa = filename:dirname(code:which(?MODULE)),
ServerNode = proplists:get_value(server_node, Config),
- rpc:call(
+ erpc:call(
ServerNode, ssl_dist_test_lib, start_ssl_node,
- [Name, "-pa " ++ Pa ++ " " ++ Args]).
+ [Name, "-pa " ++ Pa ++ " +Muacul 0 " ++ Args, Verbose]).
-stop_ssl_node_a(HA) ->
- ssl_dist_test_lib:stop_ssl_node(HA).
-
-stop_ssl_node_b(HB, Config) ->
+stop_ssl_node({client, _}, HA, _Config) ->
+ ssl_dist_test_lib:stop_ssl_node(HA);
+stop_ssl_node(server, HB, Config) ->
ServerNode = proplists:get_value(server_node, Config),
- rpc:call(ServerNode, ssl_dist_test_lib, stop_ssl_node, [HB]).
+ erpc:call(ServerNode, ssl_dist_test_lib, stop_ssl_node, [HB]).
get_node_args(Tag, Config) ->
case proplists:get_value(ssl_dist, Config) of
true ->
- proplists:get_value(Tag, Config);
+ case proplists:get_value(ktls, Config, false) of
+ true ->
+ "-ssl_dist_opt client_ktls true server_ktls true ";
+ false ->
+ ""
+ end ++ proplists:get_value(Tag, Config);
false ->
proplists:get_value(ssl_dist_args, Config, "")
end.
@@ -1018,13 +1421,13 @@ elapsed_time(StartTime) ->
microseconds(Time) ->
erlang:convert_time_unit(Time, native, microsecond).
-report(Name, Value, Unit) ->
- ct:pal("~s: ~w ~s", [Name, Value, Unit]),
+report(Name, Value, Suffix) ->
+ ct:pal("~s: ~w ~s", [Name, Value, Suffix]),
ct_event:notify(
#event{
name = benchmark_data,
data = [{value, Value}, {suite, "ssl_dist"}, {name, Name}]}),
- {comment, term_to_string(Value) ++ " " ++ Unit}.
+ {comment, term_to_string(Value) ++ " " ++ Suffix}.
term_to_string(Term) ->
unicode:characters_to_list(
@@ -1032,3 +1435,114 @@ term_to_string(Term) ->
msacc_available() ->
msacc:available().
+
+
+mem_start(HA, HB) ->
+ MemA = ssl_apply(HA, fun mem/0),
+ MemB = ssl_apply(HB, fun mem/0),
+ {MemA, MemB}.
+
+mem_stop(HA, HB, Mem1) ->
+ MemA2 = ssl_apply(HA, fun mem/0),
+ MemB2 = ssl_apply(HB, fun mem/0),
+ mem_result(mem_diff(Mem1, {MemA2, MemB2})).
+
+mem_diff({MemA1, MemB1}, {MemA2, MemB2}) ->
+ {MemA2 - MemA1, MemB2 - MemB1}.
+
+mem_result({MemDiffA, MemDiffB}) ->
+ MemSuffix =
+ io_lib:format(
+ "~.5g|~.5g MByte", [MemDiffA / (1 bsl 20), MemDiffB / (1 bsl 20)]),
+ {round(MemDiffA / (1 bsl 10)), round(MemDiffB / (1 bsl 10)), MemSuffix}.
+
+memory(Type) ->
+ try erlang:memory(Type)
+ catch error : notsup ->
+ 0
+ end.
+
+-ifdef(undefined).
+
+mem() ->
+ lists:foldl(
+ fun ({Type, F}, Acc) ->
+ F*memory(Type) + Acc
+ end, 0,
+ [{total, 1}, {processes, -1}, {atom, -1}, {code, -1},
+ {processes_used, 1}, {atom_used, 1}]).
+
+-else.
+
+mem() ->
+ {_Current, _MaxSince, MaxEver} =
+ traverse(
+ fun mem/3,
+ [erlang:system_info({allocator_sizes, Alloc})
+ || Alloc <- erlang:system_info(alloc_util_allocators)],
+ {0, 0, 0}),
+ MaxEver - memory(code). % Kind of assuming code stays allocated
+
+%% allocator_sizes traversal fun
+mem(
+ T = {instance, _, L}, [], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ T = {_, L}, [{instance, _, _}], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ T = {blocks, L}, [{_, _}, {instance, _, _}], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ T = {_, L}, [{blocks, _}, {_, _}, {instance, _, _}], Acc)
+ when is_list(L) ->
+ {tuple_size(T), Acc};
+mem(
+ {size, Current, MaxSince, MaxEver},
+ [{_, _}, {blocks, _}, {_, _}, {instance, _, _}],
+ {C, S, E}) ->
+ {0, {C + Current, S + MaxSince, E + MaxEver}};
+mem(
+ {size, Current},
+ [{_, _}, {blocks, _}, {_, _}, {instance, _, _}],
+ {C, S, E}) ->
+ %% Use Current as Max since we do not have any Max values
+ %% XXX future improvement when that gets added to
+ %% erlang:system_info(allocator_sizes, _)
+ {0, {C + Current, S + Current, E + Current}};
+mem(_, _, Acc) ->
+ {0, Acc}.
+
+%% Traverse (Fold) over all lists in a deep term;
+%% descend into the selected element of a tuple;
+%% record the descent Path and supply it to Fun
+%%
+%% Acc cannot be an integer
+traverse(Fun, Term, Acc) ->
+ traverse(Fun, Term, [], Acc).
+%%
+traverse(Fun, Term, Path, Acc) ->
+ if
+ is_list(Term) ->
+ traverse_list(Fun, Term, Path, Acc);
+ is_tuple(Term) ->
+ case Fun(Term, Path, Acc) of
+ {0, NewAcc} ->
+ NewAcc;
+ {N, NewAcc} when is_integer(N) ->
+ traverse(Fun, element(N, Term), [Term | Path], NewAcc)
+ end;
+ true ->
+ Acc
+ end.
+
+traverse_list(Fun, [Term | Terms], Path, Acc) ->
+ NewAcc = traverse(Fun, Term, Path, Acc),
+ traverse_list(Fun, Terms, Path, NewAcc);
+traverse_list(_Fun, [], _Path, Acc) ->
+ Acc.
+
+-endif.
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
index 90aae473e2..3df721f2d7 100644
--- a/lib/ssl/test/ssl_dist_test_lib.erl
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2023. 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.
@@ -26,7 +26,7 @@
-export([tstsrvr_format/2, send_to_tstcntrl/1]).
-export([apply_on_ssl_node/4, apply_on_ssl_node/2]).
--export([stop_ssl_node/1, start_ssl_node/2]).
+-export([stop_ssl_node/1, start_ssl_node/2, start_ssl_node/3]).
%%
-export([cnct2tstsrvr/1]).
@@ -94,23 +94,22 @@ stop_ssl_node(#node_handle{connection_handler = Handler,
erlang:demonitor(Mon, [flush]),
ct:pal("stop_ssl_node/1 ~s Warning ~p ~n", [Name,Error])
end,
- case file:read_file(LogPath) of
- {ok, Binary} ->
- ct:pal("LogPath(~pB) = ~p~n~s", [filelib:file_size(LogPath), LogPath,
- Binary]);
- _ ->
- ok
- end,
+ ssl_test_lib:ct_pal_file(LogPath),
ct:pal("DumpPath(~pB) = ~p~n", [filelib:file_size(DumpPath), DumpPath]).
start_ssl_node(Name, Args) ->
- {ok, LSock} = gen_tcp:listen(0,
- [binary, {packet, 4}, {active, false}]),
+ start_ssl_node(Name, Args, 1).
+%%
+start_ssl_node(Name, Args, Verbose) ->
+ {ok, LSock} =
+ gen_tcp:listen(0, [binary, {packet, 4}, {active, false}]),
{ok, ListenPort} = inet:port(LSock),
{ok, Pwd} = file:get_cwd(),
LogFilePath = filename:join([Pwd, "error_log." ++ Name]),
DumpFilePath = filename:join([Pwd, "erl_crash_dump." ++ Name]),
- CmdLine = mk_node_cmdline(ListenPort, Name, Args, LogFilePath, DumpFilePath),
+ CmdLine =
+ mk_node_cmdline(
+ ListenPort, Name, Args, Verbose, LogFilePath, DumpFilePath),
test_server:format("Attempting to start ssl node ~ts: ~ts~n", [Name, CmdLine]),
case open_port({spawn, CmdLine}, []) of
Port when is_port(Port) ->
@@ -134,7 +133,7 @@ host_name() ->
%% atom_to_list(node())),
Host.
-mk_node_cmdline(ListenPort, Name, Args, LogPath, DumpPath) ->
+mk_node_cmdline(ListenPort, Name, Args, Verbose, LogPath, DumpPath) ->
Static = "-detached -noinput",
Prog = case catch init:get_argument(progname) of
{ok,[[P]]} -> P;
@@ -148,7 +147,7 @@ mk_node_cmdline(ListenPort, Name, Args, LogPath, DumpPath) ->
++ Static ++ " "
++ NameSw ++ " " ++ Name ++ " "
++ "-run application start crypto -run application start public_key "
- ++ "-eval 'net_kernel:verbose(1)' "
+ ++ "-eval 'net_kernel:verbose("++integer_to_list(Verbose)++")' "
++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr "
++ host_name() ++ " "
++ integer_to_list(ListenPort) ++ " "
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index 1bde66a80b..f462fcefad 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
@@ -52,8 +52,7 @@
decode_empty_server_sni_correctly/1,
select_proper_tls_1_2_rsa_default_hashsign/1,
ignore_hassign_extension_pre_tls_1_2/1,
- signature_algorithms/1,
- encode_decode_srp/1]).
+ signature_algorithms/1]).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -67,8 +66,7 @@ all() -> [decode_hello_handshake,
decode_empty_server_sni_correctly,
select_proper_tls_1_2_rsa_default_hashsign,
ignore_hassign_extension_pre_tls_1_2,
- signature_algorithms,
- encode_decode_srp].
+ signature_algorithms].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
@@ -128,9 +126,9 @@ decode_hello_handshake(_Config) ->
16#00, 16#00, 16#33, 16#74, 16#00, 16#07, 16#06, 16#73,
16#70, 16#64, 16#79, 16#2f, 16#32>>,
- Version = {3, 0},
- {Records, _Buffer} = tls_handshake:get_tls_handshakes(Version, HelloPacket, <<>>,
- default_options_map()),
+ Version = ?SSL_3_0,
+ DefOpts = ssl:update_options([{verify, verify_none}], client, #{}),
+ {Records, _Buffer} = tls_handshake:get_tls_handshakes(Version, HelloPacket, <<>>, DefOpts),
{Hello, _Data} = hd(Records),
Extensions = Hello#server_hello.extensions,
@@ -138,7 +136,7 @@ decode_hello_handshake(_Config) ->
decode_single_hello_extension_correctly(_Config) ->
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
- Extensions = ssl_handshake:decode_extensions(Renegotiation, {3,3}, undefined),
+ Extensions = ssl_handshake:decode_extensions(Renegotiation, ?TLS_1_2, undefined),
#{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
decode_supported_elliptic_curves_hello_extension_correctly(_Config) ->
@@ -150,13 +148,13 @@ decode_supported_elliptic_curves_hello_extension_correctly(_Config) ->
Len = ListLen + 2,
Extension = <<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary>>,
% after decoding we should see only valid curves
- Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, {3,2}, client),
+ Extensions = ssl_handshake:decode_hello_extensions(Extension, ?TLS_1_1, ?TLS_1_1, client),
#{elliptic_curves := #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]}} = Extensions.
decode_unknown_hello_extension_correctly(_Config) ->
FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>,
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
- Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, {3,2}, client),
+ Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, ?TLS_1_1, ?TLS_1_1, client),
#{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
@@ -171,21 +169,21 @@ encode_single_hello_sni_extension_correctly(_Config) ->
decode_single_hello_sni_extension_correctly(_Config) ->
SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08,
$t, $e, $s, $t, $., $c, $o, $m>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, client),
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, ?TLS_1_2, ?TLS_1_2, client),
#{sni := #sni{hostname = "test.com"}} = Decoded.
decode_empty_server_sni_correctly(_Config) ->
SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, server),
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, ?TLS_1_2, ?TLS_1_2, server),
#{sni := #sni{hostname = ""}} = Decoded.
select_proper_tls_1_2_rsa_default_hashsign(_Config) ->
% RFC 5246 section 7.4.1.4.1 tells to use {sha1,rsa} as default signature_algorithm for RSA key exchanges
- {sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,3}),
+ {sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_2),
% Older versions use MD5/SHA1 combination
- {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,2}),
- {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,0}).
+ {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, ?TLS_1_1),
+ {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, ?SSL_3_0).
ignore_hassign_extension_pre_tls_1_2(Config) ->
@@ -193,35 +191,10 @@ ignore_hassign_extension_pre_tls_1_2(Config) ->
CertFile = proplists:get_value(certfile, Opts),
[{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile),
HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}, {sha256, rsa}]},
- {sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([{3,3}]), {3,3}),
+ {sha512, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([?TLS_1_2]), ?TLS_1_2),
%%% Ignore
- {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([{3,2}]), {3,2}),
- {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([{3,0}]), {3,0}).
-
-encode_decode_srp(_Config) ->
- Exts = #{srp => #srp{username = <<"foo">>},
- sni => #sni{hostname = "bar"},
- renegotiation_info => undefined,
- signature_algs => undefined,
- alpn => undefined,
- next_protocol_negotiation => undefined,
- ec_point_formats => undefined,
- elliptic_curves => undefined
- },
- EncodedExts0 = <<0,20, % Length
- 0,12, % SRP extension
- 0,4, % Length
- 3, % srp_I length
- 102,111,111, % username = "foo"
- 0,0, % SNI extension
- 0,8, % Length
- 0,6, % ServerNameLength
- 0, % NameType (host_name)
- 0,3, % HostNameLength
- 98,97,114>>, % hostname = "bar"
- EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> =
- ssl_handshake:encode_hello_extensions(Exts, {3,3}),
- Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client).
+ {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([?TLS_1_1]), ?TLS_1_1),
+ {md5sha, rsa} = ssl_handshake:select_hashsign({HashSigns, undefined}, Cert, ecdhe_rsa, tls_v1:default_signature_algs([?SSL_3_0]), ?SSL_3_0).
signature_algorithms(Config) ->
Opts = proplists:get_value(server_opts, Config),
@@ -238,16 +211,16 @@ signature_algorithms(Config) ->
{sha512, rsa} = ssl_handshake:select_hashsign(
{HashSigns0, Schemes0},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
HashSigns1 = #hash_sign_algos{
hash_sign_algos = [{sha, dsa},
{sha256, rsa}]},
{sha256, rsa} = ssl_handshake:select_hashsign(
{HashSigns1, Schemes0},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
Schemes1 = #signature_algorithms_cert{
signature_scheme_list = [rsa_pkcs1_sha1,
ecdsa_sha1]},
@@ -255,22 +228,22 @@ signature_algorithms(Config) ->
#alert{} = ssl_handshake:select_hashsign(
{HashSigns1, Schemes1},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
%% No scheme, hashsign is used
{sha256, rsa} = ssl_handshake:select_hashsign(
{HashSigns1, undefined},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}),
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2),
HashSigns2 = #hash_sign_algos{
hash_sign_algos = [{sha, dsa}]},
%% Signature not supported
#alert{} = ssl_handshake:select_hashsign(
{HashSigns2, Schemes1},
Cert, ecdhe_rsa,
- tls_v1:default_signature_algs([{3,3}]),
- {3,3}).
+ tls_v1:default_signature_algs([?TLS_1_2]),
+ ?TLS_1_2).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
@@ -280,7 +253,3 @@ 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_key_update_SUITE.erl b/lib/ssl/test/ssl_key_update_SUITE.erl
index 0ac3228f85..542674159c 100644
--- a/lib/ssl/test/ssl_key_update_SUITE.erl
+++ b/lib/ssl/test/ssl_key_update_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2020-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2020-2023. 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.
diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl
index 3ad466590f..0cff561938 100644
--- a/lib/ssl/test/ssl_mfl_SUITE.erl
+++ b/lib/ssl/test/ssl_mfl_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -22,6 +22,7 @@
-behaviour(ct_suite).
-include_lib("common_test/include/ct.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -107,11 +108,10 @@ client_option(Config) when is_list(Config) ->
ok.
%--------------------------------------------------------------------------------
-%% check max_fragment_length option on the server is ignored
-%% and both sides can successfully send > 512 bytes
+%% check default max_fragment_length both sides can successfully send > 512 bytes
server_option(Config) when is_list(Config) ->
Data = "mfl_server_options " ++ lists:duplicate(512, $x),
- run_mfl_handshake(Config, undefined, Data, [], [{max_fragment_length, 512}]).
+ run_mfl_handshake(Config, undefined, Data, [], []).
%--------------------------------------------------------------------------------
%% check max_fragment_length option on the client is accepted and reused
@@ -187,7 +187,7 @@ run_mfl_handshake_continue(Config, MFL) ->
receive {Client, {ext, ClientExt}} ->
ct:log("Client handshake Ext ~p~n", [ClientExt]),
case maps:get(server_hello_selected_version, ClientExt, undefined) of
- {3,4} ->
+ ?TLS_1_3 ->
%% For TLS 1.3 the ssl {handshake, hello} API is inconsistent:
%% the server gets all the extensions CH+EE, but the client only CH
ignore;
diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl
index 26c27b88cb..7cd4818088 100644
--- a/lib/ssl/test/ssl_npn_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
@@ -139,10 +139,11 @@ validate_empty_protocols_are_not_allowed(Config) when is_list(Config) ->
[{next_protocols_advertised, [<<"foo/1">>, <<"">>]}])),
{error, {options, {client_preferred_next_protocols, {invalid_protocol, <<>>}}}}
= (catch ssl:connect({127,0,0,1}, 9443,
- [{client_preferred_next_protocols,
+ [{verify, verify_none}, {client_preferred_next_protocols,
{client, [<<"foo/1">>, <<"">>], <<"foox/1">>}}], infinity)),
Option = {client_preferred_next_protocols, {invalid_protocol, <<"">>}},
- {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option], infinity)).
+ {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443,
+ [{verify, verify_none}, Option], infinity)).
%--------------------------------------------------------------------------------
@@ -154,12 +155,13 @@ validate_empty_advertisement_list_is_allowed(Config) when is_list(Config) ->
validate_advertisement_must_be_a_binary_list(Config) when is_list(Config) ->
Option = {next_protocols_advertised, blah},
- {error, {options, Option}} = (catch ssl:listen(9443, [Option])).
+ {error, {options, Option}} = (catch ssl:listen(9443, [{verify, verify_none}, Option])).
%--------------------------------------------------------------------------------
validate_client_protocols_must_be_a_tuple(Config) when is_list(Config) ->
Option = {client_preferred_next_protocols, [<<"foo/1">>]},
- {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443, [Option])).
+ {error, {options, Option}} = (catch ssl:connect({127,0,0,1}, 9443,
+ [{verify, verify_none}, Option])).
%--------------------------------------------------------------------------------
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index ee8825a724..b097a311eb 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
@@ -123,12 +123,12 @@ encode_and_decode_npn_server_hello_test(Config) ->
%%--------------------------------------------------------------------
create_server_hello_with_no_advertised_protocols_test(_Config) ->
- Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #{}),
+ Hello = ssl_handshake:server_hello(<<>>, ?SSL_3_0, create_connection_states(), #{}),
Extensions = Hello#server_hello.extensions,
#{} = Extensions.
%%--------------------------------------------------------------------
create_server_hello_with_advertised_protocols_test(_Config) ->
- Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(),
+ Hello = ssl_handshake:server_hello(<<>>, ?SSL_3_0, create_connection_states(),
#{next_protocol_negotiation => [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}),
Extensions = Hello#server_hello.extensions,
#{next_protocol_negotiation := [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]} = Extensions.
@@ -171,5 +171,4 @@ create_connection_states() ->
}.
default_options_map() ->
- Fun = fun (_Key, {Default, _}) -> Default end,
- maps:map(Fun, ?RULES).
+ ssl:update_options([{verify, verify_none}], client, #{}).
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
index 53c95c0cb7..0c26388e8c 100644
--- a/lib/ssl/test/ssl_pem_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. 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.
@@ -478,9 +478,9 @@ check_tables(ExpectedTables) ->
true ->
ok;
_ ->
- ?PAL("Mismatch for table ~w", [ActualLabel]),
- ?PAL("Expected = ~w", [ExpectedTableSorted]),
- ?PAL("Actual = ~w", [ActualTableSorted]),
+ ?CT_PAL("Mismatch for table ~w", [ActualLabel]),
+ ?CT_PAL("Expected = ~w", [ExpectedTableSorted]),
+ ?CT_PAL("Actual = ~w", [ActualTableSorted]),
ct:fail({data_mismatch, ActualLabel})
end
end,
@@ -512,7 +512,7 @@ new_root_pem_helper(Config, CleanMode,
%% ConnectedN - state after establishing Nth connection
%% Cleaned - state after periodical cleanup
%% DisconnectedN - state after closing Nth connection
- ?PAL(">>> IntermediateServerKeyId = ~w", [IntermediateServerKeyId]),
+ ?CT_PAL(">>> IntermediateServerKeyId = ~w", [IntermediateServerKeyId]),
{ServerCAFile, ClientConf0, ServerConf, ServerRootCert0, ClientBase, ServerBase} =
create_initial_config(Config),
@@ -691,16 +691,16 @@ create_initial_config(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
#{cert := ServerRootCert0} = SRoot =
public_key:pkix_test_root_cert("OTP test server ROOT",
- [{key, ?KEY(6)}]),
+ [{key, ?KEY(6)}, {digest, sha256}]),
DerConfig =
public_key:pkix_test_data(
#{server_chain =>
#{root => SRoot,
- intermediates => [[{key, ?KEY(5)}]],
- peer => [{key, ?KEY(4)}]},
+ intermediates => [[{key, ?KEY(5)}, {digest, sha256}]],
+ peer => [{key, ?KEY(4)}, {digest, sha256}]},
client_chain =>
- #{root => [{key, ?KEY(1)}],
- intermediates => [[{key, ?KEY(2)}]],
+ #{root => [{key, ?KEY(1)}, {digest, sha256}],
+ intermediates => [[{key, ?KEY(2)}, {digest, sha256} ]],
peer => [{key, ?KEY(3)}]}}),
ClientBase = filename:join(PrivDir, "client_test"),
ServerBase = filename:join(PrivDir, "server_test"),
@@ -725,12 +725,12 @@ overwrite_files_with_new_configuration(ServerRootCert0, ClientBase,
public_key:pkix_test_data(
#{server_chain =>
#{root => #{cert => ServerRootCert1, key => Key},
- intermediates => [[{key, ?KEY(IntermediateServerKey)}]],
- peer => [{key, ?KEY(4)}]},
+ intermediates => [[{key, ?KEY(IntermediateServerKey)}, {digest, sha256}]],
+ peer => [{key, ?KEY(4)}, {digest, sha256} ]},
client_chain =>
- #{root => [{key, ?KEY(1)}],
- intermediates => [[{key, ?KEY(2)}]],
- peer => [{key, ?KEY(3)}]}}),
+ #{root => [{key, ?KEY(1)}, {digest, sha256} ],
+ intermediates => [[{key, ?KEY(2)}, {digest, sha256}]],
+ peer => [{key, ?KEY(3)}, {digest, sha256}]}}),
%% Overwrite old config files
_ = x509_test:gen_pem_config_files(DerConfig1, ClientBase, ServerBase),
ServerRootCert1.
diff --git a/lib/ssl/test/ssl_reject_SUITE.erl b/lib/ssl/test/ssl_reject_SUITE.erl
index 7221b629ac..cc24ffbcfe 100644
--- a/lib/ssl/test/ssl_reject_SUITE.erl
+++ b/lib/ssl/test/ssl_reject_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2021-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2021-2023. 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.
@@ -22,7 +22,7 @@
-module(ssl_reject_SUITE).
-include_lib("common_test/include/ct.hrl").
--include_lib("ssl/src/ssl_record.hrl").
+-include("ssl_record.hrl").
-include_lib("ssl/src/ssl_alert.hrl").
-include_lib("ssl/src/ssl_handshake.hrl").
@@ -48,15 +48,15 @@
accept_sslv3_record_hello/1
]).
--define(TLS_MAJOR, 3).
--define(SSL_3_0_MAJOR, 3).
--define(SSL_3_0_MINOR, 0).
--define(TLS_1_0_MINOR, 1).
--define(TLS_1_1_MINOR, 2).
--define(TLS_1_2_MINOR, 3).
--define(TLS_1_3_MINOR, 4).
--define(SSL_2_0_MAJOR, 0).
--define(SSL_2_0_MINOR, 1).
+-define(TLS_MAJOR, (element(1, ?TLS_1_2))).
+-define(SSL_3_0_MAJOR, (element(1, ?SSL_3_0))).
+-define(SSL_3_0_MINOR, (element(2, ?SSL_3_0))).
+-define(TLS_1_0_MINOR, (element(2, ?TLS_1_0))).
+-define(TLS_1_1_MINOR, (element(2, ?TLS_1_1))).
+-define(TLS_1_2_MINOR, (element(2, ?TLS_1_2))).
+-define(TLS_1_3_MINOR, (element(2, ?TLS_1_3))).
+-define(SSL_2_0_MAJOR, (element(1, ?SSL_2_0))).
+-define(SSL_2_0_MINOR, (element(2, ?SSL_2_0))).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -194,10 +194,11 @@ accept_sslv3_record_hello(Config) when is_list(Config) ->
{ok, Socket} = gen_tcp:connect(Hostname, Port, [{active, false}]),
gen_tcp:send(Socket, ClientHello),
+ TLS_Major = ?TLS_MAJOR,
case gen_tcp:recv(Socket, 3, 5000) of
%% Minor needs to be a TLS version that is a version
%% above SSL-3.0
- {ok, [?HANDSHAKE, ?TLS_MAJOR, Minor]} when Minor > ?SSL_3_0_MINOR ->
+ {ok, [?HANDSHAKE, TLS_Major, Minor]} when Minor > ?SSL_3_0_MINOR ->
ok;
{error, timeout} ->
ct:fail(ssl3_record_not_accepted)
diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl
index 4b46863415..2a58d5ee5b 100644
--- a/lib/ssl/test/ssl_renegotiate_SUITE.erl
+++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -26,6 +26,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_record.hrl").
%% Common test
-export([all/0,
@@ -87,11 +88,10 @@ all() ->
groups() ->
[{'dtlsv1.2', [], renegotiate_tests()},
- {'dtlsv1', [], renegotiate_tests()},
- {'tlsv1.3', [], renegotiate_tests()},
- {'tlsv1.2', [], renegotiate_tests()},
- {'tlsv1.1', [], renegotiate_tests()},
- {'tlsv1', [], renegotiate_tests()}
+ {'dtlsv1', [], renegotiate_tests()},
+ {'tlsv1.2', [], renegotiate_tests()},
+ {'tlsv1.1', [], renegotiate_tests()},
+ {'tlsv1', [], renegotiate_tests()}
].
renegotiate_tests() ->
@@ -107,17 +107,6 @@ renegotiate_tests() ->
renegotiate_dos_mitigate_passive,
renegotiate_dos_mitigate_absolute].
-ssl3_renegotiate_tests() ->
- [client_renegotiate,
- server_renegotiate,
- client_renegotiate_reused_session,
- server_renegotiate_reused_session,
- client_no_wrap_sequence_number,
- server_no_wrap_sequence_number,
- renegotiate_dos_mitigate_active,
- renegotiate_dos_mitigate_passive,
- renegotiate_dos_mitigate_absolute].
-
init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
@@ -518,9 +507,7 @@ renegotiate_rejected(Socket) ->
ok.
%% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast
-treashold(N, {3,0}) ->
- (N div 2) + 1;
-treashold(N, {3,1}) ->
+treashold(N, ?TLS_1_0) ->
(N div 2) + 1;
treashold(N, _) ->
N + 1.
diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl
index b1f093351e..b94932bfc6 100644
--- a/lib/ssl/test/ssl_session_SUITE.erl
+++ b/lib/ssl/test/ssl_session_SUITE.erl
@@ -668,9 +668,9 @@ faulty_client(Host, Port) ->
encode_client_hello(CH, Random) ->
- HSBin = tls_handshake:encode_handshake(CH, {3,3}),
+ HSBin = tls_handshake:encode_handshake(CH, ?TLS_1_2),
CS = connection_states(Random),
- {Encoded, _} = tls_record:encode_handshake(HSBin, {3,3}, CS),
+ {Encoded, _} = tls_record:encode_handshake(HSBin, ?TLS_1_2, CS),
Encoded.
client_hello(Random) ->
@@ -746,7 +746,7 @@ client_hello(Random) ->
srp =>
undefined},
- #client_hello{client_version = {3,3},
+ #client_hello{client_version = ?TLS_1_2,
random = Random,
session_id = crypto:strong_rand_bytes(32),
cipher_suites = CipherSuites,
diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl
index 0c981b5b83..6e07a845e9 100644
--- a/lib/ssl/test/ssl_session_ticket_SUITE.erl
+++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl
@@ -1,6 +1,4 @@
%%
-%% %CopyrightBegin%
-%%
%% Copyright Ericsson AB 2007-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,6 +43,8 @@
ticket_reuse_anti_replay/1,
ticket_reuse_anti_replay_server_restart/0,
ticket_reuse_anti_replay_server_restart/1,
+ ticket_reuse_anti_replay_server_restart_reused_seed/0,
+ ticket_reuse_anti_replay_server_restart_reused_seed/1,
basic_stateful_stateless/0,
basic_stateful_stateless/1,
basic_stateless_stateful/0,
@@ -78,7 +78,9 @@
early_data_basic/0,
early_data_basic/1,
early_data_basic_auth/0,
- early_data_basic_auth/1]).
+ early_data_basic_auth/1,
+ stateless_multiple_servers/0,
+ stateless_multiple_servers/1]).
-include("tls_handshake.hrl").
@@ -98,13 +100,13 @@ all() ->
groups() ->
[{'tlsv1.3', [], [{group, stateful},
{group, stateless},
+ {group, stateful_with_cert},
+ {group, stateless_with_cert},
{group, mixed}]},
{stateful, [], session_tests()},
- {stateless, [], session_tests() ++
- [ticketage_smaller_than_windowsize_anti_replay,
- ticketage_bigger_than_windowsize_anti_replay,
- ticketage_out_of_lifetime_anti_replay, ticket_reuse_anti_replay,
- ticket_reuse_anti_replay_server_restart]},
+ {stateless, [], session_tests() ++ anti_replay_tests()},
+ {stateful_with_cert, [], session_tests()},
+ {stateless_with_cert, [], session_tests() ++ anti_replay_tests()},
{mixed, [], mixed_tests()}].
session_tests() ->
@@ -121,6 +123,16 @@ session_tests() ->
early_data_basic,
early_data_basic_auth].
+anti_replay_tests() ->
+ [
+ ticketage_smaller_than_windowsize_anti_replay,
+ ticketage_bigger_than_windowsize_anti_replay,
+ ticketage_out_of_lifetime_anti_replay, ticket_reuse_anti_replay,
+ ticket_reuse_anti_replay_server_restart,
+ ticket_reuse_anti_replay_server_restart_reused_seed,
+ stateless_multiple_servers
+ ].
+
mixed_tests() ->
[
basic_stateful_stateless,
@@ -145,10 +157,12 @@ 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)
+ when GroupName == stateful
+ orelse GroupName == stateless
+ orelse GroupName == stateful_with_cert
+ orelse GroupName == stateless_with_cert ->
+ [{server_ticket_mode, GroupName} | proplists:delete(server_ticket_mode, Config)];
init_per_group(GroupName, Config) ->
ssl_test_lib:init_per_group(GroupName, Config).
@@ -164,6 +178,7 @@ init_per_testcase(_, Config) ->
end_per_testcase(_TestCase, Config) ->
application:unset_env(ssl, server_session_ticket_max_early_data),
+ application:unset_env(ssl, server_session_ticket_lifetime),
Config.
%%--------------------------------------------------------------------
@@ -202,6 +217,15 @@ basic(Config) when is_list(Config) ->
{from, self()}, {options, ClientOpts}]),
ssl_test_lib:check_result(Server0, ok, Client0, ok),
+ Server0 ! get_socket,
+ SSocket0 =
+ receive
+ {Server0, {socket, Socket0}} ->
+ Socket0
+ end,
+
+ {ok, ClientCert} = ssl:peercert(SSocket0),
+
Server0 ! {listen, {mfa, {ssl_test_lib,
verify_active_session_resumption,
[true]}}},
@@ -220,6 +244,21 @@ basic(Config) when is_list(Config) ->
{from, self()}, {options, ClientOpts}]),
ssl_test_lib:check_result(Server0, ok, Client1, ok),
+ Server0 ! get_socket,
+ SSocket1 =
+ receive
+ {Server0, {socket, Socket1}} ->
+ Socket1
+ end,
+
+ ExpectedPeercert = case ServerTicketMode of
+ stateful_with_cert -> {ok, ClientCert};
+ stateless_with_cert -> {ok, ClientCert};
+ _ -> {error, no_peercert}
+ end,
+
+ ExpectedPeercert = ssl:peercert(SSocket1),
+
process_flag(trap_exit, false),
ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
@@ -243,7 +282,7 @@ ticketage_smaller_than_windowsize_anti_replay(Config) when is_list(Config) ->
ticketage_bigger_than_windowsize_anti_replay() ->
[{doc, "Session resumption with stateless tickets and anti_replay enabled."
"Fresh ClientHellos."
- "Ticket age bigger than windowsize. 0-RTT is expected to fail."
+ "Ticket age bigger than windowsize. 0-RTT is expected to succeed."
"(Erlang client - Erlang server)"}].
ticketage_bigger_than_windowsize_anti_replay(Config) when is_list(Config) ->
WindowSize = 3,
@@ -252,10 +291,10 @@ ticketage_bigger_than_windowsize_anti_replay(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server0, ok, Client0, ok),
Client1 = anti_replay_helper_connect(Server0, Client0, Port0, ClientNode,
Hostname, ClientOpts,
- {seconds, WindowSize + 2}, false),
+ {seconds, WindowSize + 2}, true),
Client2 = anti_replay_helper_connect(Server0, Client0, Port0, ClientNode,
Hostname, ClientOpts,
- {seconds, 2*WindowSize + 2}, false),
+ {seconds, 2*WindowSize + 2}, true),
process_flag(trap_exit, false),
[ssl_test_lib:close(A) || A <- [Server0, Client0, Client1, Client2]].
@@ -325,6 +364,36 @@ ticket_reuse_anti_replay_server_restart(Config) when is_list(Config) ->
process_flag(trap_exit, false),
[ssl_test_lib:close(A) || A <- [Server0, Client2, Server1]].
+ticket_reuse_anti_replay_server_restart_reused_seed() ->
+ [{doc, "Verify 2 connection attempts with same stateless tickets "
+ "and server restart between, with the server using the same session "
+ "ticket encryption seed between restarts. Second attempt is expected to "
+ "fail as long as the Bloom filter window overlaps with startup time."
+ }].
+ticket_reuse_anti_replay_server_restart_reused_seed(Config) when is_list(Config) ->
+ WindowSize = 10,
+ Seed = crypto:strong_rand_bytes(32),
+ Config1 = [{server_ticket_seed, Seed} | Config],
+ {Server1 , Port1} = anti_replay_helper_start_server(Config1, WindowSize),
+ {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientOpts1 = [{session_tickets, manual},
+ {versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0],
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% full handshake
+ verify_active_session_resumption,
+ [false, wait_reply, {tickets, 1}]}},
+ {from, self()}, {options, ClientOpts1}]),
+ [Ticket] = ssl_test_lib:check_tickets(Client1),
+ ssl_test_lib:check_result(Server1, ok),
+ ClientOpts2 = [{use_ticket, [Ticket]} | ClientOpts1],
+ {Server2, Port2} = anti_replay_helper_start_server(Config1, WindowSize),
+ Client2 = anti_replay_helper_connect(Server2, Client1, Port2, ClientNode,
+ Hostname, ClientOpts2, 0, false, false),
+ process_flag(trap_exit, false),
+ [ssl_test_lib:close(A) || A <- [Server1, Client2, Server2]].
+
anti_replay_helper_init(Config, Mode, WindowSize) ->
DefaultLifetime = ssl_config:get_ticket_lifetime(),
anti_replay_helper_init(Config, Mode, WindowSize, DefaultLifetime).
@@ -360,9 +429,17 @@ anti_replay_helper_start_server(Config, WindowSize) ->
{_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ServerTicketMode = proplists:get_value(server_ticket_mode, Config),
+ ServerTicketSeed =
+ case proplists:get_value(server_ticket_seed, Config) of
+ undefined ->
+ [];
+ Seed ->
+ [{stateless_tickets_seed, Seed}]
+ end,
ServerOpts = [{session_tickets, ServerTicketMode},
{anti_replay, {WindowSize, 5, 72985}},
- {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0],
+ {versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0
+ ] ++ ServerTicketSeed,
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -1227,6 +1304,69 @@ early_data_basic_auth(Config) when is_list(Config) ->
ssl_test_lib:close(Server0),
ssl_test_lib:close(Client1).
+stateless_multiple_servers() ->
+ [{doc, "Test session resumption with session tickets, resuming on different server"}].
+stateless_multiple_servers(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),
+
+ Seed = crypto:strong_rand_bytes(64),
+
+ %% Configure session tickets
+ ClientOpts = [{session_tickets, auto},
+ {versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0],
+ ServerOpts = [{session_tickets, stateless},
+ {stateless_tickets_seed, Seed},
+ {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),
+
+ Server1 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {options, ServerOpts}]),
+ Port1 = ssl_test_lib:inet_port(Server1),
+
+ %% Store ticket from first connection to server 0
+ 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),
+
+ %% Wait for session ticket
+ ct:sleep(100),
+
+ ssl_test_lib:close(Client0),
+
+ %% Use ticket when connecting to server 1
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, %% Short handshake
+ verify_active_session_resumption,
+ [true]}},
+ {from, self()}, {options, ClientOpts}]),
+ ssl_test_lib:check_result(Server1, ok, Client1, ok),
+
+ process_flag(trap_exit, false),
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Server1),
+ ssl_test_lib:close(Client1).
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl
index ab243bc6c9..3cc4d49684 100644
--- a/lib/ssl/test/ssl_sni_SUITE.erl
+++ b/lib/ssl/test/ssl_sni_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2023. 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.
@@ -112,8 +112,8 @@ init_per_suite(Config0) ->
#{server_config := LServerConf,
client_config := LClientConf}} = ssl_test_lib:make_rsa_sni_configs(),
%% RSA certs files needed by *dot cases
- ssl_test_lib:make_rsa_cert([{client_opts, ClientConf},
- {client_local_opts, LClientConf},
+ ssl_test_lib:make_rsa_cert([{client_opts, [{verify, verify_peer} | ClientConf]},
+ {client_local_opts, [{verify, verify_peer} | LClientConf]},
{sni_server_opts, [{sni_hosts, [{Hostname, ServerConf}]} | LServerConf]}
| Config0])
catch _:_ ->
@@ -197,8 +197,8 @@ sni_no_match_fun(Config) ->
dns_name(Config) ->
Hostname = "OTP.test.server",
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -211,6 +211,9 @@ dns_name(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
successfull_connect(ServerConf, [{verify, verify_peer},
{server_name_indication, Hostname} | ClientConf], undefined, Config),
@@ -223,8 +226,8 @@ ip_fallback(Config) ->
Hostname = net_adm:localhost(),
{ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
IPStr = tuple_to_list(IP),
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -238,14 +241,17 @@ ip_fallback(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).
no_ip_fallback(Config) ->
Hostname = net_adm:localhost(),
{ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()),
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -259,13 +265,16 @@ no_ip_fallback(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config),
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config).
dns_name_reuse(Config) ->
SNIHostname = "OTP.test.server",
- #{server_config := ServerConf,
- client_config := ClientConf} =
+ #{server_config := ServerOpts0,
+ client_config := ClientOpts0} =
public_key:pkix_test_data(#{server_chain =>
#{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
@@ -280,39 +289,42 @@ dns_name_reuse(Config) ->
#{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}],
intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(5)}]],
peer => [{key, ssl_test_lib:hardcode_rsa_key(6)}]}}),
-
+ Version = ssl_test_lib:n_version(proplists:get_value(version, Config)),
+ ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0,
+ ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0,
+
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], undefined, Config),
-
- Server =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
{mfa, {ssl_test_lib, session_info_result, []}},
{options, ServerConf}]),
Port = ssl_test_lib:inet_port(Server),
Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
+ ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, [{verify, verify_peer},
- {server_name_indication, SNIHostname} | ClientConf]}]),
+ {from, self()}, {options, [{verify, verify_peer},
+ {server_name_indication, SNIHostname} | ClientConf]}]),
receive
{Server, _} ->
ok
end,
-
+
Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
+
%% Make sure session is registered
ct:sleep(1000),
-
+
Client1 =
ssl_test_lib:start_client_error([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, session_info_result, []}},
{from, self()}, {options, [{verify, verify_peer} | ClientConf]}]),
-
+
ssl_test_lib:check_client_alert(Client1, handshake_failure),
ssl_test_lib:close(Client0).
@@ -371,8 +383,8 @@ customize_hostname_check(Config) when is_list(Config) ->
sni_no_trailing_dot() ->
[{doc,"Test that sni may not include a triling dot"}].
sni_no_trailing_dot(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(sni_server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -395,8 +407,8 @@ hostname_trailing_dot() ->
[{doc,"Test that fallback sni removes trailing dot of hostname"}].
hostname_trailing_dot(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),
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(sni_server_opts, Config),
{ClientNode, ServerNode, Hostname0} = ssl_test_lib:run_where(Config),
case trailing_dot_hostname(Hostname0) of
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 4b0618bc86..64ee3c9d0d 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -25,6 +25,9 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("ssl/src/tls_handshake_1_3.hrl").
+-include_lib("ssl/src/ssl_cipher.hrl").
+-include_lib("ssl/src/ssl_internal.hrl").
+-include_lib("ssl/src/ssl_record.hrl").
-export([clean_start/0,
clean_start/1,
@@ -42,6 +45,7 @@
default_tls_version/1,
check_sane_openssl_renegotiate/2,
check_openssl_npn_support/1,
+ check_sane_openssl_dsa/1,
start_server/1,
start_server/2,
start_client/1,
@@ -116,7 +120,6 @@
session_id/1,
update_keys/2,
sanity_check/2,
- oscp_responder/6,
supported_eccs/1,
no_result/1,
receive_tickets/1,
@@ -149,6 +152,7 @@
]).
-export([tls_version/1,
+ n_version/1,
is_protocol_version/1,
is_tls_version/1,
is_dtls_version/1,
@@ -182,8 +186,8 @@
default_cert_chain_conf/0,
cert_options/1,
rsa_non_signed_suites/1,
+ dsa_suites/1,
ecdh_dh_anonymous_suites/1,
- ecdsa_suites/1,
der_to_pem/2,
pem_to_der/1,
appropriate_sha/1,
@@ -191,7 +195,11 @@
format_cert/1,
ecdsa_conf/0,
eddsa_conf/0,
- default_ecc_cert_chain_conf/1
+ default_ecc_cert_chain_conf/1,
+ sig_algs/2,
+ all_sig_algs/0,
+ all_1_3_sig_algs/0,
+ all_1_2_sig_algs/0
]).
-export([maybe_force_ipv4/1,
@@ -214,8 +222,16 @@
portable_cmd/2,
portable_open_port/2,
close_port/1,
- verify_early_data/1
+ verify_early_data/1,
+ trace/0,
+ ct_pal_file/1
]).
+%% Tracing
+-export([handle_trace/3]).
+
+-export([ktls_os/0,
+ ktls_set_ulp/2,
+ ktls_set_cipher/4]).
-record(sslsocket, { fd = nil, pid = nil}).
-define(SLEEP, 1000).
@@ -444,9 +460,79 @@ default_ecc_cert_chain_conf(eddsa_1_3) ->
default_ecc_cert_chain_conf(_) ->
default_cert_chain_conf().
+sig_algs(rsa_pss_pss, _) ->
+ [{signature_algs, [rsa_pss_pss_sha512,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha256]}];
+sig_algs(rsa_pss_rsae, _) ->
+ [{signature_algs, [rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256]}];
+sig_algs(rsa, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
+ [{signature_algs, [rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256,
+ {sha512, rsa},
+ {sha384, rsa},
+ {sha256, rsa},
+ {sha, rsa}
+ ]}];
+sig_algs(ecdsa, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
+ [{signature_algs, [
+ {sha512, ecdsa},
+ {sha384, ecdsa},
+ {sha256, ecdsa},
+ {sha, ecdsa}]}];
+sig_algs(dsa, Version) when ?TLS_GTE(Version, ?TLS_1_2) ->
+ [{signature_algs, [{sha,dsa}]}];
+sig_algs(_,_) ->
+ [].
+
+all_sig_algs() ->
+ {signature_algs, list_1_3_sig_algs() ++ list_common_sig_algs() ++ list_1_2_sig_algs()}.
+
+all_1_3_sig_algs() ->
+ {signature_algs, list_1_3_sig_algs() ++ list_common_sig_algs()}.
+
+all_1_2_sig_algs() ->
+ {signature_algs, list_common_sig_algs() ++ list_1_2_sig_algs()}.
+
%%====================================================================
%% Internal functions
%%====================================================================
+list_1_3_sig_algs() ->
+ [
+ eddsa_ed25519,
+ eddsa_ed448,
+ ecdsa_secp521r1_sha512,
+ ecdsa_secp384r1_sha384,
+ ecdsa_secp256r1_sha256
+ ].
+
+list_common_sig_algs() ->
+ [
+ rsa_pss_pss_sha512,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha256,
+ rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256
+ ].
+
+list_1_2_sig_algs() ->
+ [
+ {sha512, ecdsa},
+ {sha512, rsa},
+ {sha384, ecdsa},
+ {sha384, rsa},
+ {sha256, ecdsa},
+ {sha256, rsa},
+ {sha224, ecdsa},
+ {sha224, rsa},
+ {sha, ecdsa},
+ {sha, rsa},
+ {sha, dsa}
+ ].
%% For now always run locally
run_where(_) ->
@@ -503,7 +589,7 @@ run_server(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, format_options(Options)]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, format_options(Options)]),
case Transport:listen(Port, Options) of
{ok, ListenSocket} ->
Pid ! {listen, up},
@@ -526,11 +612,11 @@ run_server(ListenSocket, Opts, N) ->
run_server(ListenSocket, Opts, N-1).
do_run_server(_, {error, _} = Result, Opts) ->
- ?LOG("Server error result ~p~n", [Result]),
+ ?CT_LOG("Server error result ~p~n", [Result]),
Pid = proplists:get_value(from, Opts),
Pid ! {self(), Result};
do_run_server(_, ok = Result, Opts) ->
- ?LOG("Server cancel result ~p~n", [Result]),
+ ?CT_LOG("Server cancel result ~p~n", [Result]),
Pid = proplists:get_value(from, Opts),
Pid ! {self(), Result};
do_run_server(ListenSocket, AcceptSocket, Opts) ->
@@ -541,7 +627,7 @@ do_run_server(ListenSocket, AcceptSocket, Opts) ->
no_result_msg ->
ok;
Msg ->
- ?LOG("~nServer Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nServer Msg: ~p ~n", [Msg]),
case lists:member(return_socket, Opts) of
true -> Pid ! {self(), {Msg, AcceptSocket}};
false -> Pid ! {self(), Msg}
@@ -552,14 +638,14 @@ do_run_server(ListenSocket, AcceptSocket, Opts) ->
server_apply_mfa(_, undefined) ->
no_result_msg;
server_apply_mfa(AcceptSocket, {Module, Function, Args}) ->
- ?LOG("~nServer: apply(~p,~p,~p)~n",
+ ?CT_LOG("~nServer: apply(~p,~p,~p)~n",
[Module, Function, [AcceptSocket | Args]]),
apply(Module, Function, [AcceptSocket | Args]).
client_apply_mfa(_, undefined) ->
no_result_msg;
client_apply_mfa(AcceptSocket, {Module, Function, Args}) ->
- ?LOG("~nClient: apply(~p,~p,~p)~n",
+ ?CT_LOG("~nClient: apply(~p,~p,~p)~n",
[Module, Function, [AcceptSocket | Args]]),
apply(Module, Function, [AcceptSocket | Args]).
@@ -567,7 +653,7 @@ client_apply_mfa(AcceptSocket, {Module, Function, Args}) ->
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
receive
{data, Data} ->
- ?LOG("[server] Send: ~p~n", [Data]),
+ ?CT_LOG("[server] Send: ~p~n", [Data]),
case Transport:send(AcceptSocket, Data) of
ok ->
Pid ! {self(), ok};
@@ -578,17 +664,17 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
{active_receive, Data} ->
case active_recv(AcceptSocket, length(Data)) of
ReceivedData ->
- ?LOG("[server] Received: ~p~n", [Data]),
+ ?CT_LOG("[server] Received: ~p~n", [Data]),
Pid ! {self(), ReceivedData}
end,
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
{update_keys, Type} ->
case ssl:update_keys(AcceptSocket, Type) of
ok ->
- ?LOG("[server] Update keys: ~p", [Type]),
+ ?CT_LOG("[server] Update keys: ~p", [Type]),
Pid ! {self(), ok};
{error, Reason} ->
- ?LOG("[server] Update keys failed: ~p", [Type]),
+ ?CT_LOG("[server] Update keys failed: ~p", [Type]),
Pid ! {self(), Reason}
end,
do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid);
@@ -600,10 +686,10 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) ->
{listen, MFA} ->
run_server(ListenSocket, [MFA | proplists:delete(mfa, Opts)]);
close ->
- ?LOG("~nServer closing~n", []),
+ ?CT_LOG("~nServer closing~n", []),
Result = Transport:close(AcceptSocket),
Result1 = Transport:close(ListenSocket),
- ?LOG("~nResult ~p : ~p ~n", [Result, Result1])
+ ?CT_LOG("~nResult ~p : ~p ~n", [Result, Result1])
end.
%%% To enable to test with s_client -reconnect
@@ -622,35 +708,35 @@ connect(#sslsocket{} = ListenSocket, Opts) ->
AcceptSocket
end;
connect(ListenSocket, _Opts) ->
- ?LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
AcceptSocket.
connect(_, _, 0, AcceptSocket, _, _, _) ->
AcceptSocket;
connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, format_options(SslOpts),Timeout]),
+ ?CT_LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, format_options(SslOpts),Timeout]),
case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
{ok, Socket0, Ext} ->
- ?LOG("Ext ~p:~n", [Ext]),
- ?LOG("~nssl:handshake_cancel(~p)~n", [Socket0]),
+ ?CT_LOG("Ext ~p:~n", [Ext]),
+ ?CT_LOG("~nssl:handshake_cancel(~p)~n", [Socket0]),
ssl:handshake_cancel(Socket0);
Result ->
- ?LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
+ ?CT_LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
Result
end;
connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, SslOpts,Timeout]),
+ ?CT_LOG("~nssl:handshake(~p,~p,~p)~n", [AcceptSocket, SslOpts,Timeout]),
case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
{ok, Socket0, Ext} ->
[_|_] = maps:get(sni, Ext),
- ?LOG("Ext ~p:~n", [Ext]),
+ ?CT_LOG("Ext ~p:~n", [Ext]),
ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of
{value, {_, WantExt}, ContOpts1} ->
if is_pid(WantExt) ->
@@ -662,34 +748,34 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) ->
_ ->
ContOpts0
end,
- ?LOG("~nssl:handshake_continue(~p,~p,~p)~n", [Socket0, ContOpts,Timeout]),
+ ?CT_LOG("~nssl:handshake_continue(~p,~p,~p)~n", [Socket0, ContOpts,Timeout]),
case ssl:handshake_continue(Socket0, ContOpts, Timeout) of
{ok, Socket} ->
connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts0);
Error ->
- ?LOG("~nssl:handshake_continue@~p ret ~p",[Node,Error]),
+ ?CT_LOG("~nssl:handshake_continue@~p ret ~p",[Node,Error]),
Error
end;
Result ->
- ?LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
+ ?CT_LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
Result
end;
connect(ListenSocket, Node, N, _, Timeout, [], ContOpts) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("~nssl:handshake(~p, ~p)~n", [AcceptSocket, Timeout]),
+ ?CT_LOG("~nssl:handshake(~p, ~p)~n", [AcceptSocket, Timeout]),
case ssl:handshake(AcceptSocket, Timeout) of
{ok, Socket} ->
connect(ListenSocket, Node, N-1, Socket, Timeout, [], ContOpts);
Result ->
- ?LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
+ ?CT_LOG("~nssl:handshake@~p ret ~p",[Node,Result]),
Result
end;
connect(ListenSocket, _Node, _, _, Timeout, Opts, _) ->
- ?LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
+ ?CT_LOG("ssl:transport_accept(~P)~n", [ListenSocket, ?PRINT_DEPTH]),
{ok, AcceptSocket} = ssl:transport_accept(ListenSocket),
- ?LOG("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]),
+ ?CT_LOG("ssl:handshake(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]),
ssl:handshake(AcceptSocket, Opts, Timeout),
AcceptSocket.
@@ -715,7 +801,7 @@ transport_accept_abuse(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
{ok, ListenSocket} = Transport:listen(Port, Options),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
@@ -729,7 +815,7 @@ transport_switch_control(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
{ok, ListenSocket} = Transport:listen(Port, Options),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
@@ -841,15 +927,6 @@ init_openssl_server(Mode, ResponderPort, Options) when Mode == openssl_ocsp orel
Pid ! {self(), {port, Port}},
openssl_server_loop(Pid, SslPort, Args).
-oscp_responder(Port, Index, CACerts, Cert, Key, Starter) ->
- Args = ["ocsp", "-index", Index, "-CA", CACerts, "-rsigner", Cert,
- "-rkey", Key, "-port", erlang:integer_to_list(Port)],
- Responder = portable_open_port("openssl", Args),
- wait_for_openssl_server(Port, tls),
-
- openssl_server_loop(Starter, Responder, []).
-
-
openssl_dtls_opt('dtlsv1.2') ->
["-dtls"];
openssl_dtls_opt(_Other) ->
@@ -860,34 +937,34 @@ openssl_server_loop(Pid, SslPort, Args) ->
{data, Data} ->
case port_command(SslPort, Data, [nosuspend]) of
true ->
- ?LOG("[openssl server] Send data: ~p~n", [Data]),
+ ?CT_LOG("[openssl server] Send data: ~p~n", [Data]),
Pid ! {self(), ok};
_Else ->
- ?LOG("[openssl server] Send failed, data: ~p~n", [Data]),
+ ?CT_LOG("[openssl server] Send failed, data: ~p~n", [Data]),
Pid ! {self(), {error, port_command_failed}}
end,
openssl_server_loop(Pid, SslPort, Args);
{active_receive, Data} ->
case active_recv(SslPort, length(Data)) of
ReceivedData ->
- ?LOG("[openssl server] Received: ~p~n", [Data]),
+ ?CT_LOG("[openssl server] Received: ~p~n", [Data]),
Pid ! {self(), ReceivedData}
end,
openssl_server_loop(Pid, SslPort, Args);
{update_keys, Type} ->
case Type of
write ->
- ?LOG("[openssl server] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl server] Update keys: ~p", [Type]),
true = port_command(SslPort, "k", [nosuspend]),
Pid ! {self(), ok};
read_write ->
- ?LOG("[openssl server] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl server] Update keys: ~p", [Type]),
true = port_command(SslPort, "K", [nosuspend]),
Pid ! {self(), ok}
end,
openssl_server_loop(Pid, SslPort, Args);
close ->
- ?LOG("~n[openssl server] Server closing~n", []),
+ ?CT_LOG("~n[openssl server] Server closing~n", []),
catch port_close(SslPort);
{ssl_closed, _Socket} ->
%% TODO
@@ -896,15 +973,19 @@ openssl_server_loop(Pid, SslPort, Args) ->
start_openssl_client(Args0, Config) ->
{ClientNode, _, Hostname} = run_where(Config),
- ClientOpts = get_client_opts(Config),
+
+ %% io:format("~p:~p: ~p~n",[?MODULE, ?LINE, Args0]),
+ %% io:format("~p:~p: ~p~n",[?MODULE, ?LINE, Config]),
+
+ ClientOpts0 = get_client_opts(Config),
+ ClientOpts = proplists:get_value(options, Args0, []) ++ ClientOpts0,
DefaultVersions = default_tls_version(ClientOpts),
[Version | _] = proplists:get_value(versions, ClientOpts, DefaultVersions),
Node = proplists:get_value(node, Args0, ClientNode),
- Args = [{from, self()},
- {host, Hostname},
- {options, ClientOpts} | Args0],
+ Args = [{from, self()}, {host, Hostname} | ClientOpts ++ Args0],
- Result = spawn_link(Node, ?MODULE, init_openssl_client, [[{version, Version} | lists:delete(return_port, Args)]]),
+ Result = spawn_link(Node, ?MODULE, init_openssl_client,
+ [[{version, Version} | lists:delete(return_port, Args)]]),
receive
{connected, OpenSSLPort} ->
case lists:member(return_port, Args) of
@@ -937,17 +1018,17 @@ openssl_client_loop_core(Pid, SslPort, Args) ->
{data, Data} ->
case port_command(SslPort, Data, [nosuspend]) of
true ->
- ?LOG("[openssl client] Send data: ~p~n", [Data]),
+ ?CT_LOG("[openssl client] Send data: ~p~n", [Data]),
Pid ! {self(), ok};
_Else ->
- ?LOG("[openssl client] Send failed, data: ~p~n", [Data]),
+ ?CT_LOG("[openssl client] Send failed, data: ~p~n", [Data]),
Pid ! {self(), {error, port_command_failed}}
end,
openssl_client_loop_core(Pid, SslPort, Args);
{active_receive, Data} ->
case active_recv(SslPort, length(Data)) of
ReceivedData ->
- ?LOG("[openssl client] Received: ~p~n (forward to PID=~p)~n",
+ ?CT_LOG("[openssl client] Received: ~p~n (forward to PID=~p)~n",
[Data, Pid]),
Pid ! {self(), ReceivedData}
end,
@@ -955,17 +1036,17 @@ openssl_client_loop_core(Pid, SslPort, Args) ->
{update_keys, Type} ->
case Type of
write ->
- ?LOG("[openssl client] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl client] Update keys: ~p", [Type]),
true = port_command(SslPort, "k", [nosuspend]),
Pid ! {self(), ok};
read_write ->
- ?LOG("[openssl client] Update keys: ~p", [Type]),
+ ?CT_LOG("[openssl client] Update keys: ~p", [Type]),
true = port_command(SslPort, "K", [nosuspend]),
Pid ! {self(), ok}
end,
openssl_client_loop_core(Pid, SslPort, Args);
close ->
- ?LOG("~nClient closing~n", []),
+ ?CT_LOG("~nClient closing~n", []),
catch port_close(SslPort);
{ssl_closed, _Socket} ->
%% TODO
@@ -1010,8 +1091,8 @@ run_client(Opts) ->
Options0 = proplists:get_value(options, Opts),
Options = patch_dtls_options(Options0),
ContOpts = proplists:get_value(continue_options, Opts, []),
- ?LOG("~n~p:connect(~p, ~p)@~p~n", [Transport, Host, Port, Node]),
- ?LOG("SSLOpts:~n ~0.p", [format_options(Options)]),
+ ?CT_LOG("~n~p:connect(~p, ~p)@~p~n", [Transport, Host, Port, Node]),
+ ?CT_LOG("SSLOpts:~n ~0.p", [format_options(Options)]),
case ContOpts of
[] ->
client_loop(Node, Host, Port, Pid, Transport, Options, Opts);
@@ -1023,7 +1104,7 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
case Transport:connect(Host, Port, Options) of
{ok, Socket} ->
Pid ! {connected, Socket},
- ?LOG("~nClient: connected~n", []),
+ ?CT_LOG("~nClient: connected~n", []),
%% In special cases we want to know the client port, it will
%% be indicated by sending {port, 0} in options list!
send_selected_port(Pid, proplists:get_value(port, Options), Socket),
@@ -1032,7 +1113,7 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
no_result_msg ->
ok;
Msg ->
- ?LOG("~nClient Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nClient Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg}
end,
client_loop_core(Socket, Pid, Transport);
@@ -1043,35 +1124,35 @@ client_loop(_Node, Host, Port, Pid, Transport, Options, Opts) ->
_ ->
case get(retries) of
N when N < 5 ->
- ?LOG("~neconnrefused retries=~p sleep ~p",[N,?SLEEP]),
+ ?CT_LOG("~neconnrefused retries=~p sleep ~p",[N,?SLEEP]),
put(retries, N+1),
ct:sleep(?SLEEP),
run_client(Opts);
_ ->
- ?LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
Pid ! {self(), {error, Reason}}
end
end;
{error, econnreset = Reason} ->
case get(retries) of
N when N < 5 ->
- ?LOG("~neconnreset retries=~p sleep ~p",[N,?SLEEP]),
+ ?CT_LOG("~neconnreset retries=~p sleep ~p",[N,?SLEEP]),
put(retries, N+1),
ct:sleep(?SLEEP),
run_client(Opts);
_ ->
- ?LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient failed several times: connection failed: ~p ~n", [Reason]),
Pid ! {self(), {error, Reason}}
end;
{error, Reason} ->
- ?LOG("~nClient: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient: connection failed: ~p ~n", [Reason]),
Pid ! {connect_failed, Reason}
end.
client_loop_core(Socket, Pid, Transport) ->
receive
{data, Data} ->
- ?LOG("[client] Send: ~p~n", [Data]),
+ ?CT_LOG("[client] Send: ~p~n", [Data]),
case Transport:send(Socket, Data) of
ok ->
Pid ! {self(), ok};
@@ -1082,17 +1163,17 @@ client_loop_core(Socket, Pid, Transport) ->
{active_receive, Data} ->
case active_recv(Socket, length(Data)) of
ReceivedData ->
- ?LOG("[client] Received: ~p~n", [Data]),
+ ?CT_LOG("[client] Received: ~p~n", [Data]),
Pid ! {self(), ReceivedData}
end,
client_loop_core(Socket, Pid, Transport);
{update_keys, Type} ->
case ssl:update_keys(Socket, Type) of
ok ->
- ?LOG("[client] Update keys: ~p", [Type]),
+ ?CT_LOG("[client] Update keys: ~p", [Type]),
Pid ! {self(), ok};
{error, Reason} ->
- ?LOG("[client] Update keys failed: ~p", [Type]),
+ ?CT_LOG("[client] Update keys failed: ~p", [Type]),
Pid ! {self(), Reason}
end,
client_loop_core(Socket, Pid, Transport);
@@ -1100,7 +1181,7 @@ client_loop_core(Socket, Pid, Transport) ->
Pid ! {self(), {socket, Socket}},
client_loop_core(Socket, Pid, Transport);
close ->
- ?LOG("~nClient closing~n", []),
+ ?CT_LOG("~nClient closing~n", []),
Transport:close(Socket);
{ssl_closed, Socket} ->
ok;
@@ -1124,10 +1205,10 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) ->
case Transport:connect(Host, Port, Options) of
{ok, Socket, _} ->
Result = Transport:handshake_cancel(Socket),
- ?LOG("~nClient: Cancel: ~p ~n", [Result]),
+ ?CT_LOG("~nClient: Cancel: ~p ~n", [Result]),
Pid ! {connect_failed, Result};
{error, Reason} ->
- ?LOG("~nClient: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient: connection failed: ~p ~n", [Reason]),
Pid ! {connect_failed, Reason}
end;
@@ -1145,44 +1226,44 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts0, Opts) ->
_ ->
ContOpts0
end,
- ?LOG("~nClient: handshake_continue(~p, ~p, infinity) ~n", [Socket0, ContOpts]),
+ ?CT_LOG("~nClient: handshake_continue(~p, ~p, infinity) ~n", [Socket0, ContOpts]),
case Transport:handshake_continue(Socket0, ContOpts) of
{ok, Socket} ->
Pid ! {connected, Socket},
- {Module, Function, Args} = proplists:get_value(mfa, Opts),
- ?LOG("~nClient: apply(~p,~p,~p)~n",
- [Module, Function, [Socket | Args]]),
- case apply(Module, Function, [Socket | Args]) of
+ MFA = proplists:get_value(mfa, Opts),
+ ?CT_LOG(
+ "~nClient: client_apply_mfa(~p,~p)~n", [Socket, MFA]),
+ case client_apply_mfa(Socket, MFA) of
no_result_msg ->
ok;
Msg ->
- ?LOG("~nClient Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nClient Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg}
end
end;
{error, Reason} ->
- ?LOG("~nClient: connection failed: ~p ~n", [Reason]),
+ ?CT_LOG("~nClient: connection failed: ~p ~n", [Reason]),
Pid ! {connect_failed, Reason}
end.
close(Pid) ->
- ?LOG("~nClose ~p ~n", [Pid]),
+ ?CT_LOG("~nClose ~p ~n", [Pid]),
Monitor = erlang:monitor(process, Pid),
Pid ! close,
receive
{'DOWN', Monitor, process, Pid, Reason} ->
erlang:demonitor(Monitor),
- ?LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
+ ?CT_LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
end.
close(Pid, Timeout) ->
- ?LOG("~n Close ~p ~n", [Pid]),
+ ?CT_LOG("~n Close ~p ~n", [Pid]),
Monitor = erlang:monitor(process, Pid),
Pid ! close,
receive
{'DOWN', Monitor, process, Pid, Reason} ->
erlang:demonitor(Monitor),
- ?LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
+ ?CT_LOG("~nPid: ~p down due to:~p ~n", [Pid, Reason])
after
Timeout ->
exit(Pid, kill)
@@ -1191,65 +1272,42 @@ close(Pid, Timeout) ->
get_result(Pids) ->
get_result(Pids, []).
-get_result([], Acc) ->
- Acc;
+get_result([], Acc) -> Acc;
get_result([Pid | Tail], Acc) ->
- receive
- {Pid, Msg} ->
- get_result(Tail, [{Pid, Msg} | Acc])
+ receive {Pid, Msg} -> get_result(Tail, [{Pid, Msg} | Acc])
end.
+check_result(Pid, Msg) ->
+ check_result([{Pid, Msg}]).
check_result(Server, ServerMsg, Client, ClientMsg) ->
- {ClientIP, ClientPort} = get_ip_port(ServerMsg),
+ check_result([{Server, ServerMsg}, {Client, ClientMsg}]).
+
+check_result([]) -> ok;
+check_result(Msgs) ->
receive
- {Server, ServerMsg} ->
- check_result(Client, ClientMsg);
- %% Workaround to accept local addresses (127.0.0.0/24)
- {Server, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost ->
- check_result(Client, ClientMsg);
- {Client, ClientMsg} ->
- check_result(Server, ServerMsg);
- {Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~n Openssl ~s~n",[Debug]),
- check_result(Server, ServerMsg, Client, ClientMsg);
- {Port,closed} when is_port(Port) ->
- ?LOG("~n Openssl port closed ~n",[]),
- check_result(Server, ServerMsg, Client, ClientMsg);
- {'EXIT', epipe} ->
- ?LOG("~n Openssl port died ~n",[]),
- check_result(Server, ServerMsg, Client, ClientMsg);
- Unexpected ->
- Reason = {{expected, {Client, ClientMsg}},
- {expected, {Server, ServerMsg}}, {got, Unexpected}},
- ct:fail(Reason)
+ Msg -> match_result_msg(Msg, Msgs)
end.
-check_result(Pid, Msg) ->
- {ClientIP, ClientPort} = get_ip_port(Msg),
- receive
- {Pid, Msg} ->
- ok;
- %% Workaround to accept local addresses (127.0.0.0/24)
- {Pid, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost ->
- ok;
- {Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~n Openssl ~s~n",[Debug]),
- check_result(Pid,Msg);
- {Port,closed} when is_port(Port)->
- ?LOG(" Openssl port closed ~n",[]),
- check_result(Pid, Msg);
- Unexpected ->
- Reason = {{expected, {Pid, Msg}},
- {got, Unexpected}},
- ct:fail(Reason)
+match_result_msg(Msg, Msgs) ->
+ case lists:member(Msg, Msgs) of
+ true -> check_result(lists:delete(Msg, Msgs));
+ false -> match_result_msg2(Msg, Msgs)
end.
-
-get_ip_port({ok,{ClientIP, ClientPort}}) ->
- {ClientIP, ClientPort};
-get_ip_port(_) ->
- {undefined, undefined}.
-
+match_result_msg2({Pid, {ok, {{127,_,_,_}, Port}}} = Msg, Msgs) ->
+ Match = {Pid, {ok, {localhost, Port}}},
+ case lists:member(Match, Msgs) of
+ true -> check_result(lists:delete(Match, Msgs));
+ false -> ct:fail({{expected, Msgs}, {got, Msg}})
+ end;
+match_result_msg2({Port, {data,Debug}}, Msgs) when is_port(Port) ->
+ ?CT_LOG(" Openssl (~p) ~s~n",[Port, Debug]),
+ check_result(Msgs);
+match_result_msg2({Port, closed}, Msgs) when is_port(Port) ->
+ ?CT_LOG(" Openssl port (~p) closed ~n",[Port]),
+ check_result(Msgs);
+match_result_msg2(Msg, Msgs) ->
+ ct:fail({{expected, Msgs}, {got, Msg}}).
check_server_alert(Pid, Alert) ->
receive
@@ -1334,7 +1392,7 @@ wait_for_result(Server, ServerMsg, Client, ClientMsg) ->
%% Unexpected
end;
{Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~nopenssl ~s~n",[Debug]),
+ ?CT_LOG("~nopenssl ~s~n",[Debug]),
wait_for_result(Server, ServerMsg, Client, ClientMsg)
%% Unexpected ->
%% Unexpected
@@ -1355,7 +1413,7 @@ wait_for_result(Pid, Msg) ->
{Pid, Msg} ->
ok;
{Port, {data,Debug}} when is_port(Port) ->
- ?LOG("~nopenssl ~s~n",[Debug]),
+ ?CT_LOG("~nopenssl ~s~n",[Debug]),
wait_for_result(Pid,Msg)
%% Unexpected ->
%% Unexpected
@@ -1403,78 +1461,82 @@ format_cert(#'OTPCertificate'{tbsCertificate = Cert} = OtpCert) ->
{error, _} ->
io_lib:format("~.3w:~s -> :~s", [Nr, format_subject(Subject), format_subject(Issuer)])
end
- end.
+ end;
+format_cert(Cert) ->
+ io_lib:format("Format failed for ~p", [Cert]).
format_subject({rdnSequence, Seq}) ->
format_subject(Seq);
format_subject([[{'AttributeTypeAndValue', ?'id-at-commonName', {_, String}}]|_]) ->
String;
format_subject([_|R]) ->
- format_subject(R).
+ format_subject(R);
+format_subject([]) ->
+ "no commonname".
cert_options(Config) ->
- ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "cacerts.pem"]),
- ClientCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ClientCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "cert.pem"]),
ClientCertFileDigitalSignatureOnly = filename:join([proplists:get_value(priv_dir, Config),
- "client", "digital_signature_only_cert.pem"]),
- ServerCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ "client", "digital_signature_only_cert.pem"]),
+ ServerCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"server", "cacerts.pem"]),
- ServerCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ServerCertFile = filename:join([proplists:get_value(priv_dir, Config),
"server", "cert.pem"]),
- ServerKeyFile = filename:join([proplists:get_value(priv_dir, Config),
- "server", "key.pem"]),
- ClientKeyFile = filename:join([proplists:get_value(priv_dir, Config),
- "client", "key.pem"]),
- ServerKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ServerKeyFile = filename:join([proplists:get_value(priv_dir, Config),
+ "server", "key.pem"]),
+ ClientKeyFile = filename:join([proplists:get_value(priv_dir, Config),
+ "client", "key.pem"]),
+ ServerKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
"server", "keycert.pem"]),
- ClientKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ ClientKeyCertFile = filename:join([proplists:get_value(priv_dir, Config),
"client", "keycert.pem"]),
- BadCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ BadCaCertFile = filename:join([proplists:get_value(priv_dir, Config),
"badcacert.pem"]),
- BadCertFile = filename:join([proplists:get_value(priv_dir, Config),
- "badcert.pem"]),
- BadKeyFile = filename:join([proplists:get_value(priv_dir, Config),
- "badkey.pem"]),
-
- [{client_opts, [{cacertfile, ClientCaCertFile},
- {certfile, ClientCertFile},
- {keyfile, ClientKeyFile}]},
- {client_verification_opts, [{cacertfile, ServerCaCertFile},
- {certfile, ClientCertFile},
- {keyfile, ClientKeyFile},
- {verify, verify_peer}]},
+ BadCertFile = filename:join([proplists:get_value(priv_dir, Config),
+ "badcert.pem"]),
+ BadKeyFile = filename:join([proplists:get_value(priv_dir, Config),
+ "badkey.pem"]),
+
+ [{client_opts, [{cacertfile, ClientCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile}, {verify, verify_none}]},
+ {client_verification_opts, [{cacertfile, ServerCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
{client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile},
- {certfile, ClientCertFileDigitalSignatureOnly},
- {keyfile, ClientKeyFile},
- {ssl_imp, new}]},
- {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile},
- {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
- {server_verification_opts, [{ssl_imp, new},{reuseaddr, true},
- {cacertfile, ClientCaCertFile},
- {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
- {client_kc_opts, [{certfile, ClientKeyCertFile}, {ssl_imp, new}]},
- {server_kc_opts, [{ssl_imp, new},{reuseaddr, true},
- {certfile, ServerKeyCertFile}]},
- {client_bad_ca, [{cacertfile, BadCaCertFile},
- {certfile, ClientCertFile},
- {keyfile, ClientKeyFile},
- {ssl_imp, new}]},
- {client_bad_cert, [{cacertfile, ClientCaCertFile},
- {certfile, BadCertFile},
- {keyfile, ClientKeyFile},
- {ssl_imp, new}]},
- {server_bad_ca, [{ssl_imp, new},{cacertfile, BadCaCertFile},
- {certfile, ServerCertFile},
- {keyfile, ServerKeyFile}]},
- {server_bad_cert, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
- {certfile, BadCertFile}, {keyfile, ServerKeyFile}]},
- {server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile},
- {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}
- | Config].
-
+ {certfile, ClientCertFileDigitalSignatureOnly},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
+ {server_opts, [{reuseaddr, true}, {cacertfile, ServerCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
+ {server_verification_opts, [{verify, verify_peer},{reuseaddr, true},
+ {cacertfile, ClientCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
+ {client_kc_opts, [{certfile, ClientKeyCertFile}, {verify, verify_none}]},
+ {server_kc_opts, [{reuseaddr, true},
+ {certfile, ServerKeyCertFile}]},
+ {client_bad_ca, [{cacertfile, BadCaCertFile},
+ {certfile, ClientCertFile},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
+ {client_bad_cert, [{cacertfile, ClientCaCertFile},
+ {certfile, BadCertFile},
+ {keyfile, ClientKeyFile},
+ {verify, verify_peer}]},
+ {server_bad_ca, [{cacertfile, BadCaCertFile},
+ {certfile, ServerCertFile},
+ {keyfile, ServerKeyFile}]},
+ {server_bad_cert, [{cacertfile, ServerCaCertFile},
+ {certfile, BadCertFile}, {keyfile, ServerKeyFile}]},
+ {server_bad_key, [{cacertfile, ServerCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}
+ | Config].
+
make_dsa_cert(Config) ->
CryptoSupport = crypto:supports(),
case proplists:get_bool(dss, proplists:get_value(public_keys, CryptoSupport)) of
@@ -1493,7 +1555,7 @@ make_dsa_cert(Config) ->
{server_dsa_verify_opts, [{verify, verify_peer} | ServerConf]},
{client_dsa_opts, ClientConf}
| Config];
- false ->
+ false ->
Config
end.
@@ -1578,9 +1640,7 @@ make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, Curve) -
[{server_config, ServerConf},
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- {[{verify, verify_peer} | ClientConf],
- [{reuseaddr, true}, {verify, verify_peer} | ServerConf]
- }.
+ {ClientConf, [{reuseaddr, true} | ServerConf]}.
default_cert_chain_conf() ->
%% Use only default options
@@ -1818,11 +1878,12 @@ make_ecdsa_cert(Config) ->
[{server_config, ServerConf},
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- [{server_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+ [{server_ecdsa_opts, [{reuseaddr, true} | ServerConf]},
- {server_ecdsa_verify_opts, [{ssl_imp, new}, {reuseaddr, true},
+ {server_ecdsa_verify_opts, [{reuseaddr, true},
{verify, verify_peer} | ServerConf]},
- {client_ecdsa_opts, ClientConf}
+ {client_ecdsa_opts, [{verify, verify_none} | ClientConf]},
+ {client_ecdsa_verify_opts, [{verify, verify_peer} | ClientConf]}
| Config];
false ->
Config
@@ -1844,11 +1905,11 @@ make_rsa_cert(Config) ->
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
[{server_rsa_opts, [{reuseaddr, true} | ServerConf]},
{server_rsa_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerConf]},
- {client_rsa_opts, ClientConf},
- {client_rsa_verify_opts, [{verify, verify_peer} |ClientConf]},
- {server_rsa_der_opts, [{reuseaddr, true} | ServerDerConf]},
+ {client_rsa_opts, [{verify, verify_none} | ClientConf]},
+ {client_rsa_verify_opts, [{verify, verify_peer} | ClientConf]},
+ {server_rsa_der_opts, [{reuseaddr, true}, {verify, verify_none} | ServerDerConf]},
{server_rsa_der_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerDerConf]},
- {client_rsa_der_opts, ClientDerConf},
+ {client_rsa_der_opts, [{verify, verify_none} | ClientDerConf]},
{client_rsa_der_verify_opts, [{verify, verify_peer} |ClientDerConf]}
| Config];
false ->
@@ -1898,12 +1959,12 @@ make_rsa_1024_cert(Config) ->
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
[{server_rsa_1024_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
- {server_rsa_1024_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, {verify, verify_peer} | ServerConf]},
- {client_rsa_1024_opts, ClientConf},
+ {server_rsa_1024_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerConf]},
+ {client_rsa_1024_opts, [{verify, verify_none} | ClientConf]},
{client_rsa_1024_verify_opts, [{verify, verify_peer} |ClientConf]},
- {server_rsa_1024_der_opts, [{ssl_imp, new},{reuseaddr, true} | ServerDerConf]},
- {server_rsa_1024_der_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, {verify, verify_peer} | ServerDerConf]},
- {client_rsa_1024_der_opts, ClientDerConf},
+ {server_rsa_1024_der_opts, [{reuseaddr, true} | ServerDerConf]},
+ {server_rsa_1024_der_verify_opts, [{reuseaddr, true}, {verify, verify_peer} | ServerDerConf]},
+ {client_rsa_1024_der_opts, [{verify, verify_none} | ClientDerConf]},
{client_rsa_1024_der_verify_opts, [{verify, verify_peer} |ClientDerConf]}
| Config];
false ->
@@ -1970,10 +2031,10 @@ make_rsa_ecdsa_cert(Config, Curve) ->
{client_config, ClientConf}] =
x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase),
- [{server_rsa_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]},
+ [{server_rsa_ecdsa_opts, [{reuseaddr, true} | ServerConf]},
{server_rsa_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true},
{verify, verify_peer} | ServerConf]},
- {client_rsa_ecdsa_opts, ClientConf} | Config];
+ {client_rsa_ecdsa_opts, [{verify, verify_none} | ClientConf]} | Config];
_ ->
Config
end.
@@ -1994,31 +2055,31 @@ run_upgrade_server(Opts) ->
SslOptions = proplists:get_value(ssl_options, Opts),
Pid = proplists:get_value(from, Opts),
- ?LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
+ ?CT_LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
{ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
try
{ok, SslAcceptSocket} = case TimeOut of
infinity ->
- ?LOG("~nssl:handshake(~p, ~p)~n",
+ ?CT_LOG("~nssl:handshake(~p, ~p)~n",
[AcceptSocket, SslOptions]),
ssl:handshake(AcceptSocket, SslOptions);
_ ->
- ?LOG("~nssl:handshake(~p, ~p, ~p)~n",
+ ?CT_LOG("~nssl:handshake(~p, ~p, ~p)~n",
[AcceptSocket, SslOptions, TimeOut]),
ssl:handshake(AcceptSocket, SslOptions, TimeOut)
end,
{Module, Function, Args} = proplists:get_value(mfa, Opts),
Msg = apply(Module, Function, [SslAcceptSocket | Args]),
- ?LOG("~nUpgrade Server Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nUpgrade Server Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg},
receive
close ->
- ?LOG("~nUpgrade Server closing~n", []),
+ ?CT_LOG("~nUpgrade Server closing~n", []),
ssl:close(SslAcceptSocket)
end
catch error:{badmatch, Error} ->
@@ -2036,24 +2097,24 @@ run_upgrade_client(Opts) ->
TcpOptions = proplists:get_value(tcp_options, Opts),
SslOptions = proplists:get_value(ssl_options, Opts),
- ?LOG("~ngen_tcp:connect(~p, ~p, ~p)~n",
+ ?CT_LOG("~ngen_tcp:connect(~p, ~p, ~p)~n",
[Host, Port, TcpOptions]),
{ok, Socket} = gen_tcp:connect(Host, Port, TcpOptions),
send_selected_port(Pid, Port, Socket),
- ?LOG("~nssl:connect(~p, ~p)~n", [Socket, SslOptions]),
+ ?CT_LOG("~nssl:connect(~p, ~p)~n", [Socket, SslOptions]),
{ok, SslSocket} = ssl:connect(Socket, SslOptions),
{Module, Function, Args} = proplists:get_value(mfa, Opts),
- ?LOG("~napply(~p, ~p, ~p)~n",
+ ?CT_LOG("~napply(~p, ~p, ~p)~n",
[Module, Function, [SslSocket | Args]]),
Msg = apply(Module, Function, [SslSocket | Args]),
- ?LOG("~nUpgrade Client Msg: ~p ~n", [Msg]),
+ ?CT_LOG("~nUpgrade Client Msg: ~p ~n", [Msg]),
Pid ! {self(), Msg},
receive
close ->
- ?LOG("~nUpgrade Client closing~n", []),
+ ?CT_LOG("~nUpgrade Client closing~n", []),
ssl:close(SslSocket)
end.
@@ -2068,11 +2129,11 @@ run_upgrade_client_error(Opts) ->
Timeout = proplists:get_value(timeout, Opts, infinity),
TcpOptions = proplists:get_value(tcp_options, Opts),
SslOptions = proplists:get_value(ssl_options, Opts),
- ?LOG("gen_tcp:connect(~p, ~p, ~p)",
+ ?CT_LOG("gen_tcp:connect(~p, ~p, ~p)",
[Host, Port, TcpOptions]),
{ok, Socket} = gen_tcp:connect(Host, Port, TcpOptions),
send_selected_port(Pid, Port, Socket),
- ?LOG("ssl:connect(~p, ~p)", [Socket, SslOptions]),
+ ?CT_LOG("ssl:connect(~p, ~p)", [Socket, SslOptions]),
Error = ssl:connect(Socket, SslOptions, Timeout),
Pid ! {self(), Error}.
@@ -2091,19 +2152,19 @@ run_upgrade_server_error(Opts) ->
SslOptions = proplists:get_value(ssl_options, Opts),
Pid = proplists:get_value(from, Opts),
- ?LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
+ ?CT_LOG("~ngen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
{ok, ListenSocket} = gen_tcp:listen(Port, TcpOptions),
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~ngen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
Error = case TimeOut of
infinity ->
- ?LOG("~nssl:handshake(~p, ~p)~n",
+ ?CT_LOG("~nssl:handshake(~p, ~p)~n",
[AcceptSocket, SslOptions]),
ssl:handshake(AcceptSocket, SslOptions);
_ ->
- ?LOG("~nssl:ssl_handshake(~p, ~p, ~p)~n",
+ ?CT_LOG("~nssl:ssl_handshake(~p, ~p, ~p)~n",
[AcceptSocket, SslOptions, TimeOut]),
ssl:handshake(AcceptSocket, SslOptions, TimeOut)
end,
@@ -2121,7 +2182,7 @@ run_server_error(Opts) ->
Options = proplists:get_value(options, Opts),
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
- ?LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
+ ?CT_LOG("~nssl:listen(~p, ~p)~n", [Port, Options]),
Timeout = proplists:get_value(timeout, Opts, infinity),
case Transport:listen(Port, Options) of
{ok, #sslsocket{} = ListenSocket} ->
@@ -2129,19 +2190,19 @@ run_server_error(Opts) ->
%% get {error, closed} and not {error, connection_refused}
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~nssl:transport_accept(~p)~n", [ListenSocket]),
+ ?CT_LOG("~nssl:transport_accept(~p)~n", [ListenSocket]),
case Transport:transport_accept(ListenSocket, Timeout) of
{error, _} = Error ->
Pid ! {self(), Error};
{ok, AcceptSocket} ->
- ?LOG("~nssl:handshake(~p)~n", [AcceptSocket]),
+ ?CT_LOG("~nssl:handshake(~p)~n", [AcceptSocket]),
Error = ssl:handshake(AcceptSocket),
Pid ! {self(), Error}
end;
{ok, ListenSocket} ->
Pid ! {listen, up},
send_selected_port(Pid, Port, ListenSocket),
- ?LOG("~n~p:accept(~p)~n", [Transport, ListenSocket]),
+ ?CT_LOG("~n~p:accept(~p)~n", [Transport, ListenSocket]),
case Transport:accept(ListenSocket) of
{error, _} = Error ->
Pid ! {self(), Error}
@@ -2164,7 +2225,7 @@ run_client_error(Opts) ->
Transport = proplists:get_value(transport, Opts, ssl),
Options0 = proplists:get_value(options, Opts),
Options = patch_dtls_options(Options0),
- ?LOG("~nssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]),
+ ?CT_LOG("~nssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]),
Error = Transport:connect(Host, Port, Options),
case Error of
{error, _} ->
@@ -2390,7 +2451,7 @@ start_server(erlang, _, ServerOpts, Config) ->
{mfa, {ssl_test_lib,
check_key_exchange_send_active,
[KeyEx]}},
- {options, [{verify, verify_peer} | ServerOpts]}]),
+ {options, ServerOpts}]),
{Server, inet_port(Server)}.
sig_algs(undefined) ->
@@ -2645,14 +2706,14 @@ rsa_non_signed_suites(Version) ->
true
end,
available_suites(Version)).
-
-ecdsa_suites(Version) ->
- lists:filter(fun({ecdhe_ecdsa, _, _}) ->
- true;
- (_) ->
- false
- end,
- available_suites(Version)).
+dsa_suites(Version) ->
+ ssl:filter_cipher_suites(available_suites(Version),
+ [{key_exchange,
+ fun(dhe_dss) ->
+ true;
+ (_) ->
+ false
+ end}]).
openssl_dsa_suites() ->
Ciphers = openssl_ciphers(),
@@ -2696,7 +2757,7 @@ der_to_pem(File, Entries) ->
cipher_result(Socket, Result) ->
{ok, Info} = ssl:connection_information(Socket),
Result = {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}},
- ?LOG("~nSuccessfull connect: ~p~n", [Result]),
+ ?CT_LOG("~nSuccessfull connect: ~p~n", [Result]),
%% Importante to send two packets here
%% to properly test "cipher state" handling
Hello = "Hello\n",
@@ -2778,7 +2839,7 @@ openssl_tls_version_support(Version, Config0) ->
true ->
openssl_tls_version_support(tls, TLSOpts, Port, Exe, TLSArgs);
false ->
- DTLSTupleVersion = dtls_record:protocol_version(Version),
+ DTLSTupleVersion = dtls_record:protocol_version_name(Version),
CorrespondingTLSVersion = dtls_v1:corresponding_tls_version(DTLSTupleVersion),
AtomTLSVersion = tls_record:protocol_version(CorrespondingTLSVersion),
CorrTLSOpts = [{protocol,tls}, {versions, [AtomTLSVersion]},
@@ -2805,21 +2866,21 @@ openssl_tls_version_support(Proto, Opts, Port, Exe, Args0) ->
close_port(OpensslPort),
true;
{error, {tls_alert, {protocol_version, _}}} ->
- ?PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
+ ?CT_PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
close_port(OpensslPort),
false;
{error, {tls_alert, Alert}} ->
- ?PAL("OpenSSL returned alert ~p", [Alert]),
+ ?CT_PAL("OpenSSL returned alert ~p", [Alert]),
close_port(OpensslPort),
false;
{error, timeout} ->
- ?PAL("Timed out connection to OpenSSL", []),
+ ?CT_PAL("Timed out connection to OpenSSL", []),
close_port(OpensslPort),
false
end
catch
_:_ ->
- ?PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
+ ?CT_PAL("OpenSSL does not support ~p", [proplists:get_value(versions, Opts)]),
close_port(OpensslPort),
false
end.
@@ -2842,6 +2903,8 @@ init_protocol_version(Version, Config) ->
[{protocol, tls} | NewConfig].
clean_protocol_version(Config) ->
+ application:unset_env(ssl, protocol_version),
+ application:unset_env(ssl, dtls_protocol_version),
proplists:delete(version, proplists:delete(protocol_opts, proplists:delete(protocol, Config))).
sufficient_crypto_support(Version)
@@ -2870,20 +2933,20 @@ check_key_exchange_send_active(Socket, KeyEx) ->
send_recv_result_active(Socket).
check_key_exchange({KeyEx,_, _}, KeyEx, _) ->
- ?LOG("Kex: ~p", [KeyEx]),
+ ?CT_LOG("Kex: ~p", [KeyEx]),
true;
check_key_exchange({KeyEx,_,_,_}, KeyEx, _) ->
- ?LOG("Kex: ~p", [KeyEx]),
+ ?CT_LOG("Kex: ~p", [KeyEx]),
true;
check_key_exchange(KeyEx1, KeyEx2, Version) ->
- ?LOG("Kex: ~p ~p", [KeyEx1, KeyEx2]),
+ ?CT_LOG("Kex: ~p ~p", [KeyEx1, KeyEx2]),
case Version of
'tlsv1.2' ->
v_1_2_check(element(1, KeyEx1), KeyEx2);
'dtlsv1.2' ->
v_1_2_check(element(1, KeyEx1), KeyEx2);
_ ->
- ?PAL("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]),
+ ?CT_PAL("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]),
false
end.
@@ -2927,10 +2990,10 @@ check_active_receive(Pid, Data) ->
check_active_receive_loop(Pid, Data) ->
receive
{Pid, Data} ->
- ?LOG("Received: ~p~n (from ~p)~n", [Data, Pid]),
+ ?CT_LOG("Received: ~p~n (from ~p)~n", [Data, Pid]),
Data;
{Pid, Data2} ->
- ?LOG("Received unexpected message: ~p~n (from ~p)~n", [Data2, Pid]),
+ ?CT_LOG("Received unexpected message: ~p~n (from ~p)~n", [Data2, Pid]),
check_active_receive_loop(Pid, Data)
end.
@@ -2964,15 +3027,15 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
case ssl:connection_information(Socket, [session_resumption]) of
{ok, [{session_resumption, SessionResumption}]} ->
Msg = boolean_to_log_msg(SessionResumption),
- ?LOG("~nSession resumption verified! (expected ~p, got ~p)!",
+ ?CT_LOG("~nSession resumption verified! (expected ~p, got ~p)!",
[Msg, Msg]);
{ok, [{session_resumption, Got0}]} ->
Expected = boolean_to_log_msg(SessionResumption),
Got = boolean_to_log_msg(Got0),
- ?FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
+ ?CT_FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
[Expected, Got]);
{error, Reason} ->
- ?FAIL("~nFailed to verify session resumption! Reason: ~p",
+ ?CT_FAIL("~nFailed to verify session resumption! Reason: ~p",
[Reason])
end,
@@ -2984,7 +3047,7 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
no_reply ->
ok;
Else1 ->
- ?FAIL("~nFaulty parameter: ~p", [Else1])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else1])
end,
Tickets =
case TicketOption of
@@ -2993,7 +3056,7 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
no_tickets ->
ok;
Else2 ->
- ?FAIL("~nFaulty parameter: ~p", [Else2])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else2])
end,
case EarlyData of
{verify_early_data, Atom} ->
@@ -3001,28 +3064,28 @@ verify_active_session_resumption(Socket, SessionResumption, WaitForReply, Ticket
ok ->
Tickets;
Else ->
- ?FAIL("~nFailed to verify early_data! (expected ~p, got ~p)",
+ ?CT_FAIL("~nFailed to verify early_data! (expected ~p, got ~p)",
[Atom, Else])
end;
no_early_data ->
Tickets;
Else3 ->
- ?FAIL("~nFaulty parameter: ~p", [Else3])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else3])
end.
verify_server_early_data(Socket, WaitForReply, EarlyData) ->
case ssl:connection_information(Socket, [session_resumption]) of
{ok, [{session_resumption, true}]} ->
Msg = boolean_to_log_msg(true),
- ?LOG("~nSession resumption verified! (expected ~p, got ~p)!",
+ ?CT_LOG("~nSession resumption verified! (expected ~p, got ~p)!",
[Msg, Msg]);
{ok, [{session_resumption, Got0}]} ->
Expected = boolean_to_log_msg(true),
Got = boolean_to_log_msg(Got0),
- ?FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
+ ?CT_FAIL("~nFailed to verify session resumption! (expected ~p, got ~p)",
[Expected, Got]);
{error, Reason} ->
- ?FAIL("~nFailed to verify session resumption! Reason: ~p",
+ ?CT_FAIL("~nFailed to verify session resumption! Reason: ~p",
[Reason])
end,
Data = "Hello world",
@@ -3034,14 +3097,14 @@ verify_server_early_data(Socket, WaitForReply, EarlyData) ->
_ ->
binary_to_list(EarlyData) ++ Data
end,
- ?LOG("Expected Reply: ~p~n", [Reply]),
+ ?CT_LOG("Expected Reply: ~p~n", [Reply]),
case WaitForReply of
wait_reply ->
Reply = active_recv(Socket, length(Reply));
no_reply ->
ok;
Else1 ->
- ?FAIL("~nFaulty parameter: ~p", [Else1])
+ ?CT_FAIL("~nFaulty parameter: ~p", [Else1])
end,
ok.
@@ -3052,10 +3115,10 @@ verify_session_ticket_extension([Ticket0|_], MaxEarlyDataSize) ->
indication = Size}}}} = Ticket0,
case Size of
MaxEarlyDataSize ->
- ?LOG("~nmax_early_data_size verified! (expected ~p, got ~p)!",
+ ?CT_LOG("~nmax_early_data_size verified! (expected ~p, got ~p)!",
[MaxEarlyDataSize, Size]);
Else ->
- ?LOG("~nFailed to verify max_early_data_size! (expected ~p, got ~p)!",
+ ?CT_LOG("~nFailed to verify max_early_data_size! (expected ~p, got ~p)!",
[MaxEarlyDataSize, Else])
end.
@@ -3064,7 +3127,7 @@ update_session_ticket_extension([Ticket|_], MaxEarlyDataSize) ->
extensions = #{early_data :=
#early_data_indication_nst{
indication = Size}}}} = Ticket,
- ?LOG("~nOverwrite max_early_data_size (from ~p to ~p)!",
+ ?CT_LOG("~nOverwrite max_early_data_size (from ~p to ~p)!",
[Size, MaxEarlyDataSize]),
#{ticket := #new_session_ticket{
extensions = #{early_data := _Extensions0}} = NST0} = Ticket,
@@ -3095,17 +3158,17 @@ check_tickets(Client) ->
Tickets
after
5000 ->
- ?FAIL("~nNo tickets received!", [])
+ ?CT_FAIL("~nNo tickets received!", [])
end.
active_recv_loop(Pid, SslPort, Data) ->
case active_recv(SslPort, length(Data)) of
Data ->
- ?LOG("[openssl server] Received: ~p~n (forward to PID=~p)~n",
+ ?CT_LOG("[openssl server] Received: ~p~n (forward to PID=~p)~n",
[Data, Pid]),
Pid ! {self(), Data};
Unexpected ->
- ?LOG("[openssl server] Received unexpected: ~p~n (dropping message)~n",
+ ?CT_LOG("[openssl server] Received unexpected: ~p~n (dropping message)~n",
[Unexpected]),
active_recv_loop(Pid, SslPort, Data)
end.
@@ -3298,6 +3361,22 @@ check_sane_openssl_version(Version, Config) ->
false ->
false
end.
+
+
+%% If other DSA checks have passed also check the following
+check_sane_openssl_dsa(Config) ->
+ case not is_fips(openssl, Config) of
+ true ->
+ case proplists:get_value(openssl_version, Config) of
+ "OpenSSL 1.0." ++ _ ->
+ false;
+ _ ->
+ true
+ end;
+ false ->
+ false
+ end.
+
check_sane_openssl_renegotiate(Config, Version) when Version == 'tlsv1';
Version == 'tlsv1.1';
Version == 'tlsv1.2' ->
@@ -3406,28 +3485,28 @@ close_port(Port) ->
close_loop(Port, Time, SentClose) ->
receive
{Port, {data,Debug}} when is_port(Port) ->
- ?LOG("openssl ~s~n",[Debug]),
+ ?CT_LOG("openssl ~s~n",[Debug]),
close_loop(Port, Time, SentClose);
{ssl,_,Msg} ->
- ?LOG("ssl Msg ~s~n",[Msg]),
+ ?CT_LOG("ssl Msg ~s~n",[Msg]),
close_loop(Port, Time, SentClose);
{Port, closed} ->
- ?LOG("Port Closed~n",[]),
+ ?CT_LOG("Port Closed~n",[]),
ok;
{'EXIT', Port, Reason} ->
- ?LOG("Port Closed ~p~n",[Reason]),
+ ?CT_LOG("Port Closed ~p~n",[Reason]),
ok;
Msg ->
- ?LOG("Port Msg ~p~n",[Msg]),
+ ?CT_LOG("Port Msg ~p~n",[Msg]),
close_loop(Port, Time, SentClose)
after Time ->
case SentClose of
false ->
- ?LOG("Closing port ~n",[]),
+ ?CT_LOG("Closing port ~n",[]),
catch erlang:port_close(Port),
close_loop(Port, Time, true);
true ->
- ?LOG("Timeout~n",[])
+ ?CT_LOG("Timeout~n",[])
end
end.
@@ -3439,7 +3518,7 @@ portable_open_port("openssl" = Exe, Args0) ->
case IsWindows andalso os:getenv("WSLENV") of
false ->
AbsPath = os:find_executable(Exe),
- ?LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
+ ?CT_LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
[AbsPath, lists:join($\s, Args0)]),
open_port({spawn_executable, AbsPath},
[{args, Args0}, stderr_to_stdout]);
@@ -3456,14 +3535,14 @@ portable_open_port("openssl" = Exe, Args0) ->
Args1 = [Translate(Arg) || Arg <- Args0],
Args = ["/C","wsl","openssl"| Args1] ++ ["2>&1"],
Cmd = os:find_executable("cmd"),
- ?LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
+ ?CT_LOG("open_port({spawn_executable, ~p}, [stderr_to_stdout,~n {args, \"~s\"}]).",
[Cmd, lists:join($\s, Args0)]),
open_port({spawn_executable, Cmd},
[{args, Args}, stderr_to_stdout, hide])
end;
portable_open_port(Exe, Args) ->
AbsPath = os:find_executable(Exe),
- ?LOG("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
+ ?CT_LOG("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]),
open_port({spawn_executable, AbsPath},
[{args, Args}, stderr_to_stdout]).
@@ -3546,7 +3625,7 @@ do_supports_ssl_tls_version(Port, Acc) ->
"s_client: Unknown option: " ++ _->
false;
Info when length(Info) >= 24 ->
- ?LOG("~p", [Info]),
+ ?CT_LOG("~p", [Info]),
true;
_ ->
do_supports_ssl_tls_version(Port, Acc ++ Data)
@@ -3600,8 +3679,8 @@ protocol_version(Config, atom) ->
case proplists:get_value(protocol, Config) of
dtls ->
dtls_record:protocol_version(protocol_version(Config, tuple));
- _ ->
- tls_record:protocol_version(protocol_version(Config, tuple))
+ _ ->
+ tls_record:protocol_version(protocol_version(Config, tuple))
end.
protocol_options(Config, Options) ->
@@ -3612,9 +3691,9 @@ protocol_options(Config, Options) ->
ct_log_supported_protocol_versions(Config) ->
case proplists:get_value(protocol, Config) of
dtls ->
- ?LOG("DTLS version ~p~n ", [dtls_record:supported_protocol_versions()]);
+ ?CT_LOG("DTLS version ~p~n ", [dtls_record:supported_protocol_versions()]);
_ ->
- ?LOG("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()])
+ ?CT_LOG("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()])
end.
clean_env() ->
@@ -3655,11 +3734,23 @@ clean_start(keep_version) ->
tls_version('dtlsv1' = Atom) ->
- dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
+ dtls_v1:corresponding_tls_version(dtls_record:protocol_version_name(Atom));
tls_version('dtlsv1.2' = Atom) ->
- dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Atom));
+ dtls_v1:corresponding_tls_version(dtls_record:protocol_version_name(Atom));
tls_version(Atom) ->
- tls_record:protocol_version(Atom).
+ tls_record:protocol_version_name(Atom).
+
+
+n_version(Version) when
+ Version == 'tlsv1.3';
+ Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == 'tlsv1';
+ Version == 'sslv3' ->
+ tls_record:protocol_version_name(Version);
+n_version(Version) when Version == 'dtlsv1.2';
+ Version == 'dtlsv1' ->
+ dtls_record:protocol_version_name(Version).
consume_port_exit(OpenSSLPort) ->
receive
@@ -3807,10 +3898,10 @@ client_msg(Client, ClientMsg) ->
{Client, ClientMsg} ->
ok;
{Client, {error,closed}} ->
- ?LOG("client got close", []),
+ ?CT_LOG("client got close", []),
ok;
{Client, {error, Reason}} ->
- ?LOG("client got econnaborted: ~p", [Reason]),
+ ?CT_LOG("client got econnaborted: ~p", [Reason]),
ok;
Unexpected ->
ct:fail(Unexpected)
@@ -3820,10 +3911,10 @@ server_msg(Server, ServerMsg) ->
{Server, ServerMsg} ->
ok;
{Server, {error,closed}} ->
- ?LOG("server got close", []),
+ ?CT_LOG("server got close", []),
ok;
{Server, {error, Reason}} ->
- ?LOG("server got econnaborted: ~p", [Reason]),
+ ?CT_LOG("server got econnaborted: ~p", [Reason]),
ok;
Unexpected ->
ct:fail(Unexpected)
@@ -3995,7 +4086,7 @@ new_config(PrivDir, ServerOpts0) ->
ServerOpts = proplists:delete(keyfile, ServerOpts2),
{ok, PEM} = file:read_file(NewCaCertFile),
- ?LOG("CA file content: ~p~n", [public_key:pem_decode(PEM)]),
+ ?CT_LOG("CA file content: ~p~n", [public_key:pem_decode(PEM)]),
[{cacertfile, NewCaCertFile}, {certfile, NewCertFile},
{keyfile, NewKeyFile} | ServerOpts].
@@ -4096,11 +4187,11 @@ openssl_maxfraglen_support() ->
assert_mfl(Socket, undefined) ->
InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
- ?LOG("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]),
+ ?CT_LOG("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]),
{ok, []} = InfoMFL;
assert_mfl(Socket, MFL) ->
InfoMFL = ssl:connection_information(Socket, [max_fragment_length]),
- ?LOG("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]),
+ ?CT_LOG("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]),
{ok, [{max_fragment_length, ConnMFL}]} = InfoMFL,
ConnMFL = MFL.
-define(BIG_BUF, 10000000).
@@ -4146,3 +4237,54 @@ curve_default(eddsa) ->
ed25519;
curve_default(_) ->
?DEFAULT_CURVE.
+
+trace() ->
+ ssl_trace:start(fun ct:pal/2, []),
+ ssl_trace:on().
+
+handle_trace(rle,
+ {call, {?MODULE, init_openssl_server, [Mode, ResponderPort | _]}}, Stack0) ->
+ Role = server,
+ {io_lib:format("(*~w) Mode = ~w ResponderPort = ~w",
+ [Role, Mode, ResponderPort]),
+ [{role, Role} | Stack0]}.
+
+
+ktls_os() ->
+ inet_tls_dist:ktls_os().
+
+%% Set UserLand Protocol
+ktls_set_ulp(Socket, OS) ->
+ inet_tls_dist:set_ktls_ulp(
+ #{ socket => Socket,
+ setopt_fun => fun inet_tls_dist:inet_ktls_setopt/3,
+ getopt_fun => fun inet_tls_dist:inet_ktls_getopt/3 },
+ OS).
+
+ktls_set_cipher(Socket, OS, TxRx, Seed) ->
+ TLS_version = ?TLS_1_3,
+ TLS_cipher = ?TLS_AES_256_GCM_SHA384,
+ TLS_IV = binary:copy(<<(Seed + 0)>>, 8),
+ TLS_KEY = binary:copy(<<(Seed + 1)>>, 32),
+ TLS_SALT = binary:copy(<<(Seed + 2)>>, 4),
+ KtlsInfo =
+ #{ socket => Socket,
+ tls_version => TLS_version,
+ cipher_suite => TLS_cipher,
+ setopt_fun => fun inet_tls_dist:inet_ktls_setopt/3,
+ getopt_fun => fun inet_tls_dist:inet_ktls_getopt/3 },
+ CipherState =
+ #cipher_state{
+ key = TLS_KEY,
+ iv = <<TLS_SALT/binary, TLS_IV/binary>> },
+ inet_tls_dist:set_ktls_cipher(KtlsInfo, OS, CipherState, 0, TxRx).
+
+ct_pal_file(FilePath) ->
+ case file:read_file(FilePath) of
+ {ok, Binary} ->
+ ?CT_PAL("~s ~pB~n~s",
+ [FilePath, filelib:file_size(FilePath), Binary]);
+ _ ->
+ ?CT_PAL("Failed to log ~s", [FilePath]),
+ ok
+ end.
diff --git a/lib/ssl/test/ssl_test_lib.hrl b/lib/ssl/test/ssl_test_lib.hrl
index 817e3e0904..d6d928610e 100644
--- a/lib/ssl/test/ssl_test_lib.hrl
+++ b/lib/ssl/test/ssl_test_lib.hrl
@@ -1,6 +1,16 @@
--define(FORMAT, "(~s ~p:~p in ~p) ").
--define(ARGS, [erlang:pid_to_list(self()), ?MODULE, ?LINE, ?FUNCTION_NAME]).
--define(LOG(F), ct:log(?FORMAT ++ F, ?ARGS, [esc_chars])).
--define(LOG(F, Args), ct:log(?FORMAT ++ F, ?ARGS ++ Args, [esc_chars])).
--define(PAL(F, Args), ct:pal(?FORMAT ++ F, ?ARGS ++ Args)).
--define(FAIL(F, Args), ct:fail(?FORMAT ++ F, ?ARGS ++ Args)).
+-define(SSL_TEST_LIB_FORMAT, "(~s ~p:~p in ~p) ").
+-define(SSL_TEST_LIB_ARGS,
+ [erlang:pid_to_list(self()), ?MODULE, ?LINE, ?FUNCTION_NAME]).
+-define(CT_LOG(F),
+ (ct:log(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS, [esc_chars]))).
+-define(CT_LOG(F, Args),
+ (ct:log(
+ ?SSL_TEST_LIB_FORMAT ++ F,
+ ?SSL_TEST_LIB_ARGS ++ Args,
+ [esc_chars]))).
+-define(CT_PAL(F),
+ (ct:pal(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS))).
+-define(CT_PAL(F, Args),
+ (ct:pal(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS ++ Args))).
+-define(CT_FAIL(F, Args),
+ (ct:fail(?SSL_TEST_LIB_FORMAT ++ F, ?SSL_TEST_LIB_ARGS ++ Args))).
diff --git a/lib/ssl/test/ssl_trace_SUITE.erl b/lib/ssl/test/ssl_trace_SUITE.erl
new file mode 100644
index 0000000000..c33b29e90c
--- /dev/null
+++ b/lib/ssl/test/ssl_trace_SUITE.erl
@@ -0,0 +1,493 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2022-2023. 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_trace_SUITE).
+
+-include("ssl_test_lib.hrl").
+-include_lib("common_test/include/ct.hrl").
+-include_lib("ssl/src/ssl_api.hrl").
+
+-export([suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+-export([tc_basic/0,
+ tc_basic/1,
+ tc_no_trace/0,
+ tc_no_trace/1,
+ tc_api_profile/0,
+ tc_api_profile/1,
+ tc_rle_profile/0,
+ tc_rle_profile/1,
+ tc_budget_option/0,
+ tc_budget_option/1,
+ tc_file_option/0,
+ tc_file_option/1,
+ tc_write/0,
+ tc_write/1,
+ tc_check_profiles/0,
+ tc_check_profiles/1]).
+-define(TRACE_FILE, "ssl_trace.txt").
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+suite() -> [{ct_hooks,[ts_install_cth]},
+ {timetrap,{seconds,60}}].
+
+all() -> [tc_basic, tc_no_trace, tc_api_profile, tc_rle_profile,
+ tc_budget_option, tc_write, tc_file_option, tc_check_profiles].
+
+init_per_suite(Config) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ ssl_test_lib:make_rsa_cert(Config)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_testcase(_TC, Config) ->
+ Config.
+
+end_per_testcase(_TC, Config) ->
+ ssl_trace:stop(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+tc_basic() ->
+ [{doc, "Basic test of ssl_trace API"}].
+tc_basic(_Config) ->
+ {ok, L0} = ssl_trace:start(),
+ true = is_pid(whereis(ssl_trace)),
+ true = is_list(L0),
+ {ok,L0} = ssl_trace:on(),
+ {ok,L0} = ssl_trace:on(),
+ L0 = ssl_trace:is_on(),
+ [] = ssl_trace:is_off(),
+
+ L1 = [hd(L0)],
+ L2 = tl(L0),
+ {ok,L1} = ssl_trace:off(L2),
+
+ L1 = ssl_trace:is_on(),
+ L2 = ssl_trace:is_off(),
+
+ {ok,[]} = ssl_trace:off(),
+ {ok,[]} = ssl_trace:off(),
+
+ [] = ssl_trace:is_on(),
+ L0 = ssl_trace:is_off(),
+ ok = ssl_trace:stop(),
+ undefined = whereis(ssl_trace),
+
+ {ok, [api, crt, csp, hbn, kdt, rle, ssn]} = ssl_trace:start(),
+ {ok, [api]} = ssl_trace:on(api),
+ {ok, []} = ssl_trace:off(api),
+ ok = ssl_trace:stop(),
+ ok.
+
+tc_no_trace() ->
+ [{doc, "Verify there are no traces if not enabled"}].
+tc_no_trace(Config) ->
+ Ref = ssl_trace_start(),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ ExpectedTraces =
+ #{call => [], processed => [], exception_from => [], return_from => []},
+ ExpectedTraces = receive_map(Ref),
+ ok = ssl_trace:stop(),
+ ok.
+
+tc_api_profile() ->
+ [{doc, "Verify traces for 'api' trace profile"}].
+tc_api_profile(Config) ->
+ On = [api, rle],
+ Off = [],
+ TracesAfterConnect =
+ #{
+ call =>
+ [{" (server) -> ssl:handshake/2", ssl, handshake},
+ {" (server) -> ssl_gen_statem:initial_hello/3",
+ ssl_gen_statem, initial_hello},
+ {" (client) -> ssl_gen_statem:initial_hello/3",
+ ssl_gen_statem, initial_hello}],
+ return_from =>
+ [{" (server) <- ssl:listen/2 returned", ssl, listen},
+ {" (server) <- ssl_gen_statem:initial_hello/3 returned",
+ ssl_gen_statem, initial_hello},
+ {" (client) <- ssl_gen_statem:initial_hello/3 returned",
+ ssl_gen_statem, initial_hello},
+ {" (client) <- ssl_gen_statem:connect/8 returned",
+ ssl_gen_statem, connect},
+ {" (client) <- ssl:connect/3 returned", ssl, connect},
+ {" (server) <- ssl:handshake/2 returned", ssl, handshake},
+ {" (client) <- tls_sender:init/3 returned", tls_sender, init},
+ {" (server) <- tls_sender:init/3 returned", tls_sender, init}],
+ processed =>
+ ["rle ('?') -> ssl_gen_statem:init/1 (*client)",
+ "rle ('?') -> ssl_gen_statem:init/1 (*server)",
+ "rle ('?') -> ssl:listen/2 (*server) Args",
+ "rle ('?') -> ssl:connect/3 (*client) Args",
+ "rle ('?') -> tls_sender:init/3 (*server)",
+ "rle ('?') -> tls_sender:init/3 (*client)",
+ "api (client) -> ssl_gen_statem:connect/8"]},
+ TracesAfterDisconnect =
+ #{
+ call =>
+ [{" (client) -> ssl:close/1", ssl, close},
+ {" (client) -> ssl:close/1", ssl, close},
+ {" (client) -> ssl_gen_statem:close/2", ssl_gen_statem, close},
+ {" (client) -> ssl_gen_statem:terminate_alert/1",
+ ssl_gen_statem, terminate_alert},
+ {" (server) -> ssl:close/1", ssl, close},
+ {" (server) -> ssl_gen_statem:close/2", ssl_gen_statem, close},
+ {" (server) -> ssl_gen_statem:terminate_alert/1",
+ ssl_gen_statem, terminate_alert}],
+ return_from =>
+ [{" (client) <- ssl:close/1 returned", ssl, close},
+ {" (client) <- ssl:close/1 returned", ssl, close},
+ {" (client) <- ssl_gen_statem:close/2 returned",
+ ssl_gen_statem, close},
+ {" (client) <- ssl_gen_statem:terminate_alert/1 returned",
+ ssl_gen_statem, terminate_alert},
+ {" (server) <- ssl:close/1 returned", ssl, close},
+ {" (server) <- ssl_gen_statem:close/2 returned",
+ ssl_gen_statem, close},
+ {" (server) <- ssl_gen_statem:terminate_alert/1 returned",
+ ssl_gen_statem, terminate_alert}],
+ exception_from =>
+ [{" (server) exception_from ssl_gen_statem:init/1 {exit,{shutdown,normal}}",
+ ssl_gen_statem, init},
+ {" (client) exception_from ssl_gen_statem:init/1 {exit,{shutdown,normal}}",
+ ssl_gen_statem, init}]},
+ Ref = ssl_trace_start(),
+ {ok, On} = ssl_trace:on(On),
+ Delta = On -- Off,
+ {ok, Delta} = ssl_trace:off(Off),
+ [Server, Client] = ssl_connect(Config),
+ UnhandledTraceCnt1 =
+ #{call => 2, processed => 0, exception_from => no_trace_received,
+ return_from => 2},
+ check_trace_map(Ref, TracesAfterConnect, UnhandledTraceCnt1),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ UnhandledTraceCnt2 =
+ #{call => 0, processed => no_trace_received, exception_from => 0,
+ return_from => 0},
+ check_trace_map(Ref, TracesAfterDisconnect, UnhandledTraceCnt2),
+ ssl_trace:stop(),
+ ok.
+
+tc_rle_profile() ->
+ [{doc, "Verify traces for 'rle' trace profile"}].
+tc_rle_profile(Config) ->
+ On = [rle],
+ ExpectedTraces =
+ #{
+ call =>
+ [],
+ return_from =>
+ [{" (client) <- ssl:connect/3 returned", ssl, connect},
+ {" (server) <- ssl:listen/2 returned", ssl, listen},
+ {" (client) <- tls_sender:init/3 returned", tls_sender, init},
+ {" (server) <- tls_sender:init/3 returned", tls_sender, init}],
+ processed =>
+ ["rle ('?') -> ssl:listen/2 (*server) Args =",
+ "rle ('?') -> ssl:connect/3 (*client) Args",
+ "rle ('?') -> ssl_gen_statem:init/1 (*server) Args = [[server",
+ "rle ('?') -> ssl_gen_statem:init/1 (*client) Args = [[client",
+ "rle ('?') -> tls_sender:init/3 (*server)",
+ "rle ('?') -> tls_sender:init/3 (*client)"]},
+ Ref = ssl_trace_start(),
+ {ok, On} = ssl_trace:on(On),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ UnhandledTraceCnt =
+ #{call => no_trace_received, processed => 0, exception_from => 2,
+ return_from => 0},
+ check_trace_map(Ref, ExpectedTraces, UnhandledTraceCnt),
+ ssl_trace:stop(),
+ ok.
+
+tc_budget_option() ->
+ [{doc, "Verify that budget option limits amount of traces"}].
+tc_budget_option(Config) ->
+ Ref = ssl_trace_start(make_ref(), [{budget, 10}]),
+ {ok, [api,rle]} = ssl_trace:on([api,rle]),
+ ssl_trace:write("Not a trace from dbg - not included in budget", []),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ CountReceived = fun(Reference) ->
+ ReceiveStats = check_trace_map(Reference, #{}),
+ ReceivedNumbers =
+ lists:filter(fun is_number/1,
+ maps:values(ReceiveStats)),
+ lists:sum(ReceivedNumbers)
+ end,
+ ssl_trace:stop(),
+ ExpectedTraceCnt = 10,
+ ActualTraceCnt = CountReceived(Ref),
+ case ExpectedTraceCnt == ActualTraceCnt of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected ~w traces, but found ~w",
+ [ExpectedTraceCnt, ActualTraceCnt])
+ end.
+
+tc_file_option() ->
+ [{doc, "Verify that file option redirects traces to file"}].
+tc_file_option(Config) ->
+ _Ref = ssl_trace_start(make_ref(), [{budget, 10}, file]),
+ {ok, [api,rle]} = ssl_trace:on([api,rle]),
+ [Server, Client] = ssl_connect(Config),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ ActualTraceCnt = count_line(?TRACE_FILE),
+ ExpectedTraceCnt = 11, %% budget + 1 message about end of budget
+ ssl_trace:stop(),
+ case ExpectedTraceCnt == ActualTraceCnt of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected ~w traces, but found ~w",
+ [ExpectedTraceCnt, ActualTraceCnt])
+ end.
+
+tc_write() ->
+ [{doc, "Verify that custom messages can be written"}].
+tc_write(_Config) ->
+ _Ref = ssl_trace_start(make_ref(), [{budget, 10}, file]),
+ {ok, [api,rle]} = ssl_trace:on([api,rle]),
+ ssl_trace:write("Custom trace message ~w", [msg]),
+ ActualTraceCnt = count_line(?TRACE_FILE),
+ ExpectedTraceCnt = 1,
+ ssl_trace:stop(),
+ case ExpectedTraceCnt == ActualTraceCnt of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected ~w traces, but found ~w",
+ [ExpectedTraceCnt, ActualTraceCnt])
+ end.
+
+tc_check_profiles() ->
+ [{doc, "Verify that trace profile contain valid functions"}].
+tc_check_profiles(_Config) ->
+ CheckFun =
+ fun(Profile, Module, Fun, DefinedFunctions) ->
+ case lists:member(Fun, DefinedFunctions) of
+ true -> ok;
+ _ ->
+ {F, A} = Fun,
+ ct:fail("~w:~w/~w from '~w' trace profile not found",
+ [Module, F, A, Profile])
+ end
+ end,
+ CheckModule =
+ fun(Profile, {Module, Funs}) ->
+ DefinedFunctions = Module:module_info(functions),
+ [CheckFun(Profile, Module, F, DefinedFunctions) ||
+ F <- Funs]
+ end,
+ CheckTProfile =
+ fun({Profile, _, _, ModFunsTuples}) ->
+ [CheckModule(Profile, MFTuple) ||
+ MFTuple <- ModFunsTuples]
+ end,
+ [CheckTProfile(P) || P <- ssl_trace:trace_profiles()],
+ ok.
+
+%%%----------------------------------------------------------------
+ssl_trace_start() ->
+ ssl_trace_start(make_ref(), []).
+
+ssl_trace_start(Ref, TraceOpts) ->
+ TestProcess = self(),
+ {ok, [_|_]} = ssl_trace:start(fun(Format,Args) ->
+ ct:log(Format, Args),
+ TestProcess ! {Ref, Args}
+ end,
+ TraceOpts),
+ Ref.
+
+receive_map(Ref) ->
+ Empty = #{call => [], return_from => [], exception_from => [],
+ processed => []},
+ receive_map(Ref, Empty).
+
+receive_map(Ref,
+ Map = #{call := Call, return_from := Return,
+ exception_from := Exception, processed := Processed}) ->
+ receive
+ {Ref, Msg = [_, {call, {_, _, _}}, _]} ->
+ receive_map(Ref, Map#{call => [Msg|Call]});
+ {Ref, Msg = [_, {return_from, {_, _, _}, _}, _]} ->
+ receive_map(Ref, Map#{return_from => [Msg|Return]});
+ {Ref, Msg = [_, {exception_from, {_, _, _}, _}, _]} ->
+ receive_map(Ref, Map#{exception_from => [Msg|Exception]});
+ {Ref, Msg = [_Timestamp, _Pid, _ExpectString]} ->
+ %% processed means a trace was processed by Module:handle_trace
+ %% function and is not received as a trace tuple
+ receive_map(Ref, Map#{processed => [Msg|Processed]})
+ after 5000 ->
+ Map
+ end.
+
+check_trace_map(Ref, ExpectedTraces) ->
+ Received = receive_map(Ref),
+ L = [check_key(Type, ExpectedTraces, maps:get(Type, Received)) ||
+ Type <- maps:keys(Received)],
+ maps:from_list(L).
+
+check_trace_map(Ref, ExpectedTraces, ExpectedRemainders) ->
+ ActualRemainders = check_trace_map(Ref, ExpectedTraces),
+ case ExpectedRemainders == ActualRemainders of
+ true ->
+ ok;
+ _ ->
+ ?CT_FAIL("Expected trace remainders = ~w ~n"
+ "Actual trace remainders = ~w",
+ [ExpectedRemainders, ActualRemainders])
+ end.
+
+check_key(Type, ExpectedTraces, ReceivedPerType) ->
+ ReceivedPerTypeCnt = length(ReceivedPerType),
+ ?CT_LOG("Received Type = ~w Messages# = ~w", [Type, ReceivedPerTypeCnt]),
+ case ReceivedPerTypeCnt > 0 of
+ true ->
+ ExpectedPerType = maps:get(Type, ExpectedTraces, []),
+ ExpectedPerTypeCnt = length(ExpectedPerType),
+ check_trace(Type, ExpectedPerType, ReceivedPerType),
+ {Type, ReceivedPerTypeCnt - ExpectedPerTypeCnt};
+ _ ->
+ {Type, no_trace_received}
+ end.
+
+-define(CHECK_TRACE(PATTERN, Expected),
+ fun({ExpectedString, Module, Function}) ->
+ P2 = fun(Received) ->
+ PATTERN = Received,
+ SearchResult =
+ string:str(lists:flatten(Txt), ExpectedString),
+ case {Module == M, Function == F, SearchResult > 0} of
+ {true, true, true} ->
+ true;
+ _ -> false
+ end
+ end,
+ Result = lists:any(P2, ReceivedPerType),
+ case Result of
+ false ->
+ F = "Trace not found: {~s, ~w, ~w}",
+ ?CT_FAIL(F, [ExpectedString, Module, Function]);
+ _ -> ok
+ end,
+ Result
+ end).
+
+-define(CHECK_PROCESSED_TRACE(PATTERN, Expected),
+ fun(ExpectedString) ->
+ P2 = fun(Received) ->
+ PATTERN = Received,
+ SearchResult =
+ string:str(lists:flatten(Txt), ExpectedString),
+ SearchResult > 0
+ end,
+ Result = lists:any(P2, ReceivedPerType),
+ case Result of
+ false ->
+ F = "Processed trace not found: ~s",
+ ?CT_FAIL(F, [ExpectedString]);
+ _ -> ok
+ end,
+ Result
+ end).
+
+check_trace(call, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_TRACE([Txt, {call, {M, F, _Args}}, _], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(return_from, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_TRACE([Txt, {return_from, {M, F, _Args}, _Return}, _], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(exception_from, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_TRACE([Txt, {exception_from, {M, F, _Args}, _Return}, _], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(processed, ExpectedPerType, ReceivedPerType) ->
+ P1 = ?CHECK_PROCESSED_TRACE([_Timestamp, _Pid, Txt], Expected),
+ true = lists:all(P1, ExpectedPerType);
+check_trace(Type, _ExpectedPerType, _ReceivedPerType) ->
+ ?CT_FAIL("Type = ~w not checked", [Type]),
+ ok.
+
+count_line(Filename) ->
+ case file:open(Filename, [read]) of
+ {ok, IoDevice} ->
+ Count = count_line(IoDevice, 0),
+ file:close(IoDevice),
+ Count;
+ {error, Reason} ->
+ ?CT_PAL("~s open error reason:~s~n", [Filename, Reason]),
+ ct:fail(Reason)
+ end.
+
+count_line(IoDevice, Count) ->
+ case file:read_line(IoDevice) of
+ {ok, _} -> count_line(IoDevice, Count+1);
+ eof -> Count
+ end.
+
+ssl_connect(Config) when is_list(Config) ->
+ ?CT_LOG("Establishing connection for producing traces", []),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_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, []}},
+ {options, [{keepalive, true},{active, 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, []}},
+ {options, [{keepalive, true},{active, false}
+ | ClientOpts]}]),
+ ?CT_LOG("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]),
+ [Server, Client].
diff --git a/lib/ssl/test/ssl_use_srtp_SUITE.erl b/lib/ssl/test/ssl_use_srtp_SUITE.erl
new file mode 100644
index 0000000000..a3397ce403
--- /dev/null
+++ b/lib/ssl/test/ssl_use_srtp_SUITE.erl
@@ -0,0 +1,176 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2023. 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_use_srtp_SUITE).
+
+-behaviour(ct_suite).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/inet.hrl").
+
+%% Callback functions
+-export([all/0,
+ groups/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_group/2,
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2]).
+
+%% Testcases
+-export([srtp_profiles/1,
+ srtp_mki/1
+ ]).
+
+-define(TIMEOUT, {seconds, 6}).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [
+ {group, 'dtlsv1.2'},
+ {group, 'dtlsv1'}
+ ].
+
+groups() ->
+ [
+ {'dtlsv1.2', [], use_srtp_tests()},
+ {'dtlsv1', [], use_srtp_tests()}
+ ].
+
+use_srtp_tests() ->
+ [
+ srtp_profiles,
+ srtp_mki
+ ].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl_test_lib:clean_start(),
+ {#{server_config := _ServerConf,
+ client_config := ClientConf},
+ #{server_config := _LServerConf,
+ client_config := LClientConf}} = ssl_test_lib:make_rsa_sni_configs(),
+ %% RSA certs files needed by *dot cases
+ ssl_test_lib:make_rsa_cert([{client_opts, ClientConf},
+ {client_local_opts, LClientConf}
+ | Config0])
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+init_per_group(GroupName, Config) ->
+ ssl_test_lib:init_per_group(GroupName, Config).
+
+end_per_group(GroupName, Config) ->
+ ssl_test_lib:end_per_group(GroupName, Config).
+
+end_per_suite(_) ->
+ ssl:stop(),
+ application:stop(crypto).
+
+init_per_testcase(_TestCase, Config) ->
+ ssl_test_lib:ct_log_supported_protocol_versions(Config),
+ ct:timetrap(?TIMEOUT),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+srtp_profiles(Config) ->
+ % Client sends a list of SRTP profiles it supports in client_hello
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClentSrtpOpts = [{use_srtp, #{protection_profiles => [<<0,1>>,<<0,2>>,<<0,5>>]}}],
+ ClientOpts = ClentSrtpOpts ++ [{handshake, hello}] ++ ClientOpts0,
+ ClientContOpts = [{continue_options, [{want_ext, self()}]}],
+ % Server responds with a single chosen profile in server_hello
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = [{handshake, hello}] ++ ServerOpts0,
+ ServerSrtpOts = [{use_srtp, #{protection_profiles => [<<0,2>>]}}],
+ ServerContOpts = [{continue_options, [{want_ext, self()}|ServerSrtpOts]}],
+
+ Server = ssl_test_lib:start_server(ServerContOpts,
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{port, Port} | ClientContOpts],
+ [{client_opts, ClientOpts} | Config]),
+
+ receive
+ {Server, {ext, C2SExt}} ->
+ C2SSRTP = maps:get(use_srtp, C2SExt),
+ #{protection_profiles := [<<0,1>>,<<0,2>>,<<0,5>>]} = C2SSRTP,
+ #{mki := <<>>} = C2SSRTP,
+ ssl_test_lib:close(Server)
+ end,
+ receive
+ {Client, {ext, S2CExt}} ->
+ S2CSRTP = maps:get(use_srtp, S2CExt),
+ #{protection_profiles := [<<0,2>>]} = S2CSRTP,
+ #{mki := <<>>} = S2CSRTP,
+ ssl_test_lib:close(Client)
+ end,
+ ok.
+
+
+srtp_mki(Config) ->
+ % Client sends some MKI in a client_hello
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ClientSrtpOpts = [{use_srtp, #{protection_profiles => [<<0,1>>,<<0,2>>,<<0,5>>],
+ mki => <<"client_mki">>}}],
+ ClientOpts = ClientSrtpOpts ++ [{handshake, hello}] ++ ClientOpts0,
+ ClientContOpts = [{continue_options, [{want_ext, self()}]}],
+ % Server responds with its own MKI just to ensure it is delivered to the client
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = [{handshake, hello}] ++ ServerOpts0,
+ ServerSrtpOpts = [{use_srtp, #{protection_profiles => [<<0,2>>],
+ mki => <<"server_mki">>}}],
+ ServerContOpts = [{continue_options, [{want_ext, self()}|ServerSrtpOpts]}],
+
+ Server = ssl_test_lib:start_server(ServerContOpts,
+ [{server_opts, ServerOpts} | Config]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client(
+ [{port, Port}, {options, ClientOpts} | ClientContOpts], Config),
+
+ receive
+ {Server, {ext, C2SExt}} ->
+ C2SSRTP = maps:get(use_srtp, C2SExt),
+ #{mki := <<"client_mki">>} = C2SSRTP,
+ ssl_test_lib:close(Server)
+ end,
+ receive
+ {Client, {ext, S2CExt}} ->
+ S2CSRTP = maps:get(use_srtp, S2CExt),
+ #{mki := <<"server_mki">>} = S2CSRTP,
+ ssl_test_lib:close(Client)
+ end,
+ ok.
diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl
index 75819d0565..c08bd90a02 100644
--- a/lib/ssl/test/tls_1_3_record_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_record_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
@@ -174,9 +174,9 @@ encode_decode(_Config) ->
146,152,146,151,107,126,216,210,9,93,0,0>>],
{[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates),
- CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded},
+ CipherText = #ssl_tls{type = 23, version = ?TLS_1_2, fragment = Encoded},
- {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} =
+ {#ssl_tls{type = 22, version = ?TLS_1_3, fragment = DecodedText}, _} =
tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates),
DecodedText = iolist_to_binary(PlainText),
@@ -260,7 +260,7 @@ encode_decode(_Config) ->
01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"),
{CHEncrypted, _} =
- tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull),
+ tls_record:encode_handshake(ClientHello, ?TLS_1_3, ConnStatesNull),
ClientHelloRecord = iolist_to_binary(CHEncrypted),
%% {server} extract secret "early":
@@ -515,7 +515,7 @@ encode_decode(_Config) ->
cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"),
{SHEncrypted, _} =
- tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull),
+ tls_record:encode_handshake(ServerHello, ?TLS_1_3, ConnStatesNull),
ServerHelloRecord = iolist_to_binary(SHEncrypted),
%% {server} derive write traffic keys for handshake data:
@@ -685,7 +685,7 @@ encode_decode(_Config) ->
FinishedHS = #finished{verify_data = FinishedVerifyData},
- FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}),
+ FinishedIOList = tls_handshake:encode_handshake(FinishedHS, ?TLS_1_3),
FinishedHSBin = iolist_to_binary(FinishedIOList),
%% {server} derive secret "tls13 c ap traffic":
@@ -907,7 +907,7 @@ encode_decode(_Config) ->
CFinished = #finished{verify_data = CFinishedVerifyData},
- CFinishedIOList = tls_handshake:encode_handshake(CFinished, {3,4}),
+ CFinishedIOList = tls_handshake:encode_handshake(CFinished, ?TLS_1_3),
CFinishedBin = iolist_to_binary(CFinishedIOList),
%% {client} derive write traffic keys for application data:
@@ -1054,7 +1054,7 @@ encode_decode(_Config) ->
ticket_nonce = Nonce,
ticket = Ticket,
extensions = _Extensions
- } = tls_handshake:decode_handshake({3,4}, NWT, TicketBody),
+ } = tls_handshake:decode_handshake(?TLS_1_3, NWT, TicketBody),
%% ResPRK = resumption master secret
ResExpanded = tls_v1:pre_shared_key(ResPRK, Nonce, HKDFAlgo),
@@ -1288,7 +1288,7 @@ encode_decode(_Config) ->
<<?BYTE(CH), ?UINT24(_Length), ClientHelloBody/binary>> = ClientHelloRecord,
#client_hello{extensions = #{pre_shared_key := PreSharedKey}} =
- tls_handshake:decode_handshake({3,4}, CH, ClientHelloBody),
+ tls_handshake:decode_handshake(?TLS_1_3, CH, ClientHelloBody),
#pre_shared_key_client_hello{
offered_psks = #offered_psks{
diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl
index 8a3ff288f7..b5aa0d3cad 100644
--- a/lib/ssl/test/tls_1_3_version_SUITE.erl
+++ b/lib/ssl/test/tls_1_3_version_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -57,7 +57,9 @@
middle_box_tls12_enabled_client/0,
middle_box_tls12_enabled_client/1,
middle_box_client_tls_v2_session_reused/0,
- middle_box_client_tls_v2_session_reused/1
+ middle_box_client_tls_v2_session_reused/1,
+ renegotiate_error/0,
+ renegotiate_error/1
]).
@@ -90,7 +92,8 @@ tls_1_3_1_2_tests() ->
tls12_client_tls_server,
middle_box_tls13_client,
middle_box_tls12_enabled_client,
- middle_box_client_tls_v2_session_reused
+ middle_box_client_tls_v2_session_reused,
+ renegotiate_error
].
legacy_tests() ->
[tls_client_tls10_server,
@@ -329,6 +332,26 @@ middle_box_client_tls_v2_session_reused(Config) when is_list(Config) ->
{reuse_session, {SessionId, SessData}} | ClientOpts]}]),
{ok,[{session_id, SessionId}]} = ssl:connection_information(CSock1, [session_id]).
+renegotiate_error() ->
+ [{doc, "Test that an error is returned when ssl:renegotiate/1 is called on a connection running TLS-1.3"}].
+renegotiate_error(Config) when is_list(Config) ->
+ {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{versions, ['tlsv1.3']} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Options = [{versions, ['tlsv1.3']} | ClientOpts],
+ case ssl:connect(Hostname, Port, Options) of
+ {ok, Socket} ->
+ {error, notsup} = ssl:renegotiate(Socket);
+ {error, Reason} ->
+ ct:fail(Reason)
+ end.
+
%%--------------------------------------------------------------------
%% Internal functions and callbacks -----------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl
index ccba623861..405bb74b2d 100644
--- a/lib/ssl/test/tls_api_SUITE.erl
+++ b/lib/ssl/test/tls_api_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -296,7 +296,7 @@ tls_upgrade_new_opts_with_sni_fun() ->
[{doc,"Test that you can upgrade an tcp connection to an ssl connection with new versions option provided by sni_fun"}].
tls_upgrade_new_opts_with_sni_fun(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
TcpOpts = [binary, {reuseaddr, true}],
@@ -322,8 +322,7 @@ tls_upgrade_new_opts_with_sni_fun(Config) when is_list(Config) ->
{from, self()},
{mfa, {?MODULE, upgrade_result, []}},
{tcp_options, [binary]},
- {ssl_options, [{verify, verify_peer},
- {versions, [Version |NewVersions]},
+ {ssl_options, [{versions, [Version |NewVersions]},
{ciphers, Ciphers},
{server_name_indication, Hostname} | ClientOpts]}]),
@@ -789,10 +788,10 @@ tls_reject_warning_alert_in_initial_hs() ->
[{doc,"Test sending warning ALERT instead of client hello"}].
tls_reject_warning_alert_in_initial_hs(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+ {_Clientnode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
{Major, Minor} = case ssl_test_lib:protocol_version(Config, tuple) of
- {3,4} ->
- {3,3};
+ ?TLS_1_3 ->
+ ?TLS_1_2;
Other ->
Other
end,
@@ -815,8 +814,8 @@ tls_reject_fake_warning_alert_in_initial_hs(Config) when is_list(Config) ->
ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
{Major, Minor} = case ssl_test_lib:protocol_version(Config, tuple) of
- {3,4} ->
- {3,3};
+ ?TLS_1_3 ->
+ ?TLS_1_2;
Other ->
Other
end,
@@ -841,8 +840,8 @@ tls_app_data_in_initial_hs_state(Config) when is_list(Config) ->
{_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
Version = ssl_test_lib:protocol_version(Config, tuple),
{Major, Minor} = case Version of
- {3,4} ->
- {3,3};
+ ?TLS_1_3 ->
+ ?TLS_1_2;
Other ->
Other
end,
@@ -853,7 +852,7 @@ tls_app_data_in_initial_hs_state(Config) when is_list(Config) ->
Port = ssl_test_lib:inet_port(Server),
{ok, Socket} = gen_tcp:connect("localhost", Port, [{active, false}, binary]),
AppData = case Version of
- {3, 4} ->
+ ?TLS_1_3 ->
<<?BYTE(?APPLICATION_DATA), ?BYTE(3), ?BYTE(3), ?UINT16(4), ?BYTE($F),
?BYTE($O), ?BYTE($O), ?BYTE(?APPLICATION_DATA)>>;
_ ->
diff --git a/lib/ssl/test/tls_server_session_ticket_SUITE.erl b/lib/ssl/test/tls_server_session_ticket_SUITE.erl
index 954afa9fb5..3f5b0f71b2 100644
--- a/lib/ssl/test/tls_server_session_ticket_SUITE.erl
+++ b/lib/ssl/test/tls_server_session_ticket_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2023. 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.
@@ -25,6 +25,7 @@
-include_lib("ssl/src/ssl_cipher.hrl").
-include_lib("ssl/src/ssl_internal.hrl").
-include_lib("ssl/src/tls_handshake_1_3.hrl").
+-include("ssl_record.hrl").
%% Callback functions
-export([all/0,
@@ -43,25 +44,38 @@
main_test/0,
main_test/1,
misc_test/0,
- misc_test/1]).
+ misc_test/1,
+ valid_ticket_older_than_windowsize_test/0,
+ valid_ticket_older_than_windowsize_test/1,
+ certificate_encoding_test/0,
+ certificate_encoding_test/1]).
--define(LIFETIME, 1). % tickets expire after 1s
+-define(LIFETIME, 3). % tickets expire after 3s
-define(TICKET_STORE_SIZE, 1).
-define(MASTER_SECRET, "master_secret").
-define(PRF, sha).
--define(VERSION, {3,4}).
+-define(VERSION, ?TLS_1_3).
-define(PSK, <<15,168,18,43,216,33,227,142,114,190,70,183,137,57,64,64,66,152,115,94>>).
+-define(WINDOW_SIZE, 1).
+-define(SEED, <<1,2,3,4,5>>).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
all() ->
- [{group, stateful}, {group, stateless}, {group, stateless_antireplay}].
+ [{group, stateful},
+ {group, stateful_with_cert},
+ {group, stateless},
+ {group, stateless_with_cert},
+ {group, stateless_antireplay}
+ ].
groups() ->
[{stateful, [], [main_test, expired_ticket_test, invalid_ticket_test]},
- {stateless, [], [expired_ticket_test, invalid_ticket_test, main_test]},
- {stateless_antireplay, [], [main_test, misc_test]}
+ {stateful_with_cert, [], [main_test, expired_ticket_test, invalid_ticket_test]},
+ {stateless, [], [expired_ticket_test, invalid_ticket_test, main_test, certificate_encoding_test]},
+ {stateless_with_cert, [], [expired_ticket_test, invalid_ticket_test, main_test, certificate_encoding_test]},
+ {stateless_antireplay, [], [main_test, misc_test, valid_ticket_older_than_windowsize_test, certificate_encoding_test]}
].
init_per_suite(Config0) ->
@@ -80,11 +94,13 @@ end_per_suite(_Config) ->
init_per_group(stateless_antireplay, Config) ->
check_environment([{server_session_tickets, stateless},
- {anti_replay, {10, 20, 30}}]
+ {anti_replay, {?WINDOW_SIZE, 20, 30}}]
++ Config);
-init_per_group(Group = stateless, Config) ->
+init_per_group(Group, Config)
+ when Group == stateless orelse Group == stateless_with_cert ->
check_environment([{server_session_tickets, Group} | Config]);
-init_per_group(Group = stateful, Config) ->
+init_per_group(Group, Config)
+ when Group == stateful orelse Group == stateful_with_cert ->
[{server_session_tickets, Group} | Config].
end_per_group(_GroupName, Config) ->
@@ -92,10 +108,17 @@ end_per_group(_GroupName, Config) ->
init_per_testcase(_TestCase, Config) ->
{ok, ListenSocket} = gen_tcp:listen(0, [{active, false}]),
+ AntiReplay = ?config(anti_replay, Config),
{ok, Pid} = tls_server_session_ticket:start_link(
ListenSocket, ?config(server_session_tickets, Config),
?LIFETIME, ?TICKET_STORE_SIZE, _MaxEarlyDataSize = 100,
- ?config(anti_replay, Config)),
+ AntiReplay, ?SEED),
+ % For all anti-replay test-cases we will sleep longer than the warmup period
+ case AntiReplay of
+ undefined -> undefined;
+ _ ->
+ ct:sleep({seconds, 2 * ?LIFETIME})
+ end,
[{server_pid, Pid}, {listen_socket, ListenSocket} | Config].
end_per_testcase(_TestCase, Config) ->
@@ -113,17 +136,17 @@ main_test() ->
main_test(Config) when is_list(Config) ->
Pid = ?config(server_pid, Config),
% Fill in GB tree store for stateful setup
- tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
% Reach ticket store size limit - force GB tree pruning
SessionTicket = #new_session_ticket{} =
- tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
TicketRecvTime = erlang:system_time(millisecond),
%% Sleep more than the ticket lifetime (which is in seconds) in
%% milliseconds, to confirm that the client reported age (which is in
%% milliseconds) is compared correctly with the lifetime
ct:sleep(5 * ?LIFETIME),
{HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
- AcceptResponse = {ok, {0, ?PSK}},
+ AcceptResponse = {ok, {0, ?PSK, undefined}},
AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
[iolist_to_binary(HandshakeHist)]),
% check replay attempt result
@@ -137,7 +160,7 @@ invalid_ticket_test() ->
invalid_ticket_test(Config) when is_list(Config) ->
Pid = ?config(server_pid, Config),
#new_session_ticket{ticket=Ticket} =
- tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
Ids = [#psk_identity{identity = <<"wrongidentity">>,
obfuscated_ticket_age = 0},
#psk_identity{identity = Ticket,
@@ -159,7 +182,7 @@ expired_ticket_test() ->
[{doc, "Expired ticket scenario"}].
expired_ticket_test(Config) when is_list(Config) ->
Pid = ?config(server_pid, Config),
- SessionTicket = tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET),
+ SessionTicket = tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
TicketRecvTime = erlang:system_time(millisecond),
ct:sleep({seconds, 2 * ?LIFETIME}),
{HandshakeHist, OFPSKs} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
@@ -167,6 +190,29 @@ expired_ticket_test(Config) when is_list(Config) ->
[iolist_to_binary(HandshakeHist)]),
true = is_process_alive(Pid).
+valid_ticket_older_than_windowsize_test() ->
+ [{doc, "Verify valid ticket handling of tickets older than WindowSize"}].
+
+valid_ticket_older_than_windowsize_test(Config) when is_list(Config) ->
+ Pid = ?config(server_pid, Config),
+ % Fill in GB tree store for stateful setup (Stateless tests also fail without this)
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
+ % Reach ticket store size limit - force GB tree pruning
+ SessionTicket = #new_session_ticket{} =
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
+ TicketRecvTime = erlang:system_time(millisecond),
+ %% Sleep more than the window length (which is in seconds)
+ ct:sleep({seconds, 2 * ?WINDOW_SIZE}),
+ {HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
+ AcceptResponse = {ok, {0, ?PSK, undefined}},
+ AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
+ [iolist_to_binary(HandshakeHist)]),
+ % check replay attempt result
+ ExpReplyResult = get_replay_expected_result(Config, AcceptResponse),
+ ExpReplyResult = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
+ [iolist_to_binary(HandshakeHist)]),
+ true = is_process_alive(Pid).
+
misc_test() ->
[{doc, "Miscellaneous functionality"}].
misc_test(Config) when is_list(Config) ->
@@ -178,6 +224,22 @@ misc_test(Config) when is_list(Config) ->
Pid = tls_server_session_ticket:format_status(not_relevant, Pid),
true = is_process_alive(Pid).
+certificate_encoding_test() ->
+ [{doc, "Verify certifcate encoding/decoding in ticket"}].
+
+certificate_encoding_test(Config) when is_list(Config) ->
+ Pid = ?config(server_pid, Config),
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, undefined),
+ Certificate = crypto:strong_rand_bytes(100),
+ SessionTicket = #new_session_ticket{} =
+ tls_server_session_ticket:new(Pid, ?PRF, ?MASTER_SECRET, Certificate),
+ TicketRecvTime = erlang:system_time(millisecond),
+ {HandshakeHist, OferredPsks} = get_handshake_hist(SessionTicket, TicketRecvTime, ?PSK),
+ AcceptResponse = {ok, {0, ?PSK, Certificate}},
+ AcceptResponse = tls_server_session_ticket:use(Pid, OferredPsks, ?PRF,
+ [iolist_to_binary(HandshakeHist)]),
+ true = is_process_alive(Pid).
+
%%--------------------------------------------------------------------
%% Helpers -----------------------------------------------------------
%%--------------------------------------------------------------------
@@ -214,6 +276,9 @@ get_replay_expected_result(Config, AcceptResponse) ->
stateless ->
% no protection - replayed ticket is accepted
AcceptResponse;
+ stateless_with_cert ->
+ % no protection - replayed ticket is accepted
+ AcceptResponse;
_ ->
{ok, undefined}
end.
@@ -222,6 +287,8 @@ get_alert_reason(Config) ->
case get_group(Config) of
stateful ->
stateful;
+ stateful_with_cert ->
+ stateful;
_ ->
stateless
end.
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 7b821e2bc8..db6de41e50 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 10.9
+SSL_VSN = 10.9.1