diff options
-rw-r--r-- | lib/ssl/src/dtls_handshake.erl | 2 | ||||
-rw-r--r-- | lib/ssl/src/ssl_cipher.erl | 29 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.hrl | 3 | ||||
-rw-r--r-- | lib/ssl/src/ssl_dh_groups.erl | 26 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 293 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.hrl | 3 | ||||
-rw-r--r-- | lib/ssl/src/tls_connection.erl | 18 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake.erl | 13 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.erl | 16 | ||||
-rw-r--r-- | lib/ssl/src/tls_handshake_1_3.hrl | 2 | ||||
-rw-r--r-- | lib/ssl/test/property_test/ssl_eqc_handshake.erl | 64 | ||||
-rw-r--r-- | lib/ssl/test/ssl_handshake_SUITE.erl | 2 |
12 files changed, 370 insertions, 101 deletions
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 55aa8174a3..3dbda2c91b 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -79,7 +79,7 @@ client_hello(Host, Port, Cookie, ConnectionStates, Extensions = ssl_handshake:client_hello_extensions(TLSVersion, CipherSuites, SslOpts, ConnectionStates, - Renegotiation), + Renegotiation, undefined), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index ff3e0d9c90..18109741cc 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -46,6 +46,9 @@ is_stream_ciphersuite/1, signature_scheme/1, scheme_to_components/1, hash_size/1]). +%% RFC 8446 TLS 1.3 +-export([generate_client_shares/1]). + -compile(inline). -type cipher_enum() :: integer(). @@ -1188,3 +1191,29 @@ filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) -> false -> CipherSuits -- Suites end. + + +generate_client_shares([]) -> + #key_share_client_hello{client_shares = []}; +generate_client_shares(Groups) -> + generate_client_shares(Groups, []). +%% +generate_client_shares([], Acc) -> + #key_share_client_hello{client_shares = lists:reverse(Acc)}; +generate_client_shares([Group|Groups], Acc) -> + Key = generate_key_exchange(Group), + KeyShareEntry = #key_share_entry{ + group = Group, + key_exchange = Key + }, + generate_client_shares(Groups, [KeyShareEntry|Acc]). + + +generate_key_exchange(secp256r1) -> + public_key:generate_key({namedCurve, secp256r1}); +generate_key_exchange(secp384r1) -> + public_key:generate_key({namedCurve, secp384r1}); +generate_key_exchange(secp521r1) -> + public_key:generate_key({namedCurve, secp521r1}); +generate_key_exchange(FFDHE) -> + public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)). diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 66e3182313..91467e9b26 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -96,7 +96,8 @@ %% The mecahnism is also usefull in TLS although we do not %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. - protocol_specific = #{} :: map() + protocol_specific = #{} :: map(), + key_share }). -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, diff --git a/lib/ssl/src/ssl_dh_groups.erl b/lib/ssl/src/ssl_dh_groups.erl index 36c97ed13e..20d53de430 100644 --- a/lib/ssl/src/ssl_dh_groups.erl +++ b/lib/ssl/src/ssl_dh_groups.erl @@ -20,12 +20,15 @@ -module(ssl_dh_groups). +-include_lib("public_key/include/public_key.hrl"). + -export([modp2048_generator/0, modp2048_prime/0, ffdhe2048_generator/0, ffdhe2048_prime/0, ffdhe3072_generator/0, ffdhe3072_prime/0, ffdhe4096_generator/0, ffdhe4096_prime/0, ffdhe6144_generator/0, ffdhe6144_prime/0, - ffdhe8192_generator/0, ffdhe8192_prime/0]). + ffdhe8192_generator/0, ffdhe8192_prime/0, + dh_params/1]). %% RFC3526 - 2048-bit MODP Group %% This group is assigned id 14. @@ -441,3 +444,24 @@ ffdhe8192_prime() -> "97D11D49" "F7A8443D" "0822E506" "A9F4614E" "011E2A94" "838FF88C" "D68C8BB7" "C5C6424C" "FFFFFFFF" "FFFFFFFF", list_to_integer(P, 16). + +dh_params(ffdhe2048) -> + #'DHParameter'{ + prime = ffdhe2048_prime(), + base = ffdhe2048_generator()}; +dh_params(ffdhe3072) -> + #'DHParameter'{ + prime = ffdhe3072_prime(), + base = ffdhe3072_generator()}; +dh_params(ffdhe4096) -> + #'DHParameter'{ + prime = ffdhe4096_prime(), + base = ffdhe4096_generator()}; +dh_params(ffdhe6144) -> + #'DHParameter'{ + prime = ffdhe6144_prime(), + base = ffdhe6144_generator()}; +dh_params(ffdhe8192) -> + #'DHParameter'{ + prime = ffdhe8192_prime(), + base = ffdhe8192_generator()}. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 39a627c0a2..7dec0a283f 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -30,6 +30,7 @@ -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). +-include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). -export_type([ssl_handshake/0, ssl_handshake_history/0, @@ -60,7 +61,7 @@ -export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2, encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). %% Decode --export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, decode_extensions/2, +-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, decode_extensions/3, decode_server_key/3, decode_client_key/3, decode_suites/2 ]). @@ -71,7 +72,7 @@ premaster_secret/2, premaster_secret/3, premaster_secret/4]). %% Extensions handling --export([client_hello_extensions/5, +-export([client_hello_extensions/6, handle_client_hello_extensions/9, %% Returns server hello extensions handle_server_hello_extensions/9, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, @@ -679,12 +680,29 @@ encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> VerLen = byte_size(Versions), Len = VerLen + 2, encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), - ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>); + ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>); encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) -> Version = encode_versions([Version0]), Len = byte_size(Version), %% 2 encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), - ?UINT16(Len), Version/binary, Acc/binary>>). + ?UINT16(Len), Version/binary, Acc/binary>>); +encode_extensions([#key_share_client_hello{client_shares = ClientShares0} | Rest], Acc) -> + ClientShares = encode_client_shares(ClientShares0), + ClientSharesLen = byte_size(ClientShares), + Len = ClientSharesLen + 2, + encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), + ?UINT16(Len), ?UINT16(ClientSharesLen), + ClientShares/binary, Acc/binary>>); +encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], Acc) -> + ServerShare = encode_key_share_entry(ServerShare0), + Len = byte_size(ServerShare), + encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), + ?UINT16(Len), ServerShare/binary, Acc/binary>>); +encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> + Group = tls_v1:group_to_enum(Group0), + encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), + ?UINT16(2), ?UINT16(Group), Acc/binary>>). + encode_client_protocol_negotiation(undefined, _) -> undefined; @@ -726,7 +744,7 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32 Cipher_suite:2/binary, ?BYTE(Comp_method), ?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> - HelloExtensions = decode_hello_extensions(Extensions, Version, server), + HelloExtensions = decode_hello_extensions(Extensions, Version, server_hello), #server_hello{ server_version = {Major,Minor}, @@ -783,25 +801,27 @@ decode_vector(<<?UINT16(Len), Vector:Len/binary>>) -> Vector. %%-------------------------------------------------------------------- --spec decode_hello_extensions(binary(), ssl_record:ssl_version(), client | server) -> map(). +-spec decode_hello_extensions(binary(), ssl_record:ssl_version(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- -decode_hello_extensions(Extensions, Version, Role) -> +decode_hello_extensions(Extensions, Version, MessageType0) -> + %% Convert legacy atoms MessageType = - case Role of + case MessageType0 of client -> client_hello; - server -> server_hello + server -> server_hello; + T -> T end, - decode_extensions(Extensions, Version, empty_extensions(Version, MessageType)). + decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)). %%-------------------------------------------------------------------- --spec decode_extensions(binary(),tuple()) -> map(). +-spec decode_extensions(binary(),tuple(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- -decode_extensions(Extensions, Version) -> - decode_extensions(Extensions, Version, empty_extensions()). +decode_extensions(Extensions, Version, MessageType) -> + decode_extensions(Extensions, Version, MessageType, empty_extensions()). %%-------------------------------------------------------------------- -spec decode_server_key(binary(), ssl_cipher_format:key_algo(), ssl_record:ssl_version()) -> @@ -1011,10 +1031,10 @@ premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> %%==================================================================== %% Extensions handling %%==================================================================== -client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) -> +client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation, KeyShare) -> HelloExtensions0 = add_tls12_extensions(Version, SslOpts, ConnectionStates, Renegotiation), HelloExtensions1 = add_common_extensions(Version, HelloExtensions0, CipherSuites, SslOpts), - maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts). + maybe_add_tls13_extensions(Version, HelloExtensions1, SslOpts, KeyShare). add_tls12_extensions(Version, @@ -1063,16 +1083,77 @@ add_common_extensions(_Version, maybe_add_tls13_extensions({3,4}, - HelloExtensions, + HelloExtensions0, #ssl_options{signature_algs_cert = SignatureSchemes, - versions = SupportedVersions}) -> - HelloExtensions#{client_hello_versions => - #client_hello_versions{versions = SupportedVersions}, - signature_algs_cert => - signature_algs_cert(SignatureSchemes)}; -maybe_add_tls13_extensions(_, HelloExtensions, _) -> + versions = SupportedVersions}, + KeyShare) -> + HelloExtensions = + HelloExtensions0#{client_hello_versions => + #client_hello_versions{versions = SupportedVersions}, + signature_algs_cert => + signature_algs_cert(SignatureSchemes)}, + maybe_add_key_share(HelloExtensions, KeyShare); +maybe_add_tls13_extensions(_, HelloExtensions, _, _) -> HelloExtensions. + +%% TODO: Add support for PSK key establishment + +%% RFC 8446 (TLS 1.3) - 4.2.8. Key Share +%% +%% 4.2.8.1. Diffie-Hellman Parameters +%% Diffie-Hellman [DH76] parameters for both clients and servers are +%% encoded in the opaque key_exchange field of a KeyShareEntry in a +%% KeyShare structure. The opaque value contains the Diffie-Hellman +%% public value (Y = g^X mod p) for the specified group (see [RFC7919] +%% for group definitions) encoded as a big-endian integer and padded to +%% the left with zeros to the size of p in bytes. +%% +%% 4.2.8.2. ECDHE Parameters +%% +%% ECDHE parameters for both clients and servers are encoded in the +%% opaque key_exchange field of a KeyShareEntry in a KeyShare structure. +%% +%% For secp256r1, secp384r1, and secp521r1, the contents are the +%% serialized value of the following struct: +%% +%% struct { +%% uint8 legacy_form = 4; +%% opaque X[coordinate_length]; +%% opaque Y[coordinate_length]; +%% } UncompressedPointRepresentation; +%% +%% X and Y, respectively, are the binary representations of the x and y +%% values in network byte order. There are no internal length markers, +%% so each number representation occupies as many octets as implied by +%% the curve parameters. For P-256, this means that each of X and Y use +%% 32 octets, padded on the left by zeros if necessary. For P-384, they +%% take 48 octets each. For P-521, they take 66 octets each. +maybe_add_key_share(HelloExtensions, undefined) -> + HelloExtensions; +maybe_add_key_share(HelloExtensions, KeyShare) -> + #key_share_client_hello{client_shares = ClientShares0} = KeyShare, + %% Keep only public keys + Fun = fun(#key_share_entry{ + group = Group, + key_exchange = + #'ECPrivateKey'{publicKey = PublicKey}}) -> + #key_share_entry{ + group = Group, + key_exchange = PublicKey}; + (#key_share_entry{ + group = Group, + key_exchange = + {PublicKey, _}}) -> + #key_share_entry{ + group = Group, + key_exchange = PublicKey} + end, + ClientShares = lists:map(Fun, ClientShares0), + HelloExtensions#{key_share => #key_share_client_hello{ + client_shares = ClientShares}}. + + signature_algs_cert(undefined) -> undefined; signature_algs_cert(SignatureSchemes) -> @@ -1948,6 +2029,20 @@ encode_versions([], Acc) -> encode_versions([{M,N}|T], Acc) -> encode_versions(T, <<?BYTE(M),?BYTE(N),Acc/binary>>). +encode_client_shares(ClientShares) -> + encode_client_shares(ClientShares, <<>>). +%% +encode_client_shares([], Acc) -> + Acc; +encode_client_shares([KeyShareEntry0|T], Acc) -> + KeyShareEntry = encode_key_share_entry(KeyShareEntry0), + encode_client_shares(T, <<Acc/binary,KeyShareEntry/binary>>). + +encode_key_share_entry(#key_share_entry{ + group = Group, + key_exchange = KeyExchange}) -> + Len = byte_size(KeyExchange), + <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>. hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2090,19 +2185,19 @@ dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) -> dec_server_key_signature(_, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)). -decode_extensions(<<>>, _Version, Acc) -> +decode_extensions(<<>>, _Version, _MessageType, Acc) -> Acc; decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), - ExtensionData:Len/binary, Rest/binary>>, Version, Acc) + ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len + 2 =:= ExtLen -> ALPN = #alpn{extension_data = ExtensionData}, - decode_extensions(Rest, Version, Acc#{alpn => ALPN}); + decode_extensions(Rest, Version, MessageType, Acc#{alpn => ALPN}); decode_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), - ExtensionData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtensionData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> NextP = #next_protocol_negotiation{extension_data = ExtensionData}, - decode_extensions(Rest, Version, Acc#{next_protocol_negotiation => NextP}); + decode_extensions(Rest, Version, MessageType, Acc#{next_protocol_negotiation => NextP}); decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), - Info:Len/binary, Rest/binary>>, Version, Acc) -> + Info:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> RenegotiateInfo = case Len of 1 -> % Initial handshake Info; % should be <<0>> will be matched in handle_renegotiation_info @@ -2111,49 +2206,53 @@ decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), <<?BYTE(VerifyLen), VerifyInfo/binary>> = Info, VerifyInfo end, - decode_extensions(Rest, Version, Acc#{renegotiation_info => - #renegotiation_info{renegotiated_connection = - RenegotiateInfo}}); + decode_extensions(Rest, Version, MessageType, + Acc#{renegotiation_info => + #renegotiation_info{renegotiated_connection = + RenegotiateInfo}}); decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), - SRP:SRPLen/binary, Rest/binary>>, Version, Acc) + SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc) when Len == SRPLen + 2 -> - decode_extensions(Rest, Version, Acc#{srp => #srp{username = SRP}}); + decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version < {3,4} -> SignAlgoListLen = Len - 2, <<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData, HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || <<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList], - decode_extensions(Rest, Version, Acc#{signature_algs => - #hash_sign_algos{hash_sign_algos = - HashSignAlgos}}); + decode_extensions(Rest, Version, MessageType, + Acc#{signature_algs => + #hash_sign_algos{hash_sign_algos = + HashSignAlgos}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version =:= {3,4} -> SignSchemeListLen = Len - 2, <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData, SignSchemes = [ssl_cipher:signature_scheme(SignScheme) || <<?UINT16(SignScheme)>> <= SignSchemeList], - decode_extensions(Rest, Version, Acc#{signature_algs => - #signature_algorithms{ - signature_scheme_list = SignSchemes}}); + decode_extensions(Rest, Version, MessageType, + Acc#{signature_algs => + #signature_algorithms{ + signature_scheme_list = SignSchemes}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_CERT_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> SignSchemeListLen = Len - 2, <<?UINT16(SignSchemeListLen), SignSchemeList/binary>> = ExtData, SignSchemes = [ssl_cipher:signature_scheme(SignScheme) || <<?UINT16(SignScheme)>> <= SignSchemeList], - decode_extensions(Rest, Version, Acc#{signature_algs_cert => - #signature_algorithms_cert{ - signature_scheme_list = SignSchemes}}); + decode_extensions(Rest, Version, MessageType, + Acc#{signature_algs_cert => + #signature_algorithms_cert{ + signature_scheme_list = SignSchemes}}); decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version < {3,4} -> <<?UINT16(_), EllipticCurveList/binary>> = ExtData, %% Ignore unknown curves @@ -2166,13 +2265,13 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), end end, EllipticCurves = lists:filtermap(Pick, [ECC || <<ECC:16>> <= EllipticCurveList]), - decode_extensions(Rest, Version, Acc#{elliptic_curves => - #elliptic_curves{elliptic_curve_list = - EllipticCurves}}); - + decode_extensions(Rest, Version, MessageType, + Acc#{elliptic_curves => + #elliptic_curves{elliptic_curve_list = + EllipticCurves}}); decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Version =:= {3,4} -> <<?UINT16(_), GroupList/binary>> = ExtData, %% Ignore unknown curves @@ -2185,47 +2284,84 @@ decode_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), end end, SupportedGroups = lists:filtermap(Pick, [Group || <<Group:16>> <= GroupList]), - decode_extensions(Rest, Version, Acc#{elliptic_curves => - #supported_groups{supported_groups = - SupportedGroups}}); + decode_extensions(Rest, Version, MessageType, + Acc#{elliptic_curves => + #supported_groups{supported_groups = + SupportedGroups}}); decode_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> <<?BYTE(_), ECPointFormatList/binary>> = ExtData, ECPointFormats = binary_to_list(ECPointFormatList), - decode_extensions(Rest, Version, Acc#{ec_point_formats => - #ec_point_formats{ec_point_format_list = - ECPointFormats}}); + decode_extensions(Rest, Version, MessageType, + Acc#{ec_point_formats => + #ec_point_formats{ec_point_format_list = + ECPointFormats}}); decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), - Rest/binary>>, Version, Acc) when Len == 0 -> - decode_extensions(Rest, Version, Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI + Rest/binary>>, Version, MessageType, Acc) when Len == 0 -> + decode_extensions(Rest, Version, MessageType, + Acc#{sni => #sni{hostname = ""}}); %% Server may send an empy SNI decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> <<?UINT16(_), NameList/binary>> = ExtData, - decode_extensions(Rest, Version, Acc#{sni => dec_sni(NameList)}); + decode_extensions(Rest, Version, MessageType, + Acc#{sni => dec_sni(NameList)}); decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Version, Acc) when Len > 2 -> + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 -> <<?UINT16(_),Versions/binary>> = ExtData, - decode_extensions(Rest, Version, Acc#{client_hello_versions => - #client_hello_versions{ - versions = decode_versions(Versions)}}); + decode_extensions(Rest, Version, MessageType, + Acc#{client_hello_versions => + #client_hello_versions{ + versions = decode_versions(Versions)}}); decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), - ?UINT16(SelectedVersion), Rest/binary>>, Version, Acc) + ?UINT16(SelectedVersion), Rest/binary>>, Version, MessageType, Acc) when Len =:= 2, SelectedVersion =:= 16#0304 -> - decode_extensions(Rest, Version, Acc#{server_hello_selected_version => - #server_hello_selected_version{selected_version = - {3,4}}}); + decode_extensions(Rest, Version, MessageType, + Acc#{server_hello_selected_version => + #server_hello_selected_version{selected_version = + {3,4}}}); + +decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = client_hello, Acc) -> + <<?UINT16(_),ClientShares/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{key_share => + #key_share_client_hello{ + client_shares = decode_client_shares(ClientShares)}}); + +decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = server_hello, Acc) -> + <<?UINT16(Group),?UINT16(KeyLen),KeyExchange:KeyLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{key_share => + #key_share_server_hello{ + server_share = + #key_share_entry{ + group = tls_v1:enum_to_group(Group), + key_exchange = KeyExchange}}}); + +decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = hello_retry_request, Acc) -> + <<?UINT16(Group),Rest/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{key_share => + #key_share_hello_retry_request{ + selected_group = tls_v1:enum_to_group(Group)}}); + %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. -decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, Acc) -> - decode_extensions(Rest, Version, Acc); +decode_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> + decode_extensions(Rest, Version, MessageType, Acc); %% This theoretically should not happen if the protocol is followed, but if it does it is ignored. -decode_extensions(_, _, Acc) -> +decode_extensions(_, _, _, Acc) -> Acc. dec_hashsign(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) -> @@ -2252,6 +2388,17 @@ decode_versions(<<?BYTE(M),?BYTE(N),Rest/binary>>, Acc) -> decode_versions(Rest, [{M,N}|Acc]). +decode_client_shares(ClientShares) -> + decode_client_shares(ClientShares, []). +%% +decode_client_shares(<<>>, Acc) -> + lists:reverse(Acc); +decode_client_shares(<<?UINT16(Group),?UINT16(Len),KeyExchange:Len/binary,Rest/binary>>, Acc) -> + decode_client_shares(Rest, [#key_share_entry{ + group = tls_v1:enum_to_group(Group), + key_exchange= KeyExchange + }|Acc]). + decode_next_protocols({next_protocol_negotiation, Protocols}) -> decode_protocols(Protocols, []). diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index 1fd143a641..d4233bea9b 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -107,7 +107,8 @@ sni, client_hello_versions, server_hello_selected_version, - signature_algs_cert + signature_algs_cert, + key_share }). -record(server_hello, { diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 29988edf76..5de1424414 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -498,9 +498,10 @@ init({call, From}, {start, Timeout}, session_cache = Cache, session_cache_cb = CacheCb } = State0) -> + KeyShare = maybe_generate_key_share(SslOpts), Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, - Cache, CacheCb, Renegotiation, Cert), + Cache, CacheCb, Renegotiation, Cert, KeyShare), Version = Hello#client_hello.client_version, HelloVersion = tls_record:hello_version(Version, SslOpts#ssl_options.versions), @@ -522,7 +523,8 @@ init({call, From}, {start, Timeout}, Session0#session{session_id = Hello#client_hello.session_id}, tls_handshake_history = Handshake, start_or_recv_from = From, - timer = Timer}, + timer = Timer, + key_share = KeyShare}, {Record, State} = next_record(State1), next_event(hello, Record, State); init(Type, Event, State) -> @@ -674,7 +676,7 @@ connection(internal, #hello_request{}, ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, - Cache, CacheCb, Renegotiation, Cert), + Cache, CacheCb, Renegotiation, Cert, undefined), {State1, Actions} = send_handshake(Hello, State0), {Record, State} = next_record( @@ -1101,3 +1103,13 @@ ensure_sender_terminate(_, #state{protocol_specific = #{sender := Sender}}) -> end end, spawn(Kill). + +maybe_generate_key_share(#ssl_options{ + versions = [Version|_], + supported_groups = + #supported_groups{ + supported_groups = Groups}}) + when Version =:= {3,4} -> + ssl_cipher:generate_client_shares(Groups); +maybe_generate_key_share(_) -> + undefined. diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 37f13fcbac..19535a2fcb 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -26,6 +26,7 @@ -module(tls_handshake). -include("tls_handshake.hrl"). +-include("tls_handshake_1_3.hrl"). -include("tls_record.hrl"). -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). @@ -34,7 +35,7 @@ -include_lib("kernel/include/logger.hrl"). %% Handshake handling --export([client_hello/8, hello/4]). +-export([client_hello/9, hello/4]). %% Handshake encoding -export([encode_handshake/2]). @@ -49,7 +50,8 @@ %%==================================================================== %%-------------------------------------------------------------------- -spec client_hello(host(), inet:port_number(), ssl_record:connection_states(), - #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> + #ssl_options{}, integer(), atom(), boolean(), der_cert(), + #key_share_client_hello{} | undefined) -> #client_hello{}. %% %% Description: Creates a client hello message. @@ -59,7 +61,7 @@ client_hello(Host, Port, ConnectionStates, ciphers = UserSuites, fallback = Fallback } = SslOpts, - Cache, CacheCb, Renegotiation, OwnCert) -> + Cache, CacheCb, Renegotiation, OwnCert, KeyShare) -> Version = tls_record:highest_protocol_version(Versions), %% In TLS 1.3, the client indicates its version preferences in the @@ -79,7 +81,8 @@ client_hello(Host, Port, ConnectionStates, Extensions = ssl_handshake:client_hello_extensions(Version, AvailableCipherSuites, SslOpts, ConnectionStates, - Renegotiation), + Renegotiation, + KeyShare), CipherSuites = ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation, Fallback), Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, @@ -409,7 +412,7 @@ decode_handshake(Version, ?CLIENT_HELLO, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> Exts = ssl_handshake:decode_vector(Extensions), - DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client), + DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client_hello), #client_hello{ client_version = {Major,Minor}, random = Random, diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 104017b67c..e34de9ef34 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -69,13 +69,13 @@ encode_handshake(HandshakeMsg) -> ssl_handshake:encode_handshake(HandshakeMsg, {3,4}). decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) -> - Exts = decode_extensions(EncExts), + Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ certificate_request_context = <<>>, extensions = Exts}; decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(CSize), Context:CSize/binary, ?UINT16(Size), EncExts:Size/binary>>) -> - Exts = decode_extensions(EncExts), + Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ certificate_request_context = Context, extensions = Exts}; @@ -94,12 +94,12 @@ decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary, }; decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) -> #encrypted_extensions{ - extensions = decode_extensions(EncExts) + extensions = decode_extensions(EncExts, encrypted_extensions) }; decode_handshake(?NEW_SESSION_TICKET, <<?UINT32(LifeTime), ?UINT32(Age), ?BYTE(Nonce), ?UINT16(TicketSize), Ticket:TicketSize/binary, BinExts/binary>>) -> - Exts = decode_extensions(BinExts), + Exts = decode_extensions(BinExts, encrypted_extensions), #new_session_ticket{ticket_lifetime = LifeTime, ticket_age_add = Age, ticket_nonce = Nonce, @@ -143,14 +143,14 @@ decode_cert_entries(<<>>, Acc) -> lists:reverse(Acc); decode_cert_entries(<<?UINT24(DSize), Data:DSize/binary, ?UINT16(Esize), BinExts:Esize/binary, Rest/binary>>, Acc) -> - Exts = decode_extensions(BinExts), + Exts = decode_extensions(BinExts, certificate_request), decode_cert_entries(Rest, [#certificate_entry{data = Data, extensions = Exts} | Acc]). encode_extensions(Exts)-> ssl_handshake:encode_extensions(extensions_list(Exts)). -decode_extensions(Exts) -> - ssl_handshake:decode_extensions(Exts, {3,4}). +decode_extensions(Exts, MessageType) -> + ssl_handshake:decode_extensions(Exts, {3,4}, MessageType). extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions)]. @@ -171,7 +171,7 @@ handle_client_hello(Version, case tls_record:is_acceptable_version(Version, Versions) of true -> %% Get supported_groups - %% SupportedGroups = maps:get(elliptic_curves, HelloExt, undefined), + SupportedGroups = maps:get(elliptic_curves, HelloExt, undefined), %% Get KeyShareClientHello %% Validate supported_groups + KeyShareClientHello diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 9ee0e0f845..6ef5364399 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -59,7 +59,7 @@ key_exchange %key_exchange<1..2^16-1>; }). -record(key_share_client_hello, { - entries %% KeyShareEntry client_shares<0..2^16-1>; + client_shares %% KeyShareEntry client_shares<0..2^16-1>; }). -record(key_share_hello_retry_request, { selected_group %% NamedGroup diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index 8b3b81aaf1..0165c68a18 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -298,7 +298,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% ClientCertiticateType, %% ServerCertificateType, %% Padding, - %% KeyShare, + KeyShare, %% PreSharedKey, %% PSKKeyExchangeModes, %% EarlyData, @@ -321,7 +321,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% oneof([client_cert_type(), undefined]), %% oneof([server_cert_type(), undefined]), %% oneof([padding(), undefined]), - %% oneof([key_share(), undefined]), + oneof([key_share(client_hello), undefined]), %% oneof([pre_shared_key(), undefined]), %% oneof([psk_key_exchange_modes(), undefined]), %% oneof([early_data(), undefined]), @@ -349,7 +349,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% client_cert_type => ClientCertificateType, %% server_cert_type => ServerCertificateType, %% padding => Padding, - %% key_share => KeyShare, + key_share => KeyShare, %% pre_shared_key => PreSharedKey, %% psk_key_exhange_modes => PSKKeyExchangeModes, %% early_data => EarlyData, @@ -396,12 +396,12 @@ extensions(Version, client_hello) -> })); extensions(?'TLS_v1.3' = Version, server_hello) -> ?LET({ - %% KeyShare, + KeyShare, %% PreSharedKeys, SupportedVersions }, { - %% oneof([key_share(), undefined]), + oneof([key_share(server_hello), undefined]), %% oneof([pre_shared_keys(), undefined]), oneof([server_hello_selected_version(), undefined]) }, @@ -411,7 +411,7 @@ extensions(?'TLS_v1.3' = Version, server_hello) -> true end, #{ - %% key_share => KeyShare, + key_share => KeyShare, %% pre_shared_keys => PreSharedKeys, server_hello_selected_version => SupportedVersions })); @@ -705,3 +705,55 @@ gen_string(0, Acc) -> Acc; gen_string(N, Acc) -> ?LET(Char, gen_char(), gen_string(N-1, [Char | Acc])). + +key_share(client_hello) -> + ?LET(ClientShares, key_share_entry_list(), + #key_share_client_hello{ + client_shares = ClientShares}); +key_share(server_hello) -> + ?LET([ServerShare], key_share_entry_list(1), + #key_share_server_hello{ + server_share = ServerShare}). + +key_share_entry_list() -> + ?LET(Size, choose(1,8), key_share_entry_list(Size)). +%% +key_share_entry_list(N) -> + key_share_entry_list(N, ssl:groups(), []). +%% +key_share_entry_list(0, _Pool, Acc) -> + Acc; +key_share_entry_list(N, Pool, Acc) -> + R = rand:uniform(length(Pool)), + G = lists:nth(R, Pool), + P = generate_public_key(G), + KeyShareEntry = + #key_share_entry{ + group = G, + key_exchange = P}, + key_share_entry_list(N - 1, Pool -- [G], [KeyShareEntry|Acc]). + +generate_public_key(Group) + when Group =:= secp256r1 orelse + Group =:= secp384r1 orelse + Group =:= secp521r1 -> + #'ECPrivateKey'{publicKey = PublicKey} = + public_key:generate_key({namedCurve, secp256r1}), + PublicKey; +generate_public_key(Group) -> + {PublicKey, _} = + public_key:generate_key(ssl_dh_groups:dh_params(Group)), + PublicKey. + +groups() -> + ?LET(Size, choose(1,8), group_list(Size)). + +group_list(N) -> + group_list(N, ssl:groups(), []). +%% +group_list(0, _Pool, Acc) -> + Acc; +group_list(N, Pool, Acc) -> + R = rand:uniform(length(Pool)), + G = lists:nth(R, Pool), + group_list(N - 1, Pool -- [G], [G|Acc]). diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index c35ee6cb57..e39313e5cd 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -112,7 +112,7 @@ decode_hello_handshake(_Config) -> decode_single_hello_extension_correctly(_Config) -> Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, - Extensions = ssl_handshake:decode_extensions(Renegotiation, {3,3}), + Extensions = ssl_handshake:decode_extensions(Renegotiation, {3,3}, undefined), #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions. decode_supported_elliptic_curves_hello_extension_correctly(_Config) -> |