diff options
Diffstat (limited to 'lib/ssl')
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 => [<<0,2>>, <<0,5>>]}</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 => [<<0,5>>]}</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 |