diff --git a/include/libssh/auth.h b/include/libssh/auth.h index b358b7a2..089d6472 100644 --- a/include/libssh/auth.h +++ b/include/libssh/auth.h @@ -52,6 +52,7 @@ typedef struct ssh_kbdint_struct* ssh_kbdint; ssh_kbdint ssh_kbdint_new(void); void ssh_kbdint_clean(ssh_kbdint kbd); void ssh_kbdint_free(ssh_kbdint kbd); +int ssh_userauth_gssapi_keyex(ssh_session session); /** @internal * States of authentication in the client-side. They describe @@ -88,6 +89,8 @@ enum ssh_auth_state_e { SSH_AUTH_STATE_PASSWORD_AUTH_SENT, /** We have sent a request without auth information (method 'none') */ SSH_AUTH_STATE_AUTH_NONE_SENT, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT, }; /** @internal diff --git a/include/libssh/bind.h b/include/libssh/bind.h index cd9199b6..a848003e 100644 --- a/include/libssh/bind.h +++ b/include/libssh/bind.h @@ -54,6 +54,8 @@ struct ssh_bind_struct { char *pubkey_accepted_key_types; char* moduli_file; int rsa_min_size; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; }; struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct diff --git a/include/libssh/config.h b/include/libssh/config.h index 48000d2b..87cc25be 100644 --- a/include/libssh/config.h +++ b/include/libssh/config.h @@ -69,6 +69,8 @@ enum ssh_config_opcode_e { SOC_CERTIFICATE, SOC_REQUIRED_RSA_SIZE, SOC_ADDRESSFAMILY, + SOC_GSSAPIKEYEXCHANGE, + SOC_GSSAPIKEXALGORITHMS, SOC_MAX /* Keep this one last in the list */ }; diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 58d107b2..881ffd96 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -95,6 +95,10 @@ enum ssh_key_exchange_e { /* mlkem1024nistp384-sha384 */ SSH_KEX_MLKEM1024NISTP384_SHA384, #endif /* HAVE_MLKEM */ + /* gss-group14-sha256-* */ + SSH_GSS_KEX_DH_GROUP14_SHA256, + /* gss-group16-sha512-* */ + SSH_GSS_KEX_DH_GROUP16_SHA512, }; enum ssh_cipher_e { diff --git a/include/libssh/dh-gss.h b/include/libssh/dh-gss.h new file mode 100644 index 00000000..7bab754e --- /dev/null +++ b/include/libssh/dh-gss.h @@ -0,0 +1,35 @@ +/* + * dh-gss.h - diffie-hellman GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ +#ifndef DH_GSS_H_ +#define DH_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); + +#endif /* WITH_GSSAPI */ +#endif /* DH_GSS_H_ */ diff --git a/include/libssh/gssapi.h b/include/libssh/gssapi.h index d0f3eb95..a3e24922 100644 --- a/include/libssh/gssapi.h +++ b/include/libssh/gssapi.h @@ -29,6 +29,9 @@ /* all OID begin with the tag identifier + length */ #define SSH_OID_TAG 06 +#define GSSAPI_KEY_EXCHANGE_SUPPORTED \ + "gss-group14-sha256-,gss-group16-sha512-," + typedef struct ssh_gssapi_struct *ssh_gssapi; #ifdef __cplusplus @@ -44,14 +47,12 @@ enum ssh_gssapi_state_e { struct ssh_gssapi_struct{ enum ssh_gssapi_state_e state; /* current state */ - struct gss_OID_desc_struct mech; /* mechanism being elected for auth */ gss_cred_id_t server_creds; /* credentials of server */ gss_cred_id_t client_creds; /* creds delegated by the client */ gss_ctx_id_t ctx; /* the authentication context */ gss_name_t client_name; /* Identity of the client */ char *user; /* username of client */ char *canonic_user; /* canonic form of the client's username */ - char *service; /* name of the service */ struct { gss_name_t server_name; /* identity of server */ OM_uint32 flags; /* flags used for init context */ @@ -65,6 +66,7 @@ struct ssh_gssapi_struct{ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids); SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server); SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic); +int ssh_gssapi_server_oids(gss_OID_set *selected); #endif /* WITH_SERVER */ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token); @@ -76,7 +78,17 @@ int ssh_gssapi_init(ssh_session session); void ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat); int ssh_gssapi_auth_mic(ssh_session session); void ssh_gssapi_free(ssh_session session); +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids); char *ssh_gssapi_name_to_char(gss_name_t name); +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host); +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags); + +char *ssh_gssapi_oid_hash(ssh_string oid); +char *ssh_gssapi_kex_mechs(ssh_session session, const char *gss_algs); +int ssh_gssapi_check_client_config(ssh_session session); #ifdef __cplusplus } diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index cd4909c4..a0d28d0f 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -152,13 +152,14 @@ enum ssh_auth_e { }; /* auth flags */ -#define SSH_AUTH_METHOD_UNKNOWN 0x0000u -#define SSH_AUTH_METHOD_NONE 0x0001u -#define SSH_AUTH_METHOD_PASSWORD 0x0002u -#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u -#define SSH_AUTH_METHOD_HOSTBASED 0x0008u -#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u -#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u +#define SSH_AUTH_METHOD_UNKNOWN 0x0000u +#define SSH_AUTH_METHOD_NONE 0x0001u +#define SSH_AUTH_METHOD_PASSWORD 0x0002u +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u +#define SSH_AUTH_METHOD_HOSTBASED 0x0008u +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u +#define SSH_AUTH_METHOD_GSSAPI_KEYEX 0x0040u /* messages */ enum ssh_requests_e { @@ -429,6 +430,8 @@ enum ssh_options_e { SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, SSH_OPTIONS_PKI_CONTEXT, SSH_OPTIONS_ADDRESS_FAMILY, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, }; enum { diff --git a/include/libssh/server.h b/include/libssh/server.h index 94c2b10d..47860ca3 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -59,6 +59,8 @@ enum ssh_bind_options_e { SSH_BIND_OPTIONS_MODULI, SSH_BIND_OPTIONS_RSA_MIN_SIZE, SSH_BIND_OPTIONS_IMPORT_KEY_STR, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, }; typedef struct ssh_bind_struct* ssh_bind; diff --git a/include/libssh/session.h b/include/libssh/session.h index 83c16cf2..53b608aa 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -67,7 +67,8 @@ enum ssh_pending_call_e { SSH_PENDING_CALL_AUTH_AGENT, SSH_PENDING_CALL_AUTH_KBDINT_INIT, SSH_PENDING_CALL_AUTH_KBDINT_SEND, - SSH_PENDING_CALL_AUTH_GSSAPI_MIC + SSH_PENDING_CALL_AUTH_GSSAPI_MIC, + SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX }; /* libssh calls may block an undefined amount of time */ @@ -201,6 +202,8 @@ struct ssh_session_struct { */ bool first_kex_follows_guess_wrong; + ssh_string gssapi_key_exchange_mic; + ssh_buffer in_hashbuf; ssh_buffer out_hashbuf; struct ssh_crypto_struct *current_crypto; @@ -265,6 +268,8 @@ struct ssh_session_struct { char compressionlevel; char *gss_server_identity; char *gss_client_identity; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; int gss_delegate_creds; int flags; int exp_flags; diff --git a/include/libssh/ssh2.h b/include/libssh/ssh2.h index aab88e56..65f6eef1 100644 --- a/include/libssh/ssh2.h +++ b/include/libssh/ssh2.h @@ -39,6 +39,14 @@ #define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 #define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define SSH2_MSG_KEXGSS_GROUPREQ 40 +#define SSH2_MSG_KEXGSS_GROUP 41 + #define SSH2_MSG_GLOBAL_REQUEST 80 #define SSH2_MSG_REQUEST_SUCCESS 81 #define SSH2_MSG_REQUEST_FAILURE 82 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c1d94796..f800b01c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -286,6 +286,7 @@ if (WITH_GSSAPI AND GSSAPI_FOUND) set(libssh_SRCS ${libssh_SRCS} gssapi.c + dh-gss.c ) endif (WITH_GSSAPI AND GSSAPI_FOUND) diff --git a/src/auth.c b/src/auth.c index f6da6641..5e9834fe 100644 --- a/src/auth.c +++ b/src/auth.c @@ -45,6 +45,7 @@ #include "libssh/pki.h" #include "libssh/gssapi.h" #include "libssh/legacy.h" +#include "libssh/gssapi.h" /** * @defgroup libssh_auth The SSH authentication functions @@ -88,6 +89,7 @@ static int ssh_auth_response_termination(void *user) case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: case SSH_AUTH_STATE_GSSAPI_TOKEN: case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: @@ -121,6 +123,9 @@ static const char *ssh_auth_get_current_method(ssh_session session) case SSH_AUTH_METHOD_GSSAPI_MIC: method = "gssapi"; break; + case SSH_AUTH_METHOD_GSSAPI_KEYEX: + method = "gssapi-keyex"; + break; default: break; } @@ -175,6 +180,7 @@ static int ssh_userauth_get_response(ssh_session session) case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: case SSH_AUTH_STATE_GSSAPI_TOKEN: case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: @@ -272,6 +278,9 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { if (strstr(auth_methods, "gssapi-with-mic") != NULL) { session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC; } + if (strstr(auth_methods, "gssapi-keyex") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_KEYEX; + } end: session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; @@ -2446,4 +2455,130 @@ pending: return rc; } +/** + * @brief Try to authenticate through the "gssapi-with-keyex" method. + * + * @param[in] session The ssh session to use. + * + * @returns + * - `SSH_AUTH_ERROR`: A serious error happened. + * - `SSH_AUTH_DENIED`: Authentication failed : use another method. + * - `SSH_AUTH_PARTIAL`: You've been partially authenticated, you still + * have to use another method. + * - `SSH_AUTH_SUCCESS`: Authentication success. + * - `SSH_AUTH_AGAIN`: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi_keyex(ssh_session session) +{ + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + ssh_buffer buf = ssh_buffer_new(); + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + /* Check if GSSAPI Key exchange was performed */ + switch (session->current_crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + break; + default: + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with \"gssapi-keyex\" without doing GSSAPI Key exchange."); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-with-keyex"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_KEYEX; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX; + + rc = ssh_buffer_pack(buf, + "dPbsss", + session->current_crypto->session_id_len, + session->current_crypto->session_id_len, + session->current_crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-keyex"); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, + &mic_buf, &mic_token_buf); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, + min_stat); + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return SSH_ERROR; + } + SSH_BUFFER_FREE(buf); + + rc = ssh_buffer_pack(session->out_buffer, + "bsssdP", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-keyex", + mic_token_buf.length, + (size_t)mic_token_buf.length, + mic_token_buf.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + gss_release_buffer(&min_stat, &mic_token_buf); + return rc; + } + + gss_release_buffer(&min_stat, &mic_token_buf); + + session->auth.state = SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT; + + ssh_packet_send(session); + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void) session; /* unused */ +#endif + return rc; +} + /** @} */ diff --git a/src/bind.c b/src/bind.c index e13d918d..7afcd9cc 100644 --- a/src/bind.c +++ b/src/bind.c @@ -386,6 +386,7 @@ void ssh_bind_free(ssh_bind sshbind){ SAFE_FREE(sshbind->rsakey); SAFE_FREE(sshbind->ecdsakey); SAFE_FREE(sshbind->ed25519key); + SAFE_FREE(sshbind->gssapi_key_exchange_algs); ssh_key_free(sshbind->rsa); sshbind->rsa = NULL; @@ -463,6 +464,15 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) } session->common.log_verbosity = sshbind->common.log_verbosity; + session->opts.gssapi_key_exchange = sshbind->gssapi_key_exchange; + + if (sshbind->gssapi_key_exchange_algs != NULL) { + session->opts.gssapi_key_exchange_algs = strdup(sshbind->gssapi_key_exchange_algs); + if (session->opts.gssapi_key_exchange_algs == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } if (sshbind->banner != NULL) { session->server_opts.custombanner = strdup(sshbind->banner); diff --git a/src/client.c b/src/client.c index 97badc12..4dc5975b 100644 --- a/src/client.c +++ b/src/client.c @@ -38,6 +38,7 @@ #include "libssh/socket.h" #include "libssh/session.h" #include "libssh/dh.h" +#include "libssh/dh-gss.h" #ifdef WITH_GEX #include "libssh/dh-gex.h" #endif /* WITH_GEX */ @@ -266,7 +267,13 @@ int dh_handshake(ssh_session session) switch (session->dh_handshake_state) { case DH_STATE_INIT: - switch (session->next_crypto->kex_type) { + switch(session->next_crypto->kex_type){ +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = ssh_client_gss_dh_init(session); + break; +#endif case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: diff --git a/src/config.c b/src/config.c index d038c382..6edbcfdf 100644 --- a/src/config.c +++ b/src/config.c @@ -155,6 +155,8 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { {"xauthlocation", SOC_NA, true}, {"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true}, {"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true}, + {"gssapikeyexchange", SOC_GSSAPIKEYEXCHANGE, true}, + {"gssapikexalgorithms", SOC_GSSAPIKEXALGORITHMS, true}, {NULL, SOC_UNKNOWN, false}, }; @@ -1557,6 +1559,23 @@ static int ssh_config_parse_line_internal(ssh_session session, ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); } break; + case SOC_GSSAPIKEYEXCHANGE: { + bool b = false; + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + bool b = (i == 1) ? true : false; + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b); + } + break; + } + case SOC_GSSAPIKEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p); + } + break; case SOC_REQUIRED_RSA_SIZE: l = ssh_config_get_long(&s, -1); CHECK_COND_OR_FAIL(l < 0, "Invalid argument"); diff --git a/src/dh-gss.c b/src/dh-gss.c new file mode 100644 index 00000000..cbedba21 --- /dev/null +++ b/src/dh-gss.c @@ -0,0 +1,439 @@ +/* + * dh-gss.c - diffie-hellman GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include "libssh/gssapi.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/dh-gss.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply); + +static ssh_packet_callback gss_dh_client_callbacks[]= { + ssh_packet_client_gss_dh_reply +}; + +static struct ssh_packet_callbacks_struct ssh_gss_dh_client_callbacks = { + .start = SSH2_MSG_KEXGSS_COMPLETE, + .n_callbacks = 1, + .callbacks = gss_dh_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief Starts gssapi key exchange + */ +int ssh_client_gss_dh_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; + gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ + OM_uint32 maj_stat, min_stat; + const char *gss_host = session->opts.host; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + + 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; + } + + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + goto error; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + goto error; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + maj_stat = ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, &oflags); + gss_release_oid_set(&min_stat, &selected); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "Initializing gssapi context", + maj_stat, + min_stat); + goto error; + } + 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; + } + + rc = ssh_buffer_pack(session->out_buffer, "bdPB", + SSH2_MSG_KEXGSS_INIT, + output_token.length, + (size_t)output_token.length, + output_token.value, + pubkey); + if (rc != SSH_OK) { + goto error; + } + 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); + 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 + ssh_dh_cleanup(crypto); + return SSH_ERROR; +} + +void ssh_client_gss_dh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_dh_client_callbacks); +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_dh_reply){ + struct ssh_crypto_struct *crypto=session->next_crypto; + ssh_string pubkey_blob = NULL, mic = NULL, otoken = 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; + OM_uint32 oflags; + OM_uint32 maj_stat; + + (void)type; + (void)user; + + ssh_client_gss_dh_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, + "BSbS", + &server_pubkey, + &mic, + &b, + &otoken); + if (rc == SSH_ERROR) { + goto error; + } + session->gssapi_key_exchange_mic = mic; + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + maj_stat = ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, &oflags); + if (maj_stat != GSS_S_COMPLETE) { + 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); + 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"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + 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 gss_dh_server_callbacks[] = { + ssh_packet_server_gss_dh_init, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_dh_server_callbacks = { + .start = SSH2_MSG_KEXGSS_INIT, + .n_callbacks = 1, + .callbacks = gss_dh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the gssapi kex callbacks + */ +void ssh_server_gss_dh_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); +} + +/** @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) +{ + 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 */ + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + ssh_string otoken = NULL; + OM_uint32 maj_stat, min_stat; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags=0; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + char hostname[NI_MAXHOST] = {0}; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + rc = ssh_buffer_unpack(packet, "S", &otoken); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No token in client request"); + goto error; + } + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + + rc = ssh_buffer_unpack(packet, "B", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No e number in client request"); + goto error; + } + + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, client_pubkey); + 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_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + 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"); + goto error; + } + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + 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; + } + + rc = gethostname(hostname, 64); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto error; + } + + rc = ssh_gssapi_import_name(session->gssapi, hostname); + if (rc != SSH_OK) { + goto error; + } + + maj_stat = gss_acquire_cred(&min_stat, session->gssapi->client.server_name, 0, + GSS_C_NO_OID_SET, GSS_C_ACCEPT, + &session->gssapi->server_creds, NULL, NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_TRACE, + "acquiring credentials", + maj_stat, + min_stat); + goto error; + } + + maj_stat = gss_accept_sec_context(&min_stat, &session->gssapi->ctx, session->gssapi->server_creds, + &input_token, GSS_C_NO_CHANNEL_BINDINGS, &client_name, NULL /*mech_oid*/, &output_token, &ret_flags, + NULL /*time*/, &session->gssapi->client_creds); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token failed", + maj_stat, + min_stat); + goto error; + } + SSH_STRING_FREE(otoken); + gss_release_name(&min_stat, &client_name); + if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, "GSSAPI(accept) integrity and mutual flags were not set"); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "token accepted"); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &msg, + &mic); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "creating mic failed", + maj_stat, + min_stat); + goto error; + } + + + rc = ssh_buffer_pack(session->out_buffer, + "bBdPbdP", + SSH2_MSG_KEXGSS_COMPLETE, + server_pubkey, + mic.length, + (size_t)mic.length, + mic.value, + 1, + 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); + goto error; + } + + gss_release_buffer(&min_stat, &output_token); + gss_release_buffer(&min_stat, &mic); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_COMPLETE"); + + session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_OK; +error: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif + + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @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){ + (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); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/dh.c b/src/dh.c index e19e43d1..b688ca85 100644 --- a/src/dh.c +++ b/src/dh.c @@ -26,6 +26,10 @@ #include "config.h" #include +#ifdef WITH_GSSAPI +#include +#include "libssh/gssapi.h" +#endif #include "libssh/priv.h" #include "libssh/crypto.h" @@ -36,6 +40,7 @@ #include "libssh/ssh2.h" #include "libssh/pki.h" #include "libssh/bignum.h" +#include "libssh/string.h" static unsigned char p_group1_value[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, diff --git a/src/dh_crypto.c b/src/dh_crypto.c index a646e856..647e3bdd 100644 --- a/src/dh_crypto.c +++ b/src/dh_crypto.c @@ -424,9 +424,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) break; case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); break; case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); break; case SSH_KEX_DH_GROUP18_SHA512: diff --git a/src/dh_key.c b/src/dh_key.c index d9743ceb..19785a9a 100644 --- a/src/dh_key.c +++ b/src/dh_key.c @@ -253,9 +253,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) break; case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); break; case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); break; case SSH_KEX_DH_GROUP18_SHA512: diff --git a/src/gssapi.c b/src/gssapi.c index 0d122750..f4addd64 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -23,6 +23,7 @@ #include #include +#include #ifdef HAVE_UNISTD_H #include #endif @@ -37,6 +38,9 @@ #include #include #include +#include + +static gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; /** @internal * @initializes a gssapi context for authentication @@ -110,7 +114,6 @@ ssh_gssapi_free(ssh_session session) gss_release_oid(&min, &session->gssapi->client.oid); gss_delete_sec_context(&min, &session->gssapi->ctx, GSS_C_NO_BUFFER); - SAFE_FREE(session->gssapi->mech.elements); SAFE_FREE(session->gssapi->canonic_user); SAFE_FREE(session->gssapi); } @@ -147,6 +150,45 @@ static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) #ifdef WITH_SERVER +/** @internal + * @brief get all the oids server supports + * @param[out] selected OID set of supported oids + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int +ssh_gssapi_server_oids(gss_OID_set *selected) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported; /* oids supported by server */ + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i=0; i < supported->count; ++i){ + ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length); + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, spnego_oid.elements, supported->elements[i].length) == 0) { + SAFE_FREE(ptr); + continue; + } + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + SAFE_FREE(ptr); + } + + *selected = supported; + + return SSH_OK; +} + /** @internal * @brief handles an user authentication using GSSAPI */ @@ -154,12 +196,9 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids) { - char service_name[] = "host"; - gss_buffer_desc name_buf; - gss_name_t server_name; /* local server fqdn */ + char hostname[NI_MAXHOST] = {0}; OM_uint32 maj_stat, min_stat; size_t i; - char *ptr = NULL; gss_OID_set supported; /* oids supported by server */ gss_OID_set both_supported; /* oids supported by both client and server */ gss_OID_set selected; /* oid selected for authentication */ @@ -167,12 +206,22 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, size_t oid_count=0; struct gss_OID_desc_struct oid; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + rc = gethostname(hostname, 64); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } /* Destroy earlier GSSAPI context if any */ ssh_gssapi_free(session); rc = ssh_gssapi_init(session); - if (rc == SSH_ERROR) + if (rc == SSH_ERROR) { return rc; + } /* Callback should select oid and acquire credential */ if (ssh_callbacks_exists(session->server_callbacks, @@ -197,23 +246,13 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, /* Default implementation for selecting oid and acquiring credential */ gss_create_empty_oid_set(&min_stat, &both_supported); - maj_stat = gss_indicate_mechs(&min_stat, &supported); - if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_DEBUG, "indicate mechs %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "indicate mechs", - maj_stat, - min_stat); - gss_release_oid_set(&min_stat, &both_supported); + /* Get the server supported oids */ + rc = ssh_gssapi_server_oids(&supported); + if (rc != SSH_OK) { return SSH_ERROR; } - for (i=0; i < supported->count; ++i){ - ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length); - SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); - free(ptr); - } - + /* Loop through client supported oids */ for (i=0 ; i< n_oid ; ++i){ unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); size_t len = ssh_string_len(oids[i]); @@ -225,8 +264,10 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); continue; } + /* Convert oid from string to gssapi format */ oid.elements = &oid_s[2]; oid.length = len - 2; + /* Check if this client oid is supported by server */ gss_test_oid_set_member(&min_stat,&oid,supported,&present); if(present){ gss_add_oid_set_member(&min_stat,&oid,&both_supported); @@ -241,28 +282,18 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, return SSH_OK; } - name_buf.value = service_name; - name_buf.length = strlen(name_buf.value) + 1; - maj_stat = gss_import_name(&min_stat, &name_buf, - (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name); - if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "importing name", - maj_stat, - min_stat); + rc = ssh_gssapi_import_name(session->gssapi, hostname); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); gss_release_oid_set(&min_stat, &both_supported); - return -1; + return SSH_ERROR; } - maj_stat = gss_acquire_cred(&min_stat, server_name, 0, + maj_stat = gss_acquire_cred(&min_stat, session->gssapi->client.server_name, 0, both_supported, GSS_C_ACCEPT, &session->gssapi->server_creds, &selected, NULL); - gss_release_name(&min_stat, &server_name); gss_release_oid_set(&min_stat, &both_supported); - if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_TRACE, "error acquiring credentials %d, %d", maj_stat, min_stat); ssh_gssapi_log_error(SSH_LOG_TRACE, "acquiring creds", maj_stat, @@ -270,8 +301,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, ssh_auth_reply_default(session,0); return SSH_ERROR; } - - SSH_LOG(SSH_LOG_DEBUG, "acquiring credentials %d, %d", maj_stat, min_stat); + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); /* finding which OID from client we selected */ for (i=0 ; i< n_oid ; ++i){ @@ -293,17 +323,8 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, break; } } - session->gssapi->mech.length = oid.length; - session->gssapi->mech.elements = malloc(oid.length); - if (session->gssapi->mech.elements == NULL){ - ssh_set_error_oom(session); - gss_release_oid_set(&min_stat, &selected); - return SSH_ERROR; - } - memcpy(session->gssapi->mech.elements, oid.elements, oid.length); gss_release_oid_set(&min_stat, &selected); session->gssapi->user = strdup(user); - session->gssapi->service = service_name; session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; return ssh_gssapi_send_response(session, oids[i]); } @@ -381,7 +402,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server) return SSH_PACKET_USED; } hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); - SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s", hexa); SAFE_FREE(hexa); input_token.length = ssh_string_len(token); input_token.value = ssh_string_data(token); @@ -400,7 +421,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server) } if (GSS_ERROR(maj_stat)){ ssh_gssapi_log_error(SSH_LOG_DEBUG, - "Gssapi error", + "accepting token failed", maj_stat, min_stat); gss_release_buffer(&min_stat, &output_token); @@ -626,9 +647,185 @@ fail: return SSH_ERROR; } -/** @brief returns the OIDs of the mechs that have usable credentials +/** @internal + * @brief Get the base64 encoding of md5 of the oid to add as suffix to GSSAPI + * key exchange algorithms. + * + * @param[in] oid The OID as a ssh_string + * + * @returns the hash or NULL on error */ -static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) +char * +ssh_gssapi_oid_hash(ssh_string oid) +{ + MD5CTX ctx = NULL; + unsigned char *h = NULL; + int rc; + char *base64 = NULL; + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + return NULL; + } + + ctx = md5_init(); + if (ctx == NULL) { + SAFE_FREE(h); + return NULL; + } + + rc = md5_update(ctx, + ssh_string_data(oid), + ssh_string_len(oid)); + if (rc != SSH_OK) { + SAFE_FREE(h); + md5_ctx_free(ctx); + return NULL; + } + rc = md5_final(h, ctx); + if (rc != SSH_OK) { + SAFE_FREE(h); + return NULL; + } + + base64 = (char *)bin_to_base64(h, 16); + SAFE_FREE(h); + return base64; +} + +/** @internal + * @brief Check if client has GSSAPI mechanisms configured + * + * @param[in] session The SSH session + * + * @returns SSH_OK if any one of the mechanisms is configured or NULL + */ +int +ssh_gssapi_check_client_config(ssh_session session) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported = GSS_C_NO_OID_SET; + gss_name_t client_id = GSS_C_NO_NAME; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + struct ssh_gssapi_struct *gssapi = NULL; + int ret = SSH_ERROR; + gss_OID_set one_oidset = GSS_C_NO_OID_SET; + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i = 0; i < supported->count; ++i){ + gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + gssapi->server_creds = GSS_C_NO_CREDENTIAL; + gssapi->client_creds = GSS_C_NO_CREDENTIAL; + gssapi->ctx = GSS_C_NO_CONTEXT; + gssapi->state = SSH_GSSAPI_STATE_NONE; + + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, spnego_oid.elements, supported->elements[i].length) == 0) { + ret = SSH_ERROR; + goto end; + } + + gss_create_empty_oid_set(&min_stat, &one_oidset); + gss_add_oid_set_member(&min_stat, &supported->elements[i], &one_oidset); + + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, &namebuf, + GSS_C_NT_USER_NAME, &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, client_id, GSS_C_INDEFINITE, + one_oidset, GSS_C_INITIATE, + &gssapi->client.creds, + NULL, NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ret = ssh_gssapi_import_name(gssapi, session->opts.host); + if (ret != SSH_OK) { + goto end; + } + + maj_stat = ssh_gssapi_init_ctx(gssapi, &input_token, &output_token, &oflags); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "initializing context", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length); + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + free(ptr); + + /* If any one is configured then return successfully */ + ret = SSH_OK; + +end: + if (ret == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, "GSSAPI not configured correctly"); + } + SAFE_FREE(gssapi->user); + + gss_release_oid_set(&min_stat, &one_oidset); + + gss_release_name(&min_stat, &gssapi->client.server_name); + gss_release_cred(&min_stat,&gssapi->server_creds); + gss_release_cred(&min_stat,&gssapi->client.creds); + gss_release_oid(&min_stat, &gssapi->client.oid); + gss_release_buffer(&min_stat, &output_token); + gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER); + + SAFE_FREE(gssapi->canonic_user); + SAFE_FREE(gssapi); + + if (ret == SSH_OK) { + break; + } + } + gss_release_oid_set(&min_stat, &supported); + + return ret; +} + +/** @internal + * @brief acquires a credential and returns a set of mechanisms for which it is + * valid + * + * @param[in] session The SSH session + * @param[out] valid_oids The set of OIDs for which the credential is valid + * + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids) { OM_uint32 maj_stat, min_stat, lifetime; gss_OID_set actual_mechs = GSS_C_NO_OID_SET; @@ -675,6 +872,7 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) goto end; } } + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); gss_create_empty_oid_set(&min_stat, valid_oids); @@ -702,6 +900,187 @@ end: return ret; } +/** @internal + * @brief Add suffixes of oid hash to each GSSAPI key exchange algorithm + * @param[in] session current session handler + * @returns string suffixed kex algorithms or NULL on error + */ +char * +ssh_gssapi_kex_mechs(ssh_session session, const char *gss_algs) +{ + size_t i,j; + gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + struct ssh_tokens_st *algs = NULL; + char *oid_hash = NULL; + char *new_gss_algs = NULL; + char gss_kex_algs[8000] = {0}; + OM_uint32 min_stat; + size_t offset = 0; + + /* Get supported oids */ + if (session->server) { +#ifdef WITH_SERVER + rc = ssh_gssapi_server_oids(&selected); + if (rc == SSH_ERROR) { + return NULL; + } +#endif + } else { + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + return NULL; + } + } + ssh_gssapi_free(session); + + n_oids = selected->count; + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + /* Check if algorithms are valid */ + new_gss_algs = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, gss_algs); + if (gss_algs == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI key exchange algorithms not supported or invalid"); + rc = SSH_ERROR; + goto out; + } + + algs = ssh_tokenize(new_gss_algs, ','); + if (algs == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't tokenize GSSAPI key exchange algs"); + rc = SSH_ERROR; + goto out; + } + for (i=0; ielements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, selected->elements[i].elements, + selected->elements[i].length); + + /* Get the algorithm suffix */ + oid_hash = ssh_gssapi_oid_hash(oids[i]); + if (oid_hash == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + /* For each oid loop through the algorithms, append the oid and append + * the algorithms to a string */ + for (j = 0; algs->tokens[j]; j++) { + if (sizeof(gss_kex_algs) < offset) { + ssh_set_error(session, + SSH_FATAL, + "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + rc = snprintf(&gss_kex_algs[offset], sizeof(gss_kex_algs)-offset, "%s%s,", algs->tokens[j], oid_hash); + if (rc < 0 || rc >= (ssize_t)sizeof(gss_kex_algs)) { + ssh_set_error(session, + SSH_FATAL, + "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + /* + 1 for ',' */ + offset += strlen(algs->tokens[j]) + strlen(oid_hash) + 1; + } + SAFE_FREE(oid_hash); + SSH_STRING_FREE(oids[i]); + } + + rc = SSH_OK; + +out: + SAFE_FREE(oid_hash); + SAFE_FREE(oids); + SAFE_FREE(new_gss_algs); + gss_release_oid_set(&min_stat, &selected); + ssh_tokens_free(algs); + + if (rc != SSH_OK) { + return NULL; + } + + return strdup(gss_kex_algs); +} + +int +ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host) +{ + gss_buffer_desc hostname; + char name_buf[256] = {0}; + OM_uint32 maj_stat, min_stat; + + /* import target host name */ + snprintf(name_buf, sizeof(name_buf), "host@%s", host); + + hostname.value = name_buf; + hostname.length = strlen(name_buf) + 1; + maj_stat = gss_import_name(&min_stat, + &hostname, + (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, + &gssapi->client.server_name); + SSH_LOG(SSH_LOG_DEBUG, "importing name: %s", name_buf); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "error importing name", + maj_stat, + min_stat); + } + + return maj_stat; +} + +OM_uint32 +ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags) +{ + OM_uint32 maj_stat, min_stat; + + maj_stat = gss_init_sec_context(&min_stat, + gssapi->client.creds, + &gssapi->ctx, + gssapi->client.server_name, + gssapi->client.oid, + gssapi->client.flags, + 0, + NULL, + input_token, + NULL, + output_token, + ret_flags, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "initializing gssapi context", + maj_stat, + min_stat); + } + return maj_stat; +} + /** * @brief launches a gssapi-with-mic auth request * @returns SSH_AUTH_ERROR: A serious error happened\n @@ -716,9 +1095,7 @@ int ssh_gssapi_auth_mic(ssh_session session) ssh_string *oids = NULL; int rc; size_t n_oids = 0; - OM_uint32 maj_stat, min_stat; - char name_buf[256] = {0}; - gss_buffer_desc hostname; + OM_uint32 min_stat; const char *gss_host = session->opts.host; /* Destroy earlier GSSAPI context if any */ @@ -731,20 +1108,9 @@ int ssh_gssapi_auth_mic(ssh_session session) if (session->opts.gss_server_identity != NULL) { gss_host = session->opts.gss_server_identity; } - /* import target host name */ - snprintf(name_buf, sizeof(name_buf), "host@%s", gss_host); - hostname.value = name_buf; - hostname.length = strlen(name_buf) + 1; - maj_stat = gss_import_name(&min_stat, &hostname, - (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, - &session->gssapi->client.server_name); - if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "importing name", - maj_stat, - min_stat); + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { return SSH_AUTH_DENIED; } @@ -757,7 +1123,7 @@ int ssh_gssapi_auth_mic(ssh_session session) SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi to host %s with user %s", session->opts.host, session->gssapi->user); - rc = ssh_gssapi_match(session, &selected); + rc = ssh_gssapi_client_identity(session, &selected); if (rc == SSH_ERROR) { return SSH_AUTH_DENIED; } @@ -869,22 +1235,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ session->gssapi->client.flags |= GSS_C_DELEG_FLAG; } - /* prepare the first TOKEN response */ - maj_stat = gss_init_sec_context(&min_stat, - session->gssapi->client.creds, - &session->gssapi->ctx, - session->gssapi->client.server_name, - session->gssapi->client.oid, - session->gssapi->client.flags, - 0, NULL, &input_token, NULL, - &output_token, NULL, NULL); - if(GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "Initializing gssapi context", - maj_stat, - min_stat); + maj_stat = ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + if (GSS_ERROR(maj_stat)){ goto error; } + if (output_token.length != 0){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); @@ -984,29 +1339,14 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client) hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); SAFE_FREE(hexa); + input_token.length = ssh_string_len(token); input_token.value = ssh_string_data(token); - maj_stat = gss_init_sec_context(&min_stat, - session->gssapi->client.creds, - &session->gssapi->ctx, - session->gssapi->client.server_name, - session->gssapi->client.oid, - session->gssapi->client.flags, - 0, NULL, &input_token, NULL, - &output_token, NULL, NULL); - - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "accepting token", - maj_stat, - min_stat); - SSH_STRING_FREE(token); - if (GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "Gssapi error", - maj_stat, - min_stat); + maj_stat = ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + if (GSS_ERROR(maj_stat)) { goto error; } + SSH_STRING_FREE(token); if (output_token.length != 0) { hexa = ssh_get_hexa(output_token.value, output_token.length); diff --git a/src/kex.c b/src/kex.c index 5abb82f7..9fe8ffd5 100644 --- a/src/kex.c +++ b/src/kex.c @@ -49,6 +49,8 @@ #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" @@ -806,6 +808,32 @@ int ssh_set_client_kex(ssh_session session) ssh_set_error(session, SSH_FATAL, "PRNG error"); return SSH_ERROR; } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + char *gssapi_algs = NULL; + + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + ok = ssh_gssapi_import_name(session->gssapi, session->opts.host); + if (ok != SSH_OK) { + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session, session->opts.gssapi_key_exchange_algs ? session->opts.gssapi_key_exchange_algs : GSSAPI_KEY_EXCHANGE_SUPPORTED); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + + /* Prefix the default algorithms with gsskex algs */ + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(default_methods[SSH_KEX], gssapi_algs); + SAFE_FREE(gssapi_algs); + } +#endif /* Set the list of allowed algorithms in order of preference, if it hadn't * been set yet. */ @@ -910,6 +938,10 @@ kex_select_kex_type(const char *kex) { if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { return SSH_KEX_DH_GROUP1_SHA1; + } else if (strncmp(kex, "gss-group14-sha256-", 19) == 0) { + 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 (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { return SSH_KEX_DH_GROUP14_SHA1; } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { @@ -967,6 +999,12 @@ static void revert_kex_callbacks(ssh_session session) case SSH_KEX_DH_GROUP18_SHA512: ssh_client_dh_remove_callbacks(session); break; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: +#ifdef WITH_GSSAPI + ssh_client_gss_dh_remove_callbacks(session); +#endif /* WITH_GSSAPI */ + break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA256: @@ -1436,6 +1474,23 @@ int ssh_make_sessionid(ssh_session session) goto error; } + if (server_pubkey_blob == NULL && session->opts.gssapi_key_exchange) { + ssh_string_free(server_pubkey_blob); + server_pubkey_blob = ssh_string_new(0); + } + + if (session->server) { + switch (session->next_crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + ssh_string_free(server_pubkey_blob); + server_pubkey_blob = ssh_string_new(0); + break; + default: + break; + } + } + rc = ssh_buffer_pack(buf, "dPdPS", ssh_buffer_get_len(client_hash), @@ -1457,7 +1512,9 @@ int ssh_make_sessionid(ssh_session session) case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, NULL, &client_pubkey); @@ -1651,6 +1708,7 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->secret_hash); break; case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: @@ -1686,6 +1744,7 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->secret_hash); break; case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: case SSH_KEX_ECDH_SHA2_NISTP521: case SSH_KEX_SNTRUP761X25519_SHA512: diff --git a/src/options.c b/src/options.c index 3303631c..fec3676a 100644 --- a/src/options.c +++ b/src/options.c @@ -41,6 +41,11 @@ #include "libssh/priv.h" #include "libssh/session.h" #include +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/config_parser.h" +#include "libssh/gssapi.h" +#include "libssh/token.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/bind.h" @@ -560,6 +565,16 @@ int ssh_options_set_algo(ssh_session session, * Set it to specify that GSSAPI should delegate credentials * to the server (int, 0 = false). * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set to true to do GSSAPI key exchange (bool). + * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used (const char *, + * comma-separated list). ex: + * "gss-group14-sha256-,gss-group16-sha512-" + * These will prefix the default algorithms if + * SSH_OPTIONS_GSSAPI_KEY_EXCHANGE is true. + * * - SSH_OPTIONS_PASSWORD_AUTH * Set it if password authentication should be used * in ssh_userauth_auto_pubkey(). (int, 0=false). @@ -1240,6 +1255,40 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.gss_delegate_creds = (x & 0xff); } break; +#ifdef WITH_GSSAPI + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + rc = ssh_gssapi_check_client_config(session); + if (rc == SSH_OK) { + session->opts.gssapi_key_exchange = *x; + } else { + SSH_LOG(SSH_LOG_WARN, "Disabled GSSAPI key exchange"); + session->opts.gssapi_key_exchange = false; + } + } + break; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + /* Check if algorithms are supported */ + char *ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, v); + if (ret == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI key exchange algorithms not supported or invalid"); + return -1; + } + session->opts.gssapi_key_exchange_algs = ret; + } + break; +#endif case SSH_OPTIONS_PASSWORD_AUTH: case SSH_OPTIONS_PUBKEY_AUTH: case SSH_OPTIONS_KBDINT_AUTH: @@ -2277,6 +2326,14 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * Default is 3072 bits or 2048 bits in FIPS mode. * (int) * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set true to enable GSSAPI key exchange, + * false to disable GSSAPI key exchange. (bool) + * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used (const char *, + * comma-separated list). ex: + * "gss-group14-sha256-,gss-group16-sha512-" * * @param value The value to set. This is a generic pointer and the * datatype which should be used is described at the @@ -2674,6 +2731,34 @@ ssh_bind_options_set(ssh_bind sshbind, sshbind->rsa_min_size = *x; } break; +#ifdef WITH_GSSAPI + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->gssapi_key_exchange = *x; + } + break; + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + char *ret = NULL; + SAFE_FREE(sshbind->gssapi_key_exchange_algs); + ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, value); + if (ret == NULL) { + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "GSSAPI key exchange algorithms not supported or invalid"); + return -1; + } + sshbind->gssapi_key_exchange_algs = ret; + } + break; +#endif /* WITH_GSSAPI */ default: ssh_set_error(sshbind, SSH_REQUEST_DENIED, diff --git a/src/packet.c b/src/packet.c index 2ef44857..7081bb83 100644 --- a/src/packet.c +++ b/src/packet.c @@ -594,6 +594,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT * or session->auth.state == SSH_AUTH_STATE_AUTH_NONE_SENT * * Transitions: @@ -623,6 +624,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se (session->auth.state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) && (session->auth.state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) && (session->auth.state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT) && (session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) { rc = SSH_PACKET_DENIED; diff --git a/src/packet_cb.c b/src/packet_cb.c index 7dcbfabc..f0b597f8 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -27,6 +27,10 @@ #ifdef HAVE_ARPA_INET_H #include #endif +#ifdef WITH_GSSAPI +#include +#include "libssh/gssapi.h" +#endif #include "libssh/priv.h" #include "libssh/buffer.h" @@ -173,53 +177,79 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys) /* server things are done in server.c */ session->dh_handshake_state=DH_STATE_FINISHED; } else { - ssh_key server_key = NULL; + if (session->opts.gssapi_key_exchange) { +#ifdef WITH_GSSAPI + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; - /* client */ + mic.length = ssh_string_len(session->gssapi_key_exchange_mic); + mic.value = ssh_string_data(session->gssapi_key_exchange_mic); - /* Verify the host's signature. FIXME do it sooner */ - sig_blob = session->next_crypto->dh_server_signature; - session->next_crypto->dh_server_signature = NULL; + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; - /* get the server public key */ - server_key = ssh_dh_get_next_server_publickey(session); - if (server_key == NULL) { - goto error; - } - - rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); - if (rc != SSH_OK) { - goto error; - } - - /* Check if signature from server matches user preferences */ - if (session->opts.wanted_methods[SSH_HOSTKEYS]) { - rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS], - sig->type_c); - if (rc == 0) { + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &msg, + &mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { ssh_set_error(session, SSH_FATAL, - "Public key from server (%s) doesn't match user " - "preference (%s)", - sig->type_c, - session->opts.wanted_methods[SSH_HOSTKEYS]); + "Failed to verify mic after GSSAPI Key Exchange"); goto error; } - } + SSH_STRING_FREE(session->gssapi_key_exchange_mic); +#endif + } else { + ssh_key server_key = NULL; - rc = ssh_pki_signature_verify(session, - sig, - server_key, - session->next_crypto->secret_hash, - session->next_crypto->digest_len); - SSH_SIGNATURE_FREE(sig); - if (rc == SSH_ERROR) { - ssh_set_error(session, - SSH_FATAL, - "Failed to verify server hostkey signature"); - goto error; + /* client */ + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + server_key = ssh_dh_get_next_server_publickey(session); + if (server_key == NULL) { + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc != SSH_OK) { + goto error; + } + + /* Check if signature from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + sig->type_c); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + sig->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + goto error; + } + } + + rc = ssh_pki_signature_verify(session, + sig, + server_key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify server hostkey signature"); + goto error; + } } SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); diff --git a/src/server.c b/src/server.c index 9c1f734b..09e4d213 100644 --- a/src/server.c +++ b/src/server.c @@ -60,6 +60,7 @@ #include "libssh/options.h" #include "libssh/curve25519.h" #include "libssh/token.h" +#include "libssh/gssapi.h" #define set_status(session, status) do {\ if (session->common.callbacks && session->common.callbacks->connect_status_function) \ @@ -98,6 +99,9 @@ int server_set_kex(ssh_session session) enum ssh_keytypes_e keytype; size_t len; int ok; +#ifdef WITH_GSSAPI + char *gssapi_algs = NULL; +#endif /* WITH_GSSAPI */ /* Skip if already set, for example for the rekey or when we do the guessing * it could have been already used to make some protocol decisions. */ @@ -169,6 +173,28 @@ int server_set_kex(ssh_session session) return -1; } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session, session->opts.gssapi_key_exchange_algs ? session->opts.gssapi_key_exchange_algs : GSSAPI_KEY_EXCHANGE_SUPPORTED); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + ssh_gssapi_free(session); + + /* Prefix the default algorithms with gsskex algs */ + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(ssh_kex_get_default_methods(SSH_KEX), gssapi_algs); + + SAFE_FREE(gssapi_algs); + } +#endif /* WITH_GSSAPI */ + for (i = 0; i < SSH_KEX_METHODS; i++) { wanted = session->opts.wanted_methods[i]; if (wanted == NULL) { diff --git a/src/session.c b/src/session.c index 8f41e3f1..7b48a562 100644 --- a/src/session.c +++ b/src/session.c @@ -325,6 +325,7 @@ void ssh_free(ssh_session session) #ifdef WITH_GSSAPI ssh_gssapi_free(session); + SAFE_FREE(session->opts.gssapi_key_exchange_algs); #endif /* options */ @@ -470,8 +471,12 @@ const char* ssh_get_kex_algo(ssh_session session) { return "diffie-hellman-group14-sha1"; case SSH_KEX_DH_GROUP14_SHA256: return "diffie-hellman-group14-sha256"; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + return "gss-group14-sha256-"; case SSH_KEX_DH_GROUP16_SHA512: return "diffie-hellman-group16-sha512"; + case SSH_GSS_KEX_DH_GROUP16_SHA512: + return "gss-group16-sha512-"; case SSH_KEX_DH_GROUP18_SHA512: return "diffie-hellman-group18-sha512"; case SSH_KEX_ECDH_SHA2_NISTP256: diff --git a/src/wrapper.c b/src/wrapper.c index 0943576c..3506f28f 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -54,6 +54,7 @@ #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 }, @@ -587,6 +588,12 @@ int crypt_set_algorithms_server(ssh_session session){ case SSH_KEX_DH_GROUP18_SHA512: ssh_server_dh_init(session); break; +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + ssh_server_gss_dh_init(session); + break; +#endif /* WITH_GSSAPI */ #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA256: diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index a7990f6b..02a5a69e 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -45,7 +45,8 @@ endif() if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) set(LIBSSH_CLIENT_TESTS ${LIBSSH_CLIENT_TESTS} - torture_gssapi_auth) + torture_gssapi_auth + torture_gssapi_key_exchange) endif() if (DEFAULT_C_NO_DEPRECATION_FLAGS) diff --git a/tests/client/torture_gssapi_key_exchange.c b/tests/client/torture_gssapi_key_exchange.c new file mode 100644 index 00000000..d859f2e0 --- /dev/null +++ b/tests/client/torture_gssapi_key_exchange.c @@ -0,0 +1,258 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include "libssh/crypto.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_gssapi_key_exchange(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* 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_connect(session); + assert_int_equal(rc, 0); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_key_exchange_no_tgt(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Don't run kinit */ + 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", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, 0); + + 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); + + 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; + + /* 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_int_equal(rc, 0); + + assert_int_equal(session->current_crypto->kex_type, SSH_GSS_KEX_DH_GROUP14_SHA256); + + torture_teardown_kdc_server(state); +} + +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; + + /* 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_int_equal(rc, 0); + + assert_true(session->current_crypto->kex_type == SSH_GSS_KEX_DH_GROUP16_SHA512); + + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_key_exchange_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* 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_connect(session); + assert_int_equal(rc, 0); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server(state); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + pthread_exit((void *)&rc); +} diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index a368c512..d6c14952 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -19,7 +19,8 @@ if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) ${LIBSSH_SERVER_TESTS} torture_gssapi_server_auth torture_gssapi_server_auth_cb - torture_gssapi_server_delegation) + torture_gssapi_server_delegation + torture_gssapi_server_key_exchange) endif() include_directories(${libssh_SOURCE_DIR}/include diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c index cf153c26..8c9f79f2 100644 --- a/tests/server/test_server/main.c +++ b/tests/server/test_server/main.c @@ -295,6 +295,7 @@ static int init_server_state(struct server_state_st *state, } state->parse_global_config = arguments->with_global_config; + state->gssapi_key_exchange_algs = NULL; if (arguments->config_file) { state->config_file = arguments->config_file; diff --git a/tests/server/test_server/test_server.c b/tests/server/test_server/test_server.c index 295af3ed..bf215a57 100644 --- a/tests/server/test_server/test_server.c +++ b/tests/server/test_server/test_server.c @@ -194,6 +194,30 @@ int run_server(struct server_state_st *state) goto out; } +#ifdef WITH_GSSAPI + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + &(state->gssapi_key_exchange)); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + if (state->gssapi_key_exchange_algs != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + state->gssapi_key_exchange_algs); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } +#endif /* WITH_GSSAPI */ + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &(state->port)); diff --git a/tests/server/test_server/test_server.h b/tests/server/test_server/test_server.h index 7c6bcb76..b4e17f69 100644 --- a/tests/server/test_server/test_server.h +++ b/tests/server/test_server/test_server.h @@ -53,6 +53,8 @@ struct server_state_st { bool parse_global_config; char *log_file; + bool gssapi_key_exchange; + const char *gssapi_key_exchange_algs; /* State */ int max_tries; diff --git a/tests/server/torture_gssapi_server_auth.c b/tests/server/torture_gssapi_server_auth.c index 82d45892..8058d4d0 100644 --- a/tests/server/torture_gssapi_server_auth.c +++ b/tests/server/torture_gssapi_server_auth.c @@ -160,6 +160,7 @@ setup_default_server(void **state) ss = tss->ss; s = tss->state; + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); /* Start the server using the default values */ pid = fork_run_server(ss, free_test_server_state, &tss); if (pid < 0) { @@ -335,7 +336,7 @@ torture_gssapi_server_auth_invalid_host(void **state) "echo bar | kinit alice"); rc = ssh_userauth_gssapi(session); - assert_int_equal(rc, SSH_AUTH_ERROR); + assert_int_equal(rc, SSH_AUTH_DENIED); torture_teardown_kdc_server((void **)&s); } diff --git a/tests/server/torture_gssapi_server_auth_cb.c b/tests/server/torture_gssapi_server_auth_cb.c index 290ae359..381af800 100644 --- a/tests/server/torture_gssapi_server_auth_cb.c +++ b/tests/server/torture_gssapi_server_auth_cb.c @@ -238,6 +238,7 @@ setup_callback_server(void **state) ss->server_cb->gssapi_verify_mic_function = verify_mic; ss->server_cb->userdata = &sdata; + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); /* Start the server using the default values */ pid = fork_run_server(ss, free_test_server_state, &tss); if (pid < 0) { diff --git a/tests/server/torture_gssapi_server_delegation.c b/tests/server/torture_gssapi_server_delegation.c index 65e52d6f..dd3ad335 100644 --- a/tests/server/torture_gssapi_server_delegation.c +++ b/tests/server/torture_gssapi_server_delegation.c @@ -185,6 +185,7 @@ setup_callback_server(void **state) ss->server_cb->auth_gssapi_mic_function = auth_gssapi_mic; ss->server_cb->userdata = &sdata; + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); /* Start the server using the default values */ pid = fork_run_server(ss, free_test_server_state, &tss); if (pid < 0) { diff --git a/tests/server/torture_gssapi_server_key_exchange.c b/tests/server/torture_gssapi_server_key_exchange.c new file mode 100644 index 00000000..ed255ac4 --- /dev/null +++ b/tests/server/torture_gssapi_server_key_exchange.c @@ -0,0 +1,338 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-,gss-group16-sha512-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + + +static void +torture_gssapi_server_key_exchange(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + bool t = true; + + 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); + fprintf(stderr, "%s", ssh_get_error(session)); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + pthread_exit((void *)&rc); +} diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 96abc794..00dd9055 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -93,7 +93,8 @@ extern LIBSSH_THREAD int ssh_log_level; "\tGSSAPIServerIdentity example.com\n" \ "\tGSSAPIClientIdentity home.sweet\n" \ "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n" \ - "\tRequiredRSASize 2233\n" + "\tRequiredRSASize 2233\n" \ + "\tGSSAPIKexAlgorithms gss-group14-sha256-\n" /* authentication methods */ #define LIBSSH_TESTCONFIG_STRING8 \ @@ -648,6 +649,9 @@ static void torture_config_new(void ** state, assert_int_equal(session->opts.gss_delegate_creds, 1); assert_string_equal(session->opts.gss_server_identity, "example.com"); assert_string_equal(session->opts.gss_client_identity, "home.sweet"); +#ifdef WITH_GSSAPI + assert_string_equal(session->opts.gssapi_key_exchange_algs, "gss-group14-sha256-"); +#endif /* WITH_GSSAPI */ assert_int_equal(ssh_get_log_level(), SSH_LOG_TRACE); assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); diff --git a/tests/valgrind.supp b/tests/valgrind.supp index 3c5d2c79..f518a18e 100644 --- a/tests/valgrind.supp +++ b/tests/valgrind.supp @@ -255,6 +255,70 @@ ## libkrb5 # krb5_mcc_generate_new allocates a hashtab on a static global variable # It doesn't get freed. +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_load_cache_lookup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + { Reachable memory from libkrb5 Memcheck:Leak