| From 839231516fe46ae5709658676ffa1d5463940e6b Mon Sep 17 00:00:00 2001 | |
| From: Jaeho Shin <netj@sparcs.org> | |
| Date: Tue, 10 Sep 2013 19:14:07 +0900 | |
| Subject: [PATCH] Simon Wilkinson's gsskex patch for 6.2p2 | |
| Revised Simon's last patch for 5.7p1 [1] to apply cleanly to 6.2p2. | |
| [1]: http://www.sxw.org.uk/computing/patches/openssh.html | |
| --- | |
| ChangeLog.gssapi | 113 +++++++++++++++++++ | |
| Makefile.in | 3 +- | |
| auth-krb5.c | 15 ++- | |
| auth2-gss.c | 48 +++++++- | |
| auth2.c | 2 + | |
| clientloop.c | 13 +++ | |
| configure.ac | 24 ++++ | |
| gss-genr.c | 276 ++++++++++++++++++++++++++++++++++++++++++++- | |
| gss-serv-krb5.c | 84 +++++++++++++- | |
| gss-serv.c | 220 +++++++++++++++++++++++++++++++----- | |
| kex.c | 18 +++ | |
| kex.h | 14 +++ | |
| kexgssc.c | 334 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
| kexgsss.c | 288 +++++++++++++++++++++++++++++++++++++++++++++++ | |
| key.c | 4 + | |
| key.h | 1 + | |
| monitor.c | 108 +++++++++++++++++- | |
| monitor.h | 2 + | |
| monitor_wrap.c | 47 +++++++- | |
| monitor_wrap.h | 4 +- | |
| readconf.c | 42 +++++++ | |
| readconf.h | 5 + | |
| servconf.c | 38 ++++++- | |
| servconf.h | 3 + | |
| ssh-gss.h | 39 ++++++- | |
| ssh_config | 2 + | |
| ssh_config.5 | 34 +++++- | |
| sshconnect2.c | 124 ++++++++++++++++++++- | |
| sshd.c | 110 ++++++++++++++++++ | |
| sshd_config | 2 + | |
| sshd_config.5 | 28 +++++ | |
| 31 files changed, 1989 insertions(+), 56 deletions(-) | |
| create mode 100644 ChangeLog.gssapi | |
| create mode 100644 kexgssc.c | |
| create mode 100644 kexgsss.c | |
| diff --git a/ChangeLog.gssapi b/ChangeLog.gssapi | |
| new file mode 100644 | |
| index 0000000..f117a33 | |
| --- /dev/null | |
| +++ b/ChangeLog.gssapi | |
| @@ -0,0 +1,113 @@ | |
| +20110101 | |
| + - Finally update for OpenSSH 5.6p1 | |
| + - Add GSSAPIServerIdentity option from Jim Basney | |
| + | |
| +20100308 | |
| + - [ Makefile.in, key.c, key.h ] | |
| + Updates for OpenSSH 5.4p1 | |
| + - [ servconf.c ] | |
| + Include GSSAPI options in the sshd -T configuration dump, and flag | |
| + some older configuration options as being unsupported. Thanks to Colin | |
| + Watson. | |
| + - | |
| + | |
| +20100124 | |
| + - [ sshconnect2.c ] | |
| + Adapt to deal with additional element in Authmethod structure. Thanks to | |
| + Colin Watson | |
| + | |
| +20090615 | |
| + - [ gss-genr.c gss-serv.c kexgssc.c kexgsss.c monitor.c sshconnect2.c | |
| + sshd.c ] | |
| + Fix issues identified by Greg Hudson following a code review | |
| + Check return value of gss_indicate_mechs | |
| + Protect GSSAPI calls in monitor, so they can only be used if enabled | |
| + Check return values of bignum functions in key exchange | |
| + Use BN_clear_free to clear other side's DH value | |
| + Make ssh_gssapi_id_kex more robust | |
| + Only configure kex table pointers if GSSAPI is enabled | |
| + Don't leak mechanism list, or gss mechanism list | |
| + Cast data.length before printing | |
| + If serverkey isn't provided, use an empty string, rather than NULL | |
| + | |
| +20090201 | |
| + - [ gss-genr.c gss-serv.c kex.h kexgssc.c readconf.c readconf.h ssh-gss.h | |
| + ssh_config.5 sshconnet2.c ] | |
| + Add support for the GSSAPIClientIdentity option, which allows the user | |
| + to specify which GSSAPI identity to use to contact a given server | |
| + | |
| +20080404 | |
| + - [ gss-serv.c ] | |
| + Add code to actually implement GSSAPIStrictAcceptCheck, which had somehow | |
| + been omitted from a previous version of this patch. Reported by Borislav | |
| + Stoichkov | |
| + | |
| +20070317 | |
| + - [ gss-serv-krb5.c ] | |
| + Remove C99ism, where new_ccname was being declared in the middle of a | |
| + function | |
| + | |
| +20061220 | |
| + - [ servconf.c ] | |
| + Make default for GSSAPIStrictAcceptorCheck be Yes, to match previous, and | |
| + documented, behaviour. Reported by Dan Watson. | |
| + | |
| +20060910 | |
| + - [ gss-genr.c kexgssc.c kexgsss.c kex.h monitor.c sshconnect2.c sshd.c | |
| + ssh-gss.h ] | |
| + add support for gss-group14-sha1 key exchange mechanisms | |
| + - [ gss-serv.c servconf.c servconf.h sshd_config sshd_config.5 ] | |
| + Add GSSAPIStrictAcceptorCheck option to allow the disabling of | |
| + acceptor principal checking on multi-homed machines. | |
| + <Bugzilla #928> | |
| + - [ sshd_config ssh_config ] | |
| + Add settings for GSSAPIKeyExchange and GSSAPITrustDNS to the sample | |
| + configuration files | |
| + - [ kexgss.c kegsss.c sshconnect2.c sshd.c ] | |
| + Code cleanup. Replace strlen/xmalloc/snprintf sequences with xasprintf() | |
| + Limit length of error messages displayed by client | |
| + | |
| +20060909 | |
| + - [ gss-genr.c gss-serv.c ] | |
| + move ssh_gssapi_acquire_cred() and ssh_gssapi_server_ctx to be server | |
| + only, where they belong | |
| + <Bugzilla #1225> | |
| + | |
| +20060829 | |
| + - [ gss-serv-krb5.c ] | |
| + Fix CCAPI credentials cache name when creating KRB5CCNAME environment | |
| + variable | |
| + | |
| +20060828 | |
| + - [ gss-genr.c ] | |
| + Avoid Heimdal context freeing problem | |
| + <Fixed upstream 20060829> | |
| + | |
| +20060818 | |
| + - [ gss-genr.c ssh-gss.h sshconnect2.c ] | |
| + Make sure that SPENGO is disabled | |
| + <Bugzilla #1218 - Fixed upstream 20060818> | |
| + | |
| +20060421 | |
| + - [ gssgenr.c, sshconnect2.c ] | |
| + a few type changes (signed versus unsigned, int versus size_t) to | |
| + fix compiler errors/warnings | |
| + (from jbasney AT ncsa.uiuc.edu) | |
| + - [ kexgssc.c, sshconnect2.c ] | |
| + fix uninitialized variable warnings | |
| + (from jbasney AT ncsa.uiuc.edu) | |
| + - [ gssgenr.c ] | |
| + pass oid to gss_display_status (helpful when using GSSAPI mechglue) | |
| + (from jbasney AT ncsa.uiuc.edu) | |
| + <Bugzilla #1220 > | |
| + - [ gss-serv-krb5.c ] | |
| + #ifdef HAVE_GSSAPI_KRB5 should be #ifdef HAVE_GSSAPI_KRB5_H | |
| + (from jbasney AT ncsa.uiuc.edu) | |
| + <Fixed upstream 20060304> | |
| + - [ readconf.c, readconf.h, ssh_config.5, sshconnect2.c | |
| + add client-side GssapiKeyExchange option | |
| + (from jbasney AT ncsa.uiuc.edu) | |
| + - [ sshconnect2.c ] | |
| + add support for GssapiTrustDns option for gssapi-with-mic | |
| + (from jbasney AT ncsa.uiuc.edu) | |
| + <gssapi-with-mic support is Bugzilla #1008> | |
| diff --git a/Makefile.in b/Makefile.in | |
| index d327787..dee44a7 100644 | |
| --- a/Makefile.in | |
| +++ b/Makefile.in | |
| @@ -72,6 +72,7 @@ LIBSSH_OBJS=authfd.o authfile.o bufaux.o bufbn.o buffer.o \ | |
| atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \ | |
| monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \ | |
| kexdh.o kexgex.o kexdhc.o kexgexc.o bufec.o kexecdh.o kexecdhc.o \ | |
| + kexgssc.o \ | |
| msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ | |
| jpake.o schnorr.o ssh-pkcs11.o krl.o | |
| @@ -88,7 +89,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ | |
| auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-jpake.o \ | |
| monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o kexecdhs.o \ | |
| auth-krb5.o \ | |
| - auth2-gss.o gss-serv.o gss-serv-krb5.o \ | |
| + auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o \ | |
| loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ | |
| sftp-server.o sftp-common.o \ | |
| roaming_common.o roaming_serv.o \ | |
| diff --git a/auth-krb5.c b/auth-krb5.c | |
| index 922c66c..0677477 100644 | |
| --- a/auth-krb5.c | |
| +++ b/auth-krb5.c | |
| @@ -170,8 +170,13 @@ auth_krb5_password(Authctxt *authctxt, const char *password) | |
| len = strlen(authctxt->krb5_ticket_file) + 6; | |
| authctxt->krb5_ccname = xmalloc(len); | |
| +#ifdef USE_CCAPI | |
| + snprintf(authctxt->krb5_ccname, len, "API:%s", | |
| + authctxt->krb5_ticket_file); | |
| +#else | |
| snprintf(authctxt->krb5_ccname, len, "FILE:%s", | |
| authctxt->krb5_ticket_file); | |
| +#endif | |
| #ifdef USE_PAM | |
| if (options.use_pam) | |
| @@ -230,11 +235,18 @@ ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) { | |
| char ccname[40]; | |
| mode_t old_umask; | |
| +#ifdef USE_CCAPI | |
| + char cctemplate[] = "API:krb5cc_%d"; | |
| +#else | |
| + char cctemplate[] = "FILE:/tmp/krb5cc_%d_XXXXXXXXXX"; | |
| +#endif | |
| + | |
| ret = snprintf(ccname, sizeof(ccname), | |
| - "FILE:/tmp/krb5cc_%d_XXXXXXXXXX", geteuid()); | |
| + cctemplate, geteuid()); | |
| if (ret < 0 || (size_t)ret >= sizeof(ccname)) | |
| return ENOMEM; | |
| +#ifndef USE_CCAPI | |
| old_umask = umask(0177); | |
| tmpfd = mkstemp(ccname + strlen("FILE:")); | |
| oerrno = errno; | |
| @@ -251,6 +263,7 @@ ssh_krb5_cc_gen(krb5_context ctx, krb5_ccache *ccache) { | |
| return oerrno; | |
| } | |
| close(tmpfd); | |
| +#endif | |
| return (krb5_cc_resolve(ctx, ccname, ccache)); | |
| } | |
| diff --git a/auth2-gss.c b/auth2-gss.c | |
| index 93d576b..17d4a3a 100644 | |
| --- a/auth2-gss.c | |
| +++ b/auth2-gss.c | |
| @@ -1,7 +1,7 @@ | |
| /* $OpenBSD: auth2-gss.c,v 1.18 2012/12/02 20:34:09 djm Exp $ */ | |
| /* | |
| - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. | |
| + * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| @@ -52,6 +52,40 @@ static void input_gssapi_mic(int type, u_int32_t plen, void *ctxt); | |
| static void input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt); | |
| static void input_gssapi_errtok(int, u_int32_t, void *); | |
| +/* | |
| + * The 'gssapi_keyex' userauth mechanism. | |
| + */ | |
| +static int | |
| +userauth_gsskeyex(Authctxt *authctxt) | |
| +{ | |
| + int authenticated = 0; | |
| + Buffer b; | |
| + gss_buffer_desc mic, gssbuf; | |
| + u_int len; | |
| + | |
| + mic.value = packet_get_string(&len); | |
| + mic.length = len; | |
| + | |
| + packet_check_eom(); | |
| + | |
| + ssh_gssapi_buildmic(&b, authctxt->user, authctxt->service, | |
| + "gssapi-keyex"); | |
| + | |
| + gssbuf.value = buffer_ptr(&b); | |
| + gssbuf.length = buffer_len(&b); | |
| + | |
| + /* gss_kex_context is NULL with privsep, so we can't check it here */ | |
| + if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gss_kex_context, | |
| + &gssbuf, &mic)))) | |
| + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, | |
| + authctxt->pw)); | |
| + | |
| + buffer_free(&b); | |
| + xfree(mic.value); | |
| + | |
| + return (authenticated); | |
| +} | |
| + | |
| /* | |
| * We only support those mechanisms that we know about (ie ones that we know | |
| * how to check local user kuserok and the like) | |
| @@ -244,7 +278,8 @@ input_gssapi_exchange_complete(int type, u_int32_t plen, void *ctxt) | |
| packet_check_eom(); | |
| - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); | |
| + authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user, | |
| + authctxt->pw)); | |
| authctxt->postponed = 0; | |
| dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL); | |
| @@ -279,7 +314,8 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) | |
| gssbuf.length = buffer_len(&b); | |
| if (!GSS_ERROR(PRIVSEP(ssh_gssapi_checkmic(gssctxt, &gssbuf, &mic)))) | |
| - authenticated = PRIVSEP(ssh_gssapi_userok(authctxt->user)); | |
| + authenticated = | |
| + PRIVSEP(ssh_gssapi_userok(authctxt->user, authctxt->pw)); | |
| else | |
| logit("GSSAPI MIC check failed"); | |
| @@ -294,6 +330,12 @@ input_gssapi_mic(int type, u_int32_t plen, void *ctxt) | |
| userauth_finish(authctxt, authenticated, "gssapi-with-mic", NULL); | |
| } | |
| +Authmethod method_gsskeyex = { | |
| + "gssapi-keyex", | |
| + userauth_gsskeyex, | |
| + &options.gss_authentication | |
| +}; | |
| + | |
| Authmethod method_gssapi = { | |
| "gssapi-with-mic", | |
| userauth_gssapi, | |
| diff --git a/auth2.c b/auth2.c | |
| index e367a10..d259400 100644 | |
| --- a/auth2.c | |
| +++ b/auth2.c | |
| @@ -69,6 +69,7 @@ extern Authmethod method_passwd; | |
| extern Authmethod method_kbdint; | |
| extern Authmethod method_hostbased; | |
| #ifdef GSSAPI | |
| +extern Authmethod method_gsskeyex; | |
| extern Authmethod method_gssapi; | |
| #endif | |
| #ifdef JPAKE | |
| @@ -79,6 +80,7 @@ Authmethod *authmethods[] = { | |
| &method_none, | |
| &method_pubkey, | |
| #ifdef GSSAPI | |
| + &method_gsskeyex, | |
| &method_gssapi, | |
| #endif | |
| #ifdef JPAKE | |
| diff --git a/clientloop.c b/clientloop.c | |
| index c1d1d44..2ef816a 100644 | |
| --- a/clientloop.c | |
| +++ b/clientloop.c | |
| @@ -111,6 +111,10 @@ | |
| #include "msg.h" | |
| #include "roaming.h" | |
| +#ifdef GSSAPI | |
| +#include "ssh-gss.h" | |
| +#endif | |
| + | |
| /* import options */ | |
| extern Options options; | |
| @@ -1599,6 +1603,15 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) | |
| /* Do channel operations unless rekeying in progress. */ | |
| if (!rekeying) { | |
| channel_after_select(readset, writeset); | |
| + | |
| +#ifdef GSSAPI | |
| + if (options.gss_renewal_rekey && | |
| + ssh_gssapi_credentials_updated(GSS_C_NO_CONTEXT)) { | |
| + debug("credentials updated - forcing rekey"); | |
| + need_rekeying = 1; | |
| + } | |
| +#endif | |
| + | |
| if (need_rekeying || packet_need_rekeying()) { | |
| debug("need rekeying"); | |
| xxx_kex->done = 0; | |
| diff --git a/configure.ac b/configure.ac | |
| index 88dd29e..02f8e3f 100644 | |
| --- a/configure.ac | |
| +++ b/configure.ac | |
| @@ -533,6 +533,30 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) | |
| [Use tunnel device compatibility to OpenBSD]) | |
| AC_DEFINE([SSH_TUN_PREPEND_AF], [1], | |
| [Prepend the address family to IP tunnel traffic]) | |
| + AC_MSG_CHECKING(if we have the Security Authorization Session API) | |
| + AC_TRY_COMPILE([#include <Security/AuthSession.h>], | |
| + [SessionCreate(0, 0);], | |
| + [ac_cv_use_security_session_api="yes" | |
| + AC_DEFINE(USE_SECURITY_SESSION_API, 1, | |
| + [platform has the Security Authorization Session API]) | |
| + LIBS="$LIBS -framework Security" | |
| + AC_MSG_RESULT(yes)], | |
| + [ac_cv_use_security_session_api="no" | |
| + AC_MSG_RESULT(no)]) | |
| + AC_MSG_CHECKING(if we have an in-memory credentials cache) | |
| + AC_TRY_COMPILE( | |
| + [#include <Kerberos/Kerberos.h>], | |
| + [cc_context_t c; | |
| + (void) cc_initialize (&c, 0, NULL, NULL);], | |
| + [AC_DEFINE(USE_CCAPI, 1, | |
| + [platform uses an in-memory credentials cache]) | |
| + LIBS="$LIBS -framework Security" | |
| + AC_MSG_RESULT(yes) | |
| + if test "x$ac_cv_use_security_session_api" = "xno"; then | |
| + AC_MSG_ERROR(*** Need a security framework to use the credentials cache API ***) | |
| + fi], | |
| + [AC_MSG_RESULT(no)] | |
| + ) | |
| m4_pattern_allow([AU_IPv]) | |
| AC_CHECK_DECL([AU_IPv4], [], | |
| AC_DEFINE([AU_IPv4], [0], [System only supports IPv4 audit records]) | |
| diff --git a/gss-genr.c b/gss-genr.c | |
| index 842f385..f9b39cf 100644 | |
| --- a/gss-genr.c | |
| +++ b/gss-genr.c | |
| @@ -1,7 +1,7 @@ | |
| /* $OpenBSD: gss-genr.c,v 1.20 2009/06/22 05:39:28 dtucker Exp $ */ | |
| /* | |
| - * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. | |
| + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| @@ -39,12 +39,167 @@ | |
| #include "buffer.h" | |
| #include "log.h" | |
| #include "ssh2.h" | |
| +#include "cipher.h" | |
| +#include "key.h" | |
| +#include "kex.h" | |
| +#include <openssl/evp.h> | |
| #include "ssh-gss.h" | |
| extern u_char *session_id2; | |
| extern u_int session_id2_len; | |
| +typedef struct { | |
| + char *encoded; | |
| + gss_OID oid; | |
| +} ssh_gss_kex_mapping; | |
| + | |
| +/* | |
| + * XXX - It would be nice to find a more elegant way of handling the | |
| + * XXX passing of the key exchange context to the userauth routines | |
| + */ | |
| + | |
| +Gssctxt *gss_kex_context = NULL; | |
| + | |
| +static ssh_gss_kex_mapping *gss_enc2oid = NULL; | |
| + | |
| +int | |
| +ssh_gssapi_oid_table_ok() { | |
| + return (gss_enc2oid != NULL); | |
| +} | |
| + | |
| +/* | |
| + * Return a list of the gss-group1-sha1 mechanisms supported by this program | |
| + * | |
| + * We test mechanisms to ensure that we can use them, to avoid starting | |
| + * a key exchange with a bad mechanism | |
| + */ | |
| + | |
| +char * | |
| +ssh_gssapi_client_mechanisms(const char *host, const char *client) { | |
| + gss_OID_set gss_supported; | |
| + OM_uint32 min_status; | |
| + | |
| + if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) | |
| + return NULL; | |
| + | |
| + return(ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, | |
| + host, client)); | |
| +} | |
| + | |
| +char * | |
| +ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, | |
| + const char *host, const char *client) { | |
| + Buffer buf; | |
| + size_t i; | |
| + int oidpos, enclen; | |
| + char *mechs, *encoded; | |
| + u_char digest[EVP_MAX_MD_SIZE]; | |
| + char deroid[2]; | |
| + const EVP_MD *evp_md = EVP_md5(); | |
| + EVP_MD_CTX md; | |
| + | |
| + if (gss_enc2oid != NULL) { | |
| + for (i = 0; gss_enc2oid[i].encoded != NULL; i++) | |
| + xfree(gss_enc2oid[i].encoded); | |
| + xfree(gss_enc2oid); | |
| + } | |
| + | |
| + gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * | |
| + (gss_supported->count + 1)); | |
| + | |
| + buffer_init(&buf); | |
| + | |
| + oidpos = 0; | |
| + for (i = 0; i < gss_supported->count; i++) { | |
| + if (gss_supported->elements[i].length < 128 && | |
| + (*check)(NULL, &(gss_supported->elements[i]), host, client)) { | |
| + | |
| + deroid[0] = SSH_GSS_OIDTYPE; | |
| + deroid[1] = gss_supported->elements[i].length; | |
| + | |
| + EVP_DigestInit(&md, evp_md); | |
| + EVP_DigestUpdate(&md, deroid, 2); | |
| + EVP_DigestUpdate(&md, | |
| + gss_supported->elements[i].elements, | |
| + gss_supported->elements[i].length); | |
| + EVP_DigestFinal(&md, digest, NULL); | |
| + | |
| + encoded = xmalloc(EVP_MD_size(evp_md) * 2); | |
| + enclen = __b64_ntop(digest, EVP_MD_size(evp_md), | |
| + encoded, EVP_MD_size(evp_md) * 2); | |
| + | |
| + if (oidpos != 0) | |
| + buffer_put_char(&buf, ','); | |
| + | |
| + buffer_append(&buf, KEX_GSS_GEX_SHA1_ID, | |
| + sizeof(KEX_GSS_GEX_SHA1_ID) - 1); | |
| + buffer_append(&buf, encoded, enclen); | |
| + buffer_put_char(&buf, ','); | |
| + buffer_append(&buf, KEX_GSS_GRP1_SHA1_ID, | |
| + sizeof(KEX_GSS_GRP1_SHA1_ID) - 1); | |
| + buffer_append(&buf, encoded, enclen); | |
| + buffer_put_char(&buf, ','); | |
| + buffer_append(&buf, KEX_GSS_GRP14_SHA1_ID, | |
| + sizeof(KEX_GSS_GRP14_SHA1_ID) - 1); | |
| + buffer_append(&buf, encoded, enclen); | |
| + | |
| + gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); | |
| + gss_enc2oid[oidpos].encoded = encoded; | |
| + oidpos++; | |
| + } | |
| + } | |
| + gss_enc2oid[oidpos].oid = NULL; | |
| + gss_enc2oid[oidpos].encoded = NULL; | |
| + | |
| + buffer_put_char(&buf, '\0'); | |
| + | |
| + mechs = xmalloc(buffer_len(&buf)); | |
| + buffer_get(&buf, mechs, buffer_len(&buf)); | |
| + buffer_free(&buf); | |
| + | |
| + if (strlen(mechs) == 0) { | |
| + xfree(mechs); | |
| + mechs = NULL; | |
| + } | |
| + | |
| + return (mechs); | |
| +} | |
| + | |
| +gss_OID | |
| +ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { | |
| + int i = 0; | |
| + | |
| + switch (kex_type) { | |
| + case KEX_GSS_GRP1_SHA1: | |
| + if (strlen(name) < sizeof(KEX_GSS_GRP1_SHA1_ID)) | |
| + return GSS_C_NO_OID; | |
| + name += sizeof(KEX_GSS_GRP1_SHA1_ID) - 1; | |
| + break; | |
| + case KEX_GSS_GRP14_SHA1: | |
| + if (strlen(name) < sizeof(KEX_GSS_GRP14_SHA1_ID)) | |
| + return GSS_C_NO_OID; | |
| + name += sizeof(KEX_GSS_GRP14_SHA1_ID) - 1; | |
| + break; | |
| + case KEX_GSS_GEX_SHA1: | |
| + if (strlen(name) < sizeof(KEX_GSS_GEX_SHA1_ID)) | |
| + return GSS_C_NO_OID; | |
| + name += sizeof(KEX_GSS_GEX_SHA1_ID) - 1; | |
| + break; | |
| + default: | |
| + return GSS_C_NO_OID; | |
| + } | |
| + | |
| + while (gss_enc2oid[i].encoded != NULL && | |
| + strcmp(name, gss_enc2oid[i].encoded) != 0) | |
| + i++; | |
| + | |
| + if (gss_enc2oid[i].oid != NULL && ctx != NULL) | |
| + ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); | |
| + | |
| + return gss_enc2oid[i].oid; | |
| +} | |
| + | |
| /* Check that the OID in a data stream matches that in the context */ | |
| int | |
| ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) | |
| @@ -197,7 +352,7 @@ ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, | |
| } | |
| ctx->major = gss_init_sec_context(&ctx->minor, | |
| - GSS_C_NO_CREDENTIAL, &ctx->context, ctx->name, ctx->oid, | |
| + ctx->client_creds, &ctx->context, ctx->name, ctx->oid, | |
| GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, | |
| 0, NULL, recv_tok, NULL, send_tok, flags, NULL); | |
| @@ -227,8 +382,42 @@ ssh_gssapi_import_name(Gssctxt *ctx, const char *host) | |
| } | |
| OM_uint32 | |
| +ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) | |
| +{ | |
| + gss_buffer_desc gssbuf; | |
| + gss_name_t gssname; | |
| + OM_uint32 status; | |
| + gss_OID_set oidset; | |
| + | |
| + gssbuf.value = (void *) name; | |
| + gssbuf.length = strlen(gssbuf.value); | |
| + | |
| + gss_create_empty_oid_set(&status, &oidset); | |
| + gss_add_oid_set_member(&status, ctx->oid, &oidset); | |
| + | |
| + ctx->major = gss_import_name(&ctx->minor, &gssbuf, | |
| + GSS_C_NT_USER_NAME, &gssname); | |
| + | |
| + if (!ctx->major) | |
| + ctx->major = gss_acquire_cred(&ctx->minor, | |
| + gssname, 0, oidset, GSS_C_INITIATE, | |
| + &ctx->client_creds, NULL, NULL); | |
| + | |
| + gss_release_name(&status, &gssname); | |
| + gss_release_oid_set(&status, &oidset); | |
| + | |
| + if (ctx->major) | |
| + ssh_gssapi_error(ctx); | |
| + | |
| + return(ctx->major); | |
| +} | |
| + | |
| +OM_uint32 | |
| ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) | |
| { | |
| + if (ctx == NULL) | |
| + return -1; | |
| + | |
| if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, | |
| GSS_C_QOP_DEFAULT, buffer, hash))) | |
| ssh_gssapi_error(ctx); | |
| @@ -236,6 +425,19 @@ ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) | |
| return (ctx->major); | |
| } | |
| +/* Priviledged when used by server */ | |
| +OM_uint32 | |
| +ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) | |
| +{ | |
| + if (ctx == NULL) | |
| + return -1; | |
| + | |
| + ctx->major = gss_verify_mic(&ctx->minor, ctx->context, | |
| + gssbuf, gssmic, NULL); | |
| + | |
| + return (ctx->major); | |
| +} | |
| + | |
| void | |
| ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, | |
| const char *context) | |
| @@ -249,11 +451,16 @@ ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, | |
| } | |
| int | |
| -ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |
| +ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, | |
| + const char *client) | |
| { | |
| gss_buffer_desc token = GSS_C_EMPTY_BUFFER; | |
| OM_uint32 major, minor; | |
| gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; | |
| + Gssctxt *intctx = NULL; | |
| + | |
| + if (ctx == NULL) | |
| + ctx = &intctx; | |
| /* RFC 4462 says we MUST NOT do SPNEGO */ | |
| if (oid->length == spnego_oid.length && | |
| @@ -263,6 +470,10 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |
| ssh_gssapi_build_ctx(ctx); | |
| ssh_gssapi_set_oid(*ctx, oid); | |
| major = ssh_gssapi_import_name(*ctx, host); | |
| + | |
| + if (!GSS_ERROR(major) && client) | |
| + major = ssh_gssapi_client_identity(*ctx, client); | |
| + | |
| if (!GSS_ERROR(major)) { | |
| major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, | |
| NULL); | |
| @@ -272,10 +483,67 @@ ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host) | |
| GSS_C_NO_BUFFER); | |
| } | |
| - if (GSS_ERROR(major)) | |
| + if (GSS_ERROR(major) || intctx != NULL) | |
| ssh_gssapi_delete_ctx(ctx); | |
| return (!GSS_ERROR(major)); | |
| } | |
| +int | |
| +ssh_gssapi_credentials_updated(Gssctxt *ctxt) { | |
| + static gss_name_t saved_name = GSS_C_NO_NAME; | |
| + static OM_uint32 saved_lifetime = 0; | |
| + static gss_OID saved_mech = GSS_C_NO_OID; | |
| + static gss_name_t name; | |
| + static OM_uint32 last_call = 0; | |
| + OM_uint32 lifetime, now, major, minor; | |
| + int equal; | |
| + gss_cred_usage_t usage = GSS_C_INITIATE; | |
| + | |
| + now = time(NULL); | |
| + | |
| + if (ctxt) { | |
| + debug("Rekey has happened - updating saved versions"); | |
| + | |
| + if (saved_name != GSS_C_NO_NAME) | |
| + gss_release_name(&minor, &saved_name); | |
| + | |
| + major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, | |
| + &saved_name, &saved_lifetime, NULL, NULL); | |
| + | |
| + if (!GSS_ERROR(major)) { | |
| + saved_mech = ctxt->oid; | |
| + saved_lifetime+= now; | |
| + } else { | |
| + /* Handle the error */ | |
| + } | |
| + return 0; | |
| + } | |
| + | |
| + if (now - last_call < 10) | |
| + return 0; | |
| + | |
| + last_call = now; | |
| + | |
| + if (saved_mech == GSS_C_NO_OID) | |
| + return 0; | |
| + | |
| + major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, | |
| + &name, &lifetime, NULL, NULL); | |
| + if (major == GSS_S_CREDENTIALS_EXPIRED) | |
| + return 0; | |
| + else if (GSS_ERROR(major)) | |
| + return 0; | |
| + | |
| + major = gss_compare_name(&minor, saved_name, name, &equal); | |
| + gss_release_name(&minor, &name); | |
| + if (GSS_ERROR(major)) | |
| + return 0; | |
| + | |
| + if (equal && (saved_lifetime < lifetime + now - 10)) | |
| + return 1; | |
| + | |
| + return 0; | |
| +} | |
| + | |
| #endif /* GSSAPI */ | |
| diff --git a/gss-serv-krb5.c b/gss-serv-krb5.c | |
| index 5a625ac..e7170ee 100644 | |
| --- a/gss-serv-krb5.c | |
| +++ b/gss-serv-krb5.c | |
| @@ -1,7 +1,7 @@ | |
| /* $OpenBSD: gss-serv-krb5.c,v 1.7 2006/08/03 03:34:42 deraadt Exp $ */ | |
| /* | |
| - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. | |
| + * Copyright (c) 2001-2007 Simon Wilkinson. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| @@ -120,6 +120,7 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) | |
| krb5_principal princ; | |
| OM_uint32 maj_status, min_status; | |
| int len; | |
| + const char *new_ccname; | |
| if (client->creds == NULL) { | |
| debug("No credentials stored"); | |
| @@ -168,11 +169,16 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) | |
| return; | |
| } | |
| - client->store.filename = xstrdup(krb5_cc_get_name(krb_context, ccache)); | |
| + new_ccname = krb5_cc_get_name(krb_context, ccache); | |
| + | |
| client->store.envvar = "KRB5CCNAME"; | |
| - len = strlen(client->store.filename) + 6; | |
| - client->store.envval = xmalloc(len); | |
| - snprintf(client->store.envval, len, "FILE:%s", client->store.filename); | |
| +#ifdef USE_CCAPI | |
| + xasprintf(&client->store.envval, "API:%s", new_ccname); | |
| + client->store.filename = NULL; | |
| +#else | |
| + xasprintf(&client->store.envval, "FILE:%s", new_ccname); | |
| + client->store.filename = xstrdup(new_ccname); | |
| +#endif | |
| #ifdef USE_PAM | |
| if (options.use_pam) | |
| @@ -184,6 +190,71 @@ ssh_gssapi_krb5_storecreds(ssh_gssapi_client *client) | |
| return; | |
| } | |
| +int | |
| +ssh_gssapi_krb5_updatecreds(ssh_gssapi_ccache *store, | |
| + ssh_gssapi_client *client) | |
| +{ | |
| + krb5_ccache ccache = NULL; | |
| + krb5_principal principal = NULL; | |
| + char *name = NULL; | |
| + krb5_error_code problem; | |
| + OM_uint32 maj_status, min_status; | |
| + | |
| + if ((problem = krb5_cc_resolve(krb_context, store->envval, &ccache))) { | |
| + logit("krb5_cc_resolve(): %.100s", | |
| + krb5_get_err_text(krb_context, problem)); | |
| + return 0; | |
| + } | |
| + | |
| + /* Find out who the principal in this cache is */ | |
| + if ((problem = krb5_cc_get_principal(krb_context, ccache, | |
| + &principal))) { | |
| + logit("krb5_cc_get_principal(): %.100s", | |
| + krb5_get_err_text(krb_context, problem)); | |
| + krb5_cc_close(krb_context, ccache); | |
| + return 0; | |
| + } | |
| + | |
| + if ((problem = krb5_unparse_name(krb_context, principal, &name))) { | |
| + logit("krb5_unparse_name(): %.100s", | |
| + krb5_get_err_text(krb_context, problem)); | |
| + krb5_free_principal(krb_context, principal); | |
| + krb5_cc_close(krb_context, ccache); | |
| + return 0; | |
| + } | |
| + | |
| + | |
| + if (strcmp(name,client->exportedname.value)!=0) { | |
| + debug("Name in local credentials cache differs. Not storing"); | |
| + krb5_free_principal(krb_context, principal); | |
| + krb5_cc_close(krb_context, ccache); | |
| + krb5_free_unparsed_name(krb_context, name); | |
| + return 0; | |
| + } | |
| + krb5_free_unparsed_name(krb_context, name); | |
| + | |
| + /* Name matches, so lets get on with it! */ | |
| + | |
| + if ((problem = krb5_cc_initialize(krb_context, ccache, principal))) { | |
| + logit("krb5_cc_initialize(): %.100s", | |
| + krb5_get_err_text(krb_context, problem)); | |
| + krb5_free_principal(krb_context, principal); | |
| + krb5_cc_close(krb_context, ccache); | |
| + return 0; | |
| + } | |
| + | |
| + krb5_free_principal(krb_context, principal); | |
| + | |
| + if ((maj_status = gss_krb5_copy_ccache(&min_status, client->creds, | |
| + ccache))) { | |
| + logit("gss_krb5_copy_ccache() failed. Sorry!"); | |
| + krb5_cc_close(krb_context, ccache); | |
| + return 0; | |
| + } | |
| + | |
| + return 1; | |
| +} | |
| + | |
| ssh_gssapi_mech gssapi_kerberos_mech = { | |
| "toWM5Slw5Ew8Mqkay+al2g==", | |
| "Kerberos", | |
| @@ -191,7 +262,8 @@ ssh_gssapi_mech gssapi_kerberos_mech = { | |
| NULL, | |
| &ssh_gssapi_krb5_userok, | |
| NULL, | |
| - &ssh_gssapi_krb5_storecreds | |
| + &ssh_gssapi_krb5_storecreds, | |
| + &ssh_gssapi_krb5_updatecreds | |
| }; | |
| #endif /* KRB5 */ | |
| diff --git a/gss-serv.c b/gss-serv.c | |
| index c719c13..380895e 100644 | |
| --- a/gss-serv.c | |
| +++ b/gss-serv.c | |
| @@ -1,7 +1,7 @@ | |
| /* $OpenBSD: gss-serv.c,v 1.23 2011/08/01 19:18:15 markus Exp $ */ | |
| /* | |
| - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. | |
| + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| @@ -45,15 +45,20 @@ | |
| #include "channels.h" | |
| #include "session.h" | |
| #include "misc.h" | |
| +#include "servconf.h" | |
| +#include "uidswap.h" | |
| #include "ssh-gss.h" | |
| +#include "monitor_wrap.h" | |
| + | |
| +extern ServerOptions options; | |
| static ssh_gssapi_client gssapi_client = | |
| { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, | |
| - GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL}}; | |
| + GSS_C_NO_CREDENTIAL, GSS_C_NO_NAME, NULL, {NULL, NULL, NULL}, 0, 0}; | |
| ssh_gssapi_mech gssapi_null_mech = | |
| - { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL}; | |
| + { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; | |
| #ifdef KRB5 | |
| extern ssh_gssapi_mech gssapi_kerberos_mech; | |
| @@ -81,25 +86,32 @@ ssh_gssapi_acquire_cred(Gssctxt *ctx) | |
| char lname[MAXHOSTNAMELEN]; | |
| gss_OID_set oidset; | |
| - gss_create_empty_oid_set(&status, &oidset); | |
| - gss_add_oid_set_member(&status, ctx->oid, &oidset); | |
| + if (options.gss_strict_acceptor) { | |
| + gss_create_empty_oid_set(&status, &oidset); | |
| + gss_add_oid_set_member(&status, ctx->oid, &oidset); | |
| - if (gethostname(lname, MAXHOSTNAMELEN)) { | |
| - gss_release_oid_set(&status, &oidset); | |
| - return (-1); | |
| - } | |
| + if (gethostname(lname, MAXHOSTNAMELEN)) { | |
| + gss_release_oid_set(&status, &oidset); | |
| + return (-1); | |
| + } | |
| + | |
| + if (GSS_ERROR(ssh_gssapi_import_name(ctx, lname))) { | |
| + gss_release_oid_set(&status, &oidset); | |
| + 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); | |
| - if (GSS_ERROR(ssh_gssapi_import_name(ctx, lname))) { | |
| gss_release_oid_set(&status, &oidset); | |
| return (ctx->major); | |
| + } else { | |
| + ctx->name = GSS_C_NO_NAME; | |
| + ctx->creds = GSS_C_NO_CREDENTIAL; | |
| } | |
| - | |
| - 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); | |
| + return GSS_S_COMPLETE; | |
| } | |
| /* Privileged */ | |
| @@ -114,6 +126,29 @@ ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) | |
| } | |
| /* Unprivileged */ | |
| +char * | |
| +ssh_gssapi_server_mechanisms() { | |
| + gss_OID_set supported; | |
| + | |
| + ssh_gssapi_supported_oids(&supported); | |
| + return (ssh_gssapi_kex_mechs(supported, &ssh_gssapi_server_check_mech, | |
| + NULL, NULL)); | |
| +} | |
| + | |
| +/* Unprivileged */ | |
| +int | |
| +ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, | |
| + const char *dummy) { | |
| + Gssctxt *ctx = NULL; | |
| + int res; | |
| + | |
| + res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid))); | |
| + ssh_gssapi_delete_ctx(&ctx); | |
| + | |
| + return (res); | |
| +} | |
| + | |
| +/* Unprivileged */ | |
| void | |
| ssh_gssapi_supported_oids(gss_OID_set *oidset) | |
| { | |
| @@ -123,7 +158,9 @@ ssh_gssapi_supported_oids(gss_OID_set *oidset) | |
| gss_OID_set supported; | |
| gss_create_empty_oid_set(&min_status, oidset); | |
| - gss_indicate_mechs(&min_status, &supported); | |
| + | |
| + if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) | |
| + return; | |
| while (supported_mechs[i]->name != NULL) { | |
| if (GSS_ERROR(gss_test_oid_set_member(&min_status, | |
| @@ -249,8 +286,48 @@ OM_uint32 | |
| ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) | |
| { | |
| int i = 0; | |
| + int equal = 0; | |
| + gss_name_t new_name = GSS_C_NO_NAME; | |
| + gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; | |
| + | |
| + if (options.gss_store_rekey && client->used && ctx->client_creds) { | |
| + if (client->mech->oid.length != ctx->oid->length || | |
| + (memcmp(client->mech->oid.elements, | |
| + ctx->oid->elements, ctx->oid->length) !=0)) { | |
| + debug("Rekeyed credentials have different mechanism"); | |
| + return GSS_S_COMPLETE; | |
| + } | |
| + | |
| + if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, | |
| + ctx->client_creds, ctx->oid, &new_name, | |
| + NULL, NULL, NULL))) { | |
| + ssh_gssapi_error(ctx); | |
| + return (ctx->major); | |
| + } | |
| + | |
| + ctx->major = gss_compare_name(&ctx->minor, client->name, | |
| + new_name, &equal); | |
| - gss_buffer_desc ename; | |
| + if (GSS_ERROR(ctx->major)) { | |
| + ssh_gssapi_error(ctx); | |
| + return (ctx->major); | |
| + } | |
| + | |
| + if (!equal) { | |
| + debug("Rekeyed credentials have different name"); | |
| + return GSS_S_COMPLETE; | |
| + } | |
| + | |
| + debug("Marking rekeyed credentials for export"); | |
| + | |
| + gss_release_name(&ctx->minor, &client->name); | |
| + gss_release_cred(&ctx->minor, &client->creds); | |
| + client->name = new_name; | |
| + client->creds = ctx->client_creds; | |
| + ctx->client_creds = GSS_C_NO_CREDENTIAL; | |
| + client->updated = 1; | |
| + return GSS_S_COMPLETE; | |
| + } | |
| client->mech = NULL; | |
| @@ -265,6 +342,13 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) | |
| if (client->mech == NULL) | |
| return GSS_S_FAILURE; | |
| + if (ctx->client_creds && | |
| + (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, | |
| + ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { | |
| + ssh_gssapi_error(ctx); | |
| + return (ctx->major); | |
| + } | |
| + | |
| if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, | |
| &client->displayname, NULL))) { | |
| ssh_gssapi_error(ctx); | |
| @@ -282,6 +366,8 @@ ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) | |
| return (ctx->major); | |
| } | |
| + gss_release_buffer(&ctx->minor, &ename); | |
| + | |
| /* We can't copy this structure, so we just move the pointer to it */ | |
| client->creds = ctx->client_creds; | |
| ctx->client_creds = GSS_C_NO_CREDENTIAL; | |
| @@ -329,7 +415,7 @@ ssh_gssapi_do_child(char ***envp, u_int *envsizep) | |
| /* Privileged */ | |
| int | |
| -ssh_gssapi_userok(char *user) | |
| +ssh_gssapi_userok(char *user, struct passwd *pw) | |
| { | |
| OM_uint32 lmin; | |
| @@ -339,9 +425,11 @@ ssh_gssapi_userok(char *user) | |
| return 0; | |
| } | |
| if (gssapi_client.mech && gssapi_client.mech->userok) | |
| - if ((*gssapi_client.mech->userok)(&gssapi_client, user)) | |
| + if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { | |
| + gssapi_client.used = 1; | |
| + gssapi_client.store.owner = pw; | |
| return 1; | |
| - else { | |
| + } else { | |
| /* Destroy delegated credentials if userok fails */ | |
| gss_release_buffer(&lmin, &gssapi_client.displayname); | |
| gss_release_buffer(&lmin, &gssapi_client.exportedname); | |
| @@ -354,14 +442,90 @@ ssh_gssapi_userok(char *user) | |
| return (0); | |
| } | |
| -/* Privileged */ | |
| -OM_uint32 | |
| -ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) | |
| +/* These bits are only used for rekeying. The unpriviledged child is running | |
| + * as the user, the monitor is root. | |
| + * | |
| + * In the child, we want to : | |
| + * *) Ask the monitor to store our credentials into the store we specify | |
| + * *) If it succeeds, maybe do a PAM update | |
| + */ | |
| + | |
| +/* Stuff for PAM */ | |
| + | |
| +#ifdef USE_PAM | |
| +static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, | |
| + struct pam_response **resp, void *data) | |
| { | |
| - ctx->major = gss_verify_mic(&ctx->minor, ctx->context, | |
| - gssbuf, gssmic, NULL); | |
| + return (PAM_CONV_ERR); | |
| +} | |
| +#endif | |
| - return (ctx->major); | |
| +void | |
| +ssh_gssapi_rekey_creds() { | |
| + int ok; | |
| + int ret; | |
| +#ifdef USE_PAM | |
| + pam_handle_t *pamh = NULL; | |
| + struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; | |
| + char *envstr; | |
| +#endif | |
| + | |
| + if (gssapi_client.store.filename == NULL && | |
| + gssapi_client.store.envval == NULL && | |
| + gssapi_client.store.envvar == NULL) | |
| + return; | |
| + | |
| + ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store)); | |
| + | |
| + if (!ok) | |
| + return; | |
| + | |
| + debug("Rekeyed credentials stored successfully"); | |
| + | |
| + /* Actually managing to play with the ssh pam stack from here will | |
| + * be next to impossible. In any case, we may want different options | |
| + * for rekeying. So, use our own :) | |
| + */ | |
| +#ifdef USE_PAM | |
| + if (!use_privsep) { | |
| + debug("Not even going to try and do PAM with privsep disabled"); | |
| + return; | |
| + } | |
| + | |
| + ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, | |
| + &pamconv, &pamh); | |
| + if (ret) | |
| + return; | |
| + | |
| + xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, | |
| + gssapi_client.store.envval); | |
| + | |
| + ret = pam_putenv(pamh, envstr); | |
| + if (!ret) | |
| + pam_setcred(pamh, PAM_REINITIALIZE_CRED); | |
| + pam_end(pamh, PAM_SUCCESS); | |
| +#endif | |
| +} | |
| + | |
| +int | |
| +ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { | |
| + int ok = 0; | |
| + | |
| + /* Check we've got credentials to store */ | |
| + if (!gssapi_client.updated) | |
| + return 0; | |
| + | |
| + gssapi_client.updated = 0; | |
| + | |
| + temporarily_use_uid(gssapi_client.store.owner); | |
| + if (gssapi_client.mech && gssapi_client.mech->updatecreds) | |
| + ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); | |
| + else | |
| + debug("No update function for this mechanism"); | |
| + | |
| + restore_uid(); | |
| + | |
| + return ok; | |
| } | |
| #endif | |
| diff --git a/kex.c b/kex.c | |
| index 57a79dd..f9e7a9c 100644 | |
| --- a/kex.c | |
| +++ b/kex.c | |
| @@ -50,6 +50,10 @@ | |
| #include "monitor.h" | |
| #include "roaming.h" | |
| +#ifdef GSSAPI | |
| +#include "ssh-gss.h" | |
| +#endif | |
| + | |
| #if OPENSSL_VERSION_NUMBER >= 0x00907000L | |
| # if defined(HAVE_EVP_SHA256) | |
| # define evp_ssh_sha256 EVP_sha256 | |
| @@ -369,6 +373,20 @@ choose_kex(Kex *k, char *client, char *server) | |
| k->kex_type = KEX_ECDH_SHA2; | |
| k->evp_md = kex_ecdh_name_to_evpmd(k->name); | |
| #endif | |
| +#ifdef GSSAPI | |
| + } else if (strncmp(k->name, KEX_GSS_GEX_SHA1_ID, | |
| + sizeof(KEX_GSS_GEX_SHA1_ID) - 1) == 0) { | |
| + k->kex_type = KEX_GSS_GEX_SHA1; | |
| + k->evp_md = EVP_sha1(); | |
| + } else if (strncmp(k->name, KEX_GSS_GRP1_SHA1_ID, | |
| + sizeof(KEX_GSS_GRP1_SHA1_ID) - 1) == 0) { | |
| + k->kex_type = KEX_GSS_GRP1_SHA1; | |
| + k->evp_md = EVP_sha1(); | |
| + } else if (strncmp(k->name, KEX_GSS_GRP14_SHA1_ID, | |
| + sizeof(KEX_GSS_GRP14_SHA1_ID) - 1) == 0) { | |
| + k->kex_type = KEX_GSS_GRP14_SHA1; | |
| + k->evp_md = EVP_sha1(); | |
| +#endif | |
| } else | |
| fatal("bad kex alg %s", k->name); | |
| } | |
| diff --git a/kex.h b/kex.h | |
| index 46731fa..8013ab8 100644 | |
| --- a/kex.h | |
| +++ b/kex.h | |
| @@ -73,6 +73,9 @@ enum kex_exchange { | |
| KEX_DH_GEX_SHA1, | |
| KEX_DH_GEX_SHA256, | |
| KEX_ECDH_SHA2, | |
| + KEX_GSS_GRP1_SHA1, | |
| + KEX_GSS_GRP14_SHA1, | |
| + KEX_GSS_GEX_SHA1, | |
| KEX_MAX | |
| }; | |
| @@ -131,6 +134,12 @@ struct Kex { | |
| sig_atomic_t done; | |
| int flags; | |
| const EVP_MD *evp_md; | |
| +#ifdef GSSAPI | |
| + int gss_deleg_creds; | |
| + int gss_trust_dns; | |
| + char *gss_host; | |
| + char *gss_client; | |
| +#endif | |
| char *client_version_string; | |
| char *server_version_string; | |
| int (*verify_host_key)(Key *); | |
| @@ -158,6 +167,11 @@ void kexgex_server(Kex *); | |
| void kexecdh_client(Kex *); | |
| void kexecdh_server(Kex *); | |
| +#ifdef GSSAPI | |
| +void kexgss_client(Kex *); | |
| +void kexgss_server(Kex *); | |
| +#endif | |
| + | |
| void | |
| kex_dh_hash(char *, char *, char *, int, char *, int, u_char *, int, | |
| BIGNUM *, BIGNUM *, BIGNUM *, u_char **, u_int *); | |
| diff --git a/kexgssc.c b/kexgssc.c | |
| new file mode 100644 | |
| index 0000000..39be405 | |
| --- /dev/null | |
| +++ b/kexgssc.c | |
| @@ -0,0 +1,334 @@ | |
| +/* | |
| + * Copyright (c) 2001-2009 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 "includes.h" | |
| + | |
| +#include <openssl/crypto.h> | |
| +#include <openssl/bn.h> | |
| + | |
| +#include <string.h> | |
| + | |
| +#include "xmalloc.h" | |
| +#include "buffer.h" | |
| +#include "ssh2.h" | |
| +#include "key.h" | |
| +#include "cipher.h" | |
| +#include "kex.h" | |
| +#include "log.h" | |
| +#include "packet.h" | |
| +#include "dh.h" | |
| + | |
| +#include "ssh-gss.h" | |
| + | |
| +void | |
| +kexgss_client(Kex *kex) { | |
| + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; | |
| + gss_buffer_desc recv_tok, gssbuf, msg_tok, *token_ptr; | |
| + Gssctxt *ctxt; | |
| + OM_uint32 maj_status, min_status, ret_flags; | |
| + u_int klen, kout, slen = 0, hashlen, strlen; | |
| + DH *dh; | |
| + BIGNUM *dh_server_pub = NULL; | |
| + BIGNUM *shared_secret = NULL; | |
| + BIGNUM *p = NULL; | |
| + BIGNUM *g = NULL; | |
| + u_char *kbuf, *hash; | |
| + u_char *serverhostkey = NULL; | |
| + u_char *empty = ""; | |
| + char *msg; | |
| + char *lang; | |
| + int type = 0; | |
| + int first = 1; | |
| + int nbits = 0, min = DH_GRP_MIN, max = DH_GRP_MAX; | |
| + | |
| + /* Initialise our GSSAPI world */ | |
| + ssh_gssapi_build_ctx(&ctxt); | |
| + if (ssh_gssapi_id_kex(ctxt, kex->name, kex->kex_type) | |
| + == GSS_C_NO_OID) | |
| + fatal("Couldn't identify host exchange"); | |
| + | |
| + if (ssh_gssapi_import_name(ctxt, kex->gss_host)) | |
| + fatal("Couldn't import hostname"); | |
| + | |
| + if (kex->gss_client && | |
| + ssh_gssapi_client_identity(ctxt, kex->gss_client)) | |
| + fatal("Couldn't acquire client credentials"); | |
| + | |
| + switch (kex->kex_type) { | |
| + case KEX_GSS_GRP1_SHA1: | |
| + dh = dh_new_group1(); | |
| + break; | |
| + case KEX_GSS_GRP14_SHA1: | |
| + dh = dh_new_group14(); | |
| + break; | |
| + case KEX_GSS_GEX_SHA1: | |
| + debug("Doing group exchange\n"); | |
| + nbits = dh_estimate(kex->we_need * 8); | |
| + packet_start(SSH2_MSG_KEXGSS_GROUPREQ); | |
| + packet_put_int(min); | |
| + packet_put_int(nbits); | |
| + packet_put_int(max); | |
| + | |
| + packet_send(); | |
| + | |
| + packet_read_expect(SSH2_MSG_KEXGSS_GROUP); | |
| + | |
| + if ((p = BN_new()) == NULL) | |
| + fatal("BN_new() failed"); | |
| + packet_get_bignum2(p); | |
| + if ((g = BN_new()) == NULL) | |
| + fatal("BN_new() failed"); | |
| + packet_get_bignum2(g); | |
| + packet_check_eom(); | |
| + | |
| + if (BN_num_bits(p) < min || BN_num_bits(p) > max) | |
| + fatal("GSSGRP_GEX group out of range: %d !< %d !< %d", | |
| + min, BN_num_bits(p), max); | |
| + | |
| + dh = dh_new_group(g, p); | |
| + break; | |
| + default: | |
| + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); | |
| + } | |
| + | |
| + /* Step 1 - e is dh->pub_key */ | |
| + dh_gen_key(dh, kex->we_need * 8); | |
| + | |
| + /* This is f, we initialise it now to make life easier */ | |
| + dh_server_pub = BN_new(); | |
| + if (dh_server_pub == NULL) | |
| + fatal("dh_server_pub == NULL"); | |
| + | |
| + token_ptr = GSS_C_NO_BUFFER; | |
| + | |
| + do { | |
| + debug("Calling gss_init_sec_context"); | |
| + | |
| + maj_status = ssh_gssapi_init_ctx(ctxt, | |
| + kex->gss_deleg_creds, token_ptr, &send_tok, | |
| + &ret_flags); | |
| + | |
| + if (GSS_ERROR(maj_status)) { | |
| + if (send_tok.length != 0) { | |
| + packet_start(SSH2_MSG_KEXGSS_CONTINUE); | |
| + packet_put_string(send_tok.value, | |
| + send_tok.length); | |
| + } | |
| + fatal("gss_init_context failed"); | |
| + } | |
| + | |
| + /* If we've got an old receive buffer get rid of it */ | |
| + if (token_ptr != GSS_C_NO_BUFFER) | |
| + xfree(recv_tok.value); | |
| + | |
| + 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(); | |
| + gss_release_buffer(&min_status, &send_tok); | |
| + | |
| + /* If we've sent them data, they should reply */ | |
| + do { | |
| + type = packet_read(); | |
| + if (type == SSH2_MSG_KEXGSS_HOSTKEY) { | |
| + debug("Received KEXGSS_HOSTKEY"); | |
| + if (serverhostkey) | |
| + fatal("Server host key received more than once"); | |
| + serverhostkey = | |
| + packet_get_string(&slen); | |
| + } | |
| + } while (type == SSH2_MSG_KEXGSS_HOSTKEY); | |
| + | |
| + switch (type) { | |
| + case SSH2_MSG_KEXGSS_CONTINUE: | |
| + debug("Received GSSAPI_CONTINUE"); | |
| + if (maj_status == GSS_S_COMPLETE) | |
| + fatal("GSSAPI Continue received from server when complete"); | |
| + recv_tok.value = packet_get_string(&strlen); | |
| + recv_tok.length = strlen; | |
| + break; | |
| + case SSH2_MSG_KEXGSS_COMPLETE: | |
| + debug("Received GSSAPI_COMPLETE"); | |
| + packet_get_bignum2(dh_server_pub); | |
| + msg_tok.value = packet_get_string(&strlen); | |
| + msg_tok.length = strlen; | |
| + | |
| + /* Is there a token included? */ | |
| + if (packet_get_char()) { | |
| + recv_tok.value= | |
| + packet_get_string(&strlen); | |
| + recv_tok.length = strlen; | |
| + /* If we're already complete - protocol error */ | |
| + if (maj_status == GSS_S_COMPLETE) | |
| + packet_disconnect("Protocol error: received token when complete"); | |
| + } else { | |
| + /* No token included */ | |
| + if (maj_status != GSS_S_COMPLETE) | |
| + packet_disconnect("Protocol error: did not receive final token"); | |
| + } | |
| + break; | |
| + case SSH2_MSG_KEXGSS_ERROR: | |
| + debug("Received Error"); | |
| + maj_status = packet_get_int(); | |
| + min_status = packet_get_int(); | |
| + msg = packet_get_string(NULL); | |
| + lang = packet_get_string(NULL); | |
| + fatal("GSSAPI Error: \n%.400s",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); | |
| + if (kout < 0) | |
| + fatal("DH_compute_key: failed"); | |
| + | |
| + shared_secret = BN_new(); | |
| + if (shared_secret == NULL) | |
| + fatal("kexgss_client: BN_new failed"); | |
| + | |
| + if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) | |
| + fatal("kexdh_client: BN_bin2bn failed"); | |
| + | |
| + memset(kbuf, 0, klen); | |
| + xfree(kbuf); | |
| + | |
| + switch (kex->kex_type) { | |
| + case KEX_GSS_GRP1_SHA1: | |
| + case KEX_GSS_GRP14_SHA1: | |
| + 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 ? serverhostkey : empty), slen, | |
| + dh->pub_key, /* e */ | |
| + dh_server_pub, /* f */ | |
| + shared_secret, /* K */ | |
| + &hash, &hashlen | |
| + ); | |
| + break; | |
| + case KEX_GSS_GEX_SHA1: | |
| + kexgex_hash( | |
| + kex->evp_md, | |
| + 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 ? serverhostkey : empty), slen, | |
| + min, nbits, max, | |
| + dh->p, dh->g, | |
| + dh->pub_key, | |
| + dh_server_pub, | |
| + shared_secret, | |
| + &hash, &hashlen | |
| + ); | |
| + break; | |
| + default: | |
| + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); | |
| + } | |
| + | |
| + gssbuf.value = hash; | |
| + gssbuf.length = hashlen; | |
| + | |
| + /* Verify that the hash matches the MIC we just got. */ | |
| + if (GSS_ERROR(ssh_gssapi_checkmic(ctxt, &gssbuf, &msg_tok))) | |
| + packet_disconnect("Hash's MIC didn't verify"); | |
| + | |
| + xfree(msg_tok.value); | |
| + | |
| + DH_free(dh); | |
| + if (serverhostkey) | |
| + xfree(serverhostkey); | |
| + BN_clear_free(dh_server_pub); | |
| + | |
| + /* save session id */ | |
| + if (kex->session_id == NULL) { | |
| + kex->session_id_len = hashlen; | |
| + kex->session_id = xmalloc(kex->session_id_len); | |
| + memcpy(kex->session_id, hash, kex->session_id_len); | |
| + } | |
| + | |
| + if (kex->gss_deleg_creds) | |
| + ssh_gssapi_credentials_updated(ctxt); | |
| + | |
| + if (gss_kex_context == NULL) | |
| + gss_kex_context = ctxt; | |
| + else | |
| + ssh_gssapi_delete_ctx(&ctxt); | |
| + | |
| + kex_derive_keys(kex, hash, hashlen, shared_secret); | |
| + BN_clear_free(shared_secret); | |
| + kex_finish(kex); | |
| +} | |
| + | |
| +#endif /* GSSAPI */ | |
| diff --git a/kexgsss.c b/kexgsss.c | |
| new file mode 100644 | |
| index 0000000..0c3eeaa | |
| --- /dev/null | |
| +++ b/kexgsss.c | |
| @@ -0,0 +1,288 @@ | |
| +/* | |
| + * Copyright (c) 2001-2009 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 <string.h> | |
| + | |
| +#include <openssl/crypto.h> | |
| +#include <openssl/bn.h> | |
| + | |
| +#include "xmalloc.h" | |
| +#include "buffer.h" | |
| +#include "ssh2.h" | |
| +#include "key.h" | |
| +#include "cipher.h" | |
| +#include "kex.h" | |
| +#include "log.h" | |
| +#include "packet.h" | |
| +#include "dh.h" | |
| +#include "ssh-gss.h" | |
| +#include "monitor_wrap.h" | |
| +#include "servconf.h" | |
| + | |
| +extern ServerOptions options; | |
| + | |
| +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, recv_tok, msg_tok; | |
| + gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER; | |
| + Gssctxt *ctxt = NULL; | |
| + u_int slen, klen, kout, hashlen; | |
| + u_char *kbuf, *hash; | |
| + DH *dh; | |
| + int min = -1, max = -1, nbits = -1; | |
| + BIGNUM *shared_secret = NULL; | |
| + BIGNUM *dh_client_pub = NULL; | |
| + int type = 0; | |
| + gss_OID oid; | |
| + char *mechs; | |
| + | |
| + /* Initialise GSSAPI */ | |
| + | |
| + /* If we're rekeying, privsep means that some of the private structures | |
| + * in the GSSAPI code are no longer available. This kludges them back | |
| + * into life | |
| + */ | |
| + if (!ssh_gssapi_oid_table_ok()) | |
| + if ((mechs = ssh_gssapi_server_mechanisms())) | |
| + xfree(mechs); | |
| + | |
| + debug2("%s: Identifying %s", __func__, kex->name); | |
| + oid = ssh_gssapi_id_kex(NULL, kex->name, kex->kex_type); | |
| + if (oid == GSS_C_NO_OID) | |
| + fatal("Unknown gssapi mechanism"); | |
| + | |
| + debug2("%s: Acquiring credentials", __func__); | |
| + | |
| + if (GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctxt, oid)))) | |
| + fatal("Unable to acquire credentials for the server"); | |
| + | |
| + switch (kex->kex_type) { | |
| + case KEX_GSS_GRP1_SHA1: | |
| + dh = dh_new_group1(); | |
| + break; | |
| + case KEX_GSS_GRP14_SHA1: | |
| + dh = dh_new_group14(); | |
| + break; | |
| + case KEX_GSS_GEX_SHA1: | |
| + debug("Doing group exchange"); | |
| + packet_read_expect(SSH2_MSG_KEXGSS_GROUPREQ); | |
| + min = packet_get_int(); | |
| + nbits = packet_get_int(); | |
| + max = packet_get_int(); | |
| + min = MAX(DH_GRP_MIN, min); | |
| + max = MIN(DH_GRP_MAX, max); | |
| + packet_check_eom(); | |
| + if (max < min || nbits < min || max < nbits) | |
| + fatal("GSS_GEX, bad parameters: %d !< %d !< %d", | |
| + min, nbits, max); | |
| + dh = PRIVSEP(choose_dh(min, nbits, max)); | |
| + if (dh == NULL) | |
| + packet_disconnect("Protocol error: no matching group found"); | |
| + | |
| + packet_start(SSH2_MSG_KEXGSS_GROUP); | |
| + packet_put_bignum2(dh->p); | |
| + packet_put_bignum2(dh->g); | |
| + packet_send(); | |
| + | |
| + packet_write_wait(); | |
| + break; | |
| + default: | |
| + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); | |
| + } | |
| + | |
| + dh_gen_key(dh, kex->we_need * 8); | |
| + | |
| + 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; | |
| + | |
| + if ((dh_client_pub = BN_new()) == NULL) | |
| + fatal("dh_client_pub == NULL"); | |
| + | |
| + packet_get_bignum2(dh_client_pub); | |
| + | |
| + /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */ | |
| + break; | |
| + case SSH2_MSG_KEXGSS_CONTINUE: | |
| + recv_tok.value = packet_get_string(&slen); | |
| + recv_tok.length = slen; | |
| + break; | |
| + default: | |
| + packet_disconnect( | |
| + "Protocol error: didn't expect packet type %d", | |
| + type); | |
| + } | |
| + | |
| + maj_status = PRIVSEP(ssh_gssapi_accept_ctx(ctxt, &recv_tok, | |
| + &send_tok, &ret_flags)); | |
| + | |
| + xfree(recv_tok.value); | |
| + | |
| + if (maj_status != GSS_S_COMPLETE && send_tok.length == 0) | |
| + fatal("Zero length token output when incomplete"); | |
| + | |
| + if (dh_client_pub == NULL) | |
| + fatal("No client public key"); | |
| + | |
| + if (maj_status & GSS_S_CONTINUE_NEEDED) { | |
| + debug("Sending GSSAPI_CONTINUE"); | |
| + packet_start(SSH2_MSG_KEXGSS_CONTINUE); | |
| + packet_put_string(send_tok.value, send_tok.length); | |
| + packet_send(); | |
| + gss_release_buffer(&min_status, &send_tok); | |
| + } | |
| + } while (maj_status & GSS_S_CONTINUE_NEEDED); | |
| + | |
| + if (GSS_ERROR(maj_status)) { | |
| + if (send_tok.length > 0) { | |
| + packet_start(SSH2_MSG_KEXGSS_CONTINUE); | |
| + packet_put_string(send_tok.value, send_tok.length); | |
| + packet_send(); | |
| + } | |
| + fatal("accept_ctx died"); | |
| + } | |
| + | |
| + if (!(ret_flags & GSS_C_MUTUAL_FLAG)) | |
| + fatal("Mutual Authentication flag wasn't set"); | |
| + | |
| + if (!(ret_flags & GSS_C_INTEG_FLAG)) | |
| + fatal("Integrity flag wasn't set"); | |
| + | |
| + 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); | |
| + if (kout < 0) | |
| + fatal("DH_compute_key: failed"); | |
| + | |
| + shared_secret = BN_new(); | |
| + if (shared_secret == NULL) | |
| + fatal("kexgss_server: BN_new failed"); | |
| + | |
| + if (BN_bin2bn(kbuf, kout, shared_secret) == NULL) | |
| + fatal("kexgss_server: BN_bin2bn failed"); | |
| + | |
| + memset(kbuf, 0, klen); | |
| + xfree(kbuf); | |
| + | |
| + switch (kex->kex_type) { | |
| + case KEX_GSS_GRP1_SHA1: | |
| + case KEX_GSS_GRP14_SHA1: | |
| + 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, | |
| + &hash, &hashlen | |
| + ); | |
| + break; | |
| + case KEX_GSS_GEX_SHA1: | |
| + kexgex_hash( | |
| + kex->evp_md, | |
| + 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, | |
| + min, nbits, max, | |
| + dh->p, dh->g, | |
| + dh_client_pub, | |
| + dh->pub_key, | |
| + shared_secret, | |
| + &hash, &hashlen | |
| + ); | |
| + break; | |
| + default: | |
| + fatal("%s: Unexpected KEX type %d", __func__, kex->kex_type); | |
| + } | |
| + | |
| + BN_clear_free(dh_client_pub); | |
| + | |
| + if (kex->session_id == NULL) { | |
| + kex->session_id_len = hashlen; | |
| + kex->session_id = xmalloc(kex->session_id_len); | |
| + memcpy(kex->session_id, hash, kex->session_id_len); | |
| + } | |
| + | |
| + gssbuf.value = hash; | |
| + gssbuf.length = hashlen; | |
| + | |
| + if (GSS_ERROR(PRIVSEP(ssh_gssapi_sign(ctxt,&gssbuf,&msg_tok)))) | |
| + fatal("Couldn't get MIC"); | |
| + | |
| + packet_start(SSH2_MSG_KEXGSS_COMPLETE); | |
| + packet_put_bignum2(dh->pub_key); | |
| + packet_put_string(msg_tok.value,msg_tok.length); | |
| + | |
| + if (send_tok.length != 0) { | |
| + packet_put_char(1); /* true */ | |
| + packet_put_string(send_tok.value, send_tok.length); | |
| + } else { | |
| + packet_put_char(0); /* false */ | |
| + } | |
| + packet_send(); | |
| + | |
| + gss_release_buffer(&min_status, &send_tok); | |
| + gss_release_buffer(&min_status, &msg_tok); | |
| + | |
| + if (gss_kex_context == NULL) | |
| + gss_kex_context = ctxt; | |
| + else | |
| + ssh_gssapi_delete_ctx(&ctxt); | |
| + | |
| + DH_free(dh); | |
| + | |
| + kex_derive_keys(kex, hash, hashlen, shared_secret); | |
| + BN_clear_free(shared_secret); | |
| + kex_finish(kex); | |
| + | |
| + /* If this was a rekey, then save out any delegated credentials we | |
| + * just exchanged. */ | |
| + if (options.gss_store_rekey) | |
| + ssh_gssapi_rekey_creds(); | |
| +} | |
| +#endif /* GSSAPI */ | |
| diff --git a/key.c b/key.c | |
| index 4cc5c5d..fdfed5c 100644 | |
| --- a/key.c | |
| +++ b/key.c | |
| @@ -976,6 +976,8 @@ key_ssh_name_from_type_nid(int type, int nid) | |
| } | |
| break; | |
| #endif /* OPENSSL_HAS_ECC */ | |
| + case KEY_NULL: | |
| + return "null"; | |
| } | |
| return "ssh-unknown"; | |
| } | |
| @@ -1281,6 +1283,8 @@ key_type_from_name(char *name) | |
| strcmp(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com") == 0) { | |
| return KEY_ECDSA_CERT; | |
| #endif | |
| + } else if (strcmp(name, "null") == 0) { | |
| + return KEY_NULL; | |
| } | |
| debug2("key_type_from_name: unknown key type '%s'", name); | |
| diff --git a/key.h b/key.h | |
| index ebdf456..4beaf20 100644 | |
| --- a/key.h | |
| +++ b/key.h | |
| @@ -44,6 +44,7 @@ enum types { | |
| KEY_ECDSA_CERT, | |
| KEY_RSA_CERT_V00, | |
| KEY_DSA_CERT_V00, | |
| + KEY_NULL, | |
| KEY_UNSPEC | |
| }; | |
| enum fp_type { | |
| diff --git a/monitor.c b/monitor.c | |
| index 8006b83..d7a782f 100644 | |
| --- a/monitor.c | |
| +++ b/monitor.c | |
| @@ -180,6 +180,8 @@ int mm_answer_gss_setup_ctx(int, Buffer *); | |
| int mm_answer_gss_accept_ctx(int, Buffer *); | |
| int mm_answer_gss_userok(int, Buffer *); | |
| int mm_answer_gss_checkmic(int, Buffer *); | |
| +int mm_answer_gss_sign(int, Buffer *); | |
| +int mm_answer_gss_updatecreds(int, Buffer *); | |
| #endif | |
| #ifdef SSH_AUDIT_EVENTS | |
| @@ -252,6 +254,7 @@ struct mon_table mon_dispatch_proto20[] = { | |
| {MONITOR_REQ_GSSSTEP, MON_ISAUTH, mm_answer_gss_accept_ctx}, | |
| {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, | |
| {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic}, | |
| + {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign}, | |
| #endif | |
| #ifdef JPAKE | |
| {MONITOR_REQ_JPAKE_GET_PWDATA, MON_ONCE, mm_answer_jpake_get_pwdata}, | |
| @@ -264,6 +267,12 @@ 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_GSSUPCREDS, 0, mm_answer_gss_updatecreds}, | |
| +#endif | |
| {MONITOR_REQ_MODULI, 0, mm_answer_moduli}, | |
| {MONITOR_REQ_SIGN, 0, mm_answer_sign}, | |
| {MONITOR_REQ_PTY, 0, mm_answer_pty}, | |
| @@ -372,6 +381,10 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor) | |
| /* Permit requests for moduli and signatures */ | |
| monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); | |
| monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); | |
| +#ifdef GSSAPI | |
| + /* and for the GSSAPI key exchange */ | |
| + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); | |
| +#endif | |
| } else { | |
| mon_dispatch = mon_dispatch_proto15; | |
| @@ -487,6 +500,10 @@ monitor_child_postauth(struct monitor *pmonitor) | |
| monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); | |
| monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); | |
| monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); | |
| +#ifdef GSSAPI | |
| + /* and for the GSSAPI key exchange */ | |
| + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1); | |
| +#endif | |
| } else { | |
| mon_dispatch = mon_dispatch_postauth15; | |
| monitor_permit(mon_dispatch, MONITOR_REQ_TERM, 1); | |
| @@ -1836,6 +1853,13 @@ mm_get_kex(Buffer *m) | |
| kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; | |
| kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; | |
| kex->kex[KEX_ECDH_SHA2] = kexecdh_server; | |
| +#ifdef GSSAPI | |
| + if (options.gss_keyex) { | |
| + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; | |
| + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; | |
| + kex->kex[KEX_GSS_GEX_SHA1] = kexgss_server; | |
| + } | |
| +#endif | |
| kex->server = 1; | |
| kex->hostkey_type = buffer_get_int(m); | |
| kex->kex_type = buffer_get_int(m); | |
| @@ -2042,6 +2066,9 @@ mm_answer_gss_setup_ctx(int sock, Buffer *m) | |
| OM_uint32 major; | |
| u_int len; | |
| + if (!options.gss_authentication && !options.gss_keyex) | |
| + fatal("In GSSAPI monitor when GSSAPI is disabled"); | |
| + | |
| goid.elements = buffer_get_string(m, &len); | |
| goid.length = len; | |
| @@ -2069,6 +2096,9 @@ mm_answer_gss_accept_ctx(int sock, Buffer *m) | |
| OM_uint32 flags = 0; /* GSI needs this */ | |
| u_int len; | |
| + if (!options.gss_authentication && !options.gss_keyex) | |
| + fatal("In GSSAPI monitor when GSSAPI is disabled"); | |
| + | |
| in.value = buffer_get_string(m, &len); | |
| in.length = len; | |
| major = ssh_gssapi_accept_ctx(gsscontext, &in, &out, &flags); | |
| @@ -2086,6 +2116,7 @@ mm_answer_gss_accept_ctx(int sock, Buffer *m) | |
| monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 0); | |
| monitor_permit(mon_dispatch, MONITOR_REQ_GSSUSEROK, 1); | |
| monitor_permit(mon_dispatch, MONITOR_REQ_GSSCHECKMIC, 1); | |
| + monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1); | |
| } | |
| return (0); | |
| } | |
| @@ -2097,6 +2128,9 @@ mm_answer_gss_checkmic(int sock, Buffer *m) | |
| OM_uint32 ret; | |
| u_int len; | |
| + if (!options.gss_authentication && !options.gss_keyex) | |
| + fatal("In GSSAPI monitor when GSSAPI is disabled"); | |
| + | |
| gssbuf.value = buffer_get_string(m, &len); | |
| gssbuf.length = len; | |
| mic.value = buffer_get_string(m, &len); | |
| @@ -2123,7 +2157,11 @@ mm_answer_gss_userok(int sock, Buffer *m) | |
| { | |
| int authenticated; | |
| - authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user); | |
| + if (!options.gss_authentication && !options.gss_keyex) | |
| + fatal("In GSSAPI monitor when GSSAPI is disabled"); | |
| + | |
| + authenticated = authctxt->valid && | |
| + ssh_gssapi_userok(authctxt->user, authctxt->pw); | |
| buffer_clear(m); | |
| buffer_put_int(m, authenticated); | |
| @@ -2136,6 +2174,74 @@ mm_answer_gss_userok(int sock, Buffer *m) | |
| /* Monitor loop will terminate if authenticated */ | |
| return (authenticated); | |
| } | |
| + | |
| +int | |
| +mm_answer_gss_sign(int socket, Buffer *m) | |
| +{ | |
| + gss_buffer_desc data; | |
| + gss_buffer_desc hash = GSS_C_EMPTY_BUFFER; | |
| + OM_uint32 major, minor; | |
| + u_int len; | |
| + | |
| + if (!options.gss_authentication && !options.gss_keyex) | |
| + fatal("In GSSAPI monitor when GSSAPI is disabled"); | |
| + | |
| + data.value = buffer_get_string(m, &len); | |
| + data.length = len; | |
| + if (data.length != 20) | |
| + fatal("%s: data length incorrect: %d", __func__, | |
| + (int) data.length); | |
| + | |
| + /* Save the session ID on the first time around */ | |
| + if (session_id2_len == 0) { | |
| + session_id2_len = data.length; | |
| + session_id2 = xmalloc(session_id2_len); | |
| + memcpy(session_id2, data.value, session_id2_len); | |
| + } | |
| + major = ssh_gssapi_sign(gsscontext, &data, &hash); | |
| + | |
| + xfree(data.value); | |
| + | |
| + buffer_clear(m); | |
| + buffer_put_int(m, major); | |
| + buffer_put_string(m, hash.value, hash.length); | |
| + | |
| + mm_request_send(socket, MONITOR_ANS_GSSSIGN, m); | |
| + | |
| + gss_release_buffer(&minor, &hash); | |
| + | |
| + /* Turn on getpwnam permissions */ | |
| + monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1); | |
| + | |
| + /* And credential updating, for when rekeying */ | |
| + monitor_permit(mon_dispatch, MONITOR_REQ_GSSUPCREDS, 1); | |
| + | |
| + return (0); | |
| +} | |
| + | |
| +int | |
| +mm_answer_gss_updatecreds(int socket, Buffer *m) { | |
| + ssh_gssapi_ccache store; | |
| + int ok; | |
| + | |
| + store.filename = buffer_get_string(m, NULL); | |
| + store.envvar = buffer_get_string(m, NULL); | |
| + store.envval = buffer_get_string(m, NULL); | |
| + | |
| + ok = ssh_gssapi_update_creds(&store); | |
| + | |
| + xfree(store.filename); | |
| + xfree(store.envvar); | |
| + xfree(store.envval); | |
| + | |
| + buffer_clear(m); | |
| + buffer_put_int(m, ok); | |
| + | |
| + mm_request_send(socket, MONITOR_ANS_GSSUPCREDS, m); | |
| + | |
| + return(0); | |
| +} | |
| + | |
| #endif /* GSSAPI */ | |
| #ifdef JPAKE | |
| diff --git a/monitor.h b/monitor.h | |
| index 2caa469..c0e94d6 100644 | |
| --- a/monitor.h | |
| +++ b/monitor.h | |
| @@ -61,6 +61,8 @@ enum monitor_reqtype { | |
| MONITOR_REQ_JPAKE_STEP2 = 56, MONITOR_ANS_JPAKE_STEP2 = 57, | |
| MONITOR_REQ_JPAKE_KEY_CONFIRM = 58, MONITOR_ANS_JPAKE_KEY_CONFIRM = 59, | |
| MONITOR_REQ_JPAKE_CHECK_CONFIRM = 60, MONITOR_ANS_JPAKE_CHECK_CONFIRM = 61, | |
| + MONITOR_REQ_GSSSIGN = 62, MONITOR_ANS_GSSSIGN = 63, | |
| + MONITOR_REQ_GSSUPCREDS = 64, MONITOR_ANS_GSSUPCREDS = 65, | |
| MONITOR_REQ_PAM_START = 100, | |
| MONITOR_REQ_PAM_ACCOUNT = 102, MONITOR_ANS_PAM_ACCOUNT = 103, | |
| diff --git a/monitor_wrap.c b/monitor_wrap.c | |
| index ea654a7..ed8dbda 100644 | |
| --- a/monitor_wrap.c | |
| +++ b/monitor_wrap.c | |
| @@ -1271,7 +1271,7 @@ mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) | |
| } | |
| int | |
| -mm_ssh_gssapi_userok(char *user) | |
| +mm_ssh_gssapi_userok(char *user, struct passwd *pw) | |
| { | |
| Buffer m; | |
| int authenticated = 0; | |
| @@ -1288,6 +1288,51 @@ mm_ssh_gssapi_userok(char *user) | |
| debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not "); | |
| return (authenticated); | |
| } | |
| + | |
| +OM_uint32 | |
| +mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) | |
| +{ | |
| + Buffer m; | |
| + OM_uint32 major; | |
| + u_int len; | |
| + | |
| + 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, &len); | |
| + hash->length = len; | |
| + | |
| + buffer_free(&m); | |
| + | |
| + return(major); | |
| +} | |
| + | |
| +int | |
| +mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *store) | |
| +{ | |
| + Buffer m; | |
| + int ok; | |
| + | |
| + buffer_init(&m); | |
| + | |
| + buffer_put_cstring(&m, store->filename ? store->filename : ""); | |
| + buffer_put_cstring(&m, store->envvar ? store->envvar : ""); | |
| + buffer_put_cstring(&m, store->envval ? store->envval : ""); | |
| + | |
| + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUPCREDS, &m); | |
| + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUPCREDS, &m); | |
| + | |
| + ok = buffer_get_int(&m); | |
| + | |
| + buffer_free(&m); | |
| + | |
| + return (ok); | |
| +} | |
| + | |
| #endif /* GSSAPI */ | |
| #ifdef JPAKE | |
| diff --git a/monitor_wrap.h b/monitor_wrap.h | |
| index 0c7f2e3..ec9b9b1 100644 | |
| --- a/monitor_wrap.h | |
| +++ b/monitor_wrap.h | |
| @@ -58,8 +58,10 @@ BIGNUM *mm_auth_rsa_generate_challenge(Key *); | |
| OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **, gss_OID); | |
| OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *, | |
| gss_buffer_desc *, gss_buffer_desc *, OM_uint32 *); | |
| -int mm_ssh_gssapi_userok(char *user); | |
| +int mm_ssh_gssapi_userok(char *user, struct passwd *); | |
| OM_uint32 mm_ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); | |
| +OM_uint32 mm_ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); | |
| +int mm_ssh_gssapi_update_creds(ssh_gssapi_ccache *); | |
| #endif | |
| #ifdef USE_PAM | |
| diff --git a/readconf.c b/readconf.c | |
| index 36265e4..375ca32 100644 | |
| --- a/readconf.c | |
| +++ b/readconf.c | |
| @@ -129,6 +129,8 @@ typedef enum { | |
| oClearAllForwardings, oNoHostAuthenticationForLocalhost, | |
| oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, | |
| oAddressFamily, oGssAuthentication, oGssDelegateCreds, | |
| + oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey, | |
| + oGssServerIdentity, | |
| oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, | |
| oSendEnv, oControlPath, oControlMaster, oControlPersist, | |
| oHashKnownHosts, | |
| @@ -169,10 +171,19 @@ static struct { | |
| { "afstokenpassing", oUnsupported }, | |
| #if defined(GSSAPI) | |
| { "gssapiauthentication", oGssAuthentication }, | |
| + { "gssapikeyexchange", oGssKeyEx }, | |
| { "gssapidelegatecredentials", oGssDelegateCreds }, | |
| + { "gssapitrustdns", oGssTrustDns }, | |
| + { "gssapiclientidentity", oGssClientIdentity }, | |
| + { "gssapiserveridentity", oGssServerIdentity }, | |
| + { "gssapirenewalforcesrekey", oGssRenewalRekey }, | |
| #else | |
| { "gssapiauthentication", oUnsupported }, | |
| + { "gssapikeyexchange", oUnsupported }, | |
| { "gssapidelegatecredentials", oUnsupported }, | |
| + { "gssapitrustdns", oUnsupported }, | |
| + { "gssapiclientidentity", oUnsupported }, | |
| + { "gssapirenewalforcesrekey", oUnsupported }, | |
| #endif | |
| { "fallbacktorsh", oDeprecated }, | |
| { "usersh", oDeprecated }, | |
| @@ -503,10 +514,30 @@ parse_flag: | |
| intptr = &options->gss_authentication; | |
| goto parse_flag; | |
| + case oGssKeyEx: | |
| + intptr = &options->gss_keyex; | |
| + goto parse_flag; | |
| + | |
| case oGssDelegateCreds: | |
| intptr = &options->gss_deleg_creds; | |
| goto parse_flag; | |
| + case oGssTrustDns: | |
| + intptr = &options->gss_trust_dns; | |
| + goto parse_flag; | |
| + | |
| + case oGssClientIdentity: | |
| + charptr = &options->gss_client_identity; | |
| + goto parse_string; | |
| + | |
| + case oGssServerIdentity: | |
| + charptr = &options->gss_server_identity; | |
| + goto parse_string; | |
| + | |
| + case oGssRenewalRekey: | |
| + intptr = &options->gss_renewal_rekey; | |
| + goto parse_flag; | |
| + | |
| case oBatchMode: | |
| intptr = &options->batch_mode; | |
| goto parse_flag; | |
| @@ -1158,7 +1189,12 @@ initialize_options(Options * options) | |
| options->pubkey_authentication = -1; | |
| options->challenge_response_authentication = -1; | |
| options->gss_authentication = -1; | |
| + options->gss_keyex = -1; | |
| options->gss_deleg_creds = -1; | |
| + options->gss_trust_dns = -1; | |
| + options->gss_renewal_rekey = -1; | |
| + options->gss_client_identity = NULL; | |
| + options->gss_server_identity = NULL; | |
| options->password_authentication = -1; | |
| options->kbd_interactive_authentication = -1; | |
| options->kbd_interactive_devices = NULL; | |
| @@ -1258,8 +1294,14 @@ fill_default_options(Options * options) | |
| options->challenge_response_authentication = 1; | |
| if (options->gss_authentication == -1) | |
| options->gss_authentication = 0; | |
| + if (options->gss_keyex == -1) | |
| + options->gss_keyex = 0; | |
| if (options->gss_deleg_creds == -1) | |
| options->gss_deleg_creds = 0; | |
| + if (options->gss_trust_dns == -1) | |
| + options->gss_trust_dns = 0; | |
| + if (options->gss_renewal_rekey == -1) | |
| + options->gss_renewal_rekey = 0; | |
| if (options->password_authentication == -1) | |
| options->password_authentication = 1; | |
| if (options->kbd_interactive_authentication == -1) | |
| diff --git a/readconf.h b/readconf.h | |
| index 8416489..0835cb6 100644 | |
| --- a/readconf.h | |
| +++ b/readconf.h | |
| @@ -48,7 +48,12 @@ typedef struct { | |
| int challenge_response_authentication; | |
| /* Try S/Key or TIS, authentication. */ | |
| int gss_authentication; /* Try GSS authentication */ | |
| + int gss_keyex; /* Try GSS key exchange */ | |
| int gss_deleg_creds; /* Delegate GSS credentials */ | |
| + int gss_trust_dns; /* Trust DNS for GSS canonicalization */ | |
| + int gss_renewal_rekey; /* Credential renewal forces rekey */ | |
| + char *gss_client_identity; /* Principal to initiate GSSAPI with */ | |
| + char *gss_server_identity; /* GSSAPI target principal */ | |
| int password_authentication; /* Try password | |
| * authentication. */ | |
| int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ | |
| diff --git a/servconf.c b/servconf.c | |
| index b2a60fd..cdc0293 100644 | |
| --- a/servconf.c | |
| +++ b/servconf.c | |
| @@ -102,7 +102,10 @@ initialize_server_options(ServerOptions *options) | |
| options->kerberos_ticket_cleanup = -1; | |
| options->kerberos_get_afs_token = -1; | |
| options->gss_authentication=-1; | |
| + options->gss_keyex = -1; | |
| options->gss_cleanup_creds = -1; | |
| + options->gss_strict_acceptor = -1; | |
| + options->gss_store_rekey = -1; | |
| options->password_authentication = -1; | |
| options->kbd_interactive_authentication = -1; | |
| options->challenge_response_authentication = -1; | |
| @@ -233,8 +236,14 @@ fill_default_server_options(ServerOptions *options) | |
| options->kerberos_get_afs_token = 0; | |
| if (options->gss_authentication == -1) | |
| options->gss_authentication = 0; | |
| + if (options->gss_keyex == -1) | |
| + options->gss_keyex = 0; | |
| if (options->gss_cleanup_creds == -1) | |
| options->gss_cleanup_creds = 1; | |
| + if (options->gss_strict_acceptor == -1) | |
| + options->gss_strict_acceptor = 1; | |
| + if (options->gss_store_rekey == -1) | |
| + options->gss_store_rekey = 0; | |
| if (options->password_authentication == -1) | |
| options->password_authentication = 1; | |
| if (options->kbd_interactive_authentication == -1) | |
| @@ -327,7 +336,9 @@ typedef enum { | |
| sBanner, sUseDNS, sHostbasedAuthentication, | |
| sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, | |
| sClientAliveCountMax, sAuthorizedKeysFile, | |
| - sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel, | |
| + sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, | |
| + sGssKeyEx, sGssStoreRekey, | |
| + sAcceptEnv, sPermitTunnel, | |
| sMatch, sPermitOpen, sForceCommand, sChrootDirectory, | |
| sUsePrivilegeSeparation, sAllowAgentForwarding, | |
| sZeroKnowledgePasswordAuthentication, sHostCertificate, | |
| @@ -393,10 +404,20 @@ static struct { | |
| #ifdef GSSAPI | |
| { "gssapiauthentication", sGssAuthentication, SSHCFG_ALL }, | |
| { "gssapicleanupcredentials", sGssCleanupCreds, SSHCFG_GLOBAL }, | |
| + { "gssapicleanupcreds", sGssCleanupCreds, SSHCFG_GLOBAL }, | |
| + { "gssapistrictacceptorcheck", sGssStrictAcceptor, SSHCFG_GLOBAL }, | |
| + { "gssapikeyexchange", sGssKeyEx, SSHCFG_GLOBAL }, | |
| + { "gssapistorecredentialsonrekey", sGssStoreRekey, SSHCFG_GLOBAL }, | |
| #else | |
| { "gssapiauthentication", sUnsupported, SSHCFG_ALL }, | |
| { "gssapicleanupcredentials", sUnsupported, SSHCFG_GLOBAL }, | |
| + { "gssapicleanupcreds", sUnsupported, SSHCFG_GLOBAL }, | |
| + { "gssapistrictacceptorcheck", sUnsupported, SSHCFG_GLOBAL }, | |
| + { "gssapikeyexchange", sUnsupported, SSHCFG_GLOBAL }, | |
| + { "gssapistorecredentialsonrekey", sUnsupported, SSHCFG_GLOBAL }, | |
| #endif | |
| + { "gssusesessionccache", sUnsupported, SSHCFG_GLOBAL }, | |
| + { "gssapiusesessioncredcache", sUnsupported, SSHCFG_GLOBAL }, | |
| { "passwordauthentication", sPasswordAuthentication, SSHCFG_ALL }, | |
| { "kbdinteractiveauthentication", sKbdInteractiveAuthentication, SSHCFG_ALL }, | |
| { "challengeresponseauthentication", sChallengeResponseAuthentication, SSHCFG_GLOBAL }, | |
| @@ -1049,10 +1070,22 @@ process_server_config_line(ServerOptions *options, char *line, | |
| intptr = &options->gss_authentication; | |
| goto parse_flag; | |
| + case sGssKeyEx: | |
| + intptr = &options->gss_keyex; | |
| + goto parse_flag; | |
| + | |
| case sGssCleanupCreds: | |
| intptr = &options->gss_cleanup_creds; | |
| goto parse_flag; | |
| + case sGssStrictAcceptor: | |
| + intptr = &options->gss_strict_acceptor; | |
| + goto parse_flag; | |
| + | |
| + case sGssStoreRekey: | |
| + intptr = &options->gss_store_rekey; | |
| + goto parse_flag; | |
| + | |
| case sPasswordAuthentication: | |
| intptr = &options->password_authentication; | |
| goto parse_flag; | |
| @@ -1927,7 +1960,10 @@ dump_config(ServerOptions *o) | |
| #endif | |
| #ifdef GSSAPI | |
| dump_cfg_fmtint(sGssAuthentication, o->gss_authentication); | |
| + dump_cfg_fmtint(sGssKeyEx, o->gss_keyex); | |
| dump_cfg_fmtint(sGssCleanupCreds, o->gss_cleanup_creds); | |
| + dump_cfg_fmtint(sGssStrictAcceptor, o->gss_strict_acceptor); | |
| + dump_cfg_fmtint(sGssStoreRekey, o->gss_store_rekey); | |
| #endif | |
| #ifdef JPAKE | |
| dump_cfg_fmtint(sZeroKnowledgePasswordAuthentication, | |
| diff --git a/servconf.h b/servconf.h | |
| index 870c709..06e21a9 100644 | |
| --- a/servconf.h | |
| +++ b/servconf.h | |
| @@ -110,7 +110,10 @@ typedef struct { | |
| int kerberos_get_afs_token; /* If true, try to get AFS token if | |
| * authenticated with Kerberos. */ | |
| int gss_authentication; /* If true, permit GSSAPI authentication */ | |
| + int gss_keyex; /* If true, permit GSSAPI key exchange */ | |
| int gss_cleanup_creds; /* If true, destroy cred cache on logout */ | |
| + int gss_strict_acceptor; /* If true, restrict the GSSAPI acceptor name */ | |
| + int gss_store_rekey; | |
| int password_authentication; /* If true, permit password | |
| * authentication. */ | |
| int kbd_interactive_authentication; /* If true, permit */ | |
| diff --git a/ssh-gss.h b/ssh-gss.h | |
| index 077e13c..bc6e8f9 100644 | |
| --- a/ssh-gss.h | |
| +++ b/ssh-gss.h | |
| @@ -1,6 +1,6 @@ | |
| /* $OpenBSD: ssh-gss.h,v 1.10 2007/06/12 08:20:00 djm Exp $ */ | |
| /* | |
| - * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. | |
| + * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| @@ -61,10 +61,22 @@ | |
| #define SSH_GSS_OIDTYPE 0x06 | |
| +#define SSH2_MSG_KEXGSS_INIT 30 | |
| +#define SSH2_MSG_KEXGSS_CONTINUE 31 | |
| +#define SSH2_MSG_KEXGSS_COMPLETE 32 | |
| +#define SSH2_MSG_KEXGSS_HOSTKEY 33 | |
| +#define SSH2_MSG_KEXGSS_ERROR 34 | |
| +#define SSH2_MSG_KEXGSS_GROUPREQ 40 | |
| +#define SSH2_MSG_KEXGSS_GROUP 41 | |
| +#define KEX_GSS_GRP1_SHA1_ID "gss-group1-sha1-" | |
| +#define KEX_GSS_GRP14_SHA1_ID "gss-group14-sha1-" | |
| +#define KEX_GSS_GEX_SHA1_ID "gss-gex-sha1-" | |
| + | |
| typedef struct { | |
| char *filename; | |
| char *envvar; | |
| char *envval; | |
| + struct passwd *owner; | |
| void *data; | |
| } ssh_gssapi_ccache; | |
| @@ -72,8 +84,11 @@ typedef struct { | |
| gss_buffer_desc displayname; | |
| gss_buffer_desc exportedname; | |
| gss_cred_id_t creds; | |
| + gss_name_t name; | |
| struct ssh_gssapi_mech_struct *mech; | |
| ssh_gssapi_ccache store; | |
| + int used; | |
| + int updated; | |
| } ssh_gssapi_client; | |
| typedef struct ssh_gssapi_mech_struct { | |
| @@ -84,6 +99,7 @@ typedef struct ssh_gssapi_mech_struct { | |
| int (*userok) (ssh_gssapi_client *, char *); | |
| int (*localname) (ssh_gssapi_client *, char **); | |
| void (*storecreds) (ssh_gssapi_client *); | |
| + int (*updatecreds) (ssh_gssapi_ccache *, ssh_gssapi_client *); | |
| } ssh_gssapi_mech; | |
| typedef struct { | |
| @@ -94,10 +110,11 @@ typedef struct { | |
| gss_OID oid; /* client */ | |
| gss_cred_id_t creds; /* server */ | |
| gss_name_t client; /* server */ | |
| - gss_cred_id_t client_creds; /* server */ | |
| + gss_cred_id_t client_creds; /* both */ | |
| } Gssctxt; | |
| extern ssh_gssapi_mech *supported_mechs[]; | |
| +extern Gssctxt *gss_kex_context; | |
| int ssh_gssapi_check_oid(Gssctxt *, void *, size_t); | |
| void ssh_gssapi_set_oid_data(Gssctxt *, void *, size_t); | |
| @@ -117,16 +134,30 @@ void ssh_gssapi_build_ctx(Gssctxt **); | |
| void ssh_gssapi_delete_ctx(Gssctxt **); | |
| OM_uint32 ssh_gssapi_sign(Gssctxt *, gss_buffer_t, gss_buffer_t); | |
| void ssh_gssapi_buildmic(Buffer *, const char *, const char *, const char *); | |
| -int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *); | |
| +int ssh_gssapi_check_mechanism(Gssctxt **, gss_OID, const char *, const char *); | |
| +OM_uint32 ssh_gssapi_client_identity(Gssctxt *, const char *); | |
| +int ssh_gssapi_credentials_updated(Gssctxt *); | |
| /* In the server */ | |
| +typedef int ssh_gssapi_check_fn(Gssctxt **, gss_OID, const char *, | |
| + const char *); | |
| +char *ssh_gssapi_client_mechanisms(const char *, const char *); | |
| +char *ssh_gssapi_kex_mechs(gss_OID_set, ssh_gssapi_check_fn *, const char *, | |
| + const char *); | |
| +gss_OID ssh_gssapi_id_kex(Gssctxt *, char *, int); | |
| +int ssh_gssapi_server_check_mech(Gssctxt **,gss_OID, const char *, | |
| + const char *); | |
| OM_uint32 ssh_gssapi_server_ctx(Gssctxt **, gss_OID); | |
| -int ssh_gssapi_userok(char *name); | |
| +int ssh_gssapi_userok(char *name, struct passwd *); | |
| OM_uint32 ssh_gssapi_checkmic(Gssctxt *, gss_buffer_t, gss_buffer_t); | |
| void ssh_gssapi_do_child(char ***, u_int *); | |
| void ssh_gssapi_cleanup_creds(void); | |
| void ssh_gssapi_storecreds(void); | |
| +char *ssh_gssapi_server_mechanisms(void); | |
| +int ssh_gssapi_oid_table_ok(); | |
| + | |
| +int ssh_gssapi_update_creds(ssh_gssapi_ccache *store); | |
| #endif /* GSSAPI */ | |
| #endif /* _SSH_GSS_H */ | |
| diff --git a/ssh_config b/ssh_config | |
| index 1893674..2c06ba7 100644 | |
| --- a/ssh_config | |
| +++ b/ssh_config | |
| @@ -26,6 +26,8 @@ | |
| # HostbasedAuthentication no | |
| # GSSAPIAuthentication no | |
| # GSSAPIDelegateCredentials no | |
| +# GSSAPIKeyExchange no | |
| +# GSSAPITrustDNS no | |
| # BatchMode no | |
| # CheckHostIP yes | |
| # AddressFamily any | |
| diff --git a/ssh_config.5 b/ssh_config.5 | |
| index 269529c..bd3a712 100644 | |
| --- a/ssh_config.5 | |
| +++ b/ssh_config.5 | |
| @@ -530,11 +530,43 @@ Specifies whether user authentication based on GSSAPI is allowed. | |
| The default is | |
| .Dq no . | |
| 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 no . | |
| +Note that this option applies to protocol version 2 only. | |
| +.It Cm GSSAPIClientIdentity | |
| +If set, specifies the GSSAPI client identity that ssh should use when | |
| +connecting to the server. The default is unset, which means that the default | |
| +identity will be used. | |
| +.It Cm GSSAPIServerIdentity | |
| +If set, specifies the GSSAPI server identity that ssh should expect when | |
| +connecting to the server. The default is unset, which means that the | |
| +expected GSSAPI server identity will be determined from the target | |
| +hostname. | |
| .It Cm GSSAPIDelegateCredentials | |
| Forward (delegate) credentials to the server. | |
| The default is | |
| .Dq no . | |
| -Note that this option applies to protocol version 2 only. | |
| +Note that this option applies to protocol version 2 connections using GSSAPI. | |
| +.It Cm GSSAPIRenewalForcesRekey | |
| +If set to | |
| +.Dq yes | |
| +then renewal of the client's GSSAPI credentials will force the rekeying of the | |
| +ssh connection. With a compatible server, this can delegate the renewed | |
| +credentials to a session on the server. | |
| +The default is | |
| +.Dq no . | |
| +.It Cm GSSAPITrustDns | |
| +Set to | |
| +.Dq yes to indicate that the DNS is trusted to securely canonicalize | |
| +the name of the host being connected to. If | |
| +.Dq no, the hostname entered on the | |
| +command line will be passed untouched to the GSSAPI library. | |
| +The default is | |
| +.Dq no . | |
| +This option only applies to protocol version 2 connections using GSSAPI. | |
| .It Cm HashKnownHosts | |
| Indicates that | |
| .Xr ssh 1 | |
| diff --git a/sshconnect2.c b/sshconnect2.c | |
| index 58015c0..1aa8523 100644 | |
| --- a/sshconnect2.c | |
| +++ b/sshconnect2.c | |
| @@ -160,9 +160,34 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) | |
| { | |
| Kex *kex; | |
| +#ifdef GSSAPI | |
| + char *orig = NULL, *gss = NULL; | |
| + char *gss_host = NULL; | |
| +#endif | |
| + | |
| xxx_host = host; | |
| xxx_hostaddr = hostaddr; | |
| +#ifdef GSSAPI | |
| + if (options.gss_keyex) { | |
| + /* Add the GSSAPI mechanisms currently supported on this | |
| + * client to the key exchange algorithm proposal */ | |
| + orig = myproposal[PROPOSAL_KEX_ALGS]; | |
| + | |
| + if (options.gss_trust_dns) | |
| + gss_host = (char *)get_canonical_hostname(1); | |
| + else | |
| + gss_host = host; | |
| + | |
| + gss = ssh_gssapi_client_mechanisms(gss_host, options.gss_client_identity); | |
| + if (gss) { | |
| + debug("Offering GSSAPI proposal: %s", gss); | |
| + xasprintf(&myproposal[PROPOSAL_KEX_ALGS], | |
| + "%s,%s", gss, orig); | |
| + } | |
| + } | |
| +#endif | |
| + | |
| if (options.ciphers == (char *)-1) { | |
| logit("No valid ciphers for protocol version 2 given, using defaults."); | |
| options.ciphers = NULL; | |
| @@ -197,6 +222,17 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) | |
| if (options.kex_algorithms != NULL) | |
| myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms; | |
| +#ifdef GSSAPI | |
| + /* If we've got GSSAPI algorithms, then we also support the | |
| + * 'null' hostkey, as a last resort */ | |
| + if (options.gss_keyex && gss) { | |
| + orig = myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]; | |
| + xasprintf(&myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS], | |
| + "%s,null", orig); | |
| + xfree(gss); | |
| + } | |
| +#endif | |
| + | |
| if (options.rekey_limit) | |
| packet_set_rekey_limit((u_int32_t)options.rekey_limit); | |
| @@ -207,10 +243,30 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) | |
| kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; | |
| kex->kex[KEX_DH_GEX_SHA256] = kexgex_client; | |
| kex->kex[KEX_ECDH_SHA2] = kexecdh_client; | |
| +#ifdef GSSAPI | |
| + if (options.gss_keyex) { | |
| + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client; | |
| + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_client; | |
| + kex->kex[KEX_GSS_GEX_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 | |
| + if (options.gss_keyex) { | |
| + kex->gss_deleg_creds = options.gss_deleg_creds; | |
| + kex->gss_trust_dns = options.gss_trust_dns; | |
| + kex->gss_client = options.gss_client_identity; | |
| + if (options.gss_server_identity) { | |
| + kex->gss_host = options.gss_server_identity; | |
| + } else { | |
| + kex->gss_host = gss_host; | |
| + } | |
| + } | |
| +#endif | |
| + | |
| xxx_kex = kex; | |
| dispatch_run(DISPATCH_BLOCK, &kex->done, kex); | |
| @@ -306,6 +362,7 @@ void input_gssapi_token(int type, u_int32_t, void *); | |
| void input_gssapi_hash(int type, u_int32_t, void *); | |
| void input_gssapi_error(int, u_int32_t, void *); | |
| void input_gssapi_errtok(int, u_int32_t, void *); | |
| +int userauth_gsskeyex(Authctxt *authctxt); | |
| #endif | |
| void userauth(Authctxt *, char *); | |
| @@ -321,6 +378,11 @@ static char *authmethods_get(void); | |
| Authmethod authmethods[] = { | |
| #ifdef GSSAPI | |
| + {"gssapi-keyex", | |
| + userauth_gsskeyex, | |
| + NULL, | |
| + &options.gss_authentication, | |
| + NULL}, | |
| {"gssapi-with-mic", | |
| userauth_gssapi, | |
| NULL, | |
| @@ -627,19 +689,31 @@ userauth_gssapi(Authctxt *authctxt) | |
| static u_int mech = 0; | |
| OM_uint32 min; | |
| int ok = 0; | |
| + const char *gss_host; | |
| + | |
| + if (options.gss_server_identity) | |
| + gss_host = options.gss_server_identity; | |
| + else if (options.gss_trust_dns) | |
| + gss_host = get_canonical_hostname(1); | |
| + else | |
| + gss_host = authctxt->host; | |
| /* Try one GSSAPI method at a time, rather than sending them all at | |
| * once. */ | |
| if (gss_supported == NULL) | |
| - gss_indicate_mechs(&min, &gss_supported); | |
| + if (GSS_ERROR(gss_indicate_mechs(&min, &gss_supported))) { | |
| + gss_supported = NULL; | |
| + return 0; | |
| + } | |
| /* Check to see if the mechanism is usable before we offer it */ | |
| while (mech < gss_supported->count && !ok) { | |
| /* My DER encoding requires length<128 */ | |
| if (gss_supported->elements[mech].length < 128 && | |
| ssh_gssapi_check_mechanism(&gssctxt, | |
| - &gss_supported->elements[mech], authctxt->host)) { | |
| + &gss_supported->elements[mech], gss_host, | |
| + options.gss_client_identity)) { | |
| ok = 1; /* Mechanism works */ | |
| } else { | |
| mech++; | |
| @@ -736,8 +810,8 @@ input_gssapi_response(int type, u_int32_t plen, void *ctxt) | |
| { | |
| Authctxt *authctxt = ctxt; | |
| Gssctxt *gssctxt; | |
| - int oidlen; | |
| - char *oidv; | |
| + u_int oidlen; | |
| + u_char *oidv; | |
| if (authctxt == NULL) | |
| fatal("input_gssapi_response: no authentication context"); | |
| @@ -847,6 +921,48 @@ input_gssapi_error(int type, u_int32_t plen, void *ctxt) | |
| xfree(msg); | |
| xfree(lang); | |
| } | |
| + | |
| +int | |
| +userauth_gsskeyex(Authctxt *authctxt) | |
| +{ | |
| + Buffer b; | |
| + gss_buffer_desc gssbuf; | |
| + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER; | |
| + OM_uint32 ms; | |
| + | |
| + static int attempt = 0; | |
| + if (attempt++ >= 1) | |
| + return (0); | |
| + | |
| + if (gss_kex_context == NULL) { | |
| + debug("No valid Key exchange context"); | |
| + return (0); | |
| + } | |
| + | |
| + ssh_gssapi_buildmic(&b, authctxt->server_user, authctxt->service, | |
| + "gssapi-keyex"); | |
| + | |
| + gssbuf.value = buffer_ptr(&b); | |
| + gssbuf.length = buffer_len(&b); | |
| + | |
| + if (GSS_ERROR(ssh_gssapi_sign(gss_kex_context, &gssbuf, &mic))) { | |
| + buffer_free(&b); | |
| + return (0); | |
| + } | |
| + | |
| + packet_start(SSH2_MSG_USERAUTH_REQUEST); | |
| + packet_put_cstring(authctxt->server_user); | |
| + packet_put_cstring(authctxt->service); | |
| + packet_put_cstring(authctxt->method->name); | |
| + packet_put_string(mic.value, mic.length); | |
| + packet_send(); | |
| + | |
| + buffer_free(&b); | |
| + gss_release_buffer(&ms, &mic); | |
| + | |
| + return (1); | |
| +} | |
| + | |
| #endif /* GSSAPI */ | |
| int | |
| diff --git a/sshd.c b/sshd.c | |
| index 3e9d176..d8faaeb 100644 | |
| --- a/sshd.c | |
| +++ b/sshd.c | |
| @@ -121,6 +121,10 @@ | |
| #include "ssh-sandbox.h" | |
| #include "version.h" | |
| +#ifdef USE_SECURITY_SESSION_API | |
| +#include <Security/AuthSession.h> | |
| +#endif | |
| + | |
| #ifdef LIBWRAP | |
| #include <tcpd.h> | |
| #include <syslog.h> | |
| @@ -1645,10 +1649,13 @@ main(int ac, char **av) | |
| logit("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) { | |
| logit("Disabling protocol version 2. Could not load host key"); | |
| options.protocol &= ~SSH_PROTO_2; | |
| } | |
| +#endif | |
| if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { | |
| logit("sshd: no hostkeys available -- exiting."); | |
| exit(1); | |
| @@ -1976,6 +1983,60 @@ main(int ac, char **av) | |
| /* Log the connection. */ | |
| verbose("Connection from %.500s port %d", remote_ip, remote_port); | |
| +#ifdef USE_SECURITY_SESSION_API | |
| + /* | |
| + * Create a new security session for use by the new user login if | |
| + * the current session is the root session or we are not launched | |
| + * by inetd (eg: debugging mode or server mode). We do not | |
| + * necessarily need to create a session if we are launched from | |
| + * inetd because Panther xinetd will create a session for us. | |
| + * | |
| + * The only case where this logic will fail is if there is an | |
| + * inetd running in a non-root session which is not creating | |
| + * new sessions for us. Then all the users will end up in the | |
| + * same session (bad). | |
| + * | |
| + * When the client exits, the session will be destroyed for us | |
| + * automatically. | |
| + * | |
| + * We must create the session before any credentials are stored | |
| + * (including AFS pags, which happens a few lines below). | |
| + */ | |
| + { | |
| + OSStatus err = 0; | |
| + SecuritySessionId sid = 0; | |
| + SessionAttributeBits sattrs = 0; | |
| + | |
| + err = SessionGetInfo(callerSecuritySession, &sid, &sattrs); | |
| + if (err) | |
| + error("SessionGetInfo() failed with error %.8X", | |
| + (unsigned) err); | |
| + else | |
| + debug("Current Session ID is %.8X / Session Attributes are %.8X", | |
| + (unsigned) sid, (unsigned) sattrs); | |
| + | |
| + if (inetd_flag && !(sattrs & sessionIsRoot)) | |
| + debug("Running in inetd mode in a non-root session... " | |
| + "assuming inetd created the session for us."); | |
| + else { | |
| + debug("Creating new security session..."); | |
| + err = SessionCreate(0, sessionHasTTY | sessionIsRemote); | |
| + if (err) | |
| + error("SessionCreate() failed with error %.8X", | |
| + (unsigned) err); | |
| + | |
| + err = SessionGetInfo(callerSecuritySession, &sid, | |
| + &sattrs); | |
| + if (err) | |
| + error("SessionGetInfo() failed with error %.8X", | |
| + (unsigned) err); | |
| + else | |
| + debug("New Session ID is %.8X / Session Attributes are %.8X", | |
| + (unsigned) sid, (unsigned) sattrs); | |
| + } | |
| + } | |
| +#endif | |
| + | |
| /* | |
| * We don't want to listen forever unless the other side | |
| * successfully authenticates itself. So we set up an alarm which is | |
| @@ -2357,6 +2418,48 @@ 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 there's no point advertising | |
| + * the other key exchange algorithms | |
| + */ | |
| + | |
| + 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) | |
| + xasprintf(&newstr, "%s,%s", gss, orig); | |
| + else if (gss) | |
| + newstr = gss; | |
| + else if (orig) | |
| + newstr = orig; | |
| + | |
| + /* | |
| + * If we've got GSSAPI mechanisms, then we've got the 'null' host | |
| + * key alg, but we can't tell people about it unless its the only | |
| + * host key algorithm we support | |
| + */ | |
| + if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) | |
| + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = "null"; | |
| + | |
| + if (newstr) | |
| + myproposal[PROPOSAL_KEX_ALGS] = newstr; | |
| + else | |
| + fatal("No supported key exchange algorithms"); | |
| + } | |
| +#endif | |
| + | |
| /* start key exchange */ | |
| kex = kex_setup(myproposal); | |
| kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; | |
| @@ -2364,6 +2467,13 @@ do_ssh2_kex(void) | |
| kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; | |
| kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; | |
| kex->kex[KEX_ECDH_SHA2] = kexecdh_server; | |
| +#ifdef GSSAPI | |
| + if (options.gss_keyex) { | |
| + kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server; | |
| + kex->kex[KEX_GSS_GRP14_SHA1] = kexgss_server; | |
| + kex->kex[KEX_GSS_GEX_SHA1] = kexgss_server; | |
| + } | |
| +#endif | |
| kex->server = 1; | |
| kex->client_version_string=client_version_string; | |
| kex->server_version_string=server_version_string; | |
| diff --git a/sshd_config b/sshd_config | |
| index 9cd2fdd..1af2afd 100644 | |
| --- a/sshd_config | |
| +++ b/sshd_config | |
| @@ -80,6 +80,8 @@ AuthorizedKeysFile .ssh/authorized_keys | |
| # GSSAPI options | |
| #GSSAPIAuthentication no | |
| #GSSAPICleanupCredentials yes | |
| +#GSSAPIStrictAcceptorCheck yes | |
| +#GSSAPIKeyExchange no | |
| # Set this to 'yes' to enable PAM authentication, account processing, | |
| # and session processing. If this is enabled, PAM authentication will | |
| diff --git a/sshd_config.5 b/sshd_config.5 | |
| index cfa4806..935bb62 100644 | |
| --- a/sshd_config.5 | |
| +++ b/sshd_config.5 | |
| @@ -481,12 +481,40 @@ Specifies whether user authentication based on GSSAPI is allowed. | |
| The default is | |
| .Dq no . | |
| Note that this option applies to protocol version 2 only. | |
| +.It Cm GSSAPIKeyExchange | |
| +Specifies whether key exchange based on GSSAPI is allowed. GSSAPI key exchange | |
| +doesn't rely on ssh keys to verify host identity. | |
| +The default is | |
| +.Dq no . | |
| +Note that this option applies to protocol version 2 only. | |
| .It Cm GSSAPICleanupCredentials | |
| Specifies whether to automatically destroy the user's credentials cache | |
| on logout. | |
| The default is | |
| .Dq yes . | |
| Note that this option applies to protocol version 2 only. | |
| +.It Cm GSSAPIStrictAcceptorCheck | |
| +Determines whether to be strict about the identity of the GSSAPI acceptor | |
| +a client authenticates against. If | |
| +.Dq yes | |
| +then the client must authenticate against the | |
| +.Pa host | |
| +service on the current hostname. If | |
| +.Dq no | |
| +then the client may authenticate against any service key stored in the | |
| +machine's default store. This facility is provided to assist with operation | |
| +on multi homed machines. | |
| +The default is | |
| +.Dq yes . | |
| +Note that this option applies only to protocol version 2 GSSAPI connections, | |
| +and setting it to | |
| +.Dq no | |
| +may only work with recent Kerberos GSSAPI libraries. | |
| +.It Cm GSSAPIStoreCredentialsOnRekey | |
| +Controls whether the user's GSSAPI credentials should be updated following a | |
| +successful connection rekeying. This option can be used to accepted renewed | |
| +or updated credentials from a compatible client. The default is | |
| +.Dq no . | |
| .It Cm HostbasedAuthentication | |
| Specifies whether rhosts or /etc/hosts.equiv authentication together | |
| with successful public key client host authentication is allowed | |
| -- | |
| 1.8.1.4 |