10Sstevel@tonic-gate /*
20Sstevel@tonic-gate * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
30Sstevel@tonic-gate *
40Sstevel@tonic-gate * Redistribution and use in source and binary forms, with or without
50Sstevel@tonic-gate * modification, are permitted provided that the following conditions
60Sstevel@tonic-gate * are met:
70Sstevel@tonic-gate * 1. Redistributions of source code must retain the above copyright
80Sstevel@tonic-gate * notice, this list of conditions and the following disclaimer.
90Sstevel@tonic-gate * 2. Redistributions in binary form must reproduce the above copyright
100Sstevel@tonic-gate * notice, this list of conditions and the following disclaimer in the
110Sstevel@tonic-gate * documentation and/or other materials provided with the distribution.
120Sstevel@tonic-gate *
130Sstevel@tonic-gate * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
140Sstevel@tonic-gate * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
150Sstevel@tonic-gate * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
160Sstevel@tonic-gate * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
170Sstevel@tonic-gate * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
180Sstevel@tonic-gate * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
190Sstevel@tonic-gate * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
200Sstevel@tonic-gate * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
210Sstevel@tonic-gate * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
220Sstevel@tonic-gate * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
230Sstevel@tonic-gate */
240Sstevel@tonic-gate /*
25*6080Sjp161948 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
260Sstevel@tonic-gate * Use is subject to license terms.
270Sstevel@tonic-gate */
280Sstevel@tonic-gate
29*6080Sjp161948 #pragma ident "%Z%%M% %I% %E% SMI"
300Sstevel@tonic-gate
310Sstevel@tonic-gate
320Sstevel@tonic-gate #include "includes.h"
330Sstevel@tonic-gate
340Sstevel@tonic-gate #ifdef GSSAPI
350Sstevel@tonic-gate
360Sstevel@tonic-gate #include <openssl/crypto.h>
370Sstevel@tonic-gate #include <openssl/bn.h>
380Sstevel@tonic-gate
390Sstevel@tonic-gate #include "xmalloc.h"
400Sstevel@tonic-gate #include "buffer.h"
410Sstevel@tonic-gate #include "bufaux.h"
420Sstevel@tonic-gate #include "kex.h"
430Sstevel@tonic-gate #include "log.h"
440Sstevel@tonic-gate #include "packet.h"
450Sstevel@tonic-gate #include "dh.h"
460Sstevel@tonic-gate #include "canohost.h"
470Sstevel@tonic-gate #include "ssh2.h"
480Sstevel@tonic-gate #include "ssh-gss.h"
490Sstevel@tonic-gate
500Sstevel@tonic-gate extern char *xxx_host;
510Sstevel@tonic-gate
520Sstevel@tonic-gate Gssctxt *xxx_gssctxt;
530Sstevel@tonic-gate
540Sstevel@tonic-gate static void kexgss_verbose_cleanup(void *arg);
550Sstevel@tonic-gate
560Sstevel@tonic-gate void
kexgss_client(Kex * kex)570Sstevel@tonic-gate kexgss_client(Kex *kex)
580Sstevel@tonic-gate {
59*6080Sjp161948 gss_buffer_desc gssbuf, send_tok, recv_tok, msg_tok;
600Sstevel@tonic-gate gss_buffer_t token_ptr;
610Sstevel@tonic-gate gss_OID mech = GSS_C_NULL_OID;
620Sstevel@tonic-gate Gssctxt *ctxt = NULL;
630Sstevel@tonic-gate OM_uint32 maj_status, min_status, smaj_status, smin_status;
640Sstevel@tonic-gate unsigned int klen, kout;
65*6080Sjp161948 DH *dh;
660Sstevel@tonic-gate BIGNUM *dh_server_pub = 0;
67*6080Sjp161948 BIGNUM *shared_secret = 0;
680Sstevel@tonic-gate Key *server_host_key = NULL;
690Sstevel@tonic-gate unsigned char *kbuf;
700Sstevel@tonic-gate unsigned char *hash;
710Sstevel@tonic-gate unsigned char *server_host_key_blob = NULL;
720Sstevel@tonic-gate char *msg, *lang;
730Sstevel@tonic-gate int type = 0;
740Sstevel@tonic-gate int first = 1;
75*6080Sjp161948 uint_t sbloblen = 0;
76*6080Sjp161948 uint_t strlen;
77*6080Sjp161948
78*6080Sjp161948 /* Map the negotiated kex name to a mech OID */
790Sstevel@tonic-gate ssh_gssapi_oid_of_kexname(kex->name, &mech);
800Sstevel@tonic-gate if (mech == GSS_C_NULL_OID)
810Sstevel@tonic-gate fatal("Couldn't match the negotiated GSS key exchange");
820Sstevel@tonic-gate
830Sstevel@tonic-gate ssh_gssapi_build_ctx(&ctxt, 1, mech);
840Sstevel@tonic-gate
850Sstevel@tonic-gate /* This code should match that in ssh_dh1_client */
86*6080Sjp161948
870Sstevel@tonic-gate /* Step 1 - e is dh->pub_key */
880Sstevel@tonic-gate dh = dh_new_group1();
890Sstevel@tonic-gate dh_gen_key(dh, kex->we_need * 8);
900Sstevel@tonic-gate
910Sstevel@tonic-gate /* This is f, we initialise it now to make life easier */
92*6080Sjp161948 dh_server_pub = BN_new();
93*6080Sjp161948 if (dh_server_pub == NULL) {
94*6080Sjp161948 fatal("dh_server_pub == NULL");
95*6080Sjp161948 }
96*6080Sjp161948
970Sstevel@tonic-gate token_ptr = GSS_C_NO_BUFFER;
98*6080Sjp161948
99*6080Sjp161948 recv_tok.value = NULL;
100*6080Sjp161948 recv_tok.length = 0;
1010Sstevel@tonic-gate
1020Sstevel@tonic-gate do {
1030Sstevel@tonic-gate debug("Calling gss_init_sec_context");
104*6080Sjp161948
105*6080Sjp161948 maj_status = ssh_gssapi_init_ctx(ctxt, xxx_host,
106*6080Sjp161948 kex->options.gss_deleg_creds, token_ptr, &send_tok);
1070Sstevel@tonic-gate
1080Sstevel@tonic-gate if (GSS_ERROR(maj_status)) {
1090Sstevel@tonic-gate ssh_gssapi_error(ctxt, "performing GSS-API protected "
110*6080Sjp161948 "SSHv2 key exchange");
1110Sstevel@tonic-gate (void) gss_release_buffer(&min_status, &send_tok);
1120Sstevel@tonic-gate packet_disconnect("A GSS-API error occurred during "
113*6080Sjp161948 "GSS-API protected SSHv2 key exchange\n");
1140Sstevel@tonic-gate }
1150Sstevel@tonic-gate
1160Sstevel@tonic-gate /* If we've got an old receive buffer get rid of it */
1170Sstevel@tonic-gate if (token_ptr != GSS_C_NO_BUFFER) {
1180Sstevel@tonic-gate /* We allocated recv_tok */
1190Sstevel@tonic-gate xfree(recv_tok.value);
1200Sstevel@tonic-gate recv_tok.value = NULL;
1210Sstevel@tonic-gate recv_tok.length = 0;
1220Sstevel@tonic-gate token_ptr = GSS_C_NO_BUFFER;
1230Sstevel@tonic-gate }
124*6080Sjp161948
1250Sstevel@tonic-gate if (maj_status == GSS_S_COMPLETE) {
1260Sstevel@tonic-gate /* If mutual state flag is not true, kex fails */
1270Sstevel@tonic-gate if (!(ctxt->flags & GSS_C_MUTUAL_FLAG)) {
1280Sstevel@tonic-gate fatal("Mutual authentication failed");
1290Sstevel@tonic-gate }
1300Sstevel@tonic-gate /* If integ avail flag is not true kex fails */
1310Sstevel@tonic-gate if (!(ctxt->flags & GSS_C_INTEG_FLAG)) {
1320Sstevel@tonic-gate fatal("Integrity check failed");
1330Sstevel@tonic-gate }
1340Sstevel@tonic-gate }
135*6080Sjp161948
136*6080Sjp161948 /*
137*6080Sjp161948 * If we have data to send, then the last message that we
138*6080Sjp161948 * received cannot have been a 'complete'.
139*6080Sjp161948 */
140*6080Sjp161948 if (send_tok.length != 0) {
1410Sstevel@tonic-gate if (first) {
1420Sstevel@tonic-gate packet_start(SSH2_MSG_KEXGSS_INIT);
1430Sstevel@tonic-gate packet_put_string(send_tok.value,
144*6080Sjp161948 send_tok.length);
1450Sstevel@tonic-gate packet_put_bignum2(dh->pub_key);
146*6080Sjp161948 first = 0;
1470Sstevel@tonic-gate } else {
1480Sstevel@tonic-gate packet_start(SSH2_MSG_KEXGSS_CONTINUE);
1490Sstevel@tonic-gate packet_put_string(send_tok.value,
150*6080Sjp161948 send_tok.length);
1510Sstevel@tonic-gate }
1520Sstevel@tonic-gate (void) gss_release_buffer(&min_status, &send_tok);
1530Sstevel@tonic-gate packet_send();
1540Sstevel@tonic-gate packet_write_wait();
1550Sstevel@tonic-gate
156*6080Sjp161948
157*6080Sjp161948 /*
158*6080Sjp161948 * If we've sent them data, they'd better be polite and
159*6080Sjp161948 * reply.
160*6080Sjp161948 */
161*6080Sjp161948
1620Sstevel@tonic-gate next_packet:
1630Sstevel@tonic-gate /*
1640Sstevel@tonic-gate * We need to catch connection closing w/o error
1650Sstevel@tonic-gate * tokens or messages so we can tell the user
1660Sstevel@tonic-gate * _something_ more useful than "Connection
1670Sstevel@tonic-gate * closed by ..."
1680Sstevel@tonic-gate *
1690Sstevel@tonic-gate * We use a fatal cleanup function as that's
1700Sstevel@tonic-gate * all, really, that we can do for now.
1710Sstevel@tonic-gate */
1720Sstevel@tonic-gate fatal_add_cleanup(kexgss_verbose_cleanup, NULL);
1730Sstevel@tonic-gate type = packet_read();
1740Sstevel@tonic-gate fatal_remove_cleanup(kexgss_verbose_cleanup, NULL);
1750Sstevel@tonic-gate switch (type) {
1760Sstevel@tonic-gate case SSH2_MSG_KEXGSS_HOSTKEY:
1770Sstevel@tonic-gate debug("Received KEXGSS_HOSTKEY");
1780Sstevel@tonic-gate server_host_key_blob =
179*6080Sjp161948 packet_get_string(&sbloblen);
1800Sstevel@tonic-gate server_host_key =
181*6080Sjp161948 key_from_blob(server_host_key_blob,
182*6080Sjp161948 sbloblen);
1830Sstevel@tonic-gate goto next_packet; /* there MUSt be another */
1840Sstevel@tonic-gate break;
1850Sstevel@tonic-gate case SSH2_MSG_KEXGSS_CONTINUE:
1860Sstevel@tonic-gate debug("Received GSSAPI_CONTINUE");
187*6080Sjp161948 if (maj_status == GSS_S_COMPLETE)
1880Sstevel@tonic-gate packet_disconnect("Protocol error: "
189*6080Sjp161948 "received GSS-API context token "
190*6080Sjp161948 "though the context was already "
191*6080Sjp161948 "established");
192*6080Sjp161948 recv_tok.value = packet_get_string(&strlen);
193*6080Sjp161948 recv_tok.length = strlen; /* u_int vs. size_t */
1940Sstevel@tonic-gate break;
1950Sstevel@tonic-gate case SSH2_MSG_KEXGSS_COMPLETE:
1960Sstevel@tonic-gate debug("Received GSSAPI_COMPLETE");
197*6080Sjp161948 packet_get_bignum2(dh_server_pub);
198*6080Sjp161948 msg_tok.value = packet_get_string(&strlen);
199*6080Sjp161948 msg_tok.length = strlen; /* u_int vs. size_t */
2000Sstevel@tonic-gate
2010Sstevel@tonic-gate /* Is there a token included? */
2020Sstevel@tonic-gate if (packet_get_char()) {
203*6080Sjp161948 recv_tok.value =
2040Sstevel@tonic-gate packet_get_string(&strlen);
205*6080Sjp161948 /* u_int/size_t */
206*6080Sjp161948 recv_tok.length = strlen;
2070Sstevel@tonic-gate }
2080Sstevel@tonic-gate if (recv_tok.length > 0 &&
2090Sstevel@tonic-gate maj_status == GSS_S_COMPLETE) {
2100Sstevel@tonic-gate packet_disconnect("Protocol error: "
211*6080Sjp161948 "received GSS-API context token "
212*6080Sjp161948 "though the context was already "
213*6080Sjp161948 "established");
2140Sstevel@tonic-gate } else if (recv_tok.length == 0 &&
215*6080Sjp161948 maj_status == GSS_S_CONTINUE_NEEDED) {
216*6080Sjp161948 /* No token included */
2170Sstevel@tonic-gate packet_disconnect("Protocol error: "
218*6080Sjp161948 "did not receive expected "
219*6080Sjp161948 "GSS-API context token");
2200Sstevel@tonic-gate }
2210Sstevel@tonic-gate break;
2220Sstevel@tonic-gate case SSH2_MSG_KEXGSS_ERROR:
223*6080Sjp161948 smaj_status = packet_get_int();
224*6080Sjp161948 smin_status = packet_get_int();
2250Sstevel@tonic-gate msg = packet_get_string(NULL);
2260Sstevel@tonic-gate lang = packet_get_string(NULL);
2270Sstevel@tonic-gate xfree(lang);
2280Sstevel@tonic-gate error("Server had a GSS-API error; the "
229*6080Sjp161948 "connection will close (%d/%d):\n%s",
230*6080Sjp161948 smaj_status, smin_status, msg);
2310Sstevel@tonic-gate error("Use the GssKeyEx option to disable "
232*6080Sjp161948 "GSS-API key exchange and try again.");
2330Sstevel@tonic-gate packet_disconnect("The server had a GSS-API "
234*6080Sjp161948 "error during GSS-API protected SSHv2 "
235*6080Sjp161948 "key exchange\n");
2360Sstevel@tonic-gate break;
2370Sstevel@tonic-gate default:
2380Sstevel@tonic-gate packet_disconnect("Protocol error: "
239*6080Sjp161948 "didn't expect packet type %d", type);
2400Sstevel@tonic-gate }
2410Sstevel@tonic-gate if (recv_tok.value)
242*6080Sjp161948 token_ptr = &recv_tok;
2430Sstevel@tonic-gate } else {
2440Sstevel@tonic-gate /* No data, and not complete */
2450Sstevel@tonic-gate if (maj_status != GSS_S_COMPLETE) {
2460Sstevel@tonic-gate fatal("Not complete, and no token output");
2470Sstevel@tonic-gate }
2480Sstevel@tonic-gate }
249*6080Sjp161948 } while (maj_status == GSS_S_CONTINUE_NEEDED);
250*6080Sjp161948
251*6080Sjp161948 /*
252*6080Sjp161948 * We _must_ have received a COMPLETE message in reply from the
253*6080Sjp161948 * server, which will have set dh_server_pub and msg_tok.
254*6080Sjp161948 */
255*6080Sjp161948 if (type != SSH2_MSG_KEXGSS_COMPLETE)
256*6080Sjp161948 fatal("Expected SSH2_MSG_KEXGSS_COMPLETE never arrived");
257*6080Sjp161948 if (maj_status != GSS_S_COMPLETE)
258*6080Sjp161948 fatal("Internal error in GSS-API protected SSHv2 key exchange");
259*6080Sjp161948
2600Sstevel@tonic-gate /* Check f in range [1, p-1] */
261*6080Sjp161948 if (!dh_pub_is_valid(dh, dh_server_pub))
262*6080Sjp161948 packet_disconnect("bad server public DH value");
263*6080Sjp161948
264*6080Sjp161948 /* compute K=f^x mod p */
265*6080Sjp161948 klen = DH_size(dh);
266*6080Sjp161948 kbuf = xmalloc(klen);
267*6080Sjp161948 kout = DH_compute_key(kbuf, dh_server_pub, dh);
268*6080Sjp161948
269*6080Sjp161948 shared_secret = BN_new();
270*6080Sjp161948 BN_bin2bn(kbuf, kout, shared_secret);
271*6080Sjp161948 (void) memset(kbuf, 0, klen);
272*6080Sjp161948 xfree(kbuf);
2730Sstevel@tonic-gate
274*6080Sjp161948 /* The GSS hash is identical to the DH one */
275*6080Sjp161948 hash = kex_dh_hash(
276*6080Sjp161948 kex->client_version_string,
277*6080Sjp161948 kex->server_version_string,
278*6080Sjp161948 buffer_ptr(&kex->my), buffer_len(&kex->my),
279*6080Sjp161948 buffer_ptr(&kex->peer), buffer_len(&kex->peer),
280*6080Sjp161948 server_host_key_blob, sbloblen, /* server host key */
281*6080Sjp161948 dh->pub_key, /* e */
282*6080Sjp161948 dh_server_pub, /* f */
283*6080Sjp161948 shared_secret); /* K */
284*6080Sjp161948
285*6080Sjp161948 gssbuf.value = hash;
286*6080Sjp161948 gssbuf.length = 20;
287*6080Sjp161948
288*6080Sjp161948 /* Verify that H matches the token we just got. */
289*6080Sjp161948 if ((maj_status = gss_verify_mic(&min_status, ctxt->context, &gssbuf,
290*6080Sjp161948 &msg_tok, NULL))) {
2910Sstevel@tonic-gate packet_disconnect("Hash's MIC didn't verify");
292*6080Sjp161948 }
2930Sstevel@tonic-gate
2940Sstevel@tonic-gate if (server_host_key && kex->accept_host_key != NULL)
2950Sstevel@tonic-gate (void) kex->accept_host_key(server_host_key);
296*6080Sjp161948
297*6080Sjp161948 DH_free(dh);
2980Sstevel@tonic-gate
2990Sstevel@tonic-gate xxx_gssctxt = ctxt; /* for gss keyex w/ mic userauth */
3000Sstevel@tonic-gate
301*6080Sjp161948 /* save session id */
302*6080Sjp161948 if (kex->session_id == NULL) {
303*6080Sjp161948 kex->session_id_len = 20;
304*6080Sjp161948 kex->session_id = xmalloc(kex->session_id_len);
305*6080Sjp161948 (void) memcpy(kex->session_id, hash, kex->session_id_len);
306*6080Sjp161948 }
307*6080Sjp161948
3080Sstevel@tonic-gate kex_derive_keys(kex, hash, shared_secret);
3090Sstevel@tonic-gate BN_clear_free(shared_secret);
310*6080Sjp161948 kex_finish(kex);
3110Sstevel@tonic-gate }
3120Sstevel@tonic-gate
3130Sstevel@tonic-gate /* ARGSUSED */
3140Sstevel@tonic-gate static
3150Sstevel@tonic-gate void
kexgss_verbose_cleanup(void * arg)3160Sstevel@tonic-gate kexgss_verbose_cleanup(void *arg)
3170Sstevel@tonic-gate {
3180Sstevel@tonic-gate error("The GSS-API protected key exchange has failed without "
319*6080Sjp161948 "indication\nfrom the server, possibly due to misconfiguration "
320*6080Sjp161948 "of the server.");
3210Sstevel@tonic-gate error("Use the GssKeyEx option to disable GSS-API key exchange "
322*6080Sjp161948 "and try again.");
3230Sstevel@tonic-gate }
3240Sstevel@tonic-gate
3250Sstevel@tonic-gate #endif /* GSSAPI */
326