Home | History | Annotate | Download | only in common
      1 /*
      2  * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
     14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 /*
     25  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     26  * Use is subject to license terms.
     27  */
     28 
     29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     30 
     31 
     32 #include "includes.h"
     33 
     34 #ifdef GSSAPI
     35 
     36 #include <openssl/crypto.h>
     37 #include <openssl/bn.h>
     38 
     39 #include "xmalloc.h"
     40 #include "buffer.h"
     41 #include "bufaux.h"
     42 #include "kex.h"
     43 #include "log.h"
     44 #include "packet.h"
     45 #include "dh.h"
     46 #include "canohost.h"
     47 #include "ssh2.h"
     48 #include "ssh-gss.h"
     49 
     50 extern char *xxx_host;
     51 
     52 Gssctxt *xxx_gssctxt;
     53 
     54 static void kexgss_verbose_cleanup(void *arg);
     55 
     56 void
     57 kexgss_client(Kex *kex)
     58 {
     59 	gss_buffer_desc gssbuf, send_tok, recv_tok, msg_tok;
     60 	gss_buffer_t token_ptr;
     61 	gss_OID mech = GSS_C_NULL_OID;
     62 	Gssctxt *ctxt = NULL;
     63 	OM_uint32 maj_status, min_status, smaj_status, smin_status;
     64 	unsigned int klen, kout;
     65 	DH *dh;
     66 	BIGNUM *dh_server_pub = 0;
     67 	BIGNUM *shared_secret = 0;
     68 	Key *server_host_key = NULL;
     69 	unsigned char *kbuf;
     70 	unsigned char *hash;
     71 	unsigned char *server_host_key_blob = NULL;
     72 	char *msg, *lang;
     73 	int type = 0;
     74 	int first = 1;
     75 	uint_t sbloblen = 0;
     76 	uint_t strlen;
     77 
     78 	/* Map the negotiated kex name to a mech OID */
     79 	ssh_gssapi_oid_of_kexname(kex->name, &mech);
     80 	if (mech == GSS_C_NULL_OID)
     81 		fatal("Couldn't match the negotiated GSS key exchange");
     82 
     83 	ssh_gssapi_build_ctx(&ctxt, 1, mech);
     84 
     85 	/* This code should match that in ssh_dh1_client */
     86 
     87 	/* Step 1 - e is dh->pub_key */
     88 	dh = dh_new_group1();
     89 	dh_gen_key(dh, kex->we_need * 8);
     90 
     91 	/* This is f, we initialise it now to make life easier */
     92 	dh_server_pub = BN_new();
     93 	if (dh_server_pub == NULL) {
     94 		fatal("dh_server_pub == NULL");
     95 	}
     96 
     97 	token_ptr = GSS_C_NO_BUFFER;
     98 
     99 	recv_tok.value = NULL;
    100 	recv_tok.length = 0;
    101 
    102 	do {
    103 		debug("Calling gss_init_sec_context");
    104 
    105 		maj_status = ssh_gssapi_init_ctx(ctxt, xxx_host,
    106 		    kex->options.gss_deleg_creds, token_ptr, &send_tok);
    107 
    108 		if (GSS_ERROR(maj_status)) {
    109 			ssh_gssapi_error(ctxt, "performing GSS-API protected "
    110 			    "SSHv2 key exchange");
    111 			(void) gss_release_buffer(&min_status, &send_tok);
    112 			packet_disconnect("A GSS-API error occurred during "
    113 			    "GSS-API protected SSHv2 key exchange\n");
    114 		}
    115 
    116 		/* If we've got an old receive buffer get rid of it */
    117 		if (token_ptr != GSS_C_NO_BUFFER) {
    118 			/* We allocated recv_tok */
    119 			xfree(recv_tok.value);
    120 			recv_tok.value = NULL;
    121 			recv_tok.length = 0;
    122 			token_ptr = GSS_C_NO_BUFFER;
    123 		}
    124 
    125 		if (maj_status == GSS_S_COMPLETE) {
    126 			/* If mutual state flag is not true, kex fails */
    127 			if (!(ctxt->flags & GSS_C_MUTUAL_FLAG)) {
    128 				fatal("Mutual authentication failed");
    129 			}
    130 			/* If integ avail flag is not true kex fails */
    131 			if (!(ctxt->flags & GSS_C_INTEG_FLAG)) {
    132 				fatal("Integrity check failed");
    133 			}
    134 		}
    135 
    136 		/*
    137 		 * If we have data to send, then the last message that we
    138 		 * received cannot have been a 'complete'.
    139 		 */
    140 		if (send_tok.length != 0) {
    141 			if (first) {
    142 				packet_start(SSH2_MSG_KEXGSS_INIT);
    143 				packet_put_string(send_tok.value,
    144 				    send_tok.length);
    145 				packet_put_bignum2(dh->pub_key);
    146 				first = 0;
    147 			} else {
    148 				packet_start(SSH2_MSG_KEXGSS_CONTINUE);
    149 				packet_put_string(send_tok.value,
    150 				    send_tok.length);
    151 			}
    152 			(void) gss_release_buffer(&min_status, &send_tok);
    153 			packet_send();
    154 			packet_write_wait();
    155 
    156 
    157 			/*
    158 			 * If we've sent them data, they'd better be polite and
    159 			 * reply.
    160 			 */
    161 
    162 next_packet:
    163 			/*
    164 			 * We need to catch connection closing w/o error
    165 			 * tokens or messages so we can tell the user
    166 			 * _something_ more useful than "Connection
    167 			 * closed by ..."
    168 			 *
    169 			 * We use a fatal cleanup function as that's
    170 			 * all, really, that we can do for now.
    171 			 */
    172 			fatal_add_cleanup(kexgss_verbose_cleanup, NULL);
    173 			type = packet_read();
    174 			fatal_remove_cleanup(kexgss_verbose_cleanup, NULL);
    175 			switch (type) {
    176 			case SSH2_MSG_KEXGSS_HOSTKEY:
    177 				debug("Received KEXGSS_HOSTKEY");
    178 				server_host_key_blob =
    179 				    packet_get_string(&sbloblen);
    180 				server_host_key =
    181 				    key_from_blob(server_host_key_blob,
    182 				    sbloblen);
    183 				goto next_packet; /* there MUSt be another */
    184 				break;
    185 			case SSH2_MSG_KEXGSS_CONTINUE:
    186 				debug("Received GSSAPI_CONTINUE");
    187 				if (maj_status == GSS_S_COMPLETE)
    188 					packet_disconnect("Protocol error: "
    189 					    "received GSS-API context token "
    190 					    "though the context was already "
    191 					    "established");
    192 				recv_tok.value = packet_get_string(&strlen);
    193 				recv_tok.length = strlen; /* u_int vs. size_t */
    194 				break;
    195 			case SSH2_MSG_KEXGSS_COMPLETE:
    196 				debug("Received GSSAPI_COMPLETE");
    197 				packet_get_bignum2(dh_server_pub);
    198 				msg_tok.value = packet_get_string(&strlen);
    199 				msg_tok.length = strlen; /* u_int vs. size_t */
    200 
    201 				/* Is there a token included? */
    202 				if (packet_get_char()) {
    203 					recv_tok.value =
    204 					    packet_get_string(&strlen);
    205 					/* u_int/size_t */
    206 					recv_tok.length = strlen;
    207 				}
    208 				if (recv_tok.length > 0 &&
    209 				    maj_status == GSS_S_COMPLETE) {
    210 					packet_disconnect("Protocol error: "
    211 					    "received GSS-API context token "
    212 					    "though the context was already "
    213 					    "established");
    214 				} else if (recv_tok.length == 0 &&
    215 				    maj_status == GSS_S_CONTINUE_NEEDED) {
    216 					/* No token included */
    217 					packet_disconnect("Protocol error: "
    218 					    "did not receive expected "
    219 					    "GSS-API context token");
    220 				}
    221 				break;
    222 			case SSH2_MSG_KEXGSS_ERROR:
    223 				smaj_status = packet_get_int();
    224 				smin_status = packet_get_int();
    225 				msg = packet_get_string(NULL);
    226 				lang = packet_get_string(NULL);
    227 				xfree(lang);
    228 				error("Server had a GSS-API error; the "
    229 				    "connection will close (%d/%d):\n%s",
    230 				    smaj_status, smin_status, msg);
    231 				error("Use the GssKeyEx option to disable "
    232 				    "GSS-API key exchange and try again.");
    233 				packet_disconnect("The server had a GSS-API "
    234 				    "error during GSS-API protected SSHv2 "
    235 				    "key exchange\n");
    236 				break;
    237 			default:
    238 				packet_disconnect("Protocol error: "
    239 				    "didn't expect packet type %d", type);
    240 			}
    241 			if (recv_tok.value)
    242 				token_ptr = &recv_tok;
    243 		} else {
    244 			/* No data, and not complete */
    245 			if (maj_status != GSS_S_COMPLETE) {
    246 				fatal("Not complete, and no token output");
    247 			}
    248 		}
    249 	} while (maj_status == GSS_S_CONTINUE_NEEDED);
    250 
    251 	/*
    252 	 * We _must_ have received a COMPLETE message in reply from the
    253 	 * server, which will have set dh_server_pub and msg_tok.
    254 	 */
    255 	if (type != SSH2_MSG_KEXGSS_COMPLETE)
    256 		fatal("Expected SSH2_MSG_KEXGSS_COMPLETE never arrived");
    257 	if (maj_status != GSS_S_COMPLETE)
    258 		fatal("Internal error in GSS-API protected SSHv2 key exchange");
    259 
    260 	/* Check f in range [1, p-1] */
    261 	if (!dh_pub_is_valid(dh, dh_server_pub))
    262 		packet_disconnect("bad server public DH value");
    263 
    264 	/* compute K=f^x mod p */
    265 	klen = DH_size(dh);
    266 	kbuf = xmalloc(klen);
    267 	kout = DH_compute_key(kbuf, dh_server_pub, dh);
    268 
    269 	shared_secret = BN_new();
    270 	BN_bin2bn(kbuf, kout, shared_secret);
    271 	(void) memset(kbuf, 0, klen);
    272 	xfree(kbuf);
    273 
    274 	/* The GSS hash is identical to the DH one */
    275 	hash = kex_dh_hash(
    276 	    kex->client_version_string,
    277 	    kex->server_version_string,
    278 	    buffer_ptr(&kex->my), buffer_len(&kex->my),
    279 	    buffer_ptr(&kex->peer), buffer_len(&kex->peer),
    280 	    server_host_key_blob, sbloblen, /* server host key */
    281 	    dh->pub_key,	/* e */
    282 	    dh_server_pub,	/* f */
    283 	    shared_secret);	/* K */
    284 
    285 	gssbuf.value = hash;
    286 	gssbuf.length = 20;
    287 
    288 	/* Verify that H matches the token we just got. */
    289 	if ((maj_status = gss_verify_mic(&min_status, ctxt->context, &gssbuf,
    290 	    &msg_tok, NULL))) {
    291 		packet_disconnect("Hash's MIC didn't verify");
    292 	}
    293 
    294 	if (server_host_key && kex->accept_host_key != NULL)
    295 		(void) kex->accept_host_key(server_host_key);
    296 
    297 	DH_free(dh);
    298 
    299 	xxx_gssctxt = ctxt; /* for gss keyex w/ mic userauth */
    300 
    301 	/* save session id */
    302 	if (kex->session_id == NULL) {
    303 		kex->session_id_len = 20;
    304 		kex->session_id = xmalloc(kex->session_id_len);
    305 		(void) memcpy(kex->session_id, hash, kex->session_id_len);
    306 	}
    307 
    308 	kex_derive_keys(kex, hash, shared_secret);
    309 	BN_clear_free(shared_secret);
    310 	kex_finish(kex);
    311 }
    312 
    313 /* ARGSUSED */
    314 static
    315 void
    316 kexgss_verbose_cleanup(void *arg)
    317 {
    318 	error("The GSS-API protected key exchange has failed without "
    319 	    "indication\nfrom the server, possibly due to misconfiguration "
    320 	    "of the server.");
    321 	error("Use the GssKeyEx option to disable GSS-API key exchange "
    322 	    "and try again.");
    323 }
    324 
    325 #endif /* GSSAPI */
    326