aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/src
diff options
context:
space:
mode:
authorIngela Anderton Andin <[email protected]>2016-02-05 17:39:04 +0100
committerIngela Anderton Andin <[email protected]>2016-04-06 11:43:18 +0200
commit04397344762d506ac0286118d36e1a5b330dceb2 (patch)
tree67882f128c586cf94d1161d36b303a3d39fd5921 /lib/ssl/src
parentafe72bfc1448ff426c38eceb7412f69e973aef62 (diff)
downloadotp-04397344762d506ac0286118d36e1a5b330dceb2.tar.gz
otp-04397344762d506ac0286118d36e1a5b330dceb2.tar.bz2
otp-04397344762d506ac0286118d36e1a5b330dceb2.zip
ssl: Add option signature_algs
In TLS-1.2 The signature algorithm and the hash function algorithm used to produce the digest that is used when creating the digital signature may be negotiated through the signature algorithm extension RFC 5246. We want to make these algorithm pairs configurable. In connections using lower versions of TLS these algorithms are implicit defined and can not be negotiated or configured. DTLS is updated to not cause dialyzer errors, but needs to get a real implementation later.
Diffstat (limited to 'lib/ssl/src')
-rw-r--r--lib/ssl/src/dtls_connection.erl7
-rw-r--r--lib/ssl/src/dtls_handshake.erl5
-rw-r--r--lib/ssl/src/ssl.erl25
-rw-r--r--lib/ssl/src/ssl_cipher.erl3
-rw-r--r--lib/ssl/src/ssl_connection.erl52
-rw-r--r--lib/ssl/src/ssl_handshake.erl202
-rw-r--r--lib/ssl/src/ssl_handshake.hrl2
-rw-r--r--lib/ssl/src/ssl_internal.hrl3
-rw-r--r--lib/ssl/src/tls_connection.erl23
-rw-r--r--lib/ssl/src/tls_handshake.erl48
-rw-r--r--lib/ssl/src/tls_v1.erl62
11 files changed, 297 insertions, 135 deletions
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 153d3fef48..e490de7eeb 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -196,8 +196,7 @@ hello(start, #state{host = Host, port = Port, role = client,
{Record, State} = next_record(State1),
next_state(hello, hello, Record, State);
-hello(Hello = #client_hello{client_version = ClientVersion,
- extensions = #hello_extensions{hash_signs = HashSigns}},
+hello(Hello = #client_hello{client_version = ClientVersion},
State = #state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
@@ -209,9 +208,7 @@ hello(Hello = #client_hello{client_version = ClientVersion,
{Version, {Type, Session},
ConnectionStates,
#hello_extensions{ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves} = ServerHelloExt} ->
- HashSign = ssl_handshake:select_hashsign(HashSigns, Cert,
- dtls_v1:corresponding_tls_version(Version)),
+ elliptic_curves = EllipticCurves} = ServerHelloExt, HashSign} ->
ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign},
State#state{connection_states = ConnectionStates,
negotiated_version = Version,
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 22c0ce7a13..50c84b712f 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -94,7 +94,10 @@ hello(#server_hello{server_version = Version, random = Random,
hello(#client_hello{client_version = ClientVersion}, _Options, {_,_,_,_,ConnectionStates,_}, _Renegotiation) ->
%% Return correct typ to make dialyzer happy until we have time to make the real imp.
- {ClientVersion, {new, #session{}}, ConnectionStates, #hello_extensions{}}.
+ HashSigns = tls_v1:default_signature_algs(dtls_v1:corresponding_tls_version(ClientVersion)),
+ {ClientVersion, {new, #session{}}, ConnectionStates, #hello_extensions{},
+ %% Placeholder for real hasign handling
+ hd(HashSigns)}.
%% hello(Address, Port,
%% #ssl_tls{epoch = _Epoch, sequence_number = _Seq,
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 780bef5877..9045f8fef9 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -700,6 +700,10 @@ handle_options(Opts0, Role) ->
srp_identity = handle_option(srp_identity, Opts, undefined),
ciphers = handle_cipher_option(proplists:get_value(ciphers, Opts, []),
RecordCb:highest_protocol_version(Versions)),
+ signature_algs = handle_hashsigns_option(proplists:get_value(signature_algs, Opts,
+ default_option_role(server,
+ tls_v1:default_signature_algs(Versions), Role)),
+ RecordCb:highest_protocol_version(Versions)),
%% Server side option
reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
reuse_sessions = handle_option(reuse_sessions, Opts, true),
@@ -749,7 +753,7 @@ handle_options(Opts0, Role) ->
alpn_preferred_protocols, next_protocols_advertised,
client_preferred_next_protocols, log_alert,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
- fallback],
+ fallback, signature_algs],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -989,6 +993,18 @@ validate_option(crl_cache, {Cb, {_Handle, Options}} = Value) when is_atom(Cb) an
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
+handle_hashsigns_option(Value, {Major, Minor} = Version) when is_list(Value)
+ andalso Major >= 3 andalso Minor >= 3->
+ case tls_v1:signature_algs(Version, Value) of
+ [] ->
+ throw({error, {options, no_supported_algorithms, {signature_algs, Value}}});
+ _ ->
+ Value
+ end;
+handle_hashsigns_option(_, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3->
+ handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version);
+handle_hashsigns_option(_, _Version) ->
+ undefined.
validate_options([]) ->
[];
@@ -1285,6 +1301,13 @@ new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts,
new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB);
new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
new_ssl_options(Rest, Opts#ssl_options{honor_cipher_order = validate_option(honor_cipher_order, Value)}, RecordCB);
+new_ssl_options([{signature_algs, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest,
+ Opts#ssl_options{signature_algs =
+ handle_hashsigns_option(Value,
+ RecordCB:highest_protocol_version())},
+ RecordCB);
+
new_ssl_options([{Key, Value} | _Rest], #ssl_options{}, _) ->
throw({error, {options, {Key, Value}}}).
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 974a6ec6b5..bef04c574f 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -43,11 +43,12 @@
-export_type([cipher_suite/0,
erl_cipher_suite/0, openssl_cipher_suite/0,
- key_algo/0]).
+ hash/0, key_algo/0, sign_algo/0]).
-type cipher() :: null |rc4_128 | idea_cbc | des40_cbc | des_cbc | '3des_ede_cbc'
| aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305.
-type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512.
+-type sign_algo() :: rsa | dsa | ecdsa.
-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon.
-type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2
%% TLS 1.2, internally PRE TLS 1.2 will use default_prf
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index ec7d086934..f774873269 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -304,13 +304,9 @@ hello(#hello_request{}, #state{role = client} = State0, Connection) ->
{Record, State} = Connection:next_record(State0),
Connection:next_state(hello, hello, Record, State);
-hello({common_client_hello, Type, ServerHelloExt, NegotiatedHashSign},
+hello({common_client_hello, Type, ServerHelloExt},
State, Connection) ->
- do_server_hello(Type, ServerHelloExt,
- %% Note NegotiatedHashSign is only negotiated for real if
- %% if TLS version is at least TLS-1.2
- State#state{hashsign_algorithm = NegotiatedHashSign}, Connection);
-
+ do_server_hello(Type, ServerHelloExt, State, Connection);
hello(timeout, State, _) ->
{next_state, hello, State, hibernate};
@@ -442,7 +438,8 @@ certify(#server_key_exchange{exchange_keys = Keys},
Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
Params = ssl_handshake:decode_server_key(Keys, Alg, Version),
- HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, Version),
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, Version),
case is_anonymous(Alg) of
true ->
calculate_secret(Params#server_key_params.params,
@@ -464,11 +461,18 @@ certify(#server_key_exchange{} = Msg,
certify(#certificate_request{hashsign_algorithms = HashSigns},
#state{session = #session{own_certificate = Cert},
- negotiated_version = Version} = State0, Connection) ->
- HashSign = ssl_handshake:select_hashsign(HashSigns, Cert, Version),
- {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
- Connection:next_state(certify, certify, Record,
- State#state{cert_hashsign_algorithm = HashSign});
+ key_algorithm = KeyExAlg,
+ ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
+ negotiated_version = Version} = State0, Connection) ->
+
+ case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
+ #alert {} = Alert ->
+ Connection:handle_own_alert(Alert, Version, certify, State0);
+ NegotiatedHashSign ->
+ {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
+ Connection:next_state(certify, certify, Record,
+ State#state{cert_hashsign_algorithm = NegotiatedHashSign})
+ end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(#server_hello_done{},
@@ -576,13 +580,15 @@ cipher(#hello_request{}, State0, Connection) ->
cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign},
#state{role = server,
- public_key_info = {Algo, _, _} =PublicKeyInfo,
+ key_algorithm = KexAlg,
+ public_key_info = PublicKeyInfo,
negotiated_version = Version,
session = #session{master_secret = MasterSecret},
tls_handshake_history = Handshake
} = State0, Connection) ->
-
- HashSign = ssl_handshake:select_hashsign_algs(CertHashSign, Algo, Version),
+
+ %% Use negotiated value if TLS-1.2 otherwhise return default
+ HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, Version),
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
Version, HashSign, MasterSecret, Handshake) of
valid ->
@@ -1448,7 +1454,8 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Alg
rsa_psk_key_exchange(_, _, _, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
-request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
+request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
+ signature_algs = SupportedHashSigns},
connection_states = ConnectionStates0,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
@@ -1456,7 +1463,9 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
#connection_state{security_parameters =
#security_parameters{cipher_suite = CipherSuite}} =
ssl_record:pending_connection_state(ConnectionStates0, read),
- Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version),
+ HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, Version, [Version]),
+ Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef,
+ HashSigns, Version),
State = Connection:send_handshake(Msg, State0),
State#state{client_certificate_requested = true};
@@ -1881,15 +1890,16 @@ make_premaster_secret({MajVer, MinVer}, rsa) ->
make_premaster_secret(_, _) ->
undefined.
-negotiated_hashsign(undefined, Alg, Version) ->
+negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) ->
%% Not negotiated choose default
- case is_anonymous(Alg) of
+ case is_anonymous(KexAlg) of
true ->
{null, anon};
false ->
- ssl_handshake:select_hashsign_algs(Alg, Version)
+ {PubAlg, _, _} = PubKeyInfo,
+ ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version)
end;
-negotiated_hashsign(HashSign = {_, _}, _, _) ->
+negotiated_hashsign(HashSign = {_, _}, _, _, _) ->
HashSign.
ssl_options_list(SslOptions) ->
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index e98073080a..644903cf4b 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -46,7 +46,7 @@
%% Handshake messages
-export([hello_request/0, server_hello/4, server_hello_done/0,
- certificate/4, certificate_request/4, key_exchange/3,
+ certificate/4, certificate_request/5, key_exchange/3,
finished/5, next_protocol/1]).
%% Handle handshake messages
@@ -64,8 +64,8 @@
]).
%% Cipher suites handling
--export([available_suites/2, cipher_suites/2,
- select_session/10, supported_ecc/1]).
+-export([available_suites/2, available_signature_algs/3, cipher_suites/2,
+ select_session/11, supported_ecc/1]).
%% Extensions handling
-export([client_hello_extensions/6,
@@ -74,8 +74,8 @@
]).
%% MISC
--export([select_version/3, prf/5, select_hashsign/3,
- select_hashsign_algs/2, select_hashsign_algs/3,
+-export([select_version/3, prf/5, select_hashsign/5,
+ select_hashsign_algs/3,
premaster_secret/2, premaster_secret/3, premaster_secret/4]).
%%====================================================================
@@ -120,7 +120,8 @@ server_hello(SessionId, Version, ConnectionStates, Extensions) ->
server_hello_done() ->
#server_hello_done{}.
-client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) ->
+client_hello_extensions(Host, Version, CipherSuites,
+ #ssl_options{signature_algs = SupportedHashSigns, versions = AllVersions} = SslOpts, ConnectionStates, Renegotiation) ->
{EcPointFormats, EllipticCurves} =
case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of
true ->
@@ -134,7 +135,7 @@ client_hello_extensions(Host, Version, CipherSuites, SslOpts, ConnectionStates,
renegotiation_info = renegotiation_info(tls_record, client,
ConnectionStates, Renegotiation),
srp = SRP,
- hash_signs = advertised_hash_signs(Version),
+ signature_algs = available_signature_algs(SupportedHashSigns, Version, AllVersions),
ec_point_formats = EcPointFormats,
elliptic_curves = EllipticCurves,
alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation),
@@ -203,14 +204,14 @@ client_certificate_verify(OwnCert, MasterSecret, Version,
end.
%%--------------------------------------------------------------------
--spec certificate_request(ssl_cipher:cipher_suite(), db_handle(), certdb_ref(), ssl_record:ssl_version()) ->
- #certificate_request{}.
+-spec certificate_request(ssl_cipher:cipher_suite(), db_handle(),
+ certdb_ref(), #hash_sign_algos{}, ssl_record:ssl_version()) ->
+ #certificate_request{}.
%%
%% Description: Creates a certificate_request message, called by the server.
%%--------------------------------------------------------------------
-certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version) ->
+certificate_request(CipherSuite, CertDbHandle, CertDbRef, HashSigns, Version) ->
Types = certificate_types(ssl_cipher:suite_definition(CipherSuite), Version),
- HashSigns = advertised_hash_signs(Version),
Authorities = certificate_authorities(CertDbHandle, CertDbRef),
#certificate_request{
certificate_types = Types,
@@ -351,6 +352,9 @@ verify_server_key(#server_key_params{params_bin = EncParams,
%%
%% Description: Checks that the certificate_verify message is valid.
%%--------------------------------------------------------------------
+certificate_verify(_, _, _, undefined, _, _) ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+
certificate_verify(Signature, PublicKeyInfo, Version,
HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) ->
Hash = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake),
@@ -379,10 +383,11 @@ verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey,
end;
verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) ->
public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams});
-verify_signature(_Version, Hash, {HashAlgo, ecdsa}, Signature,
+verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature,
{?'id-ecPublicKey', PublicKey, PublicKeyParams}) ->
public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}).
+
%%--------------------------------------------------------------------
-spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit,
verify_peer | verify_none, {fun(), term}, fun(), term(), term(),
@@ -573,43 +578,46 @@ prf({3,_N}, Secret, Label, Seed, WantedLength) ->
%%--------------------------------------------------------------------
--spec select_hashsign(#hash_sign_algos{}| undefined, undefined | binary(), ssl_record:ssl_version()) ->
- {atom(), atom()} | undefined.
+-spec select_hashsign(#hash_sign_algos{} | undefined, undefined | binary(),
+ atom(), [atom()], ssl_record:ssl_version()) ->
+ {atom(), atom()} | undefined | #alert{}.
%%
-%% Description:
+%% Description: Handles signature_algorithms extension
%%--------------------------------------------------------------------
-select_hashsign(_, undefined, _Version) ->
+select_hashsign(_, undefined, _, _, _Version) ->
{null, anon};
%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have
%% negotiated a lower version.
-select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, {Major, Minor} = Version)
- when Major >= 3 andalso Minor >= 3 ->
- #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp),
+select_hashsign(HashSigns, Cert, KeyExAlgo,
+ undefined, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3->
+ select_hashsign(HashSigns, Cert, KeyExAlgo, tls_v1:default_signature_algs(Version), Version);
+select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns,
+ {Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
+ #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
#'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- DefaultHashSign = {_, Sign} = select_hashsign_algs(undefined, Algo, Version),
- case lists:filter(fun({sha, dsa}) ->
+ Sign = cert_sign(Algo),
+ case lists:filter(fun({sha, dsa = S}) when S == Sign ->
true;
({_, dsa}) ->
false;
- ({Hash, S}) when S == Sign ->
- ssl_cipher:is_acceptable_hash(Hash,
- proplists:get_value(hashs, crypto:supports()));
+ ({_, _} = Algos) ->
+ is_acceptable_hash_sign(Algos, Sign, KeyExAlgo, SupportedHashSigns);
(_) ->
false
end, HashSigns) of
[] ->
- DefaultHashSign;
- [HashSign| _] ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ [HashSign | _] ->
HashSign
end;
-select_hashsign(_, Cert, Version) ->
+select_hashsign(_, Cert, _, _, Version) ->
#'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
#'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
select_hashsign_algs(undefined, Algo, Version).
%%--------------------------------------------------------------------
--spec select_hashsign_algs(#hash_sign_algos{}| undefined, oid(), ssl_record:ssl_version()) ->
+-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) ->
{atom(), atom()}.
%% Description: For TLS 1.2 hash function and signature algorithm pairs can be
@@ -642,24 +650,6 @@ select_hashsign_algs(undefined, ?rsaEncryption, _) ->
select_hashsign_algs(undefined, ?'id-dsa', _) ->
{sha, dsa}.
--spec select_hashsign_algs(atom(), ssl_record:ssl_version()) -> {atom(), atom()}.
-%% Wrap function to keep the knowledge of the default values in
-%% one place only
-select_hashsign_algs(Alg, Version) when (Alg == rsa orelse
- Alg == dhe_rsa orelse
- Alg == dh_rsa orelse
- Alg == ecdhe_rsa orelse
- Alg == ecdh_rsa orelse
- Alg == srp_rsa) ->
- select_hashsign_algs(undefined, ?rsaEncryption, Version);
-select_hashsign_algs(Alg, Version) when (Alg == dhe_dss orelse
- Alg == dh_dss orelse
- Alg == srp_dss) ->
- select_hashsign_algs(undefined, ?'id-dsa', Version);
-select_hashsign_algs(Alg, Version) when (Alg == ecdhe_ecdsa orelse
- Alg == ecdh_ecdsa) ->
- select_hashsign_algs(undefined, ?'id-ecPublicKey', Version).
-
%%--------------------------------------------------------------------
-spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{},
client | server) -> {binary(), #connection_states{}} | #alert{}.
@@ -1063,9 +1053,56 @@ available_suites(UserSuites, Version) ->
lists:member(Suite, ssl_cipher:all_suites(Version))
end, UserSuites).
-available_suites(ServerCert, UserSuites, Version, Curve) ->
+available_suites(ServerCert, UserSuites, Version, undefined, Curve) ->
ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version))
- -- unavailable_ecc_suites(Curve).
+ -- unavailable_ecc_suites(Curve);
+available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) ->
+ Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve),
+ filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns, []).
+filter_hashsigns([], [], _, Acc) ->
+ lists:reverse(Acc);
+filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns,
+ Acc) when KeyExchange == dhe_ecdsa;
+ KeyExchange == ecdhe_ecdsa ->
+ do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Acc);
+
+filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns,
+ Acc) when KeyExchange == rsa;
+ KeyExchange == dhe_rsa;
+ KeyExchange == ecdhe_rsa;
+ KeyExchange == srp_rsa;
+ KeyExchange == rsa_psk ->
+ do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Acc);
+filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
+ KeyExchange == dhe_dss;
+ KeyExchange == srp_dss ->
+ do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Acc);
+filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
+ KeyExchange == dh_dss;
+ KeyExchange == dh_rsa;
+ KeyExchange == dh_ecdsa;
+ KeyExchange == ecdh_rsa;
+ KeyExchange == ecdh_ecdsa ->
+ %% Fixed DH certificates MAY be signed with any hash/signature
+ %% algorithm pair appearing in the hash_sign extension. The names
+ %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
+ filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
+filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
+ KeyExchange == dh_anon;
+ KeyExchange == ecdh_anon;
+ KeyExchange == srp_anon;
+ KeyExchange == psk;
+ KeyExchange == dhe_psk ->
+ %% In this case hashsigns is not used as the kexchange is anonaymous
+ filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]).
+
+do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Acc) ->
+ case lists:keymember(SignAlgo, 2, HashSigns) of
+ true ->
+ filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
+ false ->
+ filter_hashsigns(Suites, Algos, HashSigns, Acc)
+ end.
unavailable_ecc_suites(no_curve) ->
ssl_cipher:ec_keyed_suites();
@@ -1077,17 +1114,17 @@ cipher_suites(Suites, false) ->
cipher_suites(Suites, true) ->
Suites.
-select_session(SuggestedSessionId, CipherSuites, Compressions, Port, #session{ecc = ECCCurve} =
+select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve} =
Session, Version,
- #ssl_options{ciphers = UserSuites, honor_cipher_order = HCO} = SslOpts,
+ #ssl_options{ciphers = UserSuites, honor_cipher_order = HonorCipherOrder} = SslOpts,
Cache, CacheCb, Cert) ->
{SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId,
SslOpts, Cert,
Cache, CacheCb),
case Resumed of
undefined ->
- Suites = available_suites(Cert, UserSuites, Version, ECCCurve),
- CipherSuite = select_cipher_suite(CipherSuites, Suites, HCO),
+ Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve),
+ CipherSuite = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder),
Compression = select_compression(Compressions),
{new, Session#session{session_id = SessionId,
cipher_suite = CipherSuite,
@@ -1155,7 +1192,7 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
#hello_extensions{renegotiation_info = Info,
srp = SRP,
ec_point_formats = ECCFormat,
- alpn = ALPN,
+ alpn = ALPN,
next_protocol_negotiation = NextProtocolNegotiation}, Version,
#ssl_options{secure_renegotiate = SecureRenegotation,
alpn_preferred_protocols = ALPNPreferredProtocols} = Opts,
@@ -1324,7 +1361,7 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo,
srp = SRP,
- hash_signs = HashSigns,
+ signature_algs = HashSigns,
ec_point_formats = EcPointFormats,
elliptic_curves = EllipticCurves,
alpn = ALPN,
@@ -1799,7 +1836,7 @@ dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
<<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData,
HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} ||
<<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList],
- dec_hello_extensions(Rest, Acc#hello_extensions{hash_signs =
+ dec_hello_extensions(Rest, Acc#hello_extensions{signature_algs =
#hash_sign_algos{hash_sign_algos = HashSignAlgos}});
dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len),
@@ -1899,7 +1936,7 @@ from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) ->
key_exchange_alg(rsa) ->
?KEY_EXCHANGE_RSA;
key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss;
- Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon ->
+ Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon ->
?KEY_EXCHANGE_DIFFIE_HELLMAN;
key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa;
Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa;
@@ -2008,27 +2045,16 @@ is_member(Suite, SupportedSuites) ->
select_compression(_CompressionMetodes) ->
?NULL.
--define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}).
--define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}).
--define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}).
-
--define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)).
-
-advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
- HashSigns = [?TLSEXT_SIGALG(sha512),
- ?TLSEXT_SIGALG(sha384),
- ?TLSEXT_SIGALG(sha256),
- ?TLSEXT_SIGALG(sha224),
- ?TLSEXT_SIGALG(sha),
- ?TLSEXT_SIGALG_DSA(sha),
- ?TLSEXT_SIGALG_RSA(md5)],
- CryptoSupport = crypto:supports(),
- HasECC = proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)),
- Hashs = proplists:get_value(hashs, CryptoSupport),
- #hash_sign_algos{hash_sign_algos =
- lists:filter(fun({Hash, ecdsa}) -> HasECC andalso proplists:get_bool(Hash, Hashs);
- ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)};
-advertised_hash_signs(_) ->
+available_signature_algs(undefined, _, _) ->
+ undefined;
+available_signature_algs(SupportedHashSigns, {Major, Minor}, AllVersions) when Major >= 3 andalso Minor >= 3 ->
+ case tls_record:lowest_protocol_version(AllVersions) of
+ {3, 3} ->
+ #hash_sign_algos{hash_sign_algos = SupportedHashSigns};
+ _ ->
+ undefined
+ end;
+available_signature_algs(_, _, _) ->
undefined.
psk_secret(PSKIdentity, PSKLookup) ->
@@ -2123,3 +2149,25 @@ distpoints_lookup([DistPoint | Rest], Callback, CRLDbHandle) ->
CRLs ->
[{DistPoint, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs]
end.
+
+cert_sign(?rsaEncryption) ->
+ rsa;
+cert_sign(?'id-ecPublicKey') ->
+ ecdsa;
+cert_sign(?'id-dsa') ->
+ dsa;
+cert_sign(Alg) ->
+ {_, Sign} =public_key:pkix_sign_types(Alg),
+ Sign.
+
+is_acceptable_hash_sign({_, Sign} = Algos, Sign, _, SupportedHashSigns) ->
+ is_acceptable_hash_sign(Algos, SupportedHashSigns);
+is_acceptable_hash_sign(Algos,_, KeyExAlgo, SupportedHashSigns) when KeyExAlgo == dh_ecdsa;
+ KeyExAlgo == ecdh_rsa;
+ KeyExAlgo == ecdh_ecdsa ->
+ is_acceptable_hash_sign(Algos, SupportedHashSigns);
+is_acceptable_hash_sign(_,_,_,_) ->
+ false.
+is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
+ lists:member(Algos, SupportedHashSigns).
+
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 58b4d5a23d..b74a65939b 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -95,7 +95,7 @@
-record(hello_extensions, {
renegotiation_info,
- hash_signs, % supported combinations of hashes/signature algos
+ signature_algs, % supported combinations of hashes/signature algos
alpn,
next_protocol_negotiation = undefined, % [binary()]
srp,
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 8c7ed9c0d1..20f0b7d0da 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -135,7 +135,8 @@
padding_check = true :: boolean(),
fallback = false :: boolean(),
crl_check :: boolean() | peer | best_effort,
- crl_cache
+ crl_cache,
+ signature_algs
}).
-record(socket_options,
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index c3f0206d25..93716d31b8 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -182,8 +182,7 @@ hello(start, #state{host = Host, port = Port, role = client,
next_state(hello, hello, Record, State);
hello(Hello = #client_hello{client_version = ClientVersion,
- extensions = #hello_extensions{hash_signs = HashSigns,
- ec_point_formats = EcPointFormats,
+ extensions = #hello_extensions{ec_point_formats = EcPointFormats,
elliptic_curves = EllipticCurves}},
State = #state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
@@ -191,27 +190,28 @@ hello(Hello = #client_hello{client_version = ClientVersion,
session_cache = Cache,
session_cache_cb = CacheCb,
negotiated_protocol = CurrentProtocol,
+ key_algorithm = KeyExAlg,
ssl_options = SslOpts}) ->
+
case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
- ConnectionStates0, Cert}, Renegotiation) of
+ ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of
#alert{} = Alert ->
handle_own_alert(Alert, ClientVersion, hello, State);
{Version, {Type, Session},
- ConnectionStates, Protocol0, ServerHelloExt} ->
-
+ ConnectionStates, Protocol0, ServerHelloExt, HashSign} ->
Protocol = case Protocol0 of
- undefined -> CurrentProtocol;
- _ -> Protocol0
- end,
-
- HashSign = ssl_handshake:select_hashsign(HashSigns, Cert, Version),
- ssl_connection:hello({common_client_hello, Type, ServerHelloExt, HashSign},
+ undefined -> CurrentProtocol;
+ _ -> Protocol0
+ end,
+ ssl_connection:hello({common_client_hello, Type, ServerHelloExt},
State#state{connection_states = ConnectionStates,
negotiated_version = Version,
+ hashsign_algorithm = HashSign,
session = Session,
client_ecc = {EllipticCurves, EcPointFormats},
negotiated_protocol = Protocol}, ?MODULE)
end;
+
hello(Hello = #server_hello{},
#state{connection_states = ConnectionStates0,
negotiated_version = ReqVersion,
@@ -1069,3 +1069,4 @@ handle_sni_extension(#client_hello{extensions = HelloExtensions}, State0) ->
end;
handle_sni_extension(_, State0) ->
State0.
+
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 0a6cb9f92d..ef718c13df 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -56,7 +56,7 @@ client_hello(Host, Port, ConnectionStates,
Version = tls_record:highest_protocol_version(Versions),
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = Pending#connection_state.security_parameters,
- AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
Extensions = ssl_handshake:client_hello_extensions(Host, Version,
AvailableCipherSuites,
SslOpts, ConnectionStates, Renegotiation),
@@ -80,13 +80,13 @@ client_hello(Host, Port, ConnectionStates,
-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
#connection_states{} | {inet:port_number(), #session{}, db_handle(),
atom(), #connection_states{},
- binary() | undefined},
+ binary() | undefined, ssl_cipher:key_algo()},
boolean()) ->
{tls_record:tls_version(), session_id(),
#connection_states{}, alpn | npn, binary() | undefined}|
{tls_record:tls_version(), {resumed | new, #session{}},
#connection_states{}, binary() | undefined,
- #hello_extensions{}} |
+ #hello_extensions{}, {ssl_cipher:hash(), ssl_cipher:sign_algo()} | undefined} |
#alert{}.
%%
%% Description: Handles a recieved hello message
@@ -149,26 +149,35 @@ get_tls_handshake(Version, Data, Buffer) ->
%%% Internal functions
%%--------------------------------------------------------------------
handle_client_hello(Version, #client_hello{session_id = SugesstedId,
- cipher_suites = CipherSuites,
- compression_methods = Compressions,
- random = Random,
- extensions = #hello_extensions{elliptic_curves = Curves} = HelloExt},
- #ssl_options{versions = Versions} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
+ cipher_suites = CipherSuites,
+ compression_methods = Compressions,
+ random = Random,
+ extensions = #hello_extensions{elliptic_curves = Curves,
+ signature_algs = ClientHashSigns} = HelloExt},
+ #ssl_options{versions = Versions,
+ signature_algs = SupportedHashSigns} = SslOpts,
+ {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) ->
case tls_record:is_acceptable_version(Version, Versions) of
true ->
+ AvailableHashSigns = available_signature_algs(ClientHashSigns, SupportedHashSigns, Cert, Version),
ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)),
{Type, #session{cipher_suite = CipherSuite} = Session1}
- = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions,
+ = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions,
Port, Session0#session{ecc = ECCCurve}, Version,
SslOpts, Cache, CacheCb, Cert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
- handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt,
- SslOpts, Session1, ConnectionStates0,
- Renegotiation)
+ {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
+ case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
+ #alert{} = Alert ->
+ Alert;
+ HashSign ->
+ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt,
+ SslOpts, Session1, ConnectionStates0,
+ Renegotiation, HashSign)
+ end
end;
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
@@ -245,14 +254,14 @@ enc_handshake(HandshakeMsg, Version) ->
handle_client_hello_extensions(Version, Type, Random, CipherSuites,
- HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation) ->
+ HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) ->
try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites,
HelloExt, Version, SslOpts,
Session0, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
- {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt}
+ {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
Alert
end.
@@ -269,3 +278,12 @@ handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
end.
+available_signature_algs(undefined, SupportedHashSigns, _, {Major, Minor}) when (Major < 3) andalso (Minor < 3) ->
+ SupportedHashSigns;
+available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns,
+ _, {Major, Minor}) when (Major < 3) andalso (Minor < 3) ->
+ ordsets:intersection(ClientHashSigns, SupportedHashSigns);
+available_signature_algs(_, _, _, _) ->
+ undefined.
+
+
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 71e5f349dd..543bd33833 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -31,7 +31,8 @@
-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7,
setup_keys/8, suites/1, prf/5,
- ecc_curves/1, oid_to_enum/1, enum_to_oid/1]).
+ ecc_curves/1, oid_to_enum/1, enum_to_oid/1,
+ default_signature_algs/1, signature_algs/2]).
%%====================================================================
%% Internal application API
@@ -258,6 +259,54 @@ suites(3) ->
] ++ suites(2).
+
+signature_algs({3, 3}, HashSigns) ->
+ CryptoSupports = crypto:supports(),
+ Hashes = proplists:get_value(hashs, CryptoSupports),
+ PubKeys = proplists:get_value(public_keys, CryptoSupports),
+ Supported = lists:foldl(fun({Hash, dsa = Sign} = Alg, Acc) ->
+ case proplists:get_bool(dss, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Alg | Acc];
+ false ->
+ Acc
+ end;
+ ({Hash, Sign} = Alg, Acc) ->
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Alg | Acc];
+ false ->
+ Acc
+ end
+ end, [], HashSigns),
+ lists:reverse(Supported).
+
+default_signature_algs({3, 3} = Version) ->
+ Default = [%% SHA2
+ {sha512, ecdsa},
+ {sha512, rsa},
+ {sha384, ecdsa},
+ {sha384, rsa},
+ {sha256, ecdsa},
+ {sha256, rsa},
+ {sha224, ecdsa},
+ {sha224, rsa},
+ %% SHA
+ {sha, ecdsa},
+ {sha, rsa},
+ {sha, dsa},
+ %% MD5
+ {md5, rsa}],
+ signature_algs(Version, Default);
+default_signature_algs(_) ->
+ undefined.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -342,6 +391,17 @@ finished_label(client) ->
finished_label(server) ->
<<"server finished">>.
+is_pair(sha, dsa, _) ->
+ true;
+is_pair(_, dsa, _) ->
+ false;
+is_pair(Hash, ecdsa, Hashs) ->
+ AtLeastSha = Hashs -- [md2,md4,md5],
+ lists:member(Hash, AtLeastSha);
+is_pair(Hash, rsa, Hashs) ->
+ AtLeastMd5 = Hashs -- [md2,md4],
+ lists:member(Hash, AtLeastMd5).
+
%% list ECC curves in prefered order
ecc_curves(_Minor) ->
TLSCurves = [sect571r1,sect571k1,secp521r1,brainpoolP512r1,