diff --git a/include/libssh/messages.h b/include/libssh/messages.h index 160306cc..9dd6b06c 100644 --- a/include/libssh/messages.h +++ b/include/libssh/messages.h @@ -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; diff --git a/include/libssh/session.h b/include/libssh/session.h index aed94072..44e95298 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -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 { diff --git a/src/auth.c b/src/auth.c index 0a5106a8..17190b67 100644 --- a/src/auth.c +++ b/src/auth.c @@ -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; } diff --git a/src/messages.c b/src/messages.c index f5a315c3..e6ae57f2 100644 --- a/src/messages.c +++ b/src/messages.c @@ -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; } diff --git a/src/packet_cb.c b/src/packet_cb.c index 54c94c84..7dcbfabc 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -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); } diff --git a/src/server.c b/src/server.c index 6151580a..9faa65e8 100644 --- a/src/server.c +++ b/src/server.c @@ -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; }