From 9ad7b718d42e43f3a285fcbc8f91193931fce324 Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Tue, 29 Dec 2009 20:42:47 +0000 Subject: import openssh-4.0p1-gssapikex.patch --- Makefile.in | 4 +- auth2-gss.c | 39 ++++++++++ auth2.c | 2 + gss-genr.c | 156 +++++++++++++++++++++++++++++++++++++ gss-serv.c | 32 +++++--- kex.c | 9 +++ kex.h | 9 +++ kexgssc.c | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ kexgsss.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++ key.c | 2 + key.h | 1 + monitor.c | 54 +++++++++++++ monitor.h | 1 + monitor_wrap.c | 21 +++++ monitor_wrap.h | 1 + ssh-gss.h | 16 +++- sshconnect2.c | 80 +++++++++++++++++++ sshd.c | 58 ++++++++++++-- 18 files changed, 913 insertions(+), 18 deletions(-) create mode 100644 kexgssc.c create mode 100644 kexgsss.c diff --git a/Makefile.in b/Makefile.in index bca425d36..e0be3d04b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -71,7 +71,7 @@ LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o buffer.o \ atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \ kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ - entropy.o scard-opensc.o gss-genr.o + entropy.o scard-opensc.o gss-genr.o kexgssc.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o @@ -84,7 +84,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ auth2-none.o auth2-passwd.o auth2-pubkey.o \ monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \ auth-krb5.o \ - auth2-gss.o gss-serv.o gss-serv-krb5.o \ + auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o\ loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ audit.o audit-bsm.o diff --git a/auth2-gss.c b/auth2-gss.c index 3289ba18e..0ac405496 100644 --- a/auth2-gss.c +++ b/auth2-gss.c @@ -47,6 +47,39 @@ static void input_gssapi_mic(int type, u_int32_t plen, void *ctxt); static void input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt); static void input_gssapi_errtok(int, u_int32_t, void *); +/* + * The 'gssapi_keyex' userauth mechanism. + */ +static int +userauth_gsskeyex(Authctxt *authctxt) +{ + int authenticated = 0; + Buffer b; + gss_buffer_desc mic, gssbuf; + u_int len; + + mic.value = packet_get_string(&len); + mic.length = len; + + packet_check_eom(); + + ssh_gssapi_buildmic(&b, authctxt->user, authctxt->service, + "gssapi-keyex"); + + gssbuf.value = buffer_ptr(&b); + gssbuf.length = buffer_len(&b); + + /* gss_kex_context is NULL with privsep, so we can't check it here */ + if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context, + &gssbuf, &mic)))) + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + + buffer_free(&b); + xfree(mic.value); + + return (authenticated); +} + /* * We only support those mechanisms that we know about (ie ones that we know * how to check local user kuserok and the like @@ -286,6 +319,12 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) userauth_finish(authctxt, authenticated, "gssapi-with-mic"); } +Authmethod method_gsskeyex = { + "gssapi-keyx", + userauth_gsskeyex, + &options.gss_authentication +}; + Authmethod method_gssapi = { "gssapi-with-mic", userauth_gssapi, diff --git a/auth2.c b/auth2.c index 2265d311e..46068b285 100644 --- a/auth2.c +++ b/auth2.c @@ -55,6 +55,7 @@ extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; #ifdef GSSAPI +extern Authmethod method_gsskeyex; extern Authmethod method_gssapi; #endif @@ -62,6 +63,7 @@ Authmethod *authmethods[] = { &method_none, &method_pubkey, #ifdef GSSAPI + &method_gsskeyex, &method_gssapi, #endif &method_passwd, diff --git a/gss-genr.c b/gss-genr.c index 3f5727b3e..36925df4e 100644 --- a/gss-genr.c +++ b/gss-genr.c @@ -34,12 +34,137 @@ #include "log.h" #include "monitor_wrap.h" #include "ssh2.h" +#include #include "ssh-gss.h" extern u_char *session_id2; extern u_int session_id2_len; +typedef struct { + char *encoded; + gss_OID oid; +} ssh_gss_kex_mapping; + +/* + * XXX - It would be nice to find a more elegant way of handling the + * XXX passing of the key exchange context to the userauth routines + */ + +Gssctxt *gss_kex_context = NULL; + +static ssh_gss_kex_mapping *gss_enc2oid = NULL; + +/* + * Return a list of the gss-group1-sha1 mechanisms supported by this program + * + * We test mechanisms to ensure that we can use them, to avoid starting + * a key exchange with a bad mechanism + */ + + +char * +ssh_gssapi_client_mechanisms(char *host) { + gss_OID_set gss_supported; + OM_uint32 min_status; + + gss_indicate_mechs(&min_status, &gss_supported); + + return(ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, + (void *)host)); +} + +char * +ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, + void *data) { + Buffer buf; + int i, oidpos, enclen; + char *mechs, *encoded; + char digest[EVP_MAX_MD_SIZE]; + char deroid[2]; + const EVP_MD *evp_md = EVP_md5(); + EVP_MD_CTX md; + + evp_md = EVP_md5(); + + if (gss_enc2oid != NULL) { + for (i=0;gss_enc2oid[i].encoded!=NULL;i++) + xfree(gss_enc2oid[i].encoded); + xfree(gss_enc2oid); + } + + gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping)* + (gss_supported->count+1)); + + buffer_init(&buf); + + oidpos = 0; + for (i=0;icount;i++) { + if (gss_supported->elements[i].length<128 && + (*check)(&(gss_supported->elements[i]), data)) { + + deroid[0] = SSH_GSS_OIDTYPE; + deroid[1] = gss_supported->elements[i].length; + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, deroid, 2); + EVP_DigestUpdate(&md, + gss_supported->elements[i].elements, + gss_supported->elements[i].length); + EVP_DigestFinal(&md, digest, NULL); + + encoded = xmalloc(EVP_MD_size(evp_md)*2); + enclen = __b64_ntop(digest, EVP_MD_size(evp_md), + encoded, EVP_MD_size(evp_md)*2); + + if (oidpos != 0) + buffer_put_char(&buf,','); + + buffer_append(&buf, KEX_GSS_SHA1, + sizeof(KEX_GSS_SHA1)-1); + buffer_append(&buf, encoded, enclen); + + gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); + gss_enc2oid[oidpos].encoded = encoded; + oidpos++; + } + } + gss_enc2oid[oidpos].oid = NULL; + gss_enc2oid[oidpos].encoded = NULL; + + buffer_put_char(&buf,'\0'); + + mechs = xmalloc(buffer_len(&buf)); + buffer_get(&buf, mechs, buffer_len(&buf)); + buffer_free(&buf); + + if (strlen(mechs) == 0) { + xfree(mechs); + mechs = NULL; + } + + return (mechs); +} + +gss_OID +ssh_gssapi_id_kex(Gssctxt *ctx, char *name) { + int i = 0; + + if (strncmp(name, KEX_GSS_SHA1, sizeof(KEX_GSS_SHA1)-1) != 0) + return NULL; + + name+=sizeof(KEX_GSS_SHA1)-1; /* Skip ID string */ + + while (gss_enc2oid[i].encoded != NULL && + strcmp(name,gss_enc2oid[i].encoded)!=0) { + i++; + } + + if (gss_enc2oid[i].oid != NULL && ctx != NULL) + ssh_gssapi_set_oid(ctx,gss_enc2oid[i].oid); + + return gss_enc2oid[i].oid; +} + /* Check that the OID in a data stream matches that in the context */ int ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) @@ -250,6 +375,9 @@ ssh_gssapi_acquire_cred(Gssctxt *ctx) OM_uint32 ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) { + if (ctx == NULL) + return -1; + if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, GSS_C_QOP_DEFAULT, buffer, hash))) ssh_gssapi_error(ctx); @@ -257,6 +385,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) return (ctx->major); } +/* Priviledged when used by server */ +OM_uint32 +ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) +{ + if (ctx == NULL) + return -1; + + ctx->major = gss_verify_mic(&ctx->minor, ctx->context, + gssbuf, gssmic, NULL); + + return (ctx->major); +} + void ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, const char *context) @@ -278,4 +419,19 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) { return (ssh_gssapi_acquire_cred(*ctx)); } +int +ssh_gssapi_check_mechanism(gss_OID oid, void *host) { + Gssctxt * ctx = NULL; + gss_buffer_desc token; + OM_uint32 major, minor; + + ssh_gssapi_build_ctx(&ctx); + ssh_gssapi_set_oid(ctx, oid); + ssh_gssapi_import_name(ctx, host); + major = ssh_gssapi_init_ctx(ctx, 0, GSS_C_NO_BUFFER, &token, NULL); + gss_release_buffer(&minor, &token); + ssh_gssapi_delete_ctx(&ctx); + return (!GSS_ERROR(major)); +} + #endif /* GSSAPI */ diff --git a/gss-serv.c b/gss-serv.c index de32a3f2e..fad79a1b4 100644 --- a/gss-serv.c +++ b/gss-serv.c @@ -61,6 +61,28 @@ ssh_gssapi_mech* supported_mechs[]= { &gssapi_null_mech, }; +/* Unpriviledged */ +char * +ssh_gssapi_server_mechanisms() { + gss_OID_set supported; + + ssh_gssapi_supported_oids(&supported); + return (ssh_gssapi_kex_mechs(supported, &ssh_gssapi_server_check_mech, + NULL)); +} + +/* Unpriviledged */ +int +ssh_gssapi_server_check_mech(gss_OID oid, void *data) { + Gssctxt * ctx = NULL; + int res; + + res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid))); + ssh_gssapi_delete_ctx(&ctx); + + return (res); +} + /* Unpriviledged */ void ssh_gssapi_supported_oids(gss_OID_set *oidset) @@ -287,14 +309,4 @@ ssh_gssapi_userok(char *user) return (0); } -/* Priviledged */ -OM_uint32 -ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) -{ - ctx->major = gss_verify_mic(&ctx->minor, ctx->context, - gssbuf, gssmic, NULL); - - return (ctx->major); -} - #endif diff --git a/kex.c b/kex.c index a668346c3..3d6f3ab54 100644 --- a/kex.c +++ b/kex.c @@ -42,6 +42,10 @@ RCSID("$OpenBSD: kex.c,v 1.60 2004/06/21 17:36:31 avsm Exp $"); #include "dispatch.h" #include "monitor.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #define KEX_COOKIE_LEN 16 /* prototype */ @@ -297,6 +301,11 @@ choose_kex(Kex *k, char *client, char *server) k->kex_type = KEX_DH_GRP14_SHA1; } else if (strcmp(k->name, KEX_DHGEX) == 0) { k->kex_type = KEX_DH_GEX_SHA1; +#ifdef GSSAPI + } else if (strncmp(k->name, KEX_GSS_SHA1, + sizeof(KEX_GSS_SHA1)-1) == 0) { + k->kex_type = KEX_GSS_GRP1_SHA1; +#endif } else fatal("bad kex alg %s", k->name); } diff --git a/kex.h b/kex.h index d9e9d6522..9536d506c 100644 --- a/kex.h +++ b/kex.h @@ -59,6 +59,7 @@ enum kex_exchange { KEX_DH_GRP1_SHA1, KEX_DH_GRP14_SHA1, KEX_DH_GEX_SHA1, + KEX_GSS_GRP1_SHA1, KEX_MAX }; @@ -110,6 +111,9 @@ struct Kex { Buffer peer; int done; int flags; +#ifdef GSSAPI + int gss_deleg_creds; +#endif char *client_version_string; char *server_version_string; int (*verify_host_key)(Key *); @@ -132,6 +136,11 @@ void kexdh_server(Kex *); void kexgex_client(Kex *); void kexgex_server(Kex *); +#ifdef GSSAPI +void kexgss_client(Kex *); +void kexgss_server(Kex *); +#endif + u_char * kex_dh_hash(char *, char *, char *, int, char *, int, u_char *, int, BIGNUM *, BIGNUM *, BIGNUM *); diff --git a/kexgssc.c b/kexgssc.c new file mode 100644 index 000000000..eee96dd23 --- /dev/null +++ b/kexgssc.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2001-2004 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" + +#ifdef GSSAPI + +#include +#include + +#include "xmalloc.h" +#include "buffer.h" +#include "bufaux.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "dh.h" +#include "canohost.h" +#include "ssh2.h" +#include "ssh-gss.h" + +void +kexgss_client(Kex *kex) { + gss_buffer_desc gssbuf, send_tok, recv_tok, msg_tok, *token_ptr; + Gssctxt *ctxt; + OM_uint32 maj_status, min_status, ret_flags; + unsigned int klen, kout; + DH *dh; + BIGNUM *dh_server_pub = 0; + BIGNUM *shared_secret = 0; + unsigned char *kbuf; + unsigned char *hash; + unsigned char *serverhostkey; + char *msg; + char *lang; + int type = 0; + int first = 1; + int slen = 0; + u_int strlen; + + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_id_kex(ctxt,kex->name) == NULL) + fatal("Couldn't identify host exchange"); + + if (ssh_gssapi_import_name(ctxt,get_canonical_hostname(1))) + fatal("Couldn't import hostname "); + + /* This code should match that in ssh_dh1_client */ + + /* Step 1 - e is dh->pub_key */ + dh = dh_new_group1(); + dh_gen_key(dh, kex->we_need * 8); + + /* This is f, we initialise it now to make life easier */ + dh_server_pub = BN_new(); + if (dh_server_pub == NULL) + fatal("dh_server_pub == NULL"); + + 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)) { + if (send_tok.length != 0) { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, + send_tok.length); + } + fatal("gss_init_context failed"); + } + + /* If we've got an old receive buffer get rid of it */ + if (token_ptr != GSS_C_NO_BUFFER) + (void) 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) { + packet_start(SSH2_MSG_KEXGSS_INIT); + packet_put_string(send_tok.value, + send_tok.length); + packet_put_bignum2(dh->pub_key); + first = 0; + } else { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, + send_tok.length); + } + packet_send(); + + /* If we've sent them data, they should reply */ + + type = packet_read(); + switch (type) { + case SSH2_MSG_KEXGSS_HOSTKEY: + debug("Received KEXGSS_HOSTKEY"); + serverhostkey = packet_get_string(&slen); + break; + case SSH2_MSG_KEXGSS_CONTINUE: + debug("Received GSSAPI_CONTINUE"); + if (maj_status == GSS_S_COMPLETE) + fatal("GSSAPI Continue received from server when complete"); + recv_tok.value = packet_get_string(&strlen); + recv_tok.length = strlen; + break; + case SSH2_MSG_KEXGSS_COMPLETE: + debug("Received GSSAPI_COMPLETE"); + packet_get_bignum2(dh_server_pub); + msg_tok.value = packet_get_string(&strlen); + msg_tok.length = strlen; + + /* Is there a token included? */ + if (packet_get_char()) { + recv_tok.value= + packet_get_string(&strlen); + recv_tok.length = strlen; + /* If we're already complete - protocol error */ + if (maj_status == GSS_S_COMPLETE) + packet_disconnect("Protocol error: received token when complete"); + } else { + /* No token included */ + if (maj_status != GSS_S_COMPLETE) + packet_disconnect("Protocol error: did not receive final token"); + } + break; + case SSH2_MSG_KEXGSS_ERROR: + debug("Received Error"); + maj_status = packet_get_int(); + min_status = packet_get_int(); + msg = packet_get_string(NULL); + lang = packet_get_string(NULL); + fprintf(stderr,"GSSAPI Error: \n%s",msg); + default: + packet_disconnect("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"); + + /* Check f in range [1, p-1] */ + if (!dh_pub_is_valid(dh, dh_server_pub)) + packet_disconnect("bad server public DH value"); + + /* compute K=f^x mod p */ + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_server_pub, dh); + + shared_secret = BN_new(); + BN_bin2bn(kbuf,kout, shared_secret); + memset(kbuf, 0, klen); + xfree(kbuf); + + /* The GSS hash is identical to the DH one */ + hash = kex_dh_hash( kex->client_version_string, + kex->server_version_string, + buffer_ptr(&kex->my), buffer_len(&kex->my), + buffer_ptr(&kex->peer), buffer_len(&kex->peer), + serverhostkey, slen, /* server host key */ + dh->pub_key, /* e */ + dh_server_pub, /* f */ + shared_secret /* K */ + ); + + gssbuf.value = hash; + gssbuf.length = 20; + + /* Verify that the hash matches the MIC we just got. */ + if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) + packet_disconnect("Hash's MIC didn't verify"); + + DH_free(dh); + /* save session id */ + if (kex->session_id == NULL) { + kex->session_id_len = 20; + kex->session_id = xmalloc(kex->session_id_len); + memcpy(kex->session_id, hash, kex->session_id_len); + } + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + kex_derive_keys(kex, hash, shared_secret); + BN_clear_free(shared_secret); + kex_finish(kex); +} + +#endif /* GSSAPI */ diff --git a/kexgsss.c b/kexgsss.c new file mode 100644 index 000000000..80c133ac9 --- /dev/null +++ b/kexgsss.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2001-2004 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" + +#ifdef GSSAPI + +#include +#include + +#include "xmalloc.h" +#include "buffer.h" +#include "bufaux.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "dh.h" +#include "ssh2.h" +#include "ssh-gss.h" +#include "monitor_wrap.h" + +void +kexgss_server(Kex *kex) +{ + OM_uint32 maj_status, min_status; + + /* + * Some GSSAPI implementations use the input value of ret_flags (an + * output variable) as a means of triggering mechanism specific + * features. Initializing it to zero avoids inadvertently + * activating this non-standard behaviour. + */ + + OM_uint32 ret_flags = 0; + gss_buffer_desc gssbuf, send_tok, recv_tok, msg_tok; + Gssctxt *ctxt = NULL; + unsigned int klen, kout; + unsigned char *kbuf, *hash; + DH *dh; + BIGNUM *shared_secret = NULL; + BIGNUM *dh_client_pub = NULL; + int type =0; + u_int slen; + gss_OID oid; + + /* Initialise GSSAPI */ + + debug2("%s: Identifying %s", __func__, kex->name); + oid = ssh_gssapi_id_kex(NULL, kex->name); + if (oid == NULL) + fatal("Unknown gssapi mechanism"); + + debug2("%s: Acquiring credentials", __func__); + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) + fatal("Unable to acquire credentials for the server"); + + do { + debug("Wait SSH2_MSG_GSSAPI_INIT"); + type = packet_read(); + switch(type) { + case SSH2_MSG_KEXGSS_INIT: + if (dh_client_pub != NULL) + fatal("Received KEXGSS_INIT after initialising"); + recv_tok.value = packet_get_string(&slen); + recv_tok.length = slen; + + dh_client_pub = BN_new(); + + if (dh_client_pub == NULL) + fatal("dh_client_pub == NULL"); + packet_get_bignum2(dh_client_pub); + + /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ + break; + case SSH2_MSG_KEXGSS_CONTINUE: + recv_tok.value = packet_get_string(&slen); + recv_tok.length = slen; + break; + default: + packet_disconnect( + "Protocol error: didn't expect packet type %d", + type); + } + + maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, + &send_tok, &ret_flags)); + + gss_release_buffer(&min_status, &recv_tok); + + if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) + fatal("Zero length token output when incomplete"); + + if (dh_client_pub == NULL) + fatal("No client public key"); + + if (maj_status & GSS_S_CONTINUE_NEEDED) { + debug("Sending GSSAPI_CONTINUE"); + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, send_tok.length); + packet_send(); + gss_release_buffer(&min_status, &send_tok); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + if (GSS_ERROR(maj_status)) { + if (send_tok.length>0) { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value, send_tok.length); + packet_send(); + } + fatal("accept_ctx died"); + } + + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) + fatal("Mutual Authentication flag wasn't set"); + + if (!(ret_flags & GSS_C_INTEG_FLAG)) + fatal("Integrity flag wasn't set"); + + dh = dh_new_group1(); + dh_gen_key(dh, kex->we_need * 8); + + if (!dh_pub_is_valid(dh, dh_client_pub)) + packet_disconnect("bad client public DH value"); + + klen = DH_size(dh); + kbuf = xmalloc(klen); + kout = DH_compute_key(kbuf, dh_client_pub, dh); + + shared_secret = BN_new(); + BN_bin2bn(kbuf, kout, shared_secret); + memset(kbuf, 0, klen); + xfree(kbuf); + + /* The GSSAPI hash is identical to the Diffie Helman one */ + hash = kex_dh_hash( + kex->client_version_string, kex->server_version_string, + buffer_ptr(&kex->peer), buffer_len(&kex->peer), + buffer_ptr(&kex->my), buffer_len(&kex->my), + NULL, 0, /* Change this if we start sending host keys */ + dh_client_pub, dh->pub_key, shared_secret + ); + BN_free(dh_client_pub); + + if (kex->session_id == NULL) { + kex->session_id_len = 20; + kex->session_id = xmalloc(kex->session_id_len); + memcpy(kex->session_id, hash, kex->session_id_len); + } + + gssbuf.value = hash; + gssbuf.length = 20; /* Hashlen appears to always be 20 */ + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt,&gssbuf,&msg_tok)))) + fatal("Couldn't get MIC"); + + packet_start(SSH2_MSG_KEXGSS_COMPLETE); + packet_put_bignum2(dh->pub_key); + packet_put_string((char *)msg_tok.value,msg_tok.length); + + if (send_tok.length!=0) { + packet_put_char(1); /* true */ + packet_put_string((char *)send_tok.value, send_tok.length); + } else { + packet_put_char(0); /* false */ + } + packet_send(); + + gss_release_buffer(&min_status, &send_tok); + + if (gss_kex_context == NULL) + gss_kex_context = ctxt; + else + ssh_gssapi_delete_ctx(&ctxt); + + DH_free(dh); + + kex_derive_keys(kex, hash, shared_secret); + BN_clear_free(shared_secret); + kex_finish(kex); +} +#endif /* GSSAPI */ diff --git a/key.c b/key.c index e41930464..db401cb12 100644 --- a/key.c +++ b/key.c @@ -650,6 +650,8 @@ key_type_from_name(char *name) return KEY_RSA; } else if (strcmp(name, "ssh-dss") == 0) { return KEY_DSA; + } else if (strcmp(name, "null") == 0) { + return KEY_NULL; } debug2("key_type_from_name: unknown key type '%s'", name); return KEY_UNSPEC; diff --git a/key.h b/key.h index 50df8500b..6358e955f 100644 --- a/key.h +++ b/key.h @@ -34,6 +34,7 @@ enum types { KEY_RSA1, KEY_RSA, KEY_DSA, + KEY_NULL, KEY_UNSPEC }; enum fp_type { diff --git a/monitor.c b/monitor.c index 301e150b3..b57b53f21 100644 --- a/monitor.c +++ b/monitor.c @@ -141,6 +141,7 @@ int mm_answer_gss_setup_ctx(int, Buffer *); int mm_answer_gss_accept_ctx(int, Buffer *); int mm_answer_gss_userok(int, Buffer *); int mm_answer_gss_checkmic(int, Buffer *); +int mm_answer_gss_sign(int, Buffer *); #endif #ifdef SSH_AUDIT_EVENTS @@ -209,11 +210,17 @@ struct mon_table mon_dispatch_proto20[] = { {MONITOR_REQ_GSSSTEP, MON_ISAUTH, mm_answer_gss_accept_ctx}, {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic}, + {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, #endif {0, 0, NULL} }; struct mon_table mon_dispatch_postauth20[] = { +#ifdef GSSAPI + {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx}, + {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx}, + {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign}, +#endif {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, {MONITOR_REQ_SIGN, 0, mm_answer_sign}, {MONITOR_REQ_PTY, 0, mm_answer_pty}, @@ -316,6 +323,10 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); +#ifdef GSSAPI + /* and for the GSSAPI key exchange */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); +#endif } else { mon_dispatch = mon_dispatch_proto15; @@ -389,6 +400,10 @@ monitor_child_postauth(struct monitor *pmonitor) monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); +#ifdef GSSAPI + /* and for the GSSAPI key exchange */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); +#endif } else { mon_dispatch = mon_dispatch_postauth15; monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); @@ -1623,6 +1638,9 @@ mm_get_kex(Buffer *m) kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; +#ifdef GSSAPI + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; +#endif kex->server = 1; kex->hostkey_type = buffer_get_int(m); kex->kex_type = buffer_get_int(m); @@ -1865,6 +1883,7 @@ mm_answer_gss_accept_ctx(int sock, Buffer *m) monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); } return (0); } @@ -1915,4 +1934,39 @@ mm_answer_gss_userok(int sock, Buffer *m) /* Monitor loop will terminate if authenticated */ return (authenticated); } + +int +mm_answer_gss_sign(int socket, Buffer *m) +{ + gss_buffer_desc data, hash; + OM_uint32 major, minor; + + data.value = buffer_get_string(m, &data.length); + if (data.length != 20) + fatal("%s: data length incorrect: %d", __func__, data.length); + + /* Save the session ID on the first time around */ + if (session_id2_len == 0) { + session_id2_len = data.length; + session_id2 = xmalloc(session_id2_len); + memcpy(session_id2, data.value, session_id2_len); + } + major = ssh_gssapi_sign(gsscontext, &data, &hash); + + xfree(data.value); + + buffer_clear(m); + buffer_put_int(m, major); + buffer_put_string(m, hash.value, hash.length); + + mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); + + gss_release_buffer(&minor, &hash); + + /* Turn on getpwnam permissions */ + monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); + + return (0); +} + #endif /* GSSAPI */ diff --git a/monitor.h b/monitor.h index 13ce3e1ca..7b306b4af 100644 --- a/monitor.h +++ b/monitor.h @@ -53,6 +53,7 @@ enum monitor_reqtype { MONITOR_REQ_GSSSTEP, MONITOR_ANS_GSSSTEP, MONITOR_REQ_GSSUSEROK, MONITOR_ANS_GSSUSEROK, MONITOR_REQ_GSSCHECKMIC, MONITOR_ANS_GSSCHECKMIC, + MONITOR_REQ_GSSSIGN, MONITOR_ANS_GSSSIGN, MONITOR_REQ_PAM_START, MONITOR_REQ_PAM_ACCOUNT, MONITOR_ANS_PAM_ACCOUNT, MONITOR_REQ_PAM_INIT_CTX, MONITOR_ANS_PAM_INIT_CTX, diff --git a/monitor_wrap.c b/monitor_wrap.c index e1b6512b4..482ff5bc3 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1217,4 +1217,25 @@ mm_ssh_gssapi_userok(char *user) debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not "); return (authenticated); } + +OM_uint32 +mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) +{ + Buffer m; + OM_uint32 major; + + buffer_init(&m); + buffer_put_string(&m, data->value, data->length); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, &m); + + major = buffer_get_int(&m); + hash->value = buffer_get_string(&m, &hash->length); + + buffer_free(&m); + + return(major); +} + #endif /* GSSAPI */ diff --git a/monitor_wrap.h b/monitor_wrap.h index 310b42513..871eabb9d 100644 --- a/monitor_wrap.h +++ b/monitor_wrap.h @@ -63,6 +63,7 @@ OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); int mm_ssh_gssapi_userok(char *user); OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); +OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); #endif #ifdef USE_PAM diff --git a/ssh-gss.h b/ssh-gss.h index 52fb49a6f..74ce6f8bc 100644 --- a/ssh-gss.h +++ b/ssh-gss.h @@ -62,6 +62,13 @@ #define SSH_GSS_OIDTYPE 0x06 +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define KEX_GSS_SHA1 "gss-group1-sha1-" + typedef struct { char *filename; char *envvar; @@ -99,6 +106,7 @@ typedef struct { } Gssctxt; extern ssh_gssapi_mech *supported_mechs[]; +extern Gssctxt *gss_kex_context; int ssh_gssapi_check_oid(Gssctxt *, void *, size_t); void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t); @@ -121,7 +129,13 @@ OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); void ssh_gssapi_buildmic(Buffer *, const char *, const char *, const char *); -/* In the server */ +typedef int ssh_gssapi_check_fn(gss_OID, void *); +char *ssh_gssapi_client_mechanisms(char *host); +char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, void *); +int ssh_gssapi_check_mechanism(gss_OID, void *); +gss_OID ssh_gssapi_id_kex(Gssctxt *, char *); + +int ssh_gssapi_server_check_mech(gss_OID, void *); int ssh_gssapi_userok(char *name); OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); void ssh_gssapi_do_child(char ***, u_int *); diff --git a/sshconnect2.c b/sshconnect2.c index 68d56d020..b69602c0c 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -84,9 +84,26 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) { Kex *kex; +#ifdef GSSAPI + char *orig, *gss; + int len; +#endif + xxx_host = host; xxx_hostaddr = hostaddr; +#ifdef GSSAPI + orig = myproposal[PROPOSAL_KEX_ALGS]; + gss = ssh_gssapi_client_mechanisms(get_canonical_hostname(1)); + debug("Offering GSSAPI proposal: %s",gss); + if (gss) { + len = strlen(orig) + strlen(gss) + 2; + myproposal[PROPOSAL_KEX_ALGS] = xmalloc(len); + snprintf(myproposal[PROPOSAL_KEX_ALGS], len, "%s,%s", gss, + orig); + } +#endif + if (options.ciphers == (char *)-1) { logit("No valid ciphers for protocol version 2 given, using defaults."); options.ciphers = NULL; @@ -114,6 +131,16 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = options.hostkeyalgorithms; +#ifdef GSSAPI + if (gss) { + orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; + len = strlen(orig) + sizeof(",null"); + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = xmalloc(len); + snprintf(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], len, + "%s,null", orig); + } +#endif + if (options.rekey_limit) packet_set_rekey_limit(options.rekey_limit); @@ -122,10 +149,17 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; +#ifdef GSSAPI + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; +#endif kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; +#ifdef GSSAPI + kex->gss_deleg_creds = options.gss_deleg_creds; +#endif + xxx_kex = kex; dispatch_run(DISPATCH_BLOCK, &kex->done, kex); @@ -208,6 +242,7 @@ void input_gssapi_token(int type, u_int32_t, void *); void input_gssapi_hash(int type, u_int32_t, void *); void input_gssapi_error(int, u_int32_t, void *); void input_gssapi_errtok(int, u_int32_t, void *); +int userauth_gsskeyx(Authctxt *authctxt); #endif void userauth(Authctxt *, char *); @@ -223,6 +258,10 @@ static char *authmethods_get(void); Authmethod authmethods[] = { #ifdef GSSAPI + {"gssapi-keyx", + userauth_gsskeyx, + &options.gss_authentication, + NULL}, {"gssapi-with-mic", userauth_gssapi, &options.gss_authentication, @@ -704,6 +743,47 @@ input_gssapi_error(int type, u_int32_t plen, void *ctxt) xfree(msg); xfree(lang); } + +int +userauth_gsskeyx(Authctxt *authctxt) +{ + Buffer b; + gss_buffer_desc gssbuf, mic; + OM_uint32 ms; + + static int attempt = 0; + if (attempt++ >= 1) + return (0); + + if (gss_kex_context == NULL) { + debug("No valid Key exchange context"); + return (0); + } + + ssh_gssapi_buildmic(&b, authctxt->server_user, authctxt->service, + "gssapi-keyex"); + + gssbuf.value = buffer_ptr(&b); + gssbuf.length = buffer_len(&b); + + if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { + buffer_free(&b); + return (0); + } + + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + packet_put_string(mic.value, mic.length); + packet_send(); + + buffer_free(&b); + gss_release_buffer(&ms, &mic); + + return (1); +} + #endif /* GSSAPI */ int diff --git a/sshd.c b/sshd.c index 11d618d11..51b476778 100644 --- a/sshd.c +++ b/sshd.c @@ -1110,10 +1110,12 @@ main(int ac, char **av) logit("Disabling protocol version 1. Could not load host key"); options.protocol &= ~SSH_PROTO_1; } +#ifndef GSSAPI if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { logit("Disabling protocol version 2. Could not load host key"); options.protocol &= ~SSH_PROTO_2; } +#endif if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { logit("sshd: no hostkeys available -- exiting."); exit(1); @@ -1990,13 +1992,59 @@ do_ssh2_kex(void) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); /* start key exchange */ - kex = kex_setup(myproposal); - kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; + +#ifdef GSSAPI + { + char *orig; + char *gss = NULL; + char *newstr = NULL; + orig = myproposal[PROPOSAL_KEX_ALGS]; + + /* + * If we don't have a host key, then there's no point advertising + * the other key exchange algorithms + */ + + if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0) + orig = NULL; + + gss = ssh_gssapi_server_mechanisms(); + + if (gss && orig) { + int len = strlen(orig) + strlen(gss) + 2; + newstr = xmalloc(len); + snprintf(newstr, len, "%s,%s", gss, orig); + } else if (gss) { + newstr = gss; + } else if (orig) { + newstr = orig; + } + /* + * If we've got GSSAPI mechanisms, then we've got the 'null' host + * key alg, but we can't tell people about it unless its the only + * host key algorithm we support + */ + if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null"; + + if (newstr) + myproposal[PROPOSAL_KEX_ALGS] = newstr; + else + fatal("No supported key exchange algorithms"); + } +#endif + + /* start key exchange */ + kex = kex_setup(myproposal); + kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; - kex->server = 1; - kex->client_version_string=client_version_string; - kex->server_version_string=server_version_string; +#ifdef GSSAPI + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; +#endif + kex->server = 1; + kex->client_version_string=client_version_string; + kex->server_version_string=server_version_string; kex->load_host_key=&get_hostkey_by_type; kex->host_key_index=&get_hostkey_index; -- cgit v1.2.3