Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/ssl/src/ssl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,11 @@ certificate signatures.
The following options are specific to the client side, or have
different semantics for the client and server:

- **`{psk_groups, Groups}`** - key exchange groups that the client
will send pre share keys for, defaults to first group in
supported_groups. Must be a subset of supported_groups and will
be sent in the same order as they appear in supported_groups.

- **`{alpn_advertised_protocols, AppProtocols}`** - Application layer protocol

The list of protocols supported by the client to be sent to the server to be
Expand All @@ -1397,6 +1402,7 @@ different semantics for the client and server:

-type client_option() :: client_option_cert() |
common_option_cert() |
{psk_groups, [group()]} |
{alpn_advertised_protocols, AppProtocols::[AppProto::binary()]} |
{max_fragment_length, MaxLen:: undefined | 512 | 1024 | 2048 | 4096} |
client_option_tls13() |
Expand Down
35 changes: 1 addition & 34 deletions lib/ssl/src/ssl_cipher.erl
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@
bulk_cipher_algorithm/1]).

%% RFC 8446 TLS 1.3
-export([generate_client_shares/1,
generate_server_share/1,
add_zero_padding/2,
-export([add_zero_padding/2,
encrypt_ticket/3,
decrypt_ticket/3,
encrypt_data/4,
Expand Down Expand Up @@ -1220,37 +1218,6 @@ filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) ->
CipherSuits -- Suites
end.

generate_server_share(Group) ->
Key = generate_key_exchange(Group),
#key_share_server_hello{
server_share = #key_share_entry{
group = Group,
key_exchange = Key
}}.

generate_client_shares(Groups) ->
KeyShareEntry = fun (Group) ->
#key_share_entry{group = Group, key_exchange = generate_key_exchange(Group)}
end,
ClientShares = lists:map(KeyShareEntry, Groups),
#key_share_client_hello{client_shares = ClientShares}.

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(x25519) ->
crypto:generate_key(ecdh, x25519);
generate_key_exchange(x448) ->
crypto:generate_key(ecdh, x448);
generate_key_exchange(MLKem) when MLKem == mlkem512;
MLKem == mlkem768;
MLKem == mlkem1024 ->
crypto:generate_key(MLKem, []);
generate_key_exchange(FFDHE) ->
public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)).


%% TODO: Move this functionality to crypto!
Expand Down
25 changes: 23 additions & 2 deletions lib/ssl/src/ssl_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ ssl_options() ->
partial_chain,
password,
protocol,
psk_groups,
psk_identity,
receiver_spawn_opts,
renegotiate_at,
Expand Down Expand Up @@ -1371,7 +1372,7 @@ handle_user_lookup(UserOpts, #{versions := Versions} = Opts) ->
end.


opt_supported_groups(UserOpts, #{versions := TlsVsns} = Opts, _Env) ->
opt_supported_groups(UserOpts, #{versions := TlsVsns} = Opts, Env) ->
SG = case get_opt_list(supported_groups, undefined, UserOpts, Opts) of
{default, undefined} ->
try assert_version_dep(supported_groups, TlsVsns, ['tlsv1.3']) of
Expand Down Expand Up @@ -1404,7 +1405,27 @@ opt_supported_groups(UserOpts, #{versions := TlsVsns} = Opts, _Env) ->
throw:_ ->
[]
end,
Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG}.
case opt_psk_groups(SG, UserOpts, Opts, Env) of
undefined ->
Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG};
PSKGroups ->
Opts#{ciphers => CPHS, eccs => ECCS, supported_groups => SG, psk_groups => PSKGroups}
end.

opt_psk_groups(undefined, _, _, _) ->
undefined;
opt_psk_groups(#supported_groups{supported_groups = SupportedGroups}, UserOpts, Opts, _Env) ->
%% Version dependency already asserted when SupportedGroups is supported
%% so is psk_groups
First = hd(SupportedGroups),
case get_opt_list(psk_groups, [First], UserOpts, Opts) of
{default, Default} ->
Default;
{new, PSKGroups} ->
[Group || Group <- SupportedGroups, lists:member(Group, PSKGroups)];
{old, PSKGroups} ->
PSKGroups
end.

opt_crl(UserOpts, Opts, _Env) ->
{_, Check} = get_opt_of(crl_check, [best_effort, peer, true, false], false, UserOpts, Opts),
Expand Down
86 changes: 73 additions & 13 deletions lib/ssl/src/ssl_handshake.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1474,21 +1474,35 @@ add_selected_version(Extensions) ->
Extensions#{server_hello_selected_version => SupportedVersions}.

kse_remove_private_key(#key_share_entry{
group = Group,
key_exchange =
#'ECPrivateKey'{publicKey = PublicKey}}) ->
group = Group,
key_exchange =
#'ECPrivateKey'{publicKey = PublicKey}}) ->
#key_share_entry{
group = Group,
key_exchange = PublicKey};
kse_remove_private_key(#key_share_entry{
group = Group,
key_exchange =
{PublicKey, _}}) ->
group = Group,
key_exchange =
{#'ECPrivateKey'{publicKey = PublicKey1},
{PublicKey2, _}}}) ->
#key_share_entry{
group = Group,
key_exchange = <<PublicKey1/binary, PublicKey2/binary>>};
kse_remove_private_key(#key_share_entry{
group = Group,
key_exchange =
{{PublicKey1, _}, {PublicKey2, _}}}) ->
#key_share_entry{
group = Group,
key_exchange = <<PublicKey1/binary, PublicKey2/binary>>};
kse_remove_private_key(#key_share_entry{
group = Group,
key_exchange =
{PublicKey, _}}) ->
#key_share_entry{
group = Group,
key_exchange = PublicKey}.


signature_algs_ext(undefined) ->
undefined;
signature_algs_ext(SignatureSchemes0) ->
Expand Down Expand Up @@ -2665,7 +2679,6 @@ encode_versions(Versions) ->

encode_client_shares(ClientShares) ->
<< << (encode_key_share_entry(KeyShareEntry0))/binary >> || KeyShareEntry0 <- ClientShares >>.

encode_key_share_entry(#key_share_entry{group = Group,
key_exchange = KeyExchange}) ->
Len = byte_size(KeyExchange),
Expand Down Expand Up @@ -3075,14 +3088,15 @@ decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
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,
assert_unique_extension(key_share, Acc),
<<?UINT16(EnumGroup),?UINT16(KeyLen),KeyExchange0:KeyLen/binary>> = ExtData,
Group = tls_v1:enum_to_group(EnumGroup),
KeyExchange = maybe_dec_server_hybrid_share(Group, KeyExchange0),
decode_extensions(Rest, Version, MessageType,
Acc#{key_share =>
#key_share_server_hello{
server_share =
#key_share_entry{
group = tls_v1:enum_to_group(Group),
group = Group,
key_exchange = KeyExchange}}});

decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len),
Expand Down Expand Up @@ -3239,8 +3253,53 @@ dec_hashsign(Value) ->
[HashSign] = decode_sign_alg(?TLS_1_2, Value),
HashSign.

maybe_dec_server_hybrid_share(x25519mlkem768, <<MLKem:1088/binary, X25519:32/binary>>) ->
%% Concatenation of an ML-KEM ciphertext returned from
%% encapsulation to the client's encapsulation key The size of the
%% server share is 1120 bytes (1088 bytes for the ML-KEM part and
%% 32 bytes for X25519).
%% Note exception algorithm should be in reveres order of name due to legacy reason
{MLKem, X25519};
maybe_dec_server_hybrid_share(secp256r1mlkem768, <<Secp256r1:65/binary, MLKem:1088/binary>>) ->
%% Concatenation of the server's ephemeral secp256r1 share encoded
%% in the same way as the client share and an ML-KEM The size of
%% the server share is 1153 bytes (1088 bytes for the ML-KEM part
%% and 65 bytes for secp256r1).
{Secp256r1, MLKem};
maybe_dec_server_hybrid_share(secp384r1mlkem1024, <<Secp384r1:97/binary, MLKem:1568/binary>>) ->
%% Concatenation of the server's ephemeral secp384r1 share encoded
%% in the same way as the client share and an ML-KEM ciphertext
%% returned from encapsulation to the client's encapsulation key
%% The size of the server share is 1665 bytes (1568 bytes for the
%% ML-KEM part and 97 bytes for secp384r1)
{Secp384r1, MLKem};
maybe_dec_server_hybrid_share(_, Share) ->
%% Not hybrid
Share.

maybe_dec_client_hybrid_share(x25519mlkem768, <<MLKem:1184/binary, X25519:32/binary>>) ->
%% Concatenation of the client's ML-KEM-768 encapsulation key and
%% the client's X25519 ephemeral share. The size of the client share
%% is 1216 bytes (1184 bytes for the ML-KEM part and 32 bytes for
%% X25519).
%% Note exception algorithm should be in reveres order of name due to legacy reason
{MLKem, X25519};
maybe_dec_client_hybrid_share(secp256r1mlkem768, <<Secp256r1:65/binary, MLKem:1184/binary>>) ->
%% Concatenation of the secp256r1 ephemeral share and ML-KEM-768
%% encapsulation key The size of the client share is 1249 bytes (65
%% bytes for the secp256r1 part and 1184 bytes for ML-KEM). Ignore
%% unknown names (only host_name is supported)
{Secp256r1, MLKem};
maybe_dec_client_hybrid_share(secp384r1mlkem1024, <<Secp384r1:97/binary, MLKem:1568/binary>>) ->
%% Concatenation of the secp384r1 ephemeral share and the
%% ML-KEM-1024 encapsulation key. The size of the client share
%% is 1665 bytes (97 bytes for the secp384r1 and the 1568 for the
%% ML-KEM).
{Secp384r1, MLKem};
maybe_dec_client_hybrid_share(_, Share) ->
%% Not hybrid
Share.

%% Ignore unknown names (only host_name is supported)
dec_sni(<<?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(Len),
HostName:Len/binary, _/binary>>) ->
#sni{hostname = binary_to_list(HostName)};
Expand All @@ -3266,12 +3325,13 @@ decode_client_shares(ClientShares) ->
%%
decode_client_shares(<<>>, Acc) ->
lists:reverse(Acc);
decode_client_shares(<<?UINT16(Group0),?UINT16(Len),KeyExchange:Len/binary,Rest/binary>>, Acc) ->
decode_client_shares(<<?UINT16(Group0),?UINT16(Len),KeyExchange0:Len/binary,Rest/binary>>, Acc) ->
case tls_v1:enum_to_group(Group0) of
undefined ->
%% Ignore key_share with unknown group
decode_client_shares(Rest, Acc);
Group ->
KeyExchange = maybe_dec_client_hybrid_share(Group, KeyExchange0),
decode_client_shares(Rest, [#key_share_entry{
group = Group,
key_exchange= KeyExchange
Expand Down
32 changes: 22 additions & 10 deletions lib/ssl/src/tls_client_connection_1_3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -593,17 +593,25 @@ maybe_resumption(_) ->
ok.

maybe_generate_client_shares(#{versions := [?TLS_1_3|_],
supported_groups :=
#supported_groups{
supported_groups = [Group|_]}}) ->
%% Generate only key_share entry for the most preferred group
ssl_cipher:generate_client_shares([Group]);
psk_groups := Groups}) ->
%% Default will be the list of only the most preferred supported group
generate_client_shares(Groups);
maybe_generate_client_shares(_) ->
undefined.

%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
generate_client_shares(Groups) ->
KeyShareEntry =
fun (Group) ->
#key_share_entry{group = Group,
key_exchange = tls_handshake_1_3:generate_kex_keys(Group)}
end,
ClientShares = lists:map(KeyShareEntry, Groups),
#key_share_client_hello{client_shares = ClientShares}.


handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State0) ->
case do_handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello,
State0) of
Expand Down Expand Up @@ -667,7 +675,7 @@ do_handle_exlusive_1_3_hello_or_hello_retry_request(
%% replace the original "key_share" extension with one containing only a
%% new KeyShareEntry for the group indicated in the selected_group field
%% of the triggering HelloRetryRequest.
ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]),
ClientKeyShare = generate_client_shares([SelectedGroup]),
TicketData =
tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
OcspNonce = maps:get(ocsp_nonce, StaplingState, undefined),
Expand Down Expand Up @@ -866,10 +874,14 @@ server_share(#key_share_hello_retry_request{selected_group = Share}) ->
client_private_key(Group, ClientShares) ->
case lists:keysearch(Group, 2, ClientShares) of
{value, #key_share_entry{key_exchange =
ClientPrivateKey = #'ECPrivateKey'{}}} ->
ClientPrivateKey;
{value, #key_share_entry{key_exchange = {_, ClientPrivateKey}}} ->
ClientPrivateKey;
PrivateKey = #'ECPrivateKey'{}}} ->
PrivateKey;
{value, #key_share_entry{key_exchange = {#'ECPrivateKey'{} = PrivateKey1, {_, PrivateKey2}}}} ->
{PrivateKey1, PrivateKey2};
{value, #key_share_entry{key_exchange = {{_, PrivateKey1}, {_, PrivateKey2}}}} ->
{PrivateKey1, PrivateKey2};
{value, #key_share_entry{key_exchange = {_, PrivateKey}}} ->
PrivateKey;
false ->
no_suitable_key
end.
Expand Down
Loading
Loading