diff options
Diffstat (limited to 'lib/ssl')
51 files changed, 4493 insertions, 3113 deletions
diff --git a/lib/ssl/doc/specs/.gitignore b/lib/ssl/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/ssl/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/ssl/doc/src/Makefile b/lib/ssl/doc/src/Makefile index c72b6d6cc4..7cf251d8f9 100644 --- a/lib/ssl/doc/src/Makefile +++ b/lib/ssl/doc/src/Makefile @@ -80,11 +80,16 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf +SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) + +TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- XML_FLAGS += DVIPS_FLAGS += +SPECS_FLAGS = -I../../../public_key/include -I../../../public_key/src -I../../.. # ---------------------------------------------------- # Targets @@ -92,7 +97,7 @@ DVIPS_FLAGS += $(HTMLDIR)/%.gif: %.gif $(INSTALL_DATA) $< $@ -docs: pdf html man +docs: html pdf man $(TOP_PDF_FILE): $(XML_FILES) @@ -105,6 +110,7 @@ clean clean_docs: rm -rf $(XMLDIR) rm -f $(MAN3DIR)/* rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f $(SPECS_FILES) rm -f errs core *~ man: $(MAN3_FILES) $(MAN6_FILES) diff --git a/lib/ssl/doc/src/specs.xml b/lib/ssl/doc/src/specs.xml new file mode 100644 index 0000000000..50e9428fec --- /dev/null +++ b/lib/ssl/doc/src/specs.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_ssl_crl_cache_api.xml"/> + <xi:include href="../specs/specs_ssl_crl_cache.xml"/> + <xi:include href="../specs/specs_ssl_session_cache_api.xml"/> + <xi:include href="../specs/specs_ssl.xml"/> +</specs> + + diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 3029977745..90a9181ede 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -28,7 +28,7 @@ <rev></rev> <file>ssl.xml</file> </header> - <module>ssl</module> + <module since="">ssl</module> <modulesummary>Interface Functions for Secure Socket Layer</modulesummary> <description> <p> @@ -37,277 +37,337 @@ <seealso marker="ssl_app">ssl(6)</seealso>. </p> </description> - - <section> - <title>DATA TYPES</title> - <p>The following data types are used in the functions for SSL/TLS/DTLS:</p> - - <taglist> - - <tag><c>boolean() =</c></tag> - <item><p><c>true | false</c></p></item> - - <tag><c>option() =</c></tag> - <item><p><c>socketoption() | ssl_option() | transport_option()</c></p> - </item> - - <tag><c>socketoption() =</c></tag> - <item><p><c>proplists:property()</c></p> - <p>The default socket options are - <c>[{mode,list},{packet, 0},{header, 0},{active, true}]</c>.</p> - <p>For valid options, see the - <seealso marker="kernel:inet">inet(3)</seealso>, - <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> and - <seealso marker="kernel:gen_tcp">gen_udp(3)</seealso> - manual pages - in Kernel. Note that stream oriented options such as packet are only relevant for SSL/TLS and not DTLS</p></item> - - <tag><marker id="type-ssloption"/><c>ssl_option() =</c></tag> - <item> - <p><c>{verify, verify_type()}</c></p> - <p><c>| {verify_fun, {fun(), term()}}</c></p> - <p><c>| {fail_if_no_peer_cert, boolean()}</c></p> - <p><c>| {depth, integer()}</c></p> - <p><c>| {cert, public_key:der_encoded()}</c></p> - <p><c>| {certfile, path()}</c></p> - <p><c>| {key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' - | 'PrivateKeyInfo', public_key:der_encoded()} | - #{algorithm := rsa | dss | ecdsa, - engine := crypto:engine_ref(), key_id := crypto:key_id(), password => crypto:password()}</c></p> - <p><c>| {keyfile, path()}</c></p> - <p><c>| {password, string()}</c></p> - <p><c>| {cacerts, [public_key:der_encoded()]}</c></p> - <p><c>| {cacertfile, path()}</c></p> - <p><c>| {dh, public_key:der_encoded()}</c></p> - <p><c>| {dhfile, path()}</c></p> - <p><c>| {ciphers, ciphers()}</c></p> - <p><c>| {user_lookup_fun, {fun(), term()}}, {psk_identity, string()}, - {srp_identity, {string(), string()}}</c></p> - <p><c>| {reuse_sessions, boolean()}</c></p> - <p><c>| {reuse_session, fun()} {next_protocols_advertised, [binary()]}</c></p> - <p><c>| {client_preferred_next_protocols, {client | server, - [binary()]} | {client | server, [binary()], binary()}}</c></p> - <p><c>| {log_alert, boolean()}</c></p> - <p><c>| {server_name_indication, hostname() | disable}</c></p> - <p><c>| {customize_hostname_check, list()}</c></p> - <p><c>| {sni_hosts, [{hostname(), [ssl_option()]}]}</c></p> - <p><c>| {sni_fun, SNIfun::fun()}</c></p> - </item> - - <tag><c>transport_option() =</c></tag> - <item><p><c>{cb_info, {CallbackModule::atom(), DataTag::atom(), - - ClosedTag::atom(), ErrTag:atom()}}</c></p> - <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error}</c> for TLS - and <c>{gen_udp, udp, udp_closed, udp_error}</c> for DTLS. Can be used - to customize the transport layer. For TLS the callback module must implement a - reliable transport protocol, behave as <c>gen_tcp</c>, and have functions - corresponding to <c>inet:setopts/2</c>, <c>inet:getopts/2</c>, - <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and <c>inet:port/1</c>. - The callback <c>gen_tcp</c> is treated specially and calls <c>inet</c> - directly. For DTLS this feature must be considered exprimental.</p> - <taglist> - <tag><c>CallbackModule =</c></tag> - <item><p><c>atom()</c></p></item> - <tag><c>DataTag =</c></tag> - <item><p><c>atom()</c></p> - <p>Used in socket data message.</p></item> - <tag><c>ClosedTag =</c></tag> - <item><p><c>atom()</c></p> - <p>Used in socket close message.</p></item> - </taglist> - </item> - - <tag><c>verify_type() =</c></tag> - <item><p><c>verify_none | verify_peer</c></p></item> - - <tag><c>path() =</c></tag> - <item><p><c>string()</c></p> - <p>Represents a file path.</p></item> - <tag><c>public_key:der_encoded() =</c></tag> - <item><p><c>binary()</c></p> - <p>ASN.1 DER-encoded entity as an Erlang binary.</p></item> + <!-- + ================================================================ + = Data types = + ================================================================ + --> - <tag><c>host() =</c></tag> - <item><p><c>hostname() | ipaddress()</c></p></item> + <datatypes> + <datatype_title>Types used in SSL/TLS/DTLS</datatype_title> - <tag><c>hostname() =</c></tag> - <item><p><c>string() - DNS hostname</c></p></item> - - <tag><c>ip_address() =</c></tag> - <item><p><c>{N1,N2,N3,N4} % IPv4 | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6 - </c></p></item> + + <datatype> + <name name="socket"/> + </datatype> + + <datatype> + <name name="sslsocket"/> + <desc> + <p>An opaque reference to the TLS/DTLS connection, may be used for equality matching.</p> + </desc> + </datatype> + + <datatype> + <name name="tls_option"/> + </datatype> + + <datatype> + <name name="tls_client_option"/> + </datatype> + + <datatype> + <name name="tls_server_option"/> + </datatype> + + + <datatype> + <name name="socket_option"/> + <desc> + <p>The default socket options are + <c>[{mode,list},{packet, 0},{header, 0},{active, true}]</c>.</p> + <p>For valid options, see the + <seealso marker="kernel:inet">inet(3)</seealso>, + <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> and + <seealso marker="kernel:gen_tcp">gen_udp(3)</seealso> + manual pages in Kernel. Note that stream oriented options such as packet + are only relevant for SSL/TLS and not DTLS</p> + </desc> + </datatype> - <tag><c>sslsocket() =</c></tag> - <item><p>opaque()</p></item> - - <tag><marker id="type-protocol"/><c> protocol_version() =</c></tag> - <item><p><c> ssl_tls_protocol() | dtls_protocol() </c></p></item> - - <item><p><c>sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'</c></p></item> - - <tag><marker id="type-protocol"/><c> dtls_protocol() =</c></tag> - <item><p><c>'dtlsv1' | 'dtlsv1.2'</c></p></item> - - <tag><c>ciphers() =</c></tag> - <item><p><c>= [ciphersuite()]</c></p> - <p>Tuples and string formats accepted by versions - before ssl-8.2.4 will be converted for backwards compatibility</p></item> - - <tag><c>ciphersuite() =</c></tag> - <item><p><c> - #{key_exchange := key_exchange(), - cipher := cipher(), - mac := MAC::hash() | aead, - prf := PRF::hash() | default_prf} </c></p></item> - - <tag><c>key_exchange()=</c></tag> - <item><p><c>rsa | dhe_dss | dhe_rsa | dh_anon | psk | dhe_psk - | rsa_psk | srp_anon | srp_dss | srp_rsa | ecdh_anon | ecdh_ecdsa - | ecdhe_ecdsa | ecdh_rsa | ecdhe_rsa</c></p></item> - - <tag><c>cipher() =</c></tag> - <item><p><c>rc4_128 | des_cbc | '3des_ede_cbc' - | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305</c></p></item> - - <tag><c>hash() =</c></tag> - <item><p><c>md5 | sha | sha224 | sha256 | sha348 | sha512</c></p></item> - - <tag><c>prf_random() =</c></tag> - <item><p><c>client_random | server_random</c></p></item> - - <tag><c>cipher_filters() =</c></tag> - <item><p><c> [{key_exchange | cipher | mac | prf, algo_filter()}])</c></p></item> - - <tag><c>algo_filter() =</c></tag> - <item><p>fun(key_exchange() | cipher() | hash() | aead | default_prf) -> true | false </p></item> - - <tag><c>srp_param_type() =</c></tag> - <item><p><c>srp_1024 | srp_1536 | srp_2048 | srp_3072 - | srp_4096 | srp_6144 | srp_8192</c></p></item> - - <tag><c>SNIfun::fun()</c></tag> - <item><p><c>= fun(ServerName :: string()) -> [ssl_option()]</c></p></item> - - <tag><c>named_curve() =</c></tag> - <item><p><c>sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 - | sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 - | sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 - | sect239k1 | sect233k1 | sect233r1 | secp224k1 | secp224r1 - | sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 - | sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2</c></p></item> - - <tag><c>hello_extensions() =</c></tag> - <item><p><c>#{renegotiation_info => binary() | undefined, - signature_algs => [{hash(), ecsda| rsa| dsa}] | undefined - alpn => binary() | undefined, - next_protocol_negotiation => binary() | undefined, - srp => string() | undefined, - ec_point_formats => list() | undefined, - elliptic_curves => [oid] | undefined, - sni => string() | undefined} - }</c></p></item> + <datatype> + <name name="active_msgs"/> + <desc> + <p>When a TLS/DTLS socket is in active mode (the default), data from the + socket is delivered to the owner of the socket in the form of + messages as described above.</p> + <p>The <c>ssl_passive</c> message is sent only when the socket is in + <c>{active, N}</c> mode and the counter dropped to 0. It indicates + that the socket has transitioned to passive (<c>{active, false}</c>) mode.</p> + </desc> + </datatype> - - </taglist> - </section> - - <section> - <title>TLS/DTLS OPTION DESCRIPTIONS - COMMON for SERVER and CLIENT</title> - - <p>The following options have the same meaning in the client and - the server:</p> + <datatype> + <name name="transport_option"/> + <desc> + <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error}</c> + for TLS and <c>{gen_udp, udp, udp_closed, udp_error}</c> for + DTLS. Can be used to customize the transport layer. The tag + values should be the values used by the underlying transport + in its active mode messages. For TLS the callback module must implement a + reliable transport protocol, behave as <c>gen_tcp</c>, and have functions + corresponding to <c>inet:setopts/2</c>, <c>inet:getopts/2</c>, + <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and <c>inet:port/1</c>. + The callback <c>gen_tcp</c> is treated specially and calls <c>inet</c> + directly. For DTLS this feature must be considered exprimental. + </p> + </desc> + </datatype> + + <datatype> + <name name="host"/> + </datatype> + + <datatype> + <name name="hostname"/> + </datatype> + + <datatype> + <name name="ip_address"/> + </datatype> + + <datatype> + <name name="protocol_version"/> + </datatype> + + <datatype> + <name name="tls_version"/> + </datatype> + + <datatype> + <name name="dtls_version"/> + </datatype> + + <datatype> + <name name="legacy_version"/> + </datatype> + + <datatype> + <name name="prf_random"/> + </datatype> + + <datatype> + <name name="verify_type"/> + </datatype> + + <datatype> + <name name="ciphers"/> + </datatype> + + <datatype> + <name name="erl_cipher_suite"/> + </datatype> + + <datatype> + <name name="cipher"/> + </datatype> - <taglist> - - <tag><c>{protocol, tls | dtls}</c></tag> - <item><p>Choose TLS or DTLS protocol for the transport layer security. - Defaults to <c>tls</c> Introduced in OTP 20, DTLS support is considered - experimental in this release. Other transports than UDP are not yet supported.</p></item> - - <tag><c>{handshake, hello | full}</c></tag> - <item><p> Defaults to <c>full</c>. If hello is specified the handshake will - pause after the hello message and give the user a possibility make decisions - based on hello extensions before continuing or aborting the handshake by calling - <seealso marker="#handshake_continue-3"> handshake_continue/3</seealso> or - <seealso marker="#handshake_cancel-1"> handshake_cancel/1</seealso> - </p></item> - - <tag><c>{cert, public_key:der_encoded()}</c></tag> - <item><p>The DER-encoded users certificate. If this option - is supplied, it overrides option <c>certfile</c>.</p></item> - - <tag><c>{certfile, path()}</c></tag> - <item><p>Path to a file containing the user certificate.</p></item> - - <tag> - <marker id="key_option_def"/> - <c>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' - |'PrivateKeyInfo', public_key:der_encoded()} | #{algorithm := rsa | dss | ecdsa, - engine := crypto:engine_ref(), key_id := crypto:key_id(), password => crypto:password()}</c></tag> - <item><p>The DER-encoded user's private key or a map refering to a crypto - engine and its key reference that optionally can be password protected, - seealso <seealso marker="crypto:crypto#engine_load-4"> crypto:engine_load/4 - </seealso> and <seealso marker="crypto:engine_load"> Crypto's Users Guide</seealso>. If this option - is supplied, it overrides option <c>keyfile</c>.</p></item> - - <tag><c>{keyfile, path()}</c></tag> - <item><p>Path to the file containing the user's - private PEM-encoded key. As PEM-files can contain several - entries, this option defaults to the same file as given by - option <c>certfile</c>.</p></item> - - <tag><c>{password, string()}</c></tag> - <item><p>String containing the user's password. Only used if the - private keyfile is password-protected.</p></item> - - <tag><c>{ciphers, ciphers()}</c></tag> - <item><p>Supported cipher suites. The function - <c>cipher_suites/0</c> can be used to find all ciphers that are - supported by default. <c>cipher_suites(all)</c> can be called - to find all available cipher suites. Pre-Shared Key - (<url href="http://www.ietf.org/rfc/rfc4279.txt">RFC 4279</url> and - <url href="http://www.ietf.org/rfc/rfc5487.txt">RFC 5487</url>), - Secure Remote Password - (<url href="http://www.ietf.org/rfc/rfc5054.txt">RFC 5054</url>), RC4 cipher suites, - and anonymous cipher suites only work if explicitly enabled by - this option; they are supported/enabled by the peer also. - Anonymous cipher suites are supported for testing purposes - only and are not be used when security matters.</p></item> - - <tag><c>{eccs, [named_curve()]}</c></tag> - <item><p> Allows to specify the order of preference for named curves - and to restrict their usage when using a cipher suite supporting them. - </p></item> - - <tag><c>{secure_renegotiate, boolean()}</c></tag> - <item><p>Specifies if to reject renegotiation attempt that does - not live up to - <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>. - By default <c>secure_renegotiate</c> is set to <c>true</c>, - that is, secure renegotiation is enforced. If set to <c>false</c> secure renegotiation - will still be used if possible, - but it falls back to insecure renegotiation if the peer - does not support - <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>.</p> - </item> - - <tag><c>{depth, integer()}</c></tag> - <item><p>Maximum number of non-self-issued + <datatype> + <name name="legacy_cipher"/> + </datatype> + + <datatype> + <name name="cipher_filters"/> + </datatype> + + <datatype> + <name name="hash"/> + </datatype> + + <datatype> + <name name="sha2"/> + </datatype> + + <datatype> + <name name="legacy_hash"/> + </datatype> + + <datatype> + <name name="old_cipher_suite"/> + </datatype> + + <datatype> + <name name="signature_algs"/> + </datatype> + + <datatype> + <name name="sign_algo"/> + </datatype> + + <datatype> + <name name="kex_algo"/> + </datatype> + + <datatype> + <name name="algo_filter"/> + </datatype> + + <datatype> + <name name="eccs"/> + </datatype> + + <datatype> + <name name="named_curve"/> + </datatype> + + <datatype> + <name name="psk_identity"/> + </datatype> + + <datatype> + <name name="srp_identity"/> + </datatype> + + <datatype> + <name name="srp_param_type"/> + </datatype> + + <datatype> + <name name="app_level_protocol"/> + </datatype> + + <datatype> + <name name="protocol_extensions"/> + </datatype> + + <datatype> + <name name="error_alert"/> + </datatype> + + <datatype> + <name name="tls_alert"/> + </datatype> + + <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - COMMON for SERVER and CLIENT</datatype_title> + + <datatype> + <name name="common_option"/> + </datatype> + + <datatype> + <name since="OTP 20" name="protocol"/> + <desc> + <p>Choose TLS or DTLS protocol for the transport layer security. + Defaults to <c>tls</c>. For DTLS other transports than UDP are not yet supported.</p> + </desc> + </datatype> + + <datatype> + <name name="handshake_completion"/> + <desc> + <p>Defaults to <c>full</c>. If hello is specified the handshake will + pause after the hello message and give the user a possibility make decisions + based on hello extensions before continuing or aborting the handshake by calling + <seealso marker="#handshake_continue-3"> handshake_continue/3</seealso> or + <seealso marker="#handshake_cancel-1"> handshake_cancel/1</seealso></p> + </desc> + </datatype> + + <datatype> + <name name="cert"/> + <desc> + <p>The DER-encoded users certificate. 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> + </desc> + </datatype> + + <datatype> + <name name="key"/> + <desc> + <p>The DER-encoded user's private key or a map refering to a crypto + engine and its key reference that optionally can be password protected, + seealso <seealso marker="crypto:crypto#engine_load-4"> crypto:engine_load/4 + </seealso> and <seealso marker="crypto:engine_load"> Crypto's Users Guide</seealso>. If this option + is supplied, it overrides option <c>keyfile</c>.</p> + </desc> + </datatype> + + <datatype> + <name name="key_pem"/> + <desc> + <p>Path to the file containing the user's + private PEM-encoded key. As PEM-files can contain several + entries, this option defaults to the same file as given by + option <c>certfile</c>.</p> + </desc> + </datatype> + + <datatype> + <name name="key_password"/> + <desc> + <p>String containing the user's password. Only used if the + private keyfile is password-protected.</p> + </desc> + </datatype> + + <datatype> + <name name="cipher_suites"/> + <desc> + <p>Supported cipher suites. The function + <c>cipher_suites/2</c> can be used to find all ciphers that + are supported by default. <c>cipher_suites(all, 'tlsv1.2')</c> can be + called to find all available cipher suites. Pre-Shared Key + (<url href="http://www.ietf.org/rfc/rfc4279.txt">RFC + 4279</url> and <url + href="http://www.ietf.org/rfc/rfc5487.txt">RFC 5487</url>), + Secure Remote Password (<url + href="http://www.ietf.org/rfc/rfc5054.txt">RFC 5054</url>), + RC4, 3DES, DES cipher suites, and anonymous cipher suites only work if + explicitly enabled by this option; they are supported/enabled + by the peer also. Anonymous cipher suites are supported for + testing purposes only and are not be used when security + matters.</p> + </desc> + </datatype> + + <datatype> + <name name="eccs"/> + <desc><p> Allows to specify the order of preference for named curves + and to restrict their usage when using a cipher suite supporting them.</p> + </desc> + </datatype> + + <datatype> + <name name="secure_renegotiation"/> + <desc><p>Specifies if to reject renegotiation attempt that does + not live up to <url + href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>. By + default <c>secure_renegotiate</c> is set to <c>true</c>, that + is, secure renegotiation is enforced. If set to <c>false</c> + secure renegotiation will still be used if possible, but it + falls back to insecure renegotiation if the peer does not + support <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC + 5746</url>.</p> + </desc> + </datatype> + + <datatype> + <name name="allowed_cert_chain_length"/> + <desc><p>Maximum number of non-self-issued intermediate certificates that can follow the peer certificate 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></item> - - <tag><marker id="verify_fun"/><c>{verify_fun, {Verifyfun :: fun(), InitialUserState :: - term()}}</c></tag> - <item><p>The verification fun is to be defined as follows:</p> + ROOT-CA, and so on. The default value is 1.</p> + </desc> + </datatype> + + <datatype> + <name name="custom_verify"/> + <desc> + <p>The verification fun is to be defined as follows:</p> <code> -fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | {revoked, -atom()}} | +fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | + {revoked, atom()}} | {extension, #'Extension'{}}, InitialUserState :: term()) -> {valid, UserState :: term()} | {valid_peer, UserState :: term()} | {fail, Reason :: term()} | {unknown, UserState :: term()}. @@ -315,20 +375,21 @@ atom()}} | <p>The verification fun is called during the X509-path validation when an error or an extension unknown to the SSL - application is encountered. It is also called - when a certificate is considered valid by the path validation - to allow access to each certificate in the path to the user - application. It differentiates between the peer - certificate and the CA certificates by using <c>valid_peer</c> or - <c>valid</c> as second argument to the verification fun. See the - <seealso marker="public_key:public_key_records">public_key User's - Guide</seealso> for definition of <c>#'OTPCertificate'{}</c> and - <c>#'Extension'{}</c>.</p> + application is encountered. It is also called when a + certificate is considered valid by the path validation to + allow access to each certificate in the path to the user + application. It differentiates between the peer certificate + and the CA certificates by using <c>valid_peer</c> or + <c>valid</c> as second argument to the verification fun. See + the <seealso marker="public_key:public_key_records">public_key + User's Guide</seealso> for definition of + <c>#'OTPCertificate'{}</c> and <c>#'Extension'{}</c>.</p> <list type="bulleted"> - <item><p>If the verify callback fun returns <c>{fail, Reason}</c>, - the verification process is immediately stopped, an alert is - sent to the peer, and the TLS/DTLS handshake terminates.</p></item> + <item><p>If the verify callback fun returns <c>{fail, + Reason}</c>, the verification process is immediately + stopped, an alert is sent to the peer, and the TLS/DTLS + handshake terminates.</p></item> <item><p>If the verify callback fun returns <c>{valid, UserState}</c>, the verification process continues.</p></item> <item><p>If the verify callback fun always returns @@ -378,10 +439,12 @@ atom()}} | <taglist> <tag><c>unknown_ca</c></tag> - <item><p>No trusted CA was found in the trusted store. The trusted CA is - normally a so called ROOT CA, which is a self-signed certificate. Trust can - be claimed for an intermediate CA (trusted anchor does not have to be - self-signed according to X-509) by using option <c>partial_chain</c>.</p> + <item><p>No trusted CA was found in the trusted store. The + trusted CA is normally a so called ROOT CA, which is a + self-signed certificate. Trust can be claimed for an + intermediate CA (trusted anchor does not have to be + self-signed according to X-509) by using option + <c>partial_chain</c>.</p> </item> <tag><c>selfsigned_peer</c></tag> @@ -392,15 +455,17 @@ atom()}} | marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso> </p></item> </taglist> - </item> - - <tag><c>{crl_check, boolean() | peer | best_effort }</c></tag> - <item> + </desc> + </datatype> + + <datatype> + <name name="crl_check"/> + <desc> <p>Perform CRL (Certificate Revocation List) verification <seealso marker="public_key:public_key#pkix_crls_validate-3"> - (public_key:pkix_crls_validate/3)</seealso> on all the certificates during the path validation - <seealso - marker="public_key:public_key#pkix_path_validation-3">(public_key:pkix_path_validation/3) + (public_key:pkix_crls_validate/3)</seealso> on all the + certificates during the path validation <seealso + marker="public_key:public_key#pkix_path_validation-3">(public_key:pkix_path_validation/3) </seealso> of the certificate chain. Defaults to <c>false</c>.</p> @@ -412,112 +477,111 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid <item>if certificate revocation status can not be determined it will be accepted as valid.</item> </taglist> - + <p>The CA certificates specified for the connection will be used to construct the certificate chain validating the CRLs.</p> <p>The CRLs will be fetched from a local or external cache. See <seealso marker="ssl:ssl_crl_cache_api">ssl_crl_cache_api(3)</seealso>.</p> - </item> - - <tag><c>{crl_cache, {Module :: atom(), {DbHandle :: internal | term(), Args :: list()}}}</c></tag> - <item> - <p>Specify how to perform lookup and caching of certificate revocation lists. - <c>Module</c> defaults to <seealso marker="ssl:ssl_crl_cache">ssl_crl_cache</seealso> - with <c> DbHandle </c> being <c>internal</c> and an - empty argument list.</p> - - <p>There are two implementations available:</p> - - <taglist> - <tag><c>ssl_crl_cache</c></tag> - <item> - <p>This module maintains a cache of CRLs. CRLs can be - added to the cache using the function <seealso - marker="ssl:ssl_crl_cache#insert-1">ssl_crl_cache:insert/1</seealso>, - and optionally automatically fetched through HTTP if the - following argument is specified:</p> - - <taglist> - <tag><c>{http, timeout()}</c></tag> - <item><p> - Enables fetching of CRLs specified as http URIs in<seealso - marker="public_key:public_key_records">X509 certificate extensions</seealso>. - Requires the OTP inets application.</p> - </item> - </taglist> - </item> - - <tag><c>ssl_crl_hash_dir</c></tag> - <item> - <p>This module makes use of a directory where CRLs are - stored in files named by the hash of the issuer name.</p> - - <p>The file names consist of eight hexadecimal digits - followed by <c>.rN</c>, where <c>N</c> is an integer, - e.g. <c>1a2b3c4d.r0</c>. For the first version of the - CRL, <c>N</c> starts at zero, and for each new version, - <c>N</c> is incremented by one. The OpenSSL utility - <c>c_rehash</c> creates symlinks according to this - pattern.</p> - - <p>For a given hash value, this module finds all - consecutive <c>.r*</c> files starting from zero, and those - files taken together make up the revocation list. CRL - files whose <c>nextUpdate</c> fields are in the past, or - that are issued by a different CA that happens to have the - same name hash, are excluded.</p> - - <p>The following argument is required:</p> - - <taglist> - <tag><c>{dir, string()}</c></tag> - <item><p>Specifies the directory in which the CRLs can be found.</p></item> - </taglist> - - </item> - - <tag><c>max_handshake_size</c></tag> - <item> - <p>Integer (24 bits unsigned). Used to limit the size of - valid TLS handshake packets to avoid DoS attacks. - Defaults to 256*1024.</p> - </item> - - </taglist> - - </item> + </desc> + </datatype> - <tag><c>{partial_chain, fun(Chain::[DerCert]) -> {trusted_ca, DerCert} | - unknown_ca }</c></tag> - <item><p>Claim an intermediate CA in the chain as trusted. TLS then - performs <seealso - marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso> - with the selected CA as trusted anchor and the rest of the chain.</p></item> + <datatype> + <name name="crl_cache_opts"/> + <desc> + <p>Specify how to perform lookup and caching of certificate revocation lists. + <c>Module</c> defaults to <seealso marker="ssl:ssl_crl_cache">ssl_crl_cache</seealso> + with <c> DbHandle </c> being <c>internal</c> and an + empty argument list.</p> + + <p>There are two implementations available:</p> + + <taglist> + <tag><c>ssl_crl_cache</c></tag> + <item> + <p>This module maintains a cache of CRLs. CRLs can be + added to the cache using the function <seealso + marker="ssl:ssl_crl_cache#insert-1">ssl_crl_cache:insert/1</seealso>, + and optionally automatically fetched through HTTP if the + following argument is specified:</p> + + <taglist> + <tag><c>{http, timeout()}</c></tag> + <item><p> + Enables fetching of CRLs specified as http URIs in<seealso + marker="public_key:public_key_records">X509 certificate extensions</seealso>. + Requires the OTP inets application.</p> + </item> + </taglist> + </item> + + <tag><c>ssl_crl_hash_dir</c></tag> + <item> + <p>This module makes use of a directory where CRLs are + stored in files named by the hash of the issuer name.</p> + + <p>The file names consist of eight hexadecimal digits + followed by <c>.rN</c>, where <c>N</c> is an integer, + e.g. <c>1a2b3c4d.r0</c>. For the first version of the + CRL, <c>N</c> starts at zero, and for each new version, + <c>N</c> is incremented by one. The OpenSSL utility + <c>c_rehash</c> creates symlinks according to this + pattern.</p> + + <p>For a given hash value, this module finds all + consecutive <c>.r*</c> files starting from zero, and those + files taken together make up the revocation list. CRL + files whose <c>nextUpdate</c> fields are in the past, or + that are issued by a different CA that happens to have the + same name hash, are excluded.</p> + + <p>The following argument is required:</p> + + <taglist> + <tag><c>{dir, string()}</c></tag> + <item><p>Specifies the directory in which the CRLs can be found.</p></item> + </taglist> + </item> + </taglist> + </desc> + </datatype> + + <datatype> + <name name="root_fun"/> + <desc> + <code> +fun(Chain::[public_key:der_encoded()]) -> + {trusted_ca, DerCert::public_key:der_encoded()} | unknown_ca} + </code> + <p>Claim an intermediate CA in the chain as trusted. TLS then + performs <seealso + marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso> + with the selected CA as trusted anchor and the rest of the chain.</p> + </desc> + </datatype> - <tag><c>{versions, [protocol_version()]}</c></tag> - <item><p>TLS protocol versions supported by started clients and servers. + <datatype> + <name name="protocol_versions"/> + <desc><p>TLS protocol versions supported by started clients and servers. This option overrides the application environment option <c>protocol_version</c> and <c>dtls_protocol_version</c>. If the environment option is not set, it defaults to all versions, except SSL-3.0, supported by the SSL application. - See also <seealso marker="ssl:ssl_app">ssl(6).</seealso></p></item> + See also <seealso marker="ssl:ssl_app">ssl(6).</seealso></p> + </desc> + </datatype> - <tag><c>{hibernate_after, integer()|undefined}</c></tag> - <item><p>When an integer-value is specified, <c>TLS/DTLS-connection</c> - goes into hibernation after the specified number of milliseconds - of inactivity, thus reducing its memory footprint. When - <c>undefined</c> is specified (this is the default), the process - never goes into hibernation.</p></item> - <tag><c>{user_lookup_fun, {Lookupfun :: fun(), UserState :: term()}}</c></tag> - <item><p>The lookup fun is to defined as follows:</p> + <datatype> + <name name="custom_user_lookup"/> + <desc><p>The lookup fun is to defined as follows:</p> <code> fun(psk, PSKIdentity ::string(), UserState :: term()) -> {ok, SharedSecret :: binary()} | error; fun(srp, Username :: string(), UserState :: term()) -> - {ok, {SRPParams :: srp_param_type(), Salt :: binary(), DerivedKey :: binary()}} | error. + {ok, {SRPParams :: srp_param_type(), Salt :: binary(), + DerivedKey :: binary()}} | error. </code> <p>For Pre-Shared Key (PSK) cipher suites, the lookup fun is @@ -533,20 +597,54 @@ fun(srp, Username :: string(), UserState :: term()) -> <url href="http://tools.ietf.org/html/rfc5054#section-2.4"> RFC 5054</url>: <c>crypto:sha([Salt, crypto:sha([Username, <<$:>>, Password])])</c> </p> - </item> + </desc> + </datatype> - <tag><c>{padding_check, boolean()}</c></tag> - <item><p>Affects TLS-1.0 connections only. + <datatype> + <name name="session_id"/> + <desc> + <p>Identifies a TLS session.</p> + </desc> + </datatype> + + <datatype> + <name name="log_alert"/> + <desc><p>If set to <c>false</c>, error reports are not displayed.</p> + </desc> + </datatype> + + <datatype> + <name name="hibernate_after"/> + <desc><p>When an integer-value is specified, <c>TLS/DTLS-connection</c> + goes into hibernation after the specified number of milliseconds + of inactivity, thus reducing its memory footprint. When + <c>undefined</c> is specified (this is the default), the process + never goes into hibernation.</p> + </desc> + </datatype> + + <datatype> + <name name="handshake_size"/> + <desc> + <p>Integer (24 bits unsigned). Used to limit the size of + valid TLS handshake packets to avoid DoS attacks. + Defaults to 256*1024.</p> + </desc> + </datatype> + + <datatype> + <name name="padding_check"/> + <desc><p>Affects TLS-1.0 connections only. If set to <c>false</c>, it disables the block cipher padding check to be able to interoperate with legacy software.</p> <warning><p>Using <c>{padding_check, boolean()}</c> makes TLS vulnerable to the Poodle attack.</p></warning> - </item> - - + </desc> + </datatype> - <tag><c>{beast_mitigation, one_n_minus_one | zero_n | disabled}</c></tag> - <item><p>Affects SSL-3.0 and TLS-1.0 connections only. Used to change the BEAST + <datatype> + <name name="beast_mitigation"/> + <desc><p>Affects SSL-3.0 and TLS-1.0 connections only. Used to change the BEAST mitigation strategy to interoperate with legacy software. Defaults to <c>one_n_minus_one</c>.</p> @@ -556,127 +654,170 @@ fun(srp, Username :: string(), UserState :: term()) -> <p><c>disabled</c> - Disable BEAST mitigation.</p> - <warning><p>Using <c>{beast_mitigation, disabled}</c> makes SSL or TLS + <warning><p>Using <c>{beast_mitigation, disabled}</c> makes SSL-3.0 or TLS-1.0 vulnerable to the BEAST attack.</p></warning> - </item> - </taglist> - - </section> - - <section> - <title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT SIDE</title> - - <p>The following options are client-specific or have a slightly different - meaning in the client than in the server:</p> - - <taglist> + </desc> + </datatype> + + <datatype> + <name name="ssl_imp"/> + <desc><p>Deprecated since OTP-17, has no affect.</p></desc> + </datatype> + + <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - CLIENT</datatype_title> + + <datatype> + <name name="client_option"/> + </datatype> + + <datatype> + <name name="client_verify_type"/> + <desc><p>In mode <c>verify_none</c> the default behavior is to allow + all x509-path validation errors. See also option <seealso marker="#type-custom_verify">verify_fun</seealso>.</p> + </desc> + </datatype> - <tag><c>{verify, verify_type()}</c></tag> - <item><p>In mode <c>verify_none</c> the default behavior is to allow - all x509-path validation errors. See also option <c>verify_fun</c>.</p> - </item> + <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> + </desc> + </datatype> - <tag><c>{reuse_sessions, boolean()}</c></tag> - <item><p>Specifies if the client is to try to reuse sessions - when possible.</p></item> + <datatype> + <name name="client_reuse_sessions"/> + <desc> + <p>When <c>save</c> is specified a new connection will be negotiated + and saved for later reuse. The session ID can be fetched with + <seealso marker="#connection_information-2">connection_information/2</seealso> + and used with the client option <seealso marker="#type-client_reuse_session">reuse_session</seealso> + The boolean value true specifies that if possible, automatized session reuse will + be performed. If a new session is created, and is unique in regard + to previous stored sessions, it will be saved for possible later reuse. Since OTP-21.3</p> + </desc> + </datatype> - <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag> - <item><p>The DER-encoded trusted certificates. If this option - is supplied it overrides option <c>cacertfile</c>.</p></item> - - <tag><c>{cacertfile, path()}</c></tag> - <item><p>Path to a file containing PEM-encoded CA certificates. The CA + <datatype> + <name name="client_cacerts"/> + <desc> + <p>The DER-encoded trusted certificates. If this option + is supplied it overrides option <c>cacertfile</c>.</p> + </desc> + </datatype> + + <datatype> + <name name="client_cafile"/> + <desc> + <p>Path to a file containing PEM-encoded CA certificates. The CA certificates are used during server authentication and when building the client certificate chain.</p> - </item> - - <tag><c>{alpn_advertised_protocols, [binary()]}</c></tag> - <item> - <p>The list of protocols supported by the client to be sent to the - server to be used for an Application-Layer Protocol Negotiation (ALPN). - If the server supports ALPN then it will choose a protocol from this - list; otherwise it will fail the connection with a "no_application_protocol" - alert. A server that does not support ALPN will ignore this value.</p> - - <p>The list of protocols must not contain an empty binary.</p> - - <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p> - </item> - - <tag><c>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()]}}</c><br/> - <c>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()], Default :: binary()}}</c></tag> - <item> - <p>Indicates that the client is to try to perform Next Protocol - Negotiation.</p> - - <p>If precedence is server, the negotiated protocol is the - first protocol to be shown on the server advertised list, which is - also on the client preference list.</p> - - <p>If precedence is client, the negotiated protocol is the - first protocol to be shown on the client preference list, which is - also on the server advertised list.</p> - - <p>If the client does not support any of the server advertised - protocols or the server does not advertise any protocols, the - client falls back to the first protocol in its list or to the - default protocol (if a default is supplied). If the - server does not support Next Protocol Negotiation, the - connection terminates if no default protocol is supplied.</p> - </item> - - <tag><c>{psk_identity, string()}</c></tag> - <item><p>Specifies the identity the client presents to the server. - The matching secret is found by calling <c>user_lookup_fun</c>.</p> - </item> - - <tag><c>{srp_identity, {Username :: string(), Password :: string()} - </c></tag> - <item><p>Specifies the username and password to use to authenticate - to the server.</p></item> - - <tag><c>{server_name_indication, HostName :: hostname()}</c></tag> - <item><p>Specify the hostname to be used in TLS Server Name Indication extension. - If not specified it will default to the <c>Host</c> argument of <seealso marker="#connect-3">connect/[3,4]</seealso> - unless it is of type inet:ipaddress().</p> - <p> - The <c>HostName</c> will also be used in the hostname verification of the peer certificate using - <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>. - </p> - </item> - <tag><c>{server_name_indication, disable}</c></tag> - <item> - <p> Prevents the Server Name Indication extension from being sent and - disables the hostname verification check - <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> </p> - </item> - - <tag><c>{customize_hostname_check, Options::list()}</c></tag> - <item> - <p> Customizes the hostname verification of the peer certificate, as different protocols that use + </desc> + </datatype> + + <datatype> + <name name="client_alpn"/> + <desc> + <p>The list of protocols supported by the client to be sent to the + server to be used for an Application-Layer Protocol Negotiation (ALPN). + If the server supports ALPN then it will choose a protocol from this + list; otherwise it will fail the connection with a "no_application_protocol" + alert. A server that does not support ALPN will ignore this value.</p> + + <p>The list of protocols must not contain an empty binary.</p> + + <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p> + </desc> + </datatype> + + <datatype> + <name name="client_preferred_next_protocols"/> + <desc> + <p>Indicates that the client is to try to perform Next Protocol + Negotiation.</p> + + <p>If precedence is server, the negotiated protocol is the + first protocol to be shown on the server advertised list, which is + also on the client preference list.</p> + + <p>If precedence is client, the negotiated protocol is the + first protocol to be shown on the client preference list, which is + also on the server advertised list.</p> + + <p>If the client does not support any of the server advertised + protocols or the server does not advertise any protocols, the + client falls back to the first protocol in its list or to the + default protocol (if a default is supplied). If the + server does not support Next Protocol Negotiation, the + connection terminates if no default protocol is supplied.</p> + </desc> + </datatype> + + <datatype> + <name name="client_psk_identity"/> + <desc> + <p>Specifies the identity the client presents to the server. + The matching secret is found by calling <c>user_lookup_fun</c></p> + </desc> + </datatype> + + <datatype> + <name name="client_srp_identity"/> + <desc> + <p>Specifies the username and password to use to authenticate + to the server.</p> + </desc> + </datatype> + + <datatype> + <name name="sni"/> + <desc> + <p>Specify the hostname to be used in TLS Server Name Indication extension. + If not specified it will default to the <c>Host</c> argument of <seealso marker="#connect-3">connect/[3,4]</seealso> + unless it is of type inet:ipaddress().</p> + <p> + The <c>HostName</c> will also be used in the hostname verification of the peer certificate using + <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso>. + </p> + <p> The special value <c>disable</c> prevents the Server Name Indication extension from being sent and + disables the hostname verification check + <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> </p> + </desc> + </datatype> + + <datatype> + <name name="customize_hostname_check"/> + <desc> + <p> Customizes the hostname verification of the peer certificate, as different protocols that use TLS such as HTTP or LDAP may want to do it differently, for possible options see <seealso marker="public_key:public_key#pkix_verify_hostname-3">public_key:pkix_verify_hostname/3</seealso> </p> - </item> - - <tag><c>{fallback, boolean()}</c></tag> - <item> - <p> Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade. - Defaults to false</p> - <warning><p>Note this option is not needed in normal TLS usage and should not be used - to implement new clients. But legacy clients that retries connections in the following manner</p> - - <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p> - <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p> - <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p> - <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p> - - <p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also - be supported by the server for the prevention to work. - </p></warning> - </item> - <tag><marker id="client_signature_algs"/><c>{signature_algs, [{hash(), ecdsa | rsa | dsa}]}</c></tag> - <item> - <p>In addition to the algorithms negotiated by the cipher + </desc> + </datatype> + + <datatype> + <name name="fallback"/> + <desc> + <p> Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade. + Defaults to false</p> + <warning><p>Note this option is not needed in normal TLS usage and should not be used + to implement new clients. But legacy clients that retries connections in the following manner</p> + + <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p> + <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p> + <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p> + <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p> + + <p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also + be supported by the server for the prevention to work. + </p></warning> + </desc> + </datatype> + + <datatype> + <name name="client_signature_algs"/> + <desc> + <p>In addition to the algorithms negotiated by the cipher suite used for key exchange, payload encryption, message authentication and pseudo random calculation, the TLS signature algorithm extension <url @@ -707,177 +848,230 @@ fun(srp, Username :: string(), UserState :: term()) -> Selected signature algorithm can restrict which hash functions that may be selected. Default support for {md5, rsa} removed in ssl-8.0 </p> - </item> - </taglist> - </section> - - <section> - <title>TLS/DTLS OPTION DESCRIPTIONS - SERVER SIDE</title> + </desc> + </datatype> + - <p>The following options are server-specific or have a slightly different - meaning in the server than in the client:</p> + <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER </datatype_title> - <taglist> - <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag> - <item><p>The DER-encoded trusted certificates. If this option - is supplied it overrides option <c>cacertfile</c>.</p></item> + <datatype> + <name name="server_option"/> + </datatype> + + <datatype> + <name name="server_cacerts"/> + <desc><p>The DER-encoded trusted certificates. If this option + is supplied it overrides option <c>cacertfile</c>.</p> + </desc> + </datatype> - <tag><c>{cacertfile, path()}</c></tag> - <item><p>Path to a file containing PEM-encoded CA - certificates. The CA certificates are used to build the server - certificate chain and for client authentication. The CAs are - also used in the list of acceptable client CAs passed to the - client when a certificate is requested. Can be omitted if there - is no need to verify the client and if there are no - intermediate CAs for the server certificate.</p></item> - - <tag><c>{dh, public_key:der_encoded()}</c></tag> - <item><p>The DER-encoded Diffie-Hellman parameters. If specified, - it overrides option <c>dhfile</c>.</p></item> - - <tag><c>{dhfile, path()}</c></tag> - <item><p>Path to a file containing PEM-encoded Diffie Hellman parameters - to be used by the server if a cipher suite using Diffie Hellman key - exchange is negotiated. If not specified, default parameters are used. - </p></item> - - <tag><c>{verify, verify_type()}</c></tag> - <item><p>A server only does x509-path validation in mode <c>verify_peer</c>, - as it then sends a certificate request to the client - (this message is not sent if the verify option is <c>verify_none</c>). - You can then also want to specify option <c>fail_if_no_peer_cert</c>. - </p></item> - - <tag><c>{fail_if_no_peer_cert, boolean()}</c></tag> - <item><p>Used together with <c>{verify, verify_peer}</c> by an TLS/DTLS server. - If set to <c>true</c>, the server fails if the client does not have - a certificate to send, that is, sends an empty certificate. If set to - <c>false</c>, it fails only if the client sends an invalid - certificate (an empty certificate is considered valid). Defaults to false.</p> - </item> - - <tag><c>{reuse_sessions, boolean()}</c></tag> - <item><p>Specifies if the server is to agree to reuse sessions - when requested by the clients. See also option <c>reuse_session</c>. - </p></item> - - <tag><c>{reuse_session, fun(SuggestedSessionId, - PeerCert, Compression, CipherSuite) -> boolean()}</c></tag> - <item><p>Enables the TLS/DTLS server to have a local policy - for deciding if a session is to be reused or not. - Meaningful only if <c>reuse_sessions</c> is set to <c>true</c>. - <c>SuggestedSessionId</c> is a <c>binary()</c>, <c>PeerCert</c> is - a DER-encoded certificate, <c>Compression</c> is an enumeration integer, - and <c>CipherSuite</c> is of type <c>ciphersuite()</c>.</p></item> - - <tag><c>{alpn_preferred_protocols, [binary()]}</c></tag> - <item> - <p>Indicates the server will try to perform Application-Layer - Protocol Negotiation (ALPN).</p> - - <p>The list of protocols is in order of preference. The protocol - negotiated will be the first in the list that matches one of the - protocols advertised by the client. If no protocol matches, the - server will fail the connection with a "no_application_protocol" alert.</p> - - <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p> - </item> - - <tag><c>{next_protocols_advertised, Protocols :: [binary()]}</c></tag> - <item><p>List of protocols to send to the client if the client indicates that - it supports the Next Protocol extension. The client can select a protocol - that is not on this list. The list of protocols must not contain an empty - binary. If the server negotiates a Next Protocol, it can be accessed - using the <c>negotiated_next_protocol/1</c> method.</p></item> - - <tag><c>{psk_identity, string()}</c></tag> - <item><p>Specifies the server identity hint, which the server presents to - the client.</p></item> - - <tag><c>{log_alert, boolean()}</c></tag> - <item><p>If set to <c>false</c>, error reports are not displayed.</p></item> - - <tag><c>{honor_cipher_order, boolean()}</c></tag> - <item><p>If set to <c>true</c>, use the server preference for cipher - selection. If set to <c>false</c> (the default), use the client - preference.</p></item> - - <tag><c>{sni_hosts, [{hostname(), [ssl_option()]}]}</c></tag> - <item><p>If the server receives a SNI (Server Name Indication) from the client - matching a host listed in the <c>sni_hosts</c> option, the specific options for - that host will override previously specified options. - - The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p></item> - - <tag><c>{sni_fun, SNIfun::fun()}</c></tag> - <item><p>If the server receives a SNI (Server Name Indication) from the client, - the given function will be called to retrieve <c>[ssl_option()]</c> for the indicated server. - These options will be merged into predefined <c>[ssl_option()]</c>. - - The function should be defined as: - <c>fun(ServerName :: string()) -> [ssl_option()]</c> - and can be specified as a fun or as named <c>fun module:function/1</c> - - The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p></item> - - <tag><c>{client_renegotiation, boolean()}</c></tag> - <item>In protocols that support client-initiated renegotiation, the cost - of resources of such an operation is higher for the server than the - client. This can act as a vector for denial of service attacks. The SSL - application already takes measures to counter-act such attempts, - but client-initiated renegotiation can be strictly disabled by setting - this option to <c>false</c>. The default value is <c>true</c>. - Note that disabling renegotiation can result in long-lived connections - becoming unusable due to limits on the number of messages the underlying - cipher suite can encipher. - </item> - - <tag><c>{honor_cipher_order, boolean()}</c></tag> - <item>If true, use the server's preference for cipher selection. If false - (the default), use the client's preference. - </item> - <tag><c>{honor_ecc_order, boolean()}</c></tag> - <item>If true, use the server's preference for ECC curve selection. If false - (the default), use the client's preference. - </item> - - <tag><c>{signature_algs, [{hash(), ecdsa | rsa | dsa}]}</c></tag> - <item><p> The algorithms specified by - this option will be the ones accepted by the server in a signature algorithm - negotiation, introduced in TLS-1.2. The algorithms will also be offered to the client if a - client certificate is requested. For more details see the <seealso marker="#client_signature_algs">corresponding client option</seealso>. - </p> </item> - - </taglist> - </section> - - <section> - <title>General</title> + <datatype> + <name name="server_cafile"/> + <desc><p>Path to a file containing PEM-encoded CA + certificates. The CA certificates are used to build the server + certificate chain and for client authentication. The CAs are + also used in the list of acceptable client CAs passed to the + client when a certificate is requested. Can be omitted if + there is no need to verify the client and if there are no + intermediate CAs for the server certificate.</p> + </desc> + </datatype> + + <datatype> + <name name="dh_der"/> + <desc><p>The DER-encoded Diffie-Hellman parameters. If + specified, it overrides option <c>dhfile</c>.</p> + </desc> + </datatype> + + <datatype> + <name name="dh_file"/> + <desc><p>Path to a file containing PEM-encoded Diffie Hellman + parameters to be used by the server if a cipher suite using + Diffie Hellman key exchange is negotiated. If not specified, + default parameters are used.</p> + </desc> + </datatype> - <p>When an TLS/DTLS socket is in active mode (the default), data from the - socket is delivered to the owner of the socket in the form of - messages:</p> - <list type="bulleted"> - <item><p><c>{ssl, Socket, Data}</c></p></item> - <item><p><c>{ssl_closed, Socket}</c></p></item> - <item><p><c>{ssl_error, Socket, Reason}</c></p></item> - </list> + <datatype> + <name name="server_verify_type"/> + <desc><p>A server only does x509-path validation in mode + <c>verify_peer</c>, as it then sends a certificate request to + the client (this message is not sent if the verify option is + <c>verify_none</c>). You can then also want to specify option + <c>fail_if_no_peer_cert</c>. </p> + </desc> + </datatype> + + <datatype> + <name name="fail_if_no_peer_cert"/> + <desc><p>Used together with <c>{verify, verify_peer}</c> by an + TLS/DTLS server. If set to <c>true</c>, the server fails if + the client does not have a certificate to send, that is, sends + an empty certificate. If set to <c>false</c>, it fails only if + the client sends an invalid certificate (an empty certificate + is considered valid). Defaults to false.</p> + </desc> + </datatype> + + <datatype> + <name name="server_reuse_sessions"/> + <desc><p>The boolean value true specifies that the server will + agree to reuse sessions. Setting it to false will result in an empty + session table, that is no sessions will be reused. + See also option <seealso marker="#type-server_reuse_session">reuse_session</seealso> + </p> + </desc> + </datatype> + + <datatype> + <name name="server_reuse_session"/> + <desc><p>Enables the TLS/DTLS server to have a local policy + for deciding if a session is to be reused or not. Meaningful + only if <c>reuse_sessions</c> is set to <c>true</c>. + <c>SuggestedSessionId</c> is a <c>binary()</c>, + <c>PeerCert</c> is a DER-encoded certificate, + <c>Compression</c> is an enumeration integer, and + <c>CipherSuite</c> is of type <c>ciphersuite()</c>.</p> + </desc> + </datatype> + + <datatype> + <name name="server_alpn"/> + <desc> + <p>Indicates the server will try to perform + Application-Layer Protocol Negotiation (ALPN).</p> + + <p>The list of protocols is in order of preference. The + protocol negotiated will be the first in the list that + matches one of the protocols advertised by the client. If no + protocol matches, the server will fail the connection with a + "no_application_protocol" alert.</p> + + <p>The negotiated protocol can be retrieved using the + <c>negotiated_protocol/1</c> function.</p> + </desc> + </datatype> + + <datatype> + <name name="server_next_protocol"/> + <desc><p>List of protocols to send to the client if the client + indicates that it supports the Next Protocol extension. The + client can select a protocol that is not on this list. The + list of protocols must not contain an empty binary. If the + server negotiates a Next Protocol, it can be accessed using + the <c>negotiated_next_protocol/1</c> method.</p> + </desc> + </datatype> + + <datatype> + <name name="server_psk_identity"/> + <desc> + <p>Specifies the server identity hint, which the server presents to + the client.</p> + </desc> + </datatype> + + <datatype> + <name name="honor_cipher_order"/> + <desc> + <p>If set to <c>true</c>, use the server preference for cipher + selection. If set to <c>false</c> (the default), use the client + preference.</p> + </desc> + </datatype> + + <datatype> + <name name="sni_hosts"/> + <desc><p>If the server receives a SNI (Server Name Indication) from the client + matching a host listed in the <c>sni_hosts</c> option, the specific options for + that host will override previously specified options. + + The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p> + </desc> + </datatype> + + <datatype> + <name name="sni_fun"/> + <desc> + <p>If the server receives a SNI (Server Name Indication) + from the client, the given function will be called to + retrieve <seealso marker="#type-server_option">[server_option()] </seealso> for the indicated server. + These options will be merged into predefined + <seealso marker="#type-server_option">[server_option()] </seealso> list. + + The function should be defined as: + fun(ServerName :: string()) -> <seealso marker="#type-server_option">[server_option()] </seealso> + and can be specified as a fun or as named <c>fun module:function/1</c> + + The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p> + </desc> + </datatype> + + <datatype> + <name name="client_renegotiation"/> + <desc><p>In protocols that support client-initiated + renegotiation, the cost of resources of such an operation is + higher for the server than the client. This can act as a + vector for denial of service attacks. The SSL application + already takes measures to counter-act such attempts, but + client-initiated renegotiation can be strictly disabled by + setting this option to <c>false</c>. The default value is + <c>true</c>. Note that disabling renegotiation can result in + long-lived connections becoming unusable due to limits on the + number of messages the underlying cipher suite can + encipher.</p> + </desc> + </datatype> + + <datatype> + <name name="honor_cipher_order"/> + <desc><p>If true, use the server's preference for cipher + selection. If false (the default), use the client's + preference.</p> + </desc> + </datatype> + + <datatype> + <name name="honor_ecc_order"/> + <desc><p>If true, use the server's preference for ECC curve + selection. If false (the default), use the client's + preference.</p> + </desc> + </datatype> + + <datatype> + <name name="server_signature_algs"/> + <desc><p> The algorithms specified by this option will be the + ones accepted by the server in a signature algorithm + negotiation, introduced in TLS-1.2. The algorithms will also + be offered to the client if a client certificate is + requested. For more details see the <seealso + marker="#type-client_signature_algs">corresponding client + option</seealso>. + </p> + </desc> + </datatype> + </datatypes> + +<!-- + ================================================================ + = Function definitions = + ================================================================ +--> - <p>A <c>Timeout</c> argument specifies a time-out in milliseconds. The - default value for argument <c>Timeout</c> is <c>infinity</c>.</p> - </section> - <funcs> <func> - <name>append_cipher_suites(Deferred, Suites) -> ciphers() </name> + <name since="OTP 20.3">append_cipher_suites(Deferred, Suites) -> ciphers() </name> <fsummary></fsummary> <type> - <v>Deferred = ciphers() | cipher_filters() </v> - <v>Suites = ciphers() </v> + <v>Deferred = <seealso marker="#type-ciphers">ciphers()</seealso> | + <seealso marker="#type-cipher_filters">cipher_filters()</seealso></v> + <v>Suites = <seealso marker="#type-ciphers">ciphers()</seealso></v> </type> <desc><p>Make <c>Deferred</c> suites become the least preferred suites, that is put them at the end of the cipher suite list @@ -889,8 +1083,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>cipher_suites() -></name> - <name>cipher_suites(Type) -> old_ciphers()</name> + <name since="OTP R14B">cipher_suites() -></name> + <name since="OTP R14B">cipher_suites(Type) -> [old_cipher_suite()]</name> <fsummary>Returns a list of supported cipher suites.</fsummary> <type> <v>Type = erlang | openssl | all</v> @@ -901,12 +1095,12 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>cipher_suites(Supported, Version) -> ciphers()</name> + <name since="OTP 20.3">cipher_suites(Supported, Version) -> ciphers()</name> <fsummary>Returns a list of all default or all supported cipher suites.</fsummary> <type> <v> Supported = default | all | anonymous </v> - <v> Version = protocol_version() </v> + <v> Version = <seealso marker="#type-protocol_version">protocol_version() </seealso></v> </type> <desc><p>Returns all default or all supported (except anonymous), or all anonymous cipher suites for a @@ -915,10 +1109,16 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>eccs() -></name> - <name>eccs(protocol_version()) -> [named_curve()]</name> + <name since="OTP 19.2">eccs() -></name> + <name since="OTP 19.2">eccs(Version) -> NamedCurves</name> <fsummary>Returns a list of supported ECCs.</fsummary> + <type> + <v> Version = <seealso marker="#type-protocol_version">protocol_version() </seealso></v> + <v> NamedCurves = <seealso marker="#type-named_curve">[named_curve()] </seealso></v> + + </type> + <desc><p>Returns a list of supported ECCs. <c>eccs()</c> is equivalent to calling <c>eccs(Protocol)</c> with all supported protocols and then deduplicating the output.</p> @@ -926,7 +1126,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>clear_pem_cache() -> ok </name> + <name since="OTP 17.5">clear_pem_cache() -> ok </name> <fsummary> Clears the pem cache</fsummary> <desc><p>PEM files, used by ssl API-functions, are cached. The @@ -938,61 +1138,68 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connect(Socket, SslOptions) -> </name> - <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} + <name since="OTP R14B">connect(Socket, Options) -> </name> + <name since="">connect(Socket, Options, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name> <fsummary>Upgrades a <c>gen_tcp</c>, or equivalent, connected socket to an TLS socket.</fsummary> <type> - <v>Socket = socket()</v> - <v>SslOptions = [{handshake, hello| full} | ssl_option()]</v> - <v>Timeout = integer() | infinity</v> - <v>SslSocket = sslsocket()</v> - <v>Ext = hello_extensions()</v> - <v>Reason = term()</v> + <v>Socket = <seealso marker="#type-socket"> socket() </seealso></v> + <v>Options = <seealso marker="#type-tls_client_option"> [tls_client_option()] </seealso></v> + <v>Timeout = timeout()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Ext = <seealso marker="#type-protocol_extensions">protocol_extensions()</seealso></v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc><p>Upgrades a <c>gen_tcp</c>, or equivalent, connected socket to an TLS socket, that is, performs the client-side TLS handshake.</p> - <note><p>If the option <c>verify</c> is set to <c>verify_peer</c> - the option <c>server_name_indication</c> shall also be specified, - if it is not no Server Name Indication extension will be sent, - and <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> - will be called with the IP-address of the connection as <c>ReferenceID</c>, which is proably not what you want.</p> + <note><p>If the option <c>verify</c> is set to + <c>verify_peer</c> the option <c>server_name_indication</c> + shall also be specified, if it is not no Server Name + Indication extension will be sent, and <seealso + marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> + will be called with the IP-address of the connection as + <c>ReferenceID</c>, which is proably not what you want.</p> </note> <p> If the option <c>{handshake, hello}</c> is used the handshake is paused after receiving the server hello message and the success response is <c>{ok, SslSocket, Ext}</c> - instead of <c>{ok, SslSocket}</c>. Thereafter the handshake is continued or - canceled by calling <seealso marker="#handshake_continue-3"> + instead of <c>{ok, SslSocket}</c>. Thereafter the handshake + is continued or canceled by calling <seealso + marker="#handshake_continue-3"> <c>handshake_continue/3</c></seealso> or <seealso - marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. + marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. </p> + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type + <seealso marker="#type-active_msgs"> active_msgs() </seealso> + </p> </desc> </func> <func> - <name>connect(Host, Port, Options) -></name> - <name>connect(Host, Port, Options, Timeout) -> + <name since="">connect(Host, Port, Options) -></name> + <name since="">connect(Host, Port, Options, Timeout) -> {ok, SslSocket}| {ok, SslSocket, Ext} | {error, Reason}</name> <fsummary>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</fsummary> <type> - <v>Host = host()</v> - <v>Port = integer()</v> - <v>Options = [option()]</v> - <v>Timeout = integer() | infinity</v> - <v>SslSocket = sslsocket()</v> - <v>Reason = term()</v> + <v>Host =<seealso marker="#type-host"> host() </seealso> </v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> + <v>Options = <seealso marker="#type-tls_client_option"> [tls_client_option()]</seealso></v> + <v>Timeout = timeout()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc><p>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</p> <p> When the option <c>verify</c> is set to <c>verify_peer</c> the check <seealso marker="public_key:public_key#pkix_verify_hostname-2">public_key:pkix_verify_hostname/2</seealso> will be performed in addition to the usual x509-path validation checks. If the check fails the error {bad_cert, hostname_check_failed} will - be propagated to the path validation fun <seealso marker="#verify_fun">verify_fun</seealso>, where it is possible to do customized + be propagated to the path validation fun <seealso marker="#type-custom_verify">verify_fun</seealso>, where it is possible to do customized checks by using the full possibilities of the <seealso marker="public_key:public_key#pkix_verify_hostname-3">public_key:pkix_verify_hostname/3</seealso> API. When the option <c>server_name_indication</c> is provided, its value (the DNS name) will be used as <c>ReferenceID</c> @@ -1014,14 +1221,19 @@ fun(srp, Username :: string(), UserState :: term()) -> <c>handshake_continue/3</c></seealso> or <seealso marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. </p> + + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type + <seealso marker="#type-active_msgs"> active_msgs() </seealso> + </p> </desc> </func> <func> - <name>close(SslSocket) -> ok | {error, Reason}</name> + <name since="">close(SslSocket) -> ok | {error, Reason}</name> <fsummary>Closes an TLS/DTLS connection.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Reason = term()</v> </type> <desc><p>Closes an TLS/DTLS connection.</p> @@ -1029,10 +1241,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name> + <name since="OTP 18.1">close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name> <fsummary>Closes an TLS connection.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>How = timeout() | {NewController::pid(), timeout()} </v> <v>Reason = term()</v> </type> @@ -1044,12 +1256,12 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>controlling_process(SslSocket, NewOwner) -> + <name since="">controlling_process(SslSocket, NewOwner) -> ok | {error, Reason}</name> <fsummary>Assigns a new controlling process to the TLS/DTLS socket.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>NewOwner = pid()</v> <v>Reason = term()</v> </type> @@ -1060,12 +1272,12 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connection_information(SslSocket) -> + <name since="OTP 18.0">connection_information(SslSocket) -> {ok, Result} | {error, Reason} </name> <fsummary>Returns all the connection information. </fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Item = protocol | selected_cipher_suite | sni_hostname | ecc | session_id | atom()</v> <d>Meaningful atoms, not specified above, are the ssl option names.</d> <v>Result = [{Item::atom(), Value::term()}]</v> @@ -1081,12 +1293,12 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connection_information(SslSocket, Items) -> + <name since="OTP 18.0">connection_information(SslSocket, Items) -> {ok, Result} | {error, Reason} </name> <fsummary>Returns the requested connection information. </fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Items = [Item]</v> <v>Item = protocol | cipher_suite | sni_hostname | ecc | session_id | client_random | server_random | master_secret | atom()</v> @@ -1103,11 +1315,11 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>filter_cipher_suites(Suites, Filters) -> ciphers()</name> + <name since="OTP 20.3">filter_cipher_suites(Suites, Filters) -> ciphers()</name> <fsummary></fsummary> <type> - <v> Suites = ciphers()</v> - <v> Filters = cipher_filters()</v> + <v> Suites = <seealso marker="#type-ciphers"> ciphers() </seealso></v> + <v> Filters = <seealso marker="#type-cipher_filters"> cipher_filters() </seealso></v> </type> <desc><p>Removes cipher suites if any of the filter functions returns false for any part of the cipher suite. This function @@ -1118,7 +1330,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>format_error(Reason) -> string()</name> + <name since="">format_error(Reason) -> string()</name> <fsummary>Returns an error string.</fsummary> <type> <v>Reason = term()</v> @@ -1129,11 +1341,11 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>getopts(SslSocket, OptionNames) -> + <name since="">getopts(SslSocket, OptionNames) -> {ok, [socketoption()]} | {error, Reason}</name> <fsummary>Gets the values of the specified options.</fsummary> <type> - <v>Socket = sslsocket()</v> + <v>Socket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>OptionNames = [atom()]</v> </type> <desc> @@ -1143,13 +1355,13 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>getstat(SslSocket) -> + <name since="OTP 19.0">getstat(SslSocket) -> {ok, OptionValues} | {error, inet:posix()}</name> - <name>getstat(SslSocket, OptionNames) -> + <name since="OTP 19.0">getstat(SslSocket, OptionNames) -> {ok, OptionValues} | {error, inet:posix()}</name> <fsummary>Get one or more statistic options for a socket</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>OptionNames = [atom()]</v> <v>OptionValues = [{inet:stat_option(), integer()}]</v> </type> @@ -1160,31 +1372,36 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>handshake(HsSocket) -> </name> - <name>handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name> + <name since="OTP 21.0">handshake(HsSocket) -> </name> + <name since="OTP 21.0">handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS handshake.</fsummary> <type> - <v>HsSocket = SslSocket = sslsocket()</v> - <v>Timeout = integer()</v> - <v>Reason = term()</v> + <v>HsSocket = SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Timeout = timeout()</v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc> <p>Performs the SSL/TLS/DTLS server-side handshake.</p> <p>Returns a new TLS/DTLS socket if the handshake is successful.</p> + + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type + <seealso marker="#type-active_msgs"> active_msgs() </seealso> + </p> </desc> </func> <func> - <name>handshake(Socket, SslOptions) -> </name> - <name>handshake(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name> + <name since="OTP 21.0">handshake(Socket, Options) -> </name> + <name since="OTP 21.0">handshake(Socket, Options, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary> <type> - <v>Socket = socket() | sslsocket() </v> - <v>SslSocket = sslsocket() </v> - <v>Ext = hello_extensions()</v> - <v>SslOptions = [{handshake, hello| full} | ssl_option()]</v> - <v>Timeout = integer()</v> - <v>Reason = term()</v> + <v>Socket = socket() | <seealso marker="#type-sslsocket"> socket() </seealso> </v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso> </v> + <v>Ext = <seealso marker="#type-protocol_extensions">protocol_extensions()</seealso></v> + <v>Options = <seealso marker="#type-tls_server_option"> [server_option()] </seealso> </v> + <v>Timeout = timeout()</v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc> <p>If <c>Socket</c> is a ordinary <c>socket()</c>: upgrades a <c>gen_tcp</c>, @@ -1196,7 +1413,8 @@ fun(srp, Username :: string(), UserState :: term()) -> is undefined. </p></warning> - <p>If <c>Socket</c> is an <c>sslsocket()</c>: provides extra SSL/TLS/DTLS + <p>If <c>Socket</c> is an + <seealso marker="#type-sslsocket"> sslsocket() </seealso>: provides extra SSL/TLS/DTLS options to those specified in <seealso marker="#listen-2">listen/2 </seealso> and then performs the SSL/TLS/DTLS handshake. Returns a new TLS/DTLS socket if the handshake is successful.</p> @@ -1210,14 +1428,20 @@ fun(srp, Username :: string(), UserState :: term()) -> <c>handshake_continue/3</c></seealso> or <seealso marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. </p> + + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type + <seealso marker="#type-active_msgs"> active_msgs() </seealso> + </p> + </desc> </func> <func> - <name>handshake_cancel(SslSocket) -> ok </name> + <name since="OTP 21.0">handshake_cancel(SslSocket) -> ok </name> <fsummary>Cancel handshake with a fatal alert</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> </type> <desc> <p>Cancel the handshake with a fatal <c>USER_CANCELED</c> alert.</p> @@ -1225,14 +1449,14 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>handshake_continue(HsSocket, SSLOptions) -> {ok, SslSocket} | {error, Reason}</name> - <name>handshake_continue(HsSocket, SSLOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name> + <name since="OTP 21.0">handshake_continue(HsSocket, Options) -> {ok, SslSocket} | {error, Reason}</name> + <name since="OTP 21.0">handshake_continue(HsSocket, Options, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary>Continue the SSL/TLS handshake.</fsummary> <type> - <v>HsSocket = SslSocket = sslsocket()</v> - <v>SslOptions = [ssl_option()]</v> - <v>Timeout = integer()</v> - <v>Reason = term()</v> + <v>HsSocket = SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Options = <seealso marker="#type-tls_option"> tls_option() </seealso> </v> + <v>Timeout = timeout()</v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc> <p>Continue the SSL/TLS handshake possiby with new, additional or changed options.</p> @@ -1240,13 +1464,13 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>listen(Port, Options) -> + <name since="">listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}</name> <fsummary>Creates an SSL listen socket.</fsummary> <type> - <v>Port = integer()</v> - <v>Options = options()</v> - <v>ListenSocket = sslsocket()</v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> + <v>Options = <seealso marker="#type-tls_server_option"> [server_option()] </seealso></v> + <v>ListenSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> </type> <desc> <p>Creates an SSL listen socket.</p> @@ -1254,10 +1478,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name> + <name since="OTP 18.0">negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name> <fsummary>Returns the protocol negotiated through ALPN or NPN extensions.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Protocol = binary()</v> </type> <desc> @@ -1268,10 +1492,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name> + <name since="">peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name> <fsummary>Returns the peer certificate.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Cert = binary()</v> </type> <desc> @@ -1283,13 +1507,13 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>peername(SslSocket) -> {ok, {Address, Port}} | + <name since="">peername(SslSocket) -> {ok, {Address, Port}} | {error, Reason}</name> <fsummary>Returns the peer address and port.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Address = ipaddress()</v> - <v>Port = integer()</v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> </type> <desc> <p>Returns the address and port number of the peer.</p> @@ -1297,11 +1521,12 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>prepend_cipher_suites(Preferred, Suites) -> ciphers()</name> + <name since="OTP 20.3">prepend_cipher_suites(Preferred, Suites) -> ciphers()</name> <fsummary></fsummary> <type> - <v>Preferred = ciphers() | cipher_filters() </v> - <v>Suites = ciphers() </v> + <v>Preferred = <seealso marker="#type-ciphers">ciphers()</seealso> | + <seealso marker="#type-cipher_filters">cipher_filters()</seealso></v> + <v>Suites = <seealso marker="#type-ciphers">ciphers()</seealso></v> </type> <desc><p>Make <c>Preferred</c> suites become the most preferred suites that is put them at the head of the cipher suite list @@ -1313,13 +1538,13 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name> + <name since="OTP R15B01">prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name> <fsummary>Uses a session Pseudo-Random Function to generate key material.</fsummary> <type> - <v>Socket = sslsocket()</v> + <v>Socket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Secret = binary() | master_secret</v> <v>Label = binary()</v> - <v>Seed = [binary() | prf_random()]</v> + <v>Seed = [binary() | <seealso marker="#type-prf_random"> prf_random()</seealso>]</v> <v>WantedLength = non_neg_integer()</v> </type> <desc> @@ -1333,14 +1558,14 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>recv(SslSocket, Length) -> </name> - <name>recv(SslSocket, Length, Timeout) -> {ok, Data} | {error, + <name since="">recv(SslSocket, Length) -> </name> + <name since="">recv(SslSocket, Length, Timeout) -> {ok, Data} | {error, Reason}</name> <fsummary>Receives data on a socket.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Length = integer()</v> - <v>Timeout = integer()</v> + <v>Timeout = timeout()</v> <v>Data = [char()] | binary()</v> </type> <desc> @@ -1360,10 +1585,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>renegotiate(SslSocket) -> ok | {error, Reason}</name> + <name since="OTP R14B">renegotiate(SslSocket) -> ok | {error, Reason}</name> <fsummary>Initiates a new handshake.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> </type> <desc><p>Initiates a new handshake. A notable return value is <c>{error, renegotiation_rejected}</c> indicating that the peer @@ -1373,10 +1598,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>send(SslSocket, Data) -> ok | {error, Reason}</name> + <name since="">send(SslSocket, Data) -> ok | {error, Reason}</name> <fsummary>Writes data to a socket.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>Data = iodata()</v> </type> <desc> @@ -1387,11 +1612,11 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>setopts(SslSocket, Options) -> ok | {error, Reason}</name> + <name since="">setopts(SslSocket, Options) -> ok | {error, Reason}</name> <fsummary>Sets socket options.</fsummary> <type> - <v>SslSocket = sslsocket()</v> - <v>Options = [socketoption]()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Options = <seealso marker="#type-socket_option"> [socket_option()] </seealso></v> </type> <desc> <p>Sets options according to <c>Options</c> for socket @@ -1400,10 +1625,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>shutdown(SslSocket, How) -> ok | {error, Reason}</name> + <name since="OTP R14B">shutdown(SslSocket, How) -> ok | {error, Reason}</name> <fsummary>Immediately closes a socket.</fsummary> <type> - <v>SslSocket = sslsocket()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> <v>How = read | write | read_write</v> <v>Reason = reason()</v> </type> @@ -1418,13 +1643,13 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>ssl_accept(SslSocket) -> </name> - <name>ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name> + <name since="">ssl_accept(SslSocket) -> </name> + <name since="">ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS handshake.</fsummary> <type> - <v>SslSocket = sslsocket()</v> - <v>Timeout = integer()</v> - <v>Reason = term()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Timeout = timeout()</v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc> <p>Deprecated in OTP 21, use <seealso marker="#handshake-1">handshake/[1,2]</seealso> instead.</p> @@ -1433,14 +1658,14 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>ssl_accept(Socket, SslOptions) -> </name> - <name>ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name> + <name since="">ssl_accept(Socket, Options) -> </name> + <name since="OTP R14B">ssl_accept(Socket, Options, Timeout) -> {ok, Socket} | ok | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary> <type> - <v>Socket = socket() | sslsocket() </v> - <v>SslOptions = [ssl_option()]</v> - <v>Timeout = integer()</v> - <v>Reason = term()</v> + <v>Socket = socket() | <seealso marker="#type-sslsocket"> sslsocket() </seealso> </v> + <v>Options = <seealso marker="#type-tls_server_option"> [server_option()] </seealso> </v> + <v>Timeout = timeout()</v> + <v>Reason = closed | timeout | <seealso marker="#type-error_alert"> error_alert() </seealso></v> </type> <desc> <p>Deprecated in OTP 21, use <seealso marker="#handshake-3">handshake/[2,3]</seealso> instead.</p> @@ -1449,13 +1674,13 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>sockname(SslSocket) -> {ok, {Address, Port}} | + <name since="">sockname(SslSocket) -> {ok, {Address, Port}} | {error, Reason}</name> <fsummary>Returns the local address and port.</fsummary> <type> - <v>SslSocket = sslsocket()</v> - <v>Address = ipaddress()</v> - <v>Port = integer()</v> + <v>SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Address = <seealso marker="#type-ip_address">ip_address()</seealso></v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> </type> <desc> <p>Returns the local address and port number of socket @@ -1464,8 +1689,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>start() -> </name> - <name>start(Type) -> ok | {error, Reason}</name> + <name since="OTP R14B">start() -> </name> + <name since="OTP R14B">start(Type) -> ok | {error, Reason}</name> <fsummary>Starts the SSL application.</fsummary> <type> <v>Type = permanent | transient | temporary</v> @@ -1477,7 +1702,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>stop() -> ok </name> + <name since="OTP R14B">stop() -> ok </name> <fsummary>Stops the SSL application.</fsummary> <desc> <p>Stops the SSL application.</p> @@ -1485,10 +1710,10 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>suite_to_str(CipherSuite) -> String</name> + <name since="OTP 21.0">suite_to_str(CipherSuite) -> String</name> <fsummary>Returns the string representation of a cipher suite.</fsummary> <type> - <v>CipherSuite = erl_cipher_suite()</v> + <v>CipherSuite = <seealso marker="#type-erl_cipher_suite"> erl_cipher_suite() </seealso></v> <v>String = string()</v> </type> <desc> @@ -1497,14 +1722,14 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>transport_accept(ListenSocket) -></name> - <name>transport_accept(ListenSocket, Timeout) -> + <name since="">transport_accept(ListenSocket) -></name> + <name since="">transport_accept(ListenSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary>Accepts an incoming connection and prepares for <c>ssl_accept</c>.</fsummary> <type> - <v>ListenSocket = SslSocket = sslsocket()</v> - <v>Timeout = integer()</v> + <v>ListenSocket = SslSocket = <seealso marker="#type-sslsocket"> sslsocket() </seealso></v> + <v>Timeout = timeout()</v> <v>Reason = reason()</v> </type> <desc> @@ -1532,7 +1757,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>versions() -> [versions_info()]</name> + <name since="OTP R14B">versions() -> [versions_info()]</name> <fsummary>Returns version information relevant for the SSL application.</fsummary> <type> diff --git a/lib/ssl/doc/src/ssl_crl_cache.xml b/lib/ssl/doc/src/ssl_crl_cache.xml index 71c6d5e49e..a33aec62a7 100644 --- a/lib/ssl/doc/src/ssl_crl_cache.xml +++ b/lib/ssl/doc/src/ssl_crl_cache.xml @@ -24,7 +24,7 @@ <file>ssl_crl_cache.xml</file> </header> - <module>ssl_crl_cache</module> + <module since="OTP 18.0">ssl_crl_cache</module> <modulesummary>CRL cache </modulesummary> <description> <p> @@ -34,32 +34,43 @@ the following functions are available. </p> </description> + + <datatypes> + <datatype_title>DATA TYPES</datatype_title> + + <datatype> + <name name="crl_src"/> + </datatype> + + <datatype> + <name name="uri"/> + </datatype> + + </datatypes> <funcs> <func> - <name>delete(Entries) -> ok | {error, Reason} </name> + <name since="OTP 18.0">delete(Entries) -> ok | {error, Reason} </name> <fsummary> </fsummary> <type> - <v> Entries = <seealso marker="stdlib:uri_string">uri_string:uri_string()</seealso> | {file, string()} | {der, [<seealso - marker="public_key:public_key"> public_key:der_encoded() </seealso>]}</v> - <v> Reason = term()</v> + <v> Entries = <seealso marker="#type-crl_src">crl_src()</seealso>]}</v> + <v> Reason = crl_reason()</v> </type> <desc> <p>Delete CRLs from the ssl applications local cache. </p> </desc> </func> <func> - <name>insert(CRLSrc) -> ok | {error, Reason}</name> - <name>insert(URI, CRLSrc) -> ok | {error, Reason}</name> + <name since="OTP 18.0">insert(CRLSrc) -> ok | {error, Reason}</name> + <name since="OTP 18.0">insert(URI, CRLSrc) -> ok | {error, Reason}</name> <fsummary> </fsummary> <type> - <v> CRLSrc = {file, string()} | {der, [ <seealso - marker="public_key:public_key"> public_key:der_encoded() </seealso> ]}</v> - <v> URI = <seealso marker="stdlib:uri_string">uri_string:uri_string() </seealso> </v> + <v> CRLSrc = <seealso marker="#type-crl_src">crl_src()</seealso>]}</v> + <v> URI = <seealso marker="#type-uri">uri()</seealso> </v> <v> Reason = term()</v> </type> <desc> - <p>Insert CRLs into the ssl applications local cache. </p> + <p>Insert CRLs, available to fetch on DER format from <c>URI</c>, into the ssl applications local cache. </p> </desc> </func> </funcs> diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml index c6774b4df6..4cba4e1de1 100644 --- a/lib/ssl/doc/src/ssl_crl_cache_api.xml +++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml @@ -24,7 +24,7 @@ <file>ssl_crl_cache_api.xml</file> </header> - <module>ssl_crl_cache_api</module> + <module since="OTP 18.0">ssl_crl_cache_api</module> <modulesummary>API for a SSL/TLS CRL (Certificate Revocation List) cache.</modulesummary> <description> <p> @@ -39,35 +39,44 @@ a CRL cache. </p> </description> - - <section> - <title>DATA TYPES</title> - - <p>The following data types are used in the functions below: - </p> - - <taglist> - - <tag><c>cache_ref() =</c></tag> - <item>opaque()</item> - <tag><c>dist_point() =</c></tag> - <item><p>#'DistributionPoint'{} see <seealso - marker="public_key:public_key_records"> X509 certificates records</seealso></p></item> - - </taglist> + + + <!-- + ================================================================ + = Data types = + ================================================================ + --> + + <datatypes> - </section> + <datatype> + <name name="crl_cache_ref"/> + <desc> + <p>Reference to the CRL cache.</p> + </desc> + </datatype> + + + <datatype> + <name name="dist_point"/> + <desc> + <p>For description see <seealso + marker="public_key:public_key_records"> X509 certificates records</seealso></p> + </desc> + </datatype> + </datatypes> + <funcs> <func> - <name>fresh_crl(DistributionPoint, CRL) -> FreshCRL</name> + <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL</name> <fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to public_key:pkix_crls_validate/3 </fsummary> <type> - <v> DistributionPoint = dist_point() </v> + <v> DistributionPoint = <seealso marker="#type-dist_point"> dist_point() </seealso> </v> <v> CRL = [<seealso - marker="public_key:public_key">public_key:der_encoded()</seealso>] </v> + marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v> <v> FreshCRL = [<seealso - marker="public_key:public_key">public_key:der_encoded()</seealso>] </v> + marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v> </type> <desc> <p> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to @@ -76,16 +85,16 @@ </func> <func> - <name>lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name> - <name>lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name> + <name since="OTP 19.0">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name> + <name since="OTP 18.0">lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name> <fsummary> </fsummary> <type> - <v> DistributionPoint = dist_point() </v> + <v> DistributionPoint = <seealso marker="#type-dist_point"> dist_point() </seealso> </v> <v> Issuer = <seealso - marker="public_key:public_key">public_key:issuer_name()</seealso> </v> - <v> DbHandle = cache_ref() </v> + marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso> </v> + <v> DbHandle = <seealso marker="#type-crl_cache_ref"> crl_cache_ref() </seealso></v> <v> CRLs = [<seealso - marker="public_key:public_key">public_key:der_encoded()</seealso>] </v> + marker="public_key:public_key#type-der_encoded">public_key:der_encoded()</seealso>] </v> </type> <desc> <p>Lookup the CRLs belonging to the distribution point <c> Distributionpoint</c>. This function may choose to only look in the cache or to follow distribution point @@ -106,12 +115,12 @@ </func> <func> - <name>select(Issuer, DbHandle) -> CRLs </name> + <name since="OTP 18.0">select(Issuer, DbHandle) -> CRLs </name> <fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary> <type> <v> Issuer = <seealso - marker="public_key:public_key">public_key:issuer_name()</seealso></v> - <v> DbHandle = cache_ref() </v> + marker="public_key:public_key#type-issuer_name">public_key:issuer_name()</seealso></v> + <v> DbHandle = <seealso marker="#type-crl_cache_ref"> cache_ref() </seealso></v> </type> <desc> <p>Select the CRLs in the cache that are issued by <c>Issuer</c> </p> diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml index a84a3dfce9..e841729e57 100644 --- a/lib/ssl/doc/src/ssl_session_cache_api.xml +++ b/lib/ssl/doc/src/ssl_session_cache_api.xml @@ -28,7 +28,7 @@ <rev></rev> <file>ssl_session_cache_api.xml</file> </header> - <module>ssl_session_cache_api</module> + <module since="OTP R14B">ssl_session_cache_api</module> <modulesummary>TLS session cache API</modulesummary> <description> @@ -38,39 +38,50 @@ defining a new callback module implementing this API. </p> </description> - <section> - <title>DATA TYPES</title> - <p>The following data types are used in the functions for - <c>ssl_session_cache_api</c>:</p> - - <taglist> - <tag><c>cache_ref() =</c></tag> - <item><p><c>opaque()</c></p></item> - - <tag><c>key() =</c></tag> - <item><p><c>{partialkey(), session_id()}</c></p></item> - - <tag><c>partialkey() =</c></tag> - <item><p><c>opaque()</c></p></item> - - <tag><c>session_id() =</c></tag> - <item><p><c>binary()</c></p></item> - - <tag><c>session()</c> =</tag> - <item><p><c>opaque()</c></p></item> - </taglist> - - </section> + <!-- + ================================================================ + = Data types = + ================================================================ + --> + + <datatypes> + + <datatype> + <name name="session_cache_ref"/> + </datatype> + + <datatype> + <name name="session_cache_key"/> + <desc> + <p>A key to an entry in the session cache.</p> + </desc> + </datatype> + + <datatype> + <name name="partial_key"/> + <desc> + <p>The opaque part of the key. Does not need to be handled + by the callback.</p> + </desc> + </datatype> + + <datatype> + <name name="session"/> + <desc> + <p>The session data that is stored for each session.</p> + </desc> + </datatype> + </datatypes> <funcs> <func> - <name>delete(Cache, Key) -> _</name> + <name since="OTP R14B">delete(Cache, Key) -> _</name> <fsummary>Deletes a cache entry.</fsummary> <type> - <v>Cache = cache_ref()</v> - <v>Key = key()</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> + <v>Key = <seealso marker="#type-session_cache_key">session_cache_key() </seealso> </v> </type> <desc> <p>Deletes a cache entry. Is only called from the cache @@ -80,10 +91,12 @@ </func> <func> - <name>foldl(Fun, Acc0, Cache) -> Acc</name> + <name since="OTP R14B">foldl(Fun, Acc0, Cache) -> Acc</name> <fsummary></fsummary> <type> - <v></v> + <v>Fun = fun()</v> + <v>Acc0 = Acc = term()</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> </type> <desc> <p>Calls <c>Fun(Elem, AccIn)</c> on successive elements of the @@ -96,10 +109,11 @@ </func> <func> - <name>init(Args) -> opaque() </name> + <name since="OTP 18.0">init(Args) -> Cache </name> <fsummary>Returns cache reference.</fsummary> <type> - <v>Args = proplists:proplist()</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> + <v>Args = <seealso marker="stdlib:proplists#type-proplist">proplists:proplist()</seealso></v> </type> <desc> <p>Includes property <c>{role, client | server}</c>. @@ -121,12 +135,12 @@ </func> <func> - <name>lookup(Cache, Key) -> Entry</name> + <name since="OTP R14B">lookup(Cache, Key) -> Entry</name> <fsummary>Looks up a cache entry.</fsummary> <type> - <v>Cache = cache_ref()</v> - <v>Key = key()</v> - <v>Entry = session() | undefined</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> + <v>Key = <seealso marker="#type-session_cache_key">session_cache_key()</seealso> </v> + <v>Session = <seealso marker="#type-session">session()</seealso> | undefined</v> </type> <desc> <p>Looks up a cache entry. Is to be callable from any @@ -136,12 +150,12 @@ </func> <func> - <name>select_session(Cache, PartialKey) -> [session()]</name> + <name since="OTP R14B">select_session(Cache, PartialKey) -> [Session]</name> <fsummary>Selects sessions that can be reused.</fsummary> <type> - <v>Cache = cache_ref()</v> - <v>PartialKey = partialkey()</v> - <v>Session = session()</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> + <v>PartialKey = <seealso marker="#type-partial_key"> partial_key() </seealso></v> + <v>Session = <seealso marker="#type-session">session()</seealso></v> </type> <desc> <p>Selects sessions that can be reused. Is to be callable @@ -151,10 +165,10 @@ </func> <func> - <name>size(Cache) -> integer()</name> + <name since="OTP 19.3">size(Cache) -> integer()</name> <fsummary>Returns the number of sessions in the cache.</fsummary> <type> - <v>Cache = cache_ref()</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> </type> <desc> <p>Returns the number of sessions in the cache. If size @@ -166,11 +180,12 @@ </func> <func> - <name>terminate(Cache) -> _</name> + <name since="OTP R14B">terminate(Cache) -> _</name> <fsummary>Called by the process that handles the cache when it is about to terminate.</fsummary> <type> - <v>Cache = term() - as returned by init/0</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> + <d>As returned by init/0</d> </type> <desc> <p>Takes care of possible cleanup that is needed when the @@ -180,12 +195,12 @@ </func> <func> - <name>update(Cache, Key, Session) -> _</name> + <name since="OTP R14B">update(Cache, Key, Session) -> _</name> <fsummary>Caches a new session or updates an already cached one.</fsummary> <type> - <v>Cache = cache_ref()</v> - <v>Key = key()</v> - <v>Session = session()</v> + <v>Cache = <seealso marker="#type-session_cache_ref"> session_cache_ref() </seealso></v> + <v>Key = <seealso marker="#type-session_cache_key">session_cache_key()</seealso> </v> + <v>Session = <seealso marker="#type-session">session()</seealso></v> </type> <desc> <p>Caches a new session or updates an already cached one. Is diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 2583667fa2..2c6b71c97a 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2018. All Rights Reserved. +%% Copyright Ericsson AB 2013-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -50,8 +50,7 @@ -export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). %% Data handling --export([encode_data/3, next_record/1, - send/3, socket/5, setopts/3, getopts/3]). +-export([next_record/1, socket/4, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -80,7 +79,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} end. %%-------------------------------------------------------------------- --spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> +-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 @@ -107,9 +106,11 @@ pids(_) -> %%==================================================================== %% State transition handling %%==================================================================== -next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> - {no_record, State#state{unprocessed_handshake_events = N-1}}; - +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, @@ -249,26 +250,29 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, #state{protocol_buffers = Buffers0, - negotiated_version = Version} = State0) -> + connection_env = #connection_env{negotiated_version = Version}} = State) -> try case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of {[], Buffers} -> - next_event(StateName, no_record, State0#state{protocol_buffers = Buffers}); + next_event(StateName, no_record, State#state{protocol_buffers = Buffers}); {Packets, Buffers} -> - State = State0#state{protocol_buffers = Buffers}, + HsEnv = State#state.handshake_env, Events = dtls_handshake_events(Packets), {next_state, StateName, - State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + 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, State0) + 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{negotiated_version = Version} = State) -> + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> case decode_alerts(EncAlerts) of Alerts = [_|_] -> handle_alerts(Alerts, {next_state, StateName, State}); @@ -299,8 +303,8 @@ 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{tls_handshake_history = Hist0, - negotiated_version = Version, +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} = State) -> @@ -308,17 +312,17 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, Hist = update_handshake_history(Handshake0, Handshake, Hist0), State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], next_sequence => Seq +1}, - tls_handshake_history = Hist}; + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; -queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, - negotiated_version = Version, +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} = State) -> Handshake = dtls_handshake:encode_handshake(Handshake0, Version, Seq), Hist = update_handshake_history(Handshake0, Handshake, Hist0), State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], next_sequence => Seq +1}, - tls_handshake_history = Hist}. + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}. queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, connection_states = ConnectionStates0} = State) -> @@ -330,11 +334,14 @@ queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, reinit(State) -> %% To be API compatible with TLS NOOP here reinit_handshake_data(State). -reinit_handshake_data(#state{protocol_buffers = Buffers} = State) -> - State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history(), - flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, +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{ @@ -358,9 +365,9 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> encode_alert(#alert{} = Alert, Version, ConnectionStates) -> dtls_record:encode_alert_record(Alert, Version, ConnectionStates). -send_alert(Alert, #state{negotiated_version = Version, - static_env = #static_env{socket = Socket, +send_alert(Alert, #state{static_env = #static_env{socket = Socket, transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, connection_states = ConnectionStates0} = State0) -> {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), @@ -384,16 +391,13 @@ protocol_name() -> %% Data handling %%==================================================================== -encode_data(Data, Version, ConnectionStates0)-> - dtls_record:encode_data(Data, Version, ConnectionStates0). - -send(Transport, {_, {{_,_}, _} = Socket}, Data) -> - send(Transport, Socket, Data); -send(Transport, Socket, Data) -> - dtls_socket:send(Transport, Socket, Data). +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, Connection, _) -> - dtls_socket:socket(Pid, Transport, Socket, Connection). +socket(Pid, Transport, Socket, _Tracker) -> + dtls_socket:socket(Pid, Transport, Socket, ?MODULE). setopts(Transport, Socket, Other) -> dtls_socket:setopts(Transport, Socket, Other). @@ -417,41 +421,34 @@ init({call, From}, {start, Timeout}, role = client, session_cache = Cache, session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + connection_env = CEnv, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _} + connection_states = ConnectionStates0 } = State0) -> - Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), - State1 = prepare_flight(State0#state{negotiated_version = Version}), - {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), - State3 = State2#state{negotiated_version = Version, %% Requested version + State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), + {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), + State3 = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion session = Session0#session{session_id = Hello#client_hello.session_id}, - start_or_recv_from = From, - timer = Timer, - flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} - }, + start_or_recv_from = From}, {Record, State} = next_record(State3), - next_event(hello, Record, State, Actions); -init({call, _} = Type, Event, #state{static_env = #static_env{role = server, - data_tag = udp}} = State) -> + next_event(hello, 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{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, - protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(), - previous_cookie_secret => <<>>, - ignored_alerts => 0, - max_ignored_alerts => 10}}), + 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({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) -> - %% I.E. DTLS over sctp - gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable}); init(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). @@ -487,6 +484,8 @@ hello(internal, #client_hello{cookie = <<>>, #state{static_env = #static_env{role = server, transport_cb = Transport, socket = Socket}, + 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), @@ -497,45 +496,54 @@ hello(internal, #client_hello{cookie = <<>>, %% 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{negotiated_version = Version}), + State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), {State2, Actions} = send_handshake(VerifyRequest, State1), {Record, State} = next_record(State2), - next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); + next_event(?FUNCTION_NAME, 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, session_cache = Cache, session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, + connection_env = CEnv, ssl_options = SslOpts, session = #session{own_certificate = OwnCert} = Session0, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _} + connection_states = ConnectionStates0 } = State0) -> Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, OwnCert), Version = Hello#client_hello.client_version, - State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}), + State1 = prepare_flight(State0#state{handshake_env = + HsEnv#handshake_env{tls_handshake_history + = ssl_handshake:init_handshake_history()}}), {State2, Actions} = send_handshake(Hello, State1), - State = State2#state{negotiated_version = Version, %% Requested version + State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}}, next_event(?FUNCTION_NAME, no_record, State, Actions); hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, + handshake_env = HsEnv, start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, - hello = Hello}, + handshake_env = HsEnv#handshake_env{hello = Hello}}, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, + handshake_env = HsEnv, start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, - hello = Hello}, + handshake_env = HsEnv#handshake_env{hello = Hello}}, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server, @@ -560,9 +568,9 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #sta hello(internal, #server_hello{} = Hello, #state{ static_env = #static_env{role = client}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + connection_env = #connection_env{negotiated_version = ReqVersion}, connection_states = ConnectionStates0, - negotiated_version = ReqVersion, - renegotiation = {Renegotiation, _}, ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> @@ -609,10 +617,11 @@ abbreviated(internal = Type, ConnectionStates1 = dtls_record:save_current_connection_state(ConnectionStates0, read), ConnectionStates = dtls_record:next_epoch(ConnectionStates1, read), gen_handshake(?FUNCTION_NAME, Type, Event, State#state{connection_states = ConnectionStates}); -abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) -> +abbreviated(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates, + protocol_specific = PS} = State) -> gen_handshake(?FUNCTION_NAME, Type, Event, prepare_flight(State#state{connection_states = ConnectionStates, - flight_state = connection})); + protocol_specific = PS#{flight_state => connection}})); abbreviated(state_timeout, Event, State) -> handle_state_timeout(Event, ?FUNCTION_NAME, State); abbreviated(Type, Event, State) -> @@ -652,10 +661,11 @@ cipher(internal = Type, #change_cipher_spec{type = <<1>>} = Event, 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); -cipher(internal = Type, #finished{} = Event, #state{connection_states = ConnectionStates} = State) -> +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, - flight_state = connection}), + protocol_specific = PS#{flight_state => connection}}), ?MODULE); cipher(state_timeout, Event, State) -> handle_state_timeout(Event, ?FUNCTION_NAME, State); @@ -673,39 +683,43 @@ connection(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host, port = Port, + data_tag = DataTag, session_cache = Cache, session_cache_cb = CacheCb }, + handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}}, + connection_env = CEnv, session = #session{own_certificate = Cert} = Session0, - ssl_options = SslOpts, connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> + protocol_specific = PS + } = State0) -> Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), State1 = prepare_flight(State0), - {State2, Actions} = send_handshake(Hello, State1#state{negotiated_version = HelloVersion}), + {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), {Record, State} = next_record( - State2#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, + State2#state{protocol_specific = PS#{flight_state => initial_flight_state(DataTag)}, session = Session0#session{session_id - = Hello#client_hello.session_id}}), + = Hello#client_hello.session_id}}), next_event(hello, Record, State, Actions); connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server}, - allow_renegotiate = true} = State) -> + 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 %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}}, + {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}, - allow_renegotiate = false} = State0) -> + 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), @@ -774,6 +788,12 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User, }, #state{static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation + }, + connection_env = #connection_env{user_application = {Monitor, User}}, socket_options = SocketOptions, %% We do not want to save the password in the state so that %% could be written in the clear into error logs. @@ -781,15 +801,17 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User, session = #session{is_resumable = new}, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, - user_application = {Monitor, User}, - user_data_buffer = <<>>, - renegotiation = {false, first}, - allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + user_data_buffer = {[],0,[]}, start_or_recv_from = undefined, flight_buffer = new_flight(), - flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} + protocol_specific = #{flight_state => 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} = State0) -> @@ -807,7 +829,7 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ acceptable_record_versions(hello, _) -> [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_DATAGRAM_SUPPORTED_VERSIONS]; -acceptable_record_versions(_, #state{negotiated_version = Version}) -> +acceptable_record_versions(_, #state{connection_env = #connection_env{negotiated_version = Version}}) -> [Version]. dtls_handshake_events(Packets) -> @@ -826,8 +848,9 @@ decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts {Alert, State} end. -dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) -> - State#state{negotiated_version = Version}; %%Inital version +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. @@ -836,11 +859,11 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, static_env = #static_env{port = Port, session_cache = Cache, session_cache_cb = CacheCb}, + handshake_env = #handshake_env{kex_algorithm = KeyExAlg, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = CEnv, session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - - negotiated_protocol = CurrentProtocol, - key_algorithm = KeyExAlg, ssl_options = SslOpts} = State0) -> case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, @@ -855,11 +878,12 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, end, State = prepare_flight(State0#state{connection_states = ConnectionStates, - negotiated_version = Version, - hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, - session = Session, - negotiated_protocol = Protocol}), + 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) @@ -879,9 +903,9 @@ handle_info({Protocol, _, _, _, Data}, StateName, handle_info({CloseTag, Socket}, StateName, #state{static_env = #static_env{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}, - negotiated_version = Version} = State) -> + protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}} = 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 @@ -917,9 +941,10 @@ handle_info(Msg, StateName, State) -> ssl_connection:StateName(info, Msg, State, ?MODULE). handle_state_timeout(flight_retransmission_timeout, StateName, - #state{flight_state = {retransmit, NextTimeout}} = State0) -> - {State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}}, - retransmit_epoch(StateName, State0)), + #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), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}. @@ -959,7 +984,7 @@ decode_alerts(Bin) -> ssl_alert:decode(Bin). gen_handshake(StateName, Type, Event, - #state{negotiated_version = Version} = State) -> + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try ssl_connection:StateName(Type, Event, State, ?MODULE) of Result -> Result @@ -970,7 +995,7 @@ gen_handshake(StateName, Type, Event, Version, StateName, State) end. -gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) -> +gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try handle_info(Event, StateName, State) of Result -> Result @@ -981,7 +1006,7 @@ gen_info(Event, connection = StateName, #state{negotiated_version = Version} = Version, StateName, State) end; -gen_info(Event, StateName, #state{negotiated_version = Version} = State) -> +gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try handle_info(Event, StateName, State) of Result -> Result @@ -1025,17 +1050,17 @@ next_flight(Flight) -> handshakes_after_change_cipher_spec => []}. handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, - flight_state = {retransmit, Timeout}} = State) -> + protocol_specific = #{flight_state := {retransmit, Timeout}}} = State) -> start_retransmision_timer(Timeout, State); handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, - flight_state = connection} = State) -> + protocol_specific = #{flight_state := connection}} = State) -> {State, []}; -handle_flight_timer(State) -> +handle_flight_timer(#state{protocol_specific = #{flight_state := reliable}} = State) -> %% No retransmision needed i.e DTLS over SCTP - {State#state{flight_state = reliable}, []}. + {State, []}. -start_retransmision_timer(Timeout, State) -> - {State#state{flight_state = {retransmit, new_timeout(Timeout)}}, +start_retransmision_timer(Timeout, #state{protocol_specific = PS} = State) -> + {State#state{protocol_specific = PS#{flight_state => {retransmit, new_timeout(Timeout)}}}, [{state_timeout, Timeout, flight_retransmission_timeout}]}. new_timeout(N) when N =< 30 -> @@ -1045,9 +1070,9 @@ new_timeout(_) -> send_handshake_flight(#state{static_env = #static_env{socket = Socket, transport_cb = Transport}, - flight_buffer = #{handshakes := Flight, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = #{handshakes := Flight, change_cipher_spec := undefined}, - negotiated_version = Version, connection_states = ConnectionStates0} = State0, Epoch) -> %% TODO remove hardcoded Max size {Encoded, ConnectionStates} = @@ -1057,10 +1082,10 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, 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 := []}, - negotiated_version = Version, connection_states = ConnectionStates0} = State0, Epoch) -> {HsBefore, ConnectionStates1} = encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch, ConnectionStates0), @@ -1071,10 +1096,10 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, 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}, - negotiated_version = Version, connection_states = ConnectionStates0} = State0, Epoch) -> {HsBefore, ConnectionStates1} = encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch-1, ConnectionStates0), @@ -1087,10 +1112,10 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, 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}, - negotiated_version = Version, connection_states = ConnectionStates0} = State0, Epoch) -> {EncChangeCipher, ConnectionStates1} = encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), @@ -1144,21 +1169,21 @@ log_ignore_alert(false, _, _,_) -> send_application_data(Data, From, _StateName, #state{static_env = #static_env{socket = Socket, - protocol_cb = Connection, transport_cb = Transport}, - negotiated_version = Version, + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = HsEnv, connection_states = ConnectionStates0, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) -> case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of true -> - renegotiate(State0#state{renegotiation = {true, internal}}, + renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}}, [{next_event, {call, From}, {application_data, Data}}]); false -> {Msgs, ConnectionStates} = - Connection:encode_data(Data, Version, ConnectionStates0), + dtls_record:encode_data(Data, Version, ConnectionStates0), State = State0#state{connection_states = ConnectionStates}, - case Connection:send(Transport, Socket, Msgs) of + case send(Transport, Socket, Msgs) of ok -> ssl_connection:hibernate_after(connection, State, [{reply, From, ok}]); Result -> @@ -1180,3 +1205,4 @@ is_time_to_renegotiate(N, M) when N < M-> false; is_time_to_renegotiate(_,_) -> true. + diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 3f70eaec8a..6e9bf99e52 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -46,7 +46,7 @@ %% Handshake handling %%==================================================================== %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), +-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -59,7 +59,7 @@ client_hello(Host, Port, ConnectionStates, SslOpts, Cache, CacheCb, Renegotiation, OwnCert). %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), term(), ssl_record:connection_states(), +-spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -123,7 +123,7 @@ cookie(Key, Address, Port, #client_hello{client_version = {Major, Minor}, Random, SessionId, CipherSuites, CompressionMethods], crypto:hmac(sha, Key, CookieData). %%-------------------------------------------------------------------- --spec hello_verify_request(binary(), dtls_record:dtls_version()) -> #hello_verify_request{}. +-spec hello_verify_request(binary(), ssl_record:ssl_version()) -> #hello_verify_request{}. %% %% Description: Creates a hello verify request message sent by server to %% verify client @@ -151,7 +151,7 @@ encode_handshake(Handshake, Version, Seq) -> %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec get_dtls_handshake(dtls_record:dtls_version(), binary(), #protocol_buffers{}) -> +-spec get_dtls_handshake(ssl_record:ssl_version(), binary(), #protocol_buffers{}) -> {[dtls_handshake()], #protocol_buffers{}}. %% %% Description: Given buffered and new data from dtls_record, collects @@ -215,8 +215,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, dtls_v1:corresponding_tls_version(Version), SslOpts, Session0, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} catch throw:Alert -> @@ -225,17 +223,16 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> - case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, - Compression, HelloExt, - dtls_v1:corresponding_tls_version(Version), - SslOpt, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; + try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, + Compression, HelloExt, + dtls_v1:corresponding_tls_version(Version), + SslOpt, ConnectionStates0, Renegotiation) of {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + catch throw:Alert -> + Alert end. - %%-------------------------------------------------------------------- enc_handshake(#hello_verify_request{protocol_version = {Major, Minor}, diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl index 50e92027d2..41da8e5c8c 100644 --- a/lib/ssl/src/dtls_handshake.hrl +++ b/lib/ssl/src/dtls_handshake.hrl @@ -27,6 +27,7 @@ -define(dtls_handshake, true). -include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes +-include("ssl_api.hrl"). -define(HELLO_VERIFY_REQUEST, 3). -define(HELLO_VERIFY_REQUEST_VERSION, {254, 255}). diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index 1497c77cf3..092366b7c0 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -144,11 +144,11 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket %% UDP socket does not have a connection and should not receive an econnreset %% This does however happens on some windows versions. Just ignoring it %% appears to make things work as expected! -handle_info({Error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) -> +handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, transport = {_,_,_, udp_error}} = State) -> Report = io_lib:format("Ignore SSL UDP Listener: Socket error: ~p ~n", [Error]), error_logger:info_report(Report), {noreply, State}; -handle_info({Error, Socket, Error}, #state{listener = Socket, transport = {_,_,_, Error}} = State) -> +handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag}} = State) -> Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]), error_logger:info_report(Report), {noreply, State#state{close=true}}; @@ -297,6 +297,9 @@ do_set_emulated_opts([], Opts) -> Opts; do_set_emulated_opts([{mode, Value} | Rest], Opts) -> do_set_emulated_opts(Rest, Opts#socket_options{mode = Value}); +do_set_emulated_opts([{active, N0} | Rest], Opts=#socket_options{active = Active}) when is_integer(N0) -> + N = tls_socket:update_active_n(N0, Active), + do_set_emulated_opts(Rest, Opts#socket_options{active = N}); do_set_emulated_opts([{active, Value} | Rest], Opts) -> do_set_emulated_opts(Rest, Opts#socket_options{active = Value}). diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index b7346d3ec8..2fe875da31 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2018. All Rights Reserved. +%% Copyright Ericsson AB 2013-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -49,9 +49,8 @@ is_acceptable_version/2, hello_version/2]). --export_type([dtls_version/0, dtls_atom_version/0]). +-export_type([dtls_atom_version/0]). --type dtls_version() :: ssl_record:ssl_version(). -type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'. -define(REPLAY_WINDOW_SIZE, 64). @@ -135,7 +134,7 @@ set_connection_state_by_epoch(ReadState, Epoch, #{saved_read := #{epoch := Epoch States#{saved_read := ReadState}. %%-------------------------------------------------------------------- --spec init_connection_state_seq(dtls_version(), ssl_record:connection_states()) -> +-spec init_connection_state_seq(ssl_record:ssl_version(), ssl_record:connection_states()) -> ssl_record:connection_state(). %% %% Description: Copy the read sequence number to the write sequence number @@ -163,7 +162,7 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, Epoch. %%-------------------------------------------------------------------- --spec get_dtls_records(binary(), [dtls_version()], binary()) -> {[binary()], binary()} | #alert{}. +-spec get_dtls_records(binary(), [ssl_record:ssl_version()], binary()) -> {[binary()], binary()} | #alert{}. %% %% Description: Given old buffer and new data from UDP/SCTP, packs up a records %% and returns it as a list of tls_compressed binaries also returns leftover @@ -188,7 +187,7 @@ get_dtls_records(Data, Versions, Buffer) -> %%==================================================================== %%-------------------------------------------------------------------- --spec encode_handshake(iolist(), dtls_version(), integer(), ssl_record:connection_states()) -> +-spec encode_handshake(iolist(), ssl_record:ssl_version(), integer(), ssl_record:connection_states()) -> {iolist(), ssl_record:connection_states()}. % %% Description: Encodes a handshake message to send on the ssl-socket. @@ -198,7 +197,7 @@ encode_handshake(Frag, Version, Epoch, ConnectionStates) -> %%-------------------------------------------------------------------- --spec encode_alert_record(#alert{}, dtls_version(), ssl_record:connection_states()) -> +-spec encode_alert_record(#alert{}, ssl_record:ssl_version(), ssl_record:connection_states()) -> {iolist(), ssl_record:connection_states()}. %% %% Description: Encodes an alert message to send on the ssl-socket. @@ -210,7 +209,7 @@ encode_alert_record(#alert{level = Level, description = Description}, ConnectionStates). %%-------------------------------------------------------------------- --spec encode_change_cipher_spec(dtls_version(), integer(), ssl_record:connection_states()) -> +-spec encode_change_cipher_spec(ssl_record:ssl_version(), integer(), ssl_record:connection_states()) -> {iolist(), ssl_record:connection_states()}. %% %% Description: Encodes a change_cipher_spec-message to send on the ssl socket. @@ -219,7 +218,7 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) -> encode_plain_text(?CHANGE_CIPHER_SPEC, Version, Epoch, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates). %%-------------------------------------------------------------------- --spec encode_data(binary(), dtls_version(), ssl_record:connection_states()) -> +-spec encode_data(binary(), ssl_record:ssl_version(), ssl_record:connection_states()) -> {iolist(),ssl_record:connection_states()}. %% %% Description: Encodes data to send on the ssl-socket. @@ -248,8 +247,8 @@ decode_cipher_text(#ssl_tls{epoch = Epoch} = CipherText, ConnnectionStates0) -> %%==================================================================== %%-------------------------------------------------------------------- --spec protocol_version(dtls_atom_version() | dtls_version()) -> - dtls_version() | dtls_atom_version(). +-spec protocol_version(dtls_atom_version() | ssl_record:ssl_version()) -> + ssl_record:ssl_version() | dtls_atom_version(). %% %% Description: Creates a protocol version record from a version atom %% or vice versa. @@ -263,7 +262,7 @@ protocol_version({254, 253}) -> protocol_version({254, 255}) -> dtlsv1. %%-------------------------------------------------------------------- --spec lowest_protocol_version(dtls_version(), dtls_version()) -> dtls_version(). +-spec lowest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version(). %% %% Description: Lowes protocol version of two given versions %%-------------------------------------------------------------------- @@ -277,7 +276,7 @@ lowest_protocol_version(_,Version) -> Version. %%-------------------------------------------------------------------- --spec lowest_protocol_version([dtls_version()]) -> dtls_version(). +-spec lowest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version(). %% %% Description: Lowest protocol version present in a list %%-------------------------------------------------------------------- @@ -288,7 +287,7 @@ lowest_protocol_version(Versions) -> lowest_list_protocol_version(Ver, Vers). %%-------------------------------------------------------------------- --spec highest_protocol_version([dtls_version()]) -> dtls_version(). +-spec highest_protocol_version([ssl_record:ssl_version()]) -> ssl_record:ssl_version(). %% %% Description: Highest protocol version present in a list %%-------------------------------------------------------------------- @@ -299,7 +298,7 @@ highest_protocol_version(Versions) -> highest_list_protocol_version(Ver, Vers). %%-------------------------------------------------------------------- --spec highest_protocol_version(dtls_version(), dtls_version()) -> dtls_version(). +-spec highest_protocol_version(ssl_record:ssl_version(), ssl_record:ssl_version()) -> ssl_record:ssl_version(). %% %% Description: Highest protocol version of two given versions %%-------------------------------------------------------------------- @@ -315,7 +314,7 @@ highest_protocol_version(_,Version) -> Version. %%-------------------------------------------------------------------- --spec is_higher(V1 :: dtls_version(), V2::dtls_version()) -> boolean(). +-spec is_higher(V1 :: ssl_record:ssl_version(), V2::ssl_record:ssl_version()) -> boolean(). %% %% Description: Is V1 > V2 %%-------------------------------------------------------------------- @@ -327,7 +326,7 @@ is_higher(_, _) -> false. %%-------------------------------------------------------------------- --spec supported_protocol_versions() -> [dtls_version()]. +-spec supported_protocol_versions() -> [ssl_record:ssl_version()]. %% %% Description: Protocol versions supported %%-------------------------------------------------------------------- @@ -370,7 +369,7 @@ supported_protocol_versions([_|_] = Vsns) -> end. %%-------------------------------------------------------------------- --spec is_acceptable_version(dtls_version(), Supported :: [dtls_version()]) -> boolean(). +-spec is_acceptable_version(ssl_record:ssl_version(), Supported :: [ssl_record:ssl_version()]) -> boolean(). %% %% Description: ssl version 2 is not acceptable security risks are too big. %% @@ -378,7 +377,7 @@ supported_protocol_versions([_|_] = Vsns) -> is_acceptable_version(Version, Versions) -> lists:member(Version, Versions). --spec hello_version(dtls_version(), [dtls_version()]) -> dtls_version(). +-spec hello_version(ssl_record:ssl_version(), [ssl_record:ssl_version()]) -> ssl_record:ssl_version(). hello_version(Version, Versions) -> case dtls_v1:corresponding_tls_version(Version) of TLSVersion when TLSVersion >= {3, 3} -> @@ -547,15 +546,15 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version, compression_algorithm = CompAlg}} = ReadState0, ConnnectionStates0) -> AAD = start_additional_data(Type, Version, Epoch, Seq), - CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0), + CipherS = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT16(Epoch), ?UINT48(Seq)>>, CipherS0), TLSVersion = dtls_v1:corresponding_tls_version(Version), - case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, TLSVersion) of - {PlainFragment, CipherState} -> - {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + case ssl_record:decipher_aead(BulkCipherAlgo, CipherS, AAD, CipherFragment, TLSVersion) of + PlainFragment when is_binary(PlainFragment) -> + {Plain, CompressionS} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ReadState0 = ReadState0#{compression_state => CompressionS1, - cipher_state => CipherState}, - ReadState = update_replay_window(Seq, ReadState0), + ReadState1 = ReadState0#{compression_state := CompressionS, + cipher_state := CipherS}, + ReadState = update_replay_window(Seq, ReadState1), ConnnectionStates = set_connection_state_by_epoch(ReadState, Epoch, ConnnectionStates0, read), {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; #alert{} = Alert -> diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl index 2001afd02f..4d07372e31 100644 --- a/lib/ssl/src/dtls_socket.erl +++ b/lib/ssl/src/dtls_socket.erl @@ -38,7 +38,9 @@ listen(Port, #config{transport_info = TransportInfo, case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}), Options ++ internal_inet_values(), SslOpts]) of {ok, Pid} -> - {ok, #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}}; + Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}, + check_active_n(EmOpts, Socket), + {ok, Socket}; Err = {error, _} -> Err end. @@ -81,8 +83,9 @@ socket(Pids, Transport, Socket, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}. -setopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> - SplitOpts = tls_socket:split_options(Options), +setopts(_, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> + SplitOpts = {_, EmOpts} = tls_socket:split_options(Options), + check_active_n(EmOpts, Socket), dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts); %%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_udp, Socket, Options) -> @@ -90,6 +93,32 @@ setopts(gen_udp, Socket, Options) -> setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). +check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}) -> + %% We check the resulting options to send an ssl_passive message if necessary. + case proplists:lookup(active, EmulatedOpts) of + %% The provided value is out of bound. + {_, N} when is_integer(N), N < -32768 -> + throw(einval); + {_, N} when is_integer(N), N > 32767 -> + throw(einval); + {_, N} when is_integer(N) -> + {ok, #socket_options{active = Active}, _} = dtls_packet_demux:get_all_opts(ListenPid), + case Active of + Atom when is_atom(Atom), N =< 0 -> + self() ! {ssl_passive, Socket}; + %% The result of the addition is out of bound. + %% We do not need to check < -32768 because Active can't be below 1. + A when is_integer(A), A + N > 32767 -> + throw(einval); + A when is_integer(A), A + N =< 0 -> + self() ! {ssl_passive, Socket}; + _ -> + ok + end; + _ -> + ok + end. + getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> SplitOpts = tls_socket:split_options(Options), dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts); @@ -161,9 +190,18 @@ emulated_socket_options(InetValues, #socket_options{ mode = proplists:get_value(mode, InetValues, Mode), packet = proplists:get_value(packet, InetValues, Packet), packet_size = proplists:get_value(packet_size, InetValues, PacketSize), - active = proplists:get_value(active, InetValues, Active) + active = emulated_active_option(InetValues, Active) }. +emulated_active_option([], Active) -> + Active; +emulated_active_option([{active, Active} | _], _) when Active =< 0 -> + false; +emulated_active_option([{active, Active} | _], _) -> + Active; +emulated_active_option([_|Tail], Active) -> + emulated_active_option(Tail, Active). + emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> validate_inet_option(mode, Value), emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); @@ -185,6 +223,9 @@ validate_inet_option(mode, Value) when Value =/= list, Value =/= binary -> throw({error, {options, {mode,Value}}}); validate_inet_option(active, Value) + when Value >= -32768, Value =< 32767 -> + ok; +validate_inet_option(active, Value) when Value =/= true, Value =/= false, Value =/= once -> throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index a4f8bb7562..5cab35fd4b 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -480,22 +480,25 @@ allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) -> allowed_nodes(PeerCert, Allowed, PeerIP) end. - - setup(Node, Type, MyNode, LongOrShortNames, SetupTime) -> gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime). gen_setup(Driver, Node, Type, MyNode, LongOrShortNames, SetupTime) -> Kernel = self(), monitor_pid( - spawn_opt( - fun() -> - do_setup( - Driver, Kernel, Node, Type, - MyNode, LongOrShortNames, SetupTime) - end, - [link, {priority, max}])). + spawn_opt(setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime), + [link, {priority, max}])). + +-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()). +setup_fun(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> + fun() -> + do_setup( + Driver, Kernel, Node, Type, + MyNode, LongOrShortNames, SetupTime) + end. + +-spec do_setup(_,_,_,_,_,_,_) -> no_return(). do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> {Name, Address} = split_node(Driver, Node, LongOrShortNames), ErlEpmd = net_kernel:epmd_module(), @@ -520,6 +523,8 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> trace({getaddr_failed, Driver, Address, Other})) end. +-spec do_setup_connect(_,_,_,_,_,_,_,_,_,_) -> no_return(). + do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) -> Opts = trace(connect_options(get_ssl_options(client))), dist_util:reset_timer(Timer), @@ -564,7 +569,7 @@ gen_close(Driver, Socket) -> %% Determine if EPMD module supports address resolving. Default %% is to use inet_tcp:getaddr/2. %% ------------------------------------------------------------ -get_address_resolver(EpmdModule, Driver) -> +get_address_resolver(EpmdModule, _Driver) -> case erlang:function_exported(EpmdModule, address_please, 3) of true -> {EpmdModule, address_please}; _ -> {erl_epmd, address_please} diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 03a1e40bfc..fb6b7ba8e8 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2018. All Rights Reserved. +%% Copyright Ericsson AB 1999-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -61,16 +61,317 @@ -deprecated({ssl_accept, 2, eventually}). -deprecated({ssl_accept, 3, eventually}). +-export_type([socket/0, + sslsocket/0, + socket_option/0, + active_msgs/0, + host/0, + tls_option/0, + tls_client_option/0, + tls_server_option/0, + erl_cipher_suite/0, + old_cipher_suite/0, + ciphers/0, + cipher/0, + hash/0, + kex_algo/0, + prf_random/0, + cipher_filters/0, + sign_algo/0, + protocol_version/0, + protocol_extensions/0, + session_id/0, + error_alert/0, + srp_param_type/0]). + +%% ------------------------------------------------------------------------------------------------------- +-type socket() :: gen_tcp:socket(). +-type socket_option() :: gen_tcp:connect_option() | gen_tcp:listen_option() | gen_udp:option(). +-type sslsocket() :: any(). +-type tls_option() :: tls_client_option() | tls_server_option(). +-type tls_client_option() :: client_option() | common_option() | socket_option() | transport_option(). +-type tls_server_option() :: server_option() | common_option() | socket_option() | transport_option(). +-type active_msgs() :: {ssl, sslsocket(), Data::binary() | list()} | {ssl_closed, sslsocket()} | + {ssl_error, sslsocket(), Reason::term()} | {ssl_passive, sslsocket()}. +-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), + ClosedTag::atom(), ErrTag::atom()}}. +-type host() :: hostname() | ip_address(). +-type hostname() :: string(). +-type ip_address() :: inet:ip_address(). +-type session_id() :: binary(). +-type protocol_version() :: tls_version() | dtls_version(). +-type tls_version() :: tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3' | legacy_version(). +-type dtls_version() :: 'dtlsv1' | 'dtlsv1.2'. +-type legacy_version() :: sslv3. +-type verify_type() :: verify_none | verify_peer. +-type cipher() :: aes_128_cbc | + aes_256_cbc | + aes_128_gcm | + aes_256_gcm | + chacha20_poly1305 | + legacy_cipher(). +-type legacy_cipher() :: rc4_128 | + des_cbc | + '3des_ede_cbc'. + +-type hash() :: sha | + sha2() | + legacy_hash(). + +-type sha2() :: sha224 | + sha256 | + sha384 | + sha512. + +-type legacy_hash() :: md5. + +-type sign_algo() :: rsa | dsa | ecdsa. +-type kex_algo() :: rsa | + dhe_rsa | dhe_dss | + ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa | + srp_rsa| srp_dss | + psk | dhe_psk | rsa_psk | + dh_anon | ecdh_anon | srp_anon | + any. %% TLS 1.3 +-type erl_cipher_suite() :: #{key_exchange := kex_algo(), + cipher := cipher(), + mac := hash() | aead, + prf := hash() | default_prf %% Old cipher suites, version dependent + }. + +-type old_cipher_suite() :: {kex_algo(), cipher(), hash()} % Pre TLS 1.2 + %% TLS 1.2, internally PRE TLS 1.2 will use default_prf + | {kex_algo(), cipher(), hash() | aead, hash()}. + +-type named_curve() :: sect571r1 | + sect571k1 | + secp521r1 | + brainpoolP512r1 | + sect409k1 | + sect409r1 | + brainpoolP384r1 | + secp384r1 | + sect283k1 | + sect283r1 | + brainpoolP256r1 | + secp256k1 | + secp256r1 | + sect239k1 | + sect233k1 | + sect233r1 | + secp224k1 | + secp224r1 | + sect193r1 | + sect193r2 | + secp192k1 | + secp192r1 | + sect163k1 | + sect163r1 | + sect163r2 | + secp160k1 | + secp160r1 | + secp160r2. + +-type srp_param_type() :: srp_1024 | + srp_1536 | + srp_2048 | + srp_3072 | + srp_4096 | + srp_6144 | + srp_8192. + +-type error_alert() :: {tls_alert, {tls_alert(), Description::string()}}. + +-type tls_alert() :: close_notify | + unexpected_message | + bad_record_mac | + record_overflow | + handshake_failure | + bad_certificate | + unsupported_certificate | + certificate_revoked | + certificate_expired | + certificate_unknown | + illegal_parameter | + unknown_ca | + access_denied | + decode_error | + decrypt_error | + export_restriction| + protocol_version | + insufficient_security | + internal_error | + inappropriate_fallback | + user_canceled | + no_renegotiation | + unsupported_extension | + certificate_unobtainable | + unrecognized_name | + bad_certificate_status_response | + bad_certificate_hash_value | + unknown_psk_identity | + no_application_protocol. +%% ------------------------------------------------------------------------------------------------------- +-type common_option() :: {protocol, protocol()} | + {handshake, handshake_completion()} | + {cert, cert()} | + {certfile, cert_pem()} | + {key, key()} | + {keyfile, key_pem()} | + {password, key_password()} | + {ciphers, cipher_suites()} | + {eccs, eccs()} | + {secure_renegotiate, secure_renegotiation()} | + {depth, allowed_cert_chain_length()} | + {verify_fun, custom_verify()} | + {crl_check, crl_check()} | + {crl_cache, crl_cache_opts()} | + {max_handshake_size, handshake_size()} | + {partial_chain, root_fun()} | + {versions, protocol_versions()} | + {user_lookup_fun, custom_user_lookup()} | + {log_alert, log_alert()} | + {hibernate_after, hibernate_after()} | + {padding_check, padding_check()} | + {beast_mitigation, beast_mitigation()} | + {ssl_imp, ssl_imp()}. + +-type protocol() :: tls | dtls. +-type handshake_completion() :: hello | full. +-type cert() :: public_key:der_encoded(). +-type cert_pem() :: file:filename(). +-type key() :: {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo', + public_key:der_encoded()} | + #{algorithm := rsa | dss | ecdsa, + engine := crypto:engine_ref(), + key_id := crypto:key_id(), + password => crypto:password()}. +-type key_pem() :: file:filename(). +-type key_password() :: string(). +-type cipher_suites() :: ciphers(). +-type ciphers() :: [erl_cipher_suite()] | + string(). % (according to old API) +-type cipher_filters() :: list({key_exchange | cipher | mac | prf, + algo_filter()}). +-type algo_filter() :: fun((kex_algo()|cipher()|hash()|aead|default_prf) -> true | false). +-type eccs() :: [named_curve()]. +-type secure_renegotiation() :: boolean(). +-type allowed_cert_chain_length() :: integer(). +-type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: term()}. +-type crl_check() :: boolean() | peer | best_effort. +-type crl_cache_opts() :: [term()]. +-type handshake_size() :: integer(). +-type hibernate_after() :: timeout(). +-type root_fun() :: fun(). +-type protocol_versions() :: [protocol_version()]. +-type signature_algs() :: [{hash(), sign_algo()}]. +-type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: term()}. +-type padding_check() :: boolean(). +-type beast_mitigation() :: one_n_minus_one | zero_n | disabled. +-type srp_identity() :: {Username :: string(), Password :: string()}. +-type psk_identity() :: string(). +-type log_alert() :: boolean(). + +%% ------------------------------------------------------------------------------------------------------- + +-type client_option() :: {verify, client_verify_type()} | + {reuse_session, client_reuse_session()} | + {reuse_sessions, client_reuse_sessions()} | + {cacerts, client_cacerts()} | + {cacertfile, client_cafile()} | + {alpn_advertised_protocols, client_alpn()} | + {client_preferred_next_protocols, client_preferred_next_protocols()} | + {psk_identity, client_psk_identity()} | + {srp_identity, client_srp_identity()} | + {server_name_indication, sni()} | + {customize_hostname_check, customize_hostname_check()} | + {signature_algs, client_signature_algs()} | + {fallback, fallback()}. + +-type client_verify_type() :: verify_type(). +-type client_reuse_session() :: session_id(). +-type client_reuse_sessions() :: boolean() | save. +-type client_cacerts() :: [public_key:der_encoded()]. +-type client_cafile() :: file:filename(). +-type app_level_protocol() :: binary(). +-type client_alpn() :: [app_level_protocol()]. +-type client_preferred_next_protocols() :: {Precedence :: server | client, + ClientPrefs :: [app_level_protocol()]} | + {Precedence :: server | client, + ClientPrefs :: [app_level_protocol()], + Default::app_level_protocol()}. +-type client_psk_identity() :: psk_identity(). +-type client_srp_identity() :: srp_identity(). +-type customize_hostname_check() :: list(). +-type sni() :: HostName :: hostname() | disable. +-type client_signature_algs() :: signature_algs(). +-type fallback() :: boolean(). +-type ssl_imp() :: new | old. + +%% ------------------------------------------------------------------------------------------------------- + +-type server_option() :: {cacerts, server_cacerts()} | + {cacertfile, server_cafile()} | + {dh, dh_der()} | + {dhfile, dh_file()} | + {verify, server_verify_type()} | + {fail_if_no_peer_cert, fail_if_no_peer_cert()} | + {reuse_sessions, server_reuse_sessions()} | + {reuse_session, server_reuse_session()} | + {alpn_preferred_protocols, server_alpn()} | + {next_protocols_advertised, server_next_protocol()} | + {psk_identity, server_psk_identity()} | + {honor_cipher_order, boolean()} | + {sni_hosts, sni_hosts()} | + {sni_fun, sni_fun()} | + {honor_cipher_order, honor_cipher_order()} | + {honor_ecc_order, honor_ecc_order()} | + {client_renegotiation, client_renegotiation()}| + {signature_algs, server_signature_algs()}. + +-type server_cacerts() :: [public_key:der_encoded()]. +-type server_cafile() :: file:filename(). +-type server_alpn() :: [app_level_protocol()]. +-type server_next_protocol() :: [app_level_protocol()]. +-type server_psk_identity() :: psk_identity(). +-type dh_der() :: binary(). +-type dh_file() :: file:filename(). +-type server_verify_type() :: verify_type(). +-type fail_if_no_peer_cert() :: boolean(). +-type server_signature_algs() :: signature_algs(). +-type server_reuse_session() :: fun(). +-type server_reuse_sessions() :: boolean(). +-type sni_hosts() :: [{hostname(), [server_option() | common_option()]}]. +-type sni_fun() :: fun(). +-type honor_cipher_order() :: boolean(). +-type honor_ecc_order() :: boolean(). +-type client_renegotiation() :: boolean(). +%% ------------------------------------------------------------------------------------------------------- +-type prf_random() :: client_random | server_random. +-type protocol_extensions() :: #{renegotiation_info => binary(), + signature_algs => signature_algs(), + alpn => app_level_protocol(), + srp => binary(), + next_protocol => app_level_protocol(), + ec_point_formats => [0..2], + elliptic_curves => [public_key:oid()], + sni => hostname()}. +%% ------------------------------------------------------------------------------------------------------- + +%%%-------------------------------------------------------------------- +%%% API +%%%-------------------------------------------------------------------- + %%-------------------------------------------------------------------- --spec start() -> ok | {error, reason()}. --spec start(permanent | transient | temporary) -> ok | {error, reason()}. %% %% Description: Utility function that starts the ssl and applications %% that it depends on. %% see application(3) %%-------------------------------------------------------------------- +-spec start() -> ok | {error, reason()}. start() -> start(temporary). +-spec start(permanent | transient | temporary) -> ok | {error, reason()}. start(Type) -> case application:ensure_all_started(ssl, Type) of {ok, _} -> @@ -87,20 +388,17 @@ stop() -> application:stop(ssl). %%-------------------------------------------------------------------- --spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | - {error, reason()}. --spec connect(host() | port(), [connect_option()] | inet:port_number(), - timeout() | list()) -> - {ok, #sslsocket{}} | {error, reason()}. --spec connect(host() | port(), inet:port_number(), list(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. - %% %% Description: Connect to an ssl server. %%-------------------------------------------------------------------- +-spec connect(host() | port(), [client_option()]) -> {ok, #sslsocket{}} | + {error, reason()}. connect(Socket, SslOptions) when is_port(Socket) -> connect(Socket, SslOptions, infinity). +-spec connect(host() | port(), [client_option()] | inet:port_number(), + timeout() | list()) -> + {ok, #sslsocket{}} | {error, reason()}. connect(Socket, SslOptions0, Timeout) when is_port(Socket), (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0, @@ -117,6 +415,8 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket), connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). +-spec connect(host() | port(), inet:port_number(), [client_option()], timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> try {ok, Config} = handle_options(Options, client, Host), @@ -132,7 +432,7 @@ connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout end. %%-------------------------------------------------------------------- --spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. +-spec listen(inet:port_number(), [tls_server_option()]) ->{ok, #sslsocket{}} | {error, reason()}. %% %% Description: Creates an ssl listen socket. @@ -148,16 +448,16 @@ listen(Port, Options0) -> Error end. %%-------------------------------------------------------------------- --spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} | - {error, reason()}. --spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {error, reason()}. %% %% Description: Performs transport accept on an ssl listen socket %%-------------------------------------------------------------------- +-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} | + {error, reason()}. transport_accept(ListenSocket) -> transport_accept(ListenSocket, infinity). +-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {error, reason()}. transport_accept(#sslsocket{pid = {ListenSocket, #config{connection_cb = ConnectionCb} = Config}}, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> @@ -169,25 +469,25 @@ transport_accept(#sslsocket{pid = {ListenSocket, end. %%-------------------------------------------------------------------- --spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}. --spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option() - | transport_option()]) -> - ok | {ok, #sslsocket{}} | {error, reason()}. - --spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) -> - ok | {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- +-spec ssl_accept(#sslsocket{}) -> ok | {error, timeout | closed | {options, any()}| error_alert()}. ssl_accept(ListenSocket) -> ssl_accept(ListenSocket, [], infinity). + +-spec ssl_accept(#sslsocket{} | port(), timeout()| [tls_server_option()]) -> + ok | {ok, #sslsocket{}} | {error, timeout | closed | {options, any()}| error_alert()}. ssl_accept(Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> ssl_accept(Socket, [], Timeout); ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> ssl_accept(ListenSocket, SslOptions, infinity); ssl_accept(Socket, Timeout) -> ssl_accept(Socket, [], Timeout). + +-spec ssl_accept(#sslsocket{} | port(), [tls_server_option()], timeout()) -> + ok | {ok, #sslsocket{}} | {error, timeout | closed | {options, any()}| error_alert()}. ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> handshake(Socket, SslOptions, Timeout); ssl_accept(Socket, SslOptions, Timeout) -> @@ -198,20 +498,18 @@ ssl_accept(Socket, SslOptions, Timeout) -> Error end. %%-------------------------------------------------------------------- --spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}. --spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() - | transport_option()]) -> - {ok, #sslsocket{}} | {error, reason()}. - --spec handshake(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- + +%% Performs the SSL/TLS/DTLS server-side handshake. +-spec handshake(#sslsocket{}) -> {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}. handshake(ListenSocket) -> handshake(ListenSocket, infinity). +-spec handshake(#sslsocket{} | port(), timeout()| [tls_server_option()]) -> + {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}. handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> ssl_connection:handshake(Socket, Timeout); @@ -219,6 +517,8 @@ handshake(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Tim handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> handshake(ListenSocket, SslOptions, infinity). +-spec handshake(#sslsocket{} | port(), [tls_server_option()], timeout()) -> + {ok, #sslsocket{}} | {error, timeout | closed | {options, any()} | error_alert()}. handshake(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> handshake(Socket, Timeout); @@ -261,7 +561,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket), %%-------------------------------------------------------------------- --spec handshake_continue(#sslsocket{}, [ssl_option()]) -> +-spec handshake_continue(#sslsocket{}, [tls_client_option() | tls_server_option()]) -> {ok, #sslsocket{}} | {error, reason()}. %% %% @@ -270,7 +570,7 @@ handshake(Socket, SslOptions, Timeout) when is_port(Socket), handshake_continue(Socket, SSLOptions) -> handshake_continue(Socket, SSLOptions, infinity). %%-------------------------------------------------------------------- --spec handshake_continue(#sslsocket{}, [ssl_option()], timeout()) -> +-spec handshake_continue(#sslsocket{}, [tls_client_option() | tls_server_option()], timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% @@ -322,7 +622,7 @@ close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _} send(#sslsocket{pid = [Pid]}, Data) when is_pid(Pid) -> ssl_connection:send(Pid, Data); send(#sslsocket{pid = [_, Pid]}, Data) when is_pid(Pid) -> - tls_sender:send_data(Pid, erlang:iolist_to_binary(Data)); + tls_sender:send_data(Pid, erlang:iolist_to_iovec(Data)); send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) -> {error,enotconn}; %% Emulate connection behaviour send(#sslsocket{pid = {dtls,_}}, _) -> @@ -331,13 +631,14 @@ send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _} Transport:send(ListenSocket, Data). %% {error,enotconn} %%-------------------------------------------------------------------- --spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}. --spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}. %% %% Description: Receives data when active = false %%-------------------------------------------------------------------- +-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}. recv(Socket, Length) -> recv(Socket, Length, infinity). + +-spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}. 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); @@ -440,13 +741,13 @@ negotiated_protocol(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) -> ssl_connection:negotiated_protocol(Pid). %%-------------------------------------------------------------------- --spec cipher_suites() -> [ssl_cipher_format:old_erl_cipher_suite()] | [string()]. +-spec cipher_suites() -> [old_cipher_suite()] | [string()]. %%-------------------------------------------------------------------- cipher_suites() -> cipher_suites(erlang). %%-------------------------------------------------------------------- -spec cipher_suites(erlang | openssl | all) -> - [ssl_cipher_format:old_erl_cipher_suite() | string()]. + [old_cipher_suite() | string()]. %% Description: Returns all supported cipher suites. %%-------------------------------------------------------------------- cipher_suites(erlang) -> @@ -460,9 +761,9 @@ cipher_suites(all) -> [ssl_cipher_format:erl_suite_definition(Suite) || Suite <- available_suites(all)]. %%-------------------------------------------------------------------- --spec cipher_suites(default | all | anonymous, tls_record:tls_version() | dtls_record:dtls_version() | +-spec cipher_suites(default | all | anonymous, ssl_record:ssl_version() | tls_record:tls_atom_version() | dtls_record:dtls_atom_version()) -> - [ssl_cipher_format:erl_cipher_suite()]. + [erl_cipher_suite()]. %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- @@ -478,9 +779,9 @@ cipher_suites(Base, Version) -> [ssl_cipher_format:suite_definition(Suite) || Suite <- supported_suites(Base, Version)]. %%-------------------------------------------------------------------- --spec filter_cipher_suites([ssl_cipher_format:erl_cipher_suite()], +-spec filter_cipher_suites([erl_cipher_suite()], [{key_exchange | cipher | mac | prf, fun()}] | []) -> - [ssl_cipher_format:erl_cipher_suite()]. + [erl_cipher_suite()]. %% Description: Removes cipher suites if any of the filter functions returns false %% for any part of the cipher suite. This function also calls default filter functions %% to make sure the cipher suite are supported by crypto. @@ -497,10 +798,10 @@ filter_cipher_suites(Suites, Filters0) -> prf_filters => add_filter(proplists:get_value(prf, Filters0), PrfF)}, ssl_cipher:filter_suites(Suites, Filters). %%-------------------------------------------------------------------- --spec prepend_cipher_suites([ssl_cipher_format:erl_cipher_suite()] | +-spec prepend_cipher_suites([erl_cipher_suite()] | [{key_exchange | cipher | mac | prf, fun()}], - [ssl_cipher_format:erl_cipher_suite()]) -> - [ssl_cipher_format:erl_cipher_suite()]. + [erl_cipher_suite()]) -> + [erl_cipher_suite()]. %% Description: Make <Preferred> suites become the most prefered %% suites that is put them at the head of the cipher suite list %% and remove them from <Suites> if present. <Preferred> may be a @@ -515,10 +816,10 @@ prepend_cipher_suites(Filters, Suites) -> Preferred = filter_cipher_suites(Suites, Filters), Preferred ++ (Suites -- Preferred). %%-------------------------------------------------------------------- --spec append_cipher_suites(Deferred :: [ssl_cipher_format:erl_cipher_suite()] | +-spec append_cipher_suites(Deferred :: [erl_cipher_suite()] | [{key_exchange | cipher | mac | prf, fun()}], - [ssl_cipher_format:erl_cipher_suite()]) -> - [ssl_cipher_format:erl_cipher_suite()]. + [erl_cipher_suite()]) -> + [erl_cipher_suite()]. %% Description: Make <Deferred> suites suites become the %% least prefered suites that is put them at the end of the cipher suite list %% and removed them from <Suites> if present. @@ -540,8 +841,8 @@ eccs() -> eccs_filter_supported(Curves). %%-------------------------------------------------------------------- --spec eccs(tls_record:tls_version() | tls_record:tls_atom_version() | - dtls_record:dtls_version() | dtls_record:dtls_atom_version()) -> +-spec eccs(tls_record:tls_atom_version() | + ssl_record:ssl_version() | dtls_record:dtls_atom_version()) -> tls_v1:curves(). %% Description: returns the curves supported for a given version of %% ssl/tls. @@ -723,7 +1024,7 @@ versions() -> SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns], AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS, AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, - [{ssl_app, ?VSN}, {supported, SupportedTLSVsns}, + [{ssl_app, "9.2"}, {supported, SupportedTLSVsns}, {supported_dtls, SupportedDTLSVsns}, {available, AvailableTLSVsns}, {available_dtls, AvailableDTLSVsns}]. @@ -783,8 +1084,8 @@ format_error(Reason) when is_list(Reason) -> Reason; format_error(closed) -> "TLS connection is closed"; -format_error({tls_alert, Description}) -> - "TLS Alert: " ++ Description; +format_error({tls_alert, {_, Description}}) -> + Description; format_error({options,{FileType, File, Reason}}) when FileType == cacertfile; FileType == certfile; FileType == keyfile; @@ -813,7 +1114,7 @@ tls_version({254, _} = Version) -> %%-------------------------------------------------------------------- --spec suite_to_str(ssl_cipher_format:erl_cipher_suite()) -> string(). +-spec suite_to_str(erl_cipher_suite()) -> string(). %% %% Description: Return the string representation of a cipher suite. %%-------------------------------------------------------------------- @@ -891,15 +1192,12 @@ handle_options(Opts0, Role, Host) -> {list, [{mode, list}]}], Opts0), assert_proplist(Opts), RecordCb = record_cb(Opts), - - ReuseSessionFun = fun(_, _, _, _) -> true end, CaCerts = handle_option(cacerts, Opts, undefined), {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} = handle_verify_options(Opts, CaCerts), CertFile = handle_option(certfile, Opts, <<>>), - RecordCb = record_cb(Opts), Versions = case handle_option(versions, Opts, []) of [] -> @@ -945,9 +1243,8 @@ handle_options(Opts0, Role, Host) -> default_option_role(server, tls_v1:default_signature_algs(Versions), Role)), tls_version(RecordCb:highest_protocol_version(Versions))), - %% Server side option - reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), - reuse_sessions = handle_option(reuse_sessions, Opts, true), + reuse_sessions = handle_reuse_sessions_option(reuse_sessions, Opts, Role), + reuse_session = handle_reuse_session_option(reuse_session, Opts, Role), secure_renegotiate = handle_option(secure_renegotiate, Opts, true), client_renegotiation = handle_option(client_renegotiation, Opts, default_option_role(server, true, Role), @@ -1003,8 +1300,8 @@ handle_options(Opts0, Role, Host) -> alpn_preferred_protocols, next_protocols_advertised, client_preferred_next_protocols, log_alert, server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache, - fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, - max_handshake_size, handshake, customize_hostname_check], + fallback, signature_algs, eccs, honor_ecc_order, + beast_mitigation, max_handshake_size, handshake, customize_hostname_check], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) end, Opts, SslOptions), @@ -1138,11 +1435,16 @@ validate_option(srp_identity, {Username, Password}) {unicode:characters_to_binary(Username), unicode:characters_to_binary(Password)}; +validate_option(reuse_session, undefined) -> + undefined; validate_option(reuse_session, Value) when is_function(Value) -> Value; +validate_option(reuse_session, Value) when is_binary(Value) -> + Value; validate_option(reuse_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(client_renegotiation, Value) when is_boolean(Value) -> @@ -1265,6 +1567,26 @@ handle_hashsigns_option(_, Version) when Version >= {3, 3} -> handle_hashsigns_option(_, _Version) -> undefined. +handle_reuse_sessions_option(Key, Opts, client) -> + Value = proplists:get_value(Key, Opts, true), + validate_option(Key, Value), + Value; +handle_reuse_sessions_option(Key, Opts0, server) -> + Opts = proplists:delete({Key, save}, Opts0), + Value = proplists:get_value(Key, Opts, true), + validate_option(Key, Value), + Value. + +handle_reuse_session_option(Key, Opts, client) -> + Value = proplists:get_value(Key, Opts, undefined), + validate_option(Key, Value), + Value; +handle_reuse_session_option(Key, Opts, server) -> + ReuseSessionFun = fun(_, _, _, _) -> true end, + Value = proplists:get_value(Key, Opts, ReuseSessionFun), + validate_option(Key, Value), + Value. + validate_options([]) -> []; validate_options([{Opt, Value} | Tail]) -> diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 34e9797f1f..2a20d13cd5 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -48,8 +48,8 @@ decode(Bin) -> decode(Bin, [], 0). %%-------------------------------------------------------------------- --spec reason_code(#alert{}, client | server) -> - closed | {tls_alert, unicode:chardata()}. +%% -spec reason_code(#alert{}, client | server) -> +%% {tls_alert, unicode:chardata()} | closed. %-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}. %% %% Description: Returns the error reason that will be returned to the @@ -58,8 +58,10 @@ decode(Bin) -> reason_code(#alert{description = ?CLOSE_NOTIFY}, _) -> closed; -reason_code(#alert{description = Description}, _) -> - {tls_alert, string:casefold(description_txt(Description))}. +reason_code(#alert{description = Description, role = Role} = Alert, Role) -> + {tls_alert, {description_atom(Description), own_alert_txt(Alert)}}; +reason_code(#alert{description = Description} = Alert, Role) -> + {tls_alert, {description_atom(Description), alert_txt(Alert#alert{role = Role})}}. %%-------------------------------------------------------------------- -spec own_alert_txt(#alert{}) -> string(). @@ -181,3 +183,70 @@ description_txt(?NO_APPLICATION_PROTOCOL) -> "No application protocol"; description_txt(Enum) -> lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])). + +description_atom(?CLOSE_NOTIFY) -> + close_notify; +description_atom(?UNEXPECTED_MESSAGE) -> + unexpected_message; +description_atom(?BAD_RECORD_MAC) -> + bad_record_mac; +description_atom(?DECRYPTION_FAILED_RESERVED) -> + decryption_failed_reserved; +description_atom(?RECORD_OVERFLOW) -> + record_overflow; +description_atom(?DECOMPRESSION_FAILURE) -> + decompression_failure; +description_atom(?HANDSHAKE_FAILURE) -> + handshake_failure; +description_atom(?NO_CERTIFICATE_RESERVED) -> + no_certificate_reserved; +description_atom(?BAD_CERTIFICATE) -> + bad_certificate; +description_atom(?UNSUPPORTED_CERTIFICATE) -> + unsupported_certificate; +description_atom(?CERTIFICATE_REVOKED) -> + certificate_revoked; +description_atom(?CERTIFICATE_EXPIRED) -> + certificate_expired; +description_atom(?CERTIFICATE_UNKNOWN) -> + certificate_unknown; +description_atom(?ILLEGAL_PARAMETER) -> + illegal_parameter; +description_atom(?UNKNOWN_CA) -> + unknown_ca; +description_atom(?ACCESS_DENIED) -> + access_denied; +description_atom(?DECODE_ERROR) -> + decode_error; +description_atom(?DECRYPT_ERROR) -> + decrypt_error; +description_atom(?EXPORT_RESTRICTION) -> + export_restriction; +description_atom(?PROTOCOL_VERSION) -> + protocol_version; +description_atom(?INSUFFICIENT_SECURITY) -> + insufficient_security; +description_atom(?INTERNAL_ERROR) -> + internal_error; +description_atom(?USER_CANCELED) -> + user_canceled; +description_atom(?NO_RENEGOTIATION) -> + no_renegotiation; +description_atom(?UNSUPPORTED_EXTENSION) -> + unsupported_extension; +description_atom(?CERTIFICATE_UNOBTAINABLE) -> + certificate_unobtainable; +description_atom(?UNRECOGNISED_NAME) -> + unrecognised_name; +description_atom(?BAD_CERTIFICATE_STATUS_RESPONSE) -> + bad_certificate_status_response; +description_atom(?BAD_CERTIFICATE_HASH_VALUE) -> + bad_certificate_hash_value; +description_atom(?UNKNOWN_PSK_IDENTITY) -> + unknown_psk_identity; +description_atom(?INAPPROPRIATE_FALLBACK) -> + inappropriate_fallback; +description_atom(?NO_APPLICATION_PROTOCOL) -> + no_application_protocol; +description_atom(_) -> + 'unsupported/unkonwn_alert'. diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl index 7b7b1cbcd9..f4594912bd 100644 --- a/lib/ssl/src/ssl_api.hrl +++ b/lib/ssl/src/ssl_api.hrl @@ -21,56 +21,7 @@ -ifndef(ssl_api). -define(ssl_api, true). --include("ssl_cipher.hrl"). - -%% Visible in API --export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0, - prf_random/0, sslsocket/0]). - - %% Looks like it does for backwards compatibility reasons -record(sslsocket, {fd = nil, pid = nil}). - --type sslsocket() :: #sslsocket{}. --type connect_option() :: socket_connect_option() | ssl_option() | transport_option(). --type socket_connect_option() :: gen_tcp:connect_option(). --type listen_option() :: socket_listen_option() | ssl_option() | transport_option(). --type socket_listen_option() :: gen_tcp:listen_option(). - --type ssl_option() :: {versions, ssl_record:ssl_atom_version()} | - {verify, verify_type()} | - {verify_fun, {fun(), InitialUserState::term()}} | - {fail_if_no_peer_cert, boolean()} | {depth, integer()} | - {cert, Der::binary()} | {certfile, path()} | - {key, {private_key_type(), Der::binary()}} | - {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | - {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | - {user_lookup_fun, {fun(), InitialUserState::term()}} | - {psk_identity, string()} | - {srp_identity, {string(), string()}} | - {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | - {reuse_session, fun()} | {hibernate_after, integer()|undefined} | - {alpn_advertised_protocols, [binary()]} | - {alpn_preferred_protocols, [binary()]} | - {next_protocols_advertised, list(binary())} | - {client_preferred_next_protocols, binary(), client | server, list(binary())}. - --type verify_type() :: verify_none | verify_peer. --type path() :: string(). --type ciphers() :: [ssl_cipher_format:erl_cipher_suite()] | - string(). % (according to old API) --type ssl_imp() :: new | old. - --type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), - ClosedTag::atom(), ErrTag::atom()}}. --type prf_random() :: client_random | server_random. - --type private_key_type() :: rsa | %% Backwards compatibility - dsa | %% Backwards compatibility - 'RSAPrivateKey' | - 'DSAPrivateKey' | - 'ECPrivateKey' | - 'PrivateKeyInfo'. - -endif. % -ifdef(ssl_api). diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 66a00c60f1..fce48d1678 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,7 +1,7 @@ % %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ rc4_suites/1, des_suites/1, rsa_suites/1, filter/3, filter_suites/1, filter_suites/2, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, - random_bytes/1, calc_mac_hash/4, + random_bytes/1, calc_mac_hash/4, calc_mac_hash/6, is_stream_ciphersuite/1]). -compile(inline). @@ -97,7 +97,8 @@ cipher_init(?AES_GCM, IV, Key) -> cipher_init(?CHACHA20_POLY1305, IV, Key) -> #cipher_state{iv = IV, key = Key, tag_len = 16}; cipher_init(_BCA, IV, Key) -> - #cipher_state{iv = IV, key = Key}. + %% Initialize random IV cache, not used for aead ciphers + #cipher_state{iv = IV, key = Key, state = <<>>}. nonce_seed(Seed, CipherState) -> CipherState#cipher_state{nonce = Seed}. @@ -112,12 +113,11 @@ nonce_seed(Seed, CipherState) -> %% data is calculated and the data plus the HMAC is ecncrypted. %%------------------------------------------------------------------- cipher(?NULL, CipherState, <<>>, Fragment, _Version) -> - GenStreamCipherList = [Fragment, <<>>], - {GenStreamCipherList, CipherState}; + {iolist_to_binary(Fragment), CipherState}; cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) -> GenStreamCipherList = [Fragment, Mac], {State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList), - {T, CipherState#cipher_state{state = State1}}; + {iolist_to_binary(T), CipherState#cipher_state{state = State1}}; cipher(?DES, CipherState, Mac, Fragment, Version) -> block_cipher(fun(Key, IV, T) -> crypto:block_encrypt(des_cbc, Key, IV, T) @@ -146,8 +146,7 @@ aead_type(?CHACHA20_POLY1305) -> build_cipher_block(BlockSz, Mac, Fragment) -> TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1, - {PaddingLength, Padding} = get_padding(TotSz, BlockSz), - [Fragment, Mac, PaddingLength, Padding]. + [Fragment, Mac, padding_with_len(TotSz, BlockSz)]. block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, Mac, Fragment, {3, N}) @@ -157,14 +156,21 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, NextIV = next_iv(T, IV), {T, CS0#cipher_state{iv=NextIV}}; -block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, +block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV, state = IV_Cache0} = CS0, Mac, Fragment, {3, N}) when N == 2; N == 3 -> - NextIV = random_iv(IV), + IV_Size = byte_size(IV), + <<NextIV:IV_Size/binary, IV_Cache/binary>> = + case IV_Cache0 of + <<>> -> + random_bytes(IV_Size bsl 5); % 32 IVs + _ -> + IV_Cache0 + end, L0 = build_cipher_block(BlockSz, Mac, Fragment), L = [NextIV|L0], T = Fun(Key, IV, L), - {T, CS0#cipher_state{iv=NextIV}}. + {T, CS0#cipher_state{iv=NextIV, state = IV_Cache}}. %%-------------------------------------------------------------------- -spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(), @@ -481,8 +487,8 @@ filter(DerCert, Ciphers0, Version) -> filter_suites_signature(Sign, Ciphers, Version). %%-------------------------------------------------------------------- --spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) -> - [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]. +-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()], map()) -> + [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]. %% %% Description: Filter suites using supplied filter funs %%------------------------------------------------------------------- @@ -508,8 +514,8 @@ filter_suite(Suite, Filters) -> filter_suite(ssl_cipher_format:suite_definition(Suite), Filters). %%-------------------------------------------------------------------- --spec filter_suites([ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) -> - [ssl_cipher_format:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]. +-spec filter_suites([ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]) -> + [ssl:erl_cipher_suite()] | [ssl_cipher_format:cipher_suite()]. %% %% Description: Filter suites for algorithms supported by crypto. %%------------------------------------------------------------------- @@ -593,7 +599,7 @@ is_acceptable_cipher(rc4_128, Algos) -> is_acceptable_cipher(des_cbc, Algos) -> proplists:get_bool(des_cbc, Algos); is_acceptable_cipher('3des_ede_cbc', Algos) -> - proplists:get_bool(des3_cbc, Algos); + proplists:get_bool(des_ede3, Algos); is_acceptable_cipher(aes_128_cbc, Algos) -> proplists:get_bool(aes_cbc128, Algos); is_acceptable_cipher(aes_256_cbc, Algos) -> @@ -633,12 +639,13 @@ random_bytes(N) -> calc_mac_hash(Type, Version, PlainFragment, #{sequence_number := SeqNo, mac_secret := MacSecret, - security_parameters:= - SecPars}) -> + security_parameters := + #security_parameters{mac_algorithm = MacAlgorithm}}) -> + calc_mac_hash(Type, Version, PlainFragment, MacAlgorithm, MacSecret, SeqNo). +%% +calc_mac_hash(Type, Version, PlainFragment, MacAlgorithm, MacSecret, SeqNo) -> Length = erlang:iolist_size(PlainFragment), - mac_hash(Version, SecPars#security_parameters.mac_algorithm, - MacSecret, SeqNo, Type, - Length, PlainFragment). + mac_hash(Version, MacAlgorithm, MacSecret, SeqNo, Type, Length, PlainFragment). is_stream_ciphersuite(#{cipher := rc4_128}) -> true; @@ -722,7 +729,6 @@ expanded_key_material(Cipher) when Cipher == aes_128_cbc; Cipher == chacha20_poly1305 -> unknown. - effective_key_bits(null) -> 0; effective_key_bits(des_cbc) -> @@ -742,18 +748,15 @@ iv_size(Cipher) when Cipher == null; Cipher == rc4_128; Cipher == chacha20_poly1305-> 0; - iv_size(Cipher) when Cipher == aes_128_gcm; Cipher == aes_256_gcm -> 4; - iv_size(Cipher) -> block_size(Cipher). block_size(Cipher) when Cipher == des_cbc; Cipher == '3des_ede_cbc' -> 8; - block_size(Cipher) when Cipher == aes_128_cbc; Cipher == aes_256_cbc; Cipher == aes_128_gcm; @@ -888,21 +891,51 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) -> %% Padding must be checked in TLS 1.1 and after is_correct_padding(#generic_block_cipher{padding_length = Len, padding = Padding}, _, _) -> - Len == byte_size(Padding) andalso - binary:copy(?byte(Len), Len) == Padding. - -get_padding(Length, BlockSize) -> - get_padding_aux(BlockSize, Length rem BlockSize). - -get_padding_aux(_, 0) -> - {0, <<>>}; -get_padding_aux(BlockSize, PadLength) -> - N = BlockSize - PadLength, - {N, binary:copy(?byte(N), N)}. + (Len == byte_size(Padding)) andalso (padding(Len) == Padding). + +padding(PadLen) -> + case PadLen of + 0 -> <<>>; + 1 -> <<1>>; + 2 -> <<2,2>>; + 3 -> <<3,3,3>>; + 4 -> <<4,4,4,4>>; + 5 -> <<5,5,5,5,5>>; + 6 -> <<6,6,6,6,6,6>>; + 7 -> <<7,7,7,7,7,7,7>>; + 8 -> <<8,8,8,8,8,8,8,8>>; + 9 -> <<9,9,9,9,9,9,9,9,9>>; + 10 -> <<10,10,10,10,10,10,10,10,10,10>>; + 11 -> <<11,11,11,11,11,11,11,11,11,11,11>>; + 12 -> <<12,12,12,12,12,12,12,12,12,12,12,12>>; + 13 -> <<13,13,13,13,13,13,13,13,13,13,13,13,13>>; + 14 -> <<14,14,14,14,14,14,14,14,14,14,14,14,14,14>>; + 15 -> <<15,15,15,15,15,15,15,15,15,15,15,15,15,15,15>>; + _ -> + binary:copy(<<PadLen>>, PadLen) + end. -random_iv(IV) -> - IVSz = byte_size(IV), - random_bytes(IVSz). +padding_with_len(TextLen, BlockSize) -> + case BlockSize - (TextLen rem BlockSize) of + 0 -> <<0>>; + 1 -> <<1,1>>; + 2 -> <<2,2,2>>; + 3 -> <<3,3,3,3>>; + 4 -> <<4,4,4,4,4>>; + 5 -> <<5,5,5,5,5,5>>; + 6 -> <<6,6,6,6,6,6,6>>; + 7 -> <<7,7,7,7,7,7,7,7>>; + 8 -> <<8,8,8,8,8,8,8,8,8>>; + 9 -> <<9,9,9,9,9,9,9,9,9,9>>; + 10 -> <<10,10,10,10,10,10,10,10,10,10,10>>; + 11 -> <<11,11,11,11,11,11,11,11,11,11,11,11>>; + 12 -> <<12,12,12,12,12,12,12,12,12,12,12,12,12>>; + 13 -> <<13,13,13,13,13,13,13,13,13,13,13,13,13,13>>; + 14 -> <<14,14,14,14,14,14,14,14,14,14,14,14,14,14,14>>; + 15 -> <<15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15>>; + PadLen -> + binary:copy(<<PadLen>>, PadLen + 1) + end. next_iv(Bin, IV) -> BinSz = byte_size(Bin), diff --git a/lib/ssl/src/ssl_cipher_format.erl b/lib/ssl/src/ssl_cipher_format.erl index c311c0d097..1d28e1e3b4 100644 --- a/lib/ssl/src/ssl_cipher_format.erl +++ b/lib/ssl/src/ssl_cipher_format.erl @@ -25,26 +25,25 @@ %%---------------------------------------------------------------------- -module(ssl_cipher_format). +-include("ssl_api.hrl"). -include("ssl_cipher.hrl"). -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export_type([cipher_suite/0, - erl_cipher_suite/0, old_erl_cipher_suite/0, openssl_cipher_suite/0, - hash/0, key_algo/0, sign_algo/0]). +-export_type([old_erl_cipher_suite/0, openssl_cipher_suite/0, cipher_suite/0]). --type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc' | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305. --type hash() :: null | md5 | sha | sha224 | sha256 | sha384 | sha512. --type sign_algo() :: rsa | dsa | ecdsa. --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. --type erl_cipher_suite() :: #{key_exchange := key_algo(), - cipher := cipher(), - mac := hash() | aead, - prf := hash() | default_prf %% Old cipher suites, version dependent +-type internal_cipher() :: null | ssl:cipher(). +-type internal_hash() :: null | ssl:hash(). +-type internal_kex_algo() :: null | ssl:kex_algo(). +-type internal_erl_cipher_suite() :: #{key_exchange := internal_kex_algo(), + cipher := internal_cipher(), + mac := internal_hash() | aead, + prf := internal_hash() | default_prf %% Old cipher suites, version dependent }. --type old_erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2 +-type old_erl_cipher_suite() :: {ssl:kex_algo(), internal_cipher(), internal_hash()} % Pre TLS 1.2 %% TLS 1.2, internally PRE TLS 1.2 will use default_prf - | {key_algo(), cipher(), hash(), hash() | default_prf}. + | {ssl:kex_algo(), internal_cipher(), internal_hash(), + internal_hash() | default_prf}. -type cipher_suite() :: binary(). -type openssl_cipher_suite() :: string(). @@ -53,7 +52,7 @@ openssl_suite/1, openssl_suite_name/1]). %%-------------------------------------------------------------------- --spec suite_to_str(erl_cipher_suite()) -> string(). +-spec suite_to_str(internal_erl_cipher_suite()) -> string(). %% %% Description: Return the string representation of a cipher suite. %%-------------------------------------------------------------------- @@ -77,7 +76,7 @@ suite_to_str(#{key_exchange := Kex, "_" ++ string:to_upper(atom_to_list(Mac)). %%-------------------------------------------------------------------- --spec suite_definition(cipher_suite()) -> erl_cipher_suite(). +-spec suite_definition(cipher_suite()) -> internal_erl_cipher_suite(). %% %% Description: Return erlang cipher suite definition. %% Note: Currently not supported suites are commented away. @@ -805,7 +804,7 @@ suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) -> prf => sha256}. %%-------------------------------------------------------------------- --spec erl_suite_definition(cipher_suite() | erl_cipher_suite()) -> old_erl_cipher_suite(). +-spec erl_suite_definition(cipher_suite() | internal_erl_cipher_suite()) -> old_erl_cipher_suite(). %% %% Description: Return erlang cipher suite definition. Filters last value %% for now (compatibility reasons). @@ -822,7 +821,7 @@ erl_suite_definition(#{key_exchange := KeyExchange, cipher := Cipher, end. %%-------------------------------------------------------------------- --spec suite(erl_cipher_suite()) -> cipher_suite(). +-spec suite(internal_erl_cipher_suite()) -> cipher_suite(). %% %% Description: Return TLS cipher suite definition. %%-------------------------------------------------------------------- @@ -1585,7 +1584,7 @@ openssl_suite("ECDH-RSA-AES256-GCM-SHA384") -> ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384. %%-------------------------------------------------------------------- --spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | erl_cipher_suite(). +-spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite() | internal_erl_cipher_suite(). %% %% Description: Return openssl cipher suite name if possible %%------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 0e2a677273..ad81288f64 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2018. All Rights Reserved. +%% Copyright Ericsson AB 2013-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ -export([connect/8, handshake/7, handshake/2, handshake/3, handshake_continue/3, handshake_cancel/1, - socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]). + socket_control/4, socket_control/5]). %% User Events -export([send/2, recv/3, close/2, shutdown/2, @@ -70,14 +70,14 @@ -export([terminate/3, format_status/2]). %% Erlang Distribution export --export([get_sslsocket/1, dist_handshake_complete/2]). +-export([dist_handshake_complete/2]). %%==================================================================== %% Setup %%==================================================================== %%-------------------------------------------------------------------- -spec connect(tls_connection | dtls_connection, - host(), inet:port_number(), + ssl:host(), inet:port_number(), port() | {tuple(), port()}, %% TLS | DTLS {#ssl_options{}, #socket_options{}, %% Tracker only needed on server side @@ -143,7 +143,7 @@ handshake(#sslsocket{pid = [Pid|_]} = Socket, SslOptions, Timeout) -> end. %%-------------------------------------------------------------------- --spec handshake_continue(#sslsocket{}, [ssl_option()], +-spec handshake_continue(#sslsocket{}, [ssl:tls_server_option()], timeout()) -> {ok, #sslsocket{}}| {error, reason()}. %% %% Description: Continues handshake with new options @@ -182,27 +182,23 @@ socket_control(Connection, Socket, Pid, Transport) -> %%-------------------------------------------------------------------- socket_control(Connection, Socket, Pids, Transport, udp_listener) -> %% dtls listener process must have the socket control - {ok, Connection:socket(Pids, Transport, Socket, Connection, undefined)}; + {ok, Connection:socket(Pids, Transport, Socket, undefined)}; socket_control(tls_connection = Connection, Socket, [Pid|_] = Pids, Transport, ListenTracker) -> case Transport:controlling_process(Socket, Pid) of ok -> - {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)}; + {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)}; {error, Reason} -> {error, Reason} end; socket_control(dtls_connection = Connection, {_, Socket}, [Pid|_] = Pids, Transport, ListenTracker) -> case Transport:controlling_process(Socket, Pid) of ok -> - {ok, Connection:socket(Pids, Transport, Socket, Connection, ListenTracker)}; + {ok, Connection:socket(Pids, Transport, Socket, ListenTracker)}; {error, Reason} -> {error, Reason} end. -start_or_recv_cancel_timer(infinity, _RecvFrom) -> - undefined; -start_or_recv_cancel_timer(Timeout, RecvFrom) -> - erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). %%==================================================================== %% User events @@ -215,9 +211,9 @@ start_or_recv_cancel_timer(Timeout, RecvFrom) -> %%-------------------------------------------------------------------- send(Pid, Data) -> call(Pid, {application_data, - %% iolist_to_binary should really - %% be called iodata_to_binary() - erlang:iolist_to_binary(Data)}). + %% iolist_to_iovec should really + %% be called iodata_to_iovec() + erlang:iolist_to_iovec(Data)}). %%-------------------------------------------------------------------- -spec recv(pid(), integer(), timeout()) -> @@ -315,9 +311,6 @@ renegotiation(ConnectionPid) -> internal_renegotiation(ConnectionPid, #{current_write := WriteState}) -> gen_statem:cast(ConnectionPid, {internal_renegotiate, WriteState}). -get_sslsocket(ConnectionPid) -> - call(ConnectionPid, get_sslsocket). - dist_handshake_complete(ConnectionPid, DHandle) -> gen_statem:cast(ConnectionPid, {dist_handshake_complete, DHandle}). @@ -356,8 +349,8 @@ handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role, transport_cb = Transport, protocol_cb = Connection, tracker = Tracker}, - start_or_recv_from = StartFrom, - renegotiation = {false, first}} = State) -> + handshake_env = #handshake_env{renegotiation = {false, first}}, + start_or_recv_from = StartFrom} = State) -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); @@ -366,8 +359,8 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = transport_cb = Transport, protocol_cb = Connection, tracker = Tracker}, - socket_options = Opts, - user_application = {_Mon, Pid}, + connection_env = #connection_env{user_application = {_Mon, Pid}}, + socket_options = Opts, start_or_recv_from = RecvFrom} = State) -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). @@ -380,9 +373,10 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName, tracker = Tracker, transport_cb = Transport, protocol_cb = Connection}, + connection_env = #connection_env{user_application = {_Mon, Pid}}, ssl_options = SslOpts, start_or_recv_from = From, - session = Session, user_application = {_Mon, Pid}, + session = Session, socket_options = Opts} = State) -> invalidate_session(Role, Host, Port, Session), log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), @@ -401,8 +395,8 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, #state{static_env = #static_env{role = Role, protocol_cb = Connection}, - ssl_options = SslOpts, - renegotiation = {true, internal}} = State) -> + handshake_env = #handshake_env{renegotiation = {true, internal}}, + ssl_options = SslOpts} = State) -> log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), handle_normal_shutdown(Alert, StateName, State), @@ -411,26 +405,26 @@ handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, #state{static_env = #static_env{role = Role, protocol_cb = Connection}, - ssl_options = SslOpts, - renegotiation = {true, From} + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = SslOpts } = State0) -> log_alert(SslOpts#ssl_options.log_alert, 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{renegotiation = undefined}); + 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}, - ssl_options = SslOpts, - renegotiation = {true, From} + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = SslOpts } = State0) -> log_alert(SslOpts#ssl_options.log_alert, 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{renegotiation = undefined}), + 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 @@ -445,103 +439,251 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName, %%==================================================================== %% Data handling %%==================================================================== -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) -> - case Buffer of - <<>> -> +passive_receive(State0 = #state{user_data_buffer = {_,BufferSize,_}}, StateName, Connection, StartTimerAction) -> + case BufferSize of + 0 -> {Record, State} = Connection:next_record(State0), - Connection:next_event(StateName, Record, State); + Connection:next_event(StateName, Record, State, StartTimerAction); _ -> case read_application_data(<<>>, State0) of {stop, _, _} = ShutdownError -> ShutdownError; {Record, State} -> - Connection:next_event(StateName, 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{static_env = #static_env{socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - tracker = Tracker}, - user_application = {_Mon, Pid}, - socket_options = SOpts, - bytes_to_read = BytesToRead, - start_or_recv_from = RecvFrom, - timer = Timer, - user_data_buffer = Buffer0} = State0) -> - Buffer1 = if - Buffer0 =:= <<>> -> Data; - Data =:= <<>> -> Buffer0; - true -> <<Buffer0/binary, Data/binary>> - end, - case get_data(SOpts, BytesToRead, Buffer1) of - {ok, ClientData, Buffer} -> % Send data - #state{ssl_options = #ssl_options{erl_dist = Dist}, - erl_dist_data = DistData} = State0, - case Dist andalso is_dist_up(DistData) of - true -> - dist_app_data(ClientData, State0#state{user_data_buffer = Buffer, - bytes_to_read = undefined}); - _ -> - SocketOpt = - deliver_app_data(Connection:pids(State0), - Transport, Socket, SOpts, - ClientData, Pid, RecvFrom, Tracker, Connection), - cancel_timer(Timer), - State = - State0#state{ - user_data_buffer = Buffer, - start_or_recv_from = undefined, - timer = undefined, - bytes_to_read = undefined, - socket_options = SocketOpt - }, - if - SocketOpt#socket_options.active =:= false; - Buffer =:= <<>> -> - %% Passive mode, wait for active once or recv - %% Active and empty, get more data - {no_record, State}; - true -> %% We have more data - read_application_data(<<>>, State) - end - end; - {more, Buffer} -> % no reply, we need more data - {no_record, State0#state{user_data_buffer = Buffer}}; - {passive, Buffer} -> - {no_record, State0#state{user_data_buffer = Buffer}}; - {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Connection:pids(State0), - Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection), - {stop, {shutdown, normal}, State0} +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. -dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := undefined, - dist_buffer := DistBuff} = DistData} = State) -> - {no_record, State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}}; -dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle, - dist_buffer := DistBuff} = ErlDistData, - user_data_buffer = Buffer, - socket_options = SOpts} = State) -> - Data = merge_dist_data(DistBuff, ClientData), - try erlang:dist_ctrl_put_data(DHandle, Data) of - _ when SOpts#socket_options.active =:= false; - Buffer =:= <<>> -> + +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, + tracker = Tracker}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}} = State, + Buffer = iolist_to_binary([Bin0,Front0|lists:reverse(Rear0)]), + deliver_packet_error( + Connection:pids(State), Transport, Socket, SocketOpts0, + Buffer, Pid, RecvFrom, Tracker, Connection), + {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, + tracker = Tracker}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}} = State, + SocketOpts = + deliver_app_data( + Connection:pids(State), Transport, Socket, SocketOpts0, Data, Pid, RecvFrom, Tracker, Connection), + if + SocketOpts#socket_options.active =:= false -> %% Passive mode, wait for active once or recv - %% Active and empty, get more data - {no_record, State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}}; - _ -> %% We have more data - read_application_data(<<>>, State) - catch error:_ -> - {stop, State, disconnect} + {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>> -> + %% 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>> -> + %% 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>> -> + %% 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 + 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]), + 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} = + iovec_from_front(4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]), + 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 + {[IncompleteLengthField|Front0],BufferSize,Rear0} + end end. -merge_dist_data(<<>>, ClientData) -> - ClientData; -merge_dist_data(DistBuff, <<>>) -> - DistBuff; -merge_dist_data(DistBuff, ClientData) -> - [DistBuff, ClientData]. +iovec_from_front(Size, [], Rear, Acc) -> + iovec_from_front(Size, lists:reverse(Rear), [], Acc); +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}; + <<_/binary>> -> % Not enough + BinSize = byte_size(Bin), + iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) + end. + + %%==================================================================== %% Help functions for tls|dtls_connection.erl %%==================================================================== @@ -554,8 +696,8 @@ handle_session(#server_hello{cipher_suite = CipherSuite, compression_method = Compression}, Version, NewId, ConnectionStates, ProtoExt, Protocol0, #state{session = #session{session_id = OldId}, - negotiated_version = ReqVersion, - negotiated_protocol = CurrentProtocol} = State0) -> + handshake_env = #handshake_env{negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv} = State0) -> #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), @@ -568,12 +710,12 @@ handle_session(#server_hello{cipher_suite = CipherSuite, {ProtoExt =:= npn, Protocol0} end, - State = State0#state{key_algorithm = KeyAlgorithm, - negotiated_version = Version, - connection_states = ConnectionStates, - premaster_secret = PremasterSecret, - expecting_next_protocol_negotiation = ExpectNPN, - negotiated_protocol = Protocol}, + 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 -> @@ -587,10 +729,9 @@ handle_session(#server_hello{cipher_suite = CipherSuite, %%-------------------------------------------------------------------- -spec ssl_config(#ssl_options{}, client | server, #state{}) -> #state{}. %%-------------------------------------------------------------------- -ssl_config(Opts, Role, State) -> - ssl_config(Opts, Role, State, new). - -ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) -> +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, @@ -602,27 +743,19 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) -> ssl_config:init(Opts, Role), TimeStamp = erlang:monotonic_time(), Session = State0#state.session, - - State = 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 - }, - private_key = Key, - diffie_hellman_params = DHParams, - ssl_options = Opts}, - case Type of - new -> - Handshake = ssl_handshake:init_handshake_history(), - State#state{tls_handshake_history = Handshake}; - continue -> - State - end. - + + 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 @@ -635,8 +768,8 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0} =State0, Type) -> %%-------------------------------------------------------------------- init({call, From}, {start, Timeout}, State0, Connection) -> - Timer = start_or_recv_cancel_timer(Timeout, From), - Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer}); + 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, @@ -683,21 +816,18 @@ hello(info, Msg, State, _) -> hello(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). -user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _) -> +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{hello = Hello, - static_env = #static_env{role = Role}, - start_or_recv_from = RecvFrom, + #state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{hello = Hello}, ssl_options = Options0} = State0, _Connection) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}), - State = ssl_config(Options, Role, State0, continue), - {next_state, hello, State#state{start_or_recv_from = From, - timer = Timer}, - [{next_event, internal, Hello}]}; + 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]}. @@ -711,41 +841,41 @@ 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}, - negotiated_version = Version, - expecting_finished = true, - tls_handshake_history = Handshake, + 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, Handshake) of + MasterSecret, Hist) of verified -> ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates, - expecting_finished = false}, Connection), - Connection:next_event(connection, Record, State); + 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}, - tls_handshake_history = Handshake0, + handshake_env = #handshake_env{tls_handshake_history = Hist0}, + connection_env = #connection_env{negotiated_version = Version}, session = #session{master_secret = MasterSecret}, - negotiated_version = Version, connection_states = ConnectionStates0} = State0, Connection) -> case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, get_pending_prf(ConnectionStates0, write), - MasterSecret, Handshake0) of + MasterSecret, Hist0) of verified -> ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), - {State1, Actions} = + {#state{handshake_env = HsEnv} = State1, Actions} = finalize_handshake(State0#state{connection_states = ConnectionStates1}, ?FUNCTION_NAME, Connection), - {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection), - Connection:next_event(connection, Record, State, Actions); + {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; @@ -753,19 +883,20 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, %% & before finished message and it is not allowed during renegotiation abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, #state{static_env = #static_env{role = server}, - expecting_next_protocol_negotiation = true} = State, + handshake_env = #handshake_env{expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) -> Connection:next_event(?FUNCTION_NAME, no_record, - State#state{negotiated_protocol = SelectedProtocol, - expecting_next_protocol_negotiation = false}); + 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} = State, Connection) -> + #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, - expecting_finished = true}); + handshake_env = HsEnv#handshake_env{expecting_finished = true}}); abbreviated(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); abbreviated(Type, Msg, State, Connection) -> @@ -784,7 +915,7 @@ certify(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, #state{static_env = #static_env{role = server}, - negotiated_version = Version, + connection_env = #connection_env{negotiated_version = Version}, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = State, _) -> @@ -798,7 +929,7 @@ certify(internal, #certificate{asn1_certificates = []}, Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); certify(internal, #certificate{}, #state{static_env = #static_env{role = server}, - negotiated_version = Version, + connection_env = #connection_env{negotiated_version = Version}, ssl_options = #ssl_options{verify = verify_none}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), @@ -810,7 +941,7 @@ certify(internal, #certificate{} = Cert, cert_db = CertDbHandle, cert_db_ref = CertDbRef, crl_db = CRLDbInfo}, - negotiated_version = Version, + connection_env = #connection_env{negotiated_version = Version}, ssl_options = Opts} = State, Connection) -> case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts, CRLDbInfo, Role, Host) of @@ -822,34 +953,42 @@ certify(internal, #certificate{} = Cert, end; certify(internal, #server_key_exchange{exchange_keys = Keys}, #state{static_env = #static_env{role = client}, - negotiated_version = Version, - key_algorithm = Alg, - public_key_info = PubKeyInfo, + 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 Alg == dhe_dss; Alg == dhe_rsa; - Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; - 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 -> - - Params = ssl_handshake:decode_server_key(Keys, Alg, ssl:tls_version(Version)), + 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, Alg, PubKeyInfo, ssl:tls_version(Version)), + HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KexAlg, PubKeyInfo, ssl:tls_version(Version)), - case is_anonymous(Alg) of + case is_anonymous(KexAlg) of true -> calculate_secret(Params#server_key_params.params, - State#state{hashsign_algorithm = HashSign}, Connection); + 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{hashsign_algorithm = HashSign, - session = session_handle_params(Params#server_key_params.params, Session)}, - Connection); + 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) @@ -857,11 +996,17 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, end; certify(internal, #certificate_request{}, #state{static_env = #static_env{role = client}, - negotiated_version = Version, - key_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 -> + 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{}, @@ -873,62 +1018,65 @@ certify(internal, #certificate_request{}, 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 = #ssl_options{signature_algs = SupportedHashSigns}, - negotiated_version = Version} = State, Connection) -> + ssl_options = #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, - cert_hashsign_algorithm = NegotiatedHashSign}) + 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}, - negotiated_version = Version, - psk_identity = PSKIdentity, - ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, - premaster_secret = undefined, - key_algorithm = Alg} = State0, Connection) - when Alg == psk -> - case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of + connection_env = #connection_env{negotiated_version = Version}, + handshake_env = #handshake_env{kex_algorithm = KexAlg, + premaster_secret = undefined, + server_psk_identity = PSKIdentity} = HsEnv, + ssl_options = #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{premaster_secret = PremasterSecret}), - client_certify_and_key_exchange(State, Connection) + 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 = #ssl_options{user_lookup_fun = PSKLookup}, - negotiated_version = {Major, Minor} = Version, - psk_identity = PSKIdentity, - premaster_secret = undefined, - key_algorithm = Alg} = State0, Connection) - when Alg == rsa_psk -> + ssl_options = #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({Alg, PSKIdentity}, PSKLookup, + 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{premaster_secret = RSAPremasterSecret}), + 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}, - session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = undefined} = State0, 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, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> @@ -940,10 +1088,10 @@ certify(internal, #server_hello_done{}, %% 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, - negotiated_version = Version, - premaster_secret = PremasterSecret} = State0, Connection) -> + connection_states = ConnectionStates0} = State0, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> @@ -962,7 +1110,8 @@ certify(internal = Type, #client_key_exchange{} = Msg, %% We expect a certificate here handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection); certify(internal, #client_key_exchange{exchange_keys = Keys}, - State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) -> + 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) @@ -986,50 +1135,51 @@ cipher(info, Msg, State, _) -> cipher(internal, #certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, #state{static_env = #static_env{role = server}, - key_algorithm = KexAlg, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - session = #session{master_secret = MasterSecret}, - tls_handshake_history = Handshake + 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, PublicKeyInfo, TLSVersion), - case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, - TLSVersion, HashSign, MasterSecret, Handshake) of + 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{cert_hashsign_algorithm = HashSign}); + 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}, - expecting_next_protocol_negotiation = true, - negotiated_protocol = undefined, negotiated_version = Version} = State0, + 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}, - negotiated_version = Version, - expecting_finished = true, + 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, - tls_handshake_history = Handshake0} = State, Connection) -> + connection_states = ConnectionStates0} = State, Connection) -> case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, opposite_role(Role), get_current_prf(ConnectionStates0, read), - MasterSecret, Handshake0) of + MasterSecret, Hist) of verified -> - Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0), + Session = handle_session(Role, SslOpts, Host, Port, Session0), cipher_role(Role, Data, Session, - State#state{expecting_finished = false}, Connection); + State#state{handshake_env = HsEnv#handshake_env{expecting_finished = false}}, Connection); #alert{} = Alert -> handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; @@ -1037,19 +1187,19 @@ cipher(internal, #finished{verify_data = Data} = Finished, %% & before finished message and it is not allowed during renegotiation cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, #state{static_env = #static_env{role = server}, - expecting_next_protocol_negotiation = true, - expecting_finished = true} = State0, Connection) -> + handshake_env = #handshake_env{expecting_finished = true, + expecting_next_protocol_negotiation = true} = HsEnv} = State0, Connection) -> {Record, State} = - Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), + Connection:next_record(State0), Connection:next_event(?FUNCTION_NAME, Record, - State#state{expecting_next_protocol_negotiation = false}); -cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = + 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{connection_states = - ConnectionStates, - expecting_finished = true}); + 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). @@ -1062,14 +1212,14 @@ connection({call, RecvFrom}, {recv, N, Timeout}, #state{static_env = #static_env{protocol_cb = Connection}, socket_options = #socket_options{active = false}} = State0, Connection) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom, - timer = Timer}, ?FUNCTION_NAME, Connection); + start_or_recv_from = RecvFrom}, ?FUNCTION_NAME, Connection, + [{{timeout, recv}, Timeout, timeout}]); -connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}} = State, +connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv} = State, Connection) -> - Connection:renegotiate(State#state{renegotiation = {true, From}}, []); + 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}}]); @@ -1080,35 +1230,36 @@ 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{negotiated_protocol = undefined} = State, _) -> + #state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); connection({call, From}, negotiated_protocol, - #state{negotiated_protocol = SelectedProtocol} = State, _) -> + #state{handshake_env = #handshake_env{negotiated_protocol = SelectedProtocol}} = 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{renegotiation = {true, internal}, + 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 = #ssl_options{erl_dist = true}, - erl_dist_data = ErlDistData, + connection_env = CEnv, socket_options = SockOpts} = State0, Connection) -> process_flag(priority, normal), State1 = State0#state{ - socket_options = - SockOpts#socket_options{active = true}, - erl_dist_data = ErlDistData#{dist_handle => DHandle}}, - {Record, State} = dist_app_data(<<>>, State1), + 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, _}, State, Connection) -> - passive_receive(State, ?FUNCTION_NAME, Connection); +connection(internal, {recv, Timeout}, State, Connection) -> + passive_receive(State, ?FUNCTION_NAME, Connection, [{{timeout, recv}, Timeout, timeout}]); connection(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). @@ -1125,34 +1276,45 @@ downgrade(Type, Event, State, Connection) -> %% common or unexpected events for the state. %%-------------------------------------------------------------------- handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, - #state{static_env = #static_env{role = client}} = State, _) -> + #state{static_env = #static_env{role = client}, + handshake_env = HsEnv} = State, _) -> %% Should not be included in handshake history - {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]}; + {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{tls_handshake_history = Hs0} = State0, + #state{handshake_env = #handshake_env{tls_handshake_history = Hist0}} = 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 - State = handle_sni_extension(PossibleSNI, State0), - HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw)), - {next_state, StateName, State#state{tls_handshake_history = HsHist}, + State = #state{handshake_env = HsEnv} = handle_sni_extension(PossibleSNI, State0), + + 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}]}; 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{negotiated_version = Version} = State, _) -> + #state{connection_env = #connection_env{negotiated_version = Version}} = State, _) -> handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, StateName, State); -handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = 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(_Type, Msg, StateName, #state{connection_env = + #connection_env{negotiated_version = Version}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, {unexpected_msg, Msg}), handle_own_alert(Alert, Version, StateName, State). @@ -1160,24 +1322,28 @@ handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} 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) -> +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{terminated = true}}; + {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}} = State, _) -> + 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{terminated = true}, [{reply, From, 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{terminated = true}} + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, + State#state{connection_env = CEnv#connection_env{terminated = true}}} end catch throw:Return -> @@ -1199,15 +1365,13 @@ handle_call({recv, _N, _Timeout}, From, _, handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) -> %% Doing renegotiate wait with handling request until renegotiate is %% finished. - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom, - timer = Timer}, - [{next_event, internal, {recv, RecvFrom}}]}; + {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{user_application = {OldMon, _}}, _) -> + State = #state{connection_env = #connection_env{user_application = {OldMon, _}} = CEnv}, _) -> NewMon = erlang:monitor(process, User), erlang:demonitor(OldMon, [flush]), - {next_state, StateName, State#state{user_application = {NewMon,User}}, + {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, @@ -1217,23 +1381,31 @@ handle_call({get_opts, OptTags}, From, _, {keep_state_and_data, [{reply, From, OptsReply}]}; handle_call({set_opts, Opts0}, From, StateName, #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, + transport_cb = Transport, + tracker = Tracker}, + 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, Tracker, 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(get_sslsocket, From, _StateName, State, Connection) -> - SslSocket = Connection:socket(State), - {keep_state_and_data, [{reply, From, SslSocket}]}; - handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, #state{connection_states = ConnectionStates, - negotiated_version = Version}, _) -> + connection_env = #connection_env{negotiated_version = Version}}, _) -> #{security_parameters := SecParams} = ssl_record:current_connection_state(ConnectionStates, read), #security_parameters{master_secret = MasterSecret, @@ -1281,14 +1453,14 @@ handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_e {stop, {shutdown,normal}, State}; handle_info({'DOWN', MonitorRef, _, _, Reason}, _, - #state{user_application = {MonitorRef, _Pid}, + #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, ssl_options = #ssl_options{erl_dist = true}}) -> {stop, {shutdown, Reason}}; handle_info({'DOWN', MonitorRef, _, _, _}, _, - #state{user_application = {MonitorRef, _Pid}}) -> + #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}}) -> {stop, {shutdown, normal}}; handle_info({'EXIT', Pid, _Reason}, StateName, - #state{user_application = {_MonitorRef, Pid}} = State) -> + #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}; @@ -1301,22 +1473,8 @@ handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_en handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> {stop,{shutdown, Reason}, State}; -handle_info(allow_renegotiate, StateName, State) -> - {next_state, StateName, State#state{allow_renegotiate = true}}; - -handle_info({cancel_start_or_recv, StartFrom}, StateName, - #state{renegotiation = {false, first}} = State) when StateName =/= connection -> - {stop_and_reply, - {shutdown, user_timeout}, - {reply, StartFrom, {error, timeout}}, - State#state{timer = undefined}}; -handle_info({cancel_start_or_recv, RecvFrom}, StateName, - #state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined -> - {next_state, StateName, State#state{start_or_recv_from = undefined, - bytes_to_read = undefined, - timer = undefined}, [{reply, RecvFrom, {error, timeout}}]}; -handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> - {next_state, StateName, State#state{timer = undefined}}; +handle_info(allow_renegotiate, StateName, #state{handshake_env = HsEnv} = State) -> + {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{allow_renegotiate = true}}}; handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) -> Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]), @@ -1326,7 +1484,7 @@ handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, err %%==================================================================== %% general gen_statem callbacks %%==================================================================== -terminate(_, _, #state{terminated = true}) -> +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 @@ -1390,14 +1548,9 @@ format_status(terminate, [_, StateName, State]) -> [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, protocol_buffers = ?SECRET_PRINTOUT, user_data_buffer = ?SECRET_PRINTOUT, - tls_handshake_history = ?SECRET_PRINTOUT, + handshake_env = ?SECRET_PRINTOUT, + connection_env = ?SECRET_PRINTOUT, session = ?SECRET_PRINTOUT, - private_key = ?SECRET_PRINTOUT, - diffie_hellman_params = ?SECRET_PRINTOUT, - diffie_hellman_keys = ?SECRET_PRINTOUT, - srp_params = ?SECRET_PRINTOUT, - srp_keys = ?SECRET_PRINTOUT, - premaster_secret = ?SECRET_PRINTOUT, ssl_options = NewOptions, flight_buffer = ?SECRET_PRINTOUT} }}]}]. @@ -1411,10 +1564,10 @@ send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} Connection:send_alert(Alert, State). connection_info(#state{static_env = #static_env{protocol_cb = Connection}, - sni_hostname = SNIHostname, + handshake_env = #handshake_env{sni_hostname = SNIHostname}, session = #session{session_id = SessionId, cipher_suite = CipherSuite, ecc = ECCCurve}, - negotiated_version = {_,_} = Version, + connection_env = #connection_env{negotiated_version = {_,_} = Version}, ssl_options = Opts}) -> RecordCB = record_cb(Connection), CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher_format:suite_definition(CipherSuite), @@ -1442,16 +1595,17 @@ security_info(#state{connection_states = ConnectionStates}) -> do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, - #state{negotiated_version = Version, + #state{connection_env = #connection_env{negotiated_version = Version}, + handshake_env = HsEnv, session = #session{session_id = SessId}, connection_states = ConnectionStates0} = State0, Connection) when is_atom(Type) -> - + ServerHello = ssl_handshake:server_hello(SessId, ssl:tls_version(Version), ConnectionStates0, ServerHelloExt), State = server_hello(ServerHello, - State0#state{expecting_next_protocol_negotiation = - NextProtocols =/= undefined}, Connection), + State0#state{handshake_env = HsEnv#handshake_env{expecting_next_protocol_negotiation = + NextProtocols =/= undefined}}, Connection), case Type of new -> new_server_hello(ServerHello, State, Connection); @@ -1462,8 +1616,8 @@ do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocol new_server_hello(#server_hello{cipher_suite = CipherSuite, compression_method = Compression, session_id = SessionId}, - #state{session = Session0, - negotiated_version = Version} = State0, Connection) -> + #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), @@ -1479,7 +1633,7 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, resumed_server_hello(#state{session = Session, connection_states = ConnectionStates0, - negotiated_version = Version} = State0, Connection) -> + connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, server) of @@ -1496,19 +1650,20 @@ resumed_server_hello(#state{session = Session, server_hello(ServerHello, State0, Connection) -> CipherSuite = ServerHello#server_hello.cipher_suite, #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), - State = Connection:queue_handshake(ServerHello, State0), - State#state{key_algorithm = KeyAlgorithm}. + #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{session = #session{cipher_suite = CipherSuite} = Session} = State0, + #state{handshake_env = HsEnv, + session = #session{cipher_suite = CipherSuite} = Session} = State0, Connection) -> - State1 = State0#state{session = - Session#session{peer_certificate = PeerCert}, - public_key_info = PublicKeyInfo}, + 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_definition(CipherSuite), State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), Connection:next_event(certify, no_record, State). @@ -1516,21 +1671,13 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo, handle_peer_cert_key(client, _, {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, PublicKeyParams}, - KeyAlg, #state{session = Session} = State) when KeyAlg == ecdh_rsa; + 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{diffie_hellman_keys = ECDHKey, + master_secret(PremasterSecret, State#state{handshake_env = HsEnv#handshake_env{kex_keys = ECDHKey}, session = Session#session{ecc = PublicKeyParams}}); -%% We do currently not support cipher suites that use fixed DH. -%% If we want to implement that the following clause can be used -%% to extract DH parameters form cert. -%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams}, -%% {_,SignAlg}, -%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when -%% SignAlg == dh_rsa; -%% SignAlg == dh_dss -> -%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State); handle_peer_cert_key(_, _, _, _, State) -> State. @@ -1546,16 +1693,16 @@ 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, - negotiated_version = Version, - private_key = PrivateKey, session = #session{master_secret = MasterSecret, - own_certificate = OwnCert}, - cert_hashsign_algorithm = HashSign, - tls_handshake_history = Handshake0} = State, Connection) -> + own_certificate = OwnCert}} = State, Connection) -> case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of + ssl:tls_version(Version), HashSign, PrivateKey, Hist) of #certificate_verify{} = Verified -> Connection:queue_handshake(Verified, State); ignore -> @@ -1566,7 +1713,7 @@ verify_client_cert(#state{static_env = #static_env{role = client}, verify_client_cert(#state{client_certificate_requested = false} = State, _) -> State. -client_certify_and_key_exchange(#state{negotiated_version = Version} = +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{} -> @@ -1591,7 +1738,9 @@ server_certify_and_key_exchange(State0, Connection) -> request_client_cert(State2, Connection). certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) -> + #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. @@ -1612,14 +1761,15 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS end, calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{diffie_hellman_params = #'DHParameter'{} = Params, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State, + #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{diffie_hellman_keys = ECDHKey} = State, Connection) -> + #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, @@ -1629,8 +1779,8 @@ certify_client_key_exchange(#client_psk_identity{} = ClientKey, 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{diffie_hellman_params = #'DHParameter'{} = Params, - diffie_hellman_keys = {_, ServerDhPrivateKey}, + #state{handshake_env = #handshake_env{diffie_hellman_params = #'DHParameter'{} = Params, + kex_keys = {_, ServerDhPrivateKey}}, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State0, Connection) -> @@ -1638,7 +1788,7 @@ certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, 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{diffie_hellman_keys = ServerEcDhPrivateKey, + #state{handshake_env = #handshake_env{kex_keys = ServerEcDhPrivateKey}, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = State, Connection) -> @@ -1646,25 +1796,26 @@ certify_client_key_exchange(#client_ecdhe_psk_identity{} = ClientKey, 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{private_key = Key, + #state{connection_env = #connection_env{private_key = Key}, ssl_options = #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{srp_params = Params, - srp_keys = Key + #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{key_algorithm = Algo} = State, _) when Algo == dh_anon; - Algo == ecdh_anon; - Algo == psk; - Algo == dhe_psk; - Algo == ecdhe_psk; - Algo == srp_anon -> +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}, @@ -1676,18 +1827,19 @@ certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, throw(Alert) end. -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) -> +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}, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version - } = State0, Connection) - when Algo == dhe_dss; - Algo == dhe_rsa; - Algo == dh_anon -> +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), @@ -1697,24 +1849,26 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Alg HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), - State = Connection:queue_handshake(Msg, State0), - State#state{diffie_hellman_keys = DHKeys}; + #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}, - private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, - key_algorithm = Algo, + handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, + connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, session = Session} = State, _) - when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> - State#state{diffie_hellman_keys = Key, + 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}, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, +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, - negotiated_version = Version - } = State0, Connection) - when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; - Algo == ecdh_anon -> + connection_states = ConnectionStates0} = State0, Connection) + when KexAlg == ecdhe_ecdsa; + KexAlg == ecdhe_rsa; + KexAlg == ecdh_anon -> ECDHKeys = public_key:generate_key(ECCCurve), #{security_parameters := SecParams} = @@ -1726,18 +1880,19 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Alg HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), - State = Connection:queue_handshake(Msg, State0), - State#state{diffie_hellman_keys = ECDHKeys}; -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, + #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 = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version - } = State0, Connection) -> + 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, @@ -1746,15 +1901,16 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk {psk, PskIdentityHint, HashSignAlgo, ClientRandom, ServerRandom, - PrivateKey}), + PrivateKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk, +key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version + 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} = @@ -1767,15 +1923,16 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), - State = Connection:queue_handshake(Msg, State0), - State#state{diffie_hellman_keys = DHKeys}; -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk, + #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 = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, + 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, - negotiated_version = Version + connection_states = ConnectionStates0 } = State0, Connection) -> ECDHKeys = public_key:generate_key(ECCCurve), #{security_parameters := SecParams} = @@ -1788,17 +1945,19 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecd HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), - State = Connection:queue_handshake(Msg, State0), - State#state{diffie_hellman_keys = ECDHKeys}; -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, + #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 = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, +key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version + 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), @@ -1810,17 +1969,18 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa ServerRandom, PrivateKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #ssl_options{user_lookup_fun = LookupFun}, - hashsign_algorithm = HashSignAlgo, + 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}, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version + connection_states = ConnectionStates0 } = State0, Connection) - when Algo == srp_dss; - Algo == srp_rsa; - Algo == srp_anon -> + 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{} -> @@ -1837,82 +1997,86 @@ key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Alg HashSignAlgo, ClientRandom, ServerRandom, PrivateKey}), - State = Connection:queue_handshake(Msg, State0), - State#state{srp_params = SrpParams, - srp_keys = Keys}; + #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}, - key_algorithm = rsa, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret} = State0, Connection) -> + 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}, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _} - } = State0, Connection) - when Algorithm == dhe_dss; - Algorithm == dhe_rsa; - Algorithm == dh_anon -> + 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}, - key_algorithm = Algorithm, - negotiated_version = Version, - session = Session, - diffie_hellman_keys = #'ECPrivateKey'{parameters = ECCurve} = Key} = State0, Connection) - when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; - Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; - Algorithm == ecdh_anon -> + 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}, - ssl_options = SslOpts, - key_algorithm = psk, - negotiated_version = Version} = State0, Connection) -> + handshake_env = #handshake_env{kex_algorithm = psk}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = SslOpts} = State0, Connection) -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {psk, SslOpts#ssl_options.psk_identity}), Connection:queue_handshake(Msg, State0); key_exchange(#state{static_env = #static_env{role = client}, - ssl_options = SslOpts, - key_algorithm = dhe_psk, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) -> + handshake_env = #handshake_env{kex_algorithm = dhe_psk, + kex_keys = {DhPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = SslOpts} = State0, Connection) -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), Connection:queue_handshake(Msg, State0); key_exchange(#state{static_env = #static_env{role = client}, - ssl_options = SslOpts, - key_algorithm = ecdhe_psk, - negotiated_version = Version, - diffie_hellman_keys = ECDHKeys} = State0, Connection) -> + handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, + kex_keys = ECDHKeys}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = SslOpts} = State0, Connection) -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdhe_psk, SslOpts#ssl_options.psk_identity, ECDHKeys}), Connection:queue_handshake(Msg, State0); key_exchange(#state{static_env = #static_env{role = client}, - ssl_options = SslOpts, - key_algorithm = rsa_psk, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret} + handshake_env = #handshake_env{kex_algorithm = rsa_psk, + public_key_info = PublicKeyInfo, + premaster_secret = PremasterSecret}, + connection_env = #connection_env{negotiated_version = Version}, + ssl_options = SslOpts} = State0, Connection) -> Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); key_exchange(#state{static_env = #static_env{role = client}, - key_algorithm = Algorithm, - negotiated_version = Version, - srp_keys = {ClientPubKey, _}} + handshake_env = #handshake_env{kex_algorithm = KexAlg, + kex_keys = {ClientPubKey, _}}, + connection_env = #connection_env{negotiated_version = Version}} = State0, Connection) - when Algorithm == srp_dss; - Algorithm == srp_rsa; - Algorithm == srp_anon -> + 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). @@ -1949,18 +2113,24 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, rsa_psk_key_exchange(_, _, _, _) -> throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). -request_client_cert(#state{key_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 -> +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 = #ssl_options{verify = verify_peer, signature_algs = SupportedHashSigns}, - connection_states = ConnectionStates0, - negotiated_version = Version} = State0, Connection) -> + connection_states = ConnectionStates0} = State0, Connection) -> #{security_parameters := #security_parameters{cipher_suite = CipherSuite}} = ssl_record:pending_connection_state(ConnectionStates0, read), @@ -1977,7 +2147,7 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = State. calculate_master_secret(PremasterSecret, - #state{negotiated_version = Version, + #state{connection_env = #connection_env{negotiated_version = Version}, connection_states = ConnectionStates0, session = Session0} = State0, Connection, _Current, Next) -> @@ -2006,11 +2176,11 @@ finalize_handshake(State0, StateName, Connection) -> next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> State; -next_protocol(#state{negotiated_protocol = undefined} = State, _) -> +next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> State; -next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) -> +next_protocol(#state{handshake_env = #handshake_env{expecting_next_protocol_negotiation = false}} = State, _) -> State; -next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) -> +next_protocol(#state{handshake_env = #handshake_env{negotiated_protocol = NextProtocol}} = State0, Connection) -> NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), Connection:queue_handshake(NextProtocolMessage, State0). @@ -2018,14 +2188,15 @@ cipher_protocol(State, Connection) -> Connection:queue_change_cipher(#change_cipher_spec{}, State). finished(#state{static_env = #static_env{role = Role}, - negotiated_version = Version, + handshake_env = #handshake_env{tls_handshake_history = Hist}, + connection_env = #connection_env{negotiated_version = Version}, session = Session, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State0, StateName, Connection) -> + 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, Handshake0), + MasterSecret, Hist), ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), Connection:send_handshake(Finished, State0#state{connection_states = ConnectionStates}). @@ -2041,65 +2212,71 @@ save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbrev calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, dh_y = ServerPublicDhKey} = Params, - State, Connection) -> + #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{diffie_hellman_keys = Keys}, + State#state{handshake_env = HsEnv#handshake_env{kex_keys = Keys}}, Connection, certify, certify); calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - State=#state{session=Session}, Connection) -> + #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{diffie_hellman_keys = ECDHKeys, + 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, Connection) -> + #state{handshake_env = HsEnv} = State, Connection) -> %% store for later use - Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint}); + 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{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = + #state{handshake_env = HsEnv, + ssl_options = #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{diffie_hellman_keys = Keys}, + 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 = #ssl_options{user_lookup_fun = PSKLookup}} = - State=#state{session=Session}, Connection) -> + #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{diffie_hellman_keys = ECDHKeys, + 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{ssl_options = #ssl_options{srp_identity = SRPId}} = State, + #state{handshake_env = HsEnv, + ssl_options = #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{srp_keys = Keys}, Connection, + 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, - negotiated_version = Version, connection_states = ConnectionStates0} = State) -> case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, Role) of @@ -2159,7 +2336,7 @@ cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} {Record, State} = prepare_connection(State0#state{session = Session, connection_states = ConnectionStates}, Connection), - Connection:next_event(connection, Record, State); + 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, @@ -2168,15 +2345,15 @@ cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0 finalize_handshake(State0#state{connection_states = ConnectionStates1, session = Session}, cipher, Connection), {Record, State} = prepare_connection(State1, Connection), - Connection:next_event(connection, Record, State, Actions). - -is_anonymous(Algo) when Algo == dh_anon; - Algo == ecdh_anon; - Algo == psk; - Algo == dhe_psk; - Algo == ecdhe_psk; - Algo == rsa_psk; - Algo == srp_anon -> + 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. @@ -2290,6 +2467,30 @@ set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockO 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) -> @@ -2353,7 +2554,7 @@ handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, ok end. -prepare_connection(#state{renegotiation = Renegotiate, +prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate}, start_or_recv_from = RecvFrom} = State0, Connection) when Renegotiate =/= {false, first}, RecvFrom =/= undefined -> @@ -2363,42 +2564,54 @@ prepare_connection(State0, Connection) -> State = Connection:reinit(State0), {no_record, ack_connection(State)}. -ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer; - Initiater == internal -> - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {true, From}} = 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{renegotiation = undefined}; -ack_connection(#state{renegotiation = {false, first}, - start_or_recv_from = StartFrom, - timer = Timer} = State) when StartFrom =/= undefined -> + 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), - cancel_timer(Timer), - State#state{renegotiation = undefined, - start_or_recv_from = undefined, timer = undefined}; + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, + start_or_recv_from = undefined}; ack_connection(State) -> State. -cancel_timer(undefined) -> - ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - ok. - session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> Session#session{ecc = ECCurve}; session_handle_params(_, Session) -> Session. -register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> +handle_session(Role = server, #ssl_options{reuse_sessions = true} = SslOpts, + Host, Port, Session0) -> + register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true); +handle_session(Role = client, #ssl_options{verify = verify_peer, + reuse_sessions = Reuse} = SslOpts, + Host, Port, Session0) when Reuse =/= false -> + register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse)); +handle_session(server, _, Host, Port, Session) -> + %% Remove "session of type new" entry from session DB + ssl_manager:invalidate_session(Host, Port, Session), + Session; +handle_session(client, _,_,_, Session) -> + %% In client case there is no entry yet, so nothing to remove + Session. + +reg_type(save) -> + true; +reg_type(true) -> + unique. + +register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) -> Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Host, Port, Session), + ssl_manager:register_session(Host, Port, Session, Save), Session; -register_session(server, _, Port, #session{is_resumable = new} = Session0) -> +register_session(server, _, Port, #session{is_resumable = new} = Session0, _) -> Session = Session0#session{is_resumable = true}, ssl_manager:register_session(Port, Session), Session; -register_session(_, _, _, Session) -> +register_session(_, _, _, Session, _) -> Session. %% Already registered host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) -> @@ -2420,9 +2633,8 @@ handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, protocol_cb = Connection, session_cache = Cache, session_cache_cb = CacheCb}, - connection_states = ConnectionStates0, - negotiated_version = Version - } = State) -> + 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 @@ -2479,8 +2691,13 @@ ssl_options_list([Key | Keys], [Value | Values], 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{connection_env = #connection_env{terminated = true}, + user_data_buffer = {_,0,_}} = State) -> + handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, all_data_deliverd), 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 = <<>>} = State0) -> + 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}]); @@ -2489,11 +2706,11 @@ handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = {stop, _, _} = Stop -> Stop end; -handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) -> +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 =/= <<>> +%% 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 @@ -2513,33 +2730,25 @@ handle_active_option(_, StateName0, To, Reply, %% Picks ClientData -get_data(_, _, <<>>) -> - {more, <<>>}; -%% Recv timed out save buffer data until next recv -get_data(#socket_options{active=false}, undefined, Buffer) -> - {passive, Buffer}; -get_data(#socket_options{active=Active, packet=Raw}, BytesToRead, Buffer) +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 - if - Active =/= false orelse BytesToRead =:= 0 -> + case Bin of + <<_/binary>> when Active =/= false orelse BytesToRead =:= 0 -> %% Active true or once, or passive mode recv(0) - {ok, Buffer, <<>>}; - byte_size(Buffer) >= BytesToRead -> + {ok, Bin, <<>>}; + <<Data:BytesToRead/binary, Rest/binary>> -> %% Passive Mode, recv(Bytes) - <<Data:BytesToRead/binary, Rest/binary>> = Buffer, - {ok, Data, Rest}; - true -> + {ok, Data, Rest}; + <<_/binary>> -> %% Passive Mode not enough data - {more, Buffer} + {more, BytesToRead} end; -get_data(#socket_options{packet=Type, packet_size=Size}, _, Buffer) -> +get_data(#socket_options{packet=Type, packet_size=Size}, _, Bin) -> PacketOpts = [{packet_size, Size}], - case decode_packet(Type, Buffer, PacketOpts) of - {more, _} -> - {more, Buffer}; - Decoded -> - Decoded - end. + decode_packet(Type, Bin, PacketOpts). decode_packet({http, headers}, Buffer, PacketOpts) -> decode_packet(httph, Buffer, PacketOpts); @@ -2557,24 +2766,39 @@ decode_packet(Type, Buffer, PacketOpts) -> %% 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, SOpts = #socket_options{active=Active, packet=Type}, - Data, Pid, From, Tracker, Connection) -> - send_or_reply(Active, Pid, From, - format_reply(CPids, Transport, Socket, SOpts, Data, Tracker, 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, +deliver_app_data( + CPids, Transport, Socket, + #socket_options{active=Active, packet=Type} = SOpts, + Data, Pid, From, Tracker, Connection) -> + %% + send_or_reply( + Active, Pid, From, + format_reply( + CPids, Transport, Socket, SOpts, Data, Tracker, 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, Tracker, Connection)), + SO#socket_options{active=false}; + N when is_integer(N) -> + SO#socket_options{active=N - 1}; _ -> SO end. @@ -2584,7 +2808,7 @@ format_reply(_, _, _,#socket_options{active = false, mode = Mode, packet = Packe {ok, do_format_reply(Mode, Packet, Header, Data)}; format_reply(CPids, Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, header = Header}, Data, Tracker, Connection) -> - {ssl, Connection:socket(CPids, Transport, Socket, Connection, Tracker), + {ssl, Connection:socket(CPids, Transport, Socket, Tracker), do_format_reply(Mode, Packet, Header, Data)}. deliver_packet_error(CPids, Transport, Socket, @@ -2596,7 +2820,7 @@ format_packet_error(_, _, _,#socket_options{active = false, mode = Mode}, Data, {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; format_packet_error(CPids, Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker, Connection) -> - {ssl_error, Connection:socket(CPids, Transport, Socket, Connection, Tracker), + {ssl_error, Connection:socket(CPids, Transport, Socket, Tracker), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode @@ -2611,6 +2835,9 @@ do_format_reply(list, Packet, _, Data) do_format_reply(list, _,_, Data) -> binary_to_list(Data). +format_passive(CPids, Transport, Socket, Tracker, Connection) -> + {ssl_passive, Connection:socket(CPids, Transport, Socket, Tracker)}. + header(0, <<>>) -> <<>>; header(_, <<>>) -> @@ -2652,12 +2879,10 @@ alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Con case ssl_alert:reason_code(Alert, Role) of closed -> send_or_reply(Active, Pid, From, - {ssl_closed, Connection:socket(Pids, - Transport, Socket, Connection, Tracker)}); + {ssl_closed, Connection:socket(Pids, Transport, Socket, Tracker)}); ReasonCode -> send_or_reply(Active, Pid, From, - {ssl_error, Connection:socket(Pids, - Transport, Socket, Connection, Tracker), ReasonCode}) + {ssl_error, Connection:socket(Pids, Transport, Socket, Tracker), ReasonCode}) end. log_alert(true, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> @@ -2676,7 +2901,9 @@ invalidate_session(server, _, Port, Session) -> handle_sni_extension(undefined, State) -> State; -handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) -> +handle_sni_extension(#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 -> @@ -2699,12 +2926,12 @@ handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{ cert_db = CertDbHandle, crl_db = CRLDbHandle, session_cache = CacheHandle - }, - private_key = Key, - diffie_hellman_params = DHParams, - ssl_options = NewOptions, - sni_hostname = Hostname - } + }, + 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(OrigSSLOptions, SNIHostname) -> @@ -2727,8 +2954,3 @@ new_emulated([], EmOpts) -> EmOpts; new_emulated(NewEmOpts, _) -> NewEmOpts. - -is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined -> - true; -is_dist_up(_) -> - false. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 2f4dfefdda..9efd65b2d2 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2018. All Rights Reserved. +%% Copyright Ericsson AB 2013-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -51,58 +51,69 @@ cert_db_ref :: certdb_ref() | 'undefined', tracker :: pid() | 'undefined' %% Tracker process for listen socket }). + +-record(handshake_env, { + client_hello_version :: ssl_record:ssl_version() | 'undefined', + unprocessed_handshake_events = 0 :: integer(), + tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() + | 'undefined', + expecting_finished = false ::boolean(), + renegotiation :: undefined | {boolean(), From::term() | internal | peer}, + allow_renegotiate = true ::boolean(), + %% Ext handling + hello, %%:: #client_hello{} | #server_hello{} + sni_hostname = undefined, + expecting_next_protocol_negotiation = false ::boolean(), + next_protocol = undefined :: undefined | binary(), + negotiated_protocol, + hashsign_algorithm = {undefined, undefined}, + cert_hashsign_algorithm = {undefined, undefined}, + %% key exchange + kex_algorithm :: ssl:kex_algo(), + kex_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), + diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), + srp_params :: #srp_user{} | secret_printout() | 'undefined', + public_key_info :: ssl_handshake:public_key_info() | 'undefined', + premaster_secret :: binary() | secret_printout() | 'undefined', + server_psk_identity :: binary() | 'undefined' % server psk identity hint + }). + +-record(connection_env, { + user_application :: {Monitor::reference(), User::pid()}, + downgrade, + terminated = false ::boolean() | closed, + negotiated_version :: ssl_record:ssl_version() | 'undefined', + erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined', + private_key :: public_key:private_key() | secret_printout() | 'undefined' + }). + -record(state, { static_env :: #static_env{}, - %% Change seldome - user_application :: {Monitor::reference(), User::pid()}, + connection_env :: #connection_env{} | secret_printout(), ssl_options :: #ssl_options{}, socket_options :: #socket_options{}, - session :: #session{} | secret_printout(), - allow_renegotiate = true ::boolean(), - terminated = false ::boolean() | closed, - negotiated_version :: ssl_record:ssl_version() | 'undefined', - bytes_to_read :: undefined | integer(), %% bytes to read in passive mode - downgrade, - %% Changed often + %% Hanshake %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + handshake_env :: #handshake_env{} | secret_printout(), + %% Buffer of TLS/DTLS records, used during the TLS + %% handshake to when possible pack more than one TLS + %% record into the underlaying packet + %% format. Introduced by DTLS - RFC 4347. The + %% mecahnism is also usefull in TLS although we do not + %% need to worry about packet loss in TLS. In DTLS we + %% need to track DTLS handshake seqnr + flight_buffer = [] :: list() | map(), + client_certificate_requested = false :: boolean(), + protocol_specific = #{} :: map(), + session :: #session{} | secret_printout(), + %% Data shuffling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% connection_states :: ssl_record:connection_states() | secret_printout(), protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr - user_data_buffer :: undefined | binary() | secret_printout(), + user_data_buffer :: undefined | {[binary()],non_neg_integer(),[binary()]} | secret_printout(), + bytes_to_read :: undefined | integer(), %% bytes to read in passive mode - %% Used only in HS - unprocessed_handshake_events = 0 :: integer(), - tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() - | 'undefined', - client_hello_version :: ssl_record:ssl_version() | 'undefined', - client_certificate_requested = false :: boolean(), - key_algorithm :: ssl_cipher_format:key_algo(), - hashsign_algorithm = {undefined, undefined}, - cert_hashsign_algorithm = {undefined, undefined}, - public_key_info :: ssl_handshake:public_key_info() | 'undefined', - private_key :: public_key:private_key() | secret_printout() | 'undefined', - diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), - diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), - psk_identity :: binary() | 'undefined', % server psk identity hint - srp_params :: #srp_user{} | secret_printout() | 'undefined', - srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', - premaster_secret :: binary() | secret_printout() | 'undefined', - renegotiation :: undefined | {boolean(), From::term() | internal | peer}, - start_or_recv_from :: term(), - timer :: undefined | reference(), % start_or_recive_timer - hello, %%:: #client_hello{} | #server_hello{}, - expecting_next_protocol_negotiation = false ::boolean(), - expecting_finished = false ::boolean(), - next_protocol = undefined :: undefined | binary(), - negotiated_protocol, - sni_hostname = undefined, - flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake - %% to when possible pack more than one TLS record into the - %% underlaying packet format. Introduced by DTLS - RFC 4347. - %% The mecahnism is also usefull in TLS although we do not - %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr - flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. - erl_dist_data = #{} :: map(), - protocol_specific = #{} :: map() + %% recv and start handling + start_or_recv_from :: term() }). diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl index 9c1af86eeb..841620ce57 100644 --- a/lib/ssl/src/ssl_crl_cache.erl +++ b/lib/ssl/src/ssl_crl_cache.erl @@ -28,6 +28,10 @@ -behaviour(ssl_crl_cache_api). +-export_type([crl_src/0, uri/0]). +-type crl_src() :: {file, file:filename()} | {der, public_key:der_encoded()}. +-type uri() :: uri_string:uri_string(). + -export([lookup/3, select/2, fresh_crl/2]). -export([insert/1, insert/2, delete/1]). diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl index d5380583e7..8a750b3929 100644 --- a/lib/ssl/src/ssl_crl_cache_api.erl +++ b/lib/ssl/src/ssl_crl_cache_api.erl @@ -21,12 +21,15 @@ %% -module(ssl_crl_cache_api). - -include_lib("public_key/include/public_key.hrl"). --type db_handle() :: term(). --type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}. +-export_type([dist_point/0, crl_cache_ref/0]). + +-type crl_cache_ref() :: any(). +-type issuer_name() :: {rdnSequence,[#'AttributeTypeAndValue'{}]}. +-type dist_point() :: #'DistributionPoint'{}. --callback lookup(#'DistributionPoint'{}, issuer_name(), db_handle()) -> not_available | [public_key:der_encoded()]. --callback select(issuer_name(), db_handle()) -> [public_key:der_encoded()]. --callback fresh_crl(#'DistributionPoint'{}, public_key:der_encoded()) -> public_key:der_encoded(). + +-callback lookup(dist_point(), issuer_name(), crl_cache_ref()) -> not_available | [public_key:der_encoded()]. +-callback select(issuer_name(), crl_cache_ref()) -> [public_key:der_encoded()]. +-callback fresh_crl(dist_point(), public_key:der_encoded()) -> public_key:der_encoded(). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index be0b241017..9ba62b3a12 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -729,7 +729,7 @@ decode_hello_extensions(Extensions) -> dec_hello_extensions(Extensions, #hello_extensions{}). %%-------------------------------------------------------------------- --spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) -> +-spec decode_server_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) -> #server_key_params{}. %% %% Description: Decode server_key data and return appropriate type @@ -738,7 +738,7 @@ decode_server_key(ServerKey, Type, Version) -> dec_server_key(ServerKey, key_exchange_alg(Type), Version). %%-------------------------------------------------------------------- --spec decode_client_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) -> +-spec decode_client_key(binary(), ssl:kex_algo(), ssl_record:ssl_version()) -> #encrypted_premaster_secret{} | #client_diffie_hellman_public{} | #client_ec_diffie_hellman_public{} @@ -972,34 +972,30 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, #session{cipher_suite = NegotiatedCipherSuite, compression_method = Compression} = Session0, ConnectionStates0, Renegotiation) -> - Session = handle_srp_extension(SRP, Session0), - ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info, - Random, NegotiatedCipherSuite, + Session = handle_srp_extension(SRP, Session0), + ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info, + Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, - ConnectionStates0, Renegotiation, SecureRenegotation), - - ServerHelloExtensions = #hello_extensions{ - renegotiation_info = renegotiation_info(RecordCB, server, - ConnectionStates, Renegotiation), - ec_point_formats = server_ecc_extension(Version, ECCFormat) - }, - + ConnectionStates0, Renegotiation, SecureRenegotation), + + ServerHelloExtensions = #hello_extensions{ + renegotiation_info = renegotiation_info(RecordCB, server, + ConnectionStates, Renegotiation), + ec_point_formats = server_ecc_extension(Version, ECCFormat) + }, + %% If we receive an ALPN extension and have ALPN configured for this connection, %% we handle it. Otherwise we check for the NPN extension. if ALPN =/= undefined, ALPNPreferredProtocols =/= undefined -> - case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of - #alert{} = Alert -> - Alert; - Protocol -> - {Session, ConnectionStates, Protocol, - ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}} - end; + Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)), + {Session, ConnectionStates, Protocol, + ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}}; true -> - ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), + ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), {Session, ConnectionStates, undefined, - ServerHelloExtensions#hello_extensions{next_protocol_negotiation= - encode_protocols_advertised_on_server(ProtocolsToAdvertise)}} + ServerHelloExtensions#hello_extensions{next_protocol_negotiation= + encode_protocols_advertised_on_server(ProtocolsToAdvertise)}} end. handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, @@ -1022,12 +1018,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, [Protocol] when not Renegotiation -> {ConnectionStates, alpn, Protocol}; undefined -> - case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of - #alert{} = Alert -> - Alert; - Protocol -> - {ConnectionStates, npn, Protocol} - end; + Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation), + {ConnectionStates, npn, Protocol}; {error, Reason} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); [] -> @@ -2189,30 +2181,26 @@ filter_unavailable_ecc_suites(_, Suites) -> handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates0, Renegotiation, SecureRenegotation) -> - case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, - ClientCipherSuites) of - {ok, ConnectionStates} -> - hello_pending_connection_states(RecordCB, Role, - Version, - NegotiatedCipherSuite, - Random, - Compression, - ConnectionStates); - #alert{} = Alert -> - throw(Alert) - end. + {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, + ClientCipherSuites), + hello_pending_connection_states(RecordCB, Role, + Version, + NegotiatedCipherSuite, + Random, + Compression, + ConnectionStates). %% Receive protocols, choose one from the list, return it. handle_alpn_extension(_, {error, Reason}) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason)); handle_alpn_extension([], _) -> - ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL); + throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)); handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> - case lists:member(ServerProtocol, ClientProtocols) of - true -> ServerProtocol; - false -> handle_alpn_extension(Tail, ClientProtocols) - end. + case lists:member(ServerProtocol, ClientProtocols) of + true -> ServerProtocol; + false -> handle_alpn_extension(Tail, ClientProtocols) + end. handle_next_protocol(undefined, _NextProtocolSelector, _Renegotiating) -> @@ -2225,14 +2213,14 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols, true -> select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector); false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension) + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)) end. handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-> case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of #alert{} = Alert -> - Alert; + throw(Alert); ProtocolsToAdvertise -> ProtocolsToAdvertise end. @@ -2428,14 +2416,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co true -> {ok, ConnectionStates}; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation) + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)) end; handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); false -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), Data = maps:get(client_verify_data, ConnectionState), @@ -2443,7 +2431,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co true -> {ok, ConnectionStates}; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation) + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)) end end; @@ -2453,7 +2441,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); false -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) end. @@ -2462,9 +2450,9 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of {_, true} -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure)); {true, false} -> - ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); + throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION)); {false, false} -> {ok, ConnectionStates} end. diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 63e751440a..57c72aa122 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -31,8 +31,6 @@ -type reply() :: term(). -type msg() :: term(). -type from() :: term(). --type host() :: inet:ip_address() | inet:hostname(). --type session_id() :: 0 | binary(). -type certdb_ref() :: reference(). -type db_handle() :: term(). -type der_cert() :: binary(). @@ -111,10 +109,10 @@ %% Local policy for the server if it want's to reuse the session %% or not. Defaluts to allways returning true. %% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean() - reuse_session, + reuse_session :: fun() | binary() | undefined, %% Server side is a fun() %% If false sessions will never be reused, if true they %% will be reused if possible. - reuse_sessions :: boolean(), + reuse_sessions :: boolean() | save, %% Only client side can use value save renegotiate_at, secure_renegotiate, client_renegotiation, @@ -148,6 +146,8 @@ max_handshake_size :: integer(), handshake, customize_hostname_check + %% , + %% save_session :: boolean() }). -record(socket_options, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 4b735b2400..c56675b691 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -30,7 +30,7 @@ connection_init/3, cache_pem_file/2, lookup_trusted_cert/4, new_session_id/1, clean_cert_db/2, - register_session/2, register_session/3, invalidate_session/2, + register_session/2, register_session/4, invalidate_session/2, insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2, invalidate_session/3, name/1]). @@ -42,6 +42,8 @@ -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). +-include("ssl_api.hrl"). + -include_lib("kernel/include/file.hrl"). -record(state, { @@ -148,7 +150,7 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> ssl_pkix_db:lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer). %%-------------------------------------------------------------------- --spec new_session_id(integer()) -> session_id(). +-spec new_session_id(integer()) -> ssl:session_id(). %% %% Description: Creates a session id for the server. %%-------------------------------------------------------------------- @@ -170,9 +172,11 @@ clean_cert_db(Ref, File) -> %% %% Description: Make the session available for reuse. %%-------------------------------------------------------------------- --spec register_session(host(), inet:port_number(), #session{}) -> ok. -register_session(Host, Port, Session) -> - cast({register_session, Host, Port, Session}). +-spec register_session(ssl:host(), inet:port_number(), #session{}, unique | true) -> ok. +register_session(Host, Port, Session, true) -> + call({register_session, Host, Port, Session}); +register_session(Host, Port, Session, unique = Save) -> + cast({register_session, Host, Port, Session, Save}). -spec register_session(inet:port_number(), #session{}) -> ok. register_session(Port, Session) -> @@ -183,7 +187,7 @@ register_session(Port, Session) -> %% a the session has been marked "is_resumable = false" for some while %% it will be safe to remove the data from the session database. %%-------------------------------------------------------------------- --spec invalidate_session(host(), inet:port_number(), #session{}) -> ok. +-spec invalidate_session(ssl:host(), inet:port_number(), #session{}) -> ok. invalidate_session(Host, Port, Session) -> load_mitigation(), cast({invalidate_session, Host, Port, Session}). @@ -301,7 +305,10 @@ handle_call({{new_session_id, Port}, _}, _, #state{session_cache_cb = CacheCb, session_cache_server = Cache} = State) -> Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), - {reply, Id, State}. + {reply, Id, State}; +handle_call({{register_session, Host, Port, Session},_}, _, State0) -> + State = client_register_session(Host, Port, Session, State0), + {reply, ok, State}. %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. @@ -311,8 +318,12 @@ handle_call({{new_session_id, Port}, _}, %% %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast({register_session, Host, Port, Session}, State0) -> - State = ssl_client_register_session(Host, Port, Session, State0), +handle_cast({register_session, Host, Port, Session, unique}, State0) -> + State = client_register_unique_session(Host, Port, Session, State0), + {noreply, State}; + +handle_cast({register_session, Host, Port, Session, true}, State0) -> + State = client_register_session(Host, Port, Session, State0), {noreply, State}; handle_cast({register_session, Port, Session}, State0) -> @@ -540,10 +551,10 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) -> ok end. -ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache, - session_cache_cb = CacheCb, - session_cache_client_max = Max, - session_client_invalidator = Pid0} = State) -> +client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache, + session_cache_cb = CacheCb, + session_cache_client_max = Max, + session_client_invalidator = Pid0} = State) -> TimeStamp = erlang:monotonic_time(), NewSession = Session#session{time_stamp = TimeStamp}, @@ -557,6 +568,17 @@ ssl_client_register_session(Host, Port, Session, #state{session_cache_client = C register_unique_session(Sessions, NewSession, {Host, Port}, State) end. +client_register_session(Host, Port, Session, #state{session_cache_client = Cache, + session_cache_cb = CacheCb, + session_cache_client_max = Max, + session_client_invalidator = Pid0} = State) -> + TimeStamp = erlang:monotonic_time(), + NewSession = Session#session{time_stamp = TimeStamp}, + Pid = do_register_session({{Host, Port}, + NewSession#session.session_id}, + NewSession, Max, Pid0, Cache, CacheCb), + State#state{session_client_invalidator = Pid}. + server_register_session(Port, Session, #state{session_cache_server_max = Max, session_cache_server = Cache, session_cache_cb = CacheCb, diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index b9d1320ef3..1a36b2dba8 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2018. All Rights Reserved. +%% Copyright Ericsson AB 2013-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -45,14 +45,16 @@ -export([compress/3, uncompress/3, compressions/0]). %% Payload encryption/decryption --export([cipher/4, decipher/4, cipher_aead/4, decipher_aead/5, is_correct_mac/2, nonce_seed/3]). +-export([cipher/4, cipher/5, decipher/4, + cipher_aead/4, cipher_aead/5, decipher_aead/5, + is_correct_mac/2, nonce_seed/3]). -export_type([ssl_version/0, ssl_atom_version/0, connection_states/0, connection_state/0]). -type ssl_version() :: {integer(), integer()}. -type ssl_atom_version() :: tls_record:tls_atom_version(). --type connection_states() :: term(). %% Map --type connection_state() :: term(). %% Map +-type connection_states() :: map(). %% Map +-type connection_state() :: map(). %% Map %%==================================================================== %% Connection state handling @@ -302,27 +304,49 @@ cipher(Version, Fragment, #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo} } = WriteState0, MacHash) -> - + %% {CipherFragment, CipherS1} = ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), {CipherFragment, WriteState0#{cipher_state => CipherS1}}. + +%%-------------------------------------------------------------------- +-spec cipher(ssl_version(), iodata(), #cipher_state{}, MacHash::binary(), #security_parameters{}) -> + {CipherFragment::binary(), #cipher_state{}}. +%% +%% Description: Payload encryption +%%-------------------------------------------------------------------- +cipher(Version, Fragment, CipherS0, MacHash, + #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo}) -> + %% + ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version). + %%-------------------------------------------------------------------- -spec cipher_aead(ssl_version(), iodata(), connection_state(), AAD::binary()) -> {CipherFragment::binary(), connection_state()}. %% Description: Payload encryption %% %%-------------------------------------------------------------------- -cipher_aead(Version, Fragment, +cipher_aead(_Version, Fragment, #{cipher_state := CipherS0, security_parameters := #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo} } = WriteState0, AAD) -> {CipherFragment, CipherS1} = - cipher_aead(BulkCipherAlgo, CipherS0, AAD, Fragment, Version), + do_cipher_aead(BulkCipherAlgo, Fragment, CipherS0, AAD), {CipherFragment, WriteState0#{cipher_state => CipherS1}}. %%-------------------------------------------------------------------- +-spec cipher_aead(ssl_version(), iodata(), #cipher_state{}, AAD::binary(), #security_parameters{}) -> + {CipherFragment::binary(), #cipher_state{}}. + +%% Description: Payload encryption +%% %%-------------------------------------------------------------------- +cipher_aead(_Version, Fragment, CipherS, AAD, + #security_parameters{bulk_cipher_algorithm = BulkCipherAlgo}) -> + do_cipher_aead(BulkCipherAlgo, Fragment, CipherS, AAD). + +%%-------------------------------------------------------------------- -spec decipher(ssl_version(), binary(), connection_state(), boolean()) -> {binary(), binary(), connection_state()} | #alert{}. %% @@ -343,9 +367,8 @@ decipher(Version, CipherFragment, Alert end. %%-------------------------------------------------------------------- --spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{}, - binary(), binary(), ssl_record:ssl_version()) -> - {binary(), #cipher_state{}} | #alert{}. +-spec decipher_aead(ssl_cipher:cipher_enum(), #cipher_state{}, binary(), binary(), ssl_record:ssl_version()) -> + binary() | #alert{}. %% %% Description: Decrypts the data and checks the associated data (AAD) MAC using %% cipher described by cipher_enum() and updating the cipher state. @@ -357,7 +380,7 @@ decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment {AAD, CipherText, CipherTag} = aead_ciphertext_split(Type, CipherState, CipherFragment, AAD0), case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of Content when is_binary(Content) -> - {Content, CipherState}; + Content; _ -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end @@ -399,11 +422,13 @@ random() -> Random_28_bytes = ssl_cipher:random_bytes(28), <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. +-compile({inline, [is_correct_mac/2]}). is_correct_mac(Mac, Mac) -> true; is_correct_mac(_M,_H) -> false. +-compile({inline, [record_protocol_role/1]}). record_protocol_role(client) -> ?CLIENT; record_protocol_role(server) -> @@ -427,13 +452,15 @@ initial_security_params(ConnectionEnd) -> compression_algorithm = ?NULL}, ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams). -cipher_aead(?CHACHA20_POLY1305 = Type, #cipher_state{key=Key} = CipherState, AAD0, Fragment, _Version) -> - AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), +-define(end_additional_data(AAD, Len), << (begin(AAD)end)/binary, ?UINT16(begin(Len)end) >>). + +do_cipher_aead(?CHACHA20_POLY1305 = Type, Fragment, #cipher_state{key=Key} = CipherState, AAD0) -> + AAD = ?end_additional_data(AAD0, erlang:iolist_size(Fragment)), Nonce = encrypt_nonce(Type, CipherState), {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), {<<Content/binary, CipherTag/binary>>, CipherState}; -cipher_aead(Type, #cipher_state{key=Key, nonce = ExplicitNonce} = CipherState, AAD0, Fragment, _Version) -> - AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), +do_cipher_aead(Type, Fragment, #cipher_state{key=Key, nonce = ExplicitNonce} = CipherState, AAD0) -> + AAD = ?end_additional_data(AAD0, erlang:iolist_size(Fragment)), Nonce = encrypt_nonce(Type, CipherState), {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), {<<ExplicitNonce:64/integer, Content/binary, CipherTag/binary>>, CipherState#cipher_state{nonce = ExplicitNonce + 1}}. @@ -449,15 +476,12 @@ decrypt_nonce(?CHACHA20_POLY1305, #cipher_state{nonce = Nonce, iv = IV}, _) -> decrypt_nonce(?AES_GCM, #cipher_state{iv = <<Salt:4/bytes, _/binary>>}, <<ExplicitNonce:8/bytes, _/binary>>) -> <<Salt/binary, ExplicitNonce/binary>>. +-compile({inline, [aead_ciphertext_split/4]}). aead_ciphertext_split(?CHACHA20_POLY1305, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> - CipherLen = size(CipherTextFragment) - Len, + CipherLen = byte_size(CipherTextFragment) - Len, <<CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment, - {end_additional_data(AAD, CipherLen), CipherText, CipherTag}; + {?end_additional_data(AAD, CipherLen), CipherText, CipherTag}; aead_ciphertext_split(?AES_GCM, #cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> - CipherLen = size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce + CipherLen = byte_size(CipherTextFragment) - (Len + 8), %% 8 is length of explicit Nonce << _:8/bytes, CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment, - {end_additional_data(AAD, CipherLen), CipherText, CipherTag}. - -end_additional_data(AAD, Len) -> - <<AAD/binary, ?UINT16(Len)>>. - + {?end_additional_data(AAD, CipherLen), CipherText, CipherTag}. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index ed007f58d7..a927fba0de 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -140,6 +140,8 @@ -define(ALERT, 21). -define(HANDSHAKE, 22). -define(APPLICATION_DATA, 23). +-define(KNOWN_RECORD_TYPE(Type), + (is_integer(Type) andalso (20 =< (Type)) andalso ((Type) =< 23))). -define(MAX_PLAIN_TEXT_LENGTH, 16384). -define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)). -define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)). diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index c9607489e9..44305c65fe 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -27,6 +27,7 @@ -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). +-include("ssl_api.hrl"). %% Internal application API -export([is_new/2, client_id/4, server_id/6, valid_session/2]). @@ -34,7 +35,7 @@ -type seconds() :: integer(). %%-------------------------------------------------------------------- --spec is_new(session_id(), session_id()) -> boolean(). +-spec is_new(ssl:session_id(), ssl:session_id()) -> boolean(). %% %% Description: Checks if the session id decided by the server is a %% new or resumed sesion id. @@ -47,12 +48,19 @@ is_new(_ClientSuggestion, _ServerDecision) -> true. %%-------------------------------------------------------------------- --spec client_id({host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(), +-spec client_id({ssl:host(), inet:port_number(), #ssl_options{}}, db_handle(), atom(), undefined | binary()) -> binary(). %% %% Description: Should be called by the client side to get an id %% for the client hello message. %%-------------------------------------------------------------------- +client_id({Host, Port, #ssl_options{reuse_session = SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)-> + case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of + undefined -> + <<>>; + #session{} -> + SessionId + end; client_id(ClientInfo, Cache, CacheCb, OwnCert) -> case select_session(ClientInfo, Cache, CacheCb, OwnCert) of no_session -> @@ -91,7 +99,8 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) -> +select_session({_, _, #ssl_options{reuse_sessions = Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true -> + %% If reuse_sessions == true | save a new session should be created no_session; select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), @@ -132,7 +141,7 @@ is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} = false -> {false, undefined} end; undefined -> - {false, undefined} + {false, undefined} end. resumable(new) -> diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl index b68c75a09b..5f96f905b1 100644 --- a/lib/ssl/src/ssl_session_cache_api.erl +++ b/lib/ssl/src/ssl_session_cache_api.erl @@ -23,14 +23,20 @@ -module(ssl_session_cache_api). -include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). +-include("ssl_api.hrl"). --type key() :: {{host(), inet:port_number()}, session_id()} | {inet:port_number(), session_id()}. +-export_type([session_cache_key/0, session/0, partial_key/0, session_cache_ref/0]). --callback init(list()) -> db_handle(). --callback terminate(db_handle()) -> any(). --callback lookup(db_handle(), key()) -> #session{} | undefined. --callback update(db_handle(), key(), #session{}) -> any(). --callback delete(db_handle(), key()) -> any(). --callback foldl(fun(), term(), db_handle()) -> term(). --callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}]. --callback size(db_handle()) -> integer(). +-type session_cache_ref() :: any(). +-type session_cache_key() :: {partial_key(), ssl:session_id()}. +-opaque session() :: #session{}. +-opaque partial_key() :: {ssl:host(), inet:port_number()} | inet:port_number(). + +-callback init(list()) -> session_cache_ref(). +-callback terminate(session_cache_ref()) -> any(). +-callback lookup(session_cache_ref(), session_cache_key()) -> #session{} | undefined. +-callback update(session_cache_ref(), session_cache_key(), #session{}) -> any(). +-callback delete(session_cache_ref(), session_cache_key()) -> any(). +-callback foldl(fun(), term(), session_cache_ref()) -> term(). +-callback select_session(session_cache_ref(), {ssl:host(), inet:port_number()} | inet:port_number()) -> [#session{}]. +-callback size(session_cache_ref()) -> integer(). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 80a8c6b72c..3229004c9d 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -57,11 +57,10 @@ %% Alert and close handling -export([send_alert/2, send_alert_in_connection/2, send_sync_alert/2, - encode_alert/3, close/5, protocol_name/0]). + close/5, protocol_name/0]). %% Data handling --export([encode_data/3, next_record/1, - send/3, socket/5, setopts/3, getopts/3]). +-export([next_record/1, socket/4, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -108,7 +107,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = end. %%-------------------------------------------------------------------- --spec start_link(atom(), pid(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> +-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 @@ -143,23 +142,17 @@ pids(#state{protocol_specific = #{sender := Sender}}) -> %%==================================================================== %% State transition handling %%==================================================================== -next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> - {no_record, State#state{unprocessed_handshake_events = N-1}}; - +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_packets = [], tls_cipher_texts = [CT | Rest]} - = Buffers, - connection_states = ConnStates0, - ssl_options = #ssl_options{padding_check = Check}} = State) -> - case tls_record:decode_cipher_text(CT, ConnStates0, Check) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{tls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end; -next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, + #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, + connection_states = ConnectionStates, + ssl_options = #ssl_options{padding_check = Check}} = State) -> + next_record(State, CipherTexts, ConnectionStates, Check); +next_record(#state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, static_env = #static_env{socket = Socket, close_tag = CloseTag, @@ -175,16 +168,48 @@ next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_ci next_record(State) -> {no_record, State}. +%% 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, [#ssl_tls{type = ?APPLICATION_DATA} = CT|CipherTexts], ConnectionStates0, Check, Acc) -> + case tls_record:decode_cipher_text(CT, ConnectionStates0, Check) of + {#ssl_tls{fragment = Fragment}, ConnectionStates} -> + next_record(State, CipherTexts, ConnectionStates, Check, [Fragment|Acc]); + #alert{} = Alert -> + Alert + end; +next_record(State, [CT|CipherTexts], ConnectionStates0, Check, []) -> + case tls_record:decode_cipher_text(CT, ConnectionStates0, Check) of + {Record, ConnectionStates} -> + next_record_done(State, CipherTexts, ConnectionStates, Record); + #alert{} = Alert -> + Alert + end; +next_record(State, CipherTexts, ConnectionStates, _Check, Acc) -> + %% Not ?APPLICATION_DATA but we have a nonempty Acc + %% -> build an ?APPLICATION_DATA record with the accumulated fragments + next_record_done(State, CipherTexts, ConnectionStates, + #ssl_tls{type = ?APPLICATION_DATA, fragment = iolist_to_binary(lists:reverse(Acc))}). + +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, State0, Actions) -> case next_record(State0) of {no_record, State} -> {next_state, StateName, State, Actions}; {#ssl_tls{} = Record, State} -> {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#alert{} = Alert, State} -> - {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + #alert{} = Alert -> + {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]} end; next_event(StateName, Record, State, Actions) -> case Record of @@ -196,6 +221,7 @@ next_event(StateName, Record, State, Actions) -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end. + %%% TLS record protocol level application data messages handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> @@ -210,7 +236,7 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, #state{protocol_buffers = #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, - negotiated_version = Version, + connection_env = #connection_env{negotiated_version = Version}, ssl_options = Options} = State0) -> try {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options), @@ -227,8 +253,12 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, connection -> ssl_connection:hibernate_after(StateName, State, Events); _ -> + HsEnv = State#state.handshake_env, {next_state, StateName, - State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + State#state{protocol_buffers = Buffers, + handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} end end catch throw:#alert{} = Alert -> @@ -239,7 +269,7 @@ handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, St {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{negotiated_version = Version} = State) -> + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try decode_alerts(EncAlerts) of Alerts = [_|_] -> handle_alerts(Alerts, {next_state, StateName, State}); @@ -263,69 +293,71 @@ handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> renegotiation(Pid, WriteState) -> gen_statem:call(Pid, {user_renegotiate, WriteState}). -renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) -> +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{tls_handshake_history = Hs0}, + {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}, - negotiated_version = Version, + 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), - send(Transport, Socket, BinMsg), + tls_socket:send(Transport, Socket, BinMsg), State = State0#state{connection_states = ConnectionStates, - tls_handshake_history = Hs0}, + 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{negotiated_version = Version, - tls_handshake_history = Hist0, - flight_buffer = Flight0, +queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, + connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = Flight0, connection_states = ConnectionStates0} = State0) -> {BinHandshake, ConnectionStates, Hist} = encode_handshake(Handshake, Version, ConnectionStates0, Hist0), State0#state{connection_states = ConnectionStates, - tls_handshake_history = Hist, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, flight_buffer = Flight0 ++ [BinHandshake]}. send_handshake_flight(#state{static_env = #static_env{socket = Socket, transport_cb = Transport}, flight_buffer = Flight} = State0) -> - send(Transport, Socket, Flight), + tls_socket:send(Transport, Socket, Flight), {State0#state{flight_buffer = []}, []}. -queue_change_cipher(Msg, #state{negotiated_version = Version, - flight_buffer = Flight0, - connection_states = ConnectionStates0} = State0) -> +queue_change_cipher(Msg, #state{connection_env = #connection_env{negotiated_version = Version}, + flight_buffer = Flight0, + connection_states = ConnectionStates0} = State0) -> {BinChangeCipher, ConnectionStates} = encode_change_cipher(Msg, Version, ConnectionStates0), State0#state{connection_states = ConnectionStates, flight_buffer = Flight0 ++ [BinChangeCipher]}. reinit(#state{protocol_specific = #{sender := Sender}, - negotiated_version = Version, + 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) -> +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{ - premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history() + 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 = HelloExtensions}) -> @@ -348,13 +380,13 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> encode_alert(#alert{} = Alert, Version, ConnectionStates) -> tls_record:encode_alert_record(Alert, Version, ConnectionStates). -send_alert(Alert, #state{negotiated_version = Version, - static_env = #static_env{socket = Socket, +send_alert(Alert, #state{static_env = #static_env{socket = Socket, transport_cb = Transport}, + connection_env = #connection_env{negotiated_version = Version}, connection_states = ConnectionStates0} = StateData0) -> {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), - send(Transport, Socket, BinMsg), + tls_socket:send(Transport, Socket, BinMsg), StateData0#state{connection_states = ConnectionStates}. %% If an ALERT sent in the connection state, should cause the TLS @@ -408,14 +440,9 @@ protocol_name() -> %%==================================================================== %% Data handling %%==================================================================== -encode_data(Data, Version, ConnectionStates0)-> - tls_record:encode_data(Data, Version, ConnectionStates0). -send(Transport, Socket, Data) -> - tls_socket:send(Transport, Socket, Data). - -socket(Pids, Transport, Socket, Connection, Tracker) -> - tls_socket:socket(Pids, Transport, Socket, Connection, Tracker). +socket(Pids, Transport, Socket, Tracker) -> + tls_socket:socket(Pids, Transport, Socket, ?MODULE, Tracker). setopts(Transport, Socket, Other) -> tls_socket:setopts(Transport, Socket, Other). @@ -440,12 +467,12 @@ init({call, From}, {start, Timeout}, socket = Socket, session_cache = Cache, session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, + connection_env = CEnv, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _} + connection_states = ConnectionStates0 } = State0) -> - Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), @@ -454,15 +481,14 @@ init({call, From}, {start, Timeout}, Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), - send(Transport, Socket, BinMsg), + tls_socket:send(Transport, Socket, BinMsg), State = State0#state{connection_states = ConnectionStates, - negotiated_version = Version, %% Requested version + connection_env = CEnv#connection_env{negotiated_version = Version}, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake, - start_or_recv_from = From, - timer = Timer}, - next_event(hello, no_record, State); + handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake}, + start_or_recv_from = From}, + next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close}]); init(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). @@ -489,15 +515,17 @@ error(_, _, _) -> %%-------------------------------------------------------------------- hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, + handshake_env = HsEnv, start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, - hello = Hello}, + handshake_env = HsEnv#handshake_env{hello = Hello}}, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, + handshake_env = HsEnv, start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, - hello = Hello}, + handshake_env = HsEnv#handshake_env{hello = Hello}}, [{reply, From, {ok, ssl_connection:map_extensions(Extensions)}}]}; hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{connection_states = ConnectionStates0, @@ -505,17 +533,18 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, port = Port, session_cache = Cache, session_cache_cb = CacheCb}, + handshake_env = #handshake_env{kex_algorithm = KeyExAlg, + renegotiation = {Renegotiation, _}, + negotiated_protocol = CurrentProtocol} = HsEnv, + connection_env = CEnv, session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - negotiated_protocol = CurrentProtocol, - key_algorithm = KeyExAlg, ssl_options = SslOpts} = State) -> case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of #alert{} = Alert -> ssl_connection:handle_own_alert(Alert, ClientVersion, hello, - State#state{negotiated_version - = ClientVersion}); + State#state{connection_env = + CEnv#connection_env{negotiated_version = ClientVersion}}); {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> Protocol = case Protocol0 of @@ -524,22 +553,24 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, end, gen_handshake(?FUNCTION_NAME, internal, {common_client_hello, Type, ServerHelloExt}, State#state{connection_states = ConnectionStates, - negotiated_version = Version, - hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, - session = Session, - negotiated_protocol = Protocol}) + 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; hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, - negotiated_version = ReqVersion, + connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv, static_env = #static_env{role = client}, - renegotiation = {Renegotiation, _}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> + #alert{} = Alert -> %%TODO ssl_connection:handle_own_alert(Alert, ReqVersion, hello, - State#state{negotiated_version = ReqVersion}); + State#state{connection_env = CEnv#connection_env{negotiated_version = ReqVersion}}); {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, Version, NewId, ConnectionStates, ProtoExt, Protocol, State) @@ -592,13 +623,16 @@ connection({call, From}, {user_renegotiate, WriteState}, [{next_event,{call, From}, renegotiate}]}; connection({call, From}, {close, {Pid, _Timeout}}, - #state{terminated = closed} = State) -> - {next_state, downgrade, State#state{terminated = true, downgrade = {Pid, From}}, + #state{connection_env = #connection_env{terminated = closed} =CEnv} = State) -> + {next_state, downgrade, State#state{connection_env = + CEnv#connection_env{terminated = true, + downgrade = {Pid, From}}}, [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; connection({call, From}, {close,{Pid, Timeout}}, #state{connection_states = ConnectionStates, - protocol_specific = #{sender := Sender} + protocol_specific = #{sender := Sender}, + connection_env = CEnv } = State0) -> case tls_sender:downgrade(Sender, Timeout) of {ok, Write} -> @@ -609,8 +643,10 @@ connection({call, From}, State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), State0#state{connection_states = ConnectionStates#{current_write => Write}}), - {next_state, downgrade, State#state{downgrade = {Pid, From}, - terminated = true}, [{timeout, Timeout, downgrade}]}; + {next_state, downgrade, State#state{connection_env = + CEnv#connection_env{downgrade = {Pid, From}, + terminated = true}}, + [{timeout, Timeout, downgrade}]}; {error, timeout} -> {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} end; @@ -620,7 +656,7 @@ connection(internal, #hello_request{}, port = Port, session_cache = Cache, session_cache_cb = CacheCb}, - renegotiation = {Renegotiation, peer}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}}, session = #session{own_certificate = Cert} = Session0, ssl_options = SslOpts, protocol_specific = #{sender := Pid}, @@ -642,7 +678,7 @@ connection(internal, #hello_request{}, port = Port, session_cache = Cache, session_cache_cb = CacheCb}, - renegotiation = {Renegotiation, _}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, session = #session{own_certificate = Cert} = Session0, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> @@ -653,7 +689,7 @@ connection(internal, #hello_request{}, = Hello#client_hello.session_id}}, Actions); connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server}, - allow_renegotiate = true, + handshake_env = #handshake_env{allow_renegotiate = true}= HsEnv, connection_states = CS, protocol_specific = #{sender := Sender} } = State) -> @@ -665,17 +701,16 @@ connection(internal, #client_hello{} = Hello, 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}, - allow_renegotiate = false, - renegotiation = {true, peer} + 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, - protocol_cb = Connection}, - allow_renegotiate = false} = State0) -> + #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 = Connection:reinit_handshake_data(State0), + State = reinit_handshake_data(State0), next_event(?FUNCTION_NAME, no_record, State); connection(Type, Event, State) -> @@ -688,15 +723,16 @@ connection(Type, Event, State) -> downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, #state{static_env = #static_env{transport_cb = Transport, socket = Socket}, - downgrade = {Pid, From}} = State) -> + connection_env = #connection_env{downgrade = {Pid, From}}} = State) -> tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), Transport:controlling_process(Socket, Pid), {stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State}; -downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State) -> +downgrade(timeout, downgrade, #state{ connection_env = #connection_env{downgrade = {_, From}}} = State) -> {stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State}; downgrade(info, {CloseTag, Socket}, #state{static_env = #static_env{socket = Socket, - close_tag = CloseTag}, downgrade = {_, From}} = + close_tag = CloseTag}, + connection_env = #connection_env{downgrade = {_, From}}} = State) -> {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State}; downgrade(info, Info, State) -> @@ -734,7 +770,6 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac erl_dist = IsErlDist} = SSLOptions, ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), - ErlDistData = erl_dist_data(IsErlDist), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> Cb; @@ -763,16 +798,18 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac }, #state{ static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation + }, + connection_env = #connection_env{user_application = {UserMonitor, User}}, socket_options = SocketOptions, ssl_options = SSLOptions, session = #session{is_resumable = new}, - erl_dist_data = ErlDistData, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, - user_application = {UserMonitor, User}, - user_data_buffer = <<>>, - renegotiation = {false, first}, - allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + user_data_buffer = {[],0,[]}, start_or_recv_from = undefined, flight_buffer = [], protocol_specific = #{sender => Sender, @@ -781,21 +818,14 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac } }. -erl_dist_data(true) -> - #{dist_handle => undefined, - dist_buffer => <<>>}; -erl_dist_data(false) -> - #{}. - initialize_tls_sender(#state{static_env = #static_env{ role = Role, transport_cb = Transport, - protocol_cb = Connection, socket = Socket, tracker = Tracker }, - socket_options = SockOpts, - negotiated_version = Version, + connection_env = #connection_env{negotiated_version = Version}, + socket_options = SockOpts, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}, connection_states = #{current_write := ConnectionWriteState}, protocol_specific = #{sender := Sender}}) -> @@ -804,19 +834,23 @@ initialize_tls_sender(#state{static_env = #static_env{ socket => Socket, socket_options => SockOpts, tracker => Tracker, - protocol_cb => Connection, transport_cb => Transport, negotiated_version => Version, renegotiate_at => RenegotiateAt}, tls_sender:initialize(Sender, Init). - -next_tls_record(Data, StateName, #state{protocol_buffers = - #protocol_buffers{tls_record_buffer = Buf0, - tls_cipher_texts = CT0} = Buffers} - = State0) -> - case tls_record:get_tls_records(Data, - acceptable_record_versions(StateName, State0), - Buf0) of + +next_tls_record(Data, StateName, + #state{protocol_buffers = + #protocol_buffers{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = Buffers} = State0) -> + Versions = + case StateName of + hello -> + [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]; + _ -> + State0#state.connection_env#connection_env.negotiated_version + end, + case tls_record:get_tls_records(Data, Versions, Buf0) of {Records, Buf1} -> CT1 = CT0 ++ Records, next_record(State0#state{protocol_buffers = @@ -827,11 +861,6 @@ next_tls_record(Data, StateName, #state{protocol_buffers = end. -acceptable_record_versions(StateName, #state{negotiated_version = Version}) when StateName =/= hello-> - Version; -acceptable_record_versions(hello, _) -> - [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]. - handle_record_alert(Alert, _) -> Alert. @@ -858,18 +887,18 @@ handle_info({tcp_passive, Socket}, StateName, State#state{protocol_specific = PS#{active_n_toggle => true}}); handle_info({CloseTag, Socket}, StateName, #state{static_env = #static_env{socket = Socket, close_tag = CloseTag}, + connection_env = #connection_env{negotiated_version = Version}, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, - user_data_buffer = Buffer, - protocol_specific = PS, - negotiated_version = Version} = State) -> + 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 (Buffer =/= <<>>)) of + case (Active == false) andalso ((CTs =/= []) or (BufferSize =/= 0)) of false -> case Version of {1, N} when N >= 1 -> @@ -902,11 +931,13 @@ handle_alerts([], Result) -> handle_alerts(_, {stop, _, _} = Stop) -> Stop; handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], - {next_state, connection = StateName, #state{user_data_buffer = Buffer, + {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 (Buffer =/= <<>>) orelse + State}) when (BufferSize =/= 0) orelse (CTs =/= []) -> - {next_state, StateName, State#state{terminated = true}}; + {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}) -> @@ -926,7 +957,7 @@ decode_alerts(Bin) -> ssl_alert:decode(Bin). gen_handshake(StateName, Type, Event, - #state{negotiated_version = Version} = State) -> + #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try ssl_connection:StateName(Type, Event, State, ?MODULE) of Result -> Result @@ -937,7 +968,7 @@ gen_handshake(StateName, Type, Event, Version, StateName, State) end. -gen_info(Event, connection = StateName, #state{negotiated_version = Version} = State) -> +gen_info(Event, connection = StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try handle_info(Event, StateName, State) of Result -> Result @@ -948,7 +979,7 @@ gen_info(Event, connection = StateName, #state{negotiated_version = Version} = Version, StateName, State) end; -gen_info(Event, StateName, #state{negotiated_version = Version} = State) -> +gen_info(Event, StateName, #state{connection_env = #connection_env{negotiated_version = Version}} = State) -> try handle_info(Event, StateName, State) of Result -> Result diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl index 0af2258932..9063b1b736 100644 --- a/lib/ssl/src/tls_connection.hrl +++ b/lib/ssl/src/tls_connection.hrl @@ -30,7 +30,6 @@ -include("tls_record.hrl"). -record(protocol_buffers, { - tls_packets = [], %% :: [#ssl_tls{}], % Not yet handled decode SSL/TLS packets. tls_record_buffer = <<>>, %% :: binary(), % Buffer of incomplete records tls_handshake_buffer = <<>>, %% :: binary(), % Buffer of incomplete handshakes tls_cipher_texts = [] %%:: [binary()] diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 19a5eb0348..0f0de5936a 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -30,6 +30,7 @@ -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). -include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Handshake handling @@ -47,7 +48,7 @@ %% Handshake handling %%==================================================================== %%-------------------------------------------------------------------- --spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), +-spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> #client_hello{}. %% @@ -81,13 +82,13 @@ client_hello(Host, Port, ConnectionStates, -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(), atom(), ssl_record:connection_states(), - binary() | undefined, ssl_cipher_format:key_algo()}, + binary() | undefined, ssl:kex_algo()}, boolean()) -> - {tls_record:tls_version(), session_id(), + {tls_record:tls_version(), ssl:session_id(), ssl_record:connection_states(), alpn | npn, binary() | undefined}| {tls_record:tls_version(), {resumed | new, #session{}}, ssl_record:connection_states(), binary() | undefined, - #hello_extensions{}, {ssl_cipher_format:hash(), ssl_cipher_format:sign_algo()} | + #hello_extensions{}, {ssl:hash(), ssl:sign_algo()} | undefined} | #alert{}. %% %% Description: Handles a received hello message @@ -223,8 +224,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, Version, SslOpts, Session0, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} @@ -235,14 +234,14 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> - case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, + try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, Compression, HelloExt, Version, SslOpt, ConnectionStates0, - Renegotiation) of - #alert{} = Alert -> - Alert; + Renegotiation) of {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + catch throw:Alert -> + Alert end. %%-------------------------------------------------------------------- enc_handshake(#hello_request{}, _Version) -> diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 1776ec2627..b456197398 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -75,15 +75,23 @@ init_connection_states(Role, BeastMitigation) -> pending_write => Pending}. %%-------------------------------------------------------------------- --spec get_tls_records(binary(), [tls_version()] | tls_version(), binary()) -> {[binary()], binary()} | #alert{}. +-spec get_tls_records( + binary(), [tls_version()] | tls_version(), + Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}) -> + {Records :: [#ssl_tls{}], + Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} | + #alert{}. %% %% and returns it as a list of tls_compressed binaries also returns leftover %% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- -get_tls_records(Data, Version, Buffer) -> - get_tls_records_aux(Version, <<Buffer/binary, Data/binary>>, []). - + +get_tls_records(Data, Versions, Buffer) when is_binary(Buffer) -> + parse_tls_records(Versions, {[Data],byte_size(Data),[]}, undefined); +get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}) -> + parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, Hdr). + %%==================================================================== %% Encoding %%==================================================================== @@ -102,8 +110,8 @@ encode_handshake(Frag, Version, ConnectionStates) -> case iolist_size(Frag) of N when N > ?MAX_PLAIN_TEXT_LENGTH -> - Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation), - encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates); + Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation), + encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) end. @@ -129,18 +137,18 @@ encode_change_cipher_spec(Version, ConnectionStates) -> encode_plain_text(?CHANGE_CIPHER_SPEC, Version, ?byte(?CHANGE_CIPHER_SPEC_PROTO), ConnectionStates). %%-------------------------------------------------------------------- --spec encode_data(binary(), tls_version(), ssl_record:connection_states()) -> - {iolist(), ssl_record:connection_states()}. +-spec encode_data([binary()], tls_version(), ssl_record:connection_states()) -> + {[[binary()]], ssl_record:connection_states()}. %% %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_data(Frag, Version, +encode_data(Data, Version, #{current_write := #{beast_mitigation := BeastMitigation, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> - Data = split_bin(Frag, Version, BCA, BeastMitigation), - encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). + Fragments = split_iovec(Data, Version, BCA, BeastMitigation), + encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates). %%==================================================================== %% Decoding @@ -152,57 +160,59 @@ encode_data(Frag, Version, %% %% Description: Decode cipher text %%-------------------------------------------------------------------- -decode_cipher_text(#ssl_tls{type = Type, version = Version, - fragment = CipherFragment} = CipherText, +decode_cipher_text(CipherText, #{current_read := - #{compression_state := CompressionS0, - sequence_number := Seq, - cipher_state := CipherS0, + #{sequence_number := Seq, security_parameters := - #security_parameters{ - cipher_type = ?AEAD, - bulk_cipher_algorithm = - BulkCipherAlgo, - compression_algorithm = CompAlg} - } = ReadState0} = ConnnectionStates0, _) -> - AAD = start_additional_data(Type, Version, ReadState0), - CipherS1 = ssl_record:nonce_seed(BulkCipherAlgo, <<?UINT64(Seq)>>, CipherS0), - case ssl_record:decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment, Version) of - {PlainFragment, CipherState} -> - {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, - PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#{ + #security_parameters{cipher_type = ?AEAD, + bulk_cipher_algorithm = BulkCipherAlgo}, + cipher_state := CipherS0 + } + } = ConnectionStates0, _) -> + SeqBin = <<?UINT64(Seq)>>, + #ssl_tls{type = Type, version = {MajVer,MinVer} = Version, fragment = Fragment} = CipherText, + StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>, + CipherS = ssl_record:nonce_seed(BulkCipherAlgo, SeqBin, CipherS0), + case ssl_record:decipher_aead( + BulkCipherAlgo, CipherS, StartAdditionalData, Fragment, Version) + of + PlainFragment when is_binary(PlainFragment) -> + #{current_read := + #{security_parameters := SecParams, + compression_state := CompressionS0} = ReadState0} = ConnectionStates0, + {Plain, CompressionS} = ssl_record:uncompress(SecParams#security_parameters.compression_algorithm, + PlainFragment, CompressionS0), + ConnectionStates = ConnectionStates0#{ current_read => ReadState0#{ - cipher_state => CipherState, + cipher_state => CipherS, sequence_number => Seq + 1, - compression_state => CompressionS1}}, - {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + compression_state => CompressionS}}, + {CipherText#ssl_tls{fragment = Plain}, ConnectionStates}; #alert{} = Alert -> Alert end; -decode_cipher_text(#ssl_tls{type = Type, version = Version, +decode_cipher_text(#ssl_tls{version = Version, fragment = CipherFragment} = CipherText, - #{current_read := - #{compression_state := CompressionS0, - sequence_number := Seq, - security_parameters := - #security_parameters{compression_algorithm = CompAlg} - } = ReadState0} = ConnnectionStates0, PaddingCheck) -> + #{current_read := ReadState0} = ConnnectionStates0, PaddingCheck) -> case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of {PlainFragment, Mac, ReadState1} -> - MacHash = ssl_cipher:calc_mac_hash(Type, Version, PlainFragment, ReadState1), + MacHash = ssl_cipher:calc_mac_hash(CipherText#ssl_tls.type, Version, PlainFragment, ReadState1), case ssl_record:is_correct_mac(Mac, MacHash) of true -> + #{sequence_number := Seq, + compression_state := CompressionS0, + security_parameters := + #security_parameters{compression_algorithm = CompAlg}} = ReadState0, {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, PlainFragment, CompressionS0), - ConnnectionStates = ConnnectionStates0#{ - current_read => ReadState1#{ - sequence_number => Seq + 1, - compression_state => CompressionS1}}, + ConnnectionStates = + ConnnectionStates0#{current_read => + ReadState1#{sequence_number => Seq + 1, + compression_state => CompressionS1}}, {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end; #alert{} = Alert -> Alert @@ -384,124 +394,222 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> server_verify_data => undefined }. -get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) when Type == ?APPLICATION_DATA; - Type == ?HANDSHAKE; - Type == ?ALERT; - Type == ?CHANGE_CIPHER_SPEC -> - get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type, - version = Version, - fragment = Data} | Acc]); -get_tls_records_aux(Versions, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc) when is_list(Versions) andalso - ((Type == ?APPLICATION_DATA) - orelse - (Type == ?HANDSHAKE) - orelse - (Type == ?ALERT) - orelse - (Type == ?CHANGE_CIPHER_SPEC)) -> - case is_acceptable_version({MajVer, MinVer}, Versions) of + +parse_tls_records(Versions, Q, undefined) -> + decode_tls_records(Versions, Q, [], undefined, undefined, undefined); +parse_tls_records(Versions, Q, #ssl_tls{type = Type, version = Version, fragment = Length}) -> + decode_tls_records(Versions, Q, [], Type, Version, Length). + +%% Generic code path +decode_tls_records(Versions, {_,Size,_} = Q0, Acc, undefined, _Version, _Length) -> + if + 5 =< Size -> + {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0), + validate_tls_records_type(Versions, Q, Acc, Type, {MajVer,MinVer}, Length); + 3 =< Size -> + {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0), + validate_tls_records_type(Versions, Q, Acc, Type, {MajVer,MinVer}, undefined); + 1 =< Size -> + {<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0), + validate_tls_records_type(Versions, Q, Acc, Type, undefined, undefined); + true -> + validate_tls_records_type(Versions, Q0, Acc, undefined, undefined, undefined) + end; +decode_tls_records(Versions, {_,Size,_} = Q0, Acc, Type, undefined, _Length) -> + if + 4 =< Size -> + {<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0), + validate_tls_record_version(Versions, Q, Acc, Type, {MajVer,MinVer}, Length); + 2 =< Size -> + {<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0), + validate_tls_record_version(Versions, Q, Acc, Type, {MajVer,MinVer}, undefined); + true -> + validate_tls_record_version(Versions, Q0, Acc, Type, undefined, undefined) + end; +decode_tls_records(Versions, {_,Size,_} = Q0, Acc, Type, Version, undefined) -> + if + 2 =< Size -> + {<<?UINT16(Length)>>, Q} = binary_from_front(2, Q0), + validate_tls_record_length(Versions, Q, Acc, Type, Version, Length); + true -> + validate_tls_record_length(Versions, Q0, Acc, Type, Version, undefined) + end; +decode_tls_records(Versions, Q, Acc, Type, Version, Length) -> + validate_tls_record_length(Versions, Q, Acc, Type, Version, Length). + +validate_tls_records_type(_Versions, Q, Acc, undefined, _Version, _Length) -> + {lists:reverse(Acc), + {undefined, Q}}; +validate_tls_records_type(Versions, Q, Acc, Type, Version, Length) -> + if + ?KNOWN_RECORD_TYPE(Type) -> + validate_tls_record_version(Versions, Q, Acc, Type, Version, Length); + true -> + %% Not ?KNOWN_RECORD_TYPE(Type) + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) + end. + +validate_tls_record_version(_Versions, Q, Acc, Type, undefined, _Length) -> + {lists:reverse(Acc), + {#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}}; +validate_tls_record_version(Versions, Q, Acc, Type, Version, Length) -> + if + is_list(Versions) -> + case is_acceptable_version(Version, Versions) of + true -> + validate_tls_record_length(Versions, Q, Acc, Type, Version, Length); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; + Version =:= Versions -> + %% Exact version match + validate_tls_record_length(Versions, Q, Acc, Type, Version, Length); true -> - get_tls_records_aux(Versions, Rest, [#ssl_tls{type = Type, - version = {MajVer, MinVer}, - fragment = Data} | Acc]); - false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end. + +validate_tls_record_length(_Versions, Q, Acc, Type, Version, undefined) -> + {lists:reverse(Acc), + {#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}}; +validate_tls_record_length(Versions, {_,Size0,_} = Q0, Acc, Type, Version, Length) -> + if + Length =< ?MAX_CIPHER_TEXT_LENGTH -> + if + Length =< Size0 -> + %% Complete record + {Fragment, Q} = binary_from_front(Length, Q0), + Record = #ssl_tls{type = Type, version = Version, fragment = Fragment}, + decode_tls_records(Versions, Q, [Record|Acc], undefined, undefined, undefined); + true -> + {lists:reverse(Acc), + {#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}} + end; + true -> + ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW) + end. + + +binary_from_front(SplitSize, {Front,Size,Rear}) -> + binary_from_front(SplitSize, Front, Size, Rear, []). +%% +binary_from_front(SplitSize, [], Size, [_] = Rear, Acc) -> + %% Optimize a simple case + binary_from_front(SplitSize, Rear, Size, [], Acc); +binary_from_front(SplitSize, [], Size, Rear, Acc) -> + binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc); +binary_from_front(SplitSize, [Bin|Front], Size, Rear, []) -> + %% Optimize a frequent case + BinSize = byte_size(Bin), + if + SplitSize < BinSize -> + {RetBin, Rest} = erlang:split_binary(Bin, SplitSize), + {RetBin, {[Rest|Front],Size - SplitSize,Rear}}; + BinSize < SplitSize -> + binary_from_front(SplitSize - BinSize, Front, Size, Rear, [Bin]); + true -> % Perfect fit + {Bin, {Front,Size - SplitSize,Rear}} end; -get_tls_records_aux(_, <<?BYTE(Type),?BYTE(_MajVer),?BYTE(_MinVer), - ?UINT16(Length), _:Length/binary, _Rest/binary>>, - _) when Type == ?APPLICATION_DATA; - Type == ?HANDSHAKE; - Type == ?ALERT; - Type == ?CHANGE_CIPHER_SPEC -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC); -get_tls_records_aux(_, <<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), - ?UINT16(Length), _/binary>>, - _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> - ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); -get_tls_records_aux(_, Data, Acc) -> - case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of - true -> - {lists:reverse(Acc), Data}; - false -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) +binary_from_front(SplitSize, [Bin|Front], Size, Rear, Acc) -> + BinSize = byte_size(Bin), + if + SplitSize < BinSize -> + {Last, Rest} = erlang:split_binary(Bin, SplitSize), + RetBin = iolist_to_binary(lists:reverse(Acc, [Last])), + {RetBin, {[Rest|Front],Size - byte_size(RetBin),Rear}}; + BinSize < SplitSize -> + binary_from_front(SplitSize - BinSize, Front, Size, Rear, [Bin|Acc]); + true -> % Perfect fit + RetBin = iolist_to_binary(lists:reverse(Acc, [Bin])), + {RetBin, {Front,Size - byte_size(RetBin),Rear}} end. + +%%-------------------------------------------------------------------- +encode_plain_text(Type, Version, Data, ConnectionStates0) -> + {[CipherText],ConnectionStates} = encode_fragments(Type, Version, [Data], ConnectionStates0), + {CipherText,ConnectionStates}. %%-------------------------------------------------------------------- -encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) -> - {CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0), - {CipherText, Write} = encode_tls_cipher_text(Type, Version, CipherFragment, Write1), - {CipherText, ConnectionStates#{current_write => Write}}. - -encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment, #{sequence_number := Seq} = Write) -> - Length = erlang:iolist_size(Fragment), - {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment], - Write#{sequence_number => Seq +1}}. - -encode_iolist(Type, Data, Version, ConnectionStates0) -> - {ConnectionStates, EncodedMsg} = - lists:foldl(fun(Text, {CS0, Encoded}) -> - {Enc, CS1} = - encode_plain_text(Type, Version, Text, CS0), - {CS1, [Enc | Encoded]} - end, {ConnectionStates0, []}, Data), - {lists:reverse(EncodedMsg), ConnectionStates}. -%%-------------------------------------------------------------------- -do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, - cipher_state := CipherS0, - sequence_number := Seq, - security_parameters := - #security_parameters{ - cipher_type = ?AEAD, - bulk_cipher_algorithm = BCAlg, - compression_algorithm = CompAlg} - } = WriteState0) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - CipherS = ssl_record:nonce_seed(BCAlg, <<?UINT64(Seq)>>, CipherS0), - WriteState = WriteState0#{compression_state => CompS1, - cipher_state => CipherS}, - AAD = start_additional_data(Type, Version, WriteState), - ssl_record:cipher_aead(Version, Comp, WriteState, AAD); -do_encode_plain_text(Type, Version, Data, #{compression_state := CompS0, - security_parameters := - #security_parameters{compression_algorithm = CompAlg} - }= WriteState0) -> - {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), - WriteState1 = WriteState0#{compression_state => CompS1}, - MacHash = ssl_cipher:calc_mac_hash(Type, Version, Comp, WriteState1), - ssl_record:cipher(Version, Comp, WriteState1, MacHash); -do_encode_plain_text(_,_,_,CS) -> +encode_fragments(Type, Version, Data, + #{current_write := #{compression_state := CompS, + cipher_state := CipherS, + sequence_number := Seq}} = ConnectionStates) -> + encode_fragments(Type, Version, Data, ConnectionStates, CompS, CipherS, Seq, []). +%% +encode_fragments(_Type, _Version, [], #{current_write := WriteS} = CS, + CompS, CipherS, Seq, CipherFragments) -> + {lists:reverse(CipherFragments), + CS#{current_write := WriteS#{compression_state := CompS, + cipher_state := CipherS, + sequence_number := Seq}}}; +encode_fragments(Type, Version, [Text|Data], + #{current_write := #{security_parameters := + #security_parameters{cipher_type = ?AEAD, + bulk_cipher_algorithm = BCAlg, + compression_algorithm = CompAlg} = SecPars}} = CS, + CompS0, CipherS0, Seq, CipherFragments) -> + {CompText, CompS} = ssl_record:compress(CompAlg, Text, CompS0), + SeqBin = <<?UINT64(Seq)>>, + CipherS1 = ssl_record:nonce_seed(BCAlg, SeqBin, CipherS0), + {MajVer, MinVer} = Version, + VersionBin = <<?BYTE(MajVer), ?BYTE(MinVer)>>, + StartAdditionalData = <<SeqBin/binary, ?BYTE(Type), VersionBin/binary>>, + {CipherFragment,CipherS} = ssl_record:cipher_aead(Version, CompText, CipherS1, StartAdditionalData, SecPars), + Length = byte_size(CipherFragment), + CipherHeader = <<?BYTE(Type), VersionBin/binary, ?UINT16(Length)>>, + encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1, + [[CipherHeader, CipherFragment] | CipherFragments]); +encode_fragments(Type, Version, [Text|Data], + #{current_write := #{security_parameters := + #security_parameters{compression_algorithm = CompAlg, + mac_algorithm = MacAlgorithm} = SecPars, + mac_secret := MacSecret}} = CS, + CompS0, CipherS0, Seq, CipherFragments) -> + {CompText, CompS} = ssl_record:compress(CompAlg, Text, CompS0), + MacHash = ssl_cipher:calc_mac_hash(Type, Version, CompText, MacAlgorithm, MacSecret, Seq), + {CipherFragment,CipherS} = ssl_record:cipher(Version, CompText, CipherS0, MacHash, SecPars), + Length = byte_size(CipherFragment), + {MajVer, MinVer} = Version, + CipherHeader = <<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, + encode_fragments(Type, Version, Data, CS, CompS, CipherS, Seq + 1, + [[CipherHeader, CipherFragment] | CipherFragments]); +encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFragments) -> exit({cs, CS}). %%-------------------------------------------------------------------- -start_additional_data(Type, {MajVer, MinVer}, - #{sequence_number := SeqNo}) -> - <<?UINT64(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. %% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are %% not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when - BCA =/= ?RC4 andalso ({3, 1} == Version orelse - {3, 0} == Version) -> - [[FirstByte]|do_split_bin(Rest)]; +split_iovec([<<FirstByte:8, Rest/binary>>|Data], Version, BCA, one_n_minus_one) + when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + [[FirstByte]|split_iovec([Rest|Data])]; %% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 %% splitting. -split_bin(Bin, Version, BCA, zero_n) when - BCA =/= ?RC4 andalso ({3, 1} == Version orelse - {3, 0} == Version) -> - [<<>>|do_split_bin(Bin)]; -split_bin(Bin, _, _, _) -> - do_split_bin(Bin). - -do_split_bin(<<>>) -> []; -do_split_bin(Bin) -> - case Bin of - <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> -> - [Chunk|do_split_bin(Rest)]; - _ -> - [Bin] - end. +split_iovec(Data, Version, BCA, zero_n) + when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + [<<>>|split_iovec(Data)]; +split_iovec(Data, _Version, _BCA, _BeatMitigation) -> + split_iovec(Data). + +split_iovec([]) -> + []; +split_iovec(Data) -> + {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []), + [Part|split_iovec(Rest)]. +%% +split_iovec([Bin|Data], SplitSize, Acc) -> + BinSize = byte_size(Bin), + if + SplitSize < BinSize -> + {Last, Rest} = erlang:split_binary(Bin, SplitSize), + {lists:reverse(Acc, [Last]), [Rest|Data]}; + BinSize < SplitSize -> + split_iovec(Data, SplitSize - BinSize, [Bin|Acc]); + true -> % Perfect match + {lists:reverse(Acc, [Bin]), Data} + end; +split_iovec([], _SplitSize, Acc) -> + {lists:reverse(Acc),[]}. + %%-------------------------------------------------------------------- lowest_list_protocol_version(Ver, []) -> Ver; diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 11fcc6def0..c07b7f49cd 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -38,19 +38,23 @@ -define(SERVER, ?MODULE). --record(data, {connection_pid, - connection_states = #{}, - role, - socket, - socket_options, - tracker, - protocol_cb, - transport_cb, - negotiated_version, - renegotiate_at, - connection_monitor, - dist_handle - }). +-record(static, + {connection_pid, + role, + socket, + socket_options, + tracker, + transport_cb, + negotiated_version, + renegotiate_at, + connection_monitor, + dist_handle + }). + +-record(data, + {static = #static{}, + connection_states = #{} + }). %%%=================================================================== %%% API @@ -171,6 +175,10 @@ dist_tls_socket(Pid) -> callback_mode() -> state_functions. + +-define(HANDLE_COMMON, + ?FUNCTION_NAME(Type, Msg, StateData) -> + handle_common(Type, Msg, StateData)). %%-------------------------------------------------------------------- -spec init(Args :: term()) -> gen_statem:init_result(atom()). @@ -192,39 +200,35 @@ init({call, From}, {Pid, #{current_write := WriteState, socket := Socket, socket_options := SockOpts, tracker := Tracker, - protocol_cb := Connection, transport_cb := Transport, negotiated_version := Version, renegotiate_at := RenegotiateAt}}, - #data{connection_states = ConnectionStates} = StateData0) -> + #data{connection_states = ConnectionStates, static = Static0} = StateData0) -> Monitor = erlang:monitor(process, Pid), StateData = - StateData0#data{connection_pid = Pid, - connection_monitor = Monitor, - connection_states = - ConnectionStates#{current_write => WriteState}, - role = Role, - socket = Socket, - socket_options = SockOpts, - tracker = Tracker, - protocol_cb = Connection, - transport_cb = Transport, - negotiated_version = Version, - renegotiate_at = RenegotiateAt}, + StateData0#data{connection_states = ConnectionStates#{current_write => WriteState}, + static = Static0#static{connection_pid = Pid, + connection_monitor = Monitor, + role = Role, + socket = Socket, + socket_options = SockOpts, + tracker = Tracker, + transport_cb = Transport, + negotiated_version = Version, + renegotiate_at = RenegotiateAt}}, {next_state, handshake, StateData, [{reply, From, ok}]}; -init(info, Msg, StateData) -> - handle_info(Msg, ?FUNCTION_NAME, StateData). +init(_, _, _) -> + %% Just in case anything else sneeks through + {keep_state_and_data, [postpone]}. + %%-------------------------------------------------------------------- -spec connection(gen_statem:event_type(), Msg :: term(), StateData :: term()) -> gen_statem:event_handler_result(atom()). %%-------------------------------------------------------------------- -connection({call, From}, renegotiate, - #data{connection_states = #{current_write := Write}} = StateData) -> - {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; connection({call, From}, {application_data, AppData}, - #data{socket_options = #socket_options{packet = Packet}} = + #data{static = #static{socket_options = #socket_options{packet = Packet}}} = StateData) -> case encode_packet(Packet, AppData) of {error, _} = Error -> @@ -232,40 +236,40 @@ connection({call, From}, {application_data, AppData}, Data -> send_application_data(Data, From, ?FUNCTION_NAME, StateData) end; -connection({call, From}, {set_opts, _} = Call, StateData) -> - handle_call(From, Call, ?FUNCTION_NAME, StateData); +connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) -> + StateData = send_tls_alert(Alert, StateData0), + {next_state, ?FUNCTION_NAME, StateData, + [{reply,From,ok}]}; +connection({call, From}, renegotiate, + #data{connection_states = #{current_write := Write}} = StateData) -> + {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; +connection({call, From}, downgrade, #data{connection_states = + #{current_write := Write}} = StateData) -> + {next_state, death_row, StateData, [{reply,From, {ok, Write}}]}; +connection({call, From}, {set_opts, Opts}, StateData) -> + handle_set_opts(From, Opts, StateData); connection({call, From}, dist_get_tls_socket, - #data{protocol_cb = Connection, - transport_cb = Transport, - socket = Socket, - connection_pid = Pid, - tracker = Tracker} = StateData) -> - TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker), + #data{static = #static{transport_cb = Transport, + socket = Socket, + connection_pid = Pid, + tracker = Tracker}} = StateData) -> + TLSSocket = tls_connection:socket([Pid, self()], Transport, Socket, Tracker), {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; connection({call, From}, {dist_handshake_complete, _Node, DHandle}, - #data{connection_pid = Pid, - socket_options = #socket_options{packet = Packet}} = - StateData) -> + #data{static = #static{connection_pid = Pid} = Static} = StateData) -> ok = erlang:dist_ctrl_input_handler(DHandle, Pid), ok = ssl_connection:dist_handshake_complete(Pid, DHandle), %% From now on we execute on normal priority process_flag(priority, normal), - {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, - [{reply, From, ok} - | case dist_data(DHandle, Packet) of - [] -> - []; - Data -> - [{next_event, internal, - {application_packets,{self(),undefined},Data}}] - end]}; -connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) -> - StateData = send_tls_alert(Alert, StateData0), - {next_state, ?FUNCTION_NAME, StateData, - [{reply,From,ok}]}; -connection({call, From}, downgrade, #data{connection_states = - #{current_write := Write}} = StateData) -> - {next_state, death_row, StateData, [{reply,From, {ok, Write}}]}; + {keep_state, StateData#data{static = Static#static{dist_handle = DHandle}}, + [{reply,From,ok}| + case dist_data(DHandle) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}] + end]}; connection(internal, {application_packets, From, Data}, StateData) -> send_application_data(Data, From, ?FUNCTION_NAME, StateData); %% @@ -273,29 +277,26 @@ connection(cast, #alert{} = Alert, StateData0) -> StateData = send_tls_alert(Alert, StateData0), {next_state, ?FUNCTION_NAME, StateData}; connection(cast, {new_write, WritesState, Version}, - #data{connection_states = ConnectionStates0} = StateData) -> + #data{connection_states = ConnectionStates, static = Static} = StateData) -> {next_state, connection, StateData#data{connection_states = - ConnectionStates0#{current_write => WritesState}, - negotiated_version = Version}}; + ConnectionStates#{current_write => WritesState}, + static = Static#static{negotiated_version = Version}}}; %% -connection(info, dist_data, - #data{dist_handle = DHandle, - socket_options = #socket_options{packet = Packet}} = - StateData) -> - {next_state, ?FUNCTION_NAME, StateData, - case dist_data(DHandle, Packet) of +connection(info, dist_data, #data{static = #static{dist_handle = DHandle}}) -> + {keep_state_and_data, + case dist_data(DHandle) of [] -> []; Data -> [{next_event, internal, - {application_packets,{self(),undefined},Data}}] + {application_packets,{self(),undefined},erlang:iolist_to_iovec(Data)}}] end}; connection(info, tick, StateData) -> consume_ticks(), - {next_state, ?FUNCTION_NAME, StateData, - [{next_event, {call, {self(), undefined}}, - {application_data, <<>>}}]}; + Data = [<<0:32>>], % encode_packet(4, <<>>) + From = {self(), undefined}, + send_application_data(Data, From, ?FUNCTION_NAME, StateData); connection(info, {send, From, Ref, Data}, _StateData) -> %% This is for testing only! %% @@ -304,29 +305,37 @@ connection(info, {send, From, Ref, Data}, _StateData) -> From ! {Ref, ok}, {keep_state_and_data, [{next_event, {call, {self(), undefined}}, - {application_data, iolist_to_binary(Data)}}]}; -connection(info, Msg, StateData) -> - handle_info(Msg, ?FUNCTION_NAME, StateData). + {application_data, erlang:iolist_to_iovec(Data)}}]}; +?HANDLE_COMMON. + %%-------------------------------------------------------------------- -spec handshake(gen_statem:event_type(), Msg :: term(), StateData :: term()) -> gen_statem:event_handler_result(atom()). %%-------------------------------------------------------------------- -handshake({call, From}, {set_opts, _} = Call, StateData) -> - handle_call(From, Call, ?FUNCTION_NAME, StateData); +handshake({call, From}, {set_opts, Opts}, StateData) -> + handle_set_opts(From, Opts, StateData); handshake({call, _}, _, _) -> + %% Postpone all calls to the connection state + {keep_state_and_data, [postpone]}; +handshake(internal, {application_packets,_,_}, _) -> {keep_state_and_data, [postpone]}; handshake(cast, {new_write, WritesState, Version}, - #data{connection_states = ConnectionStates0} = StateData) -> + #data{connection_states = ConnectionStates, static = Static} = StateData) -> {next_state, connection, - StateData#data{connection_states = - ConnectionStates0#{current_write => WritesState}, - negotiated_version = Version}}; -handshake(internal, {application_packets,_,_}, _) -> + StateData#data{connection_states = ConnectionStates#{current_write => WritesState}, + static = Static#static{negotiated_version = Version}}}; +handshake(info, dist_data, _) -> {keep_state_and_data, [postpone]}; -handshake(info, Msg, StateData) -> - handle_info(Msg, ?FUNCTION_NAME, StateData). +handshake(info, tick, _) -> + %% Ignore - data is sent anyway during handshake + consume_ticks(), + keep_state_and_data; +handshake(info, {send, _, _, _}, _) -> + %% Testing only, OTP distribution test suites... + {keep_state_and_data, [postpone]}; +?HANDLE_COMMON. %%-------------------------------------------------------------------- -spec death_row(gen_statem:event_type(), @@ -361,49 +370,66 @@ code_change(_OldVsn, State, Data, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== -handle_call(From, {set_opts, Opts}, StateName, #data{socket_options = SockOpts} = StateData) -> - {next_state, StateName, StateData#data{socket_options = set_opts(SockOpts, Opts)}, [{reply, From, ok}]}. - -handle_info({'DOWN', Monitor, _, _, Reason}, _, - #data{connection_monitor = Monitor, - dist_handle = Handle} = StateData) when Handle =/= undefined-> - {next_state, death_row, StateData, [{state_timeout, 5000, Reason}]}; -handle_info({'DOWN', Monitor, _, _, _}, _, - #data{connection_monitor = Monitor} = StateData) -> + +handle_set_opts( + From, Opts, #data{static = #static{socket_options = SockOpts} = Static} = StateData) -> + {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}}, + [{reply, From, ok}]}. + +handle_common( + {call, From}, {set_opts, Opts}, + #data{static = #static{socket_options = SockOpts} = Static} = StateData) -> + {keep_state, StateData#data{static = Static#static{socket_options = set_opts(SockOpts, Opts)}}, + [{reply, From, ok}]}; +handle_common( + info, {'DOWN', Monitor, _, _, Reason}, + #data{static = #static{connection_monitor = Monitor, + dist_handle = Handle}} = StateData) when Handle =/= undefined -> + {next_state, death_row, StateData, + [{state_timeout, 5000, Reason}]}; +handle_common( + info, {'DOWN', Monitor, _, _, _}, + #data{static = #static{connection_monitor = Monitor}} = StateData) -> {stop, normal, StateData}; -handle_info(_,_,_) -> +handle_common(info, Msg, _) -> + Report = + io_lib:format("TLS sender: Got unexpected info: ~p ~n", [Msg]), + error_logger:info_report(Report), + keep_state_and_data; +handle_common(Type, Msg, _) -> + Report = + io_lib:format( + "TLS sender: Got unexpected event: ~p ~n", [{Type,Msg}]), + error_logger:error_report(Report), keep_state_and_data. -send_tls_alert(Alert, #data{negotiated_version = Version, - socket = Socket, - protocol_cb = Connection, - transport_cb = Transport, - connection_states = ConnectionStates0} = StateData0) -> +send_tls_alert(#alert{} = Alert, + #data{static = #static{negotiated_version = Version, + socket = Socket, + transport_cb = Transport}, + connection_states = ConnectionStates0} = StateData0) -> {BinMsg, ConnectionStates} = - Connection:encode_alert(Alert, Version, ConnectionStates0), - Connection:send(Transport, Socket, BinMsg), + tls_record:encode_alert_record(Alert, Version, ConnectionStates0), + tls_socket:send(Transport, Socket, BinMsg), StateData0#data{connection_states = ConnectionStates}. send_application_data(Data, From, StateName, - #data{connection_pid = Pid, - socket = Socket, - dist_handle = DistHandle, - negotiated_version = Version, - protocol_cb = Connection, - transport_cb = Transport, - connection_states = ConnectionStates0, - renegotiate_at = RenegotiateAt} = StateData0) -> + #data{static = #static{connection_pid = Pid, + socket = Socket, + dist_handle = DistHandle, + negotiated_version = Version, + transport_cb = Transport, + renegotiate_at = RenegotiateAt}, + connection_states = ConnectionStates0} = StateData0) -> case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of true -> ssl_connection:internal_renegotiation(Pid, ConnectionStates0), {next_state, handshake, StateData0, [{next_event, internal, {application_packets, From, Data}}]}; false -> - {Msgs, ConnectionStates} = - Connection:encode_data( - iolist_to_binary(Data), Version, ConnectionStates0), + {Msgs, ConnectionStates} = tls_record:encode_data(Data, Version, ConnectionStates0), StateData = StateData0#data{connection_states = ConnectionStates}, - case Connection:send(Transport, Socket, Msgs) of + case tls_socket:send(Transport, Socket, Msgs) of ok when DistHandle =/= undefined -> {next_state, StateName, StateData, []}; Reason when DistHandle =/= undefined -> @@ -419,9 +445,9 @@ send_application_data(Data, From, StateName, encode_packet(Packet, Data) -> Len = iolist_size(Data), case Packet of - 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data]; - 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data]; - 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data]; + 1 when Len < (1 bsl 8) -> [<<Len:8>>|Data]; + 2 when Len < (1 bsl 16) -> [<<Len:16>>|Data]; + 4 when Len < (1 bsl 32) -> [<<Len:32>>|Data]; N when N =:= 1; N =:= 2; N =:= 4 -> {error, {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}}; @@ -458,22 +484,30 @@ call(FsmPid, Event) -> {error, closed} end. -%%---------------Erlang distribution -------------------------------------- +%%-------------- Erlang distribution helpers ------------------------------ -dist_data(DHandle, Packet) -> +dist_data(DHandle) -> case erlang:dist_ctrl_get_data(DHandle) of none -> erlang:dist_ctrl_get_data_notification(DHandle), []; - Data -> - %% This is encode_packet(4, Data) without Len check - %% since the emulator will always deliver a Data - %% smaller than 4 GB, and the distribution will - %% therefore always have to use {packet,4} + %% This is encode_packet(4, Data) without Len check + %% since the emulator will always deliver a Data + %% smaller than 4 GB, and the distribution will + %% therefore always have to use {packet,4} + Data when is_binary(Data) -> + Len = byte_size(Data), + [[<<Len:32>>,Data]|dist_data(DHandle)]; + [BA,BB] = Data -> + Len = byte_size(BA) + byte_size(BB), + [[<<Len:32>>|Data]|dist_data(DHandle)]; + Data when is_list(Data) -> Len = iolist_size(Data), - [<<Len:32>>,Data|dist_data(DHandle, Packet)] + [[<<Len:32>>|Data]|dist_data(DHandle)] end. + +%% Empty the inbox from distribution ticks - do not let them accumulate consume_ticks() -> receive tick -> consume_ticks() diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index a391bc53de..c3c41d3e12 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -32,6 +32,7 @@ emulated_socket_options/2, get_emulated_opts/1, set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3]). +-export([update_active_n/2]). -record(state, { emulated_opts, @@ -51,7 +52,9 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _}, case Transport:listen(Port, Options ++ internal_inet_values()) of {ok, ListenSocket} -> {ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts), - {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}}; + Socket = #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}, + check_active_n(EmOpts, Socket), + {ok, Socket}; Err = {error, _} -> Err end. @@ -117,14 +120,16 @@ socket(Pids, Transport, Socket, ConnectionCb, Tracker) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb, Tracker}}. -setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> +setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> {SockOpts, EmulatedOpts} = split_options(Options), ok = set_emulated_opts(Tracker, EmulatedOpts), + check_active_n(EmulatedOpts, Socket), inet:setopts(ListenSocket, SockOpts); -setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}, +setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}, emulated = Tracker}}}, Options) -> {SockOpts, EmulatedOpts} = split_options(Options), ok = set_emulated_opts(Tracker, EmulatedOpts), + check_active_n(EmulatedOpts, Socket), Transport:setopts(ListenSocket, SockOpts); %%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_tcp, Socket, Options) -> @@ -132,6 +137,31 @@ setopts(gen_tcp, Socket, Options) -> setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). +check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tracker}}}) -> + %% We check the resulting options to send an ssl_passive message if necessary. + case proplists:lookup(active, EmulatedOpts) of + %% The provided value is out of bound. + {_, N} when is_integer(N), N < -32768 -> + throw(einval); + {_, N} when is_integer(N), N > 32767 -> + throw(einval); + {_, N} when is_integer(N) -> + case get_emulated_opts(Tracker, [active]) of + [{_, false}] -> + self() ! {ssl_passive, Socket}, + ok; + %% The result of the addition is out of bound. + [{_, A}] when is_integer(A), A < -32768 -> + throw(einval); + [{_, A}] when is_integer(A), A > 32767 -> + throw(einval); + _ -> + ok + end; + _ -> + ok + end. + getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> {SockOptNames, EmulatedOptNames} = split_options(Options), EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames), @@ -209,7 +239,7 @@ start_link(Port, SockOpts, SslOpts) -> init([Port, Opts, SslOpts]) -> process_flag(trap_exit, true), true = link(Port), - {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}. + {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}. %%-------------------------------------------------------------------- -spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. @@ -304,9 +334,24 @@ split_options([Name | Opts], Emu, SocketOptNames, EmuOptNames) -> do_set_emulated_opts([], Opts) -> Opts; +do_set_emulated_opts([{active, N0} | Rest], Opts) when is_integer(N0) -> + N = update_active_n(N0, proplists:get_value(active, Opts, false)), + do_set_emulated_opts(Rest, [{active, N} | proplists:delete(active, Opts)]); do_set_emulated_opts([{Name,_} = Opt | Rest], Opts) -> do_set_emulated_opts(Rest, [Opt | proplists:delete(Name, Opts)]). +update_active_n(New, Current) -> + if + is_integer(Current), New + Current =< 0 -> + false; + is_integer(Current) -> + New + Current; + New =< 0 -> + false; + true -> + New + end. + get_socket_opts(_, [], _) -> []; get_socket_opts(ListenSocket, SockOptNames, Cb) -> @@ -366,6 +411,9 @@ validate_inet_option(header, Value) when not is_integer(Value) -> throw({error, {options, {header,Value}}}); validate_inet_option(active, Value) + when Value >= -32768, Value =< 32767 -> + ok; +validate_inet_option(active, Value) when Value =/= true, Value =/= false, Value =/= once -> throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index 9dfb2eba53..a10f71a3de 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -29,7 +29,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # Application version # ---------------------------------------------------- include ../vsn.mk -VSN=$(GS_VSN) +VSN=$(SSL_VSN) # ---------------------------------------------------- # Target Specs diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index 8fe7c54549..7f3371da9a 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -189,6 +189,18 @@ gencrl(Root, CA, C, CrlHours) -> Env = [{"ROOTDIR", filename:absname(Root)}], cmd(Cmd, Env). +%% This function sets the number of seconds until the next CRL is due. +gencrl_sec(Root, CA, C, CrlSecs) -> + CACnfFile = filename:join([Root, CA, "ca.cnf"]), + CACRLFile = filename:join([Root, CA, "crl.pem"]), + Cmd = [C#config.openssl_cmd, " ca" + " -gencrl ", + " -crlsec ", integer_to_list(CrlSecs), + " -out ", CACRLFile, + " -config ", CACnfFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + can_generate_expired_crls(C) -> %% OpenSSL can generate CRLs with an expiration date in the past, %% if we pass a negative number for -crlhours. However, LibreSSL diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index a5309e866b..ca8d0ec70c 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -212,53 +212,61 @@ client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> ecc_default_order(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + ecdhe_ecdsa, ecdhe_ecdsa, + Config, DefaultCurve), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [], - case ssl_test_lib:supported_eccs([{eccs, [sect571r1]}]) of - true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs([{eccs, [DefaultCurve]}]) of + true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_default_order_custom_curves(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + ecdhe_ecdsa, ecdhe_ecdsa, + Config, DefaultCurve), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{eccs, [secp256r1, sect571r1]}], + ECCOpts = [{eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of - true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_client_order(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + ecdhe_ecdsa, ecdhe_ecdsa, + Config, DefaultCurve), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, false}], - case ssl_test_lib:supported_eccs([{eccs, [sect571r1]}]) of - true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs([{eccs, [DefaultCurve]}]) of + true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_client_order_custom_curves(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + ecdhe_ecdsa, ecdhe_ecdsa, + Config, DefaultCurve), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of - true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + true -> ssl_test_lib:ecc_test(DefaultCurve, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. @@ -274,12 +282,13 @@ ecc_unknown_curve(Config) -> client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdh_rsa, ecdhe_ecdsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} @@ -287,12 +296,13 @@ client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) -> client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdh_rsa, ecdhe_rsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); @@ -301,12 +311,13 @@ client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) -> client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_rsa, ecdhe_ecdsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} @@ -314,19 +325,21 @@ client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) -> client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_rsa, ecdhe_rsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, [[], [], [{extensions, Ext}]]}, {client_chain, Default}], @@ -334,8 +347,8 @@ client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) -> COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - Expected = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), %% The certificate curve + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], + Expected = secp256r1, %% The certificate curve case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(Expected, COpts, SOpts, [], ECCOpts, Config); @@ -344,12 +357,13 @@ client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) -> client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_ecdsa, ecdhe_ecdsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} @@ -357,12 +371,13 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) -> client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_ecdsa, ecdhe_rsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} @@ -370,12 +385,13 @@ client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) -> client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_ecdsa, ecdhe_ecdsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{eccs, [secp256r1, sect571r1]}], + ECCOpts = [{eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); false -> {skip, "unsupported named curves"} @@ -383,12 +399,13 @@ client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) -> client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), + DefaultCurve = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_rsa, ecdhe_ecdsa, Config), COpts = ssl_test_lib:ssl_options(COpts0, Config), SOpts = ssl_test_lib:ssl_options(SOpts0, Config), - ECCOpts = [{eccs, [secp256r1, sect571r1]}], + ECCOpts = [{eccs, [secp256r1, DefaultCurve]}], case ssl_test_lib:supported_eccs(ECCOpts) of true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); false -> {skip, "unsupported named curves"} diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl index 04c4b257d9..dfc780479e 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl @@ -153,41 +153,41 @@ protocols_must_be_a_binary_list(Config) when is_list(Config) -> empty_client(Config) when is_list(Config) -> run_failing_handshake(Config, - [{alpn_advertised_protocols, []}], - [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], - {error,{tls_alert,"no application protocol"}}). + [{alpn_advertised_protocols, []}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], + no_application_protocol). %-------------------------------------------------------------------------------- empty_server(Config) when is_list(Config) -> run_failing_handshake(Config, - [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], - [{alpn_preferred_protocols, []}], - {error,{tls_alert,"no application protocol"}}). + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, []}], + no_application_protocol). %-------------------------------------------------------------------------------- empty_client_empty_server(Config) when is_list(Config) -> run_failing_handshake(Config, - [{alpn_advertised_protocols, []}], - [{alpn_preferred_protocols, []}], - {error,{tls_alert,"no application protocol"}}). + [{alpn_advertised_protocols, []}], + [{alpn_preferred_protocols, []}], + no_application_protocol). %-------------------------------------------------------------------------------- no_matching_protocol(Config) when is_list(Config) -> run_failing_handshake(Config, - [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], - [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], - {error,{tls_alert,"no application protocol"}}). + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], + no_application_protocol). %-------------------------------------------------------------------------------- client_alpn_and_server_alpn(Config) when is_list(Config) -> run_handshake(Config, - [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], - [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], - {ok, <<"http/1.1">>}). + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). %-------------------------------------------------------------------------------- @@ -262,52 +262,12 @@ client_renegotiate(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- session_reused(Config) when is_list(Config)-> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0, ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, - {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, session_info_result, []}}, - {options, 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, no_result_msg, []}}, - {options, ClientOpts}]), - - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - - receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:fail(Other) - end, - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). %-------------------------------------------------------------------------------- alpn_not_supported_client(Config) when is_list(Config) -> @@ -337,7 +297,7 @@ alpn_not_supported_server(Config) when is_list(Config)-> %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- -run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult) -> +run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedAlert) -> ClientOpts = ClientExtraOpts ++ ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ServerExtraOpts ++ ssl_test_lib:ssl_options(server_rsa_opts, Config), @@ -353,8 +313,7 @@ run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult) {from, self()}, {mfa, {?MODULE, placeholder, []}}, {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ExpectedResult, - Client, ExpectedResult). + ssl_test_lib:check_client_alert(Server, Client, ExpectedAlert). run_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> Data = "hello world", diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 90fcde609f..e86d2eaf9e 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -164,8 +164,10 @@ api_tests() -> accept_pool, prf, socket_options, + active_n, cipher_suites, handshake_continue, + handshake_continue_timeout, hello_client_cancel, hello_server_cancel ]. @@ -242,6 +244,7 @@ error_handling_tests()-> [close_transport_accept, recv_active, recv_active_once, + recv_active_n, recv_error_handling, call_in_error_state, close_in_error_state, @@ -654,8 +657,8 @@ new_options_in_accept(Config) when is_list(Config) -> handshake_continue() -> [{doc, "Test API function ssl:handshake_continue/3"}]. handshake_continue(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -681,6 +684,34 @@ handshake_continue(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). +%%------------------------------------------------------------------ +handshake_continue_timeout() -> + [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. +handshake_continue_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 1}, + {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + + {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, {error,timeout}), + ssl_test_lib:close(Server). + + %%-------------------------------------------------------------------- hello_client_cancel() -> [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. @@ -702,19 +733,12 @@ hello_client_cancel(Config) when is_list(Config) -> {from, self()}, {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, {continue_options, cancel}]), - receive - {Server, {error, {tls_alert, "user canceled"}}} -> - ok; - {Server, {error, closed}} -> - ct:pal("Did not receive the ALERT"), - ok - end. - + ssl_test_lib:check_server_alert(Server, user_canceled). %%-------------------------------------------------------------------- hello_server_cancel() -> [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. hello_server_cancel(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -756,8 +780,8 @@ prf(Config) when is_list(Config) -> secret_connection_info() -> [{doc,"Test the API function ssl:connection_information/2"}]. secret_connection_info(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1183,9 +1207,8 @@ fallback(Config) when is_list(Config) -> [{fallback, true}, {versions, ['tlsv1']} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, {error,{tls_alert,"inappropriate fallback"}}, - Client, {error,{tls_alert,"inappropriate fallback"}}). + ssl_test_lib:check_server_alert(Server, Client, inappropriate_fallback). + %%-------------------------------------------------------------------- cipher_format() -> @@ -1446,8 +1469,8 @@ cipher_suites_mix() -> cipher_suites_mix(Config) when is_list(Config) -> CipherSuites = [{dhe_rsa,aes_128_cbc,sha256,sha256}, {dhe_rsa,aes_128_cbc,sha}], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1957,7 +1980,7 @@ recv_active(Config) when is_list(Config) -> %%-------------------------------------------------------------------- recv_active_once() -> - [{doc,"Test recv on active socket"}]. + [{doc,"Test recv on active (once) socket"}]. recv_active_once(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), @@ -1982,6 +2005,178 @@ recv_active_once(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +recv_active_n() -> + [{doc,"Test recv on active (n) socket"}]. + +recv_active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_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, try_recv_active_once, []}}, + {options, [{active, 1} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +%% Test case adapted from gen_tcp_misc_SUITE. +active_n() -> + [{doc,"Test {active,N} option"}]. + +active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + Port = ssl_test_lib:inet_port(node()), + N = 3, + LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), + [{active,N}] = ok(ssl:getopts(LS, [active])), + active_n_common(LS, N), + Self = self(), + spawn_link(fun() -> + S0 = ok(ssl:transport_accept(LS)), + {ok, S} = ssl:handshake(S0), + ok = ssl:setopts(S, [{active,N}]), + [{active,N}] = ok(ssl:getopts(S, [active])), + ssl:controlling_process(S, Self), + Self ! {server, S} + end), + C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), + [{active,N}] = ok(ssl:getopts(C, [active])), + S = receive + {server, S0} -> S0 + after + 1000 -> + exit({error, connect}) + end, + active_n_common(C, N), + active_n_common(S, N), + ok = ssl:setopts(C, [{active,N}]), + ok = ssl:setopts(S, [{active,N}]), + ReceiveMsg = fun(Socket, Msg) -> + receive + {ssl,Socket,Msg} -> + ok; + {ssl,Socket,Begin} -> + receive + {ssl,Socket,End} -> + Msg = Begin ++ End, + ok + after 1000 -> + exit(timeout) + end + after 1000 -> + exit(timeout) + end + end, + repeat(3, fun(I) -> + Msg = "message "++integer_to_list(I), + ok = ssl:send(C, Msg), + ReceiveMsg(S, Msg), + ok = ssl:send(S, Msg), + ReceiveMsg(C, Msg) + end), + receive + {ssl_passive,S} -> + [{active,false}] = ok(ssl:getopts(S, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + receive + {ssl_passive,C} -> + [{active,false}] = ok(ssl:getopts(C, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + LS2 = ok(ssl:listen(0, [{active,0}])), + receive + {ssl_passive,LS2} -> + [{active,false}] = ok(ssl:getopts(LS2, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + ok = ssl:close(LS2), + ok = ssl:close(C), + ok = ssl:close(S), + ok = ssl:close(LS), + ok. + +active_n_common(S, N) -> + ok = ssl:setopts(S, [{active,-N}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,0}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + ok = ssl:setopts(S, [{active,32767}]), + {error,{options,_}} = ssl:setopts(S, [{active,1}]), + {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), + ok = ssl:setopts(S, [{active,-32768}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,true}]), + [{active,true}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,once}]), + [{active,once}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + {error,{options,_}} = ssl:setopts(S, [{active,32768}]), + ok = ssl:setopts(S, [{active,false}]), + [{active,false}] = ok(ssl:getopts(S, [active])), + ok. + +ok({ok,V}) -> V. + +repeat(N, Fun) -> + repeat(N, N, Fun). + +repeat(N, T, Fun) when is_integer(N), N > 0 -> + Fun(T-N), + repeat(N-1, T, Fun); +repeat(_, _, _) -> + ok. + +%%-------------------------------------------------------------------- dh_params() -> [{doc,"Test to specify DH-params file in server."}]. @@ -2358,8 +2553,8 @@ invalid_options() -> [{doc,"Test what happens when we give invalid options"}]. invalid_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> @@ -2374,27 +2569,28 @@ invalid_options(Config) when is_list(Config) -> {error, {options, Option}}) end, - TestOpts = [{versions, [sslv2, sslv3]}, - {verify, 4}, - {verify_fun, function}, - {fail_if_no_peer_cert, 0}, - {verify_client_once, 1}, - {depth, four}, - {certfile, 'cert.pem'}, - {keyfile,'key.pem' }, - {password, foo}, - {cacertfile, ""}, - {dhfile,'dh.pem' }, - {ciphers, [{foo, bar, sha, ignore}]}, - {reuse_session, foo}, - {reuse_sessions, 0}, - {renegotiate_at, "10"}, - {mode, depech}, - {packet, 8.0}, - {packet_size, "2"}, - {header, a}, - {active, trice}, - {key, 'key.pem' }], + TestOpts = + [{versions, [sslv2, sslv3]}, + {verify, 4}, + {verify_fun, function}, + {fail_if_no_peer_cert, 0}, + {verify_client_once, 1}, + {depth, four}, + {certfile, 'cert.pem'}, + {keyfile,'key.pem' }, + {password, foo}, + {cacertfile, ""}, + {dhfile,'dh.pem' }, + {ciphers, [{foo, bar, sha, ignore}]}, + {reuse_session, foo}, + {reuse_sessions, 0}, + {renegotiate_at, "10"}, + {mode, depech}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, + {active, trice}, + {key, 'key.pem' }], [begin Server = @@ -2650,8 +2846,7 @@ default_reject_anonymous(Config) when is_list(Config) -> [{ciphers,[CipherSuite]} | ClientOpts]}]), - ssl_test_lib:check_result(Server, {error, {tls_alert, "insufficient security"}}, - Client, {error, {tls_alert, "insufficient security"}}). + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). %%-------------------------------------------------------------------- ciphers_ecdsa_signed_certs() -> @@ -2687,175 +2882,69 @@ ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> reuse_session() -> [{doc,"Test reuse of sessions (short handshake)"}]. reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_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, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:log("Expected: ~p, Unexpected: ~p~n", - [SessionInfo, Other]), - ct:fail(session_not_reused) - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, [{reuse_sessions, false} - | ClientOpts]}]), - receive - {Client2, SessionInfo} -> - ct:fail( - session_reused_when_session_reuse_disabled_by_client); - {Client2, _} -> - ok - end, - - ssl_test_lib:close(Server), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, [{reuse_sessions, false} | ServerOpts]}]), - - Port1 = ssl_test_lib:inet_port(Server1), - Client3 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - SessionInfo1 = - receive - {Server1, Info1} -> - Info1 - end, - - Server1 ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client4 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - receive - {Client4, SessionInfo1} -> - ct:fail( - session_reused_when_session_reuse_disabled_by_server); - {Client4, _Other} -> - ct:log("OTHER: ~p ~n", [_Other]), - ok - end, - - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2), - ssl_test_lib:close(Client3), - ssl_test_lib:close(Client4). - + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). %%-------------------------------------------------------------------- reuse_session_expired() -> [{doc,"Test sessions is not reused when it has expired"}]. reuse_session_expired(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + Server0 ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:log("Expected: ~p, Unexpected: ~p~n", - [SessionInfo, Other]), - ct:fail(session_not_reused) + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) end, - Server ! listen, - + Server0 ! listen, + %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE+1)), - [{session_id, Id} |_] = SessionInfo, + ct:sleep((?EXPIRE*2)), - make_sure_expired(Hostname, Port, Id), + make_sure_expired(Hostname, Port0, SID), Client2 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, {from, self()}, {options, ClientOpts}]), receive - {Client2, SessionInfo} -> + {Client2, SID} -> ct:fail(session_reused_when_session_expired); {Client2, _} -> ok end, process_flag(trap_exit, false), - ssl_test_lib:close(Server), + ssl_test_lib:close(Server0), ssl_test_lib:close(Client0), ssl_test_lib:close(Client1), ssl_test_lib:close(Client2). @@ -2864,16 +2953,16 @@ make_sure_expired(Host, Port, Id) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Cache = element(2, State), + ClientCache = element(2, State), - case ssl_session_cache:lookup(Cache, {{Host, Port}, Id}) of + case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of undefined -> - ok; + ok; #session{is_resumable = false} -> - ok; + ok; _ -> ct:sleep(?SLEEP), - make_sure_expired(Host, Port, Id) + make_sure_expired(Host, Port, Id) end. %%-------------------------------------------------------------------- @@ -3609,8 +3698,7 @@ no_common_signature_algs(Config) when is_list(Config) -> {options, [{signature_algs, [{sha384, rsa}]} | ClientOpts]}]), - ssl_test_lib:check_result(Server, {error, {tls_alert, "insufficient security"}}, - Client, {error, {tls_alert, "insufficient security"}}). + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). %%-------------------------------------------------------------------- @@ -3641,7 +3729,7 @@ tls_dont_crash_on_handshake_garbage(Config) -> <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage ]), % Send unexpected change_cipher_spec - ok = gen_tcp:send(Socket, <<20, 0,0,12, 111,40,244,7,137,224,16,109,197,110,249,152>>), + ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), % Ensure we receive an alert, not sudden disconnect {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). @@ -4169,6 +4257,9 @@ rizzo_one_n_minus_one(Config) when is_list(Config) -> {cipher, fun(rc4_128) -> false; + %% TODO: remove this clause when chacha is fixed! + (chacha20_poly1305) -> + false; (_) -> true end}]), @@ -4310,8 +4401,7 @@ tls_versions_option(Config) when is_list(Config) -> {Server, _} -> ok end, - - ssl_test_lib:check_result(ErrClient, {error, {tls_alert, "protocol version"}}). + ssl_test_lib:check_client_alert(ErrClient, protocol_version). %%-------------------------------------------------------------------- @@ -4486,8 +4576,8 @@ tcp_send_recv_result(Socket) -> ok. basic_verify_test_no_close(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -4962,16 +5052,16 @@ run_suites(Ciphers, Config, Type) -> {ClientOpts, ServerOpts} = case Type of rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_opts, Config)]}; dsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_dsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_dsa_opts, Config)]}; anonymous -> %% No certs in opts! - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options([], Config)]}; psk -> @@ -4993,46 +5083,50 @@ run_suites(Ciphers, Config, Type) -> ssl_test_lib:ssl_options(server_psk_anon_hint, Config)]}; srp -> {ssl_test_lib:ssl_options(client_srp, Config), - ssl_test_lib:ssl_options(server_srp, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_srp, Config)]}; srp_anon -> {ssl_test_lib:ssl_options(client_srp, Config), - ssl_test_lib:ssl_options(server_srp_anon, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_srp_anon, Config)]}; srp_dsa -> {ssl_test_lib:ssl_options(client_srp_dsa, Config), - ssl_test_lib:ssl_options(server_srp_dsa, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_srp_dsa, Config)]}; ecdsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_ecdsa_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; ecdh_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}; + {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; rc4_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; rc4_ecdh_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; rc4_ecdsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; des_dhe_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_verification_opts, Config)]}; des_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; chacha_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; chacha_ecdsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_ecdsa_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]} end, diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 588ca153a9..c0a5367a57 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -298,15 +298,8 @@ server_require_peer_cert_fail(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {options, [{active, Active} | BadClientOpts]}]), - receive - {Server, {error, {tls_alert, "handshake failure"}}} -> - receive - {Client, {error, {tls_alert, "handshake failure"}}} -> - ok; - {Client, {error, closed}} -> - ok - end - end. + + ssl_test_lib:check_server_alert(Server, Client, handshake_failure). %%-------------------------------------------------------------------- server_require_peer_cert_empty_ok() -> @@ -365,15 +358,8 @@ server_require_peer_cert_partial_chain(Config) when is_list(Config) -> {options, [{active, Active}, {cacerts, [RootCA]} | proplists:delete(cacertfile, ClientOpts)]}]), - receive - {Server, {error, {tls_alert, "unknown ca"}}} -> - receive - {Client, {error, {tls_alert, "unknown ca"}}} -> - ok; - {Client, {error, closed}} -> - ok - end - end. + ssl_test_lib:check_server_alert(Server, Client, unknown_ca). + %%-------------------------------------------------------------------- server_require_peer_cert_allow_partial_chain() -> [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. @@ -446,17 +432,7 @@ server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), - - receive - {Server, {error, {tls_alert, "unknown ca"}}} -> - receive - {Client, {error, {tls_alert, "unknown ca"}}} -> - ok; - {Client, {error, closed}} -> - ok - end - end. - + ssl_test_lib:check_server_alert(Server, Client, unknown_ca). %%-------------------------------------------------------------------- server_require_peer_cert_partial_chain_fun_fail() -> [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. @@ -487,16 +463,7 @@ server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), - - receive - {Server, {error, {tls_alert, "unknown ca"}}} -> - receive - {Client, {error, {tls_alert, "unknown ca"}}} -> - ok; - {Client, {error, closed}} -> - ok - end - end. + ssl_test_lib:check_server_alert(Server, Client, unknown_ca). %%-------------------------------------------------------------------- verify_fun_always_run_client() -> @@ -535,14 +502,8 @@ verify_fun_always_run_client(Config) when is_list(Config) -> [{verify, verify_peer}, {verify_fun, FunAndState} | ClientOpts]}]), - %% Server error may be {tls_alert,"handshake failure"} or closed depending on timing - %% this is not a bug it is a circumstance of how tcp works! - receive - {Server, ServerError} -> - ct:log("Server Error ~p~n", [ServerError]) - end, - ssl_test_lib:check_result(Client, {error, {tls_alert, "handshake failure"}}). + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). %%-------------------------------------------------------------------- verify_fun_always_run_server() -> @@ -581,16 +542,8 @@ verify_fun_always_run_server(Config) when is_list(Config) -> {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts}]), - - %% Client error may be {tls_alert, "handshake failure" } or closed depending on timing - %% this is not a bug it is a circumstance of how tcp works! - receive - {Client, ClientError} -> - ct:log("Client Error ~p~n", [ClientError]) - end, - - ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}). - + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). %%-------------------------------------------------------------------- cert_expired() -> @@ -620,8 +573,7 @@ cert_expired(Config) when is_list(Config) -> {from, self()}, {options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]), - ssl_test_lib:check_result(Server, {error, {tls_alert, "certificate expired"}}, - Client, {error, {tls_alert, "certificate expired"}}). + ssl_test_lib:check_client_alert(Server, Client, certificate_expired). two_digits_str(N) when N < 10 -> lists:flatten(io_lib:format("0~p", [N])); @@ -727,12 +679,8 @@ critical_extension_verify_server(Config) when is_list(Config) -> {options, [{verify, verify_none}, {active, Active} | ClientOpts]}]), %% This certificate has a critical extension that we don't - %% understand. Therefore, verification should fail. - - ssl_test_lib:check_result(Server, {error, {tls_alert, "unsupported certificate"}}, - Client, {error, {tls_alert, "unsupported certificate"}}), - - ssl_test_lib:close(Server). + %% understand. Therefore, verification should fail. + ssl_test_lib:check_server_alert(Server, Client, unsupported_certificate). %%-------------------------------------------------------------------- critical_extension_verify_client() -> @@ -763,12 +711,7 @@ critical_extension_verify_client(Config) when is_list(Config) -> {mfa, {ssl_test_lib, ReceiveFunction, []}}, {options, [{verify, verify_peer}, {active, Active} | ClientOpts]}]), - %% This certificate has a critical extension that we don't - %% understand. Therefore, verification should fail. - ssl_test_lib:check_result(Server, {error, {tls_alert, "unsupported certificate"}}, - Client, {error, {tls_alert, "unsupported certificate"}}), - - ssl_test_lib:close(Server). + ssl_test_lib:check_client_alert(Server, Client, unsupported_certificate). %%-------------------------------------------------------------------- critical_extension_verify_none() -> @@ -908,10 +851,7 @@ invalid_signature_server(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {options, [{verify, verify_peer} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, {error, {tls_alert, "unknown ca"}}, - Client, {error, {tls_alert, "unknown ca"}}). - + ssl_test_lib:check_server_alert(Server, Client, unknown_ca). %%-------------------------------------------------------------------- invalid_signature_client() -> @@ -946,9 +886,7 @@ invalid_signature_client(Config) when is_list(Config) -> {from, self()}, {options, NewClientOpts}]), - ssl_test_lib:check_result(Server, {error, {tls_alert, "unknown ca"}}, - Client, {error, {tls_alert, "unknown ca"}}). - + ssl_test_lib:check_client_alert(Server, Client, unknown_ca). %%-------------------------------------------------------------------- @@ -1034,16 +972,7 @@ unknown_server_ca_fail(Config) when is_list(Config) -> [{verify, verify_peer}, {verify_fun, FunAndState} | ClientOpts]}]), - receive - {Client, {error, {tls_alert, "unknown ca"}}} -> - receive - {Server, {error, {tls_alert, "unknown ca"}}} -> - ok; - {Server, {error, closed}} -> - ok - end - end. - + ssl_test_lib:check_client_alert(Server, Client, unknown_ca). %%-------------------------------------------------------------------- unknown_server_ca_accept_verify_none() -> @@ -1193,11 +1122,7 @@ customize_hostname_check(Config) when is_list(Config) -> {mfa, {ssl_test_lib, no_result, []}}, {options, ClientOpts} ]), - ssl_test_lib:check_result(Client1, {error, {tls_alert, "handshake failure"}}, - Server, {error, {tls_alert, "handshake failure"}}), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). incomplete_chain() -> [{doc,"Test option verify_peer"}]. diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index 23c5eaf84d..b2fd3874a8 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -238,7 +238,7 @@ crl_verify_revoked(Config) when is_list(Config) -> end, crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, - "certificate revoked"). + certificate_revoked). crl_verify_no_crl() -> [{doc,"Verify a simple CRL chain when the CRL is missing"}]. @@ -277,10 +277,10 @@ crl_verify_no_crl(Config) when is_list(Config) -> %% The error "revocation status undetermined" gets turned %% into "bad certificate". crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, - "bad certificate"); + bad_certificate); peer -> crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, - "bad certificate"); + bad_certificate); best_effort -> %% In "best effort" mode, we consider the certificate not %% to be revoked if we can't find the appropriate CRL. @@ -341,7 +341,7 @@ crl_hash_dir_collision(Config) when is_list(Config) -> %% First certificate revoked; first fails, second succeeds. crl_verify_error(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts, - "certificate revoked"), + certificate_revoked), crl_verify_valid(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts), make_certs:revoke(PrivDir, CA2, "collision-client-2", CertsConfig), @@ -352,9 +352,9 @@ crl_hash_dir_collision(Config) when is_list(Config) -> %% Second certificate revoked; both fail. crl_verify_error(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts, - "certificate revoked"), + certificate_revoked), crl_verify_error(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts, - "certificate revoked"), + certificate_revoked), ok. @@ -383,8 +383,11 @@ crl_hash_dir_expired(Config) when is_list(Config) -> {verify, verify_peer}], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - %% First make a CRL that expired yesterday. - make_certs:gencrl(PrivDir, CA, CertsConfig, -24), + %% First make a CRL that will expire in one second. + make_certs:gencrl_sec(PrivDir, CA, CertsConfig, 1), + %% Sleep until the next CRL is due + ct:sleep({seconds, 1}), + CrlDir = filename:join(PrivDir, "crls"), populate_crl_hash_dir(PrivDir, CrlDir, [{CA, "1627b4b0"}], @@ -397,10 +400,10 @@ crl_hash_dir_expired(Config) when is_list(Config) -> %% The error "revocation status undetermined" gets turned %% into "bad certificate". crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, - "bad certificate"); + bad_certificate); peer -> crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, - "bad certificate"); + bad_certificate); best_effort -> %% In "best effort" mode, we consider the certificate not %% to be revoked if we can't find the appropriate CRL. @@ -448,11 +451,8 @@ crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, Expec {host, Hostname}, {from, self()}, {options, ClientOpts}]), - receive - {Server, AlertOrClose} -> - ct:pal("Server Alert or Close ~p", [AlertOrClose]) - end, - ssl_test_lib:check_result(Client, {error, {tls_alert, ExpectedAlert}}). + + ssl_test_lib:check_client_alert(Server, Client, ExpectedAlert). %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl index 7409b69639..618ad0789e 100644 --- a/lib/ssl/test/ssl_dist_bench_SUITE.erl +++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl @@ -1,7 +1,7 @@ %%%------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2017-2018. All Rights Reserved. +%% Copyright Ericsson AB 2017-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ throughput_1048576/1]). %% Debug --export([payload/1]). +-export([payload/1, roundtrip_runner/3, setup_runner/3, throughput_runner/4]). %%%------------------------------------------------------------------- @@ -407,17 +407,19 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) -> [] = ssl_apply(HA, erlang, nodes, []), [] = ssl_apply(HB, erlang, nodes, []), #{time := Time, - dist_stats := DistStats, + client_dist_stats := ClientDistStats, client_msacc_stats := ClientMsaccStats, client_prof := ClientProf, server_msacc_stats := ServerMsaccStats, - server_prof := ServerProf} = + server_prof := ServerProf, + server_gc_before := Server_GC_Before, + server_gc_after := Server_GC_After} = ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end), [B] = ssl_apply(HA, erlang, nodes, []), [A] = ssl_apply(HB, erlang, nodes, []), ClientMsaccStats =:= undefined orelse msacc:print(ClientMsaccStats), - io:format("DistStats: ~p~n", [DistStats]), + io:format("ClientDistStats: ~p~n", [ClientDistStats]), Overhead = 50 % Distribution protocol headers (empirical) (TLS+=54) + byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead @@ -436,6 +438,8 @@ throughput(A, B, Prefix, HA, HB, Packets, Size) -> end, io:format("******* ClientProf:~n", []), prof_print(ClientProf), io:format("******* ServerProf:~n", []), prof_print(ServerProf), + io:format("******* Server GC Before:~n~p~n", [Server_GC_Before]), + io:format("******* Server GC After:~n~p~n", [Server_GC_After]), Speed = round((Bytes * 1000000) / (1024 * Time)), report(Prefix++" Throughput_"++integer_to_list(Size), Speed, "kB/s"). @@ -457,10 +461,10 @@ throughput_runner(A, B, Rounds, Size) -> ok end, prof_start(), - {Time,ServerMsaccStats,ServerProf} = + #{time := Time} = Result = throughput_client(ServerPid, ServerMon, Payload, Rounds), prof_stop(), - ClientMsaccStats = + MsaccStats = case msacc:available() of true -> MStats = msacc:stats(), @@ -469,15 +473,13 @@ throughput_runner(A, B, Rounds, Size) -> false -> undefined end, - ClientProf = prof_end(), + Prof = prof_end(), [{_Node,Socket}] = dig_dist_node_sockets(), DistStats = inet:getstat(Socket), - #{time => microseconds(Time), - dist_stats => DistStats, - client_msacc_stats => ClientMsaccStats, - client_prof => ClientProf, - server_msacc_stats => ServerMsaccStats, - server_prof => ServerProf}. + Result#{time := microseconds(Time), + client_dist_stats => DistStats, + client_msacc_stats => MsaccStats, + client_prof => Prof}. dig_dist_node_sockets() -> [case DistCtrl of @@ -500,6 +502,9 @@ dig_dist_node_sockets() -> throughput_server(Pid, N) -> + GC_Before = get_server_gc_info(), + %% dbg:tracer(port, dbg:trace_port(file, "throughput_server_gc.log")), + %% dbg:p(TLSDistReceiver, garbage_collection), msacc:available() andalso begin msacc:stop(), @@ -508,9 +513,9 @@ throughput_server(Pid, N) -> ok end, prof_start(), - throughput_server_loop(Pid, N). + throughput_server_loop(Pid, GC_Before, N). -throughput_server_loop(_Pid, 0) -> +throughput_server_loop(_Pid, GC_Before, 0) -> prof_stop(), MsaccStats = case msacc:available() of @@ -523,11 +528,26 @@ throughput_server_loop(_Pid, 0) -> undefined end, Prof = prof_end(), - exit({ok,MsaccStats,Prof}); -throughput_server_loop(Pid, N) -> + %% dbg:flush_trace_port(), + exit(#{server_msacc_stats => MsaccStats, + server_prof => Prof, + server_gc_before => GC_Before, + server_gc_after => get_server_gc_info()}); +throughput_server_loop(Pid, GC_Before, N) -> receive {Pid, N, _} -> - throughput_server_loop(Pid, N-1) + throughput_server_loop(Pid, GC_Before, N-1) + end. + +get_server_gc_info() -> + case whereis(ssl_connection_sup_dist) of + undefined -> + undefined; + SupPid -> + [{_Id,TLSDistReceiver,_Type,_Modules}|_] = + supervisor:which_children(SupPid), + erlang:process_info( + TLSDistReceiver, [garbage_collection,garbage_collection_info]) end. throughput_client(Pid, Mon, Payload, N) -> @@ -535,8 +555,8 @@ throughput_client(Pid, Mon, Payload, N) -> throughput_client_loop(_Pid, Mon, _Payload, 0, StartTime) -> receive - {'DOWN', Mon, _, _, {ok,MsaccStats,Prof}} -> - {elapsed_time(StartTime),MsaccStats,Prof}; + {'DOWN', Mon, _, _, #{} = Result} -> + Result#{time => elapsed_time(StartTime)}; {'DOWN', Mon, _, _, Other} -> exit(Other) end; @@ -554,6 +574,7 @@ prof_start() -> ok. -elif(?prof =:= eprof). prof_start() -> + catch eprof:stop(), {ok,_} = eprof:start(), profiling = eprof:start_profiling(processes()), ok. diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl index 1c7d6b5f9f..878e983bb9 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -64,13 +64,12 @@ next_protocol_not_supported() -> npn_not_supported_server ]. -init_per_suite(Config) -> +init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> ssl_test_lib:clean_start(), - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), - proplists:get_value(priv_dir, Config)), + Config = ssl_test_lib:make_rsa_cert(Config0), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -196,10 +195,10 @@ client_negotiate_server_does_not_support(Config) when is_list(Config) -> renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> Data = "hello world", - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, ExpectedProtocol = {ok, <<"http/1.0">>}, @@ -221,7 +220,7 @@ renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- npn_not_supported_client(Config) when is_list(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), PrefProtocols = {client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}, ClientOpts = [PrefProtocols] ++ ClientOpts0, @@ -236,7 +235,7 @@ npn_not_supported_client(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- npn_not_supported_server(Config) when is_list(Config)-> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, ServerOpts = [AdvProtocols] ++ ServerOpts0, @@ -244,63 +243,24 @@ npn_not_supported_server(Config) when is_list(Config)-> %-------------------------------------------------------------------------------- npn_handshake_session_reused(Config) when is_list(Config)-> - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts =[{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, - {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, session_info_result, []}}, - {options, 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, no_result_msg, []}}, - {options, ClientOpts}]), - - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - - receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:fail(Other) - end, + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> Data = "hello world", - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = ClientExtraOpts ++ ClientOpts0, - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts = ServerExtraOpts ++ ServerOpts0, {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index 0f5a041a1b..27b9c258a0 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -574,50 +574,42 @@ client_active_once_server_close( [{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, active_once_recv, [Length]}}, + {mfa, {ssl_test_lib, active_once_recv, [Length]}}, {options,[{active, once}, {mode, binary} | ClientOpts]}]), %% ssl_test_lib:check_result(Server, ok, Client, ok). -send(Socket, Data, Count, Verify) -> - send(Socket, Data, Count, <<>>, Verify). -%% -send(_Socket, _Data, 0, Acc, _Verify) -> - Acc; -send(Socket, Data, Count, Acc, Verify) -> +send(_Socket, _Data, 0, _) -> + ok; +send(Socket, Data, Count, RecvEcho) -> ok = ssl:send(Socket, Data), - NewAcc = Verify(Acc), - send(Socket, Data, Count - 1, NewAcc, Verify). - + RecvEcho(), + send(Socket, Data, Count - 1, RecvEcho). send_close(Socket, Data) -> ok = ssl:send(Socket, Data), ssl:close(Socket). - + sender(Socket, Data) -> ct:log("Sender recv: ~p~n", [ssl:getopts(Socket, [active])]), - <<>> = - send( - Socket, Data, 100, - fun(Acc) -> verify_recv(Socket, Data, Acc) end), - ok. + send(Socket, Data, 100, + fun() -> + ssl_test_lib:recv_disregard(Socket, byte_size(Data)) + end). sender_active_once(Socket, Data) -> ct:log("Sender active once: ~p~n", [ssl:getopts(Socket, [active])]), - <<>> = - send( - Socket, Data, 100, - fun(Acc) -> verify_active_once(Socket, Data, Acc) end), - ok. + send(Socket, Data, 100, + fun() -> + ssl_test_lib:active_once_disregard(Socket, byte_size(Data)) + end). sender_active(Socket, Data) -> ct:log("Sender active: ~p~n", [ssl:getopts(Socket, [active])]), - <<>> = - send( - Socket, Data, 100, - fun(Acc) -> verify_active(Socket, Data, Acc) end), - ok. - + send(Socket, Data, 100, + fun() -> + ssl_test_lib:active_disregard(Socket, byte_size(Data)) + end). echoer(Socket, Size) -> ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]), @@ -633,108 +625,32 @@ echoer_active(Socket, Size) -> %% Receive Size bytes +echo_recv(_Socket, 0) -> + ok; echo_recv(Socket, Size) -> {ok, Data} = ssl:recv(Socket, 0), ok = ssl:send(Socket, Data), - NewSize = Size - byte_size(Data), - if - 0 < NewSize -> - echo_recv(Socket, NewSize); - 0 == NewSize -> - ok - end. - -%% Verify that received data is SentData, return any superflous data -verify_recv(Socket, SentData, Acc) -> - {ok, NewData} = ssl:recv(Socket, 0), - SentSize = byte_size(SentData), - NewAcc = <<Acc/binary, NewData/binary>>, - NewSize = byte_size(NewAcc), - if - SentSize < NewSize -> - {SentData,Rest} = split_binary(NewAcc, SentSize), - Rest; - NewSize < SentSize -> - verify_recv(Socket, SentData, NewAcc); - true -> - SentData = NewAcc, - <<>> - end. + echo_recv(Socket, Size - byte_size(Data)). %% Receive Size bytes +echo_active_once(_Socket, 0) -> + ok; echo_active_once(Socket, Size) -> receive {ssl, Socket, Data} -> ok = ssl:send(Socket, Data), NewSize = Size - byte_size(Data), ssl:setopts(Socket, [{active, once}]), - if - 0 < NewSize -> - echo_active_once(Socket, NewSize); - 0 == NewSize -> - ok - end + echo_active_once(Socket, NewSize) end. -%% Verify that received data is SentData, return any superflous data -verify_active_once(Socket, SentData, Acc) -> - receive - {ssl, Socket, Data} -> - SentSize = byte_size(SentData), - NewAcc = <<Acc/binary, Data/binary>>, - NewSize = byte_size(NewAcc), - ssl:setopts(Socket, [{active, once}]), - if - SentSize < NewSize -> - {SentData,Rest} = split_binary(NewAcc, SentSize), - Rest; - NewSize < SentSize -> - verify_active_once(Socket, SentData, NewAcc); - true -> - SentData = NewAcc, - <<>> - end - end. - - %% Receive Size bytes +echo_active(_Socket, 0) -> + ok; echo_active(Socket, Size) -> receive {ssl, Socket, Data} -> ok = ssl:send(Socket, Data), - NewSize = Size - byte_size(Data), - if - 0 < NewSize -> - echo_active(Socket, NewSize); - 0 == NewSize -> - ok - end - end. - -%% Verify that received data is SentData, return any superflous data -verify_active(Socket, SentData, Acc) -> - receive - {ssl, Socket, Data} -> - SentSize = byte_size(SentData), - NewAcc = <<Acc/binary, Data/binary>>, - NewSize = byte_size(NewAcc), - if - SentSize < NewSize -> - {SentData,Rest} = split_binary(NewAcc, SentSize), - Rest; - NewSize < SentSize -> - verify_active(Socket, SentData, NewAcc); - true -> - SentData = NewAcc, - <<>> - end - end. - -active_once_recv(_Socket, 0) -> - ok; -active_once_recv(Socket, N) -> - receive - {ssl, Socket, Bytes} -> - ssl:setopts(Socket, [{active, once}]), - active_once_recv(Socket, N-byte_size(Bytes)) - end. + echo_active(Socket, Size - byte_size(Data)) + end. + diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 25d2cb300d..6f11e2bbe8 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -44,11 +44,8 @@ init_per_suite(Config0) -> try crypto:start() of ok -> ssl_test_lib:clean_start(), - %% make rsa certs using oppenssl - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), - proplists:get_value(priv_dir, Config0)), - Config1 = ssl_test_lib:make_dsa_cert(Config0), - ssl_test_lib:cert_options(Config1) + %% make rsa certs + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. @@ -86,8 +83,8 @@ pem_cleanup() -> [{doc, "Test pem cache invalidate mechanism"}]. pem_cleanup(Config)when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -118,8 +115,8 @@ invalid_insert() -> invalid_insert(Config)when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), BadClientOpts = [{cacertfile, "tmp/does_not_exist.pem"} | proplists:delete(cacertfile, ClientOpts)], Server = diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index a0fab58b9d..7f33fe3204 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -48,7 +48,8 @@ all() -> session_cache_process_list, session_cache_process_mnesia, client_unique_session, - max_table_size + max_table_size, + save_specific_session ]. groups() -> @@ -60,10 +61,7 @@ init_per_suite(Config0) -> ok -> ssl_test_lib:clean_start(), %% make rsa certs using - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), - proplists:get_value(priv_dir, Config0)), - Config = ssl_test_lib:make_dsa_cert(Config0), - ssl_test_lib:cert_options(Config) + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. @@ -97,7 +95,10 @@ init_per_testcase(session_cleanup, Config) -> init_per_testcase(client_unique_session, Config) -> ct:timetrap({seconds, 40}), Config; - +init_per_testcase(save_specific_session, Config) -> + ssl_test_lib:clean_start(), + ct:timetrap({seconds, 5}), + Config; init_per_testcase(max_table_size, Config) -> ssl:stop(), application:load(ssl), @@ -141,7 +142,7 @@ end_per_testcase(max_table_size, Config) -> end_per_testcase(default_action, Config); end_per_testcase(Case, Config) when Case == session_cache_process_list; Case == session_cache_process_mnesia -> - ets:delete(ssl_test), + catch ets:delete(ssl_test), Config; end_per_testcase(_, Config) -> Config. @@ -154,8 +155,8 @@ client_unique_session() -> "sets up many connections"}]. client_unique_session(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_opts, Config), - ServerOpts = proplists:get_value(server_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -164,8 +165,7 @@ client_unique_session(Config) when is_list(Config) -> {tcp_options, [{active, false}]}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - LastClient = clients_start(Server, - ClientNode, Hostname, Port, ClientOpts, client_unique_session, 20), + LastClient = clients_start(Server, ClientNode, Hostname, Port, ClientOpts, 20), receive {LastClient, {ok, _}} -> ok @@ -185,8 +185,8 @@ session_cleanup() -> "does not grow and grow ..."}]. session_cleanup(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -254,13 +254,75 @@ session_cache_process_mnesia(Config) when is_list(Config) -> session_cache_process(mnesia,Config). %%-------------------------------------------------------------------- +save_specific_session() -> + [{doc, "Test that we can save a specific client session" + }]. +save_specific_session(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + Server ! listen, + + Client2 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + SessionID1 = + receive + {Client1, S1} -> + S1 + end, + + SessionID2 = + receive + {Client2, S2} -> + S2 + end, + + true = SessionID1 =/= SessionID2, + + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + 2 = ssl_session_cache:size(ClientCache), + + Server ! listen, + + Client3 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_session, SessionID2} | ClientOpts]}]), + receive + {Client3, SessionID2} -> + ok; + {Client3, SessionID3}-> + ct:fail({got, SessionID3, expected, SessionID2}); + Other -> + ct:fail({got,Other}) + end. + +%%-------------------------------------------------------------------- max_table_size() -> [{doc,"Test max limit on session table"}]. max_table_size(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -270,7 +332,7 @@ max_table_size(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), LastClient = clients_start(Server, - ClientNode, Hostname, Port, ClientOpts, max_table_size, 20), + ClientNode, Hostname, Port, ClientOpts, 20), receive {LastClient, {ok, _}} -> ok @@ -426,25 +488,27 @@ session_loop(Sess) -> %%-------------------------------------------------------------------- session_cache_process(_Type,Config) when is_list(Config) -> - ssl_basic_SUITE:reuse_session(Config). + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). -clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, Test, 0) -> +clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) -> %% Make sure session is registered ct:sleep(?SLEEP * 2), ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {?MODULE, connection_info_result, []}}, - {from, self()}, {options, test_copts(Test, 0, ClientOpts)}]); -clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N) -> + {from, self()}, {options, ClientOpts}]); +clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) -> spawn_link(ssl_test_lib, start_client, [[{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, test_copts(Test, N, ClientOpts)}]]), + {from, self()}, {options, ClientOpts}]]), Server ! listen, wait_for_server(), - clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N-1). + clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1). connection_info_result(Socket) -> ssl:connection_information(Socket, [protocol, cipher_suite]). @@ -481,21 +545,3 @@ get_delay_timers() -> wait_for_server() -> ct:sleep(100). - - -test_copts(_, 0, ClientOpts) -> - ClientOpts; -test_copts(max_table_size, N, ClientOpts) -> - Version = tls_record:highest_protocol_version([]), - CipherSuites = %%lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), -[ Y|| Y = {Alg,_, _, _} <- lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), Alg =/= ecdhe_ecdsa, Alg =/= ecdh_ecdsa, Alg =/= ecdh_rsa, Alg =/= ecdhe_rsa, Alg =/= dhe_dss, Alg =/= dss], - case length(CipherSuites) of - M when M >= N -> - Cipher = lists:nth(N, CipherSuites), - ct:pal("~p",[Cipher]), - [{ciphers, [Cipher]} | ClientOpts]; - _ -> - ClientOpts - end; -test_copts(_, _, ClientOpts) -> - ClientOpts. diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 251b6a2639..7629d75100 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -236,8 +236,8 @@ dns_name_reuse(Config) -> {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, [{verify, verify_peer} | ClientConf]}]), - ssl_test_lib:check_result(Client1, {error, {tls_alert, "handshake failure"}}), - ssl_test_lib:close(Client0). + ssl_test_lib:check_client_alert(Client1, handshake_failure). + %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -370,8 +370,8 @@ unsuccessfull_connect(ServerOptions, ClientOptions, Hostname0, Config) -> {from, self()}, {options, ClientOptions}]), - ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}, - Client, {error, {tls_alert, "handshake failure"}}). + ssl_test_lib:check_server_alert(Server, Client, handshake_failure). + host_name(undefined, Hostname) -> Hostname; host_name(Hostname, _) -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 7767d76a0d..c6a4a45dce 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -30,6 +30,7 @@ -record(sslsocket, { fd = nil, pid = nil}). -define(SLEEP, 1000). +-define(DEFAULT_CURVE, secp256r1). %% For now always run locally run_where(_) -> @@ -437,6 +438,37 @@ check_result(Pid, Msg) -> {got, Unexpected}}, ct:fail(Reason) end. +check_server_alert(Pid, Alert) -> + receive + {Pid, {error, {tls_alert, {Alert, _}}}} -> + ok + end. +check_server_alert(Server, Client, Alert) -> + receive + {Server, {error, {tls_alert, {Alert, _}}}} -> + receive + {Client, {error, {tls_alert, {Alert, _}}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. +check_client_alert(Pid, Alert) -> + receive + {Pid, {error, {tls_alert, {Alert, _}}}} -> + ok + end. +check_client_alert(Server, Client, Alert) -> + receive + {Client, {error, {tls_alert, {Alert, _}}}} -> + receive + {Server, {error, {tls_alert, {Alert, _}}}} -> + ok; + {Server, {error, closed}} -> + ok + end + end. + wait_for_result(Server, ServerMsg, Client, ClientMsg) -> receive @@ -523,7 +555,7 @@ cert_options(Config) -> {client_verification_opts, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, - {ssl_imp, new}]}, + {verify, verify_peer}]}, {client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFileDigitalSignatureOnly}, {keyfile, ClientKeyFile}, @@ -618,9 +650,12 @@ make_rsa_cert_chains(UserConf, Config, Suffix) -> }. make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config) -> + make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, ?DEFAULT_CURVE). +%% +make_ec_cert_chains(UserConf, ClientChainType, ServerChainType, Config, Curve) -> ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), - CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain), + CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain, Curve), ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ClientChainType)]), ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ServerChainType)]), GenCertData = public_key:pkix_test_data(CertChainConf), @@ -635,7 +670,11 @@ default_cert_chain_conf() -> %% Use only default options [[],[],[]]. -gen_conf(mix, mix, UserClient, UserServer) -> + +gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) -> + gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, ?DEFAULT_CURVE). +%% +gen_conf(mix, mix, UserClient, UserServer, _) -> ClientTag = conf_tag("client"), ServerTag = conf_tag("server"), @@ -646,12 +685,12 @@ gen_conf(mix, mix, UserClient, UserServer) -> ServerConf = merge_chain_spec(UserServer, DefaultServer, []), new_format([{ClientTag, ClientConf}, {ServerTag, ServerConf}]); -gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) -> +gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, Curve) -> ClientTag = conf_tag("client"), ServerTag = conf_tag("server"), - DefaultClient = chain_spec(client, ClientChainType), - DefaultServer = chain_spec(server, ServerChainType), + DefaultClient = chain_spec(client, ClientChainType, Curve), + DefaultServer = chain_spec(server, ServerChainType, Curve), ClientConf = merge_chain_spec(UserClient, DefaultClient, []), ServerConf = merge_chain_spec(UserServer, DefaultServer, []), @@ -673,43 +712,43 @@ proplist_to_map([Head | Rest]) -> conf_tag(Role) -> list_to_atom(Role ++ "_chain"). -chain_spec(_Role, ecdh_rsa) -> +chain_spec(_Role, ecdh_rsa, Curve) -> Digest = {digest, appropriate_sha(crypto:supports())}, - CurveOid = hd(tls_v1:ecc_curves(0)), + CurveOid = pubkey_cert_records:namedCurves(Curve), [[Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, hardcode_rsa_key(1)}], [Digest, {key, {namedCurve, CurveOid}}]]; -chain_spec(_Role, ecdhe_ecdsa) -> +chain_spec(_Role, ecdhe_ecdsa, Curve) -> Digest = {digest, appropriate_sha(crypto:supports())}, - CurveOid = hd(tls_v1:ecc_curves(0)), + CurveOid = pubkey_cert_records:namedCurves(Curve), [[Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, {namedCurve, CurveOid}}]]; -chain_spec(_Role, ecdh_ecdsa) -> +chain_spec(_Role, ecdh_ecdsa, Curve) -> Digest = {digest, appropriate_sha(crypto:supports())}, - CurveOid = hd(tls_v1:ecc_curves(0)), + CurveOid = pubkey_cert_records:namedCurves(Curve), [[Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, {namedCurve, CurveOid}}]]; -chain_spec(_Role, ecdhe_rsa) -> +chain_spec(_Role, ecdhe_rsa, _) -> Digest = {digest, appropriate_sha(crypto:supports())}, [[Digest, {key, hardcode_rsa_key(1)}], [Digest, {key, hardcode_rsa_key(2)}], [Digest, {key, hardcode_rsa_key(3)}]]; -chain_spec(_Role, ecdsa) -> +chain_spec(_Role, ecdsa, Curve) -> Digest = {digest, appropriate_sha(crypto:supports())}, - CurveOid = hd(tls_v1:ecc_curves(0)), + CurveOid = pubkey_cert_records:namedCurves(Curve), [[Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, {namedCurve, CurveOid}}], [Digest, {key, {namedCurve, CurveOid}}]]; -chain_spec(_Role, rsa) -> +chain_spec(_Role, rsa, _) -> Digest = {digest, appropriate_sha(crypto:supports())}, [[Digest, {key, hardcode_rsa_key(1)}], [Digest, {key, hardcode_rsa_key(2)}], [Digest, {key, hardcode_rsa_key(3)}]]; -chain_spec(_Role, dsa) -> +chain_spec(_Role, dsa, _) -> Digest = {digest, appropriate_sha(crypto:supports())}, [[Digest, {key, hardcode_dsa_key(1)}], [Digest, {key, hardcode_dsa_key(2)}], @@ -742,7 +781,7 @@ merge_spec(User, Default, [Conf | Rest], Acc) -> make_mix_cert(Config) -> Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), Digest = {digest, appropriate_sha(crypto:supports())}, - CurveOid = hd(tls_v1:ecc_curves(0)), + CurveOid = pubkey_cert_records:namedCurves(?DEFAULT_CURVE), Mix = proplists:get_value(mix, Config, peer_ecc), ClientChainType =ServerChainType = mix, {ClientChain, ServerChain} = mix(Mix, Digest, CurveOid, Ext), @@ -825,7 +864,8 @@ make_rsa_cert(Config) -> Config end. appropriate_sha(CryptoSupport) -> - case proplists:get_bool(sha256, CryptoSupport) of + Hashes = proplists:get_value(hashs, CryptoSupport), + case lists:member(sha256, Hashes) of true -> sha256; false -> @@ -1064,8 +1104,7 @@ ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) -> ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> {Server, Port} = start_server_ecc_error(erlang, SOpts, SECCOpts, Config), Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config), - Error = {error, {tls_alert, "insufficient security"}}, - check_result(Server, Error, Client, Error). + check_server_alert(Server, Client, insufficient_security). start_client(openssl, Port, ClientOpts, Config) -> Cert = proplists:get_value(certfile, ClientOpts), @@ -1073,11 +1112,11 @@ start_client(openssl, Port, ClientOpts, Config) -> CA = proplists:get_value(cacertfile, ClientOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", - Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), + Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), "-cert", Cert, "-CAfile", CA, "-key", Key, "-host","localhost", "-msg", "-debug"], - + Args = maybe_force_ipv4(Args0), OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), OpenSslPort; @@ -1091,6 +1130,18 @@ start_client(erlang, Port, ClientOpts, Config) -> {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, {options, [{verify, verify_peer} | ClientOpts]}]). +%% Workaround for running tests on machines where openssl +%% s_client would use an IPv6 address with localhost. As +%% this test suite and the ssl application is not prepared +%% for that we have to force s_client to use IPv4 if +%% OpenSSL supports IPv6. +maybe_force_ipv4(Args0) -> + case is_ipv6_supported() of + true -> + Args0 ++ ["-4"]; + false -> + Args0 + end. start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) -> {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1583,35 +1634,22 @@ v_1_2_check(ecdh_rsa, ecdh_ecdsa) -> v_1_2_check(_, _) -> false. -send_recv_result_active(Socket) -> - ssl:send(Socket, "Hello world"), - receive - {ssl, Socket, "H"} -> - receive - {ssl, Socket, "ello world"} -> - ok - end; - {ssl, Socket, "Hello world"} -> - ok - end. - send_recv_result(Socket) -> - ssl:send(Socket, "Hello world"), - {ok,"Hello world"} = ssl:recv(Socket, 11), + Data = "Hello world", + ssl:send(Socket, Data), + {ok, Data} = ssl:recv(Socket, length(Data)), + ok. + +send_recv_result_active(Socket) -> + Data = "Hello world", + ssl:send(Socket, Data), + Data = active_recv(Socket, length(Data)), ok. send_recv_result_active_once(Socket) -> - ssl:send(Socket, "Hello world"), - receive - {ssl, Socket, "H"} -> - ssl:setopts(Socket, [{active, once}]), - receive - {ssl, Socket, "ello world"} -> - ok - end; - {ssl, Socket, "Hello world"} -> - ok - end. + Data = "Hello world", + ssl:send(Socket, Data), + active_once_recv_list(Socket, length(Data)). active_recv(Socket, N) -> active_recv(Socket, N, []). @@ -1624,6 +1662,55 @@ active_recv(Socket, N, Acc) -> active_recv(Socket, N-length(Bytes), Acc ++ Bytes) end. +active_once_recv(_Socket, 0) -> + ok; +active_once_recv(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + ssl:setopts(Socket, [{active, once}]), + active_once_recv(Socket, N-byte_size(Bytes)) + end. + +active_once_recv_list(_Socket, 0) -> + ok; +active_once_recv_list(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + ssl:setopts(Socket, [{active, once}]), + active_once_recv_list(Socket, N-length(Bytes)) + end. +recv_disregard(_Socket, 0) -> + ok; +recv_disregard(Socket, N) -> + {ok, Bytes} = ssl:recv(Socket, 0), + recv_disregard(Socket, N-byte_size(Bytes)). + +active_disregard(_Socket, 0) -> + ok; +active_disregard(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + active_disregard(Socket, N-byte_size(Bytes)) + end. +active_once_disregard(_Socket, 0) -> + ok; +active_once_disregard(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + ssl:setopts(Socket, [{active, once}]), + active_once_disregard(Socket, N-byte_size(Bytes)) + end. + +is_ipv6_supported() -> + case os:cmd("openssl version") of + "OpenSSL 0.9.8" ++ _ -> % Does not support IPv6 + false; + "OpenSSL 1.0" ++ _ -> % Does not support IPv6 + false; + _ -> + true + end. + is_sane_ecc(openssl) -> case os:cmd("openssl version") of "OpenSSL 1.0.0a" ++ _ -> % Known bug in openssl @@ -2161,3 +2248,98 @@ server_msg(Server, ServerMsg) -> Unexpected -> ct:fail(Unexpected) end. + +session_id(Socket) -> + {ok, [{session_id, ID}]} = ssl:connection_information(Socket, [session_id]), + ID. + +reuse_session(ClientOpts, ServerOpts, Config) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + Server0 ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + Server0 ! listen, + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, false} + | ClientOpts]}]), + receive + {Client2, SID} -> + ct:fail(session_reused_when_session_reuse_disabled_by_client); + {Client2, _} -> + ok + end, + + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, [{reuse_sessions, false} |ServerOpts]}]), + Port1 = ssl_test_lib:inet_port(Server1), + + Client3 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + SID1 = receive + {Client3, Id3} -> + Id3 + end, + + Server1 ! listen, + + Client4 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + receive + {Client4, SID1} -> + ct:fail(session_reused_when_session_reuse_disabled_by_server); + {Client4, _} -> + ok + end, + + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client3), + ssl_test_lib:close(Client4). + diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 3c8b25b912..df84411b6d 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -260,8 +260,9 @@ special_init(TestCase, Config) when Config; special_init(TestCase, Config) when TestCase == erlang_client_openssl_server_renegotiate; - TestCase == erlang_client_openssl_server_nowrap_seqnum; - TestCase == erlang_server_openssl_client_nowrap_seqnum + TestCase == erlang_client_openssl_server_nowrap_seqnum; + TestCase == erlang_server_openssl_client_nowrap_seqnum; + TestCase == erlang_client_openssl_server_renegotiate_after_client_data -> {ok, Version} = application:get_env(ssl, protocol_version), check_sane_openssl_renegotaite(Config, Version); @@ -761,8 +762,8 @@ erlang_client_openssl_server_renegotiate() -> [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -771,12 +772,14 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile, "-msg"], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -806,8 +809,8 @@ erlang_client_openssl_server_renegotiate_after_client_data() -> [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -815,6 +818,7 @@ erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list( OpenSslData = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), @@ -822,6 +826,7 @@ erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list( Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile, "-msg"], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -856,7 +861,7 @@ erlang_client_openssl_server_nowrap_seqnum() -> " to lower treashold substantially."}]. erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -865,12 +870,14 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> N = 10, Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile, "-msg"], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -899,7 +906,7 @@ erlang_server_openssl_client_nowrap_seqnum() -> " to lower treashold substantially."}]. erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1242,7 +1249,7 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), ssl_test_lib:consume_port_exit(OpenSslPort), - ssl_test_lib:check_result(Server, {error, {tls_alert, "bad record mac"}}), + ssl_test_lib:check_server_alert(Server, bad_record_mac), process_flag(trap_exit, false). %%-------------------------------------------------------------------- @@ -1648,8 +1655,8 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = ErlangClientOpts ++ ClientOpts0, {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1657,6 +1664,7 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), @@ -1666,10 +1674,12 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens [] -> ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile,"-key", KeyFile]; [Opt, Value] -> ["s_server", Opt, Value, "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile,"-key", KeyFile] end, @@ -1694,8 +1704,8 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config), ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1703,12 +1713,14 @@ start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callba Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), @@ -1826,8 +1838,8 @@ start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Ca start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1835,6 +1847,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), @@ -1842,6 +1855,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac Exe = "openssl", Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -1932,6 +1946,11 @@ erlang_ssl_receive(Socket, Data) -> ct:log("Connection info: ~p~n", [ssl:connection_information(Socket)]), receive + {ssl, Socket, "R\n"} -> + %% Swallow s_client renegotiation command. + %% openssl s_client connected commands can appear on + %% server side with some openssl versions. + erlang_ssl_receive(Socket,Data); {ssl, Socket, Data} -> io:format("Received ~p~n",[Data]), %% open_ssl server sometimes hangs waiting in blocking read |