Skip to content

Commit

Permalink
ssl: Correct handling of signature algorithm selection
Browse files Browse the repository at this point in the history
In TLS-1.2 the selection of the servers algorithms and the the
possible selection of algorithms for the client certificate verify
message have different requirements.
  • Loading branch information
IngelaAndin committed Jul 8, 2016
1 parent d24b133 commit d7dcfb2
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 26 deletions.
7 changes: 3 additions & 4 deletions lib/ssl/src/ssl_connection.erl
Expand Up @@ -528,13 +528,12 @@ certify(internal, #server_key_exchange{exchange_keys = Keys},
end
end;

certify(internal, #certificate_request{hashsign_algorithms = HashSigns},
certify(internal, #certificate_request{} = CertRequest,
#state{session = #session{own_certificate = Cert},
key_algorithm = KeyExAlg,
role = client,
ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
negotiated_version = Version} = State0, Connection) ->

case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of
case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, Version) of
#alert {} = Alert ->
Connection:handle_own_alert(Alert, Version, certify, State0);
NegotiatedHashSign ->
Expand Down
139 changes: 117 additions & 22 deletions lib/ssl/src/ssl_handshake.erl
Expand Up @@ -74,7 +74,7 @@
]).

%% MISC
-export([select_version/3, prf/6, select_hashsign/5,
-export([select_version/3, prf/6, select_hashsign/4, select_hashsign/5,
select_hashsign_algs/3,
premaster_secret/2, premaster_secret/3, premaster_secret/4]).

Expand Down Expand Up @@ -581,7 +581,7 @@ prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) ->
{atom(), atom()} | undefined | #alert{}.

%%
%% Description: Handles signature_algorithms extension
%% Description: Handles signature_algorithms hello extension (server)
%%--------------------------------------------------------------------
select_hashsign(_, undefined, _, _, _Version) ->
{null, anon};
Expand All @@ -593,14 +593,17 @@ select_hashsign(HashSigns, Cert, KeyExAlgo,
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,
Sign = cert_sign(Algo),
case lists:filter(fun({sha, dsa = S}) when S == Sign ->
true;
({_, dsa}) ->
false;
({_, _} = Algos) ->
is_acceptable_hash_sign(Algos, Sign, KeyExAlgo, SupportedHashSigns);
#'OTPCertificate'{tbsCertificate = TBSCert,
signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp),
#'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,

Sign = sign_algo(SignAlgo),
SubSing = sign_algo(SubjAlgo),

case lists:filter(fun({_, S} = Algos) when S == Sign ->
is_acceptable_hash_sign(Algos, Sign,
SubSing, KeyExAlgo, SupportedHashSigns);
(_) ->
false
end, HashSigns) of
Expand All @@ -613,6 +616,49 @@ 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(#certificate_request{}, binary(),
[atom()], ssl_record:ssl_version()) ->
{atom(), atom()} | #alert{}.

%%
%% Description: Handles signature algorithms selection for certificate requests (client)
%%--------------------------------------------------------------------
select_hashsign(#certificate_request{}, undefined, _, {Major, Minor}) when Major >= 3 andalso Minor >= 3->
%% There client does not have a certificate and will send an empty reply, the server may fail
%% or accept the connection by its own preference. No signature algorihms needed as there is
%% no certificate to verify.
{undefined, undefined};

select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns},
certificate_types = Types}, Cert, SupportedHashSigns,
{Major, Minor}) when Major >= 3 andalso Minor >= 3->
#'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
#'OTPCertificate'{tbsCertificate = TBSCert,
signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp),
#'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} =
TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,

Sign = sign_algo(SignAlgo),
SubSign = sign_algo(SubjAlgo),

case is_acceptable_cert_type(SubSign, HashSigns, Types) andalso is_supported_sign(Sign, HashSigns) of
true ->
case lists:filter(fun({_, S} = Algos) when S == SubSign ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
(_) ->
false
end, HashSigns) of
[] ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
[HashSign | _] ->
HashSign
end;
false ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)
end;
select_hashsign(#certificate_request{}, Cert, _, Version) ->
select_hashsign(undefined, Cert, undefined, undefined, Version).

%%--------------------------------------------------------------------
-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) ->
Expand Down Expand Up @@ -648,6 +694,7 @@ select_hashsign_algs(undefined, ?rsaEncryption, _) ->
select_hashsign_algs(undefined, ?'id-dsa', _) ->
{sha, dsa}.


%%--------------------------------------------------------------------
-spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{},
client | server) -> {binary(), #connection_states{}} | #alert{}.
Expand Down Expand Up @@ -1143,11 +1190,13 @@ certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 ->
end;

certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa;
KeyExchange == dh_rsa;
KeyExchange == dhe_rsa;
KeyExchange == ecdhe_rsa ->
<<?BYTE(?RSA_SIGN)>>;

certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dhe_dss;
certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_dss;
KeyExchange == dhe_dss;
KeyExchange == srp_dss ->
<<?BYTE(?DSS_SIGN)>>;

Expand Down Expand Up @@ -2164,27 +2213,73 @@ distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) ->
[{DistPoint, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs]
end.

cert_sign(?rsaEncryption) ->
sign_algo(?rsaEncryption) ->
rsa;
cert_sign(?'id-ecPublicKey') ->
sign_algo(?'id-ecPublicKey') ->
ecdsa;
cert_sign(?'id-dsa') ->
sign_algo(?'id-dsa') ->
dsa;
cert_sign(Alg) ->
sign_algo(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, _, _, KeyExAlgo, SupportedHashSigns) when
KeyExAlgo == dh_dss;
KeyExAlgo == dh_rsa;
KeyExAlgo == dh_ecdsa ->
%% dh_* could be called only dh in TLS-1.2
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign(_,_,_,_) ->
false.
is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, dhe_rsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, ecdhe_rsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, rsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, srp_rsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, rsa_psk, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, dhe_dss, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) ->
is_acceptable_hash_sign(Algos, SupportedHashSigns);
is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when
KeyExAlgo == psk;
KeyExAlgo == dhe_psk;
KeyExAlgo == srp_anon;
KeyExAlgo == dh_anon;
KeyExAlgo == ecdhe_anon
->
true;
is_acceptable_hash_sign(_,_, _,_,_) ->
false.

is_acceptable_hash_sign(Algos, SupportedHashSigns) ->
lists:member(Algos, SupportedHashSigns).

is_acceptable_cert_type(Sign, _HashSigns, Types) ->
lists:member(sign_type(Sign), binary_to_list(Types)).

is_supported_sign(Sign, HashSigns) ->
[] =/= lists:dropwhile(fun({_, S}) when S =/= Sign ->
true;
(_)->
false
end, HashSigns).
sign_type(rsa) ->
?RSA_SIGN;
sign_type(dsa) ->
?DSS_SIGN;
sign_type(ecdsa) ->
?ECDSA_SIGN.


bad_key(#'DSAPrivateKey'{}) ->
unacceptable_dsa_key;
bad_key(#'RSAPrivateKey'{}) ->
Expand Down

0 comments on commit d7dcfb2

Please sign in to comment.