summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/ssl/src/dtls_handshake.erl2
-rw-r--r--lib/ssl/src/ssl_cipher.erl29
-rw-r--r--lib/ssl/src/ssl_connection.hrl3
-rw-r--r--lib/ssl/src/ssl_dh_groups.erl26
-rw-r--r--lib/ssl/src/ssl_handshake.erl293
-rw-r--r--lib/ssl/src/ssl_handshake.hrl3
-rw-r--r--lib/ssl/src/tls_connection.erl18
-rw-r--r--lib/ssl/src/tls_handshake.erl13
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl16
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl2
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl64
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl2
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) ->