1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * Copyright (C) 2011-2018 PADL Software Pty Ltd. 4 * 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 * 10 * * Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * * Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 * OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "k5-platform.h" 33 #include "gssapiP_spnego.h" 34 #include <generic/gssapiP_generic.h> 35 36 /* 37 * The initial context token emitted by the initiator is a INITIATOR_NEGO 38 * message followed by zero or more INITIATOR_META_DATA tokens, and zero 39 * or one AP_REQUEST tokens. 40 * 41 * Upon receiving this, the acceptor computes the list of mutually supported 42 * authentication mechanisms and performs the metadata exchange. The output 43 * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens, 44 * and zero or one CHALLENGE tokens. 45 * 46 * Once the metadata exchange is complete and a mechanism is selected, the 47 * selected mechanism's context token exchange continues with AP_REQUEST and 48 * CHALLENGE messages. 49 * 50 * Once the context token exchange is complete, VERIFY messages are sent to 51 * authenticate the entire exchange. 52 */ 53 54 static void 55 zero_and_release_buffer_set(gss_buffer_set_t *pbuffers) 56 { 57 OM_uint32 tmpmin; 58 gss_buffer_set_t buffers = *pbuffers; 59 uint32_t i; 60 61 if (buffers != GSS_C_NO_BUFFER_SET) { 62 for (i = 0; i < buffers->count; i++) 63 zap(buffers->elements[i].value, buffers->elements[i].length); 64 65 gss_release_buffer_set(&tmpmin, &buffers); 66 } 67 68 *pbuffers = GSS_C_NO_BUFFER_SET; 69 } 70 71 static OM_uint32 72 buffer_set_to_key(OM_uint32 *minor, gss_buffer_set_t buffers, 73 krb5_keyblock *key) 74 { 75 krb5_error_code ret; 76 77 /* Returned keys must be in two buffers, with the key contents in the first 78 * and the enctype as a 32-bit little-endian integer in the second. */ 79 if (buffers->count != 2 || buffers->elements[1].length != 4) { 80 *minor = ERR_NEGOEX_NO_VERIFY_KEY; 81 return GSS_S_FAILURE; 82 } 83 84 krb5_free_keyblock_contents(NULL, key); 85 86 key->contents = k5memdup(buffers->elements[0].value, 87 buffers->elements[0].length, &ret); 88 if (key->contents == NULL) { 89 *minor = ret; 90 return GSS_S_FAILURE; 91 } 92 key->length = buffers->elements[0].length; 93 key->enctype = load_32_le(buffers->elements[1].value); 94 95 return GSS_S_COMPLETE; 96 } 97 98 static OM_uint32 99 get_session_keys(OM_uint32 *minor, struct negoex_auth_mech *mech) 100 { 101 OM_uint32 major, tmpmin; 102 gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET; 103 104 major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context, 105 GSS_C_INQ_NEGOEX_KEY, &buffers); 106 if (major == GSS_S_COMPLETE) { 107 major = buffer_set_to_key(minor, buffers, &mech->key); 108 zero_and_release_buffer_set(&buffers); 109 if (major != GSS_S_COMPLETE) 110 return major; 111 } 112 113 major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context, 114 GSS_C_INQ_NEGOEX_VERIFY_KEY, 115 &buffers); 116 if (major == GSS_S_COMPLETE) { 117 major = buffer_set_to_key(minor, buffers, &mech->verify_key); 118 zero_and_release_buffer_set(&buffers); 119 if (major != GSS_S_COMPLETE) 120 return major; 121 } 122 123 return GSS_S_COMPLETE; 124 } 125 126 static OM_uint32 127 emit_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx) 128 { 129 OM_uint32 major; 130 uint8_t random[32]; 131 132 major = negoex_random(minor, ctx, random, 32); 133 if (major != GSS_S_COMPLETE) 134 return major; 135 136 negoex_add_nego_message(ctx, INITIATOR_NEGO, random); 137 return GSS_S_COMPLETE; 138 } 139 140 static OM_uint32 141 process_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, 142 struct negoex_message *messages, size_t nmessages) 143 { 144 struct nego_message *msg; 145 146 assert(!ctx->initiate && ctx->negoex_step == 1); 147 148 msg = negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO); 149 if (msg == NULL) { 150 *minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE; 151 return GSS_S_DEFECTIVE_TOKEN; 152 } 153 154 negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes); 155 return GSS_S_COMPLETE; 156 } 157 158 static OM_uint32 159 emit_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx) 160 { 161 OM_uint32 major; 162 uint8_t random[32]; 163 164 major = negoex_random(minor, ctx, random, 32); 165 if (major != GSS_S_COMPLETE) 166 return major; 167 168 negoex_add_nego_message(ctx, ACCEPTOR_NEGO, random); 169 return GSS_S_COMPLETE; 170 } 171 172 static OM_uint32 173 process_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, 174 struct negoex_message *messages, size_t nmessages) 175 { 176 struct nego_message *msg; 177 178 msg = negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO); 179 if (msg == NULL) { 180 *minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE; 181 return GSS_S_DEFECTIVE_TOKEN; 182 } 183 184 /* Reorder and prune our mech list to match the acceptor's list (or a 185 * subset of it). */ 186 negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes); 187 188 return GSS_S_COMPLETE; 189 } 190 191 static void 192 query_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, 193 gss_name_t target, OM_uint32 req_flags) 194 { 195 OM_uint32 major, minor; 196 struct negoex_auth_mech *p, *next; 197 198 K5_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) { 199 major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context, 200 target, req_flags, &p->metadata); 201 /* GSS_Query_meta_data failure removes mechanism from list. */ 202 if (major != GSS_S_COMPLETE) 203 negoex_delete_auth_mech(ctx, p); 204 } 205 } 206 207 static void 208 exchange_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, 209 gss_name_t target, OM_uint32 req_flags, 210 struct negoex_message *messages, size_t nmessages) 211 { 212 OM_uint32 major, minor; 213 struct negoex_auth_mech *mech; 214 enum message_type type; 215 struct exchange_message *msg; 216 uint32_t i; 217 218 type = ctx->initiate ? ACCEPTOR_META_DATA : INITIATOR_META_DATA; 219 220 for (i = 0; i < nmessages; i++) { 221 if (messages[i].type != type) 222 continue; 223 msg = &messages[i].u.e; 224 225 mech = negoex_locate_auth_scheme(ctx, msg->scheme); 226 if (mech == NULL) 227 continue; 228 229 major = gssspi_exchange_meta_data(&minor, mech->oid, cred, 230 &mech->mech_context, target, 231 req_flags, &msg->token); 232 /* GSS_Exchange_meta_data failure removes mechanism from list. */ 233 if (major != GSS_S_COMPLETE) 234 negoex_delete_auth_mech(ctx, mech); 235 } 236 } 237 238 /* 239 * In the initiator, if we are processing the acceptor's first reply, discard 240 * the optimistic context if the acceptor ignored the optimistic token. If the 241 * acceptor continued the optimistic mech, discard all other mechs. 242 */ 243 static void 244 check_optimistic_result(spnego_gss_ctx_id_t ctx, 245 struct negoex_message *messages, size_t nmessages) 246 { 247 struct negoex_auth_mech *mech; 248 OM_uint32 tmpmin; 249 250 assert(ctx->initiate && ctx->negoex_step == 2); 251 252 /* Do nothing if we didn't make an optimistic context. */ 253 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 254 if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT) 255 return; 256 257 /* If the acceptor used the optimistic token, it will send an acceptor 258 * token or a checksum (or both) in its first reply. */ 259 if (negoex_locate_exchange_message(messages, nmessages, 260 CHALLENGE) != NULL || 261 negoex_locate_verify_message(messages, nmessages) != NULL) { 262 /* The acceptor continued the optimistic mech, and metadata exchange 263 * didn't remove it. Commit to this mechanism. */ 264 negoex_select_auth_mech(ctx, mech); 265 } else { 266 /* The acceptor ignored the optimistic token. Restart the mech. */ 267 (void)gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL); 268 krb5_free_keyblock_contents(NULL, &mech->key); 269 krb5_free_keyblock_contents(NULL, &mech->verify_key); 270 mech->complete = mech->sent_checksum = FALSE; 271 } 272 } 273 274 /* Perform an initiator step of the underlying mechanism exchange. */ 275 static OM_uint32 276 mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, 277 gss_name_t target, OM_uint32 req_flags, OM_uint32 time_req, 278 struct negoex_message *messages, size_t nmessages, 279 gss_channel_bindings_t bindings, gss_buffer_t output_token, 280 OM_uint32 *time_rec) 281 { 282 OM_uint32 major, first_major = 0, first_minor = 0; 283 struct negoex_auth_mech *mech = NULL; 284 gss_buffer_t input_token = GSS_C_NO_BUFFER; 285 struct exchange_message *msg; 286 int first_mech; 287 288 output_token->value = NULL; 289 output_token->length = 0; 290 291 /* Allow disabling of optimistic token for testing. */ 292 if (ctx->negoex_step == 1 && 293 secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL) 294 return GSS_S_COMPLETE; 295 296 if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) { 297 *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS; 298 return GSS_S_FAILURE; 299 } 300 301 /* 302 * Get the input token. The challenge could be for the optimistic mech, 303 * which we might have discarded in metadata exchange, so ignore the 304 * challenge if it doesn't match the first auth mech. 305 */ 306 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 307 msg = negoex_locate_exchange_message(messages, nmessages, CHALLENGE); 308 if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme)) 309 input_token = &msg->token; 310 311 if (mech->complete) 312 return GSS_S_COMPLETE; 313 314 first_mech = TRUE; 315 316 while (!K5_TAILQ_EMPTY(&ctx->negoex_mechs)) { 317 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 318 319 major = gss_init_sec_context(minor, cred, &mech->mech_context, target, 320 mech->oid, req_flags, time_req, bindings, 321 input_token, &ctx->actual_mech, 322 output_token, &ctx->ctx_flags, time_rec); 323 324 if (major == GSS_S_COMPLETE) 325 mech->complete = 1; 326 327 if (!GSS_ERROR(major)) 328 return get_session_keys(minor, mech); 329 330 /* Remember the error we got from the first mech. */ 331 if (first_mech) { 332 first_major = major; 333 first_minor = *minor; 334 } 335 336 /* If we still have multiple mechs to try, move on to the next one. */ 337 negoex_delete_auth_mech(ctx, mech); 338 first_mech = FALSE; 339 input_token = GSS_C_NO_BUFFER; 340 } 341 342 if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) { 343 major = first_major; 344 *minor = first_minor; 345 } 346 347 return major; 348 } 349 350 /* Perform an acceptor step of the underlying mechanism exchange. */ 351 static OM_uint32 352 mech_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, 353 gss_cred_id_t cred, struct negoex_message *messages, 354 size_t nmessages, gss_channel_bindings_t bindings, 355 gss_buffer_t output_token, OM_uint32 *time_rec) 356 { 357 OM_uint32 major, tmpmin; 358 struct negoex_auth_mech *mech; 359 struct exchange_message *msg; 360 361 assert(!ctx->initiate && !K5_TAILQ_EMPTY(&ctx->negoex_mechs)); 362 363 msg = negoex_locate_exchange_message(messages, nmessages, AP_REQUEST); 364 if (msg == NULL) { 365 /* No input token is okay on the first request or if the mech is 366 * complete. */ 367 if (ctx->negoex_step == 1 || 368 K5_TAILQ_FIRST(&ctx->negoex_mechs)->complete) 369 return GSS_S_COMPLETE; 370 *minor = ERR_NEGOEX_MISSING_AP_REQUEST_MESSAGE; 371 return GSS_S_DEFECTIVE_TOKEN; 372 } 373 374 if (ctx->negoex_step == 1) { 375 /* Ignore the optimistic token if it isn't for our most preferred 376 * mech. */ 377 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 378 if (!GUID_EQ(msg->scheme, mech->scheme)) 379 return GSS_S_COMPLETE; 380 } else { 381 /* The initiator has selected a mech; discard other entries. */ 382 mech = negoex_locate_auth_scheme(ctx, msg->scheme); 383 if (mech == NULL) { 384 *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS; 385 return GSS_S_FAILURE; 386 } 387 negoex_select_auth_mech(ctx, mech); 388 } 389 390 if (mech->complete) 391 return GSS_S_COMPLETE; 392 393 if (ctx->internal_name != GSS_C_NO_NAME) 394 gss_release_name(&tmpmin, &ctx->internal_name); 395 if (ctx->deleg_cred != GSS_C_NO_CREDENTIAL) 396 gss_release_cred(&tmpmin, &ctx->deleg_cred); 397 398 major = gss_accept_sec_context(minor, &mech->mech_context, cred, 399 &msg->token, bindings, &ctx->internal_name, 400 &ctx->actual_mech, output_token, 401 &ctx->ctx_flags, time_rec, 402 &ctx->deleg_cred); 403 404 if (major == GSS_S_COMPLETE) 405 mech->complete = 1; 406 407 if (!GSS_ERROR(major)) { 408 major = get_session_keys(minor, mech); 409 } else if (ctx->negoex_step == 1) { 410 /* This was an optimistic token; pretend this never happened. */ 411 major = GSS_S_COMPLETE; 412 *minor = 0; 413 gss_release_buffer(&tmpmin, output_token); 414 gss_delete_sec_context(&tmpmin, &mech->mech_context, GSS_C_NO_BUFFER); 415 } 416 417 return major; 418 } 419 420 static krb5_keyusage 421 verify_keyusage(spnego_gss_ctx_id_t ctx, int make_checksum) 422 { 423 /* Of course, these are the wrong way around in the spec. */ 424 return (ctx->initiate ^ !make_checksum) ? 425 NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM; 426 } 427 428 static OM_uint32 429 verify_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, 430 struct negoex_message *messages, size_t nmessages, 431 gss_buffer_t input_token, int *send_alert_out) 432 { 433 krb5_error_code ret; 434 struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 435 struct verify_message *msg; 436 krb5_crypto_iov iov[3]; 437 krb5_keyusage usage = verify_keyusage(ctx, FALSE); 438 krb5_boolean valid; 439 440 *send_alert_out = FALSE; 441 assert(mech != NULL); 442 443 /* The other party may not be ready to send a verify token yet, or (in the 444 * first initiator step) may send one for a mechanism we don't support. */ 445 msg = negoex_locate_verify_message(messages, nmessages); 446 if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme)) 447 return GSS_S_COMPLETE; 448 449 /* A recoverable error may cause us to be unable to verify a token from the 450 * other party. In this case we should send an alert. */ 451 if (mech->verify_key.enctype == ENCTYPE_NULL) { 452 *send_alert_out = TRUE; 453 return GSS_S_COMPLETE; 454 } 455 456 /* Verify the checksum over the existing transcript and the portion of the 457 * input token leading up to the verify message. */ 458 assert(input_token != NULL); 459 iov[0].flags = KRB5_CRYPTO_TYPE_DATA; 460 iov[0].data = make_data(ctx->negoex_transcript.data, 461 ctx->negoex_transcript.len); 462 iov[1].flags = KRB5_CRYPTO_TYPE_DATA; 463 iov[1].data = make_data(input_token->value, msg->offset_in_token); 464 iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM; 465 iov[2].data = make_data((uint8_t *)msg->cksum, msg->cksum_len); 466 467 ret = krb5_c_verify_checksum_iov(ctx->kctx, msg->cksum_type, 468 &mech->verify_key, usage, iov, 3, &valid); 469 if (ret) { 470 *minor = ret; 471 return GSS_S_FAILURE; 472 } 473 if (!valid || !krb5_c_is_keyed_cksum(msg->cksum_type)) { 474 *minor = ERR_NEGOEX_INVALID_CHECKSUM; 475 return GSS_S_BAD_SIG; 476 } 477 478 mech->verified_checksum = TRUE; 479 return GSS_S_COMPLETE; 480 } 481 482 static OM_uint32 483 make_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx) 484 { 485 krb5_error_code ret; 486 krb5_data d; 487 krb5_keyusage usage = verify_keyusage(ctx, TRUE); 488 krb5_checksum cksum; 489 struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 490 491 assert(mech != NULL); 492 493 if (mech->key.enctype == ENCTYPE_NULL) { 494 if (mech->complete) { 495 *minor = ERR_NEGOEX_NO_VERIFY_KEY; 496 return GSS_S_UNAVAILABLE; 497 } else { 498 return GSS_S_COMPLETE; 499 } 500 } 501 502 d = make_data(ctx->negoex_transcript.data, ctx->negoex_transcript.len); 503 ret = krb5_c_make_checksum(ctx->kctx, 0, &mech->key, usage, &d, &cksum); 504 if (ret) { 505 *minor = ret; 506 return GSS_S_FAILURE; 507 } 508 509 negoex_add_verify_message(ctx, mech->scheme, cksum.checksum_type, 510 cksum.contents, cksum.length); 511 512 mech->sent_checksum = TRUE; 513 krb5_free_checksum_contents(ctx->kctx, &cksum); 514 return GSS_S_COMPLETE; 515 } 516 517 /* If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state 518 * on the mechanism so that we send another VERIFY message. */ 519 static void 520 process_alerts(spnego_gss_ctx_id_t ctx, 521 struct negoex_message *messages, uint32_t nmessages) 522 { 523 struct alert_message *msg; 524 struct negoex_auth_mech *mech; 525 526 msg = negoex_locate_alert_message(messages, nmessages); 527 if (msg != NULL && msg->verify_no_key) { 528 mech = negoex_locate_auth_scheme(ctx, msg->scheme); 529 if (mech != NULL) { 530 mech->sent_checksum = FALSE; 531 krb5_free_keyblock_contents(NULL, &mech->key); 532 krb5_free_keyblock_contents(NULL, &mech->verify_key); 533 } 534 } 535 } 536 537 static OM_uint32 538 make_output_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, 539 gss_buffer_t mech_output_token, int send_alert, 540 gss_buffer_t output_token) 541 { 542 OM_uint32 major; 543 struct negoex_auth_mech *mech; 544 enum message_type type; 545 size_t old_transcript_len = ctx->negoex_transcript.len; 546 547 output_token->length = 0; 548 output_token->value = NULL; 549 550 /* If the mech is complete and we previously sent a checksum, we just 551 * processed the last leg and don't need to send another token. */ 552 if (mech_output_token->length == 0 && 553 K5_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum) 554 return GSS_S_COMPLETE; 555 556 if (ctx->negoex_step == 1) { 557 if (ctx->initiate) 558 major = emit_initiator_nego(minor, ctx); 559 else 560 major = emit_acceptor_nego(minor, ctx); 561 if (major != GSS_S_COMPLETE) 562 return major; 563 564 type = ctx->initiate ? INITIATOR_META_DATA : ACCEPTOR_META_DATA; 565 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { 566 if (mech->metadata.length > 0) { 567 negoex_add_exchange_message(ctx, type, mech->scheme, 568 &mech->metadata); 569 } 570 } 571 } 572 573 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 574 575 if (mech_output_token->length > 0) { 576 type = ctx->initiate ? AP_REQUEST : CHALLENGE; 577 negoex_add_exchange_message(ctx, type, mech->scheme, 578 mech_output_token); 579 } 580 581 if (send_alert) 582 negoex_add_verify_no_key_alert(ctx, mech->scheme); 583 584 /* Try to add a VERIFY message if we haven't already done so. */ 585 if (!mech->sent_checksum) { 586 major = make_checksum(minor, ctx); 587 if (major != GSS_S_COMPLETE) 588 return major; 589 } 590 591 if (ctx->negoex_transcript.data == NULL) { 592 *minor = ENOMEM; 593 return GSS_S_FAILURE; 594 } 595 596 /* Copy what we added to the transcript into the output token. */ 597 output_token->length = ctx->negoex_transcript.len - old_transcript_len; 598 output_token->value = gssalloc_malloc(output_token->length); 599 if (output_token->value == NULL) { 600 *minor = ENOMEM; 601 return GSS_S_FAILURE; 602 } 603 memcpy(output_token->value, 604 (uint8_t *)ctx->negoex_transcript.data + old_transcript_len, 605 output_token->length); 606 607 return GSS_S_COMPLETE; 608 } 609 610 OM_uint32 611 negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, 612 gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req, 613 gss_buffer_t input_token, gss_channel_bindings_t bindings, 614 gss_buffer_t output_token, OM_uint32 *time_rec) 615 { 616 OM_uint32 major, tmpmin; 617 gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; 618 struct negoex_message *messages = NULL; 619 struct negoex_auth_mech *mech; 620 size_t nmessages = 0; 621 int send_alert = FALSE; 622 623 if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER && 624 input_token->length != 0) 625 return GSS_S_DEFECTIVE_TOKEN; 626 627 major = negoex_prep_context_for_negoex(minor, ctx); 628 if (major != GSS_S_COMPLETE) 629 goto cleanup; 630 631 ctx->negoex_step++; 632 633 if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) { 634 major = negoex_parse_token(minor, ctx, input_token, &messages, 635 &nmessages); 636 if (major != GSS_S_COMPLETE) 637 goto cleanup; 638 } 639 640 process_alerts(ctx, messages, nmessages); 641 642 if (ctx->negoex_step == 1) { 643 /* Choose a random conversation ID. */ 644 major = negoex_random(minor, ctx, ctx->negoex_conv_id, GUID_LENGTH); 645 if (major != GSS_S_COMPLETE) 646 goto cleanup; 647 648 /* Query each mech for its metadata (this may prune the mech list). */ 649 query_meta_data(ctx, cred, target_name, req_flags); 650 } else if (ctx->negoex_step == 2) { 651 /* See if the mech processed the optimistic token. */ 652 check_optimistic_result(ctx, messages, nmessages); 653 654 /* Pass the acceptor metadata to each mech to prune the list. */ 655 exchange_meta_data(ctx, cred, target_name, req_flags, 656 messages, nmessages); 657 658 /* Process the ACCEPTOR_NEGO message. */ 659 major = process_acceptor_nego(minor, ctx, messages, nmessages); 660 if (major != GSS_S_COMPLETE) 661 goto cleanup; 662 } 663 664 /* Process the input token and/or produce an output token. This may prune 665 * the mech list, but on success there will be at least one mech entry. */ 666 major = mech_init(minor, ctx, cred, target_name, req_flags, time_req, 667 messages, nmessages, bindings, &mech_output_token, 668 time_rec); 669 if (major != GSS_S_COMPLETE) 670 goto cleanup; 671 assert(!K5_TAILQ_EMPTY(&ctx->negoex_mechs)); 672 673 /* At this point in step 2 we have performed the metadata exchange and 674 * chosen a mech we can use, so discard any fallback mech entries. */ 675 if (ctx->negoex_step == 2) 676 negoex_select_auth_mech(ctx, K5_TAILQ_FIRST(&ctx->negoex_mechs)); 677 678 major = verify_checksum(minor, ctx, messages, nmessages, input_token, 679 &send_alert); 680 if (major != GSS_S_COMPLETE) 681 goto cleanup; 682 683 if (input_token != GSS_C_NO_BUFFER) { 684 k5_buf_add_len(&ctx->negoex_transcript, input_token->value, 685 input_token->length); 686 } 687 688 major = make_output_token(minor, ctx, &mech_output_token, send_alert, 689 output_token); 690 if (major != GSS_S_COMPLETE) 691 goto cleanup; 692 693 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 694 major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : 695 GSS_S_CONTINUE_NEEDED; 696 697 cleanup: 698 free(messages); 699 gss_release_buffer(&tmpmin, &mech_output_token); 700 negoex_prep_context_for_spnego(ctx); 701 return major; 702 } 703 704 OM_uint32 705 negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred, 706 gss_buffer_t input_token, gss_channel_bindings_t bindings, 707 gss_buffer_t output_token, OM_uint32 *time_rec) 708 { 709 OM_uint32 major, tmpmin; 710 gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; 711 struct negoex_message *messages = NULL; 712 struct negoex_auth_mech *mech; 713 size_t nmessages; 714 int send_alert = FALSE; 715 716 if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) { 717 major = GSS_S_DEFECTIVE_TOKEN; 718 goto cleanup; 719 } 720 721 major = negoex_prep_context_for_negoex(minor, ctx); 722 if (major != GSS_S_COMPLETE) 723 goto cleanup; 724 725 ctx->negoex_step++; 726 727 major = negoex_parse_token(minor, ctx, input_token, &messages, &nmessages); 728 if (major != GSS_S_COMPLETE) 729 goto cleanup; 730 731 process_alerts(ctx, messages, nmessages); 732 733 if (ctx->negoex_step == 1) { 734 /* Read the INITIATOR_NEGO message to prune the candidate mech list. */ 735 major = process_initiator_nego(minor, ctx, messages, nmessages); 736 if (major != GSS_S_COMPLETE) 737 goto cleanup; 738 739 /* 740 * Pass the initiator metadata to each mech to prune the list, and 741 * query each mech for its acceptor metadata (which may also prune the 742 * list). 743 */ 744 exchange_meta_data(ctx, cred, GSS_C_NO_NAME, 0, messages, nmessages); 745 query_meta_data(ctx, cred, GSS_C_NO_NAME, 0); 746 747 if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) { 748 *minor = ERR_NEGOEX_NO_AVAILABLE_MECHS; 749 major = GSS_S_FAILURE; 750 goto cleanup; 751 } 752 } 753 754 /* 755 * Process the input token and possibly produce an output token. This may 756 * prune the list to a single mech. Continue on error if an output token 757 * is generated, so that we send the token to the initiator. 758 */ 759 major = mech_accept(minor, ctx, cred, messages, nmessages, bindings, 760 &mech_output_token, time_rec); 761 if (major != GSS_S_COMPLETE && mech_output_token.length == 0) 762 goto cleanup; 763 764 if (major == GSS_S_COMPLETE) { 765 major = verify_checksum(minor, ctx, messages, nmessages, input_token, 766 &send_alert); 767 if (major != GSS_S_COMPLETE) 768 goto cleanup; 769 } 770 771 k5_buf_add_len(&ctx->negoex_transcript, 772 input_token->value, input_token->length); 773 774 major = make_output_token(minor, ctx, &mech_output_token, send_alert, 775 output_token); 776 if (major != GSS_S_COMPLETE) 777 goto cleanup; 778 779 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs); 780 major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : 781 GSS_S_CONTINUE_NEEDED; 782 783 cleanup: 784 free(messages); 785 gss_release_buffer(&tmpmin, &mech_output_token); 786 negoex_prep_context_for_spnego(ctx); 787 return major; 788 } 789