diff options
Diffstat (limited to 'lib/ssl')
101 files changed, 9830 insertions, 6314 deletions
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index b95f098931..ee5b40dea8 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -27,6 +27,216 @@ </header> <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 10.2.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Avoid race when the first two upgrade server handshakes + (that is servers that use a gen_tcp socket as input to + ssl:handshake/2,3) start close to each other. Could lead + to that one of the handshakes would fail.</p> + <p> + Own Id: OTP-17190 Aux Id: ERIERL-606 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 10.2.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Avoid that upgrade (from TCP to TLS) servers starts + multiple session cache handlers for the same server. This + applies to Erlang distribution over TLS servers.</p> + <p> + Own Id: OTP-17139 Aux Id: ERL-1458, OTP-16239 </p> + </item> + <item> + <p> + Legacy cipher suites defined before TLS-1.2 (but still + supported) should be possible to use in TLS-1.2. They + where accidentally excluded for available cipher suites + for TLS-1.2 in OTP-23.2.2.</p> + <p> + Own Id: OTP-17174 Aux Id: ERIERL-597 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Enable Erlang distribution over TLS to run TLS-1.3, + although TLS-1.2 will still be default.</p> + <p> + Own Id: OTP-16239 Aux Id: ERL-1458, OTP-17139 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 10.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix CVE-2020-35733 this only affects ssl-10.2 (OTP-23.2). + This vulnerability could enable a man in the middle + attack using a fake chain to a known trusted ROOT. Also + limits alternative chain handling, for handling of + possibly extraneous certs, to improve memory management.</p> + <p> + Own Id: OTP-17098</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add support for AES CCM based cipher suites defined in + RFC 7251</p> + <p> + Also Correct cipher suite name conversion to OpenSSL + names. A few names where corrected earlier in OTP-16267 + For backwards compatible reasons we support usage of + openSSL names for cipher suites. Mostly anonymous suites + names where incorrect, but also some legacy suites.</p> + <p> + Own Id: OTP-17100</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 10.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + SSL's Erlang Distribution Protocol modules inet_tls_dist + and inet6_tls_dist lacked a callback function, so the + start flag "-dist_listen false" did not work, which has + now been fixed.</p> + <p> + Own Id: OTP-15126 Aux Id: ERL-1375 </p> + </item> + <item> + <p> + Correct OpenSSL names for newer cipher suites using DHE + in their name that accidentally got the wrong value when + fixing other older names using EDH instead.</p> + <p> + Own Id: OTP-16267 Aux Id: ERIERL-571, ERIERL-477 </p> + </item> + <item> + <p> + This change improves the handling of DTLS listening + dockets, making it possible to open multiple listeners on + the same port with different IP addresses.</p> + <p> + Own Id: OTP-16849 Aux Id: ERL-1339 </p> + </item> + <item> + <p> + Fix a bug that causes cross-build failure.</p> + <p> + This change excludes the ssl.d dependency file from the + source tarballs.</p> + <p> + Own Id: OTP-16921</p> + </item> + <item> + <p> + This change fixes ssl:peername/1 when called on a DTLS + client socket.</p> + <p> + Own Id: OTP-16923 Aux Id: ERL-1341, PR-2786 </p> + </item> + <item> + <p> + Retain emulation of active once on a closed socket to + behave as before 23.1</p> + <p> + Own Id: OTP-17018 Aux Id: ERL-1409 </p> + </item> + <item> + <p> + Corrected server session cache entry deletion pre + TLS-1.3. May increase session reuse.</p> + <p> + Own Id: OTP-17019 Aux Id: ERL-1412 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Handle extraneous certs in certificate chains as well as + chains that are incomplete but can be reconstructed or + unordered chains. The cert and certfile options will now + accept a list of certificates so that the user may + specify the chain explicitly.</p> + <p> + Also, the default value of the depth option has been + increased to allow longer chains by default.</p> + <p> + Own Id: OTP-16277</p> + </item> + <item> + <p> + This change implements optional NSS-style keylog in + ssl:connection_information/2 for debugging purposes.</p> + <p> + The keylog contains various TLS secrets that can be + loaded in Wireshark to decrypt TLS packets.</p> + <p> + Own Id: OTP-16445 Aux Id: PR-2823 </p> + </item> + <item> + <p> + Use new gen_statem feature of changing callback mode to + improve code maintainability.</p> + <p> + Own Id: OTP-16529</p> + </item> + <item> + <p> + The handling of Service Name Indication has been aligned + with RFC8446.</p> + <p> + Own Id: OTP-16762</p> + </item> + <item> + <p> + Add explicit session reuse option to TLS clients for pre + TLS-1.3 sessions. Also, add documentation to Users Guide + for such sessions.</p> + <p> + Own Id: OTP-16893</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 10.1</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -296,6 +506,33 @@ </section> +<section><title>SSL 9.6.2.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct flow ctrl checks from OTP-16764 to work as + intended. Probably will not have a noticeable affect but + will make connections more well behaved under some + circumstances.</p> + <p> + Own Id: OTP-16837 Aux Id: ERL-1319, OTP-16764 </p> + </item> + <item> + <p> + Fix a bug that causes cross-build failure.</p> + <p> + This change excludes the ssl.d dependency file from the + source tar balls.</p> + <p> + Own Id: OTP-16921</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.6.2.2</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 596fbf45f3..5bd27e1d9a 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -304,15 +304,29 @@ <datatype> <name name="cert"/> <desc> - <p>The DER-encoded users certificate. If this option - is supplied, it overrides option <c>certfile</c>.</p> + <p>The DER-encoded users certificate. Note that the cert option may also + be a list of DER-encoded certificates where the first one is the users + certificate and the rest of the certificates constitutes the + certificate chain. For maximum interoperability the + certificates in the chain should be in the correct order, the + chain will be sent as is to the peer. If chain certificates + are not provided, certificates from <seetype + marker="#client_cacerts">client_cacerts()</seetype>, <seetype + marker="#server_cacerts">server_cacerts()</seetype>, or + <seetype marker="#client_cafile">client_cafile()</seetype>, + <seetype marker="#server_cafile">server_cafile()</seetype> are + used to construct the chain. If this option is supplied, it + overrides option <c>certfile</c>.</p> </desc> </datatype> <datatype> <name name="cert_pem"/> <desc> - <p>Path to a file containing the user certificate on PEM format.</p> + <p>Path to a file containing the user certificate on PEM format or possible several + certificates where the first one is the users certificate and the rest of the certificates + constitutes the certificate chain. For more details see <seetype marker="#cert">cert()</seetype>, + </p> </desc> </datatype> @@ -437,7 +451,7 @@ in a valid certification path. So, if depth is 0 the PEER must be signed by the trusted ROOT-CA directly; if 1 the path can be PEER, CA, ROOT-CA; if 2 the path can be PEER, CA, CA, - ROOT-CA, and so on. The default value is 1.</p> + ROOT-CA, and so on. The default value is 10.</p> </desc> </datatype> @@ -788,6 +802,16 @@ fun(srp, Username :: binary(), UserState :: term()) -> </desc> </datatype> + <datatype> + <name name="keep_secrets"/> + <desc><p>Configures a TLS 1.3 connection for keylogging</p> + <p>In order to retrieve keylog information on a TLS 1.3 connection, it must be configured + in advance to keep the client_random and various handshake secrets.</p> + <p>The keep_secrets functionality is disabled (<c>false</c>) by default.</p> + <p>Added in OTP 23.2</p> + </desc> + </datatype> + <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT</datatype_title> <datatype> @@ -804,8 +828,12 @@ fun(srp, Username :: binary(), UserState :: term()) -> <datatype> <name name="client_reuse_session"/> <desc> - <p>Reuses a specific session earlier saved with the option - <c>{reuse_sessions, save} since OTP-21.3 </c> + <p>Reuses a specific session. The session should be refered by its session id if it is + earlier saved with the option <c>{reuse_sessions, save}</c> since OTP-21.3 or + explicitly specified by its session id and associated data since OTP-22.3. + See also + <seeguide marker="ssl:using_ssl#session-reuse-pre-tls-1.3"> + SSL's Users Guide, Session Reuse pre TLS 1.3</seeguide> </p> </desc> </datatype> @@ -1445,7 +1473,7 @@ fun(srp, Username :: binary(), UserState :: term()) -> Indication extension will be sent, and <seemfa marker="public_key:public_key#pkix_verify_hostname/2">public_key:pkix_verify_hostname/2</seemfa> will be called with the IP-address of the connection as - <c>ReferenceID</c>, which is proably not what you want.</p> + <c>ReferenceID</c>, which is probably not what you want.</p> </note> <p> If the option <c>{handshake, hello}</c> is used the @@ -1550,7 +1578,7 @@ fun(srp, Username :: binary(), UserState :: term()) -> </fsummary> <desc><p>Returns the requested information items about the connection, if they are defined.</p> - <p>Note that client_random, server_random and master_secret are values + <p>Note that client_random, server_random, master_secret and keylog are values that affect the security of connection. Meaningful atoms, not specified above, are the ssl option names.</p> diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml index a1f561323d..aae08a2c9e 100644 --- a/lib/ssl/doc/src/ssl_protocol.xml +++ b/lib/ssl/doc/src/ssl_protocol.xml @@ -137,28 +137,42 @@ </section> <section> - <title>TLS Sessions</title> + <title>TLS Sessions - PRE TLS-1.3</title> - <p>From the TLS RFC: "A TLS session is an association between a - client and a server. Sessions are created by the handshake - protocol. Sessions define a set of cryptographic security - parameters, which can be shared among multiple - connections. Sessions are used to avoid the expensive negotiation - of new security parameters for each connection."</p> + <p>From the TLS RFC: "A TLS session is an association between a + client and a server. Sessions are created by the handshake + protocol. Sessions define a set of cryptographic security + parameters, which can be shared among multiple + connections. Sessions are used to avoid the expensive negotiation + of new security parameters for each connection."</p> - <p>Session data is by default kept by the SSL application in a - memory storage, hence session data is lost at application - restart or takeover. Users can define their own callback module - to handle session data storage if persistent data storage is - required. Session data is also invalidated after 24 hours - from it was saved, for security reasons. The amount of time the - session data is to be saved can be configured.</p> + <p>Session data is by default kept by the SSL application in a + memory storage, hence session data is lost at application + restart or takeover. Users can define their own callback module + to handle session data storage if persistent data storage is + required. Session data is also invalidated when session + database exceeds its limit or 24 hours after being saved + (RFC max lifetime recommendation). The amount of time the + session data is to be saved can be configured.</p> - <p>By default the TLS/DTLS clients try to reuse an available session and - by default the TLS/DTLS servers agree to reuse sessions when clients - ask for it.</p> + <p>By default the TLS/DTLS clients try to reuse an available + session and by default the TLS/DTLS servers agree to reuse + sessions when clients ask for it. See also + <seeguide marker="ssl:using_ssl#session-reuse-pre-tls-1.3"> Session Reuse Pre TLS-1.3 </seeguide> + </p> + </section> + <section> + <title>TLS-1.3 session tickets</title> + + <p>In TLS 1.3 the session reuse is replaced by a new session + tickets mechanism based on the pre shared key concept. This + mechanism also obsoletes the session tickets from RFC5077, not + implemented by this application. See also + <seeguide marker="ssl:using_ssl#session-tickets-and-session-resumption-in-tls-1.3">Session Tickets and Session Resumption in TLS-1.3 </seeguide> + </p> </section> + </chapter> diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml index 5e97c5c20b..6cf25d726f 100644 --- a/lib/ssl/doc/src/standards_compliance.xml +++ b/lib/ssl/doc/src/standards_compliance.xml @@ -297,8 +297,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">server_name (RFC6066)</cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22.2</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -436,8 +436,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">server_name (RFC6066)</cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22.2</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1226,8 +1226,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">server_name (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1293,8 +1293,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">server_name (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1464,6 +1464,18 @@ </row> <row> <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle">Arbitrary certificate chain orderings</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.2</em></cell> + </row> + <row> + <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle">Extraneous certificates in chain</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> + </row> + <row> + <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">status_request (RFC6066)</cell> <cell align="left" valign="middle"><em>NC</em></cell> <cell align="left" valign="middle"></cell> @@ -1551,8 +1563,8 @@ extensions are used to guide certificate selection. As servers MAY require the presence of the "server_name" extension, clients SHOULD send this extension, when applicable.</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> @@ -2028,8 +2040,8 @@ </url> </cell> <cell align="left" valign="middle"><em></em></cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -2070,8 +2082,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Server Name Indication</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> @@ -2139,14 +2151,14 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle"><em>TLS 1.3 ServerHello</em></cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST support the use of the "server_name" extension</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23.2</em></cell> </row> <row> diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml index f29606b419..4a66bf9d90 100644 --- a/lib/ssl/doc/src/using_ssl.xml +++ b/lib/ssl/doc/src/using_ssl.xml @@ -233,6 +233,182 @@ ssl:connect("localhost", 9999, </section> + + <section> + <title>Session Reuse pre TLS 1.3</title> + <p>Clients can request to reuse a session established + by a previous full handshake between that client and server by + sending the id of the session in the initial handshake + message. The server may or may not agree to reuse it. If agreed + the server will send back the id and if not it will send a new + id. The ssl application has several options for handling session + reuse.</p> + + <p>On the client side the ssl application will save session data + to try to automate session reuse on behalf of the client processes + on the Erlang node. Note that only verified sessions will be + saved for security reasons, that is session resumption relies on + the certificate validation to have been run in the original + handshake. To minimize memory consumption only unique sessions + will be saved unless the special <c>save</c> value is specified + for the following option <c> {reuse_sessions, boolean() | + save}</c> in which case a full handhake will be performed and that + specific session will have been saved before the handshake + returns. The session id and even an opaque binary containing the + session data can be retrieved using + <c>ssl:connection_information/1</c> function. A saved session + (guaranteed by the save option) can be explicitly reused using + <c>{reuse_session, SessionId}</c>. Also it is possible for the + client to reuse a session that is not saved by the ssl application + using <c>{reuse_session, {SessionId, SessionData}}</c>.</p> + + <note><p>When using explicit session reuse, it is up to the client + to make sure that the session being reused is for the correct + server and has been verified.</p></note> + + <p>Here follows a client side example, + divide into several steps for readability. + </p> + + <p>Step 1 - Automated Session Reuse</p> + + <code type="erl"> +1> ssl:start(). +ok + +2> {ok, C1} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}]). +{ok,{sslsocket,{gen_tcp,#Port<0.7>,tls_connection,undefined}, ...}} + +3> ssl:connection_information(C1, [session_id]). +{ok,[{session_id,<<95,32,43,22,35,63,249,22,26,36,106, + 152,49,52,124,56,130,192,137,161, + 146,145,164,232,...>>}]} + +%% Reuse session if possible, note that if C2 is really fast the session +%% data might not be available for reuse. +4> {ok, C2} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_sessions, true}]). +{ok,{sslsocket,{gen_tcp,#Port<0.8>,tls_connection,undefined}, ...]}} + +%% C2 got same session ID as client one, session was automatically reused. +5> ssl:connection_information(C2, [session_id]). +{ok,[{session_id,<<95,32,43,22,35,63,249,22,26,36,106, + 152,49,52,124,56,130,192,137,161, + 146,145,164,232,...>>}]} + +</code> + +<p>Step 2- Using <c>save</c> Option </p> + +<code type="erl"> +%% We want save this particular session for reuse although it has the same basis as C1 +6> {ok, C3} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_sessions, save}]). +{ok,{sslsocket,{gen_tcp,#Port<0.9>,tls_connection,undefined}, ...]}} + +%% A full handshake is performed and we get a new session ID +7> {ok, [{session_id, ID}]} = ssl:connection_information(C3, [session_id]). +{ok,[{session_id,<<91,84,27,151,183,39,84,90,143,141, + 121,190,66,192,10,1,27,192,33,95,78, + 8,34,180,...>>}]} + +%% Use automatic session reuse +8> {ok, C4} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_sessions, true}]). +{ok,{sslsocket,{gen_tcp,#Port<0.10>,tls_connection, + undefined}, ...]}} + +%% The "saved" one happened to be selected, but this is not a guarantee +9> ssl:connection_information(C4, [session_id]). +{ok,[{session_id,<<91,84,27,151,183,39,84,90,143,141, + 121,190,66,192,10,1,27,192,33,95,78, + 8,34,180,...>>}]} + +%% Make sure to reuse the "saved" session +10> {ok, C5} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_session, ID}]). +{ok,{sslsocket,{gen_tcp,#Port<0.11>,tls_connection, + undefined}, ...]}} + +11> ssl:connection_information(C5, [session_id]). +{ok,[{session_id,<<91,84,27,151,183,39,84,90,143,141, + 121,190,66,192,10,1,27,192,33,95,78, + 8,34,180,...>>}]} +</code> + +<p>Step 3 - Explicit Session Reuse </p> + +<code type="erl"> +%% Preform a full handshake and the session will not be saved for reuse +12> {ok, C9} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_sessions, false}, + {server_name_indication, disable}]). +{ok,{sslsocket,{gen_tcp,#Port<0.14>,tls_connection, ...}} + +%% Fetch session ID and data for C9 connection +12> {ok, [{session_id, ID1}, {session_data, SessData}]} = + ssl:connection_information(C9, [session_id, session_data]). +{ok,[{session_id,<<9,233,4,54,170,88,170,180,17,96,202, + 85,85,99,119,47,9,68,195,50,120,52, + 130,239,...>>}, + {session_data,<<131,104,13,100,0,7,115,101,115,115,105, + 111,110,109,0,0,0,32,9,233,4,54,170,...>>}]} + +%% Explicitly reuse the session from C9 +13> {ok, C10} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_session, {ID1, SessData}}]). +{ok,{sslsocket,{gen_tcp,#Port<0.15>,tls_connection, + undefined}, ...}} + +14> ssl:connection_information(C10, [session_id]). +{ok,[{session_id,<<9,233,4,54,170,88,170,180,17,96,202, + 85,85,99,119,47,9,68,195,50,120,52, + 130,239,...>>}]} + +</code> + +<p>Step 4 - Not Possible to Reuse Explicit Session by ID Only</p> + +<code type="erl"> +%% Try to reuse the session from C9 using only the id +15> {ok, E} = ssl:connect("localhost", 9999, [{verify, verify_peer}, + {versions, ['tlsv1.2']}, + {cacertfile, "cacerts.pem"}, + {reuse_session, ID1}]). +{ok,{sslsocket,{gen_tcp,#Port<0.18>,tls_connection, + undefined}, ...}} + +%% This will fail (as it is not saved for reuse) +%% and a full handshake will be performed, we get a new id. +16> ssl:connection_information(E, [session_id]). +{ok,[{session_id,<<87,46,43,126,175,68,160,153,37,29, + 196,240,65,160,254,88,65,224,18,63, + 18,17,174,39,...>>}]} +</code> + + <p>On the server side the the <c>{reuse_sessions, boolean()}</c> option + determines if the server will save session data and allow session + reuse or not. This can be further customized by the option + <c>{reuse_session, fun()}</c> that may introduce a local policy for + session reuse. + </p> + + </section> + <section> <title>Session Tickets and Session Resumption in TLS 1.3</title> diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index b0f4cfd2da..1a55ee7b83 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -47,6 +47,7 @@ MODULES= \ dtls_connection \ dtls_connection_sup \ dtls_handshake \ + dtls_gen_connection \ dtls_listener_sup \ dtls_packet_demux \ dtls_record \ @@ -65,7 +66,6 @@ MODULES= \ ssl_cipher \ ssl_cipher_format \ ssl_config \ - ssl_connection \ ssl_connection_sup \ ssl_crl \ ssl_crl_cache \ @@ -74,6 +74,7 @@ MODULES= \ ssl_dist_admin_sup \ ssl_dist_connection_sup \ ssl_dist_sup \ + ssl_gen_statem \ ssl_handshake \ ssl_listen_tracker_sup \ ssl_logger \ @@ -84,19 +85,24 @@ MODULES= \ ssl_server_session_cache \ ssl_server_session_cache_db \ ssl_server_session_cache_sup \ + ssl_upgrade_server_session_cache_sup \ ssl_session \ ssl_session_cache \ ssl_srp_primes \ ssl_sup \ tls_bloom_filter \ + tls_dtls_connection \ tls_connection \ tls_connection_sup \ tls_connection_1_3 \ + tls_gen_connection \ tls_handshake \ tls_handshake_1_3 \ tls_record \ tls_record_1_3 \ tls_client_ticket_store \ + tls_dist_sup \ + tls_dist_server_sup \ tls_sender \ tls_server_session_ticket\ tls_server_session_ticket_sup\ diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 784eb20a92..fb389dcb4d 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -17,12 +17,105 @@ %% %% %CopyrightEnd% %% + -module(dtls_connection). +%%---------------------------------------------------------------------- +%% Purpose: DTLS-1-DTLS-1.2 FSM (* = optional) +%%---------------------------------------------------------------------- +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% For UDP transport the following flights are used as retransmission units +%% in case of package loss. Flight timers are handled in state entry functions. +%% +%% Client Server +%% ------ ------ +%% +%% ClientHello --------> Flight 1 +%% +%% <------- HelloVerifyRequest Flight 2 +%% +%% ClientHello --------> Flight 3 +%% +%% ServerHello \ +%% Certificate* \ +%% ServerKeyExchange* Flight 4 +%% CertificateRequest* / +%% <-------- ServerHelloDone / +%% +%% Certificate* \ +%% ClientKeyExchange \ +%% CertificateVerify* Flight 5 +%% [ChangeCipherSpec] / +%% Finished --------> / +%% +%% [ChangeCipherSpec] \ Flight 6 +%% <-------- Finished / +%% +%% Message Flights for Full Handshake +%% +%% +%% Client Server +%% ------ ------ +%% +%% ClientHello --------> Abbrev Flight 1 +%% +%% ServerHello \ part 1 +%% [ChangeCipherSpec] Abbrev Flight 2 +%% <-------- Finished / part 2 +%% +%% [ChangeCipherSpec] \ Abbrev Flight 3 +%% Finished --------> / +%% +%% +%% Message Flights for Abbbriviated Handshake +%%---------------------------------------------------------------------- +%% Start FSM ---> CONFIG_ERROR +%% Send error to user +%% | and shutdown +%% | +%% V +%% INITIAL_HELLO +%% +%% | Send/ Recv Flight 1 +%% | +%% | +%% USER_HELLO | +%% <- Possibly let user provide V +%% options after looking at hello ex -> HELLO +%% | Send Recv Flight 2 to Flight 4 or +%% | Abbrev Flight 1 to Abbrev Flight 2 part 1 +%% | +%% New session | Resumed session +%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED +%% +%% <- Possibly Receive -- | | +%% OCSP Stapel ------> | Send/ Recv Flight 5 | +%% | | +%% V | Send / Recv Abbrev Flight part 2 +%% | to Abbrev Flight 3 +%% CIPHER | +%% | | +%% | Send/ Recv Flight 6 | +%% | | +%% V V +%% ---------------------------------------------------- +%% | +%% | +%% V +%% CONNECTION +%% | +%% | Renegotiaton +%% V +%% GO BACK TO HELLO +%%---------------------------------------------------------------------- + %% Internal application API -behaviour(gen_statem). +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + -include("dtls_connection.hrl"). -include("dtls_handshake.hrl"). -include("ssl_alert.hrl"). @@ -31,34 +124,31 @@ -include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). %% Internal application API %% Setup --export([start_fsm/8, start_link/7, init/1, pids/1]). +-export([init/1]). -%% State transition handling --export([next_event/3, next_event/4, handle_protocol_record/3]). - -%% Handshake handling --export([renegotiate/2, send_handshake/2, - queue_handshake/2, queue_change_cipher/2, - reinit/1, reinit_handshake_data/1, select_sni_extension/1, empty_connection_state/2]). - -%% Alert and close handling --export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). - -%% Data handling --export([socket/4, setopts/3, getopts/3]). +-export([renegotiate/2]). %% gen_statem state functions --export([init/3, error/3, downgrade/3, %% Initiation and take down states - hello/3, user_hello/3, wait_ocsp_stapling/3, certify/3, cipher/3, abbreviated/3, %% Handshake states +-export([initial_hello/3, + config_error/3, + downgrade/3, + hello/3, + user_hello/3, + wait_ocsp_stapling/3, + certify/3, + cipher/3, + abbreviated/3, connection/3]). + %% gen_statem callbacks --export([callback_mode/0, terminate/3, code_change/4, format_status/2]). +-export([callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). %%==================================================================== %% Internal application API @@ -66,373 +156,47 @@ %%==================================================================== %% Setup %%==================================================================== -start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts, - User, {CbModule, _, _, _, _} = CbInfo, - Timeout) -> - try - {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid], CbModule, Tracker), - ssl_connection:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end. - -%%-------------------------------------------------------------------- --spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> - {ok, pid()} | ignore | {error, reason()}. -%% -%% Description: Creates a gen_statem process which calls Module:init/1 to -%% initialize. -%%-------------------------------------------------------------------- -start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. - init([Role, Host, Port, Socket, Options, User, CbInfo]) -> process_flag(trap_exit, true), - State0 = #state{protocol_specific = Map} = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), + State0 = #state{protocol_specific = Map} = + initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try - State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], init, State) + State = ssl_gen_statem:ssl_config(State0#state.ssl_options, + Role, State0), + gen_statem:enter_loop(?MODULE, [], initial_hello, State) catch throw:Error -> - EState = State0#state{protocol_specific = Map#{error => Error}}, - gen_statem:enter_loop(?MODULE, [], error, EState) + EState = State0#state{protocol_specific = + Map#{error => Error}}, + gen_statem:enter_loop(?MODULE, [], config_error, EState) end. - -pids(_) -> - [self()]. - %%==================================================================== -%% State transition handling +%% Handshake %%==================================================================== -next_record(#state{handshake_env = - #handshake_env{unprocessed_handshake_events = N} = HsEnv} - = State) when N > 0 -> - {no_record, State#state{handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; -next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} - = Buffers, - connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) -> - CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read), - case dtls_record:replay_detect(CT, CurrentRead) of - false -> - decode_cipher_text(State#state{connection_states = ConnectionStates}) ; - true -> - %% Ignore replayed record - next_record(State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnectionStates}) - end; -next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]} - = Buffers, - connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State) - when Epoch > CurrentEpoch -> - %% TODO Buffer later Epoch message, drop it for now - next_record(State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnectionStates}); -next_record(#state{protocol_buffers = - #protocol_buffers{dtls_cipher_texts = [ _ | Rest]} - = Buffers, - connection_states = ConnectionStates} = State) -> - %% Drop old epoch message - next_record(State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnectionStates}); -next_record(#state{static_env = #static_env{role = server, - socket = {Listener, {Client, _}}}} = State) -> - dtls_packet_demux:active_once(Listener, Client, self()), - {no_record, State}; -next_record(#state{protocol_specific = #{active_n_toggle := true, - active_n := N} = ProtocolSpec, - static_env = #static_env{role = client, - socket = {_Server, Socket} = DTLSSocket, - close_tag = CloseTag, - transport_cb = Transport}} = State) -> - case dtls_socket:setopts(Transport, Socket, [{active,N}]) of - ok -> - {no_record, State#state{protocol_specific = - ProtocolSpec#{active_n_toggle => false}}}; - _ -> - self() ! {CloseTag, DTLSSocket}, - {no_record, State} - end; -next_record(State) -> - {no_record, State}. - -next_event(StateName, Record, State) -> - next_event(StateName, Record, State, []). - -next_event(StateName, no_record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case next_record(State0) of - {no_record, State} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {#ssl_tls{epoch = CurrentEpoch, - type = ?HANDSHAKE, - version = Version} = Record, State1} -> - State = dtls_version(StateName, Version, State1), - {next_state, StateName, State, - [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#ssl_tls{epoch = CurrentEpoch} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#ssl_tls{epoch = Epoch, - type = ?HANDSHAKE, - version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake - {#ssl_tls{epoch = Epoch, - type = ?CHANGE_CIPHER_SPEC, - version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - {#ssl_tls{epoch = _Epoch, - version = _Version}, State} -> - %% TODO maybe buffer later epoch - next_event(StateName, no_record, State, Actions); - {#alert{} = Alert, State} -> - Version = State#state.connection_env#connection_env.negotiated_version, - handle_own_alert(Alert, Version, StateName, State) - end; -next_event(connection = StateName, Record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case Record of - #ssl_tls{epoch = CurrentEpoch, - type = ?HANDSHAKE, - version = Version} = Record -> - State = dtls_version(StateName, Version, State0), - {next_state, StateName, State, - [{next_event, internal, {protocol_record, Record}} | Actions]}; - #ssl_tls{epoch = CurrentEpoch} -> - {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]}; - #ssl_tls{epoch = Epoch, - type = ?HANDSHAKE, - version = _Version} when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake - #ssl_tls{epoch = Epoch, - type = ?CHANGE_CIPHER_SPEC, - version = _Version} when Epoch == CurrentEpoch-1 -> - {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - next_event(StateName, no_record, State, Actions ++ MoreActions); - _ -> - next_event(StateName, no_record, State0, Actions) - end; -next_event(StateName, Record, - #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case Record of - #ssl_tls{epoch = CurrentEpoch, - version = Version} = Record -> - State = dtls_version(StateName, Version, State0), - {next_state, StateName, State, - [{next_event, internal, {protocol_record, Record}} | Actions]}; - #ssl_tls{epoch = _Epoch, - version = _Version} = _Record -> - %% TODO maybe buffer later epoch - next_event(StateName, no_record, State0, Actions); - #alert{} = Alert -> - Version = State0#state.connection_env#connection_env.negotiated_version, - handle_own_alert(Alert, Version, StateName, State0) - end. - -%%% DTLS record protocol level application data messages - -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) -> - case ssl_connection:read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, State1} -> - {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1), - ssl_connection:hibernate_after(StateName, State, Actions) - end; -%%% DTLS record protocol level handshake messages -handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, - fragment = Data}, - StateName, - #state{protocol_buffers = Buffers0, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = Options} = State) -> - try - case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of - {[], Buffers} -> - next_event(StateName, no_record, State#state{protocol_buffers = Buffers}); - {Packets, Buffers} -> - HsEnv = State#state.handshake_env, - Events = dtls_handshake_events(Packets), - {next_state, StateName, - State#state{protocol_buffers = Buffers, - handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events - = unprocessed_events(Events)}}, Events} - end - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State) - end; -%%% DTLS record protocol level change cipher messages -handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> - {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; -%%% DTLS record protocol level Alert messages -handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - case decode_alerts(EncAlerts) of - Alerts = [_|_] -> - handle_alerts(Alerts, {next_state, StateName, State}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State) - end; -%% Ignore unknown TLS record level protocol messages -handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> - {next_state, StateName, State, []}. - -%%==================================================================== -%% Handshake handling -%%==================================================================== - renegotiate(#state{static_env = #static_env{role = client}} = State0, Actions) -> %% Handle same way as if server requested %% the renegotiation - State = reinit_handshake_data(State0), + State = dtls_gen_connection:reinit_handshake_data(State0), {next_state, connection, State, [{next_event, internal, #hello_request{}} | Actions]}; renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), State1 = prepare_flight(State0), - {State, MoreActions} = send_handshake(HelloRequest, State1), - next_event(hello, no_record, State, Actions ++ MoreActions). - -send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> - #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), - send_handshake_flight(queue_handshake(Handshake, State), Epoch). - -queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := HsBuffer0, - change_cipher_spec := undefined, - next_sequence := Seq} = Flight0, - ssl_options = #{log_level := LogLevel}} = State) -> - Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), - Hist = update_handshake_history(Handshake0, Handshake, Hist0), - ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), - - State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], - next_sequence => Seq +1}, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; - -queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0, - next_sequence := Seq} = Flight0, - ssl_options = #{log_level := LogLevel}} = State) -> - Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), - Hist = update_handshake_history(Handshake0, Handshake, Hist0), - ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), - - State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], - next_sequence => Seq +1}, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}. - -queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, - connection_states = ConnectionStates0} = State) -> - ConnectionStates = - dtls_record:next_epoch(ConnectionStates0, write), - State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher}, - connection_states = ConnectionStates}. - -reinit(State) -> - %% To be API compatible with TLS NOOP here - reinit_handshake_data(State). -reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag}, - protocol_buffers = Buffers, - protocol_specific = PS, - handshake_env = HsEnv} = State) -> - State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), - public_key_info = undefined, - premaster_secret = undefined}, - protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, - flight_buffer = new_flight(), - protocol_buffers = - Buffers#protocol_buffers{ - dtls_handshake_next_seq = 0, - dtls_handshake_next_fragments = [], - dtls_handshake_later_fragments = [] - }}. - -select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> - SNI; -select_sni_extension(_) -> - undefined. - -empty_connection_state(ConnectionEnd, BeastMitigation) -> - Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), - dtls_record:empty_connection_state(Empty). - -%%==================================================================== -%% Alert and close handling -%%==================================================================== -encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - dtls_record:encode_alert_record(Alert, Version, ConnectionStates). - -send_alert(Alert, #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - - connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0) -> - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - send(Transport, Socket, BinMsg), - ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - State0#state{connection_states = ConnectionStates}. - -send_alert_in_connection(Alert, State) -> - _ = send_alert(Alert, State), - ok. - -close(downgrade, _,_,_,_) -> - ok; -%% Other -close(_, Socket, Transport, _,_) -> - dtls_socket:close(Transport,Socket). - -protocol_name() -> - "DTLS". - -%%==================================================================== -%% Data handling -%%==================================================================== - -send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> % Server socket - dtls_socket:send(Transport, Socket, Data); -send(Transport, Socket, Data) -> % Client socket - dtls_socket:send(Transport, Socket, Data). - -socket(Pid, Transport, Socket, _Tracker) -> - dtls_socket:socket(Pid, Transport, Socket, ?MODULE). - -setopts(Transport, Socket, Other) -> - dtls_socket:setopts(Transport, Socket, Other). - -getopts(Transport, Socket, Tag) -> - dtls_socket:getopts(Transport, Socket, Tag). + {State, MoreActions} = dtls_gen_connection:send_handshake(HelloRequest, State1), + dtls_gen_connection:next_event(hello, no_record, State, Actions ++ MoreActions). %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec init(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -init(enter, _, State) -> +initial_hello(enter, _, State) -> {keep_state, State}; -init({call, From}, {start, Timeout}, +initial_hello({call, From}, {start, Timeout}, #state{static_env = #static_env{host = Host, port = Port, role = client, @@ -441,12 +205,12 @@ init({call, From}, {start, Timeout}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, connection_env = CEnv, ssl_options = #{versions := Versions} = SslOpts, - session = #session{own_certificate = Cert} = NewSession, + session = #session{own_certificates = OwnCerts} = NewSession, connection_states = ConnectionStates0 } = State0) -> Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, Cert), + Session#session.session_id, Renegotiation, OwnCerts), MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined), ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), @@ -454,38 +218,36 @@ init({call, From}, {start, Timeout}, HelloVersion = dtls_record:hello_version(Version, Versions), State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}, connection_states = ConnectionStates1}), - {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), + {State2, Actions} = + dtls_gen_connection:send_handshake(Hello, + State1#state{connection_env = + CEnv#connection_env{negotiated_version = HelloVersion}}), State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion session = Session, start_or_recv_from = From}, - next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]); -init({call, _} = Type, Event, #state{static_env = #static_env{role = server}, - protocol_specific = PS} = State) -> - Result = gen_handshake(?FUNCTION_NAME, Type, Event, - State#state{protocol_specific = PS#{current_cookie_secret => dtls_v1:cookie_secret(), + dtls_gen_connection:next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]); +initial_hello({call, _} = Type, Event, #state{static_env = #static_env{role = server}, + protocol_specific = PS} = State) -> + Result = ssl_gen_statem:?FUNCTION_NAME(Type, Event, + State#state{protocol_specific = + PS#{current_cookie_secret => dtls_v1:cookie_secret(), previous_cookie_secret => <<>>, ignored_alerts => 0, max_ignored_alerts => 10}}), erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), Result; -init(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). +initial_hello(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- --spec error(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -error(enter, _, State) -> +config_error(enter, _, State) -> {keep_state, State}; -error({call, From}, {start, _Timeout}, - #state{protocol_specific = #{error := Error}} = State) -> - {stop_and_reply, {shutdown, normal}, - [{reply, From, {error, Error}}], State}; -error({call, _} = Call, Msg, State) -> - gen_handshake(?FUNCTION_NAME, Call, Msg, State); -error(_, _, _) -> - {keep_state_and_data, [postpone]}. +config_error(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- -spec hello(gen_statem:event_type(), @@ -506,54 +268,67 @@ hello(internal, #client_hello{cookie = <<>>, handshake_env = HsEnv, connection_env = CEnv, protocol_specific = #{current_cookie_secret := Secret}} = State0) -> - {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), - Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello), - %% FROM RFC 6347 regarding HelloVerifyRequest message: - %% The server_version field has the same syntax as in TLS. However, in - %% order to avoid the requirement to do version negotiation in the - %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS - %% version 1.0 regardless of the version of TLS that is expected to be - %% negotiated. - VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION), - State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), - {State, Actions} = send_handshake(VerifyRequest, State1), - next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{ - tls_handshake_history = - ssl_handshake:init_handshake_history()}}, - Actions); -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, - session = #session{own_certificate = Cert, session_id = Id}, - connection_states = ConnectionStates0 - } = State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{} = State1 -> + {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), + Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello), + %% FROM RFC 6347 regarding HelloVerifyRequest message: + %% The server_version field has the same syntax as in TLS. However, in + %% order to avoid the requirement to do version negotiation in the + %% initial handshake, DTLS 1.2 server implementations SHOULD use DTLS + %% version 1.0 regardless of the version of TLS that is expected to be + %% negotiated. + VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION), + State2 = prepare_flight(State1#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), + {State, Actions} = dtls_gen_connection:send_handshake(VerifyRequest, State2), + dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, + State#state{handshake_env = HsEnv#handshake_env{ + tls_handshake_history = + ssl_handshake:init_handshake_history()}}, + Actions); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version,?FUNCTION_NAME, State0) + end; +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, + session = #session{own_certificates = OwnCerts, + session_id = Id}, + connection_states = ConnectionStates0 + } = State0) -> OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt), Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, - SslOpts, Id, Renegotiation, Cert, OcspNonce), + SslOpts, Id, Renegotiation, OwnCerts, 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}}}), + ocsp_stapling_state = + OcspState0#{ocsp_nonce => OcspNonce}}}), - {State2, Actions} = send_handshake(Hello, State1), + {State2, Actions} = dtls_gen_connection:send_handshake(Hello, State1), State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version} % RequestedVersion }, - next_event(?FUNCTION_NAME, no_record, State, Actions); -hello(internal, #client_hello{extensions = Extensions} = Hello, + dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State, Actions); +hello(internal, #client_hello{extensions = Extensions, client_version = ClientVersion} = Hello, #state{ssl_options = #{handshake := hello}, handshake_env = HsEnv, - start_or_recv_from = From} = State) -> - {next_state, user_hello, State#state{start_or_recv_from = undefined, - handshake_env = HsEnv#handshake_env{hello = Hello}}, - [{reply, From, {ok, Extensions}}]}; + start_or_recv_from = From} = State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{} = State -> + {next_state, user_hello, State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{hello = Hello}}, + [{reply, From, {ok, Extensions}}]}; + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, ?FUNCTION_NAME, State0) + end; hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{ handshake := hello}, @@ -568,18 +343,18 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta socket = Socket}, protocol_specific = #{current_cookie_secret := Secret, previous_cookie_secret := PSecret} - } = State0) -> + } = State) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), case dtls_handshake:cookie(Secret, IP, Port, Hello) of Cookie -> - handle_client_hello(Hello, State0); + handle_client_hello(Hello, State); _ -> case dtls_handshake:cookie(PSecret, IP, Port, Hello) of Cookie -> - handle_client_hello(Hello, State0); + handle_client_hello(Hello, State); _ -> %% Handle bad cookie as new cookie request RFC 6347 4.1.2 - hello(internal, Hello#client_hello{cookie = <<>>}, State0) + hello(internal, Hello#client_hello{cookie = <<>>}, State) end end; hello(internal, #server_hello{} = Hello, @@ -594,12 +369,13 @@ hello(internal, #server_hello{} = Hello, ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); + ssl_gen_statem:handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} -> - ssl_connection:handle_session(Hello, + 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)}}) + State#state{handshake_env = + HsEnv#handshake_env{ + ocsp_stapling_state = maps:merge(OcspState0,OcspState)}}) end; hello(internal, {handshake, {#client_hello{cookie = <<>>} = Handshake, _}}, State) -> %% Initial hello should not be in handshake history @@ -608,8 +384,9 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> %% hello_verify should not be in handshake history {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> - {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), + {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), + {next_state, ?FUNCTION_NAME, State, Actions} = + dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}; hello(info, Event, State) -> @@ -673,10 +450,11 @@ certify(enter, _, State0) -> certify(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); certify(internal = Type, #server_hello_done{} = Event, State) -> - ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE); + gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State)); certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> - {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), + {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), + {next_state, ?FUNCTION_NAME, State, Actions} = + dtls_gen_connection:next_event(?FUNCTION_NAME, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}; certify(state_timeout, Event, State) -> @@ -697,17 +475,16 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event, #state{connection_states = ConnectionStates0} = State) -> ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), - ssl_connection:?FUNCTION_NAME(Type, Event, State#state{connection_states = ConnectionStates}, ?MODULE); + gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates}); cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, protocol_specific = PS} = State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, - prepare_flight(State#state{connection_states = ConnectionStates, - protocol_specific = PS#{flight_state => connection}}), - ?MODULE); + gen_handshake(?FUNCTION_NAME, Type, Event, + prepare_flight(State#state{connection_states = ConnectionStates, + protocol_specific = PS#{flight_state => connection}})); cipher(state_timeout, Event, State) -> handle_state_timeout(Event, ?FUNCTION_NAME, State); cipher(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -726,7 +503,7 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho }, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, connection_env = CEnv, - session = #session{own_certificate = Cert} = Session0, + session = #session{own_certificates = OwnCerts} = Session0, ssl_options = #{versions := Versions} = SslOpts, connection_states = ConnectionStates0, protocol_specific = PS @@ -734,16 +511,20 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, Cert), + Session#session.session_id, Renegotiation, OwnCerts), Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, Versions), State1 = prepare_flight(State0), - {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), - State = State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, + {State2, Actions} = + dtls_gen_connection:send_handshake(Hello, + State1#state{connection_env = + CEnv#connection_env{negotiated_version = HelloVersion}}), + State = State2#state{protocol_specific = PS#{flight_state => dtls_gen_connection:initial_flight_state(DataTag)}, session = Session}, - next_event(hello, no_record, State, Actions); -connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) -> + dtls_gen_connection:next_event(hello, no_record, State, Actions); +connection(internal, #client_hello{} = Hello, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{allow_renegotiate = true} = HsEnv} = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client @@ -753,20 +534,21 @@ connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{ro {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}, allow_renegotiate = false}}, [{next_event, internal, Hello}]}; -connection(internal, #client_hello{}, #state{static_env = #static_env{role = server}, +connection(internal, #client_hello{}, #state{static_env = #static_env{role = server, + protocol_cb = Connection}, handshake_env = #handshake_env{allow_renegotiate = false}} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - State1 = send_alert(Alert, State0), - {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), - next_event(?FUNCTION_NAME, Record, State); + State1 = dtls_gen_connection:send_alert(Alert, State0), + {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection), + dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State); connection({call, From}, {application_data, Data}, State) -> try send_application_data(Data, From, ?FUNCTION_NAME, State) catch throw:Error -> - ssl_connection:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) + ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, [{reply, From, Error}]) end; connection(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %%TODO does this make sense for DTLS ? %%-------------------------------------------------------------------- @@ -776,7 +558,7 @@ connection(Type, Event, State) -> downgrade(enter, _, State) -> {keep_state, State}; downgrade(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- %% gen_statem callbacks @@ -785,13 +567,13 @@ callback_mode() -> [state_functions, state_enter]. terminate(Reason, StateName, State) -> - ssl_connection:terminate(Reason, StateName, State). + ssl_gen_statem:terminate(Reason, StateName, State). code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. format_status(Type, Data) -> - ssl_connection:format_status(Type, Data). + ssl_gen_statem:format_status(Type, Data). %%-------------------------------------------------------------------- %%% Internal functions @@ -818,7 +600,7 @@ initial_state(Role, Host, Port, Socket, InitStatEnv = #static_env{ role = Role, transport_cb = CbModule, - protocol_cb = ?MODULE, + protocol_cb = dtls_gen_connection, data_tag = DataTag, close_tag = CloseTag, error_tag = ErrorTag, @@ -846,246 +628,95 @@ initial_state(Role, Host, Port, Socket, protocol_buffers = #protocol_buffers{}, user_data_buffer = {[],0,[]}, start_or_recv_from = undefined, - flight_buffer = new_flight(), + flight_buffer = dtls_gen_connection:new_flight(), protocol_specific = #{active_n => InternalActiveN, active_n_toggle => true, - flight_state => initial_flight_state(DataTag)} + flight_state => dtls_gen_connection:initial_flight_state(DataTag)} }. -initial_flight_state(udp)-> - {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}; -initial_flight_state(_) -> - reliable. - -next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ - dtls_record_buffer = Buf0, - dtls_cipher_texts = CT0} = Buffers, - connection_env = #connection_env{negotiated_version = Version}, - static_env = #static_env{data_tag = DataTag}, - ssl_options = SslOpts} = State0) -> - case dtls_record:get_dtls_records(Data, - {DataTag, StateName, Version, - [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]}, - Buf0, SslOpts) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - next_record(State0#state{protocol_buffers = - Buffers#protocol_buffers{dtls_record_buffer = Buf1, - dtls_cipher_texts = CT1}}); - #alert{} = Alert -> - Alert - end. - -dtls_handshake_events(Packets) -> - lists:map(fun(Packet) -> - {next_event, internal, {handshake, Packet}} - end, Packets). - -decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers, - connection_states = ConnStates0} = State) -> - case dtls_record:decode_cipher_text(CT, ConnStates0) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{dtls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end. - -dtls_version(hello, Version, #state{static_env = #static_env{role = server}, - connection_env = CEnv} = State) -> - State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version -dtls_version(_,_, State) -> - State. - -handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, - #state{connection_states = ConnectionStates0, - static_env = #static_env{trackers = Trackers}, - handshake_env = #handshake_env{kex_algorithm = KeyExAlg, - renegotiation = {Renegotiation, _}, - negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = CEnv, - session = #session{own_certificate = Cert} = Session0, - ssl_options = SslOpts} = State0) -> - SessionTracker = proplists:get_value(session_id_tracker, Trackers), - case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, - ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of - #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State0); - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> - Protocol = case Protocol0 of - undefined -> CurrentProtocol; - _ -> Protocol0 - end, - - State = prepare_flight(State0#state{connection_states = ConnectionStates, - connection_env = CEnv#connection_env{negotiated_version = Version}, - handshake_env = HsEnv#handshake_env{ - hashsign_algorithm = HashSign, +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{connection_states = ConnectionStates0, + static_env = #static_env{trackers = Trackers}, + handshake_env = #handshake_env{kex_algorithm = KeyExAlg, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = CEnv, + session = #session{own_certificates = OwnCerts} = Session0, + ssl_options = SslOpts} = State1 -> + SessionTracker = proplists:get_value(session_id_tracker, Trackers), + case dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, + ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State1); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + + State = prepare_flight(State0#state{connection_states = ConnectionStates, + connection_env = CEnv#connection_env{negotiated_version = Version}, + handshake_env = HsEnv#handshake_env{ + hashsign_algorithm = HashSign, client_hello_version = ClientVersion, - negotiated_protocol = Protocol}, - session = Session}), - - ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, - State, ?MODULE) - end. - - -%% raw data from socket, unpack records -handle_info({Protocol, _, _, _, Data}, StateName, - #state{static_env = #static_env{role = Role, - data_tag = Protocol}} = State0) -> - case next_dtls_record(Data, StateName, State0) of - {Record, State} -> - next_event(StateName, Record, State); - #alert{} = Alert -> - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), - {stop, {shutdown, own_alert}, State0} - end; - -handle_info({PassiveTag, Socket}, StateName, - #state{static_env = #static_env{socket = {_, Socket}, - passive_tag = PassiveTag}, - protocol_specific = PS} = State) -> - next_event(StateName, no_record, - State#state{protocol_specific = PS#{active_n_toggle => true}}); - -handle_info({CloseTag, Socket}, StateName, - #state{static_env = #static_env{ - role = Role, - socket = Socket, - close_tag = CloseTag}, - connection_env = #connection_env{negotiated_version = Version}, - socket_options = #socket_options{active = Active}, - protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, - protocol_specific = PS} = State) -> - %% Note that as of DTLS 1.2 (TLS 1.1), - %% failure to properly close a connection no longer requires that a - %% session not be resumed. This is a change from DTLS 1.0 to conform - %% with widespread implementation practice. - case (Active == false) andalso (CTs =/= []) of - false -> - case Version of - {254, N} when N =< 253 -> - ok; - _ -> - %% As invalidate_sessions here causes performance issues, - %% we will conform to the widespread implementation - %% practice and go aginst the spec - %%invalidate_session(Role, Host, Port, Session) - ok - end, - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown, transport_closed}, State}; - true -> - %% Fixes non-delivery of final DTLS record in {active, once}. - %% Basically allows the application the opportunity to set {active, once} again - %% and then receive the final message. - next_event(StateName, no_record, State#state{ - protocol_specific = PS#{active_n_toggle => true}}) - end; + negotiated_protocol = Protocol}, + session = Session}), + {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]} + end; + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, State0) + end. -handle_info(new_cookie_secret, StateName, - #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) -> - erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), - {next_state, StateName, State#state{protocol_specific = - CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), - previous_cookie_secret => Secret}}}; -handle_info(Msg, StateName, State) -> - ssl_connection:StateName(info, Msg, State, ?MODULE). handle_state_timeout(flight_retransmission_timeout, StateName, #state{protocol_specific = #{flight_state := {retransmit, _NextTimeout}}} = State0) -> - {State1, Actions0} = send_handshake_flight(State0, - retransmit_epoch(StateName, State0)), - {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0), + {State1, Actions0} = dtls_gen_connection:send_handshake_flight(State0, + retransmit_epoch(StateName, State0)), + {next_state, StateName, State, Actions} = + dtls_gen_connection:next_event(StateName, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}. -handle_alerts([], Result) -> - Result; -handle_alerts(_, {stop, _, _} = Stop) -> - Stop; -handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). - -handle_own_alert(Alert, Version, StateName, - #state{static_env = #static_env{data_tag = udp, - role = Role}, - ssl_options = #{log_level := LogLevel}} = State0) -> - case ignore_alert(Alert, State0) of - {true, State} -> - log_ignore_alert(LogLevel, StateName, Alert, Role), - {next_state, StateName, State}; - {false, State} -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State) - end; -handle_own_alert(Alert, Version, StateName, State) -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State). - -encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) -> - Fragments = lists:map(fun(Handshake) -> - dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize) - end, Flight), - dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates). -encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) -> - dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates). - -decode_alerts(Bin) -> - ssl_alert:decode(Bin). gen_handshake(StateName, Type, Event, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try ssl_connection:StateName(Type, Event, State, ?MODULE) of + try tls_dtls_connection:StateName(Type, Event, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), Version, StateName, State) end. gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try dtls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), Version, StateName, State) end; gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try dtls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), Version, StateName, State) end. -unprocessed_events(Events) -> - %% The first handshake event will be processed immediately - %% as it is entered first in the event queue and - %% when it is processed there will be length(Events)-1 - %% handshake events left to process before we should - %% process more TLS-records received on the socket. - erlang:length(Events)-1. -update_handshake_history(#hello_verify_request{}, _, Hist) -> - Hist; -update_handshake_history(_, Handshake, Hist) -> - ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)). prepare_flight(#state{flight_buffer = Flight, connection_states = ConnectionStates0, protocol_buffers = @@ -1096,11 +727,6 @@ prepare_flight(#state{flight_buffer = Flight, protocol_buffers = Buffers#protocol_buffers{ dtls_handshake_next_fragments = [], dtls_handshake_later_fragments = []}}. -new_flight() -> - #{next_sequence => 0, - handshakes => [], - change_cipher_spec => undefined, - handshakes_after_change_cipher_spec => []}. next_flight(Flight) -> Flight#{handshakes => [], @@ -1126,133 +752,11 @@ new_timeout(N) when N =< 30000 -> new_timeout(_) -> 60000. -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := Flight, - change_cipher_spec := undefined}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - PMTUEstimate = 1400, %% TODO make configurable - #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, - MaxSize = min(MaxFragmentLength, PMTUEstimate), - {Encoded, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0), - send(Transport, Socket, Encoded), - ssl_logger:debug(LogLevel, outbound, 'record', Encoded), - {State0#state{connection_states = ConnectionStates}, []}; - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := [_|_] = Flight0, - change_cipher_spec := ChangeCipher, - handshakes_after_change_cipher_spec := []}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - PMTUEstimate = 1400, %% TODO make configurable - #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, - MaxSize = min(MaxFragmentLength, PMTUEstimate), - {HsBefore, ConnectionStates1} = - encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0), - {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), - - send(Transport, Socket, [HsBefore, EncChangeCipher]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), - ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), - {State0#state{connection_states = ConnectionStates}, []}; - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := [_|_] = Flight0, - change_cipher_spec := ChangeCipher, - handshakes_after_change_cipher_spec := Flight1}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - PMTUEstimate = 1400, %% TODO make configurable - #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, - MaxSize = min(MaxFragmentLength, PMTUEstimate), - {HsBefore, ConnectionStates1} = - encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0), - {EncChangeCipher, ConnectionStates2} = - encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1), - {HsAfter, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2), - send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), - ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), - {State0#state{connection_states = ConnectionStates}, []}; - -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = #{handshakes := [], - change_cipher_spec := ChangeCipher, - handshakes_after_change_cipher_spec := Flight1}, - connection_states = ConnectionStates0, - ssl_options = #{log_level := LogLevel}} = State0, - Epoch) -> - PMTUEstimate = 1400, %% TODO make configurable - #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, - MaxSize = min(MaxFragmentLength, PMTUEstimate), - {EncChangeCipher, ConnectionStates1} = - encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), - {HsAfter, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1), - send(Transport, Socket, [EncChangeCipher, HsAfter]), - ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), - ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), - {State0#state{connection_states = ConnectionStates}, []}. - retransmit_epoch(_StateName, #state{connection_states = ConnectionStates}) -> #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), Epoch. -ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N, - max_ignored_alerts := N}} = State) -> - {false, State}; -ignore_alert(#alert{level = ?FATAL} = Alert, - #state{protocol_specific = #{ignored_alerts := N} = PS} = State) -> - case is_ignore_alert(Alert) of - true -> - {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}}; - false -> - {false, State} - end; -ignore_alert(_, State) -> - {false, State}. - -%% RFC 6347 4.1.2.7. Handling Invalid Records -%% recommends to silently ignore invalid DTLS records when -%% upd is the transport. Note we do not support compression so no need -%% include ?DECOMPRESSION_FAILURE -is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) -> - true; -is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) -> - true; -is_ignore_alert(#alert{description = ?DECODE_ERROR}) -> - true; -is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) -> - true; -is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) -> - true; -is_ignore_alert(_) -> - false. - -log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) -> - ssl_logger:log(info, - Level, #{alert => Alert, - alerter => ignored, - statename => StateName, - role => Role, - protocol => protocol_name()}, Location). - send_application_data(Data, From, _StateName, #state{static_env = #static_env{socket = Socket, transport_cb = Transport}, @@ -1270,12 +774,12 @@ send_application_data(Data, From, _StateName, {Msgs, ConnectionStates} = dtls_record:encode_data(Data, Version, ConnectionStates0), State = State0#state{connection_states = ConnectionStates}, - case send(Transport, Socket, Msgs) of + case dtls_gen_connection:send(Transport, Socket, Msgs) of ok -> ssl_logger:debug(LogLevel, outbound, 'record', Msgs), - ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]); + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, ok}]); Result -> - ssl_connection:hibernate_after(connection, State, [{reply, From, Result}]) + ssl_gen_statem:hibernate_after(connection, State, [{reply, From, Result}]) end end. diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl index 7d7be5743d..4c5c0a490f 100644 --- a/lib/ssl/src/dtls_connection_sup.erl +++ b/lib/ssl/src/dtls_connection_sup.erl @@ -57,10 +57,10 @@ init(_O) -> MaxT = 3600, Name = undefined, % As simple_one_for_one is used. - StartFunc = {dtls_connection, start_link, []}, + StartFunc = {ssl_gen_statem, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [dtls_connection, ssl_connection], + Modules = [ssl_gen_statem, dtls_connection], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl new file mode 100644 index 0000000000..2032d77074 --- /dev/null +++ b/lib/ssl/src/dtls_gen_connection.erl @@ -0,0 +1,685 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. 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: +%%---------------------------------------------------------------------- +-module(dtls_gen_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("dtls_connection.hrl"). +-include("dtls_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("dtls_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). + +%% Setup +-export([start_fsm/8, + pids/1]). + +%% Handshake handling +-export([send_handshake/2, + send_handshake_flight/2, + queue_handshake/2, + queue_change_cipher/2, + reinit/1, + reinit_handshake_data/1, + select_sni_extension/1, + empty_connection_state/2]). + +%% State transition handling +-export([next_event/3, + next_event/4, + handle_protocol_record/3, + new_flight/0, + initial_flight_state/1 + ]). + +%% Data handling +-export([send/3, + socket/4, + setopts/3, + getopts/3, + handle_info/3]). + +%% Alert and close handling +-export([send_alert/2, + send_alert_in_connection/2, + close/5, + protocol_name/0]). +%%==================================================================== +%% Internal application API +%%==================================================================== +%%==================================================================== +%% Setup +%%==================================================================== +start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Tracker} = Opts, + User, {CbModule, _, _, _, _} = CbInfo, + 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), + ssl_gen_statem:handshake(SslSocket, Timeout) + catch + error:{badmatch, {error, _} = Error} -> + Error + end. + +pids(_) -> + [self()]. + +%%==================================================================== +%% State transition handling +%%==================================================================== +next_record(#state{handshake_env = + #handshake_env{unprocessed_handshake_events = N} = HsEnv} + = State) when N > 0 -> + {no_record, State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := Epoch}} = ConnectionStates} = State) -> + CurrentRead = dtls_record:get_connection_state_by_epoch(Epoch, ConnectionStates, read), + case dtls_record:replay_detect(CT, CurrentRead) of + false -> + decode_cipher_text(State#state{connection_states = ConnectionStates}) ; + true -> + %% Ignore replayed record + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}) + end; +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} | Rest]} + = Buffers, + connection_states = #{current_read := #{epoch := CurrentEpoch}} = ConnectionStates} = State) + when Epoch > CurrentEpoch -> + %% TODO Buffer later Epoch message, drop it for now + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{protocol_buffers = + #protocol_buffers{dtls_cipher_texts = [ _ | Rest]} + = Buffers, + connection_states = ConnectionStates} = State) -> + %% Drop old epoch message + next_record(State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnectionStates}); +next_record(#state{static_env = #static_env{role = server, + socket = {Listener, {Client, _}}}} = State) -> + dtls_packet_demux:active_once(Listener, Client, self()), + {no_record, State}; +next_record(#state{protocol_specific = #{active_n_toggle := true, + active_n := N} = ProtocolSpec, + static_env = #static_env{role = client, + socket = {_Server, Socket} = DTLSSocket, + close_tag = CloseTag, + transport_cb = Transport}} = State) -> + case dtls_socket:setopts(Transport, Socket, [{active,N}]) of + ok -> + {no_record, State#state{protocol_specific = + ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, DTLSSocket}, + {no_record, State} + end; +next_record(State) -> + {no_record, State}. + +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(StateName, no_record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case next_record(State0) of + {no_record, State} -> + ssl_gen_statem:hibernate_after(StateName, State, Actions); + {#ssl_tls{epoch = CurrentEpoch, + type = ?HANDSHAKE, + version = Version} = Record, State1} -> + State = dtls_version(StateName, Version, State1), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#ssl_tls{epoch = CurrentEpoch} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#ssl_tls{epoch = Epoch, + type = ?HANDSHAKE, + version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake + {#ssl_tls{epoch = Epoch, + type = ?CHANGE_CIPHER_SPEC, + version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + {#ssl_tls{epoch = _Epoch, + version = _Version}, State} -> + %% TODO maybe buffer later epoch + next_event(StateName, no_record, State, Actions); + {#alert{} = Alert, State} -> + Version = State#state.connection_env#connection_env.negotiated_version, + handle_own_alert(Alert, Version, StateName, State) + end; +next_event(connection = StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch, + type = ?HANDSHAKE, + version = Version} = Record -> + State = dtls_version(StateName, Version, State0), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = CurrentEpoch} -> + {next_state, StateName, State0, [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = Epoch, + type = ?HANDSHAKE, + version = _Version} when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake + #ssl_tls{epoch = Epoch, + type = ?CHANGE_CIPHER_SPEC, + version = _Version} when Epoch == CurrentEpoch-1 -> + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); + _ -> + next_event(StateName, no_record, State0, Actions) + end; +next_event(StateName, Record, + #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> + case Record of + #ssl_tls{epoch = CurrentEpoch, + version = Version} = Record -> + State = dtls_version(StateName, Version, State0), + {next_state, StateName, State, + [{next_event, internal, {protocol_record, Record}} | Actions]}; + #ssl_tls{epoch = _Epoch, + version = _Version} = _Record -> + %% TODO maybe buffer later epoch + next_event(StateName, no_record, State0, Actions); + #alert{} = Alert -> + Version = State0#state.connection_env#connection_env.negotiated_version, + handle_own_alert(Alert, Version, StateName, State0) + end. + +initial_flight_state(udp)-> + {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}; +initial_flight_state(_) -> + reliable. + +new_flight() -> + #{next_sequence => 0, + handshakes => [], + change_cipher_spec => undefined, + handshakes_after_change_cipher_spec => []}. + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := Flight, + change_cipher_spec := undefined}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {Encoded, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0), + send(Transport, Socket, Encoded), + ssl_logger:debug(LogLevel, outbound, 'record', Encoded), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := [_|_] = Flight0, + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := []}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {HsBefore, ConnectionStates1} = + encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0), + {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), + + send(Transport, Socket, [HsBefore, EncChangeCipher]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), + ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := [_|_] = Flight0, + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := Flight1}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {HsBefore, ConnectionStates1} = + encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0), + {EncChangeCipher, ConnectionStates2} = + encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1), + {HsAfter, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2), + send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), + ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), + {State0#state{connection_states = ConnectionStates}, []}; + +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := [], + change_cipher_spec := ChangeCipher, + handshakes_after_change_cipher_spec := Flight1}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0, + Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), + {EncChangeCipher, ConnectionStates1} = + encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), + {HsAfter, ConnectionStates} = + encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1), + send(Transport, Socket, [EncChangeCipher, HsAfter]), + ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), + ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), + {State0#state{connection_states = ConnectionStates}, []}. + +%%% DTLS record protocol level application data messages + +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) -> + case ssl_gen_statem:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State1} -> + {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1), + ssl_gen_statem:hibernate_after(StateName, State, Actions) + end; +%%% DTLS record protocol level handshake messages +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, + fragment = Data}, + StateName, + #state{protocol_buffers = Buffers0, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = Options} = State) -> + try + case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0, Options) of + {[], Buffers} -> + next_event(StateName, no_record, State#state{protocol_buffers = Buffers}); + {Packets, Buffers} -> + HsEnv = State#state.handshake_env, + Events = dtls_handshake_events(Packets), + {next_state, StateName, + State#state{protocol_buffers = Buffers, + handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} + end + catch throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State) + end; +%%% DTLS record protocol level change cipher messages +handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% DTLS record protocol level Alert messages +handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + case decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + #alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State) + end; +%% Ignore unknown TLS record level protocol messages +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. + +%%==================================================================== +%% Handshake handling +%%==================================================================== +send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> + #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), + send_handshake_flight(queue_handshake(Handshake, State), Epoch). + +queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := HsBuffer0, + change_cipher_spec := undefined, + next_sequence := Seq} = Flight0, + ssl_options = #{log_level := LogLevel}} = State) -> + Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), + Hist = update_handshake_history(Handshake0, Handshake, Hist0), + ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), + + State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], + next_sequence => Seq +1}, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; + +queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0, + next_sequence := Seq} = Flight0, + ssl_options = #{log_level := LogLevel}} = State) -> + Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), + Hist = update_handshake_history(Handshake0, Handshake, Hist0), + ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake0), + + State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], + next_sequence => Seq +1}, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}. + +queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, + connection_states = ConnectionStates0} = State) -> + ConnectionStates = + dtls_record:next_epoch(ConnectionStates0, write), + State#state{flight_buffer = Flight#{change_cipher_spec => ChangeCipher}, + connection_states = ConnectionStates}. + +reinit(State) -> + %% To be API compatible with TLS NOOP here + reinit_handshake_data(State). +reinit_handshake_data(#state{static_env = #static_env{data_tag = DataTag}, + protocol_buffers = Buffers, + protocol_specific = PS, + handshake_env = HsEnv} = State) -> + State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), + public_key_info = undefined, + premaster_secret = undefined}, + protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, + flight_buffer = new_flight(), + protocol_buffers = + Buffers#protocol_buffers{ + dtls_handshake_next_seq = 0, + dtls_handshake_next_fragments = [], + dtls_handshake_later_fragments = [] + }}. + +select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> + SNI; +select_sni_extension(_) -> + undefined. + +empty_connection_state(ConnectionEnd, BeastMitigation) -> + Empty = ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation), + dtls_record:empty_connection_state(Empty). +%%==================================================================== +%% Alert and close handling +%%==================================================================== +encode_alert(#alert{} = Alert, Version, ConnectionStates) -> + dtls_record:encode_alert_record(Alert, Version, ConnectionStates). + +send_alert(Alert, #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + + connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0, + ssl_options = #{log_level := LogLevel}} = State0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), + send(Transport, Socket, BinMsg), + ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), + State0#state{connection_states = ConnectionStates}. + +send_alert_in_connection(Alert, State) -> + _ = send_alert(Alert, State), + ok. + +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + dtls_socket:close(Transport,Socket). + +protocol_name() -> + "DTLS". + +%%==================================================================== +%% Data handling +%%==================================================================== +send(Transport, {Listener, Socket}, Data) when is_pid(Listener) -> + %% Server socket + dtls_socket:send(Transport, Socket, Data); +send(Transport, Socket, Data) -> % Client socket + dtls_socket:send(Transport, Socket, Data). + +socket(Pid, Transport, Socket, _Tracker) -> + dtls_socket:socket(Pid, Transport, Socket, ?MODULE). + +setopts(Transport, Socket, Other) -> + dtls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + dtls_socket:getopts(Transport, Socket, Tag). + +%% raw data from socket, unpack records +handle_info({Protocol, _, _, _, Data}, StateName, + #state{static_env = #static_env{role = Role, + data_tag = Protocol}} = State0) -> + case next_dtls_record(Data, StateName, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), + {stop, {shutdown, own_alert}, State0} + end; + +handle_info({PassiveTag, Socket}, StateName, + #state{static_env = #static_env{socket = {_, Socket}, + passive_tag = PassiveTag}, + protocol_specific = PS} = State) -> + next_event(StateName, no_record, + State#state{protocol_specific = PS#{active_n_toggle => true}}); + +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{ + role = Role, + socket = Socket, + close_tag = CloseTag}, + connection_env = #connection_env{negotiated_version = Version}, + socket_options = #socket_options{active = Active}, + protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, + protocol_specific = PS} = State) -> + %% Note that as of DTLS 1.2 (TLS 1.1), + %% failure to properly close a connection no longer requires that a + %% session not be resumed. This is a change from DTLS 1.0 to conform + %% with widespread implementation practice. + case (Active == false) andalso (CTs =/= []) of + false -> + case Version of + {254, N} when N =< 253 -> + ok; + _ -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %%invalidate_session(Role, Host, Port, Session) + ok + end, + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown, transport_closed}, State}; + true -> + %% Fixes non-delivery of final DTLS record in {active, once}. + %% Basically allows the application the opportunity to set {active, once} again + %% and then receive the final message. + next_event(StateName, no_record, State#state{ + protocol_specific = PS#{active_n_toggle => true}}) + end; + +handle_info(new_cookie_secret, StateName, + #state{protocol_specific = #{current_cookie_secret := Secret} = CookieInfo} = State) -> + erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), + {next_state, StateName, State#state{protocol_specific = + CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), + previous_cookie_secret => Secret}}}; +handle_info(Msg, StateName, State) -> + ssl_gen_statem:handle_info(Msg, StateName, State). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +dtls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). + +unprocessed_events(Events) -> + %% The first handshake event will be processed immediately + %% as it is entered first in the event queue and + %% when it is processed there will be length(Events)-1 + %% handshake events left to process before we should + %% process more TLS-records received on the socket. + erlang:length(Events)-1. + +encode_handshake_flight(Flight, Version, MaxFragmentSize, Epoch, ConnectionStates) -> + Fragments = lists:map(fun(Handshake) -> + dtls_handshake:fragment_handshake(Handshake, MaxFragmentSize) + end, Flight), + dtls_record:encode_handshake(Fragments, Version, Epoch, ConnectionStates). + +encode_change_cipher(#change_cipher_spec{}, Version, Epoch, ConnectionStates) -> + dtls_record:encode_change_cipher_spec(Version, Epoch, ConnectionStates). + +update_handshake_history(#hello_verify_request{}, _, Hist) -> + Hist; +update_handshake_history(_, Handshake, Hist) -> + ssl_handshake:update_handshake_history(Hist, iolist_to_binary(Handshake)). + +next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ + dtls_record_buffer = Buf0, + dtls_cipher_texts = CT0} = Buffers, + connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{data_tag = DataTag}, + ssl_options = SslOpts} = State0) -> + case dtls_record:get_dtls_records(Data, + {DataTag, StateName, Version, + [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]}, + Buf0, SslOpts) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(State0#state{protocol_buffers = + Buffers#protocol_buffers{dtls_record_buffer = Buf1, + dtls_cipher_texts = CT1}}); + #alert{} = Alert -> + Alert + end. + + + +decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [ CT | Rest]} = Buffers, + connection_states = ConnStates0} = State) -> + case dtls_record:decode_cipher_text(CT, ConnStates0) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnStates}}; + #alert{} = Alert -> + {Alert, State} + end. + +decode_alerts(Bin) -> + ssl_alert:decode(Bin). + +handle_alerts([], Result) -> + Result; +handle_alerts(_, {stop, _, _} = Stop) -> + Stop; +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)); +handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> + handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)). + +handle_own_alert(Alert, Version, StateName, + #state{static_env = #static_env{data_tag = udp, + role = Role}, + ssl_options = #{log_level := LogLevel}} = State0) -> + case ignore_alert(Alert, State0) of + {true, State} -> + log_ignore_alert(LogLevel, StateName, Alert, Role), + {next_state, StateName, State}; + {false, State} -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State) + end; +handle_own_alert(Alert, Version, StateName, State) -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State). +ignore_alert(#alert{level = ?FATAL}, #state{protocol_specific = #{ignored_alerts := N, + max_ignored_alerts := N}} = State) -> + {false, State}; +ignore_alert(#alert{level = ?FATAL} = Alert, + #state{protocol_specific = #{ignored_alerts := N} = PS} = State) -> + case is_ignore_alert(Alert) of + true -> + {true, State#state{protocol_specific = PS#{ignored_alerts => N+1}}}; + false -> + {false, State} + end; +ignore_alert(_, State) -> + {false, State}. + +%% RFC 6347 4.1.2.7. Handling Invalid Records +%% recommends to silently ignore invalid DTLS records when +%% upd is the transport. Note we do not support compression so no need +%% include ?DECOMPRESSION_FAILURE +is_ignore_alert(#alert{description = ?BAD_RECORD_MAC}) -> + true; +is_ignore_alert(#alert{description = ?RECORD_OVERFLOW}) -> + true; +is_ignore_alert(#alert{description = ?DECODE_ERROR}) -> + true; +is_ignore_alert(#alert{description = ?DECRYPT_ERROR}) -> + true; +is_ignore_alert(#alert{description = ?ILLEGAL_PARAMETER}) -> + true; +is_ignore_alert(_) -> + false. + +log_ignore_alert(Level, StateName, #alert{where = Location} = Alert, Role) -> + ssl_logger:log(info, + Level, #{alert => Alert, + alerter => ignored, + statename => StateName, + role => Role, + protocol => protocol_name()}, Location). + +dtls_version(hello, Version, #state{static_env = #static_env{role = server}, + connection_env = CEnv} = State) -> + State#state{connection_env = CEnv#connection_env{negotiated_version = Version}}; %%Inital version +dtls_version(_,_, State) -> + State. diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 9c1cb8ca8c..af053ef48c 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -47,20 +47,20 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), - ssl_options(), binary(), boolean(), der_cert()) -> + ssl_options(), binary(), boolean(), [der_cert()]) -> #client_hello{}. %% %% Description: Creates a client hello message. %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, SslOpts, - Id, Renegotiation, OwnCert) -> + Id, Renegotiation, OwnCerts) -> %% First client hello (two sent in DTLS ) uses empty Cookie client_hello(Host, Port, <<>>, ConnectionStates, SslOpts, - Id, Renegotiation, OwnCert, undefined). + Id, Renegotiation, OwnCerts, undefined). %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(), - ssl_options(), binary(),boolean(), der_cert(), binary() | undefined) -> + ssl_options(), binary(),boolean(), [der_cert()], binary() | undefined) -> #client_hello{}. %% %% Description: Creates a client hello message. @@ -174,27 +174,28 @@ handle_client_hello(Version, signature_algs := SupportedHashSigns, eccs := SupportedECCs, honor_ecc_order := ECCOrder} = SslOpts, - {SessIdTracker, Session0, ConnectionStates0, Cert, _}, + {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _}, Renegotiation) -> + OwnCert = ssl_handshake:select_own_cert(OwnCerts), case dtls_record:is_acceptable_version(Version, Versions) of true -> Curves = maps:get(elliptic_curves, HelloExt, undefined), ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), TLSVersion = dtls_v1:corresponding_tls_version(Version), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, Cert,TLSVersion), + ClientHashSigns, SupportedHashSigns, OwnCert,TLSVersion), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, SessIdTracker, Session0#session{ecc = ECCCurve}, TLSVersion, - SslOpts, Cert), + SslOpts, OwnCert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); _ -> #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, Cert, KeyExAlg, + case ssl_handshake:select_hashsign({ClientHashSigns, undefined}, OwnCert, KeyExAlg, SupportedHashSigns, TLSVersion) of #alert{} = Alert -> Alert; diff --git a/lib/ssl/src/dtls_listener_sup.erl b/lib/ssl/src/dtls_listener_sup.erl index ab1c5eee20..4f46407290 100644 --- a/lib/ssl/src/dtls_listener_sup.erl +++ b/lib/ssl/src/dtls_listener_sup.erl @@ -30,8 +30,8 @@ %% API -export([start_link/0]). -export([start_child/1, - lookup_listner/1, - register_listner/2]). + lookup_listener/2, + register_listener/3]). %% Supervisor callback -export([init/1]). @@ -44,32 +44,34 @@ start_link() -> start_child(Args) -> supervisor:start_child(?MODULE, Args). - -lookup_listner(0) -> - undefined; -lookup_listner(Port) -> - try ets:lookup(dtls_listener_sup, Port) of - [{Port, {Owner, Handler}}] -> + +lookup_listener(IP, Port) -> + try ets:lookup(dtls_listener_sup, {IP, Port}) of + [] -> + undefined; + [{{IP, Port}, {Owner, Handler}}] -> case erlang:is_process_alive(Handler) of true -> - case (Owner =/= undefined) andalso erlang:is_process_alive(Owner) of + case (Owner =/= undefined) andalso + erlang:is_process_alive(Owner) of true -> + %% Trying to bind port that is already bound {error, already_listening}; false -> + %% Re-open same listen socket when the handler + %% is dead. {ok, Handler} end; false -> - ets:delete(dtls_listener_sup, Port), + ets:delete(dtls_listener_sup, {IP, Port}), undefined - end; - [] -> - undefined + end catch _:_ -> undefined end. -register_listner(OwnerAndListner, Port) -> - ets:insert(dtls_listener_sup, {Port, OwnerAndListner}). +register_listener(OwnerAndListner, IP, Port) -> + ets:insert(dtls_listener_sup, {{IP, Port}, OwnerAndListner}). %%%========================================================================= %%% Supervisor callback diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index 234d298989..f9660ea1b6 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -102,38 +102,19 @@ getstat(PacketSocket, Opts) -> %%% gen_server callbacks %%%=================================================================== -init([Port0, {TransportModule, _,_,_,_} = TransportInfo, EmOpts, InetOptions, DTLSOptions]) -> - try - {ok, Socket} = TransportModule:open(Port0, InetOptions), - InternalActiveN = case application:get_env(ssl, internal_active_n) of - {ok, N} when is_integer(N) -> - N; - _ -> - ?INTERNAL_ACTIVE_N - end, - - Port = case Port0 of - 0 -> - {ok, P} = inet:port(Socket), - P; - _ -> - Port0 - end, - - {ok, SessionIdHandle} = session_id_tracker(DTLSOptions), - - {ok, #state{active_n = InternalActiveN, - port = Port, - first = true, - transport = TransportInfo, - dtls_options = DTLSOptions, - emulated_options = EmOpts, - listener = Socket, - close = false, - session_id_tracker = SessionIdHandle}} - catch _:_ -> - {stop, {shutdown, {error, closed}}} - end. +init([Port0, TransportInfo, EmOpts, DTLSOptions, Socket]) -> + InternalActiveN = get_internal_active_n(), + {ok, SessionIdHandle} = session_id_tracker(Socket, DTLSOptions), + {ok, #state{active_n = InternalActiveN, + port = Port0, + first = true, + transport = TransportInfo, + dtls_options = DTLSOptions, + emulated_options = EmOpts, + listener = Socket, + close = false, + session_id_tracker = SessionIdHandle}}. + handle_call({accept, _}, _, #state{close = true} = State) -> {reply, {error, closed}, State}; @@ -374,5 +355,14 @@ emulated_opts_list(Opts, [active | Rest], Acc) -> %% 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(_) -> - dtls_server_session_cache_sup:start_child(ssl_server_session_cache_sup:session_opts()). +session_id_tracker(Listner,_) -> + dtls_server_session_cache_sup:start_child(Listner). + +get_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/dtls_server_session_cache_sup.erl b/lib/ssl/src/dtls_server_session_cache_sup.erl index 42cbda27ec..457eb90167 100644 --- a/lib/ssl/src/dtls_server_session_cache_sup.erl +++ b/lib/ssl/src/dtls_server_session_cache_sup.erl @@ -41,8 +41,8 @@ %%%========================================================================= start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -start_child(Args) -> - supervisor:start_child(?MODULE, [self() | Args]). +start_child(Listener) -> + supervisor:start_child(?MODULE, [Listener | ssl_config:pre_1_3_session_opts()]). %%%========================================================================= %%% Supervisor callback diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl index 5fe4311af1..f1569f5069 100644 --- a/lib/ssl/src/dtls_socket.erl +++ b/lib/ssl/src/dtls_socket.erl @@ -46,33 +46,26 @@ send(Transport, {{IP,Port},Socket}, Data) -> Transport:send(Socket, IP, Port, Data). -listen(Port, #config{transport_info = TransportInfo, - ssl = SslOpts, - emulated = EmOpts0, - inet_user = Options} = Config) -> - - Result = case dtls_listener_sup:lookup_listner(Port) of - undefined -> - Result0 = {ok, Listner0} = - dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts0, #socket_options{}), - Options ++ internal_inet_values(), SslOpts]), - dtls_listener_sup:register_listner({self(), Listner0}, Port), - Result0; - {ok, Listner0} = Result0 -> - dtls_packet_demux:new_owner(Listner0), - dtls_packet_demux:set_all_opts(Listner0, {Options, emulated_socket_options(EmOpts0, #socket_options{}), SslOpts}), - dtls_listener_sup:register_listner({self(), Listner0}, Port), - Result0; - Result0 -> - Result0 - end, - case Result of - {ok, Listner} -> - Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Listner, Port}}}}, - check_active_n(EmOpts0, Socket), - {ok, Socket}; - Err -> - Err +listen(Port, #config{inet_ssl = SockOpts, + ssl = SslOpts, + emulated = EmOpts, + inet_user = Options} = Config) -> + IP = proplists:get_value(ip, SockOpts, {0,0,0,0}), + case dtls_listener_sup:lookup_listener(IP, Port) of + undefined -> + start_new_listener(IP, Port, Config); + {ok, Listener} -> + dtls_packet_demux:new_owner(Listener), + dtls_packet_demux:set_all_opts( + Listener, {Options, + emulated_socket_options(EmOpts, + #socket_options{}), + SslOpts}), + dtls_listener_sup:register_listener({self(), Listener}, + IP, Port), + {ok, create_dtls_socket(Config, Listener, Port)}; + Error -> + Error end. accept(dtls, #config{transport_info = {Transport,_,_,_,_}, @@ -92,7 +85,7 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo inet_ssl = SocketOpts}, Timeout) -> case Transport:open(0, SocketOpts ++ internal_inet_values()) of {ok, Socket} -> - ssl_connection:connect(ConnectionCb, Address, Port, {{Address, Port},Socket}, + ssl_gen_statem:connect(ConnectionCb, Address, Port, {{Address, Port},Socket}, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); @@ -100,8 +93,11 @@ connect(Address, Port, #config{transport_info = {Transport, _, _, _, _} = CbInfo Error end. -close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port}}}}) -> - dtls_listener_sup:register_listner({undefined, Pid}, Port), +close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, Port0}, + inet_ssl = SockOpts}}}) -> + IP = proplists:get_value(ip, SockOpts, {0,0,0,0}), + Port = get_real_port(Pid, Port0), + dtls_listener_sup:register_listener({undefined, Pid}, IP, Port), dtls_packet_demux:close(Pid). close(_, dtls) -> @@ -111,10 +107,11 @@ close(gen_udp, {_Client, _Socket}) -> close(Transport, {_Client, Socket}) -> Transport:close(Socket). -socket(Pids, gen_udp = Transport, {{_, _}, Socket}, ConnectionCb) -> +socket(Pids, gen_udp = Transport, + PeerAndSock = {{_Host, _Port}, _Socket}, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility - fd = {Transport, Socket, ConnectionCb}}; + fd = {Transport, PeerAndSock, ConnectionCb}}; socket(Pids, Transport, Socket, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility @@ -179,14 +176,19 @@ getstat(gen_udp, Pid, Options) when is_pid(Pid) -> dtls_packet_demux:getstat(Pid, Options); getstat(gen_udp, {_,{_, Socket}}, Options) -> inet:getstat(Socket, Options); +getstat(gen_udp, {_, Socket}, Options) -> + inet:getstat(Socket, Options); getstat(gen_udp, Socket, Options) -> inet:getstat(Socket, Options); getstat(Transport, Socket, Options) -> Transport:getstat(Socket, Options). + peername(_, undefined) -> {error, enotconn}; peername(gen_udp, {_, {Client, _Socket}}) -> {ok, Client}; +peername(gen_udp, {Client, _Socket}) -> + {ok, Client}; peername(Transport, Socket) -> Transport:peername(Socket). sockname(gen_udp, {_, {_,Socket}}) -> @@ -270,3 +272,59 @@ validate_inet_option(active, Value) throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> ok. + +get_real_port(Listener, Port0) when is_pid(Listener) andalso + is_integer(Port0) -> + case Port0 of + 0 -> + {ok, {_, NewPort}} = dtls_packet_demux:sockname(Listener), + NewPort; + _ -> + Port0 + end. + +start_new_listener(IP, Port0, + #config{transport_info = {TransportModule, _,_,_,_}, + inet_user = Options} = Config) -> + InetOptions = Options ++ internal_inet_values(), + case TransportModule:open(Port0, InetOptions) of + {ok, Socket} -> + Port = case Port0 of + 0 -> + {ok, P} = inet:port(Socket), + P; + _ -> + Port0 + end, + start_dtls_packet_demux(Config, IP, Port, Socket); + {error, eaddrinuse} -> + {error, already_listening}; + Error -> + Error + end. + +start_dtls_packet_demux(#config{ + transport_info = + {TransportModule, _,_,_,_} = TransportInfo, + emulated = EmOpts0, + ssl = SslOpts} = Config, IP, Port, Socket) -> + EmOpts = emulated_socket_options(EmOpts0, #socket_options{}), + case dtls_listener_sup:start_child([Port, TransportInfo, EmOpts, + SslOpts, Socket]) of + {ok, Multiplexer} -> + ok = TransportModule:controlling_process(Socket, Multiplexer), + dtls_listener_sup:register_listener({self(), Multiplexer}, + IP, Port), + DTLSSocket = create_dtls_socket(Config, Multiplexer, Port), + {ok, DTLSSocket}; + Error -> + Error + end. + +create_dtls_socket(#config{emulated = EmOpts} = Config, + Listener, Port) -> + Socket = #sslsocket{ + pid = {dtls, Config#config{dtls_handler = {Listener, Port}}}}, + check_active_n(EmOpts, Socket), + Socket. + diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl index 9ce13ca281..5ca0cd6904 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-2017. All Rights Reserved. +%% Copyright Ericsson AB 2015-2020. 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,7 +23,7 @@ -export([childspecs/0]). -export([listen/2, accept/1, accept_connection/5, - setup/5, close/1, select/1]). + setup/5, close/1, select/1, address/0]). childspecs() -> inet_tls_dist:childspecs(). @@ -31,6 +31,9 @@ childspecs() -> select(Node) -> inet_tls_dist:gen_select(inet6_tcp, Node). +address() -> + inet_tls_dist:gen_address(inet6_tcp). + listen(Name, Host) -> inet_tls_dist:gen_listen(inet6_tcp, Name, Host). diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index 9bf8e40b70..eaa481f119 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2019. All Rights Reserved. +%% Copyright Ericsson AB 2011-2020. 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,11 +23,11 @@ -export([childspecs/0]). -export([listen/2, accept/1, accept_connection/5, - setup/5, close/1, select/1, is_node_name/1]). + setup/5, close/1, select/1, address/0, is_node_name/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_setup/6, gen_close/2, gen_select/2, gen_address/1]). -export([nodelay/0]). @@ -63,6 +63,14 @@ gen_select(Driver, Node) -> false end. +%% ------------------------------------------------------------ +%% Get the address family that this distribution uses +%% ------------------------------------------------------------ +address() -> + gen_address(inet_tcp). +gen_address(Driver) -> + inet_tcp_dist:gen_address(Driver). + %% ------------------------------------------------------------------------- is_node_name(Node) -> @@ -758,8 +766,8 @@ nodelay() -> get_ssl_options(Type) -> try ets:lookup(ssl_dist_opts, Type) of - [{Type, Opts}] -> - [{erl_dist, true}, {versions, ['tlsv1.2']} | Opts]; + [{Type, Opts0}] -> + [{erl_dist, true} | dist_defaults(Opts0)]; _ -> get_ssl_dist_arguments(Type) catch @@ -770,11 +778,18 @@ get_ssl_options(Type) -> get_ssl_dist_arguments(Type) -> case init:get_argument(ssl_dist_opt) of {ok, Args} -> - [{erl_dist, true}, {versions, ['tlsv1.2']} | ssl_options(Type, lists:append(Args))]; + [{erl_dist, true} | dist_defaults(ssl_options(Type, lists:append(Args)))]; _ -> - [{erl_dist, true}, {versions, ['tlsv1.2']}] + [{erl_dist, true}] end. +dist_defaults(Opts) -> + case proplists:get_value(versions, Opts, undefined) of + undefined -> + [{versions, ['tlsv1.2']} | Opts]; + _ -> + Opts + end. ssl_options(_Type, []) -> []; diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index c524aafe71..f3cc463cff 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -12,6 +12,7 @@ tls_socket, tls_v1, tls_connection_sup, + tls_gen_connection, tls_sender, tls_server_sup, tls_server_session_ticket_sup, @@ -25,6 +26,7 @@ dtls_socket, dtls_v1, dtls_connection_sup, + dtls_gen_connection, dtls_packet_demux, dtls_listener_sup, dtls_sup, @@ -34,8 +36,9 @@ ssl, %% Main API ssl_session_cache_api, %% Both TLS/SSL and DTLS + tls_dtls_connection, ssl_config, - ssl_connection, + ssl_gen_statem, ssl_handshake, ssl_record, ssl_cipher, @@ -51,12 +54,15 @@ ssl_dist_sup, ssl_dist_connection_sup, ssl_dist_admin_sup, + tls_dist_sup, + tls_dist_server_sup, %% SSL/TLS session and cert handling ssl_session, ssl_session_cache, ssl_server_session_cache, ssl_server_session_cache_db, ssl_server_session_cache_sup, + ssl_upgrade_server_session_cache_sup, ssl_manager, ssl_pem_cache, ssl_pkix_db, @@ -78,5 +84,5 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-3.5","public_key-1.8","kernel-6.0", + {runtime_dependencies, ["stdlib-3.12","public_key-1.8","kernel-6.0", "erts-10.0","crypto-4.2", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index c5471a3070..1b1b5d50f1 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -20,7 +20,8 @@ %% -%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl +%%% Purpose : Main API module for the SSL application that implements TLS and DTLS +%%% SSL is a legacy name. -module(ssl). @@ -300,7 +301,7 @@ %% ------------------------------------------------------------------------------------------------------- -type common_option() :: {protocol, protocol()} | {handshake, handshake_completion()} | - {cert, cert()} | + {cert, cert() | [cert()]} | {certfile, cert_pem()} | {key, key()} | {keyfile, key_pem()} | @@ -310,6 +311,7 @@ {signature_algs_cert, signature_schemes()} | {supported_groups, supported_groups()} | {secure_renegotiate, secure_renegotiation()} | + {keep_secrets, keep_secrets()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | {crl_check, crl_check()} | @@ -346,6 +348,7 @@ -type cipher_filters() :: list({key_exchange | cipher | mac | prf, algo_filter()}). % exported -type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false). +-type keep_secrets() :: boolean(). -type secure_renegotiation() :: boolean(). -type allowed_cert_chain_length() :: integer(). @@ -405,7 +408,7 @@ %% {ocsp_nonce, ocsp_nonce()}. -type client_verify_type() :: verify_type(). --type client_reuse_session() :: session_id(). +-type client_reuse_session() :: session_id() | {session_id(), SessionData::binary()}. -type client_reuse_sessions() :: boolean() | save. -type client_cacerts() :: [public_key:der_encoded()]. -type client_cafile() :: file:filename(). @@ -505,6 +508,7 @@ client_random | server_random | master_secret | + keylog | tls_options_name(). -type tls_options_name() :: atom(). %% ------------------------------------------------------------------------------------------------------- @@ -550,7 +554,7 @@ stop() -> TCPSocket :: socket(), TLSOptions :: [tls_client_option()]. -connect(Socket, SslOptions) when is_port(Socket) -> +connect(Socket, SslOptions) -> connect(Socket, SslOptions, infinity). -spec connect(TCPSocket, TLSOptions, Timeout) -> @@ -567,24 +571,23 @@ connect(Socket, SslOptions) when is_port(Socket) -> Port :: inet:port_number(), TLSOptions :: [tls_client_option()]. -connect(Socket, SslOptions0, Timeout) when is_port(Socket), +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), EmulatedOptions = tls_socket:emulated_options(), {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), try handle_options(SslOptions0 ++ SocketValues, client) of - {ok, Config} -> - tls_socket:upgrade(Socket, Config, Timeout) + {ok, Config} -> + tls_socket:upgrade(Socket, Config, Timeout) catch - _:{error, Reason} -> + _:{error, Reason} -> {error, Reason} - end; + end; connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). - -spec connect(Host, Port, TLSOptions, Timeout) -> {ok, sslsocket()} | {ok, sslsocket(),Ext :: protocol_extensions()} | @@ -599,9 +602,9 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout try {ok, Config} = handle_options(Options, client, Host), case Config#config.connection_cb of - tls_connection -> + tls_gen_connection -> tls_socket:connect(Host,Port,Config,Timeout); - dtls_connection -> + dtls_gen_connection -> dtls_socket:connect(Host,Port,Config,Timeout) end catch @@ -650,9 +653,9 @@ transport_accept(#sslsocket{pid = {ListenSocket, #config{connection_cb = ConnectionCb} = Config}}, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> case ConnectionCb of - tls_connection -> + tls_gen_connection -> tls_socket:accept(ListenSocket, Config, Timeout); - dtls_connection -> + dtls_gen_connection -> dtls_socket:accept(ListenSocket, Config, Timeout) end. @@ -731,7 +734,7 @@ handshake(ListenSocket) -> handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - ssl_connection:handshake(Socket, Timeout); + ssl_gen_statem:handshake(Socket, Timeout); %% If Socket is a ordinary socket(): upgrades a gen_tcp, or equivalent, socket to %% an SSL socket, that is, performs the SSL/TLS server-side handshake and returns @@ -739,9 +742,8 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim %% %% If Socket is an sslsocket(): provides extra SSL/TLS/DTLS options to those %% specified in ssl:listen/2 and then performs the SSL/TLS/DTLS handshake. -handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> +handshake(ListenSocket, SslOptions) -> handshake(ListenSocket, SslOptions, infinity). - -spec handshake(Socket, Options, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | @@ -761,7 +763,7 @@ handshake(#sslsocket{fd = {_, _, _, Trackers}} = Socket, SslOpts, Timeout) when try Tracker = proplists:get_value(option_tracker, Trackers), {ok, EmOpts, _} = tls_socket:get_all_opts(Tracker), - ssl_connection:handshake(Socket, {SslOpts, + ssl_gen_statem:handshake(Socket, {SslOpts, tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) catch Error = {error, _Reason} -> Error @@ -770,33 +772,30 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout) (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> try {ok, EmOpts, _} = dtls_packet_demux:get_all_opts(Pid), - ssl_connection:handshake(Socket, {SslOpts, + ssl_gen_statem:handshake(Socket, {SslOpts, tls_socket:emulated_socket_options(EmOpts, #socket_options{})}, Timeout) catch Error = {error, _Reason} -> Error end; -handshake(Socket, SslOptions, Timeout) when is_port(Socket), - (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> +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), EmulatedOptions = tls_socket:emulated_options(), {ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions), ConnetionCb = connection_cb(SslOptions), try handle_options(SslOptions ++ SocketValues, server) 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(SslOpts), - ssl_connection:handshake(ConnetionCb, Port, Socket, + {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) catch - Error = {error, _Reason} -> Error - end. - + Error = {error, _Reason} -> Error + end. %%-------------------------------------------------------------------- -spec handshake_continue(HsSocket, Options) -> @@ -824,14 +823,14 @@ handshake_continue(Socket, SSLOptions) -> %% Description: Continues the handshke possible with newly supplied options. %%-------------------------------------------------------------------- handshake_continue(Socket, SSLOptions, Timeout) -> - ssl_connection:handshake_continue(Socket, SSLOptions, Timeout). + ssl_gen_statem:handshake_continue(Socket, SSLOptions, Timeout). %%-------------------------------------------------------------------- -spec handshake_cancel(#sslsocket{}) -> any(). %% %% Description: Cancels the handshakes sending a close alert. %%-------------------------------------------------------------------- handshake_cancel(Socket) -> - ssl_connection:handshake_cancel(Socket). + ssl_gen_statem:handshake_cancel(Socket). %%-------------------------------------------------------------------- -spec close(SslSocket) -> ok | {error, Reason} when @@ -841,7 +840,7 @@ handshake_cancel(Socket) -> %% Description: Close an ssl connection %%-------------------------------------------------------------------- close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT}); + ssl_gen_statem:close(Pid, {close, ?DEFAULT_TIMEOUT}); close(#sslsocket{pid = {dtls, #config{dtls_handler = {_, _}}}} = DTLSListen) -> dtls_socket:close(DTLSListen); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) -> @@ -859,7 +858,7 @@ close(#sslsocket{pid = [TLSPid|_]}, {Pid, Timeout} = DownGrade) when is_pid(TLSPid), is_pid(Pid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - case ssl_connection:close(TLSPid, {close, DownGrade}) of + case ssl_gen_statem:close(TLSPid, {close, DownGrade}) of ok -> %% In normal close {error, closed} is regarded as ok, as it is not interesting which side %% that got to do the actual close. But in the downgrade case only {ok, Port} is a sucess. {error, closed}; @@ -868,7 +867,7 @@ close(#sslsocket{pid = [TLSPid|_]}, end; close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> - ssl_connection:close(TLSPid, {close, Timeout}); + ssl_gen_statem:close(TLSPid, {close, Timeout}); close(#sslsocket{pid = {dtls = ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) -> dtls_socket:close(Transport, ListenSocket); close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) -> @@ -882,7 +881,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_} %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) -> - ssl_connection:send(Pid, Data); + ssl_gen_statem:send(Pid, Data); send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) -> tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data)); send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) -> @@ -915,7 +914,7 @@ recv(Socket, Length) -> recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> - ssl_connection:recv(Pid, Length, Timeout); + ssl_gen_statem:recv(Pid, Length, Timeout); recv(#sslsocket{pid = {dtls,_}}, _, _) -> {error,enotconn}; recv(#sslsocket{pid = {Listen, @@ -933,7 +932,7 @@ recv(#sslsocket{pid = {Listen, %% or once. %%-------------------------------------------------------------------- controlling_process(#sslsocket{pid = [Pid|_]}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> - ssl_connection:new_user(Pid, NewOwner); + ssl_gen_statem:new_user(Pid, NewOwner); controlling_process(#sslsocket{pid = {dtls, _}}, NewOwner) when is_pid(NewOwner) -> ok; %% Meaningless but let it be allowed to conform with TLS @@ -953,7 +952,7 @@ controlling_process(#sslsocket{pid = {Listen, %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- connection_information(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - case ssl_connection:connection_information(Pid, false) of + case ssl_gen_statem:connection_information(Pid, false) of {ok, Info} -> {ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]}; Error -> @@ -973,7 +972,7 @@ connection_information(#sslsocket{pid = {dtls,_}}) -> %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) -> - case ssl_connection:connection_information(Pid, include_security_info(Items)) of + case ssl_gen_statem:connection_information(Pid, include_security_info(Items)) of {ok, Info} -> {ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items), Value =/= undefined]}; @@ -1009,7 +1008,7 @@ peername(#sslsocket{pid = {dtls,_}}) -> %% Description: Returns the peercert. %%-------------------------------------------------------------------- peercert(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - case ssl_connection:peer_certificate(Pid) of + case ssl_gen_statem:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; Result -> @@ -1030,7 +1029,7 @@ peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> %% protocol has been negotiated will return {error, protocol_not_negotiated} %%-------------------------------------------------------------------- negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> - ssl_connection:negotiated_protocol(Pid). + ssl_gen_statem:negotiated_protocol(Pid). %%-------------------------------------------------------------------- -spec cipher_suites() -> [old_cipher_suite()] | [string()]. @@ -1207,7 +1206,7 @@ groups(default) -> %% Description: Gets options %%-------------------------------------------------------------------- getopts(#sslsocket{pid = [Pid|_]}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> - ssl_connection:get_opts(Pid, OptionTags); + ssl_gen_statem:get_opts(Pid, OptionTags); getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, OptionTags) when is_list(OptionTags) -> try dtls_socket:getopts(Transport, ListenSocket, OptionTags) of {ok, _} = Result -> @@ -1245,11 +1244,11 @@ setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Opt Options -> case proplists:get_value(packet, Options, undefined) of undefined -> - ssl_connection:set_opts(Pid, Options); + ssl_gen_statem:set_opts(Pid, Options); PacketOpt -> case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of ok -> - ssl_connection:set_opts(Pid, Options); + ssl_gen_statem:set_opts(Pid, Options); Error -> Error end @@ -1262,7 +1261,7 @@ setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0) try proplists:expand([{binary, [{mode, binary}]}, {list, [{mode, list}]}], Options0) of Options -> - ssl_connection:set_opts(Pid, Options) + ssl_gen_statem:set_opts(Pid, Options) catch _:_ -> {error, {options, {not_a_proplist, Options0}}} @@ -1338,7 +1337,7 @@ shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}}, shutdown(#sslsocket{pid = {dtls,_}},_) -> {error, enotconn}; shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) -> - ssl_connection:shutdown(Pid, How). + ssl_gen_statem:shutdown(Pid, How). %%-------------------------------------------------------------------- -spec sockname(SslSocket) -> @@ -1403,12 +1402,12 @@ renegotiate(#sslsocket{pid = [Pid, Sender |_]}) when is_pid(Pid), is_pid(Sender) -> case tls_sender:renegotiate(Sender) of {ok, Write} -> - tls_connection:renegotiation(Pid, Write); + tls_dtls_connection:renegotiation(Pid, Write); Error -> Error end; renegotiate(#sslsocket{pid = [Pid |_]}) when is_pid(Pid) -> - ssl_connection:renegotiation(Pid); + tls_dtls_connection:renegotiation(Pid); renegotiate(#sslsocket{pid = {dtls,_}}) -> {error, enotconn}; renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> @@ -1432,7 +1431,7 @@ update_keys(#sslsocket{pid = [Pid, Sender |_]}, Type0) when is_pid(Pid) andalso read_write -> update_requested end, - tls_connection:send_key_update(Sender, Type); + tls_connection_1_3:send_key_update(Sender, Type); update_keys(_, Type) -> {error, {illegal_parameter, Type}}. @@ -1449,7 +1448,7 @@ update_keys(_, Type) -> %%-------------------------------------------------------------------- prf(#sslsocket{pid = [Pid|_]}, Secret, Label, Seed, WantedLength) when is_pid(Pid) -> - ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength); + tls_dtls_connection:prf(Pid, Secret, Label, Seed, WantedLength); prf(#sslsocket{pid = {dtls,_}}, _,_,_,_) -> {error, enotconn}; prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> @@ -1568,10 +1567,10 @@ supported_suites(all, Version) -> supported_suites(anonymous, Version) -> ssl_cipher:anonymous_suites(Version). -do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_connection) -> +do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_gen_connection) -> tls_socket:listen(Transport, Port, Config); -do_listen(Port, Config, dtls_connection) -> +do_listen(Port, Config, dtls_gen_connection) -> dtls_socket:listen(Port, Config). @@ -2128,8 +2127,11 @@ validate_option(depth, Value) when is_integer(Value), Value >= 0, Value =< 255-> Value; validate_option(cert, Value) when Value == undefined; - is_binary(Value) -> + is_list(Value)-> Value; +validate_option(cert, Value) when Value == undefined; + is_binary(Value)-> + [Value]; validate_option(certfile, undefined = Value) -> Value; validate_option(certfile, Value) when is_binary(Value) -> @@ -2203,12 +2205,17 @@ 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(keep_secrets, Value) when is_boolean(Value) -> + Value; validate_option(client_renegotiation, Value) when is_boolean(Value) -> Value; validate_option(renegotiate_at, Value) when is_integer(Value) -> @@ -2684,9 +2691,9 @@ make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> end. connection_cb(tls) -> - tls_connection; + tls_gen_connection; connection_cb(dtls) -> - dtls_connection; + dtls_gen_connection; connection_cb(Opts) -> connection_cb(proplists:get_value(protocol, Opts, tls)). @@ -2759,7 +2766,7 @@ default_cb_info(dtls) -> include_security_info([]) -> false; include_security_info([Item | Items]) -> - case lists:member(Item, [client_random, server_random, master_secret]) of + case lists:member(Item, [client_random, server_random, master_secret, keylog]) of true -> true; false -> diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 0a9749de96..b5b0a23d85 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -31,7 +31,7 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([trusted_cert_and_path/4, +-export([trusted_cert_and_paths/4, certificate_chain/3, certificate_chain/4, file_to_certificats/2, @@ -50,46 +50,47 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref(), fun()) -> - {der_cert() | unknown_ca, [der_cert()]}. +-spec trusted_cert_and_paths([der_cert()], db_handle(), certdb_ref(), fun()) -> + [{der_cert() | unknown_ca | invalid_issuer | selfsigned_peer, [der_cert()]}]. %% -%% Description: Extracts the root cert (if not presents tries to -%% look it up, if not found {bad_cert, unknown_ca} will be added verification -%% errors. Returns {RootCert, Path, VerifyErrors} +%% Description: Construct input to public_key:pkix_path_validation/3, +%% If the ROOT cert is not found {bad_cert, unknown_ca} will be returned +%% instead of the ROOT cert to be handled as a path validation error +%% by the verify_fun. +%% Returns {RootCert | RootCertRelatedError, Path} +%% Note: Path = lists:reverse(Chain) -- Root, that is on the peer cert +%% always comes first in the chain but last in the path. %%-------------------------------------------------------------------- -trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -> - Path = [BinCert | _] = lists:reverse(CertChain), - OtpCert = public_key:pkix_decode_cert(BinCert, otp), - SignedAndIssuerID = - case public_key:pkix_is_self_signed(OtpCert) of - true -> - {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self), - {self, IssuerId}; - false -> - other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) - end, - - case SignedAndIssuerID of - {error, issuer_not_found} -> - %% The root CA was not sent and cannot be found. - handle_incomplete_chain(Path, PartialChainHandler); - {self, _} when length(Path) == 1 -> - {selfsigned_peer, Path}; - {_ ,{SerialNr, Issuer}} -> - case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, Issuer) of - {ok, Trusted} -> - %% Trusted must be selfsigned or it is an incomplete chain - handle_path(Trusted, Path, PartialChainHandler); - _ -> - %% Root CA could not be verified, but partial - %% chain handler may trusted a cert that we got - handle_incomplete_chain(Path, PartialChainHandler) - end - end. +trusted_cert_and_paths([Peer] = Chain, CertDbHandle, CertDbRef, PartialChainHandler) -> + OtpCert = public_key:pkix_decode_cert(Peer, otp), + case public_key:pkix_is_self_signed(OtpCert) of + true -> + [{selfsigned_peer, [Peer]}]; + false -> + [handle_incomplete_chain(Chain, PartialChainHandler, {unknown_ca, [Peer]}, + CertDbHandle, CertDbRef)] + end; +trusted_cert_and_paths(Chain, CertDbHandle, CertDbRef, PartialChainHandler) -> + %% Construct possible certificate paths from the chain certificates. + %% If the chain contains extraneous certificates there could be + %% more than one possible path such chains might be used to phase out + %% an old certificate. + Paths = paths(Chain, CertDbHandle, CertDbRef), + lists:map(fun(Path) -> + case handle_partial_chain(Path, PartialChainHandler, CertDbHandle, CertDbRef) of + {unknown_ca, _} = Result -> + handle_incomplete_chain(Chain, + PartialChainHandler, + Result, + CertDbHandle, CertDbRef); + Result -> + Result + end + end, Paths). %%-------------------------------------------------------------------- -spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref()) -> - {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. + {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}. %% %% Description: Return the certificate chain to send to peer. %%-------------------------------------------------------------------- @@ -104,7 +105,7 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> %%-------------------------------------------------------------------- -spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref() | {extracted, list()}, [der_cert()]) -> - {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. + {error, no_cert} | {ok, der_cert() | undefined, [der_cert()]}. %% %% Description: Create certificate chain with certs from %%-------------------------------------------------------------------- @@ -359,31 +360,6 @@ other_issuer(OtpCert, BinCert, CertDbHandle, CertDbRef) -> end end. -handle_path({BinCert, OTPCert}, Path, PartialChainHandler) -> - case public_key:pkix_is_self_signed(OTPCert) of - true -> - {BinCert, lists:delete(BinCert, Path)}; - false -> - handle_incomplete_chain(Path, PartialChainHandler) - end. - -handle_incomplete_chain(Chain, Fun) -> - case catch Fun(Chain) of - {trusted_ca, DerCert} -> - new_trusteded_chain(DerCert, Chain); - unknown_ca = Error -> - {Error, Chain}; - _ -> - {unknown_ca, Chain} - end. - -new_trusteded_chain(DerCert, [DerCert | Chain]) -> - {DerCert, Chain}; -new_trusteded_chain(DerCert, [_ | Rest]) -> - new_trusteded_chain(DerCert, Rest); -new_trusteded_chain(_, []) -> - {unknown_ca, []}. - verify_hostname({fallback, Hostname}, Customize, Cert, UserState) when is_list(Hostname) -> case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of true -> @@ -462,3 +438,161 @@ pre_1_3_hash(sha1) -> sha; pre_1_3_hash(Hash) -> Hash. + +paths(Chain, CertDbHandle, CertDbRef) -> + paths(Chain, Chain, CertDbHandle, CertDbRef, []). + +paths([Root], _, _, _, Path) -> + [[Root | Path]]; +paths([Cert1, Cert2 | Rest], Chain, CertDbHandle, CertDbRef, Path) -> + case public_key:pkix_is_issuer(Cert1, Cert2) of + true -> + %% Chain orded so far + paths([Cert2 | Rest], Chain, CertDbHandle, CertDbRef, [Cert1 | Path]); + false -> + %% Chain is unorded and/or contains extraneous certificates + unorded_or_extraneous(Chain, CertDbHandle, CertDbRef) + end. + +unorded_or_extraneous([Peer | FalseChain], CertDbHandle, CertDbRef) -> + ChainCandidates = extraneous_chains(FalseChain), + lists:map(fun(Candidate) -> + path_candidate(Peer, Candidate, CertDbHandle, CertDbRef) + end, + ChainCandidates). + +path_candidate(Peer, ChainCandidateCAs, CertDbHandle, _CertDbRef) -> + {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, ChainCandidateCAs}), + %% certificate_chain/4 will make sure the chain is ordered + case certificate_chain(Peer, CertDbHandle, ExtractedCerts, []) of + {ok, undefined, Chain} -> + lists:reverse(Chain); + {ok, Root, Chain} -> + [Root | lists:reverse(Chain)] + end. + +handle_partial_chain([IssuerCert| Rest] = Path, PartialChainHandler, CertDbHandle, CertDbRef) -> + case public_key:pkix_is_self_signed(IssuerCert) of + true -> %% IssuerCert = ROOT (That is ROOT was included in chain) + {ok, {SerialNr, IssuerId}} = public_key:pkix_issuer_id(IssuerCert, self), + case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of + {ok, {IssuerCert, _}} -> %% Match sent ROOT to trusted ROOT + maybe_shorten_path(Path, PartialChainHandler, {IssuerCert, Rest}); + {ok, _} -> %% Did not match trusted ROOT + maybe_shorten_path(Path, PartialChainHandler, {invalid_issuer, Path}); + _ -> + maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path}) + end; + false -> + OTPCert = public_key:pkix_decode_cert(IssuerCert, otp), + case other_issuer(OTPCert, IssuerCert, CertDbHandle, CertDbRef) of + {other, {SerialNr, IssuerId}} -> + case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of + {ok, {NewIssuerCert, _}} -> + case public_key:pkix_is_self_signed(NewIssuerCert) of + true -> %% NewIssuerCert is a trusted ROOT cert + maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler, {NewIssuerCert, Path}); + false -> + maybe_shorten_path([NewIssuerCert | Path], PartialChainHandler, + {unknown_ca, [NewIssuerCert | Path]}) + end; + _ -> + maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path}) + end; + {error, issuer_not_found} -> + maybe_shorten_path(Path, PartialChainHandler, {unknown_ca, Path}) + end + end. + +maybe_shorten_path(Path, PartialChainHandler, Default) -> + %% This function might shorthen the + %% certificate path to be validated with + %% public_key:pkix_path_validation by letting + %% the user put its trust in an intermidate cert + %% from the certifcate chain sent by the peer. + try PartialChainHandler(Path) of + {trusted_ca, Root} -> + new_trusteded_path(Root, Path, Default); + unknown_ca -> + Default + catch _:_ -> + Default + end. + +new_trusteded_path(DerCert, [DerCert | Chain], _) -> + {DerCert, Chain}; +new_trusteded_path(DerCert, [_ | Rest], Default) -> + new_trusteded_path(DerCert, Rest, Default); +new_trusteded_path(_, [], Default) -> + %% User did not pick a cert present + %% in the cert chain so ignore + Default. + +handle_incomplete_chain([PeerCert| _] = Chain0, PartialChainHandler, Default, CertDbHandle, CertDbRef) -> + %% We received an incomplete chain, that is not all certs expected to be present are present. + %% See if we have the certificates to rebuild it. + case certificate_chain(PeerCert, CertDbHandle, CertDbRef) of + {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found + handle_partial_chain(lists:reverse(Chain), PartialChainHandler, CertDbHandle, CertDbRef); + _ -> + Default + end. + +extraneous_chains(Certs) -> + %% If some certs claim to be the same cert that is have the same + %% subject field we should create a list of possible chain certs + %% for each such cert. Only one chain, if any, should be + %% verifiable using available ROOT certs. + Subjects = [{subject(Cert), Cert} || Cert <- Certs], + Duplicates = find_duplicates(Subjects), + %% Number of certs with duplicates (same subject) has been limited + %% to two and the maximum number of combinations is limited to 4. + build_candidates(Duplicates, 2, 4). + +build_candidates(Map, Duplicates, Combinations) -> + Subjects = maps:keys(Map), + build_candidates(Subjects, Map, Duplicates, 1, Combinations, []). +%% +build_candidates([], _, _, _, _, Acc) -> + Acc; +build_candidates([H|T], Map, Duplicates, Combinations, Max, Acc0) -> + case maps:get(H, Map) of + {Certs, Counter} when Counter > 1 andalso + Duplicates > 0 andalso + Counter * Combinations =< Max -> + case Acc0 of + [] -> + Acc = [[Cert] || Cert <- Certs], + build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc); + _Else -> + Acc = [[Cert|L] || Cert <- Certs, L <- Acc0], + build_candidates(T, Map, Duplicates - 1, Combinations * Counter, Max, Acc) + end; + {[Cert|_], _} -> + case Acc0 of + [] -> + Acc = [[Cert]], + build_candidates(T, Map, Duplicates, Combinations, Max, Acc); + _Else -> + Acc = [[Cert|L] || L <- Acc0], + build_candidates(T, Map, Duplicates, Combinations, Max, Acc) + end + end. + +find_duplicates(Chain) -> + find_duplicates(Chain, #{}). +%% +find_duplicates([], Acc) -> + Acc; +find_duplicates([{Subject, Cert}|T], Acc) -> + case maps:get(Subject, Acc, none) of + none -> + find_duplicates(T, Acc#{Subject => {[Cert], 1}}); + {Certs, Counter} -> + find_duplicates(T, Acc#{Subject => {[Cert|Certs], Counter + 1}}) + end. + +subject(Cert) -> + {_Serial,Subject} = public_key:pkix_subject_id(Cert), + Subject. + diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 6bc7f6e353..a7fac8722b 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -530,13 +530,15 @@ rsa_suites(0) -> ?TLS_RSA_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_3DES_EDE_CBC_SHA ]; -rsa_suites(N) when N =< 4 -> +rsa_suites(N) when N >= 3 -> [ ?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_suites(_) -> + []. %%-------------------------------------------------------------------- -spec filter(undefined | binary(), [ssl_cipher_format:cipher_suite()], diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index 0a7c4560fb..9f2141b6f8 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -260,6 +260,18 @@ %% TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = { 0xC0, 0x0A } -define(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#0A)>>). +%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM = {0xC0,0xAC} +-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, <<?BYTE(16#C0), ?BYTE(16#AC)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM = {0xC0,0xAD} +-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, <<?BYTE(16#C0), ?BYTE(16#AD)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = {0xC0,0xAE} +-define(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AE)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = {0xC0,0xAF} +-define(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, <<?BYTE(16#C0), ?BYTE(16#AF)>>). + %% ECDH_RSA %% TLS_ECDH_RSA_WITH_NULL_SHA = { 0xC0, 0x0B } diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl index e42f0b817b..589b0facf8 100644 --- a/lib/ssl/src/ssl_cipher_format.erl +++ b/lib/ssl/src/ssl_cipher_format.erl @@ -77,13 +77,13 @@ suite_map_to_str(#{key_exchange := Kex, cipher := Cipher, mac := aead, prf := PRF}) -> - "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "TLS_" ++ kex_str(Kex) ++ "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ - "_" ++ string:to_upper(atom_to_list(PRF)); + prf_str("_", PRF); suite_map_to_str(#{key_exchange := Kex, cipher := Cipher, mac := Mac}) -> - "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "TLS_" ++ kex_str(Kex) ++ "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ "_" ++ string:to_upper(atom_to_list(Mac)). @@ -97,12 +97,6 @@ suite_str_to_map(SuiteStr)-> case string:split(Str0, "_WITH_") of [Rest] -> tls_1_3_suite_str_to_map(Rest); - [Prefix, Kex | Rest] when Prefix == "SPR"; - Prefix == "PSK"; - Prefix == "DHE"; - Prefix == "ECDHE" - -> - pre_tls_1_3_suite_str_to_map(Prefix ++ "_" ++ Kex, Rest); [Kex| Rest] -> pre_tls_1_3_suite_str_to_map(Kex, Rest) end. @@ -116,26 +110,36 @@ suite_map_to_openssl_str(#{key_exchange := null} = Suite) -> suite_map_to_str(Suite); suite_map_to_openssl_str(#{key_exchange := rsa = Kex, cipher := Cipher, - mac := Mac}) when Cipher == "des_cbc"; - Cipher == "3des_ede_cbc" -> + mac := aead, + prf := PRF}) when PRF =/= default_prf -> + openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ + "-" ++ string:to_upper(atom_to_list(PRF)); +suite_map_to_openssl_str(#{key_exchange := Kex, + cipher := Cipher, + mac := Mac}) when (Kex == rsa) orelse + (Kex == srp_anon) + andalso + (Cipher == "des_cbc") orelse + (Cipher == "3des_ede_cbc") -> openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ "-" ++ string:to_upper(atom_to_list(Mac)); suite_map_to_openssl_str(#{key_exchange := Kex, cipher := chacha20_poly1305 = Cipher, - mac := aead}) -> - openssl_suite_start(string:to_upper(atom_to_list(Kex))) - ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))); + mac := aead, + prf := sha256}) -> + openssl_suite_start(kex_str(Kex), Cipher) + ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))); suite_map_to_openssl_str(#{key_exchange := Kex, cipher := Cipher, mac := aead, prf := PRF}) -> - openssl_suite_start(string:to_upper(atom_to_list(Kex))) + openssl_suite_start(kex_str(Kex), Cipher) ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ - "-" ++ string:to_upper(atom_to_list(PRF)); + prf_str("-", PRF); suite_map_to_openssl_str(#{key_exchange := Kex, - cipher := Cipher, - mac := Mac}) -> - openssl_suite_start(string:to_upper(atom_to_list(Kex))) + cipher := Cipher, + mac := Mac}) -> + openssl_suite_start(kex_str(Kex), Cipher) ++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++ "-" ++ string:to_upper(atom_to_list(Mac)). @@ -148,14 +152,20 @@ suite_openssl_str_to_map("DES-CBC3-SHA") -> suite_str_to_map("TLS_RSA_WITH_3DES_EDE_CBC_SHA"); suite_openssl_str_to_map("SRP-DSS-DES-CBC3-SHA") -> suite_str_to_map("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA"); -suite_openssl_str_to_map("DHE-RSA-" ++ Rest) -> +suite_openssl_str_to_map("ADH" ++ Rest) -> + suite_openssl_str_to_map("DH-anon", Rest); +suite_openssl_str_to_map("AECDH" ++ Rest) -> + suite_openssl_str_to_map("ECDH-anon", Rest); +suite_openssl_str_to_map("EDH-RSA" ++ Rest) -> suite_openssl_str_to_map("DHE-RSA", Rest); -suite_openssl_str_to_map("DHE-DSS-" ++ Rest) -> +suite_openssl_str_to_map("EDH-DSS-" ++ Rest) -> suite_openssl_str_to_map("DHE-DSS", Rest); -suite_openssl_str_to_map("EDH-RSA-" ++ Rest) -> +suite_openssl_str_to_map("DHE-RSA-" ++ Rest) -> suite_openssl_str_to_map("DHE-RSA", Rest); -suite_openssl_str_to_map("EDH-DSS-" ++ Rest) -> +suite_openssl_str_to_map("DHE-DSS-" ++ Rest) -> suite_openssl_str_to_map("DHE-DSS", Rest); +suite_openssl_str_to_map("DHE-PSK-" ++ Rest) -> + suite_openssl_str_to_map("DHE-PSK", Rest); suite_openssl_str_to_map("DES" ++ _ = Rest) -> suite_openssl_str_to_map("RSA", Rest); suite_openssl_str_to_map("AES" ++ _ = Rest) -> @@ -174,8 +184,6 @@ suite_openssl_str_to_map("RSA-PSK-" ++ Rest) -> suite_openssl_str_to_map("RSA-PSK", Rest); suite_openssl_str_to_map("RSA-" ++ Rest) -> suite_openssl_str_to_map("RSA", Rest); -suite_openssl_str_to_map("DHE-PSK-" ++ Rest) -> - suite_openssl_str_to_map("DHE-PSK", Rest); suite_openssl_str_to_map("ECDHE-PSK-" ++ Rest) -> suite_openssl_str_to_map("ECDHE-PSK", Rest); suite_openssl_str_to_map("PSK-" ++ Rest) -> @@ -348,12 +356,12 @@ suite_bin_to_map(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) -> #{key_exchange => dh_anon, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) -> #{key_exchange => dh_anon, cipher => aes_256_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; %%% PSK Cipher Suites RFC 4279 suite_bin_to_map(?TLS_PSK_WITH_RC4_128_SHA) -> #{key_exchange => psk, @@ -466,7 +474,7 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CBC_SHA256) -> #{key_exchange => psk, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA384) -> #{key_exchange => psk, cipher => aes_256_cbc, @@ -476,7 +484,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) -> #{key_exchange => dhe_psk, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) -> #{key_exchange => dhe_psk, cipher => aes_256_cbc, @@ -506,7 +514,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA256) -> #{key_exchange => dhe_psk, cipher => null, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA384) -> #{key_exchange => dhe_psk, cipher => null, @@ -516,7 +524,7 @@ suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA256) -> #{key_exchange => rsa_psk, cipher => null, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA384) -> #{key_exchange => rsa_psk, cipher => null, @@ -547,7 +555,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_cbc, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) -> #{key_exchange => ecdhe_psk, cipher => aes_256_cbc, @@ -557,7 +565,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) -> #{key_exchange => ecdhe_psk, cipher => null, mac => sha256, - prf => default_prf}; + prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) -> #{key_exchange => ecdhe_psk, cipher => null, mac => sha384, @@ -566,22 +574,22 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) -> suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_gcm, - mac => null, + mac => aead, prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) -> #{key_exchange => ecdhe_psk, cipher => aes_256_gcm, - mac => null, + mac => aead, prf => sha384}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_ccm, - mac => null, + mac => aead, prf => sha256}; suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) -> #{key_exchange => ecdhe_psk, cipher => aes_128_ccm_8, - mac => null, + mac => aead, prf => sha256}; %%% SRP Cipher Suites RFC 5054 suite_bin_to_map(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) -> @@ -680,6 +688,26 @@ suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) -> cipher => aes_256_cbc, mac => sha, prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_ccm, + mac => aead, + prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_ccm, + mac => aead, + prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_128_ccm_8, + mac => aead, + prf => default_prf}; +suite_bin_to_map(?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8) -> + #{key_exchange => ecdhe_ecdsa, + cipher => aes_256_ccm_8, + mac => aead, + prf => default_prf}; suite_bin_to_map(?TLS_ECDH_RSA_WITH_NULL_SHA) -> #{key_exchange => ecdh_rsa, cipher => null, @@ -840,7 +868,7 @@ suite_bin_to_map(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) -> suite_bin_to_map(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) -> #{key_exchange => dh_dss, cipher => aes_128_gcm, - mac => null, + mac => aead, prf => sha256}; suite_bin_to_map(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) -> #{key_exchange => dh_dss, @@ -902,42 +930,42 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM) -> #{key_exchange => psk, cipher => aes_128_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM) -> #{key_exchange => psk, cipher => aes_256_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CCM) -> #{key_exchange => dhe_psk, cipher => aes_128_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CCM) -> #{key_exchange => dhe_psk, cipher => aes_256_ccm, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_WITH_AES_128_CCM_8) -> #{key_exchange => psk, cipher => aes_128_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_WITH_AES_256_CCM_8) -> #{key_exchange => psk, cipher => aes_256_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_128_CCM_8) -> #{key_exchange => dhe_psk, cipher => aes_128_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(?TLS_PSK_DHE_WITH_AES_256_CCM_8) -> #{key_exchange => dhe_psk, cipher => aes_256_ccm_8, mac => aead, - prf => sha256}; + prf => default_prf}; suite_bin_to_map(#{key_exchange := psk_dhe, cipher := aes_256_ccm_8, mac := aead, @@ -1297,22 +1325,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_psk, %%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05 suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_128_gcm, - mac := null, + mac := aead, prf := sha256}) -> ?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256; suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_256_gcm, - mac := null, + mac := aead, prf := sha384}) -> ?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384; suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_128_ccm_8, - mac := null, + mac := aead, prf := sha256}) -> ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256; suite_map_to_bin(#{key_exchange := ecdhe_psk, cipher := aes_128_ccm, - mac := null, + mac := aead, prf := sha256}) -> ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256; %%% SRP Cipher Suites RFC 5054 @@ -1393,6 +1421,22 @@ suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, cipher := aes_256_cbc, mac := sha}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_ccm, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_ccm, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_128_ccm_8, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; +suite_map_to_bin(#{key_exchange := ecdhe_ecdsa, + cipher := aes_256_ccm_8, + mac := aead}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := ecdh_rsa, cipher := null, mac := sha}) -> @@ -1616,22 +1660,22 @@ suite_map_to_bin(#{key_exchange := dhe_rsa, suite_map_to_bin(#{key_exchange := psk, cipher := aes_128_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_128_CCM; suite_map_to_bin(#{key_exchange := psk, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_128_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_PSK_WITH_AES_128_CCM; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_PSK_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := rsa, cipher := aes_128_ccm, @@ -1641,7 +1685,7 @@ suite_map_to_bin(#{key_exchange := rsa, suite_map_to_bin(#{key_exchange := rsa, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_RSA_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_128_ccm, @@ -1651,48 +1695,48 @@ suite_map_to_bin(#{key_exchange := dhe_rsa, suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_256_ccm, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_RSA_WITH_AES_256_CCM; suite_map_to_bin(#{key_exchange := psk, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := psk, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_DHE_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := dhe_psk, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_PSK_DHE_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := rsa, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_RSA_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := rsa, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_RSA_WITH_AES_256_CCM_8; suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_128_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_RSA_WITH_AES_128_CCM_8; suite_map_to_bin(#{key_exchange := dhe_rsa, cipher := aes_256_ccm_8, mac := aead, - prf := sha256}) -> + prf := default_prf}) -> ?TLS_DHE_RSA_WITH_AES_256_CCM_8; %% TLS 1.3 Cipher Suites RFC8446 @@ -1740,21 +1784,42 @@ pre_tls_1_3_suite_str_to_map(KexStr, Rest) -> cipher => Cipher, prf => Prf }. - -cipher_str_to_algs(_, CipherStr, "CCM"= End) -> %% PRE TLS 1.3 - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -cipher_str_to_algs(_, CipherStr, "8" = End) -> %% PRE TLS 1.3 - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -cipher_str_to_algs(_, CipherStr, "CHACHA20_POLY1305" = End) -> %% PRE TLS 1.3 - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -cipher_str_to_algs(_, CipherStr0, "") -> %% TLS 1.3 + +kex_str(srp_dss) -> + "SRP_SHA_DSS"; +kex_str(srp_rsa) -> + "SRP_SHA_RSA"; +kex_str(srp_anon) -> + "SRP_SHA"; +kex_str(dh_anon) -> + "DH_anon"; +kex_str(ecdh_anon) -> + "ECDH_anon"; +kex_str(Kex) -> + string:to_upper(atom_to_list(Kex)). + +prf_str(_, default_prf) -> + ""; +prf_str(Prefix, PRF) -> + Prefix ++ string:to_upper(atom_to_list(PRF)). + +cipher_str_to_algs(any, CipherStr0, "") -> %% TLS 1.3 [CipherStr, AlgStr] = string:split(CipherStr0, "_", trailing), Hash = algo_str_to_atom(AlgStr), Cipher = algo_str_to_atom(CipherStr), {Cipher, aead, Hash}; +cipher_str_to_algs(_Kex, CipherStr, "CCM"= End) -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), + {Cipher, aead, default_prf}; +cipher_str_to_algs(_Kex, CipherStr, "GCM"= End) -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), + {Cipher, aead, default_prf}; +cipher_str_to_algs(_Kex, CipherStr, "8" = End) -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), + {Cipher, aead, default_prf}; +cipher_str_to_algs(_Kex, "CHACHA20_POLY1305" = CipherStr, "") -> %% PRE TLS 1.3 + Cipher = algo_str_to_atom(CipherStr), + {Cipher, aead, sha256}; cipher_str_to_algs(Kex, CipherStr, HashStr) -> %% PRE TLS 1.3 Hash = algo_str_to_atom(HashStr), Cipher = algo_str_to_atom(CipherStr), @@ -1796,66 +1861,111 @@ openssl_is_aead_cipher("CHACHA20-POLY1305") -> openssl_is_aead_cipher(CipherStr) -> case string:split(CipherStr, "-", trailing) of [_, Rest] -> - (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "8"); + (Rest == "GCM") orelse (Rest == "CCM") orelse (Rest == "CCM8"); [_] -> false end. algo_str_to_atom("SRP_SHA_DSS") -> srp_dss; +algo_str_to_atom("SRP_SHA_RSA") -> + srp_rsa; +algo_str_to_atom("SRP_SHA") -> + srp_anon; +algo_str_to_atom("SRP") -> + srp_anon; algo_str_to_atom(AlgoStr) -> erlang:list_to_existing_atom(string:to_lower(AlgoStr)). +openssl_cipher_name(Kex, "3DES_EDE_CBC" ++ _) when Kex == ecdhe_psk; + Kex == srp_anon; + Kex == psk; + Kex == dhe_psk -> + "3DES-EDE-CBC"; openssl_cipher_name(_, "3DES_EDE_CBC" ++ _) -> "DES-CBC3"; openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == rsa; Kex == dhe_rsa; + Kex == dhe_dss; + Kex == ecdh_rsa; Kex == ecdhe_rsa; - Kex == ecdhe_ecdsa -> + Kex == ecdh_ecdsa; + Kex == ecdhe_ecdsa; + Kex == ecdh_anon; + Kex == dh_anon -> openssl_name_concat(CipherStr); openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == rsa; Kex == dhe_rsa; + Kex == dhe_dss; + Kex == ecdh_rsa; Kex == ecdhe_rsa; - Kex == ecdhe_ecdsa -> + Kex == ecdh_ecdsa; + Kex == ecdhe_ecdsa; + Kex == ecdh_anon; + Kex == dh_anon -> openssl_name_concat(CipherStr); -openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp; +openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == srp_anon; Kex == srp_rsa -> lists:append(string:replace(CipherStr, "_", "-", all)); -openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp; +openssl_cipher_name(Kex, "AES_256_CBC" ++ _ = CipherStr) when Kex == srp_anon; Kex == srp_rsa -> lists:append(string:replace(CipherStr, "_", "-", all)); openssl_cipher_name(_, "AES_128_CBC" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-CBC"; openssl_cipher_name(_, "AES_256_CBC" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-CBC"; +openssl_cipher_name(_, "AES_128_GCM_8") -> + openssl_name_concat("AES_128_GCM") ++ "-GCM8"; +openssl_cipher_name(_, "AES_256_GCM_8") -> + openssl_name_concat("AES_256_GCM") ++ "-GCM8"; +openssl_cipher_name(_, "AES_128_CCM_8") -> + openssl_name_concat("AES_128_CCM") ++ "-CCM8"; +openssl_cipher_name(_, "AES_256_CCM_8") -> + openssl_name_concat("AES_256_CCM") ++ "-CCM8"; openssl_cipher_name(_, "AES_128_GCM" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-GCM"; openssl_cipher_name(_, "AES_256_GCM" ++ _ = CipherStr) -> openssl_name_concat(CipherStr) ++ "-GCM"; +openssl_cipher_name(_, "AES_128_CCM" ++ _ = CipherStr) -> + openssl_name_concat(CipherStr) ++ "-CCM"; +openssl_cipher_name(_, "AES_256_CCM" ++ _ = CipherStr) -> + openssl_name_concat(CipherStr) ++ "-CCM"; openssl_cipher_name(_, "RC4" ++ _) -> "RC4"; openssl_cipher_name(_, CipherStr) -> lists:append(string:replace(CipherStr, "_", "-", all)). - -openssl_suite_start(Kex) -> - case openssl_kex_name(Kex) of +openssl_suite_start(Kex, Cipher) -> + case openssl_kex_name(Kex, Cipher) of "" -> ""; Name -> Name ++ "-" end. -openssl_kex_name("RSA") -> +openssl_kex_name("RSA", _) -> ""; -openssl_kex_name("DHE_RSA") -> +openssl_kex_name("DH_anon", _) -> + "ADH"; +openssl_kex_name("ECDH_anon", _) -> + "AECDH"; +openssl_kex_name("SRP_SHA", _) -> + "SRP"; +openssl_kex_name("SRP_SHA_RSA", _) -> + "SRP-RSA"; +openssl_kex_name("SRP_SHA_DSS", _) -> + "SRP-DSS"; +openssl_kex_name("DHE_RSA", Cipher) when Cipher == des_cbc; + Cipher == '3des_ede_cbc' -> "EDH-RSA"; -openssl_kex_name(Kex) -> +openssl_kex_name(Kex, _) -> lists:append(string:replace(Kex, "_", "-", all)). kex_name_from_openssl(Kex) -> case lists:append(string:replace(Kex, "-", "_", all)) of - "EDH_RSA" -> - "DHE_RSA"; + "EDH-RSA" -> + "DHE_RSA"; + "SRP" -> + "SRP_SHA"; Str -> Str end. @@ -1864,26 +1974,30 @@ cipher_name_from_openssl("AES128") -> "AES_128_CBC"; cipher_name_from_openssl("AES256") -> "AES_256_CBC"; -cipher_name_from_openssl("AES128-CBC") -> - "AES_128_CBC"; -cipher_name_from_openssl("AES256-CBC") -> - "AES_256_CBC"; -cipher_name_from_openssl("AES-128-CBC") -> - "AES_128_CBC"; -cipher_name_from_openssl("AES-256-CBC") -> - "AES_256_CBC"; -cipher_name_from_openssl("AES128-GCM") -> - "AES_128_GCM"; -cipher_name_from_openssl("AES256-GCM") -> - "AES_256_GCM"; +cipher_name_from_openssl("AES128-CCM8") -> + "AES_128_CCM_8"; +cipher_name_from_openssl("AES256-CCM8") -> + "AES_256_CCM_8"; +cipher_name_from_openssl("AES128-" ++ Suffix) -> + "AES_128_" ++ lists:append(string:replace(Suffix, "-", "_", all)); +cipher_name_from_openssl("AES256-" ++ Suffix) -> + "AES_256_" ++ lists:append(string:replace(Suffix, "-", "_", all)); +cipher_name_from_openssl("AES128_" ++ Suffix) -> + "AES_128_" ++ Suffix; +cipher_name_from_openssl("AES256_" ++ Suffix) -> + "AES_256_" ++ Suffix; cipher_name_from_openssl("DES-CBC") -> "DES_CBC"; cipher_name_from_openssl("DES-CBC3") -> "3DES_EDE_CBC"; +cipher_name_from_openssl("3DES-EDE-CBC") -> + "3DES_EDE_CBC"; cipher_name_from_openssl("RC4") -> "RC4_128"; +cipher_name_from_openssl("CHACHA20-POLY1305") -> + "CHACHA20_POLY1305"; cipher_name_from_openssl(Str) -> - Str. + lists:append(string:replace(Str, "-", "_", all)). openssl_name_concat(Str0) -> [Str, _] = string:split(Str0, "_", trailing), @@ -1893,8 +2007,8 @@ openssl_name_concat(Str0) -> suite_openssl_str_to_map(Kex0, Rest) -> Kex = algo_str_to_atom(kex_name_from_openssl(Kex0)), - [CipherStr, AlgStr] = string:split(Rest, "-", trailing), - {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, CipherStr, AlgStr), + [Part1, Part2] = string:split(Rest, "-", trailing), + {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, Part1, Part2), #{key_exchange => Kex, mac => Mac, cipher => Cipher, @@ -1902,19 +2016,25 @@ suite_openssl_str_to_map(Kex0, Rest) -> }. %% Does only need own implementation PRE TLS 1.3 -openssl_cipher_str_to_algs(_, CipherStr, "CCM"= End) -> - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -openssl_cipher_str_to_algs(_, CipherStr, "8" = End) -> - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), +openssl_cipher_str_to_algs(_, Part1, "CCM" = End) -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, Part1, "GCM" = End) -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part1 ++ "_" ++ End)), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, Part2, "CCM8") -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-CCM-8")), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, Part2, "GCM8") -> + Cipher = algo_str_to_atom(cipher_name_from_openssl(Part2 ++ "-GCM-8")), + {Cipher, aead, default_prf}; +openssl_cipher_str_to_algs(_, "CHACHA20", "POLY1305") -> + Cipher = chacha20_poly1305, {Cipher, aead, sha256}; -openssl_cipher_str_to_algs(_, CipherStr, "POLY1305" = End) -> - Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End), - {Cipher, aead, sha256}; -openssl_cipher_str_to_algs(Kex, CipherStr, HashStr) -> - Hash = algo_str_to_atom(HashStr), - Cipher = algo_str_to_atom(cipher_name_from_openssl(CipherStr)), - case openssl_is_aead_cipher(CipherStr) of +openssl_cipher_str_to_algs(Kex, Part1, Part2) -> + Hash = algo_str_to_atom(Part2), + Cipher = algo_str_to_atom(cipher_name_from_openssl(string:strip(Part1, left, $-))), + case openssl_is_aead_cipher(Part1) of true -> {Cipher, aead, Hash}; false -> diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 10f95d5b3c..6d09af9b1c 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -26,8 +26,15 @@ -include("ssl_connection.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([init/2]). +-define(DEFAULT_MAX_SESSION_CACHE, 1000). +-export([init/2, + pre_1_3_session_opts/0 + ]). + +%%==================================================================== +%% Internal application API +%%==================================================================== init(#{erl_dist := ErlDist, key := Key, keyfile := KeyFile, @@ -44,6 +51,24 @@ init(#{erl_dist := ErlDist, DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role), {ok, Config#{private_key => PrivateKey, dh_params => DHParams}}. +pre_1_3_session_opts() -> + CbOpts = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + InitArgs = session_cb_init_args(), + #{session_cb => Cb, + session_cb_init_args => InitArgs}; + _ -> + #{session_cb => ssl_server_session_cache_db, + session_cb_init_args => []} + end, + LifeTime = session_lifetime(), + Max = max_session_cache_size(), + [CbOpts#{lifetime => LifeTime, max => Max}]. + + +%%==================================================================== +%% Internal functions +%%==================================================================== init_manager_name(false) -> put(ssl_manager, ssl_manager:name(normal)), put(ssl_pem_cache, ssl_pem_cache:name(normal)); @@ -54,7 +79,7 @@ init_manager_name(true) -> init_certificates(#{cacerts := CaCerts, cacertfile := CACertFile, certfile := CertFile, - cert := Cert, + cert := OwnCerts, crl_cache := CRLCache }, Role) -> {ok, Config} = @@ -70,31 +95,31 @@ init_certificates(#{cacerts := CaCerts, _:Reason -> file_error(CACertFile, {cacertfile, Reason}) end, - init_certificates(Cert, Config, CertFile, Role). + init_certificates(OwnCerts, Config, CertFile, Role). init_certificates(undefined, Config, <<>>, _) -> - {ok, Config#{own_certificate => undefined}}; + {ok, Config#{own_certificates => undefined}}; init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) -> try - %% Ignoring potential proxy-certificates see: - %% http://dev.globus.org/wiki/Security/ProxyFileFormat - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config#{own_certificate => OwnCert}} + %% OwnCert | [OwnCert | Chain] + OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), + {ok, Config#{own_certificates => OwnCerts}} catch _Error:_Reason -> - {ok, Config#{own_certificate => undefined}} + {ok, Config#{own_certificates => undefined}} end; init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) -> try - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config#{own_certificate => OwnCert}} + %% OwnCert | [OwnCert | Chain] + OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), + {ok, Config#{own_certificates => OwnCerts}} catch _:Reason -> file_error(CertFile, {certfile, Reason}) end; -init_certificates(Cert, Config, _, _) -> - {ok, Config#{own_certificate => Cert}}. +init_certificates(OwnCerts, Config, _, _) -> + {ok, Config#{own_certificates => OwnCerts}}. init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa; Alg == rsa; Alg == dss -> @@ -176,3 +201,27 @@ init_diffie_hellman(DbHandle,_, DHParamFile, server) -> _:Reason -> file_error(DHParamFile, {dhfile, Reason}) end. + +session_cb_init_args() -> + case application:get_env(ssl, session_cb_init_args) of + {ok, Args} when is_list(Args) -> + Args; + _ -> + [] + end. + +session_lifetime() -> + case application:get_env(ssl, session_lifetime) of + {ok, Time} when is_integer(Time) -> + Time; + _ -> + ?'24H_in_sec' + end. + +max_session_cache_size() -> + case application:get_env(ssl, session_cache_server_max) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_SESSION_CACHE + end. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl deleted file mode 100644 index a854f50ee9..0000000000 --- a/lib/ssl/src/ssl_connection.erl +++ /dev/null @@ -1,3182 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2013-2020. 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: Common handling of a TLS/SSL/DTLS connection, see also -%% tls_connection.erl and dtls_connection.erl -%%---------------------------------------------------------------------- - --module(ssl_connection). - --include("ssl_api.hrl"). --include("ssl_connection.hrl"). --include("ssl_handshake.hrl"). --include("ssl_alert.hrl"). --include("ssl_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). - -%% Setup - --export([connect/8, handshake/7, handshake/2, handshake/3, handle_common_event/5, - handshake_continue/3, handshake_cancel/1, - socket_control/4, socket_control/5]). - -%% User Events --export([send/2, recv/3, close/2, shutdown/2, - new_user/2, get_opts/2, set_opts/2, - peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5, - connection_information/2 - ]). - -%% Alert and close handling --export([handle_own_alert/4, handle_alert/3, - handle_normal_shutdown/3, - handle_trusted_certs_db/1, - maybe_invalidate_session/6]). - -%% Data handling --export([read_application_data/2, internal_renegotiation/2]). - -%% Help functions for tls|dtls_connection.erl --export([handle_session/7, ssl_config/3, - prepare_connection/2, hibernate_after/3]). - -%% General gen_statem state functions with extra callback argument -%% to determine if it is an SSL/TLS or DTLS gen_statem machine --export([init/4, error/4, hello/4, user_hello/4, abbreviated/4, certify/4, wait_ocsp_stapling/4, cipher/4, - connection/4, downgrade/4]). - -%% gen_statem callbacks --export([terminate/3, format_status/2]). - -%% Erlang Distribution export --export([dist_handshake_complete/2]). - -%%==================================================================== -%% Setup -%%==================================================================== -%%-------------------------------------------------------------------- --spec connect(tls_connection | dtls_connection, - ssl:host(), inet:port_number(), - port() | {tuple(), port()}, %% TLS | DTLS - {ssl_options(), #socket_options{}, - %% Tracker only needed on server side - undefined}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Connect to an ssl server. -%%-------------------------------------------------------------------- -connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> - try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo, - Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. -%%-------------------------------------------------------------------- --spec handshake(tls_connection | dtls_connection, - inet:port_number(), port(), - {ssl_options(), #socket_options{}, list()}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- -handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> - try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, - CbInfo, Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {ok, #sslsocket{}, map()}| {error, reason()}. -%% -%% Description: Starts ssl handshake. -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> - case call(Pid, {start, Timeout}) of - connected -> - {ok, Socket}; - {ok, Ext} -> - {ok, Socket, no_records(Ext)}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) -> - {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}. -%% -%% Description: Starts ssl handshake with some new options -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> - case call(Pid, {start, SslOptions, Timeout}) of - connected -> - {ok, Socket}; - Error -> - Error - end. - -%%-------------------------------------------------------------------- --spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()], - timeout()) -> {ok, #sslsocket{}}| {error, reason()}. -%% -%% Description: Continues handshake with new options -%%-------------------------------------------------------------------- -handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> - case call(Pid, {handshake_continue, SslOptions, Timeout}) of - connected -> - {ok, Socket}; - Error -> - Error - end. -%%-------------------------------------------------------------------- --spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}. -%% -%% Description: Cancels connection -%%-------------------------------------------------------------------- -handshake_cancel(#sslsocket{pid = [Pid|_]}) -> - case call(Pid, cancel) of - closed -> - ok; - Error -> - Error - end. -%-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Set the ssl process to own the accept socket -%%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pid, Transport) -> - socket_control(Connection, Socket, Pid, Transport, undefined). - -%-------------------------------------------------------------------- --spec socket_control(tls_connection | dtls_connection, port(), [pid()], atom(), [pid()] | atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%%-------------------------------------------------------------------- -socket_control(Connection, Socket, Pids, Transport, udp_listener) -> - %% dtls listener process must have the socket control - {ok, Connection:socket(Pids, Transport, Socket, undefined)}; - -socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; - {error, Reason} -> - {error, Reason} - end; -socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, Trackers) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; - {error, Reason} -> - {error, Reason} - end. - - -%%==================================================================== -%% User events -%%==================================================================== - -%%-------------------------------------------------------------------- --spec send(pid(), iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(Pid, Data) -> - call(Pid, {application_data, - %% iolist_to_iovec should really - %% be called iodata_to_iovec() - erlang:iolist_to_iovec(Data)}). - -%%-------------------------------------------------------------------- --spec recv(pid(), integer(), timeout()) -> - {ok, binary() | list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Pid, Length, Timeout) -> - call(Pid, {recv, Length, Timeout}). - -%%-------------------------------------------------------------------- --spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Get the SNI hostname -%%-------------------------------------------------------------------- -connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) -> - call(Pid, {connection_information, IncludeSecrityInfo}). - -%%-------------------------------------------------------------------- --spec close(pid(), {close, Timeout::integer() | - {NewController::pid(), Timeout::integer()}}) -> - ok | {ok, port()} | {error, reason()}. -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -close(ConnectionPid, How) -> - case call(ConnectionPid, How) of - {error, closed} -> - ok; - Other -> - Other - end. -%%-------------------------------------------------------------------- --spec shutdown(pid(), atom()) -> ok | {error, reason()}. -%% -%% Description: Same as gen_tcp:shutdown/2 -%%-------------------------------------------------------------------- -shutdown(ConnectionPid, How) -> - call(ConnectionPid, {shutdown, How}). - -%%-------------------------------------------------------------------- --spec new_user(pid(), pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -new_user(ConnectionPid, User) -> - call(ConnectionPid, {new_user, User}). - -%%-------------------------------------------------------------------- --spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the negotiated protocol -%%-------------------------------------------------------------------- -negotiated_protocol(ConnectionPid) -> - call(ConnectionPid, negotiated_protocol). - -%%-------------------------------------------------------------------- --spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Same as inet:getopts/2 -%%-------------------------------------------------------------------- -get_opts(ConnectionPid, OptTags) -> - call(ConnectionPid, {get_opts, OptTags}). -%%-------------------------------------------------------------------- --spec set_opts(pid(), list()) -> ok | {error, reason()}. -%% -%% Description: Same as inet:setopts/2 -%%-------------------------------------------------------------------- -set_opts(ConnectionPid, Options) -> - call(ConnectionPid, {set_opts, Options}). - -%%-------------------------------------------------------------------- --spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. -%% -%% Description: Returns the peer cert -%%-------------------------------------------------------------------- -peer_certificate(ConnectionPid) -> - call(ConnectionPid, peer_certificate). - -%%-------------------------------------------------------------------- --spec renegotiation(pid()) -> ok | {error, reason()}. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -renegotiation(ConnectionPid) -> - call(ConnectionPid, renegotiate). - -%%-------------------------------------------------------------------- --spec internal_renegotiation(pid(), ssl_record:connection_states()) -> - ok. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> - gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}). - -dist_handshake_complete(ConnectionPid, DHandle) -> - gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). - -%%-------------------------------------------------------------------- --spec prf(pid(), binary() | 'master_secret', binary(), - [binary() | ssl:prf_random()], non_neg_integer()) -> - {ok, binary()} | {error, reason()} | {'EXIT', term()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> - call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). - - -%%==================================================================== -%% Alert and close handling -%%==================================================================== -handle_own_alert(Alert0, _, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - ssl_options = #{log_level := LogLevel}} = State) -> - try %% Try to tell the other side - send_alert(Alert0, StateName, State) - catch _:_ -> %% Can crash if we are in a uninitialized state - ignore - end, - try %% Try to tell the local user - Alert = Alert0#alert{role = Role}, - log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), - handle_normal_shutdown(Alert,StateName, State) - catch _:_ -> - ok - end, - {stop, {shutdown, own_alert}, State}. - -handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - trackers = Trackers}, - handshake_env = #handshake_env{renegotiation = {false, first}}, - start_or_recv_from = StartFrom} = State) -> - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection); - -handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - trackers = Trackers}, - connection_env = #connection_env{user_application = {_Mon, Pid}}, - socket_options = Opts, - start_or_recv_from = RecvFrom} = State) -> - Pids = Connection:pids(State), - alert_user(Pids, Transport, 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 = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - downgrade= StateName, State) -> - {next_state, StateName, State, [{next_event, internal, Alert}]}; -handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0, - StateName, #state{static_env = #static_env{role = Role}} = State) -> - Alert = Alert0#alert{role = opposite_role(Role)}, - handle_normal_shutdown(Alert, StateName, State), - {stop,{shutdown, peer_close}, State}; -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, internal}}, - ssl_options = #{log_level := LogLevel}} = State) -> - Alert = Alert0#alert{role = opposite_role(Role)}, - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert), - handle_normal_shutdown(Alert, StateName, State), - {stop,{shutdown, peer_close}, State}; - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, - ssl_options = #{log_level := LogLevel} - } = State0) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - gen_statem:reply(From, {error, renegotiation_rejected}), - State = Connection:reinit_handshake_data(State0), - Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}); - -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, - ssl_options = #{log_level := LogLevel} - } = State0) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - gen_statem:reply(From, {error, renegotiation_rejected}), - %% Go back to connection! - State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}), - Connection:next_event(connection, no_record, State); - -%% Gracefully log and ignore all other warning alerts -handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{static_env = #static_env{role = Role, - protocol_cb = Connection}, - ssl_options = #{log_level := LogLevel}} = State) -> - log_alert(LogLevel, Role, - Connection:protocol_name(), StateName, - Alert#alert{role = opposite_role(Role)}), - Connection:next_event(StateName, no_record, State). - -maybe_invalidate_session(undefined,_, _, _, _, _) -> - ok; -maybe_invalidate_session({3, 4},_, _, _, _, _) -> - ok; -maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 -> - maybe_invalidate_session(Type, Role, Host, Port, Session). - -%%==================================================================== -%% Data handling -%%==================================================================== -passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, - %% Assert! Erl distribution uses active sockets - connection_env = #connection_env{erl_dist_handle = undefined}} - = State0, StateName, Connection, StartTimerAction) -> - case BufferSize of - 0 -> - Connection:next_event(StateName, no_record, State0, StartTimerAction); - _ -> - case read_application_data(State0, Front, BufferSize, Rear) of - {stop, _, _} = ShutdownError -> - ShutdownError; - {Record, State} -> - case State#state.start_or_recv_from of - undefined -> - %% Cancel recv timeout as data has been delivered - Connection:next_event(StateName, Record, State, - [{{timeout, recv}, infinity, timeout}]); - _ -> - Connection:next_event(StateName, Record, State, StartTimerAction) - end - end - end. - -read_application_data( - Data, - #state{ - user_data_buffer = {Front0,BufferSize0,Rear0}, - connection_env = #connection_env{erl_dist_handle = DHandle}} = State) -> - %% - Front = Front0, - BufferSize = BufferSize0 + byte_size(Data), - Rear = [Data|Rear0], - case DHandle of - undefined -> - read_application_data(State, Front, BufferSize, Rear); - _ -> - try read_application_dist_data(DHandle, Front, BufferSize, Rear) of - Buffer -> - {no_record, State#state{user_data_buffer = Buffer}} - catch error:_ -> - {stop,disconnect, - State#state{user_data_buffer = {Front,BufferSize,Rear}}} - end - end. - - -read_application_data(#state{ - socket_options = SocketOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) -> - read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead). - -%% Pick binary from queue front, if empty wait for more data -read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> - read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin); -read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) -> - 0 = BufferSize, % Assert - {no_record, State#state{socket_options = SocketOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {Front,BufferSize,Rear}}}; -read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> - [Bin|Front] = lists:reverse(Rear), - read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin). - -read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) -> - %% Done with this binary - get next - read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead); -read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) -> - %% Decode one packet from a binary - case get_data(SocketOpts0, BytesToRead, Bin0) of - {ok, Data, Bin} -> % Send data - BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)), - read_application_data_deliver( - State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data); - {more, undefined} -> - %% We need more data, do not know how much - if - byte_size(Bin0) < BufferSize0 -> - %% We have more data in the buffer besides the first binary - concatenate all and retry - Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), - read_application_data_bin( - State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin); - true -> - %% All data is in the first binary, no use to retry - wait for more - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}} - end; - {more, Size} when Size =< BufferSize0 -> - %% We have a packet in the buffer - collect it in a binary and decode - {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]), - Bin = iolist_to_binary(Data), - read_application_data_bin( - State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin); - {more, _Size} -> - %% We do not have a packet in the buffer - wait for more - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; - passive -> - {no_record, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; - {error,_Reason} -> - %% Invalid packet in packet mode - #state{ - static_env = - #static_env{ - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}} = State, - Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), - deliver_packet_error( - Connection:pids(State), Transport, Socket, SocketOpts0, - Buffer, Pid, RecvFrom, Trackers, Connection), - {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - user_data_buffer = {[Buffer],BufferSize0,[]}}} - end. - -read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) -> - #state{ - static_env = - #static_env{ - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}} = State, - SocketOpts = - deliver_app_data( - Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection), - if - SocketOpts#socket_options.active =:= false -> - %% Passive mode, wait for active once or recv - {no_record, - State#state{ - user_data_buffer = {Front,BufferSize,Rear}, - start_or_recv_from = undefined, - bytes_to_read = undefined, - socket_options = SocketOpts - }}; - true -> %% Try to deliver more data - read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined) - end. - - -read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) -> - read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin); -read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) -> - BufferSize = 0, - {Front,BufferSize,Rear}; -read_application_dist_data(DHandle, [], BufferSize, Rear) -> - [Bin|Front] = lists:reverse(Rear), - read_application_dist_data(DHandle, Front, BufferSize, [], Bin). -%% -read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> - case Bin0 of - %% - %% START Optimization - %% It is cheaper to match out several packets in one match operation than to loop for each - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, - SizeD:32, DataD:SizeD/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD -> - %% We have 4 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - erlang:dist_ctrl_put_data(DHandle, DataC), - erlang:dist_ctrl_put_data(DHandle, DataD), - read_application_dist_data( - DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest); - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB, 0 < SizeC -> - %% We have 3 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - erlang:dist_ctrl_put_data(DHandle, DataC), - read_application_dist_data( - DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest); - <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, Rest/binary>> - when 0 < SizeA, 0 < SizeB -> - %% We have 2 complete packets in the first binary - erlang:dist_ctrl_put_data(DHandle, DataA), - erlang:dist_ctrl_put_data(DHandle, DataB), - read_application_dist_data( - DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest); - %% END Optimization - %% - %% Basic one packet code path - <<Size:32, Data:Size/binary, Rest/binary>> -> - %% We have a complete packet in the first binary - 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), - read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest); - <<Size:32, FirstData/binary>> when 4+Size =< BufferSize -> - %% We have a complete packet in the buffer - %% - fetch the missing content from the buffer front - {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]), - 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), - read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear); - <<Bin/binary>> -> - %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we - %% match out the whole binary which will trick the optimization into keeping the match context - %% for the first binary contains complete packet code above - case Bin of - <<_Size:32, _InsufficientData/binary>> -> - %% We have a length field in the first binary but there is not enough data - %% in the buffer to form a complete packet - await more data - {[Bin|Front0],BufferSize,Rear0}; - <<IncompleteLengthField/binary>> when 4 < BufferSize -> - %% We do not have a length field in the first binary but the buffer - %% contains enough data to maybe form a packet - %% - fetch a tiny binary from the buffer front to complete the length field - {LengthField,Front,Rear} = - case IncompleteLengthField of - <<>> -> - iovec_from_front(4, Front0, Rear0, []); - _ -> - iovec_from_front( - 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]) - end, - LengthBin = iolist_to_binary(LengthField), - read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin); - <<IncompleteLengthField/binary>> -> - %% We do not have enough data in the buffer to even form a length field - await more data - case IncompleteLengthField of - <<>> -> - {Front0,BufferSize,Rear0}; - _ -> - {[IncompleteLengthField|Front0],BufferSize,Rear0} - end - end - end. - -iovec_from_front(0, Front, Rear, Acc) -> - {lists:reverse(Acc),Front,Rear}; -iovec_from_front(Size, [], Rear, Acc) -> - case Rear of - %% Avoid lists:reverse/1 for simple cases. - %% Case clause for [] to avoid infinite loop. - [_] -> - iovec_from_front(Size, Rear, [], Acc); - [Bin2,Bin1] -> - iovec_from_front(Size, [Bin1,Bin2], [], Acc); - [Bin3,Bin2,Bin1] -> - iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc); - [_,_,_|_] = Rear -> - iovec_from_front(Size, lists:reverse(Rear), [], Acc) - end; -iovec_from_front(Size, [Bin|Front], Rear, []) -> - case Bin of - <<Last:Size/binary>> -> % Just enough - {[Last],Front,Rear}; - <<Last:Size/binary, Rest/binary>> -> % More than enough, split here - {[Last],[Rest|Front],Rear}; - <<>> -> % Not enough, skip empty binaries - iovec_from_front(Size, Front, Rear, []); - <<_/binary>> -> % Not enough - BinSize = byte_size(Bin), - iovec_from_front(Size - BinSize, Front, Rear, [Bin]) - end; -iovec_from_front(Size, [Bin|Front], Rear, Acc) -> - case Bin of - <<Last:Size/binary>> -> % Just enough - {lists:reverse(Acc, [Last]),Front,Rear}; - <<Last:Size/binary, Rest/binary>> -> % More than enough, split here - {lists:reverse(Acc, [Last]),[Rest|Front],Rear}; - <<>> -> % Not enough, skip empty binaries - iovec_from_front(Size, Front, Rear, Acc); - <<_/binary>> -> % Not enough - BinSize = byte_size(Bin), - iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) - end. - - -%%==================================================================== -%% Help functions for tls|dtls_connection.erl -%%==================================================================== -%%-------------------------------------------------------------------- --spec handle_session(#server_hello{}, ssl_record:ssl_version(), - binary(), ssl_record:connection_states(), _,_, #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -handle_session(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression}, - Version, NewId, ConnectionStates, ProtoExt, Protocol0, - #state{session = #session{session_id = OldId}, - handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> - #{key_exchange := KeyAlgorithm} = - ssl_cipher_format:suite_bin_to_map(CipherSuite), - - PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - - {ExpectNPN, Protocol} = case Protocol0 of - undefined -> - - {false, CurrentProtocol}; - _ -> - {ProtoExt =:= npn, Protocol0} - end, - - State = State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm, - premaster_secret = PremasterSecret, - expecting_next_protocol_negotiation = ExpectNPN, - negotiated_protocol = Protocol}, - connection_env = CEnv#connection_env{negotiated_version = Version}}, - - case ssl_session:is_new(OldId, NewId) of - true -> - handle_new_session(NewId, CipherSuite, Compression, - State#state{connection_states = ConnectionStates}); - false -> - handle_resumed_session(NewId, - State#state{connection_states = ConnectionStates}) - end. - -%%-------------------------------------------------------------------- --spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}. -%%-------------------------------------------------------------------- -ssl_config(Opts, Role, #state{static_env = InitStatEnv0, - handshake_env = HsEnv, - connection_env = CEnv} = State0) -> - {ok, #{cert_db_ref := Ref, - cert_db_handle := CertDbHandle, - fileref_db_handle := FileRefHandle, - session_cache := CacheHandle, - crl_db_info := CRLDbHandle, - private_key := Key, - dh_params := DHParams, - own_certificate := OwnCert}} = - ssl_config:init(Opts, Role), - TimeStamp = erlang:monotonic_time(), - Session = State0#state.session, - - State0#state{session = Session#session{own_certificate = OwnCert, - time_stamp = TimeStamp}, - static_env = InitStatEnv0#static_env{ - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle - }, - handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, - connection_env = CEnv#connection_env{private_key = Key}, - ssl_options = Opts}. - -%%==================================================================== -%% gen_statem general state functions with connection cb argument -%%==================================================================== -%%-------------------------------------------------------------------- --spec init(gen_statem:event_type(), - {start, timeout()} | {start, {list(), list()}, timeout()}| term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- - -init({call, From}, {start, Timeout}, State0, Connection) -> - Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From}, - [{{timeout, handshake}, Timeout, close}]); -init({call, From}, {start, {Opts, EmOpts}, Timeout}, - #state{static_env = #static_env{role = Role}, - ssl_options = OrigSSLOptions, - socket_options = SockOpts} = State0, Connection) -> - try - SslOpts = ssl:handle_options(Opts, Role, OrigSSLOptions), - State = ssl_config(SslOpts, Role, State0), - init({call, From}, {start, Timeout}, - State#state{ssl_options = SslOpts, - socket_options = new_emulated(EmOpts, SockOpts)}, Connection) - catch throw:Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} - end; -init({call, From}, {new_user, _} = Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -init({call, From}, _Msg, _State, _Connection) -> - {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]}; -init(_Type, _Event, _State, _Connection) -> - {keep_state_and_data, [postpone]}. - -%%-------------------------------------------------------------------- --spec error(gen_statem:event_type(), - {start, timeout()} | term(), #state{}, - tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -error({call, From}, {close, _}, State, _Connection) -> - {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State}; -error({call, From}, _Msg, State, _Connection) -> - {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}. - -%%-------------------------------------------------------------------- --spec hello(gen_statem:event_type(), - #hello_request{} | #server_hello{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -hello({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> - do_server_hello(Type, ServerHelloExt, State, Connection); -hello(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -hello(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) -> - gen_statem:reply(From, ok), - handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), - Version, ?FUNCTION_NAME, State); -user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, - #state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{hello = Hello}, - ssl_options = Options0} = State0, _Connection) -> - Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), - State = ssl_config(Options, Role, State0), - {next_state, hello, State#state{start_or_recv_from = From}, - [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; -user_hello(_, _, _, _) -> - {keep_state_and_data, [postpone]}. - -%%-------------------------------------------------------------------- --spec abbreviated(gen_statem:event_type(), - #hello_request{} | #finished{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -abbreviated({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - expecting_finished = true} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = - State0, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, - get_current_prf(ConnectionStates0, write), - MasterSecret, Hist) of - verified -> - ConnectionStates = - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), - {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist0}, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, - get_pending_prf(ConnectionStates0, write), - MasterSecret, Hist0) of - verified -> - ConnectionStates1 = - ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - {#state{handshake_env = HsEnv} = State1, Actions} = - finalize_handshake(State0#state{connection_states = ConnectionStates1}, - ?FUNCTION_NAME, Connection), - {Record, State} = prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State, - Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}}); -abbreviated(internal, - #change_cipher_spec{type = <<1>>}, - #state{connection_states = ConnectionStates0, - handshake_env = HsEnv} = State, Connection) -> - 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}}); -abbreviated(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -abbreviated(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec wait_ocsp_stapling(gen_statem:event_type(), - #certificate{} | #certificate_status{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_ocsp_stapling(internal, #certificate{}, State, Connection) -> - %% Postpone message, should be handled in certify after receiving staple message - Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]); -%% Receive OCSP staple message -wait_ocsp_stapling(internal, #certificate_status{} = CertStatus, - #state{handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState} = HsEnv} = State, - Connection) -> - Connection:next_event(certify, no_record, 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{handshake_env = #handshake_env{ - ocsp_stapling_state = OcspState} = HsEnv} = State, Connection) - 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) -> - Connection:next_event(certify, no_record, State#state{handshake_env = - HsEnv#handshake_env{ocsp_stapling_state = OcspState#{ocsp_expect => undetermined}}}, - [{postpone, true}]); -wait_ocsp_stapling(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec certify(gen_statem:event_type(), - #hello_request{} | #certificate{} | #server_key_exchange{} | - #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -certify({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -certify(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -certify(internal, #certificate{asn1_certificates = []}, - #state{static_env = #static_env{role = server}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer, - fail_if_no_peer_cert := true}} = - State, _) -> - Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided), - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); -certify(internal, #certificate{asn1_certificates = []}, - #state{static_env = #static_env{role = server}, - ssl_options = #{verify := verify_peer, - fail_if_no_peer_cert := false}} = - State0, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); -certify(internal, #certificate{}, - #state{static_env = #static_env{role = server}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_none}} = - State, _) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); -certify(internal, #certificate{}, - #state{handshake_env = #handshake_env{ - ocsp_stapling_state = #{ocsp_expect := staple}}} = State, Connection) -> - Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]); -certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, #state{static_env = - #static_env{ - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbInfo}, - handshake_env = #handshake_env{ - ocsp_stapling_state = #{ocsp_expect := Status} = OcspState}, - connection_env = #connection_env{ - negotiated_version = Version}, - ssl_options = Opts} = State, Connection) when Status =/= staple -> - OcspInfo = ocsp_info(OcspState, Opts, Peer), - case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, - Opts, CRLDbInfo, Role, Host, - ensure_tls(Version), OcspInfo) of - {PeerCert, PublicKeyInfo} -> - handle_peer_cert(Role, PeerCert, PublicKeyInfo, - State#state{client_certificate_requested = false}, Connection, []); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -certify(internal, #server_key_exchange{exchange_keys = Keys}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates} = State, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdhe_ecdsa; - KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - - Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)), - - %% Use negotiated value if TLS-1.2 otherwhise return default - HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)), - - case is_anonymous(KexAlg) of - true -> - calculate_secret(Params#server_key_params.params, - State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection); - false -> - case ssl_handshake:verify_server_key(Params, HashSign, - ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of - true -> - calculate_secret(Params#server_key_params.params, - State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}, - session = session_handle_params(Params#server_key_params.params, Session)}, - Connection); - false -> - handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), - Version, ?FUNCTION_NAME, State) - end - end; -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg}, - connection_env = #connection_env{negotiated_version = Version}} = State, _) - when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), - Version, ?FUNCTION_NAME, State); -certify(internal, #certificate_request{}, - #state{static_env = #static_env{role = client}, - session = #session{own_certificate = undefined}} = State, Connection) -> - %% The client does not have a certificate and will send an empty reply, the server may fail - %% or accept the connection by its own preference. No signature algorihms needed as there is - %% no certificate to verify. - Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); -certify(internal, #certificate_request{} = CertRequest, - #state{static_env = #static_env{role = client}, - handshake_env = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{own_certificate = Cert}, - ssl_options = #{signature_algs := SupportedHashSigns}} = State, Connection) -> - case ssl_handshake:select_hashsign(CertRequest, Cert, - SupportedHashSigns, ssl:tls_version(Version)) of - #alert {} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); - NegotiatedHashSign -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{client_certificate_requested = true, - handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}}) - end; -%% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - session = #session{master_secret = undefined}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) - when KexAlg == psk -> - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - PremasterSecret -> - State = master_secret(PremasterSecret, - State0#state{handshake_env = - HsEnv#handshake_env{premaster_secret = PremasterSecret}}), - client_certify_and_key_exchange(State, Connection) - end; -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - premaster_secret = undefined, - server_psk_identity = PSKIdentity} = HsEnv, - session = #session{master_secret = undefined}, - ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) - when KexAlg == rsa_psk -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, - case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, - RSAPremasterSecret) of - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - PremasterSecret -> - State = master_secret(PremasterSecret, - State0#state{handshake_env = - HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}), - client_certify_and_key_exchange(State, Connection) - end; -%% Master secret was determined with help of server-key exchange msg -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{premaster_secret = undefined}, - session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - State = State0#state{connection_states = ConnectionStates}, - client_certify_and_key_exchange(State, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -%% Master secret is calculated from premaster_secret -certify(internal, #server_hello_done{}, - #state{static_env = #static_env{role = client}, - connection_env = #connection_env{negotiated_version = Version}, - handshake_env = #handshake_env{premaster_secret = PremasterSecret}, - session = Session0, - connection_states = ConnectionStates0} = State0, Connection) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - client_certify_and_key_exchange(State, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) - end; -certify(internal = Type, #client_key_exchange{} = Msg, - #state{static_env = #static_env{role = server}, - client_certificate_requested = true, - ssl_options = #{fail_if_no_peer_cert := true}} = State, - Connection) -> - %% We expect a certificate here - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection); -certify(internal, #client_key_exchange{exchange_keys = Keys}, - State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, - connection_env = #connection_env{negotiated_version = Version}}, Connection) -> - try - certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)), - State, Connection) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -certify(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec cipher(gen_statem:event_type(), - #hello_request{} | #certificate_verify{} | #finished{} | term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -cipher({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -cipher(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -cipher(internal, #certificate_verify{signature = Signature, - hashsign_algorithm = CertHashSign}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} - } = State, Connection) -> - - TLSVersion = ssl:tls_version(Version), - %% Use negotiated value if TLS-1.2 otherwhise return default - HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion), - case ssl_handshake:certificate_verify(Signature, PubKeyInfo, - TLSVersion, HashSign, MasterSecret, Hist) of - valid -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -%% client must send a next protocol message if we are expecting it -cipher(internal, #finished{}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_next_protocol_negotiation = true, - negotiated_protocol = undefined}, - connection_env = #connection_env{negotiated_version = Version}} = State0, - _Connection) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); -cipher(internal, #finished{verify_data = Data} = Finished, - #state{static_env = #static_env{role = Role, - host = Host, - port = Port, - trackers = Trackers}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - expecting_finished = true} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} - = Session0, - ssl_options = SslOpts, - connection_states = ConnectionStates0} = State, Connection) -> - case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, - opposite_role(Role), - get_current_prf(ConnectionStates0, read), - MasterSecret, Hist) of - verified -> - Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0), - cipher_role(Role, Data, Session, - State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection); - #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) - end; -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{expecting_finished = true, - expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}}); -cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} = - State, Connection) -> - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true}, - connection_states = ConnectionStates}); -cipher(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec connection(gen_statem:event_type(), term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connection({call, RecvFrom}, {recv, N, Timeout}, - #state{static_env = #static_env{protocol_cb = Connection}, - socket_options = - #socket_options{active = false}} = State0, Connection) -> - passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, - [{{timeout, recv}, Timeout, timeout}]); - -connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = HsEnv} = State, - Connection) -> - Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); -connection({call, From}, peer_certificate, - #state{session = #session{peer_certificate = Cert}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); -connection({call, From}, {connection_information, true}, State, _) -> - Info = connection_info(State) ++ security_info(State), - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); -connection({call, From}, {connection_information, false}, State, _) -> - Info = connection_info(State), - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = undefined, - negotiated_protocol = undefined}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = undefined, - negotiated_protocol = SelectedProtocol}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, - [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{alpn = SelectedProtocol, - negotiated_protocol = undefined}} = State, _) -> - hibernate_after(?FUNCTION_NAME, State, - [{reply, From, {ok, SelectedProtocol}}]); -connection({call, From}, Msg, State, Connection) -> - handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = HsEnv, - connection_states = ConnectionStates} - = State, Connection) -> - Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, - connection_states = ConnectionStates#{current_write => WriteState}}, []); -connection(cast, {dist_handshake_complete, DHandle}, - #state{ssl_options = #{erl_dist := true}, - connection_env = CEnv, - socket_options = SockOpts} = State0, Connection) -> - process_flag(priority, normal), - State1 = - State0#state{ - socket_options = SockOpts#socket_options{active = true}, - connection_env = CEnv#connection_env{erl_dist_handle = DHandle}, - bytes_to_read = undefined}, - {Record, State} = read_application_data(<<>>, State1), - Connection:next_event(connection, Record, State); -connection(info, Msg, State, _) -> - handle_info(Msg, ?FUNCTION_NAME, State); -connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom} = State, Connection) -> - passive_receive(State, ?FUNCTION_NAME, Connection, []); -connection(Type, Msg, State, Connection) -> - handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- --spec downgrade(gen_statem:event_type(), term(), - #state{}, tls_connection | dtls_connection) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -downgrade(Type, Event, State, Connection) -> - handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection). - -%%-------------------------------------------------------------------- -%% Event handling functions called by state functions to handle -%% common or unexpected events for the state. -%%-------------------------------------------------------------------- -handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, - #state{static_env = #static_env{role = client}, - handshake_env = HsEnv} = State, _) -> - %% Should not be included in handshake history - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}}, - [{next_event, internal, Handshake}]}; -handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, - #state{static_env = #static_env{role = client}}, _) - when StateName =/= connection -> - keep_state_and_data; -handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, - #state{handshake_env = #handshake_env{tls_handshake_history = Hist0}, - connection_env = #connection_env{negotiated_version = Version}} = State0, - Connection) -> - - PossibleSNI = Connection:select_sni_extension(Handshake), - %% This function handles client SNI hello extension when Handshake is - %% a client_hello, which needs to be determined by the connection callback. - %% In other cases this is a noop - case handle_sni_extension(PossibleSNI, State0) of - #state{handshake_env = HsEnv} = State -> - Hist = ssl_handshake:update_handshake_history(Hist0, Raw), - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}, - [{next_event, internal, Handshake}]}; - #alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0) - end; -handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) -> - Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); -handle_common_event(timeout, hibernate, _, _, _) -> - {keep_state_and_data, [hibernate]}; -handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, - #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) -> - handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, - StateName, State); -handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State, _) -> - {stop_and_reply, - {shutdown, user_timeout}, - {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}}; -handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State, _) -> - {next_state, StateName, State#state{start_or_recv_from = undefined, - bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]}; -handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}, _) when - StateName =/= connection -> - {keep_state_and_data, [postpone]}; -handle_common_event(Type, Msg, StateName, #state{connection_env = - #connection_env{negotiated_version = Version}} = State, - _) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type,Msg}}), - handle_own_alert(Alert, Version, StateName, State). - -handle_call({application_data, _Data}, _, _, _, _) -> - %% In renegotiation priorities handshake, send data when handshake is finished - {keep_state_and_data, [postpone]}; -handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State, _Connection) -> - %% Run terminate before returning so that the reuseaddr - %% inet-option works properly - Result = terminate(Close, StateName, State), - {stop_and_reply, - {shutdown, normal}, - {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}}; -handle_call({shutdown, read_write = How}, From, StateName, - #state{static_env = #static_env{transport_cb = Transport, - socket = Socket}, - connection_env = CEnv} = State, _) -> - try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - StateName, State) of - _ -> - case Transport:shutdown(Socket, How) of - ok -> - {next_state, StateName, State#state{connection_env = - CEnv#connection_env{terminated = true}}, - [{reply, From, ok}]}; - Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, Error}, - State#state{connection_env = CEnv#connection_env{terminated = true}}} - end - catch - throw:Return -> - Return - end; -handle_call({shutdown, How0}, From, StateName, - #state{static_env = #static_env{transport_cb = Transport, - socket = Socket}} = State, _) -> - case Transport:shutdown(Socket, How0) of - ok -> - {next_state, StateName, State, [{reply, From, ok}]}; - Error -> - {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State} - end; -handle_call({recv, _N, _Timeout}, From, _, - #state{socket_options = - #socket_options{active = Active}}, _) when Active =/= false -> - {keep_state_and_data, [{reply, From, {error, einval}}]}; -handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) -> - %% Doing renegotiate wait with handling request until renegotiate is - %% finished. - {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, - [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]}; -handle_call({new_user, User}, From, StateName, - State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) -> - NewMon = erlang:monitor(process, User), - erlang:demonitor(OldMon, [flush]), - {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}}, - [{reply, From, ok}]}; -handle_call({get_opts, OptTags}, From, _, - #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - socket_options = SockOpts}, Connection) -> - OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), - {keep_state_and_data, [{reply, From, OptsReply}]}; -handle_call({set_opts, Opts0}, From, StateName, - #state{static_env = #static_env{socket = Socket, - transport_cb = Transport, - trackers = Trackers}, - connection_env = - #connection_env{user_application = {_Mon, Pid}}, - socket_options = Opts1 - } = State0, Connection) -> - {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), - case {proplists:lookup(active, Opts0), Opts} of - {{_, N}, #socket_options{active=false}} when is_integer(N) -> - send_user( - Pid, - format_passive( - Connection:pids(State0), Transport, Socket, Trackers, Connection)); - _ -> - ok - end, - State = State0#state{socket_options = Opts}, - handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); - -handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> - {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; - -handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, - #state{connection_states = ConnectionStates, - connection_env = #connection_env{negotiated_version = Version}}, _) -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - #security_parameters{master_secret = MasterSecret, - client_random = ClientRandom, - server_random = ServerRandom, - prf_algorithm = PRFAlgorithm} = SecParams, - Reply = try - SecretToUse = case Secret of - _ when is_binary(Secret) -> Secret; - master_secret -> MasterSecret - end, - SeedToUse = lists:reverse( - lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; - (client_random, Acc) -> [ClientRandom|Acc]; - (server_random, Acc) -> [ServerRandom|Acc] - end, [], Seed)), - ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) - catch - exit:_ -> {error, badarg}; - error:Reason -> {error, Reason} - end, - {keep_state_and_data, [{reply, From, Reply}]}; -handle_call(_,_,_,_,_) -> - {keep_state_and_data, [postpone]}. - -handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{static_env = #static_env{role = Role, - host = Host, - port = Port, - socket = Socket, - transport_cb = Transport, - error_tag = ErrorTag, - trackers = Trackers, - protocol_cb = Connection}, - handshake_env = #handshake_env{renegotiation = Type}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - start_or_recv_from = StartFrom - } = State) when StateName =/= connection -> - - maybe_invalidate_session(Version, Type, Role, Host, Port, Session), - Pids = Connection:pids(State), - alert_user(Pids, Transport, Trackers,Socket, - StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), - {stop, {shutdown, normal}, State}; - -handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{ - role = Role, - socket = Socket, - error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(info, Level, #{description => "Socket error", - reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION), - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), - handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown,normal}, State}; - -handle_info({'DOWN', MonitorRef, _, _, Reason}, _, - #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, - ssl_options = #{erl_dist := true}}) -> - {stop, {shutdown, Reason}}; -handle_info({'DOWN', MonitorRef, _, _, _}, _, - #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) -> - {stop, {shutdown, normal}}; -handle_info({'EXIT', Pid, _Reason}, StateName, - #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) -> - %% It seems the user application has linked to us - %% - ignore that and let the monitor handle this - {next_state, StateName, State}; -%%% So that terminate will be run when supervisor issues shutdown -handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> - {stop, shutdown, State}; -handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> - %% Handle as transport close" - {stop,{shutdown, transport_closed}, State}; -handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> - {stop,{shutdown, Reason}, State}; - -handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> - {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; - -handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}, - ssl_options = #{log_level := Level}} = State) -> - ssl_logger:log(notice, Level, #{description => "Unexpected INFO message", - reason => [{message, Msg}, {socket, Socket}, - {error_tag, ErrorTag}]}, ?LOCATION), - {next_state, StateName, State}. - -%%==================================================================== -%% general gen_statem callbacks -%%==================================================================== -terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) -> - %% Happens when user closes the connection using ssl:close/1 - %% we want to guarantee that Transport:close has been called - %% when ssl:close/1 returns unless it is a downgrade where - %% we want to guarantee that close alert is received before - %% returning. In both cases terminate has been run manually - %% before run by gen_statem which will end up here - ok; -terminate({shutdown, transport_closed} = Reason, - _StateName, #state{static_env = #static_env{protocol_cb = Connection, - socket = Socket, - transport_cb = Transport}} = State) -> - handle_trusted_certs_db(State), - Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate({shutdown, own_alert}, _StateName, #state{ - static_env = #static_env{protocol_cb = Connection, - socket = Socket, - transport_cb = Transport}} = State) -> - handle_trusted_certs_db(State), - case application:get_env(ssl, alert_timeout) of - {ok, Timeout} when is_integer(Timeout) -> - Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); - _ -> - Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) - end; -terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection, - transport_cb = Transport, - socket = Socket} - } = State) -> - handle_trusted_certs_db(State), - Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate(Reason, connection, #state{static_env = #static_env{ - protocol_cb = Connection, - transport_cb = Transport, - socket = Socket}, - connection_states = ConnectionStates, - ssl_options = #{padding_check := Check} - } = State) -> - handle_trusted_certs_db(State), - Alert = terminate_alert(Reason), - %% Send the termination ALERT if possible - catch (ok = Connection:send_alert_in_connection(Alert, State)), - Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); -terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, - protocol_cb = Connection, - socket = Socket} - } = State) -> - handle_trusted_certs_db(State), - Connection:close(Reason, Socket, Transport, undefined, undefined). - -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, - cacerts => ?SECRET_PRINTOUT, - key => ?SECRET_PRINTOUT, - dh => ?SECRET_PRINTOUT, - psk_identity => ?SECRET_PRINTOUT, - srp_identity => ?SECRET_PRINTOUT}, - [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, - protocol_buffers = ?SECRET_PRINTOUT, - user_data_buffer = ?SECRET_PRINTOUT, - handshake_env = ?SECRET_PRINTOUT, - connection_env = ?SECRET_PRINTOUT, - session = ?SECRET_PRINTOUT, - ssl_options = NewOptions, - flight_buffer = ?SECRET_PRINTOUT} - }}]}]. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> - Connection:send_alert_in_connection(Alert, State); -send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> - Connection:send_alert(Alert, State). - -connection_info(#state{static_env = #static_env{protocol_cb = Connection}, - handshake_env = #handshake_env{sni_hostname = SNIHostname, - resumption = Resumption}, - session = #session{session_id = SessionId, - cipher_suite = CipherSuite, - srp_username = SrpUsername, - ecc = ECCCurve}, - connection_states = #{current_write := CurrentWrite}, - connection_env = #connection_env{negotiated_version = {_,_} = Version}, - ssl_options = Opts}) -> - RecordCB = record_cb(Connection), - CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - IsNamedCurveSuite = lists:member(KexAlg, - [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]), - CurveInfo = case ECCCurve of - {namedCurve, Curve} when IsNamedCurveSuite -> - [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; - _ -> - [] - end, - - MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of - MaxFragmentLength when is_integer(MaxFragmentLength) -> - [{max_fragment_length, MaxFragmentLength}]; - _ -> - [] - end, - [{protocol, RecordCB:protocol_version(Version)}, - {session_id, SessionId}, - {session_resumption, Resumption}, - {selected_cipher_suite, CipherSuiteDef}, - {sni_hostname, SNIHostname}, - {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts). - -security_info(#state{connection_states = ConnectionStates}) -> - #{security_parameters := - #security_parameters{client_random = ClientRand, - server_random = ServerRand, - master_secret = MasterSecret}} = - ssl_record:current_connection_state(ConnectionStates, read), - [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]. - -do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} = - ServerHelloExt, - #state{connection_env = #connection_env{negotiated_version = Version}, - handshake_env = HsEnv, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - ssl_options = #{versions := [HighestVersion|_]}} - = State0, Connection) when is_atom(Type) -> - %% TLS 1.3 - Section 4.1.3 - %% Override server random values for TLS 1.3 downgrade protection mechanism. - ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion), - State1 = State0#state{connection_states = ConnectionStates1}, - ServerHello = - ssl_handshake:server_hello(SessId, ssl:tls_version(Version), - ConnectionStates1, ServerHelloExt), - State = server_hello(ServerHello, - State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation = - NextProtocols =/= undefined}}, Connection), - case Type of - new -> - new_server_hello(ServerHello, State, Connection); - resumed -> - resumed_server_hello(State, Connection) - end. - -update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} = - ReadState0, - pending_write := #{security_parameters := WriteSecParams0} = - WriteState0} = ConnectionStates, - Version, HighestVersion) -> - ReadRandom = override_server_random( - ReadSecParams0#security_parameters.server_random, - Version, - HighestVersion), - WriteRandom = override_server_random( - WriteSecParams0#security_parameters.server_random, - Version, - HighestVersion), - ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom}, - WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom}, - ReadState = ReadState0#{security_parameters => ReadSecParams}, - WriteState = WriteState0#{security_parameters => WriteSecParams}, - - ConnectionStates#{pending_read => ReadState, pending_write => WriteState}. - -%% TLS 1.3 - Section 4.1.3 -%% -%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes -%% of their Random value to the bytes: -%% -%% 44 4F 57 4E 47 52 44 01 -%% -%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 -%% servers SHOULD set the last eight bytes of their Random value to the -%% bytes: -%% -%% 44 4F 57 4E 47 52 44 00 -override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) - when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above - if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2 - Down = ?RANDOM_OVERRIDE_TLS12, - <<Random0/binary,Down/binary>>; - M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <<Random0/binary,Down/binary>>; - true -> - Random - end; -override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) - when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 - if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior - Down = ?RANDOM_OVERRIDE_TLS11, - <<Random0/binary,Down/binary>>; - true -> - Random - end; -override_server_random(Random, _, _) -> - Random. - -new_server_hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression, - session_id = SessionId}, - #state{session = Session0, - connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> - try server_certify_and_key_exchange(State0, Connection) of - #state{} = State1 -> - {State, Actions} = server_hello_done(State1, Connection), - Session = - Session0#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}, - Connection:next_event(certify, no_record, State#state{session = Session}, Actions) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -resumed_server_hello(#state{session = Session, - connection_states = ConnectionStates0, - connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> - - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, server) of - {_, ConnectionStates1} -> - State1 = State0#state{connection_states = ConnectionStates1, - session = Session}, - {State, Actions} = - finalize_handshake(State1, abbreviated, Connection), - Connection:next_event(abbreviated, no_record, State, Actions); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -server_hello(ServerHello, State0, Connection) -> - CipherSuite = ServerHello#server_hello.cipher_suite, - #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}. - -server_hello_done(State, Connection) -> - HelloDone = ssl_handshake:server_hello_done(), - Connection:send_handshake(HelloDone, State). - -handle_peer_cert(Role, PeerCert, PublicKeyInfo, - #state{handshake_env = HsEnv, - session = #session{cipher_suite = CipherSuite} = Session} = State0, - Connection, Actions) -> - State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}, - session = - Session#session{peer_certificate = PeerCert}}, - #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), - Connection:next_event(certify, no_record, State, Actions). - -handle_peer_cert_key(client, _, - {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, - PublicKeyParams}, - KeyAlg, #state{handshake_env = HsEnv, - session = Session} = State) when KeyAlg == ecdh_rsa; - KeyAlg == ecdh_ecdsa -> - ECDHKey = public_key:generate_key(PublicKeyParams), - PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), - master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey}, - session = Session#session{ecc = PublicKeyParams}}); -handle_peer_cert_key(_, _, _, _, State) -> - State. - -certify_client(#state{static_env = #static_env{role = client, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - client_certificate_requested = true, - session = #session{own_certificate = OwnCert}} - = State, Connection) -> - Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), - Connection:queue_handshake(Certificate, State); -certify_client(#state{client_certificate_requested = false} = State, _) -> - State. - -verify_client_cert(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{tls_handshake_history = Hist, - cert_hashsign_algorithm = HashSign}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - client_certificate_requested = true, - session = #session{master_secret = MasterSecret, - own_certificate = OwnCert}} = State, Connection) -> - - case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - ssl:tls_version(Version), HashSign, PrivateKey, Hist) of - #certificate_verify{} = Verified -> - Connection:queue_handshake(Verified, State); - ignore -> - State; - #alert{} = Alert -> - throw(Alert) - end; -verify_client_cert(#state{client_certificate_requested = false} = State, _) -> - State. - -client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} = - State0, Connection) -> - try do_client_certify_and_key_exchange(State0, Connection) of - State1 = #state{} -> - {State2, Actions} = finalize_handshake(State1, certify, Connection), - State = State2#state{ - %% Reinitialize - client_certificate_requested = false}, - Connection:next_event(cipher, no_record, State, Actions) - catch - throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. - -do_client_certify_and_key_exchange(State0, Connection) -> - State1 = certify_client(State0, Connection), - State2 = key_exchange(State1, Connection), - verify_client_cert(State2, Connection). - -server_certify_and_key_exchange(State0, Connection) -> - State1 = certify_server(State0, Connection), - State2 = key_exchange(State1, Connection), - request_client_cert(State2, Connection). - -certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{connection_env = #connection_env{private_key = Key}, - handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} - = State, Connection) -> - FakeSecret = make_premaster_secret(Version, rsa), - %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret - %% and fail handshake later.RFC 5246 section 7.4.7.1. - PremasterSecret = - try ssl_handshake:premaster_secret(EncPMS, Key) of - Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> - case Secret of - <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct - <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>; - <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch - <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> - end; - _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES - FakeSecret - catch - #alert{description = ?DECRYPT_ERROR} -> - FakeSecret - end, - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); -certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}} - } = State, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); -certify_client_key_exchange(#client_psk_identity{} = ClientKey, - #state{ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); -certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, - #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, - kex_keys = {_, ServerDhPrivateKey}}, - ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, - Connection) -> - PremasterSecret = - ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); -certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, - #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, - ssl_options = - #{user_lookup_fun := PSKLookup}} = State, - Connection) -> - PremasterSecret = - ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), - calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); -certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, - #state{connection_env = #connection_env{private_key = Key}, - ssl_options = - #{user_lookup_fun := PSKLookup}} = State0, - Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); -certify_client_key_exchange(#client_srp_public{} = ClientKey, - #state{handshake_env = #handshake_env{srp_params = Params, - kex_keys = Key} - } = State0, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), - calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher). - -certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} = - State, _) when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == srp_anon -> - State; -certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - session = #session{own_certificate = OwnCert}} = State, Connection) -> - case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of - Cert = #certificate{} -> - Connection:queue_handshake(Cert, State); - Alert = #alert{} -> - throw(Alert) - end. - -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - diffie_hellman_params = #'DHParameter'{} = Params, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == dh_anon -> - DHKeys = public_key:generate_key(Params), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, - connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, - session = Session} = State, _) - when KexAlg == ecdh_ecdsa; - KexAlg == ecdh_rsa -> - State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key}, - session = Session#session{ecc = ECCurve}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, - connection_states = ConnectionStates0} = State0, Connection) - when KexAlg == ecdhe_ecdsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdh_anon -> - - ECDHKeys = public_key:generate_key(ECCCurve), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = psk}, - ssl_options = #{psk_identity := undefined}} = State, _) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) -> - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = dhe_psk, - diffie_hellman_params = #'DHParameter'{} = Params, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - DHKeys = public_key:generate_key(Params), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {dhe_psk, - PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - ECDHKeys = public_key:generate_key(ECCCurve), - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {ecdhe_psk, - PskIdentityHint, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; -key_exchange(#state{static_env = #static_env{role = server}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk}, - ssl_options = #{psk_identity := undefined}} = State, _) -> - State; -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{psk_identity := PskIdentityHint}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0 - } = State0, Connection) -> - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, - ssl_options = #{user_lookup_fun := LookupFun}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{srp_username = Username}, - connection_states = ConnectionStates0 - } = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = case generate_srp_server_keys(SrpParams, 0) of - Alert = #alert{} -> - throw(Alert); - Keys0 = {_,_} -> - Keys0 - end, - #{security_parameters := SecParams} = - ssl_record:pending_connection_state(ConnectionStates0, read), - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), - {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), - State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams, - kex_keys = Keys}}; -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = rsa, - public_key_info = PublicKeyInfo, - premaster_secret = PremasterSecret}, - connection_env = #connection_env{negotiated_version = Version} - } = State0, Connection) -> - Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = {DhPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version} - } = State0, Connection) - when KexAlg == dhe_dss; - KexAlg == dhe_rsa; - KexAlg == dh_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session - } = State0, Connection) - when KexAlg == ecdhe_ecdsa; - KexAlg == ecdhe_rsa; - KexAlg == ecdh_ecdsa; - KexAlg == ecdh_rsa; - KexAlg == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), - Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = psk}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {psk, PSKIdentity}), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = dhe_psk, - kex_keys = {DhPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {dhe_psk, - PSKIdentity, DhPubKey}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, - kex_keys = ECDHKeys}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {ecdhe_psk, - PSKIdentity, ECDHKeys}), - Connection:queue_handshake(Msg, State0); - -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = rsa_psk, - public_key_info = PublicKeyInfo, - premaster_secret = PremasterSecret}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{psk_identity := PSKIdentity}} - = State0, Connection) -> - Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity, - PremasterSecret, PublicKeyInfo), - Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = client}, - handshake_env = #handshake_env{kex_algorithm = KexAlg, - kex_keys = {ClientPubKey, _}}, - connection_env = #connection_env{negotiated_version = Version}} - = State0, Connection) - when KexAlg == srp_dss; - KexAlg == srp_rsa; - KexAlg == srp_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}), - Connection:queue_handshake(Msg, State0). - -rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {premaster_secret, PremasterSecret, - PublicKeyInfo}); -rsa_key_exchange(_, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). - -rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, - PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - ssl_handshake:key_exchange(client, ssl:tls_version(Version), - {psk_premaster_secret, PskIdentity, PremasterSecret, - PublicKeyInfo}); -rsa_psk_key_exchange(_, _, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). - -request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _) - when Alg == dh_anon; - Alg == ecdh_anon; - Alg == psk; - Alg == dhe_psk; - Alg == ecdhe_psk; - Alg == rsa_psk; - Alg == srp_dss; - Alg == srp_rsa; - Alg == srp_anon -> - State; - -request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, - cert_db_ref = CertDbRef}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{verify := verify_peer, - signature_algs := SupportedHashSigns}, - connection_states = ConnectionStates0} = State0, Connection) -> - #{security_parameters := - #security_parameters{cipher_suite = CipherSuite}} = - ssl_record:pending_connection_state(ConnectionStates0, read), - TLSVersion = ssl:tls_version(Version), - HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, - TLSVersion), - Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, - HashSigns, TLSVersion), - State = Connection:queue_handshake(Msg, State0), - State#state{client_certificate_requested = true}; - -request_client_cert(#state{ssl_options = #{verify := verify_none}} = - State, _) -> - State. - -calculate_master_secret(PremasterSecret, - #state{connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0, - session = Session0} = State0, Connection, - _Current, Next) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, server) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - Connection:next_event(Next, no_record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. - -finalize_handshake(State0, StateName, Connection) -> - #state{connection_states = ConnectionStates0} = - State1 = cipher_protocol(State0, Connection), - - ConnectionStates = - ssl_record:activate_pending_connection_state(ConnectionStates0, - write, Connection), - - State2 = State1#state{connection_states = ConnectionStates}, - State = next_protocol(State2, Connection), - finished(State, StateName, Connection). - -next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) -> - State; -next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) -> - NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), - Connection:queue_handshake(NextProtocolMessage, State0). - -cipher_protocol(State, Connection) -> - Connection:queue_change_cipher(#change_cipher_spec{}, State). - -finished(#state{static_env = #static_env{role = Role}, - handshake_env = #handshake_env{tls_handshake_history = Hist}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates0} = State0, - StateName, Connection) -> - MasterSecret = Session#session.master_secret, - Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, - get_current_prf(ConnectionStates0, write), - MasterSecret, Hist), - ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), - Connection:send_handshake(Finished, State0#state{connection_states = - ConnectionStates}). - -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> - ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> - ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). - -calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, - dh_y = ServerPublicDhKey} = Params, - #state{handshake_env = HsEnv} = State, Connection) -> - Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), - PremasterSecret = - ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, - Connection, certify, certify); - -calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - #state{handshake_env = HsEnv, - session = Session} = State, Connection) -> - ECDHKeys = public_key:generate_key(ECCurve), - PremasterSecret = - ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, - session = Session#session{ecc = ECCurve}}, - Connection, certify, certify); - -calculate_secret(#server_psk_params{ - hint = IdentityHint}, - #state{handshake_env = HsEnv} = State, Connection) -> - %% store for later use - Connection:next_event(certify, no_record, - State#state{handshake_env = - HsEnv#handshake_env{server_psk_identity = IdentityHint}}); - -calculate_secret(#server_dhe_psk_params{ - dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, - #state{handshake_env = HsEnv, - ssl_options = #{user_lookup_fun := PSKLookup}} = - State, Connection) -> - Keys = {_, PrivateDhKey} = - crypto:generate_key(dh, [Prime, Base]), - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup), - calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, - Connection, certify, certify); - -calculate_secret(#server_ecdhe_psk_params{ - dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey, - #state{ssl_options = #{user_lookup_fun := PSKLookup}} = - #state{handshake_env = HsEnv, - session = Session} = State, Connection) -> - ECDHKeys = public_key:generate_key(ECCurve), - - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup), - calculate_master_secret(PremasterSecret, - State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, - session = Session#session{ecc = ECCurve}}, - Connection, certify, certify); - -calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, - #state{handshake_env = HsEnv, - ssl_options = #{srp_identity := SRPId}} = State, - Connection) -> - Keys = generate_srp_client_keys(Generator, Prime, 0), - PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId), - calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection, - certify, certify). - -master_secret(#alert{} = Alert, _) -> - Alert; -master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session, - connection_states = ConnectionStates0} = State) -> - case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, - ConnectionStates0, Role) of - {MasterSecret, ConnectionStates} -> - State#state{ - session = - Session#session{master_secret = MasterSecret}, - connection_states = ConnectionStates}; - #alert{} = Alert -> - Alert - end. - -generate_srp_server_keys(_SrpParams, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_server_keys(SrpParams = - #srp_user{generator = Generator, prime = Prime, - verifier = Verifier}, N) -> - try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of - Keys -> - Keys - catch - error:_ -> - generate_srp_server_keys(SrpParams, N+1) - end. - -generate_srp_client_keys(_Generator, _Prime, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_client_keys(Generator, Prime, N) -> - - try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of - Keys -> - Keys - catch - error:_ -> - generate_srp_client_keys(Generator, Prime, N+1) - end. - -handle_srp_identity(Username, {Fun, UserState}) -> - case Fun(srp, Username, UserState) of - {ok, {SRPParams, Salt, DerivedKey}} - when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> - {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), - Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), - #srp_user{generator = Generator, prime = Prime, - salt = Salt, verifier = Verifier}; - #alert{} = Alert -> - throw(Alert); - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end. - - -cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0, - Connection) -> - ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, - ConnectionStates0), - {Record, State} = prepare_connection(State0#state{session = Session, - connection_states = ConnectionStates}, - Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); -cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0, - Connection) -> - ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, - ConnectionStates0), - {State1, Actions} = - finalize_handshake(State0#state{connection_states = ConnectionStates1, - session = Session}, cipher, Connection), - {Record, State} = prepare_connection(State1, Connection), - Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]). - -is_anonymous(KexAlg) when KexAlg == dh_anon; - KexAlg == ecdh_anon; - KexAlg == psk; - KexAlg == dhe_psk; - KexAlg == ecdhe_psk; - KexAlg == rsa_psk; - KexAlg == srp_anon -> - true; -is_anonymous(_) -> - false. - -get_current_prf(CStates, Direction) -> - #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction), - SecParams#security_parameters.prf_algorithm. -get_pending_prf(CStates, Direction) -> - #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), - SecParams#security_parameters.prf_algorithm. - -opposite_role(client) -> - server; -opposite_role(server) -> - client. - -record_cb(tls_connection) -> - tls_record; -record_cb(dtls_connection) -> - dtls_record. - -call(FsmPid, Event) -> - try gen_statem:call(FsmPid, Event) - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{{shutdown, _},_} -> - {error, closed} - end. - -get_socket_opts(_, _,_,[], _, Acc) -> - {ok, Acc}; -get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{mode, SockOpts#socket_options.mode} | Acc]); -get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> - case SockOpts#socket_options.packet of - {Type, headers} -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); - Type -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) - end; -get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{header, SockOpts#socket_options.header} | Acc]); -get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, - [{active, SockOpts#socket_options.active} | Acc]); -get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) -> - case Connection:getopts(Transport, Socket, [Tag]) of - {ok, [Opt]} -> - get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]); - {error, Reason} -> - {error, {options, {socket_options, Tag, Reason}}} - end; -get_socket_opts(_,_, _,Opts, _,_) -> - {error, {options, {socket_options, Opts, function_clause}}}. - -set_socket_opts(_,_,_, [], SockOpts, []) -> - {ok, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> - %% Set non emulated options - try ConnectionCb:setopts(Transport, Socket, Other) of - ok -> - {ok, SockOpts}; - {error, InetError} -> - {{error, {options, {socket_options, Other, InetError}}}, SockOpts} - catch - _:Error -> - %% So that inet behavior does not crash our process - {{error, {options, {socket_options, Other, Error}}}, SockOpts} - end; - -set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) - when Mode == list; Mode == binary -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) - when Packet == raw; - Packet == 0; - Packet == 1; - Packet == 2; - Packet == 4; - Packet == asn1; - Packet == cdr; - Packet == sunrm; - Packet == fcgi; - Packet == tpkt; - Packet == line; - Packet == http; - Packet == httph; - Packet == http_bin; - Packet == httph_bin -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other) - when is_integer(Header) -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{header = Header}, Other); -set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) -> - {{error,{options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other) - when Active == once; - Active == true; - Active == false -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other); -set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts], - SockOpts=#socket_options{active = Active0}, Other) - when Active1 >= -32768, Active1 =< 32767 -> - Active = if - is_integer(Active0), Active0 + Active1 < -32768 -> - error; - is_integer(Active0), Active0 + Active1 =< 0 -> - false; - is_integer(Active0), Active0 + Active1 > 32767 -> - error; - Active1 =< 0 -> - false; - is_integer(Active0) -> - Active0 + Active1; - true -> - Active1 - end, - case Active of - error -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; - _ -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other) - end; -set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; -set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> - set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]). - - - -hibernate_after(connection = StateName, - #state{ssl_options= #{hibernate_after := HibernateAfter}} = State, - Actions) -> - {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; -hibernate_after(StateName, State, Actions) -> - {next_state, StateName, State, Actions}. - - -terminate_alert(normal) -> - ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); -terminate_alert({Reason, _}) when Reason == close; - Reason == shutdown -> - ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); -terminate_alert(_) -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR). - -handle_trusted_certs_db(#state{ssl_options = - #{cacertfile := <<>>, cacerts := []}}) -> - %% 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 -> - %% 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); -handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> - %% Something went wrong early (typically cacertfile does not - %% exist) so there is nothing to handle - ok; -handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, - file_ref_db = RefDb}, - ssl_options = #{cacertfile := File}}) -> - case ssl_pkix_db:ref_count(Ref, RefDb, -1) of - 0 -> - ssl_manager:clean_cert_db(Ref, File); - _ -> - ok - end. - -prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate}, - start_or_recv_from = RecvFrom} = State0, Connection) - when Renegotiate =/= {false, first}, - RecvFrom =/= undefined -> - State = Connection:reinit(State0), - {no_record, ack_connection(State)}; -prepare_connection(State0, Connection) -> - State = Connection:reinit(State0), - {no_record, ack_connection(State)}. - -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; - Initiater == internal -> - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) -> - gen_statem:reply(From, ok), - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; -ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv, - start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined -> - gen_statem:reply(StartFrom, connected), - State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, - start_or_recv_from = undefined}; -ack_connection(State) -> - State. - -session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> - Session#session{ecc = ECCurve}; -session_handle_params(_, Session) -> - Session. - -handle_session(server, #{reuse_sessions := true}, - _Host, _Port, Trackers, #session{is_resumable = false} = Session) -> - Tracker = proplists:get_value(session_id_tracker, Trackers), - server_register_session(Tracker, Session#session{is_resumable = true}); -handle_session(Role = client, #{verify := verify_peer, - reuse_sessions := Reuse} = SslOpts, - Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false -> - client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true}, - reg_type(Reuse)); -handle_session(_,_,_,_,_, Session) -> - Session. - -reg_type(save) -> - true; -reg_type(true) -> - unique. - -client_register_session(Host, Port, Session, Save) -> - ssl_manager:register_session(Host, Port, Session, Save), - Session. -server_register_session(Tracker, Session) -> - ssl_server_session_cache:register_session(Tracker, Session), - Session. - -host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) -> - Hostname; -host_id(_, Host, _) -> - Host. - -handle_new_session(NewId, CipherSuite, Compression, - #state{static_env = #static_env{protocol_cb = Connection}, - session = Session0 - } = State0) -> - Session = Session0#session{session_id = NewId, - cipher_suite = CipherSuite, - compression_method = Compression}, - Connection:next_event(certify, no_record, State0#state{session = Session}). - -handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, - port = Port, - protocol_cb = Connection, - session_cache = Cache, - session_cache_cb = CacheCb}, - connection_env = #connection_env{negotiated_version = Version}, - connection_states = ConnectionStates0} = State) -> - Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case ssl_handshake:master_secret(ssl:tls_version(Version), Session, - ConnectionStates0, client) of - {_, ConnectionStates} -> - Connection:next_event(abbreviated, no_record, State#state{ - connection_states = ConnectionStates, - session = Session}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State) - end. - -make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; -make_premaster_secret(_, _) -> - undefined. - -negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> - %% Not negotiated choose default - case is_anonymous(KexAlg) of - true -> - {null, anon}; - false -> - {PubAlg, _, _} = PubKeyInfo, - ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) - end; -negotiated_hashsign(HashSign = {_, _}, _, _, _) -> - HashSign. - -ssl_options_list(SslOptions) -> - L = maps:to_list(SslOptions), - ssl_options_list(L, []). - -ssl_options_list([], Acc) -> - lists:reverse(Acc); -%% Skip internal options, only return user options -ssl_options_list([{protocol, _}| T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{erl_dist, _}|T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{renegotiate_at, _}|T], Acc) -> - ssl_options_list(T, Acc); -ssl_options_list([{max_fragment_length, _}|T], Acc) -> - %% skip max_fragment_length from options since it is taken above from connection_states - ssl_options_list(T, Acc); -ssl_options_list([{ciphers = Key, Value}|T], Acc) -> - ssl_options_list(T, - [{Key, lists:map( - fun(Suite) -> - ssl_cipher_format:suite_bin_to_map(Suite) - end, Value)} - | Acc]); -ssl_options_list([{Key, Value}|T], Acc) -> - ssl_options_list(T, [{Key, Value} | Acc]). - -handle_active_option(false, connection = StateName, To, Reply, State) -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - -handle_active_option(_, connection = StateName, To, _Reply, #state{static_env = #static_env{role = Role}, - connection_env = #connection_env{terminated = true}, - user_data_buffer = {_,0,_}} = State) -> - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd), - handle_normal_shutdown(Alert#alert{role = Role}, StateName, - State#state{start_or_recv_from = To}), - {stop,{shutdown, peer_close}, State}; -handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, - user_data_buffer = {_,0,_}} = State0) -> - case Connection:next_event(StateName0, no_record, State0) of - {next_state, StateName, State} -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - {next_state, StateName, State, Actions} -> - hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); - {stop, _, _} = Stop -> - Stop - end; -handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) -> - %% Active once already set - {next_state, StateName, State, [{reply, To, Reply}]}; - -%% user_data_buffer nonempty -handle_active_option(_, StateName0, To, Reply, - #state{static_env = #static_env{protocol_cb = Connection}} = State0) -> - case read_application_data(<<>>, State0) of - {stop, _, _} = Stop -> - Stop; - {Record, State1} -> - %% Note: Renogotiation may cause StateName0 =/= StateName - case Connection:next_event(StateName0, Record, State1) of - {next_state, StateName, State} -> - hibernate_after(StateName, State, [{reply, To, Reply}]); - {next_state, StateName, State, Actions} -> - hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); - {stop, _, _} = Stop -> - Stop - end - end. - - -%% Picks ClientData -get_data(#socket_options{active=false}, undefined, _Bin) -> - %% Recv timed out save buffer data until next recv - passive; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin) - when Raw =:= raw; Raw =:= 0 -> %% Raw Mode - case Bin of - <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 -> - %% Active true or once, or passive mode recv(0) - {ok, Bin, <<>>}; - <<Data:BytesToRead/binary, Rest/binary>> -> - %% Passive Mode, recv(Bytes) - {ok, Data, Rest}; - <<_/binary>> -> - %% Passive Mode not enough data - {more, BytesToRead} - end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) -> - PacketOpts = [{packet_size, Size}], - decode_packet(Type, Bin, PacketOpts). - -decode_packet({http, headers}, Buffer, PacketOpts) -> - decode_packet(httph, Buffer, PacketOpts); -decode_packet({http_bin, headers}, Buffer, PacketOpts) -> - decode_packet(httph_bin, Buffer, PacketOpts); -decode_packet(Type, Buffer, PacketOpts) -> - erlang:decode_packet(Type, Buffer, PacketOpts). - -%% Just like with gen_tcp sockets, an ssl socket that has been configured with -%% {packet, http} (or {packet, http_bin}) will automatically switch to expect -%% HTTP headers after it sees a HTTP Request or HTTP Response line. We -%% represent the current state as follows: -%% #socket_options.packet =:= http: Expect a HTTP Request/Response line -%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers -%% Note that if the user has explicitly configured the socket to expect -%% HTTP headers using the {packet, httph} option, we don't do any automatic -%% switching of states. -deliver_app_data( - CPids, Transport, Socket, - #socket_options{active=Active, packet=Type} = SOpts, - Data, Pid, From, Trackers, Connection) -> - %% - send_or_reply( - Active, Pid, From, - format_reply( - CPids, Transport, Socket, SOpts, Data, Trackers, Connection)), - SO = - case Data of - {P, _, _, _} - when ((P =:= http_request) or (P =:= http_response)), - ((Type =:= http) or (Type =:= http_bin)) -> - SOpts#socket_options{packet={Type, headers}}; - http_eoh when tuple_size(Type) =:= 2 -> - %% End of headers - expect another Request/Response line - {Type1, headers} = Type, - SOpts#socket_options{packet=Type1}; - _ -> - SOpts - end, - case Active of - once -> - SO#socket_options{active=false}; - 1 -> - send_user( - Pid, - format_passive( - CPids, Transport, Socket, Trackers, Connection)), - SO#socket_options{active=false}; - N when is_integer(N) -> - SO#socket_options{active=N - 1}; - _ -> - SO - end. - -format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet, - header = Header}, Data, _, _) -> - {ok, do_format_reply(Mode, Packet, Header, Data)}; -format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, - header = Header}, Data, Trackers, Connection) -> - {ssl, Connection:socket(CPids, Transport, Socket, Trackers), - do_format_reply(Mode, Packet, Header, Data)}. - -deliver_packet_error(CPids, Transport, Socket, - SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) -> - send_or_reply(Active, Pid, From, format_packet_error(CPids, - Transport, Socket, SO, Data, Trackers, Connection)). - -format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> - {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; -format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, - Data, Trackers, Connection) -> - {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers), - {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. - -do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode - header(N, Data); -do_format_reply(binary, _, _, Data) -> - Data; -do_format_reply(list, Packet, _, Data) - when Packet == http; Packet == {http, headers}; - Packet == http_bin; Packet == {http_bin, headers}; - Packet == httph; Packet == httph_bin -> - Data; -do_format_reply(list, _,_, Data) -> - binary_to_list(Data). - -format_passive(CPids, Transport, Socket, Trackers, Connection) -> - {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}. - -header(0, <<>>) -> - <<>>; -header(_, <<>>) -> - []; -header(0, Binary) -> - Binary; -header(N, Binary) -> - <<?BYTE(ByteN), NewBinary/binary>> = Binary, - [ByteN | header(N-1, NewBinary)]. - -send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_statem:reply(From, Data); -send_or_reply(false, Pid, undefined, _) when is_pid(Pid) -> - ok; -send_or_reply(_, no_pid, _, _) -> - ok; -send_or_reply(_, Pid, _, Data) -> - send_user(Pid, Data). - -send_user(Pid, Msg) -> - Pid ! Msg, - ok. - -alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection); -alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection). - -alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) -> - alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection). - -alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined -> - %% If there is an outstanding ssl_accept | recv - %% From will be defined and send_or_reply will - %% send the appropriate error message. - ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName), - send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) -> - case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of - closed -> - send_or_reply(Active, Pid, From, - {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)}); - ReasonCode -> - send_or_reply(Active, Pid, From, - {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode}) - end. - -log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> - ssl_logger:log(notice, Level, #{protocol => ProtocolName, - role => Role, - statename => StateName, - alert => Alert, - alerter => own}, Alert#alert.where); -log_alert(Level, Role, ProtocolName, StateName, Alert) -> - ssl_logger:log(notice, Level, #{protocol => ProtocolName, - role => Role, - statename => StateName, - alert => Alert, - alerter => peer}, Alert#alert.where). - -maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) -> - invalidate_session(Role, Host, Port, Session); -maybe_invalidate_session(_, _, _, _, _) -> - ok. - -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, _, _) -> - ok. - -handle_sni_extension(undefined, State) -> - State; -handle_sni_extension(#sni{hostname = Hostname}, State) -> - case is_sni_value(Hostname) of - true -> - handle_sni_extension(Hostname, State); - false -> - ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, {sni_included_trailing_dot, Hostname}) - end; -handle_sni_extension(Hostname, #state{static_env = #static_env{role = Role} = InitStatEnv0, - handshake_env = HsEnv, - connection_env = CEnv} = State0) -> - NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), - case NewOptions of - undefined -> - State0; - _ -> - {ok, #{cert_db_ref := Ref, - cert_db_handle := CertDbHandle, - fileref_db_handle := FileRefHandle, - session_cache := CacheHandle, - crl_db_info := CRLDbHandle, - private_key := Key, - dh_params := DHParams, - own_certificate := OwnCert}} = - ssl_config:init(NewOptions, Role), - State0#state{ - session = State0#state.session#session{own_certificate = OwnCert}, - static_env = InitStatEnv0#static_env{ - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle - }, - connection_env = CEnv#connection_env{private_key = Key}, - ssl_options = NewOptions, - handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, - diffie_hellman_params = DHParams} - } - end. - -update_ssl_options_from_sni(#{sni_fun := SNIFun, - sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) -> - SSLOption = - case SNIFun of - undefined -> - proplists:get_value(SNIHostname, - SNIHosts); - SNIFun -> - SNIFun(SNIHostname) - end, - case SSLOption of - undefined -> - undefined; - _ -> - ssl:handle_options(SSLOption, server, OrigSSLOptions) - end. - -new_emulated([], EmOpts) -> - EmOpts; -new_emulated(NewEmOpts, _) -> - NewEmOpts. - -no_records(Extensions) -> - maps:map(fun(_, Value) -> - ssl_handshake:extension_value(Value) - end, Extensions). - -is_sni_value(Hostname) -> - case hd(lists:reverse(Hostname)) of - $. -> - false; - _ -> - true - end. - -ensure_tls({254, _} = 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) -> - #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]}, - ocsp_responder_certs => OcspResponderCerts, - 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 - }. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index b8a17f0409..371599bbe8 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -36,7 +36,7 @@ -record(static_env, { role :: client | server, transport_cb :: atom(), % callback module - protocol_cb :: tls_connection | dtls_connection, + protocol_cb :: tls_gen_connection | dtls_gen_connection, data_tag :: atom(), % ex tcp. close_tag :: atom(), % ex tcp_closed error_tag :: atom(), % ex tcp_error @@ -63,6 +63,7 @@ renegotiation :: undefined | {boolean(), From::term() | internal | peer}, resumption = false :: boolean(), %% TLS 1.3 change_cipher_spec_sent = false :: boolean(), %% TLS 1.3 + sni_guided_cert_selection = false :: boolean(), %% TLS 1.3 allow_renegotiate = true ::boolean(), %% Ext handling hello, %%:: #client_hello{} | #server_hello{} diff --git a/lib/ssl/src/ssl_dist_connection_sup.erl b/lib/ssl/src/ssl_dist_connection_sup.erl index 28c8692ca5..441a7577be 100644 --- a/lib/ssl/src/ssl_dist_connection_sup.erl +++ b/lib/ssl/src/ssl_dist_connection_sup.erl @@ -42,48 +42,20 @@ start_link() -> %%%========================================================================= %%% Supervisor callback %%%========================================================================= - init([]) -> - - TLSConnetionManager = tls_connection_manager_child_spec(), - %% Handles emulated options so that they inherited by the accept - %% socket, even when setopts is performed on the listen socket - ListenOptionsTracker = listen_options_tracker_child_spec(), - Pre_1_3SessionTracker = ssl_server_session_child_spec(), - - {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager, - ListenOptionsTracker, - Pre_1_3SessionTracker - ]}}. + TLSSup = tls_sup_child_spec(), + {ok, {{one_for_one, 10, 3600}, [TLSSup]}}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -tls_connection_manager_child_spec() -> - Name = dist_tls_connection, - StartFunc = {tls_connection_sup, start_link_dist, []}, +tls_sup_child_spec() -> + Name = dist_tls_sup, + StartFunc = {tls_dist_sup, start_link, []}, Restart = permanent, Shutdown = 4000, - Modules = [tls_connection_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -listen_options_tracker_child_spec() -> - Name = dist_tls_socket, - StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [tls_socket], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - -ssl_server_session_child_spec() -> - Name = dist_ssl_server_session_cache_sup, - StartFunc = {ssl_server_session_cache_sup, start_link_dist, []}, - Restart = permanent, - Shutdown = 4000, - Modules = [ssl_server_session_cache_sup], + Modules = [tls_dist_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl index bea67935d8..ae0887c3d9 100644 --- a/lib/ssl/src/ssl_dist_sup.erl +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -70,16 +70,16 @@ ssl_admin_child_spec() -> StartFunc = {ssl_dist_admin_sup, start_link , []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_admin_sup], + Modules = [ssl_dist_admin_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. ssl_connection_sup() -> - Name = ssl_dist_connection_sup, - StartFunc = {ssl_dist_connection_sup, start_link, []}, + Name = tls_dist_sup, + StartFunc = {tls_dist_sup, start_link, []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_connection_sup], + Modules = [tls_dist_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl new file mode 100644 index 0000000000..aa2a5541a1 --- /dev/null +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -0,0 +1,2011 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2020. 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: Provid help function to handle generic parts of TLS +%% connection fsms +%%---------------------------------------------------------------------- + +-module(ssl_gen_statem). + +-include_lib("kernel/include/logger.hrl"). + +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_alert.hrl"). +-include("tls_handshake.hrl"). + +%% Initial Erlang process setup +-export([start_link/7, + start_link/8, + init/1]). + +%% TLS connection setup +-export([ssl_config/3, + connect/8, + handshake/7, + handshake/2, + handshake/3, + handshake_continue/3, + handshake_cancel/1, + handle_sni_extension/2, + socket_control/4, + socket_control/5, + prepare_connection/2]). + +%% User Events +-export([send/2, + recv/3, + close/2, + shutdown/2, + new_user/2, + get_opts/2, + set_opts/2, + peer_certificate/1, + negotiated_protocol/1, + connection_information/2 + ]). + +%% Erlang Distribution export +-export([dist_handshake_complete/2]). + +%% Generic fsm states +-export([initial_hello/3, + config_error/3, + connection/3]). + +-export([call/2, + handle_common_event/4, + handle_call/4, + handle_info/3 + ]). + +-export([hibernate_after/3]). + +%% Data handling +-export([read_application_data/2]). + +%% Alert and close handling +-export([send_alert/3, + handle_own_alert/4, + handle_alert/3, + handle_normal_shutdown/3, + handle_trusted_certs_db/1, + maybe_invalidate_session/6, + maybe_invalidate_session/5, + terminate/3]). + +%% Log handling +-export([format_status/2]). + +%%-------------------------------------------------------------------- +%%% Initial Erlang process setup +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +-spec start_link(client| server, pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a process which calls Module:init/1 to +%% choose appropriat gen_statem and initialize. +%%-------------------------------------------------------------------- +start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. + +%%-------------------------------------------------------------------- +-spec start_link(atom(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a gen_statem process which calls Module:init/1 to +%% initialize. +%%-------------------------------------------------------------------- +start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. + + +%%-------------------------------------------------------------------- +-spec init(list()) -> no_return(). +%% Description: Initialization +%%-------------------------------------------------------------------- +init([_Role, Sender, _Host, _Port, _Socket, {#{erl_dist := ErlDist} = TLSOpts, _, _}, _User, _CbInfo] = InitArgs) -> + process_flag(trap_exit, true), + link(Sender), + case ErlDist 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) -> + process_flag(trap_exit, true), + ConnectionFsm = dtls_connection_fsm(TLSOpts), + ConnectionFsm:init(InitArgs). + +%%==================================================================== +%% TLS connection setup +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec ssl_config(ssl_options(), client | server, #state{}) -> #state{}. +%%-------------------------------------------------------------------- +ssl_config(Opts, Role, #state{static_env = InitStatEnv0, + handshake_env = HsEnv, + connection_env = CEnv} = State0) -> + {ok, #{cert_db_ref := Ref, + cert_db_handle := CertDbHandle, + fileref_db_handle := FileRefHandle, + session_cache := CacheHandle, + crl_db_info := CRLDbHandle, + private_key := Key, + dh_params := DHParams, + own_certificates := OwnCerts}} = + ssl_config:init(Opts, Role), + TimeStamp = erlang:monotonic_time(), + Session = State0#state.session, + + State0#state{session = Session#session{own_certificates = OwnCerts, + time_stamp = TimeStamp}, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, + handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, + connection_env = CEnv#connection_env{private_key = Key}, + ssl_options = Opts}. + +%%-------------------------------------------------------------------- +-spec connect(tls_gen_connection | dtls_gen_connection, + ssl:host(), inet:port_number(), + port() | {tuple(), port()}, %% TLS | DTLS + {ssl_options(), #socket_options{}, + %% Tracker only needed on server side + undefined}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Connect to an ssl server. +%%-------------------------------------------------------------------- +connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> + try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo, + Timeout) + catch + exit:{noproc, _} -> + {error, ssl_not_started} + end. +%%-------------------------------------------------------------------- +-spec handshake(tls_gen_connection | dtls_gen_connection, + inet:port_number(), port(), + {ssl_options(), #socket_options{}, list()}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Performs accept on an ssl listen socket. e.i. performs +%% ssl handshake. +%%-------------------------------------------------------------------- +handshake(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> + try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, + CbInfo, Timeout) + catch + exit:{noproc, _} -> + {error, ssl_not_started} + end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {ok, #sslsocket{}, map()}| {error, reason()}. +%% +%% Description: Starts ssl handshake. +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> + case call(Pid, {start, Timeout}) of + connected -> + {ok, Socket}; + {ok, Ext} -> + {ok, Socket, no_records(Ext)}; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, {ssl_options(),#socket_options{}}, timeout()) -> + {ok, #sslsocket{}} | {ok, #sslsocket{}, map()} | {error, reason()}. +%% +%% Description: Starts ssl handshake with some new options +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> + case call(Pid, {start, SslOptions, Timeout}) of + connected -> + {ok, Socket}; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()], + timeout()) -> {ok, #sslsocket{}}| {error, reason()}. +%% +%% Description: Continues handshake with new options +%%-------------------------------------------------------------------- +handshake_continue(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> + case call(Pid, {handshake_continue, SslOptions, Timeout}) of + connected -> + {ok, Socket}; + Error -> + Error + end. +%%-------------------------------------------------------------------- +-spec handshake_cancel(#sslsocket{}) -> ok | {error, reason()}. +%% +%% Description: Cancels connection +%%-------------------------------------------------------------------- +handshake_cancel(#sslsocket{pid = [Pid|_]}) -> + case call(Pid, cancel) of + closed -> + ok; + Error -> + Error + end. +%-------------------------------------------------------------------- +-spec socket_control(tls_gen_connection | dtls_gen_connection, port(), [pid()], atom()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Set the ssl process to own the accept socket +%%-------------------------------------------------------------------- +socket_control(Connection, Socket, Pid, Transport) -> + socket_control(Connection, Socket, Pid, Transport, undefined). + +%-------------------------------------------------------------------- +-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) -> + %% dtls listener process must have the socket control + {ok, Connection:socket(Pids, Transport, Socket, undefined)}; + +socket_control(tls_gen_connection = Connection, Socket, [Pid|_] = Pids, Transport, Trackers) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + {ok, Connection:socket(Pids, Transport, Socket, Trackers)}; + {error, Reason} -> + {error, Reason} + end; +socket_control(dtls_gen_connection = Connection, {PeerAddrPort, Socket}, [Pid|_] = Pids, Transport, Trackers) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + {ok, Connection:socket(Pids, Transport, {PeerAddrPort, Socket}, Trackers)}; + {error, Reason} -> + {error, Reason} + end. + +prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate}, + start_or_recv_from = RecvFrom} = State0, Connection) + when Renegotiate =/= {false, first}, + RecvFrom =/= undefined -> + State = Connection:reinit(State0), + {no_record, ack_connection(State)}; +prepare_connection(State0, Connection) -> + State = Connection:reinit(State0), + {no_record, ack_connection(State)}. + +%%==================================================================== +%% User events +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec send(pid(), iodata()) -> ok | {error, reason()}. +%% +%% Description: Sends data over the ssl connection +%%-------------------------------------------------------------------- +send(Pid, Data) -> + call(Pid, {application_data, + %% iolist_to_iovec should really + %% be called iodata_to_iovec() + erlang:iolist_to_iovec(Data)}). + +%%-------------------------------------------------------------------- +-spec recv(pid(), integer(), timeout()) -> + {ok, binary() | list()} | {error, reason()}. +%% +%% Description: Receives data when active = false +%%-------------------------------------------------------------------- +recv(Pid, Length, Timeout) -> + call(Pid, {recv, Length, Timeout}). + +%%-------------------------------------------------------------------- +-spec connection_information(pid(), boolean()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Get connection information +%%-------------------------------------------------------------------- +connection_information(Pid, IncludeSecrityInfo) when is_pid(Pid) -> + case call(Pid, {connection_information, IncludeSecrityInfo}) of + {ok, Info} when IncludeSecrityInfo == true -> + {ok, maybe_add_keylog(Info)}; + Other -> + Other + end. + +%%-------------------------------------------------------------------- +-spec close(pid(), {close, Timeout::integer() | + {NewController::pid(), Timeout::integer()}}) -> + ok | {ok, port()} | {error, reason()}. +%% +%% Description: Close an ssl connection +%%-------------------------------------------------------------------- +close(ConnectionPid, How) -> + case call(ConnectionPid, How) of + {error, closed} -> + ok; + Other -> + Other + end. +%%-------------------------------------------------------------------- +-spec shutdown(pid(), atom()) -> ok | {error, reason()}. +%% +%% Description: Same as gen_tcp:shutdown/2 +%%-------------------------------------------------------------------- +shutdown(ConnectionPid, How) -> + call(ConnectionPid, {shutdown, How}). + +%%-------------------------------------------------------------------- +-spec new_user(pid(), pid()) -> ok | {error, reason()}. +%% +%% Description: Changes process that receives the messages when active = true +%% or once. +%%-------------------------------------------------------------------- +new_user(ConnectionPid, User) -> + call(ConnectionPid, {new_user, User}). + +%%-------------------------------------------------------------------- +-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Same as inet:getopts/2 +%%-------------------------------------------------------------------- +get_opts(ConnectionPid, OptTags) -> + call(ConnectionPid, {get_opts, OptTags}). +%%-------------------------------------------------------------------- +-spec set_opts(pid(), list()) -> ok | {error, reason()}. +%% +%% Description: Same as inet:setopts/2 +%%-------------------------------------------------------------------- +set_opts(ConnectionPid, Options) -> + call(ConnectionPid, {set_opts, Options}). + +%%-------------------------------------------------------------------- +-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. +%% +%% Description: Returns the peer cert +%%-------------------------------------------------------------------- +peer_certificate(ConnectionPid) -> + call(ConnectionPid, peer_certificate). + +%%-------------------------------------------------------------------- +-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the negotiated protocol +%%-------------------------------------------------------------------- +negotiated_protocol(ConnectionPid) -> + call(ConnectionPid, negotiated_protocol). + +dist_handshake_complete(ConnectionPid, DHandle) -> + gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). + +handle_sni_extension(undefined, State) -> + {ok, State}; +handle_sni_extension(#sni{hostname = Hostname}, State0) -> + case check_hostname(State0, Hostname) of + valid -> + State1 = handle_sni_hostname(Hostname, State0), + State = set_sni_guided_cert_selection(State1, true), + {ok, State}; + unrecognized_name -> + {ok, handle_sni_hostname(Hostname, State0)}; + #alert{} = Alert -> + {error, Alert} + end. + +%%==================================================================== +%% Generic states +%%==================================================================== +%%-------------------------------------------------------------------- +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | {start, {list(), list()}, timeout()}| term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +initial_hello({call, From}, {start, Timeout}, + #state{static_env = #static_env{role = client = Role, + host = Host, + port = Port, + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + ocsp_stapling_state = OcspState0} = HsEnv, + connection_env = CEnv, + ssl_options = #{log_level := LogLevel, + %% Use highest version in initial ClientHello. + %% Versions is a descending list of supported versions. + versions := [HelloVersion|_] = Versions, + session_tickets := SessionTickets, + ocsp_stapling := OcspStaplingOpt, + ocsp_nonce := OcspNonceOpt} = SslOpts, + session = Session, + connection_states = ConnectionStates0 + } = State0) -> + + KeyShare = maybe_generate_client_shares(SslOpts), + %% Update UseTicket in case of automatic session resumption + {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0), + TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), + OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt), + Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Session#session.session_id, + Renegotiation, + Session#session.own_certificates, + KeyShare, + TicketData, + OcspNonce), + + Handshake0 = ssl_handshake:init_handshake_history(), + + %% Update pre_shared_key extension with binders (TLS 1.3) + Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion), + + MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), + + {BinMsg, ConnectionStates, Handshake} = + Connection:encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0), + + tls_socket:send(Transport, Socket, BinMsg), + ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1), + ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), + + %% RequestedVersion is used as the legacy record protocol version and shall be + %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the + %% lowest supported protocol version. + %% + %% negotiated_version is also used by the TLS 1.3 state machine and is set after + %% ServerHello is processed. + RequestedVersion = tls_record:hello_version(Versions), + State = State1#state{connection_states = ConnectionStates, + connection_env = CEnv#connection_env{ + negotiated_version = RequestedVersion}, + session = Session, + handshake_env = HsEnv#handshake_env{ + tls_handshake_history = Handshake, + ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}, + start_or_recv_from = From, + key_share = KeyShare}, + NextState = next_statem_state(Versions, Role), + Connection:next_event(NextState, no_record, State, + [{{timeout, handshake}, Timeout, close}]); +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), + 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), + State = ssl_config(SslOpts, Role, State0), + initial_hello({call, From}, {start, Timeout}, + State#state{ssl_options = SslOpts, + socket_options = new_emulated(EmOpts, SockOpts)}) + catch throw:Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} + end; +initial_hello({call, From}, {new_user, _} = Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +initial_hello({call, From}, _Msg, _State) -> + {keep_state_and_data, [{reply, From, {error, notsup_on_transport_accept_socket}}]}; +initial_hello(_Type, _Event, _State) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec config_error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +config_error({call, From}, {start, _Timeout}, + #state{protocol_specific = #{error := Error}} = State) -> + {stop_and_reply, {shutdown, normal}, + [{reply, From, {error, Error}}], State}; +config_error({call, From}, {close, _}, State) -> + {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State}; +config_error({call, From}, _Msg, State) -> + {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}; +config_error(_Type, _Event, _State) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection({call, RecvFrom}, {recv, N, Timeout}, + #state{static_env = #static_env{protocol_cb = Connection}, + socket_options = + #socket_options{active = false}} = State0) -> + passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, + [{{timeout, recv}, Timeout, timeout}]); +connection({call, From}, peer_certificate, + #state{session = #session{peer_certificate = Cert}} = State) -> + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); +connection({call, From}, {connection_information, true}, State) -> + Info = connection_info(State) ++ security_info(State), + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); +connection({call, From}, {connection_information, false}, State) -> + Info = connection_info(State), + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = undefined}} = State) -> + hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = SelectedProtocol}} = State) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = SelectedProtocol, + negotiated_protocol = undefined}} = State) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, + {close, {Pid, _Timeout}}, + #state{connection_env = #connection_env{terminated = closed} = CEnv, + protocol_specific = PS} = State) -> + {next_state, downgrade, State#state{connection_env = + CEnv#connection_env{terminated = true, + downgrade = {Pid, From}}, + protocol_specific = PS#{active_n_toggle => true, + active_n => 1} + }, + [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; +connection({call, From}, + {close,{Pid, Timeout}}, + #state{connection_states = ConnectionStates, + static_env = #static_env{protocol_cb = Connection}, + protocol_specific = #{sender := Sender} = PS, + connection_env = CEnv + } = State0) -> + case tls_sender:downgrade(Sender, Timeout) of + {ok, Write} -> + %% User downgrades connection + %% When downgrading an TLS connection to a transport connection + %% we must recive the close alert from the peer before releasing the + %% transport socket. + State = Connection:send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + State0#state{connection_states = + ConnectionStates#{current_write => Write}}), + {next_state, downgrade, State#state{connection_env = + CEnv#connection_env{downgrade = {Pid, From}, + terminated = true}, + protocol_specific = PS#{active_n_toggle => true, + active_n => 1} + }, + [{timeout, Timeout, downgrade}]}; + {error, timeout} -> + {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} + end; +connection({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +connection(cast, {dist_handshake_complete, DHandle}, + #state{ssl_options = #{erl_dist := true}, + static_env = #static_env{protocol_cb = Connection}, + connection_env = CEnv, + socket_options = SockOpts} = State0) -> + process_flag(priority, normal), + State1 = + State0#state{ + socket_options = SockOpts#socket_options{active = true}, + connection_env = CEnv#connection_env{erl_dist_handle = DHandle}, + bytes_to_read = undefined}, + {Record, State} = read_application_data(<<>>, State1), + Connection:next_event(connection, Record, State); +connection(info, Msg, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:handle_info(Msg, ?FUNCTION_NAME, State); +connection(internal, {recv, RecvFrom}, #state{start_or_recv_from = RecvFrom, + static_env = #static_env{protocol_cb = Connection}} = State) -> + passive_receive(State, ?FUNCTION_NAME, Connection, []); +connection(Type, Msg, State) -> + handle_common_event(Type, Msg, ?FUNCTION_NAME, State). + +%%==================================================================== +%% Event/Msg handling +%%==================================================================== +handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, + #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = _Version}} = State0) -> + Hist = ssl_handshake:update_handshake_history(Hist0, Raw), + {next_state, StateName, + State0#state{handshake_env = + HsEnv#handshake_env{tls_handshake_history = Hist}}, + [{next_event, internal, Handshake}]}; +handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, + #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); +handle_common_event(timeout, hibernate, _, _) -> + {keep_state_and_data, [hibernate]}; +handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State); +handle_common_event({timeout, handshake}, close, _StateName, #state{start_or_recv_from = StartFrom} = State) -> + {stop_and_reply, + {shutdown, user_timeout}, + {reply, StartFrom, {error, timeout}}, State#state{start_or_recv_from = undefined}}; +handle_common_event({timeout, recv}, timeout, StateName, #state{start_or_recv_from = RecvFrom} = State) -> + {next_state, StateName, State#state{start_or_recv_from = undefined, + bytes_to_read = undefined}, [{reply, RecvFrom, {error, timeout}}]}; +handle_common_event(internal, {recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom}) when + StateName =/= connection -> + {keep_state_and_data, [postpone]}; +handle_common_event(Type, Msg, StateName, #state{connection_env = + #connection_env{negotiated_version = Version}} = State) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}), + handle_own_alert(Alert, Version, StateName, State). + +handle_call({application_data, _Data}, _, _, _) -> + %% In renegotiation priorities handshake, send data when handshake is finished + {keep_state_and_data, [postpone]}; +handle_call({close, _} = Close, From, StateName, #state{connection_env = CEnv} = State) -> + %% Run terminate before returning so that the reuseaddr + %% inet-option works properly + Result = terminate(Close, StateName, State), + {stop_and_reply, + {shutdown, normal}, + {reply, From, Result}, State#state{connection_env = CEnv#connection_env{terminated = true}}}; +handle_call({shutdown, read_write = How}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, + connection_env = CEnv} = State) -> + try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + StateName, State) of + _ -> + case Transport:shutdown(Socket, How) of + ok -> + {next_state, StateName, State#state{connection_env = + CEnv#connection_env{terminated = true}}, + [{reply, From, ok}]}; + Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, + State#state{connection_env = CEnv#connection_env{terminated = true}}} + end + catch + throw:Return -> + Return + end; +handle_call({shutdown, How0}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State) -> + case Transport:shutdown(Socket, How0) of + ok -> + {next_state, StateName, State, [{reply, From, ok}]}; + Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State} + end; +handle_call({recv, _N, _Timeout}, From, _, + #state{socket_options = + #socket_options{active = Active}}) when Active =/= false -> + {keep_state_and_data, [{reply, From, {error, einval}}]}; +handle_call({recv, N, Timeout}, RecvFrom, StateName, State) -> + %% Doing renegotiate wait with handling request until renegotiate is + %% finished. + {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom}, + [{next_event, internal, {recv, RecvFrom}} , {{timeout, recv}, Timeout, timeout}]}; +handle_call({new_user, User}, From, StateName, + State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}) -> + NewMon = erlang:monitor(process, User), + erlang:demonitor(OldMon, [flush]), + {next_state, StateName, State#state{connection_env = CEnv#connection_env{user_application = {NewMon, User}}}, + [{reply, From, ok}]}; +handle_call({get_opts, OptTags}, From, _, + #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}, + socket_options = SockOpts}) -> + OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), + {keep_state_and_data, [{reply, From, OptsReply}]}; +handle_call({set_opts, Opts0}, From, StateName, + #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport, + trackers = Trackers}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}, + socket_options = Opts1 + } = State0) -> + {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), + case {proplists:lookup(active, Opts0), Opts} of + {{_, N}, #socket_options{active=false}} when is_integer(N) -> + send_user( + Pid, + format_passive( + Connection:pids(State0), Transport, Socket, Trackers, Connection)); + _ -> + ok + end, + State = State0#state{socket_options = Opts}, + handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); + +handle_call(renegotiate, From, StateName, _) when StateName =/= connection -> + {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; + +handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, + #state{connection_states = ConnectionStates, + connection_env = #connection_env{negotiated_version = Version}}) -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates, read), + #security_parameters{master_secret = MasterSecret, + client_random = ClientRandom, + server_random = ServerRandom, + prf_algorithm = PRFAlgorithm} = SecParams, + Reply = try + SecretToUse = case Secret of + _ when is_binary(Secret) -> Secret; + master_secret -> MasterSecret + end, + SeedToUse = lists:reverse( + lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; + (client_random, Acc) -> [ClientRandom|Acc]; + (server_random, Acc) -> [ServerRandom|Acc] + end, [], Seed)), + ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) + catch + exit:_ -> {error, badarg}; + error:Reason -> {error, Reason} + end, + {keep_state_and_data, [{reply, From, Reply}]}; +handle_call(_,_,_,_) -> + {keep_state_and_data, [postpone]}. +handle_info({ErrorTag, Socket, econnaborted}, StateName, + #state{static_env = #static_env{role = Role, + host = Host, + port = Port, + socket = Socket, + transport_cb = Transport, + error_tag = ErrorTag, + trackers = Trackers, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = Type}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + start_or_recv_from = StartFrom + } = State) when StateName =/= connection -> + + maybe_invalidate_session(Version, Type, Role, Host, Port, Session), + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers,Socket, + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), + {stop, {shutdown, normal}, State}; + +handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{ + role = Role, + socket = Socket, + error_tag = ErrorTag}, + ssl_options = #{log_level := Level}} = State) -> + ssl_logger:log(info, Level, #{description => "Socket error", + reason => [{error_tag, ErrorTag}, {description, Reason}]}, ?LOCATION), + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), + handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown,normal}, State}; + +handle_info({'DOWN', MonitorRef, _, _, Reason}, _, + #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, + ssl_options = #{erl_dist := true}}) -> + {stop, {shutdown, Reason}}; +handle_info({'DOWN', MonitorRef, _, _, _}, _, + #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) -> + {stop, {shutdown, normal}}; +handle_info({'EXIT', Pid, _Reason}, StateName, + #state{connection_env = #connection_env{user_application = {_MonitorRef, Pid}}} = State) -> + %% It seems the user application has linked to us + %% - ignore that and let the monitor handle this + {next_state, StateName, State}; +%%% So that terminate will be run when supervisor issues shutdown +handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> + {stop, shutdown, State}; +handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> + %% Handle as transport close" + {stop,{shutdown, transport_closed}, State}; +handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> + {stop,{shutdown, Reason}, State}; +handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> %% PRE TLS-1.3 + {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; +handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = ErrorTag}, + ssl_options = #{log_level := Level}} = State) -> + ssl_logger:log(notice, Level, #{description => "Unexpected INFO message", + reason => [{message, Msg}, {socket, Socket}, + {error_tag, ErrorTag}]}, ?LOCATION), + {next_state, StateName, State}. + +%%==================================================================== +%% Application Data +%%==================================================================== +read_application_data(Data, + #state{user_data_buffer = + {Front0,BufferSize0,Rear0}, + connection_env = + #connection_env{erl_dist_handle = DHandle}} + = State) -> + Front = Front0, + BufferSize = BufferSize0 + byte_size(Data), + Rear = [Data|Rear0], + case DHandle of + undefined -> + read_application_data(State, Front, BufferSize, Rear); + _ -> + try read_application_dist_data(DHandle, Front, BufferSize, Rear) of + Buffer -> + {no_record, State#state{user_data_buffer = Buffer}} + catch error:_ -> + {stop,disconnect, + State#state{user_data_buffer = {Front,BufferSize,Rear}}} + end + end. +passive_receive(#state{user_data_buffer = {Front,BufferSize,Rear}, + %% Assert! Erl distribution uses active sockets + connection_env = #connection_env{erl_dist_handle = undefined}} + = State0, StateName, Connection, StartTimerAction) -> + case BufferSize of + 0 -> + Connection:next_event(StateName, no_record, State0, StartTimerAction); + _ -> + case read_application_data(State0, Front, BufferSize, Rear) of + {stop, _, _} = ShutdownError -> + ShutdownError; + {Record, State} -> + case State#state.start_or_recv_from of + undefined -> + %% Cancel recv timeout as data has been delivered + Connection:next_event(StateName, Record, State, + [{{timeout, recv}, infinity, timeout}]); + _ -> + Connection:next_event(StateName, Record, State, StartTimerAction) + end + end + end. + +%%==================================================================== +%% Hibernation +%%==================================================================== + +hibernate_after(connection = StateName, + #state{ssl_options= #{hibernate_after := HibernateAfter}} = State, + Actions) -> + {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; +hibernate_after(StateName, State, Actions) -> + {next_state, StateName, State, Actions}. + +%%==================================================================== +%% Alert and close handling +%%==================================================================== +send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:send_alert_in_connection(Alert, State); +send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + Connection:send_alert(Alert, State). + +handle_own_alert(Alert0, _, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = #{log_level := LogLevel}} = State) -> + try %% Try to tell the other side + send_alert(Alert0, StateName, State) + catch _:_ -> %% Can crash if we are in a uninitialized state + ignore + end, + try %% Try to tell the local user + Alert = Alert0#alert{role = Role}, + log_alert(LogLevel, Role, Connection:protocol_name(), StateName, Alert), + handle_normal_shutdown(Alert,StateName, State) + catch _:_ -> + ok + end, + {stop, {shutdown, own_alert}, State}. + +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + trackers = Trackers}, + handshake_env = #handshake_env{renegotiation = {false, first}}, + start_or_recv_from = StartFrom} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, Trackers, Socket, StartFrom, Alert, Role, StateName, Connection); + +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + trackers = Trackers}, + connection_env = #connection_env{user_application = {_Mon, Pid}}, + socket_options = Opts, + start_or_recv_from = RecvFrom} = State) -> + Pids = Connection:pids(State), + alert_user(Pids, Transport, 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 = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + downgrade= StateName, State) -> + {next_state, StateName, State, [{next_event, internal, Alert}]}; +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert0, + StateName, #state{static_env = #static_env{role = Role}} = State) -> + Alert = Alert0#alert{role = opposite_role(Role)}, + handle_normal_shutdown(Alert, StateName, State), + {stop,{shutdown, peer_close}, State}; +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, internal}}, + ssl_options = #{log_level := LogLevel}} = State) -> + Alert = Alert0#alert{role = opposite_role(Role)}, + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, Alert), + handle_normal_shutdown(Alert, StateName, State), + {stop,{shutdown, peer_close}, State}; + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = #{log_level := LogLevel} + } = State0) -> + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + gen_statem:reply(From, {error, renegotiation_rejected}), + State = Connection:reinit_handshake_data(State0), + Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}); + +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = #{log_level := LogLevel} + } = State0) -> + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + gen_statem:reply(From, {error, renegotiation_rejected}), + %% Go back to connection! + State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}), + Connection:next_event(connection, no_record, State); + +%% Gracefully log and ignore all other warning alerts +handle_alert(#alert{level = ?WARNING} = Alert, StateName, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = #{log_level := LogLevel}} = State) -> + log_alert(LogLevel, Role, + Connection:protocol_name(), StateName, + Alert#alert{role = opposite_role(Role)}), + Connection:next_event(StateName, no_record, State). +handle_trusted_certs_db(#state{ssl_options = + #{cacertfile := <<>>, cacerts := []}}) -> + %% 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 -> + %% 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); +handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> + %% Something went wrong early (typically cacertfile does not + %% exist) so there is nothing to handle + ok; +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + file_ref_db = RefDb}, + ssl_options = #{cacertfile := File}}) -> + case ssl_pkix_db:ref_count(Ref, RefDb, -1) of + 0 -> + ssl_manager:clean_cert_db(Ref, File); + _ -> + ok + end. + +maybe_invalidate_session({3, 4},_, _, _, _, _) -> + ok; +maybe_invalidate_session({3, N}, Type, Role, Host, Port, Session) when N < 4 -> + maybe_invalidate_session(Type, Role, Host, Port, Session). + +maybe_invalidate_session({false, first}, server = Role, Host, Port, Session) -> + invalidate_session(Role, Host, Port, Session); +maybe_invalidate_session(_, _, _, _, _) -> + ok. + +terminate(_, _, #state{connection_env = #connection_env{terminated = true}}) -> + %% Happens when user closes the connection using ssl:close/1 + %% we want to guarantee that Transport:close has been called + %% when ssl:close/1 returns unless it is a downgrade where + %% we want to guarantee that close alert is received before + %% returning. In both cases terminate has been run manually + %% before run by gen_statem which will end up here + ok; +terminate({shutdown, transport_closed} = Reason, + _StateName, #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate({shutdown, own_alert}, _StateName, #state{ + static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> + handle_trusted_certs_db(State), + case application:get_env(ssl, alert_timeout) of + {ok, Timeout} when is_integer(Timeout) -> + Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); + _ -> + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) + end; +terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket} + } = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate(Reason, connection, #state{static_env = #static_env{ + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, + connection_states = ConnectionStates, + ssl_options = #{padding_check := Check} + } = State) -> + handle_trusted_certs_db(State), + Alert = terminate_alert(Reason), + %% Send the termination ALERT if possible + catch (ok = Connection:send_alert_in_connection(Alert, State)), + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, + protocol_cb = Connection, + socket = Socket} + } = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined). + +%%==================================================================== +%% Log handling +%%==================================================================== +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, + cacerts => ?SECRET_PRINTOUT, + key => ?SECRET_PRINTOUT, + dh => ?SECRET_PRINTOUT, + psk_identity => ?SECRET_PRINTOUT, + srp_identity => ?SECRET_PRINTOUT}, + [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, + protocol_buffers = ?SECRET_PRINTOUT, + user_data_buffer = ?SECRET_PRINTOUT, + handshake_env = ?SECRET_PRINTOUT, + connection_env = ?SECRET_PRINTOUT, + session = ?SECRET_PRINTOUT, + ssl_options = NewOptions, + flight_buffer = ?SECRET_PRINTOUT} + }}]}]. +%%-------------------------------------------------------------------- +%%% 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} -> + wait_sh; + _ -> + hello + end; +next_statem_state([Version], server) -> + case ssl:tls_version(Version) of + {3,4} -> + start; + _ -> + hello + end; +next_statem_state(_, _) -> + hello. + +call(FsmPid, Event) -> + try gen_statem:call(FsmPid, Event) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. + +check_hostname(#state{ssl_options = SslOptions}, Hostname) -> + case is_sni_value(Hostname) of + true -> + case is_hostname_recognized(SslOptions, Hostname) of + true -> + valid; + false -> + %% We should send an alert but for interoperability reasons we + %% allow the connection to be established. + %% ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME) + unrecognized_name + end; + false -> + ?ALERT_REC(?FATAL, ?UNRECOGNIZED_NAME, + {sni_included_trailing_dot, Hostname}) + end. + +is_sni_value(Hostname) -> + case hd(lists:reverse(Hostname)) of + $. -> + false; + _ -> + true + end. + +is_hostname_recognized(#{sni_fun := undefined, + sni_hosts := SNIHosts}, Hostname) -> + proplists:is_defined(Hostname, SNIHosts); +is_hostname_recognized(_, _) -> + true. + +handle_sni_hostname(Hostname, + #state{static_env = #static_env{role = Role} = InitStatEnv0, + handshake_env = HsEnv, + connection_env = CEnv} = State0) -> + NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), + case NewOptions of + undefined -> + State0; + _ -> + {ok, #{cert_db_ref := Ref, + cert_db_handle := CertDbHandle, + fileref_db_handle := FileRefHandle, + session_cache := CacheHandle, + crl_db_info := CRLDbHandle, + private_key := Key, + dh_params := DHParams, + own_certificates := OwnCerts}} = + ssl_config:init(NewOptions, Role), + State0#state{ + session = State0#state.session#session{own_certificates = OwnCerts}, + static_env = InitStatEnv0#static_env{ + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle + }, + connection_env = CEnv#connection_env{private_key = Key}, + ssl_options = NewOptions, + handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, + diffie_hellman_params = DHParams} + } + end. + +update_ssl_options_from_sni(#{sni_fun := SNIFun, + sni_hosts := SNIHosts} = OrigSSLOptions, SNIHostname) -> + SSLOption = + case SNIFun of + undefined -> + proplists:get_value(SNIHostname, + SNIHosts); + SNIFun -> + SNIFun(SNIHostname) + end, + case SSLOption of + undefined -> + undefined; + _ -> + ssl:handle_options(SSLOption, server, OrigSSLOptions) + end. + +set_sni_guided_cert_selection(#state{handshake_env = HsEnv0} = State, Bool) -> + HsEnv = HsEnv0#handshake_env{sni_guided_cert_selection = Bool}, + State#state{handshake_env = HsEnv}. + +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; + Initiater == internal -> + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) -> + gen_statem:reply(From, ok), + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv, + start_or_recv_from = StartFrom} = State) when StartFrom =/= undefined -> + gen_statem:reply(StartFrom, connected), + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, + start_or_recv_from = undefined}; +ack_connection(State) -> + State. + +no_records(Extensions) -> + maps:map(fun(_, Value) -> + ssl_handshake:extension_value(Value) + end, Extensions). + +handle_active_option(false, connection = StateName, To, Reply, State) -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + +handle_active_option(_, connection = StateName, To, Reply, #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{terminated = true}, + user_data_buffer = {_,0,_}} = State) -> + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_delivered), + handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop_and_reply,{shutdown, peer_close}, [{reply, To, Reply}]}; +handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, + user_data_buffer = {_,0,_}} = State0) -> + case Connection:next_event(StateName0, no_record, State0) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, _, _} = Stop -> + Stop + end; +handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = {_,0,_}} = State) -> + %% Active once already set + {next_state, StateName, State, [{reply, To, Reply}]}; + +%% user_data_buffer nonempty +handle_active_option(_, StateName0, To, Reply, + #state{static_env = #static_env{protocol_cb = Connection}} = State0) -> + case read_application_data(<<>>, State0) of + {stop, _, _} = Stop -> + Stop; + {Record, State1} -> + %% Note: Renogotiation may cause StateName0 =/= StateName + case Connection:next_event(StateName0, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, _, _} = Stop -> + Stop + end + end. + +read_application_data(#state{ + socket_options = SocketOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom} = State, Front, BufferSize, Rear) -> + read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead). + +%% Pick binary from queue front, if empty wait for more data +read_application_data(State, [Bin|Front], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> + read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, Bin); +read_application_data(State, [] = Front, BufferSize, [] = Rear, SocketOpts, RecvFrom, BytesToRead) -> + 0 = BufferSize, % Assert + {no_record, State#state{socket_options = SocketOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {Front,BufferSize,Rear}}}; +read_application_data(State, [], BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead) -> + [Bin|Front] = lists:reverse(Rear), + read_application_data_bin(State, Front, BufferSize, [], SocketOpts, RecvFrom, BytesToRead, Bin). + +read_application_data_bin(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead, <<>>) -> + %% Done with this binary - get next + read_application_data(State, Front, BufferSize, Rear, SocketOpts, RecvFrom, BytesToRead); +read_application_data_bin(State, Front0, BufferSize0, Rear0, SocketOpts0, RecvFrom, BytesToRead, Bin0) -> + %% Decode one packet from a binary + case get_data(SocketOpts0, BytesToRead, Bin0) of + {ok, Data, Bin} -> % Send data + BufferSize = BufferSize0 - (byte_size(Bin0) - byte_size(Bin)), + read_application_data_deliver( + State, [Bin|Front0], BufferSize, Rear0, SocketOpts0, RecvFrom, Data); + {more, undefined} -> + %% We need more data, do not know how much + if + byte_size(Bin0) < BufferSize0 -> + %% We have more data in the buffer besides the first binary - concatenate all and retry + Bin = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), + read_application_data_bin( + State, [], BufferSize0, [], SocketOpts0, RecvFrom, BytesToRead, Bin); + true -> + %% All data is in the first binary, no use to retry - wait for more + {no_record, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}} + end; + {more, Size} when Size =< BufferSize0 -> + %% We have a packet in the buffer - collect it in a binary and decode + {Data,Front,Rear} = iovec_from_front(Size - byte_size(Bin0), Front0, Rear0, [Bin0]), + Bin = iolist_to_binary(Data), + read_application_data_bin( + State, Front, BufferSize0, Rear, SocketOpts0, RecvFrom, BytesToRead, Bin); + {more, _Size} -> + %% We do not have a packet in the buffer - wait for more + {no_record, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; + passive -> + {no_record, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Bin0|Front0],BufferSize0,Rear0}}}; + {error,_Reason} -> + %% Invalid packet in packet mode + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}} = State, + Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), + deliver_packet_error( + Connection:pids(State), Transport, Socket, SocketOpts0, + Buffer, Pid, RecvFrom, Trackers, Connection), + {stop, {shutdown, normal}, State#state{socket_options = SocketOpts0, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + user_data_buffer = {[Buffer],BufferSize0,[]}}} + end. + +read_application_data_deliver(State, Front, BufferSize, Rear, SocketOpts0, RecvFrom, Data) -> + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + trackers = Trackers}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}} = State, + SocketOpts = + deliver_app_data( + Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Trackers, Connection), + if + SocketOpts#socket_options.active =:= false -> + %% Passive mode, wait for active once or recv + {no_record, + State#state{ + user_data_buffer = {Front,BufferSize,Rear}, + start_or_recv_from = undefined, + bytes_to_read = undefined, + socket_options = SocketOpts + }}; + true -> %% Try to deliver more data + read_application_data(State, Front, BufferSize, Rear, SocketOpts, undefined, undefined) + end. + + +read_application_dist_data(DHandle, [Bin|Front], BufferSize, Rear) -> + read_application_dist_data(DHandle, Front, BufferSize, Rear, Bin); +read_application_dist_data(_DHandle, [] = Front, BufferSize, [] = Rear) -> + BufferSize = 0, + {Front,BufferSize,Rear}; +read_application_dist_data(DHandle, [], BufferSize, Rear) -> + [Bin|Front] = lists:reverse(Rear), + read_application_dist_data(DHandle, Front, BufferSize, [], Bin). +%% +read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> + case Bin0 of + %% + %% START Optimization + %% It is cheaper to match out several packets in one match operation than to loop for each + <<SizeA:32, DataA:SizeA/binary, + SizeB:32, DataB:SizeB/binary, + SizeC:32, DataC:SizeC/binary, + SizeD:32, DataD:SizeD/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD -> + %% We have 4 complete packets in the first binary + erlang:dist_ctrl_put_data(DHandle, DataA), + erlang:dist_ctrl_put_data(DHandle, DataB), + erlang:dist_ctrl_put_data(DHandle, DataC), + erlang:dist_ctrl_put_data(DHandle, DataD), + read_application_dist_data( + DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest); + <<SizeA:32, DataA:SizeA/binary, + SizeB:32, DataB:SizeB/binary, + SizeC:32, DataC:SizeC/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB, 0 < SizeC -> + %% We have 3 complete packets in the first binary + erlang:dist_ctrl_put_data(DHandle, DataA), + erlang:dist_ctrl_put_data(DHandle, DataB), + erlang:dist_ctrl_put_data(DHandle, DataC), + read_application_dist_data( + DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest); + <<SizeA:32, DataA:SizeA/binary, + SizeB:32, DataB:SizeB/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB -> + %% We have 2 complete packets in the first binary + erlang:dist_ctrl_put_data(DHandle, DataA), + erlang:dist_ctrl_put_data(DHandle, DataB), + read_application_dist_data( + DHandle, Front0, BufferSize - (2*4+SizeA+SizeB), Rear0, Rest); + %% END Optimization + %% + %% Basic one packet code path + <<Size:32, Data:Size/binary, Rest/binary>> -> + %% We have a complete packet in the first binary + 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), + read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest); + <<Size:32, FirstData/binary>> when 4+Size =< BufferSize -> + %% We have a complete packet in the buffer + %% - fetch the missing content from the buffer front + {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]), + 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), + read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear); + <<Bin/binary>> -> + %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we + %% match out the whole binary which will trick the optimization into keeping the match context + %% for the first binary contains complete packet code above + case Bin of + <<_Size:32, _InsufficientData/binary>> -> + %% We have a length field in the first binary but there is not enough data + %% in the buffer to form a complete packet - await more data + {[Bin|Front0],BufferSize,Rear0}; + <<IncompleteLengthField/binary>> when 4 < BufferSize -> + %% We do not have a length field in the first binary but the buffer + %% contains enough data to maybe form a packet + %% - fetch a tiny binary from the buffer front to complete the length field + {LengthField,Front,Rear} = + case IncompleteLengthField of + <<>> -> + iovec_from_front(4, Front0, Rear0, []); + _ -> + iovec_from_front( + 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]) + end, + LengthBin = iolist_to_binary(LengthField), + read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin); + <<IncompleteLengthField/binary>> -> + %% We do not have enough data in the buffer to even form a length field - await more data + case IncompleteLengthField of + <<>> -> + {Front0,BufferSize,Rear0}; + _ -> + {[IncompleteLengthField|Front0],BufferSize,Rear0} + end + end + end. + +iovec_from_front(0, Front, Rear, Acc) -> + {lists:reverse(Acc),Front,Rear}; +iovec_from_front(Size, [], Rear, Acc) -> + case Rear of + %% Avoid lists:reverse/1 for simple cases. + %% Case clause for [] to avoid infinite loop. + [_] -> + iovec_from_front(Size, Rear, [], Acc); + [Bin2,Bin1] -> + iovec_from_front(Size, [Bin1,Bin2], [], Acc); + [Bin3,Bin2,Bin1] -> + iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc); + [_,_,_|_] = Rear -> + iovec_from_front(Size, lists:reverse(Rear), [], Acc) + end; +iovec_from_front(Size, [Bin|Front], Rear, []) -> + case Bin of + <<Last:Size/binary>> -> % Just enough + {[Last],Front,Rear}; + <<Last:Size/binary, Rest/binary>> -> % More than enough, split here + {[Last],[Rest|Front],Rear}; + <<>> -> % Not enough, skip empty binaries + iovec_from_front(Size, Front, Rear, []); + <<_/binary>> -> % Not enough + BinSize = byte_size(Bin), + iovec_from_front(Size - BinSize, Front, Rear, [Bin]) + end; +iovec_from_front(Size, [Bin|Front], Rear, Acc) -> + case Bin of + <<Last:Size/binary>> -> % Just enough + {lists:reverse(Acc, [Last]),Front,Rear}; + <<Last:Size/binary, Rest/binary>> -> % More than enough, split here + {lists:reverse(Acc, [Last]),[Rest|Front],Rear}; + <<>> -> % Not enough, skip empty binaries + iovec_from_front(Size, Front, Rear, Acc); + <<_/binary>> -> % Not enough + BinSize = byte_size(Bin), + iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) + end. +%% Picks ClientData +get_data(#socket_options{active=false}, undefined, _Bin) -> + %% Recv timed out save buffer data until next recv + passive; +get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Bin) + when Raw =:= raw; Raw =:= 0 -> %% Raw Mode + case Bin of + <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 -> + %% Active true or once, or passive mode recv(0) + {ok, Bin, <<>>}; + <<Data:BytesToRead/binary, Rest/binary>> -> + %% Passive Mode, recv(Bytes) + {ok, Data, Rest}; + <<_/binary>> -> + %% Passive Mode not enough data + {more, BytesToRead} + end; +get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) -> + PacketOpts = [{packet_size, Size}], + decode_packet(Type, Bin, PacketOpts). + +decode_packet({http, headers}, Buffer, PacketOpts) -> + decode_packet(httph, Buffer, PacketOpts); +decode_packet({http_bin, headers}, Buffer, PacketOpts) -> + decode_packet(httph_bin, Buffer, PacketOpts); +decode_packet(Type, Buffer, PacketOpts) -> + erlang:decode_packet(Type, Buffer, PacketOpts). + +%% Just like with gen_tcp sockets, an ssl socket that has been configured with +%% {packet, http} (or {packet, http_bin}) will automatically switch to expect +%% HTTP headers after it sees a HTTP Request or HTTP Response line. We +%% represent the current state as follows: +%% #socket_options.packet =:= http: Expect a HTTP Request/Response line +%% #socket_options.packet =:= {http, headers}: Expect HTTP Headers +%% Note that if the user has explicitly configured the socket to expect +%% HTTP headers using the {packet, httph} option, we don't do any automatic +%% switching of states. +deliver_app_data(CPids, Transport, Socket, + #socket_options{active=Active, packet=Type} = SOpts, + Data, Pid, From, Trackers, Connection) -> + send_or_reply(Active, Pid, From, + format_reply(CPids, Transport, Socket, + SOpts, Data, Trackers, Connection)), + SO = + case Data of + {P, _, _, _} + when ((P =:= http_request) or (P =:= http_response)), + ((Type =:= http) or (Type =:= http_bin)) -> + SOpts#socket_options{packet={Type, headers}}; + http_eoh when tuple_size(Type) =:= 2 -> + %% End of headers - expect another Request/Response line + {Type1, headers} = Type, + SOpts#socket_options{packet=Type1}; + _ -> + SOpts + end, + case Active of + once -> + SO#socket_options{active=false}; + 1 -> + send_user(Pid, + format_passive(CPids, Transport, + Socket, Trackers, Connection)), + SO#socket_options{active=false}; + N when is_integer(N) -> + SO#socket_options{active=N - 1}; + _ -> + SO + end. + +format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packet, + header = Header}, Data, _, _) -> + {ok, do_format_reply(Mode, Packet, Header, Data)}; +format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, + header = Header}, Data, Trackers, Connection) -> + {ssl, Connection:socket(CPids, Transport, Socket, Trackers), + do_format_reply(Mode, Packet, Header, Data)}. + +deliver_packet_error(CPids, Transport, Socket, + SO= #socket_options{active = Active}, Data, Pid, From, Trackers, Connection) -> + send_or_reply(Active, Pid, From, format_packet_error(CPids, + Transport, Socket, SO, Data, Trackers, Connection)). + +format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, _, _) -> + {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; +format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, + Data, Trackers, Connection) -> + {ssl_error, Connection:socket(CPids, Transport, Socket, Trackers), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. + +do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode + header(N, Data); +do_format_reply(binary, _, _, Data) -> + Data; +do_format_reply(list, Packet, _, Data) + when Packet == http; Packet == {http, headers}; + Packet == http_bin; Packet == {http_bin, headers}; + Packet == httph; Packet == httph_bin -> + Data; +do_format_reply(list, _,_, Data) -> + binary_to_list(Data). + +format_passive(CPids, Transport, Socket, Trackers, Connection) -> + {ssl_passive, Connection:socket(CPids, Transport, Socket, Trackers)}. + +header(0, <<>>) -> + <<>>; +header(_, <<>>) -> + []; +header(0, Binary) -> + Binary; +header(N, Binary) -> + <<?BYTE(ByteN), NewBinary/binary>> = Binary, + [ByteN | header(N-1, NewBinary)]. + +send_or_reply(false, _Pid, From, Data) when From =/= undefined -> + gen_statem:reply(From, Data); +send_or_reply(false, Pid, undefined, _) when is_pid(Pid) -> + ok; +send_or_reply(_, no_pid, _, _) -> + ok; +send_or_reply(_, Pid, _, Data) -> + send_user(Pid, Data). + +send_user(Pid, Msg) -> + Pid ! Msg, + ok. + +alert_user(Pids, Transport, Trackers, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Trackers, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection); +alert_user(Pids, Transport, Trackers, Socket,_, _, _, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection). + +alert_user(Pids, Transport, Trackers, Socket, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Trackers, Socket, false, no_pid, From, Alert, Role, StateName, Connection). + +alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined -> + %% If there is an outstanding ssl_accept | recv + %% From will be defined and send_or_reply will + %% send the appropriate error message. + ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName), + send_or_reply(Active, Pid, From, {error, ReasonCode}); +alert_user(Pids, Transport, Trackers, Socket, Active, Pid, From, Alert, Role, StateName, Connection) -> + case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of + closed -> + send_or_reply(Active, Pid, From, + {ssl_closed, Connection:socket(Pids, Transport, Socket, Trackers)}); + ReasonCode -> + send_or_reply(Active, Pid, From, + {ssl_error, Connection:socket(Pids, Transport, Socket, Trackers), ReasonCode}) + end. + +log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> + ssl_logger:log(notice, Level, #{protocol => ProtocolName, + role => Role, + statename => StateName, + alert => Alert, + alerter => own}, Alert#alert.where); +log_alert(Level, Role, ProtocolName, StateName, Alert) -> + ssl_logger:log(notice, Level, #{protocol => ProtocolName, + role => Role, + statename => StateName, + alert => Alert, + alerter => peer}, Alert#alert.where). +terminate_alert(normal) -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert({Reason, _}) when Reason == close; + Reason == shutdown -> + ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY); +terminate_alert(_) -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR). + +invalidate_session(client, Host, Port, Session) -> + ssl_manager:invalidate_session(Host, Port, Session); +invalidate_session(server, _, _, _) -> + ok. + +opposite_role(client) -> + server; +opposite_role(server) -> + client. + +connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname, + resumption = Resumption}, + session = #session{session_id = SessionId, + cipher_suite = CipherSuite, + srp_username = SrpUsername, + ecc = ECCCurve} = Session, + connection_states = #{current_write := CurrentWrite}, + connection_env = #connection_env{negotiated_version = {_,_} = Version}, + ssl_options = #{protocol := Protocol} = Opts}) -> + RecordCB = record_cb(Protocol), + CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + IsNamedCurveSuite = lists:member(KexAlg, + [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]), + CurveInfo = case ECCCurve of + {namedCurve, Curve} when IsNamedCurveSuite -> + [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; + _ -> + [] + end, + MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of + MaxFragmentLength when is_integer(MaxFragmentLength) -> + [{max_fragment_length, MaxFragmentLength}]; + _ -> + [] + end, + [{protocol, RecordCB:protocol_version(Version)}, + {session_id, SessionId}, + {session_data, term_to_binary(Session)}, + {session_resumption, Resumption}, + {selected_cipher_suite, CipherSuiteDef}, + {sni_hostname, SNIHostname}, + {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts). + +security_info(#state{connection_states = ConnectionStates, + static_env = #static_env{role = Role}, + ssl_options = #{keep_secrets := KeepSecrets}}) -> + ReadState = ssl_record:current_connection_state(ConnectionStates, read), + #{security_parameters := + #security_parameters{client_random = ClientRand, + server_random = ServerRand, + master_secret = MasterSecret, + application_traffic_secret = AppTrafSecretRead}} = ReadState, + BaseSecurityInfo = [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}], + if KeepSecrets =/= true -> + BaseSecurityInfo; + true -> + #{security_parameters := + #security_parameters{application_traffic_secret = AppTrafSecretWrite}} = + ssl_record:current_connection_state(ConnectionStates, write), + BaseSecurityInfo ++ + if Role == server -> + [{server_traffic_secret_0, AppTrafSecretWrite}, {client_traffic_secret_0, AppTrafSecretRead}]; + true -> + [{client_traffic_secret_0, AppTrafSecretWrite}, {server_traffic_secret_0, AppTrafSecretRead}] + end ++ + case ReadState of + #{client_handshake_traffic_secret := ClientHSTrafficSecret, + server_handshake_traffic_secret := ServerHSTrafficSecret} -> + [{client_handshake_traffic_secret, ClientHSTrafficSecret}, + {server_handshake_traffic_secret, ServerHSTrafficSecret}]; + _ -> + [] + end + end. + +record_cb(tls) -> + tls_record; +record_cb(dtls) -> + dtls_record. + +get_socket_opts(_, _,_,[], _, Acc) -> + {ok, Acc}; +get_socket_opts(Connection, Transport, Socket, [mode | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, + [{mode, SockOpts#socket_options.mode} | Acc]); +get_socket_opts(Connection, Transport, Socket, [packet | Tags], SockOpts, Acc) -> + case SockOpts#socket_options.packet of + {Type, headers} -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); + Type -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) + end; +get_socket_opts(Connection, Transport, Socket, [header | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, + [{header, SockOpts#socket_options.header} | Acc]); +get_socket_opts(Connection, Transport, Socket, [active | Tags], SockOpts, Acc) -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, + [{active, SockOpts#socket_options.active} | Acc]); +get_socket_opts(Connection, Transport, Socket, [Tag | Tags], SockOpts, Acc) -> + case Connection:getopts(Transport, Socket, [Tag]) of + {ok, [Opt]} -> + get_socket_opts(Connection, Transport, Socket, Tags, SockOpts, [Opt | Acc]); + {error, Reason} -> + {error, {options, {socket_options, Tag, Reason}}} + end; +get_socket_opts(_,_, _,Opts, _,_) -> + {error, {options, {socket_options, Opts, function_clause}}}. + +set_socket_opts(_,_,_, [], SockOpts, []) -> + {ok, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [], SockOpts, Other) -> + %% Set non emulated options + try ConnectionCb:setopts(Transport, Socket, Other) of + ok -> + {ok, SockOpts}; + {error, InetError} -> + {{error, {options, {socket_options, Other, InetError}}}, SockOpts} + catch + _:Error -> + %% So that inet behavior does not crash our process + {{error, {options, {socket_options, Other, Error}}}, SockOpts} + end; + +set_socket_opts(ConnectionCb, Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) + when Mode == list; Mode == binary -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{mode = Mode}, Other); +set_socket_opts(_, _, _, [{mode, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(ConnectionCb, Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) + when Packet == raw; + Packet == 0; + Packet == 1; + Packet == 2; + Packet == 4; + Packet == asn1; + Packet == cdr; + Packet == sunrm; + Packet == fcgi; + Packet == tpkt; + Packet == line; + Packet == http; + Packet == httph; + Packet == http_bin; + Packet == httph_bin -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{packet = Packet}, Other); +set_socket_opts(_, _, _, [{packet, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [{header, Header}| Opts], SockOpts, Other) + when is_integer(Header) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{header = Header}, Other); +set_socket_opts(_, _, _, [{header, _} = Opt| _], SockOpts, _) -> + {{error,{options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockOpts, Other) + when Active == once; + Active == true; + Active == false -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{active = Active}, Other); +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts], + SockOpts=#socket_options{active = Active0}, Other) + when Active1 >= -32768, Active1 =< 32767 -> + Active = if + is_integer(Active0), Active0 + Active1 < -32768 -> + error; + is_integer(Active0), Active0 + Active1 =< 0 -> + false; + is_integer(Active0), Active0 + Active1 > 32767 -> + error; + Active1 =< 0 -> + false; + is_integer(Active0) -> + Active0 + Active1; + true -> + Active1 + end, + case Active of + error -> + {{error, {options, {socket_options, Opt}} }, SockOpts}; + _ -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{active = Active}, Other) + end; +set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}} }, SockOpts}; +set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts, [Opt | Other]). +ssl_options_list(SslOptions) -> + L = maps:to_list(SslOptions), + ssl_options_list(L, []). + +new_emulated([], EmOpts) -> + EmOpts; +new_emulated(NewEmOpts, _) -> + NewEmOpts. + +ssl_options_list([], Acc) -> + lists:reverse(Acc); +%% Skip internal options, only return user options +ssl_options_list([{protocol, _}| T], Acc) -> + ssl_options_list(T, Acc); +ssl_options_list([{erl_dist, _}|T], Acc) -> + ssl_options_list(T, Acc); +ssl_options_list([{renegotiate_at, _}|T], Acc) -> + ssl_options_list(T, Acc); +ssl_options_list([{max_fragment_length, _}|T], Acc) -> + %% skip max_fragment_length from options since it is taken above from connection_states + ssl_options_list(T, Acc); +ssl_options_list([{ciphers = Key, Value}|T], Acc) -> + ssl_options_list(T, + [{Key, lists:map( + fun(Suite) -> + ssl_cipher_format:suite_bin_to_map(Suite) + end, Value)} + | Acc]); +ssl_options_list([{Key, Value}|T], Acc) -> + ssl_options_list(T, [{Key, Value} | Acc]). + +%% Maybe add NSS keylog info according to +%% https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format +maybe_add_keylog(Info) -> + maybe_add_keylog(lists:keyfind(protocol, 1, Info), Info). + +maybe_add_keylog({_, 'tlsv1.2'}, Info) -> + try + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + {master_secret, MasterSecretBin} = lists:keyfind(master_secret, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + MasterSecret = binary:decode_unsigned(MasterSecretBin), + Keylog = [io_lib:format("CLIENT_RANDOM ~64.16.0B ~96.16.0B", [ClientRandom, MasterSecret])], + Info ++ [{keylog,Keylog}] + catch + _Cxx:_Exx -> + Info + end; +maybe_add_keylog({_, 'tlsv1.3'}, Info) -> + try + {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), + {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info), + {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info), + {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info), + {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info), + {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info), + ClientRandom = binary:decode_unsigned(ClientRandomBin), + ClientTrafficSecret0 = keylog_secret(ClientTrafficSecret0Bin, Prf), + ServerTrafficSecret0 = keylog_secret(ServerTrafficSecret0Bin, Prf), + ClientHSecret = keylog_secret(ClientHSecretBin, Prf), + ServerHSecret = keylog_secret(ServerHSecretBin, Prf), + Keylog = [io_lib:format("CLIENT_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ClientHSecret, + io_lib:format("SERVER_HANDSHAKE_TRAFFIC_SECRET ~64.16.0B ", [ClientRandom]) ++ ServerHSecret, + io_lib:format("CLIENT_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ClientTrafficSecret0, + io_lib:format("SERVER_TRAFFIC_SECRET_0 ~64.16.0B ", [ClientRandom]) ++ ServerTrafficSecret0], + Info ++ [{keylog,Keylog}] + catch + _Cxx:_Exx -> + Info + end; +maybe_add_keylog(_, Info) -> + Info. + +keylog_secret(SecretBin, sha256) -> + io_lib:format("~64.16.0B", [binary:decode_unsigned(SecretBin)]); +keylog_secret(SecretBin, sha384) -> + io_lib:format("~96.16.0B", [binary:decode_unsigned(SecretBin)]); +keylog_secret(SecretBin, sha512) -> + io_lib:format("~128.16.0B", [binary:decode_unsigned(SecretBin)]). + +maybe_generate_client_shares(#{versions := [Version|_], + supported_groups := + #supported_groups{ + supported_groups = [Group|_]}}) + when Version =:= {3,4} -> + %% Generate only key_share entry for the most preferred group + ssl_cipher:generate_client_shares([Group]); +maybe_generate_client_shares(_) -> + undefined. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 4e2fdd1e8f..7d6c21438e 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -81,9 +81,10 @@ ]). -export([get_cert_params/1, + select_own_cert/1, server_name/3, validation_fun_and_state/4, - handle_path_validation_error/7]). + path_validation_alert/1]). %%==================================================================== %% Create handshake messages @@ -125,30 +126,33 @@ server_hello_done() -> #server_hello_done{}. %%-------------------------------------------------------------------- --spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. +-spec certificate([der_cert()] | undefined, db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. %% %% Description: Creates a certificate message. %%-------------------------------------------------------------------- -certificate(OwnCert, CertDbHandle, CertDbRef, client) -> +certificate(undefined, _, _, client) -> + %% If no suitable certificate is available, the client + %% SHOULD send a certificate message containing no + %% certificates. (chapter 7.4.6. RFC 4346) + #certificate{asn1_certificates = []}; +certificate([OwnCert], CertDbHandle, CertDbRef, client) -> Chain = case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, CertChain} -> CertChain; {error, _} -> - %% If no suitable certificate is available, the client - %% SHOULD send a certificate message containing no - %% certificates. (chapter 7.4.6. RFC 4346) - [] - end, + certificate(undefined, CertDbHandle, CertDbRef, client) + end, #certificate{asn1_certificates = Chain}; - -certificate(OwnCert, CertDbHandle, CertDbRef, server) -> +certificate([OwnCert], CertDbHandle, CertDbRef, server) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> #certificate{asn1_certificates = Chain}; {error, Error} -> ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error}) - end. + end; +certificate([_, _ |_] = Chain, _, _, _) -> + #certificate{asn1_certificates = Chain}. %%-------------------------------------------------------------------- -spec client_certificate_verify(undefined | der_cert(), binary(), @@ -162,7 +166,7 @@ client_certificate_verify(undefined, _, _, _, _, _) -> ignore; client_certificate_verify(_, _, _, _, undefined, _) -> ignore; -client_certificate_verify(OwnCert, MasterSecret, Version, +client_certificate_verify([OwnCert|_], MasterSecret, Version, {HashAlgo, SignAlgo}, PrivateKey, {Handshake, _}) -> case public_key:pkix_is_fixed_dh_cert(OwnCert) of @@ -344,46 +348,21 @@ next_protocol(SelectedProtocol) -> %%-------------------------------------------------------------------- certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, #{server_name_indication := ServerNameIndication, - partial_chain := PartialChain, - verify_fun := VerifyFun, - customize_hostname_check := CustomizeHostnameCheck, - crl_check := CrlCheck, - log_level := Level, - signature_algs := SignAlgos, - depth := Depth} = Opts, CRLDbHandle, Role, Host, Version, - #{cert_ext := CertExt, - ocsp_responder_certs := OcspResponderCerts, - ocsp_state := OcspState}) -> + partial_chain := PartialChain} = SSlOptions, + CRLDbHandle, Role, Host, Version, CertExt) -> ServerName = server_name(ServerNameIndication, Host, Role), - [PeerCert | ChainCerts ] = ASN1Certs, + [PeerCert | _ChainCerts ] = ASN1Certs, try - {TrustedCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, - PartialChain), - ValidationFunAndState = validation_fun_and_state(VerifyFun, #{role => Role, - certdb => CertDbHandle, - certdb_ref => CertDbRef, - server_name => ServerName, - customize_hostname_check => - CustomizeHostnameCheck, - signature_algs => SignAlgos, - signature_algs_cert => undefined, - version => Version, - crl_check => CrlCheck, - crl_db => CRLDbHandle, - cert_ext => CertExt, - issuer => TrustedCert, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState}, - CertPath, Level), - Options = [{max_path_length, Depth}, - {verify_fun, ValidationFunAndState}], - case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of + PathsAndAnchors = + ssl_certificate:trusted_cert_and_paths(ASN1Certs, CertDbHandle, CertDbRef, + PartialChain), + + case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SSlOptions, CertExt) of {ok, {PublicKeyInfo, _}} -> {PeerCert, PublicKeyInfo}; {error, Reason} -> - handle_path_validation_error(Reason, PeerCert, ChainCerts, Opts, Options, - CertDbHandle, CertDbRef) + path_validation_alert(Reason) end catch error:{_,{error, {asn1, Asn1Reason}}} -> @@ -706,6 +685,11 @@ encode_extensions([#signature_algorithms_cert{ Len = ListLen + 2, encode_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len), ?UINT16(ListLen), SignSchemeList/binary, Acc/binary>>); +encode_extensions([#sni{hostname = ""} | Rest], Acc) -> + HostnameBin = <<>>, + encode_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(0), + HostnameBin/binary, + Acc/binary>>); encode_extensions([#sni{hostname = Hostname} | Rest], Acc) -> HostLen = length(Hostname), HostnameBin = list_to_binary(Hostname), @@ -1640,6 +1624,11 @@ get_cert_params(Cert) -> end, {SignAlgo, Param, PublicKeyAlgo, RSAKeySize}. +select_own_cert([OwnCert| _]) -> + OwnCert; +select_own_cert(undefined) -> + undefined. + get_signature_scheme(undefined) -> undefined; get_signature_scheme(#signature_algorithms_cert{ @@ -1876,58 +1865,6 @@ maybe_check_hostname(OtpCert, valid_peer, SslState) -> maybe_check_hostname(_, valid, _) -> valid. -handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain, - Opts, Options, CertDbHandle, CertsDbRef) -> - handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason); -handle_path_validation_error({bad_cert, invalid_issuer} = Reason, PeerCert, Chain0, - Opts, Options, CertDbHandle, CertsDbRef) -> - handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason); -handle_path_validation_error(Reason, _, _, _, _,_, _) -> - path_validation_alert(Reason). - -handle_incomplete_chain(PeerCert, Chain0, - #{partial_chain := PartialChain} = Opts, Options, CertDbHandle, CertsDbRef, Reason) -> - case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, CertsDbRef) of - {ok, _, [PeerCert | _] = Chain} when Chain =/= Chain0 -> %% Chain candidate found - case ssl_certificate:trusted_cert_and_path(Chain, - CertDbHandle, CertsDbRef, - PartialChain) of - {unknown_ca, []} -> - path_validation_alert(Reason); - {Trusted, Path} -> - case public_key:pkix_path_validation(Trusted, Path, Options) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, PathError} -> - handle_unordered_chain(PeerCert, Chain0, Opts, Options, - CertDbHandle, CertsDbRef, PathError) - end - end; - _ -> - handle_unordered_chain(PeerCert, Chain0, Opts, Options, CertDbHandle, CertsDbRef, Reason) - end. - -handle_unordered_chain(PeerCert, Chain0, - #{partial_chain := PartialChain}, Options, CertDbHandle, CertsDbRef, Reason) -> - {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, Chain0}), - case ssl_certificate:certificate_chain(PeerCert, CertDbHandle, ExtractedCerts, Chain0) of - {ok, _, Chain} when Chain =/= Chain0 -> %% Chain appaears to be unordered - case ssl_certificate:trusted_cert_and_path(Chain, - CertDbHandle, CertsDbRef, - PartialChain) of - {unknown_ca, []} -> - path_validation_alert(Reason); - {Trusted, Path} -> - case public_key:pkix_path_validation(Trusted, Path, Options) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, PathError} -> - path_validation_alert(PathError) - end - end; - _ -> - path_validation_alert(Reason) - end. path_validation_alert({bad_cert, cert_expired}) -> ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); @@ -3594,3 +3531,51 @@ empty_extensions(_, server_hello) -> handle_log(Level, {LogLevel, ReportMap, Meta}) -> ssl_logger:log(Level, LogLevel, ReportMap, Meta). + + +path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, + CRLDbHandle, Version, SslOptions, CertExt); +path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + case path_validation(TrustedCert, Path, ServerName, + Role, CertDbHandle, CRLDbHandle, CertDbRef, + Version, SslOptions, CertExt) of + {ok, _} = Result -> + Result; + {error, _} -> + path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) + end. + +path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, + #{verify_fun := VerifyFun, + customize_hostname_check := CustomizeHostnameCheck, + crl_check := CrlCheck, + log_level := Level, + signature_algs := SignAlgos, + depth := Depth}, + #{cert_ext := CertExt, + ocsp_responder_certs := OcspResponderCerts, + ocsp_state := OcspState}) -> + ValidationFunAndState = + validation_fun_and_state(VerifyFun, #{role => Role, + certdb => CertDbHandle, + certdb_ref => CertDbRef, + server_name => ServerName, + customize_hostname_check => + CustomizeHostnameCheck, + signature_algs => SignAlgos, + signature_algs_cert => undefined, + version => Version, + crl_check => CrlCheck, + crl_db => CRLDbHandle, + cert_ext => CertExt, + issuer => TrustedCert, + ocsp_responder_certs => OcspResponderCerts, + ocsp_state => OcspState}, + Path, Level), + Options = [{max_path_length, Depth}, + {verify_fun, ValidationFunAndState}], + public_key:pkix_path_validation(TrustedCert, Path, Options). diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 6ce064fbed..790a115983 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -40,7 +40,7 @@ -record(session, { session_id, peer_certificate, - own_certificate, + own_certificates, compression_method, cipher_suite, master_secret, diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index b9758ae763..6477f5ab57 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -137,7 +137,7 @@ crl_cache => {{ssl_crl_cache, {internal, []}}, [versions]}, crl_check => {false, [versions]}, customize_hostname_check => {[], [versions]}, - depth => {1, [versions]}, + depth => {10, [versions]}, dh => {undefined, [versions]}, dhfile => {undefined, [versions]}, eccs => {undefined, [versions]}, @@ -175,6 +175,7 @@ reuse_session => {undefined, [versions]}, reuse_sessions => {true, [versions]}, secure_renegotiate => {true, [versions]}, + keep_secrets => {false, [versions]}, server_name_indication => {undefined, [versions]}, session_tickets => {disabled, [versions]}, signature_algs => {undefined, [versions]}, diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl index f7e97bcb76..6afd1c0009 100644 --- a/lib/ssl/src/ssl_listen_tracker_sup.erl +++ b/lib/ssl/src/ssl_listen_tracker_sup.erl @@ -69,4 +69,4 @@ init(_O) -> tracker_name(normal) -> ?MODULE; tracker_name(dist) -> - list_to_atom(atom_to_list(?MODULE) ++ "dist"). + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 5cc8dfaf79..4f6f12163f 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -515,14 +515,14 @@ exists_equivalent(_, []) -> false; exists_equivalent(#session{ peer_certificate = PeerCert, - own_certificate = OwnCert, + own_certificates = [OwnCert | _], compression_method = Compress, cipher_suite = CipherSuite, srp_username = SRP, ecc = ECC} , [#session{ peer_certificate = PeerCert, - own_certificate = OwnCert, + own_certificates = [OwnCert | _], compression_method = Compress, cipher_suite = CipherSuite, srp_username = SRP, diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index c19c6eeea9..47a9f11829 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -88,7 +88,8 @@ pending_connection_state(ConnectionStates, write) -> maps:get(pending_write, ConnectionStates). %%-------------------------------------------------------------------- --spec activate_pending_connection_state(connection_states(), read | write, tls_connection | dtls_connection) -> +-spec activate_pending_connection_state(connection_states(), read | write, + tls_gen_connection | dtls_gen_connection) -> connection_states(). %% %% Description: Creates a new instance of the connection_states record diff --git a/lib/ssl/src/ssl_server_session_cache.erl b/lib/ssl/src/ssl_server_session_cache.erl index 022255258e..44862e5cad 100644 --- a/lib/ssl/src/ssl_server_session_cache.erl +++ b/lib/ssl/src/ssl_server_session_cache.erl @@ -66,6 +66,8 @@ {error, Error :: {already_started, pid()}} | {error, Error :: term()} | ignore. +start_link(ssl_unknown_listener = Listner, Map) -> + gen_server:start_link({local, Listner}, ?MODULE, [Listner, Map], []); start_link(Listner, Map) -> gen_server:start_link(?MODULE, [Listner, Map], []). @@ -113,7 +115,7 @@ init([Listner, #{lifetime := Lifetime, max := Max }]) -> process_flag(trap_exit, true), - erlang:monitor(process, Listner), + Monitor = monitor_listener(Listner), DbRef = init(Cb, [{role, server} | InitArgs]), State = #state{store_cb = Cb, lifetime = Lifetime, @@ -121,7 +123,7 @@ init([Listner, #{lifetime := Lifetime, max = Max, session_index = #{}, id_generator = crypto:strong_rand_bytes(16), - listner = Listner + listner = Monitor }, {ok, State}. @@ -196,7 +198,7 @@ handle_cast({register_session, #session{session_id = SessionId} = Session}, -spec handle_info(Info :: timeout() | term(), State :: term()) -> {noreply, NewState :: term()}. -handle_info({'DOWN', _, process, Listner, _}, #state{listner = Listner} = State) -> +handle_info({'DOWN', Monitor, _, _, _}, #state{listner = Monitor} = State) -> {stop, normal, State}; handle_info(_, State) -> {noreply, State}. @@ -270,3 +272,10 @@ size(Cb,Cache) -> error:undef -> Cb:foldl(fun(_, Acc) -> Acc + 1 end, 0, Cache) end. + +monitor_listener(ssl_unknown_listener) -> + %% Backwards compatible Erlang node + %% global process. + undefined; +monitor_listener(Listen) when is_port(Listen) -> + erlang:monitor(port, Listen). diff --git a/lib/ssl/src/ssl_server_session_cache_db.erl b/lib/ssl/src/ssl_server_session_cache_db.erl index 090b139271..cab018c5c2 100644 --- a/lib/ssl/src/ssl_server_session_cache_db.erl +++ b/lib/ssl/src/ssl_server_session_cache_db.erl @@ -68,7 +68,7 @@ update(Cache, Key, Session) -> %% Will only be called from the ssl_server_cache process. %%-------------------------------------------------------------------- delete(Cache, Key) -> - gb_trees:delete(Cache, Key). + gb_trees:delete(Key, Cache). %%-------------------------------------------------------------- %% Description: Returns the cache size diff --git a/lib/ssl/src/ssl_server_session_cache_sup.erl b/lib/ssl/src/ssl_server_session_cache_sup.erl index ef8a24f19b..2f0c3dc823 100644 --- a/lib/ssl/src/ssl_server_session_cache_sup.erl +++ b/lib/ssl/src/ssl_server_session_cache_sup.erl @@ -29,30 +29,21 @@ -include("ssl_internal.hrl"). %% API --export([start_link/0, - start_link_dist/0]). --export([start_child/1, - start_child_dist/1, - session_opts/0]). +-export([start_link/0]). +-export([start_child/1]). %% Supervisor callback -export([init/1]). --define(DEFAULT_MAX_SESSION_CACHE, 1000). %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []). + supervisor:start_link({local, ?MODULE}, ?MODULE, []). -start_link_dist() -> - supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []). +start_child(Listner) -> + supervisor:start_child(?MODULE, [Listner | ssl_config:pre_1_3_session_opts()]). -start_child(Args) -> - supervisor:start_child(tracker_name(normal), [self() | Args]). - -start_child_dist(Args) -> - supervisor:start_child(tracker_name(dist), [self() | Args]). %%%========================================================================= %%% Supervisor callback @@ -72,45 +63,3 @@ init(_O) -> ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. -tracker_name(normal) -> - ?MODULE; -tracker_name(dist) -> - list_to_atom(atom_to_list(?MODULE) ++ "dist"). - -session_opts() -> - CbOpts = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - InitArgs = session_cb_init_args(), - #{session_cb => Cb, - session_cb_init_args => InitArgs}; - _ -> - #{session_cb => ssl_server_session_cache_db, - session_cb_init_args => []} - end, - LifeTime = session_lifetime(), - Max = max_session_cache_size(), - [CbOpts#{lifetime => LifeTime, max => Max}]. - -session_cb_init_args() -> - case application:get_env(ssl, session_cb_init_args) of - {ok, Args} when is_list(Args) -> - Args; - _ -> - [] - end. - -session_lifetime() -> - case application:get_env(ssl, session_lifetime) of - {ok, Time} when is_integer(Time) -> - Time; - _ -> - ?'24H_in_sec' - end. - -max_session_cache_size() -> - case application:get_env(ssl, session_cache_server_max) of - {ok, Size} when is_integer(Size) -> - Size; - _ -> - ?DEFAULT_MAX_SESSION_CACHE - end. diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index 1b04f88d12..ccc5c9ded7 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -30,11 +30,22 @@ -include("ssl_api.hrl"). %% Internal application API --export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2]). +-export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2, legacy_session_id/0]). -type seconds() :: integer(). %%-------------------------------------------------------------------- +-spec legacy_session_id() -> 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() -> + crypto:strong_rand_bytes(32). + +%%-------------------------------------------------------------------- -spec is_new(ssl:session_id(), ssl:session_id()) -> boolean(). %% %% Description: Checks if the session id decided by the server is a @@ -63,7 +74,7 @@ client_select_session({_, _, #{versions := Versions, case Version of {3, N} when N >= 4 -> - NewSession#session{session_id = crypto:strong_rand_bytes(32)}; + NewSession#session{session_id = legacy_session_id()}; _ -> do_client_select_session(ClientInfo, Cache, CacheCb, NewSession) end. @@ -100,7 +111,15 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- - +do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession) when is_binary(SessionId) andalso + is_binary(SessionData) -> + try binary_to_term(SessionData, [safe]) of + Session -> + Session + catch + _:_ -> + NewSession#session{session_id = <<>>} + end; do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession) when is_binary(SessionId)-> case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of undefined -> @@ -109,8 +128,8 @@ do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, Cac Session end; do_client_select_session(ClientInfo, - Cache, CacheCb, #session{own_certificate = OwnCert} = NewSession) -> - case select_session(ClientInfo, Cache, CacheCb, OwnCert) of + Cache, CacheCb, #session{own_certificates = OwnCerts} = NewSession) -> + case select_session(ClientInfo, Cache, CacheCb, OwnCerts) of no_session -> NewSession#session{session_id = <<>>}; Session -> @@ -120,18 +139,18 @@ do_client_select_session(ClientInfo, select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true -> %% If reuse_sessions == false | save a new session should be created no_session; -select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) -> +select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCerts) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), - select_session(Sessions, SslOpts, OwnCert). + select_session(Sessions, SslOpts, OwnCerts). select_session([], _, _) -> no_session; -select_session(Sessions, #{ciphers := Ciphers}, OwnCert) -> +select_session(Sessions, #{ciphers := Ciphers}, OwnCerts) -> IsNotResumable = fun(Session) -> not (resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) - andalso (OwnCert == Session#session.own_certificate)) + andalso (OwnCerts == Session#session.own_certificates)) end, case lists:dropwhile(IsNotResumable, Sessions) of [] -> no_session; @@ -143,7 +162,7 @@ is_resumable(_, _, #{reuse_sessions := false}, _) -> is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCert) -> case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of #session{cipher_suite = CipherSuite, - own_certificate = SessionOwnCert, + own_certificates = [SessionOwnCert | _], compression_method = Compression, is_resumable = IsResumable, peer_certificate = PeerCert} = Session -> diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index a8a1c3ed95..b2b33b9af3 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.erl @@ -42,7 +42,7 @@ terminate(Cache) -> ets:delete(Cache). %%-------------------------------------------------------------------- -%% Description: Looks up a cach entry. Should be callable from any +%% Description: Looks up a cache entry. Should be callable from any %% process. %%-------------------------------------------------------------------- lookup(Cache, Key) -> @@ -64,7 +64,7 @@ update(Cache, Key, Session) -> ets:insert(Cache, {Key, Session}). %%-------------------------------------------------------------------- -%% Description: Delets a cache entry. +%% Description: Deletes a cache entry. %% Will only be called from the ssl_manager process. %%-------------------------------------------------------------------- delete(Cache, Key) -> diff --git a/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl new file mode 100644 index 0000000000..936ffcc0ac --- /dev/null +++ b/lib/ssl/src/ssl_upgrade_server_session_cache_sup.erl @@ -0,0 +1,90 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor for a listen options tracker +%%---------------------------------------------------------------------- +-module(ssl_upgrade_server_session_cache_sup). + +-behaviour(supervisor). + +-include("ssl_internal.hrl"). + +%% API +-export([start_link/0, + start_link_dist/0]). +-export([start_child/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, sup_name(normal)}, ?MODULE, []). + +start_link_dist() -> + supervisor:start_link({local, sup_name(dist)}, ?MODULE, []). + +start_child(Type) -> + SupName = sup_name(Type), + Children = supervisor:count_children(SupName), + Workers = proplists:get_value(workers, Children), + case Workers of + 0 -> + %% In case two upgrade servers are started very close to each other + %% only one will be able to grab the local name and we will use + %% that process for handling pre TLS-1.3 sessions for + %% servers with to us unknown listeners. + case supervisor:start_child(SupName, [ssl_unknown_listener | ssl_config:pre_1_3_session_opts()]) of + {error, {already_started, Child}} -> + {ok, Child}; + {ok, _} = Return -> + Return + end; + 1 -> + [{_,Child,_, _}] = supervisor:which_children(SupName), + {ok, Child} + end. + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 3, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {ssl_server_session_cache, start_link, []}, + Restart = transient, % Should be restarted only on abnormal termination + Shutdown = 4000, + Modules = [ssl_server_session_cache], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + +sup_name(normal) -> + ?MODULE; +sup_name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index c13e4fd022..e96e0e2cf6 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -19,399 +19,144 @@ %% %% %%---------------------------------------------------------------------- -%% Purpose: Handles an ssl connection, e.i. both the setup -%% e.i. SSL-Handshake, SSL-Alert and SSL-Cipher protocols and delivering -%% data to the application. All data on the connectinon is received and -%% sent according to the SSL-record protocol. +%% Purpose: TLS-1.0-TLS-1.2 FSM (* = optional) +%% %%---------------------------------------------------------------------- +%% TLS Handshake protocol full Handshake +%% Client Server +%% +%% ClientHello --------> Flight 1 +%% ServerHello \ +%% Certificate* \ +%% ServerKeyExchange* Flight 2 +%% CertificateRequest* / +%% <-------- ServerHelloDone / +%% Certificate* \ +%% ClientKeyExchange \ +%% CertificateVerify* Flight 3 part 1 +%% [ChangeCipherSpec] / +%% Finished --------> / Flight 3 part 2 +%% [ChangeCipherSpec] +%% <-------- Finished Flight 4 +%% Application Data <-------> Application Data +%% +%% +%% TLS Handshake protocol abbreviated Handshake +%% Client Server +%% +%% ClientHello --------> Abbrev Flight 1 +%% ServerHello Abbrev Flight 2 part 1 +%% [ChangeCipherSpec] +%% <-------- Finished Abbrev Flight 2 part 2 +%% [ChangeCipherSpec] +%% Finished --------> Abbrev Flight 3 +%% Application Data <-------> Application Data +%% +%% +%% +%% Start FSM ---> CONFIG_ERROR +%% Send error to user +%% | and shutdown +%% | +%% V +%% INITIAL_HELLO +%% +%% | Send/Recv Flight 1 +%% | +%% | +%% USER_HELLO | +%% <- Possibly let user provide V +%% options after looking at hello ex -> HELLO +%% | Send/Recv Flight 2 or Abbrev Flight 1 - Abbrev Flight 2 part 1 +%% | +%% New session | Resumed session +%% WAIT_OCSP_STAPELING CERTIFY <----------------------------------> ABBRIVIATED +%% +%% <- Possibly Receive -- | | +%% OCSP Stapel ------> | Flight 3 part 1 | +%% | | +%% V | Abbrev Flight 2 part 2 to Abbrev Flight 3 +%% CIPHER | +%% | | +%% | Fligth 3 part 2 to Flight 4 | +%% | | +%% V V +%% ---------------------------------------------------- +%% | +%% | +%% V +%% CONNECTION +%% | +%% | Renegotiaton +%% V +%% GO BACK TO HELLO %%---------------------------------------------------------------------- -module(tls_connection). -behaviour(gen_statem). +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + -include("tls_connection.hrl"). -include("tls_handshake.hrl"). --include("tls_handshake_1_3.hrl"). -include("ssl_alert.hrl"). -include("tls_record.hrl"). -include("ssl_cipher.hrl"). -include("ssl_api.hrl"). -include("ssl_internal.hrl"). --include("ssl_srp.hrl"). --include_lib("public_key/include/public_key.hrl"). --include_lib("kernel/include/logger.hrl"). %% Internal application API %% Setup --export([start_fsm/8, start_link/8, init/1, pids/1]). - -%% State transition handling --export([next_event/3, next_event/4, - handle_protocol_record/3]). +-export([init/1]). -%% Handshake handling --export([renegotiation/2, renegotiate/2, send_handshake/2, - send_handshake_flight/1, - queue_handshake/2, queue_change_cipher/2, - reinit/1, reinit_handshake_data/1, select_sni_extension/1, - empty_connection_state/2]). - -%% Alert and close handling --export([send_alert/2, send_alert_in_connection/2, - send_sync_alert/2, - close/5, protocol_name/0]). - -%% Data handling --export([socket/4, setopts/3, getopts/3]). +-export([renegotiate/2]). %% gen_statem state functions --export([init/3, error/3, downgrade/3, %% Initiation and take down states - hello/3, user_hello/3, wait_ocsp_stapling/3, certify/3, cipher/3, abbreviated/3, %% Handshake states +-export([initial_hello/3, + config_error/3, + downgrade/3, + hello/3, + user_hello/3, + wait_ocsp_stapling/3, + certify/3, + cipher/3, + abbreviated/3, connection/3]). -%% TLS 1.3 state functions (server) --export([start/3, %% common state with client - negotiated/3, - recvd_ch/3, - wait_cert/3, %% common state with client - wait_cv/3, %% common state with client - wait_eoed/3, - wait_finished/3, %% common state with client - wait_flight2/3, - connected/3 %% common state with client - ]). -%% TLS 1.3 state functions (client) --export([wait_cert_cr/3, - wait_ee/3, - wait_sh/3 - ]). + %% gen_statem callbacks --export([callback_mode/0, terminate/3, code_change/4, format_status/2]). +-export([callback_mode/0, + terminate/3, + code_change/4, + format_status/2]). --export([encode_handshake/4, send_key_update/2, update_cipher_key/2]). - --define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). - %%==================================================================== %% Internal application API %%==================================================================== -%%==================================================================== -%% Setup -%%==================================================================== -start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts, - User, {CbModule, _,_, _, _} = CbInfo, - Timeout) -> - try - {ok, Sender} = tls_sender:start(), - {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), - ssl_connection:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end; - -start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts, - User, {CbModule, _,_, _, _} = CbInfo, - Timeout) -> - try - {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]), - {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), - ssl_connection:handshake(SslSocket, Timeout) - catch - error:{badmatch, {error, _} = Error} -> - Error - end. - -%%-------------------------------------------------------------------- --spec start_link(atom(), pid(), ssl:host(), inet:port_number(), port(), list(), pid(), tuple()) -> - {ok, pid()} | ignore | {error, reason()}. -%% -%% Description: Creates a gen_statem process which calls Module:init/1 to -%% initialize. -%%-------------------------------------------------------------------- -start_link(Role, Sender, Host, Port, Socket, Options, User, CbInfo) -> - {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Sender, Host, Port, Socket, Options, User, CbInfo]])}. - -init([Role, Sender, Host, Port, Socket, {#{erl_dist := ErlDist}, _, _} = Options, User, CbInfo]) -> - process_flag(trap_exit, true), - link(Sender), - case ErlDist of - true -> - process_flag(priority, max); - _ -> - ok - end, +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_connection:ssl_config(State0#state.ssl_options, Role, State0), - initialize_tls_sender(State), - gen_statem:enter_loop(?MODULE, [], init, State) + State1 = #state{static_env = #static_env{session_cache = Cache, + session_cache_cb = CacheCb + }, + ssl_options = SslOptions, + session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0), + State = case Role of + client -> + Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0), + State1#state{session = Session}; + server -> + State1 + end, + 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, [], error, EState) - end. - -pids(#state{protocol_specific = #{sender := Sender}}) -> - [self(), Sender]. - -%%==================================================================== -%% State transition handling -%%==================================================================== -next_record(_, #state{handshake_env = - #handshake_env{unprocessed_handshake_events = N} = HsEnv} - = State) when N > 0 -> - {no_record, State#state{handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; -next_record(_, #state{protocol_buffers = - #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, - connection_states = ConnectionStates, - ssl_options = #{padding_check := Check}} = State) -> - next_record(State, CipherTexts, ConnectionStates, Check); -next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, - protocol_specific = #{active_n_toggle := true} - } = State) -> - %% If ssl application user is not reading data wait to activate socket - flow_ctrl(State); - -next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, - protocol_specific = #{active_n_toggle := true} - } = State) -> - activate_socket(State); -next_record(_, 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 -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = undefined} = State) when Size =/= 0 -> - %% Passive mode wait for new recv request or socket activation - %% that is preserv some tcp back pressure by waiting to activate - %% socket - {no_record, State}; -%%%%%%%%%% A packet mode is set and socket is passive %%%%%%%%%% -flow_ctrl(#state{socket_options = #socket_options{active = false, - packet = Packet}} = State) - when ((Packet =/= 0) andalso (Packet =/= raw)) -> - %% We need more data to complete the packet. - activate_socket(State); -%%%%%%%%% No packet mode set and socket is passive %%%%%%%%%%%% -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = 0} = State) when Size == 0 -> - %% Passive mode no available bytes, get some - activate_socket(State); -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = 0} = State) when Size =/= 0 -> - %% There is data in the buffer to deliver - {no_record, State}; -flow_ctrl(#state{user_data_buffer = {_,Size,_}, - socket_options = #socket_options{active = false}, - bytes_to_read = BytesToRead} = State) when (BytesToRead > 0) -> - case (Size >= BytesToRead) of - true -> %% There is enough data bufferd - {no_record, State}; - false -> %% We need more data to complete the delivery of <BytesToRead> size - activate_socket(State) - end; -%%%%%%%%%%% Active mode or more data needed %%%%%%%%%% -flow_ctrl(State) -> - activate_socket(State). - - -activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, - static_env = #static_env{socket = Socket, - close_tag = CloseTag, - transport_cb = Transport} - } = State) -> - case tls_socket:setopts(Transport, Socket, [{active, N}]) of - ok -> - {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; - _ -> - self() ! {CloseTag, Socket}, - {no_record, State} + gen_statem:enter_loop(?MODULE, [], config_error, EState) end. -%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one -%% -next_record(State, CipherTexts, ConnectionStates, Check) -> - next_record(State, CipherTexts, ConnectionStates, Check, []). -%% -next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, Acc) -> - case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> - case CipherTexts of - [] -> - %% End of cipher texts - build and deliver an ?APPLICATION_DATA record - %% from the accumulated fragments - next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); - [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) - end; - {Record, ConnectionStates} when Acc =:= [] -> - %% Singelton non-?APPLICATION_DATA record - deliver - next_record_done(State, CipherTexts, ConnectionStates, Record); - {_Record, _ConnectionStates_to_forget} -> - %% Not ?APPLICATION_DATA but we have accumulated fragments - %% -> build an ?APPLICATION_DATA record with concatenated fragments - %% and forget about decrypting this record - we'll decrypt it again next time - %% Will not work for stream ciphers - next_record_done(State, [CT|CipherTexts], ConnectionStates0, - #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}); - #alert{} = Alert -> - Alert - end; -next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) -> - case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> - case CipherTexts of - [] -> - %% End of cipher texts - build and deliver an ?APPLICATION_DATA record - %% from the accumulated fragments - next_record_done(State, [], ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); - [_|_] -> - next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) - end; - #alert{} = Alert -> - Alert - end; -next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) -> - next_record_done(State, CipherTexts, ConnectionStates, - #ssl_tls{type = ?APPLICATION_DATA, - fragment = iolist_to_binary(lists:reverse(Acc))}); -next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, - [CT|CipherTexts], ConnectionStates0, Check, []) -> - case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of - {Record, ConnectionStates} -> - %% Singelton non-?APPLICATION_DATA record - deliver - next_record_done(State, CipherTexts, ConnectionStates, Record); - #alert{} = Alert -> - Alert - end. - -next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) -> - {Record, - State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, - connection_states = ConnectionStates}}. - -next_event(StateName, Record, State) -> - next_event(StateName, Record, State, []). -%% -next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) -> - case next_record(StateName, State0) of - {no_record, State} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {Record, State} -> - next_event(StateName, Record, State, Actions); - #alert{} = Alert -> - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), - {stop, {shutdown, own_alert}, State0} - end; -next_event(StateName, #ssl_tls{} = Record, State, Actions) -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; -next_event(StateName, #alert{} = Alert, State, Actions) -> - {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}. - -%%% TLS record protocol level application data messages -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, - #state{start_or_recv_from = From, - socket_options = #socket_options{active = false}} = State0) when From =/= undefined -> - case ssl_connection:read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, #state{start_or_recv_from = Caller} = State} -> - TimerAction = case Caller of - undefined -> %% Passive recv complete cancel timer - [{{timeout, recv}, infinity, timeout}]; - _ -> - [] - end, - next_event(StateName, Record, State, TimerAction) - end; -handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> - case ssl_connection:read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, State} -> - next_event(StateName, Record, State) - end; -%%% TLS record protocol level handshake messages -handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, - StateName, #state{protocol_buffers = - #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, - connection_env = #connection_env{negotiated_version = Version}, - static_env = #static_env{role = Role}, - ssl_options = Options} = State0) -> - try - %% Calculate the effective version that should be used when decoding an incoming handshake - %% message. - EffectiveVersion = effective_version(Version, Options, Role), - {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), - State = - State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, - case Packets of - [] -> - assert_buffer_sanity(Buf, Options), - next_event(StateName, no_record, State); - _ -> - Events = tls_handshake_events(Packets), - case StateName of - connection -> - ssl_connection:hibernate_after(StateName, State, Events); - _ -> - HsEnv = State#state.handshake_env, - {next_state, StateName, - State#state{handshake_env = - HsEnv#handshake_env{unprocessed_handshake_events - = unprocessed_events(Events)}}, Events} - end - end - catch throw:#alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State0) - end; -%%% TLS record protocol level change cipher messages -handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> - {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; -%%% TLS record protocol level Alert messages -handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try decode_alerts(EncAlerts) of - Alerts = [_|_] -> - handle_alerts(Alerts, {next_state, StateName, State}); - [] -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), - Version, StateName, State); - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State) - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), - Version, StateName, State) - - end; -%% Ignore unknown TLS record level protocol messages -handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> - {next_state, StateName, State, []}. -%%==================================================================== -%% Handshake handling -%%==================================================================== -renegotiation(Pid, WriteState) -> - gen_statem:call(Pid, {user_renegotiate, WriteState}). - renegotiate(#state{static_env = #static_env{role = client}, handshake_env = HsEnv} = State, Actions) -> %% Handle same way as if server requested @@ -434,249 +179,26 @@ renegotiate(#state{static_env = #static_env{role = server, State = State0#state{connection_states = ConnectionStates, handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, - next_event(hello, no_record, State, Actions). - -send_handshake(Handshake, State) -> - send_handshake_flight(queue_handshake(Handshake, State)). - -queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = Flight0, - ssl_options = #{log_level := LogLevel}, - connection_states = ConnectionStates0} = State0) -> - {BinHandshake, ConnectionStates, Hist} = - encode_handshake(Handshake, Version, ConnectionStates0, Hist0), - ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake), - ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake), - - State0#state{connection_states = ConnectionStates, - handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, - flight_buffer = Flight0 ++ [BinHandshake]}. - --spec send_handshake_flight(StateIn) -> {StateOut, FlightBuffer} when - StateIn :: #state{}, - StateOut :: #state{}, - FlightBuffer :: list(). -send_handshake_flight(#state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - flight_buffer = Flight} = State0) -> - tls_socket:send(Transport, Socket, Flight), - {State0#state{flight_buffer = []}, []}. - - -queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version}, - flight_buffer = Flight0, - ssl_options = #{log_level := LogLevel}, - connection_states = ConnectionStates0} = State0) -> - {BinChangeCipher, ConnectionStates} = - encode_change_cipher(Msg, Version, ConnectionStates0), - ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), - State0#state{connection_states = ConnectionStates, - flight_buffer = Flight0 ++ [BinChangeCipher]}. - -reinit(#state{protocol_specific = #{sender := Sender}, - connection_env = #connection_env{negotiated_version = Version}, - connection_states = #{current_write := Write}} = State) -> - tls_sender:update_connection_state(Sender, Write, Version), - reinit_handshake_data(State). - -reinit_handshake_data(#state{handshake_env = HsEnv} =State) -> - %% premaster_secret, public_key_info and tls_handshake_info - %% are only needed during the handshake phase. - %% To reduce memory foot print of a connection reinitialize them. - State#state{ - handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), - public_key_info = undefined, - premaster_secret = undefined} - }. - -select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> - SNI; -select_sni_extension(_) -> - undefined. - -empty_connection_state(ConnectionEnd, BeastMitigation) -> - ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation). - -%%==================================================================== -%% Alert and close handling -%%==================================================================== - -%%-------------------------------------------------------------------- --spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> - {iolist(), ssl_record:connection_states()}. -%% -%% Description: Encodes an alert -%%-------------------------------------------------------------------- -encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - tls_record:encode_alert_record(Alert, Version, ConnectionStates). - -send_alert(Alert, #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, - connection_env = #connection_env{negotiated_version = Version}, - ssl_options = #{log_level := LogLevel}, - connection_states = ConnectionStates0} = StateData0) -> - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - tls_socket:send(Transport, Socket, BinMsg), - ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - StateData0#state{connection_states = ConnectionStates}. - -%% If an ALERT sent in the connection state, should cause the TLS -%% connection to end, we need to synchronize with the tls_sender -%% process so that the ALERT if possible (that is the tls_sender process is -%% not blocked) is sent before the connection process terminates and -%% thereby closes the transport socket. -send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) -> - send_sync_alert(Alert, State); -send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> - send_sync_alert(Alert, State); -send_alert_in_connection(Alert, - #state{protocol_specific = #{sender := Sender}}) -> - tls_sender:send_alert(Sender, Alert). -send_sync_alert( - Alert, #state{protocol_specific = #{sender := Sender}} = State) -> - try tls_sender:send_and_ack_alert(Sender, Alert) - catch - _:_ -> - throw({stop, {shutdown, own_alert}, State}) - end. - -%% User closes or recursive call! -close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> - tls_socket:setopts(Transport, Socket, [{active, false}]), - Transport:shutdown(Socket, write), - _ = Transport:recv(Socket, 0, Timeout), - ok; -%% Peer closed socket -close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> - close({close, 0}, Socket, Transport, ConnectionStates, Check); -%% We generate fatal alert -close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> - %% Standard trick to try to make sure all - %% data sent to the tcp port is really delivered to the - %% peer application before tcp port is closed so that the peer will - %% get the correct TLS alert message and not only a transport close. - %% Will return when other side has closed or after timout millisec - %% e.g. we do not want to hang if something goes wrong - %% with the network but we want to maximise the odds that - %% peer application gets all data sent on the tcp connection. - close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); -close(downgrade, _,_,_,_) -> - ok; -%% Other -close(_, Socket, Transport, _,_) -> - tls_socket:close(Transport, Socket). -protocol_name() -> - "TLS". - -%%==================================================================== -%% Data handling -%%==================================================================== - -socket(Pids, Transport, Socket, Trackers) -> - tls_socket:socket(Pids, Transport, Socket, ?MODULE, Trackers). - -setopts(Transport, Socket, Other) -> - tls_socket:setopts(Transport, Socket, Other). - -getopts(Transport, Socket, Tag) -> - tls_socket:getopts(Transport, Socket, Tag). + tls_gen_connection:next_event(hello, no_record, State, Actions). %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec init(gen_statem:event_type(), - {start, timeout()} | term(), #state{}) -> - gen_statem:state_function_result(). +-spec initial_hello(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- - -init({call, From}, {start, Timeout}, - #state{static_env = #static_env{role = client, - host = Host, - port = Port, - transport_cb = Transport, - socket = Socket, - session_cache = Cache, - session_cache_cb = CacheCb}, - handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, - ocsp_stapling_state = OcspState0} = HsEnv, - connection_env = CEnv, - ssl_options = #{log_level := LogLevel, - %% Use highest version in initial ClientHello. - %% Versions is a descending list of supported versions. - versions := [HelloVersion|_] = Versions, - session_tickets := SessionTickets, - ocsp_stapling := OcspStaplingOpt, - ocsp_nonce := OcspNonceOpt} = SslOpts, - session = NewSession, - connection_states = ConnectionStates0 - } = State0) -> - KeyShare = maybe_generate_client_shares(SslOpts), - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession), - %% Update UseTicket in case of automatic session resumption - {UseTicket, State1} = tls_handshake_1_3:maybe_automatic_session_resumption(State0), - TicketData = tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket), - OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt), - Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, - Renegotiation, - Session#session.own_certificate, - KeyShare, - TicketData, - OcspNonce), - - Handshake0 = ssl_handshake:init_handshake_history(), - - %% Update pre_shared_key extension with binders (TLS 1.3) - Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion), - - MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined), - ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), - - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0), - - tls_socket:send(Transport, Socket, BinMsg), - ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1), - ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), - - %% RequestedVersion is used as the legacy record protocol version and shall be - %% {3,3} in case of TLS 1.2 and higher. In all other cases it defaults to the - %% lowest supported protocol version. - %% - %% negotiated_version is also used by the TLS 1.3 state machine and is set after - %% ServerHello is processed. - RequestedVersion = tls_record:hello_version(Versions), - State = State1#state{connection_states = ConnectionStates, - connection_env = CEnv#connection_env{ - negotiated_version = RequestedVersion}, - session = Session, - handshake_env = HsEnv#handshake_env{ - tls_handshake_history = Handshake, - ocsp_stapling_state = OcspState0#{ocsp_nonce => OcspNonce}}, - start_or_recv_from = From, - key_share = KeyShare}, - next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]); - -init(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). +initial_hello(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- --spec error(gen_statem:event_type(), +-spec config_error(gen_statem:event_type(), {start, timeout()} | term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -error({call, From}, {start, _Timeout}, - #state{protocol_specific = #{error := Error}} = State) -> - {stop_and_reply, {shutdown, normal}, - [{reply, From, {error, Error}}], State}; - -error({call, _} = Call, Msg, State) -> - gen_handshake(?FUNCTION_NAME, Call, Msg, State); -error(_, _, _) -> - {keep_state_and_data, [postpone]}. +config_error(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- -spec hello(gen_statem:event_type(), @@ -699,52 +221,22 @@ hello(internal, #server_hello{extensions = Extensions} = Hello, {next_state, user_hello, State#state{start_or_recv_from = undefined, handshake_env = HsEnv#handshake_env{ - hello = Hello}}, [{reply, From, {ok, Extensions}}]}; -hello(internal, #client_hello{client_version = ClientVersion} = Hello, - #state{connection_states = ConnectionStates0, - static_env = #static_env{ - trackers = Trackers}, - handshake_env = #handshake_env{kex_algorithm = KeyExAlg, - renegotiation = {Renegotiation, _}, - negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = CEnv, - session = #session{own_certificate = Cert} = Session0, - ssl_options = SslOpts} = State) -> - - case choose_tls_version(SslOpts, Hello) of - 'tls_v1.3' -> + hello = Hello}}, [{reply, From, {ok, Extensions}}]}; +hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{ssl_options = SslOpts0, + connection_env = CEnv} = State0) -> + case choose_tls_fsm(SslOpts0, Hello) of + tls_1_3_fsm -> %% Continue in TLS 1.3 'start' state - {next_state, start, State, [{next_event, internal, Hello}]}; - 'tls_v1.2' -> - SessionTracker = proplists:get_value(session_id_tracker, Trackers), - case tls_handshake:hello(Hello, - SslOpts, - {SessionTracker, Session0, - ConnectionStates0, Cert, KeyExAlg}, - Renegotiation) of - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, ClientVersion, hello, - State#state{connection_env = CEnv#connection_env{negotiated_version - = ClientVersion}}); - {Version, {Type, Session}, - ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> - Protocol = case Protocol0 of - undefined -> CurrentProtocol; - _ -> Protocol0 - end, - gen_handshake(?FUNCTION_NAME, - internal, - {common_client_hello, Type, ServerHelloExt}, - State#state{connection_states = ConnectionStates, - connection_env = CEnv#connection_env{negotiated_version = Version}, - handshake_env = HsEnv#handshake_env{ - hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, - negotiated_protocol = Protocol}, - session = Session - }) + {next_state, start, State0, [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]}; + tls_1_0_to_1_2_fsm -> + case handle_client_hello(Hello, State0) of + {ServerHelloExt, Type, State} -> + {next_state, hello, State, [{next_event, internal, {common_client_hello, Type, ServerHelloExt}}]}; + Alert -> + ssl_gen_statem:handle_own_alert(Alert, ClientVersion, hello, + State0#state{connection_env = CEnv#connection_env{negotiated_version + = ClientVersion}}) end - end; hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, @@ -757,13 +249,13 @@ hello(internal, #server_hello{} = Hello, ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, ReqVersion, hello, + ssl_gen_statem:handle_own_alert(Alert, ReqVersion, hello, State#state{connection_env = CEnv#connection_env{negotiated_version = ReqVersion} }); %% Legacy TLS 1.2 and older {Version, NewId, ConnectionStates, ProtoExt, Protocol, OcspState} -> - ssl_connection:handle_session(Hello, + tls_dtls_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State#state{ handshake_env = HsEnv#handshake_env{ @@ -774,15 +266,15 @@ hello(internal, #server_hello{} = Hello, {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}}, - [{next_event, internal, Hello}]} + [{change_callback_module, tls_connection_1_3}, {next_event, internal, Hello}]} end; hello(info, Event, State) -> - handle_info(Event, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Event, ?FUNCTION_NAME, State); hello(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). user_hello(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec abbreviated(gen_statem:event_type(), term(), #state{}) -> @@ -791,7 +283,7 @@ user_hello(Type, Event, State) -> abbreviated(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); abbreviated(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec wait_ocsp_stapling(gen_statem:event_type(), term(), #state{}) -> @@ -800,7 +292,7 @@ abbreviated(Type, Event, State) -> wait_ocsp_stapling(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); wait_ocsp_stapling(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec certify(gen_statem:event_type(), term(), #state{}) -> @@ -809,7 +301,7 @@ wait_ocsp_stapling(Type, Event, State) -> certify(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); certify(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec cipher(gen_statem:event_type(), term(), #state{}) -> @@ -818,7 +310,7 @@ certify(Type, Event, State) -> cipher(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); cipher(Type, Event, State) -> - gen_handshake(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:gen_handshake(?FUNCTION_NAME, Type, Event, State). %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), @@ -831,42 +323,6 @@ connection({call, From}, {user_renegotiate, WriteState}, #state{connection_states = ConnectionStates} = State) -> {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, [{next_event,{call, From}, renegotiate}]}; -connection({call, From}, - {close, {Pid, _Timeout}}, - #state{connection_env = #connection_env{terminated = closed} = CEnv, - protocol_specific = PS} = State) -> - {next_state, downgrade, State#state{connection_env = - CEnv#connection_env{terminated = true, - downgrade = {Pid, From}}, - protocol_specific = PS#{active_n_toggle => true, - active_n => 1} - }, - [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; -connection({call, From}, - {close,{Pid, Timeout}}, - #state{connection_states = ConnectionStates, - protocol_specific = #{sender := Sender} = PS, - connection_env = CEnv - } = State0) -> - case tls_sender:downgrade(Sender, Timeout) of - {ok, Write} -> - %% User downgrades connection - %% When downgrading an TLS connection to a transport connection - %% we must recive the close alert from the peer before releasing the - %% transport socket. - State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - State0#state{connection_states = - ConnectionStates#{current_write => Write}}), - {next_state, downgrade, State#state{connection_env = - CEnv#connection_env{downgrade = {Pid, From}, - terminated = true}, - protocol_specific = PS#{active_n_toggle => true, - active_n => 1} - }, - [{timeout, Timeout, downgrade}]}; - {error, timeout} -> - {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} - end; connection(internal, #hello_request{}, #state{static_env = #static_env{role = client, host = Host, @@ -876,7 +332,7 @@ connection(internal, #hello_request{}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, peer}, ocsp_stapling_state = OcspState}, - session = #session{own_certificate = Cert} = Session0, + session = #session{own_certificates = OwnCerts} = Session0, ssl_options = SslOpts, protocol_specific = #{sender := Pid}, connection_states = ConnectionStates} = State0) -> @@ -885,11 +341,13 @@ connection(internal, #hello_request{}, Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Session#session.session_id, - Renegotiation, Cert, undefined, + Renegotiation, OwnCerts, undefined, undefined, maps:get(ocsp_nonce, OcspState, undefined)), - {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}, - session = Session}), - next_event(hello, no_record, State, Actions) + {State, Actions} = tls_gen_connection:send_handshake(Hello, + State0#state{connection_states = + ConnectionStates#{current_write => Write}, + session = Session}), + tls_gen_connection:next_event(hello, no_record, State, Actions) catch _:_ -> {stop, {shutdown, sender_blocked}, State0} @@ -901,15 +359,15 @@ connection(internal, #hello_request{}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}, ocsp_stapling_state = OcspState}, - session = #session{own_certificate = Cert}, + session = #session{own_certificates = OwnCerts}, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, - <<>>, Renegotiation, Cert, undefined, + <<>>, Renegotiation, OwnCerts, undefined, undefined, maps:get(ocsp_nonce, OcspState, undefined)), - {State, Actions} = send_handshake(Hello, State0), - next_event(hello, no_record, State, Actions); + {State, Actions} = tls_gen_connection:send_handshake(Hello, State0), + tls_gen_connection:next_event(hello, no_record, State, Actions); connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server}, handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv, @@ -923,36 +381,21 @@ connection(internal, #client_hello{} = Hello, %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), {ok, Write} = tls_sender:renegotiate(Sender), - next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write}, - handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}, - allow_renegotiate = false} - }, - [{next_event, internal, Hello}]); + tls_gen_connection:next_event(hello, no_record, + State#state{connection_states = CS#{current_write => Write}, + handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}, + allow_renegotiate = false} + }, + [{next_event, internal, Hello}]); connection(internal, #client_hello{}, #state{static_env = #static_env{role = server}, handshake_env = #handshake_env{allow_renegotiate = false}} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - send_alert_in_connection(Alert, State0), - State = reinit_handshake_data(State0), - next_event(?FUNCTION_NAME, no_record, State); - -connection(internal, #new_session_ticket{} = NewSessionTicket, State) -> - %% TLS 1.3 - handle_new_session_ticket(NewSessionTicket, State), - next_event(?FUNCTION_NAME, no_record, State); - -connection(internal, #key_update{} = KeyUpdate, State0) -> - %% TLS 1.3 - case handle_key_update(KeyUpdate, State0) of - {ok, State} -> - next_event(?FUNCTION_NAME, no_record, State); - {error, State, Alert} -> - ssl_connection:handle_own_alert(Alert, {3,4}, connection, State), - next_event(?FUNCTION_NAME, no_record, State) - end; - + tls_gen_connection:send_alert_in_connection(Alert, State0), + State = tls_gen_connection:reinit_handshake_data(State0), + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); connection(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %%-------------------------------------------------------------------- -spec downgrade(gen_statem:event_type(), term(), #state{}) -> @@ -974,120 +417,9 @@ downgrade(info, {CloseTag, Socket}, State) -> {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State}; downgrade(info, Info, State) -> - handle_info(Info, ?FUNCTION_NAME, State); + tls_gen_connection:handle_info(Info, ?FUNCTION_NAME, State); downgrade(Type, Event, State) -> - ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). - -%%-------------------------------------------------------------------- -%% TLS 1.3 state functions -%%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- --spec start(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -start(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -start(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec negotiated(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -negotiated(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -negotiated(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec recvd_ch(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -recvd_ch(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -recvd_ch(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_cert(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_cert(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_cv(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cv(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_cv(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_eoed(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_eoed(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_eoed(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_finished(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_finished(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_finished(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_flight2(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_flight2(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_flight2(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec connected(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -connected(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -connected(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_cert_cr(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_cert_cr(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_cert_cr(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_ee(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_ee(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_ee(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). - -%%-------------------------------------------------------------------- --spec wait_sh(gen_statem:event_type(), term(), #state{}) -> - gen_statem:state_function_result(). -%%-------------------------------------------------------------------- -wait_sh(info, Event, State) -> - gen_info_1_3(Event, ?FUNCTION_NAME, State); -wait_sh(Type, Event, State) -> - gen_handshake_1_3(?FUNCTION_NAME, Type, Event, State). + tls_dtls_connection:?FUNCTION_NAME(Type, Event, State). %-------------------------------------------------------------------- %% gen_statem callbacks @@ -1099,14 +431,14 @@ terminate({shutdown, {sender_died, Reason}}, _StateName, #state{static_env = #static_env{socket = Socket, transport_cb = Transport}} = State) -> - ssl_connection:handle_trusted_certs_db(State), - close(Reason, Socket, Transport, undefined, undefined); + ssl_gen_statem:handle_trusted_certs_db(State), + tls_gen_connection:close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, StateName, State) -> - catch ssl_connection:terminate(Reason, StateName, State), + catch ssl_gen_statem:terminate(Reason, StateName, State), ensure_sender_terminate(Reason, State). format_status(Type, Data) -> - ssl_connection:format_status(Type, Data). + ssl_gen_statem:format_status(Type, Data). code_change(_OldVsn, StateName, State, _) -> {ok, StateName, State}. @@ -1136,7 +468,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac InitStatEnv = #static_env{ role = Role, transport_cb = CbModule, - protocol_cb = ?MODULE, + protocol_cb = tls_gen_connection, data_tag = DataTag, close_tag = CloseTag, error_tag = ErrorTag, @@ -1169,288 +501,75 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac } }. -initialize_tls_sender(#state{static_env = #static_env{ - role = Role, - transport_cb = Transport, - socket = Socket, - trackers = Trackers - }, - connection_env = #connection_env{negotiated_version = Version}, - socket_options = SockOpts, - ssl_options = #{renegotiate_at := RenegotiateAt, - key_update_at := KeyUpdateAt, - log_level := LogLevel}, - connection_states = #{current_write := ConnectionWriteState}, - protocol_specific = #{sender := Sender}}) -> - Init = #{current_write => ConnectionWriteState, - role => Role, - socket => Socket, - socket_options => SockOpts, - trackers => Trackers, - transport_cb => Transport, - negotiated_version => Version, - renegotiate_at => RenegotiateAt, - key_update_at => KeyUpdateAt, - log_level => LogLevel}, - tls_sender:initialize(Sender, Init). - -next_tls_record(Data, StateName, - #state{protocol_buffers = - #protocol_buffers{tls_record_buffer = Buf0, - tls_cipher_texts = CT0} = Buffers, - ssl_options = SslOpts} = State0) -> - Versions = - %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all - %% record version are accepted when receiving initial ClientHello and - %% ServerHello. This can happen in state 'hello' in case of all TLS - %% versions and also in state 'start' when TLS 1.3 is negotiated. - %% After the version is negotiated all subsequent TLS records shall have - %% the proper legacy_record_version (= negotiated_version). - %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this - %% point it is the same as the negotiated protocol version. - %% TODO: Refactor state machine and introduce a record_protocol_version beside - %% the negotiated_version. - case StateName of - State when State =:= hello orelse - State =:= start -> - [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]; - _ -> - State0#state.connection_env#connection_env.negotiated_version - end, - #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states, - case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - next_record(StateName, State0#state{protocol_buffers = - Buffers#protocol_buffers{tls_record_buffer = Buf1, - tls_cipher_texts = CT1}}); - #alert{} = Alert -> - handle_record_alert(Alert, State0) - end. - - -handle_record_alert(Alert, _) -> - Alert. - -tls_handshake_events(Packets) -> - lists:map(fun(Packet) -> - {next_event, internal, {handshake, Packet}} - end, Packets). - -%% raw data from socket, upack records -handle_info({Protocol, _, Data}, StateName, - #state{static_env = #static_env{data_tag = Protocol}, - connection_env = #connection_env{negotiated_version = Version}} = State0) -> - case next_tls_record(Data, StateName, State0) of - {Record, State} -> - next_event(StateName, Record, State); - #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State0) - end; -handle_info({PassiveTag, Socket}, StateName, - #state{static_env = #static_env{socket = Socket, - passive_tag = PassiveTag}, - start_or_recv_from = From, - protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, - protocol_specific = PS - } = State0) -> - case (From =/= undefined) andalso (CTs == []) of - true -> - {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}), - next_event(StateName, Record, State); - false -> - next_event(StateName, no_record, - State0#state{protocol_specific = PS#{active_n_toggle => true}}) - end; -handle_info({CloseTag, Socket}, StateName, - #state{static_env = #static_env{ - role = Role, - host = Host, - port = Port, - socket = Socket, - close_tag = CloseTag}, - handshake_env = #handshake_env{renegotiation = Type}, - connection_env = #connection_env{negotiated_version = Version}, - session = Session} = State) when StateName =/= connection -> - ssl_connection:maybe_invalidate_session(Version, Type, Role, Host, Port, Session), - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown, transport_closed}, State}; -handle_info({CloseTag, Socket}, StateName, - #state{static_env = #static_env{ - role = Role, - socket = Socket, - close_tag = CloseTag}, - socket_options = #socket_options{active = Active}, - protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, - user_data_buffer = {_,BufferSize,_}, - protocol_specific = PS} = State) -> - - %% Note that as of TLS 1.1, - %% failure to properly close a connection no longer requires that a - %% session not be resumed. This is a change from TLS 1.0 to conform - %% with widespread implementation practice. - - case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of - false -> - %% As invalidate_sessions here causes performance issues, - %% we will conform to the widespread implementation - %% practice and go aginst the spec - %% case Version of - %% {3, N} when N >= 1 -> - %% ok; - %% _ -> - %% invalidate_session(Role, Host, Port, Session) - %% ok - %% end, - Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), - ssl_connection:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), - {stop, {shutdown, transport_closed}, State}; - true -> - %% Fixes non-delivery of final TLS record in {active, once}. - %% Basically allows the application the opportunity to set {active, once} again - %% and then receive the final message. Set internal active_n to zero - %% to ensure socket close message is sent if there is not enough data to deliver. - next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}) - end; -handle_info({'EXIT', Sender, Reason}, _, - #state{protocol_specific = #{sender := Sender}} = State) -> - {stop, {shutdown, {sender_died, Reason}}, State}; -handle_info(Msg, StateName, State) -> - ssl_connection:StateName(info, Msg, State, ?MODULE). - -handle_alerts([], Result) -> - Result; -handle_alerts(_, {stop, _, _} = Stop) -> - Stop; -handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], - {next_state, connection = StateName, #state{connection_env = CEnv, - socket_options = #socket_options{active = false}, - user_data_buffer = {_,BufferSize,_}, - protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} = - State}) when (BufferSize =/= 0) orelse - (CTs =/= []) -> - {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}}; -handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)); -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> - handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). - -encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> - Frag = tls_handshake:encode_handshake(Handshake, Version), - Hist = ssl_handshake:update_handshake_history(Hist0, Frag), - {Encoded, ConnectionStates} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), - {Encoded, ConnectionStates, Hist}. - -encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> - tls_record:encode_change_cipher_spec(Version, ConnectionStates). - -decode_alerts(Bin) -> - ssl_alert:decode(Bin). - -gen_handshake(StateName, Type, Event, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try ssl_connection:StateName(Type, Event, State, ?MODULE) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) - end. - - -gen_handshake_1_3(StateName, Type, Event, - #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try tls_connection_1_3:StateName(Type, Event, State, ?MODULE) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) +handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State0) -> + case tls_dtls_connection:handle_sni_extension(State0, Hello) of + #state{connection_states = ConnectionStates0, + static_env = #static_env{trackers = Trackers}, + handshake_env = #handshake_env{ + kex_algorithm = KeyExAlg, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol, + sni_guided_cert_selection = SNICertSelection} = HsEnv, + connection_env = CEnv, + session = #session{own_certificates = OwnCerts} = Session0, + ssl_options = SslOpts} = State -> + SessionTracker = proplists:get_value(session_id_tracker, Trackers), + case tls_handshake:hello(Hello, + SslOpts, + {SessionTracker, Session0, + ConnectionStates0, OwnCerts, KeyExAlg}, + Renegotiation) of + #alert{} = Alert -> + Alert; + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt0, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + ServerHelloExt = + case SNICertSelection of + true -> + ServerHelloExt0#{sni => #sni{hostname = ""}}; + false -> + ServerHelloExt0 + end, + {ServerHelloExt, Type, State#state{connection_states = ConnectionStates, + connection_env = CEnv#connection_env{negotiated_version = Version}, + handshake_env = HsEnv#handshake_env{ + hashsign_algorithm = HashSign, + client_hello_version = ClientVersion, + negotiated_protocol = Protocol}, + session = Session + }} + end; + #alert{} = Alert -> + Alert end. gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try tls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, malformed_data), Version, StateName, State) end; gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of + try tls_gen_connection:handle_info(Event, StateName, State) of Result -> Result catch _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data), Version, StateName, State) end. - -gen_info_1_3(Event, connected = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, - malformed_data), - Version, StateName, State) - end; - -gen_info_1_3(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> - try handle_info(Event, StateName, State) of - Result -> - Result - catch - _:_ -> - ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data), - Version, StateName, State) - end. - -unprocessed_events(Events) -> - %% The first handshake event will be processed immediately - %% as it is entered first in the event queue and - %% when it is processed there will be length(Events)-1 - %% handshake events left to process before we should - %% process more TLS-records received on the socket. - erlang:length(Events)-1. - - -assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, - #{max_handshake_size := Max}) when - Length =< Max -> - case size(Rest) of - N when N < Length -> - true; - N when N > Length -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - too_big_handshake_data)); - _ -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data)) - end; -assert_buffer_sanity(Bin, _) -> - case size(Bin) of - N when N < 3 -> - true; - _ -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, - malformed_handshake_data)) - end. - ensure_sender_terminate(downgrade, _) -> ok; %% Do not terminate sender during downgrade phase ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> @@ -1464,125 +583,17 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> end, spawn(Kill). -maybe_generate_client_shares(#{versions := [Version|_], - supported_groups := - #supported_groups{ - supported_groups = [Group|_]}}) - when Version =:= {3,4} -> - %% Generate only key_share entry for the most preferred group - ssl_cipher:generate_client_shares([Group]); -maybe_generate_client_shares(_) -> - undefined. - -choose_tls_version(#{versions := Versions}, - #client_hello{ - extensions = #{client_hello_versions := - #client_hello_versions{versions = ClientVersions} - } - }) -> +choose_tls_fsm(#{versions := Versions}, + #client_hello{ + extensions = #{client_hello_versions := + #client_hello_versions{versions = ClientVersions} + } + }) -> case ssl_handshake:select_supported_version(ClientVersions, Versions) of {3,4} -> - 'tls_v1.3'; + tls_1_3_fsm; _Else -> - 'tls_v1.2' + tls_1_0_to_1_2_fsm end; -choose_tls_version(_, _) -> - 'tls_v1.2'. - - -%% Special version handling for TLS 1.3 clients: -%% In the shared state 'init' negotiated_version is set to requested version and -%% that is expected by the legacy part of the state machine. However, in order to -%% be able to process new TLS 1.3 extensions, the effective version shall be set -%% {3,4}. -%% When highest supported version is {3,4} the negotiated version is set to {3,3}. -effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} -> - Version; -%% Use highest supported version during startup (TLS server, all versions). -effective_version(undefined, #{versions := [Version|_]}, _) -> - Version; -%% Use negotiated version in all other cases. -effective_version(Version, _, _) -> - Version. - - -handle_new_session_ticket(_, #state{ssl_options = #{session_tickets := disabled}}) -> - ok; -handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, - #state{connection_states = ConnectionStates, - ssl_options = #{session_tickets := SessionTickets, - server_name_indication := SNI}, - connection_env = #connection_env{user_application = {_, User}}}) - when SessionTickets =:= manual -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - HKDF = SecParams#security_parameters.prf_algorithm, - RMS = SecParams#security_parameters.resumption_master_secret, - PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), - send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK); -handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, - #state{connection_states = ConnectionStates, - ssl_options = #{session_tickets := SessionTickets, - server_name_indication := SNI}}) - when SessionTickets =:= auto -> - #{security_parameters := SecParams} = - ssl_record:current_connection_state(ConnectionStates, read), - HKDF = SecParams#security_parameters.prf_algorithm, - RMS = SecParams#security_parameters.resumption_master_secret, - PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), - tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK). - - -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. - - -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 - #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), - {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, Cipher, 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}. - - -send_key_update(Sender, Type) -> - KeyUpdate = tls_handshake_1_3:key_update(Type), - tls_sender:send_post_handshake(Sender, KeyUpdate). - - -%% Send ticket data to user as opaque binary -send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) -> - Timestamp = erlang:system_time(seconds), - TicketData = #{hkdf => HKDF, - sni => SNI, - psk => PSK, - timestamp => Timestamp, - ticket => NewSessionTicket}, - User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}. +choose_tls_fsm(_, _) -> + tls_1_0_to_1_2_fsm. diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 0b5ff98474..fc4b4f673f 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -20,9 +20,16 @@ %% %%---------------------------------------------------------------------- -%% Purpose: TODO +%% 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 %% @@ -104,27 +111,162 @@ -include("ssl_alert.hrl"). -include("ssl_connection.hrl"). +-include("tls_connection.hrl"). -include("tls_handshake.hrl"). -include("tls_handshake_1_3.hrl"). -%% gen_statem helper functions --export([start/4, - negotiated/4, - wait_cert/4, - wait_cv/4, - wait_finished/4, - wait_sh/4, - wait_ee/4, - wait_cert_cr/4 +-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, + negotiated/3, + wait_cert/3, + wait_cv/3, + wait_finished/3, + wait_sh/3, + wait_ee/3, + wait_cert_cr/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 + #{cipher := Cipher} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + {Key, IV} = tls_v1:calculate_traffic_keys(HKDF, Cipher, 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. + +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, 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(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(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). -start(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -start(internal, #client_hello{} = Hello, State0, _Module) -> + +user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} + = State) -> + gen_statem:reply(From, ok), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), + Version, ?FUNCTION_NAME, State); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{hello = Hello}, + ssl_options = Options0} = State0) -> + Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), + State = ssl_gen_statem:ssl_config(Options, Role, State0), + Next = case Role of + client -> + wait_sh; + server -> + start + end, + {next_state, Next, State#state{start_or_recv_from = From}, + [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; +user_hello(_, _, _) -> + {keep_state_and_data, [postpone]}. + +start(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +start(internal, #client_hello{extensions = Extensions} = Hello, + #state{ssl_options = #{handshake := hello}, + start_or_recv_from = From, + handshake_env = HsEnv} = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = Hello}}, [{reply, From, {ok, Extensions}}]}; +start(internal, #client_hello{} = Hello, State0) -> case tls_handshake_1_3:do_start(Hello, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0); {State, start} -> {next_state, start, State, []}; {State, negotiated} -> @@ -132,116 +274,274 @@ start(internal, #client_hello{} = Hello, State0, _Module) -> {State, negotiated, PSK} -> %% Session Resumption with PSK {next_state, negotiated, State, [{next_event, internal, {start_handshake, PSK}}]} end; -start(internal, #server_hello{} = ServerHello, State0, _Module) -> +start(internal, #server_hello{extensions = Extensions} = ServerHello, + #state{ssl_options = #{handshake := hello}, + handshake_env = HsEnv, + start_or_recv_from = From} + = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = ServerHello}}, [{reply, From, {ok, Extensions}}]}; +start(internal, #server_hello{} = ServerHello, State0) -> case tls_handshake_1_3:do_start(ServerHello, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, start, State0); {State, NextState} -> {next_state, NextState, State, []} end; -start(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - +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(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -negotiated(internal, Message, State0, _Module) -> +negotiated(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +negotiated(internal, Message, State0) -> case tls_handshake_1_3:do_negotiated(Message, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, negotiated, State0); {State, NextState} -> {next_state, NextState, State, []} - end. - + end; +negotiated(info, Msg, State) -> + tls_gen_connection:handle_info(Msg, ?FUNCTION_NAME, State). -wait_cert(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cert(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); wait_cert(internal, - #certificate_1_3{} = Certificate, State0, _Module) -> + #certificate_1_3{} = Certificate, State0) -> case tls_handshake_1_3:do_wait_cert(Certificate, State0) of {#alert{} = Alert, State} -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert, State); {State, NextState} -> - tls_connection:next_event(NextState, no_record, State) + tls_gen_connection:next_event(NextState, no_record, State) end; -wait_cert(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - +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(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cv(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); wait_cv(internal, - #certificate_verify_1_3{} = CertificateVerify, State0, _Module) -> + #certificate_verify_1_3{} = CertificateVerify, State0) -> case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of {#alert{} = Alert, State} -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cv, State); {State, NextState} -> - tls_connection:next_event(NextState, no_record, State) + tls_gen_connection:next_event(NextState, no_record, State) end; -wait_cv(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +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(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_finished(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); wait_finished(internal, - #finished{} = Finished, State0, Module) -> + #finished{} = Finished, State0) -> case tls_handshake_1_3:do_wait_finished(Finished, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, finished, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, finished, State0); State1 -> - {Record, State} = ssl_connection:prepare_connection(State1, Module), - tls_connection:next_event(connection, Record, State, + {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(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +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(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_sh(internal, #server_hello{} = Hello, State0, _Module) -> +wait_sh(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_sh(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #{handshake := hello}, + start_or_recv_from = From, + handshake_env = HsEnv} = State) -> + {next_state, user_hello, + State#state{start_or_recv_from = undefined, + handshake_env = HsEnv#handshake_env{ + hello = Hello}}, [{reply, From, {ok, Extensions}}]}; +wait_sh(internal, #server_hello{} = Hello, State0) -> case tls_handshake_1_3:do_wait_sh(Hello, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_sh, State0); {State1, start, ServerHello} -> %% hello_retry_request: go to start {next_state, start, State1, [{next_event, internal, ServerHello}]}; {State1, wait_ee} -> - tls_connection:next_event(wait_ee, no_record, State1) + tls_gen_connection:next_event(wait_ee, no_record, State1) end; -wait_sh(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +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). -wait_ee(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) -> +wait_ee(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_ee(internal, #encrypted_extensions{} = EE, State0) -> case tls_handshake_1_3:do_wait_ee(EE, State0) of #alert{} = Alert -> - ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_ee, State0); {State1, NextState} -> - tls_connection:next_event(NextState, no_record, State1) + tls_gen_connection:next_event(NextState, no_record, State1) end; -wait_ee(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +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(internal, #change_cipher_spec{}, State, _Module) -> - tls_connection:next_event(?FUNCTION_NAME, no_record, State); -wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) -> +wait_cert_cr(internal, #change_cipher_spec{}, State) -> + tls_gen_connection:next_event(?FUNCTION_NAME, no_record, 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_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State); {State1, NextState} -> - tls_connection:next_event(NextState, no_record, State1) + tls_gen_connection:next_event(NextState, no_record, State1) end; -wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) -> +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_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); + ssl_gen_statem:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); {State1, NextState} -> - tls_connection:next_event(NextState, no_record, State1) + tls_gen_connection:next_event(NextState, no_record, State1) end; -wait_cert_cr(Type, Msg, State, Connection) -> - ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). +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). + +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, {3,4}, 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(Type, Event, State) -> + tls_connection:?FUNCTION_NAME(Type, Event, State). + +%-------------------------------------------------------------------- +%% internal functions +%%-------------------------------------------------------------------- +initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trackers}, User, + {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) -> + #{erl_dist := IsErlDist, + client_renegotiation := ClientRenegotiation} = SSLOptions, + ConnectionStates = tls_record:init_connection_states(Role, disabled), + InternalActiveN = case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) andalso (not IsErlDist) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end, + 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()}, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_data_buffer = {[],0,[]}, + start_or_recv_from = undefined, + flight_buffer = [], + protocol_specific = #{sender => Sender, + active_n => InternalActiveN, + active_n_toggle => true + } + }. + +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), + HKDF = SecParams#security_parameters.prf_algorithm, + RMS = SecParams#security_parameters.resumption_master_secret, + PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), + send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK); +handle_new_session_ticket(#new_session_ticket{ticket_nonce = Nonce} = NewSessionTicket, + #state{connection_states = ConnectionStates, + ssl_options = #{session_tickets := SessionTickets, + server_name_indication := SNI}}) + when SessionTickets =:= auto -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates, read), + HKDF = SecParams#security_parameters.prf_algorithm, + RMS = SecParams#security_parameters.resumption_master_secret, + PSK = tls_v1:pre_shared_key(RMS, Nonce, HKDF), + tls_client_ticket_store:store_ticket(NewSessionTicket, HKDF, SNI, PSK). + + +%% Send ticket data to user as opaque binary +send_ticket_data(User, NewSessionTicket, HKDF, SNI, PSK) -> + Timestamp = erlang:system_time(seconds), + TicketData = #{hkdf => HKDF, + sni => SNI, + psk => PSK, + timestamp => Timestamp, + ticket => NewSessionTicket}, + User ! {ssl, session_ticket, {SNI, erlang:term_to_binary(TicketData)}}. + +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. diff --git a/lib/ssl/src/tls_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl index d5b228dc94..b7f80ad524 100644 --- a/lib/ssl/src/tls_connection_sup.erl +++ b/lib/ssl/src/tls_connection_sup.erl @@ -40,13 +40,13 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). start_link_dist() -> - supervisor:start_link({local, ssl_connection_sup_dist}, ?MODULE, []). + supervisor:start_link({local, tls_dist_connection_sup}, ?MODULE, []). start_child(Args) -> supervisor:start_child(?MODULE, Args). start_child_dist(Args) -> - supervisor:start_child(ssl_connection_sup_dist, Args). + supervisor:start_child(tls_dist_connection_sup, Args). %%%========================================================================= %%% Supervisor callback @@ -57,10 +57,10 @@ init(_O) -> MaxT = 3600, Name = undefined, % As simple_one_for_one is used. - StartFunc = {tls_connection, start_link, []}, + StartFunc = {ssl_gen_statem, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [tls_connection, ssl_connection], + Modules = [ssl_gen_statem, tls_connection, tls_connection_1_3], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/tls_dist_server_sup.erl b/lib/ssl/src/tls_dist_server_sup.erl new file mode 100644 index 0000000000..96603a7495 --- /dev/null +++ b/lib/ssl/src/tls_dist_server_sup.erl @@ -0,0 +1,89 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2021-2021. 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_dist_server_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + ListenTracker = listen_options_tracker_child_spec(), + SessionTracker = tls_server_session_child_spec(), + Pre_1_3SessionTracker = ssl_server_session_child_spec(), + + {ok, {{one_for_all, 10, 3600}, [ListenTracker, + SessionTracker, + Pre_1_3SessionTracker + ]}}. + + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +%% Handles emulated options so that they inherited by the accept +%% socket, even when setopts is performed on the listen socket +listen_options_tracker_child_spec() -> + Name = dist_tls_socket, + StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_listen_tracker_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +tls_server_session_child_spec() -> + Name = dist_tls_server_session_ticket, + StartFunc = {tls_server_session_ticket_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_server_session_ticket_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +ssl_server_session_child_spec() -> + Name = dist_ssl_server_session_cache_sup, + StartFunc = {ssl_upgrade_server_session_cache_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_server_session_cache_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + diff --git a/lib/ssl/src/tls_dist_sup.erl b/lib/ssl/src/tls_dist_sup.erl new file mode 100644 index 0000000000..54e0a6a514 --- /dev/null +++ b/lib/ssl/src/tls_dist_sup.erl @@ -0,0 +1,75 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2021-2021. 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_dist_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_link() -> {ok, pid()} | ignore | {error, term()}. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= + +init([]) -> + + TLSConnetionSup = tls_connection_child_spec(), + ServerInstanceSup = server_instance_child_spec(), + + {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup, + ServerInstanceSup + ]}}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +tls_connection_child_spec() -> + Name = dist_tls_connection, + StartFunc = {tls_connection_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_connection_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +server_instance_child_spec() -> + Name = dist_tls_server_sup, + StartFunc = {tls_dist_server_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_dist_server_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl new file mode 100644 index 0000000000..c27feadfcf --- /dev/null +++ b/lib/ssl/src/tls_dtls_connection.erl @@ -0,0 +1,1687 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2020. 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: Common handling of a TLS/SSL/DTLS connection, see also +%% tls_connection.erl and dtls_connection.erl +%%---------------------------------------------------------------------- + +-module(tls_dtls_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("ssl_api.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). + +%% TLS-1.0 to TLS-1.2 Specific User Events +-export([renegotiation/1, renegotiation/2, prf/5]). + +%% Data handling. Note renegotiation is replaced by sesion key update mechanism in TLS-1.3 +-export([internal_renegotiation/2]). + +%% Help functions for tls|dtls_connection.erl +-export([handle_session/7, + handle_sni_extension/2]). + +%% General state handlingfor TLS-1.0 to TLS-1.2 and gen_handshake that wrapps +%% handling of common state handling for handshake messages for error handling +-export([hello/3, + user_hello/3, + abbreviated/3, + certify/3, + wait_ocsp_stapling/3, + cipher/3, + connection/3, + downgrade/3, + gen_handshake/4]). + +%%-------------------------------------------------------------------- +-spec internal_renegotiation(pid(), ssl_record:connection_states()) -> + ok. +%% +%% Description: Starts a renegotiation of the ssl session. +%%-------------------------------------------------------------------- +internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> + gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}). + +%%==================================================================== +%% User events +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec renegotiation(pid()) -> ok | {error, reason()}. +%% +%% Description: Starts a renegotiation of the ssl session. +%%-------------------------------------------------------------------- +renegotiation(ConnectionPid) -> + ssl_gen_statem:call(ConnectionPid, renegotiate). + +renegotiation(Pid, WriteState) -> + ssl_gen_statem:call(Pid, {user_renegotiate, WriteState}). + +%%-------------------------------------------------------------------- +-spec prf(pid(), binary() | 'master_secret', binary(), + [binary() | ssl:prf_random()], non_neg_integer()) -> + {ok, binary()} | {error, reason()} | {'EXIT', term()}. +%% +%% Description: use a ssl sessions TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> + ssl_gen_statem:call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). + +%%==================================================================== +%% Help functions for tls|dtls_connection.erl +%%==================================================================== +%%-------------------------------------------------------------------- +-spec handle_session(#server_hello{}, ssl_record:ssl_version(), + binary(), ssl_record:connection_states(), _,_, #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +handle_session(#server_hello{cipher_suite = CipherSuite, + compression_method = Compression}, + Version, NewId, ConnectionStates, ProtoExt, Protocol0, + #state{session = #session{session_id = OldId}, + handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> + #{key_exchange := KeyAlgorithm} = + ssl_cipher_format:suite_bin_to_map(CipherSuite), + + PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), + + {ExpectNPN, Protocol} = case Protocol0 of + undefined -> + + {false, CurrentProtocol}; + _ -> + {ProtoExt =:= npn, Protocol0} + end, + + State = State0#state{connection_states = ConnectionStates, + handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm, + premaster_secret = PremasterSecret, + expecting_next_protocol_negotiation = ExpectNPN, + negotiated_protocol = Protocol}, + connection_env = CEnv#connection_env{negotiated_version = Version}}, + + case ssl_session:is_new(OldId, NewId) of + true -> + handle_new_session(NewId, CipherSuite, Compression, + State#state{connection_states = ConnectionStates}); + false -> + handle_resumed_session(NewId, + State#state{connection_states = ConnectionStates}) + end. + + +%%==================================================================== +%% gen_statem general state functions with connection cb argument +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #server_hello{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +hello(internal, {common_client_hello, Type, ServerHelloExt}, State) -> + do_server_hello(Type, ServerHelloExt, State); +hello(info, Msg, State) -> + handle_info(Msg, ?FUNCTION_NAME, State); +hello(internal, #hello_request{}, _) -> + keep_state_and_data; +hello(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec user_hello(gen_statem:event_type(), + #hello_request{} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +user_hello({call, From}, cancel, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + gen_statem:reply(From, ok), + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), + Version, ?FUNCTION_NAME, State); +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{hello = Hello}, + ssl_options = Options0} = State0) -> + Options = ssl:handle_options(NewOptions, Role, Options0#{handshake => full}), + State = ssl_gen_statem:ssl_config(Options, Role, State0), + {next_state, hello, State#state{start_or_recv_from = From}, + [{next_event, internal, Hello}, {{timeout, handshake}, Timeout, close}]}; +user_hello(_, _, _) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + expecting_finished = true} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret}, + connection_states = ConnectionStates0} = + State0) -> + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, + get_current_prf(ConnectionStates0, write), + MasterSecret, Hist) of + verified -> + ConnectionStates = + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), + {Record, State} = + ssl_gen_statem:prepare_connection(State0#state{connection_states = ConnectionStates, + handshake_env = HsEnv#handshake_env{expecting_finished = false}}, + Connection), + Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + handshake_env = #handshake_env{tls_handshake_history = Hist0}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret}, + connection_states = ConnectionStates0} = State0) -> + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, + get_pending_prf(ConnectionStates0, write), + MasterSecret, Hist0) of + verified -> + ConnectionStates1 = + ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), + {#state{handshake_env = HsEnv} = State1, Actions} = + finalize_handshake(State0#state{connection_states = ConnectionStates1}, + ?FUNCTION_NAME, Connection), + {Record, State} = + ssl_gen_statem:prepare_connection(State1#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, + Connection), + Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State) -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}}); +abbreviated(internal, + #change_cipher_spec{type = <<1>>}, + #state{static_env = #static_env{protocol_cb = Connection}, + connection_states = ConnectionStates0, + 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}}); +abbreviated(info, Msg, State) -> + handle_info(Msg, ?FUNCTION_NAME, State); +abbreviated(internal, #hello_request{}, _) -> + keep_state_and_data; +abbreviated(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec wait_ocsp_stapling(gen_statem:event_type(), + #certificate{} | #certificate_status{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +wait_ocsp_stapling(internal, #certificate{}, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> + %% Postpone message, should be handled in certify after receiving staple message + Connection:next_event(?FUNCTION_NAME, no_record, State, [{postpone, true}]); +%% 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) -> + Connection:next_event(certify, no_record, + 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) + 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) -> + Connection:next_event(certify, no_record, + 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) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), + #hello_request{} | #certificate{} | #server_key_exchange{} | + #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +certify(info, Msg, State) -> + handle_info(Msg, ?FUNCTION_NAME, State); +certify(internal, #certificate{asn1_certificates = []}, + #state{static_env = #static_env{role = server}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{verify := verify_peer, + fail_if_no_peer_cert := true}} = + State) -> + Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, no_client_certificate_provided), + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +certify(internal, #certificate{asn1_certificates = []}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + ssl_options = #{verify := verify_peer, + fail_if_no_peer_cert := false}} = + State0) -> + Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); +certify(internal, #certificate{}, + #state{static_env = #static_env{role = server}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{verify := verify_none}} = + State) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +certify(internal, #certificate{}, + #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = #handshake_env{ + ocsp_stapling_state = #{ocsp_expect := staple}}} = State) -> + Connection:next_event(wait_ocsp_stapling, no_record, State, [{postpone, true}]); +certify(internal, #certificate{asn1_certificates = [Peer|_]} = Cert, + #state{static_env = #static_env{ + role = Role, + host = Host, + protocol_cb = Connection, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbInfo}, + handshake_env = #handshake_env{ + ocsp_stapling_state = #{ocsp_expect := Status} = OcspState}, + connection_env = #connection_env{ + negotiated_version = Version}, + ssl_options = Opts} = State) when Status =/= staple -> + OcspInfo = ocsp_info(OcspState, Opts, Peer), + case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, + Opts, CRLDbInfo, Role, Host, + ensure_tls(Version), OcspInfo) of + {PeerCert, PublicKeyInfo} -> + handle_peer_cert(Role, PeerCert, PublicKeyInfo, + State#state{client_certificate_requested = false}, Connection, []); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +certify(internal, #server_key_exchange{exchange_keys = Keys}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + public_key_info = PubKeyInfo} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + connection_states = ConnectionStates} = State) + when KexAlg == dhe_dss; + KexAlg == dhe_rsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdhe_ecdsa; + KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == rsa_psk; + KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + + Params = ssl_handshake:decode_server_key(Keys, KexAlg, ssl:tls_version(Version)), + + %% Use negotiated value if TLS-1.2 otherwhise return default + HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)), + + case is_anonymous(KexAlg) of + true -> + calculate_secret(Params#server_key_params.params, + State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}}, Connection); + false -> + case ssl_handshake:verify_server_key(Params, HashSign, + ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of + true -> + calculate_secret(Params#server_key_params.params, + State#state{handshake_env = HsEnv#handshake_env{hashsign_algorithm = HashSign}, + session = session_handle_params(Params#server_key_params.params, Session)}, + Connection); + false -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), + Version, ?FUNCTION_NAME, State) + end + end; +certify(internal, #certificate_request{}, + #state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = KexAlg}, + connection_env = #connection_env{negotiated_version = Version}} = State) + when KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == rsa_psk; + KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), + Version, ?FUNCTION_NAME, State); +certify(internal, #certificate_request{}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + session = #session{own_certificates = undefined}} = State) -> + %% The client does not have a certificate and will send an empty reply, the server may fail + %% or accept the connection by its own preference. No signature algorihms needed as there is + %% no certificate to verify. + Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); +certify(internal, #certificate_request{} = CertRequest, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + handshake_env = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{own_certificates = [Cert|_]}, + ssl_options = #{signature_algs := SupportedHashSigns}} = State) -> + case ssl_handshake:select_hashsign(CertRequest, Cert, + SupportedHashSigns, ssl:tls_version(Version)) of + #alert {} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); + NegotiatedHashSign -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{client_certificate_requested = true, + handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}}) + end; +%% PSK and RSA_PSK might bypass the Server-Key-Exchange +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + session = #session{master_secret = undefined}, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + premaster_secret = undefined, + server_psk_identity = PSKIdentity} = HsEnv, + ssl_options = #{user_lookup_fun := PSKLookup}} = State0) + when KexAlg == psk -> + case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); + PremasterSecret -> + State = master_secret(PremasterSecret, + State0#state{handshake_env = + HsEnv#handshake_env{premaster_secret = PremasterSecret}}), + client_certify_and_key_exchange(State, Connection) + end; +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = {Major, Minor}} = Version, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + premaster_secret = undefined, + server_psk_identity = PSKIdentity} = HsEnv, + session = #session{master_secret = undefined}, + ssl_options = #{user_lookup_fun := PSKLookup}} = State0) + when KexAlg == rsa_psk -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, + case ssl_handshake:premaster_secret({KexAlg, PSKIdentity}, PSKLookup, + RSAPremasterSecret) of + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); + PremasterSecret -> + State = master_secret(PremasterSecret, + State0#state{handshake_env = + HsEnv#handshake_env{premaster_secret = RSAPremasterSecret}}), + client_certify_and_key_exchange(State, Connection) + end; +%% Master secret was determined with help of server-key exchange msg +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = #handshake_env{premaster_secret = undefined}, + session = #session{master_secret = MasterSecret} = Session, + connection_states = ConnectionStates0} = State0) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, + ConnectionStates0, client) of + {MasterSecret, ConnectionStates} -> + State = State0#state{connection_states = ConnectionStates}, + client_certify_and_key_exchange(State, Connection); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +%% Master secret is calculated from premaster_secret +certify(internal, #server_hello_done{}, + #state{static_env = #static_env{role = client, + protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = #handshake_env{premaster_secret = PremasterSecret}, + session = Session0, + connection_states = ConnectionStates0} = State0) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, client) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State = State0#state{connection_states = ConnectionStates, + session = Session}, + client_certify_and_key_exchange(State, Connection); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + end; +certify(internal = Type, #client_key_exchange{} = Msg, + #state{static_env = #static_env{role = server}, + client_certificate_requested = true, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{fail_if_no_peer_cert := true}} = State) -> + %% We expect a certificate here + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, {Type, Msg}}), + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); +certify(internal, #client_key_exchange{exchange_keys = Keys}, + State = #state{handshake_env = #handshake_env{kex_algorithm = KeyAlg}, + static_env = #static_env{protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}}) -> + try + certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, ssl:tls_version(Version)), + State, Connection) + catch + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +certify(internal, #hello_request{}, _) -> + keep_state_and_data; +certify(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), + #hello_request{} | #certificate_verify{} | #finished{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher({call, From}, Msg, State) -> + handle_call(Msg, From, ?FUNCTION_NAME, State); +cipher(info, Msg, State) -> + handle_info(Msg, ?FUNCTION_NAME, State); +cipher(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + kex_algorithm = KexAlg, + public_key_info = PubKeyInfo} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} + } = State) -> + + TLSVersion = ssl:tls_version(Version), + %% Use negotiated value if TLS-1.2 otherwhise return default + HashSign = negotiated_hashsign(CertHashSign, KexAlg, PubKeyInfo, TLSVersion), + case ssl_handshake:certificate_verify(Signature, PubKeyInfo, + TLSVersion, HashSign, MasterSecret, Hist) of + valid -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}}); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +%% client must send a next protocol message if we are expecting it +cipher(internal, #finished{}, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{expecting_next_protocol_negotiation = true, + negotiated_protocol = undefined}, + connection_env = #connection_env{negotiated_version = Version}} = State0) -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); +cipher(internal, #finished{verify_data = Data} = Finished, + #state{static_env = #static_env{role = Role, + host = Host, + port = Port, + trackers = Trackers}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + expecting_finished = true} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{master_secret = MasterSecret} + = Session0, + ssl_options = SslOpts, + connection_states = ConnectionStates0} = State) -> + case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, + opposite_role(Role), + get_current_prf(ConnectionStates0, read), + MasterSecret, Hist) of + verified -> + Session = handle_session(Role, SslOpts, Host, Port, Trackers, Session0), + cipher_role(Role, Data, Session, + State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) + end; +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{static_env = #static_env{role = server, protocol_cb = Connection}, + handshake_env = #handshake_env{expecting_finished = true, + expecting_next_protocol_negotiation = true} = HsEnv} = State) -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}}); +cipher(internal, #change_cipher_spec{type = <<1>>}, + #state{handshake_env = HsEnv, + static_env = #static_env{protocol_cb = Connection}, + connection_states = ConnectionStates0} = State) -> + ConnectionStates = + ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), + Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{expecting_finished = true}, + connection_states = ConnectionStates}); +cipher(internal, #hello_request{}, _) -> + keep_state_and_data; +cipher(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = tls_gen_connection}, + handshake_env = HsEnv} = State) -> + tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); +connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = dtls_gen_connection}, + handshake_env = HsEnv} = State) -> + dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = 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 = undefined, + negotiated_protocol = SelectedProtocol}} = State) -> + ssl_gen_statem:hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +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(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = tls_gen_connection}, + handshake_env = HsEnv, + connection_states = ConnectionStates} + = State) -> + tls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, + connection_states = ConnectionStates#{current_write => WriteState}}, []); +connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = dtls_gen_connection}, + handshake_env = HsEnv, + connection_states = ConnectionStates} + = State) -> + dtls_connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, + connection_states = ConnectionStates#{current_write => WriteState}}, []); + +connection(internal, {handshake, {#hello_request{} = Handshake, _}}, + #state{handshake_env = HsEnv} = State) -> + %% Should not be included in handshake history + {next_state, ?FUNCTION_NAME, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}}, + [{next_event, internal, Handshake}]}; +connection(Type, Event, State) -> + ssl_gen_statem:?FUNCTION_NAME(Type, Event, State). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(Type, Event, State) -> + ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). + +gen_handshake(StateName, Type, Event, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + try tls_dtls_connection:StateName(Type, Event, State) of + Result -> + Result + catch + _:_ -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data), + Version, StateName, State) + end. + +%%-------------------------------------------------------------------- +%% Event handling functions called by state functions to handle +%% common or unexpected events for the state. +%%-------------------------------------------------------------------- +handle_call(renegotiate, From, StateName, _) when StateName =/= connection -> + {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; + +handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, + #state{connection_states = ConnectionStates, + connection_env = #connection_env{negotiated_version = Version}}) -> + #{security_parameters := SecParams} = + ssl_record:current_connection_state(ConnectionStates, read), + #security_parameters{master_secret = MasterSecret, + client_random = ClientRandom, + server_random = ServerRandom, + prf_algorithm = PRFAlgorithm} = SecParams, + Reply = try + SecretToUse = case Secret of + _ when is_binary(Secret) -> Secret; + master_secret -> MasterSecret + end, + SeedToUse = lists:reverse( + lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; + (client_random, Acc) -> [ClientRandom|Acc]; + (server_random, Acc) -> [ServerRandom|Acc] + end, [], Seed)), + ssl_handshake:prf(ssl:tls_version(Version), PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) + catch + exit:_ -> {error, badarg}; + error:Reason -> {error, Reason} + end, + {keep_state_and_data, [{reply, From, Reply}]}; +handle_call(Msg, From, StateName, State) -> + ssl_gen_statem:handle_call(Msg, From, StateName, State). + +handle_info(Msg, StateName, State) -> + ssl_gen_statem:handle_info(Msg, StateName, State). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +do_server_hello(Type, #{next_protocol_negotiation := NextProtocols} = + ServerHelloExt, + #state{connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv, + session = #session{session_id = SessId}, + connection_states = ConnectionStates0, + ssl_options = #{versions := [HighestVersion|_]}} + = State0) when is_atom(Type) -> + %% TLS 1.3 - Section 4.1.3 + %% Override server random values for TLS 1.3 downgrade protection mechanism. + ConnectionStates1 = update_server_random(ConnectionStates0, Version, HighestVersion), + State1 = State0#state{connection_states = ConnectionStates1}, + ServerHello = + ssl_handshake:server_hello(SessId, ssl:tls_version(Version), + ConnectionStates1, ServerHelloExt), + State = server_hello(ServerHello, + State1#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation = + NextProtocols =/= undefined}}, Connection), + case Type of + new -> + new_server_hello(ServerHello, State, Connection); + resumed -> + resumed_server_hello(State, Connection) + end. + +update_server_random(#{pending_read := #{security_parameters := ReadSecParams0} = + ReadState0, + pending_write := #{security_parameters := WriteSecParams0} = + WriteState0} = ConnectionStates, + Version, HighestVersion) -> + ReadRandom = override_server_random( + ReadSecParams0#security_parameters.server_random, + Version, + HighestVersion), + WriteRandom = override_server_random( + WriteSecParams0#security_parameters.server_random, + Version, + HighestVersion), + ReadSecParams = ReadSecParams0#security_parameters{server_random = ReadRandom}, + WriteSecParams = WriteSecParams0#security_parameters{server_random = WriteRandom}, + ReadState = ReadState0#{security_parameters => ReadSecParams}, + WriteState = WriteState0#{security_parameters => WriteSecParams}, + + ConnectionStates#{pending_read => ReadState, pending_write => WriteState}. + +%% TLS 1.3 - Section 4.1.3 +%% +%% If negotiating TLS 1.2, TLS 1.3 servers MUST set the last eight bytes +%% of their Random value to the bytes: +%% +%% 44 4F 57 4E 47 52 44 01 +%% +%% If negotiating TLS 1.1 or below, TLS 1.3 servers MUST and TLS 1.2 +%% servers SHOULD set the last eight bytes of their Random value to the +%% bytes: +%% +%% 44 4F 57 4E 47 52 44 00 +override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) + when Major > 3 orelse Major =:= 3 andalso Minor >= 4 -> %% TLS 1.3 or above + if M =:= 3 andalso N =:= 3 -> %% Negotating TLS 1.2 + Down = ?RANDOM_OVERRIDE_TLS12, + <<Random0/binary,Down/binary>>; + M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <<Random0/binary,Down/binary>>; + true -> + Random + end; +override_server_random(<<Random0:24/binary,_:8/binary>> = Random, {M,N}, {Major,Minor}) + when Major =:= 3 andalso Minor =:= 3 -> %% TLS 1.2 + if M =:= 3 andalso N < 3 -> %% Negotating TLS 1.1 or prior + Down = ?RANDOM_OVERRIDE_TLS11, + <<Random0/binary,Down/binary>>; + true -> + Random + end; +override_server_random(Random, _, _) -> + Random. + +new_server_hello(#server_hello{cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId}, + #state{session = Session0, + static_env = #static_env{protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> + try server_certify_and_key_exchange(State0, Connection) of + #state{} = State1 -> + {State, Actions} = server_hello_done(State1, Connection), + Session = + Session0#session{session_id = SessionId, + cipher_suite = CipherSuite, + compression_method = Compression}, + Connection:next_event(certify, no_record, State#state{session = Session}, Actions) + catch + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0) + end. + +resumed_server_hello(#state{session = Session, + connection_states = ConnectionStates0, + static_env = #static_env{protocol_cb = Connection}, + connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> + + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, + ConnectionStates0, server) of + {_, ConnectionStates1} -> + State1 = State0#state{connection_states = ConnectionStates1, + session = Session}, + {State, Actions} = + finalize_handshake(State1, abbreviated, Connection), + Connection:next_event(abbreviated, no_record, State, Actions); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, hello, State0) + end. + +server_hello(ServerHello, State0, Connection) -> + CipherSuite = ServerHello#server_hello.cipher_suite, + #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(ServerHello, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_algorithm = KeyAlgorithm}}. + +server_hello_done(State, Connection) -> + HelloDone = ssl_handshake:server_hello_done(), + Connection:send_handshake(HelloDone, State). + +handle_peer_cert(Role, PeerCert, PublicKeyInfo, + #state{handshake_env = HsEnv, + static_env = #static_env{protocol_cb = Connection}, + session = #session{cipher_suite = CipherSuite} = Session} = State0, + Connection, Actions) -> + State1 = State0#state{handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}, + session = + Session#session{peer_certificate = PeerCert}}, + #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_bin_to_map(CipherSuite), + State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), + Connection:next_event(certify, no_record, State, Actions). + +handle_peer_cert_key(client, _, + {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, + PublicKeyParams}, + KeyAlg, #state{handshake_env = HsEnv, + session = Session} = State) when KeyAlg == ecdh_rsa; + KeyAlg == ecdh_ecdsa -> + ECDHKey = public_key:generate_key(PublicKeyParams), + PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), + master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey}, + session = Session#session{ecc = PublicKeyParams}}); +handle_peer_cert_key(_, _, _, _, State) -> + State. + +certify_client(#state{static_env = #static_env{role = client, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + client_certificate_requested = true, + session = #session{own_certificates = OwnCerts}} + = State, Connection) -> + Certificate = ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, client), + Connection:queue_handshake(Certificate, State); +certify_client(#state{client_certificate_requested = false} = State, _) -> + State. + +verify_client_cert(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{tls_handshake_history = Hist, + cert_hashsign_algorithm = HashSign}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + client_certificate_requested = true, + session = #session{master_secret = MasterSecret, + own_certificates = OwnCerts}} = State, Connection) -> + + case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret, + ssl:tls_version(Version), HashSign, PrivateKey, Hist) of + #certificate_verify{} = Verified -> + Connection:queue_handshake(Verified, State); + ignore -> + State; + #alert{} = Alert -> + throw(Alert) + end; +verify_client_cert(#state{client_certificate_requested = false} = State, _) -> + State. + +client_certify_and_key_exchange(#state{connection_env = #connection_env{negotiated_version = Version}} = + State0, Connection) -> + try do_client_certify_and_key_exchange(State0, Connection) of + State1 = #state{} -> + {State2, Actions} = finalize_handshake(State1, certify, Connection), + State = State2#state{ + %% Reinitialize + client_certificate_requested = false}, + Connection:next_event(cipher, no_record, State, Actions) + catch + throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0) + end. + +do_client_certify_and_key_exchange(State0, Connection) -> + State1 = certify_client(State0, Connection), + State2 = key_exchange(State1, Connection), + verify_client_cert(State2, Connection). + +server_certify_and_key_exchange(State0, Connection) -> + State1 = certify_server(State0, Connection), + State2 = key_exchange(State1, Connection), + request_client_cert(State2, Connection). + +certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, + #state{connection_env = #connection_env{private_key = Key}, + handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} + = State, Connection) -> + FakeSecret = make_premaster_secret(Version, rsa), + %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret + %% and fail handshake later.RFC 5246 section 7.4.7.1. + PremasterSecret = + try ssl_handshake:premaster_secret(EncPMS, Key) of + Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> + case Secret of + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>; + <<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch + <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> + end; + _ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES + FakeSecret + catch + #alert{description = ?DECRYPT_ERROR} -> + FakeSecret + end, + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); +certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, + #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, + kex_keys = {_, ServerDhPrivateKey}} + } = State, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + +certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, + #state{handshake_env = #handshake_env{kex_keys = ECDHKey}} = State, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); +certify_client_key_exchange(#client_psk_identity{} = ClientKey, + #state{ssl_options = + #{user_lookup_fun := PSKLookup}} = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, + #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, + kex_keys = {_, ServerDhPrivateKey}}, + ssl_options = + #{user_lookup_fun := PSKLookup}} = State0, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, + #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, + ssl_options = + #{user_lookup_fun := PSKLookup}} = State, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerEcDhPrivateKey, PSKLookup), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); +certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, + #state{connection_env = #connection_env{private_key = Key}, + ssl_options = + #{user_lookup_fun := PSKLookup}} = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +certify_client_key_exchange(#client_srp_public{} = ClientKey, + #state{handshake_env = #handshake_env{srp_params = Params, + kex_keys = Key} + } = State0, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher). + +certify_server(#state{handshake_env = #handshake_env{kex_algorithm = KexAlg}} = + State, _) when KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == srp_anon -> + State; +certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + session = #session{own_certificates = OwnCerts}} = State, Connection) -> + case ssl_handshake:certificate(OwnCerts, CertDbHandle, CertDbRef, server) of + Cert = #certificate{} -> + Connection:queue_handshake(Cert, State); + Alert = #alert{} -> + throw(Alert) + end. + +key_exchange(#state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{kex_algorithm = rsa}} = State,_) -> + State; +key_exchange(#state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + diffie_hellman_params = #'DHParameter'{} = Params, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + connection_states = ConnectionStates0} = State0, Connection) + when KexAlg == dhe_dss; + KexAlg == dhe_rsa; + KexAlg == dh_anon -> + DHKeys = public_key:generate_key(Params), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), {dh, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; +key_exchange(#state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, + connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, + session = Session} = State, _) + when KexAlg == ecdh_ecdsa; + KexAlg == ecdh_rsa -> + State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key}, + session = Session#session{ecc = ECCurve}}; +key_exchange(#state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + session = #session{ecc = ECCCurve}, + connection_states = ConnectionStates0} = State0, Connection) + when KexAlg == ecdhe_ecdsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdh_anon -> + + ECDHKeys = public_key:generate_key(ECCCurve), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdh, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; +key_exchange(#state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{kex_algorithm = psk}, + ssl_options = #{psk_identity := undefined}} = State, _) -> + State; +key_exchange(#state{static_env = #static_env{role = server}, + ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = psk, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + connection_states = ConnectionStates0} = State0, Connection) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = server}, + ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = dhe_psk, + diffie_hellman_params = #'DHParameter'{} = Params, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + connection_states = ConnectionStates0 + } = State0, Connection) -> + DHKeys = public_key:generate_key(Params), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {dhe_psk, + PskIdentityHint, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; +key_exchange(#state{static_env = #static_env{role = server}, + ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + session = #session{ecc = ECCCurve}, + connection_states = ConnectionStates0 + } = State0, Connection) -> + ECDHKeys = public_key:generate_key(ECCCurve), + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {ecdhe_psk, + PskIdentityHint, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}}; +key_exchange(#state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{kex_algorithm = rsa_psk}, + ssl_options = #{psk_identity := undefined}} = State, _) -> + State; +key_exchange(#state{static_env = #static_env{role = server}, + ssl_options = #{psk_identity := PskIdentityHint}, + handshake_env = #handshake_env{kex_algorithm = rsa_psk, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + connection_states = ConnectionStates0 + } = State0, Connection) -> + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = server}, + ssl_options = #{user_lookup_fun := LookupFun}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + hashsign_algorithm = HashSignAlgo}, + connection_env = #connection_env{negotiated_version = Version, + private_key = PrivateKey}, + session = #session{srp_username = Username}, + connection_states = ConnectionStates0 + } = State0, Connection) + when KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + SrpParams = handle_srp_identity(Username, LookupFun), + Keys = case generate_srp_server_keys(SrpParams, 0) of + Alert = #alert{} -> + throw(Alert); + Keys0 = {_,_} -> + Keys0 + end, + #{security_parameters := SecParams} = + ssl_record:pending_connection_state(ConnectionStates0, read), + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, ssl:tls_version(Version), + {srp, Keys, SrpParams, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + #state{handshake_env = HsEnv} = State = Connection:queue_handshake(Msg, State0), + State#state{handshake_env = HsEnv#handshake_env{srp_params = SrpParams, + kex_keys = Keys}}; +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = rsa, + public_key_info = PublicKeyInfo, + premaster_secret = PremasterSecret}, + connection_env = #connection_env{negotiated_version = Version} + } = State0, Connection) -> + Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = {DhPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version} + } = State0, Connection) + when KexAlg == dhe_dss; + KexAlg == dhe_rsa; + KexAlg == dh_anon -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = #'ECPrivateKey'{parameters = ECCurve} = Key}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session + } = State0, Connection) + when KexAlg == ecdhe_ecdsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdh_ecdsa; + KexAlg == ecdh_rsa; + KexAlg == ecdh_anon -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), + Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = psk}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {psk, PSKIdentity}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = dhe_psk, + kex_keys = {DhPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {dhe_psk, + PSKIdentity, DhPubKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, + kex_keys = ECDHKeys}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {ecdhe_psk, + PSKIdentity, ECDHKeys}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = rsa_psk, + public_key_info = PublicKeyInfo, + premaster_secret = PremasterSecret}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{psk_identity := PSKIdentity}} + = State0, Connection) -> + Msg = rsa_psk_key_exchange(ssl:tls_version(Version), PSKIdentity, + PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = {ClientPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version}} + = State0, Connection) + when KexAlg == srp_dss; + KexAlg == srp_rsa; + KexAlg == srp_anon -> + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {srp, ClientPubKey}), + Connection:queue_handshake(Msg, State0). + +rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {premaster_secret, PremasterSecret, + PublicKeyInfo}); +rsa_key_exchange(_, _, _) -> + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). + +rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, + PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, ssl:tls_version(Version), + {psk_premaster_secret, PskIdentity, PremasterSecret, + PublicKeyInfo}); +rsa_psk_key_exchange(_, _, _, _) -> + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). + +request_client_cert(#state{handshake_env = #handshake_env{kex_algorithm = Alg}} = State, _) + when Alg == dh_anon; + Alg == ecdh_anon; + Alg == psk; + Alg == dhe_psk; + Alg == ecdhe_psk; + Alg == rsa_psk; + Alg == srp_dss; + Alg == srp_rsa; + Alg == srp_anon -> + State; + +request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{verify := verify_peer, + signature_algs := SupportedHashSigns}, + connection_states = ConnectionStates0} = State0, Connection) -> + #{security_parameters := + #security_parameters{cipher_suite = CipherSuite}} = + ssl_record:pending_connection_state(ConnectionStates0, read), + TLSVersion = ssl:tls_version(Version), + HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, + TLSVersion), + Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, + HashSigns, TLSVersion), + State = Connection:queue_handshake(Msg, State0), + State#state{client_certificate_requested = true}; + +request_client_cert(#state{ssl_options = #{verify := verify_none}} = + State, _) -> + State. + +calculate_master_secret(PremasterSecret, + #state{connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0, + session = Session0} = State0, Connection, + _Current, Next) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, server) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State = State0#state{connection_states = ConnectionStates, + session = Session}, + Connection:next_event(Next, no_record, State); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, certify, State0) + end. + +finalize_handshake(State0, StateName, Connection) -> + #state{connection_states = ConnectionStates0} = + State1 = cipher_protocol(State0, Connection), + + ConnectionStates = + ssl_record:activate_pending_connection_state(ConnectionStates0, + write, Connection), + + State2 = State1#state{connection_states = ConnectionStates}, + State = next_protocol(State2, Connection), + finished(State, StateName, Connection). + +next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> + State; +next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> + State; +next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) -> + State; +next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) -> + NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), + Connection:queue_handshake(NextProtocolMessage, State0). + +cipher_protocol(State, Connection) -> + Connection:queue_change_cipher(#change_cipher_spec{}, State). + +finished(#state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + connection_states = ConnectionStates0} = State0, + StateName, Connection) -> + MasterSecret = Session#session.master_secret, + Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, + get_current_prf(ConnectionStates0, write), + MasterSecret, Hist), + ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), + Connection:send_handshake(Finished, State0#state{connection_states = + ConnectionStates}). + +save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> + ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); +save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> + ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); +save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); +save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> + ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). + +calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, + dh_y = ServerPublicDhKey} = Params, + #state{handshake_env = HsEnv} = State, Connection) -> + Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), + PremasterSecret = + ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), + calculate_master_secret(PremasterSecret, + State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, + Connection, certify, certify); + +calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, + #state{handshake_env = HsEnv, + session = Session} = State, Connection) -> + ECDHKeys = public_key:generate_key(ECCurve), + PremasterSecret = + ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), + calculate_master_secret(PremasterSecret, + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, + session = Session#session{ecc = ECCurve}}, + Connection, certify, certify); + +calculate_secret(#server_psk_params{ + hint = IdentityHint}, + #state{handshake_env = HsEnv} = State, Connection) -> + %% store for later use + Connection:next_event(certify, no_record, + State#state{handshake_env = + HsEnv#handshake_env{server_psk_identity = IdentityHint}}); + +calculate_secret(#server_dhe_psk_params{ + dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, + #state{handshake_env = HsEnv, + ssl_options = #{user_lookup_fun := PSKLookup}} = + State, Connection) -> + Keys = {_, PrivateDhKey} = + crypto:generate_key(dh, [Prime, Base]), + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup), + calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, + Connection, certify, certify); + +calculate_secret(#server_ecdhe_psk_params{ + dh_params = #server_ecdh_params{curve = ECCurve}} = ServerKey, + #state{ssl_options = #{user_lookup_fun := PSKLookup}} = + #state{handshake_env = HsEnv, + session = Session} = State, Connection) -> + ECDHKeys = public_key:generate_key(ECCurve), + + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, ECDHKeys, PSKLookup), + calculate_master_secret(PremasterSecret, + State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKeys}, + session = Session#session{ecc = ECCurve}}, + Connection, certify, certify); + +calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, + #state{handshake_env = HsEnv, + ssl_options = #{srp_identity := SRPId}} = State, + Connection) -> + Keys = generate_srp_client_keys(Generator, Prime, 0), + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId), + calculate_master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection, + certify, certify). + +master_secret(#alert{} = Alert, _) -> + Alert; +master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, + connection_env = #connection_env{negotiated_version = Version}, + session = Session, + connection_states = ConnectionStates0} = State) -> + case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, + ConnectionStates0, Role) of + {MasterSecret, ConnectionStates} -> + State#state{ + session = + Session#session{master_secret = MasterSecret}, + connection_states = ConnectionStates}; + #alert{} = Alert -> + Alert + end. + +generate_srp_server_keys(_SrpParams, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_server_keys(SrpParams = + #srp_user{generator = Generator, prime = Prime, + verifier = Verifier}, N) -> + try crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of + Keys -> + Keys + catch + error:_ -> + generate_srp_server_keys(SrpParams, N+1) + end. + +generate_srp_client_keys(_Generator, _Prime, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_client_keys(Generator, Prime, N) -> + + try crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of + Keys -> + Keys + catch + error:_ -> + generate_srp_client_keys(Generator, Prime, N+1) + end. + +handle_srp_identity(Username, {Fun, UserState}) -> + case Fun(srp, Username, UserState) of + {ok, {SRPParams, Salt, DerivedKey}} + when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> + {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), + Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), + #srp_user{generator = Generator, prime = Prime, + salt = Salt, verifier = Verifier}; + #alert{} = Alert -> + throw(Alert); + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + + +cipher_role(client, Data, Session, #state{static_env = #static_env{protocol_cb = Connection}, + connection_states = ConnectionStates0} = State0) -> + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, + ConnectionStates0), + {Record, State} = ssl_gen_statem:prepare_connection(State0#state{session = Session, + connection_states = ConnectionStates}, + Connection), + Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close}]); +cipher_role(server, Data, Session, #state{static_env = #static_env{protocol_cb = Connection}, + connection_states = ConnectionStates0} = State0) -> + ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, + ConnectionStates0), + {State1, Actions} = + finalize_handshake(State0#state{connection_states = ConnectionStates1, + session = Session}, cipher, Connection), + {Record, State} = ssl_gen_statem:prepare_connection(State1, Connection), + Connection:next_event(connection, Record, State, [{{timeout, handshake}, infinity, close} | Actions]). + +is_anonymous(KexAlg) when KexAlg == dh_anon; + KexAlg == ecdh_anon; + KexAlg == psk; + KexAlg == dhe_psk; + KexAlg == ecdhe_psk; + KexAlg == rsa_psk; + KexAlg == srp_anon -> + true; +is_anonymous(_) -> + false. + +get_current_prf(CStates, Direction) -> + #{security_parameters := SecParams} = ssl_record:current_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. +get_pending_prf(CStates, Direction) -> + #{security_parameters := SecParams} = ssl_record:pending_connection_state(CStates, Direction), + SecParams#security_parameters.prf_algorithm. + +opposite_role(client) -> + server; +opposite_role(server) -> + client. + + + +session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> + Session#session{ecc = ECCurve}; +session_handle_params(_, Session) -> + Session. + +handle_session(server, #{reuse_sessions := true}, + _Host, _Port, Trackers, #session{is_resumable = false} = Session) -> + Tracker = proplists:get_value(session_id_tracker, Trackers), + server_register_session(Tracker, Session#session{is_resumable = true}); +handle_session(Role = client, #{verify := verify_peer, + reuse_sessions := Reuse} = SslOpts, + Host, Port, _, #session{is_resumable = false} = Session) when Reuse =/= false -> + client_register_session(host_id(Role, Host, SslOpts), Port, Session#session{is_resumable = true}, + reg_type(Reuse)); +handle_session(_,_,_,_,_, Session) -> + Session. + +reg_type(save) -> + true; +reg_type(true) -> + unique. + +client_register_session(Host, Port, Session, Save) -> + ssl_manager:register_session(Host, Port, Session, Save), + Session. +server_register_session(Tracker, Session) -> + ssl_server_session_cache:register_session(Tracker, Session), + Session. + +host_id(client, _Host, #{server_name_indication := Hostname}) when is_list(Hostname) -> + Hostname; +host_id(_, Host, _) -> + Host. + +handle_new_session(NewId, CipherSuite, Compression, + #state{static_env = #static_env{protocol_cb = Connection}, + session = Session0 + } = State0) -> + Session = Session0#session{session_id = NewId, + cipher_suite = CipherSuite, + compression_method = Compression}, + Connection:next_event(certify, no_record, State0#state{session = Session}). + +handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, + port = Port, + protocol_cb = Connection, + session_cache = Cache, + session_cache_cb = CacheCb}, + connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0, + ssl_options = Opts} = State) -> + + Session = case maps:get(reuse_session, Opts, undefined) of + {SessId,SessionData} when is_binary(SessId), is_binary(SessionData) -> + binary_to_term(SessionData, [safe]); + _Else -> + CacheCb:lookup(Cache, {{Host, Port}, SessId}) + end, + + case ssl_handshake:master_secret(ssl:tls_version(Version), Session, + ConnectionStates0, client) of + {_, ConnectionStates} -> + Connection:next_event(abbreviated, no_record, State#state{ + connection_states = ConnectionStates, + session = Session}); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, hello, State) + end. + +make_premaster_secret({MajVer, MinVer}, rsa) -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; +make_premaster_secret(_, _) -> + undefined. + +negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> + %% Not negotiated choose default + case is_anonymous(KexAlg) of + true -> + {null, anon}; + false -> + {PubAlg, _, _} = PubKeyInfo, + ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) + end; +negotiated_hashsign(HashSign = {_, _}, _, _, _) -> + HashSign. + +%% Handle SNI extension in pre-TLS 1.3 and DTLS +handle_sni_extension(#state{static_env = + #static_env{protocol_cb = Connection}} = State0, + Hello) -> + PossibleSNI = Connection:select_sni_extension(Hello), + case ssl_gen_statem:handle_sni_extension(PossibleSNI, State0) of + {ok, State} -> + State; + {error, Alert} -> + Alert + end. + +ensure_tls({254, _} = 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) -> + #{cert_ext => #{public_key:pkix_subject_id(PeerCert) => [CertStatus]}, + ocsp_responder_certs => OcspResponderCerts, + 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 + }. diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl new file mode 100644 index 0000000000..7c16bfa3de --- /dev/null +++ b/lib/ssl/src/tls_gen_connection.erl @@ -0,0 +1,767 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2020. 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: +%%---------------------------------------------------------------------- + +-module(tls_gen_connection). + +-include_lib("public_key/include/public_key.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-include("tls_connection.hrl"). +-include("tls_handshake.hrl"). +-include("tls_record.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). + +%% Setup +-export([start_fsm/8, + pids/1, + initialize_tls_sender/1]). + +%% Handshake handling +-export([renegotiation/2, + renegotiate/2, + send_handshake/2, + send_handshake_flight/1, + queue_handshake/2, + queue_change_cipher/2, + reinit/1, + reinit_handshake_data/1, + select_sni_extension/1, + empty_connection_state/2, + encode_handshake/4]). + +%% State transition handling +-export([next_event/3, + next_event/4, + handle_protocol_record/3]). + +%% Data handling +-export([socket/4, + setopts/3, + getopts/3, + handle_info/3]). + +%% Alert and close handling +-export([send_alert/2, + send_alert_in_connection/2, + send_sync_alert/2, + close/5, + protocol_name/0]). + +-define(DIST_CNTRL_SPAWN_OPTS, [{priority, max}]). + +%%==================================================================== +%% Internal application API +%%==================================================================== +%%==================================================================== +%% Setup +%%==================================================================== +start_fsm(Role, Host, Port, Socket, {#{erl_dist := false},_, Trackers} = Opts, + User, {CbModule, _,_, _, _} = CbInfo, + Timeout) -> + try + {ok, Sender} = tls_sender:start(), + {ok, Pid} = tls_connection_sup:start_child([Role, Sender, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), + ssl_gen_statem:handshake(SslSocket, Timeout) + catch + error:{badmatch, {error, _} = Error} -> + Error + end; + +start_fsm(Role, Host, Port, Socket, {#{erl_dist := true},_, Trackers} = Opts, + User, {CbModule, _,_, _, _} = CbInfo, + Timeout) -> + try + {ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]), + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Sender, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_gen_statem:socket_control(?MODULE, Socket, [Pid, Sender], CbModule, Trackers), + ssl_gen_statem:handshake(SslSocket, Timeout) + catch + error:{badmatch, {error, _} = Error} -> + Error + end. + +pids(#state{protocol_specific = #{sender := Sender}}) -> + [self(), Sender]. + +initialize_tls_sender(#state{static_env = #static_env{ + role = Role, + transport_cb = Transport, + socket = Socket, + trackers = Trackers + }, + connection_env = #connection_env{negotiated_version = Version}, + socket_options = SockOpts, + ssl_options = #{renegotiate_at := RenegotiateAt, + key_update_at := KeyUpdateAt, + log_level := LogLevel}, + connection_states = #{current_write := ConnectionWriteState}, + protocol_specific = #{sender := Sender}}) -> + Init = #{current_write => ConnectionWriteState, + role => Role, + socket => Socket, + socket_options => SockOpts, + trackers => Trackers, + transport_cb => Transport, + negotiated_version => Version, + renegotiate_at => RenegotiateAt, + key_update_at => KeyUpdateAt, + log_level => LogLevel}, + tls_sender:initialize(Sender, Init). + +%%==================================================================== +%% Handshake handling +%%==================================================================== +renegotiation(Pid, WriteState) -> + gen_statem:call(Pid, {user_renegotiate, WriteState}). + +renegotiate(#state{static_env = #static_env{role = client}, + handshake_env = HsEnv} = State, Actions) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_handshake_history(), + {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, + [{next_event, internal, #hello_request{}} | Actions]}; +renegotiate(#state{static_env = #static_env{role = server, + socket = Socket, + transport_cb = Transport}, + handshake_env = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + connection_states = ConnectionStates0} = State0, Actions) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = tls_handshake:encode_handshake(HelloRequest, Version), + Hs0 = ssl_handshake:init_handshake_history(), + {BinMsg, ConnectionStates} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + tls_socket:send(Transport, Socket, BinMsg), + State = State0#state{connection_states = + ConnectionStates, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, + next_event(hello, no_record, State, Actions). + +send_handshake(Handshake, State) -> + send_handshake_flight(queue_handshake(Handshake, State)). + +queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = Flight0, + ssl_options = #{log_level := LogLevel}, + connection_states = ConnectionStates0} = State0) -> + {BinHandshake, ConnectionStates, Hist} = + encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + ssl_logger:debug(LogLevel, outbound, 'handshake', Handshake), + ssl_logger:debug(LogLevel, outbound, 'record', BinHandshake), + + State0#state{connection_states = ConnectionStates, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, + flight_buffer = Flight0 ++ [BinHandshake]}. + +-spec send_handshake_flight(StateIn) -> {StateOut, FlightBuffer} when + StateIn :: #state{}, + StateOut :: #state{}, + FlightBuffer :: list(). +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + flight_buffer = Flight} = State0) -> + tls_socket:send(Transport, Socket, Flight), + {State0#state{flight_buffer = []}, []}. + + +queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = Flight0, + ssl_options = #{log_level := LogLevel}, + connection_states = ConnectionStates0} = State0) -> + {BinChangeCipher, ConnectionStates} = + encode_change_cipher(Msg, Version, ConnectionStates0), + ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), + State0#state{connection_states = ConnectionStates, + flight_buffer = Flight0 ++ [BinChangeCipher]}. + +reinit(#state{protocol_specific = #{sender := Sender}, + connection_env = #connection_env{negotiated_version = Version}, + connection_states = #{current_write := Write}} = State) -> + tls_sender:update_connection_state(Sender, Write, Version), + reinit_handshake_data(State). + +reinit_handshake_data(#state{handshake_env = HsEnv} =State) -> + %% premaster_secret, public_key_info and tls_handshake_info + %% are only needed during the handshake phase. + %% To reduce memory foot print of a connection reinitialize them. + State#state{ + handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history(), + public_key_info = undefined, + premaster_secret = undefined} + }. + +select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> + SNI; +select_sni_extension(_) -> + undefined. + +empty_connection_state(ConnectionEnd, BeastMitigation) -> + ssl_record:empty_connection_state(ConnectionEnd, BeastMitigation). + +%%==================================================================== +%% Data handling +%%==================================================================== + +socket(Pids, Transport, Socket, Trackers) -> + tls_socket:socket(Pids, Transport, Socket, tls_connection, Trackers). + +setopts(Transport, Socket, Other) -> + tls_socket:setopts(Transport, Socket, Other). + +getopts(Transport, Socket, Tag) -> + tls_socket:getopts(Transport, Socket, Tag). + +%% raw data from socket, upack records +handle_info({Protocol, _, Data}, StateName, + #state{static_env = #static_env{data_tag = Protocol}, + connection_env = #connection_env{negotiated_version = Version}} = State0) -> + case next_tls_record(Data, StateName, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0) + end; +handle_info({PassiveTag, Socket}, StateName, + #state{static_env = #static_env{socket = Socket, + passive_tag = PassiveTag}, + start_or_recv_from = From, + protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, + protocol_specific = PS + } = State0) -> + case (From =/= undefined) andalso (CTs == []) of + true -> + {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}), + next_event(StateName, Record, State); + false -> + next_event(StateName, no_record, + State0#state{protocol_specific = PS#{active_n_toggle => true}}) + end; +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{ + role = Role, + host = Host, + port = Port, + socket = Socket, + close_tag = CloseTag}, + handshake_env = #handshake_env{renegotiation = Type}, + session = Session} = State) when StateName =/= connection -> + ssl_gen_statem:maybe_invalidate_session(Type, Role, Host, Port, Session), + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown, transport_closed}, State}; +handle_info({CloseTag, Socket}, StateName, + #state{static_env = #static_env{ + role = Role, + socket = Socket, + close_tag = CloseTag}, + start_or_recv_from = From, + socket_options = #socket_options{active = Active}, + protocol_specific = PS} = State) -> + + %% Note that as of TLS 1.1, + %% failure to properly close a connection no longer requires that a + %% session not be resumed. This is a change from TLS 1.0 to conform + %% with widespread implementation practice. + + case (Active == false) andalso (From == undefined) of + false -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %% case Version of + %% {3, N} when N >= 1 -> + %% ok; + %% _ -> + %% invalidate_session(Role, Host, Port, Session) + %% ok + %% end, + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown, transport_closed}, State}; + true -> + %% Wait for next socket operation (most probably + %% ssl:setopts(S, [{active, true | once | N}]) or + %% ssl:recv(S, N, Timeout) before closing. Possible + %% buffered data will be deliverd by the code handling + %% these options before closing. In the case of the + %% peer resetting the connection hard, that is + %% we do not receive any close ALERT, and an active once (or possible N) + %% strategy is used by the client we want to later trigger a new + %% "transport closed" message. This is achieved by setting the internal + %% active_n_toggle here which will cause + %% this to happen when tls_connection:activate_socket/1 + %% is called after all data has been deliver. + {next_state, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}, []} + end; +handle_info({'EXIT', Sender, Reason}, _, + #state{protocol_specific = #{sender := Sender}} = State) -> + {stop, {shutdown, {sender_died, Reason}}, State}; +handle_info(Msg, StateName, State) -> + ssl_gen_statem:handle_info(Msg, StateName, State). + +%%==================================================================== +%% State transition handling +%%==================================================================== +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(StateName, no_record, #state{static_env = #static_env{role = Role}} = State0, Actions) -> + case next_record(StateName, State0) of + {no_record, State} -> + ssl_gen_statem:hibernate_after(StateName, State, Actions); + {Record, State} -> + next_event(StateName, Record, State, Actions); + #alert{} = Alert -> + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = Role}, StateName, State0), + {stop, {shutdown, own_alert}, State0} + end; +next_event(StateName, #ssl_tls{} = Record, State, Actions) -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; +next_event(StateName, #alert{} = Alert, State, Actions) -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}. + +%%% TLS record protocol level application data messages +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, + #state{start_or_recv_from = From, + socket_options = #socket_options{active = false}} = State0) when From =/= undefined -> + case ssl_gen_statem:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, #state{start_or_recv_from = Caller} = State} -> + TimerAction = case Caller of + undefined -> %% Passive recv complete cancel timer + [{{timeout, recv}, infinity, timeout}]; + _ -> + [] + end, + next_event(StateName, Record, State, TimerAction) + end; +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> + case ssl_gen_statem:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State} -> + next_event(StateName, Record, State) + end; +%%% TLS record protocol level handshake messages +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, + StateName, #state{protocol_buffers = + #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, + connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{role = Role}, + ssl_options = Options} = State0) -> + try + %% Calculate the effective version that should be used when decoding an incoming handshake + %% message. + EffectiveVersion = effective_version(Version, Options, Role), + {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), + State = + State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, + case Packets of + [] -> + assert_buffer_sanity(Buf, Options), + next_event(StateName, no_record, State); + _ -> + Events = tls_handshake_events(Packets), + case StateName of + connection -> + ssl_gen_statem:hibernate_after(StateName, State, Events); + _ -> + HsEnv = State#state.handshake_env, + {next_state, StateName, + State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} + end + end + catch throw:#alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State0) + end; +%%% TLS record protocol level change cipher messages +handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% TLS record protocol level Alert messages +handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> + try decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + [] -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), + Version, StateName, State); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, Version, StateName, State) + catch + _:_ -> + ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), + Version, StateName, State) + + end; +%% Ignore unknown TLS record level protocol messages +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. + +%%==================================================================== +%% Alert and close handling +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec encode_alert(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> + {iolist(), ssl_record:connection_states()}. +%% +%% Description: Encodes an alert +%%-------------------------------------------------------------------- +encode_alert(#alert{} = Alert, Version, ConnectionStates) -> + tls_record:encode_alert_record(Alert, Version, ConnectionStates). + +send_alert(Alert, #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = #{log_level := LogLevel}, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), + tls_socket:send(Transport, Socket, BinMsg), + ssl_logger:debug(LogLevel, outbound, 'record', BinMsg), + StateData0#state{connection_states = ConnectionStates}. + +%% If an ALERT sent in the connection state, should cause the TLS +%% connection to end, we need to synchronize with the tls_sender +%% process so that the ALERT if possible (that is the tls_sender process is +%% not blocked) is sent before the connection process terminates and +%% thereby closes the transport socket. +send_alert_in_connection(#alert{level = ?FATAL} = Alert, State) -> + send_sync_alert(Alert, State); +send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> + send_sync_alert(Alert, State); +send_alert_in_connection(Alert, + #state{protocol_specific = #{sender := Sender}}) -> + tls_sender:send_alert(Sender, Alert). +send_sync_alert( + Alert, #state{protocol_specific = #{sender := Sender}} = State) -> + try tls_sender:send_and_ack_alert(Sender, Alert) + catch + _:_ -> + throw({stop, {shutdown, own_alert}, State}) + end. + +%% User closes or recursive call! +close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> + tls_socket:setopts(Transport, Socket, [{active, false}]), + Transport:shutdown(Socket, write), + _ = Transport:recv(Socket, 0, Timeout), + ok; +%% Peer closed socket +close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + close({close, 0}, Socket, Transport, ConnectionStates, Check); +%% We generate fatal alert +close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + %% Standard trick to try to make sure all + %% data sent to the tcp port is really delivered to the + %% peer application before tcp port is closed so that the peer will + %% get the correct TLS alert message and not only a transport close. + %% Will return when other side has closed or after timout millisec + %% e.g. we do not want to hang if something goes wrong + %% with the network but we want to maximise the odds that + %% peer application gets all data sent on the tcp connection. + close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + tls_socket:close(Transport, Socket). +protocol_name() -> + "TLS". + + +%%==================================================================== +%% Internal functions +%%==================================================================== +tls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). + +unprocessed_events(Events) -> + %% The first handshake event will be processed immediately + %% as it is entered first in the event queue and + %% when it is processed there will be length(Events)-1 + %% handshake events left to process before we should + %% process more TLS-records received on the socket. + erlang:length(Events)-1. + +encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> + Frag = tls_handshake:encode_handshake(Handshake, Version), + Hist = ssl_handshake:update_handshake_history(Hist0, Frag), + {Encoded, ConnectionStates} = + tls_record:encode_handshake(Frag, Version, ConnectionStates0), + {Encoded, ConnectionStates, Hist}. + +encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> + tls_record:encode_change_cipher_spec(Version, ConnectionStates). + +next_tls_record(Data, StateName, + #state{protocol_buffers = + #protocol_buffers{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = Buffers, + ssl_options = SslOpts} = State0) -> + Versions = + %% TLSPlaintext.legacy_record_version is ignored in TLS 1.3 and thus all + %% record version are accepted when receiving initial ClientHello and + %% ServerHello. This can happen in state 'hello' in case of all TLS + %% versions and also in state 'start' when TLS 1.3 is negotiated. + %% After the version is negotiated all subsequent TLS records shall have + %% the proper legacy_record_version (= negotiated_version). + %% Note: TLS record version {3,4} is used internally in TLS 1.3 and at this + %% point it is the same as the negotiated protocol version. + %% TODO: Refactor state machine and introduce a record_protocol_version beside + %% the negotiated_version. + case StateName of + State when State =:= hello orelse + State =:= start -> + [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]; + _ -> + State0#state.connection_env#connection_env.negotiated_version + end, + #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states, + case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(StateName, State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_record_buffer = Buf1, + tls_cipher_texts = CT1}}); + #alert{} = Alert -> + handle_record_alert(Alert, State0) + end. + +next_record(_, #state{handshake_env = + #handshake_env{unprocessed_handshake_events = N} = HsEnv} + = State) when N > 0 -> + {no_record, State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; +next_record(_, #state{protocol_buffers = + #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, + connection_states = ConnectionStates, + ssl_options = #{padding_check := Check}} = State) -> + next_record(State, CipherTexts, ConnectionStates, Check); +next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true} + } = State) -> + %% If ssl application user is not reading data wait to activate socket + flow_ctrl(State); + +next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true} + } = State) -> + activate_socket(State); +next_record(_, 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 +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = undefined} = State) when Size =/= 0 -> + %% Passive mode wait for new recv request or socket activation + %% that is preserv some tcp back pressure by waiting to activate + %% socket + {no_record, State}; +%%%%%%%%%% A packet mode is set and socket is passive %%%%%%%%%% +flow_ctrl(#state{socket_options = #socket_options{active = false, + packet = Packet}} = State) + when ((Packet =/= 0) andalso (Packet =/= raw)) -> + %% We need more data to complete the packet. + activate_socket(State); +%%%%%%%%% No packet mode set and socket is passive %%%%%%%%%%%% +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = 0} = State) when Size == 0 -> + %% Passive mode no available bytes, get some + activate_socket(State); +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = 0} = State) when Size =/= 0 -> + %% There is data in the buffer to deliver + {no_record, State}; +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = BytesToRead} = State) when (BytesToRead > 0) -> + case (Size >= BytesToRead) of + true -> %% There is enough data bufferd + {no_record, State}; + false -> %% We need more data to complete the delivery of <BytesToRead> size + activate_socket(State) + end; +%%%%%%%%%%% Active mode or more data needed %%%%%%%%%% +flow_ctrl(State) -> + activate_socket(State). + + +activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, + static_env = #static_env{socket = Socket, + close_tag = CloseTag, + transport_cb = Transport} + } = State) -> + case tls_socket:setopts(Transport, Socket, [{active, N}]) of + ok -> + {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, Socket}, + {no_record, State} + end. + +%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one +%% +next_record(State, CipherTexts, ConnectionStates, Check) -> + next_record(State, CipherTexts, ConnectionStates, Check, []). +%% +next_record(#state{connection_env = #connection_env{negotiated_version = {3,4} = Version}} = State, + [CT|CipherTexts], ConnectionStates0, Check, Acc) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> + case CipherTexts of + [] -> + %% End of cipher texts - build and deliver an ?APPLICATION_DATA record + %% from the accumulated fragments + next_record_done(State, [], ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + [_|_] -> + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) + end; + {Record, ConnectionStates} when Acc =:= [] -> + %% Singelton non-?APPLICATION_DATA record - deliver + next_record_done(State, CipherTexts, ConnectionStates, Record); + {_Record, _ConnectionStates_to_forget} -> + %% Not ?APPLICATION_DATA but we have accumulated fragments + %% -> build an ?APPLICATION_DATA record with concatenated fragments + %% and forget about decrypting this record - we'll decrypt it again next time + %% Will not work for stream ciphers + next_record_done(State, [CT|CipherTexts], ConnectionStates0, + #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}); + #alert{} = Alert -> + Alert + end; +next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, + [#ssl_tls{type = ?APPLICATION_DATA} = CT |CipherTexts], ConnectionStates0, Check, Acc) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {#ssl_tls{type = ?APPLICATION_DATA, fragment = Fragment}, ConnectionStates} -> + case CipherTexts of + [] -> + %% End of cipher texts - build and deliver an ?APPLICATION_DATA record + %% from the accumulated fragments + next_record_done(State, [], ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc, [Fragment]))}); + [_|_] -> + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]) + end; + #alert{} = Alert -> + Alert + end; +next_record(State, CipherTexts, ConnectionStates, _, [_|_] = Acc) -> + next_record_done(State, CipherTexts, ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, + fragment = iolist_to_binary(lists:reverse(Acc))}); +next_record(#state{connection_env = #connection_env{negotiated_version = Version}} = State, + [CT|CipherTexts], ConnectionStates0, Check, []) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {Record, ConnectionStates} -> + %% Singelton non-?APPLICATION_DATA record - deliver + next_record_done(State, CipherTexts, ConnectionStates, Record); + #alert{} = Alert -> + Alert + end. + +next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, ConnectionStates, Record) -> + {Record, + State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, + connection_states = ConnectionStates}}. + +%% Special version handling for TLS 1.3 clients: +%% In the shared state 'init' negotiated_version is set to requested version and +%% that is expected by the legacy part of the state machine. However, in order to +%% be able to process new TLS 1.3 extensions, the effective version shall be set +%% {3,4}. +%% When highest supported version is {3,4} the negotiated version is set to {3,3}. +effective_version({3,3} , #{versions := [Version|_]}, client) when Version >= {3,4} -> + Version; +%% Use highest supported version during startup (TLS server, all versions). +effective_version(undefined, #{versions := [Version|_]}, _) -> + Version; +%% Use negotiated version in all other cases. +effective_version(Version, _, _) -> + Version. + +assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, + #{max_handshake_size := Max}) when + Length =< Max -> + case size(Rest) of + N when N < Length -> + true; + N when N > Length -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + too_big_handshake_data)); + _ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data)) + end; +assert_buffer_sanity(Bin, _) -> + case size(Bin) of + N when N < 3 -> + true; + _ -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, + malformed_handshake_data)) + end. + +decode_alerts(Bin) -> + ssl_alert:decode(Bin). + +handle_alerts([], Result) -> + Result; +handle_alerts(_, {stop, _, _} = Stop) -> + Stop; +handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], + {next_state, connection = StateName, #state{connection_env = CEnv, + socket_options = #socket_options{active = false}, + start_or_recv_from = From} = State}) when From == undefined -> + {next_state, StateName, State#state{connection_env = CEnv#connection_env{terminated = true}}}; +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)); +handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> + handle_alerts(Alerts, ssl_gen_statem:handle_alert(Alert, StateName, State)). + +handle_record_alert(Alert, _) -> + Alert. + diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index e1cc27069b..d7c899c7cf 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -325,28 +325,29 @@ handle_client_hello(Version, signature_algs := SupportedHashSigns, eccs := SupportedECCs, honor_ecc_order := ECCOrder} = SslOpts, - {SessIdTracker, Session0, ConnectionStates0, Cert, _}, + {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _}, Renegotiation) -> + OwnCert = ssl_handshake:select_own_cert(OwnCerts), case tls_record:is_acceptable_version(Version, Versions) of true -> Curves = maps:get(elliptic_curves, HelloExt, undefined), ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, Cert, Version), + ClientHashSigns, SupportedHashSigns, OwnCert, Version), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, SessIdTracker, Session0#session{ecc = ECCCurve}, - Version, SslOpts, Cert), + Version, SslOpts, OwnCert), case CipherSuite of no_suite -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers); _ -> #{key_exchange := KeyExAlg} = ssl_cipher_format:suite_bin_to_map(CipherSuite), case ssl_handshake:select_hashsign({ClientHashSigns, ClientSignatureSchemes}, - Cert, KeyExAlg, + OwnCert, KeyExAlg, SupportedHashSigns, Version) of #alert{} = Alert -> diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index e2af29889f..dbde7ad476 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -178,8 +178,14 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) -> MaxFragEnum -> E1#{max_frag_enum => MaxFragEnum} end, + E = case HandshakeEnv#handshake_env.sni_guided_cert_selection of + false -> + E2; + true -> + E2#{sni => #sni{hostname = ""}} + end, #encrypted_extensions{ - extensions = E2 + extensions = E }. @@ -235,7 +241,11 @@ filter_tls13_algs(Algo) -> %% opaque certificate_request_context<0..2^8-1>; %% CertificateEntry certificate_list<0..2^24-1>; %% } Certificate; -certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> +certificate(undefined, _, _, _, client) -> + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = []}}; +certificate([OwnCert], CertDbHandle, CertDbRef, _CRContext, Role) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> CertList = chain_to_cert_list(Chain), @@ -257,8 +267,12 @@ certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> {ok, #certificate_1_3{ certificate_request_context = <<>>, certificate_list = []}} - end. - + end; +certificate([_,_| _] = Chain, _,_,_,_) -> + CertList = chain_to_cert_list(Chain), + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = CertList}}. certificate_verify(PrivateKey, SignatureScheme, #state{connection_states = ConnectionStates, @@ -575,39 +589,41 @@ build_content(Context, THash) -> %% TLS Server do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, - extensions = Extensions} = _Hello, - #state{connection_states = ConnectionStates0, - ssl_options = #{ciphers := ServerCiphers, + extensions = Extensions} = Hello, + #state{ssl_options = #{ciphers := ServerCiphers, signature_algs := ServerSignAlgs, supported_groups := ServerGroups0, alpn_preferred_protocols := ALPNPreferredProtocols, - honor_cipher_order := HonorCipherOrder}, - session = #session{own_certificate = Cert}} = State0) -> - + keep_secrets := KeepSecrets, + honor_cipher_order := HonorCipherOrder}} = State0) -> + SNI = maps:get(sni, Extensions, undefined), ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), - ClientGroups = get_supported_groups(ClientGroups0), - ServerGroups = 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)), - - CookieExt = maps:get(cookie, Extensions, undefined), - Cookie = get_cookie(CookieExt), - {Ref,Maybe} = maybe(), - try - Maybe(validate_cookie(Cookie, State0)), + 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)), + + CookieExt = maps:get(cookie, Extensions, undefined), + Cookie = get_cookie(CookieExt), + + #state{connection_states = ConnectionStates0, + session = #session{own_certificates = [Cert | _]}} = 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)), @@ -636,24 +652,30 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - State1 = case maps:get(max_frag_enum, Extensions, undefined) of + 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 = (State0#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum}, - State0#state{handshake_env = HsEnv1, + HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum}, + State1#state{handshake_env = HsEnv1, connection_states = ConnectionStates1}; _ -> - State0 + State1 end, - State2 = update_start_state(State1, - #{cipher => Cipher, - key_share => KeyShare, - session_id => SessionId, - group => Group, - sign_alg => SelectedSignAlg, - peer_public_key => ClientPubKey, - alpn => ALPNProtocol}), + State3 = if KeepSecrets =:= true -> + set_client_random(State2, Hello#client_hello.random); + true -> + State2 + end, + + State = 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 %% @@ -661,12 +683,12 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% message if it is able to find an acceptable set of parameters but the %% ClientHello does not contain sufficient information to proceed with %% the handshake. - case Maybe(send_hello_retry_request(State2, ClientPubKey, KeyShare, SessionId)) of + case Maybe(send_hello_retry_request(State, ClientPubKey, KeyShare, SessionId)) of {_, start} = NextStateTuple -> NextStateTuple; {_, negotiated} = NextStateTuple -> %% Exclude any incompatible PSKs. - PSK = Maybe(handle_pre_shared_key(State2, OfferedPSKs, Cipher)), + PSK = Maybe(handle_pre_shared_key(State, OfferedPSKs, Cipher)), Maybe(session_resumption(NextStateTuple, PSK)) end catch @@ -680,6 +702,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, #state{static_env = #static_env{role = client, host = Host, port = Port, + protocol_cb = Connection, transport_cb = Transport, socket = Socket}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, @@ -690,15 +713,15 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, use_ticket := UseTicket, session_tickets := SessionTickets, log_level := LogLevel} = SslOpts, - session = #session{own_certificate = Cert} = Session0, + session = #session{own_certificates = OwnCerts} = Session0, connection_states = ConnectionStates0 } = State0) -> - ClientGroups = get_supported_groups(ClientGroups0), - CookieExt = maps:get(cookie, Extensions, undefined), - Cookie = get_cookie(CookieExt), - {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), @@ -722,7 +745,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, 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, Cert, ClientKeyShare, + SessionId, Renegotiation, OwnCerts, ClientKeyShare, TicketData, OcspNonce), %% Echo cookie received in HelloRetryrequest Hello1 = maybe_add_cookie_extension(Cookie, Hello0), @@ -742,7 +765,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, Hello = tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0, TicketData, NegotiatedVersion), {BinMsg0, ConnectionStates, HHistory} = - tls_connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0), + Connection:encode_handshake(Hello, NegotiatedVersion, ConnectionStates0, HHistory0), %% D.4. Middlebox Compatibility Mode {#state{handshake_env = HsEnv} = State3, BinMsg} = @@ -767,6 +790,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, do_negotiated({start_handshake, PSK0}, #state{connection_states = ConnectionStates0, + static_env = #static_env{protocol_cb = Connection}, session = #session{session_id = SessionId, ecc = SelectedGroup, dh_public_value = ClientPublicKey}, @@ -783,10 +807,10 @@ do_negotiated({start_handshake, PSK0}, try %% Create server_hello ServerHello = server_hello(server_hello, SessionId, KeyShare, PSK0, ConnectionStates0), - State1 = tls_connection:queue_handshake(ServerHello, State0), + State1 = Connection:queue_handshake(ServerHello, State0), %% D.4. Middlebox Compatibility Mode State2 = maybe_queue_change_cipher_spec(State1, last), - {State3, _} = tls_connection:send_handshake_flight(State2), + {State3, _} = Connection:send_handshake_flight(State2), PSK = get_pre_shared_key(PSK0, HKDF), @@ -800,7 +824,7 @@ do_negotiated({start_handshake, PSK0}, EncryptedExtensions = encrypted_extensions(State5), %% Encode EncryptedExtensions - State6 = tls_connection:queue_handshake(EncryptedExtensions, State5), + State6 = Connection:queue_handshake(EncryptedExtensions, State5), %% Create and send CertificateRequest ({verify, verify_peer}) {State7, NextState} = maybe_send_certificate_request(State6, SslOpts, PSK0), @@ -815,10 +839,10 @@ do_negotiated({start_handshake, PSK0}, Finished = finished(State9), %% Encode Finished - State10= tls_connection:queue_handshake(Finished, State9), + State10= Connection:queue_handshake(Finished, State9), %% Send first flight - {State, _} = tls_connection:send_handshake_flight(State10), + {State, _} = Connection:send_handshake_flight(State10), {State, NextState} @@ -874,34 +898,27 @@ do_wait_finished(#finished{verify_data = VerifyData}, end; %% TLS Client do_wait_finished(#finished{verify_data = VerifyData}, - #state{static_env = #static_env{role = client}} = State0) -> - + #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), - %% Maybe send Certificate + CertificateVerify State2 = Maybe(maybe_queue_cert_cert_cv(State1)), - Finished = finished(State2), - %% Encode Finished - State3 = tls_connection:queue_handshake(Finished, State2), - + State3 = Connection:queue_handshake(Finished, State2), %% Send first flight - {State4, _} = tls_connection:send_handshake_flight(State3), - + {State4, _} = Connection:send_handshake_flight(State3), State5 = calculate_traffic_secrets(State4), State6 = maybe_calculate_resumption_master_secret(State5), State7 = forget_master_secret(State6), - %% Configure traffic keys ssl_record:step_encryption_state(State7) - catch {Ref, #alert{} = Alert} -> Alert @@ -916,14 +933,15 @@ do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, supported_groups := ClientGroups0, session_tickets := SessionTickets, use_ticket := UseTicket}} = State0) -> - ClientGroups = get_supported_groups(ClientGroups0), - ServerKeyShare0 = maps:get(key_share, Extensions, undefined), - ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined), - SelectedIdentity = get_selected_identity(ServerPreSharedKey), - ClientKeyShare = get_key_shares(ClientKeyShare0), - + {Ref,Maybe} = maybe(), 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)), @@ -1108,12 +1126,13 @@ maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) - {ok, State}; maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, session = #session{session_id = _SessionId, - own_certificate = OwnCert}, + 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, @@ -1122,11 +1141,10 @@ maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, {Ref,Maybe} = maybe(), try %% Create Certificate - Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)), + Certificate = Maybe(certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, client)), %% Encode Certificate - State1 = tls_connection:queue_handshake(Certificate, State0), - + State1 = Connection:queue_handshake(Certificate, State0), %% Maybe create and queue CertificateVerify State = Maybe(maybe_queue_cert_verify(Certificate, State1)), {ok, State} @@ -1144,12 +1162,13 @@ maybe_queue_cert_verify(_Certificate, #state{connection_states = _ConnectionStates0, session = #session{sign_alg = SignatureScheme}, connection_env = #connection_env{private_key = CertPrivateKey}, - static_env = #static_env{role = client} + static_env = #static_env{role = client, + protocol_cb = Connection} } = State) -> {Ref,Maybe} = maybe(), try CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)), - {ok, tls_connection:queue_handshake(CertificateVerify, State)} + {ok, Connection:queue_handshake(CertificateVerify, State)} catch {Ref, #alert{} = Alert} -> {error, Alert} @@ -1182,15 +1201,16 @@ compare_verify_data(_, _) -> {error, ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)}. -send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, +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 = tls_connection:queue_handshake(ServerHello, State1), + State2 = Connection:queue_handshake(ServerHello, State1), %% D.4. Middlebox Compatibility Mode State3 = maybe_queue_change_cipher_spec(State2, last), - {State4, _} = tls_connection:send_handshake_flight(State3), + {State4, _} = Connection:send_handshake_flight(State3), %% Update handshake history State5 = replace_ch1_with_message_hash(State4), @@ -1216,22 +1236,23 @@ maybe_send_certificate_request(State, _, PSK) when PSK =/= undefined -> {State, wait_finished}; maybe_send_certificate_request(State, #{verify := verify_none}, _) -> {State, wait_finished}; -maybe_send_certificate_request(State, #{verify := verify_peer, - signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert}, _) -> +maybe_send_certificate_request(#state{static_env = #static_env{protocol_cb = Connection}} = State, + #{verify := verify_peer, + signature_algs := SignAlgs, + signature_algs_cert := SignAlgsCert}, _) -> CertificateRequest = certificate_request(SignAlgs, SignAlgsCert), - {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}. - + {Connection:queue_handshake(CertificateRequest, State), wait_cert}. maybe_send_certificate(State, PSK) when PSK =/= undefined -> {ok, State}; -maybe_send_certificate(#state{session = #session{own_certificate = OwnCert}, +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(OwnCert, CertDbHandle, CertDbRef, <<>>, server) of + case certificate(OwnCerts, CertDbHandle, CertDbRef, <<>>, server) of {ok, Certificate} -> - {ok, tls_connection:queue_handshake(Certificate, State)}; + {ok, Connection:queue_handshake(Certificate, State)}; Error -> Error end. @@ -1240,11 +1261,12 @@ maybe_send_certificate(#state{session = #session{own_certificate = OwnCert}, maybe_send_certificate_verify(State, PSK) when PSK =/= undefined -> {ok, State}; maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme}, + static_env = #static_env{protocol_cb = Connection}, connection_env = #connection_env{ private_key = CertPrivateKey}} = State, _) -> case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of {ok, CertificateVerify} -> - {ok, tls_connection:queue_handshake(CertificateVerify, State)}; + {ok, Connection:queue_handshake(CertificateVerify, State)}; Error -> Error end. @@ -1266,14 +1288,17 @@ maybe_send_session_ticket(#state{ssl_options = #{session_tickets := disabled}} = maybe_send_session_ticket(State, 0) -> State; maybe_send_session_ticket(#state{connection_states = ConnectionStates, - static_env = #static_env{trackers = Trackers}} = State0, N) -> + 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, _} = tls_connection:send_handshake(Ticket, State0), + {State, _} = Connection:send_handshake(Ticket, State0), maybe_send_session_ticket(State, N - 1). create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) -> @@ -1296,12 +1321,13 @@ create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) -> [BinChangeCipher]. process_certificate_request(#certificate_request_1_3{}, - #state{session = #session{own_certificate = undefined}} = State) -> + #state{session = #session{own_certificates = undefined}} = State) -> {ok, {State#state{client_certificate_requested = true}, wait_cert}}; process_certificate_request(#certificate_request_1_3{ extensions = Extensions}, - #state{session = #session{own_certificate = Cert} = Session} = State) -> + #state{session = #session{own_certificates = [Cert|_]} = Session} = + State) -> ServerSignAlgs = get_signature_scheme_list( maps:get(signature_algs, Extensions, undefined)), ServerSignAlgsCert = get_signature_scheme_list( @@ -1316,7 +1342,7 @@ process_certificate_request(#certificate_request_1_3{ {error, _} -> %% Certificate not supported: send empty certificate in state 'wait_finished' {ok, {State#state{client_certificate_requested = true, - session = Session#session{own_certificate = undefined}}, wait_cert}} + session = Session#session{own_certificates = undefined}}, wait_cert}} end. @@ -1331,7 +1357,6 @@ process_certificate(#certificate_1_3{ 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 @@ -1377,57 +1402,31 @@ update_encryption_state(client, State) -> validate_certificate_chain(CertEntries, CertDbHandle, CertDbRef, #{server_name_indication := ServerNameIndication, partial_chain := PartialChain, - verify_fun := VerifyFun, - customize_hostname_check := CustomizeHostnameCheck, - crl_check := CrlCheck, - log_level := LogLevel, - depth := Depth, - ocsp_responder_certs := OcspResponderCerts, - signature_algs := SignAlgs, - signature_algs_cert := SignAlgsCert + ocsp_responder_certs := OcspResponderCerts } = SslOptions, CRLDbHandle, Role, Host, OcspState0) -> {Certs, CertExt, OcspState} = split_cert_entries(CertEntries, OcspState0), ServerName = ssl_handshake:server_name(ServerNameIndication, Host, Role), - [PeerCert | ChainCerts ] = Certs, + [PeerCert | _ChainCerts ] = Certs, try - {TrustedCert, CertPath} = - ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef, + PathsAndAnchors = + ssl_certificate:trusted_cert_and_paths(Certs, CertDbHandle, CertDbRef, PartialChain), - 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(SignAlgs), - signature_algs_cert => filter_tls13_algs(SignAlgsCert), - version => {3,4}, - issuer => TrustedCert, - cert_ext => CertExt, - ocsp_responder_certs => OcspResponderCerts, - ocsp_state => OcspState - }, - CertPath, LogLevel), - Options = [{max_path_length, Depth}, - {verify_fun, ValidationFunAndState}], - case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of - {ok, {PublicKeyInfo,_}} -> - {ok, {PeerCert, PublicKeyInfo}}; - {error, Reason} -> - {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts, - SslOptions, Options, - CertDbHandle, CertDbRef)} - end - catch - error:{badmatch,{error, {asn1, Asn1Reason}}} -> - %% ASN-1 decode of certificate somehow failed - {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})}; - error:OtherReason -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})} - end. + case path_validate(PathsAndAnchors, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + {3, 4}, SslOptions, #{cert_ext => CertExt, + ocsp_state => OcspState, + ocsp_responder_certs => OcspResponderCerts}) of + {ok, {PublicKeyInfo,_}} -> + {ok, {PeerCert, PublicKeyInfo}}; + {error, Reason} -> + {ok, ssl_handshake:path_validation_alert(Reason)} + end + catch + error:{badmatch,{error, {asn1, Asn1Reason}}} -> + %% ASN-1 decode of certificate somehow failed + {error, ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason})}; + error:OtherReason -> + {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {unexpected_error, OtherReason})} + end. store_peer_cert(#state{session = Session, handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) -> @@ -1524,7 +1523,9 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK, ReadFinishedKey = tls_v1:finished_key(ClientHSTrafficSecret, HKDFAlgo), WriteFinishedKey = tls_v1:finished_key(ServerHSTrafficSecret, HKDFAlgo), - update_pending_connection_states(State0, HandshakeSecret, undefined, + State1 = maybe_store_handshake_traffic_secret(State0, ClientHSTrafficSecret, ServerHSTrafficSecret), + + update_pending_connection_states(State1, HandshakeSecret, undefined, undefined, undefined, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey). @@ -1681,6 +1682,36 @@ overwrite_master_secret(ConnectionState = #{security_parameters := SecurityParam ConnectionState#{security_parameters => SecurityParameters}. +set_client_random(#state{connection_states = + #{pending_read := PendingRead, + pending_write := PendingWrite, + current_read := CurrentRead, + current_write := CurrentWrite} = CS} = State, ClientRandom) -> + State#state{connection_states = CS#{pending_read => overwrite_client_random(PendingRead, ClientRandom), + pending_write => overwrite_client_random(PendingWrite, ClientRandom), + current_read => overwrite_client_random(CurrentRead, ClientRandom), + current_write => overwrite_client_random(CurrentWrite, ClientRandom)}}. + + +overwrite_client_random(ConnectionState = #{security_parameters := SecurityParameters0}, ClientRandom) -> + SecurityParameters = SecurityParameters0#security_parameters{client_random = ClientRandom}, + ConnectionState#{security_parameters => SecurityParameters}. + + +maybe_store_handshake_traffic_secret(#state{connection_states = + #{pending_read := PendingRead} = CS, + ssl_options = #{keep_secrets := true}} = State, + ClientHSTrafficSecret, ServerHSTrafficSecret) -> + PendingRead1 = store_handshake_traffic_secret(PendingRead, ClientHSTrafficSecret, ServerHSTrafficSecret), + State#state{connection_states = CS#{pending_read => PendingRead1}}; +maybe_store_handshake_traffic_secret(State, _, _) -> + State. + +store_handshake_traffic_secret(ConnectionState, ClientHSTrafficSecret, ServerHSTrafficSecret) -> + ConnectionState#{client_handshake_traffic_secret => ClientHSTrafficSecret, + server_handshake_traffic_secret => ServerHSTrafficSecret}. + + update_pending_connection_states(#state{ static_env = #static_env{role = server}, connection_states = @@ -2249,8 +2280,10 @@ get_signature_scheme_list(#signature_algorithms{ lists:filter(fun (E) -> is_atom(E) andalso E =/= unassigned end, ClientSignatureSchemes). +get_supported_groups(undefined = Groups) -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, {supported_groups, Groups})}; get_supported_groups(#supported_groups{supported_groups = Groups}) -> - Groups. + {ok, Groups}. get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; @@ -2523,3 +2556,53 @@ process_ticket(Bin, N) when is_binary(Bin) -> %% (see Section 4.6.1), modulo 2^32. obfuscate_ticket_age(TicketAge, AgeAdd) -> (TicketAge + AgeAdd) rem round(math:pow(2,32)). + +path_validate([{TrustedCert, Path}], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, + CRLDbHandle, Version, SslOptions, CertExt); +path_validate([{TrustedCert, Path} | Rest], ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) -> + case path_validation(TrustedCert, Path, ServerName, + Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) of + {ok, _} = Result -> + Result; + {error, _} -> + path_validate(Rest, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, + Version, SslOptions, CertExt) + end. + +path_validation(TrustedCert, Path, ServerName, Role, CertDbHandle, CertDbRef, CRLDbHandle, Version, + #{verify_fun := VerifyFun, + customize_hostname_check := CustomizeHostnameCheck, + crl_check := CrlCheck, + log_level := LogLevel, + signature_algs := SignAlgos, + signature_algs_cert := SignAlgosCert, + depth := Depth}, + #{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}, + {verify_fun, ValidationFunAndState}], + public_key:pkix_path_validation(TrustedCert, Path, Options). diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 51cf0e6073..3d2cafa24c 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -272,13 +272,13 @@ connection({call, From}, dist_get_tls_socket, socket = Socket, connection_pid = Pid, trackers = Trackers}} = StateData) -> - TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Trackers), + TLSSocket = tls_gen_connection:socket([Pid, self()], Transport, Socket, Trackers), {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{static = #static{connection_pid = Pid} = Static} = StateData) -> false = erlang:dist_ctrl_set_opt(DHandle, get_size, true), ok = erlang:dist_ctrl_input_handler(DHandle, Pid), - ok = ssl_connection:dist_handshake_complete(Pid, DHandle), + ok = ssl_gen_statem:dist_handshake_complete(Pid, DHandle), %% From now on we execute on normal priority process_flag(priority, normal), {keep_state, StateData#data{static = Static#static{dist_handle = DHandle}}, @@ -454,7 +454,7 @@ send_application_data(Data, From, StateName, {next_event, internal, {key_update, From}}, {next_event, internal, {application_packets, From, Data}}]}; renegotiate -> - ssl_connection:internal_renegotiation(Pid, ConnectionStates0), + tls_dtls_connection:internal_renegotiation(Pid, ConnectionStates0), {next_state, handshake, StateData0, [{next_event, internal, {application_packets, From, Data}}]}; chunk_and_key_update -> @@ -513,7 +513,7 @@ send_post_handshake_data(Handshake, From, StateName, maybe_update_cipher_key(#data{connection_states = ConnectionStates0, static = Static0} = StateData, #key_update{}) -> - ConnectionStates = tls_connection:update_cipher_key(current_write, ConnectionStates0), + ConnectionStates = tls_connection_1_3:update_cipher_key(current_write, ConnectionStates0), Static = Static0#static{bytes_sent = 0}, StateData#data{connection_states = ConnectionStates, static = Static}; diff --git a/lib/ssl/src/tls_server_session_ticket_sup.erl b/lib/ssl/src/tls_server_session_ticket_sup.erl index 7ee4bb7b2c..bdde94ecea 100644 --- a/lib/ssl/src/tls_server_session_ticket_sup.erl +++ b/lib/ssl/src/tls_server_session_ticket_sup.erl @@ -27,26 +27,34 @@ -behaviour(supervisor). %% API --export([start_link/0, start_link_dist/0]). --export([start_child/1, start_child_dist/1]). +-export([start_link/0, + start_link_dist/0]). +-export([start_child/1, + start_child_dist/1]). %% Supervisor callback --export([init/1]). +-export([init/1, + sup_name/1]). %%%========================================================================= %%% API %%%========================================================================= start_link() -> - supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []). + supervisor:start_link({local, sup_name(normal)}, ?MODULE, []). start_link_dist() -> - supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []). + supervisor:start_link({local, sup_name(dist)}, ?MODULE, []). start_child(Args) -> - supervisor:start_child(tracker_name(normal), Args). + supervisor:start_child(sup_name(normal), Args). start_child_dist(Args) -> - supervisor:start_child(tracker_name(dist), Args). + supervisor:start_child(sup_name(dist), Args). + +sup_name(normal) -> + ?MODULE; +sup_name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "_dist"). %%%========================================================================= %%% Supervisor callback @@ -66,7 +74,3 @@ init(_O) -> ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. -tracker_name(normal) -> - ?MODULE; -tracker_name(dist) -> - list_to_atom(atom_to_list(?MODULE) ++ "dist"). diff --git a/lib/ssl/src/tls_server_sup.erl b/lib/ssl/src/tls_server_sup.erl index 5d3278fe32..b2f011f221 100644 --- a/lib/ssl/src/tls_server_sup.erl +++ b/lib/ssl/src/tls_server_sup.erl @@ -47,10 +47,12 @@ init([]) -> ListenTracker = listen_options_tracker_child_spec(), SessionTracker = tls_server_session_child_spec(), Pre_1_3SessionTracker = ssl_server_session_child_spec(), - + Pre_1_3UpgradeSessionTracker = ssl_upgrade_server_session_child_spec(), + {ok, {{one_for_all, 10, 3600}, [ListenTracker, SessionTracker, - Pre_1_3SessionTracker + Pre_1_3SessionTracker, + Pre_1_3UpgradeSessionTracker ]}}. @@ -86,3 +88,12 @@ ssl_server_session_child_spec() -> Modules = [ssl_server_session_cache_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +ssl_upgrade_server_session_child_spec() -> + Name = ssl_upgrade_server_session_cache_sup, + StartFunc = {ssl_upgrade_server_session_cache_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_upgrade_server_session_cache_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index e2ec4e2f0a..48f1935e81 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -49,7 +49,7 @@ start_link/3, terminate/2, inherit_tracker/3, - session_id_tracker/1, + session_id_tracker/2, emulated_socket_options/2, get_emulated_opts/1, set_emulated_opts/2, @@ -63,7 +63,7 @@ -record(state, { emulated_opts, - port, + listen_monitor, ssl_opts }). @@ -84,7 +84,7 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _}, %% TLS-1.3 session handling {ok, SessionHandler} = session_tickets_tracker(LifeTime, TicketStoreSize, SslOpts), %% PRE TLS-1.3 session handling - {ok, SessionIdHandle} = session_id_tracker(SslOpts), + {ok, SessionIdHandle} = session_id_tracker(ListenSocket, SslOpts), Trackers = [{option_tracker, Tracker}, {session_tickets_tracker, SessionHandler}, {session_id_tracker, SessionIdHandle}], Socket = #sslsocket{pid = {ListenSocket, Config#config{trackers = Trackers}}}, @@ -108,7 +108,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Trackers}, self(), CbInfo], case tls_connection_sup:start_child(ConnArgs) of {ok, Pid} -> - ssl_connection:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers); + ssl_gen_statem:socket_control(ConnectionCb, Socket, [Pid, Sender], Transport, Trackers); {error, Reason} -> {error, Reason} end; @@ -122,7 +122,7 @@ upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo, ok = setopts(Transport, Socket, tls_socket:internal_inet_values()), case peername(Transport, Socket) of {ok, {Address, Port}} -> - ssl_connection:connect(ConnectionCb, Address, Port, Socket, + ssl_gen_statem:connect(ConnectionCb, Address, Port, Socket, {SslOptions, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); @@ -137,7 +137,7 @@ connect(Address, Port, {Transport, _, _, _, _} = CbInfo, try Transport:connect(Address, Port, SocketOpts, Timeout) of {ok, Socket} -> - ssl_connection:connect(ConnetionCb, Address, Port, Socket, + ssl_gen_statem:connect(ConnetionCb, Address, Port, Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, self(), CbInfo, Timeout); @@ -264,24 +264,37 @@ inherit_tracker(ListenSocket, EmOpts, #{erl_dist := true} = SslOpts) -> session_tickets_tracker(_, _, #{erl_dist := false, session_tickets := disabled}) -> {ok, disabled}; -session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := false, - session_tickets := Mode, - anti_replay := AntiReplay}) -> +session_tickets_tracker(Lifetime, TicketStoreSize, + #{erl_dist := false, + session_tickets := Mode, + anti_replay := AntiReplay}) -> tls_server_session_ticket_sup:start_child([Mode, Lifetime, TicketStoreSize, AntiReplay]); -session_tickets_tracker(Lifetime, TicketStoreSize, #{erl_dist := true, - session_tickets := Mode}) -> - tls_server_session_ticket_sup:start_child_dist([Mode, Lifetime, TicketStoreSize]). - -session_id_tracker(#{versions := [{3,4}]}) -> +session_tickets_tracker(Lifetime, TicketStoreSize, + #{erl_dist := true, + session_tickets := Mode, + anti_replay := AntiReplay}) -> + 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([Mode, Lifetime, TicketStoreSize, AntiReplay]); + 1 -> + [{_,Child,_, _}] = supervisor:which_children(SupName), + {ok, Child} + end. +session_id_tracker(_, #{versions := [{3,4}]}) -> {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(#{erl_dist := false}) -> - ssl_server_session_cache_sup:start_child(ssl_server_session_cache_sup:session_opts()); -session_id_tracker(#{erl_dist := true}) -> - ssl_server_session_cache_sup:start_child_dist(ssl_server_session_cache_sup:session_opts()). - +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). + get_emulated_opts(TrackerPid) -> call(TrackerPid, get_emulated_opts). set_emulated_opts(TrackerPid, InetValues) -> @@ -303,10 +316,12 @@ start_link(Port, SockOpts, SslOpts) -> %% %% Description: Initiates the server %%-------------------------------------------------------------------- -init([Port, Opts, SslOpts]) -> +init([Listen, Opts, SslOpts]) -> process_flag(trap_exit, true), - true = link(Port), - {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}. + Monitor = monitor_listen(Listen), + {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), + listen_monitor = Monitor, + ssl_opts = SslOpts}}. %%-------------------------------------------------------------------- -spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. @@ -351,7 +366,7 @@ handle_cast(_, State)-> %% %% Description: Handling all non call/cast messages %%------------------------------------------------------------------- -handle_info({'EXIT', Port, _}, #state{port = Port} = State) -> +handle_info({'DOWN', Monitor, _, _, _}, #state{listen_monitor = Monitor} = State) -> {stop, normal, State}. @@ -380,6 +395,9 @@ code_change(_OldVsn, State, _Extra) -> call(Pid, Msg) -> gen_server:call(Pid, Msg, infinity). +monitor_listen(Listen) when is_port(Listen) -> + erlang:monitor(port, Listen). + split_options(Opts) -> split_options(Opts, emulated_options(), [], []). split_options([], _, SocketOpts, EmuOpts) -> diff --git a/lib/ssl/src/tls_sup.erl b/lib/ssl/src/tls_sup.erl index 25c1db0272..a425ae31e2 100644 --- a/lib/ssl/src/tls_sup.erl +++ b/lib/ssl/src/tls_sup.erl @@ -45,10 +45,10 @@ start_link() -> init([]) -> - TLSConnetionManager = tls_connection_manager_child_spec(), + TLSConnetionSup = tls_connection_child_spec(), ServerInstanceSup = server_instance_child_spec(), - {ok, {{one_for_one, 10, 3600}, [TLSConnetionManager, + {ok, {{one_for_one, 10, 3600}, [TLSConnetionSup, ServerInstanceSup ]}}. @@ -57,7 +57,7 @@ init([]) -> %%% Internal functions %%-------------------------------------------------------------------- -tls_connection_manager_child_spec() -> +tls_connection_child_spec() -> Name = tls_connection, StartFunc = {tls_connection_sup, start_link, []}, Restart = permanent, diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 8e6807d0ab..cbba413ee2 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -486,21 +486,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, -spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()]. suites(Minor) when Minor == 1; Minor == 2 -> - [ - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA - ]; + exclusive_suites(2); suites(3) -> exclusive_suites(3) ++ suites(2); @@ -518,36 +504,42 @@ exclusive_suites(3) -> [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM, + ?TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM, + ?TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, - ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, - - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - - ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - - ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + + ?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, @@ -564,19 +556,19 @@ exclusive_suites(3) -> ]; exclusive_suites(Minor) when Minor == 1; Minor == 2 -> [ - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA ]. signature_algs({3, 4}, HashSigns) -> diff --git a/lib/ssl/test/dtls_api_SUITE.erl b/lib/ssl/test/dtls_api_SUITE.erl index 85a31a19be..eb7a16e0f1 100644 --- a/lib/ssl/test/dtls_api_SUITE.erl +++ b/lib/ssl/test/dtls_api_SUITE.erl @@ -37,7 +37,19 @@ dtls_listen_close/0, dtls_listen_close/1, dtls_listen_reopen/0, - dtls_listen_reopen/1 + dtls_listen_reopen/1, + dtls_listen_two_sockets_1/0, + dtls_listen_two_sockets_1/1, + dtls_listen_two_sockets_2/0, + dtls_listen_two_sockets_2/1, + dtls_listen_two_sockets_3/0, + dtls_listen_two_sockets_3/1, + dtls_listen_two_sockets_4/0, + dtls_listen_two_sockets_4/1, + dtls_listen_two_sockets_5/0, + dtls_listen_two_sockets_5/1, + dtls_listen_two_sockets_6/0, + dtls_listen_two_sockets_6/1 ]). %%-------------------------------------------------------------------- @@ -59,7 +71,13 @@ api_tests() -> [ dtls_listen_owner_dies, dtls_listen_close, - dtls_listen_reopen + dtls_listen_reopen, + dtls_listen_two_sockets_1, + dtls_listen_two_sockets_2, + dtls_listen_two_sockets_3, + dtls_listen_two_sockets_4, + dtls_listen_two_sockets_5, + dtls_listen_two_sockets_6 ]. init_per_suite(Config0) -> @@ -84,6 +102,20 @@ init_per_group(GroupName, Config) -> end_per_group(GroupName, Config) -> ssl_test_lib:end_per_group(GroupName, Config). +init_per_testcase(Testcase, Config) + when Testcase =:= dtls_listen_two_sockets_1 orelse + Testcase =:= dtls_listen_two_sockets_5 orelse + Testcase =:= dtls_listen_two_sockets_6 -> + case ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,2}}]) of + {ok, S} -> + test_listen_on_all_interfaces(S, Config), + ssl:close(S), + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + maybe_skip_tc_on_windows(Testcase, Config); + {error, _} -> + {skip, "127.0.0.x address not available"} + end; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 10}), @@ -188,7 +220,107 @@ dtls_listen_reopen(Config) when is_list(Config) -> {ssl, Client2, "from server 2"} -> ssl:close(Client2) end. + +dtls_listen_two_sockets_1() -> + [{doc, "Test with two DTLS dockets: 127.0.0.2:Port, 127.0.0.3:Port"}]. +dtls_listen_two_sockets_1(_Config) when is_list(_Config) -> + {ok, S1} = ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,2}}]), + {ok, {_, Port}} = ssl:sockname(S1), + {ok, S2} = ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]), + ssl:close(S1), + ssl:close(S2), + ok. + +dtls_listen_two_sockets_2() -> + [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, <all_interfaces>:Port"}]. +dtls_listen_two_sockets_2(_Config) when is_list(_Config) -> + {ok, S1} = ssl:listen(0, [{protocol, dtls}]), + {ok, {_, Port}} = ssl:sockname(S1), + {error, already_listening} = + ssl:listen(Port, [{protocol, dtls}]), + ssl:close(S1), + ok. + +dtls_listen_two_sockets_3() -> + [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, <all_interfaces>:Port"}]. +dtls_listen_two_sockets_3(_Config) when is_list(_Config) -> + {ok, S1} = ssl:listen(0, [{protocol, dtls}]), + {ok, {_, Port}} = ssl:sockname(S1), + {error, already_listening} = + ssl:listen(Port, [{protocol, dtls}]), + ssl:close(S1), + {ok, S2} = ssl:listen(Port, [{protocol, dtls}]), + ssl:close(S2), + ok. + +dtls_listen_two_sockets_4() -> + [{doc, "Test with two DTLS dockets: process1 - <all_interfaces>:Port, process2 - <all_interfaces>:Port"}]. +dtls_listen_two_sockets_4(_Config) when is_list(_Config) -> + Test = self(), + Pid = spawn(fun() -> + {ok, S1} = ssl:listen(0, [{protocol, dtls}]), + {ok, {_, Port0}} = ssl:sockname(S1), + Test ! {self(), Port0} + end), + Port = + receive + {Pid, Port1} -> + Port1 + end, + {ok, S2} = + ssl:listen(Port, [{protocol, dtls}]), + ssl:close(S2), + ok. + +dtls_listen_two_sockets_5() -> + [{doc, "Test with two DTLS dockets: <all_interfaces>:Port, 127.0.0.3:Port"}]. +dtls_listen_two_sockets_5(_Config) when is_list(_Config) -> + {ok, S1} = ssl:listen(0, [{protocol, dtls}]), + {ok, {_, Port}} = ssl:sockname(S1), + {error, already_listening} = + ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]), + ssl:close(S1), + {ok, S2} = + ssl:listen(Port, [{protocol, dtls}, {ip, {127,0,0,3}}]), + {error, already_listening} = + ssl:listen(Port, [{protocol, dtls}]), + ssl:close(S2), + ok. + +dtls_listen_two_sockets_6() -> + [{doc, "Test with two DTLS dockets: 127.0.0.3:Port, 0.0.0.0:Port"}]. +dtls_listen_two_sockets_6(_Config) when is_list(_Config) -> + {ok, S1} = ssl:listen(0, [{protocol, dtls}, {ip, {127,0,0,3}}]), + {ok, {_, Port}} = ssl:sockname(S1), + {error, already_listening} = + ssl:listen(Port, [{protocol, dtls}, {ip, {0,0,0,0}}]), + ssl:close(S1), + ok. + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- +%% Helper function for init_per_testcase. +test_listen_on_all_interfaces(S0, Config) -> + {ok, {_, Port}} = ssl:sockname(S0), + case ssl:listen(Port, [{protocol, dtls}, {ip, {0,0,0,0}}]) of + {ok, S1} -> + ssl:close(S0), + ssl:close(S1), + {skip, "Testcase is not supported on this OS."}; + {error, _} -> + Config + end. + +maybe_skip_tc_on_windows(Testcase, Config) + when Testcase =:= dtls_listen_two_sockets_5 orelse + Testcase =:= dtls_listen_two_sockets_6 -> + case os:type() of + {win32, _} -> + {skip, "Testcase not supported in Windows"}; + _ -> + Config + end; +maybe_skip_tc_on_windows(_, Config) -> + Config. diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl index 6d8147a3df..eb823471c8 100644 --- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl +++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl @@ -82,10 +82,14 @@ aes_128_gcm_sha256/1, chacha20_poly1305_sha256/1, aes_128_ccm_sha256/1, - aes_128_ccm_8_sha256/1 + aes_128_ccm_8_sha256/1, + ecdhe_ecdsa_with_aes_128_ccm/1, + ecdhe_ecdsa_with_aes_256_ccm/1, + ecdhe_ecdsa_with_aes_128_ccm_8/1, + ecdhe_ecdsa_with_aes_256_ccm_8/1 ]). --define(DEFAULT_TIMEOUT, {seconds, 6}). +-define(DEFAULT_TIMEOUT, {seconds, 10}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -140,7 +144,11 @@ groups() -> ecdhe_ecdsa_aes_128_gcm, ecdhe_ecdsa_aes_256_cbc, ecdhe_ecdsa_aes_256_gcm, - ecdhe_ecdsa_chacha20_poly1305 + ecdhe_ecdsa_chacha20_poly1305, + ecdhe_ecdsa_with_aes_128_ccm, + ecdhe_ecdsa_with_aes_256_ccm, + ecdhe_ecdsa_with_aes_128_ccm_8, + ecdhe_ecdsa_with_aes_256_ccm_8 ]}, {rsa, [], [rsa_des_cbc, rsa_3des_ede_cbc, @@ -298,7 +306,8 @@ do_init_per_group(ecdhe_ecdsa = GroupName, Config) -> end; do_init_per_group(dhe_dss = GroupName, Config) -> PKAlg = proplists:get_value(public_keys, crypto:supports()), - case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) + andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of true -> init_certs(GroupName, Config); false -> @@ -306,7 +315,8 @@ do_init_per_group(dhe_dss = GroupName, Config) -> end; do_init_per_group(srp_dss = GroupName, Config) -> PKAlg = proplists:get_value(public_keys, crypto:supports()), - case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) of + case lists:member(dss, PKAlg) andalso lists:member(srp, PKAlg) + andalso (ssl_test_lib:openssl_dsa_suites() =/= []) of true -> init_certs(GroupName, Config); false -> @@ -339,11 +349,11 @@ do_init_per_group(dhe_rsa = GroupName, Config) -> end; do_init_per_group(rsa = GroupName, Config) -> PKAlg = proplists:get_value(public_keys, crypto:supports()), - case lists:member(rsa, PKAlg) of + case lists:member(rsa, PKAlg) andalso ssl_test_lib:openssl_support_rsa_kex() of true -> init_certs(GroupName, Config); false -> - {skip, "Missing SRP crypto support"} + {skip, "Missing RSA key exchange support"} end; do_init_per_group(dh_anon = GroupName, Config) -> PKAlg = proplists:get_value(public_keys, crypto:supports()), @@ -375,7 +385,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc; SupCiphers = proplists:get_value(ciphers, crypto:supports()), case lists:member(des_ede3, SupCiphers) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap({seconds, ?DEFAULT_TIMEOUT}), Config; _ -> {skip, "Missing 3DES crypto support"} @@ -478,6 +488,28 @@ init_per_testcase(aes_128_ccm_8_sha256, Config) -> {skip, "Missing AES_128_CCM_8 crypto support"} end; +init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_128_ccm; + TestCase == ecdhe_ecdsa_with_aes_128_ccm_8-> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_128_ccm, SupCiphers) of + true -> + ct:timetrap(?DEFAULT_TIMEOUT), + Config; + _ -> + {skip, "Missing AES_128_CCM crypto support"} + end; + +init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_256_ccm; + TestCase == ecdhe_ecdsa_with_aes_256_ccm_8 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_256_ccm, SupCiphers) of + true -> + ct:timetrap(?DEFAULT_TIMEOUT), + Config; + _ -> + {skip, "Missing AES_256_CCM crypto support"} + end; + init_per_testcase(TestCase, Config) -> Cipher = ssl_test_lib:test_cipher(TestCase, Config), SupCiphers = proplists:get_value(ciphers, crypto:supports()), @@ -728,6 +760,18 @@ ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) -> ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) -> run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config). + +ecdhe_ecdsa_with_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm', Config). + +ecdhe_ecdsa_with_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm', Config). + +ecdhe_ecdsa_with_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm_8', Config). + +ecdhe_ecdsa_with_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm_8', Config). %%-------------------------------------------------------------------- %% DHE_DSS -------------------------------------------------------- %%-------------------------------------------------------------------- @@ -897,7 +941,7 @@ run_ciphers_test(Kex, Cipher, Config) -> {skip, {not_sup, Kex, Cipher, Version}} end. -cipher_suite_test(CipherSuite, _Version, Config) -> +cipher_suite_test(CipherSuite, Version, Config) -> #{server_config := SOpts, client_config := COpts} = proplists:get_value(tls_config, Config), ServerOpts = ssl_test_lib:ssl_options(SOpts, Config), @@ -905,11 +949,17 @@ cipher_suite_test(CipherSuite, _Version, Config) -> ct:log("Testing CipherSuite ~p~n", [CipherSuite]), ct:log("Server Opts ~p~n", [ServerOpts]), ct:log("Client Opts ~p~n", [ClientOpts]), - ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, Config). - + case proplists:get_value(server_type, Config) of + erlang -> + ssl_test_lib:basic_test([{ciphers, ssl:cipher_suites(all, Version)} | COpts], + [{ciphers, [CipherSuite]} | SOpts], Config); + _ -> + ssl_test_lib:basic_test([{versions, [Version]}, {ciphers, [CipherSuite]} | COpts], + [{ciphers, ssl:cipher_suites(all, Version)} | SOpts], Config) + end. test_ciphers(Kex, Cipher, Version) -> - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version) ++ ssl:cipher_suites(anonymous, Version), + Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version), [{key_exchange, fun(Kex0) when (Kex0 == Kex) andalso (Version =/= 'tlsv1.3') -> true; (Kex0) when (Kex0 == any) andalso (Version == 'tlsv1.3') -> true; @@ -920,7 +970,7 @@ test_ciphers(Kex, Cipher, Version) -> (_) -> false end}]), ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), - OpenSSLCiphers = openssl_ciphers(), + OpenSSLCiphers = ssl_test_lib:openssl_ciphers(), ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), lists:filter(fun(C) -> ct:log("Cipher ~p~n", [C]), @@ -928,6 +978,3 @@ test_ciphers(Kex, Cipher, Version) -> end, Ciphers). -openssl_ciphers() -> - Str = os:cmd("openssl ciphers"), - string:split(string:strip(Str, right, $\n), ":", all). diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl index 7adfdf32a5..7e8d842f14 100644 --- a/lib/ssl/test/openssl_client_cert_SUITE.erl +++ b/lib/ssl/test/openssl_client_cert_SUITE.erl @@ -292,7 +292,8 @@ end_per_group(GroupName, Config) -> init_per_testcase(TestCase, Config) when TestCase == client_auth_empty_cert_accepted; TestCase == client_auth_empty_cert_rejected -> - Version = proplists:get_value(version,Config), + Version = ssl_test_lib:protocol_version(Config), + case Version of sslv3 -> %% Openssl client sends "No Certificate Reserved" warning ALERT diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl index 4402765ea2..e71bfc8e5c 100644 --- a/lib/ssl/test/openssl_server_cert_SUITE.erl +++ b/lib/ssl/test/openssl_server_cert_SUITE.erl @@ -275,7 +275,7 @@ init_per_group(ecdsa_1_3 = Group, Config0) -> COpts = proplists:get_value(client_ecdsa_opts, Config), SOpts = proplists:get_value(server_ecdsa_opts, Config), %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server - Version = proplists:get_value(version,Config), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), case Ciphers of [_|_] -> @@ -301,7 +301,7 @@ init_per_group(Group, Config0) when Group == dsa -> COpts = proplists:get_value(client_dsa_opts, Config), SOpts = proplists:get_value(server_dsa_opts, Config), %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server - Version = proplists:get_value(version,Config), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> true; (dhe_dss) -> diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl index 0fdc7d6dab..08369733dc 100644 --- a/lib/ssl/test/openssl_session_SUITE.erl +++ b/lib/ssl/test/openssl_session_SUITE.erl @@ -95,7 +95,11 @@ init_per_suite(Config0) -> try crypto:start() of ok -> ssl_test_lib:clean_start(), - ssl_test_lib:make_rsa_cert(Config0) + {ClientOpts, ServerOpts} = + ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()}, + {client_chain, ssl_test_lib:default_cert_chain_conf()}], + Config0, "openssl_session_SUITE"), + [{client_opts, ClientOpts}, {server_opts, ServerOpts} | Config0] catch _:_ -> {skip, "Crypto did not start"} end @@ -120,19 +124,24 @@ init_per_testcase(reuse_session_erlang_client, Config) -> ssl:start(), Config; init_per_testcase(reuse_session_erlang_server, Config) -> - Version = ssl_test_lib:protocol_version(Config), - case ssl_test_lib:is_dtls_version(Version) of + case ssl_test_lib:working_openssl_client() of true -> - case ssl_test_lib:openssl_sane_dtls_session_reuse() of + Version = ssl_test_lib:protocol_version(Config), + case ssl_test_lib:is_dtls_version(Version) of true -> - ct:timetrap(?TIMEOUT), - Config; + case ssl_test_lib:openssl_sane_dtls_session_reuse() of + true -> + ct:timetrap(?TIMEOUT), + Config; + false -> + {skip, "Broken OpenSSL DTLS session reuse"} + end; false -> - {skip, "Broken OpenSSL DTLS session reuse"} + ct:timetrap(?TIMEOUT), + Config end; - false -> - ct:timetrap(?TIMEOUT), - Config + false -> + {skip, "Broken OpenSSL s_client"} end; init_per_testcase(_TestCase, Config) -> ct:timetrap(?TIMEOUT), @@ -153,24 +162,28 @@ reuse_session_erlang_server() -> [{doc, "Test erlang server with openssl client that reconnects with the" "same session id, to test reusing of sessions."}]. reuse_session_erlang_server(Config) when is_list(Config) -> - ClientOpts = proplists:get_value(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = proplists:get_value(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + Version = ssl_test_lib:protocol_version(Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Ciphers = common_ciphers(Version), + Data = "From openssl to erlang", Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, active_recv, [length(Data)]}}, {reconnect_times, 5}, - {options, ServerOpts}]), + {options, [{ciphers, Ciphers}, + {versions, [Version]}| ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port}, {reconnect, true}, - {options, ClientOpts}, + {options, [{ciphers, Ciphers} | ClientOpts]}, return_port], Config), true = port_command(OpenSSLPort, Data), @@ -182,12 +195,14 @@ reuse_session_erlang_server(Config) when is_list(Config) -> reuse_session_erlang_client() -> [{doc, "Test erlang ssl client that wants to reuse sessions"}]. reuse_session_erlang_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = proplists:get_value(server_rsa_opts, Config), - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = proplists:get_value(server_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + Ciphers = common_ciphers(Version), Server = ssl_test_lib:start_server(openssl, [], - [{server_opts, ServerOpts} | Config]), + [{server_opts, [{ciphers, Ciphers} | ServerOpts]} | Config]), Port = ssl_test_lib:inet_port(Server), Client0 = @@ -196,7 +211,9 @@ reuse_session_erlang_client(Config) when is_list(Config) -> {mfa, {ssl_test_lib, session_id, []}}, {from, self()}, {options, [{reuse_sessions, save}, - {verify, verify_peer}| ClientOpts]}]), + {verify, verify_peer}, + {ciphers, Ciphers}, + {versions, [Version]} | ClientOpts]}]), SID = receive {Client0, Id0} -> @@ -209,7 +226,9 @@ reuse_session_erlang_client(Config) when is_list(Config) -> ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]), + {from, self()}, {options, [ {ciphers, Ciphers}, + {versions, [Version]}, + {reuse_session, SID} | ClientOpts]}]), receive {Client1, SID} -> ok @@ -226,7 +245,8 @@ reuse_session_erlang_client(Config) when is_list(Config) -> ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, {options, [{ciphers, Ciphers}, + {versions, [Version]} | ClientOpts]}]), receive {Client2, ID} -> case ID of @@ -242,3 +262,10 @@ reuse_session_erlang_client(Config) when is_list(Config) -> %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- + +common_ciphers(Version) -> + OpenSSLCiphers = ssl_test_lib:openssl_ciphers(), + ErlOpenSSLCiphers = [ssl:str_to_suite(C) || C <- OpenSSLCiphers], + ErlCiphers = ssl:cipher_suites(all, Version), + [Suite || Suite <- ErlOpenSSLCiphers, lists:member(Suite, ErlCiphers)]. + diff --git a/lib/ssl/test/property_test/ssl_eqc_chain.erl b/lib/ssl/test/property_test/ssl_eqc_chain.erl new file mode 100644 index 0000000000..e78dc3fc0e --- /dev/null +++ b/lib/ssl/test/property_test/ssl_eqc_chain.erl @@ -0,0 +1,411 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2020. 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% +%% +%% + +-module(ssl_eqc_chain). + +%%-export([prop_tls_orded_path/1]). +-compile(export_all). + +-proptest(eqc). +-proptest([triq,proper]). + +-ifndef(EQC). +-ifndef(PROPER). +-ifndef(TRIQ). +-define(EQC,true). +-endif. +-endif. +-endif. + +-ifdef(EQC). +-include_lib("eqc/include/eqc.hrl"). +-define(MOD_eqc,eqc). + +-else. +-ifdef(PROPER). +-include_lib("proper/include/proper.hrl"). +-define(MOD_eqc,proper). + +-else. +-ifdef(TRIQ). +-define(MOD_eqc,triq). +-include_lib("triq/include/triq.hrl"). + +-endif. +-endif. +-endif. + +-include_lib("public_key/include/public_key.hrl"). +%%-------------------------------------------------------------------- +%% Properties -------------------------------------------------------- +%%-------------------------------------------------------------------- +prop_tls_unordered_path(PrivDir) -> + ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_options(Version, PrivDir)), + try + [TLSVersion] = proplists:get_value(versions, ClientOptions), + ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang}, + {client_type, erlang}, + {version, TLSVersion} + ]) + of + _ -> + true + catch + _:_ -> + false + end + ). + +prop_tls_extraneous_path(PrivDir) -> + ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extraneous_options(Version, PrivDir)), + try + [TLSVersion] = proplists:get_value(versions, ClientOptions), + ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang}, + {client_type, erlang}, + {version, TLSVersion} + ]) + of + _ -> + true + catch + _:_ -> + false + end + ). + +prop_tls_extraneous_paths() -> + ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), extra_extraneous_options(Version)), + try + [TLSVersion] = proplists:get_value(versions, ClientOptions), + ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang}, + {client_type, erlang}, + {version, TLSVersion} + ]) + of + _ -> + true + catch + _:_ -> + false + end + ). + +prop_tls_extraneous_and_unordered_path() -> + ?FORALL({ClientOptions, ServerOptions}, ?LET(Version, tls_version(), unordered_extraneous_options(Version)), + try + [TLSVersion] = proplists:get_value(versions, ClientOptions), + ssl_test_lib:basic_test(ClientOptions, ServerOptions, [{server_type, erlang}, + {client_type, erlang}, + {version, TLSVersion} + ]) + of + _ -> + true + catch + _:_ -> + false + end + ). + +%%-------------------------------------------------------------------- +%% Chain Generators ----------------------------------------------- +%%-------------------------------------------------------------------- +tls_version() -> + Versions = [Version || Version <- ['tlsv1.3', 'tlsv1.2', 'tlsv1.1', 'tlsv1', 'dtlsv1.2', 'dtlsv1'], + 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]); +key_alg(_) -> + oneof([rsa]). + +server_options('tlsv1.3') -> + [{verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {reuseaddr, true}]; +server_options(_) -> + [{verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {reuse_sessions, false}, + {reuseaddr, true}]. + +client_options(_) -> + [{verify, verify_peer}]. + +unordered_options(Version, PrivDir) -> + oneof([der_unordered_options(Version), pem_unordered_options(Version, PrivDir)]). + +der_unordered_options(Version) -> + ?LET(Alg, key_alg(Version), unordered_der_cert_chain_opts(Version, Alg)). + +pem_unordered_options(Version, PrivDir) -> + ?LET(Alg, key_alg(Version), unordered_pem_cert_chain_opts(Version, Alg, PrivDir)). + +unordered_der_cert_chain_opts(Version, Alg) -> + #{server_config := ServerConf, + 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), + 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)]}. + +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), + intermediates => intermediates(Alg, 4), + peer => peer_key(Alg)}}), + + ClientBase = filename:join(PrivDir, "client_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)]}. + +unordered_der_conf(Config) -> + Cert = proplists:get_value(cert, Config), + {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}), + {ok, _, [Cert | Path]} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), ExtractedCAs, []), + [{cert, [Cert | lists:reverse(Path)]}| proplists:delete(cert, Config)]. + +unordered_pem_conf(Config) -> + CertFile = proplists:get_value(certfile, Config), + CACertFile = proplists:get_value(cacertfile, Config), + [{_, Cert, _}| _] = ssl_test_lib:pem_to_der(CertFile), + PemCAs = ssl_test_lib:pem_to_der(CACertFile), + DerList = [DerCert || {'Certificate', DerCert, not_encrypted} <- PemCAs], + {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, DerList}), + {ok, _, [Cert | Path]} = ssl_certificate:certificate_chain(Cert, ets:new(foo, []), ExtractedCAs, []), + Unorded = lists:reverse(Path), + UnordedPemEntries = [{'Certificate', DerCert, not_encrypted} || DerCert <- Unorded], + PEM = public_key:pem_encode([{'Certificate', Cert, not_encrypted} |UnordedPemEntries]), + file:write_file(CertFile, PEM), + Config. + +extraneous_options(Version, PrivDir) -> + oneof([der_extraneous_options(Version), + pem_extraneous_options(Version, PrivDir) + ]). +extra_extraneous_options(Version) -> + oneof([extra_der_extraneous_options(Version)]). + +der_extraneous_options(Version) -> + ?LET(Alg, key_alg(Version), extraneous_der_cert_chain_opts(Version, Alg)). + +pem_extraneous_options(Version, PrivDir) -> + ?LET(Alg, key_alg(Version), extraneous_pem_cert_chain_opts(Version, Alg, PrivDir)). + +extra_der_extraneous_options(Version) -> + ?LET(Alg, key_alg(Version), extra_extraneous_der_cert_chain_opts(Version, Alg)). + +unordered_extraneous_options(Version) -> + oneof([der_extraneous_and_unorder_options(Version)]). + +der_extraneous_and_unorder_options(Version) -> + ?LET(Alg, key_alg(Version), der_extraneous_and_unorder_chain(Version, Alg)). + +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)), + + #{server_config := ServerConf0, + client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot, + intermediates => intermediates(Alg, 1), + peer => []}, + client_chain => #{root => CRoot, + intermediates => intermediates(Alg, 1), + peer => []}}), + + {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)]}. + +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)), + + #{server_config := ServerConf0, + client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot, + intermediates => intermediates(Alg, 1), + peer => []}, + client_chain => #{root => CRoot, + intermediates => intermediates(Alg, 1), + peer => []}}), + + {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)]}. + +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)), + + #{server_config := ServerConf0, + client_config := ClientConf0} = public_key:pkix_test_data(#{server_chain => #{root => SRoot, + intermediates => intermediates(Alg, 3), + peer => []}, + client_chain => #{root => CRoot, + intermediates => intermediates(Alg, 3), + peer => []}}), + + + {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)]}. + + +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)), + + #{server_config := ServerConf0, + client_config := ClientConf0} = + public_key:pkix_test_data(#{server_chain => #{root => SRoot, + intermediates => intermediates(Alg, 3), + peer => []}, + client_chain => #{root => CRoot, + intermediates => intermediates(Alg, 3), + peer => []}}), + + {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)]}. + +chain_and_root(Config) -> + OwnCert = proplists:get_value(cert, Config), + {ok, ExtractedCAs} = ssl_pkix_db:extract_trusted_certs({der, proplists:get_value(cacerts, Config)}), + {ok, Root, Chain} = ssl_certificate:certificate_chain(OwnCert, ets:new(foo, []), ExtractedCAs, []), + {Chain, Root}. + +extraneous_chain_and_root(Config, Name, 1) -> + #{cert := NewRoot, key := Key} = public_key:pkix_test_root_cert(Name, []), + {[OwnCert, CA0, OldRoot], OldRoot} = chain_and_root(Config), + CA1 = new_intermediat(CA0, Key), + {[OwnCert, CA1, CA0], NewRoot}; +extraneous_chain_and_root(Config, Name, 2) -> + #{cert := NewRoot, key := Key} = public_key:pkix_test_root_cert(Name, []), + {[OwnCert, CA0, CA1, CA2, OldRoot], OldRoot} = chain_and_root(Config), + CA3 = new_intermediat(CA2, Key), + {[OwnCert, CA0, CA1, CA2, CA3], NewRoot}. + +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)), + CaCerts = proplists:get_value(cacerts, Config0), + NewCas = [NewRoot | CaCerts -- [OldRoot]], + Entries = [{'Certificate', DerCert, not_encrypted} || DerCert <- Chain], + PemBin = public_key:pem_encode(Entries), + file:write_file(FileName, PemBin), + Config1 = [{cacerts, NewCas} | proplists:delete(cacerts, Config0)], + [{certfile, FileName} | proplists:delete(cert, Config1)]. + +protocol('dtlsv1.2') -> + {protocol, dtls}; +protocol('dtlsv1') -> + {protocol, dtls}; +protocol(_) -> + {protocol,tls}. + +create_extraneous_chains([Client, _CCA0, _CCA1, CCA2, _CCA3], [Client, OCCA0, OCCA1, OCCA2, OCROOT], + [Server, _SCA0, _SCA1, SCA2, _SROOT], [Server, OSCA0, OSCA1, OSCA2, OSROOT]) -> + {[Client, OCCA0, OCCA1, CCA2, OCCA2, OCROOT], [Server, OSCA0, OSCA1, SCA2, OSCA2, OSROOT]}. +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) -> + []; %% Just generate one +root_key(rsa) -> + %% As rsa keygen is not guaranteed to be fast + [{key, ssl_test_lib:hardcode_rsa_key(6)}]. + +peer_key(ecdsa) -> + []; %% Just generate one +peer_key(rsa) -> + %% As rsa keygen is not guaranteed to be fast + [{key, ssl_test_lib:hardcode_rsa_key(6)}]. + +intermediates(ecdsa, N) -> + lists:duplicate(N, []); +intermediates(rsa, N) when N =< 4 -> + Default = lists:duplicate(N, []), + %% As rsa keygen is not guaranteed to be fast + hardcode_rsa_keys(Default, N, []). + +hardcode_rsa_keys([], 0, Acc) -> + Acc; +hardcode_rsa_keys([Head | Tail], N, Acc) -> + hardcode_rsa_keys(Tail, N-1, [[{key, ssl_test_lib:hardcode_rsa_key(N)} | Head] | Acc]). + +new_intermediat(CA0, Key) -> + OTPCert = public_key:pkix_decode_cert(CA0, otp), + TBSCert = OTPCert#'OTPCertificate'.tbsCertificate, + Num = TBSCert#'OTPTBSCertificate'.serialNumber, + public_key:pkix_sign(TBSCert#'OTPTBSCertificate'{serialNumber = Num+1}, Key). + + diff --git a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl index b661ec8806..cf6ed755f7 100644 --- a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl +++ b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl @@ -73,6 +73,21 @@ prop_tls_cipher_suite_rfc_name() -> prop_tls_cipher_suite_openssl_name() -> ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}), case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of + CipherSuite -> + case ssl:suite_to_openssl_str(CipherSuite) of + "TLS_" ++ _ -> + true; + OpensslName -> + lists:member(OpensslName, openssl_legacy_names()) + end; + _ -> + false + end + ). + +prop_tls_anon_cipher_suite_rfc_name() -> + ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, pre_tls_1_3_version(), {anon_cipher_suite(Version), Version}), + case ssl:str_to_suite(ssl:suite_to_str(CipherSuite)) of CipherSuite -> true; _ -> @@ -80,6 +95,15 @@ prop_tls_cipher_suite_openssl_name() -> end ). +prop_tls_anon_cipher_suite_openssl_name() -> + ?FORALL({CipherSuite, _TLSVersion}, ?LET(Version, pre_tls_1_3_version(), {anon_cipher_suite(Version), Version}), + case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of + CipherSuite -> + lists:member(ssl:suite_to_openssl_str(CipherSuite), openssl_legacy_names()); + _ -> + false + end + ). %%-------------------------------------------------------------------- %% Generators ----------------------------------------------- @@ -87,9 +111,164 @@ prop_tls_cipher_suite_openssl_name() -> tls_version() -> oneof([?'TLS_v1.3', ?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']). +pre_tls_1_3_version() -> + oneof([?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1']). + cipher_suite(Version) -> oneof(cipher_suites(Version)). cipher_suites(Version) -> - ssl:cipher_suites(all, Version). + ssl:cipher_suites(default, Version). + +anon_cipher_suite(Version) -> + oneof(ssl:cipher_suites(anonymous, Version)). + +openssl_legacy_names() -> + %% Only include names that we support + [ + %% Legacy with RSA keyexchange + "AES128-SHA", + "AES256-SHA", + "AES128-SHA256", + "AES256-SHA256", + "AES256-GCM-SHA256", + "AES256-GCM-SHA384", + "DES-CBC-SHA", + "DES-CBC3-SHA", + "RC4-MD5", + "RC4-SHA", + + %% DH based + "DH-RSA-AES128-SHA", + "DH-RSA-AES256-SHA", + "DH-RSA-AES128-SHA256", + "DH-RSA-AES256-SHA256", + "DH-DSS-AES128-SHA", + "DH-DSS-AES256-SHA", + "DH-DSS-AES128-SHA256", + "DH-DSS-AES256-SHA256", + "EDH-RSA-DES-CBC-SHA", + "EDH-RSA-DES-CBC3-SHA", + "DHE-RSA-AES128-SHA", + "DHE-RSA-AES256-SHA", + "DHE-RSA-AES128-SHA256", + "DHE-RSA-AES256-SHA256", + "DHE-RSA-AES256-SHA384", + "DHE-RSA-AES128-GCM-SHA256", + "DHE-RSA-AES256-GCM-SHA384", + "DHE-RSA-AES128-CCM-SHA256", + "DHE-RSA-AES256-CCM-SHA384", + "DHE-RSA-AES128-CCM8-SHA256", + "DHE-RSA-AES256-CCM8-SHA384", + "DHE-RSA-CHACHA20-POLY1305", + "EDH-DSS-DES-CBC-SHA", + "EDH-DSS-DES-CBC3-SHA", + "DHE-DSS-AES128-SHA", + "DHE-DSS-AES256-SHA", + "DHE-DSS-AES128-SHA256", + "DHE-DSS-AES256-SHA256", + "DHE-DSS-AES256-SHA384", + "DHE-DSS-AES128-GCM-SHA256", + "DHE-DSS-AES256-GCM-SHA384", + "DHE-DSS-RC4-SHA", + "ADH-AES128-SHA256", + "ADH-AES256-SHA256", + "ADH-AES128-CBC-SHA256", + "ADH-AES128-GCM-SHA256", + "ADH-AES256-GCM-SHA384", + "ADH-RC4-MD5", + "ADH-DES-CBC-SHA", + "ADH-DES-CBC3-SHA", + "ADH-AES256-SHA", + "ADH-AES256-SHA256", + + %% ECDH based + "ECDH-ECDSA-AES128-SHA", + "ECDH-ECDSA-AES256-SHA", + "ECDH-ECDSA-AES128-SHA256", + "ECDH-ECDSA-AES256-SHA384", + "ECDH-ECDSA-AES128-GCM-SHA256", + "ECDH-ECDSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-AES128-CCM", + "ECDHE-ECDSA-AES128-CCM8", + "ECDHE-ECDSA-AES256-CCM", + "ECDHE-ECDSA-AES256-CCM8", + "ECDH-ECDSA-CHACHA20-POLY1305", + "ECDHE-ECDSA-AES128-SHA", + "ECDHE-ECDSA-AES256-SHA", + "ECDHE-ECDSA-AES128-SHA256", + "ECDHE-ECDSA-AES256-SHA384", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-CHACHA20-POLY1305", + "ECDH-RSA-AES128-SHA", + "ECDH-RSA-AES256-SHA", + "ECDH-RSA-AES128-SHA256", + "ECDH-RSA-AES256-SHA384", + "ECDH-RSA-AES128-GCM-SHA256", + "ECDH-RSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES128-SHA", + "ECDHE-RSA-AES256-SHA", + "ECDHE-RSA-AES128-SHA256", + "ECDHE-RSA-AES256-SHA384", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA256", + "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-RSA-CHACHA20-POLY1305", + "ECDHE-PSK-RC4-SHA", + "ECDHE-PSK-3DES-EDE-CBC-SHA", + "ECDHE-PSK-AES128-CBC-SHA", + "ECDHE-PSK-AES128-CBC-SHA256", + "ECDHE-PSK-AES256-CBC-SHA384", + "ECDHE-PSK-AES128-GCM-SHA256", + "ECDHE-PSK-AES256-GCM-SHA384", + "ECDHE-PSK-AES128-CCM-SHA256", + "ECDHE-PSK-AES128-CCM8-SHA256", + "ECDHE-PSK-CHACHA20-POLY1305", + "AECDH-DES-CBC3-SHA", + "AECDH-AES128-SHA", + "AECDH-AES256-SHA", + %% PSK based + "DHE-PSK-NULL-SHA", + "DHE-PSK-RC4-SHA", + "DHE-PSK-3DES-EDE-CBC-SHA", + "DHE-PSK-AES128-CBC-SHA", + "DHE-PSK-AES256-CBC-SHA", + "DHE-PSK-AES128-CBC-SHA256", + "DHE-PSK-AES256-CBC-SHA384", + "DHE-PSK-AES128-GCM-SHA256", + "DHE-PSK-AES256-GCM-SHA384", + "DHE-PSK-AES128-CCM", + "DHE-PSK-AES128-CCM8", + "DHE-PSK-AES256-CCM", + "DHE-PSK-AES256-CCM8", + "DHE-PSK-AES128-CCM-SHA256", + "DHE-PSK-AES128-CCM8-SHA256", + "DHE-PSK-CHACHA20-POLY1305", + "PSK-NULL-SHA", + "PSK-RC4-SHA", + "PSK-3DES-EDE-CBC-SHA", + "PSK-AES128-CBC-SHA", + "PSK-AES256-CBC-SHA", + "PSK-AES128-CBC-SHA256", + "PSK-AES256-CBC-SHA256", + "PSK-AES128-CCM", + "PSK-AES128-CCM8", + "PSK-AES256-CCM", + "PSK-AES256-CCM8", + "PSK-AES128-GCM-SHA256", + "PSK-AES256-CBC-SHA384", + "PSK-AES256-GCM-SHA384", + "PSK-CHACHA20-POLY1305", + "RSA-PSK-NULL-SHA", + "RSA-PSK-CHACHA20-POLY1305", + + %% SRP based + "SRP-3DES-EDE-CBC-SHA", + "SRP-RSA-3DES-EDE-CBC-SHA", + "SRP-DSS-3DES-EDE-CBC-SHA", + "SRP-AES-128-CBC-SHA", + "SRP-AES-256-CBC-SHA" + ]. diff --git a/lib/ssl/test/ssl_ECC.erl b/lib/ssl/test/ssl_ECC.erl index 56b67361e1..73dafcd1fc 100644 --- a/lib/ssl/test/ssl_ECC.erl +++ b/lib/ssl/test/ssl_ECC.erl @@ -23,6 +23,8 @@ -module(ssl_ECC). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index 496cb38fd9..c628f3b6d9 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_ECC_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). @@ -123,7 +125,8 @@ end_per_suite(_Config) -> %%-------------------------------------------------------------------- init_per_group(GroupName, Config) -> case ssl_test_lib:is_protocol_version(GroupName) of - true -> + true -> + ct:log("Ciphers: ~p~n ", [ssl:cipher_suites(default, GroupName)]), ssl_test_lib:init_per_group(GroupName, [{client_type, erlang}, {server_type, erlang}, @@ -139,7 +142,6 @@ end_per_group(GroupName, Config) -> init_per_testcase(TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), end_per_testcase(TestCase, Config), ssl:start(), ct:timetrap({seconds, 15}), diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl index ffdc9ea916..3b32d3ece1 100644 --- a/lib/ssl/test/ssl_alert_SUITE.erl +++ b/lib/ssl/test/ssl_alert_SUITE.erl @@ -20,6 +20,8 @@ -module(ssl_alert_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_alpn_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl index f0be740716..e3c10caa43 100644 --- a/lib/ssl/test/ssl_alpn_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_alpn_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Callback functions @@ -324,7 +326,7 @@ ssl_receive(Socket, Data) -> ssl_receive(Socket, Data, Buffer) -> ct:log("Connection info: ~p~n", - [ssl:connection_information(Socket)]), + [ssl:connection_information(Socket)]), receive {ssl, Socket, MoreData} -> ct:log("Received ~p~n",[MoreData]), diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 877458e776..6efd8424a9 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_api_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("ssl/src/ssl_api.hrl"). @@ -52,6 +54,8 @@ connection_information/1, secret_connection_info/0, secret_connection_info/1, + keylog_connection_info/0, + keylog_connection_info/1, versions/0, versions/1, active_n/0, @@ -66,6 +70,8 @@ hibernate_right_away/1, listen_socket/0, listen_socket/1, + peername/0, + peername/1, recv_active/0, recv_active/1, recv_active_once/0, @@ -168,6 +174,7 @@ -export([connection_information_result/1, connection_info_result/1, secret_connection_info_result/1, + keylog_connection_info_result/2, check_srp_in_connection_information/3, check_connection_info/2, prf_verify_value/4, @@ -238,18 +245,21 @@ pre_1_3() -> default_reject_anonymous, connection_information_with_srp ]. + gen_api_tests() -> [ peercert, peercert_with_client_cert, connection_information, secret_connection_info, + keylog_connection_info, versions, active_n, dh_params, hibernate, hibernate_right_away, listen_socket, + peername, recv_active, recv_active_once, recv_active_n, @@ -379,6 +389,15 @@ init_per_testcase(connection_information_with_srp, Config) -> false -> {skip, "Missing SRP crypto support"} end; +init_per_testcase(conf_signature_algs, Config) -> + case ssl_test_lib:appropriate_sha(crypto:supports()) of + sha256 -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config; + sha -> + {skip, "Tests needs certs with sha256"} + end; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 10}), @@ -568,6 +587,57 @@ secret_connection_info(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +keylog_connection_info() -> + [{doc,"Test the API function ssl:connection_information/2"}]. +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), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, keylog_connection_info_result, [KeepSecrets]}}, + {options, [{verify, verify_peer}, {keep_secrets, KeepSecrets} | ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, keylog_connection_info_result, [KeepSecrets]}}, + {options, [{verify, verify_peer}, {keep_secrets, KeepSecrets} |ClientOpts]}]), + + ct:log("Testcase ~p, KeepSecrets ~p Client ~p Server ~p ~n", + [self(), KeepSecrets, Client, Server]), + + ServerKeylog = receive + {Server, {ok, Keylog}} -> + Keylog; + {Server, ServerError} -> + ct:fail({server, ServerError}) + after 5000 -> + ct:fail({server, timeout}) + end, + + receive + {Client, {ok, ServerKeylog}} -> + ok; + {Client, {ok, ClientKeylog}} -> + ct:fail({mismatch, {ServerKeylog, ClientKeylog}}); + {Client, ClientError} -> + ct:fail({client, ClientError}) + after 5000 -> + ct:fail({client, timeout}) + end, + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- prf() -> [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. prf(Config) when is_list(Config) -> @@ -627,14 +697,16 @@ conf_signature_algs(Config) when is_list(Config) -> ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]), + {options, [{active, false}, {signature_algs, [{sha256, rsa}]}, + {versions, ['tlsv1.2']} | 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, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]), + {options, [{active, false}, {signature_algs, [{sha256, rsa}]}, + {versions, ['tlsv1.2']} | ClientOpts]}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), @@ -715,7 +787,11 @@ handshake_continue_tls13_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - + SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'), + [{key_exchange, fun(srp_rsa) -> false; + (srp_anon) -> false; + (srp_dss) -> false; + (_) -> true end}]), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -723,6 +799,7 @@ handshake_continue_tls13_client(Config) when is_list(Config) -> {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {log_level, debug}, {verify, verify_peer}, + {ciphers, SCiphers}, {handshake, hello} | ServerOpts ], Config)}, @@ -765,6 +842,7 @@ handshake_continue_tls13_client(Config) when is_list(Config) -> 'tlsv1.1', 'tlsv1' ]}, + {ciphers, ssl:cipher_suites(all, 'tlsv1.3')}, {verify, verify_peer} | ClientOpts ], Config)}, @@ -1090,6 +1168,46 @@ listen_socket(Config) -> ok = ssl:close(ListenSocket). %%-------------------------------------------------------------------- +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), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server( + [ + {node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, CSocket} = ssl_test_lib:start_client( + [return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + Server ! get_socket, + SSocket = + receive + {Server, {socket, Socket}} -> + Socket + end, + + {ok, ServerPeer} = ssl:peername(SSocket), + ct:log("Server's peer: ~p~n", [ServerPeer]), + {ok, ClientPeer} = ssl:peername(CSocket), + ct:log("Client's peer: ~p~n", [ClientPeer]), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- recv_active() -> [{doc,"Test recv on active socket"}]. @@ -2280,7 +2398,6 @@ cookie(Config) when is_list(Config) -> cookie_extension(Config, true), cookie_extension(Config, false). - %%% Checker functions connection_information_result(Socket) -> {ok, Info = [_ | _]} = ssl:connection_information(Socket), @@ -2292,11 +2409,28 @@ connection_information_result(Socket) -> false -> ct:fail(no_ssl_options_returned) end. + secret_connection_info_result(Socket) -> {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), {ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]), check_connection_info(Protocol, ConnInfo). +keylog_connection_info_result(Socket, KeepSecrets) -> + {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), + {ok, ConnInfo} = ssl:connection_information(Socket, [keylog]), + check_keylog_info(Protocol, ConnInfo, KeepSecrets). + +check_keylog_info('tlsv1.3', [{keylog, ["CLIENT_HANDSHAKE_TRAFFIC_SECRET"++_,_|_]=Keylog}], true) -> + {ok, Keylog}; +check_keylog_info('tlsv1.3', []=Keylog, false) -> + {ok, Keylog}; +check_keylog_info('tlsv1.2', [{keylog, ["CLIENT_RANDOM"++_]=Keylog}], _) -> + {ok, Keylog}; +check_keylog_info(NotSup, [], _) when NotSup == 'tlsv1.1'; NotSup == tlsv1; NotSup == 'dtlsv1.2'; NotSup == dtlsv1 -> + {ok, []}; +check_keylog_info(_, Unexpected, _) -> + {unexpected, Unexpected}. + check_srp_in_connection_information(_Socket, _Username, client) -> ok; check_srp_in_connection_information(Socket, Username, server) -> @@ -2437,8 +2571,8 @@ prf_ciphers_and_expected(TlsVer, PRFs, Results) -> case TlsVer of TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> - Ciphers = ssl:cipher_suites(), - {_, Expected} = lists:keyfind(md5sha, 1, Results), + Ciphers = ssl:cipher_suites(default, TlsVer), + Expected = [Expect#{prf := md5sha} || Expect <- Results], [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> lists:foldl( @@ -2449,22 +2583,23 @@ prf_ciphers_and_expected(TlsVer, PRFs, Results) -> ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), Acc; Ciphers -> - {_, Expected} = lists:keyfind(PRF, 1, Results), + Expected = [Expect#{prf := PRF} || Expect <- Results], [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, PRF}] | Acc] end end, [], PRFs) end. -prf_get_ciphers(_, PRF) -> - lists:filter( - fun(C) when tuple_size(C) == 4 andalso - element(4, C) == PRF -> - true; - (_) -> - false - end, - ssl:cipher_suites()). +prf_get_ciphers(TlsVer, PRF) -> + PrfFilter = fun(Value) -> + case Value of + PRF -> + true; + _ -> + false + end + end, + ssl:filter_cipher_suites(ssl:cipher_suites(default, TlsVer), [{prf, PrfFilter}]). prf_run_test(_, TlsVer, [], _, Prf) -> ct:fail({error, cipher_list_empty, TlsVer, Prf}); diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl index ee9c3ad745..98e96023b2 100644 --- a/lib/ssl/test/ssl_app_env_SUITE.erl +++ b/lib/ssl/test/ssl_app_env_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_app_env_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("ssl/src/ssl_api.hrl"). diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 0039b0244f..a5ece4fad8 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -22,7 +22,10 @@ -module(ssl_basic_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). -include_lib("ssl/src/ssl_api.hrl"). %% Callback functions @@ -62,12 +65,21 @@ unordered_protocol_versions_server/0, unordered_protocol_versions_server/1, unordered_protocol_versions_client/0, - unordered_protocol_versions_client/1 + unordered_protocol_versions_client/1, + fake_root/0, + fake_root/1, + fake_root_legacy/0, + fake_root_legacy/1, + fake_root_no_intermediate/0, + fake_root_no_intermediate/1, + fake_root_no_intermediate_legacy/0, + fake_root_no_intermediate_legacy/1, + fake_intermediate_cert/0, + fake_intermediate_cert/1 ]). %% Apply export --export([send_recv_result/1, - tcp_send_recv_result/1, +-export([tcp_send_recv_result/1, result_ok/1, protocol_info_result/1, version_info_result/1, @@ -110,7 +122,12 @@ basic_tests() -> eccs, cipher_suites, old_cipher_suites, - cipher_suites_mix + cipher_suites_mix, + fake_root, + fake_root_no_intermediate, + fake_root_legacy, + fake_root_no_intermediate_legacy, + fake_intermediate_cert ]. options_tests() -> @@ -291,7 +308,7 @@ cipher_suites(Config) when is_list(Config) -> cipher => 'aes_128_cbc', mac => sha, prf => default_prf}, - Version = ssl_test_lib:protocol_version(Config), + Version = tls_record:highest_protocol_version([]), All = [_|_] = ssl:cipher_suites(all, Version), Default = [_|_] = ssl:cipher_suites(default, Version), Anonymous = [_|_] = ssl:cipher_suites(anonymous, Version), @@ -496,14 +513,252 @@ tls_versions_option(Config) when is_list(Config) -> end, ssl_test_lib:check_client_alert(ErrClient, protocol_version). +fake_root() -> + [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}]. +fake_root(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 = #{cert := Cert, + key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)}, + {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), + + + AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}], + authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber}, + [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier', + 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). + +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."}]. + +fake_root_no_intermediate(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 = #{cert := Cert, + key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)}, + {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), + + AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}], + authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber}, + [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier', + 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). + +fake_root_legacy() -> + [{doc,"Test that we can not use a fake root signed by other key but with correct name and serial number."}]. +fake_root_legacy(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 = #{cert := Cert, + key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)}, + {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). + +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."}]. +fake_root_no_intermediate_legacy(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 = #{cert := Cert, + key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)}, + {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 => [], + 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). + +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."}]. + +fake_intermediate_cert(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 = #{cert := Cert, + key := _Key} = public_key:pkix_test_root_cert("SERVER ROOT CA", [{key, ssl_test_lib:hardcode_rsa_key(6)}, + {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}]), + 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)}]}} + ), + OTPCert = public_key:pkix_decode_cert(Cert, otp), + TBS = OTPCert#'OTPCertificate'.tbsCertificate, + TBSExt = TBS#'OTPTBSCertificate'.extensions, + AuthExt = #'AuthorityKeyIdentifier'{authorityCertIssuer = [{directoryName, TBS#'OTPTBSCertificate'.issuer}], + authorityCertSerialNumber = TBS#'OTPTBSCertificate'.serialNumber}, + [AuthKeyExt] = x509_test:extensions([{?'id-ce-authorityKeyIdentifier', + AuthExt, + false}]), + + + CAs = proplists:get_value(cacerts, OtherServerConf), + + [ICA] = CAs -- [OtherSCert, OtherCCert], + + OTPICACert = public_key:pkix_decode_cert(ICA, otp), + ICATBS = OTPICACert#'OTPCertificate'.tbsCertificate, + + FakeICA = public_key:pkix_sign(ICATBS#'OTPTBSCertificate'{extensions = [AuthKeyExt | TBSExt]}, OtherSKey), + + ServerCert = proplists:get_value(cert, OtherServerConf), + FakeServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{cert, [ServerCert, FakeICA]} | + proplists:delete(cert, OtherServerConf)] + }]), + Port1 = ssl_test_lib:inet_port(FakeServer), + + Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port1}, + {host, Hostname}, + {from, self()}, + {options, [{verify, verify_peer} | ClientConf]}]), + + ssl_test_lib:check_client_alert(Client1, bad_certificate). %%-------------------------------------------------------------------- %% callback functions ------------------------------------------------ %%-------------------------------------------------------------------- -send_recv_result(Socket) -> - ssl:send(Socket, "Hello world"), - {ok,"Hello world"} = ssl:recv(Socket, 11), - ok. tcp_send_recv_result(Socket) -> gen_tcp:send(Socket, "Hello world"), {ok,"Hello world"} = gen_tcp:recv(Socket, 11), @@ -574,3 +829,60 @@ remove_supported_versions(Available, Supported) -> Versions0 end. + +test_fake_root(Hostname, ServerNode, ClientNode, ServerConf, ClientConf, FakeCert, FakeServerConf, ResultRootIncluded, ResultRootExcluded) -> + RealServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerConf}]), + Port0 = ssl_test_lib:inet_port(RealServer), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port0}, + {host, Hostname}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {from, self()}, + {options, [{verify, verify_peer} | ClientConf]}]), + + ssl_test_lib:check_result(RealServer, ok, Client0, ok), + + ssl_test_lib:close(RealServer), + ssl_test_lib:close(Client0), + + %% Fake server sends ROOT cert + FakeServer = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, FakeServerConf}]), + Port1 = ssl_test_lib:inet_port(FakeServer), + + Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port1}, + {host, Hostname}, + {from, self()}, + {options, [{verify, verify_peer} | ClientConf]}]), + + ssl_test_lib:check_client_alert(Client1, ResultRootIncluded), + + + %%Fake server does not send ROOT cert + CAS0 = proplists:get_value(cacerts, FakeServerConf), + CAS1 = CAS0 -- [FakeCert], + + FakeServer1 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{cacerts, CAS1} | proplists:delete(cacerts, FakeServerConf)]}]), + + Port2 = ssl_test_lib:inet_port(FakeServer1), + + Client2 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port2}, + {host, Hostname}, + {from, self()}, + {options, [{verify, verify_peer} | ClientConf]}]), + + ssl_test_lib:check_client_alert(Client2, ResultRootExcluded), + + ssl_test_lib:close(FakeServer1). + + + + + diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index fe30a83a7e..f78ac9c9cc 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -19,6 +19,8 @@ %% -module(ssl_bench_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct_event.hrl"). %% Callback functions diff --git a/lib/ssl/test/ssl_bench_test_lib.erl b/lib/ssl/test/ssl_bench_test_lib.erl index cbb9cfe47a..74ab142993 100644 --- a/lib/ssl/test/ssl_bench_test_lib.erl +++ b/lib/ssl/test/ssl_bench_test_lib.erl @@ -19,6 +19,8 @@ %% -module(ssl_bench_test_lib). +-behaviour(ct_suite). + %% API -export([setup/1]). diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl index bec6057d91..efa01fda76 100644 --- a/lib/ssl/test/ssl_cert_SUITE.erl +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_cert_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). @@ -54,6 +56,8 @@ client_auth_partial_chain_fun_fail/1, client_auth_sni/0, client_auth_sni/1, + client_auth_seelfsigned_peer/0, + client_auth_seelfsigned_peer/1, missing_root_cert_no_auth/0, missing_root_cert_no_auth/1, missing_root_cert_auth/0, @@ -199,6 +203,7 @@ all_version_tests() -> client_auth_do_not_allow_partial_chain, client_auth_partial_chain_fun_fail, client_auth_sni, + client_auth_seelfsigned_peer, missing_root_cert_no_auth, missing_root_cert_auth, missing_root_cert_auth_user_verify_fun_accept, @@ -382,6 +387,11 @@ client_auth_sni() -> ssl_cert_tests:client_auth_sni(). client_auth_sni(Config) when is_list(Config) -> ssl_cert_tests:client_auth_sni(Config). +%%-------------------------------------------------------------------- +client_auth_seelfsigned_peer() -> + ssl_cert_tests:client_auth_seelfsigned_peer(). +client_auth_seelfsigned_peer(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_seelfsigned_peer(Config). %%-------------------------------------------------------------------- missing_root_cert_no_auth() -> diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl index 5422ff7fe4..d9d535106a 100644 --- a/lib/ssl/test/ssl_cert_tests.erl +++ b/lib/ssl/test/ssl_cert_tests.erl @@ -21,6 +21,8 @@ %% -module(ssl_cert_tests). +-behaviour(ct_suite). + -include_lib("public_key/include/public_key.hrl"). %% Test cases @@ -42,6 +44,8 @@ client_auth_partial_chain_fun_fail/1, client_auth_sni/0, client_auth_sni/1, + client_auth_seelfsigned_peer/0, + client_auth_seelfsigned_peer/1, missing_root_cert_no_auth/0, missing_root_cert_no_auth/1, invalid_signature_client/0, @@ -235,6 +239,18 @@ client_auth_sni(Config) when is_list(Config) -> ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure). %%-------------------------------------------------------------------- +client_auth_seelfsigned_peer() -> + [{doc, "Check that selfsigned peer raises alert"}]. +client_auth_seelfsigned_peer(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyCertSign, cRLSign, digitalSignature, keyAgreement]}]), + #{cert := Cert, + key := Key} = public_key:pkix_test_root_cert("OTP test server ROOT", [{key, ssl_test_lib:hardcode_rsa_key(6)}, + {extensions, Ext}]), + DerKey = public_key:der_encode('RSAPrivateKey', Key), + ssl_test_lib:basic_alert(ssl_test_lib:ssl_options([{verify, verify_peer}, {cacerts , [Cert]}], Config), + ssl_test_lib:ssl_options([{cert, Cert}, + {key, {'RSAPrivateKey', DerKey}}], Config), Config, bad_certificate). +%%-------------------------------------------------------------------- missing_root_cert_no_auth() -> [{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}]. diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index eee8d8078b..31e60269f8 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -20,6 +20,8 @@ -module(ssl_cipher_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include("tls_record.hrl"). -include("ssl_cipher.hrl"). diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl index 1aab56f924..22dbc3663c 100644 --- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_cipher_suite_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Callback functions -export([all/0, @@ -122,10 +124,14 @@ aes_128_gcm_sha256/1, chacha20_poly1305_sha256/1, aes_128_ccm_sha256/1, - aes_128_ccm_8_sha256/1 + aes_128_ccm_8_sha256/1, + ecdhe_ecdsa_with_aes_128_ccm/1, + ecdhe_ecdsa_with_aes_256_ccm/1, + ecdhe_ecdsa_with_aes_128_ccm_8/1, + ecdhe_ecdsa_with_aes_256_ccm_8/1 ]). --define(TIMEOUT, {seconds, 5}). +-define(TIMEOUT, {seconds, 10}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -169,7 +175,11 @@ groups() -> ecdhe_ecdsa_aes_128_gcm, ecdhe_ecdsa_aes_256_cbc, ecdhe_ecdsa_aes_256_gcm, - ecdhe_ecdsa_chacha20_poly1305 + ecdhe_ecdsa_chacha20_poly1305, + ecdhe_ecdsa_with_aes_128_ccm, + ecdhe_ecdsa_with_aes_256_ccm, + ecdhe_ecdsa_with_aes_128_ccm_8, + ecdhe_ecdsa_with_aes_256_ccm_8 ]}, {rsa, [], [rsa_3des_ede_cbc, rsa_aes_128_cbc, @@ -480,6 +490,26 @@ init_per_testcase(aes_128_ccm_8_sha256, Config) -> _ -> {skip, "Missing AES_128_CCM_8_SHA256 crypto support"} end; +init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_128_ccm; + TestCase == ecdhe_ecdsa_with_aes_128_ccm_8-> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_128_ccm, SupCiphers) of + true -> + ct:timetrap(?TIMEOUT), + Config; + _ -> + {skip, "Missing AES_128_CCM crypto support"} + end; +init_per_testcase(TestCase, Config) when TestCase == ecdhe_ecdsa_with_aes_256_ccm; + TestCase == ecdhe_ecdsa_with_aes_256_ccm_8 -> + SupCiphers = proplists:get_value(ciphers, crypto:supports()), + case lists:member(aes_256_ccm, SupCiphers) of + true -> + ct:timetrap(?TIMEOUT), + Config; + _ -> + {skip, "Missing AES_256_CCM crypto support"} + end; init_per_testcase(TestCase, Config) -> Cipher = ssl_test_lib:test_cipher(TestCase, Config), SupCiphers = proplists:get_value(ciphers, crypto:supports()), @@ -742,6 +772,18 @@ ecdhe_ecdsa_aes_256_gcm(Config) when is_list(Config) -> ecdhe_ecdsa_chacha20_poly1305(Config) when is_list(Config) -> run_ciphers_test(ecdhe_ecdsa, 'chacha20_poly1305', Config). + +ecdhe_ecdsa_with_aes_128_ccm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm', Config). + +ecdhe_ecdsa_with_aes_256_ccm(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm', Config). + +ecdhe_ecdsa_with_aes_128_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_128_ccm_8', Config). + +ecdhe_ecdsa_with_aes_256_ccm_8(Config) when is_list(Config) -> + run_ciphers_test(ecdhe_ecdsa, 'aes_256_ccm_8', Config). %%-------------------------------------------------------------------- %% DHE_DSS -------------------------------------------------------- %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index ad75d29a78..f9fac82962 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -21,6 +21,8 @@ -module(ssl_crl_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 20463114f4..019e22eaa8 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -20,6 +20,8 @@ -module(ssl_dist_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). -include("ssl_dist_test_lib.hrl"). diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl index 67944c74d2..2216c6b04b 100644 --- a/lib/ssl/test/ssl_dist_bench_SUITE.erl +++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl @@ -19,6 +19,8 @@ %% -module(ssl_dist_bench_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct_event.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl index fa1f4217fe..bf010e6ad4 100644 --- a/lib/ssl/test/ssl_dist_test_lib.erl +++ b/lib/ssl/test/ssl_dist_test_lib.erl @@ -20,6 +20,8 @@ -module(ssl_dist_test_lib). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). -include("ssl_dist_test_lib.hrl"). diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl index 4e88c6e46c..c0e28120be 100644 --- a/lib/ssl/test/ssl_engine_SUITE.erl +++ b/lib/ssl/test/ssl_engine_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_engine_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_eqc_SUITE.erl b/lib/ssl/test/ssl_eqc_SUITE.erl index 52edb39d9c..3c9a1d0ab0 100644 --- a/lib/ssl/test/ssl_eqc_SUITE.erl +++ b/lib/ssl/test/ssl_eqc_SUITE.erl @@ -20,6 +20,7 @@ -module(ssl_eqc_SUITE). +-behaviour(ct_suite). %% Common test -export([all/0, @@ -32,7 +33,13 @@ %% Test cases -export([tls_handshake_encoding/1, tls_cipher_suite_names/1, - tls_cipher_openssl_suite_names/1 + tls_cipher_openssl_suite_names/1, + tls_anon_cipher_suite_names/1, + tls_anon_cipher_openssl_suite_names/1, + tls_unorded_chains/1, + tls_extraneous_chain/1, + tls_extraneous_chains/1, + tls_extraneous_and_unorder_chains/1 ]). %%-------------------------------------------------------------------- @@ -43,7 +50,13 @@ all() -> [ tls_handshake_encoding, tls_cipher_suite_names, - tls_cipher_openssl_suite_names + tls_cipher_openssl_suite_names, + tls_anon_cipher_suite_names, + tls_anon_cipher_openssl_suite_names, + tls_unorded_chains, + tls_extraneous_chain, + tls_extraneous_chains, + tls_extraneous_and_unorder_chains ]. %%-------------------------------------------------------------------- @@ -76,3 +89,37 @@ tls_cipher_openssl_suite_names(Config) when is_list(Config) -> %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()). true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_openssl_name(), Config). +tls_anon_cipher_suite_names(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name()). + true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_anon_cipher_suite_rfc_name(), + Config). + +tls_anon_cipher_openssl_suite_names(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()). + true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_anon_cipher_suite_openssl_name(), + Config). + +tls_unorded_chains(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path("/tmp") + ssl:start(), + PrivDir = proplists:get_value(priv_dir, Config), + true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_unordered_path(PrivDir), + Config). + +tls_extraneous_chain(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path("/tmp") + ssl:start(), + PrivDir = proplists:get_value(priv_dir, Config), + true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_path(PrivDir), + Config). + +tls_extraneous_chains(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path() + ssl:start(), + true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_paths(), + Config). +tls_extraneous_and_unorder_chains(Config) when is_list(Config) -> + %% manual test: proper:quickcheck(ssl_eqc_chain:prop_tls_ordered_path() + ssl:start(), + true = ct_property_test:quickcheck(ssl_eqc_chain:prop_tls_extraneous_and_unordered_path(), + Config). diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index f990b9328a..df8f867f24 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_handshake_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include("ssl_alert.hrl"). -include("ssl_handshake.hrl"). @@ -50,7 +52,6 @@ decode_empty_server_sni_correctly/1, select_proper_tls_1_2_rsa_default_hashsign/1, ignore_hassign_extension_pre_tls_1_2/1, - unorded_chain/1, signature_algorithms/1, encode_decode_srp/1]). @@ -66,7 +67,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, - unorded_chain, signature_algorithms, + signature_algorithms, encode_decode_srp]. %%-------------------------------------------------------------------- @@ -197,24 +198,6 @@ ignore_hassign_extension_pre_tls_1_2(Config) -> {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}). -unorded_chain(Config) when is_list(Config) -> - DefConf = ssl_test_lib:default_cert_chain_conf(), - CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), - #{server_config := ServerConf, - client_config := _ClientConf} = public_key:pkix_test_data(CertChainConf), - PeerCert = proplists:get_value(cert, ServerConf), - CaCerts = [_, C1, C2] = proplists:get_value(cacerts, ServerConf), - {ok, ExtractedCerts} = ssl_pkix_db:extract_trusted_certs({der, CaCerts}), - UnordedChain = case public_key:pkix_is_self_signed(C1) of - true -> - [C1, C2]; - false -> - [C2, C1] - end, - OrderedChain = [PeerCert | lists:reverse(UnordedChain)], - {ok, _, OrderedChain} = - ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain). - encode_decode_srp(_Config) -> Exts = #{srp => #srp{username = <<"foo">>}, sni => #sni{hostname = "bar"}, diff --git a/lib/ssl/test/ssl_key_update_SUITE.erl b/lib/ssl/test/ssl_key_update_SUITE.erl index 2816f1a39e..aba6a02ddc 100644 --- a/lib/ssl/test/ssl_key_update_SUITE.erl +++ b/lib/ssl/test/ssl_key_update_SUITE.erl @@ -19,6 +19,8 @@ %% -module(ssl_key_update_SUITE). +-behaviour(ct_suite). + %% Callback functions -export([all/0, groups/0, diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl index e8fa0ddf52..0f9aeb1c67 100644 --- a/lib/ssl/test/ssl_mfl_SUITE.erl +++ b/lib/ssl/test/ssl_mfl_SUITE.erl @@ -18,6 +18,9 @@ %% %CopyrightEnd% %% -module(ssl_mfl_SUITE). + +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Common test diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl index addf105e59..81c75ecff0 100644 --- a/lib/ssl/test/ssl_npn_SUITE.erl +++ b/lib/ssl/test/ssl_npn_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_npn_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Callback functions diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index ee84383c61..dae07aae63 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_npn_hello_SUITE). +-behaviour(ct_suite). + -include_lib("ssl/src/tls_record.hrl"). -include_lib("ssl/src/tls_handshake.hrl"). -include_lib("ssl/src/ssl_cipher.hrl"). diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index bd390d67bf..a65173f172 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_packet_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Callback functions diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index df8394ab14..e594c745b6 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -20,6 +20,8 @@ -module(ssl_payload_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Common test -export([all/0, diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 2469b47040..50d8089054 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_pem_cache_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl index 18ca56b6f9..4b46863415 100644 --- a/lib/ssl/test/ssl_renegotiate_SUITE.erl +++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_renegotiate_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_rfc_5869_SUITE.erl b/lib/ssl/test/ssl_rfc_5869_SUITE.erl index d350dd099d..497a05107b 100644 --- a/lib/ssl/test/ssl_rfc_5869_SUITE.erl +++ b/lib/ssl/test/ssl_rfc_5869_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_rfc_5869_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Common test diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl index c9f457ce8a..b11e49ad89 100644 --- a/lib/ssl/test/ssl_session_SUITE.erl +++ b/lib/ssl/test/ssl_session_SUITE.erl @@ -21,6 +21,8 @@ %% -module(ssl_session_SUITE). +-behaviour(ct_suite). + -include("tls_handshake.hrl"). -include("ssl_record.hrl"). @@ -45,6 +47,8 @@ reuse_session_expired/1, server_does_not_want_to_reuse_session/0, server_does_not_want_to_reuse_session/1, + explicit_session_reuse/0, + explicit_session_reuse/1, no_reuses_session_server_restart_new_cert/0, no_reuses_session_server_restart_new_cert/1, no_reuses_session_server_restart_new_cert_file/0, @@ -81,6 +85,7 @@ session_tests() -> [reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session, + explicit_session_reuse, no_reuses_session_server_restart_new_cert, no_reuses_session_server_restart_new_cert_file]. @@ -138,8 +143,9 @@ reuse_session() -> reuse_session(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). + Version = ssl_test_lib:protocol_version(Config), + ssl_test_lib:reuse_session([{versions,[Version]} | ClientOpts], + [{versions,[Version]} | ServerOpts], Config). %%-------------------------------------------------------------------- reuse_session_expired() -> [{doc,"Test sessions is not reused when it has expired"}]. @@ -268,6 +274,46 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client1). +%%-------------------------------------------------------------------- +explicit_session_reuse() -> + [{doc,"Test {reuse_session, {ID, Data}}} option for explicit reuse of sessions not" + " saved in the clients automated session reuse"}]. +explicit_session_reuse(Config) when is_list(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}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client0, Client0Sock} = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, [{reuse_sessions, false} | ClientOpts]}, + return_socket + ]), + + {ok, [{session_id, ID}, {session_data, SessData}]} = ssl:connection_information(Client0Sock, [session_id, session_data]), + + ssl_test_lib:close(Client0), + + Server ! listen, + + {_, Client1Sock} = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, [{reuse_session, {ID, SessData}} | ClientOpts]}, + return_socket]), + + {ok, [{session_id, ID}]} = ssl:connection_information(Client1Sock, [session_id]). + + +%%-------------------------------------------------------------------- no_reuses_session_server_restart_new_cert() -> [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> @@ -435,18 +481,6 @@ faulty_client(Host, Port) -> gen_tcp:close(Sock). -server(LOpts, Port) -> - {ok, LSock} = ssl:listen(Port, LOpts), - Pid = spawn_link(?MODULE, accept_loop, [LSock]), - ssl:controlling_process(LSock, Pid), - Pid. - -accept_loop(Sock) -> - {ok, CSock} = ssl:transport_accept(Sock), - _ = ssl:handshake(CSock), - accept_loop(Sock). - - encode_client_hello(CH, Random) -> HSBin = tls_handshake:encode_handshake(CH, {3,3}), CS = connection_states(Random), diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index c53161f168..7c182ee063 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -22,6 +22,8 @@ -module(ssl_session_cache_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Callback functions diff --git a/lib/ssl/test/ssl_session_ticket_SUITE.erl b/lib/ssl/test/ssl_session_ticket_SUITE.erl index 218c4a1564..16791e2c36 100644 --- a/lib/ssl/test/ssl_session_ticket_SUITE.erl +++ b/lib/ssl/test/ssl_session_ticket_SUITE.erl @@ -20,6 +20,8 @@ -module(ssl_session_ticket_SUITE). +-behaviour(ct_suite). + %% Callback functions -export([all/0, groups/0, diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 1014dda5af..9e2f9fddb1 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -21,6 +21,8 @@ -module(ssl_sni_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"). @@ -131,7 +133,7 @@ end_per_suite(_) -> init_per_testcase(customize_hostname_check, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ssl_test_lib:clean_start(), + ssl_test_lib:clean_start(keep_version), ct:timetrap(?TIMEOUT), Config; init_per_testcase(_TestCase, Config) -> diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl index a6bce2a414..e10ec5afaf 100644 --- a/lib/ssl/test/ssl_socket_SUITE.erl +++ b/lib/ssl/test/ssl_socket_SUITE.erl @@ -20,6 +20,8 @@ -module(ssl_socket_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 207a08ec7f..ab09e4e67a 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -21,10 +21,13 @@ %% -module(ssl_test_lib). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). -export([clean_start/0, + clean_start/1, clean_env/0, init_per_group/2, init_per_group_openssl/2, @@ -128,7 +131,9 @@ session_info_result/1, reuse_session/3, test_ciphers/3, - test_cipher/2 + test_cipher/2, + openssl_ciphers/0, + openssl_support_rsa_kex/0 ]). -export([tls_version/1, @@ -164,7 +169,8 @@ ecdh_dh_anonymous_suites/1, ecdsa_suites/1, der_to_pem/2, - pem_to_der/1 + pem_to_der/1, + appropriate_sha/1 ]). -export([maybe_force_ipv4/1, @@ -263,24 +269,25 @@ get_client_opts(Config) -> ssl_options(COpts, Config). %% Default callback functions -init_per_group(GroupName, Config) -> +init_per_group(GroupName, Config0) -> case is_protocol_version(GroupName) andalso sufficient_crypto_support(GroupName) of true -> - clean_protocol_version(Config), - init_protocol_version(GroupName, Config); + Config = clean_protocol_version(Config0), + [{version, GroupName}|init_protocol_version(GroupName, Config)]; _ -> case sufficient_crypto_support(GroupName) of true -> ssl:start(), - Config; + Config0; false -> {skip, "Missing crypto support"} end end. -init_per_group_openssl(GroupName, Config) -> +init_per_group_openssl(GroupName, Config0) -> case is_tls_version(GroupName) andalso sufficient_crypto_support(GroupName) of true -> + Config = clean_protocol_version(Config0), case openssl_tls_version_support(GroupName, Config) of true -> @@ -292,7 +299,7 @@ init_per_group_openssl(GroupName, Config) -> case sufficient_crypto_support(GroupName) of true -> ssl:start(), - Config; + Config0; false -> {skip, "Missing crypto support"} end @@ -314,6 +321,24 @@ openssl_ocsp_support() -> false end. +openssl_ciphers() -> + Str = portable_cmd("openssl", ["ciphers"]), + Ciphers = string:split(string:strip(Str, right, $\n), ":", all), + case portable_cmd("openssl", ["version"]) of + "LibreSSL 3." ++ _ -> + Ciphers -- ["DES-CBC3-SHA","AES128-SHA", "AES256-SHA", "RC4-SHA", "RC4-MD5"]; + _ -> + Ciphers + end. + +openssl_support_rsa_kex() -> + case portable_cmd("openssl", ["version"]) of + "OpenSSL 1.1.1" ++ _Rest -> + false; + _ -> + true + end. + %%==================================================================== %% Internal functions %%==================================================================== @@ -640,7 +665,7 @@ init_openssl_server(openssl, _, Options) -> Pid = proplists:get_value(from, Options), Exe = "openssl", - Ciphers = proplists:get_value(ciphers, Options, ssl:cipher_suites(default,Version)), + Ciphers = proplists:get_value(ciphers, Options, default_ciphers(Version)), Groups0 = proplists:get_value(groups, Options), CertArgs = openssl_cert_options(Options, server), AlpnArgs = openssl_alpn_options(proplists:get_value(alpn, Options, undefined)), @@ -1606,11 +1631,16 @@ make_rsa_1024_cert(Config) -> appropriate_sha(CryptoSupport) -> Hashes = proplists:get_value(hashs, CryptoSupport), - case lists:member(sha256, Hashes) of - true -> - sha256; - false -> - sha1 + case portable_cmd("openssl", ["version"]) of + "OpenSSL 0.9.8" ++ _ -> + sha; + _ -> + case lists:member(sha256, Hashes) of + true -> + sha256; + false -> + sha + end end. %% RFC 4492, Sect. 2.3. ECDH_RSA @@ -2007,13 +2037,12 @@ start_server(openssl, ClientOpts, ServerOpts, Config) -> start_server(erlang, _, ServerOpts, Config) -> {_, ServerNode, _} = run_where(Config), KeyEx = proplists:get_value(check_keyex, Config, false), - Versions = protocol_versions(Config), Server = start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, - {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]), + {options, [{verify, verify_peer} | ServerOpts]}]), {Server, inet_port(Server)}. sig_algs(undefined) -> @@ -2064,7 +2093,6 @@ openssl_maxfag_option(Int) -> openssl_debug_options() -> ["-msg", "-debug"]. - start_server_with_raw_key(erlang, ServerOpts, Config) -> {_, ServerNode, _} = run_where(Config), Server = start_server([{node, ServerNode}, {port, 0}, @@ -2131,7 +2159,8 @@ openssl_cert_options(Opts, Role) -> Other end; _ -> - cert_option("-cert", Cert) ++ cert_option("-CAfile", CA) ++ + cert_option("-cert", Cert) ++ cert_option("-CAfile", CA) + ++ cert_option("-cert_chain", CA) ++ cert_option("-key", Key) ++ openssl_verify(Opts) ++ ["2"] end. @@ -2145,6 +2174,13 @@ openssl_verify(Opts) -> cert_option(_, undefined) -> []; +cert_option("-cert_chain", Value) -> + case portable_cmd("openssl", ["version"]) of + "OpenSSL 1.1.1" ++ _ -> + ["-cert_chain", Value]; + _ -> + "" + end; cert_option(Opt, Value) -> [Opt, Value]. @@ -2399,7 +2435,7 @@ init_protocol_version(Version, Config) -> [{protocol, tls} | NewConfig]. clean_protocol_version(Config) -> - proplists:delete(protocol_opts, proplists:delete(protocol, Config)). + proplists:delete(version, proplists:delete(protocol_opts, proplists:delete(protocol, Config))). sufficient_crypto_support(Version) when Version == 'tlsv1.3' -> @@ -2594,12 +2630,18 @@ active_recv(_Socket, N, Acc) when N < 0 -> active_recv(Socket, N, Acc) -> receive {ssl, Socket, Bytes} -> - active_recv(Socket, N-length(Bytes), Acc ++ Bytes); + active_recv(Socket, N-data_length(Bytes), Acc ++ Bytes); {Socket, {data, Bytes0}} -> Bytes = filter_openssl_debug_data(Bytes0), - active_recv(Socket, N-length(Bytes), Acc ++ Bytes) + active_recv(Socket, N-data_length(Bytes), Acc ++ Bytes) end. + +data_length(Bytes) when is_list(Bytes) -> + length(Bytes); +data_length(Bytes) when is_binary(Bytes)-> + size(Bytes). + filter_openssl_debug_data(Bytes) -> re:replace(Bytes, "(read.*\n|write to.*\n|[\\dabcdefABCDEF]{4,4} -.*\n|>>> .*\n|<<< .*\n| \\d\\d.*\n|KEYUPDATE\n|.*Read BLOCK\n)*", @@ -3055,12 +3097,29 @@ clean_env() -> application:unset_env(ssl, bypass_pem_cache), application:unset_env(ssl, alert_timeout), application:unset_env(ssl, internal_active_n). +%% +clean_env(keep_version) -> + application:unset_env(ssl, session_lifetime), + application:unset_env(ssl, session_cb), + application:unset_env(ssl, session_cb_init_args), + application:unset_env(ssl, session_cache_client_max), + application:unset_env(ssl, session_cache_server_max), + application:unset_env(ssl, ssl_pem_cache_clean), + application:unset_env(ssl, bypass_pem_cache), + application:unset_env(ssl, alert_timeout), + application:unset_env(ssl, internal_active_n). clean_start() -> ssl:stop(), application:load(ssl), clean_env(), ssl:start(). +%% +clean_start(keep_version) -> + ssl:stop(), + application:load(ssl), + clean_env(keep_version), + ssl:start(). is_psk_anon_suite({psk, _,_}) -> true; @@ -3549,9 +3608,21 @@ assert_mfl(Socket, MFL) -> bigger_buffers() -> case os:type() of {unix,sunos} -> - [{recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}]; + [{buffer, ?BIG_BUF}, {recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}]; {unix,openbsd} -> - [{recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}]; + [{buffer, ?BIG_BUF}, {recbuf, ?BIG_BUF},{sndbuf, ?BIG_BUF}]; _ -> [] end. + +default_ciphers(Version) -> + OpenSSLCiphers = openssl_ciphers(), + Ciphers = + case portable_cmd("openssl", ["version"]) of + "OpenSSL 0.9" ++ _ -> + ssl:cipher_suites(all,Version); + _ -> + ssl:cipher_suites(default, Version) + end, + [Cipher || Cipher <- Ciphers, lists:member(ssl:suite_to_openssl_str(Cipher), OpenSSLCiphers)]. + diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl index b258b9b057..cc5db69788 100644 --- a/lib/ssl/test/ssl_upgrade_SUITE.erl +++ b/lib/ssl/test/ssl_upgrade_SUITE.erl @@ -19,6 +19,8 @@ %% -module(ssl_upgrade_SUITE). +-behaviour(ct_suite). + -include_lib("common_test/include/ct.hrl"). %% Common test diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl index 3fd1683029..03fcb9afe5 100644 --- a/lib/ssl/test/tls_1_3_version_SUITE.erl +++ b/lib/ssl/test/tls_1_3_version_SUITE.erl @@ -59,25 +59,27 @@ %%-------------------------------------------------------------------- all() -> [ - {group, 'tlsv1.3'} + cert_groups() ]. groups() -> [ - {'tlsv1.3', [], cert_groups()}, - {rsa, [], tests()}, - {ecdsa, [], tests()} + {rsa, [], tls_1_3_1_2_tests() ++ legacy_tests()}, + {ecdsa, [], tls_1_3_1_2_tests()} ]. cert_groups() -> [{group, rsa}, {group, ecdsa}]. -tests() -> +tls_1_3_1_2_tests() -> [tls13_client_tls12_server, tls13_client_with_ext_tls12_server, tls12_client_tls13_server, - tls_client_tls10_server, + tls_client_tls12_server, + tls12_client_tls_server]. +legacy_tests() -> + [tls_client_tls10_server, tls_client_tls11_server, tls_client_tls12_server, tls10_client_tls_server, @@ -88,9 +90,14 @@ init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> - ssl_test_lib:clean_start(), - [{client_type, erlang}, {server_type, erlang} | - Config] + case ssl_test_lib:sufficient_crypto_support('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + [{client_type, erlang}, {server_type, erlang} | + Config]; + false -> + {skip, "Insufficient crypto support for TLS-1.3"} + end catch _:_ -> {skip, "Crypto did not start"} end. @@ -99,23 +106,14 @@ end_per_suite(_Config) -> ssl:stop(), application:stop(crypto). -init_per_group(GroupName, Config) -> - case ssl_test_lib:is_protocol_version(GroupName) of - true -> - ssl_test_lib:init_per_group(GroupName, - [{client_type, erlang}, - {server_type, erlang} | Config]); - false -> - do_init_per_group(GroupName, Config) - end. - -do_init_per_group(rsa, Config0) -> +init_per_group(rsa, 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), - [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + [{client_type, erlang}, + {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} | lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; -do_init_per_group(ecdsa, Config0) -> +init_per_group(ecdsa, Config0) -> PKAlg = crypto:supports(public_keys), case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of @@ -123,7 +121,8 @@ do_init_per_group(ecdsa, Config0) -> Config = ssl_test_lib:make_ecdsa_cert(Config0), COpts = proplists:get_value(client_ecdsa_opts, Config), SOpts = proplists:get_value(server_ecdsa_opts, Config), - [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + [{client_type, erlang}, + {server_type, erlang},{client_cert_opts, COpts}, {server_cert_opts, SOpts} | lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; false -> {skip, "Missing EC crypto support"} @@ -175,21 +174,38 @@ tls12_client_tls13_server(Config) when is_list(Config) -> tls_client_tls10_server() -> [{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.0 server."}]. tls_client_tls10_server(Config) when is_list(Config) -> + CCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'), + [{key_exchange, fun(srp_rsa) -> false; + (srp_anon) -> false; + (srp_dss) -> false; + (_) -> true end}]), ClientOpts = [{versions, - ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} | + ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']}, + {ciphers, CCiphers} + | ssl_test_lib:ssl_options(client_cert_opts, Config)], ServerOpts = [{versions, - ['tlsv1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ['tlsv1']}, + {ciphers, ssl:cipher_suites(all, 'tlsv1')} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). tls_client_tls11_server() -> [{doc,"Test that a TLS 1.0-1.3 client can connect to a TLS 1.1 server."}]. tls_client_tls11_server(Config) when is_list(Config) -> + CCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'), + [{key_exchange, fun(srp_rsa) -> false; + (srp_anon) -> false; + (srp_dss) -> false; + (_) -> true end}]), ClientOpts = [{versions, - ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']} | + ['tlsv1', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3']}, + {ciphers, CCiphers} | ssl_test_lib:ssl_options(client_cert_opts, Config)], ServerOpts = [{versions, - ['tlsv1.1']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ['tlsv1.1']}, + {ciphers, ssl:cipher_suites(all, 'tlsv1.1')} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). tls_client_tls12_server() -> @@ -205,20 +221,36 @@ tls_client_tls12_server(Config) when is_list(Config) -> tls10_client_tls_server() -> [{doc,"Test that a TLS 1.0 client can connect to a TLS 1.0-1.3 server."}]. tls10_client_tls_server(Config) when is_list(Config) -> + SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'), + [{key_exchange, fun(srp_rsa) -> false; + (srp_anon) -> false; + (srp_dss) -> false; + (_) -> true end}]), ClientOpts = [{versions, - ['tlsv1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ['tlsv1']}, {ciphers, ssl:cipher_suites(all, 'tlsv1')} | ssl_test_lib:ssl_options(client_cert_opts, Config)], ServerOpts = [{versions, - ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} | + ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']}, + {ciphers, SCiphers} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). tls11_client_tls_server() -> [{doc,"Test that a TLS 1.1 client can connect to a TLS 1.0-1.3 server."}]. tls11_client_tls_server(Config) when is_list(Config) -> + SCiphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, 'tlsv1.3'), + [{key_exchange, fun(srp_rsa) -> false; + (srp_anon) -> false; + (srp_dss) -> false; + (_) -> true end}]), + ClientOpts = [{versions, - ['tlsv1.1']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ['tlsv1.1']}, {ciphers, ssl:cipher_suites(all, 'tlsv1.1')} | + ssl_test_lib:ssl_options(client_cert_opts, Config)], ServerOpts = [{versions, - ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']} | + ['tlsv1','tlsv1.1', 'tlsv1.2', 'tlsv1.3']}, + {ciphers, SCiphers} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl index 82b785faa4..26f086f11c 100644 --- a/lib/ssl/test/tls_api_SUITE.erl +++ b/lib/ssl/test/tls_api_SUITE.erl @@ -55,6 +55,8 @@ tls_client_closes_socket/1, tls_closed_in_active_once/0, tls_closed_in_active_once/1, + tls_reset_in_active_once/0, + tls_reset_in_active_once/1, tls_tcp_msg/0, tls_tcp_msg/1, tls_tcp_msg_big/0, @@ -123,6 +125,7 @@ api_tests() -> tls_shutdown_error, tls_client_closes_socket, tls_closed_in_active_once, + tls_reset_in_active_once, tls_tcp_msg, tls_tcp_msg_big, tls_dont_crash_on_handshake_garbage, @@ -367,10 +370,10 @@ tls_client_closes_socket(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, {error,closed}). %%-------------------------------------------------------------------- -tls_closed_in_active_once() -> +tls_reset_in_active_once() -> [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. -tls_closed_in_active_once(Config) when is_list(Config) -> +tls_reset_in_active_once(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -396,6 +399,40 @@ tls_closed_in_active_once(Config) when is_list(Config) -> ok -> ok; _ -> ct:fail(Result) end. + +%%-------------------------------------------------------------------- +tls_closed_in_active_once() -> + [{doc, "Test that active once can be used to deliver not only all data" + " but even the close message, see ERL-1409, in normal operation." + " This is also test, with slighly diffrent circumstances in" + " the old tls_closed_in_active_once test" + " renamed tls_reset_in_active_once"}]. + +tls_closed_in_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + Port = ssl_test_lib:inet_port(node()), + Server = fun() -> + {ok, Listen} = gen_tcp:listen(Port, TcpOpts), + {ok, TcpServerSocket} = gen_tcp:accept(Listen), + {ok, ServerSocket} = ssl:handshake(TcpServerSocket, ServerOpts), + lists:foreach( + fun(_) -> + ssl:send(ServerSocket, "some random message\r\n") + end, lists:seq(1, 20)), + ssl:close(ServerSocket) + end, + spawn_link(Server), + {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), + Result = tls_closed_in_active_once_loop(Socket), + ssl:close(Socket), + case Result of + ok -> ok; + _ -> ct:fail(Result) + end. + %%-------------------------------------------------------------------- tls_tcp_msg() -> [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. @@ -468,32 +505,35 @@ tls_dont_crash_on_handshake_garbage() -> tls_dont_crash_on_handshake_garbage(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), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - unlink(Server), monitor(process, Server), + {from, self()}, + {mfa, ssl_test_lib, no_result}, + {options, [{versions, [Version]} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - % Send hello and garbage record + %% Send hello and garbage record ok = gen_tcp:send(Socket, [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello 16#deadbeef:256, % 32 'random' bytes = 256 bits 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values - <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage ]), - % Send unexpected change_cipher_spec + %% Send unexpected change_cipher_spec ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), - + gen_tcp:close(Socket), % Ensure we receive an alert, not sudden disconnect - {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). - + case Version of + 'tlsv1.3' -> + ssl_test_lib:check_server_alert(Server, illegal_parameter); + _ -> + ssl_test_lib:check_server_alert(Server, handshake_failure) + end. + %%-------------------------------------------------------------------- tls_tcp_error_propagation_in_active_mode() -> [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. @@ -774,15 +814,9 @@ upgrade_result(Socket) -> ok = ssl:send(Socket, "Hello world"), %% Make sure binary is inherited from tcp socket and that we do %% not get the list default! - receive - {ssl, _, <<"H">>} -> - receive - {ssl, _, <<"ello world">>} -> - ok - end; - {ssl, _, <<"Hello world">>} -> - ok - end. + <<"Hello world">> = ssl_test_lib:active_recv(Socket, length("Hello world")), + ok. + tls_downgrade_result(Socket, Pid) -> ok = ssl_test_lib:send_recv_result(Socket), Pid ! {self(), ready}, @@ -794,16 +828,8 @@ tls_downgrade_result(Socket, Pid) -> {ok, TCPSocket} -> inet:setopts(TCPSocket, [{active, true}]), gen_tcp:send(TCPSocket, "Downgraded"), - receive - {tcp, TCPSocket, <<"Downgraded">>} -> - ct:sleep(?SLEEP), - ok; - {tcp_closed, TCPSocket} -> - ct:fail("Did not receive TCP data"), - ok; - Other -> - {error, Other} - end; + <<"Downgraded">> = active_tcp_recv(TCPSocket, length("Downgraded")), + ok; {error, timeout} -> ct:comment("Timed out, downgrade aborted"), ok; @@ -843,19 +869,9 @@ tls_closed_in_active_once_loop(Socket) -> tls_closed_in_active_once_loop(Socket); {ssl_closed, Socket} -> ok - after 5000 -> - no_ssl_closed_received end; {error, closed} -> - ok - end. - -drop_handshakes(Socket, Timeout) -> - {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), - {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), - case RecType of - 22 -> drop_handshakes(Socket, Timeout); - _ -> {ok, <<Header/binary, Frag/binary>>} + {error, ssl_setopt_failed} end. receive_msg(_) -> @@ -877,3 +893,14 @@ tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) ct:log("All opts ~p~n", [All]), ok. +active_tcp_recv(Socket, N) -> + active_tcp_recv(Socket, N, []). + +active_tcp_recv(_Socket, 0, Acc) -> + Acc; +active_tcp_recv(Socket, N, Acc) -> + receive + {tcp, Socket, Bytes} -> + active_tcp_recv(Socket, N-size(Bytes), Acc ++ Bytes) + end. + diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index b50afeb1be..2b9e7f5e6b 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 10.1 +SSL_VSN = 10.2.3 |