Skip to content

Commit a61d2d5

Browse files
committed
ssl: Add support for ML-KEM
1 parent 8b23445 commit a61d2d5

File tree

9 files changed

+143
-8
lines changed

9 files changed

+143
-8
lines changed

lib/ssl/src/ssl_cipher.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,10 @@ generate_key_exchange(x25519) ->
12401240
crypto:generate_key(ecdh, x25519);
12411241
generate_key_exchange(x448) ->
12421242
crypto:generate_key(ecdh, x448);
1243+
generate_key_exchange(MLKem) when MLKem == mlkem512;
1244+
MLKem == mlkem768;
1245+
MLKem == mlkem1024 ->
1246+
crypto:generate_key(MLKem, []);
12431247
generate_key_exchange(FFDHE) ->
12441248
public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)).
12451249

lib/ssl/src/tls_handshake_1_3.erl

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -915,8 +915,9 @@ handle_secrets(State0) ->
915915
State3 = State2#state{protocol_specific = PS#{exporter_master_secret => ExporterSecret}},
916916
forget_master_secret(State3).
917917

918-
calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK,
918+
calculate_handshake_secrets(PublicKey, PrivateKeyOrSecret, SelectedGroup, PSK,
919919
#state{connection_states = ConnectionStates,
920+
static_env = #static_env{role = Role},
920921
handshake_env =
921922
#handshake_env{
922923
tls_handshake_history = HHistory}} = State0) ->
@@ -926,8 +927,15 @@ calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, PSK,
926927
cipher_suite = CipherSuite} = SecParamsR,
927928
EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}),
928929

929-
IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup),
930-
HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
930+
HandshakeSecret =
931+
case is_mlkem(SelectedGroup) of
932+
true ->
933+
IKM = mlkem_calculate_shared_secret(Role, SelectedGroup, PublicKey, PrivateKeyOrSecret),
934+
tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret);
935+
false ->
936+
IKM = calculate_shared_secret(PublicKey, PrivateKeyOrSecret, SelectedGroup),
937+
tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret)
938+
end,
931939

932940
%% Calculate [sender]_handshake_traffic_secret
933941
{Messages, _} = HHistory,
@@ -1155,6 +1163,10 @@ calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
11551163
Point = #'ECPoint'{point = OthersKey},
11561164
public_key:compute_key(Point, MyKey).
11571165

1166+
mlkem_calculate_shared_secret(client, Group, CipherText, PrivKey) ->
1167+
crypto:decapsulate_key(Group, PrivKey, CipherText);
1168+
mlkem_calculate_shared_secret(server, _, _, Secret) ->
1169+
Secret.
11581170

11591171
maybe_calculate_resumption_master_secret(#state{ssl_options = #{session_tickets := disabled}} = State) ->
11601172
State;
@@ -2032,3 +2044,10 @@ plausible_missing_chain([_] = EncodedChain, undefined, SignAlg, Key, Session0) -
20322044
};
20332045
plausible_missing_chain(_,Plausible,_,_,_) ->
20342046
Plausible.
2047+
2048+
is_mlkem(Group) when Group == mlkem512;
2049+
Group == mlkem768;
2050+
Group == mlkem1024 ->
2051+
true;
2052+
is_mlkem(_) ->
2053+
false.

lib/ssl/src/tls_handshake_1_3.hrl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@
180180
-define(BRAINPOOLP384R1TLS13, 16#0020).
181181
-define(BRAINPOOLP512R1TLS13, 16#0021).
182182

183+
%% ML-KEM
184+
-define(MLKEM512, 16#0200).
185+
-define(MLKEM768, 16#0201).
186+
-define(MLKEM1024, 16#0202).
187+
183188
%% RFC 8446 Finite Field Groups (DHE)
184189
-define(FFDHE2048, 16#0100).
185190
-define(FFDHE3072, 16#0101).

lib/ssl/src/tls_server_connection_1_3.erl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
462462
{Group, ClientPubKey} = select_client_public_key(Groups, ClientShares),
463463

464464
%% Generate server_share
465-
KeyShare = ssl_cipher:generate_server_share(Group),
465+
KeyShare = generate_server_share(Group, ClientPubKey),
466466

467467
State2 = case maps:get(max_frag_enum, Extensions, undefined) of
468468
MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
@@ -763,6 +763,25 @@ default_or_fallback({fallback, _}, #session{} = Default) ->
763763
default_or_fallback(Default, _) ->
764764
Default.
765765

766+
is_mlkem(Group) when Group == mlkem512;
767+
Group == mlkem768;
768+
Group == mlkem1024 ->
769+
true;
770+
is_mlkem(_) ->
771+
false.
772+
773+
generate_server_share(Group, OtherPubKey) ->
774+
case is_mlkem(Group) of
775+
true ->
776+
{Secret, CipherText} = crypto:encapsulate_key(Group, OtherPubKey),
777+
#key_share_server_hello{server_share = #key_share_entry{
778+
group = Group,
779+
key_exchange = {CipherText, Secret}
780+
}};
781+
false ->
782+
ssl_cipher:generate_server_share(Group)
783+
end.
784+
766785
select_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
767786
select_private_key(ServerShare).
768787

lib/ssl/src/tls_v1.erl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,9 @@ groups(all) ->
12271227
brainpoolP256r1tls13,
12281228
brainpoolP384r1tls13,
12291229
brainpoolP512r1tls13,
1230+
mlkem512,
1231+
mlkem768,
1232+
mlkem1024,
12301233
ffdhe2048,
12311234
ffdhe3072,
12321235
ffdhe4096,
@@ -1240,7 +1243,10 @@ groups(default) ->
12401243
secp256r1,
12411244
brainpoolP512r1tls13,
12421245
brainpoolP384r1tls13,
1243-
brainpoolP256r1tls13
1246+
brainpoolP256r1tls13,
1247+
mlkem512,
1248+
mlkem768,
1249+
mlkem1024
12441250
];
12451251
groups(TLSGroups) when is_list(TLSGroups) ->
12461252
CryptoGroups = crypto_supported_groups(),
@@ -1251,7 +1257,7 @@ default_groups() ->
12511257
groups(TLSGroups).
12521258

12531259
crypto_supported_groups() ->
1254-
crypto:supports(curves) ++
1260+
crypto:supports(curves) ++ crypto:supports(kems) ++
12551261
[ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192].
12561262

12571263
group_to_enum(secp256r1) -> ?SECP256R1;
@@ -1262,6 +1268,9 @@ group_to_enum(x448) -> ?X448;
12621268
group_to_enum(brainpoolP256r1tls13) -> ?BRAINPOOLP256R1TLS13;
12631269
group_to_enum(brainpoolP384r1tls13) -> ?BRAINPOOLP384R1TLS13;
12641270
group_to_enum(brainpoolP512r1tls13) -> ?BRAINPOOLP512R1TLS13;
1271+
group_to_enum(mlkem512) -> ?MLKEM512;
1272+
group_to_enum(mlkem768) -> ?MLKEM768;
1273+
group_to_enum(mlkem1024) -> ?MLKEM1024;
12651274
group_to_enum(ffdhe2048) -> ?FFDHE2048;
12661275
group_to_enum(ffdhe3072) -> ?FFDHE3072;
12671276
group_to_enum(ffdhe4096) -> ?FFDHE4096;
@@ -1276,6 +1285,9 @@ enum_to_group(?X448) -> x448;
12761285
enum_to_group(?BRAINPOOLP256R1TLS13) -> brainpoolP256r1tls13;
12771286
enum_to_group(?BRAINPOOLP384R1TLS13) -> brainpoolP384r1tls13;
12781287
enum_to_group(?BRAINPOOLP512R1TLS13) -> brainpoolP512r1tls13;
1288+
enum_to_group(?MLKEM512) -> mlkem512;
1289+
enum_to_group(?MLKEM768) -> mlkem768;
1290+
enum_to_group(?MLKEM1024) -> mlkem1024;
12791291
enum_to_group(?FFDHE2048) -> ffdhe2048;
12801292
enum_to_group(?FFDHE3072) -> ffdhe3072;
12811293
enum_to_group(?FFDHE4096) -> ffdhe4096;

lib/ssl/test/openssl_client_cert_SUITE.erl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
hello_retry_request/1,
6363
custom_groups/0,
6464
custom_groups/1,
65+
mlkem_groups/0,
66+
mlkem_groups/1,
6567
hello_retry_client_auth/0,
6668
hello_retry_client_auth/1,
6769
hello_retry_client_auth_empty_cert_accepted/0,
@@ -142,6 +144,7 @@ tls_1_3_tests() ->
142144
[
143145
hello_retry_request,
144146
custom_groups,
147+
mlkem_groups,
145148
hello_retry_client_auth,
146149
hello_retry_client_auth_empty_cert_accepted,
147150
hello_retry_client_auth_empty_cert_rejected
@@ -540,6 +543,10 @@ custom_groups() ->
540543
ssl_cert_tests:custom_groups().
541544
custom_groups(Config) ->
542545
ssl_cert_tests:custom_groups(Config).
546+
mlkem_groups() ->
547+
ssl_cert_tests:mlkem_groups().
548+
mlkem_groups(Config) ->
549+
ssl_cert_tests:mlkem_groups(Config).
543550
unsupported_sign_algo_cert_client_auth() ->
544551
ssl_cert_tests:unsupported_sign_algo_cert_client_auth().
545552
unsupported_sign_algo_cert_client_auth(Config) ->

lib/ssl/test/openssl_server_cert_SUITE.erl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
hello_retry_request/1,
6060
custom_groups/0,
6161
custom_groups/1,
62+
mlkem_groups/0,
63+
mlkem_groups/1,
6264
hello_retry_client_auth/0,
6365
hello_retry_client_auth/1,
6466
hello_retry_client_auth_empty_cert_accepted/0,
@@ -135,6 +137,7 @@ tls_1_3_tests() ->
135137
[
136138
hello_retry_request,
137139
custom_groups,
140+
mlkem_groups,
138141
hello_retry_client_auth,
139142
hello_retry_client_auth_empty_cert_accepted,
140143
hello_retry_client_auth_empty_cert_rejected
@@ -459,6 +462,10 @@ custom_groups() ->
459462
ssl_cert_tests:custom_groups().
460463
custom_groups(Config) ->
461464
ssl_cert_tests:custom_groups(Config).
465+
mlkem_groups() ->
466+
ssl_cert_tests:mlkem_groups().
467+
mlkem_groups(Config) ->
468+
ssl_cert_tests:mlkem_groups(Config).
462469
unsupported_sign_algo_cert_client_auth() ->
463470
ssl_cert_tests:unsupported_sign_algo_cert_client_auth().
464471
unsupported_sign_algo_cert_client_auth(Config) ->

lib/ssl/test/ssl_cert_SUITE.erl

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
hello_retry_request/1,
128128
custom_groups/0,
129129
custom_groups/1,
130+
mlkem_groups/0,
131+
mlkem_groups/1,
130132
hello_retry_client_auth/0,
131133
hello_retry_client_auth/1,
132134
hello_retry_client_auth_empty_cert_accepted/0,
@@ -180,7 +182,7 @@ groups() ->
180182
{rsa_pss_rsae_1_3, [parallel], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
181183
{rsa_pss_pss, [parallel], all_version_tests()},
182184
{rsa_pss_pss_1_3, [parallel], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()},
183-
{ecdsa_1_3, [parallel], all_version_tests() ++ tls_1_3_tests() ++ partial_chain_with_ecdsa()
185+
{ecdsa_1_3, [parallel], all_version_tests() ++ tls_1_3_tests() ++ partial_chain_with_ecdsa() ++
184186
[signature_algorithms_bad_curve_secp256r1,
185187
signature_algorithms_bad_curve_secp384r1,
186188
signature_algorithms_bad_curve_secp521r1]},
@@ -213,6 +215,7 @@ tls_1_3_tests() ->
213215
[
214216
hello_retry_request,
215217
custom_groups,
218+
mlkem_groups,
216219
client_auth_no_suitable_chain,
217220
cert_auth_in_first_ca,
218221
hello_retry_client_auth,
@@ -488,7 +491,7 @@ end_per_group(GroupName, Config) ->
488491
ssl_test_lib:end_per_group(GroupName, Config).
489492

490493

491-
init_per_group(mlkem_groups, Config) ->
494+
init_per_testcase(mlkem_groups, Config) ->
492495
case [] =/= crypto:supports(kems) of
493496
true ->
494497
Config;
@@ -1364,6 +1367,26 @@ custom_groups(Config) ->
13641367
ClientOpts = [{supported_groups,[secp384r1, secp256r1, x25519]}|ClientOpts1],
13651368
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
13661369

1370+
%%--------------------------------------------------------------------
1371+
mlkem_groups() ->
1372+
[{doc,"Test that ssl server can select a common group for key-exchange"}].
1373+
1374+
mlkem_groups(Config) ->
1375+
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
1376+
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
1377+
1378+
mlkem_kex(mlkem512, ClientOpts0, ServerOpts0, Config),
1379+
mlkem_kex(mlkem768, ClientOpts0, ServerOpts0, Config),
1380+
mlkem_kex(mlkem1024, ClientOpts0, ServerOpts0, Config).
1381+
1382+
mlkem_kex(MLKem, ClientOpts0, ServerOpts0, Config) ->
1383+
%% Set versions
1384+
ServerOpts = [{versions, ['tlsv1.3']},
1385+
{supported_groups, [MLKem]}|ServerOpts0],
1386+
ClientOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
1387+
ClientOpts = [{supported_groups,[ MLKem, secp384r1, secp256r1, x25519]}|ClientOpts1],
1388+
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
1389+
13671390
%%--------------------------------------------------------------------
13681391
%% Triggers a Server Alert as ssl client does not have a certificate with a
13691392
%% signature algorithm supported by the server (signature_algorithms_cert extension

lib/ssl/test/ssl_cert_tests.erl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
hello_retry_request/1,
6464
custom_groups/0,
6565
custom_groups/1,
66+
mlkem_groups/0,
67+
mlkem_groups/1,
6668
hello_retry_client_auth/0,
6769
hello_retry_client_auth/1,
6870
hello_retry_client_auth_empty_cert_accepted/0,
@@ -404,6 +406,25 @@ custom_groups(Config) ->
404406

405407
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
406408

409+
%%--------------------------------------------------------------------
410+
mlkem_groups() ->
411+
[{doc,"Test that ssl server can select a common mlkem group for key-exchange"}].
412+
413+
mlkem_groups(Config) ->
414+
test_mlkem(Config, mlkem512),
415+
test_mlkem(Config, mlkem768),
416+
test_mlkem(Config, mlkem1024).
417+
418+
test_mlkem(Config, MLKemGroup) ->
419+
ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config),
420+
ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config),
421+
422+
{ServerOpts, ClientOpts} = group_config_mlkem(Config,
423+
[{versions, ['tlsv1.3']} | ServerOpts0],
424+
[{versions, ['tlsv1.3']} | ClientOpts0], MLKemGroup),
425+
426+
ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config).
427+
407428
%%--------------------------------------------------------------------
408429
%% Triggers a Server Alert as ssl client does not have a certificate with a
409430
%% signature algorithm supported by the server (signature_algorithms_cert extension
@@ -566,6 +587,24 @@ group_config(Config, ServerOpts, ClientOpts) ->
566587
[{groups,"P-256:X25519"} | ClientOpts]}
567588
end.
568589

590+
591+
group_config_mlkem(Config, ServerOpts, ClientOpts, Group) ->
592+
case proplists:get_value(client_type, Config) of
593+
erlang ->
594+
{[{groups, openssl_mlkem(Group)} | ServerOpts],
595+
[{supported_groups, [Group]} | ClientOpts]};
596+
openssl ->
597+
{[{supported_groups, [Group]} | ServerOpts],
598+
[{groups, openssl_mlkem(Group)} | ClientOpts]}
599+
end.
600+
601+
openssl_mlkem(mlkem512) ->
602+
"MLKEM512";
603+
openssl_mlkem(mlkem768) ->
604+
"MLKEM768";
605+
openssl_mlkem(mlkem1024) ->
606+
"MLKEM1024".
607+
569608
choose_custom_key(#'RSAPrivateKey'{} = Key, Version)
570609
when (Version == 'dtlsv1') or (Version == 'tlsv1') or (Version == 'tlsv1.1') ->
571610
EFun = fun (PlainText, Options) ->

0 commit comments

Comments
 (0)