TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} $(SFTP_PROGS)
-LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o dh.o dispatch.o fatal.o mac.o hostfile.o key.o kex.o kexdh.o kexgex.o kexgss.o log.o match.o misc.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o scard.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o uuencode.o xmalloc.o gss-genr.o
+LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o dh.o dispatch.o fatal.o mac.o hostfile.o key.o kex.o kexdh.o kexgex.o log.o match.o misc.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o scard.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o uuencode.o xmalloc.o kexgss.o gss-genr.o
SSHOBJS= ssh.o sshconnect.o sshconnect1.o sshconnect2.o sshtty.o readconf.o clientloop.o
#ifdef KRB5
#include <krb5.h>
+#ifndef HEIMDAL
+#define krb5_get_err_text(context,code) error_message(code)
+#endif /* !HEIMDAL */
extern ServerOptions options;
goto err;
fd = packet_get_connection_in();
+#ifdef HEIMDAL
problem = krb5_auth_con_setaddrs_from_fd(authctxt->krb5_ctx,
authctxt->krb5_auth_ctx, &fd);
+#else
+ problem = krb5_auth_con_genaddrs(authctxt->krb5_ctx,
+ authctxt->krb5_auth_ctx,fd,
+ KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR |
+ KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR);
+#endif
if (problem)
goto err;
if (problem)
goto err;
+#ifdef HEIMDAL
problem = krb5_copy_principal(authctxt->krb5_ctx, ticket->client,
&authctxt->krb5_user);
+#else
+ problem = krb5_copy_principal(authctxt->krb5_ctx,
+ ticket->enc_part2->client,
+ &authctxt->krb5_user);
+#endif
if (problem)
goto err;
krb5_error_code problem;
krb5_ccache ccache = NULL;
char *pname;
+ krb5_creds **creds;
if (authctxt->pw == NULL || authctxt->krb5_user == NULL)
return (0);
temporarily_use_uid(authctxt->pw);
+#ifdef HEIMDAL
problem = krb5_cc_gen_new(authctxt->krb5_ctx, &krb5_fcc_ops, &ccache);
+#else
+{
+ char ccname[40];
+ int tmpfd;
+
+ snprintf(ccname,sizeof(ccname),"FILE:/tmp/krb5cc_%d_XXXXXX",geteuid());
+
+ if ((tmpfd = mkstemp(ccname))==-1) {
+ log("mkstemp(): %.100s", strerror(errno));
+ problem = errno;
+ goto fail;
+ }
+ if (fchmod(tmpfd,S_IRUSR | S_IWUSR) == -1) {
+ log("fchmod(): %.100s", strerror(errno));
+ close(tmpfd);
+ problem = errno;
+ goto fail;
+ }
+ close(tmpfd);
+ problem = krb5_cc_resolve(authctxt->krb5_ctx, ccname, &ccache);
+}
+#endif
if (problem)
goto fail;
if (problem)
goto fail;
+#ifdef HEIMDAL
problem = krb5_rd_cred2(authctxt->krb5_ctx, authctxt->krb5_auth_ctx,
ccache, tgt);
if (problem)
goto fail;
+#else
+ problem = krb5_rd_cred(authctxt->krb5_ctx, authctxt->krb5_auth_ctx,
+ tgt, &creds, NULL);
+ if (problem)
+ goto fail;
+ problem = krb5_cc_store_cred(authctxt->krb5_ctx, ccache, *creds);
+ if (problem)
+ goto fail;
+#endif
authctxt->krb5_fwd_ccache = ccache;
ccache = NULL;
int
auth_krb5_password(Authctxt *authctxt, const char *password)
{
+#ifndef HEIMDAL
+ krb5_creds creds;
+ krb5_principal server;
+#endif
krb5_error_code problem;
if (authctxt->pw == NULL)
if (problem)
goto out;
+#ifdef HEIMDAL
problem = krb5_cc_gen_new(authctxt->krb5_ctx, &krb5_mcc_ops,
&authctxt->krb5_fwd_ccache);
+#else
+ problem = krb5_cc_resolve(authctxt->krb5_ctx, "MEMORY:",
+ &authctxt->krb5_fwd_ccache);
+#endif
if (problem)
goto out;
goto out;
restore_uid();
+
+#ifdef HEIMDAL
problem = krb5_verify_user(authctxt->krb5_ctx, authctxt->krb5_user,
authctxt->krb5_fwd_ccache, password, 1, NULL);
+ if (problem) {
+ temporarily_use_uid(authctxt->pw);
+ goto out;
+ }
+#else
+ problem = krb5_get_init_creds_password(authctxt->krb5_ctx, &creds,
+ authctxt->krb5_user, password, NULL, NULL, 0, NULL, NULL);
+ if (problem) {
+ temporarily_use_uid(authctxt->pw);
+ goto out;
+ }
+
+ problem = krb5_sname_to_principal(authctxt->krb5_ctx, NULL, NULL,
+ KRB5_NT_SRV_HST, &server);
+ if (problem) {
+ temporarily_use_uid(authctxt->pw);
+ goto out;
+ }
+
+ problem = krb5_verify_init_creds(authctxt->krb5_ctx, &creds, server,
+ NULL, NULL, NULL);
+
+ krb5_free_principal(authctxt->krb5_ctx, server);
+
+ temporarily_use_uid(authctxt->pw);
+ if (problem) {
+ goto out;
+ }
+
+ problem= krb5_cc_store_cred(authctxt->krb5_ctx, authctxt->krb5_fwd_ccache,
+ &creds);
+ if (problem) {
+ temporarily_use_uid(authctxt->pw);
+ goto out;
+ }
+#endif
temporarily_use_uid(authctxt->pw);
if (problem)
#endif /* HAVE_PAM_GETENVLIST */
}
+/* Set a PAM environment string. We need to do this so that the session
+ * modules can handle things like Kerberos/GSI credentials that appear
+ * during the ssh authentication process.
+ */
+
+int do_pam_putenv(char *name, char *value) {
+ char *compound;
+ int ret=1;
+
+#ifdef HAVE_PAM_PUTENV
+ compound=xmalloc(strlen(name)+strlen(value)+2);
+ if (compound) {
+ sprintf(compound,"%s=%s",name,value);
+ ret=pam_putenv(__pamh,compound);
+ xfree(compound);
+ }
+#endif
+ return(ret);
+}
+
/* Print any messages that have been generated during authentication */
/* or account checking to stderr */
void print_pam_messages(void)
#include "canohost.h"
#include "match.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
/* import */
extern ServerOptions options;
extern u_char *session_id2;
static int userauth_hostbased(Authctxt *);
static int userauth_kbdint(Authctxt *);
+#ifdef GSSAPI
+int userauth_external(Authctxt *authctxt);
+int userauth_gssapi(Authctxt *authctxt);
+#endif
+
Authmethod authmethods[] = {
{"none",
userauth_none,
&one},
+#ifdef GSSAPI
+ {"external-keyx",
+ userauth_external,
+ &options.gss_authentication},
+ {"gssapi",
+ userauth_gssapi,
+ &options.gss_authentication},
+#endif
{"publickey",
userauth_pubkey,
&options.pubkey_authentication},
}
/* reset state */
auth2_challenge_stop(authctxt);
+
+#ifdef GSSAPI
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL);
+#endif
+
authctxt->postponed = 0;
/* try to authenticate user */
/*--*/
-u_int
+u_int
cipher_blocksize(Cipher *c)
{
return (c->block_size);
}
-u_int
+u_int
cipher_keylen(Cipher *c)
{
return (c->key_len);
}
+u_int
+cipher_get_number(Cipher *c)
+{
+ return (c->number);
+}
u_int
cipher_mask_ssh1(int client)
EVP_CIPH_ALWAYS_CALL_INIT;
return (&rijndal_cbc);
}
+
+/*
+ * Exports an IV from the CipherContext required to export the key
+ * state back from the unprivileged child to the privileged parent
+ * process.
+ */
+
+int
+cipher_get_keyiv_len(CipherContext *cc)
+{
+ Cipher *c = cc->cipher;
+ int ivlen;
+
+ if (c->number == SSH_CIPHER_3DES)
+ ivlen = 24;
+ else
+ ivlen = EVP_CIPHER_CTX_iv_length(&cc->evp);
+ return (ivlen);
+}
+
+void
+cipher_get_keyiv(CipherContext *cc, u_char *iv, u_int len)
+{
+ Cipher *c = cc->cipher;
+ u_char *civ = NULL;
+ int evplen;
+
+ switch (c->number) {
+ case SSH_CIPHER_SSH2:
+ case SSH_CIPHER_DES:
+ case SSH_CIPHER_BLOWFISH:
+ evplen = EVP_CIPHER_CTX_iv_length(&cc->evp);
+ if (evplen == 0)
+ return;
+ if (evplen != len)
+ fatal("%s: wrong iv length %d != %d", __FUNCTION__,
+ evplen, len);
+
+ if (strncmp(c->name, "aes", 3) == 0) {
+ struct ssh_rijndael_ctx *aesc;
+
+ aesc = EVP_CIPHER_CTX_get_app_data(&cc->evp);
+ if (aesc == NULL)
+ fatal("%s: no rijndael context", __FUNCTION__);
+ civ = aesc->r_iv;
+ } else {
+ civ = cc->evp.iv;
+ }
+ break;
+ case SSH_CIPHER_3DES: {
+ struct ssh1_3des_ctx *desc;
+ if (len != 24)
+ fatal("%s: bad 3des iv length: %d", __FUNCTION__, len);
+ desc = EVP_CIPHER_CTX_get_app_data(&cc->evp);
+ if (desc == NULL)
+ fatal("%s: no 3des context", __FUNCTION__);
+ debug3("%s: Copying 3DES IV", __FUNCTION__);
+ memcpy(iv, desc->k1.iv, 8);
+ memcpy(iv + 8, desc->k2.iv, 8);
+ memcpy(iv + 16, desc->k3.iv, 8);
+ return;
+ }
+ default:
+ fatal("%s: bad cipher %d", __FUNCTION__, c->number);
+ }
+ memcpy(iv, civ, len);
+}
+
+void
+cipher_set_keyiv(CipherContext *cc, u_char *iv)
+{
+ Cipher *c = cc->cipher;
+ u_char *div = NULL;
+ int evplen = 0;
+
+ switch (c->number) {
+ case SSH_CIPHER_SSH2:
+ case SSH_CIPHER_DES:
+ case SSH_CIPHER_BLOWFISH:
+ evplen = EVP_CIPHER_CTX_iv_length(&cc->evp);
+ if (evplen == 0)
+ return;
+
+ if (strncmp(c->name, "aes", 3) == 0) {
+ struct ssh_rijndael_ctx *aesc;
+
+ aesc = EVP_CIPHER_CTX_get_app_data(&cc->evp);
+ if (aesc == NULL)
+ fatal("%s: no rijndael context", __FUNCTION__);
+ div = aesc->r_iv;
+ }else {
+ div = cc->evp.iv;
+ }
+ break;
+ case SSH_CIPHER_3DES: {
+ struct ssh1_3des_ctx *desc;
+ desc = EVP_CIPHER_CTX_get_app_data(&cc->evp);
+ if (desc == NULL)
+ fatal("%s: no 3des context", __FUNCTION__);
+ debug3("%s: Installed 3DES IV", __FUNCTION__);
+ memcpy(desc->k1.iv, iv, 8);
+ memcpy(desc->k2.iv, iv + 8, 8);
+ memcpy(desc->k3.iv, iv + 16, 8);
+ return;
+ }
+ default:
+ fatal("%s: bad cipher %d", __FUNCTION__, c->number);
+ }
+ memcpy(div, iv, evplen);
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x00907000L
+#define EVP_X_STATE(evp) &(evp).c
+#define EVP_X_STATE_LEN(evp) sizeof((evp).c)
+#else
+#define EVP_X_STATE(evp) (evp).cipher_data
+#define EVP_X_STATE_LEN(evp) (evp).cipher->ctx_size
+#endif
+
+int
+cipher_get_keycontext(CipherContext *cc, u_char *dat)
+{
+ Cipher *c = cc->cipher;
+ int plen;
+
+ if (c->number == SSH_CIPHER_3DES) {
+ struct ssh1_3des_ctx *desc;
+ desc = EVP_CIPHER_CTX_get_app_data(&cc->evp);
+ if (desc == NULL)
+ fatal("%s: no 3des context", __FUNCTION__);
+ plen = EVP_X_STATE_LEN(desc->k1);
+ if (dat == NULL)
+ return (3*plen);
+ memcpy(dat, EVP_X_STATE(desc->k1), plen);
+ memcpy(dat + plen, EVP_X_STATE(desc->k2), plen);
+ memcpy(dat + 2*plen, EVP_X_STATE(desc->k3), plen);
+ return (3*plen);
+ }
+
+ /* Generic EVP */
+ plen = EVP_X_STATE_LEN(cc->evp);
+ if (dat == NULL)
+ return (plen);
+
+ memcpy(dat, EVP_X_STATE(cc->evp), plen);
+ return (plen);
+}
+
+void
+cipher_set_keycontext(CipherContext *cc, u_char *dat)
+{
+ Cipher *c = cc->cipher;
+ int plen;
+
+ if (c->number == SSH_CIPHER_3DES) {
+ struct ssh1_3des_ctx *desc;
+ desc = EVP_CIPHER_CTX_get_app_data(&cc->evp);
+ if (desc == NULL)
+ fatal("%s: no 3des context", __FUNCTION__);
+ plen = EVP_X_STATE_LEN(desc->k1);
+ memcpy(EVP_X_STATE(desc->k1), dat, plen);
+ memcpy(EVP_X_STATE(desc->k2), dat + plen, plen);
+ memcpy(EVP_X_STATE(desc->k3), dat + 2*plen, plen);
+ } else {
+ plen = EVP_X_STATE_LEN(cc->evp);
+ memcpy(EVP_X_STATE(cc->evp), dat, plen);
+ }
+}
{ "OpenSSH_2.5.0p1*,"
"OpenSSH_2.5.1p1*",
SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX|
- SSH_BUG_NOREKEY },
+ SSH_BUG_NOREKEY|SSH_OLD_GSSAPI },
{ "OpenSSH_2.5.0*,"
"OpenSSH_2.5.1*,"
"OpenSSH_2.5.2*", SSH_OLD_DHGEX|SSH_BUG_NOREKEY },
{ "OpenSSH_2.5.3*", SSH_BUG_NOREKEY },
+ { "OpenSSH_2.9p*", SSH_OLD_GSSAPI },
{ "Sun_SSH_1.0*", SSH_BUG_NOREKEY },
{ "OpenSSH*", 0 },
{ "*MindTerm*", 0 },
#define SSH_BUG_OPENFAILURE 0x00020000
#define SSH_BUG_DERIVEKEY 0x00040000
#define SSH_BUG_DUMMYCHAN 0x00100000
+#define SSH_OLD_GSSAPI 0x00200000
void enable_compat13(void);
void enable_compat20(void);
AC_CHECK_LIB(dl, dlopen, , )
AC_CHECK_LIB(pam, pam_set_item, , AC_MSG_ERROR([*** libpam missing]))
AC_CHECK_FUNCS(pam_getenvlist)
+ AC_CHECK_FUNCS(pam_putenv)
disable_shadow=yes
PAM_MSG="yes"
else
LIBPAM="-lpam"
fi
+
AC_SUBST(LIBPAM)
fi
]
--- /dev/null
+/*
+ * Copyright (c) 2001 Simon Wilkinson. All rights reserved. *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#ifdef GSSAPI
+
+#include "ssh.h"
+#include "ssh2.h"
+#include "xmalloc.h"
+#include "buffer.h"
+#include "bufaux.h"
+#include "packet.h"
+#include "compat.h"
+#include <openssl/evp.h>
+#include "cipher.h"
+#include "kex.h"
+#include "log.h"
+#include "compat.h"
+
+#include <netdb.h>
+
+#include "ssh-gss.h"
+
+/* Assorted globals for tracking the clients identity once they've
+ * authenticated */
+
+gss_buffer_desc gssapi_client_name = {0,NULL}; /* Name of our client */
+gss_cred_id_t gssapi_client_creds = GSS_C_NO_CREDENTIAL; /* Their credentials */
+enum ssh_gss_id gssapi_client_type = GSS_LAST_ENTRY;
+
+/* The mechanism name used in the list below is defined in the internet
+ * draft as the Base 64 encoding of the MD5 hash of the ASN.1 DER encoding
+ * of the underlying GSSAPI mechanism's OID.
+ *
+ * Also from the draft, before considering adding SPNEGO, bear in mind that
+ * "mechanisms ... MUST NOT use SPNEGO as the underlying GSSAPI mechanism"
+ */
+
+/* These must be in the same order as ssh_gss_id, in ssh-gss.h */
+
+ssh_gssapi_mech supported_mechs[]= {
+#ifdef KRB5
+ /* Official OID - 1.2.850.113554.1.2.2 */
+ {"Se3H81ismmOC3OE+FwYCiQ==","Kerberos",
+ {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}},
+#endif
+#ifdef GSI
+ /* gssapi_ssleay 1.3.6.1.4.1.3536.1.1 */
+ {"N3+k7/4wGxHyuP8Yxi4RhA==",
+ "GSI",
+ {9, "\x2B\x06\x01\x04\x01\x9B\x50\x01\x01"}
+ },
+#endif /* GSI */
+ {NULL,NULL,{0,0}}
+};
+
+char gssprefix[]=KEX_GSS_SHA1;
+
+/* Return a list of the gss-group1-sha1-x mechanisms supported by this
+ * program.
+ *
+ * We only support the mechanisms that we've indicated in the list above,
+ * but we check that they're supported by the GSSAPI mechanism on the
+ * machine. We also check, before including them in the list, that
+ * we have the necesary information in order to carry out the key exchange
+ * (that is, that the user has credentials, the server's creds are accessible,
+ * etc)
+ *
+ * The way that this is done is fairly nasty, as we do a lot of work that
+ * is then thrown away. This should possibly be implemented with a cache
+ * that stores the results (in an expanded Gssctxt structure), which are
+ * then used by the first calls if that key exchange mechanism is chosen.
+ */
+
+char *
+ssh_gssapi_mechanisms(int server,char *host) {
+ gss_OID_set supported;
+ OM_uint32 maj_status, min_status;
+ Buffer buf;
+ int i = 0;
+ int present;
+ char * mechs;
+ Gssctxt ctx;
+ gss_buffer_desc token;
+
+ if (datafellows & SSH_OLD_GSSAPI) return NULL;
+
+ gss_indicate_mechs(&min_status, &supported);
+
+ buffer_init(&buf);
+
+ do {
+ if ((maj_status=gss_test_oid_set_member(&min_status,
+ &supported_mechs[i].oid,
+ supported,
+ &present))) {
+ present=0;
+ }
+ if (present) {
+ ssh_gssapi_build_ctx(&ctx);
+ ssh_gssapi_set_oid(&ctx,&supported_mechs[i].oid);
+ if (server) {
+ if (ssh_gssapi_acquire_cred(&ctx)) {
+ ssh_gssapi_delete_ctx(&ctx);
+ continue;
+ }
+ }
+ else { /* client */
+ if (ssh_gssapi_import_name(&ctx,host))
+ continue;
+ maj_status=ssh_gssapi_init_ctx(&ctx, 0,
+ GSS_C_NO_BUFFER,
+ &token,
+ NULL);
+ ssh_gssapi_delete_ctx(&ctx);
+ if (GSS_ERROR(maj_status)) {
+ continue;
+ }
+ }
+
+ /* Append gss_group1_sha1_x to our list */
+ buffer_append(&buf, gssprefix,
+ strlen(gssprefix));
+ buffer_append(&buf, supported_mechs[i].enc_name,
+ strlen(supported_mechs[i].enc_name));
+ }
+ } while (supported_mechs[++i].name != NULL);
+
+ buffer_put_char(&buf,'\0');
+
+ mechs=xmalloc(buffer_len(&buf));
+ buffer_get(&buf,mechs,buffer_len(&buf));
+ buffer_free(&buf);
+ if (strlen(mechs)==0)
+ return(NULL);
+ else
+ return(mechs);
+}
+
+void ssh_gssapi_supported_oids(gss_OID_set *oidset) {
+ enum ssh_gss_id i =0;
+ OM_uint32 maj_status,min_status;
+ int present;
+ gss_OID_set supported;
+
+ gss_create_empty_oid_set(&min_status,oidset);
+ gss_indicate_mechs(&min_status, &supported);
+
+ while (supported_mechs[i].name!=NULL) {
+ if ((maj_status=gss_test_oid_set_member(&min_status,
+ &supported_mechs[i].oid,
+ supported,
+ &present))) {
+ present=0;
+ }
+ if (present) {
+ gss_add_oid_set_member(&min_status,
+ &supported_mechs[i].oid,
+ oidset);
+ }
+ i++;
+ }
+}
+
+/* Set the contexts OID from a data stream */
+void ssh_gssapi_set_oid_data(Gssctxt *ctx, void *data, size_t len) {
+ if (ctx->oid != GSS_C_NO_OID) {
+ xfree(ctx->oid->elements);
+ xfree(ctx->oid);
+ }
+ ctx->oid=xmalloc(sizeof(gss_OID_desc));
+ ctx->oid->length=len;
+ ctx->oid->elements=xmalloc(len);
+ memcpy(ctx->oid->elements,data,len);
+}
+
+/* Set the contexts OID */
+void ssh_gssapi_set_oid(Gssctxt *ctx, gss_OID oid) {
+ ssh_gssapi_set_oid_data(ctx,oid->elements,oid->length);
+}
+
+/* Find out which GSS type (out of the list we define in ssh-gss.h) a
+ * particular connection is using
+ */
+enum ssh_gss_id ssh_gssapi_get_ctype(Gssctxt *ctxt) {
+ enum ssh_gss_id i=0;
+
+ while(supported_mechs[i].name!=NULL &&
+ supported_mechs[i].oid.length != ctxt->oid->length &&
+ (memcmp(supported_mechs[i].oid.elements,
+ ctxt->oid->elements,ctxt->oid->length) !=0)) {
+ i++;
+ }
+ return(i);
+}
+
+/* Set the GSS context's OID to the oid indicated by the given key exchange
+ * name. */
+int ssh_gssapi_id_kex(Gssctxt *ctx, char *name) {
+ enum ssh_gss_id i=0;
+
+ if (strncmp(name, gssprefix, strlen(gssprefix)-1) !=0) {
+ return(1);
+ }
+
+ name+=strlen(gssprefix); /* Move to the start of the MIME string */
+
+ while (supported_mechs[i].name!=NULL &&
+ strcmp(name,supported_mechs[i].enc_name)!=0) {
+ i++;
+ }
+
+ if (supported_mechs[i].name==NULL)
+ return (1);
+
+ ssh_gssapi_set_oid(ctx,&supported_mechs[i].oid);
+
+ return 0;
+}
+
+
+/* All this effort to report an error ... */
+void
+ssh_gssapi_error(OM_uint32 major_status,OM_uint32 minor_status) {
+ OM_uint32 lmaj, lmin;
+ gss_buffer_desc msg;
+ OM_uint32 ctx;
+
+ ctx = 0;
+ /* The GSSAPI error */
+ do {
+ lmaj = gss_display_status(&lmin, major_status,
+ GSS_C_GSS_CODE,
+ GSS_C_NULL_OID,
+ &ctx, &msg);
+ if (lmaj == GSS_S_COMPLETE) {
+ debug((char *)msg.value);
+ (void) gss_release_buffer(&lmin, &msg);
+ }
+ } while (ctx!=0);
+
+ /* The mechanism specific error */
+ do {
+ lmaj = gss_display_status(&lmin, minor_status,
+ GSS_C_MECH_CODE,
+ GSS_C_NULL_OID,
+ &ctx, &msg);
+ if (lmaj == GSS_S_COMPLETE) {
+ debug((char *)msg.value);
+ (void) gss_release_buffer(&lmin, &msg);
+ }
+ } while (ctx!=0);
+}
+
+/* Initialise our GSSAPI context. We use this opaque structure to contain all
+ * of the data which both the client and server need to persist across
+ * {accept,init}_sec_context calls, so that when we do it from the userauth
+ * stuff life is a little easier
+ */
+void
+ssh_gssapi_build_ctx(Gssctxt *ctx)
+{
+ ctx->context=GSS_C_NO_CONTEXT;
+ ctx->name=GSS_C_NO_NAME;
+ ctx->oid=GSS_C_NO_OID;
+ ctx->creds=GSS_C_NO_CREDENTIAL;
+ ctx->client=GSS_C_NO_NAME;
+ ctx->client_creds=GSS_C_NO_CREDENTIAL;
+}
+
+/* Delete our context, providing it has been built correctly */
+void
+ssh_gssapi_delete_ctx(Gssctxt *ctx)
+{
+ OM_uint32 ms;
+
+ if (ctx->context != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&ms,&ctx->context,GSS_C_NO_BUFFER);
+ if (ctx->name != GSS_C_NO_NAME)
+ gss_release_name(&ms,&ctx->name);
+ if (ctx->oid != GSS_C_NO_OID) {
+ xfree(ctx->oid->elements);
+ xfree(ctx->oid);
+ ctx->oid = GSS_C_NO_OID;
+ }
+ if (ctx->creds != GSS_C_NO_CREDENTIAL)
+ gss_release_cred(&ms,&ctx->creds);
+ if (ctx->client != GSS_C_NO_NAME)
+ gss_release_name(&ms,&ctx->client);
+ if (ctx->client_creds != GSS_C_NO_CREDENTIAL)
+ gss_release_cred(&ms,&ctx->client_creds);
+}
+
+/* Wrapper to init_sec_context
+ * Requires that the context contains:
+ * oid
+ * server name (from ssh_gssapi_import_name)
+ */
+OM_uint32
+ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok,
+ gss_buffer_desc* send_tok, OM_uint32 *flags)
+{
+ OM_uint32 maj_status, min_status;
+ int deleg_flag = 0;
+
+ if (deleg_creds) {
+ deleg_flag=GSS_C_DELEG_FLAG;
+ debug("Delegating credentials");
+ }
+
+ maj_status=gss_init_sec_context(&min_status,
+ GSS_C_NO_CREDENTIAL, /* def. cred */
+ &ctx->context,
+ ctx->name,
+ ctx->oid,
+ GSS_C_MUTUAL_FLAG |
+ GSS_C_INTEG_FLAG |
+ deleg_flag,
+ 0, /* default lifetime */
+ NULL, /* no channel bindings */
+ recv_tok,
+ NULL,
+ send_tok,
+ flags,
+ NULL);
+ ctx->status=maj_status;
+ if (GSS_ERROR(maj_status)) {
+ ssh_gssapi_error(maj_status,min_status);
+ }
+ return(maj_status);
+}
+
+/* Wrapper arround accept_sec_context
+ * Requires that the context contains:
+ * oid
+ * credentials (from ssh_gssapi_acquire_cred)
+ */
+OM_uint32 ssh_gssapi_accept_ctx(Gssctxt *ctx,gss_buffer_desc *recv_tok,
+ gss_buffer_desc *send_tok, OM_uint32 *flags)
+{
+ OM_uint32 maj_status, min_status;
+ gss_OID mech;
+
+ maj_status=gss_accept_sec_context(&min_status,
+ &ctx->context,
+ ctx->creds,
+ recv_tok,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &ctx->client,
+ &mech,
+ send_tok,
+ flags,
+ NULL,
+ &ctx->client_creds);
+ if (GSS_ERROR(maj_status)) {
+ ssh_gssapi_error(maj_status,min_status);
+ }
+
+ if (ctx->client_creds) {
+ debug("Received some client credentials");
+ } else {
+ debug("Got no client credentials");
+ }
+
+ /* FIXME: We should check that the mechanism thats being used is
+ * the one that we asked for (in ctx->oid) */
+
+ ctx->status=maj_status;
+
+ return(maj_status);
+}
+
+/* Create a service name for the given host */
+OM_uint32
+ssh_gssapi_import_name(Gssctxt *ctx, const char *host) {
+ gss_buffer_desc gssbuf;
+ OM_uint32 maj_status, min_status;
+ struct hostent *hostinfo = NULL;
+ char *xhost;
+
+ /* Make a copy of the host name, in case it was returned by a
+ * previous call to gethostbyname(). */
+ xhost = xstrdup(host);
+
+ /* Make sure we have the FQDN. Some GSSAPI implementations don't do
+ * this for us themselves */
+
+ hostinfo = gethostbyname(xhost);
+
+ if ((hostinfo == NULL) || (hostinfo->h_name == NULL)) {
+ debug("Unable to get FQDN for \"%s\"", xhost);
+ } else {
+ xfree(xhost);
+ xhost = xstrdup(hostinfo->h_name);
+ }
+
+ gssbuf.length = sizeof("host@")+strlen(xhost);
+
+ gssbuf.value = xmalloc(gssbuf.length);
+ if (gssbuf.value == NULL) {
+ xfree(xhost);
+ return(-1);
+ }
+ snprintf(gssbuf.value,gssbuf.length,"host@%s",xhost);
+ if ((maj_status=gss_import_name(&min_status,
+ &gssbuf,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ &ctx->name))) {
+ ssh_gssapi_error(maj_status,min_status);
+ }
+
+ xfree(xhost);
+ xfree(gssbuf.value);
+ return(maj_status);
+}
+
+/* Acquire credentials for a server running on the current host.
+ * Requires that the context structure contains a valid OID
+ */
+OM_uint32
+ssh_gssapi_acquire_cred(Gssctxt *ctx) {
+ OM_uint32 maj_status, min_status;
+ char lname[MAXHOSTNAMELEN];
+ gss_OID_set oidset;
+
+ gss_create_empty_oid_set(&min_status,&oidset);
+ gss_add_oid_set_member(&min_status,ctx->oid,&oidset);
+
+ if (gethostname(lname, MAXHOSTNAMELEN)) {
+ return(-1);
+ }
+
+ if ((maj_status=ssh_gssapi_import_name(ctx,lname))) {
+ return(maj_status);
+ }
+ if ((maj_status=gss_acquire_cred(&min_status,
+ ctx->name,
+ 0,
+ oidset,
+ GSS_C_ACCEPT,
+ &ctx->creds,
+ NULL,
+ NULL))) {
+ ssh_gssapi_error(maj_status,min_status);
+ }
+
+ gss_release_oid_set(&min_status, &oidset);
+ return(maj_status);
+}
+
+/* Extract the client details from a given context. This can only reliably
+ * be called once for a context */
+
+OM_uint32
+ssh_gssapi_getclient(Gssctxt *ctx, enum ssh_gss_id *type,
+ gss_buffer_desc *name, gss_cred_id_t *creds) {
+
+ OM_uint32 maj_status,min_status;
+
+ *type=ssh_gssapi_get_ctype(ctx);
+ if ((maj_status=gss_display_name(&min_status,ctx->client,name,NULL))) {
+ ssh_gssapi_error(maj_status,min_status);
+ }
+
+ /* This is icky. There appears to be no way to copy this structure,
+ * rather than the pointer to it, so we simply copy the pointer and
+ * mark the originator as empty so we don't destroy it.
+ */
+ *creds=ctx->client_creds;
+ ctx->client_creds=GSS_C_NO_CREDENTIAL;
+ return(maj_status);
+}
+
+#endif /* GSSAPI */
#include "session.h"
#include "dispatch.h"
#include "servconf.h"
+#include "compat.h"
#include "ssh-gss.h"
#endif
case GSS_LAST_ENTRY:
debug("No GSSAPI credentials stored");
-
+ break;
default:
log("ssh_gssapi_do_child: Unknown mechanism");
}
if (!authctxt->valid || authctxt->user == NULL)
return 0;
+
+ if (datafellows & SSH_OLD_GSSAPI) {
+ debug("Early drafts of GSSAPI userauth not supported");
+ return 0;
+ }
+
mechs=packet_get_int();
if (mechs==0) {
debug("Mechanism negotiation is not supported");
enum kex_exchange {
DH_GRP1_SHA1,
- DH_GEX_SHA1
+ DH_GEX_SHA1,
+ GSS_GRP1_SHA1
};
#define KEX_INIT_SENT 0x0001
Mac mac;
Comp comp;
};
+
+struct KexOptions {
+ int gss_deleg_creds;
+};
+
struct Kex {
u_char *session_id;
int session_id_len;
Buffer peer;
int done;
int flags;
+ char *host;
char *client_version_string;
char *server_version_string;
int (*verify_host_key)(Key *);
Key *(*load_host_key)(int);
+ struct KexOptions options;
};
Kex *kex_setup(char *[PROPOSAL_MAX]);
void kexdh(Kex *);
void kexgex(Kex *);
+#ifdef GSSAPI
+void kexgss(Kex *);
+#endif
Newkeys *kex_get_newkeys(int);
--- /dev/null
+/*
+ * Copyright (c) 2001,2002 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 <openssl/crypto.h>
+#include <openssl/bn.h>
+
+#include "xmalloc.h"
+#include "buffer.h"
+#include "bufaux.h"
+#include "kex.h"
+#include "log.h"
+#include "packet.h"
+#include "dh.h"
+#include "ssh2.h"
+#include "ssh-gss.h"
+
+/* This is now the same as the DH hash ... */
+
+u_char *
+kex_gssapi_hash(
+ char *client_version_string,
+ char *server_version_string,
+ char *ckexinit, int ckexinitlen,
+ char *skexinit, int skexinitlen,
+ u_char *serverhostkeyblob, int sbloblen,
+ BIGNUM *client_dh_pub,
+ BIGNUM *server_dh_pub,
+ BIGNUM *shared_secret)
+{
+ Buffer b;
+ static u_char digest[EVP_MAX_MD_SIZE];
+ EVP_MD *evp_md = EVP_sha1();
+ EVP_MD_CTX md;
+
+ buffer_init(&b);
+ buffer_put_string(&b, client_version_string, strlen(client_version_string));
+ buffer_put_string(&b, server_version_string, strlen(server_version_string));
+
+ /* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */
+ buffer_put_int(&b, ckexinitlen+1);
+ buffer_put_char(&b, SSH2_MSG_KEXINIT);
+ buffer_append(&b, ckexinit, ckexinitlen);
+ buffer_put_int(&b, skexinitlen+1);
+ buffer_put_char(&b, SSH2_MSG_KEXINIT);
+ buffer_append(&b, skexinit, skexinitlen);
+
+ buffer_put_string(&b, serverhostkeyblob, sbloblen);
+ buffer_put_bignum2(&b, client_dh_pub);
+ buffer_put_bignum2(&b, server_dh_pub);
+ buffer_put_bignum2(&b, shared_secret);
+
+#ifdef DEBUG_KEX
+ buffer_dump(&b);
+#endif
+ EVP_DigestInit(&md, evp_md);
+ EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
+ EVP_DigestFinal(&md, digest, NULL);
+
+ buffer_free(&b);
+
+#ifdef DEBUG_KEX
+ dump_digest("hash", digest, evp_md->md_size);
+#endif
+ return digest;
+}
+
+void
+kexgss_client(Kex *kex)
+{
+ gss_buffer_desc gssbuf,send_tok,recv_tok, msg_tok, *token_ptr;
+ Gssctxt ctxt;
+ OM_uint32 maj_status, min_status, ret_flags;
+ unsigned int klen, kout;
+ DH *dh;
+ BIGNUM *dh_server_pub = 0;
+ BIGNUM *shared_secret = 0;
+ unsigned char *kbuf;
+ unsigned char *hash;
+ unsigned char *serverhostkey;
+ int type = 0;
+ int first = 1;
+ int slen = 0;
+
+ /* Initialise our GSSAPI world */
+ ssh_gssapi_build_ctx(&ctxt);
+ if (ssh_gssapi_id_kex(&ctxt,kex->name)) {
+ fatal("Couldn't identify host exchange");
+ }
+ if (ssh_gssapi_import_name(&ctxt,kex->host)) {
+ fatal("Couldn't import hostname ");
+ }
+
+ /* This code should match that in ssh_dh1_client */
+
+ /* Step 1 - e is dh->pub_key */
+ dh = dh_new_group1();
+ dh_gen_key(dh, kex->we_need * 8);
+
+ /* This is f, we initialise it now to make life easier */
+ dh_server_pub = BN_new();
+ if (dh_server_pub == NULL) {
+ fatal("dh_server_pub == NULL");
+ }
+
+ token_ptr = GSS_C_NO_BUFFER;
+
+ do {
+ debug("Calling gss_init_sec_context");
+
+ maj_status=ssh_gssapi_init_ctx(&ctxt,
+ kex->options.gss_deleg_creds,
+ token_ptr,&send_tok,
+ &ret_flags);
+
+ if (GSS_ERROR(maj_status)) {
+ fatal("gss_init_context failed");
+ }
+
+ /* If we've got an old receive buffer get rid of it */
+ if (token_ptr != GSS_C_NO_BUFFER)
+ (void) gss_release_buffer(&min_status, &recv_tok);
+
+
+ if (maj_status == GSS_S_COMPLETE) {
+ /* If mutual state flag is not true, kex fails */
+ if (!(ret_flags & GSS_C_MUTUAL_FLAG)) {
+ fatal("Mutual authentication failed");
+ }
+ /* If integ avail flag is not true kex fails */
+ if (!(ret_flags & GSS_C_INTEG_FLAG)) {
+ fatal("Integrity check failed");
+ }
+ }
+
+ /* If we have data to send, then the last message that we
+ * received cannot have been a 'complete'. */
+ if (send_tok.length !=0) {
+ if (first) {
+ packet_start(SSH2_MSG_KEXGSS_INIT);
+ packet_put_string(send_tok.value,
+ send_tok.length);
+ packet_put_bignum2(dh->pub_key);
+ first=0;
+ } else {
+ packet_start(SSH2_MSG_KEXGSS_CONTINUE);
+ packet_put_string(send_tok.value,
+ send_tok.length);
+ }
+ packet_send();
+ packet_write_wait();
+
+
+ /* If we've sent them data, they'd better be polite
+ * and reply. */
+
+ type = packet_read();
+ switch (type) {
+ case SSH2_MSG_KEXGSS_HOSTKEY:
+ debug("Received KEXGSS_HOSTKEY");
+ serverhostkey=packet_get_string(&slen);
+ break;
+ case SSH2_MSG_KEXGSS_CONTINUE:
+ debug("Received GSSAPI_CONTINUE");
+ if (maj_status == GSS_S_COMPLETE)
+ fatal("GSSAPI Continue received from server when complete");
+ recv_tok.value=packet_get_string(&recv_tok.length);
+ break;
+ case SSH2_MSG_KEXGSS_COMPLETE:
+ debug("Received GSSAPI_COMPLETE");
+ packet_get_bignum2(dh_server_pub);
+ msg_tok.value=
+ packet_get_string(&msg_tok.length);
+
+ /* Is there a token included? */
+ if (packet_get_char()) {
+ recv_tok.value=
+ packet_get_string(&recv_tok.length);
+ /* If we're already complete - protocol error */
+ if (maj_status == GSS_S_COMPLETE)
+ packet_disconnect("Protocol error: received token when complete");
+ } else {
+ /* No token included */
+ if (maj_status != GSS_S_COMPLETE)
+ packet_disconnect("Protocol error: did not receive final token");
+ }
+ break;
+ default:
+ packet_disconnect("Protocol error: didn't expect packet type %d",
+ type);
+ }
+ token_ptr=&recv_tok;
+ }
+
+ } while (maj_status & GSS_S_CONTINUE_NEEDED);
+
+ /* We _must_ have received a COMPLETE message in reply from the
+ * server, which will have set dh_server_pub and msg_tok */
+
+ if (type!=SSH2_MSG_KEXGSS_COMPLETE)
+ fatal("Didn't receive a SSH2_MSG_KEXGSS_COMPLETE when I expected it");
+
+ /* Check f in range [1, p-1] */
+ if (!dh_pub_is_valid(dh, dh_server_pub))
+ packet_disconnect("bad server public DH value");
+
+ /* compute K=f^x mod p */
+ klen = DH_size(dh);
+ kbuf = xmalloc(klen);
+ kout = DH_compute_key(kbuf, dh_server_pub, dh);
+
+ shared_secret = BN_new();
+ BN_bin2bn(kbuf,kout, shared_secret);
+ memset(kbuf, 0, klen);
+ xfree(kbuf);
+
+ hash = kex_gssapi_hash(
+ kex->client_version_string,
+ kex->server_version_string,
+ buffer_ptr(&kex->my), buffer_len(&kex->my),
+ buffer_ptr(&kex->peer), buffer_len(&kex->peer),
+ serverhostkey, slen, /* server host key */
+ dh->pub_key, /* e */
+ dh_server_pub, /* f */
+ shared_secret /* K */
+ );
+
+ gssbuf.value=hash;
+ gssbuf.length=20;
+
+ /* Verify that H matches the token we just got. */
+ if ((maj_status = gss_verify_mic(&min_status,
+ ctxt.context,
+ &gssbuf,
+ &msg_tok,
+ NULL))) {
+
+ packet_disconnect("Hash's MIC didn't verify");
+ }
+
+ DH_free(dh);
+ ssh_gssapi_delete_ctx(&ctxt);
+ /* save session id */
+ if (kex->session_id == NULL) {
+ kex->session_id_len = 20;
+ kex->session_id = xmalloc(kex->session_id_len);
+ memcpy(kex->session_id, hash, kex->session_id_len);
+ }
+
+ kex_derive_keys(kex, hash, shared_secret);
+ BN_clear_free(shared_secret);
+ kex_finish(kex);
+}
+
+
+
+
+void
+kexgss_server(Kex *kex)
+{
+
+ OM_uint32 maj_status, min_status;
+
+ /* Some GSSAPI implementations use the input value of ret_flags (an
+ * output variable) as a means of triggering mechanism specific
+ * features. Initializing it to zero avoids inadvertently
+ * activating this non-standard behaviour.*/
+
+ OM_uint32 ret_flags = 0;
+ gss_buffer_desc gssbuf,send_tok,recv_tok,msg_tok;
+ Gssctxt ctxt;
+ unsigned int klen, kout;
+ unsigned char *kbuf;
+ unsigned char *hash;
+ DH *dh;
+ BIGNUM *shared_secret = NULL;
+ BIGNUM *dh_client_pub = NULL;
+ int type =0;
+
+ /* Initialise GSSAPI */
+
+ ssh_gssapi_build_ctx(&ctxt);
+ if (ssh_gssapi_id_kex(&ctxt,kex->name))
+ fatal("Unknown gssapi mechanism");
+ if (ssh_gssapi_acquire_cred(&ctxt))
+ fatal("Unable to acquire credentials for the server");
+
+ 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(&recv_tok.length);
+
+ dh_client_pub = BN_new();
+
+ if (dh_client_pub == NULL)
+ fatal("dh_client_pub == NULL");
+ packet_get_bignum2(dh_client_pub);
+
+ /* Send SSH_MSG_KEXGSS_HOSTKEY here, if we want */
+ break;
+ case SSH2_MSG_KEXGSS_CONTINUE:
+ if (dh_client_pub == NULL)
+ fatal("Received KEXGSS_CONTINUE without initialising");
+ recv_tok.value=packet_get_string(&recv_tok.length);
+ break;
+ default:
+ packet_disconnect("Protocol error: didn't expect packet type %d",
+ type);
+ }
+ maj_status=ssh_gssapi_accept_ctx(&ctxt,&recv_tok, &send_tok,
+ &ret_flags);
+
+ gss_release_buffer(&min_status,&recv_tok);
+
+ if (maj_status & GSS_S_CONTINUE_NEEDED) {
+ debug("Sending GSSAPI_CONTINUE");
+ packet_start(SSH2_MSG_KEXGSS_CONTINUE);
+ packet_put_string(send_tok.value,send_tok.length);
+ packet_send();
+ packet_write_wait();
+ gss_release_buffer(&min_status, &send_tok);
+ }
+ } while (maj_status & GSS_S_CONTINUE_NEEDED);
+
+ if (GSS_ERROR(maj_status))
+ fatal("gss_accept_context died");
+
+ debug("gss_complete");
+ if (!(ret_flags & GSS_C_MUTUAL_FLAG))
+ fatal("mutual authentication flag wasn't set");
+
+ if (!(ret_flags & GSS_C_INTEG_FLAG))
+ fatal("Integrity flag wasn't set");
+
+
+ dh = dh_new_group1();
+ dh_gen_key(dh, kex->we_need * 8);
+
+ if (!dh_pub_is_valid(dh, dh_client_pub))
+ packet_disconnect("bad client public DH value");
+
+ klen = DH_size(dh);
+ kbuf = xmalloc(klen);
+ kout = DH_compute_key(kbuf, dh_client_pub, dh);
+
+ shared_secret = BN_new();
+ BN_bin2bn(kbuf, kout, shared_secret);
+ memset(kbuf, 0, klen);
+ xfree(kbuf);
+
+ hash = kex_gssapi_hash(
+ kex->client_version_string,
+ kex->server_version_string,
+ buffer_ptr(&kex->peer), buffer_len(&kex->peer),
+ buffer_ptr(&kex->my), buffer_len(&kex->my),
+ NULL, 0, /* Change this if we start sending host keys */
+ dh_client_pub,
+ dh->pub_key,
+ shared_secret
+ );
+ BN_free(dh_client_pub);
+
+ if (kex->session_id == NULL) {
+ kex->session_id_len = 20;
+ kex->session_id = xmalloc(kex->session_id_len);
+ memcpy(kex->session_id, hash, kex->session_id_len);
+ }
+
+ gssbuf.value = hash;
+ gssbuf.length = 20; /* Hashlen appears to always be 20 */
+
+ if ((maj_status=gss_get_mic(&min_status,
+ ctxt.context,
+ GSS_C_QOP_DEFAULT,
+ &gssbuf,
+ &msg_tok))) {
+ ssh_gssapi_error(maj_status,min_status);
+ fatal("Couldn't get MIC");
+ }
+
+ packet_start(SSH2_MSG_KEXGSS_COMPLETE);
+ packet_put_bignum2(dh->pub_key);
+ packet_put_string((char *)msg_tok.value,msg_tok.length);
+
+ if (send_tok.length!=0) {
+ packet_put_char(1); /* true */
+ packet_put_string((char *)send_tok.value,send_tok.length);
+ } else {
+ packet_put_char(0); /* false */
+ }
+ packet_send();
+ packet_write_wait();
+
+ /* Store the client name, and the delegated credentials for later
+ * use */
+ if (ssh_gssapi_getclient(&ctxt,&gssapi_client_type,
+ &gssapi_client_name,
+ &gssapi_client_creds)) {
+ fatal("Couldn't convert client name");
+ }
+
+ gss_release_buffer(&min_status, &send_tok);
+ ssh_gssapi_delete_ctx(&ctxt);
+ DH_free(dh);
+
+ kex_derive_keys(kex, hash, shared_secret);
+ BN_clear_free(shared_secret);
+ kex_finish(kex);
+}
+
+void
+kexgss(Kex *kex)
+{
+ if (kex->server)
+ kexgss_server(kex);
+ else
+ kexgss_client(kex);
+}
+
+#endif /* GSSAPI */
#include "includes.h"
RCSID("$OpenBSD: servconf.c,v 1.101 2002/02/04 12:15:25 markus Exp $");
-#if defined(KRB4) || defined(KRB5)
+#if defined(KRB4)
+#include <krb.h>
+#endif
+#if defined(KRB5)
+#ifdef HEIMDAL
#include <krb.h>
+#else
+/* Bodge - but then, so is using the kerberos IV KEYFILE to get a Kerberos V
+ * keytab */
+#define KEYFILE "/etc/krb5.keytab"
+#endif
#endif
#ifdef AFS
#include <kafs.h>
options->hostbased_uses_name_from_packet_only = -1;
options->rsa_authentication = -1;
options->pubkey_authentication = -1;
+#ifdef GSSAPI
+ options->gss_authentication=-1;
+ options->gss_keyex=-1;
+ options->gss_use_session_ccache = -1;
+ options->gss_cleanup_creds = -1;
+#endif
#if defined(KRB4) || defined(KRB5)
options->kerberos_authentication = -1;
options->kerberos_or_local_passwd = -1;
options->rsa_authentication = 1;
if (options->pubkey_authentication == -1)
options->pubkey_authentication = 1;
+#ifdef GSSAPI
+ if (options->gss_authentication == -1)
+ options->gss_authentication = 1;
+ if (options->gss_keyex == -1)
+ options->gss_keyex =1;
+ if (options->gss_use_session_ccache == -1)
+ options->gss_use_session_ccache = 1;
+ if (options->gss_cleanup_creds == -1)
+ options->gss_cleanup_creds = 1;
+#endif
#if defined(KRB4) || defined(KRB5)
if (options->kerberos_authentication == -1)
options->kerberos_authentication = (access(KEYFILE, R_OK) == 0);
sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime,
sPermitRootLogin, sLogFacility, sLogLevel,
sRhostsAuthentication, sRhostsRSAAuthentication, sRSAAuthentication,
+#ifdef GSSAPI
+ sGssAuthentication, sGssKeyEx, sGssUseSessionCredCache, sGssCleanupCreds,
+#endif
#if defined(KRB4) || defined(KRB5)
sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
#endif
{ "rsaauthentication", sRSAAuthentication },
{ "pubkeyauthentication", sPubkeyAuthentication },
{ "dsaauthentication", sPubkeyAuthentication }, /* alias */
+#ifdef GSSAPI
+ { "gssapiauthentication", sGssAuthentication },
+ { "gssapikeyexchange", sGssKeyEx },
+ { "gssusesessionccache", sGssUseSessionCredCache },
+ { "gssapiusesessioncredcache", sGssUseSessionCredCache },
+ { "gssapicleanupcreds", sGssCleanupCreds },
+#endif
#if defined(KRB4) || defined(KRB5)
{ "kerberosauthentication", sKerberosAuthentication },
{ "kerberosorlocalpasswd", sKerberosOrLocalPasswd },
case sPubkeyAuthentication:
intptr = &options->pubkey_authentication;
goto parse_flag;
+#ifdef GSSAPI
+ case sGssAuthentication:
+ intptr = &options->gss_authentication;
+ goto parse_flag;
+ case sGssKeyEx:
+ intptr = &options->gss_keyex;
+ goto parse_flag;
+ case sGssUseSessionCredCache:
+ intptr = &options->gss_use_session_ccache;
+ goto parse_flag;
+ case sGssCleanupCreds:
+ intptr = &options->gss_cleanup_creds;
+ goto parse_flag;
+#endif
#if defined(KRB4) || defined(KRB5)
case sKerberosAuthentication:
intptr = &options->kerberos_authentication;
#include "canohost.h"
#include "session.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
#ifdef HAVE_CYGWIN
#include <windows.h>
#include <sys/cygwin.h>
session_proctitle(s);
+#if defined(GSSAPI)
+ temporarily_use_uid(s->pw);
+ ssh_gssapi_storecreds();
+ restore_uid();
+#endif
+
#if defined(USE_PAM)
do_pam_session(s->pw->pw_name, NULL);
do_pam_setcred(1);
ptyfd = s->ptyfd;
ttyfd = s->ttyfd;
+#if defined(GSSAPI)
+ temporarily_use_uid(s->pw);
+ ssh_gssapi_storecreds();
+ restore_uid();
+#endif
+
#if defined(USE_PAM)
do_pam_session(s->pw->pw_name, s->tty);
do_pam_setcred(1);
* Sets the value of the given variable in the environment. If the variable
* already exists, its value is overriden.
*/
-static void
+void
child_set_env(char ***envp, u_int *envsizep, const char *name,
const char *value)
{
copy_environment(environ, &env, &envsize);
#endif
+#ifdef GSSAPI
+ /* Allow any GSSAPI methods that we've used to alter
+ * the childs environment as they see fit
+ */
+ ssh_gssapi_do_child(&env,&envsize);
+#endif
+
if (!options.use_login) {
/* Set basic environment. */
child_set_env(&env, &envsize, "USER", pw->pw_name);
do_authenticated2(Authctxt *authctxt)
{
server_loop2(authctxt);
+#if defined(GSSAPI)
+ ssh_gssapi_cleanup_creds(NULL);
+#endif
}
#endif
#ifdef KRB5
#include <krb5.h>
+#ifndef HEIMDAL
+#define krb5_get_err_text(context,code) error_message(code)
+#endif /* !HEIMDAL */
#endif
#ifdef AFS
#include <kafs.h>
ret = 0;
goto out;
}
+
+ problem = krb5_auth_con_init(*context, auth_context);
+ if (problem) {
+ debug("Kerberos v5: krb5_auth_con_init failed");
+ ret = 0;
+ goto out;
+ }
+
+#ifndef HEIMDAL
+ problem = krb5_auth_con_setflags(*context, *auth_context,
+ KRB5_AUTH_CONTEXT_RET_TIME);
+ if (problem) {
+ debug("Keberos v5: krb5_auth_con_setflags failed");
+ ret = 0;
+ goto out;
+ }
+#endif
tkfile = krb5_cc_default_name(*context);
if (strncmp(tkfile, "FILE:", 5) == 0)
if (reply != NULL)
krb5_free_ap_rep_enc_part(*context, reply);
if (ap.length > 0)
+#ifdef HEIMDAL
krb5_data_free(&ap);
+#else
+ krb5_free_data_contents(*context, &ap);
+#endif
return (ret);
}
krb5_data outbuf;
krb5_ccache ccache = NULL;
krb5_creds creds;
+#ifdef HEIMDAL
krb5_kdc_flags flags;
+#else
+ int forwardable;
+#endif
const char *remotehost;
memset(&creds, 0, sizeof(creds));
fd = packet_get_connection_in();
+#ifdef HEIMDAL
problem = krb5_auth_con_setaddrs_from_fd(context, auth_context, &fd);
+#else
+ problem = krb5_auth_con_genaddrs(context, auth_context, fd,
+ KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR |
+ KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR);
+#endif
if (problem)
goto out;
if (problem)
goto out;
+ remotehost = get_canonical_hostname(1);
+
+#ifdef HEIMDAL
problem = krb5_build_principal(context, &creds.server,
strlen(creds.client->realm), creds.client->realm,
"krbtgt", creds.client->realm, NULL);
+#else
+ problem = krb5_build_principal(context, &creds.server,
+ creds.client->realm.length, creds.client->realm.data,
+ "host", remotehost, NULL);
+#endif
if (problem)
goto out;
creds.times.endtime = 0;
+#ifdef HEIMDAL
flags.i = 0;
flags.b.forwarded = 1;
flags.b.forwardable = krb5_config_get_bool(context, NULL,
"libdefaults", "forwardable", NULL);
-
- remotehost = get_canonical_hostname(1);
-
problem = krb5_get_forwarded_creds(context, auth_context,
ccache, flags.i, remotehost, &creds, &outbuf);
+#else
+ forwardable = 1;
+ problem = krb5_fwd_tgt_creds(context, auth_context, remotehost,
+ creds.client, creds.server, ccache, forwardable, &outbuf);
+#endif
+
if (problem)
goto out;
#include "dispatch.h"
#include "canohost.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
/* import */
extern char *client_version_string;
extern char *server_version_string;
ssh_kex2(char *host, struct sockaddr *hostaddr)
{
Kex *kex;
+#ifdef GSSAPI
+ char *orig, *gss;
+ int len;
+#endif
xxx_host = host;
xxx_hostaddr = hostaddr;
+#ifdef GSSAPI
+ /* Add the GSSAPI mechanisms currently supported on this client to
+ * the key exchange algorithm proposal */
+ orig = myproposal[PROPOSAL_KEX_ALGS];
+ gss = ssh_gssapi_mechanisms(0,host);
+ if (gss) {
+ len = strlen(orig)+strlen(gss)+2;
+ myproposal[PROPOSAL_KEX_ALGS]=xmalloc(len);
+ snprintf(myproposal[PROPOSAL_KEX_ALGS],len,"%s,%s",gss,orig);
+ }
+#endif
+
if (options.ciphers == (char *)-1) {
log("No valid ciphers for protocol version 2 given, using defaults.");
options.ciphers = NULL;
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] =
options.hostkeyalgorithms;
+#ifdef GSSAPI
+ /* If we've got GSSAPI algorithms, then we also support the
+ * 'null' hostkey, as a last resort */
+ if (gss) {
+ orig=myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS];
+ len = strlen(orig)+sizeof(",null");
+ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]=xmalloc(len);
+ snprintf(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],len,"%s,null",orig);
+ }
+#endif
+
/* start key exchange */
kex = kex_setup(myproposal);
kex->client_version_string=client_version_string;
kex->server_version_string=server_version_string;
kex->verify_host_key=&verify_host_key_callback;
+ kex->host=host;
+
+#ifdef GSSAPI
+ kex->options.gss_deleg_creds=options.gss_deleg_creds;
+#endif
xxx_kex = kex;
int nkeys;
/* kbd-interactive */
int info_req_seen;
+ /* generic */
+ void *methoddata;
};
struct Authmethod {
char *name; /* string to compare against server's list */
int userauth_kbdint(Authctxt *);
int userauth_hostbased(Authctxt *);
+#ifdef GSSAPI
+int userauth_external(Authctxt *authctxt);
+int userauth_gssapi(Authctxt *authctxt);
+void input_gssapi_response(int type, u_int32_t plen, void *ctxt);
+void input_gssapi_token(int type, u_int32_t plen, void *ctxt);
+void input_gssapi_hash(int type, u_int32_t plen, void *ctxt);
+#endif
+
void userauth(Authctxt *, char *);
static int sign_and_send_pubkey(Authctxt *, Key *, sign_cb_fn *);
static char *authmethods_get(void);
Authmethod authmethods[] = {
+#ifdef GSSAPI
+ {"external-keyx",
+ userauth_external,
+ &options.gss_authentication,
+ NULL},
+ {"gssapi",
+ userauth_gssapi,
+ &options.gss_authentication,
+ NULL},
+#endif
{"hostbased",
userauth_hostbased,
&options.hostbased_authentication,
authctxt.success = 0;
authctxt.method = authmethod_lookup("none");
authctxt.authlist = NULL;
+ authctxt.methoddata = NULL;
authctxt.keys = keys;
authctxt.nkeys = nkeys;
authctxt.info_req_seen = 0;
void
userauth(Authctxt *authctxt, char *authlist)
{
+ if (authctxt->methoddata!=NULL) {
+ xfree(authctxt->methoddata);
+ authctxt->methoddata=NULL;
+ }
+
if (authlist == NULL) {
authlist = authctxt->authlist;
} else {
fatal("input_userauth_success: no authentication context");
if (authctxt->authlist)
xfree(authctxt->authlist);
+ if (authctxt->methoddata)
+ xfree(authctxt->methoddata);
clear_auth_state(authctxt);
authctxt->success = 1; /* break out */
}
}
+#ifdef GSSAPI
+int
+userauth_gssapi(Authctxt *authctxt)
+{
+ int i;
+ Gssctxt *gssctxt;
+ static int tries=0;
+
+ /* For now, we only make one attempt at this. We could try offering
+ * the server different GSSAPI OIDs until we get bored, I suppose.
+ */
+ if (tries++>0) return 0;
+
+ if (datafellows & SSH_OLD_GSSAPI) return 0;
+
+ gssctxt=xmalloc(sizeof(Gssctxt));
+
+ /* Initialise as much of our context as we can, so failures can be
+ * trapped before sending any packets.
+ */
+ ssh_gssapi_build_ctx(gssctxt);
+ if (ssh_gssapi_import_name(gssctxt,authctxt->host)) {
+ return(0);
+ }
+ authctxt->methoddata=(void *)gssctxt;
+
+ packet_start(SSH2_MSG_USERAUTH_REQUEST);
+ packet_put_cstring(authctxt->server_user);
+ packet_put_cstring(authctxt->service);
+ packet_put_cstring(authctxt->method->name);
+
+ /* FIXME: This assumes that our current GSSAPI implementation
+ * supports all of the mechanisms listed in supported_mechs.
+ * This may not be the case - we should use something along
+ * the lines of the code in gss_genr to remove the ones that
+ * aren't supported */
+ packet_put_int(GSS_LAST_ENTRY);
+ for (i=0;i<GSS_LAST_ENTRY;i++) {
+ packet_put_string(supported_mechs[i].oid.elements,
+ supported_mechs[i].oid.length);
+ }
+ packet_send();
+ packet_write_wait();
+
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,&input_gssapi_response);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,&input_gssapi_token);
+
+ return 1;
+}
+
+void
+input_gssapi_response(int type, u_int32_t plen, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ Gssctxt *gssctxt;
+ OM_uint32 status,ms;
+ int oidlen;
+ char *oidv;
+ gss_buffer_desc send_tok;
+
+ if (authctxt == NULL)
+ fatal("input_gssapi_response: no authentication context");
+ gssctxt = authctxt->methoddata;
+
+ /* Setup our OID */
+ oidv=packet_get_string(&oidlen);
+ ssh_gssapi_set_oid_data(gssctxt,oidv,oidlen);
+
+ packet_check_eom();
+
+ status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds,
+ GSS_C_NO_BUFFER, &send_tok,
+ NULL);
+ if (GSS_ERROR(status)) {
+ /* Start again with next method on list */
+ debug("Trying to start again");
+ userauth(authctxt,NULL);
+ return;
+ }
+
+ /* We must have data to send */
+ packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ packet_put_string(send_tok.value,send_tok.length);
+ packet_send();
+ packet_write_wait();
+ gss_release_buffer(&ms, &send_tok);
+}
+
+void
+input_gssapi_token(int type, u_int32_t plen, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ Gssctxt *gssctxt;
+ gss_buffer_desc send_tok,recv_tok;
+ OM_uint32 status;
+
+ if (authctxt == NULL)
+ fatal("input_gssapi_response: no authentication context");
+ gssctxt = authctxt->methoddata;
+
+ recv_tok.value=packet_get_string(&recv_tok.length);
+
+ status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds,
+ &recv_tok, &send_tok, NULL);
+
+ packet_check_eom();
+
+ if (GSS_ERROR(status)) {
+ /* Start again with the next method in the list */
+ userauth(authctxt,NULL);
+ return;
+ }
+
+ if (send_tok.length>0) {
+ packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ packet_put_string(send_tok.value,send_tok.length);
+ packet_send();
+ packet_write_wait();
+ }
+
+ if (status == GSS_S_COMPLETE) {
+ /* If that succeeded, send a exchange complete message */
+ packet_start(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE);
+ packet_send();
+ packet_write_wait();
+ }
+}
+
+int
+userauth_external(Authctxt *authctxt)
+{
+ static int attempt =0;
+
+ if (attempt++ >= 1)
+ return 0;
+
+ debug2("userauth_external");
+ packet_start(SSH2_MSG_USERAUTH_REQUEST);
+ packet_put_cstring(authctxt->server_user);
+ packet_put_cstring(authctxt->service);
+ packet_put_cstring(authctxt->method->name);
+ packet_send();
+ packet_write_wait();
+ return 1;
+}
+#endif /* GSSAPI */
+
int
userauth_none(Authctxt *authctxt)
{
packet_put_cstring(authctxt->method->name);
packet_send();
return 1;
+
}
int