mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-02-04 12:20:42 +09:00
feat: add gssapi key exchange
feat: add generic functions for importing name and initializing ctx feat: add suffix to gsskex algs dynamically feat: move gssapi key exchange to another file feat: add gssapi key exchange for server refactor: remove unnecessary fields in gssapi struct refactor: add some documentation and improve logging fix: remove gss_dh callbacks feat: add a check to see if GSSAPI is configured correctly fix: memory leaks feat: add client side "gssapi-keyex" auth feat: add gssapi_key_exchange_algs for server fix: some memory issues feat: add gssapi kex options to config feat: add check to see if GSSAPI key exchange was performed feat: add more tests for gssapi key exchange fix: add valgrind supp Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com> Reviewed-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
This commit is contained in:
committed by
Jakub Jelen
parent
701a2155a7
commit
bc5211d055
@@ -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)
|
||||
|
||||
|
||||
135
src/auth.c
135
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;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
10
src/bind.c
10
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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
19
src/config.c
19
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");
|
||||
|
||||
439
src/dh-gss.c
Normal file
439
src/dh-gss.c
Normal file
@@ -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 <xaerru@gmail.com>
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <gssapi/gssapi.h>
|
||||
#include <errno.h>
|
||||
#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 */
|
||||
5
src/dh.c
5
src/dh.c
@@ -26,6 +26,10 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef WITH_GSSAPI
|
||||
#include <gssapi/gssapi.h>
|
||||
#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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
540
src/gssapi.c
540
src/gssapi.c
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -37,6 +38,9 @@
|
||||
#include <libssh/callbacks.h>
|
||||
#include <libssh/string.h>
|
||||
#include <libssh/server.h>
|
||||
#include <libssh/token.h>
|
||||
|
||||
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; i<n_oids; ++i){
|
||||
oids[i] = ssh_string_new(selected->elements[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);
|
||||
|
||||
59
src/kex.c
59
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:
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
#include "libssh/priv.h"
|
||||
#include "libssh/session.h"
|
||||
#include <sys/types.h>
|
||||
#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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
108
src/packet_cb.c
108
src/packet_cb.c
@@ -27,6 +27,10 @@
|
||||
#ifdef HAVE_ARPA_INET_H
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#ifdef WITH_GSSAPI
|
||||
#include <gssapi/gssapi.h>
|
||||
#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");
|
||||
|
||||
|
||||
26
src/server.c
26
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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user