From bd0f19c6fa1944365edf03febf75354642fc2240 Mon Sep 17 00:00:00 2001 From: Fred Hebert Date: Thu, 20 Oct 2016 13:55:45 -0400 Subject: Add ECC curve selection order config in TLS server As per RFC 4492 Sec 5.1, the preferred order of selection of named curves is based on client preferences. Currently, the SSL application only picks entries according to the absolute order of entries as tracked in a hardcoded list in code. This patch changes things so that the client-specified order is preferred. It also allows a mode where the server can be configured to override the client's preferred order with its own, although the chosen ECC must still be within both lists. The configuration is done through the following options: - `eccs`, shared by clients and servers alike, allows the specification of the supported named curves, in their preferred order, and may eventually support more values for explicit primes and so on. - `honor_ecc_order`, a server-only option, is similar to `honor_cipher_order` and will, by default let the server pick the client-preferred ECC, and otherwise pick the server-preferred one. The default value for `eccs` is the same as before, although the server-chosen ECC now defaults to the client rather than previous choice. A function `ssl:eccs()` has been added that returns the highest supported ECCs for the library. --- lib/ssl/doc/src/ssl.xml | 31 +++++- lib/ssl/src/ssl.erl | 56 +++++++++- lib/ssl/src/ssl_connection.erl | 20 +++- lib/ssl/src/ssl_handshake.erl | 45 +++++--- lib/ssl/src/ssl_internal.hrl | 2 + lib/ssl/src/tls_handshake.erl | 6 +- lib/ssl/src/tls_v1.erl | 33 ++++-- lib/ssl/test/ssl_ECC_SUITE.erl | 232 ++++++++++++++++++++++++++++++++++++++- lib/ssl/test/ssl_basic_SUITE.erl | 29 +++++ 9 files changed, 420 insertions(+), 34 deletions(-) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 68f2f97b6e..edc7e0d8b2 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -170,6 +170,14 @@ SNIfun::fun()

= fun(ServerName :: string()) -> [ssl_option()]

+ named_curve() = +

sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 + | sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 + | sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 + | sect239k1 | sect233k1 | sect233r1 | secp224k1 | secp224r1 + | sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 + | sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2

+ @@ -217,6 +225,11 @@ Anonymous cipher suites are supported for testing purposes only and are not be used when security matters.

+ {eccs, [named_curve()]} +

Allows to specify the order of preference for named curves + and to restrict their usage when using a cipher suite supporting them. +

+ {secure_renegotiate, boolean()}

Specifies if to reject renegotiation attempt that does not live up to @@ -751,6 +764,11 @@ fun(srp, Username :: string(), UserState :: term()) -> (the default), use the client's preference. + {honor_ecc_order, boolean()} + If true, use the server's preference for ECC curve selection. If false + (the default), use the client's preference. + + {signature_algs, [{hash(), ecdsa | rsa | dsa}]}

The algorithms specified by this option will be the ones accepted by the server in a signature algorithm @@ -803,6 +821,17 @@ fun(srp, Username :: string(), UserState :: term()) -> + + eccs() -> + eccs(protocol()) -> [named_curve()] + Returns a list of supported ECCs. + +

Returns a list of supported ECCs. eccs() + is equivalent to calling eccs(Protocol) with all + supported protocols and then deduplicating the output.

+ + + clear_pem_cache() -> ok Clears the pem cache @@ -898,7 +927,7 @@ fun(srp, Username :: string(), UserState :: term()) -> Returns all the connection information. - Item = protocol | cipher_suite | sni_hostname | atom() + Item = protocol | cipher_suite | sni_hostname | ecc | atom() Meaningful atoms, not specified above, are the ssl option names. Result = [{Item::atom(), Value::term()}] Reason = term() diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 27b753af2e..aa62ab8865 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -38,7 +38,7 @@ getopts/2, setopts/2, getstat/1, getstat/2 ]). %% SSL/TLS protocol handling --export([cipher_suites/0, cipher_suites/1, +-export([cipher_suites/0, cipher_suites/1, eccs/0, eccs/1, connection_info/1, versions/0, session_info/1, format_error/1, renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, connection_information/1, connection_information/2]). @@ -419,6 +419,33 @@ cipher_suites(openssl) -> cipher_suites(all) -> [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(all)]. +%%-------------------------------------------------------------------- +-spec eccs() -> tls_v1:curves(). +%% Description: returns all supported curves across all versions +%%-------------------------------------------------------------------- +eccs() -> + Curves = tls_v1:ecc_curves(all), % only tls_v1 has named curves right now + eccs_filter_supported(Curves). + +%%-------------------------------------------------------------------- +-spec eccs(tls_record:tls_version() | tls_record:tls_atom_version()) -> + tls_v1:curves(). +%% Description: returns the curves supported for a given version of +%% ssl/tls. +%%-------------------------------------------------------------------- +eccs({3,0}) -> + []; +eccs({3,_}) -> + Curves = tls_v1:ecc_curves(all), + eccs_filter_supported(Curves); +eccs(AtomVersion) when is_atom(AtomVersion) -> + eccs(tls_record:protocol_version(AtomVersion)). + +eccs_filter_supported(Curves) -> + CryptoCurves = crypto:ec_curves(), + lists:filter(fun(Curve) -> proplists:get_bool(Curve, CryptoCurves) end, + Curves). + %%-------------------------------------------------------------------- -spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> {ok, [gen_tcp:option()]} | {error, reason()}. @@ -647,6 +674,8 @@ do_connect(Address, Port, end. %% Handle extra ssl options given to ssl_accept +-spec handle_options([any()], #ssl_options{}) -> #ssl_options{} + ; ([any()], client | server) -> {ok, #config{}}. handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, cacertfile = CaCertFile0} = InheritedSslOpts) -> RecordCB = record_cb(Protocol), @@ -725,6 +754,8 @@ 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)), + eccs = handle_eccs_option(proplists:get_value(eccs, Opts, eccs()), + 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)), @@ -755,6 +786,9 @@ handle_options(Opts0, Role) -> honor_cipher_order = handle_option(honor_cipher_order, Opts, default_option_role(server, false, Role), server, Role), + honor_ecc_order = handle_option(honor_ecc_order, Opts, + default_option_role(server, false, Role), + server, Role), protocol = proplists:get_value(protocol, Opts, tls), padding_check = proplists:get_value(padding_check, Opts, true), beast_mitigation = handle_option(beast_mitigation, Opts, one_n_minus_one), @@ -780,7 +814,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, signature_algs, beast_mitigation, v2_hello_compatible], + fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, v2_hello_compatible], SockOpts = lists:foldl(fun(Key, PropList) -> proplists:delete(Key, PropList) @@ -1010,6 +1044,8 @@ validate_option(sni_fun, Fun) when is_function(Fun) -> Fun; validate_option(honor_cipher_order, Value) when is_boolean(Value) -> Value; +validate_option(honor_ecc_order, Value) when is_boolean(Value) -> + Value; validate_option(padding_check, Value) when is_boolean(Value) -> Value; validate_option(fallback, Value) when is_boolean(Value) -> @@ -1164,6 +1200,14 @@ binary_cipher_suites(Version, Ciphers0) -> Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], binary_cipher_suites(Version, Ciphers). +handle_eccs_option(Value, {_Major, Minor}) when is_list(Value) -> + try tls_v1:ecc_curves(Minor, Value) of + Curves -> #elliptic_curves{elliptic_curve_list = Curves} + catch + exit:_ -> throw({error, {options, {eccs, Value}}}); + error:_ -> throw({error, {options, {eccs, Value}}}) + end. + unexpected_format(Error) -> lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). @@ -1334,6 +1378,14 @@ 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([{honor_ecc_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{honor_ecc_order = validate_option(honor_ecc_order, Value)}, RecordCB); +new_ssl_options([{eccs, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, + Opts#ssl_options{eccs = + handle_eccs_option(Value, RecordCB:highest_protocol_version()) + }, + RecordCB); new_ssl_options([{signature_algs, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> new_ssl_options(Rest, Opts#ssl_options{signature_algs = diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 08fca76123..b6e4d5b433 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1172,14 +1172,23 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName, %%% Internal functions %%-------------------------------------------------------------------- connection_info(#state{sni_hostname = SNIHostname, - session = #session{cipher_suite = CipherSuite}, + session = #session{cipher_suite = CipherSuite, ecc = ECCCurve}, protocol_cb = Connection, negotiated_version = {_,_} = Version, ssl_options = Opts}) -> RecordCB = record_cb(Connection), + CipherSuiteDef = ssl_cipher:erl_suite_definition(CipherSuite), + IsNamedCurveSuite = lists:member(element(1,CipherSuiteDef), + [ecdh_ecdsa, ecdhe_ecdsa, ecdh_anon]), + CurveInfo = case ECCCurve of + {namedCurve, Curve} when IsNamedCurveSuite -> + [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; + _ -> + [] + end, [{protocol, RecordCB:protocol_version(Version)}, - {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuite)}, - {sni_hostname, SNIHostname}] ++ ssl_options_list(Opts). + {cipher_suite, CipherSuiteDef}, + {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts). do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt, @@ -1741,12 +1750,13 @@ calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, Connection, certify, certify); calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - State, Connection) -> + State=#state{session=Session}, Connection) -> ECDHKeys = public_key:generate_key(ECCurve), PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), calculate_master_secret(PremasterSecret, - State#state{diffie_hellman_keys = ECDHKeys}, + State#state{diffie_hellman_keys = ECDHKeys, + session = Session#session{ecc = ECCurve}}, Connection, certify, certify); calculate_secret(#server_psk_params{ diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 5b51ac0916..4acc745c5f 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -70,7 +70,7 @@ %% Extensions handling -export([client_hello_extensions/6, handle_client_hello_extensions/9, %% Returns server hello extensions - handle_server_hello_extensions/9, select_curve/2 + handle_server_hello_extensions/9, select_curve/2, select_curve/3 ]). %% MISC @@ -120,11 +120,13 @@ server_hello_done() -> #server_hello_done{}. client_hello_extensions(Host, Version, CipherSuites, - #ssl_options{signature_algs = SupportedHashSigns, versions = AllVersions} = SslOpts, ConnectionStates, Renegotiation) -> + #ssl_options{signature_algs = SupportedHashSigns, + eccs = SupportedECCs, + versions = AllVersions} = SslOpts, ConnectionStates, Renegotiation) -> {EcPointFormats, EllipticCurves} = case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of true -> - client_ecc_extensions(tls_v1, Version); + client_ecc_extensions(SupportedECCs); false -> {undefined, undefined} end, @@ -1169,8 +1171,9 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, {resumed, Resumed} end. -supported_ecc({Major, Minor} = Version) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> - Curves = tls_v1:ecc_curves(Version), +%% Deprecated? +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}; supported_ecc(_) -> #elliptic_curves{elliptic_curve_list = []}. @@ -1454,12 +1457,12 @@ srp_user(#ssl_options{srp_identity = {UserName, _}}) -> srp_user(_) -> undefined. -client_ecc_extensions(Module, Version) -> +client_ecc_extensions(SupportedECCs) -> CryptoSupport = proplists:get_value(public_keys, crypto:supports()), case proplists:get_bool(ecdh, CryptoSupport) of true -> EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, - EllipticCurves = #elliptic_curves{elliptic_curve_list = Module:ecc_curves(Version)}, + EllipticCurves = SupportedECCs, {EcPointFormats, EllipticCurves}; _ -> {undefined, undefined} @@ -1493,22 +1496,34 @@ advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> true; advertises_ec_ciphers([_| Rest]) -> advertises_ec_ciphers(Rest). -select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, - #elliptic_curves{elliptic_curve_list = ServerCurves}) -> - select_curve(ClientCurves, ServerCurves); -select_curve(undefined, _) -> + +select_curve(Client, Server) -> + select_curve(Client, Server, false). + +select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, + #elliptic_curves{elliptic_curve_list = ServerCurves}, + ServerOrder) -> + case ServerOrder of + false -> + select_shared_curve(ClientCurves, ServerCurves); + true -> + select_shared_curve(ServerCurves, ClientCurves) + end; +select_curve(undefined, _, _) -> %% Client did not send ECC extension use default curve if %% ECC cipher is negotiated - {namedCurve, ?secp256r1}; -select_curve(_, []) -> + {namedCurve, ?secp256r1}. + +select_shared_curve([], _) -> no_curve; -select_curve(Curves, [Curve| Rest]) -> +select_shared_curve([Curve | Rest], Curves) -> case lists:member(Curve, Curves) of true -> {namedCurve, Curve}; false -> - select_curve(Curves, Rest) + select_shared_curve(Rest, Curves) end. + %% RFC 6066, Section 3: Currently, the only server names supported are %% DNS hostnames sni(_, disable) -> diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index c19c1787ff..487d1fa096 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -140,6 +140,8 @@ crl_check :: boolean() | peer | best_effort, crl_cache, signature_algs, + eccs, + honor_ecc_order :: boolean(), v2_hello_compatible :: boolean() }). diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index a2486bf752..2bd103c18a 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -160,13 +160,15 @@ handle_client_hello(Version, #client_hello{session_id = SugesstedId, extensions = #hello_extensions{elliptic_curves = Curves, signature_algs = ClientHashSigns} = HelloExt}, #ssl_options{versions = Versions, - signature_algs = SupportedHashSigns} = SslOpts, + signature_algs = SupportedHashSigns, + eccs = SupportedECCs, + honor_ecc_order = ECCOrder} = SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) -> case tls_record:is_acceptable_version(Version, Versions) of true -> AvailableHashSigns = ssl_handshake:available_signature_algs( ClientHashSigns, SupportedHashSigns, Cert, Version), - ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)), + ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, Port, Session0#session{ecc = ECCCurve}, Version, diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 711db77708..7f24ce5192 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -31,9 +31,18 @@ -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, ecc_curves/2, oid_to_enum/1, enum_to_oid/1, default_signature_algs/1, signature_algs/2]). +-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 | + sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 | + sect283k1 | sect283r1 | brainpoolP256r1 | secp256k1 | secp256r1 | + sect239k1 | sect233k1 | sect233r1 | secp224k1 | secp224r1 | + sect193r1 | sect193r2 | secp192k1 | secp192r1 | sect163k1 | + sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2. +-type curves() :: [named_curve()]. +-export_type([curves/0, named_curve/0]). + %%==================================================================== %% Internal application API %%==================================================================== @@ -399,13 +408,20 @@ is_pair(Hash, rsa, Hashs) -> lists:member(Hash, AtLeastMd5). %% list ECC curves in prefered order -ecc_curves(_Minor) -> - TLSCurves = [sect571r1,sect571k1,secp521r1,brainpoolP512r1, - sect409k1,sect409r1,brainpoolP384r1,secp384r1, - sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1, - sect239k1,sect233k1,sect233r1,secp224k1,secp224r1, - sect193r1,sect193r2,secp192k1,secp192r1,sect163k1, - sect163r1,sect163r2,secp160k1,secp160r1,secp160r2], +-spec ecc_curves(1..3 | all) -> [named_curve()]. +ecc_curves(all) -> + [sect571r1,sect571k1,secp521r1,brainpoolP512r1, + sect409k1,sect409r1,brainpoolP384r1,secp384r1, + sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1, + sect239k1,sect233k1,sect233r1,secp224k1,secp224r1, + sect193r1,sect193r2,secp192k1,secp192r1,sect163k1, + sect163r1,sect163r2,secp160k1,secp160r1,secp160r2]; +ecc_curves(Minor) -> + TLSCurves = ecc_curves(all), + ecc_curves(Minor, TLSCurves). + +-spec ecc_curves(1..3, [named_curve()]) -> [named_curve()]. +ecc_curves(_Minor, TLSCurves) -> CryptoCurves = crypto:ec_curves(), lists:foldr(fun(Curve, Curves) -> case proplists:get_bool(Curve, CryptoCurves) of @@ -414,6 +430,7 @@ ecc_curves(_Minor) -> end end, [], TLSCurves). + %% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005) oid_to_enum(?sect163k1) -> 1; oid_to_enum(?sect163r1) -> 2; diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index 258922d128..bd0c630d41 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -46,7 +46,7 @@ groups() -> {'tlsv1', [], all_versions_groups()}, {'erlang_server', [], key_cert_combinations()}, {'erlang_client', [], key_cert_combinations()}, - {'erlang', [], key_cert_combinations() ++ misc()} + {'erlang', [], key_cert_combinations() ++ misc() ++ ecc_negotiation()} ]. all_versions_groups ()-> @@ -68,6 +68,23 @@ key_cert_combinations() -> misc()-> [client_ecdsa_server_ecdsa_with_raw_key]. +ecc_negotiation() -> + [ecc_default_order, + ecc_default_order_custom_curves, + ecc_client_order, + ecc_client_order_custom_curves, + ecc_unknown_curve, + client_ecdh_server_ecdh_ecc_server_custom, + client_rsa_server_ecdh_ecc_server_custom, + client_ecdh_server_rsa_ecc_server_custom, + client_rsa_server_rsa_ecc_server_custom, + client_ecdsa_server_ecdsa_ecc_server_custom, + client_ecdsa_server_rsa_ecc_server_custom, + client_rsa_server_ecdsa_ecc_server_custom, + client_ecdsa_server_ecdsa_ecc_client_custom, + client_rsa_server_ecdsa_ecc_client_custom + ]. + %%-------------------------------------------------------------------- init_per_suite(Config0) -> end_per_suite(Config0), @@ -218,6 +235,132 @@ client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> check_result(Server, SType, Client, CType), close(Server, Client). +ecc_default_order(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [], + case supported_eccs([{eccs, [sect571r1]}]) of + true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +ecc_default_order_custom_curves(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +ecc_client_order(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{honor_ecc_order, false}], + case supported_eccs([{eccs, [sect571r1]}]) of + true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +ecc_client_order_custom_curves(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +ecc_unknown_curve(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{eccs, ['123_fake_curve']}], + ecc_test_error(COpts, SOpts, [], ECCOpts, Config). + +%% We can only expect to see a named curve on a conn with +%% a server supporting ecdsa. Otherwise the curve is selected +%% but not used and communicated to the client? +client_ecdh_server_ecdh_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_ecdh_rsa_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_ecdh_server_rsa_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_ecdh_rsa_opts, Config), + SOpts = proplists:get_value(server_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_rsa_server_ecdh_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_rsa_server_rsa_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_ecdsa_server_ecdsa_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_ecdsa_server_rsa_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_rsa_server_ecdsa_ecc_server_custom(Config) -> + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + false -> {skip, "unsupported named curves"} + end. + +client_ecdsa_server_ecdsa_ecc_client_custom(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); + false -> {skip, "unsupported named curves"} + end. + +client_rsa_server_ecdsa_ecc_client_custom(Config) -> + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + ECCOpts = [{eccs, [secp256r1, sect571r1]}], + case supported_eccs(ECCOpts) of + true -> ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); + false -> {skip, "unsupported named curves"} + end. + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -244,6 +387,30 @@ basic_test(ClientCert, ClientKey, ClientCA, ServerCert, ServerKey, ServerCA, Con check_result(Server, SType, Client, CType), close(Server, Client). +ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) -> + CCA = proplists:get_value(cacertfile, COpts), + CCert = proplists:get_value(certfile, COpts), + CKey = proplists:get_value(keyfile, COpts), + SCA = proplists:get_value(cacertfile, SOpts), + SCert = proplists:get_value(certfile, SOpts), + SKey = proplists:get_value(keyfile, SOpts), + {Server, Port} = start_server_ecc(erlang, CCA, SCA, SCert, SKey, Expect, SECCOpts, Config), + Client = start_client_ecc(erlang, Port, SCA, CCA, CCert, CKey, Expect, CECCOpts, Config), + ssl_test_lib:check_result(Server, ok, Client, ok), + close(Server, Client). + +ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> + CCA = proplists:get_value(cacertfile, COpts), + CCert = proplists:get_value(certfile, COpts), + CKey = proplists:get_value(keyfile, COpts), + SCA = proplists:get_value(cacertfile, SOpts), + SCert = proplists:get_value(certfile, SOpts), + SKey = proplists:get_value(keyfile, SOpts), + {Server, Port} = start_server_ecc_error(erlang, CCA, SCA, SCert, SKey, SECCOpts, Config), + Client = start_client_ecc_error(erlang, Port, SCA, CCA, CCert, CKey, CECCOpts, Config), + Error = {error, {tls_alert, "insufficient security"}}, + ssl_test_lib:check_result(Server, Error, Client, Error). + start_client(openssl, Port, PeerCA, OwnCa, Cert, Key, _Config) -> CA = new_openssl_ca("openssl_client_ca", PeerCA, OwnCa), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), @@ -267,6 +434,31 @@ start_client(erlang, Port, PeerCA, OwnCa, Cert, Key, Config) -> {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]). +start_client_ecc(erlang, Port, PeerCA, OwnCa, Cert, Key, Expect, ECCOpts, Config) -> + CA = new_ca("erlang_client_ca", PeerCA, OwnCa), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, check_ecc, [client, Expect]}}, + {options, + ECCOpts ++ + [{verify, verify_peer}, + {cacertfile, CA}, + {certfile, Cert}, {keyfile, Key}]}]). + +start_client_ecc_error(erlang, Port, PeerCA, OwnCa, Cert, Key, ECCOpts, Config) -> + CA = new_ca("erlang_client_ca", PeerCA, OwnCa), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, + ECCOpts ++ + [{verify, verify_peer}, + {cacertfile, CA}, + {certfile, Cert}, {keyfile, Key}]}]). + start_server(openssl, PeerCA, OwnCa, Cert, Key, _Config) -> CA = new_openssl_ca("openssl_server_ca", PeerCA, OwnCa), Port = ssl_test_lib:inet_port(node()), @@ -290,6 +482,7 @@ start_server(erlang, PeerCA, OwnCa, Cert, Key, Config) -> [{verify, verify_peer}, {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]), {Server, ssl_test_lib:inet_port(Server)}. + start_server_with_raw_key(erlang, PeerCA, OwnCa, Cert, Key, Config) -> CA = new_ca("erlang_server_ca", PeerCA, OwnCa), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -303,6 +496,29 @@ start_server_with_raw_key(erlang, PeerCA, OwnCa, Cert, Key, Config) -> {certfile, Cert}, {key, Key}]}]), {Server, ssl_test_lib:inet_port(Server)}. +start_server_ecc(erlang, PeerCA, OwnCa, Cert, Key, Expect, ECCOpts, Config) -> + CA = new_ca("erlang_server_ca", PeerCA, OwnCa), + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, check_ecc, [server, Expect]}}, + {options, + ECCOpts ++ + [{verify, verify_peer}, {cacertfile, CA}, + {certfile, Cert}, {keyfile, Key}]}]), + {Server, ssl_test_lib:inet_port(Server)}. + +start_server_ecc_error(erlang, PeerCA, OwnCa, Cert, Key, ECCOpts, Config) -> + CA = new_ca("erlang_server_ca", PeerCA, OwnCa), + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, + ECCOpts ++ + [{verify, verify_peer}, {cacertfile, CA}, + {certfile, Cert}, {keyfile, Key}]}]), + {Server, ssl_test_lib:inet_port(Server)}. + check_result(Server, erlang, Client, erlang) -> ssl_test_lib:check_result(Server, ok, Client, ok); check_result(Server, erlang, _, _) -> @@ -362,3 +578,17 @@ new_openssl_ca(FileName, CA, OwnCa) -> file:write_file(FileName, Pem) end, FileName. + +supported_eccs(Opts) -> + ToCheck = proplists:get_value(eccs, Opts, []), + Supported = ssl:eccs(), + lists:all(fun(Curve) -> lists:member(Curve, Supported) end, ToCheck). + +check_ecc(SSL, Role, Expect) -> + {ok, Data} = ssl:connection_information(SSL), + case lists:keyfind(ecc, 1, Data) of + {ecc, {named_curve, Expect}} -> ok; + false when Expect =:= undefined -> ok; + Other -> {error, Role, Expect, Other} + end. + diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 1be43c56c4..f8dea736ae 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -150,6 +150,7 @@ api_tests() -> peercert_with_client_cert, sockname, versions, + eccs, controlling_process, getstat, close_with_timeout, @@ -456,6 +457,15 @@ init_per_testcase(accept_pool, Config) -> init_per_testcase(controller_dies, Config) -> ct:timetrap({seconds, 10}), Config; +init_per_testcase(eccs, Config) -> + case ssl:eccs() of + [] -> + {skip, "named curves not supported"}; + [_|_] -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 5}), + Config + end; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 5}), @@ -1504,6 +1514,25 @@ versions(Config) when is_list(Config) -> [_|_] = Versions = ssl:versions(), ct:log("~p~n", [Versions]). + +%%-------------------------------------------------------------------- +eccs() -> + [{doc, "Test API functions eccs/0 and eccs/1"}]. + +eccs(Config) when is_list(Config) -> + [_|_] = All = ssl:eccs(), + [] = SSL3 = ssl:eccs({3,0}), + [_|_] = Tls = ssl:eccs({3,1}), + [_|_] = Tls1 = ssl:eccs({3,2}), + [_|_] = Tls2 = ssl:eccs({3,3}), + [] = SSL3 = ssl:eccs(sslv3), + [_|_] = Tls = ssl:eccs(tlsv1), + [_|_] = Tls1 = ssl:eccs('tlsv1.1'), + [_|_] = Tls2 = ssl:eccs('tlsv1.2'), + %% ordering is currently unverified by the test + true = lists:sort(All) =:= lists:usort(SSL3 ++ Tls ++ Tls1 ++ Tls2), + ok. + %%-------------------------------------------------------------------- send_recv() -> [{doc,""}]. -- cgit v1.2.3