mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-02-09 09:54:25 +09:00
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:
@@ -28,6 +28,7 @@ struct ssh_auth_request {
|
|||||||
int method;
|
int method;
|
||||||
char *password;
|
char *password;
|
||||||
struct ssh_key_struct *pubkey;
|
struct ssh_key_struct *pubkey;
|
||||||
|
struct ssh_key_struct *server_pubkey;
|
||||||
char *sigtype;
|
char *sigtype;
|
||||||
enum ssh_publickey_state_e signature_state;
|
enum ssh_publickey_state_e signature_state;
|
||||||
char kbdint_response;
|
char kbdint_response;
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ enum ssh_pending_call_e {
|
|||||||
/* server-sig-algs extension */
|
/* server-sig-algs extension */
|
||||||
#define SSH_EXT_SIG_RSA_SHA256 0x02
|
#define SSH_EXT_SIG_RSA_SHA256 0x02
|
||||||
#define SSH_EXT_SIG_RSA_SHA512 0x04
|
#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 */
|
/* members that are common to ssh_session and ssh_bind */
|
||||||
struct ssh_common_struct {
|
struct ssh_common_struct {
|
||||||
|
|||||||
59
src/auth.c
59
src/auth.c
@@ -462,13 +462,56 @@ fail:
|
|||||||
return SSH_AUTH_ERROR;
|
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
|
* @internal
|
||||||
*
|
*
|
||||||
* @brief Build a public key authentication request.
|
* @brief Build a public key authentication request.
|
||||||
*
|
*
|
||||||
* This helper function creates a SSH2_MSG_USERAUTH_REQUEST message for public
|
* 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] session The SSH session.
|
||||||
* @param[in] username The username, may be NULL.
|
* @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)
|
ssh_string pubkey_s)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
const char *auth_method = "publickey";
|
||||||
|
|
||||||
|
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND) {
|
||||||
|
auth_method = "publickey-hostbound-v00@openssh.com";
|
||||||
|
}
|
||||||
|
|
||||||
/* request */
|
/* request */
|
||||||
rc = ssh_buffer_pack(session->out_buffer,
|
rc = ssh_buffer_pack(session->out_buffer,
|
||||||
@@ -493,7 +541,7 @@ static int build_pubkey_auth_request(ssh_session session,
|
|||||||
SSH2_MSG_USERAUTH_REQUEST,
|
SSH2_MSG_USERAUTH_REQUEST,
|
||||||
username ? username : session->opts.username,
|
username ? username : session->opts.username,
|
||||||
"ssh-connection",
|
"ssh-connection",
|
||||||
"publickey",
|
auth_method,
|
||||||
has_signature, /* private key? */
|
has_signature, /* private key? */
|
||||||
sig_type_c, /* algo */
|
sig_type_c, /* algo */
|
||||||
pubkey_s /* public key */
|
pubkey_s /* public key */
|
||||||
@@ -502,6 +550,13 @@ static int build_pubkey_auth_request(ssh_session session,
|
|||||||
return SSH_ERROR;
|
return SSH_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND) {
|
||||||
|
rc = add_hostbound_pubkey(session);
|
||||||
|
if (rc < 0) {
|
||||||
|
return SSH_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SSH_OK;
|
return SSH_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -642,6 +642,7 @@ void ssh_message_free(ssh_message msg){
|
|||||||
SAFE_FREE(msg->auth_request.password);
|
SAFE_FREE(msg->auth_request.password);
|
||||||
}
|
}
|
||||||
ssh_key_free(msg->auth_request.pubkey);
|
ssh_key_free(msg->auth_request.pubkey);
|
||||||
|
ssh_key_free(msg->auth_request.server_pubkey);
|
||||||
break;
|
break;
|
||||||
case SSH_REQUEST_CHANNEL_OPEN:
|
case SSH_REQUEST_CHANNEL_OPEN:
|
||||||
SAFE_FREE(msg->channel_request_open.originator);
|
SAFE_FREE(msg->channel_request_open.originator);
|
||||||
@@ -733,7 +734,8 @@ error:
|
|||||||
static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
|
static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
|
||||||
ssh_message msg,
|
ssh_message msg,
|
||||||
const char *service,
|
const char *service,
|
||||||
ssh_string algo)
|
ssh_string algo,
|
||||||
|
const char *method)
|
||||||
{
|
{
|
||||||
struct ssh_crypto_struct *crypto = NULL;
|
struct ssh_crypto_struct *crypto = NULL;
|
||||||
ssh_buffer buffer;
|
ssh_buffer buffer;
|
||||||
@@ -763,8 +765,8 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
|
|||||||
SSH2_MSG_USERAUTH_REQUEST, /* type */
|
SSH2_MSG_USERAUTH_REQUEST, /* type */
|
||||||
msg->auth_request.username,
|
msg->auth_request.username,
|
||||||
service,
|
service,
|
||||||
"publickey", /* method */
|
method,
|
||||||
1, /* has to be signed (true) */
|
1, /* has to be signed (true) */
|
||||||
ssh_string_get_char(algo), /* pubkey algorithm */
|
ssh_string_get_char(algo), /* pubkey algorithm */
|
||||||
str); /* public key as a blob */
|
str); /* public key as a blob */
|
||||||
|
|
||||||
@@ -775,6 +777,25 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session,
|
|||||||
return NULL;
|
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;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,19 +891,41 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
|
|||||||
goto end;
|
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 algo = NULL;
|
||||||
ssh_string pubkey_blob = NULL;
|
ssh_string pubkey_blob = NULL;
|
||||||
|
ssh_string server_pubkey_blob = NULL;
|
||||||
uint8_t has_sign;
|
uint8_t has_sign;
|
||||||
|
|
||||||
msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY;
|
msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY;
|
||||||
SAFE_FREE(method);
|
|
||||||
rc = ssh_buffer_unpack(packet, "bSS", &has_sign, &algo, &pubkey_blob);
|
rc = ssh_buffer_unpack(packet, "bSS", &has_sign, &algo, &pubkey_blob);
|
||||||
|
|
||||||
if (rc != SSH_OK) {
|
if (rc != SSH_OK) {
|
||||||
goto error;
|
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);
|
rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey);
|
||||||
SSH_STRING_FREE(pubkey_blob);
|
SSH_STRING_FREE(pubkey_blob);
|
||||||
pubkey_blob = NULL;
|
pubkey_blob = NULL;
|
||||||
@@ -914,7 +957,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
|
|||||||
goto error;
|
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);
|
SSH_STRING_FREE(algo);
|
||||||
algo = NULL;
|
algo = NULL;
|
||||||
if (digest == NULL) {
|
if (digest == NULL) {
|
||||||
@@ -965,8 +1012,47 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
|
|||||||
|
|
||||||
SSH_LOG(SSH_LOG_PACKET, "Valid signature received");
|
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;
|
msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SAFE_FREE(method);
|
||||||
SSH_STRING_FREE(algo);
|
SSH_STRING_FREE(algo);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,7 +291,6 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info)
|
|||||||
for (i = 0; i < nr_extensions; i++) {
|
for (i = 0; i < nr_extensions; i++) {
|
||||||
char *name = NULL;
|
char *name = NULL;
|
||||||
char *value = NULL;
|
char *value = NULL;
|
||||||
int cmp;
|
|
||||||
|
|
||||||
rc = ssh_buffer_unpack(packet, "ss", &name, &value);
|
rc = ssh_buffer_unpack(packet, "ss", &name, &value);
|
||||||
if (rc != SSH_OK) {
|
if (rc != SSH_OK) {
|
||||||
@@ -299,8 +298,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info)
|
|||||||
return SSH_PACKET_USED;
|
return SSH_PACKET_USED;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmp = strcmp(name, "server-sig-algs");
|
if (strcmp(name, "server-sig-algs") == 0) {
|
||||||
if (cmp == 0) {
|
|
||||||
/* TODO check for NULL bytes */
|
/* TODO check for NULL bytes */
|
||||||
SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value);
|
SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value);
|
||||||
|
|
||||||
@@ -313,6 +311,9 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info)
|
|||||||
if (rc == 1) {
|
if (rc == 1) {
|
||||||
session->extensions |= SSH_EXT_SIG_RSA_SHA256;
|
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 {
|
} else {
|
||||||
SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name);
|
SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,11 +229,13 @@ static int ssh_server_send_extensions(ssh_session session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rc = ssh_buffer_pack(session->out_buffer,
|
rc = ssh_buffer_pack(session->out_buffer,
|
||||||
"bdss",
|
"bdssss",
|
||||||
SSH2_MSG_EXT_INFO,
|
SSH2_MSG_EXT_INFO,
|
||||||
1, /* nr. of extensions */
|
2, /* nr. of extensions */
|
||||||
"server-sig-algs",
|
"server-sig-algs",
|
||||||
hostkey_algorithms);
|
hostkey_algorithms,
|
||||||
|
"publickey-hostbound@openssh.com",
|
||||||
|
"0");
|
||||||
if (rc != SSH_OK) {
|
if (rc != SSH_OK) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user