extensions: Host-bound public key authentication

Signed-off-by: Abdallah Alhadad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
Abdallah Alhadad
2025-03-15 03:17:56 +02:00
parent fe381d6aa4
commit aa681c268e
6 changed files with 161 additions and 14 deletions

View File

@@ -28,6 +28,7 @@ struct ssh_auth_request {
int method;
char *password;
struct ssh_key_struct *pubkey;
struct ssh_key_struct *server_pubkey;
char *sigtype;
enum ssh_publickey_state_e signature_state;
char kbdint_response;

View File

@@ -120,6 +120,8 @@ enum ssh_pending_call_e {
/* server-sig-algs extension */
#define SSH_EXT_SIG_RSA_SHA256 0x02
#define SSH_EXT_SIG_RSA_SHA512 0x04
/* Host-bound public key authentication extension */
#define SSH_EXT_PUBLICKEY_HOSTBOUND 0x08
/* members that are common to ssh_session and ssh_bind */
struct ssh_common_struct {

View File

@@ -462,13 +462,56 @@ fail:
return SSH_AUTH_ERROR;
}
/**
* @internal
*
* @brief Adds the server's public key to the authentication request.
*
* This function is used internally when the hostbound public key authentication
* extension is enabled. It export the server's public key and adds it to the
* authentication buffer.
*
* @param[in] session The SSH session.
*
* @returns SSH_OK on success, SSH_ERROR if an error occurred.
*/
static int add_hostbound_pubkey(ssh_session session)
{
int rc;
ssh_string server_pubkey_s = NULL;
if (session == NULL || session->current_crypto == NULL ||
session->current_crypto->server_pubkey == NULL) {
ssh_set_error(session,
SSH_FATAL,
"Invalid session or server public key");
return SSH_ERROR;
}
rc = ssh_pki_export_pubkey_blob(session->current_crypto->server_pubkey,
&server_pubkey_s);
if (rc < 0) {
goto error;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_s);
if (rc < 0) {
goto error;
}
error:
SSH_STRING_FREE(server_pubkey_s);
return rc;
}
/**
* @internal
*
* @brief Build a public key authentication request.
*
* This helper function creates a SSH2_MSG_USERAUTH_REQUEST message for public
* key authentication.
* key authentication and adds the server's public key if the hostbound
* extension is enabled.
*
* @param[in] session The SSH session.
* @param[in] username The username, may be NULL.
@@ -486,6 +529,11 @@ static int build_pubkey_auth_request(ssh_session session,
ssh_string pubkey_s)
{
int rc;
const char *auth_method = "publickey";
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND) {
auth_method = "publickey-hostbound-v00@openssh.com";
}
/* request */
rc = ssh_buffer_pack(session->out_buffer,
@@ -493,7 +541,7 @@ static int build_pubkey_auth_request(ssh_session session,
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"publickey",
auth_method,
has_signature, /* private key? */
sig_type_c, /* algo */
pubkey_s /* public key */
@@ -502,6 +550,13 @@ static int build_pubkey_auth_request(ssh_session session,
return SSH_ERROR;
}
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND) {
rc = add_hostbound_pubkey(session);
if (rc < 0) {
return SSH_ERROR;
}
}
return SSH_OK;
}

View File

@@ -642,6 +642,7 @@ void ssh_message_free(ssh_message msg){
SAFE_FREE(msg->auth_request.password);
}
ssh_key_free(msg->auth_request.pubkey);
ssh_key_free(msg->auth_request.server_pubkey);
break;
case SSH_REQUEST_CHANNEL_OPEN:
SAFE_FREE(msg->channel_request_open.originator);
@@ -733,7 +734,8 @@ error:
static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
ssh_message msg,
const char *service,
ssh_string algo)
ssh_string algo,
const char *method)
{
struct ssh_crypto_struct *crypto = NULL;
ssh_buffer buffer;
@@ -763,8 +765,8 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
SSH2_MSG_USERAUTH_REQUEST, /* type */
msg->auth_request.username,
service,
"publickey", /* method */
1, /* has to be signed (true) */
method,
1, /* has to be signed (true) */
ssh_string_get_char(algo), /* pubkey algorithm */
str); /* public key as a blob */
@@ -775,6 +777,25 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
return NULL;
}
/* Add server public key for hostbound extension */
if (strcmp(method, "publickey-hostbound-v00@openssh.com") == 0 &&
msg->auth_request.server_pubkey != NULL) {
rc = ssh_pki_export_pubkey_blob(msg->auth_request.server_pubkey, &str);
if (rc < 0) {
SSH_BUFFER_FREE(buffer);
return NULL;
}
rc = ssh_buffer_add_ssh_string(buffer, str);
SSH_STRING_FREE(str);
if (rc < 0) {
ssh_set_error_oom(session);
SSH_BUFFER_FREE(buffer);
return NULL;
}
}
return buffer;
}
@@ -870,19 +891,41 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
goto end;
}
if (strcmp(method, "publickey") == 0) {
if (strcmp(method, "publickey") == 0 ||
strcmp(method, "publickey-hostbound-v00@openssh.com") == 0) {
ssh_string algo = NULL;
ssh_string pubkey_blob = NULL;
ssh_string server_pubkey_blob = NULL;
uint8_t has_sign;
msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY;
SAFE_FREE(method);
rc = ssh_buffer_unpack(packet, "bSS", &has_sign, &algo, &pubkey_blob);
if (rc != SSH_OK) {
goto error;
}
cmp = strcmp(method, "publickey-hostbound-v00@openssh.com");
if (cmp == 0) {
server_pubkey_blob = ssh_buffer_get_ssh_string(packet);
if (server_pubkey_blob == NULL) {
SSH_STRING_FREE(pubkey_blob);
SSH_STRING_FREE(algo);
goto error;
}
rc = ssh_pki_import_pubkey_blob(server_pubkey_blob,
&msg->auth_request.server_pubkey);
SSH_STRING_FREE(server_pubkey_blob);
if (rc < 0) {
SSH_STRING_FREE(pubkey_blob);
SSH_STRING_FREE(algo);
goto error;
}
}
rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey);
SSH_STRING_FREE(pubkey_blob);
pubkey_blob = NULL;
@@ -914,7 +957,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
goto error;
}
digest = ssh_msg_userauth_build_digest(session, msg, service, algo);
digest = ssh_msg_userauth_build_digest(session,
msg,
service,
algo,
method);
SSH_STRING_FREE(algo);
algo = NULL;
if (digest == NULL) {
@@ -965,8 +1012,47 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
SSH_LOG(SSH_LOG_PACKET, "Valid signature received");
cmp = strcmp(method, "publickey-hostbound-v00@openssh.com");
if (cmp == 0) {
ssh_key server_key = NULL;
if (msg->auth_request.server_pubkey == NULL) {
SSH_LOG(SSH_LOG_PACKET,
"Server public key not provided by client");
msg->auth_request.signature_state =
SSH_PUBLICKEY_STATE_WRONG;
goto error;
}
rc = ssh_get_server_publickey(session, &server_key);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_PACKET,
"Failed to get server public key for hostbound "
"verification");
msg->auth_request.signature_state =
SSH_PUBLICKEY_STATE_ERROR;
ssh_key_free(server_key);
goto error;
}
if (ssh_key_cmp(server_key,
msg->auth_request.server_pubkey,
SSH_KEY_CMP_PUBLIC) != 0) {
SSH_LOG(SSH_LOG_PACKET,
"Server public key doesn't match the one provided "
"by client");
msg->auth_request.signature_state =
SSH_PUBLICKEY_STATE_WRONG;
ssh_key_free(server_key);
goto error;
}
ssh_key_free(server_key);
}
msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID;
}
SAFE_FREE(method);
SSH_STRING_FREE(algo);
goto end;
}

View File

@@ -291,7 +291,6 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info)
for (i = 0; i < nr_extensions; i++) {
char *name = NULL;
char *value = NULL;
int cmp;
rc = ssh_buffer_unpack(packet, "ss", &name, &value);
if (rc != SSH_OK) {
@@ -299,8 +298,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info)
return SSH_PACKET_USED;
}
cmp = strcmp(name, "server-sig-algs");
if (cmp == 0) {
if (strcmp(name, "server-sig-algs") == 0) {
/* TODO check for NULL bytes */
SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value);
@@ -313,6 +311,9 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info)
if (rc == 1) {
session->extensions |= SSH_EXT_SIG_RSA_SHA256;
}
} else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) {
SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value);
session->extensions |= SSH_EXT_PUBLICKEY_HOSTBOUND;
} else {
SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name);
}

View File

@@ -229,11 +229,13 @@ static int ssh_server_send_extensions(ssh_session session) {
}
rc = ssh_buffer_pack(session->out_buffer,
"bdss",
"bdssss",
SSH2_MSG_EXT_INFO,
1, /* nr. of extensions */
2, /* nr. of extensions */
"server-sig-algs",
hostkey_algorithms);
hostkey_algorithms,
"publickey-hostbound@openssh.com",
"0");
if (rc != SSH_OK) {
goto error;
}