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 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 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