1/* $OpenBSD: gss-serv.c,v 1.23 2011/08/01 19:18:15 markus Exp $ */ 2 3/* 4 * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "includes.h" 28 29#ifdef GSSAPI 30 31#include <sys/types.h> 32#include <sys/param.h> 33 34#include <stdarg.h> 35#include <string.h> 36#include <unistd.h> 37 38#include "openbsd-compat/sys-queue.h" 39#include "xmalloc.h" 40#include "buffer.h" 41#include "key.h" 42#include "hostfile.h" 43#include "auth.h" 44#include "log.h" 45#include "channels.h" 46#include "session.h" 47#include "misc.h" 48#include "servconf.h" 49#include "uidswap.h" 50 51#include "ssh-gss.h" 52#include "monitor_wrap.h" 53 54extern ServerOptions options; 55 56static ssh_gssapi_client gssapi_client = 57 { GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER, 58 GSS_C_NO_CREDENTIAL, GSS_C_NO_NAME, NULL, {NULL, NULL, NULL}, 0, 0}; 59 60ssh_gssapi_mech gssapi_null_mech = 61 { NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL, NULL}; 62 63#ifdef KRB5 64extern ssh_gssapi_mech gssapi_kerberos_mech; 65#endif 66 67ssh_gssapi_mech* supported_mechs[]= { 68#ifdef KRB5 69 &gssapi_kerberos_mech, 70#endif 71 &gssapi_null_mech, 72}; 73 74 75/* 76 * Acquire credentials for a server running on the current host. 77 * Requires that the context structure contains a valid OID 78 */ 79 80/* Returns a GSSAPI error code */ 81/* Privileged (called from ssh_gssapi_server_ctx) */ 82static OM_uint32 83ssh_gssapi_acquire_cred(Gssctxt *ctx) 84{ 85 OM_uint32 status; 86 char lname[MAXHOSTNAMELEN]; 87 gss_OID_set oidset; 88 89 if (options.gss_strict_acceptor) { 90 gss_create_empty_oid_set(&status, &oidset); 91 gss_add_oid_set_member(&status, ctx->oid, &oidset); 92 93 if (gethostname(lname, MAXHOSTNAMELEN)) { 94 gss_release_oid_set(&status, &oidset); 95 return (-1); 96 } 97 98 if (GSS_ERROR(ssh_gssapi_import_name(ctx, lname))) { 99 gss_release_oid_set(&status, &oidset); 100 return (ctx->major); 101 } 102 103 if ((ctx->major = gss_acquire_cred(&ctx->minor, 104 ctx->name, 0, oidset, GSS_C_ACCEPT, &ctx->creds, 105 NULL, NULL))) 106 ssh_gssapi_error(ctx); 107 108 gss_release_oid_set(&status, &oidset); 109 return (ctx->major); 110 } else { 111 ctx->name = GSS_C_NO_NAME; 112 ctx->creds = GSS_C_NO_CREDENTIAL; 113 } 114 return GSS_S_COMPLETE; 115} 116 117/* Privileged */ 118OM_uint32 119ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) 120{ 121 if (*ctx) 122 ssh_gssapi_delete_ctx(ctx); 123 ssh_gssapi_build_ctx(ctx); 124 ssh_gssapi_set_oid(*ctx, oid); 125 return (ssh_gssapi_acquire_cred(*ctx)); 126} 127 128/* Unprivileged */ 129char * 130ssh_gssapi_server_mechanisms() { 131 gss_OID_set supported; 132 133 ssh_gssapi_supported_oids(&supported); 134 return (ssh_gssapi_kex_mechs(supported, &ssh_gssapi_server_check_mech, 135 NULL, NULL)); 136} 137 138/* Unprivileged */ 139int 140ssh_gssapi_server_check_mech(Gssctxt **dum, gss_OID oid, const char *data, 141 const char *dummy) { 142 Gssctxt *ctx = NULL; 143 int res; 144 145 res = !GSS_ERROR(PRIVSEP(ssh_gssapi_server_ctx(&ctx, oid))); 146 ssh_gssapi_delete_ctx(&ctx); 147 148 return (res); 149} 150 151/* Unprivileged */ 152void 153ssh_gssapi_supported_oids(gss_OID_set *oidset) 154{ 155 int i = 0; 156 OM_uint32 min_status; 157 int present; 158 gss_OID_set supported; 159 160 gss_create_empty_oid_set(&min_status, oidset); 161 162 if (GSS_ERROR(gss_indicate_mechs(&min_status, &supported))) 163 return; 164 165 while (supported_mechs[i]->name != NULL) { 166 if (GSS_ERROR(gss_test_oid_set_member(&min_status, 167 &supported_mechs[i]->oid, supported, &present))) 168 present = 0; 169 if (present) 170 gss_add_oid_set_member(&min_status, 171 &supported_mechs[i]->oid, oidset); 172 i++; 173 } 174 175 gss_release_oid_set(&min_status, &supported); 176} 177 178 179/* Wrapper around accept_sec_context 180 * Requires that the context contains: 181 * oid 182 * credentials (from ssh_gssapi_acquire_cred) 183 */ 184/* Privileged */ 185OM_uint32 186ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_desc *recv_tok, 187 gss_buffer_desc *send_tok, OM_uint32 *flags) 188{ 189 OM_uint32 status; 190 gss_OID mech; 191 192 ctx->major = gss_accept_sec_context(&ctx->minor, 193 &ctx->context, ctx->creds, recv_tok, 194 GSS_C_NO_CHANNEL_BINDINGS, &ctx->client, &mech, 195 send_tok, flags, NULL, &ctx->client_creds); 196 197 if (GSS_ERROR(ctx->major)) 198 ssh_gssapi_error(ctx); 199 200 if (ctx->client_creds) 201 debug("Received some client credentials"); 202 else 203 debug("Got no client credentials"); 204 205 status = ctx->major; 206 207 /* Now, if we're complete and we have the right flags, then 208 * we flag the user as also having been authenticated 209 */ 210 211 if (((flags == NULL) || ((*flags & GSS_C_MUTUAL_FLAG) && 212 (*flags & GSS_C_INTEG_FLAG))) && (ctx->major == GSS_S_COMPLETE)) { 213 if (ssh_gssapi_getclient(ctx, &gssapi_client)) 214 fatal("Couldn't convert client name"); 215 } 216 217 return (status); 218} 219 220/* 221 * This parses an exported name, extracting the mechanism specific portion 222 * to use for ACL checking. It verifies that the name belongs the mechanism 223 * originally selected. 224 */ 225static OM_uint32 226ssh_gssapi_parse_ename(Gssctxt *ctx, gss_buffer_t ename, gss_buffer_t name) 227{ 228 u_char *tok; 229 OM_uint32 offset; 230 OM_uint32 oidl; 231 232 tok = ename->value; 233 234 /* 235 * Check that ename is long enough for all of the fixed length 236 * header, and that the initial ID bytes are correct 237 */ 238 239 if (ename->length < 6 || memcmp(tok, "\x04\x01", 2) != 0) 240 return GSS_S_FAILURE; 241 242 /* 243 * Extract the OID, and check it. Here GSSAPI breaks with tradition 244 * and does use the OID type and length bytes. To confuse things 245 * there are two lengths - the first including these, and the 246 * second without. 247 */ 248 249 oidl = get_u16(tok+2); /* length including next two bytes */ 250 oidl = oidl-2; /* turn it into the _real_ length of the variable OID */ 251 252 /* 253 * Check the BER encoding for correct type and length, that the 254 * string is long enough and that the OID matches that in our context 255 */ 256 if (tok[4] != 0x06 || tok[5] != oidl || 257 ename->length < oidl+6 || 258 !ssh_gssapi_check_oid(ctx, tok+6, oidl)) 259 return GSS_S_FAILURE; 260 261 offset = oidl+6; 262 263 if (ename->length < offset+4) 264 return GSS_S_FAILURE; 265 266 name->length = get_u32(tok+offset); 267 offset += 4; 268 269 if (UINT_MAX - offset < name->length) 270 return GSS_S_FAILURE; 271 if (ename->length < offset+name->length) 272 return GSS_S_FAILURE; 273 274 name->value = xmalloc(name->length+1); 275 memcpy(name->value, tok+offset, name->length); 276 ((char *)name->value)[name->length] = 0; 277 278 return GSS_S_COMPLETE; 279} 280 281/* Extract the client details from a given context. This can only reliably 282 * be called once for a context */ 283 284/* Privileged (called from accept_secure_ctx) */ 285OM_uint32 286ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client) 287{ 288 int i = 0; 289 int equal = 0; 290 gss_name_t new_name = GSS_C_NO_NAME; 291 gss_buffer_desc ename = GSS_C_EMPTY_BUFFER; 292 293 if (options.gss_store_rekey && client->used && ctx->client_creds) { 294 if (client->mech->oid.length != ctx->oid->length || 295 (memcmp(client->mech->oid.elements, 296 ctx->oid->elements, ctx->oid->length) !=0)) { 297 debug("Rekeyed credentials have different mechanism"); 298 return GSS_S_COMPLETE; 299 } 300 301 if ((ctx->major = gss_inquire_cred_by_mech(&ctx->minor, 302 ctx->client_creds, ctx->oid, &new_name, 303 NULL, NULL, NULL))) { 304 ssh_gssapi_error(ctx); 305 return (ctx->major); 306 } 307 308 ctx->major = gss_compare_name(&ctx->minor, client->name, 309 new_name, &equal); 310 311 if (GSS_ERROR(ctx->major)) { 312 ssh_gssapi_error(ctx); 313 return (ctx->major); 314 } 315 316 if (!equal) { 317 debug("Rekeyed credentials have different name"); 318 return GSS_S_COMPLETE; 319 } 320 321 debug("Marking rekeyed credentials for export"); 322 323 gss_release_name(&ctx->minor, &client->name); 324 gss_release_cred(&ctx->minor, &client->creds); 325 client->name = new_name; 326 client->creds = ctx->client_creds; 327 ctx->client_creds = GSS_C_NO_CREDENTIAL; 328 client->updated = 1; 329 return GSS_S_COMPLETE; 330 } 331 332 client->mech = NULL; 333 334 while (supported_mechs[i]->name != NULL) { 335 if (supported_mechs[i]->oid.length == ctx->oid->length && 336 (memcmp(supported_mechs[i]->oid.elements, 337 ctx->oid->elements, ctx->oid->length) == 0)) 338 client->mech = supported_mechs[i]; 339 i++; 340 } 341 342 if (client->mech == NULL) 343 return GSS_S_FAILURE; 344 345 if (ctx->client_creds && 346 (ctx->major = gss_inquire_cred_by_mech(&ctx->minor, 347 ctx->client_creds, ctx->oid, &client->name, NULL, NULL, NULL))) { 348 ssh_gssapi_error(ctx); 349 return (ctx->major); 350 } 351 352 if ((ctx->major = gss_display_name(&ctx->minor, ctx->client, 353 &client->displayname, NULL))) { 354 ssh_gssapi_error(ctx); 355 return (ctx->major); 356 } 357 358 if ((ctx->major = gss_export_name(&ctx->minor, ctx->client, 359 &ename))) { 360 ssh_gssapi_error(ctx); 361 return (ctx->major); 362 } 363 364 if ((ctx->major = ssh_gssapi_parse_ename(ctx,&ename, 365 &client->exportedname))) { 366 return (ctx->major); 367 } 368 369 gss_release_buffer(&ctx->minor, &ename); 370 371 /* We can't copy this structure, so we just move the pointer to it */ 372 client->creds = ctx->client_creds; 373 ctx->client_creds = GSS_C_NO_CREDENTIAL; 374 return (ctx->major); 375} 376 377/* As user - called on fatal/exit */ 378void 379ssh_gssapi_cleanup_creds(void) 380{ 381 if (gssapi_client.store.filename != NULL) { 382 /* Unlink probably isn't sufficient */ 383 debug("removing gssapi cred file\"%s\"", 384 gssapi_client.store.filename); 385 unlink(gssapi_client.store.filename); 386 } 387} 388 389/* As user */ 390void 391ssh_gssapi_storecreds(void) 392{ 393 if (gssapi_client.mech && gssapi_client.mech->storecreds) { 394 (*gssapi_client.mech->storecreds)(&gssapi_client); 395 } else 396 debug("ssh_gssapi_storecreds: Not a GSSAPI mechanism"); 397} 398 399/* This allows GSSAPI methods to do things to the childs environment based 400 * on the passed authentication process and credentials. 401 */ 402/* As user */ 403void 404ssh_gssapi_do_child(char ***envp, u_int *envsizep) 405{ 406 407 if (gssapi_client.store.envvar != NULL && 408 gssapi_client.store.envval != NULL) { 409 debug("Setting %s to %s", gssapi_client.store.envvar, 410 gssapi_client.store.envval); 411 child_set_env(envp, envsizep, gssapi_client.store.envvar, 412 gssapi_client.store.envval); 413 } 414} 415 416/* Privileged */ 417int 418ssh_gssapi_userok(char *user, struct passwd *pw) 419{ 420 OM_uint32 lmin; 421 422 if (gssapi_client.exportedname.length == 0 || 423 gssapi_client.exportedname.value == NULL) { 424 debug("No suitable client data"); 425 return 0; 426 } 427 if (gssapi_client.mech && gssapi_client.mech->userok) 428 if ((*gssapi_client.mech->userok)(&gssapi_client, user)) { 429 gssapi_client.used = 1; 430 gssapi_client.store.owner = pw; 431 return 1; 432 } else { 433 /* Destroy delegated credentials if userok fails */ 434 gss_release_buffer(&lmin, &gssapi_client.displayname); 435 gss_release_buffer(&lmin, &gssapi_client.exportedname); 436 gss_release_cred(&lmin, &gssapi_client.creds); 437 memset(&gssapi_client, 0, sizeof(ssh_gssapi_client)); 438 return 0; 439 } 440 else 441 debug("ssh_gssapi_userok: Unknown GSSAPI mechanism"); 442 return (0); 443} 444 445/* These bits are only used for rekeying. The unpriviledged child is running 446 * as the user, the monitor is root. 447 * 448 * In the child, we want to : 449 * *) Ask the monitor to store our credentials into the store we specify 450 * *) If it succeeds, maybe do a PAM update 451 */ 452 453/* Stuff for PAM */ 454 455#ifdef USE_PAM 456static int ssh_gssapi_simple_conv(int n, const struct pam_message **msg, 457 struct pam_response **resp, void *data) 458{ 459 return (PAM_CONV_ERR); 460} 461#endif 462 463void 464ssh_gssapi_rekey_creds() { 465 int ok; 466 int ret; 467#ifdef USE_PAM 468 pam_handle_t *pamh = NULL; 469 struct pam_conv pamconv = {ssh_gssapi_simple_conv, NULL}; 470 char *envstr; 471#endif 472 473 if (gssapi_client.store.filename == NULL && 474 gssapi_client.store.envval == NULL && 475 gssapi_client.store.envvar == NULL) 476 return; 477 478 ok = PRIVSEP(ssh_gssapi_update_creds(&gssapi_client.store)); 479 480 if (!ok) 481 return; 482 483 debug("Rekeyed credentials stored successfully"); 484 485 /* Actually managing to play with the ssh pam stack from here will 486 * be next to impossible. In any case, we may want different options 487 * for rekeying. So, use our own :) 488 */ 489#ifdef USE_PAM 490 if (!use_privsep) { 491 debug("Not even going to try and do PAM with privsep disabled"); 492 return; 493 } 494 495 ret = pam_start("sshd-rekey", gssapi_client.store.owner->pw_name, 496 &pamconv, &pamh); 497 if (ret) 498 return; 499 500 xasprintf(&envstr, "%s=%s", gssapi_client.store.envvar, 501 gssapi_client.store.envval); 502 503 ret = pam_putenv(pamh, envstr); 504 if (!ret) 505 pam_setcred(pamh, PAM_REINITIALIZE_CRED); 506 pam_end(pamh, PAM_SUCCESS); 507#endif 508} 509 510int 511ssh_gssapi_update_creds(ssh_gssapi_ccache *store) { 512 int ok = 0; 513 514 /* Check we've got credentials to store */ 515 if (!gssapi_client.updated) 516 return 0; 517 518 gssapi_client.updated = 0; 519 520 temporarily_use_uid(gssapi_client.store.owner); 521 if (gssapi_client.mech && gssapi_client.mech->updatecreds) 522 ok = (*gssapi_client.mech->updatecreds)(store, &gssapi_client); 523 else 524 debug("No update function for this mechanism"); 525 526 restore_uid(); 527 528 return ok; 529} 530 531#endif 532