From 5598e598f2b9714c3b08a211d401c780e74dabe2 Mon Sep 17 00:00:00 2001 From: jbasney Date: Mon, 11 Mar 2002 15:28:15 +0000 Subject: [PATCH] GSSAPI patch for OpenSSH 3.0.2p1 Protocol version 2 by Simon Wilkinson from http://www.sxw.org.uk/computing/patches/openssh-3.0.2p1-gssapi.patch --- openssh/Makefile.in | 4 +- openssh/acconfig.h | 6 + openssh/auth-pam.c | 18 ++ openssh/auth-pam.h | 1 + openssh/auth.h | 1 + openssh/auth2.c | 23 ++ openssh/configure.ac | 111 ++++++++ openssh/gss-genr.c | 491 ++++++++++++++++++++++++++++++++++ openssh/gss-serv.c | 586 +++++++++++++++++++++++++++++++++++++++++ openssh/kex.c | 15 +- openssh/kex.h | 10 +- openssh/kexgss.c | 450 +++++++++++++++++++++++++++++++ openssh/key.c | 2 + openssh/key.h | 1 + openssh/makegssname.pl | 46 ++++ openssh/readconf.c | 50 ++++ openssh/readconf.h | 9 + openssh/servconf.c | 40 +++ openssh/servconf.h | 7 + openssh/session.c | 29 +- openssh/session.h | 3 +- openssh/ssh-gss.h | 122 +++++++++ openssh/sshconnect2.c | 209 +++++++++++++++ openssh/sshd.8 | 19 ++ openssh/sshd.c | 46 ++++ 25 files changed, 2293 insertions(+), 6 deletions(-) create mode 100644 openssh/gss-genr.c create mode 100644 openssh/gss-serv.c create mode 100644 openssh/kexgss.c create mode 100644 openssh/makegssname.pl create mode 100644 openssh/ssh-gss.h diff --git a/openssh/Makefile.in b/openssh/Makefile.in index 87a7c13..f4ab6f0 100644 --- a/openssh/Makefile.in +++ b/openssh/Makefile.in @@ -46,11 +46,11 @@ INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) $(SFTP_PROGS) -LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o dh.o dispatch.o mac.o hostfile.o key.o kex.o kexdh.o kexgex.o log.o match.o misc.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o scard.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o uuencode.o xmalloc.o +LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o dh.o dispatch.o mac.o hostfile.o key.o kex.o kexdh.o kexgex.o kexgss.o log.o match.o misc.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o scard.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o uuencode.o xmalloc.o gss-genr.o SSHOBJS= ssh.o sshconnect.o sshconnect1.o sshconnect2.o sshtty.o readconf.o clientloop.o -SSHDOBJS= sshd.o auth.o auth1.o auth2.o auth-chall.o auth2-chall.o auth-rhosts.o auth-options.o auth-krb4.o auth-krb5.o auth-pam.o auth2-pam.o auth-passwd.o auth-rsa.o auth-rh-rsa.o auth-sia.o sshpty.o sshlogin.o loginrec.o servconf.o serverloop.o md5crypt.o session.o groupaccess.o auth-skey.o auth-bsdauth.o +SSHDOBJS= sshd.o auth.o auth1.o auth2.o auth-chall.o auth2-chall.o auth-rhosts.o auth-options.o auth-krb4.o auth-krb5.o auth-pam.o auth2-pam.o auth-passwd.o auth-rsa.o auth-rh-rsa.o auth-sia.o sshpty.o sshlogin.o loginrec.o servconf.o serverloop.o md5crypt.o session.o groupaccess.o auth-skey.o auth-bsdauth.o gss-serv.o MANPAGES = scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out MANPAGES_IN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 diff --git a/openssh/acconfig.h b/openssh/acconfig.h index 405f200..84e0e64 100644 --- a/openssh/acconfig.h +++ b/openssh/acconfig.h @@ -184,6 +184,9 @@ /* Define if libc defines __progname */ #undef HAVE___PROGNAME +/* Define this is you want GSSAPI support in the version 2 protocol */ +#undef GSSAPI + /* Define if you want Kerberos 5 support */ #undef KRB5 @@ -196,6 +199,9 @@ /* Define if you want AFS support */ #undef AFS +/* Define if you want GSI/Globus authentication support */ +#undef GSI + /* Define if you want S/Key support */ #undef SKEY diff --git a/openssh/auth-pam.c b/openssh/auth-pam.c index 801c9eb..8e170d4 100644 --- a/openssh/auth-pam.c +++ b/openssh/auth-pam.c @@ -394,6 +394,24 @@ char **fetch_pam_environment(void) #endif /* HAVE_PAM_GETENVLIST */ } +/* Set a PAM environment string. We need to do this so that the session + * modules can handle things like Kerberos/GSI credentials that appear + * during the ssh authentication process. + */ + +int do_pam_putenv(char *name, char *value) { + char *compound; + int ret=1; + + compound=xmalloc(strlen(name)+strlen(value)+2); + if (compound) { + sprintf(compound,"%s=%s",name,value); + ret=pam_putenv(__pamh,compound); + xfree(compound); + } + return(ret); +} + /* Print any messages that have been generated during authentication */ /* or account checking to stderr */ void print_pam_messages(void) diff --git a/openssh/auth-pam.h b/openssh/auth-pam.h index 30e4df5..de27b83 100644 --- a/openssh/auth-pam.h +++ b/openssh/auth-pam.h @@ -17,6 +17,7 @@ void print_pam_messages(void); int is_pam_password_change_required(void); void do_pam_chauthtok(void); void do_pam_set_conv(struct pam_conv *); +int do_pam_putenv(char *, char *); void message_cat(char **p, const char *a); #endif /* USE_PAM */ diff --git a/openssh/auth.h b/openssh/auth.h index edfc9fb..656918e 100644 --- a/openssh/auth.h +++ b/openssh/auth.h @@ -67,6 +67,7 @@ struct Authctxt { krb5_principal krb5_user; char *krb5_ticket_file; #endif + void *methoddata; }; /* diff --git a/openssh/auth2.c b/openssh/auth2.c index 1920eb3..2fbe819 100644 --- a/openssh/auth2.c +++ b/openssh/auth2.c @@ -52,6 +52,10 @@ RCSID("$OpenBSD: auth2.c,v 1.72 2001/11/07 22:41:51 markus Exp $"); #include "canohost.h" #include "match.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* import */ extern ServerOptions options; extern u_char *session_id2; @@ -87,10 +91,23 @@ static int userauth_pubkey(Authctxt *); static int userauth_hostbased(Authctxt *); static int userauth_kbdint(Authctxt *); +#ifdef GSSAPI +int userauth_external(Authctxt *authctxt); +int userauth_gssapi(Authctxt *authctxt); +#endif + Authmethod authmethods[] = { {"none", userauth_none, &one}, +#ifdef GSSAPI + {"external-keyx", + userauth_external, + &options.gss_authentication}, + {"gssapi", + userauth_gssapi, + &options.gss_authentication}, +#endif {"publickey", userauth_pubkey, &options.pubkey_authentication}, @@ -221,6 +238,12 @@ input_userauth_request(int type, int plen, void *ctxt) } /* reset state */ dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, &protocol_error); + +#ifdef GSSAPI + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, &protocol_error); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, &protocol_error); +#endif + authctxt->postponed = 0; #ifdef BSD_AUTH if (authctxt->as) { diff --git a/openssh/configure.ac b/openssh/configure.ac index ca36df1..717d19d 100644 --- a/openssh/configure.ac +++ b/openssh/configure.ac @@ -490,6 +490,92 @@ int main(void){struct dirent d;return(sizeof(d.d_name)<=sizeof(char));} ] ) +# Check whether the user wants GSI (Globus) support +gsi_path="no" +AC_ARG_WITH(gsi, + [ --with-gsi=PATH Enable GSI/Globus authentication support], + [ + gsi_path="$withval" + ] +) + +AC_ARG_WITH(globus, + [ --with-globus=PATH Enable GSI/Globus authentication support], + [ + gsi_path="$withval" + ] +) + +if test "x$gsi_path" != "xno" ; then + # Globus GSSAPI configuration + AC_DEFINE(GSSAPI) + AC_DEFINE(GSI) + + # Find GLOBUS/GSI installation Directory + AC_MSG_CHECKING(for Globus/GSI installation directory) + + globus_install_dir=$gsi_path + + if test "x$globus_install_dir" = "xyes" ; then + if test -n "$GLOBUS_INSTALL_PATH" ; then + globus_install_dir=$GLOBUS_INSTALL_PATH + elif test -n "$GSI_INSTALL_PATH" ; then + globus_install_dir=$GSI_INSTALL_PATH + elif test -d /usr/local/globus ; then + globus_install_dir="/usr/local/globus" + elif test -d /usr/local/gsi ; then + globus_install_dir="/usr/local/gsi" + else + AC_MSG_ERROR(Cannot find Globus/GSI installation directory) + fi + fi + AC_MSG_RESULT($globus_install_dir) + + # Find GLOBUS/GSI development directory + AC_MSG_CHECKING(for Globus/GSI development directory) + + if test -d ${globus_install_dir}/lib ; then + # Looks like a flat directory structure from configure/make + # and not globus-install or gsi-install + globus_dev_dir=$globus_install_dir + + else + # Assume a true globus installation with architecture + # directories and run globus-development-path to find + # the development directory + + # Set GLOBUS_INSTALL_PATH + GLOBUS_INSTALL_PATH=$globus_install_dir + export GLOBUS_INSTALL_PATH + + dev_path_program=${globus_install_dir}/bin/globus-development-path + + if test ! -x ${dev_path_program} ; then + AC_MSG_ERROR(Cannot find Globus/GSI installation directory: program ${dev_path_program} does not exist or is not executable) + fi + + globus_dev_dir=`${dev_path_program}` + + if test -z "$globus_dev_dir" -o "X$globus_dev_dir" = "X" ; then + AC_MSG_ERROR(Cannot find Globus/GSI development directory) + fi + + if test ! -d "$globus_dev_dir" ; then + AC_MSG_ERROR(Cannot find Globus/GSI development directory: $globus_dev_dir does not exist) + fi + fi + AC_MSG_RESULT($globus_dev_dir) + + GSI_LIBS="-lglobus_gss_assist -lglobus_gss -lglobus_gaa -lssl -lcrypto" + GSI_LDFLAGS="-L${globus_dev_dir}/lib" + GSI_CFLAGS="-I${globus_dev_dir}/include" + + LIBS="$LIBS $GSI_LIBS" + LDFLAGS="$LDFLAGS $GSI_LDFLAGS" + CFLAGS="$CFLAGS $GSI_CFLAGS" +# End Globus/GSI section +fi + # Check whether user wants S/Key support SKEY_MSG="no" AC_ARG_WITH(skey, @@ -1475,6 +1561,31 @@ AC_ARG_WITH(kerberos5, fi AC_CHECK_LIB(resolv, dn_expand, , ) + AC_CHECK_LIB(gssapi,gss_init_sec_context, + [ AC_DEFINE(GSSAPI) + K5LIBS="-lgssapi $K5LIBS" ], + [ AC_CHECK_LIB(gssapi_krb5,gss_init_sec_context, + [ AC_DEFINE(GSSAPI) + K5LIBS="-lgssapi_krb5 $K5LIBS" ], + AC_MSG_WARN([Cannot find any suitable gss-api library - build may fail]), + $K5LIBS) + ], + $K5LIBS) + + AC_CHECK_HEADER(gssapi.h, , + [ unset ac_cv_header_gssapi_h + CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include/gssapi" + AC_CHECK_HEADERS(gssapi.h, , + AC_MSG_WARN([Cannot find any suitable gss-api header - build may fail]) + ) + ] + ) + + oldCPP="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include/gssapi" + AC_CHECK_HEADER(gssapi_krb5.h, , + [ CPPFLAGS="$oldCPP" ]) + KRB5=yes fi ] diff --git a/openssh/gss-genr.c b/openssh/gss-genr.c new file mode 100644 index 0000000..be8208a --- /dev/null +++ b/openssh/gss-genr.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2001 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 "ssh.h" +#include "ssh2.h" +#include "xmalloc.h" +#include "buffer.h" +#include "bufaux.h" +#include "packet.h" +#include "compat.h" +#include +#include "cipher.h" +#include "kex.h" +#include "log.h" + +#include + +#include "ssh-gss.h" + +/* Assorted globals for tracking the clients identity once they've + * authenticated */ + +gss_buffer_desc gssapi_client_name = {0,NULL}; /* Name of our client */ +gss_cred_id_t gssapi_client_creds = GSS_C_NO_CREDENTIAL; /* Their credentials */ +enum ssh_gss_id gssapi_client_type = GSS_LAST_ENTRY; + +/* The mechanism name used in the list below is defined in the internet + * draft as the Base 64 encoding of the MD5 hash of the ASN.1 DER encoding + * of the underlying GSSAPI mechanism's OID. + * + * Also from the draft, before considering adding SPNEGO, bear in mind that + * "mechanisms ... MUST NOT use SPNEGO as the underlying GSSAPI mechanism" + */ + +/* These must be in the same order as ssh_gss_id, in ssh-gss.h */ + +ssh_gssapi_mech supported_mechs[]= { +#ifdef KRB5 + /* Official OID - 1.2.850.113554.1.2.2 */ + {"Se3H81ismmOC3OE+FwYCiQ==","Kerberos", + {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}}, +#endif +#ifdef GSI + /* gssapi_ssleay 1.3.6.1.4.1.3536.1.1 */ + {"N3+k7/4wGxHyuP8Yxi4RhA==", + "GSI", + {9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"} + }, +#endif /* GSI */ + {NULL,NULL,{0,0}} +}; + +char gssprefix[]=KEX_GSS_SHA1; + +/* Return a list of the gss-group1-sha1-x mechanisms supported by this + * program. + * + * We only support the mechanisms that we've indicated in the list above, + * but we check that they're supported by the GSSAPI mechanism on the + * machine. We also check, before including them in the list, that + * we have the necesary information in order to carry out the key exchange + * (that is, that the user has credentials, the server's creds are accessible, + * etc) + * + * The way that this is done is fairly nasty, as we do a lot of work that + * is then thrown away. This should possibly be implemented with a cache + * that stores the results (in an expanded Gssctxt structure), which are + * then used by the first calls if that key exchange mechanism is chosen. + */ + +char * +ssh_gssapi_mechanisms(int server,char *host) { + gss_OID_set supported; + OM_uint32 maj_status, min_status; + Buffer buf; + int i = 0; + int present; + char * mechs; + Gssctxt ctx; + gss_buffer_desc token; + + + gss_indicate_mechs(&min_status, &supported); + + buffer_init(&buf); + + do { + if ((maj_status=gss_test_oid_set_member(&min_status, + &supported_mechs[i].oid, + supported, + &present))) { + present=0; + } + if (present) { + ssh_gssapi_build_ctx(&ctx); + ssh_gssapi_set_oid(&ctx,&supported_mechs[i].oid); + if (server) { + if (ssh_gssapi_acquire_cred(&ctx)) { + ssh_gssapi_delete_ctx(&ctx); + continue; + } + } + else { /* client */ + if (ssh_gssapi_import_name(&ctx,host)) + continue; + maj_status=ssh_gssapi_init_ctx(&ctx, 0, + GSS_C_NO_BUFFER, + &token, + NULL); + ssh_gssapi_delete_ctx(&ctx); + if (GSS_ERROR(maj_status)) { + continue; + } + } + + /* Append gss_group1_sha1_x to our list */ + buffer_append(&buf, gssprefix, + strlen(gssprefix)); + buffer_append(&buf, supported_mechs[i].enc_name, + strlen(supported_mechs[i].enc_name)); + } + } while (supported_mechs[++i].name != 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) + return(NULL); + else + return(mechs); +} + +void ssh_gssapi_supported_oids(gss_OID_set *oidset) { + enum ssh_gss_id i =0; + OM_uint32 maj_status,min_status; + int present; + gss_OID_set supported; + + gss_create_empty_oid_set(&min_status,oidset); + gss_indicate_mechs(&min_status, &supported); + + while (supported_mechs[i].name!=NULL) { + if ((maj_status=gss_test_oid_set_member(&min_status, + &supported_mechs[i].oid, + supported, + &present))) { + present=0; + } + if (present) { + gss_add_oid_set_member(&min_status, + &supported_mechs[i].oid, + oidset); + } + i++; + } +} + +/* Set the contexts OID from a data stream */ +void ssh_gssapi_set_oid_data(Gssctxt *ctx, void *data, size_t len) { + if (ctx->oid != GSS_C_NO_OID) { + xfree(ctx->oid->elements); + xfree(ctx->oid); + } + ctx->oid=xmalloc(sizeof(gss_OID_desc)); + ctx->oid->length=len; + ctx->oid->elements=xmalloc(len); + memcpy(ctx->oid->elements,data,len); +} + +/* Set the contexts OID */ +void ssh_gssapi_set_oid(Gssctxt *ctx, gss_OID oid) { + ssh_gssapi_set_oid_data(ctx,oid->elements,oid->length); +} + +/* Find out which GSS type (out of the list we define in ssh-gss.h) a + * particular connection is using + */ +enum ssh_gss_id ssh_gssapi_get_ctype(Gssctxt *ctxt) { + enum ssh_gss_id i=0; + + while(supported_mechs[i].name!=NULL && + supported_mechs[i].oid.length != ctxt->oid->length && + (memcmp(supported_mechs[i].oid.elements, + ctxt->oid->elements,ctxt->oid->length) !=0)) { + i++; + } + return(i); +} + +/* Set the GSS context's OID to the oid indicated by the given key exchange + * name. */ +int ssh_gssapi_id_kex(Gssctxt *ctx, char *name) { + enum ssh_gss_id i=0; + + if (strncmp(name, gssprefix, strlen(gssprefix)-1) !=0) { + return(1); + } + + name+=strlen(gssprefix); /* Move to the start of the MIME string */ + + while (supported_mechs[i].name!=NULL && + strcmp(name,supported_mechs[i].enc_name)!=0) { + i++; + } + + if (supported_mechs[i].name==NULL) + return (1); + + ssh_gssapi_set_oid(ctx,&supported_mechs[i].oid); + + return 0; +} + + +/* All this effort to report an error ... */ +void +ssh_gssapi_error(OM_uint32 major_status,OM_uint32 minor_status) { + OM_uint32 lmaj, lmin; + gss_buffer_desc msg; + OM_uint32 ctx; + + ctx = 0; + /* The GSSAPI error */ + do { + lmaj = gss_display_status(&lmin, major_status, + GSS_C_GSS_CODE, + GSS_C_NULL_OID, + &ctx, &msg); + if (lmaj == GSS_S_COMPLETE) { + debug((char *)msg.value); + (void) gss_release_buffer(&lmin, &msg); + } + } while (ctx!=0); + + /* The mechanism specific error */ + do { + lmaj = gss_display_status(&lmin, minor_status, + GSS_C_MECH_CODE, + GSS_C_NULL_OID, + &ctx, &msg); + if (lmaj == GSS_S_COMPLETE) { + debug((char *)msg.value); + (void) gss_release_buffer(&lmin, &msg); + } + } while (ctx!=0); +} + +/* Initialise our GSSAPI context. We use this opaque structure to contain all + * of the data which both the client and server need to persist across + * {accept,init}_sec_context calls, so that when we do it from the userauth + * stuff life is a little easier + */ +void +ssh_gssapi_build_ctx(Gssctxt *ctx) +{ + ctx->context=GSS_C_NO_CONTEXT; + ctx->name=GSS_C_NO_NAME; + ctx->oid=GSS_C_NO_OID; + ctx->creds=GSS_C_NO_CREDENTIAL; + ctx->client=GSS_C_NO_NAME; + ctx->client_creds=GSS_C_NO_CREDENTIAL; +} + +/* Delete our context, providing it has been built correctly */ +void +ssh_gssapi_delete_ctx(Gssctxt *ctx) +{ + OM_uint32 ms; + + if (ctx->context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&ms,&ctx->context,GSS_C_NO_BUFFER); + if (ctx->name != GSS_C_NO_NAME) + gss_release_name(&ms,&ctx->name); + if (ctx->oid != GSS_C_NO_OID) { + xfree(ctx->oid->elements); + xfree(ctx->oid); + ctx->oid = GSS_C_NO_OID; + } + if (ctx->creds != GSS_C_NO_CREDENTIAL) + gss_release_cred(&ms,&ctx->creds); + if (ctx->client != GSS_C_NO_NAME) + gss_release_name(&ms,&ctx->client); + if (ctx->client_creds != GSS_C_NO_CREDENTIAL) + gss_release_cred(&ms,&ctx->client_creds); +} + +/* Wrapper to init_sec_context + * Requires that the context contains: + * oid + * server name (from ssh_gssapi_import_name) + */ +OM_uint32 +ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, + gss_buffer_desc* send_tok, OM_uint32 *flags) +{ + OM_uint32 maj_status, min_status; + int deleg_flag = 0; + + if (deleg_creds) { + deleg_flag=GSS_C_DELEG_FLAG; + debug("Delegating credentials"); + } + + maj_status=gss_init_sec_context(&min_status, + GSS_C_NO_CREDENTIAL, /* def. cred */ + &ctx->context, + ctx->name, + ctx->oid, + GSS_C_MUTUAL_FLAG | + GSS_C_INTEG_FLAG | + deleg_flag, + 0, /* default lifetime */ + NULL, /* no channel bindings */ + recv_tok, + NULL, + send_tok, + flags, + NULL); + ctx->status=maj_status; + if (GSS_ERROR(maj_status)) { + ssh_gssapi_error(maj_status,min_status); + } + return(maj_status); +} + +/* Wrapper arround accept_sec_context + * Requires that the context contains: + * oid + * credentials (from ssh_gssapi_acquire_cred) + */ +OM_uint32 ssh_gssapi_accept_ctx(Gssctxt *ctx,gss_buffer_desc *recv_tok, + gss_buffer_desc *send_tok, OM_uint32 *flags) +{ + OM_uint32 maj_status, min_status; + gss_OID mech; + + maj_status=gss_accept_sec_context(&min_status, + &ctx->context, + ctx->creds, + recv_tok, + GSS_C_NO_CHANNEL_BINDINGS, + &ctx->client, + &mech, + send_tok, + flags, + NULL, + &ctx->client_creds); + if (GSS_ERROR(maj_status)) { + ssh_gssapi_error(maj_status,min_status); + } + + if (ctx->client_creds) { + debug("Received some client credentials"); + } else { + debug("Got no client credentials"); + } + + /* FIXME: We should check that the mechanism thats being used is + * the one that we asked for (in ctx->oid) */ + + ctx->status=maj_status; + + return(maj_status); +} + +/* Create a service name for the given host */ +OM_uint32 +ssh_gssapi_import_name(Gssctxt *ctx,char *host) { + gss_buffer_desc gssbuf; + OM_uint32 maj_status, min_status; + struct hostent *hostinfo = NULL; + char *xhost; + + /* Make a copy of the host name, in case it was returned by a + * previous call to gethostbyname(). */ + xhost = xstrdup(host); + + /* Make sure we have the FQDN. Some GSSAPI implementations don't do + * this for us themselves */ + + hostinfo = gethostbyname(xhost); + + if ((hostinfo == NULL) || (hostinfo->h_name == NULL)) { + debug("Unable to get FQDN for \"%s\"", xhost); + } else { + host = hostinfo->h_name; + } + xfree(xhost); + + gssbuf.length = sizeof("host@")+strlen(host); + + gssbuf.value = xmalloc(gssbuf.length); + if (gssbuf.value == NULL) { + return(-1); + } + snprintf(gssbuf.value,gssbuf.length,"host@%s",host); + if ((maj_status=gss_import_name(&min_status, + &gssbuf, + GSS_C_NT_HOSTBASED_SERVICE, + &ctx->name))) { + ssh_gssapi_error(maj_status,min_status); + } + + xfree(gssbuf.value); + return(maj_status); +} + +/* Acquire credentials for a server running on the current host. + * Requires that the context structure contains a valid OID + */ +OM_uint32 +ssh_gssapi_acquire_cred(Gssctxt *ctx) { + OM_uint32 maj_status, min_status; + char lname[MAXHOSTNAMELEN]; + gss_OID_set oidset; + + gss_create_empty_oid_set(&min_status,&oidset); + gss_add_oid_set_member(&min_status,ctx->oid,&oidset); + + if (gethostname(lname, MAXHOSTNAMELEN)) { + return(-1); + } + + if ((maj_status=ssh_gssapi_import_name(ctx,lname))) { + return(maj_status); + } + if ((maj_status=gss_acquire_cred(&min_status, + ctx->name, + 0, + oidset, + GSS_C_ACCEPT, + &ctx->creds, + NULL, + NULL))) { + ssh_gssapi_error(maj_status,min_status); + } + + gss_release_oid_set(&min_status, &oidset); + return(maj_status); +} + +/* Extract the client details from a given context. This can only reliably + * be called once for a context */ + +OM_uint32 +ssh_gssapi_getclient(Gssctxt *ctx, enum ssh_gss_id *type, + gss_buffer_desc *name, gss_cred_id_t *creds) { + + OM_uint32 maj_status,min_status; + + *type=ssh_gssapi_get_ctype(ctx); + if ((maj_status=gss_display_name(&min_status,ctx->client,name,NULL))) { + ssh_gssapi_error(maj_status,min_status); + } + + /* This is icky. There appears to be no way to copy this structure, + * rather than the pointer to it, so we simply copy the pointer and + * mark the originator as empty so we don't destroy it. + */ + *creds=ctx->client_creds; + ctx->client_creds=GSS_C_NO_CREDENTIAL; + return(maj_status); +} + +#endif /* GSSAPI */ diff --git a/openssh/gss-serv.c b/openssh/gss-serv.c new file mode 100644 index 0000000..762c210 --- /dev/null +++ b/openssh/gss-serv.c @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2001 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 "ssh.h" +#include "ssh2.h" +#include "xmalloc.h" +#include "buffer.h" +#include "bufaux.h" +#include "packet.h" +#include "compat.h" +#include +#include "cipher.h" +#include "kex.h" +#include "auth.h" +#include "log.h" +#include "session.h" +#include "dispatch.h" +#include "servconf.h" + +#include "ssh-gss.h" + +extern ServerOptions options; +extern u_char *session_id2; +extern int session_id2_len; + + +typedef struct ssh_gssapi_cred_cache { + char *filename; + char *envvar; + char *envval; + void *data; +} ssh_gssapi_cred_cache; + +static struct ssh_gssapi_cred_cache gssapi_cred_store = {NULL,NULL,NULL}; + +#ifdef KRB5 + +#ifdef HEIMDAL +#include +#else +#include +#define krb5_get_err_text(context,code) error_message(code) +#endif + +static krb5_context krb_context = NULL; + +/* Initialise the krb5 library, so we can use it for those bits that + * GSSAPI won't do */ + +int ssh_gssapi_krb5_init() { + krb5_error_code problem; + + if (krb_context !=NULL) + return 1; + + problem = krb5_init_context(&krb_context); + if (problem) { + log("Cannot initialize krb5 context"); + return 0; + } + krb5_init_ets(krb_context); + + return 1; +} + +/* Check if this user is OK to login. This only works with krb5 - other + * GSSAPI mechanisms will need their own. + * Returns true if the user is OK to log in, otherwise returns 0 + */ + +int +ssh_gssapi_krb5_userok(char *name) { + krb5_principal princ; + int retval; + + if (ssh_gssapi_krb5_init() == 0) + return 0; + + if ((retval=krb5_parse_name(krb_context, gssapi_client_name.value, + &princ))) { + log("krb5_parse_name(): %.100s", + krb5_get_err_text(krb_context,retval)); + return 0; + } + if (krb5_kuserok(krb_context, princ, name)) { + retval = 1; + log("Authorized to %s, krb5 principal %s (krb5_kuserok)",name, + (char *)gssapi_client_name.value); + } + else + retval = 0; + + krb5_free_principal(krb_context, princ); + return retval; +} + +/* Make sure that this is called _after_ we've setuid to the user */ + +/* This writes out any forwarded credentials. Its specific to the Kerberos + * GSSAPI mechanism + * + * We assume that our caller has made sure that the user has selected + * delegated credentials, and that the client_creds structure is correctly + * populated. + */ + +void +ssh_gssapi_krb5_storecreds() { + krb5_ccache ccache; + krb5_error_code problem; + krb5_principal princ; + char ccname[35]; + static char name[40]; + int tmpfd; + OM_uint32 maj_status,min_status; + + + if (gssapi_client_creds==NULL) { + debug("No credentials stored"); + return; + } + + if (ssh_gssapi_krb5_init() == 0) + return; + + if (options.gss_use_session_ccache) { + snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d_XXXXXX",geteuid()); + + if ((tmpfd = mkstemp(ccname))==-1) { + log("mkstemp(): %.100s", strerror(errno)); + return; + } + if (fchmod(tmpfd, S_IRUSR | S_IWUSR) == -1) { + log("fchmod(): %.100s", strerror(errno)); + close(tmpfd); + return; + } + } else { + snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d",geteuid()); + tmpfd = open(ccname, O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); + if (tmpfd == -1) { + log("open(): %.100s", strerror(errno)); + return; + } + } + + close(tmpfd); + snprintf(name, sizeof(name), "FILE:%s",ccname); + + if ((problem = krb5_cc_resolve(krb_context, name, &ccache))) { + log("krb5_cc_default(): %.100s", + krb5_get_err_text(krb_context,problem)); + return; + } + + if ((problem = krb5_parse_name(krb_context, gssapi_client_name.value, + &princ))) { + log("krb5_parse_name(): %.100s", + krb5_get_err_text(krb_context,problem)); + krb5_cc_destroy(krb_context,ccache); + return; + } + + if ((problem = krb5_cc_initialize(krb_context, ccache, princ))) { + log("krb5_cc_initialize(): %.100s", + krb5_get_err_text(krb_context,problem)); + krb5_free_principal(krb_context,princ); + krb5_cc_destroy(krb_context,ccache); + return; + } + + krb5_free_principal(krb_context,princ); + + #ifdef HEIMDAL + if ((problem = krb5_cc_copy_cache(krb_context, + gssapi_client_creds->ccache, + ccache))) { + log("krb5_cc_copy_cache(): %.100s", + krb5_get_err_text(krb_context,problem)); + krb5_cc_destroy(krb_context,ccache); + return; + } + #else + if ((maj_status = gss_krb5_copy_ccache(&min_status, + gssapi_client_creds, + ccache))) { + log("gss_krb5_copy_ccache() failed"); + ssh_gssapi_error(maj_status,min_status); + krb5_cc_destroy(krb_context,ccache); + return; + } + #endif + + krb5_cc_close(krb_context,ccache); + + +#ifdef USE_PAM + do_pam_putenv("KRB5CCNAME",name); +#endif + + gssapi_cred_store.filename=strdup(ccname); + gssapi_cred_store.envvar="KRB5CCNAME"; + gssapi_cred_store.envval=strdup(name); + + return; +} + +#endif /* KRB5 */ + +#ifdef GSI +#include + +/* + * Check if this user is OK to login under GSI. User has been authenticated + * as identity in global 'client_name.value' and is trying to log in as passed + * username in 'name'. + * + * Returns non-zero if user is authorized, 0 otherwise. + */ +int +ssh_gssapi_gsi_userok(char *name) +{ + int authorized = 0; + + /* This returns 0 on success */ + authorized = (globus_gss_assist_userok(gssapi_client_name.value, + name) == 0); + + debug("GSI user %s is%s authorized as target user %s", + (char *) gssapi_client_name.value, + (authorized ? "" : " not"), + name); + + return authorized; +} + +/* + * Handle setting up child environment for GSI. + * + * Make sure that this is called _after_ we've setuid to the user. + */ +void +ssh_gssapi_gsi_storecreds() +{ + OM_uint32 major_status; + OM_uint32 minor_status; + + + if (gssapi_client_creds != NULL) + { + char *creds_env = NULL; + + /* + * This is the current hack with the GSI gssapi library to + * export credentials to disk. + */ + + debug("Exporting delegated credentials"); + + minor_status = 0xdee0; /* Magic value */ + major_status = + gss_inquire_cred(&minor_status, + gssapi_client_creds, + (gss_name_t *) &creds_env, + NULL, + NULL, + NULL); + + if ((major_status == GSS_S_COMPLETE) && + (minor_status == 0xdee1) && + (creds_env != NULL)) + { + char *value; + + /* + * String is of the form: + * X509_USER_DELEG_PROXY=filename + * so we parse out the filename + * and then set X509_USER_PROXY + * to point at it. + */ + value = strchr(creds_env, '='); + + if (value != NULL) + { + *value = '\0'; + value++; +#ifdef USE_PAM + do_pam_putenv("X509_USER_PROXY",value); +#endif + gssapi_cred_store.filename=NULL; + gssapi_cred_store.envvar="X509_USER_PROXY"; + gssapi_cred_store.envval=strdup(value); + + return; + } + else + { + log("Failed to parse delegated credentials string '%s'", + creds_env); + } + } + else + { + log("Failed to export delegated credentials (error %ld)", + major_status); + } + } +} + +#endif /* GSI */ + +void +ssh_gssapi_cleanup_creds(void *ignored) +{ + if (gssapi_cred_store.filename!=NULL) { + /* Unlink probably isn't sufficient */ + debug("removing gssapi cred file\"%s\"",gssapi_cred_store.filename); + unlink(gssapi_cred_store.filename); + } +} + +void +ssh_gssapi_storecreds() +{ + switch (gssapi_client_type) { +#ifdef KRB5 + case GSS_KERBEROS: + ssh_gssapi_krb5_storecreds(); + break; +#endif +#ifdef GSI + case GSS_GSI: + ssh_gssapi_gsi_storecreds(); + break; +#endif /* GSI */ + case GSS_LAST_ENTRY: + /* GSSAPI not used in this authentication */ + debug("No GSSAPI credentials stored"); + break; + default: + log("ssh_gssapi_do_child: Unknown mechanism"); + + } + + if (options.gss_cleanup_creds) { + fatal_add_cleanup(ssh_gssapi_cleanup_creds, NULL); + } + +} + +/* This allows GSSAPI methods to do things to the childs environment based + * on the passed authentication process and credentials. + * + * Question: If we didn't use userauth_external for some reason, should we + * still delegate credentials? + */ +void +ssh_gssapi_do_child(char ***envp, u_int *envsizep) +{ + + if (gssapi_cred_store.envvar!=NULL && + gssapi_cred_store.envval!=NULL) { + + debug("Setting %s to %s", gssapi_cred_store.envvar, + gssapi_cred_store.envval); + child_set_env(envp, envsizep, gssapi_cred_store.envvar, + gssapi_cred_store.envval); + } + + switch(gssapi_client_type) { +#ifdef KRB5 + case GSS_KERBEROS: break; +#endif +#ifdef GSI + case GSS_GSI: break; +#endif + case GSS_LAST_ENTRY: + debug("No GSSAPI credentials stored"); + + default: + log("ssh_gssapi_do_child: Unknown mechanism"); + } +} + +int +ssh_gssapi_userok(char *user) +{ + if (gssapi_client_name.length==0 || + gssapi_client_name.value==NULL) { + debug("No suitable client data"); + return 0; + } + switch (gssapi_client_type) { +#ifdef KRB5 + case GSS_KERBEROS: + return(ssh_gssapi_krb5_userok(user)); + break; /* Not reached */ +#endif +#ifdef GSI + case GSS_GSI: + return(ssh_gssapi_gsi_userok(user)); + break; /* Not reached */ +#endif /* GSI */ + case GSS_LAST_ENTRY: + debug("Client not GSSAPI"); + break; + default: + debug("Unknown client authentication type"); + } + return(0); +} + +int +userauth_external(Authctxt *authctxt) +{ + packet_done(); + + return(ssh_gssapi_userok(authctxt->user)); +} + +void input_gssapi_token(int type, int plen, void *ctxt); +void input_gssapi_exchange_complete(int type, int plen, void *ctxt); + +/* We only support those mechanisms that we know about (ie ones that we know + * how to check local user kuserok and the like + */ +int +userauth_gssapi(Authctxt *authctxt) +{ + gss_OID_desc oid= {0,NULL}; + Gssctxt *ctxt; + int mechs; + gss_OID_set supported; + int present; + OM_uint32 ms; + + if (!authctxt->valid || authctxt->user == NULL) + return 0; + mechs=packet_get_int(); + if (mechs==0) { + debug("Mechanism negotiation is not supported"); + return 0; + } + + ssh_gssapi_supported_oids(&supported); + do { + if (oid.elements) + xfree(oid.elements); + oid.elements = packet_get_string(&oid.length); + gss_test_oid_set_member(&ms, &oid, supported, &present); + mechs--; + } while (mechs>0 && !present); + + if (!present) { + xfree(oid.elements); + return(0); + } + + ctxt=xmalloc(sizeof(Gssctxt)); + authctxt->methoddata=(void *)ctxt; + + ssh_gssapi_build_ctx(ctxt); + ssh_gssapi_set_oid(ctxt,&oid); + + if (ssh_gssapi_acquire_cred(ctxt)) + return 0; + + /* Send SSH_MSG_USERAUTH_GSSAPI_RESPONSE */ + + packet_start(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE); + packet_put_string(oid.elements,oid.length); + packet_send(); + packet_write_wait(); + xfree(oid.elements); + + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + &input_gssapi_token); + authctxt->postponed = 1; + + return 0; +} + +void +input_gssapi_token(int type, int plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + gss_buffer_desc send_tok,recv_tok; + OM_uint32 maj_status, min_status; + + if (authctxt == NULL || authctxt->methoddata == NULL) + fatal("No authentication or GSSAPI context"); + + gssctxt=authctxt->methoddata; + + recv_tok.value=packet_get_string(&recv_tok.length); + + maj_status=ssh_gssapi_accept_ctx(gssctxt, &recv_tok, &send_tok, NULL); + packet_done(); + + if (GSS_ERROR(maj_status)) { + /* Failure */ + authctxt->postponed = 0; + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); + userauth_finish(authctxt, 0, "gssapi"); + } + + if (send_tok.length != 0) { + /* Send a packet back to the client */ + packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + gss_release_buffer(&min_status, &send_tok); + } + + if (maj_status == GSS_S_COMPLETE) { + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, + &input_gssapi_exchange_complete); + } +} + +/* This is called when the client thinks we've completed authentication. + * It should only be enabled in the dispatch handler by the function above, + * which only enables it once the GSSAPI exchange is complete. + */ + +void +input_gssapi_exchange_complete(int type, int plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + int authenticated; + + if (authctxt == NULL || authctxt->methoddata == NULL) + fatal("No authentication or GSSAPI context"); + + gssctxt=authctxt->methoddata; + + /* This should never happen, but better safe than sorry. */ + if (gssctxt->status != GSS_S_COMPLETE) { + packet_disconnect("Context negotiation is not complete"); + } + + if (ssh_gssapi_getclient(gssctxt,&gssapi_client_type, + &gssapi_client_name, + &gssapi_client_creds)) { + fatal("Couldn't convert client name"); + } + + authenticated = ssh_gssapi_userok(authctxt->user); + + authctxt->postponed = 0; + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); + userauth_finish(authctxt, authenticated, "gssapi"); +} + +#endif /* GSSAPI */ diff --git a/openssh/kex.c b/openssh/kex.c index 1a412ce..3a4478b 100644 --- a/openssh/kex.c +++ b/openssh/kex.c @@ -41,6 +41,10 @@ RCSID("$OpenBSD: kex.c,v 1.36 2001/06/25 08:25:37 markus Exp $"); #include "match.h" #include "dispatch.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #define KEX_COOKIE_LEN 16 /* prototype */ @@ -226,6 +230,11 @@ kex_kexinit_finish(Kex *kex) case DH_GEX_SHA1: kexgex(kex); break; +#ifdef GSSAPI + case GSS_GRP1_SHA1: + kexgss(kex); + break; +#endif default: fatal("Unsupported key exchange %d", kex->kex_type); } @@ -280,11 +289,15 @@ choose_kex(Kex *k, char *client, char *server) { k->name = match_list(client, server, NULL); if (k->name == NULL) - fatal("no kex alg"); + fatal("No key exchange algorithm"); if (strcmp(k->name, KEX_DH1) == 0) { k->kex_type = DH_GRP1_SHA1; } else if (strcmp(k->name, KEX_DHGEX) == 0) { k->kex_type = DH_GEX_SHA1; +#ifdef GSSAPI + } else if (strncmp(k->name, KEX_GSS_SHA1, sizeof(KEX_GSS_SHA1)-1) == 0) { + k->kex_type = GSS_GRP1_SHA1; +#endif } else fatal("bad kex alg %s", k->name); } diff --git a/openssh/kex.h b/openssh/kex.h index fe33921..0fde811 100644 --- a/openssh/kex.h +++ b/openssh/kex.h @@ -56,7 +56,8 @@ enum kex_modes { enum kex_exchange { DH_GRP1_SHA1, - DH_GEX_SHA1 + DH_GEX_SHA1, + GSS_GRP1_SHA1 }; #define KEX_INIT_SENT 0x0001 @@ -92,6 +93,11 @@ struct Newkeys { Mac mac; Comp comp; }; + +struct KexOptions { + int gss_deleg_creds; +}; + struct Kex { u_char *session_id; int session_id_len; @@ -105,10 +111,12 @@ struct Kex { Buffer peer; int done; int flags; + char *host; char *client_version_string; char *server_version_string; int (*verify_host_key)(Key *); Key *(*load_host_key)(int); + struct KexOptions options; }; Kex *kex_setup(char *[PROPOSAL_MAX]); diff --git a/openssh/kexgss.c b/openssh/kexgss.c new file mode 100644 index 0000000..1eb11ff --- /dev/null +++ b/openssh/kexgss.c @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2001 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" + +/* This is now the same as the DH hash ... */ + +u_char * +kex_gssapi_hash( + char *client_version_string, + char *server_version_string, + char *ckexinit, int ckexinitlen, + char *skexinit, int skexinitlen, + u_char *serverhostkeyblob, int sbloblen, + BIGNUM *client_dh_pub, + BIGNUM *server_dh_pub, + BIGNUM *shared_secret) +{ + Buffer b; + static u_char digest[EVP_MAX_MD_SIZE]; + EVP_MD *evp_md = EVP_sha1(); + EVP_MD_CTX md; + + buffer_init(&b); + buffer_put_string(&b, client_version_string, strlen(client_version_string)); + buffer_put_string(&b, server_version_string, strlen(server_version_string)); + + /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */ + buffer_put_int(&b, ckexinitlen+1); + buffer_put_char(&b, SSH2_MSG_KEXINIT); + buffer_append(&b, ckexinit, ckexinitlen); + buffer_put_int(&b, skexinitlen+1); + buffer_put_char(&b, SSH2_MSG_KEXINIT); + buffer_append(&b, skexinit, skexinitlen); + + buffer_put_string(&b, serverhostkeyblob, sbloblen); + buffer_put_bignum2(&b, client_dh_pub); + buffer_put_bignum2(&b, server_dh_pub); + buffer_put_bignum2(&b, shared_secret); + +#ifdef DEBUG_KEX + buffer_dump(&b); +#endif + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); + EVP_DigestFinal(&md, digest, NULL); + + buffer_free(&b); + +#ifdef DEBUG_KEX + dump_digest("hash", digest, evp_md->md_size); +#endif + return digest; +} + +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; + int plen,dlen; + unsigned int klen, kout; + DH *dh; + BIGNUM *dh_server_pub = 0; + BIGNUM *shared_secret = 0; + unsigned char *kbuf; + unsigned char *hash; + unsigned char *serverhostkey; + int type = 0; + int first = 1; + int slen = 0; + + /* Initialise our GSSAPI world */ + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_id_kex(&ctxt,kex->name)) { + fatal("Couldn't identify host exchange"); + } + if (ssh_gssapi_import_name(&ctxt,kex->host)) { + 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->options.gss_deleg_creds, + token_ptr,&send_tok, + &ret_flags); + + if (GSS_ERROR(maj_status)) { + 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(); + packet_write_wait(); + + + /* If we've sent them data, they'd better be polite + * and reply. */ + + type = packet_read(&plen); + 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(&recv_tok.length); + break; + case SSH2_MSG_KEXGSS_COMPLETE: + debug("Received GSSAPI_COMPLETE"); + packet_get_bignum2(dh_server_pub, &dlen); + msg_tok.value= + packet_get_string(&msg_tok.length); + + /* Is there a token included? */ + if (packet_get_char()) { + recv_tok.value= + packet_get_string(&recv_tok.length); + /* 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; + default: + packet_disconnect("Protocol error: didn't expect packet type %d", + type); + } + token_ptr=&recv_tok; + } + + } 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); + + hash = kex_gssapi_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 H matches the token we just got. */ + if ((maj_status = gss_verify_mic(&min_status, + ctxt.context, + &gssbuf, + &msg_tok, + NULL))) { + + packet_disconnect("Hash's MIC didn't verify"); + } + + DH_free(dh); + ssh_gssapi_delete_ctx(&ctxt); + /* 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); + } + + kex_derive_keys(kex, hash, shared_secret); + BN_clear_free(shared_secret); + kex_finish(kex); +} + + + + +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; + int plen; + int dlen=0; + unsigned int klen, kout; + unsigned char *kbuf; + unsigned char *hash; + DH *dh; + BIGNUM *shared_secret = 0; + BIGNUM *dh_client_pub = 0; + int type =0; + + /* Initialise GSSAPI */ + + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_id_kex(&ctxt,kex->name)) + fatal("Unknown gssapi mechanism"); + if (ssh_gssapi_acquire_cred(&ctxt)) + fatal("Unable to acquire credentials for the server"); + + /* Initialise some bignums */ + dh_client_pub = BN_new(); + if (dh_client_pub == NULL) + fatal("dh_client_pub == NULL"); + + do { + debug("Wait SSH2_MSG_GSSAPI_INIT"); + type = packet_read(&plen); + switch(type) { + case SSH2_MSG_KEXGSS_INIT: + if (dlen!=0) + fatal("Received KEXGSS_INIT after initialising"); + recv_tok.value=packet_get_string(&recv_tok.length); + packet_get_bignum2(dh_client_pub, &dlen); + /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ + break; + case SSH2_MSG_KEXGSS_CONTINUE: + if (dlen==0) + fatal("Received KEXGSS_CONTINUE without initialising"); + recv_tok.value=packet_get_string(&recv_tok.length); + break; + default: + packet_disconnect("Protocol error: didn't expect packet type %d", + type); + } + maj_status=ssh_gssapi_accept_ctx(&ctxt,&recv_tok, &send_tok, + &ret_flags); + + gss_release_buffer(&min_status,&recv_tok); + + 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(); + packet_write_wait(); + gss_release_buffer(&min_status, &send_tok); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + if (GSS_ERROR(maj_status)) + fatal("gss_accept_context died"); + + debug("gss_complete"); + 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); + + hash = kex_gssapi_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 ((maj_status=gss_get_mic(&min_status, + ctxt.context, + GSS_C_QOP_DEFAULT, + &gssbuf, + &msg_tok))) { + ssh_gssapi_error(maj_status,min_status); + 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(); + packet_write_wait(); + + /* Store the client name, and the delegated credentials for later + * use */ + if (ssh_gssapi_getclient(&ctxt,&gssapi_client_type, + &gssapi_client_name, + &gssapi_client_creds)) { + fatal("Couldn't convert client name"); + } + + gss_release_buffer(&min_status, &send_tok); + ssh_gssapi_delete_ctx(&ctxt); + DH_free(dh); + + kex_derive_keys(kex, hash, shared_secret); + BN_clear_free(shared_secret); + kex_finish(kex); +} + +void +kexgss(Kex *kex) +{ + if (kex->server) + kexgss_server(kex); + else + kexgss_client(kex); +} + +#endif /* GSSAPI */ diff --git a/openssh/key.c b/openssh/key.c index 57df5b9..db3a689 100644 --- a/openssh/key.c +++ b/openssh/key.c @@ -625,6 +625,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/openssh/key.h b/openssh/key.h index 00eebb7..6a878d8 100644 --- a/openssh/key.h +++ b/openssh/key.h @@ -34,6 +34,7 @@ enum types { KEY_RSA1, KEY_RSA, KEY_DSA, + KEY_NULL, KEY_UNSPEC }; enum fp_type { diff --git a/openssh/makegssname.pl b/openssh/makegssname.pl new file mode 100644 index 0000000..b91f30b --- /dev/null +++ b/openssh/makegssname.pl @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +use Convert::ASN1 qw(:tag); +use Digest::MD5 qw(md5); +use MIME::Base64; + +$oid=shift; +$encoded=encode_object_id($oid); + +@entries=unpack("C*",$encoded); +shift @entries; # Get rid of the NULL + +print "DER representation: "; +foreach $entry (@entries) { + print "\\x"; + printf "%02X",$entry; +} +print "\n"; + +$digest = md5($encoded); +# We only want the first 10 characters; +# Conversations with the authors suggest that we want to use all of the +# characters of the digest. +#$digest = substr($digest,0,10); +print "gsskeyex representation: ",encode_base64($digest),"\n"; + +sub encode_object_id { + $string=""; + + my @data = ($_[0] =~ /(\d+)/g); + + if(@data < 2) { + @data = (0); + } + else { + my $first = $data[1] + ($data[0] * 40); + splice(@data,0,2,$first); + } + +# my $l = length $string; + $string .= pack("cw*", 0, @data); +# substr($string,$l,1) = asn_encode_length(length($string) - $l - 1); + return $string; +} + + diff --git a/openssh/readconf.c b/openssh/readconf.c index 63035b3..1f35670 100644 --- a/openssh/readconf.c +++ b/openssh/readconf.c @@ -99,6 +99,12 @@ typedef enum { #if defined(KRB4) || defined(KRB5) oKerberosAuthentication, #endif +#ifdef GSSAPI + oGssAuthentication, oGssDelegateCreds, +#ifdef GSI + oGssGlobusDelegateLimitedCreds, +#endif /* GSI */ +#endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) oKerberosTgtPassing, #endif @@ -144,6 +150,15 @@ static struct { #if defined(KRB4) || defined(KRB5) { "kerberosauthentication", oKerberosAuthentication }, #endif +#ifdef GSSAPI + { "gssapiauthentication", oGssAuthentication }, + { "gssapidelegatecredentials", oGssDelegateCreds }, +#ifdef GSI + /* For backwards compatability with old 1.2.27 client code */ + { "forwardgssapiglobusproxy", oGssDelegateCreds }, /* alias */ + { "forwardgssapiglobuslimitedproxy", oGssGlobusDelegateLimitedCreds }, +#endif /* GSI */ +#endif /* GSSAPI */ #if defined(AFS) || defined(KRB5) { "kerberostgtpassing", oKerberosTgtPassing }, #endif @@ -363,6 +378,23 @@ parse_flag: intptr = &options->kerberos_authentication; goto parse_flag; #endif +#ifdef GSSAPI + case oGssAuthentication: + intptr = &options->gss_authentication; + goto parse_flag; + + case oGssDelegateCreds: + intptr = &options->gss_deleg_creds; + goto parse_flag; + +#ifdef GSI + case oGssGlobusDelegateLimitedCreds: + intptr = &options->gss_globus_deleg_limited_proxy; + goto parse_flag; +#endif /* GSI */ + +#endif /* GSSAPI */ + #if defined(AFS) || defined(KRB5) case oKerberosTgtPassing: intptr = &options->kerberos_tgt_passing; @@ -751,6 +783,14 @@ initialize_options(Options * options) options->rsa_authentication = -1; options->pubkey_authentication = -1; options->challenge_response_authentication = -1; +#ifdef GSSAPI + options->gss_authentication = -1; + options->gss_deleg_creds = -1; +#ifdef GSI + options->gss_globus_deleg_limited_proxy = -1; +#endif /* GSI */ +#endif /* GSSAPI */ + #if defined(KRB4) || defined(KRB5) options->kerberos_authentication = -1; #endif @@ -831,6 +871,16 @@ fill_default_options(Options * options) options->pubkey_authentication = 1; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 1; +#ifdef GSSAPI + if (options->gss_authentication == -1) + options->gss_authentication = 1; + if (options->gss_deleg_creds == -1) + options->gss_deleg_creds = 1; +#ifdef GSI + if (options->gss_globus_deleg_limited_proxy == -1) + options->gss_globus_deleg_limited_proxy = 0; +#endif /* GSI */ +#endif /* GSSAPI */ #if defined(KRB4) || defined(KRB5) if (options->kerberos_authentication == -1) options->kerberos_authentication = 1; diff --git a/openssh/readconf.h b/openssh/readconf.h index 25ffa46..6a3823b 100644 --- a/openssh/readconf.h +++ b/openssh/readconf.h @@ -47,6 +47,15 @@ typedef struct { #if defined(AFS) || defined(KRB5) int kerberos_tgt_passing; /* Try Kerberos TGT passing. */ #endif + +#ifdef GSSAPI + int gss_authentication; + int gss_deleg_creds; +#ifdef GSI + int gss_globus_deleg_limited_proxy; +#endif /* GSI */ +#endif /* GSSAPI */ + #ifdef AFS int afs_token_passing; /* Try AFS token passing. */ #endif diff --git a/openssh/servconf.c b/openssh/servconf.c index 1587765..461da8d 100644 --- a/openssh/servconf.c +++ b/openssh/servconf.c @@ -83,6 +83,12 @@ initialize_server_options(ServerOptions *options) options->hostbased_uses_name_from_packet_only = -1; options->rsa_authentication = -1; options->pubkey_authentication = -1; +#ifdef GSSAPI + options->gss_authentication=-1; + options->gss_keyex=-1; + options->gss_use_session_ccache = -1; + options->gss_cleanup_creds = -1; +#endif #if defined(KRB4) || defined(KRB5) options->kerberos_authentication = -1; options->kerberos_or_local_passwd = -1; @@ -187,6 +193,16 @@ fill_default_server_options(ServerOptions *options) options->rsa_authentication = 1; if (options->pubkey_authentication == -1) options->pubkey_authentication = 1; +#ifdef GSSAPI + if (options->gss_authentication == -1) + options->gss_authentication = 1; + if (options->gss_keyex == -1) + options->gss_keyex =1; + if (options->gss_use_session_ccache == -1) + options->gss_use_session_ccache = 1; + if (options->gss_cleanup_creds == -1) + options->gss_cleanup_creds = 1; +#endif #if defined(KRB4) || defined(KRB5) if (options->kerberos_authentication == -1) options->kerberos_authentication = (access(KEYFILE, R_OK) == 0); @@ -249,6 +265,9 @@ typedef enum { sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime, sPermitRootLogin, sLogFacility, sLogLevel, sRhostsAuthentication, sRhostsRSAAuthentication, sRSAAuthentication, +#ifdef GSSAPI + sGssAuthentication, sGssKeyEx, sGssUseSessionCredCache, sGssCleanupCreds, +#endif #if defined(KRB4) || defined(KRB5) sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, #endif @@ -298,6 +317,13 @@ static struct { { "rsaauthentication", sRSAAuthentication }, { "pubkeyauthentication", sPubkeyAuthentication }, { "dsaauthentication", sPubkeyAuthentication }, /* alias */ +#ifdef GSSAPI + { "gssapiauthentication", sGssAuthentication }, + { "gssapikeyexchange", sGssKeyEx }, + { "gssusesessionccache", sGssUseSessionCredCache }, + { "gssapiusesessioncredcache", sGssUseSessionCredCache }, + { "gssapicleanupcreds", sGssCleanupCreds }, +#endif #if defined(KRB4) || defined(KRB5) { "kerberosauthentication", sKerberosAuthentication }, { "kerberosorlocalpasswd", sKerberosOrLocalPasswd }, @@ -623,6 +649,20 @@ parse_flag: case sPubkeyAuthentication: intptr = &options->pubkey_authentication; goto parse_flag; +#ifdef GSSAPI + case sGssAuthentication: + intptr = &options->gss_authentication; + goto parse_flag; + case sGssKeyEx: + intptr = &options->gss_keyex; + goto parse_flag; + case sGssUseSessionCredCache: + intptr = &options->gss_use_session_ccache; + goto parse_flag; + case sGssCleanupCreds: + intptr = &options->gss_cleanup_creds; + goto parse_flag; +#endif #if defined(KRB4) || defined(KRB5) case sKerberosAuthentication: intptr = &options->kerberos_authentication; diff --git a/openssh/servconf.h b/openssh/servconf.h index 2e10b1c..e58e994 100644 --- a/openssh/servconf.h +++ b/openssh/servconf.h @@ -72,6 +72,13 @@ typedef struct { int hostbased_uses_name_from_packet_only; /* experimental */ int rsa_authentication; /* If true, permit RSA authentication. */ int pubkey_authentication; /* If true, permit ssh2 pubkey authentication. */ + #ifdef GSSAPI + int gss_authentication; + int gss_keyex; + int gss_use_session_ccache; /* If true, delegated credentials are + * stored in a session specific cache */ + int gss_cleanup_creds; /* If true, destroy cred cache on logout */ +#endif #if defined(KRB4) || defined(KRB5) int kerberos_authentication; /* If true, permit Kerberos * authentication. */ diff --git a/openssh/session.c b/openssh/session.c index 02c0c52..d7e4806 100644 --- a/openssh/session.c +++ b/openssh/session.c @@ -57,6 +57,10 @@ RCSID("$OpenBSD: session.c,v 1.108 2001/10/11 13:45:21 markus Exp $"); #include "canohost.h" #include "session.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #ifdef WITH_IRIX_PROJECT #include #endif /* WITH_IRIX_PROJECT */ @@ -436,6 +440,12 @@ do_exec_no_pty(Session *s, const char *command) session_proctitle(s); +#if defined(GSSAPI) + temporarily_use_uid(s->pw); + ssh_gssapi_storecreds(); + restore_uid(); +#endif + #if defined(USE_PAM) do_pam_session(s->pw->pw_name, NULL); do_pam_setcred(1); @@ -554,6 +564,12 @@ do_exec_pty(Session *s, const char *command) ptyfd = s->ptyfd; ttyfd = s->ttyfd; +#if defined(GSSAPI) + temporarily_use_uid(s->pw); + ssh_gssapi_storecreds(); + restore_uid(); +#endif + #if defined(USE_PAM) do_pam_session(s->pw->pw_name, s->tty); do_pam_setcred(1); @@ -807,7 +823,7 @@ check_quietlogin(Session *s, const char *command) * Sets the value of the given variable in the environment. If the variable * already exists, its value is overriden. */ -static void +void child_set_env(char ***envp, u_int *envsizep, const char *name, const char *value) { @@ -1121,6 +1137,7 @@ do_child(Session *s, const char *command) exit(1); } endgrent(); + # ifdef USE_PAM /* * PAM credentials may take the form of @@ -1216,6 +1233,13 @@ do_child(Session *s, const char *command) copy_environment(&env, &envsize); #endif +#ifdef GSSAPI + /* Allow any GSSAPI methods that we've used to alter + * the childs environment as they see fit + */ + ssh_gssapi_do_child(&env,&envsize); +#endif + if (!options.use_login) { /* Set basic environment. */ child_set_env(&env, &envsize, "USER", pw->pw_name); @@ -2074,4 +2098,7 @@ static void do_authenticated2(Authctxt *authctxt) { server_loop2(authctxt); +#if defined(GSSAPI) + ssh_gssapi_cleanup_creds(NULL); +#endif } diff --git a/openssh/session.h b/openssh/session.h index 6d5b8e6..e752c66 100644 --- a/openssh/session.h +++ b/openssh/session.h @@ -33,5 +33,6 @@ void session_input_channel_req(int, void *); void session_close_by_pid(pid_t, int); void session_close_by_channel(int, void *); void session_destroy_all(void); - +void child_set_env(char ***envp, u_int *envsizep, const char *name, + const char *value); #endif diff --git a/openssh/ssh-gss.h b/openssh/ssh-gss.h new file mode 100644 index 0000000..7442a43 --- /dev/null +++ b/openssh/ssh-gss.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2001 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. + */ + +#ifdef GSSAPI + +#include "kex.h" +#include "buffer.h" + +#include + +#ifdef KRB5 +#ifndef HEIMDAL +#include + +/* MIT Kerberos doesn't seem to define GSS_NT_HOSTBASED_SERVICE */ + +#ifndef GSS_C_NT_HOSTBASED_SERVICE +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#endif /* GSS_C_NT_... */ +#endif /* !HEIMDAL */ +#endif /* KRB5 */ + +/* draft-ietf-secsh-gsskeyex-01 */ +#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 KEX_GSS_SHA1 "gss-group1-sha1-" + +/* draft-galb-secsh-gssapi-01 */ +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_HASH 62 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 + +enum ssh_gss_id { +#ifdef KRB5 + GSS_KERBEROS, +#endif +#ifdef GSI + GSS_GSI, +#endif /* GSI */ + GSS_LAST_ENTRY +}; + +typedef struct ssh_gss_mech_struct { + char *enc_name; + char *name; + gss_OID_desc oid; +} ssh_gssapi_mech; + +typedef struct { + OM_uint32 status; /* both */ + gss_ctx_id_t context; /* both */ + gss_name_t name; /* both */ + gss_OID oid; /* client */ + gss_cred_id_t creds; /* server */ + gss_name_t client; /* server */ + gss_cred_id_t client_creds; /* server */ +} Gssctxt; + +extern ssh_gssapi_mech supported_mechs[]; +extern gss_buffer_desc gssapi_client_name; +extern gss_cred_id_t gssapi_client_creds; +extern enum ssh_gss_id gssapi_client_type; + +char *ssh_gssapi_mechanisms(int server, char *host); +int ssh_gssapi_id_kex(Gssctxt *ctx, char *name); +void ssh_gssapi_set_oid_data(Gssctxt *ctx, void *data, size_t len); +void ssh_gssapi_set_oid(Gssctxt *ctx, gss_OID oid); +void ssh_gssapi_supported_oids(gss_OID_set *oidset); +enum ssh_gss_id ssh_gssapi_get_ctype(Gssctxt *ctxt); + +OM_uint32 ssh_gssapi_import_name(Gssctxt *ctx, char *host); +OM_uint32 ssh_gssapi_acquire_cred(Gssctxt *ctx); +OM_uint32 ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, + gss_buffer_desc *recv_tok, + gss_buffer_desc *send_tok, OM_uint32 *flags); +OM_uint32 ssh_gssapi_accept_ctx(Gssctxt *ctx, + gss_buffer_desc *recv_tok, + gss_buffer_desc *send_tok, + OM_uint32 *flags); +OM_uint32 ssh_gssapi_getclient(Gssctxt *ctx, + enum ssh_gss_id *type, + gss_buffer_desc *name, + gss_cred_id_t *creds); +void ssh_gssapi_error(OM_uint32 major_status,OM_uint32 minor_status); +void ssh_gssapi_build_ctx(Gssctxt *ctx); +void ssh_gssapi_delete_ctx(Gssctxt *ctx); + +/* In the client */ +void ssh_gssapi_client(Kex *kex, char *host, struct sockaddr *hostaddr, + Buffer *client_kexinit, Buffer *server_kexinit); + +/* In the server */ +void ssh_gssapi_server(Kex *kex, Buffer *client_kexinit, + Buffer *server_kexinit); +void ssh_gssapi_do_child(char ***envp, u_int *envsizep); +void ssh_gssapi_cleanup_creds(void *ignored); +void ssh_gssapi_storecreds(); +#endif /* GSSAPI */ diff --git a/openssh/sshconnect2.c b/openssh/sshconnect2.c index 3107885..d97aed4 100644 --- a/openssh/sshconnect2.c +++ b/openssh/sshconnect2.c @@ -54,6 +54,10 @@ RCSID("$OpenBSD: sshconnect2.c,v 1.85 2001/11/07 16:03:17 markus Exp $"); #include "dispatch.h" #include "canohost.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* import */ extern char *client_version_string; extern char *server_version_string; @@ -87,6 +91,30 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) xxx_host = host; xxx_hostaddr = hostaddr; +#ifdef GSSAPI + /* This is a bit of a nasty kludge. This adds the GSSAPI included + * key exchange methods to the top of the list, allowing the GSSAPI + * code to decide whether each one should be included or not. + */ + { + char *orig, *gss; + int len; + orig = myproposal[PROPOSAL_KEX_ALGS]; + gss = ssh_gssapi_mechanisms(0,host); + if (gss) { + len = strlen(orig)+strlen(gss)+2; + myproposal[PROPOSAL_KEX_ALGS]=xmalloc(len); + snprintf(myproposal[PROPOSAL_KEX_ALGS],len,"%s,%s",gss,orig); + /* If we've got GSSAPI algorithms, then we also support the + * 'null' hostkey, as a last resort */ + 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.ciphers == (char *)-1) { log("No valid ciphers for protocol version 2 given, using defaults."); options.ciphers = NULL; @@ -119,6 +147,11 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; + kex->host=host; + +#ifdef GSSAPI + kex->options.gss_deleg_creds=options.gss_deleg_creds; +#endif xxx_kex = kex; @@ -166,6 +199,8 @@ struct Authctxt { int nkeys; /* kbd-interactive */ int info_req_seen; + /* generic */ + void *methoddata; }; struct Authmethod { char *name; /* string to compare against server's list */ @@ -187,6 +222,16 @@ int userauth_passwd(Authctxt *authctxt); int userauth_kbdint(Authctxt *authctxt); int userauth_hostbased(Authctxt *authctxt); +#ifdef GSSAPI +int userauth_external(Authctxt *authctxt); +int userauth_gssapi(Authctxt *authctxt); +void input_gssapi_response(int type, int plen, void *ctxt); +void input_gssapi_token(int type, int plen, void *ctxt); +void input_gssapi_hash(int type, int plen, void *ctxt); + +int gss_host_key_ok=0; +#endif + void userauth(Authctxt *authctxt, char *authlist); static int sign_and_send_pubkey(Authctxt *, Key *, sign_cb_fn *); @@ -197,6 +242,16 @@ static Authmethod *authmethod_lookup(const char *name); static char *authmethods_get(void); Authmethod authmethods[] = { +#ifdef GSSAPI + {"external-keyx", + userauth_external, + &options.gss_authentication, + NULL}, + {"gssapi", + userauth_gssapi, + &options.gss_authentication, + NULL}, +#endif {"hostbased", userauth_hostbased, &options.hostbased_authentication, @@ -263,6 +318,7 @@ ssh_userauth2(const char *local_user, const char *server_user, char *host, authctxt.success = 0; authctxt.method = authmethod_lookup("none"); authctxt.authlist = NULL; + authctxt.methoddata = NULL; authctxt.keys = keys; authctxt.nkeys = nkeys; authctxt.info_req_seen = 0; @@ -286,6 +342,11 @@ ssh_userauth2(const char *local_user, const char *server_user, char *host, void userauth(Authctxt *authctxt, char *authlist) { + if (authctxt->methoddata!=NULL) { + xfree(authctxt->methoddata); + authctxt->methoddata=NULL; + } + if (authlist == NULL) { authlist = authctxt->authlist; } else { @@ -332,6 +393,8 @@ input_userauth_success(int type, int plen, void *ctxt) fatal("input_userauth_success: no authentication context"); if (authctxt->authlist) xfree(authctxt->authlist); + if (authctxt->methoddata) + xfree(authctxt->methoddata); clear_auth_state(authctxt); authctxt->success = 1; /* break out */ } @@ -424,6 +487,151 @@ input_userauth_pk_ok(int type, int plen, void *ctxt) } +#ifdef GSSAPI +int +userauth_gssapi(Authctxt *authctxt) +{ + int i; + Gssctxt *gssctxt; + static int tries=0; + + /* For now, we only make one attempt at this. We could try offering + * the server different GSSAPI OIDs until we get bored, I suppose. + */ + if (tries++>0) return 0; + + gssctxt=xmalloc(sizeof(Gssctxt)); + + /* Initialise as much of our context as we can, so failures can be + * trapped before sending any packets. + */ + ssh_gssapi_build_ctx(gssctxt); + if (ssh_gssapi_import_name(gssctxt,authctxt->host)) { + return(0); + } + authctxt->methoddata=(void *)gssctxt; + + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + + /* FIXME: This assumes that our current GSSAPI implementation + * supports all of the mechanisms listed in supported_mechs. + * This may not be the case - we should use something along + * the lines of the code in gss_genr to remove the ones that + * aren't supported */ + packet_put_int(GSS_LAST_ENTRY); + for (i=0;imethoddata; + + /* Setup our OID */ + oidv=packet_get_string(&oidlen); + ssh_gssapi_set_oid_data(gssctxt,oidv,oidlen); + + packet_done(); + + status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, + GSS_C_NO_BUFFER, &send_tok, + NULL); + if (GSS_ERROR(status)) { + /* Start again with next method on list */ + debug("Trying to start again"); + userauth(authctxt,NULL); + return; + } + + /* We must have data to send */ + packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + gss_release_buffer(&ms, &send_tok); +} + +void +input_gssapi_token(int type, int plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + gss_buffer_desc send_tok,recv_tok; + OM_uint32 status; + + if (authctxt == NULL) + fatal("input_gssapi_response: no authentication context"); + gssctxt = authctxt->methoddata; + + recv_tok.value=packet_get_string(&recv_tok.length); + + status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, + &recv_tok, &send_tok, NULL); + + packet_done(); + + if (GSS_ERROR(status)) { + /* Start again with the next method in the list */ + userauth(authctxt,NULL); + return; + } + + if (send_tok.length>0) { + packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + } + + if (status == GSS_S_COMPLETE) { + /* If that succeeded, send a exchange complete message */ + packet_start(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE); + packet_send(); + packet_write_wait(); + } +} + +int +userauth_external(Authctxt *authctxt) +{ + static int attempt =0; + + if (attempt++ >= 1) + return 0; + + debug2("userauth_external"); + packet_start(SSH2_MSG_USERAUTH_REQUEST); + packet_put_cstring(authctxt->server_user); + packet_put_cstring(authctxt->service); + packet_put_cstring(authctxt->method->name); + packet_send(); + packet_write_wait(); + return 1; +} +#endif /* GSSAPI */ + int userauth_none(Authctxt *authctxt) { @@ -434,6 +642,7 @@ userauth_none(Authctxt *authctxt) packet_put_cstring(authctxt->method->name); packet_send(); return 1; + } int diff --git a/openssh/sshd.8 b/openssh/sshd.8 index 5a6624b..ab83cfb 100644 --- a/openssh/sshd.8 +++ b/openssh/sshd.8 @@ -466,6 +466,25 @@ This option is similar to and applies to protocol version 2 only. The default is .Dq no . +.It Cm GssapiAuthentication +Specifies whether authentication based on GSSAPI may be used, either using +the result of a successful key exchange, or using GSSAPI user +authentication. +The default is +.Dq yes . +Note that this option applies to protocol version 2 only. +.It Cm GssapiKeyExchange +Specifies whether key exchange based on GSSAPI may be used. When using +GSSAPI key exchange the server need not have a host key. +The default is +.Dq yes . +Note that this option applies to protocol version 2 only. +.It Cm GssapiUseSessionCredCache +Specifies whether a unique credentials cache name should be generated per +session for storing delegated credentials. +The default is +.Dq yes . +Note that this option applies to protocol version 2 only. .It Cm HostKey Specifies the file containing the private host keys (default .Pa /etc/ssh_host_key ) diff --git a/openssh/sshd.c b/openssh/sshd.c index 71a5c2c..5c7d8d0 100644 --- a/openssh/sshd.c +++ b/openssh/sshd.c @@ -73,6 +73,10 @@ RCSID("$OpenBSD: sshd.c,v 1.209 2001/11/10 13:19:45 markus Exp $"); #include "dispatch.h" #include "channels.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #ifdef LIBWRAP #include #include @@ -739,10 +743,13 @@ main(int ac, char **av) log("Disabling protocol version 1. Could not load host key"); options.protocol &= ~SSH_PROTO_1; } +#ifndef GSSAPI + /* The GSSAPI key exchange can run without a host key */ if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { log("Disabling protocol version 2. Could not load host key"); options.protocol &= ~SSH_PROTO_2; } +#endif if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { log("sshd: no hostkeys available -- exiting."); exit(1); @@ -1463,6 +1470,45 @@ do_ssh2_kex(void) } myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); +#ifdef GSSAPI + { + char *orig; + char *gss = NULL; + char *newstr = NULL; + orig = myproposal[PROPOSAL_KEX_ALGS]; + + /* If we don't have a host key, then all of the algorithms + * currently in myproposal are useless */ + if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])==0) + orig= NULL; + + if (options.gss_keyex) + gss = ssh_gssapi_mechanisms(1,NULL); + else + gss = NULL; + + 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 also got the 'null' + host key algorithm, but we're not allowed to advertise it, unless + its the only host key algorithm we're supporting */ + 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->server = 1; -- 2.45.2