1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-12-03 13:31:11 +03:00

kex: Implement mlkem768x25519-sha256

The implementation largely follows that of sntrup761x25519-sha512.

Most of the work was done by Sahana with the help of Claude,
Pavol provided fixes to match specs and did a final clean up.

Co-Authored-By: Sahana Prasad <sahana@redhat.com>
Co-Authored-By: Pavol Žáčik <pzacik@redhat.com>
Co-Authored-By: Claude <noreply@anthropic.com>

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
Sahana Prasad
2025-09-11 13:22:09 +02:00
committed by Jakub Jelen
parent d307bfa239
commit a9c8f942a5
17 changed files with 907 additions and 3 deletions

View File

@@ -104,6 +104,11 @@ if (OPENSSL_FOUND)
check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES)
check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20)
# Check for ML-KEM768 availability (OpenSSL 3.5+)
if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.5.0")
set(HAVE_MLKEM 1)
endif ()
unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES) unset(CMAKE_REQUIRED_LIBRARIES)
endif() endif()

View File

@@ -191,6 +191,9 @@
/* Define to 1 if we have support for blowfish */ /* Define to 1 if we have support for blowfish */
#cmakedefine HAVE_BLOWFISH 1 #cmakedefine HAVE_BLOWFISH 1
/* Define to 1 if we have support for ML-KEM */
#cmakedefine HAVE_MLKEM 1
/*************************** LIBRARIES ***************************/ /*************************** LIBRARIES ***************************/
/* Define to 1 if you have the `crypto' library (-lcrypto). */ /* Define to 1 if you have the `crypto' library (-lcrypto). */

View File

@@ -19,7 +19,7 @@ the interesting functions as you go.
The libssh library provides: The libssh library provides:
- <strong>Key Exchange Methods</strong>: <i>sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521</i>, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 - <strong>Key Exchange Methods</strong>: <i>sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, mlkem768x25519-sha256, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521</i>, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
- <strong>Public Key Algorithms</strong>: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256 - <strong>Public Key Algorithms</strong>: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256
- <strong>Ciphers</strong>: <i>aes256-ctr, aes192-ctr, aes128-ctr</i>, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc - <strong>Ciphers</strong>: <i>aes256-ctr, aes192-ctr, aes128-ctr</i>, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc
- <strong>Compression Schemes</strong>: zlib, <i>zlib@openssh.com</i>, none - <strong>Compression Schemes</strong>: zlib, <i>zlib@openssh.com</i>, none

View File

@@ -50,6 +50,9 @@
#include "libssh/ecdh.h" #include "libssh/ecdh.h"
#include "libssh/kex.h" #include "libssh/kex.h"
#include "libssh/sntrup761.h" #include "libssh/sntrup761.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
#define DIGEST_MAX_LEN 64 #define DIGEST_MAX_LEN 64
@@ -87,6 +90,10 @@ enum ssh_key_exchange_e {
SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM, SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM,
/* sntrup761x25519-sha512 */ /* sntrup761x25519-sha512 */
SSH_KEX_SNTRUP761X25519_SHA512, SSH_KEX_SNTRUP761X25519_SHA512,
#ifdef HAVE_MLKEM
/* mlkem768x25519-sha256 */
SSH_KEX_MLKEM768X25519_SHA256,
#endif /* HAVE_MLKEM */
}; };
enum ssh_cipher_e { enum ssh_cipher_e {
@@ -140,6 +147,11 @@ struct ssh_crypto_struct {
ssh_curve25519_pubkey curve25519_client_pubkey; ssh_curve25519_pubkey curve25519_client_pubkey;
ssh_curve25519_pubkey curve25519_server_pubkey; ssh_curve25519_pubkey curve25519_server_pubkey;
#endif #endif
#ifdef HAVE_MLKEM
ssh_mlkem768_privkey mlkem768_client_privkey;
ssh_mlkem768_pubkey mlkem768_client_pubkey;
ssh_mlkem768_ciphertext mlkem768_ciphertext;
#endif
#ifdef HAVE_SNTRUP761 #ifdef HAVE_SNTRUP761
ssh_sntrup761_privkey sntrup761_privkey; ssh_sntrup761_privkey sntrup761_privkey;
ssh_sntrup761_pubkey sntrup761_client_pubkey; ssh_sntrup761_pubkey sntrup761_client_pubkey;

63
include/libssh/mlkem768.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Sahana Prasad <sahana@redhat.com>
* Author: Claude (Anthropic)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MLKEM768_H_
#define MLKEM768_H_
#include "config.h"
/* ML-KEM768 key and ciphertext sizes as defined in FIPS 203 */
#define MLKEM768_PUBLICKEY_SIZE 1184
#define MLKEM768_SECRETKEY_SIZE 2400
#define MLKEM768_CIPHERTEXT_SIZE 1088
#define MLKEM768_SHARED_SECRET_SIZE 32
/* Hybrid ML-KEM768x25519 combined sizes */
#define MLKEM768X25519_CLIENT_PUBKEY_SIZE \
(MLKEM768_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)
#define MLKEM768X25519_SERVER_RESPONSE_SIZE \
(MLKEM768_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)
#define MLKEM768X25519_SHARED_SECRET_SIZE \
(MLKEM768_SHARED_SECRET_SIZE + CURVE25519_PUBKEY_SIZE)
typedef unsigned char ssh_mlkem768_pubkey[MLKEM768_PUBLICKEY_SIZE];
typedef unsigned char ssh_mlkem768_privkey[MLKEM768_SECRETKEY_SIZE];
typedef unsigned char ssh_mlkem768_ciphertext[MLKEM768_CIPHERTEXT_SIZE];
#ifdef __cplusplus
extern "C" {
#endif
/* ML-KEM768x25519 key exchange functions */
int ssh_client_mlkem768x25519_init(ssh_session session);
void ssh_client_mlkem768x25519_remove_callbacks(ssh_session session);
#ifdef WITH_SERVER
void ssh_server_mlkem768x25519_init(ssh_session session);
#endif /* WITH_SERVER */
#ifdef __cplusplus
}
#endif
#endif /* MLKEM768_H_ */

View File

@@ -18,6 +18,8 @@
#define SSH2_MSG_KEX_ECDH_REPLY 31 #define SSH2_MSG_KEX_ECDH_REPLY 31
#define SSH2_MSG_ECMQV_INIT 30 #define SSH2_MSG_ECMQV_INIT 30
#define SSH2_MSG_ECMQV_REPLY 31 #define SSH2_MSG_ECMQV_REPLY 31
#define SSH2_MSG_KEX_HYBRID_INIT 30
#define SSH2_MSG_KEX_HYBRID_REPLY 31
#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 #define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30
#define SSH2_MSG_KEX_DH_GEX_GROUP 31 #define SSH2_MSG_KEX_DH_GEX_GROUP 31

View File

@@ -286,6 +286,13 @@ if (NOT WITH_NACL)
endif() endif()
endif (NOT WITH_NACL) endif (NOT WITH_NACL)
if (HAVE_MLKEM)
set(libssh_SRCS
${libssh_SRCS}
mlkem768.c
)
endif (HAVE_MLKEM)
# Set the path to the default map file # Set the path to the default map file
set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map") set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map")

View File

@@ -46,6 +46,9 @@
#include "libssh/misc.h" #include "libssh/misc.h"
#include "libssh/pki.h" #include "libssh/pki.h"
#include "libssh/kex.h" #include "libssh/kex.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
#ifndef _WIN32 #ifndef _WIN32
#ifdef HAVE_PTHREAD #ifdef HAVE_PTHREAD
@@ -295,6 +298,11 @@ int dh_handshake(ssh_session session)
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
rc = ssh_client_sntrup761x25519_init(session); rc = ssh_client_sntrup761x25519_init(session);
break; break;
#endif
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_client_mlkem768x25519_init(session);
break;
#endif #endif
default: default:
rc = SSH_ERROR; rc = SSH_ERROR;

View File

@@ -41,6 +41,9 @@
#include "libssh/string.h" #include "libssh/string.h"
#include "libssh/curve25519.h" #include "libssh/curve25519.h"
#include "libssh/sntrup761.h" #include "libssh/sntrup761.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
#include "libssh/knownhosts.h" #include "libssh/knownhosts.h"
#include "libssh/misc.h" #include "libssh/misc.h"
#include "libssh/pki.h" #include "libssh/pki.h"
@@ -102,6 +105,12 @@
#define SNTRUP761X25519 "" #define SNTRUP761X25519 ""
#endif /* HAVE_SNTRUP761 */ #endif /* HAVE_SNTRUP761 */
#ifdef HAVE_MLKEM
#define MLKEM768X25519 "mlkem768x25519-sha256,"
#else
#define MLKEM768X25519 ""
#endif /* HAVE_MLKEM */
#ifdef HAVE_ECC #ifdef HAVE_ECC
#define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," #define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,"
#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \ #define EC_HOSTKEYS "ecdsa-sha2-nistp521," \
@@ -167,6 +176,7 @@
#define DEFAULT_KEY_EXCHANGE \ #define DEFAULT_KEY_EXCHANGE \
CURVE25519 \ CURVE25519 \
SNTRUP761X25519 \ SNTRUP761X25519 \
MLKEM768X25519 \
ECDH \ ECDH \
"diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \
GEX_SHA256 \ GEX_SHA256 \
@@ -926,6 +936,10 @@ kex_select_kex_type(const char *kex)
return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM; return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM;
} else if (strcmp(kex, "sntrup761x25519-sha512") == 0) { } else if (strcmp(kex, "sntrup761x25519-sha512") == 0) {
return SSH_KEX_SNTRUP761X25519_SHA512; return SSH_KEX_SNTRUP761X25519_SHA512;
#ifdef HAVE_MLKEM
} else if (strcmp(kex, "mlkem768x25519-sha256") == 0) {
return SSH_KEX_MLKEM768X25519_SHA256;
#endif
} }
/* should not happen. We should be getting only valid names at this stage */ /* should not happen. We should be getting only valid names at this stage */
return 0; return 0;
@@ -971,6 +985,11 @@ static void revert_kex_callbacks(ssh_session session)
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
ssh_client_sntrup761x25519_remove_callbacks(session); ssh_client_sntrup761x25519_remove_callbacks(session);
break; break;
#endif
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
ssh_client_mlkem768x25519_remove_callbacks(session);
break;
#endif #endif
} }
} }
@@ -1555,6 +1574,33 @@ int ssh_make_sessionid(ssh_session session)
} }
break; break;
#endif /* HAVE_SNTRUP761 */ #endif /* HAVE_SNTRUP761 */
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_buffer_pack(buf,
"dPPdPP",
MLKEM768X25519_CLIENT_PUBKEY_SIZE,
(size_t)MLKEM768_PUBLICKEY_SIZE,
session->next_crypto->mlkem768_client_pubkey,
(size_t)CURVE25519_PUBKEY_SIZE,
session->next_crypto->curve25519_client_pubkey,
MLKEM768X25519_SERVER_RESPONSE_SIZE,
(size_t)MLKEM768_CIPHERTEXT_SIZE,
session->next_crypto->mlkem768_ciphertext,
(size_t)CURVE25519_PUBKEY_SIZE,
session->next_crypto->curve25519_server_pubkey);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Failed to pack ML-KEM768 individual components");
goto error;
}
break;
#endif /* HAVE_MLKEM */
default:
/* Handle unsupported kex types - this should not happen in normal operation */
rc = SSH_ERROR;
ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm");
goto error;
} }
switch (session->next_crypto->kex_type) { switch (session->next_crypto->kex_type) {
case SSH_KEX_SNTRUP761X25519_SHA512: case SSH_KEX_SNTRUP761X25519_SHA512:
@@ -1564,6 +1610,14 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->shared_secret, session->next_crypto->shared_secret,
SHA512_DIGEST_LEN); SHA512_DIGEST_LEN);
break; break;
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_buffer_pack(buf,
"F",
session->next_crypto->shared_secret,
SHA256_DIGEST_LEN);
break;
#endif /* HAVE_MLKEM */
default: default:
rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret);
break; break;
@@ -1599,6 +1653,9 @@ int ssh_make_sessionid(ssh_session session)
case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256:
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
#endif
#ifdef WITH_GEX #ifdef WITH_GEX
case SSH_KEX_DH_GEX_SHA256: case SSH_KEX_DH_GEX_SHA256:
#endif /* WITH_GEX */ #endif /* WITH_GEX */
@@ -1639,6 +1696,11 @@ int ssh_make_sessionid(ssh_session session)
ssh_buffer_get_len(buf), ssh_buffer_get_len(buf),
session->next_crypto->secret_hash); session->next_crypto->secret_hash);
break; break;
default:
/* Handle unsupported kex types - this should not happen in normal operation */
ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm for hash computation");
rc = SSH_ERROR;
goto error;
} }
/* During the first kex, secret hash and session ID are equal. However, after /* During the first kex, secret hash and session ID are equal. However, after
@@ -1769,8 +1831,11 @@ int ssh_generate_session_keys(ssh_session session)
switch (session->next_crypto->kex_type) { switch (session->next_crypto->kex_type) {
case SSH_KEX_SNTRUP761X25519_SHA512: case SSH_KEX_SNTRUP761X25519_SHA512:
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
#endif /* HAVE_MLKEM */
k_string = ssh_make_padded_bignum_string(crypto->shared_secret, k_string = ssh_make_padded_bignum_string(crypto->shared_secret,
SHA512_DIGEST_LEN); crypto->digest_len);
break; break;
default: default:
k_string = ssh_make_bignum_string(crypto->shared_secret); k_string = ssh_make_bignum_string(crypto->shared_secret);

674
src/mlkem768.c Normal file
View File

@@ -0,0 +1,674 @@
/*
* mlkem768x25519.c - ML-KEM768x25519 hybrid key exchange
* mlkem768x25519-sha256
*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Sahana Prasad <sahana@redhat.com>
* Author: Claude (Anthropic)
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/bignum.h"
#include "libssh/buffer.h"
#include "libssh/crypto.h"
#include "libssh/curve25519.h"
#include "libssh/dh.h"
#include "libssh/mlkem768.h"
#include "libssh/pki.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/ssh2.h"
#include <string.h>
#include <openssl/evp.h>
#include <openssl/err.h>
static SSH_PACKET_CALLBACK(ssh_packet_client_mlkem768x25519_reply);
static ssh_packet_callback dh_client_callbacks[] = {
ssh_packet_client_mlkem768x25519_reply,
};
static struct ssh_packet_callbacks_struct ssh_mlkem768x25519_client_callbacks =
{
.start = SSH2_MSG_KEX_HYBRID_REPLY,
.n_callbacks = 1,
.callbacks = dh_client_callbacks,
.user = NULL,
};
/* Generate ML-KEM768 keypair using OpenSSL */
static int mlkem768_keypair_gen(ssh_mlkem768_pubkey pubkey,
ssh_mlkem768_privkey privkey)
{
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *pkey = NULL;
int rc, ret = SSH_ERROR;
size_t pubkey_len = MLKEM768_PUBLICKEY_SIZE;
size_t privkey_len = MLKEM768_SECRETKEY_SIZE;
ctx = EVP_PKEY_CTX_new_from_name(NULL, "ML-KEM-768", NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_keygen_init(ctx);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM-768 keygen: %s",
ERR_error_string(ERR_get_error(), NULL));
}
rc = EVP_PKEY_keygen(ctx, &pkey);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM-768 keygen: %s",
ERR_error_string(ERR_get_error(), NULL));
}
rc = EVP_PKEY_get_raw_public_key(pkey, pubkey, &pubkey_len);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to extract ML-KEM-768 public key: %s",
ERR_error_string(ERR_get_error(), NULL));
}
rc = EVP_PKEY_get_raw_private_key(pkey, privkey, &privkey_len);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to extract ML-KEM-768 private key: %s",
ERR_error_string(ERR_get_error(), NULL));
}
ret = SSH_OK;
cleanup:
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
/* Encapsulate shared secret using ML-KEM768 - used by server side */
static int mlkem768_encapsulate(const ssh_mlkem768_pubkey pubkey,
ssh_mlkem768_ciphertext ciphertext,
unsigned char *shared_secret)
{
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL;
int rc, ret = SSH_ERROR;
size_t ct_len = MLKEM768_CIPHERTEXT_SIZE;
size_t ss_len = MLKEM768_SHARED_SECRET_SIZE;
pkey = EVP_PKEY_new_raw_public_key_ex(NULL,
"ML-KEM-768",
NULL,
pubkey,
MLKEM768_PUBLICKEY_SIZE);
if (pkey == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 public key from raw data: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_encapsulate_init(ctx, NULL);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM-768 encapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_encapsulate(ctx, ciphertext, &ct_len, shared_secret, &ss_len);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM-768 encapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ret = SSH_OK;
cleanup:
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
/* Decapsulate shared secret using ML-KEM768 - used by client side */
static int mlkem768_decapsulate(const ssh_mlkem768_privkey privkey,
const ssh_mlkem768_ciphertext ciphertext,
unsigned char *shared_secret)
{
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL;
int rc, ret = SSH_ERROR;
size_t ss_len = MLKEM768_SHARED_SECRET_SIZE;
pkey = EVP_PKEY_new_raw_private_key_ex(NULL,
"ML-KEM-768",
NULL,
privkey,
MLKEM768_SECRETKEY_SIZE);
if (pkey == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
}
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_decapsulate_init(ctx, NULL);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM-768 decapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_decapsulate(ctx,
shared_secret,
&ss_len,
ciphertext,
MLKEM768_CIPHERTEXT_SIZE);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM-768 decapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ret = SSH_OK;
cleanup:
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
int ssh_client_mlkem768x25519_init(ssh_session session)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_buffer client_pubkey = NULL;
ssh_string pubkey_blob = NULL;
int rc;
SSH_LOG(SSH_LOG_TRACE, "Initializing ML-KEM768x25519 key exchange");
/* Initialize Curve25519 component first */
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
return rc;
}
/* Generate ML-KEM768 keypair */
rc = mlkem768_keypair_gen(crypto->mlkem768_client_pubkey,
crypto->mlkem768_client_privkey);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate ML-KEM768 keypair");
return SSH_ERROR;
}
/* Create hybrid client public key: ML-KEM768 + Curve25519 */
client_pubkey = ssh_buffer_new();
if (client_pubkey == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
rc = ssh_buffer_pack(client_pubkey,
"PP",
MLKEM768_PUBLICKEY_SIZE,
crypto->mlkem768_client_pubkey,
CURVE25519_PUBKEY_SIZE,
crypto->curve25519_client_pubkey);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
/* Convert to string for sending */
pubkey_blob = ssh_string_new(ssh_buffer_get_len(client_pubkey));
if (pubkey_blob == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
ssh_string_fill(pubkey_blob,
ssh_buffer_get(client_pubkey),
ssh_buffer_get_len(client_pubkey));
/* Send the hybrid public key to server */
rc = ssh_buffer_pack(session->out_buffer,
"bS",
SSH2_MSG_KEX_HYBRID_INIT,
pubkey_blob);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_INIT_SENT;
ssh_packet_set_callbacks(session, &ssh_mlkem768x25519_client_callbacks);
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_INIT");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
cleanup:
ssh_buffer_free(client_pubkey);
ssh_string_free(pubkey_blob);
return rc;
}
static SSH_PACKET_CALLBACK(ssh_packet_client_mlkem768x25519_reply)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string s_server_blob = NULL;
ssh_string s_pubkey_blob = NULL;
ssh_string s_signature = NULL;
const unsigned char *server_data = NULL;
unsigned char mlkem_shared_secret[MLKEM768_SHARED_SECRET_SIZE];
unsigned char curve25519_shared_secret[CURVE25519_PUBKEY_SIZE];
unsigned char combined_secret[MLKEM768X25519_SHARED_SECRET_SIZE];
unsigned char hashed_secret[SHA256_DIGEST_LEN];
size_t server_blob_len;
int rc;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM768x25519 server reply");
ssh_client_mlkem768x25519_remove_callbacks(session);
s_pubkey_blob = ssh_buffer_get_ssh_string(packet);
if (s_pubkey_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No public key in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_dh_import_next_pubkey_blob(session, s_pubkey_blob);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL, "Failed to import next public key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get server blob containing ML-KEM768 ciphertext + Curve25519 pubkey */
s_server_blob = ssh_buffer_get_ssh_string(packet);
if (s_server_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No server blob in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
server_data = ssh_string_data(s_server_blob);
server_blob_len = ssh_string_len(s_server_blob);
/* Expect ML-KEM768 ciphertext + Curve25519 pubkey */
if (server_blob_len != MLKEM768X25519_SERVER_RESPONSE_SIZE) {
ssh_set_error(session, SSH_FATAL, "Invalid server blob size");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store ML-KEM768 ciphertext for sessionid calculation */
memcpy(crypto->mlkem768_ciphertext, server_data, MLKEM768_CIPHERTEXT_SIZE);
/* Decapsulate ML-KEM768 shared secret */
rc = mlkem768_decapsulate(crypto->mlkem768_client_privkey,
crypto->mlkem768_ciphertext,
mlkem_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ML-KEM768 decapsulation failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store server Curve25519 public key for shared secret computation */
memcpy(crypto->curve25519_server_pubkey,
server_data + MLKEM768_CIPHERTEXT_SIZE,
CURVE25519_PUBKEY_SIZE);
/* Derive Curve25519 shared secret using existing libssh function */
rc = ssh_curve25519_create_k(session, curve25519_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Curve25519 ECDH failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Combine secrets: ML-KEM768 + Curve25519 for hybrid approach */
memcpy(combined_secret, mlkem_shared_secret, MLKEM768_SHARED_SECRET_SIZE);
memcpy(combined_secret + MLKEM768_SHARED_SECRET_SIZE,
curve25519_shared_secret,
CURVE25519_PUBKEY_SIZE);
sha256(combined_secret, MLKEM768X25519_SHARED_SECRET_SIZE, hashed_secret);
bignum_bin2bn(hashed_secret, SHA256_DIGEST_LEN, &crypto->shared_secret);
if (crypto->shared_secret == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to create shared secret bignum");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get signature for verification */
s_signature = ssh_buffer_get_ssh_string(packet);
if (s_signature == NULL) {
ssh_set_error(session, SSH_FATAL, "No signature in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
crypto->dh_server_signature = s_signature;
s_signature = NULL;
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
cleanup:
/* Clear sensitive data */
explicit_bzero(mlkem_shared_secret, sizeof(mlkem_shared_secret));
explicit_bzero(curve25519_shared_secret, sizeof(curve25519_shared_secret));
explicit_bzero(combined_secret, sizeof(combined_secret));
explicit_bzero(hashed_secret, sizeof(hashed_secret));
ssh_string_free(s_pubkey_blob);
ssh_string_free(s_server_blob);
ssh_string_free(s_signature);
return SSH_PACKET_USED;
}
void ssh_client_mlkem768x25519_remove_callbacks(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_mlkem768x25519_client_callbacks);
}
#ifdef WITH_SERVER
static SSH_PACKET_CALLBACK(ssh_packet_server_mlkem768x25519_init);
static ssh_packet_callback dh_server_callbacks[] = {
ssh_packet_server_mlkem768x25519_init,
};
static struct ssh_packet_callbacks_struct ssh_mlkem768x25519_server_callbacks =
{
.start = SSH2_MSG_KEX_HYBRID_INIT,
.n_callbacks = 1,
.callbacks = dh_server_callbacks,
.user = NULL,
};
static SSH_PACKET_CALLBACK(ssh_packet_server_mlkem768x25519_init)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string client_pubkey_blob = NULL;
ssh_string server_pubkey_blob = NULL;
ssh_buffer server_response = NULL;
const unsigned char *client_data = NULL;
unsigned char mlkem_shared_secret[MLKEM768_SHARED_SECRET_SIZE];
unsigned char curve25519_shared_secret[CURVE25519_PUBKEY_SIZE];
unsigned char combined_secret[MLKEM768X25519_SHARED_SECRET_SIZE];
unsigned char mlkem_ciphertext[MLKEM768_CIPHERTEXT_SIZE];
unsigned char hashed_secret[SHA256_DIGEST_LEN];
size_t client_blob_len;
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
ssh_string sig_blob = NULL;
ssh_string server_hostkey_blob = NULL;
int rc = SSH_ERROR;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM768x25519 client init");
ssh_packet_remove_callbacks(session, &ssh_mlkem768x25519_server_callbacks);
/* Get client hybrid public key: ML-KEM768 + Curve25519 */
client_pubkey_blob = ssh_buffer_get_ssh_string(packet);
if (client_pubkey_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No client public key in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
client_data = ssh_string_data(client_pubkey_blob);
client_blob_len = ssh_string_len(client_pubkey_blob);
/* Expect ML-KEM768 pubkey + Curve25519 pubkey */
if (client_blob_len != MLKEM768X25519_CLIENT_PUBKEY_SIZE) {
ssh_set_error(session, SSH_FATAL, "Invalid client public key size");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Extract client ML-KEM768 public key */
memcpy(crypto->mlkem768_client_pubkey,
client_data,
MLKEM768_PUBLICKEY_SIZE);
/* Extract client Curve25519 public key */
memcpy(crypto->curve25519_client_pubkey,
client_data + MLKEM768_PUBLICKEY_SIZE,
CURVE25519_PUBKEY_SIZE);
/* Generate server Curve25519 keypair */
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate server Curve25519 key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Derive Curve25519 shared secret */
rc = ssh_curve25519_create_k(session, curve25519_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Curve25519 ECDH failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Encapsulate ML-KEM768 shared secret using client's public key */
rc = mlkem768_encapsulate(client_data, mlkem_ciphertext, mlkem_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ML-KEM768 encapsulation failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store ML-KEM768 ciphertext for sessionid calculation */
memcpy(crypto->mlkem768_ciphertext, mlkem_ciphertext, MLKEM768_CIPHERTEXT_SIZE);
/* Combine secrets: ML-KEM768 + Curve25519 for hybrid approach */
memcpy(combined_secret, mlkem_shared_secret, MLKEM768_SHARED_SECRET_SIZE);
memcpy(combined_secret + MLKEM768_SHARED_SECRET_SIZE,
curve25519_shared_secret,
CURVE25519_PUBKEY_SIZE);
sha256(combined_secret, MLKEM768X25519_SHARED_SECRET_SIZE, hashed_secret);
/* Store the combined secret */
bignum_bin2bn(hashed_secret, SHA256_DIGEST_LEN, &crypto->shared_secret);
if (crypto->shared_secret == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to create shared secret bignum");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Create server response: ML-KEM768 ciphertext + Curve25519 pubkey */
server_response = ssh_buffer_new();
if (server_response == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_buffer_pack(server_response,
"PP",
MLKEM768_CIPHERTEXT_SIZE,
mlkem_ciphertext,
CURVE25519_PUBKEY_SIZE,
crypto->curve25519_server_pubkey);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Convert to string for sending */
server_pubkey_blob = ssh_string_new(ssh_buffer_get_len(server_response));
if (server_pubkey_blob == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
ssh_string_fill(server_pubkey_blob,
ssh_buffer_get(server_response),
ssh_buffer_get_len(server_response));
/* Add MSG_KEX_ECDH_REPLY header */
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_HYBRID_REPLY);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get server host key */
rc = ssh_get_key_params(session, &privkey, &digest);
if (rc == SSH_ERROR) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Build session ID */
rc = ssh_make_sessionid(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not create a session id");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_dh_get_next_server_publickey_blob(session, &server_hostkey_blob);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL, "Could not export server public key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add server host key to output */
rc = ssh_buffer_add_ssh_string(session->out_buffer, server_hostkey_blob);
SSH_STRING_FREE(server_hostkey_blob);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add server response (ciphertext + pubkey) */
rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Sign the exchange hash */
sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest);
if (sig_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "Could not sign the session id");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add signature */
rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob);
SSH_STRING_FREE(sig_blob);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_REPLY");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
cleanup:
/* Clear sensitive data */
explicit_bzero(mlkem_shared_secret, sizeof(mlkem_shared_secret));
explicit_bzero(curve25519_shared_secret, sizeof(curve25519_shared_secret));
explicit_bzero(combined_secret, sizeof(combined_secret));
explicit_bzero(hashed_secret, sizeof(hashed_secret));
ssh_string_free(client_pubkey_blob);
ssh_string_free(server_pubkey_blob);
ssh_buffer_free(server_response);
return SSH_PACKET_USED;
}
void ssh_server_mlkem768x25519_init(ssh_session session)
{
SSH_LOG(SSH_LOG_TRACE, "Setting up ML-KEM768x25519 server callbacks");
ssh_packet_set_callbacks(session, &ssh_mlkem768x25519_server_callbacks);
}
#endif /* WITH_SERVER */

View File

@@ -457,6 +457,10 @@ const char* ssh_get_kex_algo(ssh_session session) {
return "sntrup761x25519-sha512@openssh.com"; return "sntrup761x25519-sha512@openssh.com";
case SSH_KEX_SNTRUP761X25519_SHA512: case SSH_KEX_SNTRUP761X25519_SHA512:
return "sntrup761x25519-sha512"; return "sntrup761x25519-sha512";
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
return "mlkem768x25519-sha256";
#endif /* HAVE_MLKEM */
#ifdef WITH_GEX #ifdef WITH_GEX
case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA1:
return "diffie-hellman-group-exchange-sha1"; return "diffie-hellman-group-exchange-sha1";

View File

@@ -51,6 +51,9 @@
#include "libssh/curve25519.h" #include "libssh/curve25519.h"
#include "libssh/ecdh.h" #include "libssh/ecdh.h"
#include "libssh/sntrup761.h" #include "libssh/sntrup761.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
static struct ssh_hmac_struct ssh_hmac_tab[] = { static struct ssh_hmac_struct ssh_hmac_tab[] = {
{ "hmac-sha1", SSH_HMAC_SHA1, false }, { "hmac-sha1", SSH_HMAC_SHA1, false },
@@ -598,6 +601,11 @@ int crypt_set_algorithms_server(ssh_session session){
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
ssh_server_sntrup761x25519_init(session); ssh_server_sntrup761x25519_init(session);
break; break;
#endif
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
ssh_server_mlkem768x25519_init(session);
break;
#endif #endif
default: default:
ssh_set_error(session, ssh_set_error(session,

View File

@@ -157,7 +157,7 @@ if (SSH_EXECUTABLE)
diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256
diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521
sntrup761x25519-sha512@openssh.com sntrup761x25519-sha512 sntrup761x25519-sha512@openssh.com sntrup761x25519-sha512 mlkem768x25519-sha256
curve25519-sha256 curve25519-sha256@libssh.org curve25519-sha256 curve25519-sha256@libssh.org
ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa
ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521

View File

@@ -752,6 +752,22 @@ torture_algorithms_ecdh_sntrup761x25519_sha512(void **state)
} }
#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ #endif /* OPENSSH_SNTRUP761X25519_SHA512 */
#if defined(HAVE_MLKEM) && defined(OPENSSH_MLKEM768X25519_SHA256)
static void torture_algorithms_ecdh_mlkem768x25519_sha256(void **state)
{
struct torture_state *s = *state;
if (ssh_fips_mode()) {
skip();
}
test_algorithm(s->ssh.session,
"mlkem768x25519-sha256",
NULL /*cipher*/,
NULL /*hmac*/);
}
#endif /* HAVE_MLKEM && defined(OPENSSH_MLKEM768X25519_SHA256) */
static void torture_algorithms_dh_group1(void **state) { static void torture_algorithms_dh_group1(void **state) {
struct torture_state *s = *state; struct torture_state *s = *state;
@@ -1029,6 +1045,11 @@ int torture_run_tests(void) {
session_setup, session_setup,
session_teardown), session_teardown),
#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ #endif /* OPENSSH_SNTRUP761X25519_SHA512 */
#if defined(HAVE_MLKEM) && defined(OPENSSH_MLKEM768X25519_SHA256)
cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768x25519_sha256,
session_setup,
session_teardown),
#endif /* HAVE_MLKEM && defined(OPENSSH_MLKEM768X25519_SHA256) */
#if defined(HAVE_ECC) #if defined(HAVE_ECC)
cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256, cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256,
session_setup, session_setup,

View File

@@ -307,10 +307,21 @@ static int torture_pkd_setup_ecdsa_521(void **state) {
#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) #define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd)
#endif #endif
#if defined(HAVE_MLKEM) && defined(OPENSSH_MLKEM768X25519_SHA256)
#define PKDTESTS_KEX_MLKEM768(f, client, kexcmd) \
f(client, rsa_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_rsa, teardown) \
f(client, ecdsa_256_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_256, teardown) \
f(client, ecdsa_384_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_384, teardown) \
f(client, ecdsa_521_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_521, teardown)
#else
#define PKDTESTS_KEX_MLKEM768(f, client, kexcmd)
#endif
#define PKDTESTS_KEX_COMMON(f, client, kexcmd) \ #define PKDTESTS_KEX_COMMON(f, client, kexcmd) \
PKDTESTS_KEX_FIPS(f, client, kexcmd) \ PKDTESTS_KEX_FIPS(f, client, kexcmd) \
PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \
PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \
PKDTESTS_KEX_MLKEM768(f, client, kexcmd) \
f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown) \ f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown) \
f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown) \ f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown) \
f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown) \ f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown) \
@@ -357,10 +368,18 @@ static int torture_pkd_setup_ecdsa_521(void **state) {
#define PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd) #define PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd)
#endif #endif
#if defined(HAVE_MLKEM) && defined(OPENSSH_MLKEM768X25519_SHA256)
#define PKDTESTS_KEX_OPENSSHONLY_MLKEM768(f, client, kexcmd) \
f(client, ed25519_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ed25519, teardown)
#else
#define PKDTESTS_KEX_OPENSSHONLY_MLKEM768(f, client, kexcmd)
#endif
#define PKDTESTS_KEX_OPENSSHONLY(f, client, kexcmd) \ #define PKDTESTS_KEX_OPENSSHONLY(f, client, kexcmd) \
/* Kex algorithms. */ \ /* Kex algorithms. */ \
PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd) \ PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd) \
PKDTESTS_KEX_OPENSSHONLY_SNTRUP761_OPENSSH(f, client, kexcmd) \ PKDTESTS_KEX_OPENSSHONLY_SNTRUP761_OPENSSH(f, client, kexcmd) \
PKDTESTS_KEX_OPENSSHONLY_MLKEM768(f, client, kexcmd) \
f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown) \ f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown) \
f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \ f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \
f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \ f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \

View File

@@ -52,6 +52,7 @@
#cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1 #cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1
#cmakedefine OPENSSH_SNTRUP761X25519_SHA512 1 #cmakedefine OPENSSH_SNTRUP761X25519_SHA512 1
#cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1 #cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1
#cmakedefine OPENSSH_MLKEM768X25519_SHA256 1
#cmakedefine OPENSSH_SSH_ED25519 1 #cmakedefine OPENSSH_SSH_ED25519 1
#cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1 #cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1
#cmakedefine OPENSSH_SSH_RSA 1 #cmakedefine OPENSSH_SSH_RSA 1

View File

@@ -281,6 +281,17 @@ static void torture_options_get_key_exchange(void **state)
"diffie-hellman-group16-sha512," "diffie-hellman-group16-sha512,"
"diffie-hellman-group18-sha512"); "diffie-hellman-group18-sha512");
} else { } else {
#ifdef HAVE_MLKEM
assert_string_equal(value,
"curve25519-sha256,curve25519-sha256@libssh.org,"
"sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,"
"mlkem768x25519-sha256,"
"ecdh-sha2-nistp256,ecdh-sha2-nistp384,"
"ecdh-sha2-nistp521,diffie-hellman-group18-sha512,"
"diffie-hellman-group16-sha512,"
"diffie-hellman-group-exchange-sha256,"
"diffie-hellman-group14-sha256");
#else
assert_string_equal(value, assert_string_equal(value,
"curve25519-sha256,curve25519-sha256@libssh.org," "curve25519-sha256,curve25519-sha256@libssh.org,"
"sntrup761x25519-sha512," "sntrup761x25519-sha512,"
@@ -290,6 +301,7 @@ static void torture_options_get_key_exchange(void **state)
"diffie-hellman-group16-sha512," "diffie-hellman-group16-sha512,"
"diffie-hellman-group-exchange-sha256," "diffie-hellman-group-exchange-sha256,"
"diffie-hellman-group14-sha256"); "diffie-hellman-group14-sha256");
#endif
} }
ssh_string_free_char(value); ssh_string_free_char(value);