From 83f9c5c032cc502df38b52d8a5b0389657b37801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Wed, 17 Jul 2019 16:40:04 +0200 Subject: ssl: Implement psk_key_exchange_modes extension Implement encode/decode of psk_key_exchange_modes. --- lib/ssl/src/ssl_handshake.erl | 40 +++++++++++++++++++++++++++++++++++++-- lib/ssl/src/tls_handshake_1_3.hrl | 36 +++++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 488e4bb72a..07c7f10f2b 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -709,7 +709,13 @@ encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> Group = tls_v1:group_to_enum(Group0), encode_extensions(Rest, <>). + ?UINT16(2), ?UINT16(Group), Acc/binary>>); +encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> + KEModes = encode_psk_key_exchange_modes(KEModes0), + KEModesLen = byte_size(KEModes), + ExtLen = KEModesLen + 1, + encode_extensions(Rest, <>). encode_client_protocol_negotiation(undefined, _) -> @@ -2093,6 +2099,17 @@ encode_key_share_entry(#key_share_entry{ Len = byte_size(KeyExchange), <>. +encode_psk_key_exchange_modes(KEModes) -> + encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>). +%% +encode_psk_key_exchange_modes([], Acc) -> + Acc; +encode_psk_key_exchange_modes([psk_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <>); +encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <>). + + hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2445,6 +2462,13 @@ decode_extensions(<>, Version, MessageType, Acc) -> + <> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{psk_key_exchange_modes => + #psk_key_exchange_modes{ + ke_modes = decode_psk_key_exchange_modes(KEModes)}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. @@ -2504,6 +2528,18 @@ decode_protocols(<>, Acc) -> decode_protocols(_Bytes, _Acc) -> {error, invalid_protocols}. + +decode_psk_key_exchange_modes(KEModes) -> + decode_psk_key_exchange_modes(KEModes, []). +%% +decode_psk_key_exchange_modes(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_key_exchange_modes(<>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_ke|Acc]); +decode_psk_key_exchange_modes(<>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]). + + %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). @@ -3042,7 +3078,7 @@ empty_extensions({3,4}, client_hello) -> %% padding => undefined, key_share => undefined, pre_shared_key => undefined, - %% psk_key_exhange_modes => undefined, + psk_key_exchange_modes => undefined, %% early_data => undefined, %% cookie => undefined, client_hello_versions => undefined, diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 7ae1b93e1c..78756c69db 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -74,29 +74,41 @@ y % opaque Y[coordinate_length]; }). +%% RFC 8446 4.2.9. Pre-Shared Key Exchange Modes + +%% enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode; -define(PSK_KE, 0). -define(PSK_DHE_KE, 1). --record(psk_keyexchange_modes, { +-record(psk_key_exchange_modes, { ke_modes % ke_modes<1..255> }). + +%% RFC 8446 4.2.10. Early Data Indication -record(empty, { }). -record(early_data_indication, { indication % uint32 max_early_data_size (new_session_ticket) | %% #empty{} (client_hello, encrypted_extensions) }). --record(psk_identity, { - identity, % opaque identity<1..2^16-1> - obfuscated_ticket_age % uint32 - }). --record(offered_psks, { - psk_identity, %identities<7..2^16-1>; - psk_binder_entry %binders<33..2^16-1>, opaque PskBinderEntry<32..255> - }). --record(pre_shared_keyextension,{ - extension %OfferedPsks (client_hello) | uint16 selected_identity (server_hello) - }). + +%% RFC 8446 4.2.11. Pre-Shared Key Extension +-record(psk_identity, + { + identity, % opaque identity<1..2^16-1> + obfuscated_ticket_age % uint32 + }). + +-record(offered_psks, + { + identities, % PskIdentity identities<7..2^16-1>; + binders % PskBinderEntry binders<33..2^16-1>; opaque PskBinderEntry<32..255> + }). + +-record(pre_shared_key, + { + data % OfferedPsks (client_hello) | uint16 selected_identity (server_hello) + }). %% RFC 8446 B.3.1.2. -record(cookie, { -- cgit v1.2.3 From 1dbba57853b7b093bf7e770f29eefa958d04b131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 18 Jul 2019 16:49:53 +0200 Subject: ssl: Update property tests with psk_key_exchange_modes --- lib/ssl/test/property_test/ssl_eqc_handshake.erl | 25 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index 21aad26425..f495161e78 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -119,11 +119,11 @@ tls_msg(Version) -> %% client_hello(?'TLS_v1.3' = Version) -> #client_hello{session_id = session_id(), - client_version = ?'TLS_v1.2', - cipher_suites = cipher_suites(Version), - compression_methods = compressions(Version), - random = client_random(Version), - extensions = client_hello_extensions(Version) + client_version = ?'TLS_v1.2', + cipher_suites = cipher_suites(Version), + compression_methods = compressions(Version), + random = client_random(Version), + extensions = client_hello_extensions(Version) }; client_hello(Version) -> #client_hello{session_id = session_id(), @@ -323,7 +323,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% Padding, KeyShare, %% PreSharedKey, - %% PSKKeyExchangeModes, + PSKKeyExchangeModes, %% EarlyData, %% Cookie, SupportedVersions, @@ -346,7 +346,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% oneof([padding(), undefined]), oneof([key_share(client_hello), undefined]), %% oneof([pre_shared_key(), undefined]), - %% oneof([psk_key_exchange_modes(), undefined]), + oneof([psk_key_exchange_modes(), undefined]), %% oneof([early_data(), undefined]), %% oneof([cookie(), undefined]), oneof([client_hello_versions(Version)]), @@ -374,7 +374,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% padding => Padding, key_share => KeyShare, %% pre_shared_key => PreSharedKey, - %% psk_key_exhange_modes => PSKKeyExchangeModes, + psk_key_exchange_modes => PSKKeyExchangeModes, %% early_data => EarlyData, %% cookie => Cookie, client_hello_versions => SupportedVersions, @@ -826,3 +826,12 @@ group_list(N, Pool, Acc) -> R = rand:uniform(length(Pool)), G = lists:nth(R, Pool), group_list(N - 1, Pool -- [G], [G|Acc]). + + +ke_modes() -> + oneof([[psk_ke],[psk_dhe_ke],[psk_ke,psk_dhe_ke]]). + +psk_key_exchange_modes() -> + ?LET(KEModes, ke_modes(), + #psk_key_exchange_modes{ + ke_modes = KEModes}). -- cgit v1.2.3 From c45076cbbcb954434234dc7b33fee68692768cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Mon, 22 Jul 2019 11:25:46 +0200 Subject: ssl: Implement pre_shared_key extension Implement encode/decode of pre_shared_key. --- lib/ssl/src/ssl_handshake.erl | 78 ++++++++++++++++++++++++++++++++++++++- lib/ssl/src/tls_handshake_1_3.hrl | 15 +++++++- 2 files changed, 90 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 07c7f10f2b..0585aebcb2 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -715,7 +715,19 @@ encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> KEModesLen = byte_size(KEModes), ExtLen = KEModesLen + 1, encode_extensions(Rest, <>). + ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = Identities0, + binders = Binders0} = PSKs} | Rest], Acc) -> + Identities = encode_psk_identities(Identities0), + Binders = encode_psk_binders(Binders0), + Len = byte_size(Identities) + byte_size(Binders), + encode_extensions(Rest, <>); +encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) -> + encode_extensions(Rest, <>). encode_client_protocol_negotiation(undefined, _) -> @@ -2110,6 +2122,30 @@ encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) -> encode_psk_key_exchange_modes(T, <>). +encode_psk_identities(Identities) -> + encode_psk_identities(Identities, <<>>). +%% +encode_psk_identities([], Acc) -> + Len = byte_size(Acc), + <>; +encode_psk_identities([#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|T], Acc) -> + IdLen = byte_size(Identity), + encode_psk_identities(T, <>). + + +encode_psk_binders(Binders) -> + encode_psk_binders(Binders, <<>>). +%% +encode_psk_binders([], Acc) -> + Len = byte_size(Acc), + <>; +encode_psk_binders([Binder|T], Acc) -> + Len = byte_size(Binder), + encode_psk_binders(T, <>). + + hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2470,6 +2506,26 @@ decode_extensions(<>, + Version, MessageType = client_hello, Acc) -> + <> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = decode_psk_identities(Identities), + binders = decode_psk_binders(Binders)}}}); + +decode_extensions(<>, + Version, MessageType = server_hello, Acc) -> + <> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_server_hello{ + selected_identity = Identity}}); + %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. decode_extensions(<>, Version, MessageType, Acc) -> @@ -2540,6 +2596,26 @@ decode_psk_key_exchange_modes(<>, Acc) -> decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]). +decode_psk_identities(Identities) -> + decode_psk_identities(Identities, []). +%% +decode_psk_identities(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_identities(<>, Acc) -> + decode_psk_identities(Rest, [#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|Acc]). + + +decode_psk_binders(Binders) -> + decode_psk_binders(Binders, []). +%% +decode_psk_binders(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_binders(<>, Acc) -> + decode_psk_binders(Rest, [Binder|Acc]). + + %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 78756c69db..eb85f216c8 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -105,9 +105,20 @@ binders % PskBinderEntry binders<33..2^16-1>; opaque PskBinderEntry<32..255> }). --record(pre_shared_key, +%% struct { +%% select (Handshake.msg_type) { +%% case client_hello: OfferedPsks; +%% case server_hello: uint16 selected_identity; +%% }; +%% } PreSharedKeyExtension; +-record(pre_shared_key_client_hello, { - data % OfferedPsks (client_hello) | uint16 selected_identity (server_hello) + offered_psks + }). + +-record(pre_shared_key_server_hello, + { + selected_identity }). %% RFC 8446 B.3.1.2. -- cgit v1.2.3 From 9339bfd586b4c2dca1ac623b95ac4b377e187a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Mon, 22 Jul 2019 16:43:54 +0200 Subject: ssl: Update property tests with pre_shared_key --- lib/ssl/test/property_test/ssl_eqc_handshake.erl | 66 ++++++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index f495161e78..82c9aebe36 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -307,7 +307,7 @@ pre_shared_keyextension() -> %% | | | %% | signature_algorithms_cert (RFC 8446) | CH, CR | %% +--------------------------------------------------+-------------+ -extensions(?'TLS_v1.3' = Version, client_hello) -> +extensions(?'TLS_v1.3' = Version, MsgType = client_hello) -> ?LET({ ServerName, %% MaxFragmentLength, @@ -322,7 +322,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% ServerCertificateType, %% Padding, KeyShare, - %% PreSharedKey, + PreSharedKey, PSKKeyExchangeModes, %% EarlyData, %% Cookie, @@ -344,8 +344,8 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% oneof([client_cert_type(), undefined]), %% oneof([server_cert_type(), undefined]), %% oneof([padding(), undefined]), - oneof([key_share(client_hello), undefined]), - %% oneof([pre_shared_key(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), oneof([psk_key_exchange_modes(), undefined]), %% oneof([early_data(), undefined]), %% oneof([cookie(), undefined]), @@ -373,7 +373,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% server_cert_type => ServerCertificateType, %% padding => Padding, key_share => KeyShare, - %% pre_shared_key => PreSharedKey, + pre_shared_key => PreSharedKey, psk_key_exchange_modes => PSKKeyExchangeModes, %% early_data => EarlyData, %% cookie => Cookie, @@ -417,15 +417,15 @@ extensions(Version, client_hello) -> srp => SRP %% renegotiation_info => RenegotiationInfo })); -extensions(?'TLS_v1.3' = Version, server_hello) -> +extensions(?'TLS_v1.3' = Version, MsgType = server_hello) -> ?LET({ KeyShare, - %% PreSharedKeys, + PreSharedKey, SupportedVersions }, { - oneof([key_share(server_hello), undefined]), - %% oneof([pre_shared_keys(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), oneof([server_hello_selected_version()]) }, maps:filter(fun(_, undefined) -> @@ -435,7 +435,7 @@ extensions(?'TLS_v1.3' = Version, server_hello) -> end, #{ key_share => KeyShare, - %% pre_shared_keys => PreSharedKeys, + pre_shared_key => PreSharedKey, server_hello_selected_version => SupportedVersions })); extensions(Version, server_hello) -> @@ -835,3 +835,49 @@ psk_key_exchange_modes() -> ?LET(KEModes, ke_modes(), #psk_key_exchange_modes{ ke_modes = KEModes}). + +pre_shared_key(client_hello) -> + ?LET(OfferedPsks, offered_psks(), + #pre_shared_key_client_hello{ + offered_psks = OfferedPsks}); +pre_shared_key(server_hello) -> + ?LET(SelectedIdentity, selected_identity(), + #pre_shared_key_server_hello{ + selected_identity = SelectedIdentity}). + +selected_identity() -> + rand:uniform(32). + +offered_psks() -> + ?LET(Size, choose(1,5), + #offered_psks{ + identities = psk_identities(Size), + binders = psk_binders(Size)}). + +psk_identities(Size) -> + psk_identities(Size, []). +%% +psk_identities(0, Acc) -> + Acc; +psk_identities(N, Acc) -> + psk_identities(N - 1, [psk_identity()|Acc]). + +psk_identity() -> + Len = rand:uniform(32), + Identity = crypto:strong_rand_bytes(Len), + Age = crypto:strong_rand_bytes(4), + #psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}. + +psk_binders(Size) -> + psk_binders(Size, []). +%% +psk_binders(0, Acc) -> + Acc; +psk_binders(N, Acc) -> + psk_binders(N - 1, [psk_binder()|Acc]). + +psk_binder() -> + Len = rand:uniform(224) + 31, + crypto:strong_rand_bytes(Len). -- cgit v1.2.3 From c9a091eaaf6b5741c7a4c403e2e2619c74b38090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 23 Jul 2019 09:59:30 +0200 Subject: ssl: Fix property tests for SSLv3 --- lib/ssl/test/property_test/ssl_eqc_handshake.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index 82c9aebe36..2ceb540e15 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -139,7 +139,7 @@ client_hello(?'SSL_v3' = Version) -> cipher_suites = cipher_suites(Version), compression_methods = compressions(Version), random = client_random(Version), - extensions = undefined + extensions = ssl_handshake:empty_extensions(Version, client_hello) }. server_hello(?'TLS_v1.3' = Version) -> @@ -156,7 +156,7 @@ server_hello(?'SSL_v3' = Version) -> random = server_random(Version), cipher_suite = cipher_suite(Version), compression_method = compression(Version), - extensions = undefined + extensions = ssl_handshake:empty_extensions(Version, server_hello) }; server_hello(Version) -> #server_hello{server_version = Version, -- cgit v1.2.3 From 317196e482c3665d5fd1f124c9b28144f84c75be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 23 Jul 2019 14:03:31 +0200 Subject: ssl: Fix ssl_api_SUITE:connection_information This commit fixes the connection_information testcase with TLS 1.3 connections. --- lib/ssl/test/ssl_api_SUITE.erl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 128832c23c..72a49bf4a9 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -45,7 +45,7 @@ all() -> groups() -> [ %%{'tlsv1.3', [], gen_api_tests() ++ handshake_paus_tests()}, - {'tlsv1.3', [], (gen_api_tests() -- [secret_connection_info, dh_params, honor_server_cipher_order, honor_client_cipher_order, + {'tlsv1.3', [], (gen_api_tests() -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, new_options_in_handshake]) ++ (since_1_2() -- [conf_signature_algs])}, {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, @@ -1618,10 +1618,23 @@ connection_information_result(Socket) -> ct:fail(no_ssl_options_returned) end. secret_connection_info_result(Socket) -> - {ok, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]} - = ssl:connection_information(Socket, [client_random, server_random, master_secret]), - is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret). + {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), + {ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]), + check_connection_info(Protocol, ConnInfo). + +%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple. +%% client_random and server_random are not used in the TLS 1.3 key schedule. +check_connection_info('tlsv1.3', [{client_random, ClientRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ClientRand) andalso is_binary(MasterSecret); +check_connection_info('tlsv1.3', [{server_random, ServerRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]) -> + is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, _) -> + false. + + prf_create_plan(TlsVersions, PRFs, Results) -> lists:foldl(fun(Ver, Acc) -> A = prf_ciphers_and_expected(Ver, PRFs, Results), -- cgit v1.2.3 From cbc8e9ce89ce30e3be90d9ad500becd3c26370e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 23 Jul 2019 15:57:58 +0200 Subject: ssl: Document option supported_groups --- lib/ssl/doc/src/ssl.xml | 23 ++++++++++++++++++++++- lib/ssl/src/ssl.erl | 7 +++++-- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 3aa6e09c2c..05590666da 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -207,6 +207,10 @@ + + + + @@ -363,7 +367,20 @@

- + + + + +

TLS 1.3 introduces the "supported_groups" extension that is used for negotiating + the Diffie-Hellman parameters in a TLS 1.3 handshake. Both client and server + can specify a list of parameters that they are willing to use. +

+

If it is not specified it will use a default list ([x25519, x448, secp256r1, secp384r1]) that + is filtered based on the installed crypto library version. +

+
+
+

Specifies if to reject renegotiation attempt that does @@ -919,6 +936,8 @@ fun(srp, Username :: string(), UserState :: term()) ->

The DER-encoded Diffie-Hellman parameters. If specified, it overrides option dhfile.

+

The dh_der option is not supported by TLS 1.3. Use the + supported_groups option instead.

@@ -928,6 +947,8 @@ fun(srp, Username :: string(), UserState :: term()) -> parameters to be used by the server if a cipher suite using Diffie Hellman key exchange is negotiated. If not specified, default parameters are used.

+

The dh_file option is not supported by TLS 1.3. Use the + supported_groups option instead.

diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 20b1e85ceb..ce639e8fde 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -128,7 +128,8 @@ tls_alert/0, srp_param_type/0, named_curve/0, - sign_scheme/0]). + sign_scheme/0, + group/0]). %% ------------------------------------------------------------------------------------------------------- @@ -243,7 +244,7 @@ secp160r2. % exported -type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 | - ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. + ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported -type srp_param_type() :: srp_1024 | srp_1536 | @@ -296,6 +297,7 @@ {ciphers, cipher_suites()} | {eccs, [named_curve()]} | {signature_algs_cert, signature_schemes()} | + {supported_groups, supported_groups()} | {secure_renegotiate, secure_renegotiation()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | @@ -342,6 +344,7 @@ -type protocol_versions() :: [protocol_version()]. -type signature_algs() :: [{hash(), sign_algo()}]. -type signature_schemes() :: [sign_scheme()]. +-type supported_groups() :: [group()]. -type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: any()}. -type padding_check() :: boolean(). -type beast_mitigation() :: one_n_minus_one | zero_n | disabled. -- cgit v1.2.3 From b4fddea062fc13b0562bf8bf2f4cc3dc02cd193c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Tue, 23 Jul 2019 16:44:05 +0200 Subject: ssl: Add test for option supported_groups --- lib/ssl/test/ssl_api_SUITE.erl | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 72a49bf4a9..a7b2a71690 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -45,7 +45,7 @@ all() -> groups() -> [ %%{'tlsv1.3', [], gen_api_tests() ++ handshake_paus_tests()}, - {'tlsv1.3', [], (gen_api_tests() -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, + {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, new_options_in_handshake]) ++ (since_1_2() -- [conf_signature_algs])}, {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, @@ -125,6 +125,12 @@ beast_mitigation_test() -> rizzo_one_n_minus_one ]. +tls13_group() -> + [ + supported_groups + ]. + + init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of @@ -419,7 +425,32 @@ no_common_signature_algs(Config) when is_list(Config) -> ssl_test_lib:check_server_alert(Server, Client, insufficient_security). %%-------------------------------------------------------------------- +supported_groups() -> + [{doc,"Test the supported_groups option in TLS 1.3."}]. + +supported_groups(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups, [x448, x25519]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups,[x448]} | ClientOpts]}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- handshake_continue() -> [{doc, "Test API function ssl:handshake_continue/3"}]. handshake_continue(Config) when is_list(Config) -> -- cgit v1.2.3 From 73b526ce765dc7ac71fdae349da44941d8201d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Wed, 24 Jul 2019 11:11:07 +0200 Subject: ssl: Implement option honor_cipher_order in TLS 1.3 --- lib/ssl/src/tls_handshake_1_3.erl | 15 ++++++--- lib/ssl/test/ssl_api_SUITE.erl | 64 ++++++++++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 6aeb9f5663..c29366e717 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -504,7 +504,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers, ssl_options = #ssl_options{ciphers = ServerCiphers, signature_algs = ServerSignAlgs, supported_groups = ServerGroups0, - alpn_preferred_protocols = ALPNPreferredProtocols}, + alpn_preferred_protocols = ALPNPreferredProtocols, + honor_cipher_order = HonorCipherOrder}, session = #session{own_certificate = Cert}} = State0) -> ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), @@ -531,7 +532,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% cipher suite, an (EC)DHE group and key share for key establishment, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. - Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), + Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), @@ -1731,15 +1732,19 @@ handle_alpn([ServerProtocol|T], ClientProtocols) -> end. -select_cipher_suite([], _) -> +select_cipher_suite(_, [], _) -> {error, no_suitable_cipher}; -select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> +%% If honor_cipher_order is set to true, use the server's preference for +%% cipher suite selection. +select_cipher_suite(true, ClientCiphers, ServerCiphers) -> + select_cipher_suite(false, ServerCiphers, ClientCiphers); +select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; false -> - select_cipher_suite(ClientCiphers, ServerCiphers) + select_cipher_suite(false, ClientCiphers, ServerCiphers) end. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index a7b2a71690..4b44b4dc3e 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -127,7 +127,9 @@ beast_mitigation_test() -> tls13_group() -> [ - supported_groups + supported_groups, + honor_server_cipher_order_tls13, + honor_client_cipher_order_tls13 ]. @@ -1198,11 +1200,35 @@ honor_server_cipher_order(Config) when is_list(Config) -> cipher => aes_128_cbc, mac => sha, prf => default_prf}], - honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, mac => sha, prf => default_prf}). %%-------------------------------------------------------------------- +honor_server_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_server_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}). +%%-------------------------------------------------------------------- honor_client_cipher_order() -> [{doc,"Test API honor server cipher order."}]. honor_client_cipher_order(Config) when is_list(Config) -> @@ -1222,10 +1248,34 @@ honor_client_cipher_order(Config) when is_list(Config) -> cipher => aes_128_cbc, mac => sha, prf => default_prf}], -honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}). + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}). +%%-------------------------------------------------------------------- +honor_client_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_client_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}). %%-------------------------------------------------------------------- ipv6() -> [{require, ipv6_hosts}, -- cgit v1.2.3 From b08621c8a9bf4f8e780de8bba022ad902308cfc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 25 Jul 2019 14:16:34 +0200 Subject: ssl: Fix handshake pause in TLS 1.3 --- lib/ssl/src/ssl_handshake.erl | 6 +++++- lib/ssl/src/tls_record_1_3.erl | 9 +++++++++ lib/ssl/test/ssl_api_SUITE.erl | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 0585aebcb2..bd2efa9fbb 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -1508,8 +1508,12 @@ extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) -> Schemes; extension_value(#key_share_client_hello{client_shares = ClientShares}) -> ClientShares; +extension_value(#key_share_server_hello{server_share = ServerShare}) -> + ServerShare; extension_value(#client_hello_versions{versions = Versions}) -> - Versions. + Versions; +extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) -> + SelectedVersion. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index 74321a1ae2..d713062284 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -138,6 +138,15 @@ decode_cipher_text(#ssl_tls{type = ?ALERT, {#ssl_tls{type = ?ALERT, version = {3,4}, %% Internally use real version fragment = <<2,47>>}, ConnectionStates0}; +%% TLS 1.3 server can receive a User Cancelled Alert when handshake is +%% paused and then cancelled on the client side. +decode_cipher_text(#ssl_tls{type = ?ALERT, + version = ?LEGACY_VERSION, + fragment = <<2,90>>}, + ConnectionStates0) -> + {#ssl_tls{type = ?ALERT, + version = {3,4}, %% Internally use real version + fragment = <<2,90>>}, ConnectionStates0}; %% RFC8446 - TLS 1.3 %% D.4. Middlebox Compatibility Mode %% - If not offering early data, the client sends a dummy diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 4b44b4dc3e..989f36164f 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -45,7 +45,7 @@ all() -> groups() -> [ %%{'tlsv1.3', [], gen_api_tests() ++ handshake_paus_tests()}, - {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, + {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, new_options_in_handshake]) ++ (since_1_2() -- [conf_signature_algs])}, {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, -- cgit v1.2.3 From 91e7806587ac256346a4e4b1c8bd5cdd744a2934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Thu, 25 Jul 2019 14:31:39 +0200 Subject: ssl: Reorder testcases in ssl_api_SUITE --- lib/ssl/test/ssl_api_SUITE.erl | 153 +++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 75 deletions(-) (limited to 'lib') diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index 989f36164f..4394d0cd2f 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -44,7 +44,6 @@ all() -> groups() -> [ - %%{'tlsv1.3', [], gen_api_tests() ++ handshake_paus_tests()}, {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, new_options_in_handshake]) ++ (since_1_2() -- [conf_signature_algs])}, @@ -426,32 +425,6 @@ no_common_signature_algs(Config) when is_list(Config) -> ssl_test_lib:check_server_alert(Server, Client, insufficient_security). -%%-------------------------------------------------------------------- -supported_groups() -> - [{doc,"Test the supported_groups option in TLS 1.3."}]. - -supported_groups(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{supported_groups, [x448, x25519]} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{supported_groups,[x448]} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - %%-------------------------------------------------------------------- handshake_continue() -> [{doc, "Test API function ssl:handshake_continue/3"}]. @@ -1204,30 +1177,7 @@ honor_server_cipher_order(Config) when is_list(Config) -> cipher => aes_256_cbc, mac => sha, prf => default_prf}). -%%-------------------------------------------------------------------- -honor_server_cipher_order_tls13() -> - [{doc,"Test API honor server cipher order in TLS 1.3."}]. -honor_server_cipher_order_tls13(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => any, - cipher => aes_256_gcm, - mac => aead, - prf => sha384}, - #{key_exchange => any, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}], - ServerCiphers = [#{key_exchange => any, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}, - #{key_exchange => any, - cipher => aes_256_gcm, - mac => aead, - prf => sha384}], - honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}). + %%-------------------------------------------------------------------- honor_client_cipher_order() -> [{doc,"Test API honor server cipher order."}]. @@ -1252,30 +1202,7 @@ honor_client_cipher_order(Config) when is_list(Config) -> cipher => aes_128_cbc, mac => sha, prf => default_prf}). -%%-------------------------------------------------------------------- -honor_client_cipher_order_tls13() -> - [{doc,"Test API honor server cipher order in TLS 1.3."}]. -honor_client_cipher_order_tls13(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => any, - cipher => aes_256_gcm, - mac => aead, - prf => sha384}, - #{key_exchange => any, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}], - ServerCiphers = [#{key_exchange => any, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}, - #{key_exchange => any, - cipher => aes_256_gcm, - mac => aead, - prf => sha384}], - honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => any, - cipher => aes_256_gcm, - mac => aead, - prf => sha384}). + %%-------------------------------------------------------------------- ipv6() -> [{require, ipv6_hosts}, @@ -1681,6 +1608,82 @@ rizzo_one_n_minus_one (Config) -> ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +supported_groups() -> + [{doc,"Test the supported_groups option in TLS 1.3."}]. + +supported_groups(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups, [x448, x25519]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups,[x448]} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +honor_client_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_client_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_server_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}). + + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- -- cgit v1.2.3