gssapi: Add support for ECDH GSSAPI KEX

In particular, gss-nistp256-sha256-* and
gss-curve25519-sha256-*.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
This commit is contained in:
Pavol Žáčik
2025-11-25 11:52:48 +01:00
committed by Jakub Jelen
parent 5fed1bc8be
commit 88c2ea6752
16 changed files with 467 additions and 323 deletions

View File

@@ -99,6 +99,10 @@ enum ssh_key_exchange_e {
SSH_GSS_KEX_DH_GROUP14_SHA256,
/* gss-group16-sha512-* */
SSH_GSS_KEX_DH_GROUP16_SHA512,
/* gss-nistp256-sha256-* */
SSH_GSS_KEX_ECDH_NISTP256_SHA256,
/* gss-curve25519-sha256-* */
SSH_GSS_KEX_CURVE25519_SHA256,
};
enum ssh_cipher_e {

View File

@@ -29,7 +29,10 @@
/* all OID begin with the tag identifier + length */
#define SSH_OID_TAG 06
#define GSSAPI_KEY_EXCHANGE_SUPPORTED "gss-group14-sha256-,gss-group16-sha512-,"
#define GSSAPI_KEY_EXCHANGE_SUPPORTED "gss-group14-sha256-," \
"gss-group16-sha512-," \
"gss-nistp256-sha256-," \
"gss-curve25519-sha256-"
typedef struct ssh_gssapi_struct *ssh_gssapi;

View File

@@ -1,5 +1,5 @@
/*
* dh-gss.h - diffie-hellman GSSAPI key exchange
* kex-gss.h - GSSAPI key exchange
*
* This file is part of the SSH Library
*
@@ -20,17 +20,17 @@
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#ifndef DH_GSS_H_
#define DH_GSS_H_
#ifndef KEX_GSS_H_
#define KEX_GSS_H_
#include "config.h"
#ifdef WITH_GSSAPI
int ssh_client_gss_dh_init(ssh_session session);
void ssh_server_gss_dh_init(ssh_session session);
int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet);
void ssh_client_gss_dh_remove_callbacks(ssh_session session);
void ssh_client_gss_dh_remove_callback_hostkey(ssh_session session);
int ssh_client_gss_kex_init(ssh_session session);
void ssh_server_gss_kex_init(ssh_session session);
int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet);
void ssh_client_gss_kex_remove_callbacks(ssh_session session);
void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session);
#endif /* WITH_GSSAPI */
#endif /* DH_GSS_H_ */
#endif /* KEX_GSS_H_ */

View File

@@ -286,7 +286,7 @@ if (WITH_GSSAPI AND GSSAPI_FOUND)
set(libssh_SRCS
${libssh_SRCS}
gssapi.c
dh-gss.c
kex-gss.c
)
endif (WITH_GSSAPI AND GSSAPI_FOUND)

View File

@@ -31,7 +31,7 @@
#endif
#include "libssh/buffer.h"
#include "libssh/dh-gss.h"
#include "libssh/kex-gss.h"
#include "libssh/dh.h"
#include "libssh/options.h"
#include "libssh/packet.h"
@@ -271,7 +271,9 @@ int dh_handshake(ssh_session session)
#ifdef WITH_GSSAPI
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
rc = ssh_client_gss_dh_init(session);
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_client_gss_kex_init(session);
break;
#endif
case SSH_KEX_DH_GROUP1_SHA1:

View File

@@ -56,6 +56,7 @@ static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768NISTP256_SHA256:
#endif
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
return NISTP256;
case SSH_KEX_ECDH_SHA2_NISTP384:
#ifdef HAVE_MLKEM

View File

@@ -37,7 +37,8 @@
* @brief Map the given key exchange enum value to its curve name.
*/
static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) {
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256 ||
kex_type == SSH_GSS_KEX_ECDH_NISTP256_SHA256) {
return "NIST P-256";
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) {
return "NIST P-384";

View File

@@ -39,7 +39,8 @@
#ifdef HAVE_ECDH
static mbedtls_ecp_group_id ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) {
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256 ||
kex_type == SSH_GSS_KEX_ECDH_NISTP256_SHA256) {
return MBEDTLS_ECP_DP_SECP256R1;
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) {
return MBEDTLS_ECP_DP_SECP384R1;

View File

@@ -1,5 +1,5 @@
/*
* dh-gss.c - diffie-hellman GSSAPI key exchange
* kex-gss.c - GSSAPI key exchange
*
* This file is part of the SSH Library
*
@@ -30,50 +30,112 @@
#include "libssh/buffer.h"
#include "libssh/crypto.h"
#include "libssh/dh-gss.h"
#include "libssh/kex-gss.h"
#include "libssh/bignum.h"
#include "libssh/curve25519.h"
#include "libssh/ecdh.h"
#include "libssh/dh.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/ssh2.h"
static SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply);
static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply);
static ssh_packet_callback gss_dh_client_callbacks[] = {
ssh_packet_client_gss_dh_reply,
static ssh_packet_callback gss_kex_client_callbacks[] = {
ssh_packet_client_gss_kex_reply,
};
static struct ssh_packet_callbacks_struct ssh_gss_dh_client_callbacks = {
static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callbacks = {
.start = SSH2_MSG_KEXGSS_COMPLETE,
.n_callbacks = 1,
.callbacks = gss_dh_client_callbacks,
.callbacks = gss_kex_client_callbacks,
.user = NULL,
};
static SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_hostkey);
static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey);
static ssh_packet_callback gss_dh_client_callback_hostkey[] = {
ssh_packet_client_gss_dh_hostkey,
static ssh_packet_callback gss_kex_client_callback_hostkey[] = {
ssh_packet_client_gss_kex_hostkey,
};
static struct ssh_packet_callbacks_struct ssh_gss_dh_client_callback_hostkey = {
static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callback_hostkey = {
.start = SSH2_MSG_KEXGSS_HOSTKEY,
.n_callbacks = 1,
.callbacks = gss_dh_client_callback_hostkey,
.callbacks = gss_kex_client_callback_hostkey,
.user = NULL,
};
static ssh_string dh_init(ssh_session session)
{
int rc, keypair;
#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L
const_bignum const_pubkey;
#endif
bignum pubkey = NULL;
ssh_string pubkey_string = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
if (session->server) {
keypair = DH_SERVER_KEYPAIR;
} else {
keypair = DH_CLIENT_KEYPAIR;
}
rc = ssh_dh_init_common(crypto);
if (rc != SSH_OK) {
goto end;
}
rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, keypair);
if (rc != SSH_OK) {
goto end;
}
#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L
rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &const_pubkey);
bignum_dup(const_pubkey, &pubkey);
#else
rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &pubkey);
#endif
if (rc != SSH_OK) {
goto end;
}
pubkey_string = ssh_make_bignum_string(pubkey);
end:
bignum_safe_free(pubkey);
return pubkey_string;
}
static int dh_import_peer_key(ssh_session session, ssh_string peer_key)
{
int rc, keypair;
bignum peer_key_bn;
struct ssh_crypto_struct *crypto = session->next_crypto;
if (session->server) {
keypair = DH_CLIENT_KEYPAIR;
} else {
keypair = DH_SERVER_KEYPAIR;
}
peer_key_bn = ssh_make_string_bn(peer_key);
rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, keypair, NULL, peer_key_bn);
if (rc != SSH_OK) {
bignum_safe_free(peer_key_bn);
}
return rc;
}
/** @internal
* @brief Starts gssapi key exchange
*/
int ssh_client_gss_dh_init(ssh_session session)
int ssh_client_gss_kex_init(ssh_session session)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L
const_bignum pubkey;
#else
bignum pubkey = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
int rc;
int rc, ret = SSH_ERROR;
/* oid selected for authentication */
gss_OID_set selected = GSS_C_NO_OID_SET;
OM_uint32 maj_stat, min_stat;
@@ -81,27 +143,52 @@ int ssh_client_gss_dh_init(ssh_session session)
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
OM_uint32 oflags;
ssh_string pubkey = NULL;
rc = ssh_dh_init_common(crypto);
if (rc == SSH_ERROR) {
goto error;
}
rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR);
if (rc == SSH_ERROR) {
goto error;
}
rc = ssh_dh_keypair_get_keys(crypto->dh_ctx,
DH_CLIENT_KEYPAIR,
NULL,
&pubkey);
if (rc != SSH_OK) {
goto error;
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
pubkey = dh_init(session);
if (pubkey == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to generate DH keypair");
goto out;
}
break;
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate ECDH keypair");
goto out;
}
pubkey = ssh_string_copy(crypto->ecdh_client_pubkey);
break;
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate Curve25519 keypair");
goto out;
}
pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE);
if (pubkey == NULL) {
ssh_set_error_oom(session);
goto out;
}
rc = ssh_string_fill(pubkey,
crypto->curve25519_client_pubkey,
CURVE25519_PUBKEY_SIZE);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey");
goto out;
}
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method");
goto out;
}
rc = ssh_gssapi_init(session);
if (rc == SSH_ERROR) {
goto error;
if (rc != SSH_OK) {
goto out;
}
if (session->opts.gss_server_identity != NULL) {
@@ -110,12 +197,12 @@ int ssh_client_gss_dh_init(ssh_session session)
rc = ssh_gssapi_import_name(session->gssapi, gss_host);
if (rc != SSH_OK) {
goto error;
goto out;
}
rc = ssh_gssapi_client_identity(session, &selected);
if (rc == SSH_ERROR) {
goto error;
if (rc != SSH_OK) {
goto out;
}
session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
@@ -129,61 +216,58 @@ int ssh_client_gss_dh_init(ssh_session session)
"Initializing gssapi context",
maj_stat,
min_stat);
goto error;
goto out;
}
if (!(oflags & GSS_C_INTEG_FLAG) || !(oflags & GSS_C_MUTUAL_FLAG)) {
SSH_LOG(SSH_LOG_WARN,
"GSSAPI(init) integrity and mutual flags were not set");
goto error;
goto out;
}
rc = ssh_buffer_pack(session->out_buffer,
"bdPB",
"bdPS",
SSH2_MSG_KEXGSS_INIT,
output_token.length,
(size_t)output_token.length,
output_token.value,
pubkey);
if (rc != SSH_OK) {
goto error;
goto out;
}
gss_release_buffer(&min_stat, &output_token);
#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L
bignum_safe_free(pubkey);
#endif
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_gss_dh_client_callbacks);
ssh_packet_set_callbacks(session, &ssh_gss_dh_client_callback_hostkey);
ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callbacks);
ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callback_hostkey);
session->dh_handshake_state = DH_STATE_INIT_SENT;
rc = ssh_packet_send(session);
return rc;
error:
#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L
bignum_safe_free(pubkey);
#endif
if (rc != SSH_OK) {
goto out;
}
ret = SSH_OK;
out:
gss_release_buffer(&min_stat, &output_token);
ssh_dh_cleanup(crypto);
return SSH_ERROR;
ssh_string_free(pubkey);
return ret;
}
void ssh_client_gss_dh_remove_callbacks(ssh_session session)
void ssh_client_gss_kex_remove_callbacks(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_gss_dh_client_callbacks);
ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callbacks);
}
void ssh_client_gss_dh_remove_callback_hostkey(ssh_session session)
void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_gss_dh_client_callback_hostkey);
ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callback_hostkey);
}
SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply)
SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string pubkey_blob = NULL, mic = NULL, otoken = NULL;
ssh_string mic = NULL, otoken = NULL, server_pubkey = NULL;
uint8_t b;
bignum server_pubkey;
int rc;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
@@ -193,12 +277,14 @@ SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply)
(void)type;
(void)user;
ssh_client_gss_dh_remove_callbacks(session);
ssh_client_gss_kex_remove_callbacks(session);
rc = ssh_buffer_unpack(packet, "BSbS", &server_pubkey, &mic, &b, &otoken);
if (rc == SSH_ERROR) {
rc = ssh_buffer_unpack(packet, "SSbS", &server_pubkey, &mic, &b, &otoken);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "No public key in server reply");
goto error;
}
SSH_STRING_FREE(session->gssapi_key_exchange_mic);
session->gssapi_key_exchange_mic = mic;
input_token.length = ssh_string_len(otoken);
@@ -211,23 +297,37 @@ SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply)
goto error;
}
SSH_STRING_FREE(otoken);
rc = ssh_dh_keypair_set_keys(crypto->dh_ctx,
DH_SERVER_KEYPAIR,
NULL,
server_pubkey);
if (rc != SSH_OK) {
SSH_STRING_FREE(pubkey_blob);
bignum_safe_free(server_pubkey);
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
rc = dh_import_peer_key(session, server_pubkey);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not import server pubkey");
goto error;
}
rc = ssh_dh_compute_shared_secret(crypto->dh_ctx,
DH_CLIENT_KEYPAIR,
DH_SERVER_KEYPAIR,
&crypto->shared_secret);
ssh_dh_debug_crypto(crypto);
break;
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
crypto->ecdh_server_pubkey = ssh_string_copy(server_pubkey);
rc = ecdh_build_k(session);
break;
case SSH_GSS_KEX_CURVE25519_SHA256:
memcpy(crypto->curve25519_server_pubkey,
ssh_string_data(server_pubkey),
CURVE25519_PUBKEY_SIZE);
rc = ssh_curve25519_build_k(session);
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method");
goto error;
}
rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx,
DH_CLIENT_KEYPAIR,
DH_SERVER_KEYPAIR,
&session->next_crypto->shared_secret);
ssh_dh_debug_crypto(session->next_crypto);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Could not generate shared secret");
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not derive shared secret");
goto error;
}
@@ -236,15 +336,18 @@ SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply)
if (rc == SSH_ERROR) {
goto error;
}
ssh_string_free(server_pubkey);
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
return SSH_PACKET_USED;
error:
ssh_dh_cleanup(session->next_crypto);
ssh_string_free(server_pubkey);
session->session_state = SSH_SESSION_STATE_ERROR;
return SSH_PACKET_USED;
}
SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_hostkey)
SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey)
{
ssh_string pubkey_blob = NULL;
int rc;
@@ -252,7 +355,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_hostkey)
(void)type;
(void)user;
ssh_client_gss_dh_remove_callback_hostkey(session);
ssh_client_gss_kex_remove_callback_hostkey(session);
rc = ssh_buffer_unpack(packet, "S", &pubkey_blob);
if (rc == SSH_ERROR) {
@@ -270,52 +373,45 @@ SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_hostkey)
return SSH_PACKET_USED;
error:
ssh_dh_cleanup(session->next_crypto);
session->session_state = SSH_SESSION_STATE_ERROR;
return SSH_PACKET_USED;
}
#ifdef WITH_SERVER
static SSH_PACKET_CALLBACK(ssh_packet_server_gss_dh_init);
static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init);
static ssh_packet_callback gss_dh_server_callbacks[] = {
ssh_packet_server_gss_dh_init,
static ssh_packet_callback gss_kex_server_callbacks[] = {
ssh_packet_server_gss_kex_init,
};
static struct ssh_packet_callbacks_struct ssh_gss_dh_server_callbacks = {
static struct ssh_packet_callbacks_struct ssh_gss_kex_server_callbacks = {
.start = SSH2_MSG_KEXGSS_INIT,
.n_callbacks = 1,
.callbacks = gss_dh_server_callbacks,
.callbacks = gss_kex_server_callbacks,
.user = NULL,
};
/** @internal
* @brief sets up the gssapi kex callbacks
*/
void ssh_server_gss_dh_init(ssh_session session)
void ssh_server_gss_kex_init(ssh_session session)
{
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_gss_dh_server_callbacks);
ssh_dh_init_common(session->next_crypto);
ssh_packet_set_callbacks(session, &ssh_gss_kex_server_callbacks);
}
/** @internal
* @brief processes a SSH_MSG_KEXGSS_INIT and sends
* the appropriate SSH_MSG_KEXGSS_COMPLETE
*/
int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet)
int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
bignum client_pubkey;
#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L
const_bignum server_pubkey;
#else
bignum server_pubkey = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
ssh_string client_pubkey = NULL;
ssh_string server_pubkey = NULL;
int rc;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
@@ -336,33 +432,70 @@ int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet)
input_token.length = ssh_string_len(otoken);
input_token.value = ssh_string_data(otoken);
rc = ssh_buffer_unpack(packet, "B", &client_pubkey);
rc = ssh_buffer_unpack(packet, "S", &client_pubkey);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "No e number in client request");
ssh_set_error(session, SSH_FATAL, "No public key in client request");
goto error;
}
rc = ssh_dh_keypair_set_keys(crypto->dh_ctx,
DH_CLIENT_KEYPAIR,
NULL,
client_pubkey);
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
server_pubkey = dh_init(session);
if (server_pubkey == NULL) {
ssh_set_error(session, SSH_FATAL, "Could not generate a DH keypair");
goto error;
}
rc = dh_import_peer_key(session, client_pubkey);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not import client pubkey");
goto error;
}
rc = ssh_dh_compute_shared_secret(crypto->dh_ctx,
DH_SERVER_KEYPAIR,
DH_CLIENT_KEYPAIR,
&crypto->shared_secret);
ssh_dh_debug_crypto(crypto);
break;
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not generate an ECDH keypair");
goto error;
}
crypto->ecdh_client_pubkey = ssh_string_copy(client_pubkey);
server_pubkey = ssh_string_copy(crypto->ecdh_server_pubkey);
rc = ecdh_build_k(session);
break;
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not generate a Curve25519 keypair");
goto error;
}
server_pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE);
if (server_pubkey == NULL) {
ssh_set_error_oom(session);
goto error;
}
rc = ssh_string_fill(server_pubkey,
crypto->curve25519_server_pubkey,
CURVE25519_PUBKEY_SIZE);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey");
goto error;
}
memcpy(crypto->curve25519_client_pubkey,
ssh_string_data(client_pubkey),
CURVE25519_PUBKEY_SIZE);
rc = ssh_curve25519_build_k(session);
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method");
goto error;
}
if (rc != SSH_OK) {
bignum_safe_free(client_pubkey);
goto error;
}
rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR);
if (rc == SSH_ERROR) {
goto error;
}
rc = ssh_dh_compute_shared_secret(crypto->dh_ctx,
DH_SERVER_KEYPAIR,
DH_CLIENT_KEYPAIR,
&crypto->shared_secret);
ssh_dh_debug_crypto(crypto);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Could not generate shared secret");
ssh_set_error(session, SSH_FATAL, "Could not derive shared secret");
goto error;
}
@@ -400,14 +533,6 @@ int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet)
SSH_STRING_FREE(server_pubkey_blob);
}
rc = ssh_dh_keypair_get_keys(crypto->dh_ctx,
DH_SERVER_KEYPAIR,
NULL,
&server_pubkey);
if (rc != SSH_OK) {
goto error;
}
rc = ssh_gssapi_init(session);
if (rc == SSH_ERROR) {
goto error;
@@ -485,7 +610,7 @@ int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet)
}
rc = ssh_buffer_pack(session->out_buffer,
"bBdPbdP",
"bSdPbdP",
SSH2_MSG_KEXGSS_COMPLETE,
server_pubkey,
mic.length,
@@ -495,9 +620,6 @@ int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet)
output_token.length,
(size_t)output_token.length,
output_token.value);
#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L
bignum_safe_free(server_pubkey);
#endif
if (rc != SSH_OK) {
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
@@ -520,15 +642,14 @@ int ssh_server_gss_dh_process_init(ssh_session session, ssh_buffer packet)
goto error;
}
ssh_string_free(server_pubkey);
ssh_string_free(client_pubkey);
return SSH_OK;
error:
SSH_STRING_FREE(server_pubkey_blob);
#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L
bignum_safe_free(server_pubkey);
#endif
ssh_string_free(server_pubkey);
ssh_string_free(client_pubkey);
session->session_state = SSH_SESSION_STATE_ERROR;
ssh_dh_cleanup(session->next_crypto);
return SSH_ERROR;
}
@@ -536,13 +657,13 @@ error:
* @brief parse an incoming SSH_MSG_KEXGSS_INIT packet and complete
* Diffie-Hellman key exchange
**/
static SSH_PACKET_CALLBACK(ssh_packet_server_gss_dh_init)
static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init)
{
(void)type;
(void)user;
SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXGSS_INIT");
ssh_packet_remove_callbacks(session, &ssh_gss_dh_server_callbacks);
ssh_server_gss_dh_process_init(session, packet);
ssh_packet_remove_callbacks(session, &ssh_gss_kex_server_callbacks);
ssh_server_gss_kex_process_init(session, packet);
return SSH_PACKET_USED;
}

View File

@@ -44,13 +44,13 @@
#ifdef HAVE_MLKEM
#include "libssh/hybrid_mlkem.h"
#endif
#include "libssh/kex-gss.h"
#include "libssh/knownhosts.h"
#include "libssh/misc.h"
#include "libssh/pki.h"
#include "libssh/bignum.h"
#include "libssh/token.h"
#include "libssh/gssapi.h"
#include "libssh/dh-gss.h"
#ifdef HAVE_BLOWFISH
# define BLOWFISH ",blowfish-cbc"
@@ -958,6 +958,10 @@ kex_select_kex_type(const char *kex)
return SSH_GSS_KEX_DH_GROUP14_SHA256;
} else if (strncmp(kex, "gss-group16-sha512-", 19) == 0) {
return SSH_GSS_KEX_DH_GROUP16_SHA512;
} else if (strncmp(kex, "gss-nistp256-sha256-", 20) == 0) {
return SSH_GSS_KEX_ECDH_NISTP256_SHA256;
} else if (strncmp(kex, "gss-curve25519-sha256-", 22) == 0) {
return SSH_GSS_KEX_CURVE25519_SHA256;
} else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) {
return SSH_KEX_DH_GROUP14_SHA1;
} else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) {
@@ -1017,8 +1021,10 @@ static void revert_kex_callbacks(ssh_session session)
break;
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
#ifdef WITH_GSSAPI
ssh_client_gss_dh_remove_callbacks(session);
ssh_client_gss_kex_remove_callbacks(session);
#endif /* WITH_GSSAPI */
break;
#ifdef WITH_GEX
@@ -1591,6 +1597,7 @@ int ssh_make_sessionid(ssh_session session)
case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_ECDH_SHA2_NISTP384:
case SSH_KEX_ECDH_SHA2_NISTP521:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
if (session->next_crypto->ecdh_client_pubkey == NULL ||
session->next_crypto->ecdh_server_pubkey == NULL) {
SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing");
@@ -1609,6 +1616,7 @@ int ssh_make_sessionid(ssh_session session)
#ifdef HAVE_CURVE25519
case SSH_KEX_CURVE25519_SHA256:
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_buffer_pack(buf,
"dPdP",
CURVE25519_PUBKEY_SIZE,
@@ -1727,6 +1735,8 @@ int ssh_make_sessionid(ssh_session session)
case SSH_KEX_MLKEM768X25519_SHA256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
#endif
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
#ifdef WITH_GEX
case SSH_KEX_DH_GEX_SHA256:
#endif /* WITH_GEX */
@@ -2043,6 +2053,8 @@ bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto)
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
return true;
default:
return false;

View File

@@ -434,7 +434,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
*
* Transitions:
* - session->dh_handshake_state = DH_STATE_INIT_SENT
* then calls ssh_packet_client_gss_dh_reply which triggers:
* then calls ssh_packet_client_gss_kex_reply which triggers:
* - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT
* */

View File

@@ -489,6 +489,8 @@ const char* ssh_get_kex_algo(ssh_session session) {
return "diffie-hellman-group18-sha512";
case SSH_KEX_ECDH_SHA2_NISTP256:
return "ecdh-sha2-nistp256";
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
return "gss-nistp256-sha256-";
case SSH_KEX_ECDH_SHA2_NISTP384:
return "ecdh-sha2-nistp384";
case SSH_KEX_ECDH_SHA2_NISTP521:
@@ -497,6 +499,8 @@ const char* ssh_get_kex_algo(ssh_session session) {
return "curve25519-sha256";
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
return "curve25519-sha256@libssh.org";
case SSH_GSS_KEX_CURVE25519_SHA256:
return "gss-curve25519-sha256-";
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
return "sntrup761x25519-sha512@openssh.com";
case SSH_KEX_SNTRUP761X25519_SHA512:

View File

@@ -49,12 +49,12 @@
#include "libssh/dh-gex.h"
#endif /* WITH_GEX */
#include "libssh/curve25519.h"
#include "libssh/kex-gss.h"
#include "libssh/ecdh.h"
#include "libssh/sntrup761.h"
#ifdef HAVE_MLKEM
#include "libssh/hybrid_mlkem.h"
#endif
#include "libssh/dh-gss.h"
static struct ssh_hmac_struct ssh_hmac_tab[] = {
{ "hmac-sha1", SSH_HMAC_SHA1, false },
@@ -591,7 +591,9 @@ int crypt_set_algorithms_server(ssh_session session){
#ifdef WITH_GSSAPI
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
ssh_server_gss_dh_init(session);
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
ssh_server_gss_kex_init(session);
break;
#endif /* WITH_GSSAPI */
#ifdef WITH_GEX

View File

@@ -130,90 +130,80 @@ static void torture_gssapi_key_exchange_no_tgt(void **state)
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_not_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP14_SHA256);
assert_int_not_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP16_SHA512);
assert_false(ssh_kex_is_gss(session->current_crypto));
torture_teardown_kdc_server(state);
}
static void torture_gssapi_key_exchange_alg(void **state,
const char *kex_string,
enum ssh_key_exchange_e kex_type)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
int rc;
bool t = true;
/* Skip test if in FIPS mode */
if (ssh_fips_mode()) {
skip();
}
/* Valid */
torture_setup_kdc_server(
state,
"kadmin.local addprinc -randkey host/server.libssh.site \n"
"kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n"
"kadmin.local addprinc -pw bar alice \n"
"kadmin.local list_principals",
"echo bar | kinit alice");
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_options_set(s->ssh.session,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
kex_string);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_equal(session->current_crypto->kex_type, kex_type);
torture_teardown_kdc_server(state);
}
static void torture_gssapi_key_exchange_gss_group14_sha256(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
int rc;
bool t = true;
/* Skip test if in FIPS mode */
if (ssh_fips_mode()) {
skip();
}
/* Valid */
torture_setup_kdc_server(
state,
"kadmin.local addprinc -randkey host/server.libssh.site \n"
"kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n"
"kadmin.local addprinc -pw bar alice \n"
"kadmin.local list_principals",
"echo bar | kinit alice");
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_options_set(s->ssh.session,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
"gss-group14-sha256-");
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP14_SHA256);
torture_teardown_kdc_server(state);
torture_gssapi_key_exchange_alg(state,
"gss-group14-sha256-",
SSH_GSS_KEX_DH_GROUP14_SHA256);
}
static void torture_gssapi_key_exchange_gss_group16_sha512(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
int rc;
bool t = true;
torture_gssapi_key_exchange_alg(state,
"gss-group16-sha512-",
SSH_GSS_KEX_DH_GROUP16_SHA512);
}
/* Skip test if in FIPS mode */
static void torture_gssapi_key_exchange_gss_nistp256_sha256(void **state)
{
torture_gssapi_key_exchange_alg(state,
"gss-nistp256-sha256-",
SSH_GSS_KEX_ECDH_NISTP256_SHA256);
}
static void torture_gssapi_key_exchange_gss_curve25519_sha256(void **state)
{
if (ssh_fips_mode()) {
skip();
}
/* Valid */
torture_setup_kdc_server(
state,
"kadmin.local addprinc -randkey host/server.libssh.site \n"
"kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n"
"kadmin.local addprinc -pw bar alice \n"
"kadmin.local list_principals",
"echo bar | kinit alice");
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_options_set(s->ssh.session,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
"gss-group16-sha512-");
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_true(session->current_crypto->kex_type ==
SSH_GSS_KEX_DH_GROUP16_SHA512);
torture_teardown_kdc_server(state);
torture_gssapi_key_exchange_alg(state,
"gss-curve25519-sha256-",
SSH_GSS_KEX_CURVE25519_SHA256);
}
static void torture_gssapi_key_exchange_auth(void **state)
@@ -304,6 +294,14 @@ int torture_run_tests(void)
torture_gssapi_key_exchange_gss_group16_sha512,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_gssapi_key_exchange_gss_nistp256_sha256,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_gssapi_key_exchange_gss_curve25519_sha256,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_auth,
session_setup,
session_teardown),

View File

@@ -137,7 +137,10 @@ static void setup_config(void **state)
/* Enable GSSAPI key exchange */
ss->gssapi_key_exchange = true;
ss->gssapi_key_exchange_algs = "gss-group14-sha256-,gss-group16-sha512-";
ss->gssapi_key_exchange_algs = "gss-group14-sha256-,"
"gss-group16-sha512-,"
"gss-nistp256-sha256-,"
"gss-curve25519-sha256-";
tss->state = s;
tss->ss = ss;
@@ -348,108 +351,89 @@ static void torture_gssapi_server_key_exchange_no_tgt(void **state)
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_not_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP14_SHA256);
assert_int_not_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP16_SHA512);
assert_false(ssh_kex_is_gss(session->current_crypto));
torture_teardown_kdc_server((void **)&s);
}
static void torture_gssapi_server_key_exchange_alg(void **state,
const char *kex_string,
enum ssh_key_exchange_e kex_type)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
ssh_session session;
int rc;
bool t = true;
/* Skip test if in FIPS mode */
if (ssh_fips_mode()) {
skip();
}
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
session = s->ssh.session;
assert_non_null(session);
/* Valid */
torture_setup_kdc_server(
(void **)&s,
"kadmin.local addprinc -randkey host/server.libssh.site \n"
"kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n"
"kadmin.local addprinc -pw bar alice \n"
"kadmin.local list_principals",
"echo bar | kinit alice");
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_options_set(s->ssh.session,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
kex_string);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_equal(session->current_crypto->kex_type, kex_type);
torture_teardown_kdc_server((void **)&s);
}
static void torture_gssapi_server_key_exchange_gss_group14_sha256(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
ssh_session session;
int rc;
bool t = true;
/* Skip test if in FIPS mode */
if (ssh_fips_mode()) {
skip();
}
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
session = s->ssh.session;
assert_non_null(session);
/* Valid */
torture_setup_kdc_server(
(void **)&s,
"kadmin.local addprinc -randkey host/server.libssh.site \n"
"kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n"
"kadmin.local addprinc -pw bar alice \n"
"kadmin.local list_principals",
"echo bar | kinit alice");
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_options_set(s->ssh.session,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
"gss-group14-sha256-");
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP14_SHA256);
torture_teardown_kdc_server((void **)&s);
torture_gssapi_server_key_exchange_alg(state,
"gss-group14-sha256-",
SSH_GSS_KEX_DH_GROUP14_SHA256);
}
static void torture_gssapi_server_key_exchange_gss_group16_sha512(void **state)
{
struct test_server_st *tss = *state;
struct torture_state *s = NULL;
ssh_session session;
int rc;
bool t = true;
torture_gssapi_server_key_exchange_alg(state,
"gss-group16-sha512-",
SSH_GSS_KEX_DH_GROUP16_SHA512);
}
/* Skip test if in FIPS mode */
static void torture_gssapi_server_key_exchange_gss_nistp256_sha256(void **state)
{
torture_gssapi_server_key_exchange_alg(state,
"gss-nistp256-sha256-",
SSH_GSS_KEX_ECDH_NISTP256_SHA256);
}
static void torture_gssapi_server_key_exchange_gss_curve25519_sha256(void **state)
{
if (ssh_fips_mode()) {
skip();
}
assert_non_null(tss);
s = tss->state;
assert_non_null(s);
session = s->ssh.session;
assert_non_null(session);
/* Valid */
torture_setup_kdc_server(
(void **)&s,
"kadmin.local addprinc -randkey host/server.libssh.site \n"
"kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n"
"kadmin.local addprinc -pw bar alice \n"
"kadmin.local list_principals",
"echo bar | kinit alice");
rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t);
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_options_set(s->ssh.session,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
"gss-group16-sha512-");
assert_ssh_return_code(s->ssh.session, rc);
rc = ssh_connect(session);
assert_ssh_return_code(session, rc);
assert_int_equal(session->current_crypto->kex_type,
SSH_GSS_KEX_DH_GROUP16_SHA512);
torture_teardown_kdc_server((void **)&s);
torture_gssapi_server_key_exchange_alg(state,
"gss-curve25519-sha256-",
SSH_GSS_KEX_CURVE25519_SHA256);
}
static void torture_gssapi_server_key_exchange_auth(void **state)
@@ -559,6 +543,14 @@ int torture_run_tests(void)
torture_gssapi_server_key_exchange_gss_group16_sha512,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_gssapi_server_key_exchange_gss_nistp256_sha256,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(
torture_gssapi_server_key_exchange_gss_curve25519_sha256,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_auth,
session_setup,
session_teardown),

View File

@@ -95,7 +95,10 @@ static void setup_config(void **state)
/* Enable GSSAPI key exchange */
ss->gssapi_key_exchange = true;
ss->gssapi_key_exchange_algs = "gss-group14-sha256-,gss-group16-sha512-";
ss->gssapi_key_exchange_algs = "gss-group14-sha256-,"
"gss-group16-sha512-,"
"gss-nistp256-sha256-,"
"gss-curve25519-sha256-";
tss->state = s;
tss->ss = ss;