From c0fc58180a8334fc0c3fa967cb79e8b53fa8e0f8 Mon Sep 17 00:00:00 2001 From: jbasney Date: Mon, 5 May 2003 16:46:08 +0000 Subject: [PATCH] openssh-3.6.1p2-gssapi-20030430.diff from Simon --- openssh/Makefile.in | 7 +- openssh/acconfig.h | 6 + openssh/auth-pam.c | 20 ++ openssh/auth-pam.h | 1 + openssh/auth.c | 2 +- openssh/auth.h | 1 + openssh/auth2-gss.c | 273 ++++++++++++++++++++++++ openssh/auth2.c | 16 ++ openssh/compat.c | 10 +- openssh/compat.h | 2 + openssh/configure.ac | 70 ++++++ openssh/gss-genr.c | 461 ++++++++++++++++++++++++++++++++++++++++ openssh/gss-serv-gsi.c | 164 ++++++++++++++ openssh/gss-serv-krb5.c | 261 +++++++++++++++++++++++ openssh/gss-serv.c | 388 +++++++++++++++++++++++++++++++++ openssh/kex.c | 8 + openssh/kex.h | 11 + openssh/kexgssc.c | 253 ++++++++++++++++++++++ openssh/kexgsss.c | 241 +++++++++++++++++++++ openssh/key.c | 2 + openssh/key.h | 1 + openssh/makegssname.pl | 50 +++++ openssh/monitor.c | 217 +++++++++++++++++++ openssh/monitor.h | 9 + openssh/monitor_wrap.c | 161 ++++++++++++++ openssh/monitor_wrap.h | 12 ++ openssh/readconf.c | 50 +++++ openssh/readconf.h | 9 + openssh/servconf.c | 40 ++++ openssh/servconf.h | 7 + openssh/session.c | 28 ++- openssh/session.h | 3 + openssh/ssh-gss.h | 145 +++++++++++++ openssh/sshconnect2.c | 332 ++++++++++++++++++++++++++++- openssh/sshd.c | 49 +++++ openssh/sshd_config.5 | 20 ++ 36 files changed, 3321 insertions(+), 9 deletions(-) create mode 100644 openssh/auth2-gss.c create mode 100644 openssh/gss-genr.c create mode 100644 openssh/gss-serv-gsi.c create mode 100644 openssh/gss-serv-krb5.c create mode 100644 openssh/gss-serv.c create mode 100644 openssh/kexgssc.c create mode 100644 openssh/kexgsss.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 270b267..a13f295 100644 --- a/openssh/Makefile.in +++ b/openssh/Makefile.in @@ -67,7 +67,7 @@ LIBSSH_OBJS=authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o \ key.o dispatch.o kex.o mac.o uuencode.o misc.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 \ - entropy.o + entropy.o kexgssc.o gss-genr.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect1.o sshconnect2.o @@ -79,8 +79,9 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ auth2-none.o auth2-passwd.o auth2-pubkey.o \ monitor_mm.o monitor.o monitor_wrap.o monitor_fdpass.o \ - kexdhs.o kexgexs.o \ - auth-krb5.o auth-krb4.o \ + kexdhs.o kexgexs.o kexgsss.o \ + auth-krb5.o auth-krb4.o auth2-gss.o \ + gss-serv.o gss-serv-krb5.o gss-serv-gsi.o \ loginrec.o auth-pam.o auth2-pam.o auth-sia.o md5crypt.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 ssh-rand-helper.8.out ssh-keysign.8.out sshd_config.5.out ssh_config.5.out diff --git a/openssh/acconfig.h b/openssh/acconfig.h index 3b919d5..76ba024 100644 --- a/openssh/acconfig.h +++ b/openssh/acconfig.h @@ -201,6 +201,9 @@ /* Define if compiler implements __func__ */ #undef HAVE___func__ +/* Define this is you want GSSAPI support in the version 2 protocol */ +#undef GSSAPI + /* Define if you want Kerberos 5 support */ #undef KRB5 @@ -213,6 +216,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 d58e74d..da74bc0 100644 --- a/openssh/auth-pam.c +++ b/openssh/auth-pam.c @@ -423,6 +423,26 @@ void free_pam_environment(char **env) } } +/* 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; + +#ifdef HAVE_PAM_PUTENV + compound=xmalloc(strlen(name)+strlen(value)+2); + if (compound) { + sprintf(compound,"%s=%s",name,value); + ret=pam_putenv(__pamh,compound); + xfree(compound); + } +#endif + 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 585ceb8..50bf8f3 100644 --- a/openssh/auth-pam.h +++ b/openssh/auth-pam.h @@ -44,6 +44,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.c b/openssh/auth.c index 1268acc..ccedf9d 100644 --- a/openssh/auth.c +++ b/openssh/auth.c @@ -263,7 +263,7 @@ auth_log(Authctxt *authctxt, int authenticated, char *method, char *info) authmsg, method, authctxt->valid ? "" : "illegal user ", - authctxt->user, + (authctxt->user[0]) ? authctxt->user : "", get_remote_ipaddr(), get_remote_port(), info); diff --git a/openssh/auth.h b/openssh/auth.h index c75d753..8e63132 100644 --- a/openssh/auth.h +++ b/openssh/auth.h @@ -70,6 +70,7 @@ struct Authctxt { krb5_principal krb5_user; char *krb5_ticket_file; #endif + void *methoddata; }; struct Authmethod { diff --git a/openssh/auth2-gss.c b/openssh/auth2-gss.c new file mode 100644 index 0000000..774382b --- /dev/null +++ b/openssh/auth2-gss.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2001-2003 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 "auth.h" +#include "ssh2.h" +#include "xmalloc.h" +#include "log.h" +#include "dispatch.h" +#include "servconf.h" +#include "compat.h" +#include "packet.h" +#include "monitor_wrap.h" + +#include "ssh-gss.h" + +extern ServerOptions options; + +static int +userauth_external(Authctxt *authctxt) +{ + packet_check_eom(); + + return(PRIVSEP(ssh_gssapi_userok(authctxt->user))); +} + +static void ssh_gssapi_userauth_error(Gssctxt *ctxt); +static void input_gssapi_token(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 *); + +/* We only support those mechanisms that we know about (ie ones that we know + * how to check local user kuserok and the like + */ +static int +userauth_gssapi(Authctxt *authctxt) +{ + gss_OID_desc oid= {0,NULL}; + Gssctxt *ctxt = NULL; + int mechs; + gss_OID_set supported; + int present; + OM_uint32 ms; + u_int len; + char * doid = NULL; + + if (!authctxt->valid || authctxt->user == NULL) + return 0; + + if (datafellows & SSH_OLD_GSSAPI) { + debug("Early drafts of GSSAPI userauth not supported"); + return 0; + } + + mechs=packet_get_int(); + if (mechs==0) { + debug("Mechanism negotiation is not supported"); + return 0; + } + + ssh_gssapi_supported_oids(&supported); + do { + mechs--; + + if (doid) + xfree(doid); + + debug("Trying to get OID string"); + doid = packet_get_string(&len); + debug("Got string"); + + if (doid[0]!=0x06 || doid[1]!=len-2) { + log("Mechanism OID received using the old encoding form"); + oid.elements = doid; + oid.length = len; + } else { + oid.elements = doid + 2; + oid.length = len - 2; + } + gss_test_oid_set_member(&ms, &oid, supported, &present); + } while (mechs>0 && !present); + + if (!present) { + xfree(doid); + return(0); + } + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt,&oid)))) { + ssh_gssapi_userauth_error(ctxt); + return(0); + } + + authctxt->methoddata=(void *)ctxt; + + /* Send SSH_MSG_USERAUTH_GSSAPI_RESPONSE */ + + packet_start(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE); + + /* Just return whatever they sent */ + packet_put_string(doid,len); + + packet_send(); + packet_write_wait(); + xfree(doid); + + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + &input_gssapi_token); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, + &input_gssapi_errtok); + authctxt->postponed = 1; + + return 0; +} + +static void +input_gssapi_token(int type, u_int32_t plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + gss_buffer_desc send_tok,recv_tok; + OM_uint32 maj_status, min_status; + u_int len; + + if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + fatal("No authentication or GSSAPI context"); + + gssctxt=authctxt->methoddata; + recv_tok.value=packet_get_string(&len); + recv_tok.length=len; /* u_int vs. size_t */ + + maj_status=PRIVSEP(ssh_gssapi_accept_ctx(gssctxt, &recv_tok, + &send_tok, NULL)); + packet_check_eom(); + + if (GSS_ERROR(maj_status)) { + ssh_gssapi_userauth_error(gssctxt); + if (send_tok.length != 0) { + packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + } + authctxt->postponed = 0; + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); + userauth_finish(authctxt, 0, "gssapi"); + } else { + 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 (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); + } + } + + gss_release_buffer(&min_status, &send_tok); +} + +static void +input_gssapi_errtok(int type, u_int32_t plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + gss_buffer_desc send_tok,recv_tok; + OM_uint32 maj_status; + + if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + fatal("No authentication or GSSAPI context"); + + gssctxt=authctxt->methoddata; + recv_tok.value=packet_get_string(&recv_tok.length); + + /* Push the error token into GSSAPI to see what it says */ + maj_status=PRIVSEP(ssh_gssapi_accept_ctx(gssctxt, &recv_tok, + &send_tok, NULL)); + packet_check_eom(); + + /* We can't return anything to the client, even if we wanted to */ + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,NULL); + + /* The client will have already moved on to the next auth */ + +} + +/* 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. + */ + +static void +input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + int authenticated; + + if (authctxt == NULL || (authctxt->methoddata == NULL && !use_privsep)) + fatal("No authentication or GSSAPI context"); + + gssctxt=authctxt->methoddata; + + /* We don't need to check the status, because the stored credentials + * which userok uses are only populated once the context init step + * has returned complete. + */ + + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); + + authctxt->postponed = 0; + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); + userauth_finish(authctxt, authenticated, "gssapi"); +} + +static void ssh_gssapi_userauth_error(Gssctxt *ctxt) { + char *errstr; + OM_uint32 maj,min; + + errstr=PRIVSEP(ssh_gssapi_last_error(ctxt,&maj,&min)); + if (errstr) { + packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERROR); + packet_put_int(maj); + packet_put_int(min); + packet_put_cstring(errstr); + packet_put_cstring(""); + packet_send(); + packet_write_wait(); + xfree(errstr); + } +} + +Authmethod method_external = { + "external-keyx", + userauth_external, + &options.gss_authentication +}; + +Authmethod method_gssapi = { + "gssapi", + userauth_gssapi, + &options.gss_authentication +}; + +#endif /* GSSAPI */ diff --git a/openssh/auth2.c b/openssh/auth2.c index 1b21eb2..922073c 100644 --- a/openssh/auth2.c +++ b/openssh/auth2.c @@ -36,6 +36,10 @@ RCSID("$OpenBSD: auth2.c,v 1.96 2003/02/06 21:22:43 markus Exp $"); #include "pathnames.h" #include "monitor_wrap.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* import */ extern ServerOptions options; extern u_char *session_id2; @@ -50,9 +54,15 @@ extern Authmethod method_pubkey; extern Authmethod method_passwd; extern Authmethod method_kbdint; extern Authmethod method_hostbased; +extern Authmethod method_external; +extern Authmethod method_gssapi; Authmethod *authmethods[] = { &method_none, +#ifdef GSSAPI + &method_external, + &method_gssapi, +#endif &method_pubkey, &method_passwd, &method_kbdint, @@ -180,6 +190,12 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt) } /* reset state */ auth2_challenge_stop(authctxt); + +#ifdef GSSAPI + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL); +#endif + authctxt->postponed = 0; /* try to authenticate user */ diff --git a/openssh/compat.c b/openssh/compat.c index 5e1774a..bd37d4c 100644 --- a/openssh/compat.c +++ b/openssh/compat.c @@ -71,15 +71,21 @@ compat_datafellows(const char *version) { "OpenSSH_2.5.0p1*," "OpenSSH_2.5.1p1*", SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX| - SSH_BUG_NOREKEY|SSH_BUG_EXTEOF}, + SSH_BUG_NOREKEY|SSH_BUG_EXTEOF| + SSH_OLD_GSSAPI}, { "OpenSSH_2.5.0*," "OpenSSH_2.5.1*," "OpenSSH_2.5.2*", SSH_OLD_DHGEX|SSH_BUG_NOREKEY| SSH_BUG_EXTEOF}, { "OpenSSH_2.5.3*", SSH_BUG_NOREKEY|SSH_BUG_EXTEOF}, + { "OpenSSH_2.9p*", SSH_BUG_EXTEOF|SSH_OLD_GSSAPI}, { "OpenSSH_2.*," "OpenSSH_3.0*," - "OpenSSH_3.1*", SSH_BUG_EXTEOF}, + "OpenSSH_3.1*", SSH_BUG_EXTEOF|SSH_BUG_GSSAPI_BER}, + { "OpenSSH_3.2*," + "OpenSSH_3.3*," + "OpenSSH_3.4*," + "OpenSSH_3.5*", SSH_BUG_GSSAPI_BER}, { "Sun_SSH_1.0*", SSH_BUG_NOREKEY|SSH_BUG_EXTEOF}, { "OpenSSH*", 0 }, { "*MindTerm*", 0 }, diff --git a/openssh/compat.h b/openssh/compat.h index 881e450..75bc083 100644 --- a/openssh/compat.h +++ b/openssh/compat.h @@ -56,6 +56,8 @@ #define SSH_BUG_K5USER 0x00400000 #define SSH_BUG_PROBE 0x00800000 #define SSH_BUG_FIRSTKEX 0x01000000 +#define SSH_OLD_GSSAPI 0x02000000 +#define SSH_BUG_GSSAPI_BER 0x04000000 void enable_compat13(void); void enable_compat20(void); diff --git a/openssh/configure.ac b/openssh/configure.ac index dfd87c9..5bd6eb9 100644 --- a/openssh/configure.ac +++ b/openssh/configure.ac @@ -741,6 +741,7 @@ AC_ARG_WITH(pam, AC_CHECK_LIB(dl, dlopen, , ) AC_CHECK_LIB(pam, pam_set_item, , AC_MSG_ERROR([*** libpam missing])) AC_CHECK_FUNCS(pam_getenvlist) + AC_CHECK_FUNCS(pam_putenv) disable_shadow=yes PAM_MSG="yes" @@ -751,6 +752,7 @@ AC_ARG_WITH(pam, else LIBPAM="-lpam" fi + AC_SUBST(LIBPAM) fi ] @@ -781,6 +783,47 @@ if test "x$check_for_libcrypt_before" = "x1"; then AC_CHECK_LIB(crypt, crypt) fi +# Start of GSI/Globus 2.0 mods +# Check whether the user wants GSI (Globus 2.0) support +# if we are using GSI, we will also use the +# OPenSSL that is built by Globus. This is called +# -lcrypto_FLAVOR +# and it will be in the GSI path. +# The includes will be in the include/FLAVOR/openssl +# therfore we will not process the -with-ssl parameter. + +gsi_path="no" +AC_ARG_WITH(gsi, + [ --with-gsi=PATH Enable GSI/Globus GSSAPI support], + [ + gsi_path="$withval" + ] +) + +gsi_flavor=gcc32dbg +AC_ARG_WITH(gsi-flavor, + [ --with-gsi-flavor=FLAVOR Globus build flavor ], + [ + gsi_flavor="$withval" + ] +) + +if test "x$gsi_path" != "xno" ; then + # Globus GSSAPI configuration + AC_DEFINE(GSSAPI) + AC_DEFINE(GSI) + AC_DEFINE(HAVE_OPENSSL) + LDFLAGS="$LDFLAGS -L${gsi_path}/lib" + if test ! -z "$need_dash_r" ; then + LDFLAGS="$LDFLAGS -R${gsi_path}/lib" + fi + if test ! -z "$blibpath" ; then + blibpath="$blibpath:${gsi_path}/lib" + fi + LIBS="$LIBS -lcrypto_${gsi_flavor} -lglobus_gss_assist_${gsi_flavor} -lglobus_gssapi_gsi_${gsi_flavor}" + CPPFLAGS="$CPPFLAGS -I${gsi_path}/include -I${gsi_path}/include/${gsi_flavor}" +else + # Search for OpenSSL saved_CPPFLAGS="$CPPFLAGS" saved_LDFLAGS="$LDFLAGS" @@ -915,6 +958,8 @@ if test "x$PAM_MSG" = "xno" -a "x$check_for_libcrypt_later" = "x1"; then AC_CHECK_LIB(crypt, crypt, LIBS="$LIBS -lcrypt") fi +fi +#end of GSI/Globus 2.0 mods ### Configure cryptographic random number support @@ -1836,6 +1881,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..85dbb86 --- /dev/null +++ b/openssh/gss-genr.c @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2001-2003 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 "compat.h" +#include "monitor_wrap.h" + +#include + +#include "ssh-gss.h" + +typedef struct { + char *encoded; + gss_OID oid; +} ssh_gss_kex_mapping; + +static ssh_gss_kex_mapping *gss_enc2oid; + +/* Return a list of the gss-group1-sha1-x mechanisms supported by this + * program. + * + * On the client side, we don't need to worry about whether we 'know' + * about the mechanism or not - we assume that any mechanism that we've been + * linked against is suitable for inclusion. + * + * XXX - We might want to make this configurable in the future, so as to + * XXX - allow the user control over which mechanisms to use. + */ + +char * +ssh_gssapi_client_mechanisms(char *host) { + gss_OID_set supported; + OM_uint32 min_status; + Buffer buf; + int i = 0; + char *mechs; + char *encoded; + int enclen; + char digest[EVP_MAX_MD_SIZE]; + char deroid[2]; + const EVP_MD *evp_md = EVP_md5(); + EVP_MD_CTX md; + int oidpos=0; + + if (datafellows & SSH_OLD_GSSAPI) return NULL; + + gss_indicate_mechs(&min_status,&supported); + if (datafellows & SSH_BUG_GSSAPI_BER) { + gss_enc2oid=xmalloc(sizeof(ssh_gss_kex_mapping) + *((supported->count*2)+1)); + } else { + gss_enc2oid=xmalloc(sizeof(ssh_gss_kex_mapping) + *(supported->count+1)); + } + + buffer_init(&buf); + + + for (i=0;icount;i++) { + + gss_enc2oid[oidpos].encoded=NULL; + + if (supported->elements[i].length<128 && + ssh_gssapi_check_mechanism(&(supported->elements[i]),host)) { + + /* Earlier versions of this code interpreted the + * spec incorrectly with regard to OID encoding. They + * also mis-encoded the krb5 OID. The following + * _temporary_ code interfaces with these broken + * servers */ + + if (datafellows & SSH_BUG_GSSAPI_BER) { + char *bodge=NULL; + gss_OID_desc krb5oid={9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; + gss_OID_desc gsioid={9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"}; + + if (supported->elements[i].length==krb5oid.length && + memcmp(supported->elements[i].elements, + krb5oid.elements, krb5oid.length)==0) { + bodge="Se3H81ismmOC3OE+FwYCiQ=="; + } + + if (supported->elements[i].length==gsioid.length && + memcmp(supported->elements[i].elements, + gsioid.elements, gsioid.length)==0) { + bodge="N3+k7/4wGxHyuP8Yxi4RhA=="; + } + + if (bodge) { + if (oidpos!=0) { + buffer_put_char(&buf,','); + } + + buffer_append(&buf, KEX_GSS_SHA1, sizeof(KEX_GSS_SHA1)-1); + buffer_append(&buf, bodge, strlen(bodge)); + + gss_enc2oid[oidpos].oid=&(supported->elements[i]); + gss_enc2oid[oidpos].encoded=bodge; + + oidpos++; + } + } + + /* Add the required DER encoding octets and MD5 hash */ + deroid[0]=0x06; /* Object Identifier */ + deroid[1]=supported->elements[i].length; + + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md,deroid,2); + EVP_DigestUpdate(&md, + supported->elements[i].elements, + supported->elements[i].length); + EVP_DigestFinal(&md, digest, NULL); + + /* Base64 encode it */ + 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); + + debug("Mechanism encoded as %s",encoded); + + gss_enc2oid[oidpos].oid=&(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) + return(NULL); + else + return(mechs); +} + +gss_OID +ssh_gssapi_client_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; /* Move to the start of the ID string */ + + while (gss_enc2oid[i].encoded!=NULL && + strcmp(name,gss_enc2oid[i].encoded)!=0) { + i++; + } + + if (gss_enc2oid[i].oid!=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) { + + return (ctx!=NULL && ctx->oid != GSS_C_NO_OID && + ctx->oid->length == len && + memcmp(ctx->oid->elements,data,len)==0); +} + +/* 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); +} + +/* All this effort to report an error ... */ + +void +ssh_gssapi_error(Gssctxt *ctxt) { + + debug(ssh_gssapi_last_error(ctxt,NULL,NULL)); +} + +char * +ssh_gssapi_last_error(Gssctxt *ctxt, + OM_uint32 *major_status, OM_uint32 *minor_status) { + OM_uint32 lmin; + gss_buffer_desc msg; + OM_uint32 ctx; + Buffer b; + char *ret; + + buffer_init(&b); + + if (major_status!=NULL) *major_status=ctxt->major; + if (minor_status!=NULL) *minor_status=ctxt->minor; + + ctx = 0; + /* The GSSAPI error */ + do { + gss_display_status(&lmin, ctxt->major, + GSS_C_GSS_CODE, GSS_C_NULL_OID, + &ctx, &msg); + + buffer_append(&b,msg.value,msg.length); + buffer_put_char(&b,'\n'); + + gss_release_buffer(&lmin, &msg); + } while (ctx!=0); + + /* The mechanism specific error */ + do { + gss_display_status(&lmin, ctxt->minor, + GSS_C_MECH_CODE, GSS_C_NULL_OID, + &ctx, &msg); + + buffer_append(&b,msg.value,msg.length); + buffer_put_char(&b,'\n'); + + gss_release_buffer(&lmin, &msg); + } while (ctx!=0); + + buffer_put_char(&b,'\0'); + ret=xmalloc(buffer_len(&b)); + buffer_get(&b,ret,buffer_len(&b)); + buffer_free(&b); + return(ret); +} + +/* 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=xmalloc(sizeof (Gssctxt)); + (*ctx)->major=0; + (*ctx)->minor=0; + (*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; + + /* Return if there's no context */ + if ((*ctx)==NULL) + return; + + 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); + + xfree(*ctx); + *ctx=NULL; +} + +/* 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) +{ + int deleg_flag = 0; + + if (deleg_creds) { + deleg_flag=GSS_C_DELEG_FLAG; + debug("Delegating credentials"); + } + + ctx->major=gss_init_sec_context(&ctx->minor, + 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); + if (GSS_ERROR(ctx->major)) { + ssh_gssapi_error(ctx); + } + return(ctx->major); +} + +/* Create a service name for the given host */ +OM_uint32 +ssh_gssapi_import_name(Gssctxt *ctx, const char *host) { + gss_buffer_desc gssbuf; + + 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 ((ctx->major=gss_import_name(&ctx->minor, + &gssbuf, + GSS_C_NT_HOSTBASED_SERVICE, + &ctx->name))) { + ssh_gssapi_error(ctx); + } + + xfree(gssbuf.value); + return(ctx->major); +} + +/* Acquire credentials for a server running on the current host. + * Requires that the context structure contains a valid OID + */ + +/* Returns a GSSAPI error code */ +OM_uint32 +ssh_gssapi_acquire_cred(Gssctxt *ctx) { + OM_uint32 status; + char lname[MAXHOSTNAMELEN]; + gss_OID_set oidset; + + gss_create_empty_oid_set(&status,&oidset); + gss_add_oid_set_member(&status,ctx->oid,&oidset); + + if (gethostname(lname, MAXHOSTNAMELEN)) { + return(-1); + } + + if (GSS_ERROR(ssh_gssapi_import_name(ctx,lname))) { + return(ctx->major); + } + + if ((ctx->major=gss_acquire_cred(&ctx->minor, + ctx->name, + 0, + oidset, + GSS_C_ACCEPT, + &ctx->creds, + NULL, + NULL))) { + ssh_gssapi_error(ctx); + } + + gss_release_oid_set(&status, &oidset); + return(ctx->major); +} + +OM_uint32 +ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *buffer, gss_buffer_desc *hash) { + + if ((ctx->major=gss_get_mic(&ctx->minor,ctx->context, + GSS_C_QOP_DEFAULT, buffer, hash))) { + ssh_gssapi_error(ctx); + } + + return(ctx->major); +} + +OM_uint32 +ssh_gssapi_server_ctx(Gssctxt **ctx,gss_OID oid) { + if (*ctx) ssh_gssapi_delete_ctx(ctx); + ssh_gssapi_build_ctx(ctx); + ssh_gssapi_set_oid(*ctx,oid); + return(ssh_gssapi_acquire_cred(*ctx)); +} + +int +ssh_gssapi_check_mechanism(gss_OID oid, char *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/openssh/gss-serv-gsi.c b/openssh/gss-serv-gsi.c new file mode 100644 index 0000000..69ef4c4 --- /dev/null +++ b/openssh/gss-serv-gsi.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2001-2003 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 +#ifdef GSI + +#include "auth.h" +#include "auth-pam.h" +#include "xmalloc.h" +#include "log.h" +#include "servconf.h" + +#include "ssh-gss.h" + +#include + +static int ssh_gssapi_gsi_userok(ssh_gssapi_client *client, char *name); +static int ssh_gssapi_gsi_localname(ssh_gssapi_client *client, char **user); +static void ssh_gssapi_gsi_storecreds(ssh_gssapi_client *client); + +ssh_gssapi_mech gssapi_gsi_mech_old = { + "N3+k7/4wGxHyuP8Yxi4RhA==", + "GSI", + {9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"}, + NULL, + &ssh_gssapi_gsi_userok, + &ssh_gssapi_gsi_localname, + &ssh_gssapi_gsi_storecreds +}; + +ssh_gssapi_mech gssapi_gsi_mech = { + "dZuIebMjgUqaxvbF7hDbAw==", + "GSI", + {9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"}, + NULL, + &ssh_gssapi_gsi_userok, + &ssh_gssapi_gsi_localname, + &ssh_gssapi_gsi_storecreds +}; + +/* + * 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. + */ +static int +ssh_gssapi_gsi_userok(ssh_gssapi_client *client, char *name) +{ + int authorized = 0; + +#ifdef GLOBUS_GSI_GSS_ASSIST_MODULE + if (globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != 0) { + return 0; + } +#endif + + /* This returns 0 on success */ + authorized = (globus_gss_assist_userok(client->name.value, + name) == 0); + + log("GSI user %s is%s authorized as target user %s", + (char *) client->name.value, (authorized ? "" : " not"), name); + + return authorized; +} + +/* + * Return the local username associated with the GSI credentials. + */ +int +ssh_gssapi_gsi_localname(ssh_gssapi_client *client, char **user) +{ +#ifdef GLOBUS_GSI_GSS_ASSIST_MODULE + if (globus_module_activate(GLOBUS_GSI_GSS_ASSIST_MODULE) != 0) { + return 0; + } +#endif + return(globus_gss_assist_gridmap(client->name.value, user) == 0); +} + +/* + * Export GSI credentials to disk. + */ +static void +ssh_gssapi_gsi_storecreds(ssh_gssapi_client *client) +{ + OM_uint32 major_status; + OM_uint32 minor_status; + gss_buffer_desc export_cred = GSS_C_EMPTY_BUFFER; + char * p; + + if (!client || !client->creds) { + return; + } + + major_status = gss_export_cred(&minor_status, + client->creds, + GSS_C_NO_OID, + 1, + &export_cred); + if (GSS_ERROR(major_status) && major_status != GSS_S_UNAVAILABLE) { + Gssctxt *ctx; + ssh_gssapi_build_ctx(&ctx); + ctx->major = major_status; + ctx->minor = minor_status; + ssh_gssapi_set_oid(ctx, &gssapi_gsi_mech.oid); + ssh_gssapi_error(ctx); + ssh_gssapi_delete_ctx(&ctx); + return; + } + + p = strchr((char *) export_cred.value, '='); + if (p == NULL) { + log("Failed to parse exported credentials string '%.100s'", + (char *)export_cred.value); + gss_release_buffer(&minor_status, &export_cred); + return; + } + *p++ = '\0'; + if (strcmp((char *)export_cred.value,"X509_USER_DELEG_PROXY") == 0) { + client->store.envvar = strdup("X509_USER_PROXY"); + } else { + client->store.envvar = strdup((char *)export_cred.value); + } + client->store.envval = strdup(p); +#ifdef USE_PAM + do_pam_putenv(client->store.envvar, client->store.envval); +#endif + if (strncmp(p, "FILE:", 5) == 0) { + p += 5; + } + if (access(p, R_OK) == 0) { + client->store.filename = strdup(p); + } + gss_release_buffer(&minor_status, &export_cred); +} + +#endif /* GSI */ +#endif /* GSSAPI */ diff --git a/openssh/gss-serv-krb5.c b/openssh/gss-serv-krb5.c new file mode 100644 index 0000000..d9f0677 --- /dev/null +++ b/openssh/gss-serv-krb5.c @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2001-2003 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 +#ifdef KRB5 + +#include "auth.h" +#include "auth-pam.h" +#include "xmalloc.h" +#include "log.h" +#include "servconf.h" + +#include "ssh-gss.h" + +extern ServerOptions options; + +#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 */ + +static 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 + */ + +static int +ssh_gssapi_krb5_userok(ssh_gssapi_client *client, char *name) { + krb5_principal princ; + int retval; + + if (ssh_gssapi_krb5_init() == 0) + return 0; + + if ((retval=krb5_parse_name(krb_context, 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 *)client->name.value); + } + else + retval = 0; + + krb5_free_principal(krb_context, princ); + return retval; +} + +/* Retrieve the local username associated with a set of Kerberos + * credentials. Hopefully we can use this for the 'empty' username + * logins discussed in the draft */ +static int +ssh_gssapi_krb5_localname(ssh_gssapi_client *client, char **user) { + krb5_principal princ; + int retval; + + if (ssh_gssapi_krb5_init() == 0) + return 0; + + if ((retval=krb5_parse_name(krb_context, client->name.value, + &princ))) { + log("krb5_parse_name(): %.100s", + krb5_get_err_text(krb_context,retval)); + return 0; + } + + /* We've got to return a malloc'd string */ + *user = (char *)xmalloc(256); + if (krb5_aname_to_localname(krb_context, princ, 256, *user)) { + xfree(*user); + *user = NULL; + return(0); + } + + return(1); +} + +/* 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. + */ + +static void +ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) { + 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 (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, 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, + 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, + client->creds, + ccache))) { + log("gss_krb5_copy_ccache() failed"); + krb5_cc_destroy(krb_context,ccache); + return; + } + #endif + + krb5_cc_close(krb_context,ccache); + +#ifdef USE_PAM + do_pam_putenv("KRB5CCNAME",name); +#endif + + client->store.filename=strdup(ccname); + client->store.envvar="KRB5CCNAME"; + client->store.envval=strdup(name); + + return; +} + +/* We've been using a wrongly encoded mechanism ID for yonks */ + +ssh_gssapi_mech gssapi_kerberos_mech_old = { + "Se3H81ismmOC3OE+FwYCiQ==", + "Kerberos", + {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}, + &ssh_gssapi_krb5_init, + &ssh_gssapi_krb5_userok, + &ssh_gssapi_krb5_localname, + &ssh_gssapi_krb5_storecreds +}; + +ssh_gssapi_mech gssapi_kerberos_mech = { + "toWM5Slw5Ew8Mqkay+al2g==", + "Kerberos", + {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}, + NULL, + &ssh_gssapi_krb5_userok, + &ssh_gssapi_krb5_localname, + &ssh_gssapi_krb5_storecreds +}; + +#endif /* KRB5 */ + +#endif /* GSSAPI */ diff --git a/openssh/gss-serv.c b/openssh/gss-serv.c new file mode 100644 index 0000000..b75bfaf --- /dev/null +++ b/openssh/gss-serv.c @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2001-2003 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 "channels.h" +#include "session.h" +#include "dispatch.h" +#include "servconf.h" +#include "compat.h" +#include "monitor_wrap.h" + +#include "ssh-gss.h" + +extern ServerOptions options; +extern u_char *session_id2; +extern int session_id2_len; + +static ssh_gssapi_client gssapi_client = + { {0,NULL}, GSS_C_NO_CREDENTIAL, NULL, {NULL,NULL,NULL}}; + +ssh_gssapi_mech gssapi_null_mech + = {NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; + +#ifdef KRB5 +extern ssh_gssapi_mech gssapi_kerberos_mech; +extern ssh_gssapi_mech gssapi_kerberos_mech_old; +#endif +#ifdef GSI +extern ssh_gssapi_mech gssapi_gsi_mech; +extern ssh_gssapi_mech gssapi_gsi_mech_old; +#endif + +ssh_gssapi_mech* supported_mechs[]= { +#ifdef KRB5 + &gssapi_kerberos_mech, + &gssapi_kerberos_mech_old, /* Support for legacy clients */ +#endif +#ifdef GSI + &gssapi_gsi_mech, + &gssapi_gsi_mech_old, /* Support for legacy clients */ +#endif + &gssapi_null_mech, +}; + +/* 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. + */ + +/* Unpriviledged */ +char * +ssh_gssapi_server_mechanisms() { + gss_OID_set supported; + Gssctxt *ctx = NULL; + OM_uint32 maj_status, min_status; + Buffer buf; + int i = 0; + int first = 0; + int present; + char * mechs; + + if (datafellows & SSH_OLD_GSSAPI) return NULL; + + ssh_gssapi_supported_oids(&supported); + + buffer_init(&buf); + + 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) { + if (!GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, + &supported_mechs[i]->oid)))) { + /* Append gss_group1_sha1_x to our list */ + if (first++!=0) + buffer_put_char(&buf,','); + buffer_append(&buf, KEX_GSS_SHA1, + sizeof(KEX_GSS_SHA1)-1); + buffer_append(&buf, + supported_mechs[i]->enc_name, + strlen(supported_mechs[i]->enc_name)); + debug("GSSAPI mechanism %s (%s%s) supported", + supported_mechs[i]->name, KEX_GSS_SHA1, + supported_mechs[i]->enc_name); + } else { + debug("no credentials for GSSAPI mechanism %s", + supported_mechs[i]->name); + } + } else { + debug("GSSAPI mechanism %s not supported", + supported_mechs[i]->name); + } + ssh_gssapi_delete_ctx(&ctx); + i++; + } + + 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); +} + +/* Unpriviledged */ +void ssh_gssapi_supported_oids(gss_OID_set *oidset) { + int i =0; + OM_uint32 maj_status,min_status; + int present; + gss_OID_set supported; + + gss_create_empty_oid_set(&min_status,oidset); + PRIVSEP(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++; + } +} + +/* Find out which GSS type (out of the list we define in ssh-gss.h) a + * particular connection is using + */ + +/* Priviledged (called ssh_gssapi_accept_ctx -> ssh_gssapi_getclient ->) */ +ssh_gssapi_mech * +ssh_gssapi_get_ctype(Gssctxt *ctxt) { + int i=0; + + while(supported_mechs[i]->name!=NULL) { + if (supported_mechs[i]->oid.length == ctxt->oid->length && + (memcmp(supported_mechs[i]->oid.elements, + ctxt->oid->elements,ctxt->oid->length)==0)) { + return supported_mechs[i]; + } + i++; + } + return NULL; +} + +/* Return the OID that corresponds to the given context name */ + +/* Unpriviledged */ +gss_OID +ssh_gssapi_server_id_kex(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; /* 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 (NULL); + + debug("using GSSAPI mechanism %s (%s%s)", supported_mechs[i]->name, + KEX_GSS_SHA1, supported_mechs[i]->enc_name); + + return &supported_mechs[i]->oid; +} + +/* Wrapper around accept_sec_context + * Requires that the context contains: + * oid + * credentials (from ssh_gssapi_acquire_cred) + */ +/* Priviledged */ +OM_uint32 ssh_gssapi_accept_ctx(Gssctxt *ctx,gss_buffer_desc *recv_tok, + gss_buffer_desc *send_tok, OM_uint32 *flags) +{ + OM_uint32 status; + gss_OID mech; + + ctx->major=gss_accept_sec_context(&ctx->minor, + &ctx->context, + ctx->creds, + recv_tok, + GSS_C_NO_CHANNEL_BINDINGS, + &ctx->client, + &mech, /* read-only pointer */ + send_tok, + flags, + NULL, + &ctx->client_creds); + if (GSS_ERROR(ctx->major)) { + ssh_gssapi_error(ctx); + } + + if (ctx->client_creds) { + debug("Received some client credentials"); + } else { + debug("Got no client credentials"); + } + + /* FIXME: We should check that the me + * the one that we asked for (in ctx->oid) */ + + status=ctx->major; + + /* Now, if we're complete and we have the right flags, then + * we flag the user as also having been authenticated + */ + + if (((flags==NULL) || ((*flags & GSS_C_MUTUAL_FLAG) && + (*flags & GSS_C_INTEG_FLAG))) && + (ctx->major == GSS_S_COMPLETE)) { + if (ssh_gssapi_getclient(ctx,&gssapi_client.mech, + &gssapi_client.name, + &gssapi_client.creds)) + fatal("Couldn't convert client name"); + } + + /* Make sure that the getclient call hasn't stamped on this */ + return(status); +} + +/* Extract the client details from a given context. This can only reliably + * be called once for a context */ + +/* Priviledged (called from accept_secure_ctx) */ +OM_uint32 +ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_mech **type, + gss_buffer_desc *name, gss_cred_id_t *creds) { + + *type=ssh_gssapi_get_ctype(ctx); + if ((ctx->major=gss_display_name(&ctx->minor,ctx->client,name,NULL))) { + ssh_gssapi_error(ctx); + return(ctx->major); + } + + /* 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(ctx->major); +} + +/* As user - called through fatal cleanup hook */ +void +ssh_gssapi_cleanup_creds(void *ignored) +{ + if (gssapi_client.store.filename!=NULL) { + /* Unlink probably isn't sufficient */ + debug("removing gssapi cred file\"%s\"",gssapi_client.store.filename); + unlink(gssapi_client.store.filename); + } +} + +/* As user */ +void +ssh_gssapi_storecreds() +{ + if (gssapi_client.mech && gssapi_client.mech->storecreds) { + (*gssapi_client.mech->storecreds)(&gssapi_client); + if (options.gss_cleanup_creds) { + fatal_add_cleanup(ssh_gssapi_cleanup_creds, NULL); + } + } else { + debug("ssh_gssapi_storecreds: Not a GSSAPI mechanism"); + } +} + +/* This allows GSSAPI methods to do things to the childs environment based + * on the passed authentication process and credentials. + */ +/* As user */ +void +ssh_gssapi_do_child(char ***envp, u_int *envsizep) +{ + + if (gssapi_client.store.envvar!=NULL && + gssapi_client.store.envval!=NULL) { + + debug("Setting %s to %s", gssapi_client.store.envvar, + gssapi_client.store.envval); + child_set_env(envp, envsizep, gssapi_client.store.envvar, + gssapi_client.store.envval); + } +} + +/* Priviledged */ +int +ssh_gssapi_userok(char *user) +{ + if (gssapi_client.name.length==0 || + gssapi_client.name.value==NULL) { + debug("No suitable client data"); + return 0; + } + if (gssapi_client.mech && gssapi_client.mech->userok) { + return((*gssapi_client.mech->userok)(&gssapi_client,user)); + } else { + debug("ssh_gssapi_userok: Unknown GSSAPI mechanism"); + } + return(0); +} + +/* Priviledged */ +int +ssh_gssapi_localname(char **user) +{ + *user = NULL; + if (gssapi_client.name.length==0 || + gssapi_client.name.value==NULL) { + debug("No suitable client data"); + return(0);; + } + if (gssapi_client.mech && gssapi_client.mech->localname) { + return((*gssapi_client.mech->localname)(&gssapi_client,user)); + } else { + debug("Unknown client authentication type"); + } + return(0); +} +#endif diff --git a/openssh/kex.c b/openssh/kex.c index b070ccf..046b87a 100644 --- a/openssh/kex.c +++ b/openssh/kex.c @@ -42,6 +42,10 @@ RCSID("$OpenBSD: kex.c,v 1.55 2003/04/01 10:31:26 markus Exp $"); #include "dispatch.h" #include "monitor.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #define KEX_COOKIE_LEN 16 /* prototype */ @@ -295,6 +299,10 @@ choose_kex(Kex *k, char *client, char *server) k->kex_type = KEX_DH_GRP1_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/openssh/kex.h b/openssh/kex.h index 52d442e..50b8659 100644 --- a/openssh/kex.h +++ b/openssh/kex.h @@ -57,6 +57,7 @@ enum kex_modes { enum kex_exchange { KEX_DH_GRP1_SHA1, KEX_DH_GEX_SHA1, + KEX_GSS_GRP1_SHA1, KEX_MAX }; @@ -95,6 +96,11 @@ struct Newkeys { Mac mac; Comp comp; }; + +struct KexOptions { + int gss_deleg_creds; +}; + struct Kex { u_char *session_id; u_int session_id_len; @@ -110,6 +116,7 @@ struct Kex { int flags; char *client_version_string; char *server_version_string; + struct KexOptions options; int (*verify_host_key)(Key *); Key *(*load_host_key)(int); int (*host_key_index)(Key *); @@ -129,6 +136,10 @@ void kexdh_client(Kex *); 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, diff --git a/openssh/kexgssc.c b/openssh/kexgssc.c new file mode 100644 index 0000000..52b569f --- /dev/null +++ b/openssh/kexgssc.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2001-2003 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; + + /* Initialise our GSSAPI world */ + ssh_gssapi_build_ctx(&ctxt); + if (ssh_gssapi_client_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->options.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(); + packet_write_wait(); + + + /* If we've sent them data, they'd better be polite + * and 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; /* u_int vs. size_t */ + 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; /* u_int vs. size_t */ + + /* Is there a token included? */ + if (packet_get_char()) { + recv_tok.value= + packet_get_string(&strlen); + recv_tok.length=strlen; /*u_int/size_t*/ + /* 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 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); +} + +#endif /* GSSAPI */ diff --git a/openssh/kexgsss.c b/openssh/kexgsss.c new file mode 100644 index 0000000..5ec58bf --- /dev/null +++ b/openssh/kexgsss.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2001-2003 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" + +static void kex_gss_send_error(Gssctxt *ctxt); + +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; + unsigned char *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_server_id_kex(kex->name); + if (oid==NULL) { + fatal("Unknown gssapi mechanism"); + } + + debug2("%s: Acquiring credentials",__func__); + + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt,oid)))) { + kex_gss_send_error(ctxt); + 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; /* int vs. size_t */ + + 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; /* int vs. size_t */ + 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(); + packet_write_wait(); + gss_release_buffer(&min_status, &send_tok); + } + } while (maj_status & GSS_S_CONTINUE_NEEDED); + + if (GSS_ERROR(maj_status)) { + kex_gss_send_error(ctxt); + if (send_tok.length>0) { + packet_start(SSH2_MSG_KEXGSS_CONTINUE); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + } + fatal("accept_ctx 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); + + /* 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)))) { + kex_gss_send_error(ctxt); + 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(); + + /* We used to store the client name and credentials here for later + * use. With privsep, its easier to do this as a by product of the + * call to accept_context, which stores delegated information when + * the context is complete */ + + gss_release_buffer(&min_status, &send_tok); + + /* If we've got a context, delete it. It may be NULL if we've been + * using privsep */ + ssh_gssapi_delete_ctx(&ctxt); + + DH_free(dh); + + kex_derive_keys(kex, hash, shared_secret); + BN_clear_free(shared_secret); + kex_finish(kex); +} + +static void +kex_gss_send_error(Gssctxt *ctxt) { + char *errstr; + OM_uint32 maj,min; + + errstr=PRIVSEP(ssh_gssapi_last_error(ctxt,&maj,&min)); + if (errstr) { + packet_start(SSH2_MSG_KEXGSS_ERROR); + packet_put_int(maj); + packet_put_int(min); + packet_put_cstring(errstr); + packet_put_cstring(""); + packet_send(); + packet_write_wait(); + /* XXX - We should probably log the error locally here */ + xfree(errstr); + } +} +#endif /* GSSAPI */ diff --git a/openssh/key.c b/openssh/key.c index 060b637..2271004 100644 --- a/openssh/key.c +++ b/openssh/key.c @@ -646,6 +646,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 725c7a0..4ad321f 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..1350cdd --- /dev/null +++ b/openssh/makegssname.pl @@ -0,0 +1,50 @@ +#!/usr/bin/perl + +use Convert::ASN1 qw(:tag); +use Digest::MD5 qw(md5); +use MIME::Base64; +use Data::Dumper; + +$oid=shift; +my $asn=Convert::ASN1->new; +$asn->prepare("oid OBJECT IDENTIFIER"); +$encoded=$asn->encode(oid => $oid); +Convert::ASN1::asn_dump($encoded); +print Dumper($asn->decode($encoded)); + +@entries=unpack("C*",$encoded); + +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/monitor.c b/openssh/monitor.c index bce9e68..fe021f8 100644 --- a/openssh/monitor.c +++ b/openssh/monitor.c @@ -59,6 +59,11 @@ RCSID("$OpenBSD: monitor.c,v 1.36 2003/04/01 10:22:21 markus Exp $"); #include "ssh2.h" #include "mpaux.h" +#ifdef GSSAPI +#include "ssh-gss.h" +static Gssctxt *gsscontext = NULL; +#endif + /* Imports */ extern ServerOptions options; extern u_int utmp_len; @@ -127,6 +132,16 @@ int mm_answer_krb4(int, Buffer *); int mm_answer_krb5(int, Buffer *); #endif +#ifdef GSSAPI +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_sign(int, Buffer *); +int mm_answer_gss_error(int, Buffer *); +int mm_answer_gss_indicate_mechs(int, Buffer *); +int mm_answer_gss_localname(int, Buffer *); +#endif + static Authctxt *authctxt; static BIGNUM *ssh1_challenge = NULL; /* used for ssh1 rsa auth */ @@ -171,6 +186,18 @@ struct mon_table mon_dispatch_proto20[] = { #ifdef SKEY {MONITOR_REQ_SKEYQUERY, MON_ISAUTH, mm_answer_skeyquery}, {MONITOR_REQ_SKEYRESPOND, MON_AUTH, mm_answer_skeyrespond}, +#endif +#ifdef GSSAPI + {MONITOR_REQ_GSSSETUP, MON_ISAUTH, mm_answer_gss_setup_ctx}, + {MONITOR_REQ_GSSSTEP, MON_ISAUTH, mm_answer_gss_accept_ctx}, + {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, + {MONITOR_REQ_GSSERR, MON_ISAUTH | MON_ONCE, mm_answer_gss_error}, + {MONITOR_REQ_GSSMECHS, MON_ISAUTH, mm_answer_gss_indicate_mechs}, + {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, +/* Turn this off until we use it */ +#if 0 + {MONITOR_REQ_GSSLOCALNAME, MON_ISAUTH, mm_answer_gss_localname}, +#endif #endif {MONITOR_REQ_KEYALLOWED, MON_ISAUTH, mm_answer_keyallowed}, {MONITOR_REQ_KEYVERIFY, MON_AUTH, mm_answer_keyverify}, @@ -178,6 +205,13 @@ struct mon_table mon_dispatch_proto20[] = { }; 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}, + {MONITOR_REQ_GSSERR, 0, mm_answer_gss_error}, + {MONITOR_REQ_GSSMECHS, 0, mm_answer_gss_indicate_mechs}, +#endif {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, {MONITOR_REQ_SIGN, 0, mm_answer_sign}, {MONITOR_REQ_PTY, 0, mm_answer_pty}, @@ -267,6 +301,12 @@ monitor_child_preauth(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); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSMECHS, 1); +#endif } else { mon_dispatch = mon_dispatch_proto15; @@ -321,6 +361,13 @@ monitor_child_postauth(struct monitor *pmonitor) 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_GSSMECHS,1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP,1); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR,1); +#endif + } else { mon_dispatch = mon_dispatch_postauth15; monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); @@ -1468,6 +1515,9 @@ mm_get_kex(Buffer *m) kex->we_need = buffer_get_int(m); kex->kex[KEX_DH_GRP1_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); @@ -1648,3 +1698,170 @@ monitor_reinit(struct monitor *mon) mon->m_recvfd = pair[0]; mon->m_sendfd = pair[1]; } + +#ifdef GSSAPI + +int +mm_answer_gss_setup_ctx(int socket, Buffer *m) { + gss_OID_desc oid; + OM_uint32 major; + u_int len; + + oid.elements=buffer_get_string(m,&len); + oid.length=len; + + major=ssh_gssapi_server_ctx(&gsscontext,&oid); + + xfree(oid.elements); + + buffer_clear(m); + buffer_put_int(m,major); + + mm_request_send(socket,MONITOR_ANS_GSSSETUP,m); + + /* Now we have a context, enable the step and sign */ + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP,1); + + return(0); +} + +int +mm_answer_gss_accept_ctx(int socket, Buffer *m) { + gss_buffer_desc in,out; + OM_uint32 major,minor; + OM_uint32 flags = 0; /* GSI needs this */ + + in.value = buffer_get_string(m,&in.length); + major=ssh_gssapi_accept_ctx(gsscontext,&in,&out,&flags); + xfree(in.value); + + buffer_clear(m); + buffer_put_int(m, major); + buffer_put_string(m, out.value, out.length); + buffer_put_int(m, flags); + mm_request_send(socket,MONITOR_ANS_GSSSTEP,m); + + gss_release_buffer(&minor, &out); + + /* Complete - now we can do signing */ + if (major==GSS_S_COMPLETE) { + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP,0); + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN,1); + } + return(0); +} + +int +mm_answer_gss_userok(int socket, Buffer *m) { + int authenticated; + + authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); + + buffer_clear(m); + buffer_put_int(m, authenticated); + + debug3("%s: sending result %d", __func__, authenticated); + mm_request_send(socket, MONITOR_ANS_GSSUSEROK, m); + + /* XXX - auth method could also be 'external' */ + auth_method="gssapi"; + + /* 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 - only first time round */ + 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 permissions for getpwnam */ + monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); + + return(0); +} + +int +mm_answer_gss_error(int socket, Buffer *m) { + OM_uint32 major,minor; + char *msg; + + msg=ssh_gssapi_last_error(gsscontext,&major,&minor); + buffer_clear(m); + buffer_put_int(m,major); + buffer_put_int(m,minor); + buffer_put_cstring(m,msg); + + mm_request_send(socket,MONITOR_ANS_GSSERR,m); + + xfree(msg); + + return(0); +} + +int +mm_answer_gss_indicate_mechs(int socket, Buffer *m) { + OM_uint32 major,minor; + gss_OID_set mech_set; + int i; + + major=gss_indicate_mechs(&minor, &mech_set); + + buffer_clear(m); + buffer_put_int(m, major); + buffer_put_int(m, mech_set->count); + for (i=0; i < mech_set->count; i++) { + buffer_put_string(m, mech_set->elements[i].elements, + mech_set->elements[i].length); + } + + gss_release_oid_set(&minor,&mech_set); + + mm_request_send(socket,MONITOR_ANS_GSSMECHS,m); + + return(0); +} + +int +mm_answer_gss_localname(int socket, Buffer *m) { + char *name; + + ssh_gssapi_localname(&name); + + buffer_clear(m); + if (name) { + buffer_put_cstring(m, name); + debug3("%s: sending result %s", __func__, name); + xfree(name); + } else { + buffer_put_cstring(m, ""); + debug3("%s: sending result \"\"", __func__); + } + + mm_request_send(socket, MONITOR_ANS_GSSLOCALNAME, m); + + return(0); +} +#endif /* GSSAPI */ diff --git a/openssh/monitor.h b/openssh/monitor.h index 668ac98..086bab6 100644 --- a/openssh/monitor.h +++ b/openssh/monitor.h @@ -39,6 +39,15 @@ enum monitor_reqtype { MONITOR_REQ_BSDAUTHRESPOND, MONITOR_ANS_BSDAUTHRESPOND, MONITOR_REQ_SKEYQUERY, MONITOR_ANS_SKEYQUERY, MONITOR_REQ_SKEYRESPOND, MONITOR_ANS_SKEYRESPOND, +#ifdef GSSAPI + MONITOR_REQ_GSSSETUP,MONITOR_ANS_GSSSETUP, + MONITOR_REQ_GSSSTEP,MONITOR_ANS_GSSSTEP, + MONITOR_REQ_GSSSIGN,MONITOR_ANS_GSSSIGN, + MONITOR_REQ_GSSUSEROK,MONITOR_ANS_GSSUSEROK, + MONITOR_REQ_GSSMECHS,MONITOR_ANS_GSSMECHS, + MONITOR_REQ_GSSLOCALNAME,MONITOR_ANS_GSSLOCALNAME, + MONITOR_REQ_GSSERR,MONITOR_ANS_GSSERR, +#endif MONITOR_REQ_KEYALLOWED, MONITOR_ANS_KEYALLOWED, MONITOR_REQ_KEYVERIFY, MONITOR_ANS_KEYVERIFY, MONITOR_REQ_KEYEXPORT, diff --git a/openssh/monitor_wrap.c b/openssh/monitor_wrap.c index c971413..0beeb9b 100644 --- a/openssh/monitor_wrap.c +++ b/openssh/monitor_wrap.c @@ -52,6 +52,10 @@ RCSID("$OpenBSD: monitor_wrap.c,v 1.24 2003/04/01 10:22:21 markus Exp $"); #include "channels.h" #include "session.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* Imports */ extern int compat20; extern Newkeys *newkeys[]; @@ -1019,3 +1023,160 @@ mm_auth_krb5(void *ctx, void *argp, char **userp, void *resp) return (success); } #endif +#ifdef GSSAPI +OM_uint32 +mm_ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) { + Buffer m; + OM_uint32 major; + + /* Client doesn't get to see the context */ + *ctx=NULL; + + buffer_init(&m); + buffer_put_string(&m,oid->elements,oid->length); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSETUP, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSETUP, &m); + + major=buffer_get_int(&m); + + buffer_free(&m); + return(major); +} + +OM_uint32 +mm_ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_desc *in, + gss_buffer_desc *out, OM_uint32 *flags) { + + Buffer m; + OM_uint32 major; + + buffer_init(&m); + buffer_put_string(&m, in->value, in->length); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSTEP, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSTEP, &m); + + major=buffer_get_int(&m); + out->value=buffer_get_string(&m,&out->length); + if (flags) *flags=buffer_get_int(&m); + + buffer_free(&m); + + return(major); +} + +int +mm_ssh_gssapi_userok(char *user) { + Buffer m; + int authenticated = 0; + + buffer_init(&m); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUSEROK, + &m); + + authenticated = buffer_get_int(&m); + + buffer_free(&m); + 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); +} + +char * +mm_ssh_gssapi_last_error(Gssctxt *ctx, OM_uint32 *major, OM_uint32 *minor) { + Buffer m; + OM_uint32 maj,min; + char *errstr; + + buffer_init(&m); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSERR, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSERR, &m); + + maj = buffer_get_int(&m); + min = buffer_get_int(&m); + + if (major) *major=maj; + if (minor) *minor=min; + + errstr=buffer_get_string(&m,NULL); + + buffer_free(&m); + + return(errstr); +} + +OM_uint32 +mm_gss_indicate_mechs(OM_uint32 *minor_status, gss_OID_set *mech_set) +{ + Buffer m; + OM_uint32 major,minor; + int count; + gss_OID_desc oid; + u_int length; + + buffer_init(&m); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSMECHS, &m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSMECHS, + &m); + major=buffer_get_int(&m); + count=buffer_get_int(&m); + + gss_create_empty_oid_set(&minor,mech_set); + while(count-->0) { + oid.elements=buffer_get_string(&m,&length); + oid.length=length; + gss_add_oid_set_member(&minor,&oid,mech_set); + } + + buffer_free(&m); + + return(major); +} + +int +mm_ssh_gssapi_localname(char **lname) +{ + Buffer m; + + buffer_init(&m); + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSLOCALNAME, &m); + + debug3("%s: waiting for MONITOR_ANS_GSSLOCALNAME", __func__); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSLOCALNAME, + &m); + + *lname = buffer_get_string(&m, NULL); + + buffer_free(&m); + if (lname[0] == '\0') { + debug3("%s: gssapi identity mapping failed", __func__); + } else { + debug3("%s: gssapi identity mapped to %s", __func__, *lname); + } + + return(0); +} +#endif /* GSSAPI */ diff --git a/openssh/monitor_wrap.h b/openssh/monitor_wrap.h index d960a3d..ffd2f28 100644 --- a/openssh/monitor_wrap.h +++ b/openssh/monitor_wrap.h @@ -59,6 +59,18 @@ BIGNUM *mm_auth_rsa_generate_challenge(Key *); void mm_start_pam(char *); #endif +#ifdef GSSAPI +#include "ssh-gss.h" +OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **ctxt, gss_OID oid); +OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *ctxt, gss_buffer_desc *recv, + gss_buffer_desc *send, OM_uint32 *flags); +OM_uint32 mm_ssh_gssapi_sign(Gssctxt *ctxt, gss_buffer_desc *buffer, + gss_buffer_desc *hash); +int mm_ssh_gssapi_userok(char *user); +char *mm_ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *maj, OM_uint32 *min); + +#endif + void mm_terminate(void); int mm_pty_allocate(int *, int *, char *, int); void mm_session_pty_cleanup2(void *); diff --git a/openssh/readconf.c b/openssh/readconf.c index 1df5ce2..7bf3b08 100644 --- a/openssh/readconf.c +++ b/openssh/readconf.c @@ -97,6 +97,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 @@ -366,6 +381,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; @@ -748,6 +780,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 @@ -825,6 +865,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 78e04fe..aad8c82 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 2510659..f9b8996 100644 --- a/openssh/servconf.c +++ b/openssh/servconf.c @@ -86,6 +86,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; @@ -200,6 +206,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 = 0; @@ -280,6 +296,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 @@ -330,6 +349,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 }, @@ -645,6 +671,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 024987d..1c947da 100644 --- a/openssh/servconf.h +++ b/openssh/servconf.h @@ -73,6 +73,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 c75fea9..e85bc9e 100644 --- a/openssh/session.c +++ b/openssh/session.c @@ -58,6 +58,10 @@ RCSID("$OpenBSD: session.c,v 1.154 2003/03/05 22:33:43 markus Exp $"); #include "session.h" #include "monitor_wrap.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #ifdef HAVE_CYGWIN #include #include @@ -455,6 +459,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); @@ -582,6 +592,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); @@ -840,7 +856,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) { @@ -967,6 +983,13 @@ do_setup_env(Session *s, const char *shell) copy_environment(environ, &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); @@ -2121,4 +2144,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 d3ddfab..7fc3653 100644 --- a/openssh/session.h +++ b/openssh/session.h @@ -68,4 +68,7 @@ Session *session_new(void); Session *session_by_tty(char *); void session_close(Session *); void do_setusercontext(struct passwd *); + +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..2260232 --- /dev/null +++ b/openssh/ssh-gss.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2001-2003 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. + */ + +#ifndef _SSH_GSS_H +#define _SSH_GSS_H + +#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-03 */ +#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 SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 + +#define KEX_GSS_SHA1 "gss-group1-sha1-" + +typedef struct { + char *filename; + char *envvar; + char *envval; + void *data; +} ssh_gssapi_ccache; + +typedef struct { + gss_buffer_desc name; + gss_cred_id_t creds; + struct ssh_gssapi_mech_struct *mech; + ssh_gssapi_ccache store; +} ssh_gssapi_client; + +typedef struct ssh_gssapi_mech_struct { + char *enc_name; + char *name; + gss_OID_desc oid; + int (*dochild) (ssh_gssapi_client *); + int (*userok) (ssh_gssapi_client *, char *); + int (*localname) (ssh_gssapi_client *, char **); + void (*storecreds) (ssh_gssapi_client *); +} ssh_gssapi_mech; + + + +typedef struct { + OM_uint32 major; /* both */ + OM_uint32 minor; /* 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[]; + +char *ssh_gssapi_mechanisms(char *host); +char *ssh_gssapi_client_mechanisms(char *host); +gss_OID ssh_gssapi_client_id_kex(Gssctxt *ctx, char *name); +int ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len); +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); +ssh_gssapi_mech *ssh_gssapi_get_ctype(Gssctxt *ctxt); + +OM_uint32 ssh_gssapi_import_name(Gssctxt *ctx, const 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, + ssh_gssapi_mech **mech, + gss_buffer_desc *name, + gss_cred_id_t *creds); +void ssh_gssapi_error(Gssctxt *ctx); +char *ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *maj, OM_uint32 *min); +void ssh_gssapi_build_ctx(Gssctxt **ctx); +void ssh_gssapi_delete_ctx(Gssctxt **ctx); +OM_uint32 ssh_gssapi_server_ctx(Gssctxt **ctx,gss_OID oid); + +int ssh_gssapi_check_mechanism(gss_OID oid, char *host); + +/* In the server */ +gss_OID ssh_gssapi_server_id_kex(char *name); +int ssh_gssapi_userok(char *name); +int ssh_gssapi_localname(char **name); +void ssh_gssapi_server(Kex *kex, Buffer *client_kexinit, + Buffer *server_kexinit); + +OM_uint32 ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *buffer, + gss_buffer_desc *hash); + +void ssh_gssapi_do_child(char ***envp, u_int *envsizep); +void ssh_gssapi_cleanup_creds(void *ignored); +void ssh_gssapi_storecreds(); +char *ssh_gssapi_server_mechanisms(); +#endif /* GSSAPI */ + +#endif /* _SSH_GSS_H */ diff --git a/openssh/sshconnect2.c b/openssh/sshconnect2.c index 642b34b..4f6933a 100644 --- a/openssh/sshconnect2.c +++ b/openssh/sshconnect2.c @@ -48,6 +48,10 @@ RCSID("$OpenBSD: sshconnect2.c,v 1.114 2003/04/01 10:22:21 markus Exp $"); #include "msg.h" #include "pathnames.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + /* import */ extern char *client_version_string; extern char *server_version_string; @@ -77,10 +81,26 @@ void 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 + /* Add the GSSAPI mechanisms currently supported on this client to + * the key exchange algorithm proposal */ + orig = myproposal[PROPOSAL_KEX_ALGS]; + gss = ssh_gssapi_client_mechanisms(get_canonical_hostname(1)); + 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) { log("No valid ciphers for protocol version 2 given, using defaults."); options.ciphers = NULL; @@ -108,14 +128,29 @@ ssh_kex2(char *host, struct sockaddr *hostaddr) myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = options.hostkeyalgorithms; +#ifdef GSSAPI + /* If we've got GSSAPI algorithms, then we also support the + * 'null' hostkey, as a last resort */ + 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 /* start key exchange */ kex = kex_setup(myproposal); kex->kex[KEX_DH_GRP1_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->options.gss_deleg_creds=options.gss_deleg_creds; +#endif xxx_kex = kex; dispatch_run(DISPATCH_BLOCK, &kex->done, kex); @@ -160,6 +195,8 @@ struct Authctxt { Sensitive *sensitive; /* kbd-interactive */ int info_req_seen; + /* generic */ + void *methoddata; }; struct Authmethod { char *name; /* string to compare against server's list */ @@ -182,6 +219,16 @@ int userauth_passwd(Authctxt *); int userauth_kbdint(Authctxt *); int userauth_hostbased(Authctxt *); +#ifdef GSSAPI +int userauth_external(Authctxt *authctxt); +int userauth_gssapi(Authctxt *authctxt); +void input_gssapi_response(int type, u_int32_t, void *); +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 *); +#endif + void userauth(Authctxt *, char *); static int sign_and_send_pubkey(Authctxt *, Key *, sign_cb_fn *); @@ -192,6 +239,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, @@ -256,6 +313,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.sensitive = sensitive; authctxt.info_req_seen = 0; if (authctxt.method == NULL) @@ -278,6 +336,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 { @@ -327,6 +390,8 @@ input_userauth_success(int type, u_int32_t seq, 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 */ } @@ -428,6 +493,263 @@ input_userauth_pk_ok(int type, u_int32_t seq, void *ctxt) } +#ifdef GSSAPI +int +userauth_gssapi(Authctxt *authctxt) +{ + Gssctxt *gssctxt = NULL; + static gss_OID_set supported = NULL; + static int mech=0; + OM_uint32 min; + int ok=0; + + /* Things work better if we send one mechanism at a time, rather + * than them all at once. This means that if we fail at some point + * in the middle of a negotiation, we can come back and try something + * different. */ + + if (datafellows & SSH_OLD_GSSAPI) return 0; + + /* Before we offer a mechanism, check that we can support it. Don't + * bother trying to get credentials - as the standard fallback will + * deal with that kind of failure. + */ + + if (supported==NULL) gss_indicate_mechs(&min, &supported); + + while (mechcount && !ok) { + if (gssctxt) ssh_gssapi_delete_ctx(&gssctxt); + ssh_gssapi_build_ctx(&gssctxt); + ssh_gssapi_set_oid(gssctxt,&supported->elements[mech]); + + /* The DER encoding below only works for lengths<128, + * so check this here + */ + if (supported->elements[mech].length<128 && + !GSS_ERROR(ssh_gssapi_import_name(gssctxt, + authctxt->host))) { + ok = 1; /* Mechanism works */ + } else { + mech++; + } + } + + if (!ok) 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); + + packet_put_int(1); + + /* The newest gsskeyex draft stipulates that OIDs should + * be DER encoded, so we need to add the object type and + * length information back on */ + if (datafellows & SSH_BUG_GSSAPI_BER) { + packet_put_string(supported->elements[mech].elements, + supported->elements[mech].length); + } else { + packet_put_int((supported->elements[mech].length)+2); + packet_put_char(0x06); + packet_put_char(supported->elements[mech].length); + packet_put_raw(supported->elements[mech].elements, + supported->elements[mech].length); + } + + packet_send(); + packet_write_wait(); + + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,&input_gssapi_response); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,&input_gssapi_token); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR,&input_gssapi_error); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,&input_gssapi_errtok); + + mech++; /* Move along to next candidate */ + + return 1; +} + +void +input_gssapi_response(int type, u_int32_t plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + OM_uint32 status,ms; + int oidlen; + char *oidv; + gss_buffer_desc send_tok; + + if (authctxt == NULL) + fatal("input_gssapi_response: no authentication context"); + gssctxt = authctxt->methoddata; + + /* Setup our OID */ + oidv=packet_get_string(&oidlen); + + if (datafellows & SSH_BUG_GSSAPI_BER) { + if (!ssh_gssapi_check_oid(gssctxt,oidv,oidlen)) { + fatal("Server returned different OID than expected"); + } + ssh_gssapi_set_oid_data(gssctxt,oidv,oidlen); + } else { + if(oidv[0]!=0x06 || oidv[1]!=oidlen-2) { + debug("Badly encoded mechanism OID received"); + clear_auth_state(authctxt); + userauth(authctxt,NULL); + return; + } + if (!ssh_gssapi_check_oid(gssctxt,oidv+2,oidlen-2)) { + fatal("Server returned different OID than expected"); + } + ssh_gssapi_set_oid_data(gssctxt,oidv+2,oidlen-2); + } + + packet_check_eom(); + + status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, + GSS_C_NO_BUFFER, &send_tok, + NULL); + if (GSS_ERROR(status)) { + if (send_tok.length>0) { + packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + } + /* Start again with next method on list */ + debug("Trying to start again"); + clear_auth_state(authctxt); + 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, u_int32_t plen, void *ctxt) +{ + Authctxt *authctxt = ctxt; + Gssctxt *gssctxt; + gss_buffer_desc send_tok,recv_tok; + OM_uint32 status; + u_int slen; + + if (authctxt == NULL) + fatal("input_gssapi_response: no authentication context"); + gssctxt = authctxt->methoddata; + + recv_tok.value=packet_get_string(&slen); + recv_tok.length=slen; /* safe typecast */ + + status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, + &recv_tok, &send_tok, NULL); + + packet_check_eom(); + + if (GSS_ERROR(status)) { + if (send_tok.length>0) { + packet_start(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK); + packet_put_string(send_tok.value,send_tok.length); + packet_send(); + packet_write_wait(); + } + /* Start again with the next method in the list */ + clear_auth_state(authctxt); + 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(); + } +} + +void +input_gssapi_errtok(int type, u_int32_t 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); + + /* Stick it into GSSAPI and see what it says */ + status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds, + &recv_tok, &send_tok, NULL); + + packet_check_eom(); + + /* We can't send a packet to the server */ + + /* The draft says that we should wait for the server to fail + * before starting the next authentication. So, we clear the + * state, but don't do anything else */ + clear_auth_state(authctxt); + return; +} + +void +input_gssapi_error(int type, u_int32_t plen, void *ctxt) +{ + OM_uint32 maj,min; + char *msg; + char *lang; + + maj=packet_get_int(); + min=packet_get_int(); + msg=packet_get_string(NULL); + lang=packet_get_string(NULL); + + packet_check_eom(); + + fprintf(stderr, "Server GSSAPI Error:\n%s\n", msg); + xfree(msg); + xfree(lang); +} + +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) { @@ -438,6 +760,7 @@ userauth_none(Authctxt *authctxt) packet_put_cstring(authctxt->method->name); packet_send(); return 1; + } int @@ -544,7 +867,12 @@ clear_auth_state(Authctxt *authctxt) { /* XXX clear authentication state */ dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, NULL); - +#ifdef GSSAPI + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,NULL); + dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR,NULL); +#endif + if (authctxt->last_key != NULL && authctxt->last_key_hint == -1) { debug3("clear_auth_state: key_free %p", authctxt->last_key); key_free(authctxt->last_key); diff --git a/openssh/sshd.c b/openssh/sshd.c index 0f2b2a3..2e0fee5 100644 --- a/openssh/sshd.c +++ b/openssh/sshd.c @@ -85,6 +85,10 @@ RCSID("$OpenBSD: sshd.c,v 1.263 2003/02/16 17:09:57 markus Exp $"); #include "monitor_wrap.h" #include "monitor_fdpass.h" +#ifdef GSSAPI +#include "ssh-gss.h" +#endif + #ifdef LIBWRAP #include #include @@ -1013,10 +1017,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); @@ -1816,10 +1823,52 @@ 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_server_mechanisms(); + 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->kex[KEX_DH_GRP1_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->client_version_string=client_version_string; kex->server_version_string=server_version_string; diff --git a/openssh/sshd_config.5 b/openssh/sshd_config.5 index 6f38a26..93124e5 100644 --- a/openssh/sshd_config.5 +++ b/openssh/sshd_config.5 @@ -253,6 +253,26 @@ keys are used for version 1 and or .Dq rsa are used for version 2 of the SSH protocol. +.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 IgnoreRhosts Specifies that .Pa .rhosts -- 2.45.1