Skip to content

ssh: add opt-in pure ML-KEM key exchanges - #11750

Open
alexandrejbr wants to merge 2 commits into
erlang:masterfrom
alexandrejbr:alexandrejbr/add-ml-kem-kex-to-ssh
Open

alexandrejbr wants to merge 2 commits into
erlang:masterfrom
alexandrejbr:alexandrejbr/add-ml-kem-kex-to-ssh

Conversation

@alexandrejbr

Copy link
Copy Markdown
Contributor

Implement mlkem512-sha256, mlkem768-sha256, and mlkem1024-sha384 from draft-harrison-sshm-mlkem using crypto's existing ML-KEM APIs.

Add the KEX init and reply messages, exact size validation, exchange hash and key derivation, negotiation, and rekey support. Advertise only methods supported by crypto, and keep these draft methods out of the default KEX list.

Document configuration and add wire-format and connection/rekey tests.

This was tested with CycloneSSH and a https://github.com/open-quantum-safe/openssh/blob/OQS-v10

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

CT Test Results

    2 files     27 suites   11m 45s ⏱️
  531 tests   502 ✅ 28 💤 1 ❌
1 755 runs  1 710 ✅ 44 💤 1 ❌

For more details on these failures, see this check.

Results for commit 3df8a19.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

@u3s u3s added the team:PS Assigned to OTP team PS label Oct 2, 2026
Implement mlkem512-sha256, mlkem768-sha256, and mlkem1024-sha384
from draft-harrison-sshm-mlkem using crypto's existing ML-KEM APIs.

Add the KEX init and reply messages, exact size validation, exchange
hash and key derivation, negotiation, and rekey support. Advertise only
methods supported by crypto, and keep these draft methods out of the
default KEX list.

Document configuration and add wire-format and connection/rekey tests.
@alexandrejbr
alexandrejbr force-pushed the alexandrejbr/add-ml-kem-kex-to-ssh branch from 01ba6f4 to c69e46c Compare October 2, 2026 13:00
%% Handle reason returned from handle_packet_part, or {start_packet_discard, ...}
%% A wrong first_kex_packet_follows guess is discarded before decoding: its
%% format can differ from the negotiated key exchange method.
handle_packet_part_result({packet_decrypted, <<Op, _/binary>>, EncryptedDataRest, Ssh},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking into alternative implementations of this and worst case scenario cleaning up the dead code now in ssh_fsm_kexinit.erl as the clause to handle ignore_initial_kex_message will never be reached.

So expect some changes in this area. But I welcome feedback if you have.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ok to do it like this, but if you have other ideas please share them.

@Mikaka27 Mikaka27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the discussion about guess functionality looks ok to me.

Also this comment from another PR applies to this as well: #11752 (comment)

Mikaka27
Mikaka27 previously approved these changes Oct 13, 2026

@Mikaka27 Mikaka27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the discussion about guess functionality looks ok to me.

Also this comment from another PR applies to this as well: #11752 (comment)

Comment thread lib/ssh/src/ssh_transport.erl Outdated
opts = Opts}) ->
Kem = kem(Kex),
try
true = byte_size(C_init) =:= kem_public_key_size(Kem),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already checked in decode_kem_init, I'm not sure if it's better to leave the check here or there (or both are fine?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that most of the things that the RFC specifies can be seen here in this implementation, but redundant checks are not so good and failing fast is better so the check when decoding seems better.

I will remove from here but add a comment just to try to help a future reader that looks at the RFC and the code to check if all is good.

Comment thread lib/ssh/src/ssh_transport.erl Outdated
algorithms = #alg{kex = Kex}}) ->
try
true = Kem =:= kem(Kex),
true = byte_size(S_reply) =:= kem_ciphertext_size(Kem),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was checked in decode_kem_reply, same as comment above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same reply as above

@Mikaka27 Mikaka27 added testing currently being tested, tag is used by OTP internal CI and removed testing currently being tested, tag is used by OTP internal CI labels Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team:PS Assigned to OTP team PS testing currently being tested, tag is used by OTP internal CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants