summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIngela Anderton Andin <ingela@erlang.org>2022-01-05 20:03:56 +0100
committerIngela Anderton Andin <ingela@erlang.org>2022-02-28 09:50:49 +0100
commitbf7081f32a19ee457f0c0c685b89ab1215a3ec53 (patch)
tree98e519da7d69dc0f8b1bfbddaa599a6f2a906c36
parent950f81c0187cab943874037ab87dfd22ee0cd6d9 (diff)
downloaderlang-bf7081f32a19ee457f0c0c685b89ab1215a3ec53.tar.gz
ssl: Prepare code to have several certificate key pairs to choose from
-rw-r--r--lib/ssl/src/dtls_connection.erl27
-rw-r--r--lib/ssl/src/dtls_handshake.erl24
-rw-r--r--lib/ssl/src/ssl_config.erl14
-rw-r--r--lib/ssl/src/ssl_connection.hrl4
-rw-r--r--lib/ssl/src/ssl_gen_statem.erl29
-rw-r--r--lib/ssl/src/ssl_handshake.erl78
-rw-r--r--lib/ssl/src/ssl_handshake.hrl1
-rw-r--r--lib/ssl/src/ssl_session.erl55
-rw-r--r--lib/ssl/src/tls_connection.erl19
-rw-r--r--lib/ssl/src/tls_dtls_connection.erl123
-rw-r--r--lib/ssl/src/tls_handshake.erl18
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl87
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
<<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% 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.