/* * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #if defined(GSSAPI) && defined(WITH_OPENSSL) #include "includes.h" #include #include #include #include "xmalloc.h" #include "sshbuf.h" #include "ssh2.h" #include "sshkey.h" #include "cipher.h" #include "kex.h" #include "log.h" #include "packet.h" #include "dh.h" #include "digest.h" #include "ssherr.h" #include "ssh-gss.h" int 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 *server_blob = NULL; struct sshbuf *shared_secret = NULL; struct sshbuf *server_host_key_blob = NULL; struct sshbuf *empty = NULL; u_char *msg; int type = 0; int first = 1; 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("%s: Unexpected KEX type %d", __func__, kex->kex_type); } if (r != 0) 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) { debug("Received KEXGSS_HOSTKEY"); if (server_host_key_blob) fatal("Server host key received more than once"); if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0) fatal("Failed to read server host key: %s", ssh_err(r)); } } 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); break; case KEX_GSS_C25519_SHA256: if (sshbuf_ptr(server_blob)[sshbuf_len(server_blob)] & 0x80) fatal("The received key has MSB of last octet set!"); r = kex_c25519_dec(kex, 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_ptr(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); break; default: r = SSH_ERR_INVALID_ARGUMENT; break; } if (r != 0) goto out; if ((empty = sshbuf_new()) == NULL) { 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) fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); gssbuf.value = hash; gssbuf.length = hashlen; /* Verify that the hash matches the MIC we just got. */ if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); gss_release_buffer(&min_status, &msg_tok); if (kex->gss_deleg_creds) ssh_gssapi_credentials_updated(ctxt); if (gss_kex_context == NULL) gss_kex_context = ctxt; else ssh_gssapi_delete_ctx(&ctxt); if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) r = kex_send_newkeys(ssh); 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); sshbuf_free(shared_secret); sshbuf_free(kex->client_pub); kex->client_pub = NULL; return r; } int kexgssgex_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) 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"); debug("Doing group exchange"); nbits = dh_estimate(kex->dh_need * 8); 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)); if ((r = ssh_packet_read_expect(ssh, SSH2_MSG_KEXGSS_GROUP)) != 0) fatal("Error: %s", ssh_err(r)); 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) < min || BN_num_bits(p) > max) fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", min, BN_num_bits(p), 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) goto out; DH_get0_key(kex->dh, &pub_key, NULL); token_ptr = GSS_C_NO_BUFFER; 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"); } /* 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_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) { debug("Received KEXGSS_HOSTKEY"); if (server_host_key_blob) fatal("Server host key received more than once"); if ((r = sshpkt_getb_froms(ssh, &server_host_key_blob)) != 0) fatal("sshpkt failed: %s", ssh_err(r)); } } 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); /* * We _must_ have received a COMPLETE message in reply from the * server, which will have set dh_server_pub 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) goto out; sshbuf_free(buf); buf = NULL; if ((shared_secret = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = kex_dh_compute_key(kex, dh_server_pub, shared_secret)) != 0) goto out; if ((empty = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } 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) fatal("Failed to calculate hash: %s", ssh_err(r)); gssbuf.value = hash; gssbuf.length = hashlen; /* Verify that the hash matches the MIC we just got. */ if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) sshpkt_disconnect(ssh, "Hash's MIC didn't verify"); gss_release_buffer(&min_status, &msg_tok); /* save session id */ if (kex->session_id == NULL) { kex->session_id_len = hashlen; kex->session_id = xmalloc(kex->session_id_len); memcpy(kex->session_id, hash, kex->session_id_len); } if (kex->gss_deleg_creds) ssh_gssapi_credentials_updated(ctxt); if (gss_kex_context == NULL) gss_kex_context = ctxt; else ssh_gssapi_delete_ctx(&ctxt); /* Finally derive the keys and send them */ if ((r = kex_derive_keys(ssh, hash, hashlen, shared_secret)) == 0) r = kex_send_newkeys(ssh); 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; } #endif /* defined(GSSAPI) && defined(WITH_OPENSSL) */