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:
committed by
Jakub Jelen
parent
d307bfa239
commit
a9c8f942a5
@@ -104,6 +104,11 @@ if (OPENSSL_FOUND)
|
||||
check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES)
|
||||
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_LIBRARIES)
|
||||
endif()
|
||||
|
||||
@@ -191,6 +191,9 @@
|
||||
/* Define to 1 if we have support for blowfish */
|
||||
#cmakedefine HAVE_BLOWFISH 1
|
||||
|
||||
/* Define to 1 if we have support for ML-KEM */
|
||||
#cmakedefine HAVE_MLKEM 1
|
||||
|
||||
/*************************** LIBRARIES ***************************/
|
||||
|
||||
/* Define to 1 if you have the `crypto' library (-lcrypto). */
|
||||
|
||||
@@ -19,7 +19,7 @@ the interesting functions as you go.
|
||||
|
||||
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>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
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
#include "libssh/ecdh.h"
|
||||
#include "libssh/kex.h"
|
||||
#include "libssh/sntrup761.h"
|
||||
#ifdef HAVE_MLKEM
|
||||
#include "libssh/mlkem768.h"
|
||||
#endif
|
||||
|
||||
#define DIGEST_MAX_LEN 64
|
||||
|
||||
@@ -87,6 +90,10 @@ enum ssh_key_exchange_e {
|
||||
SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM,
|
||||
/* sntrup761x25519-sha512 */
|
||||
SSH_KEX_SNTRUP761X25519_SHA512,
|
||||
#ifdef HAVE_MLKEM
|
||||
/* mlkem768x25519-sha256 */
|
||||
SSH_KEX_MLKEM768X25519_SHA256,
|
||||
#endif /* HAVE_MLKEM */
|
||||
};
|
||||
|
||||
enum ssh_cipher_e {
|
||||
@@ -140,6 +147,11 @@ struct ssh_crypto_struct {
|
||||
ssh_curve25519_pubkey curve25519_client_pubkey;
|
||||
ssh_curve25519_pubkey curve25519_server_pubkey;
|
||||
#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
|
||||
ssh_sntrup761_privkey sntrup761_privkey;
|
||||
ssh_sntrup761_pubkey sntrup761_client_pubkey;
|
||||
|
||||
63
include/libssh/mlkem768.h
Normal file
63
include/libssh/mlkem768.h
Normal 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_ */
|
||||
@@ -18,6 +18,8 @@
|
||||
#define SSH2_MSG_KEX_ECDH_REPLY 31
|
||||
#define SSH2_MSG_ECMQV_INIT 30
|
||||
#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_GROUP 31
|
||||
|
||||
@@ -286,6 +286,13 @@ if (NOT WITH_NACL)
|
||||
endif()
|
||||
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(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map")
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
#include "libssh/misc.h"
|
||||
#include "libssh/pki.h"
|
||||
#include "libssh/kex.h"
|
||||
#ifdef HAVE_MLKEM
|
||||
#include "libssh/mlkem768.h"
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef HAVE_PTHREAD
|
||||
@@ -295,6 +298,11 @@ int dh_handshake(ssh_session session)
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
|
||||
rc = ssh_client_sntrup761x25519_init(session);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_MLKEM
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
rc = ssh_client_mlkem768x25519_init(session);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
rc = SSH_ERROR;
|
||||
|
||||
67
src/kex.c
67
src/kex.c
@@ -41,6 +41,9 @@
|
||||
#include "libssh/string.h"
|
||||
#include "libssh/curve25519.h"
|
||||
#include "libssh/sntrup761.h"
|
||||
#ifdef HAVE_MLKEM
|
||||
#include "libssh/mlkem768.h"
|
||||
#endif
|
||||
#include "libssh/knownhosts.h"
|
||||
#include "libssh/misc.h"
|
||||
#include "libssh/pki.h"
|
||||
@@ -102,6 +105,12 @@
|
||||
#define SNTRUP761X25519 ""
|
||||
#endif /* HAVE_SNTRUP761 */
|
||||
|
||||
#ifdef HAVE_MLKEM
|
||||
#define MLKEM768X25519 "mlkem768x25519-sha256,"
|
||||
#else
|
||||
#define MLKEM768X25519 ""
|
||||
#endif /* HAVE_MLKEM */
|
||||
|
||||
#ifdef HAVE_ECC
|
||||
#define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,"
|
||||
#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \
|
||||
@@ -167,6 +176,7 @@
|
||||
#define DEFAULT_KEY_EXCHANGE \
|
||||
CURVE25519 \
|
||||
SNTRUP761X25519 \
|
||||
MLKEM768X25519 \
|
||||
ECDH \
|
||||
"diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \
|
||||
GEX_SHA256 \
|
||||
@@ -926,6 +936,10 @@ kex_select_kex_type(const char *kex)
|
||||
return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM;
|
||||
} else if (strcmp(kex, "sntrup761x25519-sha512") == 0) {
|
||||
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 */
|
||||
return 0;
|
||||
@@ -971,6 +985,11 @@ static void revert_kex_callbacks(ssh_session session)
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
|
||||
ssh_client_sntrup761x25519_remove_callbacks(session);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_MLKEM
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
ssh_client_mlkem768x25519_remove_callbacks(session);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1555,6 +1574,33 @@ int ssh_make_sessionid(ssh_session session)
|
||||
}
|
||||
break;
|
||||
#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) {
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
@@ -1564,6 +1610,14 @@ int ssh_make_sessionid(ssh_session session)
|
||||
session->next_crypto->shared_secret,
|
||||
SHA512_DIGEST_LEN);
|
||||
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:
|
||||
rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret);
|
||||
break;
|
||||
@@ -1599,6 +1653,9 @@ int ssh_make_sessionid(ssh_session session)
|
||||
case SSH_KEX_ECDH_SHA2_NISTP256:
|
||||
case SSH_KEX_CURVE25519_SHA256:
|
||||
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
|
||||
#ifdef HAVE_MLKEM
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
#endif
|
||||
#ifdef WITH_GEX
|
||||
case SSH_KEX_DH_GEX_SHA256:
|
||||
#endif /* WITH_GEX */
|
||||
@@ -1639,6 +1696,11 @@ int ssh_make_sessionid(ssh_session session)
|
||||
ssh_buffer_get_len(buf),
|
||||
session->next_crypto->secret_hash);
|
||||
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
|
||||
@@ -1769,8 +1831,11 @@ int ssh_generate_session_keys(ssh_session session)
|
||||
switch (session->next_crypto->kex_type) {
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
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,
|
||||
SHA512_DIGEST_LEN);
|
||||
crypto->digest_len);
|
||||
break;
|
||||
default:
|
||||
k_string = ssh_make_bignum_string(crypto->shared_secret);
|
||||
|
||||
674
src/mlkem768.c
Normal file
674
src/mlkem768.c
Normal 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 */
|
||||
@@ -457,6 +457,10 @@ const char* ssh_get_kex_algo(ssh_session session) {
|
||||
return "sntrup761x25519-sha512@openssh.com";
|
||||
case SSH_KEX_SNTRUP761X25519_SHA512:
|
||||
return "sntrup761x25519-sha512";
|
||||
#ifdef HAVE_MLKEM
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
return "mlkem768x25519-sha256";
|
||||
#endif /* HAVE_MLKEM */
|
||||
#ifdef WITH_GEX
|
||||
case SSH_KEX_DH_GEX_SHA1:
|
||||
return "diffie-hellman-group-exchange-sha1";
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
#include "libssh/curve25519.h"
|
||||
#include "libssh/ecdh.h"
|
||||
#include "libssh/sntrup761.h"
|
||||
#ifdef HAVE_MLKEM
|
||||
#include "libssh/mlkem768.h"
|
||||
#endif
|
||||
|
||||
static struct ssh_hmac_struct ssh_hmac_tab[] = {
|
||||
{ "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:
|
||||
ssh_server_sntrup761x25519_init(session);
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_MLKEM
|
||||
case SSH_KEX_MLKEM768X25519_SHA256:
|
||||
ssh_server_mlkem768x25519_init(session);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ssh_set_error(session,
|
||||
|
||||
@@ -157,7 +157,7 @@ if (SSH_EXECUTABLE)
|
||||
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-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
|
||||
ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa
|
||||
ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521
|
||||
|
||||
@@ -752,6 +752,22 @@ torture_algorithms_ecdh_sntrup761x25519_sha512(void **state)
|
||||
}
|
||||
#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) {
|
||||
struct torture_state *s = *state;
|
||||
|
||||
@@ -1029,6 +1045,11 @@ int torture_run_tests(void) {
|
||||
session_setup,
|
||||
session_teardown),
|
||||
#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)
|
||||
cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256,
|
||||
session_setup,
|
||||
|
||||
@@ -307,10 +307,21 @@ static int torture_pkd_setup_ecdsa_521(void **state) {
|
||||
#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd)
|
||||
#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) \
|
||||
PKDTESTS_KEX_FIPS(f, client, kexcmd) \
|
||||
PKDTESTS_KEX_SNTRUP761(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_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) \
|
||||
@@ -357,10 +368,18 @@ static int torture_pkd_setup_ecdsa_521(void **state) {
|
||||
#define PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(f, client, kexcmd)
|
||||
#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) \
|
||||
/* Kex algorithms. */ \
|
||||
PKDTESTS_KEX_OPENSSHONLY_SNTRUP761(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_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \
|
||||
f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1
|
||||
#cmakedefine OPENSSH_SNTRUP761X25519_SHA512 1
|
||||
#cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1
|
||||
#cmakedefine OPENSSH_MLKEM768X25519_SHA256 1
|
||||
#cmakedefine OPENSSH_SSH_ED25519 1
|
||||
#cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1
|
||||
#cmakedefine OPENSSH_SSH_RSA 1
|
||||
|
||||
@@ -281,6 +281,17 @@ static void torture_options_get_key_exchange(void **state)
|
||||
"diffie-hellman-group16-sha512,"
|
||||
"diffie-hellman-group18-sha512");
|
||||
} 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,
|
||||
"curve25519-sha256,curve25519-sha256@libssh.org,"
|
||||
"sntrup761x25519-sha512,"
|
||||
@@ -290,6 +301,7 @@ static void torture_options_get_key_exchange(void **state)
|
||||
"diffie-hellman-group16-sha512,"
|
||||
"diffie-hellman-group-exchange-sha256,"
|
||||
"diffie-hellman-group14-sha256");
|
||||
#endif
|
||||
}
|
||||
ssh_string_free_char(value);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user