You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1966 lines
62 KiB
1966 lines
62 KiB
4 weeks ago
|
diff --color -ruNp a/gss-genr.c b/gss-genr.c
|
||
|
--- a/gss-genr.c 2024-05-16 15:49:43.999411060 +0200
|
||
|
+++ b/gss-genr.c 2024-06-26 12:17:55.586856954 +0200
|
||
|
@@ -346,6 +346,7 @@ ssh_gssapi_build_ctx(Gssctxt **ctx)
|
||
|
(*ctx)->creds = GSS_C_NO_CREDENTIAL;
|
||
|
(*ctx)->client = GSS_C_NO_NAME;
|
||
|
(*ctx)->client_creds = GSS_C_NO_CREDENTIAL;
|
||
|
+ (*ctx)->first = 1;
|
||
|
}
|
||
|
|
||
|
/* Delete our context, providing it has been built correctly */
|
||
|
@@ -371,6 +372,12 @@ ssh_gssapi_delete_ctx(Gssctxt **ctx)
|
||
|
gss_release_name(&ms, &(*ctx)->client);
|
||
|
if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL)
|
||
|
gss_release_cred(&ms, &(*ctx)->client_creds);
|
||
|
+ sshbuf_free((*ctx)->shared_secret);
|
||
|
+ sshbuf_free((*ctx)->server_pubkey);
|
||
|
+ sshbuf_free((*ctx)->server_host_key_blob);
|
||
|
+ sshbuf_free((*ctx)->server_blob);
|
||
|
+ explicit_bzero((*ctx)->hash, sizeof((*ctx)->hash));
|
||
|
+ BN_clear_free((*ctx)->dh_client_pub);
|
||
|
|
||
|
free(*ctx);
|
||
|
*ctx = NULL;
|
||
|
diff --color -ruNp a/kexgssc.c b/kexgssc.c
|
||
|
--- a/kexgssc.c 2024-05-16 15:49:43.820407648 +0200
|
||
|
+++ b/kexgssc.c 2024-07-02 16:26:25.628746744 +0200
|
||
|
@@ -47,566 +47,658 @@
|
||
|
|
||
|
#include "ssh-gss.h"
|
||
|
|
||
|
-int
|
||
|
-kexgss_client(struct ssh *ssh)
|
||
|
+static int input_kexgss_hostkey(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgss_continue(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgss_complete(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgss_error(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgssgex_group(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgssgex_continue(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgssgex_complete(int, u_int32_t, struct ssh *);
|
||
|
+
|
||
|
+static int
|
||
|
+kexgss_final(struct ssh *ssh)
|
||
|
{
|
||
|
struct kex *kex = ssh->kex;
|
||
|
- gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER,
|
||
|
- recv_tok = GSS_C_EMPTY_BUFFER,
|
||
|
- gssbuf, msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr;
|
||
|
- Gssctxt *ctxt;
|
||
|
- OM_uint32 maj_status, min_status, ret_flags;
|
||
|
- struct sshbuf *server_blob = NULL;
|
||
|
- struct sshbuf *shared_secret = NULL;
|
||
|
- struct sshbuf *server_host_key_blob = NULL;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
struct sshbuf *empty = NULL;
|
||
|
- u_char *msg;
|
||
|
- int type = 0;
|
||
|
- int first = 1;
|
||
|
+ struct sshbuf *shared_secret = NULL;
|
||
|
u_char hash[SSH_DIGEST_MAX_LENGTH];
|
||
|
size_t hashlen;
|
||
|
- u_char c;
|
||
|
int r;
|
||
|
|
||
|
- /* Initialise our GSSAPI world */
|
||
|
- ssh_gssapi_build_ctx(&ctxt);
|
||
|
- if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type)
|
||
|
- == GSS_C_NO_OID)
|
||
|
- fatal("Couldn't identify host exchange");
|
||
|
-
|
||
|
- if (ssh_gssapi_import_name(ctxt, kex->gss_host))
|
||
|
- fatal("Couldn't import hostname");
|
||
|
-
|
||
|
- if (kex->gss_client &&
|
||
|
- ssh_gssapi_client_identity(ctxt, kex->gss_client))
|
||
|
- fatal("Couldn't acquire client credentials");
|
||
|
-
|
||
|
- /* Step 1 */
|
||
|
- switch (kex->kex_type) {
|
||
|
- case KEX_GSS_GRP1_SHA1:
|
||
|
- case KEX_GSS_GRP14_SHA1:
|
||
|
- case KEX_GSS_GRP14_SHA256:
|
||
|
- case KEX_GSS_GRP16_SHA512:
|
||
|
- r = kex_dh_keypair(kex);
|
||
|
- break;
|
||
|
- case KEX_GSS_NISTP256_SHA256:
|
||
|
- r = kex_ecdh_keypair(kex);
|
||
|
- break;
|
||
|
- case KEX_GSS_C25519_SHA256:
|
||
|
- r = kex_c25519_keypair(kex);
|
||
|
- break;
|
||
|
- default:
|
||
|
- fatal_f("Unexpected KEX type %d", kex->kex_type);
|
||
|
- }
|
||
|
- if (r != 0) {
|
||
|
- ssh_gssapi_delete_ctx(&ctxt);
|
||
|
- return r;
|
||
|
- }
|
||
|
-
|
||
|
- token_ptr = GSS_C_NO_BUFFER;
|
||
|
-
|
||
|
- do {
|
||
|
- debug("Calling gss_init_sec_context");
|
||
|
-
|
||
|
- maj_status = ssh_gssapi_init_ctx(ctxt,
|
||
|
- kex->gss_deleg_creds, token_ptr, &send_tok,
|
||
|
- &ret_flags);
|
||
|
-
|
||
|
- if (GSS_ERROR(maj_status)) {
|
||
|
- /* XXX Useles code: Missing send? */
|
||
|
- if (send_tok.length != 0) {
|
||
|
- if ((r = sshpkt_start(ssh,
|
||
|
- SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value,
|
||
|
- send_tok.length)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- }
|
||
|
- fatal("gss_init_context failed");
|
||
|
- }
|
||
|
-
|
||
|
- /* If we've got an old receive buffer get rid of it */
|
||
|
- if (token_ptr != GSS_C_NO_BUFFER)
|
||
|
- gss_release_buffer(&min_status, &recv_tok);
|
||
|
-
|
||
|
- if (maj_status == GSS_S_COMPLETE) {
|
||
|
- /* If mutual state flag is not true, kex fails */
|
||
|
- if (!(ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
- fatal("Mutual authentication failed");
|
||
|
-
|
||
|
- /* If integ avail flag is not true kex fails */
|
||
|
- if (!(ret_flags & GSS_C_INTEG_FLAG))
|
||
|
- fatal("Integrity check failed");
|
||
|
- }
|
||
|
-
|
||
|
- /*
|
||
|
- * If we have data to send, then the last message that we
|
||
|
- * received cannot have been a 'complete'.
|
||
|
- */
|
||
|
- if (send_tok.length != 0) {
|
||
|
- if (first) {
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value,
|
||
|
- send_tok.length)) != 0 ||
|
||
|
- (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0)
|
||
|
- fatal("failed to construct packet: %s", ssh_err(r));
|
||
|
- first = 0;
|
||
|
- } else {
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value,
|
||
|
- send_tok.length)) != 0)
|
||
|
- fatal("failed to construct packet: %s", ssh_err(r));
|
||
|
- }
|
||
|
- if ((r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("failed to send packet: %s", ssh_err(r));
|
||
|
- gss_release_buffer(&min_status, &send_tok);
|
||
|
-
|
||
|
- /* If we've sent them data, they should reply */
|
||
|
- do {
|
||
|
- type = ssh_packet_read(ssh);
|
||
|
- if (type == SSH2_MSG_KEXGSS_HOSTKEY) {
|
||
|
- u_char *tmp = NULL;
|
||
|
- size_t tmp_len = 0;
|
||
|
-
|
||
|
- debug("Received KEXGSS_HOSTKEY");
|
||
|
- if (server_host_key_blob)
|
||
|
- fatal("Server host key received more than once");
|
||
|
- if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0)
|
||
|
- fatal("Failed to read server host key: %s", ssh_err(r));
|
||
|
- if ((server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL)
|
||
|
- fatal("sshbuf_from failed");
|
||
|
- }
|
||
|
- } while (type == SSH2_MSG_KEXGSS_HOSTKEY);
|
||
|
-
|
||
|
- switch (type) {
|
||
|
- case SSH2_MSG_KEXGSS_CONTINUE:
|
||
|
- debug("Received GSSAPI_CONTINUE");
|
||
|
- if (maj_status == GSS_S_COMPLETE)
|
||
|
- fatal("GSSAPI Continue received from server when complete");
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("Failed to read token: %s", ssh_err(r));
|
||
|
- break;
|
||
|
- case SSH2_MSG_KEXGSS_COMPLETE:
|
||
|
- debug("Received GSSAPI_COMPLETE");
|
||
|
- if (msg_tok.value != NULL)
|
||
|
- fatal("Received GSSAPI_COMPLETE twice?");
|
||
|
- if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 ||
|
||
|
- (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &msg_tok)) != 0)
|
||
|
- fatal("Failed to read message: %s", ssh_err(r));
|
||
|
-
|
||
|
- /* Is there a token included? */
|
||
|
- if ((r = sshpkt_get_u8(ssh, &c)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- if (c) {
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(
|
||
|
- ssh, &recv_tok)) != 0)
|
||
|
- fatal("Failed to read token: %s", ssh_err(r));
|
||
|
- /* If we're already complete - protocol error */
|
||
|
- if (maj_status == GSS_S_COMPLETE)
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: received token when complete");
|
||
|
- } else {
|
||
|
- /* No token included */
|
||
|
- if (maj_status != GSS_S_COMPLETE)
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
|
||
|
- }
|
||
|
- if ((r = sshpkt_get_end(ssh)) != 0) {
|
||
|
- fatal("Expecting end of packet.");
|
||
|
- }
|
||
|
- break;
|
||
|
- case SSH2_MSG_KEXGSS_ERROR:
|
||
|
- debug("Received Error");
|
||
|
- if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 ||
|
||
|
- (r = sshpkt_get_u32(ssh, &min_status)) != 0 ||
|
||
|
- (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
|
||
|
- (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt_get failed: %s", ssh_err(r));
|
||
|
- fatal("GSSAPI Error: \n%.400s", msg);
|
||
|
- default:
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d",
|
||
|
- type);
|
||
|
- }
|
||
|
- token_ptr = &recv_tok;
|
||
|
- } else {
|
||
|
- /* No data, and not complete */
|
||
|
- if (maj_status != GSS_S_COMPLETE)
|
||
|
- fatal("Not complete, and no token output");
|
||
|
- }
|
||
|
- } while (maj_status & GSS_S_CONTINUE_NEEDED);
|
||
|
-
|
||
|
/*
|
||
|
* We _must_ have received a COMPLETE message in reply from the
|
||
|
* server, which will have set server_blob and msg_tok
|
||
|
*/
|
||
|
|
||
|
- if (type != SSH2_MSG_KEXGSS_COMPLETE)
|
||
|
- fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
|
||
|
-
|
||
|
/* compute shared secret */
|
||
|
switch (kex->kex_type) {
|
||
|
case KEX_GSS_GRP1_SHA1:
|
||
|
case KEX_GSS_GRP14_SHA1:
|
||
|
case KEX_GSS_GRP14_SHA256:
|
||
|
case KEX_GSS_GRP16_SHA512:
|
||
|
- r = kex_dh_dec(kex, server_blob, &shared_secret);
|
||
|
+ r = kex_dh_dec(kex, gss->server_blob, &shared_secret);
|
||
|
break;
|
||
|
case KEX_GSS_C25519_SHA256:
|
||
|
- if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80)
|
||
|
+ if (sshbuf_ptr(gss->server_blob)[sshbuf_len(gss->server_blob)] & 0x80)
|
||
|
fatal("The received key has MSB of last octet set!");
|
||
|
- r = kex_c25519_dec(kex, server_blob, &shared_secret);
|
||
|
+ r = kex_c25519_dec(kex, gss->server_blob, &shared_secret);
|
||
|
break;
|
||
|
case KEX_GSS_NISTP256_SHA256:
|
||
|
- if (sshbuf_len(server_blob) != 65)
|
||
|
- fatal("The received NIST-P256 key did not match"
|
||
|
- "expected length (expected 65, got %zu)", sshbuf_len(server_blob));
|
||
|
+ if (sshbuf_len(gss->server_blob) != 65)
|
||
|
+ fatal("The received NIST-P256 key did not match "
|
||
|
+ "expected length (expected 65, got %zu)",
|
||
|
+ sshbuf_len(gss->server_blob));
|
||
|
|
||
|
- if (sshbuf_ptr(server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED)
|
||
|
+ if (sshbuf_ptr(gss->server_blob)[0] != POINT_CONVERSION_UNCOMPRESSED)
|
||
|
fatal("The received NIST-P256 key does not have first octet 0x04");
|
||
|
|
||
|
- r = kex_ecdh_dec(kex, server_blob, &shared_secret);
|
||
|
+ r = kex_ecdh_dec(kex, gss->server_blob, &shared_secret);
|
||
|
break;
|
||
|
default:
|
||
|
r = SSH_ERR_INVALID_ARGUMENT;
|
||
|
break;
|
||
|
}
|
||
|
- if (r != 0)
|
||
|
+ if (r != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
goto out;
|
||
|
+ }
|
||
|
|
||
|
if ((empty = sshbuf_new()) == NULL) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
r = SSH_ERR_ALLOC_FAIL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
hashlen = sizeof(hash);
|
||
|
- if ((r = kex_gen_hash(
|
||
|
- kex->hash_alg,
|
||
|
- kex->client_version,
|
||
|
- kex->server_version,
|
||
|
- kex->my,
|
||
|
- kex->peer,
|
||
|
- (server_host_key_blob ? server_host_key_blob : empty),
|
||
|
- kex->client_pub,
|
||
|
- server_blob,
|
||
|
- shared_secret,
|
||
|
- hash, &hashlen)) != 0)
|
||
|
+ r = kex_gen_hash(kex->hash_alg, kex->client_version,
|
||
|
+ kex->server_version, kex->my, kex->peer,
|
||
|
+ (gss->server_host_key_blob ? gss->server_host_key_blob : empty),
|
||
|
+ kex->client_pub, gss->server_blob, shared_secret,
|
||
|
+ hash, &hashlen);
|
||
|
+ sshbuf_free(empty);
|
||
|
+ if (r != 0)
|
||
|
fatal_f("Unexpected KEX type %d", kex->kex_type);
|
||
|
|
||
|
- gssbuf.value = hash;
|
||
|
- gssbuf.length = hashlen;
|
||
|
+ gss->buf.value = hash;
|
||
|
+ gss->buf.length = hashlen;
|
||
|
|
||
|
/* Verify that the hash matches the MIC we just got. */
|
||
|
- if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok)))
|
||
|
+ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok)))
|
||
|
sshpkt_disconnect(ssh, "Hash's MIC didn't verify");
|
||
|
|
||
|
- gss_release_buffer(&min_status, &msg_tok);
|
||
|
+ gss_release_buffer(&gss->minor, &gss->msg_tok);
|
||
|
|
||
|
if (kex->gss_deleg_creds)
|
||
|
- ssh_gssapi_credentials_updated(ctxt);
|
||
|
+ ssh_gssapi_credentials_updated(gss);
|
||
|
|
||
|
if (gss_kex_context == NULL)
|
||
|
- gss_kex_context = ctxt;
|
||
|
+ gss_kex_context = gss;
|
||
|
else
|
||
|
- ssh_gssapi_delete_ctx(&ctxt);
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
|
||
|
if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
|
||
|
r = kex_send_newkeys(ssh);
|
||
|
|
||
|
+ if (kex->gss != NULL) {
|
||
|
+ sshbuf_free(gss->server_host_key_blob);
|
||
|
+ gss->server_host_key_blob = NULL;
|
||
|
+ sshbuf_free(gss->server_blob);
|
||
|
+ gss->server_blob = NULL;
|
||
|
+ }
|
||
|
out:
|
||
|
- explicit_bzero(hash, sizeof(hash));
|
||
|
explicit_bzero(kex->c25519_client_key, sizeof(kex->c25519_client_key));
|
||
|
- sshbuf_free(empty);
|
||
|
- sshbuf_free(server_host_key_blob);
|
||
|
- sshbuf_free(server_blob);
|
||
|
+ explicit_bzero(hash, sizeof(hash));
|
||
|
sshbuf_free(shared_secret);
|
||
|
sshbuf_free(kex->client_pub);
|
||
|
kex->client_pub = NULL;
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
+static int
|
||
|
+kexgss_init_ctx(struct ssh *ssh,
|
||
|
+ gss_buffer_desc *token_ptr)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
+ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ OM_uint32 ret_flags;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ debug("Calling gss_init_sec_context");
|
||
|
+
|
||
|
+ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds,
|
||
|
+ token_ptr, &send_tok, &ret_flags);
|
||
|
+
|
||
|
+ if (GSS_ERROR(gss->major)) {
|
||
|
+ /* XXX Useless code: Missing send? */
|
||
|
+ if (send_tok.length != 0) {
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ }
|
||
|
+ fatal("gss_init_context failed");
|
||
|
+ }
|
||
|
+
|
||
|
+ /* If we've got an old receive buffer get rid of it */
|
||
|
+ if (token_ptr != GSS_C_NO_BUFFER)
|
||
|
+ gss_release_buffer(&gss->minor, token_ptr);
|
||
|
+
|
||
|
+ if (gss->major == GSS_S_COMPLETE) {
|
||
|
+ /* If mutual state flag is not true, kex fails */
|
||
|
+ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
+ fatal("Mutual authentication failed");
|
||
|
+
|
||
|
+ /* If integ avail flag is not true kex fails */
|
||
|
+ if (!(ret_flags & GSS_C_INTEG_FLAG))
|
||
|
+ fatal("Integrity check failed");
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * If we have data to send, then the last message that we
|
||
|
+ * received cannot have been a 'complete'.
|
||
|
+ */
|
||
|
+ if (send_tok.length != 0) {
|
||
|
+ if (gss->first) {
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
|
||
|
+ (r = sshpkt_put_stringb(ssh, kex->client_pub)) != 0)
|
||
|
+ fatal("failed to construct packet: %s", ssh_err(r));
|
||
|
+ gss->first = 0;
|
||
|
+ } else {
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
|
||
|
+ fatal("failed to construct packet: %s", ssh_err(r));
|
||
|
+ }
|
||
|
+ if ((r = sshpkt_send(ssh)) != 0)
|
||
|
+ fatal("failed to send packet: %s", ssh_err(r));
|
||
|
+ gss_release_buffer(&gss->minor, &send_tok);
|
||
|
+
|
||
|
+ /* If we've sent them data, they should reply */
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgss_complete);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error);
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+ /* No data, and not complete */
|
||
|
+ if (gss->major != GSS_S_COMPLETE)
|
||
|
+ fatal("Not complete, and no token output");
|
||
|
+
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return kexgss_init_ctx(ssh, token_ptr);
|
||
|
+
|
||
|
+ return kexgss_final(ssh);
|
||
|
+}
|
||
|
+
|
||
|
int
|
||
|
-kexgssgex_client(struct ssh *ssh)
|
||
|
+kexgss_client(struct ssh *ssh)
|
||
|
{
|
||
|
struct kex *kex = ssh->kex;
|
||
|
- gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER,
|
||
|
- recv_tok = GSS_C_EMPTY_BUFFER, gssbuf,
|
||
|
- msg_tok = GSS_C_EMPTY_BUFFER, *token_ptr;
|
||
|
- Gssctxt *ctxt;
|
||
|
- OM_uint32 maj_status, min_status, ret_flags;
|
||
|
- struct sshbuf *shared_secret = NULL;
|
||
|
- BIGNUM *p = NULL;
|
||
|
- BIGNUM *g = NULL;
|
||
|
- struct sshbuf *buf = NULL;
|
||
|
- struct sshbuf *server_host_key_blob = NULL;
|
||
|
- struct sshbuf *server_blob = NULL;
|
||
|
- BIGNUM *dh_server_pub = NULL;
|
||
|
- u_char *msg;
|
||
|
- int type = 0;
|
||
|
- int first = 1;
|
||
|
- u_char hash[SSH_DIGEST_MAX_LENGTH];
|
||
|
- size_t hashlen;
|
||
|
- const BIGNUM *pub_key, *dh_p, *dh_g;
|
||
|
- int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX;
|
||
|
- struct sshbuf *empty = NULL;
|
||
|
- u_char c;
|
||
|
int r;
|
||
|
|
||
|
/* Initialise our GSSAPI world */
|
||
|
- ssh_gssapi_build_ctx(&ctxt);
|
||
|
- if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type)
|
||
|
- == GSS_C_NO_OID)
|
||
|
+ ssh_gssapi_build_ctx(&kex->gss);
|
||
|
+ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID)
|
||
|
fatal("Couldn't identify host exchange");
|
||
|
|
||
|
- if (ssh_gssapi_import_name(ctxt, kex->gss_host))
|
||
|
+ if (ssh_gssapi_import_name(kex->gss, kex->gss_host))
|
||
|
fatal("Couldn't import hostname");
|
||
|
|
||
|
if (kex->gss_client &&
|
||
|
- ssh_gssapi_client_identity(ctxt, kex->gss_client))
|
||
|
+ ssh_gssapi_client_identity(kex->gss, kex->gss_client))
|
||
|
fatal("Couldn't acquire client credentials");
|
||
|
|
||
|
- debug("Doing group exchange");
|
||
|
- nbits = dh_estimate(kex->dh_need * 8);
|
||
|
+ /* Step 1 */
|
||
|
+ switch (kex->kex_type) {
|
||
|
+ case KEX_GSS_GRP1_SHA1:
|
||
|
+ case KEX_GSS_GRP14_SHA1:
|
||
|
+ case KEX_GSS_GRP14_SHA256:
|
||
|
+ case KEX_GSS_GRP16_SHA512:
|
||
|
+ r = kex_dh_keypair(kex);
|
||
|
+ break;
|
||
|
+ case KEX_GSS_NISTP256_SHA256:
|
||
|
+ r = kex_ecdh_keypair(kex);
|
||
|
+ break;
|
||
|
+ case KEX_GSS_C25519_SHA256:
|
||
|
+ r = kex_c25519_keypair(kex);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ fatal_f("Unexpected KEX type %d", kex->kex_type);
|
||
|
+ }
|
||
|
+ if (r != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ return r;
|
||
|
+ }
|
||
|
+ return kexgss_init_ctx(ssh, GSS_C_NO_BUFFER);
|
||
|
+}
|
||
|
|
||
|
- kex->min = DH_GRP_MIN;
|
||
|
- kex->max = DH_GRP_MAX;
|
||
|
- kex->nbits = nbits;
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 ||
|
||
|
- (r = sshpkt_put_u32(ssh, min)) != 0 ||
|
||
|
- (r = sshpkt_put_u32(ssh, nbits)) != 0 ||
|
||
|
- (r = sshpkt_put_u32(ssh, max)) != 0 ||
|
||
|
- (r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("Failed to construct a packet: %s", ssh_err(r));
|
||
|
+static int
|
||
|
+input_kexgss_hostkey(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ u_char *tmp = NULL;
|
||
|
+ size_t tmp_len = 0;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ debug("Received KEXGSS_HOSTKEY");
|
||
|
+ if (gss->server_host_key_blob)
|
||
|
+ fatal("Server host key received more than once");
|
||
|
+ if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0)
|
||
|
+ fatal("Failed to read server host key: %s", ssh_err(r));
|
||
|
+ if ((gss->server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL)
|
||
|
+ fatal("sshbuf_from failed");
|
||
|
+ return 0;
|
||
|
+}
|
||
|
|
||
|
- if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0)
|
||
|
- fatal("Error: %s", ssh_err(r));
|
||
|
+static int
|
||
|
+input_kexgss_continue(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ int r;
|
||
|
|
||
|
- if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 ||
|
||
|
- (r = sshpkt_get_bignum2(ssh, &g)) != 0 ||
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL);
|
||
|
+
|
||
|
+ debug("Received GSSAPI_CONTINUE");
|
||
|
+ if (gss->major == GSS_S_COMPLETE)
|
||
|
+ fatal("GSSAPI Continue received from server when complete");
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 ||
|
||
|
(r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("shpkt_get_bignum2 failed: %s", ssh_err(r));
|
||
|
+ fatal("Failed to read token: %s", ssh_err(r));
|
||
|
+ if (!(gss->major & GSS_S_CONTINUE_NEEDED))
|
||
|
+ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
|
||
|
+ return kexgss_init_ctx(ssh, &recv_tok);
|
||
|
+}
|
||
|
|
||
|
- if (BN_num_bits(p) < min || BN_num_bits(p) > max)
|
||
|
- fatal("GSSGRP_GEX group out of range: %d !< %d !< %d",
|
||
|
- min, BN_num_bits(p), max);
|
||
|
+static int
|
||
|
+input_kexgss_complete(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ u_char c;
|
||
|
+ int r;
|
||
|
|
||
|
- if ((kex->dh = dh_new_group(g, p)) == NULL)
|
||
|
- fatal("dn_new_group() failed");
|
||
|
- p = g = NULL; /* belong to kex->dh now */
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL);
|
||
|
+
|
||
|
+ debug("Received GSSAPI_COMPLETE");
|
||
|
+ if (gss->msg_tok.value != NULL)
|
||
|
+ fatal("Received GSSAPI_COMPLETE twice?");
|
||
|
+ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 ||
|
||
|
+ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0)
|
||
|
+ fatal("Failed to read message: %s", ssh_err(r));
|
||
|
+
|
||
|
+ /* Is there a token included? */
|
||
|
+ if ((r = sshpkt_get_u8(ssh, &c)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ if (c) {
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0)
|
||
|
+ fatal("Failed to read token: %s", ssh_err(r));
|
||
|
+ /* If we're already complete - protocol error */
|
||
|
+ if (gss->major == GSS_S_COMPLETE)
|
||
|
+ sshpkt_disconnect(ssh, "Protocol error: received token when complete");
|
||
|
+ } else {
|
||
|
+ if (gss->major != GSS_S_COMPLETE)
|
||
|
+ sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
|
||
|
+ }
|
||
|
+ if ((r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("Expecting end of packet.");
|
||
|
|
||
|
- if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
|
||
|
- goto out;
|
||
|
- DH_get0_key(kex->dh, &pub_key, NULL);
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return kexgss_init_ctx(ssh, &recv_tok);
|
||
|
|
||
|
- token_ptr = GSS_C_NO_BUFFER;
|
||
|
+ return kexgss_final(ssh);
|
||
|
+}
|
||
|
|
||
|
- do {
|
||
|
- /* Step 2 - call GSS_Init_sec_context() */
|
||
|
- debug("Calling gss_init_sec_context");
|
||
|
-
|
||
|
- maj_status = ssh_gssapi_init_ctx(ctxt,
|
||
|
- kex->gss_deleg_creds, token_ptr, &send_tok,
|
||
|
- &ret_flags);
|
||
|
-
|
||
|
- if (GSS_ERROR(maj_status)) {
|
||
|
- /* XXX Useles code: Missing send? */
|
||
|
- if (send_tok.length != 0) {
|
||
|
- if ((r = sshpkt_start(ssh,
|
||
|
- SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value,
|
||
|
- send_tok.length)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- }
|
||
|
- fatal("gss_init_context failed");
|
||
|
- }
|
||
|
+static int
|
||
|
+input_kexgss_error(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ u_char *msg;
|
||
|
+ int r;
|
||
|
|
||
|
- /* If we've got an old receive buffer get rid of it */
|
||
|
- if (token_ptr != GSS_C_NO_BUFFER)
|
||
|
- gss_release_buffer(&min_status, &recv_tok);
|
||
|
-
|
||
|
- if (maj_status == GSS_S_COMPLETE) {
|
||
|
- /* If mutual state flag is not true, kex fails */
|
||
|
- if (!(ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
- fatal("Mutual authentication failed");
|
||
|
-
|
||
|
- /* If integ avail flag is not true kex fails */
|
||
|
- if (!(ret_flags & GSS_C_INTEG_FLAG))
|
||
|
- fatal("Integrity check failed");
|
||
|
- }
|
||
|
+ debug("Received Error");
|
||
|
+ if ((r = sshpkt_get_u32(ssh, &gss->major)) != 0 ||
|
||
|
+ (r = sshpkt_get_u32(ssh, &gss->minor)) != 0 ||
|
||
|
+ (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
|
||
|
+ (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("sshpkt_get failed: %s", ssh_err(r));
|
||
|
+ fatal("GSSAPI Error: \n%.400s", msg);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
|
||
|
- /*
|
||
|
- * If we have data to send, then the last message that we
|
||
|
- * received cannot have been a 'complete'.
|
||
|
- */
|
||
|
- if (send_tok.length != 0) {
|
||
|
- if (first) {
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value,
|
||
|
- send_tok.length)) != 0 ||
|
||
|
- (r = sshpkt_put_bignum2(ssh, pub_key)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- first = 0;
|
||
|
- } else {
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh,send_tok.value,
|
||
|
- send_tok.length)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- }
|
||
|
- if ((r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("sshpkt_send failed: %s", ssh_err(r));
|
||
|
- gss_release_buffer(&min_status, &send_tok);
|
||
|
-
|
||
|
- /* If we've sent them data, they should reply */
|
||
|
- do {
|
||
|
- type = ssh_packet_read(ssh);
|
||
|
- if (type == SSH2_MSG_KEXGSS_HOSTKEY) {
|
||
|
- u_char *tmp = NULL;
|
||
|
- size_t tmp_len = 0;
|
||
|
-
|
||
|
- debug("Received KEXGSS_HOSTKEY");
|
||
|
- if (server_host_key_blob)
|
||
|
- fatal("Server host key received more than once");
|
||
|
- if ((r = sshpkt_get_string(ssh, &tmp, &tmp_len)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- if ((server_host_key_blob = sshbuf_from(tmp, tmp_len)) == NULL)
|
||
|
- fatal("sshbuf_from failed");
|
||
|
- }
|
||
|
- } while (type == SSH2_MSG_KEXGSS_HOSTKEY);
|
||
|
-
|
||
|
- switch (type) {
|
||
|
- case SSH2_MSG_KEXGSS_CONTINUE:
|
||
|
- debug("Received GSSAPI_CONTINUE");
|
||
|
- if (maj_status == GSS_S_COMPLETE)
|
||
|
- fatal("GSSAPI Continue received from server when complete");
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- break;
|
||
|
- case SSH2_MSG_KEXGSS_COMPLETE:
|
||
|
- debug("Received GSSAPI_COMPLETE");
|
||
|
- if (msg_tok.value != NULL)
|
||
|
- fatal("Received GSSAPI_COMPLETE twice?");
|
||
|
- if ((r = sshpkt_getb_froms(ssh, &server_blob)) != 0 ||
|
||
|
- (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &msg_tok)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
-
|
||
|
- /* Is there a token included? */
|
||
|
- if ((r = sshpkt_get_u8(ssh, &c)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- if (c) {
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(
|
||
|
- ssh, &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- /* If we're already complete - protocol error */
|
||
|
- if (maj_status == GSS_S_COMPLETE)
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: received token when complete");
|
||
|
- } else {
|
||
|
- /* No token included */
|
||
|
- if (maj_status != GSS_S_COMPLETE)
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
|
||
|
- }
|
||
|
- break;
|
||
|
- case SSH2_MSG_KEXGSS_ERROR:
|
||
|
- debug("Received Error");
|
||
|
- if ((r = sshpkt_get_u32(ssh, &maj_status)) != 0 ||
|
||
|
- (r = sshpkt_get_u32(ssh, &min_status)) != 0 ||
|
||
|
- (r = sshpkt_get_string(ssh, &msg, NULL)) != 0 ||
|
||
|
- (r = sshpkt_get_string(ssh, NULL, NULL)) != 0 || /* lang tag */
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- fatal("GSSAPI Error: \n%.400s", msg);
|
||
|
- default:
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: didn't expect packet type %d",
|
||
|
- type);
|
||
|
- }
|
||
|
- token_ptr = &recv_tok;
|
||
|
- } else {
|
||
|
- /* No data, and not complete */
|
||
|
- if (maj_status != GSS_S_COMPLETE)
|
||
|
- fatal("Not complete, and no token output");
|
||
|
- }
|
||
|
- } while (maj_status & GSS_S_CONTINUE_NEEDED);
|
||
|
+/*******************************************************/
|
||
|
+/******************** KEXGSSGEX ************************/
|
||
|
+/*******************************************************/
|
||
|
+
|
||
|
+int
|
||
|
+kexgssgex_client(struct ssh *ssh)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ /* Initialise our GSSAPI world */
|
||
|
+ ssh_gssapi_build_ctx(&kex->gss);
|
||
|
+ if (ssh_gssapi_id_kex(kex->gss, kex->name, kex->kex_type) == GSS_C_NO_OID)
|
||
|
+ fatal("Couldn't identify host exchange");
|
||
|
+
|
||
|
+ if (ssh_gssapi_import_name(kex->gss, kex->gss_host))
|
||
|
+ fatal("Couldn't import hostname");
|
||
|
+
|
||
|
+ if (kex->gss_client &&
|
||
|
+ ssh_gssapi_client_identity(kex->gss, kex->gss_client))
|
||
|
+ fatal("Couldn't acquire client credentials");
|
||
|
+
|
||
|
+ debug("Doing group exchange");
|
||
|
+ kex->min = DH_GRP_MIN;
|
||
|
+ kex->max = DH_GRP_MAX;
|
||
|
+ kex->nbits = dh_estimate(kex->dh_need * 8);
|
||
|
+
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUPREQ)) != 0 ||
|
||
|
+ (r = sshpkt_put_u32(ssh, kex->min)) != 0 ||
|
||
|
+ (r = sshpkt_put_u32(ssh, kex->nbits)) != 0 ||
|
||
|
+ (r = sshpkt_put_u32(ssh, kex->max)) != 0 ||
|
||
|
+ (r = sshpkt_send(ssh)) != 0)
|
||
|
+ fatal("Failed to construct a packet: %s", ssh_err(r));
|
||
|
+
|
||
|
+ debug("Wait SSH2_MSG_KEXGSS_GROUP");
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, &input_kexgssgex_group);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+kexgssgex_final(struct ssh *ssh)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
+ struct sshbuf *buf = NULL;
|
||
|
+ struct sshbuf *empty = NULL;
|
||
|
+ struct sshbuf *shared_secret = NULL;
|
||
|
+ BIGNUM *dh_server_pub = NULL;
|
||
|
+ const BIGNUM *pub_key, *dh_p, *dh_g;
|
||
|
+ u_char hash[SSH_DIGEST_MAX_LENGTH];
|
||
|
+ size_t hashlen;
|
||
|
+ int r = SSH_ERR_INTERNAL_ERROR;
|
||
|
|
||
|
/*
|
||
|
* We _must_ have received a COMPLETE message in reply from the
|
||
|
- * server, which will have set dh_server_pub and msg_tok
|
||
|
+ * server, which will have set server_blob and msg_tok
|
||
|
*/
|
||
|
|
||
|
- if (type != SSH2_MSG_KEXGSS_COMPLETE)
|
||
|
- fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
|
||
|
-
|
||
|
/* 7. C verifies that the key Q_S is valid */
|
||
|
/* 8. C computes shared secret */
|
||
|
if ((buf = sshbuf_new()) == NULL ||
|
||
|
- (r = sshbuf_put_stringb(buf, server_blob)) != 0 ||
|
||
|
- (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0)
|
||
|
+ (r = sshbuf_put_stringb(buf, gss->server_blob)) != 0 ||
|
||
|
+ (r = sshbuf_get_bignum2(buf, &dh_server_pub)) != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
goto out;
|
||
|
+ }
|
||
|
sshbuf_free(buf);
|
||
|
buf = NULL;
|
||
|
|
||
|
if ((shared_secret = sshbuf_new()) == NULL) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
r = SSH_ERR_ALLOC_FAIL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
- if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0)
|
||
|
+ if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
goto out;
|
||
|
+ }
|
||
|
+
|
||
|
if ((empty = sshbuf_new()) == NULL) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
r = SSH_ERR_ALLOC_FAIL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
+ DH_get0_key(kex->dh, &pub_key, NULL);
|
||
|
DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
|
||
|
hashlen = sizeof(hash);
|
||
|
- if ((r = kexgex_hash(
|
||
|
- kex->hash_alg,
|
||
|
- kex->client_version,
|
||
|
- kex->server_version,
|
||
|
- kex->my,
|
||
|
- kex->peer,
|
||
|
- (server_host_key_blob ? server_host_key_blob : empty),
|
||
|
- kex->min, kex->nbits, kex->max,
|
||
|
- dh_p, dh_g,
|
||
|
- pub_key,
|
||
|
- dh_server_pub,
|
||
|
- sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
|
||
|
- hash, &hashlen)) != 0)
|
||
|
+ r = kexgex_hash(kex->hash_alg, kex->client_version,
|
||
|
+ kex->server_version, kex->my, kex->peer,
|
||
|
+ (gss->server_host_key_blob ? gss->server_host_key_blob : empty),
|
||
|
+ kex->min, kex->nbits, kex->max, dh_p, dh_g, pub_key,
|
||
|
+ dh_server_pub, sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
|
||
|
+ hash, &hashlen);
|
||
|
+ sshbuf_free(empty);
|
||
|
+ if (r != 0)
|
||
|
fatal("Failed to calculate hash: %s", ssh_err(r));
|
||
|
|
||
|
- gssbuf.value = hash;
|
||
|
- gssbuf.length = hashlen;
|
||
|
+ gss->buf.value = hash;
|
||
|
+ gss->buf.length = hashlen;
|
||
|
|
||
|
/* Verify that the hash matches the MIC we just got. */
|
||
|
- if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok)))
|
||
|
+ if (GSS_ERROR(ssh_gssapi_checkmic(gss, &gss->buf, &gss->msg_tok)))
|
||
|
sshpkt_disconnect(ssh, "Hash's MIC didn't verify");
|
||
|
|
||
|
- gss_release_buffer(&min_status, &msg_tok);
|
||
|
+ gss_release_buffer(&gss->minor, &gss->msg_tok);
|
||
|
|
||
|
if (kex->gss_deleg_creds)
|
||
|
- ssh_gssapi_credentials_updated(ctxt);
|
||
|
+ ssh_gssapi_credentials_updated(gss);
|
||
|
|
||
|
if (gss_kex_context == NULL)
|
||
|
- gss_kex_context = ctxt;
|
||
|
+ gss_kex_context = gss;
|
||
|
else
|
||
|
- ssh_gssapi_delete_ctx(&ctxt);
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
|
||
|
/* Finally derive the keys and send them */
|
||
|
if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
|
||
|
r = kex_send_newkeys(ssh);
|
||
|
+
|
||
|
+ if (kex->gss != NULL) {
|
||
|
+ sshbuf_free(gss->server_host_key_blob);
|
||
|
+ gss->server_host_key_blob = NULL;
|
||
|
+ sshbuf_free(gss->server_blob);
|
||
|
+ gss->server_blob = NULL;
|
||
|
+ }
|
||
|
out:
|
||
|
- sshbuf_free(buf);
|
||
|
- sshbuf_free(server_blob);
|
||
|
- sshbuf_free(empty);
|
||
|
explicit_bzero(hash, sizeof(hash));
|
||
|
DH_free(kex->dh);
|
||
|
kex->dh = NULL;
|
||
|
BN_clear_free(dh_server_pub);
|
||
|
sshbuf_free(shared_secret);
|
||
|
- sshbuf_free(server_host_key_blob);
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
+static int
|
||
|
+kexgssgex_init_ctx(struct ssh *ssh,
|
||
|
+ gss_buffer_desc *token_ptr)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
+ const BIGNUM *pub_key;
|
||
|
+ gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ OM_uint32 ret_flags;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ /* Step 2 - call GSS_Init_sec_context() */
|
||
|
+ debug("Calling gss_init_sec_context");
|
||
|
+
|
||
|
+ gss->major = ssh_gssapi_init_ctx(gss, kex->gss_deleg_creds,
|
||
|
+ token_ptr, &send_tok, &ret_flags);
|
||
|
+
|
||
|
+ if (GSS_ERROR(gss->major)) {
|
||
|
+ /* XXX Useless code: Missing send? */
|
||
|
+ if (send_tok.length != 0) {
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ }
|
||
|
+ fatal("gss_init_context failed");
|
||
|
+ }
|
||
|
+
|
||
|
+ /* If we've got an old receive buffer get rid of it */
|
||
|
+ if (token_ptr != GSS_C_NO_BUFFER)
|
||
|
+ gss_release_buffer(&gss->minor, token_ptr);
|
||
|
+
|
||
|
+ if (gss->major == GSS_S_COMPLETE) {
|
||
|
+ /* If mutual state flag is not true, kex fails */
|
||
|
+ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
+ fatal("Mutual authentication failed");
|
||
|
+
|
||
|
+ /* If integ avail flag is not true kex fails */
|
||
|
+ if (!(ret_flags & GSS_C_INTEG_FLAG))
|
||
|
+ fatal("Integrity check failed");
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * If we have data to send, then the last message that we
|
||
|
+ * received cannot have been a 'complete'.
|
||
|
+ */
|
||
|
+ if (send_tok.length != 0) {
|
||
|
+ if (gss->first) {
|
||
|
+ DH_get0_key(kex->dh, &pub_key, NULL);
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_INIT)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
|
||
|
+ (r = sshpkt_put_bignum2(ssh, pub_key)) != 0)
|
||
|
+ fatal("failed to construct packet: %s", ssh_err(r));
|
||
|
+ gss->first = 0;
|
||
|
+ } else {
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
|
||
|
+ fatal("failed to construct packet: %s", ssh_err(r));
|
||
|
+ }
|
||
|
+ if ((r = sshpkt_send(ssh)) != 0)
|
||
|
+ fatal("failed to send packet: %s", ssh_err(r));
|
||
|
+ gss_release_buffer(&gss->minor, &send_tok);
|
||
|
+
|
||
|
+ /* If we've sent them data, they should reply */
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, &input_kexgss_hostkey);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, &input_kexgssgex_complete);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, &input_kexgss_error);
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
+ /* No data, and not complete */
|
||
|
+ if (gss->major != GSS_S_COMPLETE)
|
||
|
+ fatal("Not complete, and no token output");
|
||
|
+
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return kexgssgex_init_ctx(ssh, token_ptr);
|
||
|
+
|
||
|
+ return kexgssgex_final(ssh);
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgssgex_group(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ BIGNUM *p = NULL;
|
||
|
+ BIGNUM *g = NULL;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ debug("Received SSH2_MSG_KEXGSS_GROUP");
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUP, NULL);
|
||
|
+
|
||
|
+ if ((r = sshpkt_get_bignum2(ssh, &p)) != 0 ||
|
||
|
+ (r = sshpkt_get_bignum2(ssh, &g)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("shpkt_get_bignum2 failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ if (BN_num_bits(p) < kex->min || BN_num_bits(p) > kex->max)
|
||
|
+ fatal("GSSGRP_GEX group out of range: %d !< %d !< %d",
|
||
|
+ kex->min, BN_num_bits(p), kex->max);
|
||
|
+
|
||
|
+ if ((kex->dh = dh_new_group(g, p)) == NULL)
|
||
|
+ fatal("dn_new_group() failed");
|
||
|
+ p = g = NULL; /* belong to kex->dh now */
|
||
|
+
|
||
|
+ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ DH_free(kex->dh);
|
||
|
+ kex->dh = NULL;
|
||
|
+ return r;
|
||
|
+ }
|
||
|
+
|
||
|
+ return kexgssgex_init_ctx(ssh, GSS_C_NO_BUFFER);
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgssgex_continue(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL);
|
||
|
+
|
||
|
+ debug("Received GSSAPI_CONTINUE");
|
||
|
+ if (gss->major == GSS_S_COMPLETE)
|
||
|
+ fatal("GSSAPI Continue received from server when complete");
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("Failed to read token: %s", ssh_err(r));
|
||
|
+ if (!(gss->major & GSS_S_CONTINUE_NEEDED))
|
||
|
+ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
|
||
|
+ return kexgssgex_init_ctx(ssh, &recv_tok);
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgssgex_complete(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ u_char c;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_HOSTKEY, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_COMPLETE, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_ERROR, NULL);
|
||
|
+
|
||
|
+ debug("Received GSSAPI_COMPLETE");
|
||
|
+ if (gss->msg_tok.value != NULL)
|
||
|
+ fatal("Received GSSAPI_COMPLETE twice?");
|
||
|
+ if ((r = sshpkt_getb_froms(ssh, &gss->server_blob)) != 0 ||
|
||
|
+ (r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &gss->msg_tok)) != 0)
|
||
|
+ fatal("Failed to read message: %s", ssh_err(r));
|
||
|
+
|
||
|
+ /* Is there a token included? */
|
||
|
+ if ((r = sshpkt_get_u8(ssh, &c)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ if (c) {
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0)
|
||
|
+ fatal("Failed to read token: %s", ssh_err(r));
|
||
|
+ /* If we're already complete - protocol error */
|
||
|
+ if (gss->major == GSS_S_COMPLETE)
|
||
|
+ sshpkt_disconnect(ssh, "Protocol error: received token when complete");
|
||
|
+ } else {
|
||
|
+ if (gss->major != GSS_S_COMPLETE)
|
||
|
+ sshpkt_disconnect(ssh, "Protocol error: did not receive final token");
|
||
|
+ }
|
||
|
+ if ((r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("Expecting end of packet.");
|
||
|
+
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return kexgssgex_init_ctx(ssh, &recv_tok);
|
||
|
+
|
||
|
+ return kexgssgex_final(ssh);
|
||
|
+}
|
||
|
+
|
||
|
#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
|
||
|
diff --color -ruNp a/kexgsss.c b/kexgsss.c
|
||
|
--- a/kexgsss.c 2024-05-16 15:49:43.820407648 +0200
|
||
|
+++ b/kexgsss.c 2024-07-02 16:29:05.744790839 +0200
|
||
|
@@ -50,33 +50,18 @@
|
||
|
|
||
|
extern ServerOptions options;
|
||
|
|
||
|
+static int input_kexgss_init(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgss_continue(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgssgex_groupreq(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgssgex_init(int, u_int32_t, struct ssh *);
|
||
|
+static int input_kexgssgex_continue(int, u_int32_t, struct ssh *);
|
||
|
+
|
||
|
int
|
||
|
kexgss_server(struct ssh *ssh)
|
||
|
{
|
||
|
struct kex *kex = ssh->kex;
|
||
|
- OM_uint32 maj_status, min_status;
|
||
|
-
|
||
|
- /*
|
||
|
- * Some GSSAPI implementations use the input value of ret_flags (an
|
||
|
- * output variable) as a means of triggering mechanism specific
|
||
|
- * features. Initializing it to zero avoids inadvertently
|
||
|
- * activating this non-standard behaviour.
|
||
|
- */
|
||
|
-
|
||
|
- OM_uint32 ret_flags = 0;
|
||
|
- gss_buffer_desc gssbuf = {0, NULL}, recv_tok, msg_tok;
|
||
|
- gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
- Gssctxt *ctxt = NULL;
|
||
|
- struct sshbuf *shared_secret = NULL;
|
||
|
- struct sshbuf *client_pubkey = NULL;
|
||
|
- struct sshbuf *server_pubkey = NULL;
|
||
|
- struct sshbuf *empty = sshbuf_new();
|
||
|
- int type = 0;
|
||
|
gss_OID oid;
|
||
|
char *mechs;
|
||
|
- u_char hash[SSH_DIGEST_MAX_LENGTH];
|
||
|
- size_t hashlen;
|
||
|
- int r;
|
||
|
|
||
|
/* Initialise GSSAPI */
|
||
|
|
||
|
@@ -92,135 +77,91 @@ kexgss_server(struct ssh *ssh)
|
||
|
debug2_f("Identifying %s", kex->name);
|
||
|
oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
|
||
|
if (oid == GSS_C_NO_OID)
|
||
|
- fatal("Unknown gssapi mechanism");
|
||
|
+ fatal("Unknown gssapi mechanism");
|
||
|
|
||
|
debug2_f("Acquiring credentials");
|
||
|
|
||
|
- if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctxt, oid)))
|
||
|
+ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid)))
|
||
|
fatal("Unable to acquire credentials for the server");
|
||
|
|
||
|
- do {
|
||
|
- debug("Wait SSH2_MSG_KEXGSS_INIT");
|
||
|
- type = ssh_packet_read(ssh);
|
||
|
- switch(type) {
|
||
|
- case SSH2_MSG_KEXGSS_INIT:
|
||
|
- if (gssbuf.value != NULL)
|
||
|
- fatal("Received KEXGSS_INIT after initialising");
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ ssh_gssapi_build_ctx(&kex->gss);
|
||
|
+ if (kex->gss == NULL)
|
||
|
+ fatal("Unable to allocate memory for gss context");
|
||
|
+
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgss_init);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgss_continue);
|
||
|
+ debug("Wait SSH2_MSG_KEXGSS_INIT");
|
||
|
+ return 0;
|
||
|
+}
|
||
|
|
||
|
- switch (kex->kex_type) {
|
||
|
- case KEX_GSS_GRP1_SHA1:
|
||
|
- case KEX_GSS_GRP14_SHA1:
|
||
|
- case KEX_GSS_GRP14_SHA256:
|
||
|
- case KEX_GSS_GRP16_SHA512:
|
||
|
- r = kex_dh_enc(kex, client_pubkey, &server_pubkey,
|
||
|
- &shared_secret);
|
||
|
- break;
|
||
|
- case KEX_GSS_NISTP256_SHA256:
|
||
|
- r = kex_ecdh_enc(kex, client_pubkey, &server_pubkey,
|
||
|
- &shared_secret);
|
||
|
- break;
|
||
|
- case KEX_GSS_C25519_SHA256:
|
||
|
- r = kex_c25519_enc(kex, client_pubkey, &server_pubkey,
|
||
|
- &shared_secret);
|
||
|
- break;
|
||
|
- default:
|
||
|
- fatal_f("Unexpected KEX type %d", kex->kex_type);
|
||
|
- }
|
||
|
- if (r != 0)
|
||
|
- goto out;
|
||
|
-
|
||
|
- /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
|
||
|
-
|
||
|
- /* Calculate the hash early so we can free the
|
||
|
- * client_pubkey, which has reference to the parent
|
||
|
- * buffer state->incoming_packet
|
||
|
- */
|
||
|
- hashlen = sizeof(hash);
|
||
|
- if ((r = kex_gen_hash(
|
||
|
- kex->hash_alg,
|
||
|
- kex->client_version,
|
||
|
- kex->server_version,
|
||
|
- kex->peer,
|
||
|
- kex->my,
|
||
|
- empty,
|
||
|
- client_pubkey,
|
||
|
- server_pubkey,
|
||
|
- shared_secret,
|
||
|
- hash, &hashlen)) != 0)
|
||
|
- goto out;
|
||
|
-
|
||
|
- gssbuf.value = hash;
|
||
|
- gssbuf.length = hashlen;
|
||
|
-
|
||
|
- sshbuf_free(client_pubkey);
|
||
|
- client_pubkey = NULL;
|
||
|
-
|
||
|
- break;
|
||
|
- case SSH2_MSG_KEXGSS_CONTINUE:
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- break;
|
||
|
- default:
|
||
|
- sshpkt_disconnect(ssh,
|
||
|
- "Protocol error: didn't expect packet type %d",
|
||
|
- type);
|
||
|
- }
|
||
|
+static inline void
|
||
|
+kexgss_accept_ctx(struct ssh *ssh,
|
||
|
+ gss_buffer_desc *recv_tok,
|
||
|
+ gss_buffer_desc *send_tok,
|
||
|
+ OM_uint32 *ret_flags)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ int r;
|
||
|
|
||
|
- maj_status = mm_ssh_gssapi_accept_ctx(ctxt, &recv_tok,
|
||
|
- &send_tok, &ret_flags);
|
||
|
+ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags);
|
||
|
+ gss_release_buffer(&gss->minor, recv_tok);
|
||
|
|
||
|
- gss_release_buffer(&min_status, &recv_tok);
|
||
|
+ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0)
|
||
|
+ fatal("Zero length token output when incomplete");
|
||
|
|
||
|
- if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
|
||
|
- fatal("Zero length token output when incomplete");
|
||
|
+ if (gss->buf.value == NULL)
|
||
|
+ fatal("No client public key");
|
||
|
|
||
|
- if (gssbuf.value == NULL)
|
||
|
- fatal("No client public key");
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED) {
|
||
|
+ debug("Sending GSSAPI_CONTINUE");
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 ||
|
||
|
+ (r = sshpkt_send(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ gss_release_buffer(&gss->minor, send_tok);
|
||
|
+ }
|
||
|
+}
|
||
|
|
||
|
- if (maj_status & GSS_S_CONTINUE_NEEDED) {
|
||
|
- debug("Sending GSSAPI_CONTINUE");
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
|
||
|
- (r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- gss_release_buffer(&min_status, &send_tok);
|
||
|
- }
|
||
|
- } while (maj_status & GSS_S_CONTINUE_NEEDED);
|
||
|
+static inline int
|
||
|
+kexgss_final(struct ssh *ssh,
|
||
|
+ gss_buffer_desc *send_tok,
|
||
|
+ OM_uint32 *ret_flags)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
+ gss_buffer_desc msg_tok;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
|
||
|
|
||
|
- if (GSS_ERROR(maj_status)) {
|
||
|
- if (send_tok.length > 0) {
|
||
|
+ if (GSS_ERROR(gss->major)) {
|
||
|
+ if (send_tok->length > 0) {
|
||
|
if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 ||
|
||
|
(r = sshpkt_send(ssh)) != 0)
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
}
|
||
|
fatal("accept_ctx died");
|
||
|
}
|
||
|
|
||
|
- if (!(ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
+ if (!(*ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
fatal("Mutual Authentication flag wasn't set");
|
||
|
|
||
|
- if (!(ret_flags & GSS_C_INTEG_FLAG))
|
||
|
+ if (!(*ret_flags & GSS_C_INTEG_FLAG))
|
||
|
fatal("Integrity flag wasn't set");
|
||
|
|
||
|
- if (GSS_ERROR(mm_ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok)))
|
||
|
+ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok)))
|
||
|
fatal("Couldn't get MIC");
|
||
|
|
||
|
if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
|
||
|
- (r = sshpkt_put_stringb(ssh, server_pubkey)) != 0 ||
|
||
|
+ (r = sshpkt_put_stringb(ssh, gss->server_pubkey)) != 0 ||
|
||
|
(r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
|
||
|
- if (send_tok.length != 0) {
|
||
|
+ if (send_tok->length != 0) {
|
||
|
if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0)
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
} else {
|
||
|
if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
|
||
|
@@ -229,59 +170,139 @@ kexgss_server(struct ssh *ssh)
|
||
|
if ((r = sshpkt_send(ssh)) != 0)
|
||
|
fatal("sshpkt_send failed: %s", ssh_err(r));
|
||
|
|
||
|
- gss_release_buffer(&min_status, &send_tok);
|
||
|
- gss_release_buffer(&min_status, &msg_tok);
|
||
|
+ gss_release_buffer(&gss->minor, send_tok);
|
||
|
+ gss_release_buffer(&gss->minor, &msg_tok);
|
||
|
|
||
|
if (gss_kex_context == NULL)
|
||
|
- gss_kex_context = ctxt;
|
||
|
+ gss_kex_context = gss;
|
||
|
else
|
||
|
- ssh_gssapi_delete_ctx(&ctxt);
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
|
||
|
- if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
|
||
|
+ if ((r = kex_derive_keys(ssh, gss->hash, gss->hashlen, gss->shared_secret)) == 0)
|
||
|
r = kex_send_newkeys(ssh);
|
||
|
|
||
|
/* If this was a rekey, then save out any delegated credentials we
|
||
|
* just exchanged. */
|
||
|
if (options.gss_store_rekey)
|
||
|
ssh_gssapi_rekey_creds();
|
||
|
-out:
|
||
|
- sshbuf_free(empty);
|
||
|
- explicit_bzero(hash, sizeof(hash));
|
||
|
- sshbuf_free(shared_secret);
|
||
|
- sshbuf_free(client_pubkey);
|
||
|
- sshbuf_free(server_pubkey);
|
||
|
+
|
||
|
+ if (kex->gss != NULL) {
|
||
|
+ explicit_bzero(gss->hash, sizeof(gss->hash));
|
||
|
+ sshbuf_free(gss->shared_secret);
|
||
|
+ gss->shared_secret = NULL;
|
||
|
+ sshbuf_free(gss->server_pubkey);
|
||
|
+ gss->server_pubkey = NULL;
|
||
|
+ }
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
-int
|
||
|
-kexgssgex_server(struct ssh *ssh)
|
||
|
+static int
|
||
|
+input_kexgss_init(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
{
|
||
|
struct kex *kex = ssh->kex;
|
||
|
- OM_uint32 maj_status, min_status;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
+ struct sshbuf *empty;
|
||
|
+ struct sshbuf *client_pubkey = NULL;
|
||
|
+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ OM_uint32 ret_flags = 0;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ debug("SSH2_MSG_KEXGSS_INIT received");
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL);
|
||
|
|
||
|
- /*
|
||
|
- * Some GSSAPI implementations use the input value of ret_flags (an
|
||
|
- * output variable) as a means of triggering mechanism specific
|
||
|
- * features. Initializing it to zero avoids inadvertently
|
||
|
- * activating this non-standard behaviour.
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 ||
|
||
|
+ (r = sshpkt_getb_froms(ssh, &client_pubkey)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ switch (kex->kex_type) {
|
||
|
+ case KEX_GSS_GRP1_SHA1:
|
||
|
+ case KEX_GSS_GRP14_SHA1:
|
||
|
+ case KEX_GSS_GRP14_SHA256:
|
||
|
+ case KEX_GSS_GRP16_SHA512:
|
||
|
+ r = kex_dh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret);
|
||
|
+ break;
|
||
|
+ case KEX_GSS_NISTP256_SHA256:
|
||
|
+ r = kex_ecdh_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret);
|
||
|
+ break;
|
||
|
+ case KEX_GSS_C25519_SHA256:
|
||
|
+ r = kex_c25519_enc(kex, client_pubkey, &gss->server_pubkey, &gss->shared_secret);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ fatal_f("Unexpected KEX type %d", kex->kex_type);
|
||
|
+ }
|
||
|
+ if (r != 0) {
|
||
|
+ sshbuf_free(client_pubkey);
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ return r;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
|
||
|
+
|
||
|
+ if ((empty = sshbuf_new()) == NULL) {
|
||
|
+ sshbuf_free(client_pubkey);
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ return SSH_ERR_ALLOC_FAIL;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Calculate the hash early so we can free the
|
||
|
+ * client_pubkey, which has reference to the parent
|
||
|
+ * buffer state->incoming_packet
|
||
|
*/
|
||
|
+ gss->hashlen = sizeof(gss->hash);
|
||
|
+ r = kex_gen_hash(kex->hash_alg, kex->client_version, kex->server_version,
|
||
|
+ kex->peer, kex->my, empty, client_pubkey, gss->server_pubkey,
|
||
|
+ gss->shared_secret, gss->hash, &gss->hashlen);
|
||
|
+ sshbuf_free(empty);
|
||
|
+ sshbuf_free(client_pubkey);
|
||
|
+ if (r != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ return r;
|
||
|
+ }
|
||
|
+
|
||
|
+ gss->buf.value = gss->hash;
|
||
|
+ gss->buf.length = gss->hashlen;
|
||
|
+
|
||
|
+ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags);
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return 0;
|
||
|
|
||
|
+ return kexgss_final(ssh, &send_tok, &ret_flags);
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgss_continue(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
OM_uint32 ret_flags = 0;
|
||
|
- gss_buffer_desc gssbuf, recv_tok, msg_tok;
|
||
|
- gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
- Gssctxt *ctxt = NULL;
|
||
|
- struct sshbuf *shared_secret = NULL;
|
||
|
- int type = 0;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ kexgss_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags);
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ return kexgss_final(ssh, &send_tok, &ret_flags);
|
||
|
+}
|
||
|
+
|
||
|
+/*******************************************************/
|
||
|
+/******************** KEXGSSGEX ************************/
|
||
|
+/*******************************************************/
|
||
|
+
|
||
|
+int
|
||
|
+kexgssgex_server(struct ssh *ssh)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
gss_OID oid;
|
||
|
char *mechs;
|
||
|
- u_char hash[SSH_DIGEST_MAX_LENGTH];
|
||
|
- size_t hashlen;
|
||
|
- BIGNUM *dh_client_pub = NULL;
|
||
|
- const BIGNUM *pub_key, *dh_p, *dh_g;
|
||
|
- int min = -1, max = -1, nbits = -1;
|
||
|
- int cmin = -1, cmax = -1; /* client proposal */
|
||
|
- struct sshbuf *empty = sshbuf_new();
|
||
|
- int r;
|
||
|
|
||
|
/* Initialise GSSAPI */
|
||
|
|
||
|
@@ -289,153 +310,125 @@ kexgssgex_server(struct ssh *ssh)
|
||
|
* in the GSSAPI code are no longer available. This kludges them back
|
||
|
* into life
|
||
|
*/
|
||
|
- if (!ssh_gssapi_oid_table_ok())
|
||
|
- if ((mechs = ssh_gssapi_server_mechanisms()))
|
||
|
- free(mechs);
|
||
|
+ if (!ssh_gssapi_oid_table_ok()) {
|
||
|
+ mechs = ssh_gssapi_server_mechanisms();
|
||
|
+ free(mechs);
|
||
|
+ }
|
||
|
|
||
|
debug2_f("Identifying %s", kex->name);
|
||
|
oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type);
|
||
|
if (oid == GSS_C_NO_OID)
|
||
|
- fatal("Unknown gssapi mechanism");
|
||
|
+ fatal("Unknown gssapi mechanism");
|
||
|
|
||
|
debug2_f("Acquiring credentials");
|
||
|
|
||
|
- if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&ctxt, oid)))
|
||
|
+ if (GSS_ERROR(mm_ssh_gssapi_server_ctx(&kex->gss, oid)))
|
||
|
fatal("Unable to acquire credentials for the server");
|
||
|
|
||
|
- /* 5. S generates an ephemeral key pair (do the allocations early) */
|
||
|
- debug("Doing group exchange");
|
||
|
- ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUPREQ);
|
||
|
- /* store client proposal to provide valid signature */
|
||
|
- if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 ||
|
||
|
- (r = sshpkt_get_u32(ssh, &nbits)) != 0 ||
|
||
|
- (r = sshpkt_get_u32(ssh, &cmax)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- kex->nbits = nbits;
|
||
|
- kex->min = cmin;
|
||
|
- kex->max = cmax;
|
||
|
- min = MAX(DH_GRP_MIN, cmin);
|
||
|
- max = MIN(DH_GRP_MAX, cmax);
|
||
|
- nbits = MAXIMUM(DH_GRP_MIN, nbits);
|
||
|
- nbits = MINIMUM(DH_GRP_MAX, nbits);
|
||
|
- if (max < min || nbits < min || max < nbits)
|
||
|
- fatal("GSS_GEX, bad parameters: %d !< %d !< %d",
|
||
|
- min, nbits, max);
|
||
|
- kex->dh = mm_choose_dh(min, nbits, max);
|
||
|
- if (kex->dh == NULL) {
|
||
|
- sshpkt_disconnect(ssh, "Protocol error: no matching group found");
|
||
|
- fatal("Protocol error: no matching group found");
|
||
|
- }
|
||
|
+ ssh_gssapi_build_ctx(&kex->gss);
|
||
|
+ if (kex->gss == NULL)
|
||
|
+ fatal("Unable to allocate memory for gss context");
|
||
|
|
||
|
- DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 ||
|
||
|
- (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
|
||
|
- (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
|
||
|
- (r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
-
|
||
|
- if ((r = ssh_packet_write_wait(ssh)) != 0)
|
||
|
- fatal("ssh_packet_write_wait: %s", ssh_err(r));
|
||
|
-
|
||
|
- /* Compute our exchange value in parallel with the client */
|
||
|
- if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0)
|
||
|
- goto out;
|
||
|
+ debug("Doing group exchange");
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, &input_kexgssgex_groupreq);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
|
||
|
- do {
|
||
|
- debug("Wait SSH2_MSG_GSSAPI_INIT");
|
||
|
- type = ssh_packet_read(ssh);
|
||
|
- switch(type) {
|
||
|
- case SSH2_MSG_KEXGSS_INIT:
|
||
|
- if (dh_client_pub != NULL)
|
||
|
- fatal("Received KEXGSS_INIT after initialising");
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_get_bignum2(ssh, &dh_client_pub)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+static inline void
|
||
|
+kexgssgex_accept_ctx(struct ssh *ssh,
|
||
|
+ gss_buffer_desc *recv_tok,
|
||
|
+ gss_buffer_desc *send_tok,
|
||
|
+ OM_uint32 *ret_flags)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ int r;
|
||
|
|
||
|
- /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
|
||
|
- break;
|
||
|
- case SSH2_MSG_KEXGSS_CONTINUE:
|
||
|
- if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh,
|
||
|
- &recv_tok)) != 0 ||
|
||
|
- (r = sshpkt_get_end(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- break;
|
||
|
- default:
|
||
|
- sshpkt_disconnect(ssh,
|
||
|
- "Protocol error: didn't expect packet type %d",
|
||
|
- type);
|
||
|
- }
|
||
|
+ gss->major = mm_ssh_gssapi_accept_ctx(gss, recv_tok, send_tok, ret_flags);
|
||
|
+ gss_release_buffer(&gss->minor, recv_tok);
|
||
|
|
||
|
- maj_status = mm_ssh_gssapi_accept_ctx(ctxt, &recv_tok,
|
||
|
- &send_tok, &ret_flags);
|
||
|
+ if (gss->major != GSS_S_COMPLETE && send_tok->length == 0)
|
||
|
+ fatal("Zero length token output when incomplete");
|
||
|
|
||
|
- gss_release_buffer(&min_status, &recv_tok);
|
||
|
+ if (gss->dh_client_pub == NULL)
|
||
|
+ fatal("No client public key");
|
||
|
|
||
|
- if (maj_status != GSS_S_COMPLETE && send_tok.length == 0)
|
||
|
- fatal("Zero length token output when incomplete");
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED) {
|
||
|
+ debug("Sending GSSAPI_CONTINUE");
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 ||
|
||
|
+ (r = sshpkt_send(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ gss_release_buffer(&gss->minor, send_tok);
|
||
|
+ }
|
||
|
+}
|
||
|
|
||
|
- if (dh_client_pub == NULL)
|
||
|
- fatal("No client public key");
|
||
|
+static inline int
|
||
|
+kexgssgex_final(struct ssh *ssh,
|
||
|
+ gss_buffer_desc *send_tok,
|
||
|
+ OM_uint32 *ret_flags)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ Gssctxt *gss = kex->gss;
|
||
|
+ gss_buffer_desc msg_tok;
|
||
|
+ u_char hash[SSH_DIGEST_MAX_LENGTH];
|
||
|
+ size_t hashlen;
|
||
|
+ const BIGNUM *pub_key, *dh_p, *dh_g;
|
||
|
+ struct sshbuf *shared_secret = NULL;
|
||
|
+ struct sshbuf *empty = NULL;
|
||
|
+ int r;
|
||
|
|
||
|
- if (maj_status & GSS_S_CONTINUE_NEEDED) {
|
||
|
- debug("Sending GSSAPI_CONTINUE");
|
||
|
- if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
|
||
|
- (r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
- gss_release_buffer(&min_status, &send_tok);
|
||
|
- }
|
||
|
- } while (maj_status & GSS_S_CONTINUE_NEEDED);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, NULL);
|
||
|
|
||
|
- if (GSS_ERROR(maj_status)) {
|
||
|
- if (send_tok.length > 0) {
|
||
|
+ if (GSS_ERROR(gss->major)) {
|
||
|
+ if (send_tok->length > 0) {
|
||
|
if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_CONTINUE)) != 0 ||
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0 ||
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0 ||
|
||
|
(r = sshpkt_send(ssh)) != 0)
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
}
|
||
|
fatal("accept_ctx died");
|
||
|
}
|
||
|
|
||
|
- if (!(ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
+ if (!(*ret_flags & GSS_C_MUTUAL_FLAG))
|
||
|
fatal("Mutual Authentication flag wasn't set");
|
||
|
|
||
|
- if (!(ret_flags & GSS_C_INTEG_FLAG))
|
||
|
+ if (!(*ret_flags & GSS_C_INTEG_FLAG))
|
||
|
fatal("Integrity flag wasn't set");
|
||
|
|
||
|
/* calculate shared secret */
|
||
|
- if ((shared_secret = sshbuf_new()) == NULL) {
|
||
|
+ shared_secret = sshbuf_new();
|
||
|
+ if (shared_secret == NULL) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
r = SSH_ERR_ALLOC_FAIL;
|
||
|
goto out;
|
||
|
}
|
||
|
- if ((r = kex_dh_compute_key(kex, dh_client_pub, shared_secret)) != 0)
|
||
|
+ if ((r = kex_dh_compute_key(kex, gss->dh_client_pub, shared_secret)) != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ if ((empty = sshbuf_new()) == NULL) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ r = SSH_ERR_ALLOC_FAIL;
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
|
||
|
DH_get0_key(kex->dh, &pub_key, NULL);
|
||
|
DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
|
||
|
hashlen = sizeof(hash);
|
||
|
- if ((r = kexgex_hash(
|
||
|
- kex->hash_alg,
|
||
|
- kex->client_version,
|
||
|
- kex->server_version,
|
||
|
- kex->peer,
|
||
|
- kex->my,
|
||
|
- empty,
|
||
|
- cmin, nbits, cmax,
|
||
|
- dh_p, dh_g,
|
||
|
- dh_client_pub,
|
||
|
- pub_key,
|
||
|
- sshbuf_ptr(shared_secret), sshbuf_len(shared_secret),
|
||
|
- hash, &hashlen)) != 0)
|
||
|
+ r = kexgex_hash(kex->hash_alg, kex->client_version, kex->server_version,
|
||
|
+ kex->peer, kex->my, empty, kex->min, kex->nbits, kex->max, dh_p, dh_g,
|
||
|
+ gss->dh_client_pub, pub_key, sshbuf_ptr(shared_secret),
|
||
|
+ sshbuf_len(shared_secret), hash, &hashlen);
|
||
|
+ sshbuf_free(empty);
|
||
|
+ if (r != 0)
|
||
|
fatal("kexgex_hash failed: %s", ssh_err(r));
|
||
|
|
||
|
- gssbuf.value = hash;
|
||
|
- gssbuf.length = hashlen;
|
||
|
+ gss->buf.value = hash;
|
||
|
+ gss->buf.length = hashlen;
|
||
|
|
||
|
- if (GSS_ERROR(mm_ssh_gssapi_sign(ctxt, &gssbuf, &msg_tok)))
|
||
|
+ if (GSS_ERROR(mm_ssh_gssapi_sign(gss, &gss->buf, &msg_tok)))
|
||
|
fatal("Couldn't get MIC");
|
||
|
|
||
|
if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_COMPLETE)) != 0 ||
|
||
|
@@ -443,24 +436,24 @@ kexgssgex_server(struct ssh *ssh)
|
||
|
(r = sshpkt_put_string(ssh, msg_tok.value, msg_tok.length)) != 0)
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
|
||
|
- if (send_tok.length != 0) {
|
||
|
+ if (send_tok->length != 0) {
|
||
|
if ((r = sshpkt_put_u8(ssh, 1)) != 0 || /* true */
|
||
|
- (r = sshpkt_put_string(ssh, send_tok.value, send_tok.length)) != 0)
|
||
|
+ (r = sshpkt_put_string(ssh, send_tok->value, send_tok->length)) != 0)
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
} else {
|
||
|
if ((r = sshpkt_put_u8(ssh, 0)) != 0) /* false */
|
||
|
fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
}
|
||
|
if ((r = sshpkt_send(ssh)) != 0)
|
||
|
- fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+ fatal("sshpkt_send failed: %s", ssh_err(r));
|
||
|
|
||
|
- gss_release_buffer(&min_status, &send_tok);
|
||
|
- gss_release_buffer(&min_status, &msg_tok);
|
||
|
+ gss_release_buffer(&gss->minor, send_tok);
|
||
|
+ gss_release_buffer(&gss->minor, &msg_tok);
|
||
|
|
||
|
if (gss_kex_context == NULL)
|
||
|
- gss_kex_context = ctxt;
|
||
|
+ gss_kex_context = gss;
|
||
|
else
|
||
|
- ssh_gssapi_delete_ctx(&ctxt);
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
|
||
|
/* Finally derive the keys and send them */
|
||
|
if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0)
|
||
|
@@ -470,13 +463,128 @@ kexgssgex_server(struct ssh *ssh)
|
||
|
* just exchanged. */
|
||
|
if (options.gss_store_rekey)
|
||
|
ssh_gssapi_rekey_creds();
|
||
|
+
|
||
|
+ if (kex->gss != NULL)
|
||
|
+ BN_clear_free(gss->dh_client_pub);
|
||
|
+
|
||
|
out:
|
||
|
- sshbuf_free(empty);
|
||
|
explicit_bzero(hash, sizeof(hash));
|
||
|
DH_free(kex->dh);
|
||
|
kex->dh = NULL;
|
||
|
- BN_clear_free(dh_client_pub);
|
||
|
sshbuf_free(shared_secret);
|
||
|
return r;
|
||
|
}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgssgex_groupreq(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ struct kex *kex = ssh->kex;
|
||
|
+ const BIGNUM *dh_p, *dh_g;
|
||
|
+ int min = -1, max = -1, nbits = -1;
|
||
|
+ int cmin = -1, cmax = -1; /* client proposal */
|
||
|
+ int r;
|
||
|
+
|
||
|
+ /* 5. S generates an ephemeral key pair (do the allocations early) */
|
||
|
+
|
||
|
+ debug("SSH2_MSG_KEXGSS_GROUPREQ received");
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_GROUPREQ, NULL);
|
||
|
+
|
||
|
+ /* store client proposal to provide valid signature */
|
||
|
+ if ((r = sshpkt_get_u32(ssh, &cmin)) != 0 ||
|
||
|
+ (r = sshpkt_get_u32(ssh, &nbits)) != 0 ||
|
||
|
+ (r = sshpkt_get_u32(ssh, &cmax)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ kex->nbits = nbits;
|
||
|
+ kex->min = cmin;
|
||
|
+ kex->max = cmax;
|
||
|
+ min = MAX(DH_GRP_MIN, cmin);
|
||
|
+ max = MIN(DH_GRP_MAX, cmax);
|
||
|
+ nbits = MAXIMUM(DH_GRP_MIN, nbits);
|
||
|
+ nbits = MINIMUM(DH_GRP_MAX, nbits);
|
||
|
+
|
||
|
+ if (max < min || nbits < min || max < nbits)
|
||
|
+ fatal("GSS_GEX, bad parameters: %d !< %d !< %d", min, nbits, max);
|
||
|
+
|
||
|
+ kex->dh = mm_choose_dh(min, nbits, max);
|
||
|
+ if (kex->dh == NULL) {
|
||
|
+ sshpkt_disconnect(ssh, "Protocol error: no matching group found");
|
||
|
+ fatal("Protocol error: no matching group found");
|
||
|
+ }
|
||
|
+
|
||
|
+ DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
|
||
|
+ if ((r = sshpkt_start(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0 ||
|
||
|
+ (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
|
||
|
+ (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
|
||
|
+ (r = sshpkt_send(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ if ((r = ssh_packet_write_wait(ssh)) != 0)
|
||
|
+ fatal("ssh_packet_write_wait: %s", ssh_err(r));
|
||
|
+
|
||
|
+ /* Compute our exchange value in parallel with the client */
|
||
|
+ if ((r = dh_gen_key(kex->dh, kex->we_need * 8)) != 0) {
|
||
|
+ ssh_gssapi_delete_ctx(&kex->gss);
|
||
|
+ DH_free(kex->dh);
|
||
|
+ kex->dh = NULL;
|
||
|
+ return r;
|
||
|
+ }
|
||
|
+
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, &input_kexgssgex_init);
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_CONTINUE, &input_kexgssgex_continue);
|
||
|
+ debug("Wait SSH2_MSG_KEXGSS_INIT");
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgssgex_init(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ OM_uint32 ret_flags = 0;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ debug("SSH2_MSG_KEXGSS_INIT received");
|
||
|
+ ssh_dispatch_set(ssh, SSH2_MSG_KEXGSS_INIT, NULL);
|
||
|
+
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 ||
|
||
|
+ (r = sshpkt_get_bignum2(ssh, &gss->dh_client_pub)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
|
||
|
+
|
||
|
+ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags);
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ return kexgssgex_final(ssh, &send_tok, &ret_flags);
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+input_kexgssgex_continue(int type,
|
||
|
+ u_int32_t seq,
|
||
|
+ struct ssh *ssh)
|
||
|
+{
|
||
|
+ Gssctxt *gss = ssh->kex->gss;
|
||
|
+ gss_buffer_desc recv_tok, send_tok = GSS_C_EMPTY_BUFFER;
|
||
|
+ OM_uint32 ret_flags = 0;
|
||
|
+ int r;
|
||
|
+
|
||
|
+ if ((r = ssh_gssapi_sshpkt_get_buffer_desc(ssh, &recv_tok)) != 0 ||
|
||
|
+ (r = sshpkt_get_end(ssh)) != 0)
|
||
|
+ fatal("sshpkt failed: %s", ssh_err(r));
|
||
|
+
|
||
|
+ kexgssgex_accept_ctx(ssh, &recv_tok, &send_tok, &ret_flags);
|
||
|
+ if (gss->major & GSS_S_CONTINUE_NEEDED)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ return kexgssgex_final(ssh, &send_tok, &ret_flags);
|
||
|
+}
|
||
|
+
|
||
|
#endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */
|
||
|
diff --color -ruNp a/kex.h b/kex.h
|
||
|
--- a/kex.h 2024-05-16 15:49:43.986410812 +0200
|
||
|
+++ b/kex.h 2024-06-18 12:19:48.580347469 +0200
|
||
|
@@ -29,6 +29,10 @@
|
||
|
#include "mac.h"
|
||
|
#include "crypto_api.h"
|
||
|
|
||
|
+#ifdef GSSAPI
|
||
|
+# include "ssh-gss.h" /* Gssctxt */
|
||
|
+#endif
|
||
|
+
|
||
|
#ifdef WITH_OPENSSL
|
||
|
# include <openssl/bn.h>
|
||
|
# include <openssl/dh.h>
|
||
|
@@ -177,6 +181,7 @@ struct kex {
|
||
|
int hash_alg;
|
||
|
int ec_nid;
|
||
|
#ifdef GSSAPI
|
||
|
+ Gssctxt *gss;
|
||
|
int gss_deleg_creds;
|
||
|
int gss_trust_dns;
|
||
|
char *gss_host;
|
||
|
diff --color -ruNp a/ssh-gss.h b/ssh-gss.h
|
||
|
--- a/ssh-gss.h 2024-05-16 15:49:43.837407972 +0200
|
||
|
+++ b/ssh-gss.h 2024-06-27 14:12:48.659866937 +0200
|
||
|
@@ -88,6 +88,8 @@ extern char **k5users_allowed_cmds;
|
||
|
KEX_GSS_GRP14_SHA1_ID "," \
|
||
|
KEX_GSS_GEX_SHA1_ID
|
||
|
|
||
|
+#include "digest.h" /* SSH_DIGEST_MAX_LENGTH */
|
||
|
+
|
||
|
typedef struct {
|
||
|
char *filename;
|
||
|
char *envvar;
|
||
|
@@ -127,6 +129,16 @@ typedef struct {
|
||
|
gss_cred_id_t creds; /* server */
|
||
|
gss_name_t client; /* server */
|
||
|
gss_cred_id_t client_creds; /* both */
|
||
|
+ struct sshbuf *shared_secret; /* both */
|
||
|
+ struct sshbuf *server_pubkey; /* server */
|
||
|
+ struct sshbuf *server_blob; /* client */
|
||
|
+ struct sshbuf *server_host_key_blob; /* client */
|
||
|
+ gss_buffer_desc msg_tok; /* client */
|
||
|
+ gss_buffer_desc buf; /* both */
|
||
|
+ u_char hash[SSH_DIGEST_MAX_LENGTH]; /* both */
|
||
|
+ size_t hashlen; /* both */
|
||
|
+ int first; /* client */
|
||
|
+ BIGNUM *dh_client_pub; /* server (gex) */
|
||
|
} Gssctxt;
|
||
|
|
||
|
extern ssh_gssapi_mech *supported_mechs[];
|