From bf7081f32a19ee457f0c0c685b89ab1215a3ec53 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 5 Jan 2022 20:03:56 +0100 Subject: ssl: Prepare code to have several certificate key pairs to choose from --- lib/ssl/src/dtls_connection.erl | 27 ++++---- lib/ssl/src/dtls_handshake.erl | 24 +++---- lib/ssl/src/ssl_config.erl | 14 ++-- lib/ssl/src/ssl_connection.hrl | 4 +- lib/ssl/src/ssl_gen_statem.erl | 29 ++++----- lib/ssl/src/ssl_handshake.erl | 78 +++++++++++++++++------ lib/ssl/src/ssl_handshake.hrl | 1 + lib/ssl/src/ssl_session.erl | 55 ++++++++++------ lib/ssl/src/tls_connection.erl | 19 +++--- lib/ssl/src/tls_dtls_connection.erl | 123 ++++++++++++++++++++---------------- lib/ssl/src/tls_handshake.erl | 18 +++--- lib/ssl/src/tls_handshake_1_3.erl | 87 +++++++++++++++---------- 12 files changed, 277 insertions(+), 202 deletions(-) diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index fbcae64a4c..b87c716af5 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -208,16 +208,16 @@ initial_hello({call, From}, {start, Timeout}, session_cache_cb = CacheCb}, protocol_specific = PS, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - connection_env = CEnv, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, ssl_options = #{versions := Versions} = SslOpts, - session = #session{own_certificates = OwnCerts} = NewSession, + session = Session0, connection_states = ConnectionStates0 } = State0) -> Packages = maps:get(active_n, PS), dtls_socket:setopts(Transport, Socket, [{active,Packages}]), - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, NewSession), + Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, OwnCerts), + Session#session.session_id, Renegotiation), MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined), ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), @@ -303,14 +303,13 @@ hello(internal, #hello_verify_request{cookie = Cookie}, connection_env = CEnv, ssl_options = #{ocsp_stapling := OcspStaplingOpt, ocsp_nonce := OcspNonceOpt} = SslOpts, - session = #session{own_certificates = OwnCerts, - session_id = Id}, + session = #session{session_id = Id}, connection_states = ConnectionStates0, protocol_specific = PS } = State0) -> OcspNonce = tls_handshake:ocsp_nonce(OcspNonceOpt, OcspStaplingOpt), Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, - SslOpts, Id, Renegotiation, OwnCerts, OcspNonce), + SslOpts, Id, Renegotiation, OcspNonce), Version = Hello#client_hello.client_version, State1 = prepare_flight(State0#state{handshake_env = HsEnv#handshake_env{tls_handshake_history @@ -517,16 +516,16 @@ connection(internal, #hello_request{}, #state{static_env = #static_env{host = Ho session_cache_cb = CacheCb }, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, - connection_env = CEnv, - session = #session{own_certificates = OwnCerts} = Session0, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + session = Session0, ssl_options = #{versions := Versions} = SslOpts, connection_states = ConnectionStates0, protocol_specific = PS } = State0) -> #{current_cookie_secret := Cookie} = PS, - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0), + Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, - Session#session.session_id, Renegotiation, OwnCerts, undefined), + Session#session.session_id, Renegotiation, undefined), Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, Versions), State1 = prepare_flight(State0), @@ -682,14 +681,14 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State handshake_env = #handshake_env{kex_algorithm = KeyExAlg, renegotiation = {Renegotiation, _}, negotiated_protocol = CurrentProtocol} = HsEnv, - connection_env = CEnv, - session = #session{own_certificates = OwnCerts} = Session0, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + session = Session0, ssl_options = SslOpts} = tls_dtls_connection:handle_sni_extension(State0, Hello), SessionTracker = proplists:get_value(session_id_tracker, Trackers), {Version, {Type, Session}, ConnectionStates, Protocol0, ServerHelloExt, HashSign} = dtls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, - ConnectionStates0, OwnCerts, KeyExAlg}, Renegotiation), + ConnectionStates0, CertKeyPairs, KeyExAlg}, Renegotiation), Protocol = case Protocol0 of undefined -> CurrentProtocol; _ -> Protocol0 diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index cd22c74f18..89b09c2112 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -30,7 +30,7 @@ -include("ssl_alert.hrl"). %% Handshake handling --export([client_hello/7, client_hello/9, cookie/4, hello/5, hello/4, +-export([client_hello/6, client_hello/8, cookie/4, hello/5, hello/4, hello_verify_request/2]). %% Handshake encoding @@ -47,20 +47,20 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), - ssl_options(), binary(), boolean(), [der_cert()]) -> - #client_hello{}. + ssl_options(), binary(), boolean()) -> + #client_hello{}. %% %% Description: Creates a client hello message. %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, SslOpts, - Id, Renegotiation, OwnCerts) -> + Id, Renegotiation) -> %% First client hello (two sent in DTLS ) uses empty Cookie client_hello(Host, Port, <<>>, ConnectionStates, SslOpts, - Id, Renegotiation, OwnCerts, undefined). + Id, Renegotiation, undefined). %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), term(), ssl_record:connection_states(), - ssl_options(), binary(),boolean(), [der_cert()], binary() | undefined) -> + ssl_options(), binary(),boolean(), binary() | undefined) -> #client_hello{}. %% %% Description: Creates a client hello message. @@ -69,7 +69,7 @@ client_hello(_Host, _Port, Cookie, ConnectionStates, #{versions := Versions, ciphers := UserSuites, fallback := Fallback} = SslOpts, - Id, Renegotiation, _OwnCert, OcspNonce) -> + Id, Renegotiation, OcspNonce) -> Version = dtls_record:highest_protocol_version(Versions), Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = maps:get(security_parameters, Pending), @@ -178,22 +178,22 @@ handle_client_hello(Version, signature_algs := SupportedHashSigns, eccs := SupportedECCs, honor_ecc_order := ECCOrder} = SslOpts, - {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _}, + {SessIdTracker, Session0, ConnectionStates0, CertKeyPairs, _}, Renegotiation) -> - OwnCert = ssl_handshake:select_own_cert(OwnCerts), case dtls_record:is_acceptable_version(Version, Versions) of true -> Curves = maps:get(elliptic_curves, HelloExt, undefined), ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), TLSVersion = dtls_v1:corresponding_tls_version(Version), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, OwnCert,TLSVersion), + ClientHashSigns, SupportedHashSigns, TLSVersion), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, TLSVersion, ECCOrder), - {Type, #session{cipher_suite = CipherSuite} = Session1} + {Type, #session{cipher_suite = CipherSuite, + own_certificates = [OwnCert |_]} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, SessIdTracker, Session0#session{ecc = ECCCurve}, TLSVersion, - SslOpts, OwnCert), + SslOpts, CertKeyPairs), case CipherSuite of no_suite -> throw(?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY)); diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 80a5b4f44c..65d4259ab4 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -49,12 +49,12 @@ init(#{erl_dist := ErlDist, init_manager_name(ErlDist), - {ok, #{pem_cache := PemCache} = Config} + {ok, #{pem_cache := PemCache} = Config, Certs} = init_certificates(SslOpts, Role), PrivateKey = init_private_key(PemCache, Key, KeyFile, Password, Role), DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role), - {ok, Config#{private_key => PrivateKey, dh_params => DHParams}}. + {ok, Config#{cert_key_pairs => [#{private_key => PrivateKey, certs => Certs}], dh_params => DHParams}}. pre_1_3_session_opts(Role) -> {Cb, InitArgs} = session_cb_opts(Role), @@ -141,28 +141,28 @@ init_certificates(#{cacerts := CaCerts, init_certificates(OwnCerts, Config, CertFile, Role). init_certificates(undefined, Config, <<>>, _) -> - {ok, Config#{own_certificates => undefined}}; + {ok, Config, undefined}; init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) -> try %% OwnCert | [OwnCert | Chain] OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config#{own_certificates => OwnCerts}} + {ok, Config, OwnCerts} catch _Error:_Reason -> - {ok, Config#{own_certificates => undefined}} + {ok, Config, undefined} end; init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) -> try %% OwnCert | [OwnCert | Chain] OwnCerts = ssl_certificate:file_to_certificats(CertFile, PemCache), - {ok, Config#{own_certificates => OwnCerts}} + {ok, Config, OwnCerts} catch _:Reason -> file_error(CertFile, {certfile, Reason}) end; init_certificates(OwnCerts, Config, _, _) -> - {ok, Config#{own_certificates => OwnCerts}}. + {ok, Config, OwnCerts}. init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa; Alg == rsa; Alg == dss -> diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index a08545cafc..a54d4bf380 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -97,7 +97,9 @@ socket_tls_closed = false ::boolean(), negotiated_version :: ssl_record:ssl_version() | 'undefined', erl_dist_handle = undefined :: erlang:dist_handle() | 'undefined', - private_key :: public_key:private_key() | secret_printout() | 'undefined' + cert_key_pairs = undefined :: [#{private_key => public_key:private_key(), + certs => [public_key:der_encoded()]}] + | secret_printout() | 'undefined' }). -record(state, { diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index 9e8d1ef667..9d5bdea68d 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -156,15 +156,13 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0, fileref_db_handle := FileRefHandle, session_cache := CacheHandle, crl_db_info := CRLDbHandle, - private_key := Key, - dh_params := DHParams, - own_certificates := OwnCerts}} = + cert_key_pairs := CertKeyPairs, + dh_params := DHParams}} = ssl_config:init(Opts, Role), TimeStamp = erlang:monotonic_time(), Session = State0#state.session, - State0#state{session = Session#session{own_certificates = OwnCerts, - time_stamp = TimeStamp}, + State0#state{session = Session#session{time_stamp = TimeStamp}, static_env = InitStatEnv0#static_env{ file_ref_db = FileRefHandle, cert_db_ref = Ref, @@ -173,7 +171,7 @@ ssl_config(Opts, Role, #state{static_env = InitStatEnv0, session_cache = CacheHandle }, handshake_env = HsEnv#handshake_env{diffie_hellman_params = DHParams}, - connection_env = CEnv#connection_env{private_key = Key}, + connection_env = CEnv#connection_env{cert_key_pairs = CertKeyPairs}, ssl_options = Opts}. %%-------------------------------------------------------------------- @@ -464,7 +462,6 @@ initial_hello({call, From}, {start, Timeout}, Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Session#session.session_id, Renegotiation, - Session#session.own_certificates, KeyShare, TicketData, OcspNonce), @@ -1276,20 +1273,18 @@ handle_sni_hostname(Hostname, fileref_db_handle := FileRefHandle, session_cache := CacheHandle, crl_db_info := CRLDbHandle, - private_key := Key, - dh_params := DHParams, - own_certificates := OwnCerts}} = + cert_key_pairs := CertKeyPairs, + dh_params := DHParams}} = ssl_config:init(NewOptions, Role), State0#state{ - session = State0#state.session#session{own_certificates = OwnCerts}, static_env = InitStatEnv0#static_env{ - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle }, - connection_env = CEnv#connection_env{private_key = Key}, + connection_env = CEnv#connection_env{cert_key_pairs = CertKeyPairs}, ssl_options = NewOptions, handshake_env = HsEnv#handshake_env{sni_hostname = Hostname, diffie_hellman_params = DHParams} diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 2dce5f35d6..c3df04166b 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -67,7 +67,7 @@ ]). %% Cipher suites handling --export([available_suites/2, available_signature_algs/2, available_signature_algs/4, +-export([available_suites/2, available_signature_algs/2, available_signature_algs/3, cipher_suites/3, prf/6, select_session/9, supported_ecc/1, premaster_secret/2, premaster_secret/3, premaster_secret/4]). @@ -1000,11 +1000,11 @@ available_signature_algs(SupportedHashSigns, Version) when Version >= {3, 3} -> available_signature_algs(_, _) -> undefined. -available_signature_algs(undefined, SupportedHashSigns, _, Version) when +available_signature_algs(undefined, SupportedHashSigns, Version) when Version >= {3,3} -> SupportedHashSigns; available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns0, - _, Version) when Version >= {3,3} -> + Version) when Version >= {3,3} -> SupportedHashSigns = case (Version == {3,3}) andalso contains_scheme(SupportedHashSigns0) of true -> @@ -1014,7 +1014,7 @@ available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, Su end, sets:to_list(sets:intersection(sets:from_list(ClientHashSigns), sets:from_list(SupportedHashSigns))); -available_signature_algs(_, _, _, _) -> +available_signature_algs(_, _, _) -> undefined. contains_scheme([]) -> @@ -1042,25 +1042,57 @@ cipher_suites(Suites, true) -> prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. -select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, #session{ecc = ECCCurve0} = - Session, Version, - #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = SslOpts,Cert) -> +select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, SessIdTracker, Session0, Version, SslOpts, CertKeyPairs) -> {SessionId, Resumed} = ssl_session:server_select_session(Version, SessIdTracker, SuggestedSessionId, - SslOpts, Cert), + SslOpts, CertKeyPairs), case Resumed of undefined -> - Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), - CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), - {ECCCurve, CipherSuite} = cert_curve(Cert, ECCCurve0, CipherSuite0), - Compression = select_compression(Compressions), - {new, Session#session{session_id = SessionId, - ecc = ECCCurve, - cipher_suite = CipherSuite, - compression_method = Compression}}; + %% Select Cert + Session = new_session_parameters(SessionId, Session0, CipherSuites, + SslOpts, Version, Compressions, + HashSigns, CertKeyPairs), + {new, Session}; _ -> {resumed, Resumed} end. + +new_session_parameters(SessionId, #session{ecc = ECCCurve0} = Session, CipherSuites, SslOpts, + Version, Compressions, HashSigns, CertKeyPairs) -> + Compression = select_compression(Compressions), + {Certs, Key, {ECCCurve, CipherSuite}} = select_cert_key_pair_and_params(CipherSuites, CertKeyPairs, HashSigns, + ECCCurve0, SslOpts, Version), + Session#session{session_id = SessionId, + ecc = ECCCurve, + own_certificates = Certs, + private_key = Key, + cipher_suite = CipherSuite, + compression_method = Compression}. + +%% Possibly support part of "trusted_ca_keys" that correspnds to TLS-1.3 certificate_authorities?! +select_cert_key_pair_and_params(CipherSuites, [#{private_key := undefined, certs := undefined}], HashSigns, ECCCurve0, + #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) -> + Suites = available_suites(undefined, UserSuites, Version, HashSigns, ECCCurve0), + CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), + CurveAndSuite = cert_curve(undefined, ECCCurve0, CipherSuite0), + {[undefined], undefined, CurveAndSuite}; +select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs}], HashSigns, ECCCurve0, + #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder}, Version) -> + Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), + CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), + CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), + {Certs, Key, CurveAndSuite}; +select_cert_key_pair_and_params(CipherSuites, [#{private_key := Key, certs := [Cert | _] = Certs} | Rest], HashSigns, ECCCurve0, + #{ciphers := UserSuites, honor_cipher_order := HonorCipherOrder} = Opts, Version) -> + Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), + case select_cipher_suite(CipherSuites, Suites, HonorCipherOrder) of + no_suite -> + select_cert_key_pair_and_params(CipherSuites, Rest, HashSigns, ECCCurve0, Opts, Version); + CipherSuite0 -> + CurveAndSuite = cert_curve(Cert, ECCCurve0, CipherSuite0), + {Certs, Key, CurveAndSuite} + end. + supported_ecc({Major, Minor}) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> Curves = tls_v1:ecc_curves(Minor), #elliptic_curves{elliptic_curve_list = Curves}; @@ -1606,7 +1638,7 @@ select_hashsign(#certificate_request{ certificate_types = Types}, Cert, SupportedHashSigns, - {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + {3, 3}) -> {SignAlgo0, Param, PublicKeyAlgo0, _, _} = get_cert_params(Cert), SignAlgo = {_, KeyType} = sign_algo(SignAlgo0, Param), PublicKeyAlgo = ssl_certificate:public_key_type(PublicKeyAlgo0), @@ -1632,10 +1664,12 @@ select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Versio do_select_hashsign(HashSigns, PublicKeyAlgo, SupportedHashSigns) -> - case lists:filter(fun({H, rsa_pss_pss = S}) when S == PublicKeyAlgo -> - is_acceptable_hash_sign(list_to_existing_atom(atom_to_list(S) ++ "_" ++ atom_to_list(H)), SupportedHashSigns); - ({H, rsa_pss_rsae = S}) when PublicKeyAlgo == rsa -> - is_acceptable_hash_sign(list_to_existing_atom(atom_to_list(S) ++ "_" ++ atom_to_list(H)), SupportedHashSigns); + case lists:filter(fun({H, rsa_pss_pss = S} = Algos) when S == PublicKeyAlgo -> + is_acceptable_hash_sign(list_to_existing_atom(atom_to_list(S) ++ "_" ++ atom_to_list(H)), SupportedHashSigns) orelse + is_acceptable_hash_sign(Algos, SupportedHashSigns); + ({H, rsa_pss_rsae = S} = Algos) when PublicKeyAlgo == rsa -> + is_acceptable_hash_sign(list_to_existing_atom(atom_to_list(S) ++ "_" ++ atom_to_list(H)), SupportedHashSigns) orelse + is_acceptable_hash_sign(Algos, SupportedHashSigns); ({_, S} = Algos) when S == PublicKeyAlgo -> is_acceptable_hash_sign(Algos, SupportedHashSigns); (_A) -> @@ -3492,6 +3526,8 @@ sign_algo(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = sign_algo(Alg, _) -> public_key:pkix_sign_types(Alg). +sign_type(rsa_pss_pss) -> + ?RSA_SIGN; sign_type(rsa) -> ?RSA_SIGN; sign_type(dsa) -> diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index c56ee31fdd..9beaf3abff 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -42,6 +42,7 @@ internal_id, peer_certificate, own_certificates, + private_key, compression_method, cipher_suite, master_secret, diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index ccc5c9ded7..bfc23566a6 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -30,7 +30,7 @@ -include("ssl_api.hrl"). %% Internal application API --export([is_new/2, client_select_session/4, server_select_session/5, valid_session/2, legacy_session_id/0]). +-export([is_new/2, client_select_session/5, server_select_session/5, valid_session/2, legacy_session_id/0]). -type seconds() :: integer(). @@ -60,14 +60,14 @@ is_new(_ClientSuggestion, _ServerDecision) -> %%-------------------------------------------------------------------- -spec client_select_session({ssl:host(), inet:port_number(), map()}, db_handle(), atom(), - #session{}) -> #session{}. + #session{}, list()) -> #session{}. %% %% Description: Should be called by the client side to get an id %% for the client hello message. %%-------------------------------------------------------------------- client_select_session({_, _, #{versions := Versions, protocol := Protocol}} = ClientInfo, - Cache, CacheCb, NewSession) -> + Cache, CacheCb, NewSession, CertKeyPairs) -> RecordCb = record_cb(Protocol), Version = RecordCb:lowest_protocol_version(Versions), @@ -76,20 +76,20 @@ client_select_session({_, _, #{versions := Versions, {3, N} when N >= 4 -> NewSession#session{session_id = legacy_session_id()}; _ -> - do_client_select_session(ClientInfo, Cache, CacheCb, NewSession) + do_client_select_session(ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs) end. %%-------------------------------------------------------------------- -spec server_select_session(ssl_record:ssl_version(), pid(), binary(), map(), - binary()) -> {binary(), #session{} | undefined}. + list()) -> {binary(), #session{} | undefined}. %% %% Description: Should be called by the server side to get an id %% for the client hello message. %%-------------------------------------------------------------------- -server_select_session(_, SessIdTracker, <<>>, _SslOpts, _Cert) -> +server_select_session(_, SessIdTracker, <<>>, _SslOpts, _CertKeyPairs) -> {ssl_server_session_cache:new_session_id(SessIdTracker), undefined}; -server_select_session(_, SessIdTracker, SuggestedId, Options, Cert) -> - case is_resumable(SuggestedId, SessIdTracker, Options, Cert) +server_select_session(_, SessIdTracker, SuggestedId, Options, CertKeyPairs) -> + case is_resumable(SuggestedId, SessIdTracker, Options, CertKeyPairs) of {true, Resumed} -> {SuggestedId, Resumed}; @@ -111,8 +111,8 @@ valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession) when is_binary(SessionId) andalso - is_binary(SessionData) -> +do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _, _, NewSession, _) when is_binary(SessionId) andalso + is_binary(SessionData) -> try binary_to_term(SessionData, [safe]) of Session -> Session @@ -120,37 +120,43 @@ do_client_select_session({_, _, #{reuse_session := {SessionId, SessionData}}}, _ _:_ -> NewSession#session{session_id = <<>>} end; -do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession) when is_binary(SessionId)-> +do_client_select_session({Host, Port, #{reuse_session := SessionId}}, Cache, CacheCb, NewSession, _) when is_binary(SessionId)-> case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of undefined -> NewSession#session{session_id = <<>>}; #session{} = Session-> Session end; -do_client_select_session(ClientInfo, - Cache, CacheCb, #session{own_certificates = OwnCerts} = NewSession) -> - case select_session(ClientInfo, Cache, CacheCb, OwnCerts) of +do_client_select_session(ClientInfo, Cache, CacheCb, NewSession, CertKeyPairs) -> + case select_session(ClientInfo, Cache, CacheCb, CertKeyPairs) of no_session -> NewSession#session{session_id = <<>>}; Session -> Session end. -select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true -> +select_session({_, _, #{reuse_sessions := Reuse}}, _Cache, _CacheCb, _) when Reuse =/= true -> %% If reuse_sessions == false | save a new session should be created no_session; -select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCerts) -> +select_session({HostIP, Port, SslOpts}, Cache, CacheCb, CertKeyPairs) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), - select_session(Sessions, SslOpts, OwnCerts). + select_session(Sessions, SslOpts, CertKeyPairs). select_session([], _, _) -> no_session; -select_session(Sessions, #{ciphers := Ciphers}, OwnCerts) -> +select_session(Sessions, #{ciphers := Ciphers}, CertKeyPairs) -> IsNotResumable = fun(Session) -> + SessionOwnCert = + case Session#session.own_certificates of + [OwnCert |_] -> + OwnCert; + Other -> + Other + end, not (resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) - andalso (OwnCerts == Session#session.own_certificates)) + andalso (is_owncert(SessionOwnCert, CertKeyPairs) orelse (SessionOwnCert == undefined))) end, case lists:dropwhile(IsNotResumable, Sessions) of [] -> no_session; @@ -159,7 +165,7 @@ select_session(Sessions, #{ciphers := Ciphers}, OwnCerts) -> is_resumable(_, _, #{reuse_sessions := false}, _) -> {false, undefined}; -is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCert) -> +is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = Options, OwnCertKeyPairs) -> case ssl_server_session_cache:reuse_session(SessIdTracker, SuggestedSessionId) of #session{cipher_suite = CipherSuite, own_certificates = [SessionOwnCert | _], @@ -167,7 +173,7 @@ is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = O is_resumable = IsResumable, peer_certificate = PeerCert} = Session -> case resumable(IsResumable) - andalso (OwnCert == SessionOwnCert) + andalso is_owncert(SessionOwnCert, OwnCertKeyPairs) andalso reusable_options(Options, Session) andalso ReuseFun(SuggestedSessionId, PeerCert, Compression, CipherSuite) @@ -179,6 +185,13 @@ is_resumable(SuggestedSessionId, SessIdTracker, #{reuse_session := ReuseFun} = O {false, undefined} end. +is_owncert(_, []) -> + false; +is_owncert(SessionOwnCert, [#{certs := [SessionOwnCert | _]} | _]) -> + true; +is_owncert(SessionOwnCert, [_| Rest]) -> + is_owncert(SessionOwnCert, Rest). + resumable(new) -> false; resumable(IsResumable) -> diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 69cefc727d..28b1bfc653 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -141,11 +141,12 @@ init([Role, Sender, Host, Port, Socket, Options, User, CbInfo]) -> State1 = #state{static_env = #static_env{session_cache = Cache, session_cache_cb = CacheCb }, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs}, ssl_options = SslOptions, session = Session0} = ssl_gen_statem:ssl_config(State0#state.ssl_options, Role, State0), State = case Role of client -> - Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0), + Session = ssl_session:client_select_session({Host, Port, SslOptions}, Cache, CacheCb, Session0, CertKeyPairs), State1#state{session = Session}; server -> State1 @@ -348,16 +349,17 @@ connection(internal, #hello_request{}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, peer}, ocsp_stapling_state = OcspState}, - session = #session{own_certificates = OwnCerts} = Session0, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs}, + session = Session0, ssl_options = SslOpts, protocol_specific = #{sender := Pid}, connection_states = ConnectionStates} = State0) -> try tls_sender:peer_renegotiate(Pid) of {ok, Write} -> - Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0), + Session = ssl_session:client_select_session({Host, Port, SslOpts}, Cache, CacheCb, Session0, CertKeyPairs), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Session#session.session_id, - Renegotiation, OwnCerts, undefined, + Renegotiation, undefined, undefined, maps:get(ocsp_nonce, OcspState, undefined)), {State, Actions} = tls_gen_connection:send_handshake(Hello, State0#state{connection_states = @@ -375,11 +377,10 @@ connection(internal, #hello_request{}, handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}, ocsp_stapling_state = OcspState}, - session = #session{own_certificates = OwnCerts}, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, - <<>>, Renegotiation, OwnCerts, undefined, + <<>>, Renegotiation, undefined, undefined, maps:get(ocsp_nonce, OcspState, undefined)), {State, Actions} = tls_gen_connection:send_handshake(Hello, State0), @@ -503,8 +504,8 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State renegotiation = {Renegotiation, _}, negotiated_protocol = CurrentProtocol, sni_guided_cert_selection = SNICertSelection} = HsEnv, - connection_env = CEnv, - session = #session{own_certificates = OwnCerts} = Session0, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs} = CEnv, + session = Session0, ssl_options = SslOpts} = State, SessionTracker = proplists:get_value(session_id_tracker, Trackers), {Version, {Type, Session}, @@ -512,7 +513,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State tls_handshake:hello(Hello, SslOpts, {SessionTracker, Session0, - ConnectionStates0, OwnCerts, KeyExAlg}, + ConnectionStates0, CertKeyPairs, KeyExAlg}, Renegotiation), Protocol = case Protocol0 of undefined -> CurrentProtocol; diff --git a/lib/ssl/src/tls_dtls_connection.erl b/lib/ssl/src/tls_dtls_connection.erl index 81a187369c..9e09413ac3 100644 --- a/lib/ssl/src/tls_dtls_connection.erl +++ b/lib/ssl/src/tls_dtls_connection.erl @@ -408,37 +408,28 @@ certify(internal, #certificate_request{}, certify(internal, #certificate_request{}, #state{static_env = #static_env{role = client, protocol_cb = Connection}, - session = #session{own_certificates = undefined}} = State) -> + connection_env = #connection_env{cert_key_pairs = undefined}} = State) -> %% The client does not have a certificate and will send an empty reply, the server may fail %% or accept the connection by its own preference. No signature algorithms needed as there is %% no certificate to verify. Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); certify(internal, #certificate_request{} = CertRequest, #state{static_env = #static_env{role = client, - protocol_cb = Connection}, - handshake_env = #handshake_env{hashsign_algorithm = NegotiatedHashSign} = HsEnv, - connection_env = #connection_env{negotiated_version = Version}, - session = #session{own_certificates = [Cert|_]}, + protocol_cb = Connection, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + connection_env = #connection_env{negotiated_version = Version, + cert_key_pairs = CertKeyPairs}, + session = Session0, ssl_options = #{signature_algs := SupportedHashSigns}} = State) -> TLSVersion = ssl:tls_version(Version), - case NegotiatedHashSign of - {Hash, Sign} when TLSVersion == {3,3} andalso Hash =/= undefined andalso - Sign =/= undefined -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{client_certificate_requested = true, - handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = NegotiatedHashSign}}); - _ -> - case ssl_handshake:select_hashsign(CertRequest, Cert, - SupportedHashSigns, TLSVersion) of - #alert {} = Alert -> - throw(Alert); - SelectedHashSign -> - Connection:next_event(?FUNCTION_NAME, no_record, - State#state{client_certificate_requested = true, - handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = SelectedHashSign}}) - end - end; + Session = select_client_cert_key_pair(Session0, CertRequest, CertKeyPairs, + SupportedHashSigns, TLSVersion, + CertDbHandle, CertDbRef), + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{client_certificate_requested = true, + session = Session}); %% PSK and RSA_PSK might bypass the Server-Key-Exchange certify(internal, #server_hello_done{}, #state{static_env = #static_env{role = client, @@ -552,9 +543,9 @@ cipher(internal, #certificate_verify{signature = Signature, protocol_cb = Connection}, handshake_env = #handshake_env{tls_handshake_history = Hist, kex_algorithm = KexAlg, - public_key_info = PubKeyInfo} = HsEnv, + public_key_info = PubKeyInfo}, connection_env = #connection_env{negotiated_version = Version}, - session = #session{master_secret = MasterSecret} + session = #session{master_secret = MasterSecret} = Session0 } = State) -> TLSVersion = ssl:tls_version(Version), @@ -564,7 +555,7 @@ cipher(internal, #certificate_verify{signature = Signature, TLSVersion, HashSign, MasterSecret, Hist) of valid -> Connection:next_event(?FUNCTION_NAME, no_record, - State#state{handshake_env = HsEnv#handshake_env{cert_hashsign_algorithm = HashSign}}); + State#state{session = Session0#session{sign_alg = HashSign}}); #alert{} = Alert -> throw(Alert) end; @@ -674,7 +665,8 @@ downgrade(Type, Event, State) -> ssl_gen_statem:handle_common_event(Type, Event, ?FUNCTION_NAME, State). gen_handshake(StateName, Type, Event, State) -> - try tls_dtls_connection:StateName(Type, Event, State) + try + tls_dtls_connection:StateName(Type, Event, State) catch error:_ -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake_data)) end. @@ -876,13 +868,13 @@ 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}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, + connection_env = #connection_env{negotiated_version = Version}, client_certificate_requested = true, - session = #session{master_secret = MasterSecret, - own_certificates = OwnCerts}} = State, Connection) -> + session = #session{sign_alg = HashSign, + master_secret = MasterSecret, + private_key = PrivateKey, + own_certificates = OwnCerts}} = State, Connection) -> case ssl_handshake:client_certificate_verify(OwnCerts, MasterSecret, ssl:tls_version(Version), HashSign, PrivateKey, Hist) of #certificate_verify{} = Verified -> @@ -912,14 +904,14 @@ server_certify_and_key_exchange(State0, Connection) -> request_client_cert(State2, Connection). certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{connection_env = #connection_env{private_key = Key}, + #state{session = #session{private_key = PrivateKey}, handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} = State, Connection) -> FakeSecret = make_premaster_secret(Version, rsa), %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret %% and fail handshake later.RFC 5246 section 7.4.7.1. PremasterSecret = - try ssl_handshake:premaster_secret(EncPMS, Key) of + try ssl_handshake:premaster_secret(EncPMS, PrivateKey) of Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES -> case Secret of <> -> %% Correct @@ -970,11 +962,11 @@ 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{connection_env = #connection_env{private_key = Key}, + #state{session = #session{private_key = PrivateKey}, ssl_options = #{user_lookup_fun := PSKLookup}} = State0, Connection) -> - PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PrivateKey, PSKLookup), calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); certify_client_key_exchange(#client_srp_public{} = ClientKey, #state{handshake_env = #handshake_env{srp_params = Params, @@ -1005,8 +997,8 @@ 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_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, connection_states = ConnectionStates0} = State0, Connection) when KexAlg == dhe_dss; KexAlg == dhe_rsa; @@ -1024,8 +1016,7 @@ key_exchange(#state{static_env = #static_env{role = server}, State#state{handshake_env = HsEnv#handshake_env{kex_keys = DHKeys}}; key_exchange(#state{static_env = #static_env{role = server}, handshake_env = #handshake_env{kex_algorithm = KexAlg} = HsEnv, - connection_env = #connection_env{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key}, - session = Session} = State, _) + session = #session{private_key = #'ECPrivateKey'{parameters = ECCurve} = Key} = Session} = State, _) when KexAlg == ecdh_ecdsa; KexAlg == ecdh_rsa -> State#state{handshake_env = HsEnv#handshake_env{kex_keys = Key}, @@ -1033,9 +1024,8 @@ key_exchange(#state{static_env = #static_env{role = server}, 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_env = #connection_env{negotiated_version = Version}, + session = #session{ecc = ECCCurve, private_key = PrivateKey}, connection_states = ConnectionStates0} = State0, Connection) when KexAlg == ecdhe_ecdsa; KexAlg == ecdhe_rsa; @@ -1061,9 +1051,9 @@ key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #{psk_identity := PskIdentityHint}, handshake_env = #handshake_env{kex_algorithm = psk, hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - connection_states = ConnectionStates0} = State0, Connection) -> + connection_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, + connection_states = ConnectionStates0} = State0, Connection) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates0, read), #security_parameters{client_random = ClientRandom, @@ -1079,8 +1069,8 @@ key_exchange(#state{static_env = #static_env{role = server}, 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_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, connection_states = ConnectionStates0 } = State0, Connection) -> DHKeys = public_key:generate_key(Params), @@ -1100,9 +1090,8 @@ key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #{psk_identity := PskIdentityHint}, handshake_env = #handshake_env{kex_algorithm = ecdhe_psk, hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{ecc = ECCCurve}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{ecc = ECCCurve, private_key = PrivateKey}, connection_states = ConnectionStates0 } = State0, Connection) -> ECDHKeys = public_key:generate_key(ECCCurve), @@ -1126,8 +1115,8 @@ key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #{psk_identity := PskIdentityHint}, handshake_env = #handshake_env{kex_algorithm = rsa_psk, hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{private_key = PrivateKey}, connection_states = ConnectionStates0 } = State0, Connection) -> #{security_parameters := SecParams} = @@ -1144,9 +1133,8 @@ key_exchange(#state{static_env = #static_env{role = server}, ssl_options = #{user_lookup_fun := LookupFun}, handshake_env = #handshake_env{kex_algorithm = KexAlg, hashsign_algorithm = HashSignAlgo}, - connection_env = #connection_env{negotiated_version = Version, - private_key = PrivateKey}, - session = #session{srp_username = Username}, + connection_env = #connection_env{negotiated_version = Version}, + session = #session{srp_username = Username, private_key = PrivateKey}, connection_states = ConnectionStates0 } = State0, Connection) when KexAlg == srp_dss; @@ -1653,3 +1641,26 @@ ocsp_info(#{ocsp_expect := no_staple} = OcspState, _, PeerCert) -> ocsp_responder_certs => [], ocsp_state => OcspState }. + +select_client_cert_key_pair(Session0, _, [], _, _) -> + %% No certificate compliant: empty certificate will be sent + Session0#session{own_certificates = undefined, + private_key = undefined}; +select_client_cert_key_pair(Session0, _, + [#{private_key := undefined = NoKey, certs := undefined = NoCerts}], + _, _) -> + %% No certificate supplied : empty certificate will be sent + Session0#session{own_certificates = NoCerts, + private_key = NoKey}; +select_client_cert_key_pair(Session0, CertRequest, + [#{private_key := PrivateKey, certs := [Cert| _] = Certs} | Rest], + SupportedHashSigns, TLSVersion) -> + case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, TLSVersion) of + #alert {} -> + select_client_cert_key_pair(Session0, CertRequest, Rest, SupportedHashSigns, TLSVersion); + SelectedHashSign -> + Session0#session{sign_alg = SelectedHashSign, + own_certificates = Certs, + private_key = PrivateKey + } + end. diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 8b81e1dcc5..3bc6be3bfe 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -36,7 +36,7 @@ -include_lib("kernel/include/logger.hrl"). %% Handshake handling --export([client_hello/10, hello/5, hello/4]). +-export([client_hello/9, hello/5, hello/4]). %% Handshake encoding -export([encode_handshake/2]). @@ -54,7 +54,7 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(ssl:host(), inet:port_number(), ssl_record:connection_states(), - ssl_options(), binary(), boolean(), der_cert(), + ssl_options(), binary(), boolean(), #key_share_client_hello{} | undefined, tuple() | undefined, binary() | undefined) -> #client_hello{}. @@ -66,7 +66,7 @@ client_hello(_Host, _Port, ConnectionStates, ciphers := UserSuites, fallback := Fallback } = SslOpts, - Id, Renegotiation, _OwnCert, KeyShare, TicketData, OcspNonce) -> + Id, Renegotiation, KeyShare, TicketData, OcspNonce) -> Version = tls_record:highest_protocol_version(Versions), %% In TLS 1.3, the client indicates its version preferences in the @@ -212,7 +212,7 @@ hello(#server_hello{server_version = Version, %%-------------------------------------------------------------------- -spec hello(#client_hello{}, ssl_options(), {pid(), #session{}, ssl_record:connection_states(), - binary() | undefined, ssl:kex_algo()}, + list(), ssl:kex_algo()}, boolean()) -> {tls_record:tls_version(), ssl:session_id(), ssl_record:connection_states(), alpn | npn, binary() | undefined}| @@ -324,23 +324,23 @@ handle_client_hello(Version, #{versions := Versions, eccs := SupportedECCs, honor_ecc_order := ECCOrder} = SslOpts, - {SessIdTracker, Session0, ConnectionStates0, OwnCerts, _}, + {SessIdTracker, Session0, ConnectionStates0, CertKeyPairs, _}, Renegotiation) -> case tls_record:is_acceptable_version(Version, Versions) of true -> - OwnCert = ssl_handshake:select_own_cert(OwnCerts), SupportedHashSigns = maps:get(signature_algs, SslOpts, undefined), Curves = maps:get(elliptic_curves, HelloExt, undefined), ClientHashSigns = maps:get(signature_algs, HelloExt, undefined), ClientSignatureSchemes = maps:get(signature_algs_cert, HelloExt, undefined), AvailableHashSigns = ssl_handshake:available_signature_algs( - ClientHashSigns, SupportedHashSigns, OwnCert, Version), + ClientHashSigns, SupportedHashSigns, Version), ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, Version, ECCOrder), - {Type, #session{cipher_suite = CipherSuite} = Session1} + {Type, #session{cipher_suite = CipherSuite, + own_certificates = [OwnCert |_]} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, SessIdTracker, Session0#session{ecc = ECCCurve}, - Version, SslOpts, OwnCert), + Version, SslOpts, CertKeyPairs), case CipherSuite of no_suite -> throw(?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers)); diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 8bc2a35d18..40ad07d21e 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -548,8 +548,8 @@ encode_extensions(Exts)-> decode_extensions(Exts, MessageType) -> ssl_handshake:decode_extensions(Exts, {3,4}, MessageType). -extensions_list(HelloExtensions) -> - [Ext || {_, Ext} <- maps:to_list(HelloExtensions)]. +extensions_list(Extensions) -> + [Ext || {_, Ext} <- maps:to_list(Extensions)]. %% TODO: add extensions! @@ -650,7 +650,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers, Cookie = get_cookie(CookieExt), #state{connection_states = ConnectionStates0, - session = #session{own_certificates = [Cert | _]}} = State1 = + session = Session0, + connection_env = #connection_env{cert_key_pairs = CertKeyPairs}} = State1 = Maybe(ssl_gen_statem:handle_sni_extension(SNI, State0)), Maybe(validate_cookie(Cookie, State1)), @@ -665,6 +666,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers, Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), + #session{own_certificates = [Cert|_]} = Session = + select_server_cert_key_pair(Session0, CertKeyPairs, ClientSignAlgs, ClientSignAlgsCert), {PublicKeyAlgo, SignAlgo, SignHash, RSAKeySize, Curve} = get_certificate_params(Cert), %% Check if client supports signature algorithm of server certificate @@ -689,9 +692,10 @@ do_start(#client_hello{cipher_suites = ClientCiphers, ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum}, State1#state{handshake_env = HsEnv1, + session = Session, connection_states = ConnectionStates1}; _ -> - State1 + State1#state{session = Session} end, State3 = if KeepSecrets =:= true -> @@ -747,7 +751,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, use_ticket := UseTicket, session_tickets := SessionTickets, log_level := LogLevel} = SslOpts, - session = #session{own_certificates = OwnCerts} = Session0, + session = Session0, connection_states = ConnectionStates0 } = State0) -> {Ref,Maybe} = maybe(), @@ -779,7 +783,7 @@ do_start(#server_hello{cipher_suite = SelectedCipherSuite, TicketData = get_ticket_data(self(), SessionTickets, UseTicket), OcspNonce = maps:get(ocsp_nonce, OcspState, undefined), Hello0 = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - SessionId, Renegotiation, OwnCerts, ClientKeyShare, + SessionId, Renegotiation, ClientKeyShare, TicketData, OcspNonce), %% Echo cookie received in HelloRetryrequest Hello1 = maybe_add_cookie_extension(Cookie, Hello0), @@ -1241,8 +1245,8 @@ maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) -> {ok, State}; maybe_queue_cert_verify(_Certificate, #state{connection_states = _ConnectionStates0, - session = #session{sign_alg = SignatureScheme}, - connection_env = #connection_env{private_key = CertPrivateKey}, + session = #session{sign_alg = SignatureScheme, + private_key = CertPrivateKey}, static_env = #static_env{role = client, protocol_cb = Connection} } = State) -> @@ -1362,10 +1366,10 @@ maybe_send_certificate(#state{session = #session{own_certificates = OwnCerts}, maybe_send_certificate_verify(State, PSK) when PSK =/= undefined -> {ok, State}; -maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme}, - static_env = #static_env{protocol_cb = Connection}, - connection_env = #connection_env{ - private_key = CertPrivateKey}} = State, _) -> +maybe_send_certificate_verify(#state{session = #session{sign_alg = SignatureScheme, + private_key = CertPrivateKey}, + static_env = #static_env{protocol_cb = Connection} + } = State, _) -> case certificate_verify(CertPrivateKey, SignatureScheme, State, server) of {ok, CertificateVerify} -> {ok, Connection:queue_handshake(CertificateVerify, State)}; @@ -1422,38 +1426,19 @@ create_change_cipher_spec(#state{ssl_options = #{log_level := LogLevel}}) -> ssl_logger:debug(LogLevel, outbound, 'record', BinChangeCipher), [BinChangeCipher]. -process_certificate_request(#certificate_request_1_3{}, - #state{session = #session{own_certificates = undefined}} = State) -> - {ok, {State#state{client_certificate_requested = true}, wait_cert}}; - process_certificate_request(#certificate_request_1_3{ extensions = Extensions}, #state{ssl_options = #{signature_algs := ClientSignAlgs}, - session = #session{own_certificates = [Cert|_]} = Session} = + connection_env = #connection_env{cert_key_pairs = CertKeyPairs}, + session = Session0} = State) -> ServerSignAlgs = get_signature_scheme_list( maps:get(signature_algs, Extensions, undefined)), ServerSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - {PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize, Curve} = get_certificate_params(Cert), - {Ref, Maybe} = maybe(), - try - SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs, Curve)), - %% Check if server supports signature algorithm of client certificate - case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of - ok -> - {ok, {State#state{client_certificate_requested = true, - session = Session#session{sign_alg = SelectedSignAlg}}, wait_cert}}; - {error, _} -> - %% Certificate not supported: send empty certificate in state 'wait_finished' - {ok, {State#state{client_certificate_requested = true, - session = Session#session{own_certificates = undefined}}, wait_cert}} - end - catch - {Ref, #alert{} = Alert} -> - Alert - end. + Session = select_client_cert_key_pair(Session0, CertKeyPairs, ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs), + {ok, {State#state{client_certificate_requested = true, session = Session}, wait_cert}}. process_certificate(#certificate_1_3{ certificate_request_context = <<>>, @@ -2969,3 +2954,35 @@ supported_groups_from_extensions(Extensions) -> undefined -> {ok, undefined} end. + +select_server_cert_key_pair(Session, [#{private_key := Key, certs := [_ | _] = Certs}], _ClientSignAlgs, _ClientSignAlgsCert) -> + Session#session{own_certificates = Certs, private_key = Key}. + +select_client_cert_key_pair(Session, [], _, _, _) -> + %% Certificate not supported: send empty certificate in state 'wait_finished' + Session#session{own_certificates = undefined, + private_key = undefined}; +select_client_cert_key_pair(Session0, + [#{private_key := undefined = NoKey, certs := undefined = NoCerts}], + _, _, _) -> + %% No certificate supplied : send empty certificate + Session0#session{own_certificates = NoCerts, + private_key = NoKey}; +select_client_cert_key_pair(Session, [#{private_key := Key, certs := [Cert| _] = Certs} | Rest], + ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs) -> + {PublicKeyAlgo, SignAlgo, SignHash, MaybeRSAKeySize, Curve} = get_certificate_params(Cert), + case select_sign_algo(PublicKeyAlgo, MaybeRSAKeySize, ServerSignAlgs, ClientSignAlgs, Curve) of + {ok, SelectedSignAlg} -> + %% Check if server supports signature algorithm of client certificate + case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of + ok -> + Session#session{sign_alg = SelectedSignAlg, + own_certificates = Certs, + private_key = Key + }; + _ -> + select_client_cert_key_pair(Session, Rest, ServerSignAlgs, ServerSignAlgsCert, ClientSignAlgs) + end; + {error, _} -> + select_client_cert_key_pair(Session, Rest, ServerSignAlgsCert, ServerSignAlgsCert, ClientSignAlgs) + end. -- cgit v1.2.1