1
0
mirror of https://github.com/libssh2/libssh2.git synced 2026-10-24 03:48:58 +03:00

Hybrid ML-KEM KEX algorithms support with OpenSSL 3.5 (#1644)

Notes:
Adds mlkem768x25519-sha256, mlkem768nistp256-sha256 and
mlkem1024nistp384-sha384 according to
[draft-ietf-sshm-mlkem-hybrid-kex](https://datatracker.ietf.org/doc/draft-ietf-sshm-mlkem-hybrid-kex/)
if libssh2 built against OpenSSL 3.5 or newer.

Reduces code duplication in `kex.c` by moving code blocks that
are common for all KEXes into `process_host_key` and `finish_kex`.

---------

Credit: Yaroslav Rosomakho
Co-authored-by: Viktor Szakats <vszakats@users.noreply.github.com>
This commit is contained in:
Yaroslav Rosomakho
2026-04-03 15:45:34 -07:00
committed by GitHub
parent 3186a1a7ce
commit 3ba252e2a6
12 changed files with 1581 additions and 1068 deletions

View File

@@ -79,6 +79,18 @@ void _libssh2_hmac_cleanup(libssh2_hmac_ctx *ctx);
#define LIBSSH2_ED25519_KEY_LEN 32
#define LIBSSH2_ED25519_PRIVATE_KEY_LEN 64
#define LIBSSH2_ED25519_SIG_LEN 64
#define LIBSSH2_EC_P256_PUBLIC_KEY_LEN 65
#define LIBSSH2_EC_P384_PUBLIC_KEY_LEN 97
#define LIBSSH2_MLKEM_SHARED_SECRET_LEN 32
#define LIBSSH2_MLKEM_512_PRIVATE_KEY_LEN 1632
#define LIBSSH2_MLKEM_512_PUBLIC_KEY_LEN 800
#define LIBSSH2_MLKEM_512_CIPHERTEXT 768
#define LIBSSH2_MLKEM_768_PRIVATE_KEY_LEN 2400
#define LIBSSH2_MLKEM_768_PUBLIC_KEY_LEN 1184
#define LIBSSH2_MLKEM_768_CIPHERTEXT 1088
#define LIBSSH2_MLKEM_1024_PRIVATE_KEY_LEN 3168
#define LIBSSH2_MLKEM_1024_PUBLIC_KEY_LEN 1568
#define LIBSSH2_MLKEM_1024_CIPHERTEXT 1568
#if LIBSSH2_RSA
int _libssh2_rsa_new(libssh2_rsa_ctx ** rsa,
@@ -293,6 +305,21 @@ _libssh2_ed25519_new_private_frommemory_sk(libssh2_ed25519_ctx **ed_ctx,
#endif /* LIBSSH2_ED25519 */
#if LIBSSH2_MLKEM
int
_libssh2_mlkem_new(LIBSSH2_SESSION *session,
int ml_kem_size,
unsigned char **out_public_key,
unsigned char **out_private_key);
int
_libssh2_mlkem_get_sk(unsigned char *out_shared_key,
int ml_kem_size,
uint8_t *private_key,
uint8_t *server_ciphertext);
#endif /* LIBSSH2_MLKEM */
int _libssh2_cipher_init(_libssh2_cipher_ctx * h,
_libssh2_cipher_type(algo),
unsigned char *iv,

View File

@@ -45,6 +45,11 @@
#define LIBSSH2_ED25519 0
#endif
#ifdef LIBSSH2_NO_MLKEM
#undef LIBSSH2_MLKEM
#define LIBSSH2_MLKEM 0
#endif
#ifdef LIBSSH2_NO_AES_CTR
#undef LIBSSH2_AES_CTR
#define LIBSSH2_AES_CTR 0

2404
src/kex.c

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,7 @@
#define LIBSSH2_DSA 1
#define LIBSSH2_ECDSA 0
#define LIBSSH2_ED25519 0
#define LIBSSH2_MLKEM 0
#include "crypto_config.h"

View File

@@ -361,7 +361,9 @@ typedef struct key_exchange_state_low_t
kmdhgGPshakex_state_t exchange_state;
_libssh2_bn *p; /* SSH2 defined value (p_value) */
_libssh2_bn *g; /* SSH2 defined value (2) */
unsigned char request[256]; /* Must fit EC_MAX_POINT_LEN + data */
/* Request must fit mlkem1024nistp384 keys + 5 bytes overhead */
unsigned char request[LIBSSH2_MLKEM_1024_PUBLIC_KEY_LEN +
LIBSSH2_EC_P384_PUBLIC_KEY_LEN + 5];
unsigned char *data;
size_t request_len;
size_t data_len;
@@ -373,6 +375,8 @@ typedef struct key_exchange_state_low_t
bytes */
unsigned char *curve25519_private_key; /* curve25519 private key, 32
bytes */
unsigned char *mlkem_public_key; /* ML-KEM public key */
unsigned char *mlkem_private_key; /* ML-KEM private key */
} key_exchange_state_low_t;
typedef struct key_exchange_state_t

View File

@@ -91,6 +91,7 @@
# define LIBSSH2_ECDSA 0
#endif
#define LIBSSH2_ED25519 0
#define LIBSSH2_MLKEM 0
#include "crypto_config.h"

View File

@@ -296,6 +296,32 @@ int _libssh2_store_str(unsigned char **buf, const char *str, size_t len)
return len_stored == len;
}
/* _libssh2_store_str */
int _libssh2_store_hybrid_str(unsigned char **buf, const char *str_1,
size_t len_1, const char *str_2, size_t len_2)
{
uint32_t len_stored;
if(len_1 > UINT32_MAX - len_2)
return 0;
len_stored = (uint32_t)len_1 + (uint32_t)len_2;
_libssh2_store_u32(buf, len_stored);
if(len_1) {
memcpy(*buf, str_1, len_1);
*buf += len_1;
}
if(len_2) {
memcpy(*buf, str_2, len_2);
*buf += len_2;
}
assert(len_stored == len_1 + len_2);
return len_stored == len_1 + len_2;
}
/* _libssh2_store_bignum2_bytes */
int _libssh2_store_bignum2_bytes(unsigned char **buf,
const unsigned char *bytes,

View File

@@ -114,6 +114,8 @@ void _libssh2_htonu32(unsigned char *buf, uint32_t val);
void _libssh2_store_u32(unsigned char **buf, uint32_t value);
void _libssh2_store_u64(unsigned char **buf, libssh2_uint64_t value);
int _libssh2_store_str(unsigned char **buf, const char *str, size_t len);
int _libssh2_store_hybrid_str(unsigned char **buf, const char *str_1,
size_t len_1, const char *str_2, size_t len_2);
int _libssh2_store_bignum2_bytes(unsigned char **buf,
const unsigned char *bytes,
size_t len);

View File

@@ -2745,6 +2745,174 @@ _libssh2_ed25519_new_public(libssh2_ed25519_ctx ** ed_ctx,
}
#endif /* LIBSSH2_ED25519 */
#if LIBSSH2_MLKEM
int
_libssh2_mlkem_new(LIBSSH2_SESSION *session,
int ml_kem_size,
unsigned char **out_public_key,
unsigned char **out_private_key)
{
EVP_PKEY *key = NULL;
EVP_PKEY_CTX *pctx = NULL;
unsigned char *priv = NULL, *pub = NULL;
const char *evp_name;
size_t privLen, actualPrivLen, pubLen, actualPubLen;
int rc = -1;
switch(ml_kem_size) {
case 512:
evp_name = "ML-KEM-512";
privLen = LIBSSH2_MLKEM_512_PRIVATE_KEY_LEN;
pubLen = LIBSSH2_MLKEM_512_PUBLIC_KEY_LEN;
break;
case 768:
evp_name = "ML-KEM-768";
privLen = LIBSSH2_MLKEM_768_PRIVATE_KEY_LEN;
pubLen = LIBSSH2_MLKEM_768_PUBLIC_KEY_LEN;
break;
case 1024:
evp_name = "ML-KEM-1024";
privLen = LIBSSH2_MLKEM_1024_PRIVATE_KEY_LEN;
pubLen = LIBSSH2_MLKEM_1024_PUBLIC_KEY_LEN;
break;
default:
goto clean_exit;
}
pctx = EVP_PKEY_CTX_new_from_name(NULL, evp_name, NULL);
if(!pctx)
return -1;
if(EVP_PKEY_keygen_init(pctx) != 1 ||
EVP_PKEY_keygen(pctx, &key) != 1) {
goto clean_exit;
}
if(out_private_key) {
priv = LIBSSH2_ALLOC(session, privLen);
actualPrivLen = privLen;
if(!priv)
goto clean_exit;
if(EVP_PKEY_get_raw_private_key(key, priv, &actualPrivLen) != 1 ||
privLen != actualPrivLen) {
goto clean_exit;
}
*out_private_key = priv;
priv = NULL;
}
if(out_public_key) {
pub = LIBSSH2_ALLOC(session, pubLen);
if(!pub)
goto clean_exit;
if(EVP_PKEY_get_raw_public_key(key, pub, &actualPubLen) != 1 ||
pubLen != actualPubLen) {
goto clean_exit;
}
*out_public_key = pub;
pub = NULL;
}
/* success */
rc = 0;
clean_exit:
if(pctx)
EVP_PKEY_CTX_free(pctx);
if(key)
EVP_PKEY_free(key);
if(priv)
LIBSSH2_FREE(session, priv);
if(pub)
LIBSSH2_FREE(session, pub);
return rc;
}
int
_libssh2_mlkem_get_sk(unsigned char *out_shared_key,
int ml_kem_size,
uint8_t *private_key,
uint8_t *server_ciphertext)
{
int rc = -1;
EVP_PKEY *client_key = NULL;
EVP_PKEY_CTX *client_key_ctx = NULL;
size_t out_len = 0;
const char *evp_name;
size_t privLen, ciphertextLen;
switch(ml_kem_size) {
case 512:
evp_name = "ML-KEM-512";
privLen = LIBSSH2_MLKEM_512_PRIVATE_KEY_LEN;
ciphertextLen = LIBSSH2_MLKEM_512_CIPHERTEXT;
break;
case 768:
evp_name = "ML-KEM-768";
privLen = LIBSSH2_MLKEM_768_PRIVATE_KEY_LEN;
ciphertextLen = LIBSSH2_MLKEM_768_CIPHERTEXT;
break;
case 1024:
evp_name = "ML-KEM-1024";
privLen = LIBSSH2_MLKEM_1024_PRIVATE_KEY_LEN;
ciphertextLen = LIBSSH2_MLKEM_1024_CIPHERTEXT;
break;
default:
goto clean_exit;
}
if(!out_shared_key)
return -1;
client_key = EVP_PKEY_new_raw_private_key_ex(NULL, evp_name,
NULL, private_key, privLen);
if(!client_key) {
goto clean_exit;
}
client_key_ctx = EVP_PKEY_CTX_new(client_key, NULL);
if(!client_key_ctx) {
goto clean_exit;
}
rc = EVP_PKEY_decapsulate_init(client_key_ctx, NULL);
if(rc <= 0) {
goto clean_exit;
}
rc = EVP_PKEY_decapsulate(client_key_ctx, NULL, &out_len,
server_ciphertext, ciphertextLen);
if(rc <= 0) {
goto clean_exit;
}
if(out_len != LIBSSH2_MLKEM_SHARED_SECRET_LEN) {
rc = -1;
goto clean_exit;
}
rc = EVP_PKEY_decapsulate(client_key_ctx, out_shared_key, &out_len,
server_ciphertext, ciphertextLen);
clean_exit:
if(client_key_ctx)
EVP_PKEY_CTX_free(client_key_ctx);
if(client_key)
EVP_PKEY_free(client_key);
return (rc == 1) ? 0 : -1;
}
#endif
#if LIBSSH2_RSA
int
_libssh2_rsa_sha2_sign(LIBSSH2_SESSION * session,

View File

@@ -155,6 +155,13 @@
# define LIBSSH2_ED25519 0
#endif
#if OPENSSL_VERSION_NUMBER >= 0x30500000L && \
!defined(LIBRESSL_VERSION_NUMBER)
# define LIBSSH2_MLKEM 1
#else
# define LIBSSH2_MLKEM 0
#endif
#ifdef OPENSSL_NO_MD5
# define LIBSSH2_MD5 0
#else

View File

@@ -187,6 +187,7 @@
#define LIBSSH2_DSA 0
#define LIBSSH2_ECDSA 0
#define LIBSSH2_ED25519 0
#define LIBSSH2_MLKEM 0
#include "crypto_config.h"

View File

@@ -72,6 +72,7 @@
#define LIBSSH2_RSA_SHA2 1
#define LIBSSH2_DSA 1
#define LIBSSH2_ED25519 0
#define LIBSSH2_MLKEM 0
/*
* Conditionally enable ECDSA support.