1/* $OpenBSD: gss-genr.c,v 1.20 2009/06/22 05:39:28 dtucker 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 "xmalloc.h" 39#include "buffer.h" 40#include "log.h" 41#include "ssh2.h" 42#include "cipher.h" 43#include "key.h" 44#include "kex.h" 45#ifdef __APPLE_CRYPTO__ 46#include "ossl-evp.h" 47#else 48#include <openssl/evp.h> 49#endif 50 51#include "ssh-gss.h" 52 53extern u_char *session_id2; 54extern u_int session_id2_len; 55 56typedef struct { 57 char *encoded; 58 gss_OID oid; 59} ssh_gss_kex_mapping; 60 61/* 62 * XXX - It would be nice to find a more elegant way of handling the 63 * XXX passing of the key exchange context to the userauth routines 64 */ 65 66Gssctxt *gss_kex_context = NULL; 67 68static ssh_gss_kex_mapping *gss_enc2oid = NULL; 69 70int 71ssh_gssapi_oid_table_ok() { 72 return (gss_enc2oid != NULL); 73} 74 75/* 76 * Return a list of the gss-group1-sha1 mechanisms supported by this program 77 * 78 * We test mechanisms to ensure that we can use them, to avoid starting 79 * a key exchange with a bad mechanism 80 */ 81 82char * 83ssh_gssapi_client_mechanisms(const char *host, const char *client) { 84 gss_OID_set gss_supported; 85 OM_uint32 min_status; 86 87 if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported))) 88 return NULL; 89 90 return(ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism, 91 host, client)); 92} 93 94char * 95ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check, 96 const char *host, const char *client) { 97 Buffer buf; 98 size_t i; 99 int oidpos, enclen; 100 char *mechs, *encoded; 101 u_char digest[EVP_MAX_MD_SIZE]; 102 char deroid[2]; 103 const EVP_MD *evp_md = EVP_md5(); 104 EVP_MD_CTX md; 105 106 if (gss_enc2oid != NULL) { 107 for (i = 0; gss_enc2oid[i].encoded != NULL; i++) 108 xfree(gss_enc2oid[i].encoded); 109 xfree(gss_enc2oid); 110 } 111 112 gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) * 113 (gss_supported->count + 1)); 114 115 buffer_init(&buf); 116 117 oidpos = 0; 118 for (i = 0; i < gss_supported->count; i++) { 119 if (gss_supported->elements[i].length < 128 && 120 (*check)(NULL, &(gss_supported->elements[i]), host, client)) { 121 122 deroid[0] = SSH_GSS_OIDTYPE; 123 deroid[1] = gss_supported->elements[i].length; 124 125 EVP_DigestInit(&md, evp_md); 126 EVP_DigestUpdate(&md, deroid, 2); 127 EVP_DigestUpdate(&md, 128 gss_supported->elements[i].elements, 129 gss_supported->elements[i].length); 130 EVP_DigestFinal(&md, digest, NULL); 131 132 encoded = xmalloc(EVP_MD_size(evp_md) * 2); 133 enclen = __b64_ntop(digest, EVP_MD_size(evp_md), 134 encoded, EVP_MD_size(evp_md) * 2); 135 136 if (oidpos != 0) 137 buffer_put_char(&buf, ','); 138 139 buffer_append(&buf, KEX_GSS_GEX_SHA1_ID, 140 sizeof(KEX_GSS_GEX_SHA1_ID) - 1); 141 buffer_append(&buf, encoded, enclen); 142 buffer_put_char(&buf, ','); 143 buffer_append(&buf, KEX_GSS_GRP1_SHA1_ID, 144 sizeof(KEX_GSS_GRP1_SHA1_ID) - 1); 145 buffer_append(&buf, encoded, enclen); 146 buffer_put_char(&buf, ','); 147 buffer_append(&buf, KEX_GSS_GRP14_SHA1_ID, 148 sizeof(KEX_GSS_GRP14_SHA1_ID) - 1); 149 buffer_append(&buf, encoded, enclen); 150 151 gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]); 152 gss_enc2oid[oidpos].encoded = encoded; 153 oidpos++; 154 } 155 } 156 gss_enc2oid[oidpos].oid = NULL; 157 gss_enc2oid[oidpos].encoded = NULL; 158 159 buffer_put_char(&buf, '\0'); 160 161 mechs = xmalloc(buffer_len(&buf)); 162 buffer_get(&buf, mechs, buffer_len(&buf)); 163 buffer_free(&buf); 164 165 if (strlen(mechs) == 0) { 166 xfree(mechs); 167 mechs = NULL; 168 } 169 170 return (mechs); 171} 172 173gss_OID 174ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) { 175 int i = 0; 176 177 switch (kex_type) { 178 case KEX_GSS_GRP1_SHA1: 179 if (strlen(name) < sizeof(KEX_GSS_GRP1_SHA1_ID)) 180 return GSS_C_NO_OID; 181 name += sizeof(KEX_GSS_GRP1_SHA1_ID) - 1; 182 break; 183 case KEX_GSS_GRP14_SHA1: 184 if (strlen(name) < sizeof(KEX_GSS_GRP14_SHA1_ID)) 185 return GSS_C_NO_OID; 186 name += sizeof(KEX_GSS_GRP14_SHA1_ID) - 1; 187 break; 188 case KEX_GSS_GEX_SHA1: 189 if (strlen(name) < sizeof(KEX_GSS_GEX_SHA1_ID)) 190 return GSS_C_NO_OID; 191 name += sizeof(KEX_GSS_GEX_SHA1_ID) - 1; 192 break; 193 default: 194 return GSS_C_NO_OID; 195 } 196 197 while (gss_enc2oid[i].encoded != NULL && 198 strcmp(name, gss_enc2oid[i].encoded) != 0) 199 i++; 200 201 if (gss_enc2oid[i].oid != NULL && ctx != NULL) 202 ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid); 203 204 return gss_enc2oid[i].oid; 205} 206 207/* Check that the OID in a data stream matches that in the context */ 208int 209ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len) 210{ 211 return (ctx != NULL && ctx->oid != GSS_C_NO_OID && 212 ctx->oid->length == len && 213 memcmp(ctx->oid->elements, data, len) == 0); 214} 215 216/* Set the contexts OID from a data stream */ 217void 218ssh_gssapi_set_oid_data(Gssctxt *ctx, void *data, size_t len) 219{ 220 if (ctx->oid != GSS_C_NO_OID) { 221 xfree(ctx->oid->elements); 222 xfree(ctx->oid); 223 } 224 ctx->oid = xmalloc(sizeof(gss_OID_desc)); 225 ctx->oid->length = len; 226 ctx->oid->elements = xmalloc(len); 227 memcpy(ctx->oid->elements, data, len); 228} 229 230/* Set the contexts OID */ 231void 232ssh_gssapi_set_oid(Gssctxt *ctx, gss_OID oid) 233{ 234 ssh_gssapi_set_oid_data(ctx, oid->elements, oid->length); 235} 236 237/* All this effort to report an error ... */ 238void 239ssh_gssapi_error(Gssctxt *ctxt) 240{ 241 char *s; 242 243 s = ssh_gssapi_last_error(ctxt, NULL, NULL); 244 debug("%s", s); 245 xfree(s); 246} 247 248char * 249ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *major_status, 250 OM_uint32 *minor_status) 251{ 252 OM_uint32 lmin; 253 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; 254 OM_uint32 ctx; 255 Buffer b; 256 char *ret; 257 258 buffer_init(&b); 259 260 if (major_status != NULL) 261 *major_status = ctxt->major; 262 if (minor_status != NULL) 263 *minor_status = ctxt->minor; 264 265 ctx = 0; 266 /* The GSSAPI error */ 267 do { 268 gss_display_status(&lmin, ctxt->major, 269 GSS_C_GSS_CODE, ctxt->oid, &ctx, &msg); 270 271 buffer_append(&b, msg.value, msg.length); 272 buffer_put_char(&b, '\n'); 273 274 gss_release_buffer(&lmin, &msg); 275 } while (ctx != 0); 276 277 /* The mechanism specific error */ 278 do { 279 gss_display_status(&lmin, ctxt->minor, 280 GSS_C_MECH_CODE, ctxt->oid, &ctx, &msg); 281 282 buffer_append(&b, msg.value, msg.length); 283 buffer_put_char(&b, '\n'); 284 285 gss_release_buffer(&lmin, &msg); 286 } while (ctx != 0); 287 288 buffer_put_char(&b, '\0'); 289 ret = xmalloc(buffer_len(&b)); 290 buffer_get(&b, ret, buffer_len(&b)); 291 buffer_free(&b); 292 return (ret); 293} 294 295/* 296 * Initialise our GSSAPI context. We use this opaque structure to contain all 297 * of the data which both the client and server need to persist across 298 * {accept,init}_sec_context calls, so that when we do it from the userauth 299 * stuff life is a little easier 300 */ 301void 302ssh_gssapi_build_ctx(Gssctxt **ctx) 303{ 304 *ctx = xcalloc(1, sizeof (Gssctxt)); 305 (*ctx)->context = GSS_C_NO_CONTEXT; 306 (*ctx)->name = GSS_C_NO_NAME; 307 (*ctx)->oid = GSS_C_NO_OID; 308 (*ctx)->creds = GSS_C_NO_CREDENTIAL; 309 (*ctx)->client = GSS_C_NO_NAME; 310 (*ctx)->client_creds = GSS_C_NO_CREDENTIAL; 311} 312 313/* Delete our context, providing it has been built correctly */ 314void 315ssh_gssapi_delete_ctx(Gssctxt **ctx) 316{ 317 OM_uint32 ms; 318 319 if ((*ctx) == NULL) 320 return; 321 if ((*ctx)->context != GSS_C_NO_CONTEXT) 322 gss_delete_sec_context(&ms, &(*ctx)->context, GSS_C_NO_BUFFER); 323 if ((*ctx)->name != GSS_C_NO_NAME) 324 gss_release_name(&ms, &(*ctx)->name); 325 if ((*ctx)->oid != GSS_C_NO_OID) { 326 xfree((*ctx)->oid->elements); 327 xfree((*ctx)->oid); 328 (*ctx)->oid = GSS_C_NO_OID; 329 } 330 if ((*ctx)->creds != GSS_C_NO_CREDENTIAL) 331 gss_release_cred(&ms, &(*ctx)->creds); 332 if ((*ctx)->client != GSS_C_NO_NAME) 333 gss_release_name(&ms, &(*ctx)->client); 334 if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL) 335 gss_release_cred(&ms, &(*ctx)->client_creds); 336 337 xfree(*ctx); 338 *ctx = NULL; 339} 340 341/* 342 * Wrapper to init_sec_context 343 * Requires that the context contains: 344 * oid 345 * server name (from ssh_gssapi_import_name) 346 */ 347OM_uint32 348ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok, 349 gss_buffer_desc* send_tok, OM_uint32 *flags) 350{ 351 int deleg_flag = 0; 352 353 if (deleg_creds) { 354 deleg_flag = GSS_C_DELEG_FLAG; 355 debug("Delegating credentials"); 356 } 357 358 ctx->major = gss_init_sec_context(&ctx->minor, 359 ctx->client_creds, &ctx->context, ctx->name, ctx->oid, 360 GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag, 361 0, NULL, recv_tok, NULL, send_tok, flags, NULL); 362 363 if (GSS_ERROR(ctx->major)) 364 ssh_gssapi_error(ctx); 365 366 return (ctx->major); 367} 368 369/* Create a service name for the given host */ 370OM_uint32 371ssh_gssapi_import_name(Gssctxt *ctx, const char *host) 372{ 373 gss_buffer_desc gssbuf; 374 char *val; 375 376 xasprintf(&val, "host@%s", host); 377 gssbuf.value = val; 378 gssbuf.length = strlen(gssbuf.value); 379 380 if ((ctx->major = gss_import_name(&ctx->minor, 381 &gssbuf, GSS_C_NT_HOSTBASED_SERVICE, &ctx->name))) 382 ssh_gssapi_error(ctx); 383 384 xfree(gssbuf.value); 385 return (ctx->major); 386} 387 388OM_uint32 389ssh_gssapi_client_identity(Gssctxt *ctx, const char *name) 390{ 391 gss_buffer_desc gssbuf; 392 gss_name_t gssname; 393 OM_uint32 status; 394 gss_OID_set oidset; 395 396 gssbuf.value = (void *) name; 397 gssbuf.length = strlen(gssbuf.value); 398 399 gss_create_empty_oid_set(&status, &oidset); 400 gss_add_oid_set_member(&status, ctx->oid, &oidset); 401 402 ctx->major = gss_import_name(&ctx->minor, &gssbuf, 403 GSS_C_NT_USER_NAME, &gssname); 404 405 if (!ctx->major) 406 ctx->major = gss_acquire_cred(&ctx->minor, 407 gssname, 0, oidset, GSS_C_INITIATE, 408 &ctx->client_creds, NULL, NULL); 409 410 gss_release_name(&status, &gssname); 411 gss_release_oid_set(&status, &oidset); 412 413 if (ctx->major) 414 ssh_gssapi_error(ctx); 415 416 return(ctx->major); 417} 418 419OM_uint32 420ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash) 421{ 422 if (ctx == NULL) 423 return -1; 424 425 if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context, 426 GSS_C_QOP_DEFAULT, buffer, hash))) 427 ssh_gssapi_error(ctx); 428 429 return (ctx->major); 430} 431 432/* Priviledged when used by server */ 433OM_uint32 434ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic) 435{ 436 if (ctx == NULL) 437 return -1; 438 439 ctx->major = gss_verify_mic(&ctx->minor, ctx->context, 440 gssbuf, gssmic, NULL); 441 442 return (ctx->major); 443} 444 445void 446ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service, 447 const char *context) 448{ 449 buffer_init(b); 450 buffer_put_string(b, session_id2, session_id2_len); 451 buffer_put_char(b, SSH2_MSG_USERAUTH_REQUEST); 452 buffer_put_cstring(b, user); 453 buffer_put_cstring(b, service); 454 buffer_put_cstring(b, context); 455} 456 457int 458ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host, 459 const char *client) 460{ 461 gss_buffer_desc token = GSS_C_EMPTY_BUFFER; 462 OM_uint32 major, minor; 463 gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; 464 Gssctxt *intctx = NULL; 465 466 if (ctx == NULL) 467 ctx = &intctx; 468 469 /* RFC 4462 says we MUST NOT do SPNEGO */ 470 if (oid->length == spnego_oid.length && 471 (memcmp(oid->elements, spnego_oid.elements, oid->length) == 0)) 472 return 0; /* false */ 473 474 ssh_gssapi_build_ctx(ctx); 475 ssh_gssapi_set_oid(*ctx, oid); 476 major = ssh_gssapi_import_name(*ctx, host); 477 478 if (!GSS_ERROR(major) && client) 479 major = ssh_gssapi_client_identity(*ctx, client); 480 481 if (!GSS_ERROR(major)) { 482 major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token, 483 NULL); 484 gss_release_buffer(&minor, &token); 485 if ((*ctx)->context != GSS_C_NO_CONTEXT) 486 gss_delete_sec_context(&minor, &(*ctx)->context, 487 GSS_C_NO_BUFFER); 488 } 489 490 if (GSS_ERROR(major) || intctx != NULL) 491 ssh_gssapi_delete_ctx(ctx); 492 493 return (!GSS_ERROR(major)); 494} 495 496int 497ssh_gssapi_credentials_updated(Gssctxt *ctxt) { 498 static gss_name_t saved_name = GSS_C_NO_NAME; 499 static OM_uint32 saved_lifetime = 0; 500 static gss_OID saved_mech = GSS_C_NO_OID; 501 static gss_name_t name; 502 static OM_uint32 last_call = 0; 503 OM_uint32 lifetime, now, major, minor; 504 int equal; 505 gss_cred_usage_t usage = GSS_C_INITIATE; 506 507 now = time(NULL); 508 509 if (ctxt) { 510 debug("Rekey has happened - updating saved versions"); 511 512 if (saved_name != GSS_C_NO_NAME) 513 gss_release_name(&minor, &saved_name); 514 515 major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, 516 &saved_name, &saved_lifetime, NULL, NULL); 517 518 if (!GSS_ERROR(major)) { 519 saved_mech = ctxt->oid; 520 saved_lifetime+= now; 521 } else { 522 /* Handle the error */ 523 } 524 return 0; 525 } 526 527 if (now - last_call < 10) 528 return 0; 529 530 last_call = now; 531 532 if (saved_mech == GSS_C_NO_OID) 533 return 0; 534 535 major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL, 536 &name, &lifetime, NULL, NULL); 537 if (major == GSS_S_CREDENTIALS_EXPIRED) 538 return 0; 539 else if (GSS_ERROR(major)) 540 return 0; 541 542 major = gss_compare_name(&minor, saved_name, name, &equal); 543 gss_release_name(&minor, &name); 544 if (GSS_ERROR(major)) 545 return 0; 546 547 if (equal && (saved_lifetime < lifetime + now - 10)) 548 return 1; 549 550 return 0; 551} 552 553#endif /* GSSAPI */ 554