4747 handle_kexinit_msg /4 , handle_kexdh_init /2 ,
4848 handle_kex_dh_gex_group /2 , handle_kex_dh_gex_init /2 , handle_kex_dh_gex_reply /2 ,
4949 handle_new_keys /2 , handle_kex_dh_gex_request /2 ,
50- handle_kexdh_reply /2 ,
50+ handle_kexdh_reply /2 ,
5151 handle_kex_ecdh_init /2 ,
5252 handle_kex_ecdh_reply /2 ,
53+ handle_kex_hybrid_init /2 ,
54+ handle_kex_hybrid_reply /2 ,
5355 parallell_gen_key /1 ,
5456 ssh_packet /2 , pack /2 ,
5557 valid_key_sha_alg /3 ,
@@ -208,6 +210,7 @@ supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()].
208210supported_algorithms (kex ) ->
209211 select_crypto_supported (
210212 [
213+ {'mlkem768x25519-sha256' , [{kems , mlkem768 }, {public_keys ,ecdh }, {curves ,x25519 }, {hashs ,sha256 }]},
211214 {'curve25519-sha256' , [{public_keys ,ecdh }, {curves ,x25519 }, {hashs ,sha256 }]},
212215 {
'[email protected] ' , [{
public_keys ,
ecdh }, {
curves ,
x25519 }, {
hashs ,
sha256 }]},
213216 {'curve448-sha512' , [{public_keys ,ecdh }, {curves ,x448 }, {hashs ,sha512 }]},
@@ -573,8 +576,17 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ;
573576 Curve = ecdh_curve (Kex ),
574577 {Public , Private } = generate_key (ecdh , Curve ),
575578 {SshPacket , Ssh1 } = ssh_packet (# ssh_msg_kex_ecdh_init {q_c = Public }, Ssh0 ),
576- {ok , SshPacket ,
577- Ssh1 # ssh {keyex_key = {{Public ,Private },Curve }}}.
579+ {ok , SshPacket ,
580+ Ssh1 # ssh {keyex_key = {{Public ,Private },Curve }}};
581+ key_exchange_first_msg (Kex , Ssh0 ) when Kex == 'mlkem768x25519-sha256' ->
582+ Curve = ecdh_curve (Kex ),
583+ {C_publickey1 , C_privkey1 } = generate_key (ecdh , Curve ),
584+ {C_publickey2 , C_privkey2 } = generate_key (mlkem768 , []),
585+ {SshPacket , Ssh1 } = ssh_packet (
586+ # ssh_msg_kex_hybrid_init {c_init = {C_publickey2 , C_publickey1 }}, Ssh0 ),
587+ {ok , SshPacket ,
588+ Ssh1 # ssh {keyex_key = {{mlkem768 , {C_publickey2 , C_privkey2 }},
589+ {Curve , {C_publickey1 , C_privkey1 }}}}}.
578590
579591% %%----------------------------------------------------------------
580592% %%
@@ -904,6 +916,91 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
904916 [Class ,Error ], [{chars_limit , ssh_lib :max_log_len (Ssh0 )}]))
905917 end .
906918
919+ % %%---- PQ/T Hybrid Key Exchange Method
920+ handle_kex_hybrid_init (# ssh_msg_kex_hybrid_init {c_init = C_init },
921+ Ssh0 = # ssh {algorithms = # alg {kex = Kex ,
922+ hkey = SignAlg },
923+ opts = Opts }) ->
924+ % % at server
925+ Curve = ecdh_curve (Kex ),
926+ {S_publickey1 , S_privkey1 } = generate_key (ecdh , Curve ),
927+ try
928+ compute_key (hybrid_server , C_init , S_privkey1 , Curve )
929+ of
930+ {S_ciphertext2 , K_enc } ->
931+ MyPrivHostKey = get_host_key (SignAlg , Opts ),
932+ MyPubHostKey = ssh_file :extract_public_key (MyPrivHostKey ),
933+ S_reply = <<S_ciphertext2 /binary , S_publickey1 /binary >>,
934+ H = kex_hash (Ssh0 , MyPubHostKey , sha (Curve ),
935+ {mlkem , C_init , S_reply , K_enc }),
936+ case sign (H , SignAlg , MyPrivHostKey , Ssh0 ) of
937+ {ok , H_SIG } ->
938+ {SshPacket , Ssh1 } =
939+ ssh_packet (# ssh_msg_kex_hybrid_reply {
940+ public_host_key = {MyPubHostKey ,SignAlg },
941+ s_reply = S_reply ,
942+ h_sig = H_SIG
943+ },
944+ Ssh0 ),
945+ {ok , SshPacket , Ssh1 # ssh {
946+ shared_secret = K_enc ,
947+ exchanged_hash = H ,
948+ session_id = sid (Ssh1 , H )}};
949+ {error , unsupported_sign_alg } ->
950+ ? DISCONNECT (? SSH_DISCONNECT_KEY_EXCHANGE_FAILED ,
951+ io_lib :format (" Unsupported algorithm ~p " , [SignAlg ],
952+ [{chars_limit , ssh_lib :max_log_len (Opts )}]))
953+ end
954+ catch
955+ Class :Reason0 :_Stacktrace ->
956+ Reason = ssh_lib :trim_reason (Reason0 ),
957+ MsgFun =
958+ fun (debug ) ->
959+ io_lib :format (" Hybrid compute key failed in server: ~p :~p~n "
960+ " Kex: ~p , Curve: ~p~n "
961+ " C_init: ~p~n " ,
962+ [Class , Reason , Kex , Curve , C_init ],
963+ [{chars_limit , ssh_lib :max_log_len (Ssh0 )}]);
964+ (_ ) ->
965+ io_lib :format (" Hybrid compute key failed in server: ~p :~p " ,
966+ [Class ,Reason ],
967+ [{chars_limit , ssh_lib :max_log_len (Ssh0 )}])
968+ end ,
969+ ? DISCONNECT (? SSH_DISCONNECT_KEY_EXCHANGE_FAILED , ? SELECT_MSG (MsgFun ))
970+ end .
971+
972+ handle_kex_hybrid_reply (# ssh_msg_kex_hybrid_reply {public_host_key = PeerPubHostKey ,
973+ s_reply = S_reply ,
974+ h_sig = H_SIG },
975+ # ssh {keyex_key =
976+ {{mlkem768 , {C_publickey2 , C_privkey2 }},
977+ {Curve , {C_publickey1 , C_privkey1 }}}
978+ } = Ssh0
979+ ) ->
980+ % % at client
981+ try
982+ compute_key (hybrid_client , S_reply , {C_privkey2 , C_privkey1 }, Curve )
983+ of
984+ K_enc ->
985+ H = kex_hash (Ssh0 , PeerPubHostKey , sha (Curve ),
986+ {mlkem , {C_publickey2 , C_publickey1 }, S_reply , K_enc }),
987+ case verify_host_key (Ssh0 , PeerPubHostKey , H , H_SIG ) of
988+ ok ->
989+ {SshPacket , Ssh } = ssh_packet (# ssh_msg_newkeys {}, Ssh0 ),
990+ {ok , SshPacket , install_alg (snd , Ssh # ssh {shared_secret = K_enc ,
991+ exchanged_hash = H ,
992+ session_id = sid (Ssh , H )})};
993+ Error ->
994+ ? DISCONNECT (? SSH_DISCONNECT_KEY_EXCHANGE_FAILED ,
995+ io_lib :format (" Hybrid reply failed. Verify host key: ~p " ,[Error ],
996+ [{chars_limit , ssh_lib :max_log_len (Ssh0 )}]))
997+ end
998+ catch
999+ Class :Error :_Stacktrace ->
1000+ ? DISCONNECT (? SSH_DISCONNECT_KEY_EXCHANGE_FAILED ,
1001+ io_lib :format (" Peer Hybrid public key seem invalid: ~p :~p " ,
1002+ [Class ,Error ], [{chars_limit , ssh_lib :max_log_len (Ssh0 )}]))
1003+ end .
9071004
9081005% %%----------------------------------------------------------------
9091006handle_new_keys (# ssh_msg_newkeys {}, Ssh0 ) ->
@@ -1384,8 +1481,6 @@ pack(common, rfc4253, PlainText, DeltaLenTst,
13841481 {Ssh1 , CipherPkt } = encrypt (Ssh0 , PlainPkt ),
13851482 MAC0 = mac (MacAlg , MacKey , SeqNum , PlainPkt ),
13861483 {<<CipherPkt /binary ,MAC0 /binary >>, Ssh1 };
1387-
1388-
13891484pack (common , enc_then_mac , PlainText , DeltaLenTst ,
13901485 # ssh {send_sequence = SeqNum ,
13911486 send_mac = MacAlg ,
@@ -1398,7 +1493,6 @@ pack(common, enc_then_mac, PlainText, DeltaLenTst,
13981493 EncPacketPkt = <<? UINT32 (PlainLen ), CipherPkt /binary >>,
13991494 MAC0 = mac (MacAlg , MacKey , SeqNum , EncPacketPkt ),
14001495 {<<? UINT32 (PlainLen ), CipherPkt /binary , MAC0 /binary >>, Ssh1 };
1401-
14021496pack (aead , _ , PlainText , DeltaLenTst , Ssh0 ) ->
14031497 PadLen = padding_length (1 + byte_size (PlainText ), Ssh0 ),
14041498 Pad = ssh_bits :random (PadLen ),
@@ -1758,10 +1852,8 @@ encrypt_final(Ssh) ->
17581852 encrypt_ctx = undefined
17591853 }}.
17601854
1761-
17621855encrypt (# ssh {encrypt = none } = Ssh , Data ) ->
17631856 {Ssh , Data };
1764-
17651857encrypt (
# ssh {
encrypt = '[email protected] ' ,
17661858 encrypt_keys = {K1 ,K2 },
17671859 send_sequence = Seq } = Ssh ,
17781870 Ctag = crypto :mac (poly1305 , PolyKey , EncBytes ),
17791871 % % Result
17801872 {Ssh , {EncBytes ,Ctag }};
1781-
17821873encrypt (# ssh {encrypt = SshCipher ,
17831874 encrypt_cipher = CryptoCipher ,
17841875 encrypt_keys = K ,
@@ -1788,7 +1879,6 @@ encrypt(#ssh{encrypt = SshCipher,
17881879 {Ctext ,Ctag } = crypto :crypto_one_time_aead (CryptoCipher , K , IV0 , PayloadData , LenData , true ),
17891880 IV = next_gcm_iv (IV0 ),
17901881 {Ssh # ssh {encrypt_ctx = IV }, {<<LenData /binary ,Ctext /binary >>,Ctag }};
1791-
17921882encrypt (# ssh {encrypt_ctx = Ctx0 } = Ssh , Data ) ->
17931883 Enc = crypto :crypto_update (Ctx0 , Data ),
17941884 {Ssh , Enc }.
@@ -1843,7 +1933,6 @@ decrypt_final(Ssh) ->
18431933
18441934decrypt (Ssh , <<>>) ->
18451935 {Ssh , <<>>};
1846-
18471936decrypt (
# ssh {
decrypt = '[email protected] ' ,
18481937 decrypt_keys = {K1 ,K2 },
18491938 recv_sequence = Seq } = Ssh , Data ) ->
18661955 {Ssh ,error }
18671956 end
18681957 end ;
1869-
18701958decrypt (# ssh {decrypt = none } = Ssh , Data ) ->
18711959 {Ssh , Data };
1872-
18731960decrypt (# ssh {decrypt = SshCipher ,
18741961 decrypt_cipher = CryptoCipher ,
18751962 decrypt_keys = K ,
@@ -2067,20 +2154,23 @@ kex_plaintext(SSH, Key, Args) ->
20672154 ? Ebinary (EncodedKey ),
20682155 (kex_alg_dependent (Args ))/binary >>.
20692156
2070-
2157+ kex_alg_dependent ({mlkem , {C_publickey2 , C_publickey1 }, S_reply , K_enc }) ->
2158+ % % mlkem client
2159+ C_init = <<C_publickey2 /binary , C_publickey1 /binary >>,
2160+ kex_alg_dependent ({mlkem , C_init , S_reply , K_enc });
2161+ kex_alg_dependent ({mlkem , C_init , S_reply , K_enc }) ->
2162+ % % mlkem common
2163+ <<? Ebinary (C_init ), ? Ebinary (S_reply ), K_enc /binary >>;
20712164kex_alg_dependent ({Q_c , Q_s , K }) when is_binary (Q_c ), is_binary (Q_s ) ->
20722165 % % ecdh
20732166 <<? Ebinary (Q_c ), ? Ebinary (Q_s ), ? Empint (K )>>;
2074-
20752167kex_alg_dependent ({E , F , K }) ->
20762168 % % diffie-hellman
20772169 <<? Empint (E ), ? Empint (F ), ? Empint (K )>>;
2078-
20792170kex_alg_dependent ({- 1 , NBits , - 1 , Prime , Gen , E , F , K }) ->
20802171 % % ssh_msg_kex_dh_gex_request_old
20812172 <<? Euint32 (NBits ),
20822173 ? Empint (Prime ), ? Empint (Gen ), ? Empint (E ), ? Empint (F ), ? Empint (K )>>;
2083-
20842174kex_alg_dependent ({Min , NBits , Max , Prime , Gen , E , F , K }) ->
20852175 % % diffie-hellman group exchange
20862176 <<? Euint32 (Min ), ? Euint32 (NBits ), ? Euint32 (Max ),
@@ -2156,10 +2246,10 @@ sha('curve25519-sha256' ) -> sha256;
21562246sha (
'[email protected] ' )
-> sha256 ;
21572247sha ('curve448-sha512' ) -> sha512 ;
21582248sha (x25519 ) -> sha256 ;
2249+ sha ('mlkem768x25519-sha256' ) -> sha256 ;
21592250sha (x448 ) -> sha512 ;
21602251sha (Str ) when is_list (Str ), length (Str )< 50 -> sha (list_to_existing_atom (Str )).
21612252
2162-
21632253mac_key_bytes ('hmac-sha1' ) -> 20 ;
21642254mac_key_bytes (
'[email protected] ' )
-> 20 ;
21652255mac_key_bytes ('hmac-sha1-96' ) -> 20 ;
@@ -2193,7 +2283,6 @@ mac_digest_size(none) -> 0.
21932283% % Diffie-Hellman utils
21942284% %
21952285% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2196-
21972286dh_group ('diffie-hellman-group1-sha1' ) -> ? dh_group1 ;
21982287dh_group ('diffie-hellman-group14-sha1' ) -> ? dh_group14 ;
21992288dh_group ('diffie-hellman-group14-sha256' ) -> ? dh_group14 ;
@@ -2207,18 +2296,34 @@ parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}},
22072296 {Public , Private } = generate_key (dh , [P ,G ,2 * Sz ]),
22082297 Ssh # ssh {keyex_key = {{Private , Public }, {G , P }}}.
22092298
2210-
2299+ generate_key (mlkem768 , Args ) ->
2300+ crypto :generate_key (mlkem768 , Args );
22112301generate_key (ecdh , Args ) ->
22122302 crypto :generate_key (ecdh , Args );
22132303generate_key (dh , [P ,G ,Sz2 ]) ->
22142304 {Public ,Private } = crypto :generate_key (dh , [P , G , max (Sz2 ,? MIN_DH_KEY_SIZE )] ),
22152305 {crypto :bytes_to_integer (Public ), crypto :bytes_to_integer (Private )}.
22162306
2217-
2218- compute_key (Algorithm , OthersPublic , MyPrivate , Args ) ->
2219- Shared = crypto :compute_key (Algorithm , OthersPublic , MyPrivate , Args ),
2307+ compute_key (hybrid_server , C_init , S_privkey1 , Curve ) ->
2308+ <<C_publickey2 :1184 /binary , C_publickey1 :32 /binary >> = C_init ,
2309+ {K_pq_secret , S_ciphertext2 } = crypto :encapsulate_key (mlkem768 , C_publickey2 ),
2310+ SharedSecret = hybrid_common (K_pq_secret , Curve , C_publickey1 , S_privkey1 ),
2311+ {S_ciphertext2 , <<? Ebinary (SharedSecret )>>};
2312+ compute_key (hybrid_client , S_reply , {C_privkey2 , C_privkey1 }, Curve ) ->
2313+ <<S_ciphertext2 :1088 /binary , S_publickey1 :32 /binary >> = S_reply ,
2314+ K_pq_secret = crypto :decapsulate_key (mlkem768 , C_privkey2 , S_ciphertext2 ),
2315+ SharedSecret = hybrid_common (K_pq_secret , Curve , S_publickey1 , C_privkey1 ),
2316+ <<? Ebinary (SharedSecret )>>;
2317+ compute_key (Algorithm , PeerPublic , MyPrivate , Args ) ->
2318+ Shared = crypto :compute_key (Algorithm , PeerPublic , MyPrivate , Args ),
22202319 crypto :bytes_to_integer (Shared ).
22212320
2321+ hybrid_common (K_pq_secret , Curve , PeerPublic , MyPrivate ) ->
2322+ K_cl_secret = compute_key (ecdh , PeerPublic , MyPrivate , Curve ),
2323+ K_cl_secret_mpint = <<? Empint (K_cl_secret )>>,
2324+ K_cl_secret_mpint_trim =
2325+ binary :part (K_cl_secret_mpint , byte_size (K_cl_secret_mpint ), - 32 ),
2326+ crypto :hash (sha (Curve ), <<K_pq_secret /binary , K_cl_secret_mpint_trim /binary >>).
22222327
22232328dh_bits (# alg {encrypt = Encrypt ,
22242329 send_mac = SendMac }) ->
@@ -2233,8 +2338,9 @@ ecdh_curve('ecdh-sha2-nistp256') -> secp256r1;
22332338ecdh_curve ('ecdh-sha2-nistp384' ) -> secp384r1 ;
22342339ecdh_curve ('ecdh-sha2-nistp521' ) -> secp521r1 ;
22352340ecdh_curve ('curve448-sha512' ) -> x448 ;
2236- ecdh_curve ('curve25519-sha256' ) -> x25519 ;
2237- ecdh_curve (
'[email protected] ' )
-> x25519 .
2341+ ecdh_curve ('curve25519-sha256' ) -> x25519 ;
2342+ ecdh_curve ('mlkem768x25519-sha256' ) -> x25519 ;
2343+ ecdh_curve (
'[email protected] ' )
-> x25519 .
22382344
22392345% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
22402346% %
0 commit comments