Home | History | Annotate | Download | only in sshd
      1 /*
      2  * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
     14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 /*
     25  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     26  * Use is subject to license terms.
     27  */
     28 
     29 #include "includes.h"
     30 
     31 #ifdef GSSAPI
     32 
     33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     34 
     35 #include "includes.h"
     36 #include "ssh.h"
     37 #include "ssh2.h"
     38 #include "xmalloc.h"
     39 #include "buffer.h"
     40 #include "bufaux.h"
     41 #include "packet.h"
     42 #include "compat.h"
     43 #include <openssl/evp.h>
     44 #include "cipher.h"
     45 #include "kex.h"
     46 #include "auth.h"
     47 #include "log.h"
     48 #include "channels.h"
     49 #include "session.h"
     50 #include "dispatch.h"
     51 #include "servconf.h"
     52 #include "uidswap.h"
     53 #include "compat.h"
     54 #include <pwd.h>
     55 
     56 #include "ssh-gss.h"
     57 
     58 extern char **environ;
     59 
     60 extern ServerOptions options;
     61 extern uchar_t *session_id2;
     62 extern int session_id2_len;
     63 
     64 Gssctxt	*xxx_gssctxt;
     65 
     66 void
     67 ssh_gssapi_server_kex_hook(Kex *kex, char **proposal)
     68 {
     69 	gss_OID_set mechs = GSS_C_NULL_OID_SET;
     70 
     71 	if (kex == NULL || !kex->server)
     72 		fatal("INTERNAL ERROR (%s)", __func__);
     73 
     74 	ssh_gssapi_server_mechs(&mechs);
     75 	ssh_gssapi_modify_kex(kex, mechs, proposal);
     76 }
     77 
     78 void
     79 ssh_gssapi_server_mechs(gss_OID_set *mechs)
     80 {
     81 	static gss_OID_set	supported = GSS_C_NULL_OID_SET;
     82 	gss_OID_set	s, acquired, indicated = GSS_C_NULL_OID_SET;
     83 	gss_cred_id_t	creds;
     84 	OM_uint32	maj, min;
     85 	int		i;
     86 
     87 	if (!mechs) {
     88 		(void) gss_release_oid_set(&min, &supported);
     89 		return;
     90 	}
     91 
     92 	if (supported != GSS_C_NULL_OID_SET) {
     93 		*mechs = supported;
     94 		return;
     95 	}
     96 
     97 	*mechs = GSS_C_NULL_OID_SET;
     98 
     99 	maj = gss_create_empty_oid_set(&min, &s);
    100 	if (GSS_ERROR(maj)) {
    101 		debug("Could not allocate GSS-API resources (%s)",
    102 		    ssh_gssapi_last_error(NULL, &maj, &min));
    103 		return;
    104 	}
    105 
    106 	maj = gss_indicate_mechs(&min, &indicated);
    107 	if (GSS_ERROR(maj)) {
    108 		debug("No GSS-API mechanisms are installed");
    109 		return;
    110 	}
    111 
    112 	maj = gss_acquire_cred(&min, GSS_C_NO_NAME, 0, indicated,
    113 	    GSS_C_ACCEPT, &creds, &acquired, NULL);
    114 
    115 	if (GSS_ERROR(maj))
    116 		debug("Failed to acquire GSS-API credentials for any "
    117 		    "mechanisms (%s)", ssh_gssapi_last_error(NULL, &maj, &min));
    118 
    119 	(void) gss_release_oid_set(&min, &indicated);
    120 	(void) gss_release_cred(&min, &creds);
    121 
    122 	if (acquired == GSS_C_NULL_OID_SET || acquired->count == 0)
    123 		return;
    124 
    125 	for (i = 0; i < acquired->count; i++) {
    126 		if (ssh_gssapi_is_spnego(&acquired->elements[i]))
    127 			continue;
    128 
    129 		maj = gss_add_oid_set_member(&min, &acquired->elements[i], &s);
    130 		if (GSS_ERROR(maj)) {
    131 			debug("Could not allocate GSS-API resources (%s)",
    132 			    ssh_gssapi_last_error(NULL, &maj, &min));
    133 			return;
    134 		}
    135 	}
    136 	(void) gss_release_oid_set(&min, &acquired);
    137 
    138 	if (s->count) {
    139 		supported = s;
    140 		*mechs = s;
    141 	}
    142 }
    143 
    144 /*
    145  * Wrapper around accept_sec_context. Requires that the context contains:
    146  *
    147  *    oid
    148  *    credentials	(from ssh_gssapi_acquire_cred)
    149  */
    150 /* Priviledged */
    151 OM_uint32
    152 ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_t recv_tok,
    153     gss_buffer_t send_tok)
    154 {
    155 	/*
    156 	 * Acquiring a cred for the ctx->desired_mech for GSS_C_NO_NAME
    157 	 * may well be probably better than using GSS_C_NO_CREDENTIAL
    158 	 * and then checking that ctx->desired_mech agrees with
    159 	 * ctx->actual_mech...
    160 	 */
    161 	ctx->major = gss_accept_sec_context(&ctx->minor, &ctx->context,
    162 	    GSS_C_NO_CREDENTIAL, recv_tok, GSS_C_NO_CHANNEL_BINDINGS,
    163 	    &ctx->src_name, &ctx->actual_mech, send_tok, &ctx->flags,
    164 	    NULL, &ctx->deleg_creds);
    165 
    166 	if (GSS_ERROR(ctx->major))
    167 		ssh_gssapi_error(ctx, "accepting security context");
    168 
    169 	if (ctx->major == GSS_S_CONTINUE_NEEDED && send_tok->length == 0)
    170 		fatal("Zero length GSS context token output when "
    171 		    "continue needed");
    172 	else if (GSS_ERROR(ctx->major) && send_tok->length == 0)
    173 		debug2("Zero length GSS context error token output");
    174 
    175 	if (ctx->major == GSS_S_COMPLETE &&
    176 	    ctx->desired_mech != GSS_C_NULL_OID &&
    177 	    (ctx->desired_mech->length != ctx->actual_mech->length ||
    178 	    memcmp(ctx->desired_mech->elements, ctx->actual_mech->elements,
    179 	    ctx->desired_mech->length) != 0)) {
    180 
    181 		gss_OID_set supported;
    182 		OM_uint32 min;
    183 		int present = 0;
    184 
    185 		debug("The client did not use the GSS-API mechanism it "
    186 		    "asked for");
    187 
    188 		/* Let it slide as long as the mech is supported */
    189 		ssh_gssapi_server_mechs(&supported);
    190 		if (supported != GSS_C_NULL_OID_SET) {
    191 			(void) gss_test_oid_set_member(&min, ctx->actual_mech,
    192 			    supported, &present);
    193 		}
    194 		if (!present)
    195 			ctx->major = GSS_S_BAD_MECH;
    196 	}
    197 
    198 	if (ctx->deleg_creds)
    199 		debug("Received delegated GSS credentials");
    200 
    201 	if (ctx->major == GSS_S_COMPLETE) {
    202 		ctx->major = gss_inquire_context(&ctx->minor, ctx->context,
    203 		    NULL, &ctx->dst_name, NULL, NULL, NULL, NULL,
    204 		    &ctx->established);
    205 
    206 		if (GSS_ERROR(ctx->major)) {
    207 			ssh_gssapi_error(ctx,
    208 			    "inquiring established sec context");
    209 			return (ctx->major);
    210 		}
    211 
    212 		xxx_gssctxt = ctx;
    213 	}
    214 
    215 	return (ctx->major);
    216 }
    217 
    218 
    219 /* As user - called through fatal cleanup hook */
    220 void
    221 ssh_gssapi_cleanup_creds(Gssctxt *ctx)
    222 {
    223 #ifdef HAVE_GSS_STORE_CRED
    224 	/* pam_setcred() will take care of this */
    225 	return;
    226 #else
    227 	return;
    228 /* #error "Portability broken in cleanup of stored creds" */
    229 #endif /* HAVE_GSS_STORE_CRED */
    230 }
    231 
    232 void
    233 ssh_gssapi_storecreds(Gssctxt *ctx, Authctxt *authctxt)
    234 {
    235 #ifdef USE_PAM
    236 	char **penv, **tmp_env;
    237 #endif /* USE_PAM */
    238 
    239 	if (authctxt == NULL) {
    240 		error("Missing context while storing GSS-API credentials");
    241 		return;
    242 	}
    243 
    244 	if (ctx == NULL && xxx_gssctxt == NULL)
    245 		return;
    246 
    247 	if (ctx == NULL)
    248 		ctx = xxx_gssctxt;
    249 
    250 	if (!options.gss_cleanup_creds ||
    251 	    ctx->deleg_creds == GSS_C_NO_CREDENTIAL) {
    252 		debug3("Not storing delegated GSS credentials"
    253 		    " (none delegated)");
    254 		return;
    255 	}
    256 
    257 	if (!authctxt->valid || authctxt->pw == NULL) {
    258 		debug3("Not storing delegated GSS credentials"
    259 		    " for invalid user");
    260 		return;
    261 	}
    262 
    263 	debug("Storing delegated GSS-API credentials");
    264 
    265 	/*
    266 	 * The GSS-API has a flaw in that it does not provide a
    267 	 * mechanism by which delegated credentials can be made
    268 	 * available for acquisition by GSS_Acquire_cred() et. al.;
    269 	 * gss_store_cred() is the proposed GSS-API extension for
    270 	 * generically storing delegated credentials.
    271 	 *
    272 	 * gss_store_cred() does not speak to how credential stores are
    273 	 * referenced.  Generically this may be done by switching to the
    274 	 * user context of the user in whose default credential store we
    275 	 * wish to place delegated credentials.  But environment
    276 	 * variables could conceivably affect the choice of credential
    277 	 * store as well, and perhaps in a mechanism-specific manner.
    278 	 *
    279 	 * SUNW -- On Solaris the euid selects the current credential
    280 	 * store, but PAM modules could select alternate stores by
    281 	 * setting, for example, KRB5CCNAME, so we also use the PAM
    282 	 * environment temporarily.
    283 	 */
    284 
    285 #ifdef HAVE_GSS_STORE_CRED
    286 #ifdef USE_PAM
    287 	/*
    288 	 * PAM may have set mechanism-specific variables (e.g.,
    289 	 * KRB5CCNAME).  fetch_pam_environment() protects against LD_*
    290 	 * and other environment variables.
    291 	 */
    292 	penv = fetch_pam_environment(authctxt);
    293 	tmp_env = environ;
    294 	environ = penv;
    295 #endif /* USE_PAM */
    296 	if (authctxt->pw->pw_uid != geteuid()) {
    297 		temporarily_use_uid(authctxt->pw);
    298 		ctx->major = gss_store_cred(&ctx->minor, ctx->deleg_creds,
    299 		    GSS_C_INITIATE, GSS_C_NULL_OID, 0, ctx->default_creds,
    300 		    NULL, NULL);
    301 		restore_uid();
    302 	} else {
    303 		/* only when logging in as the privileged user used by sshd */
    304 		ctx->major = gss_store_cred(&ctx->minor, ctx->deleg_creds,
    305 		    GSS_C_INITIATE, GSS_C_NULL_OID, 0, ctx->default_creds,
    306 		    NULL, NULL);
    307 	}
    308 #ifdef USE_PAM
    309 	environ = tmp_env;
    310 	free_pam_environment(penv);
    311 #endif /* USE_PAM */
    312 	if (GSS_ERROR(ctx->major))
    313 		ssh_gssapi_error(ctx, "storing delegated credentials");
    314 
    315 #else
    316 #ifdef KRB5_GSS
    317 #error "MIT/Heimdal krb5-specific code missing in ssh_gssapi_storecreds()"
    318 	if (ssh_gssapi_is_krb5(ctx->mech))
    319 		ssh_gssapi_krb5_storecreds(ctx);
    320 #endif /* KRB5_GSS */
    321 #ifdef GSI_GSS
    322 #error "GSI krb5-specific code missing in ssh_gssapi_storecreds()"
    323 	if (ssh_gssapi_is_gsi(ctx->mech))
    324 		ssh_gssapi_krb5_storecreds(ctx);
    325 #endif /* GSI_GSS */
    326 /* #error "Mechanism-specific code missing in ssh_gssapi_storecreds()" */
    327 	return;
    328 #endif /* HAVE_GSS_STORE_CRED */
    329 }
    330 
    331 void
    332 ssh_gssapi_do_child(Gssctxt *ctx, char ***envp, uint_t *envsizep)
    333 {
    334 	/*
    335 	 * MIT/Heimdal/GSI specific code goes here.
    336 	 *
    337 	 * On Solaris there's nothing to do here as the GSS store and
    338 	 * related environment variables are to be set by PAM, if at all
    339 	 * (no environment variables are needed to address the default
    340 	 * credential store -- the euid does that).
    341 	 */
    342 #ifdef KRB5_GSS
    343 #error "MIT/Heimdal krb5-specific code missing in ssh_gssapi_storecreds()"
    344 #endif /* KRB5_GSS */
    345 #ifdef GSI_GSS
    346 #error "GSI krb5-specific code missing in ssh_gssapi_storecreds()"
    347 #endif /* GSI_GSS */
    348 }
    349 
    350 int
    351 ssh_gssapi_userok(Gssctxt *ctx, char *user)
    352 {
    353 	if (ctx == NULL) {
    354 		debug3("INTERNAL ERROR: %s", __func__);
    355 		return (0);
    356 	}
    357 
    358 	if (user == NULL || *user == '\0')
    359 		return (0);
    360 
    361 #ifdef HAVE___GSS_USEROK
    362 	{
    363 		int user_ok = 0;
    364 
    365 		ctx->major = __gss_userok(&ctx->minor, ctx->src_name, user,
    366 		    &user_ok);
    367 		if (GSS_ERROR(ctx->major)) {
    368 			debug2("__GSS_userok() failed");
    369 			return (0);
    370 		}
    371 
    372 		if (user_ok)
    373 			return (1);
    374 
    375 		/* fall through */
    376 	}
    377 #else
    378 #ifdef GSSAPI_SIMPLE_USEROK
    379 	{
    380 		/* Mechanism-generic */
    381 		OM_uint32	min;
    382 		gss_buffer_desc	buf, ename1, ename2;
    383 		gss_name_t	iname, cname;
    384 		int		eql;
    385 
    386 		buf.value = user;
    387 		buf.length = strlen(user);
    388 		ctx->major = gss_import_name(&ctx->minor, &buf,
    389 		    GSS_C_NULL_OID, &iname);
    390 		if (GSS_ERROR(ctx->major)) {
    391 			ssh_gssapi_error(ctx,
    392 			    "importing name for authorizing initiator");
    393 			goto failed_simple_userok;
    394 		}
    395 
    396 		ctx->major = gss_canonicalize_name(&ctx->minor, iname,
    397 		    ctx->actual_mech, &cname);
    398 		(void) gss_release_name(&min, &iname);
    399 		if (GSS_ERROR(ctx->major)) {
    400 			ssh_gssapi_error(ctx, "canonicalizing name");
    401 			goto failed_simple_userok;
    402 		}
    403 
    404 		ctx->major = gss_export_name(&ctx->minor, cname, &ename1);
    405 		(void) gss_release_name(&min, &cname);
    406 		if (GSS_ERROR(ctx->major)) {
    407 			ssh_gssapi_error(ctx, "exporting name");
    408 			goto failed_simple_userok;
    409 		}
    410 
    411 		ctx->major = gss_export_name(&ctx->minor, ctx->src_name,
    412 		    &ename2);
    413 		if (GSS_ERROR(ctx->major)) {
    414 			ssh_gssapi_error(ctx,
    415 			    "exporting client principal name");
    416 			(void) gss_release_buffer(&min, &ename1);
    417 			goto failed_simple_userok;
    418 		}
    419 
    420 		eql = (ename1.length == ename2.length &&
    421 		    memcmp(ename1.value, ename2.value, ename1.length) == 0);
    422 
    423 		(void) gss_release_buffer(&min, &ename1);
    424 		(void) gss_release_buffer(&min, &ename2);
    425 
    426 		if (eql)
    427 			return (1);
    428 		/* fall through */
    429 	}
    430 failed_simple_userok:
    431 #endif /* GSSAPI_SIMPLE_USEROK */
    432 #ifdef HAVE_GSSCRED_API
    433 	{
    434 		/* Mechanism-generic, Solaris-specific */
    435 		OM_uint32	 maj;
    436 		uid_t		 uid;
    437 		struct passwd	*pw;
    438 
    439 		maj = gsscred_name_to_unix_cred(ctx->src_name,
    440 		    ctx->actual_mech, &uid, NULL, NULL, NULL);
    441 
    442 		if (GSS_ERROR(maj))
    443 			goto failed_simple_gsscred_userok;
    444 
    445 		if ((pw = getpwnam(user)) == NULL)
    446 			goto failed_simple_gsscred_userok;
    447 
    448 		if (pw->pw_uid == uid)
    449 			return (1);
    450 		/* fall through */
    451 	}
    452 
    453 failed_simple_gsscred_userok:
    454 #endif /* HAVE_GSSCRED_API */
    455 #ifdef KRB5_GSS
    456 	if (ssh_gssapi_is_krb5(ctx->mech))
    457 		if (ssh_gssapi_krb5_userok(ctx->src_name, user))
    458 			return (1);
    459 #endif /* KRB5_GSS */
    460 #ifdef GSI_GSS
    461 	if (ssh_gssapi_is_gsi(ctx->mech))
    462 		if (ssh_gssapi_gsi_userok(ctx->src_name, user))
    463 			return (1);
    464 #endif /* GSI_GSS */
    465 #endif /* HAVE___GSS_USEROK */
    466 
    467 	/* default to not authorized */
    468 	return (0);
    469 }
    470 
    471 char *
    472 ssh_gssapi_localname(Gssctxt *ctx)
    473 {
    474 	if (ctx == NULL) {
    475 		debug3("INTERNAL ERROR: %s", __func__);
    476 		return (NULL);
    477 	}
    478 
    479 	debug2("Mapping initiator GSS-API principal to local username");
    480 #ifdef HAVE_GSSCRED_API
    481 	{
    482 		/* Mechanism-generic, Solaris-specific */
    483 		OM_uint32	 maj;
    484 		uid_t		 uid;
    485 		struct passwd	*pw;
    486 
    487 		if (ctx->src_name == GSS_C_NO_NAME)
    488 			goto failed_gsscred_localname;
    489 
    490 		maj = gsscred_name_to_unix_cred(ctx->src_name,
    491 		    ctx->actual_mech, &uid, NULL, NULL, NULL);
    492 
    493 		if (GSS_ERROR(maj))
    494 			goto failed_gsscred_localname;
    495 
    496 		if ((pw = getpwuid(uid)) == NULL)
    497 			goto failed_gsscred_localname;
    498 
    499 		debug2("Mapped the initiator to: %s", pw->pw_name);
    500 		return (xstrdup(pw->pw_name));
    501 	}
    502 failed_gsscred_localname:
    503 #endif /* HAVE_GSSCRED_API */
    504 #ifdef KRB5_GSS
    505 #error "ssh_gssapi_krb5_localname() not implemented"
    506 	if (ssh_gssapi_is_krb5(ctx->mech))
    507 		return (ssh_gssapi_krb5_localname(ctx->src_name));
    508 #endif /* KRB5_GSS */
    509 #ifdef GSI_GSS
    510 #error "ssh_gssapi_gsi_localname() not implemented"
    511 	if (ssh_gssapi_is_gsi(ctx->mech))
    512 		return (ssh_gssapi_gsi_localname(ctx->src_name));
    513 #endif /* GSI_GSS */
    514 	return (NULL);
    515 }
    516 #endif /* GSSAPI */
    517