xref: /titanic_44/usr/src/cmd/ssh/libssh/common/kexgssc.c (revision 7aec1d6e253b21f9e9b7ef68b4d81ab9859b51fe)
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 2004 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 	u_int sbloblen = 0;
76 	u_int 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,
106 					       xxx_host,
107 					       kex->options.gss_deleg_creds,
108 					       token_ptr,
109 					       &send_tok);
110 
111 		if (GSS_ERROR(maj_status)) {
112 			ssh_gssapi_error(ctxt, "performing GSS-API protected "
113 				"SSHv2 key exchange");
114 			(void) gss_release_buffer(&min_status, &send_tok);
115 			packet_disconnect("A GSS-API error occurred during "
116 				"GSS-API protected SSHv2 key exchange\n");
117 		}
118 
119 		/* If we've got an old receive buffer get rid of it */
120 		if (token_ptr != GSS_C_NO_BUFFER) {
121 			/* We allocated recv_tok */
122 			xfree(recv_tok.value);
123 			recv_tok.value = NULL;
124 			recv_tok.length = 0;
125 			token_ptr = GSS_C_NO_BUFFER;
126 		}
127 
128 		if (maj_status == GSS_S_COMPLETE) {
129 			/* If mutual state flag is not true, kex fails */
130 			if (!(ctxt->flags & GSS_C_MUTUAL_FLAG)) {
131 				fatal("Mutual authentication failed");
132 			}
133 			/* If integ avail flag is not true kex fails */
134 			if (!(ctxt->flags & GSS_C_INTEG_FLAG)) {
135 				fatal("Integrity check failed");
136 			}
137 		}
138 
139 		/* If we have data to send, then the last message that we
140 		 * received cannot have been a 'complete'. */
141 		if (send_tok.length !=0) {
142 			if (first) {
143 				packet_start(SSH2_MSG_KEXGSS_INIT);
144 				packet_put_string(send_tok.value,
145 					  	  send_tok.length);
146 				packet_put_bignum2(dh->pub_key);
147 				first=0;
148 			} else {
149 				packet_start(SSH2_MSG_KEXGSS_CONTINUE);
150 				packet_put_string(send_tok.value,
151 						  send_tok.length);
152 			}
153 			(void) gss_release_buffer(&min_status, &send_tok);
154 			packet_send();
155 			packet_write_wait();
156 
157 
158 			/* If we've sent them data, they'd better be polite
159 			 * and reply. */
160 
161 next_packet:
162 			/*
163 			 * We need to catch connection closing w/o error
164 			 * tokens or messages so we can tell the user
165 			 * _something_ more useful than "Connection
166 			 * closed by ..."
167 			 *
168 			 * We use a fatal cleanup function as that's
169 			 * all, really, that we can do for now.
170 			 */
171 			fatal_add_cleanup(kexgss_verbose_cleanup, NULL);
172 			type = packet_read();
173 			fatal_remove_cleanup(kexgss_verbose_cleanup, NULL);
174 			switch (type) {
175 			case SSH2_MSG_KEXGSS_HOSTKEY:
176 				debug("Received KEXGSS_HOSTKEY");
177 				server_host_key_blob =
178 					packet_get_string(&sbloblen);
179 				server_host_key =
180 					key_from_blob(server_host_key_blob,
181 						sbloblen);
182 				goto next_packet; /* there MUSt be another */
183 				break;
184 			case SSH2_MSG_KEXGSS_CONTINUE:
185 				debug("Received GSSAPI_CONTINUE");
186 				if (maj_status == GSS_S_COMPLETE)
187 					packet_disconnect("Protocol error: "
188 						"received GSS-API context token"
189 						" though the context was already"
190 						" established");
191 				recv_tok.value=packet_get_string(&strlen);
192 				recv_tok.length=strlen; /* u_int vs. size_t */
193 				break;
194 			case SSH2_MSG_KEXGSS_COMPLETE:
195 				debug("Received GSSAPI_COMPLETE");
196 			        packet_get_bignum2(dh_server_pub);
197 			    	msg_tok.value=packet_get_string(&strlen);
198 				msg_tok.length=strlen; /* u_int vs. size_t */
199 
200 				/* Is there a token included? */
201 				if (packet_get_char()) {
202 					recv_tok.value=
203 					    packet_get_string(&strlen);
204 					recv_tok.length=strlen; /*u_int/size_t*/
205 				}
206 				if (recv_tok.length > 0 &&
207 				    maj_status == GSS_S_COMPLETE) {
208 					packet_disconnect("Protocol error: "
209 						"received GSS-API context token"
210 						" though the context was already"
211 						" established");
212 				} else if (recv_tok.length == 0 &&
213 					maj_status == GSS_S_CONTINUE_NEEDED) {
214 				   	/* No token included */
215 					packet_disconnect("Protocol error: "
216 						"did not receive expected "
217 						"GSS-API context token");
218 				}
219 				break;
220 			case SSH2_MSG_KEXGSS_ERROR:
221 				smaj_status=packet_get_int();
222 				smin_status=packet_get_int();
223 				msg = packet_get_string(NULL);
224 				lang = packet_get_string(NULL);
225 				xfree(lang);
226 				error("Server had a GSS-API error; the "
227 					"connection will close (%d/%d):\n%s",
228 					smaj_status, smin_status, msg);
229 				error("Use the GssKeyEx option to disable "
230 					"GSS-API key exchange and try again.");
231 				packet_disconnect("The server had a GSS-API "
232 					"error during GSS-API protected SSHv2 "
233 					"key exchange\n");
234 				break;
235 			default:
236 				packet_disconnect("Protocol error: "
237 					"didn't expect packet type %d", type);
238 			}
239 			if (recv_tok.value)
240 				token_ptr=&recv_tok;
241 		} else {
242 			/* No data, and not complete */
243 			if (maj_status != GSS_S_COMPLETE) {
244 				fatal("Not complete, and no token output");
245 			}
246 		}
247     	} while (maj_status == GSS_S_CONTINUE_NEEDED);
248 
249     	/* We _must_ have received a COMPLETE message in reply from the
250     	 * server, which will have set dh_server_pub and msg_tok */
251 
252     	if (type != SSH2_MSG_KEXGSS_COMPLETE)
253 	    fatal("Expected SSH2_MSG_KEXGSS_COMPLETE never arrived");
254     	if (maj_status != GSS_S_COMPLETE)
255 	    fatal("Internal error in GSS-API protected SSHv2 key exchange");
256 
257 	/* Check f in range [1, p-1] */
258         if (!dh_pub_is_valid(dh, dh_server_pub))
259                         packet_disconnect("bad server public DH value");
260 
261         /* compute K=f^x mod p */
262         klen = DH_size(dh);
263         kbuf = xmalloc(klen);
264         kout = DH_compute_key(kbuf, dh_server_pub, dh);
265 
266         shared_secret = BN_new();
267         BN_bin2bn(kbuf,kout, shared_secret);
268         (void) memset(kbuf, 0, klen);
269         xfree(kbuf);
270 
271         /* The GSS hash is identical to the DH one */
272         hash = kex_dh_hash(
273  	    kex->client_version_string,
274             kex->server_version_string,
275             buffer_ptr(&kex->my), buffer_len(&kex->my),
276             buffer_ptr(&kex->peer), buffer_len(&kex->peer),
277             server_host_key_blob, sbloblen, /* server host key */
278             dh->pub_key,	/* e */
279             dh_server_pub,	/* f */
280             shared_secret	/* K */
281         );
282 
283         gssbuf.value=hash;
284         gssbuf.length=20;
285 
286         /* Verify that H matches the token we just got. */
287 	if ((maj_status = gss_verify_mic(&min_status,
288         	       		         ctxt->context,
289         	                         &gssbuf,
290         	                         &msg_tok,
291         	                         NULL))) {
292 
293 		packet_disconnect("Hash's MIC didn't verify");
294       	}
295 
296 	if (server_host_key && kex->accept_host_key != NULL)
297 		(void) kex->accept_host_key(server_host_key);
298 
299         DH_free(dh);
300 
301 	xxx_gssctxt = ctxt; /* for gss keyex w/ mic userauth */
302 
303         /* save session id */
304         if (kex->session_id == NULL) {
305         	kex->session_id_len = 20;
306         	kex->session_id = xmalloc(kex->session_id_len);
307         	(void) memcpy(kex->session_id, hash, kex->session_id_len);
308         }
309 
310 	kex_derive_keys(kex, hash, shared_secret);
311 	BN_clear_free(shared_secret);
312         kex_finish(kex);
313 }
314 
315 /* ARGSUSED */
316 static
317 void
318 kexgss_verbose_cleanup(void *arg)
319 {
320 	error("The GSS-API protected key exchange has failed without "
321 		"indication\nfrom the server, possibly due to misconfiguration "
322 		"of the server.");
323 	error("Use the GssKeyEx option to disable GSS-API key exchange "
324 		"and try again.");
325 }
326 
327 #endif /* GSSAPI */
328