lib/gssapi/spnego: _gss_negoex_init do not leak error message
[heimdal.git] / lib / gssapi / spnego / negoex_ctx.c
blobfd337eee2bf8a1402078ce6b6ebbf440a7bcf0f7
1 /*
2 * Copyright (C) 2011-2021 PADL Software Pty Ltd.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "spnego_locl.h"
34 * The initial context token emitted by the initiator is a INITIATOR_NEGO
35 * message followed by zero or more INITIATOR_META_DATA tokens, and zero
36 * or one AP_REQUEST tokens.
38 * Upon receiving this, the acceptor computes the list of mutually supported
39 * authentication mechanisms and performs the metadata exchange. The output
40 * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens,
41 * and zero or one CHALLENGE tokens.
43 * Once the metadata exchange is complete and a mechanism is selected, the
44 * selected mechanism's context token exchange continues with AP_REQUEST and
45 * CHALLENGE messages.
47 * Once the context token exchange is complete, VERIFY messages are sent to
48 * authenticate the entire exchange.
51 static OM_uint32
52 buffer_set_to_crypto(OM_uint32 *minor,
53 krb5_context context,
54 gss_buffer_set_t buffers,
55 krb5_crypto *crypto)
57 krb5_error_code ret;
58 krb5_keyblock keyblock;
59 OM_uint32 tmp;
62 * Returned keys must be in two buffers, with the key contents in
63 * the first and the enctype as a 32-bit little-endian integer in
64 * the second.
66 if (buffers->count != 2 ||
67 buffers->elements[1].length != sizeof(tmp)) {
68 *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
69 return GSS_S_FAILURE;
72 if (*crypto != NULL) {
73 krb5_crypto_destroy(context, *crypto);
74 *crypto = NULL;
77 keyblock.keyvalue.data = buffers->elements[0].value;
78 keyblock.keyvalue.length = buffers->elements[0].length;
79 _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp);
80 keyblock.keytype = tmp;
82 ret = krb5_crypto_init(context, &keyblock, 0, crypto);
83 if (ret) {
84 *minor = ret;
85 return GSS_S_FAILURE;
88 return GSS_S_COMPLETE;
91 #define NEGOEX_SIGN_KEY 1
92 #define NEGOEX_VERIFY_KEY 2
93 #define NEGOEX_BOTH_KEYS (NEGOEX_SIGN_KEY|NEGOEX_VERIFY_KEY)
95 static OM_uint32
96 get_session_keys(OM_uint32 *minor,
97 krb5_context context,
98 OM_uint32 flags,
99 struct negoex_auth_mech *mech)
101 OM_uint32 major, tmpMinor;
102 gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
104 if (flags & NEGOEX_SIGN_KEY) {
105 major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
106 GSS_C_INQ_NEGOEX_KEY, &buffers);
107 if (major == GSS_S_COMPLETE) {
108 major = buffer_set_to_crypto(minor, context,
109 buffers, &mech->crypto);
110 _gss_secure_release_buffer_set(&tmpMinor, &buffers);
111 if (major != GSS_S_COMPLETE)
112 return major;
116 if (flags & NEGOEX_VERIFY_KEY) {
117 major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
118 GSS_C_INQ_NEGOEX_VERIFY_KEY,
119 &buffers);
120 if (major == GSS_S_COMPLETE) {
121 major = buffer_set_to_crypto(minor, context,
122 buffers, &mech->verify_crypto);
123 _gss_secure_release_buffer_set(&tmpMinor, &buffers);
124 if (major != GSS_S_COMPLETE)
125 return major;
129 return GSS_S_COMPLETE;
132 static OM_uint32
133 emit_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx)
135 uint8_t random[32];
136 struct negoex_auth_mech *mech;
137 size_t i = 0;
139 krb5_generate_random_block(random, sizeof(random));
141 HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
142 _gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme);
144 return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random);
147 static OM_uint32
148 process_initiator_nego(OM_uint32 *minor,
149 gssspnego_ctx ctx,
150 struct negoex_message *messages,
151 size_t nmessages)
153 struct nego_message *msg;
154 size_t i;
156 heim_assert(!ctx->flags.local && ctx->negoex_step == 1,
157 "NegoEx INITIATOR_NEGO token received after first leg");
159 msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
160 if (msg == NULL) {
161 *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
162 return GSS_S_DEFECTIVE_TOKEN;
165 for (i = 0; i < msg->nschemes; i++)
166 _gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]);
168 _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
170 return GSS_S_COMPLETE;
173 static OM_uint32
174 emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx)
176 uint8_t random[32];
178 krb5_generate_random_block(random, 32);
180 return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random);
183 static OM_uint32
184 process_acceptor_nego(OM_uint32 *minor,
185 gssspnego_ctx ctx,
186 struct negoex_message *messages,
187 size_t nmessages)
189 struct nego_message *msg;
191 msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
192 if (msg == NULL) {
193 *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
194 return GSS_S_DEFECTIVE_TOKEN;
198 * Reorder and prune our mech list to match the acceptor's list (or a
199 * subset of it).
201 _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
203 return GSS_S_COMPLETE;
206 static void
207 query_meta_data(gssspnego_ctx ctx,
208 struct gssspnego_optimistic_ctx *opt,
209 gss_cred_id_t cred,
210 OM_uint32 req_flags)
212 OM_uint32 major, minor;
213 struct negoex_auth_mech *p, *next;
216 * Note that if we received an optimistic context token from SPNEGO,
217 * then we will call QMD after ISC, rather than before. Mechanisms
218 * must be prepared to handle this and must not assume the context
219 * will be NULL on entry.
221 HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
222 if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0)
223 p->mech_context = opt->gssctx;;
225 major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
226 ctx->target_name, req_flags, &p->metadata);
227 /* GSS_Query_meta_data failure removes mechanism from list. */
228 if (major != GSS_S_COMPLETE)
229 _gss_negoex_delete_auth_mech(ctx, p);
233 static void
234 exchange_meta_data(gssspnego_ctx ctx,
235 gss_cred_id_t cred,
236 OM_uint32 req_flags,
237 struct negoex_message *messages,
238 size_t nmessages)
240 OM_uint32 major, minor;
241 struct negoex_auth_mech *mech;
242 enum message_type type;
243 struct exchange_message *msg;
244 uint32_t i;
246 type = ctx->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
248 for (i = 0; i < nmessages; i++) {
249 if (messages[i].type != type)
250 continue;
251 msg = &messages[i].u.e;
253 mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
254 if (mech == NULL)
255 continue;
257 major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
258 &mech->mech_context,
259 ctx->target_name,
260 req_flags, &msg->token);
261 /* GSS_Exchange_meta_data failure removes mechanism from list. */
262 if (major != GSS_S_COMPLETE)
263 _gss_negoex_delete_auth_mech(ctx, mech);
267 static void
268 release_mech_crypto(struct negoex_auth_mech *mech)
270 krb5_context context = NULL;
272 if (mech->crypto || mech->verify_crypto)
273 context = _gss_mg_krb5_context();
275 if (mech->crypto) {
276 krb5_crypto_destroy(context, mech->crypto);
277 mech->crypto = NULL;
280 if (mech->verify_crypto) {
281 krb5_crypto_destroy(context, mech->verify_crypto);
282 mech->verify_crypto = NULL;
285 mech->sent_checksum = FALSE;
289 * In the initiator, if we are processing the acceptor's first reply, discard
290 * the optimistic context if the acceptor ignored the optimistic token. If the
291 * acceptor continued the optimistic mech, discard all other mechs.
293 static void
294 check_optimistic_result(gssspnego_ctx ctx,
295 struct negoex_message *messages,
296 size_t nmessages)
298 struct negoex_auth_mech *mech;
299 OM_uint32 tmpMinor;
301 heim_assert(ctx->flags.local && ctx->negoex_step == 2,
302 "NegoEx optimistic result should only be checked in second leg");
304 /* Do nothing if we didn't make an optimistic context. */
305 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
306 if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
307 return;
310 * If the acceptor used the optimistic token, it will send an acceptor
311 * token or a checksum (or both) in its first reply.
313 if (_gss_negoex_locate_exchange_message(messages, nmessages,
314 CHALLENGE) != NULL ||
315 _gss_negoex_locate_verify_message(messages, nmessages) != NULL) {
317 * The acceptor continued the optimistic mech, and metadata exchange
318 * didn't remove it. Commit to this mechanism.
320 _gss_negoex_select_auth_mech(ctx, mech);
321 } else {
323 * The acceptor ignored the optimistic token. Restart the mech.
325 gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
326 release_mech_crypto(mech);
327 mech->complete = FALSE;
331 /* Perform an initiator step of the underlying mechanism exchange. */
332 static OM_uint32
333 mech_init(OM_uint32 *minor,
334 struct gssspnego_optimistic_ctx *opt,
335 gssspnego_ctx ctx,
336 gss_cred_id_t cred,
337 OM_uint32 req_flags,
338 OM_uint32 time_req,
339 const gss_channel_bindings_t input_chan_bindings,
340 struct negoex_message *messages,
341 size_t nmessages,
342 gss_buffer_t output_token,
343 int *mech_error)
345 OM_uint32 major, first_major = GSS_S_COMPLETE, first_minor = 0;
346 struct negoex_auth_mech *mech = NULL;
347 gss_buffer_t input_token = GSS_C_NO_BUFFER;
348 struct exchange_message *msg;
349 int first_mech;
350 krb5_context context = _gss_mg_krb5_context();
352 output_token->value = NULL;
353 output_token->length = 0;
355 *mech_error = FALSE;
357 /* Allow disabling of optimistic token for testing. */
358 if (ctx->negoex_step == 1 &&
359 secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
360 return GSS_S_COMPLETE;
362 if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
363 *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
364 return GSS_S_FAILURE;
368 * Get the input token. The challenge could be for the optimistic mech,
369 * which we might have discarded in metadata exchange, so ignore the
370 * challenge if it doesn't match the first auth mech.
372 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
373 msg = _gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE);
374 if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme))
375 input_token = &msg->token;
377 if (mech->complete)
378 return GSS_S_COMPLETE;
380 first_mech = TRUE;
381 major = GSS_S_BAD_MECH;
383 while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
384 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
387 * If SPNEGO generated an optimistic token when probing available
388 * mechanisms, we can reuse it here. This avoids a potentially
389 * expensive and redundant call to GSS_Init_sec_context();
391 if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) {
392 heim_assert(ctx->negoex_step == 1,
393 "SPNEGO optimistic token only valid for NegoEx first leg");
395 major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token);
396 if (GSS_ERROR(major))
397 return major;
399 ctx->negotiated_mech_type = opt->negotiated_mech_type;
400 ctx->mech_flags = opt->optimistic_flags;
401 ctx->mech_time_rec = opt->optimistic_time_rec;
403 mech->mech_context = opt->gssctx;
404 opt->gssctx = NULL; /* steal it */
406 mech->complete = opt->complete;
407 major = GSS_S_COMPLETE;
408 } else {
409 major = gss_init_sec_context(minor, cred, &mech->mech_context,
410 ctx->target_name, mech->oid,
411 req_flags, time_req,
412 input_chan_bindings, input_token,
413 &ctx->negotiated_mech_type, output_token,
414 &ctx->mech_flags, &ctx->mech_time_rec);
415 if (major == GSS_S_COMPLETE)
416 mech->complete = 1;
417 else if (GSS_ERROR(major)) {
418 gss_mg_collect_error(mech->oid, major, *minor);
419 *mech_error = TRUE;
422 if (!GSS_ERROR(major))
423 return get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech);
425 /* Remember the error we got from the first mech. */
426 if (first_mech) {
427 first_major = major;
428 first_minor = *minor;
431 /* If we still have multiple mechs to try, move on to the next one. */
432 _gss_negoex_delete_auth_mech(ctx, mech);
433 first_mech = FALSE;
434 input_token = GSS_C_NO_BUFFER;
437 if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
438 major = first_major;
439 *minor = first_minor;
442 return major;
445 /* Perform an acceptor step of the underlying mechanism exchange. */
446 static OM_uint32
447 mech_accept(OM_uint32 *minor,
448 gssspnego_ctx ctx,
449 gss_cred_id_t cred,
450 const gss_channel_bindings_t input_chan_bindings,
451 struct negoex_message *messages,
452 size_t nmessages,
453 gss_buffer_t output_token,
454 gss_cred_id_t *deleg_cred,
455 int *mech_error)
457 OM_uint32 major, tmpMinor;
458 struct negoex_auth_mech *mech;
459 struct exchange_message *msg;
460 krb5_context context = _gss_mg_krb5_context();
462 heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
463 "Acceptor NegoEx function called in wrong sequence");
465 *mech_error = FALSE;
467 msg = _gss_negoex_locate_exchange_message(messages, nmessages, AP_REQUEST);
468 if (msg == NULL) {
470 * No input token is okay on the first request or if the mech is
471 * complete.
473 if (ctx->negoex_step == 1 ||
474 HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
475 return GSS_S_COMPLETE;
476 *minor = (OM_uint32)NEGOEX_MISSING_AP_REQUEST_MESSAGE;
477 return GSS_S_DEFECTIVE_TOKEN;
480 if (ctx->negoex_step == 1) {
482 * Ignore the optimistic token if it isn't for our most preferred
483 * mech.
485 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
486 if (!GUID_EQ(msg->scheme, mech->scheme)) {
487 _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech");
488 return GSS_S_COMPLETE;
490 } else {
491 /* The initiator has selected a mech; discard other entries. */
492 mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
493 if (mech == NULL) {
494 *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
495 return GSS_S_FAILURE;
497 _gss_negoex_select_auth_mech(ctx, mech);
500 if (mech->complete)
501 return GSS_S_COMPLETE;
503 if (ctx->mech_src_name != GSS_C_NO_NAME)
504 gss_release_name(&tmpMinor, &ctx->mech_src_name);
505 if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL)
506 gss_release_cred(&tmpMinor, deleg_cred);
508 major = gss_accept_sec_context(minor, &mech->mech_context, cred,
509 &msg->token, input_chan_bindings,
510 &ctx->mech_src_name, &ctx->negotiated_mech_type,
511 output_token, &ctx->mech_flags,
512 &ctx->mech_time_rec, deleg_cred);
513 if (major == GSS_S_COMPLETE)
514 mech->complete = 1;
516 if (!GSS_ERROR(major)) {
517 if (major == GSS_S_COMPLETE &&
518 !gss_oid_equal(ctx->negotiated_mech_type, mech->oid))
519 _gss_mg_log(1, "negoex client didn't send the mech they said they would");
521 major = get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech);
522 } else if (ctx->negoex_step == 1) {
523 gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor);
524 *mech_error = TRUE;
526 /* This was an optimistic token; pretend this never happened. */
527 major = GSS_S_COMPLETE;
528 *minor = 0;
529 gss_release_buffer(&tmpMinor, output_token);
530 gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
533 return major;
536 static krb5_keyusage
537 verify_keyusage(gssspnego_ctx ctx, int make_checksum)
539 /* Of course, these are the wrong way around in the spec. */
540 return (ctx->flags.local ^ !make_checksum) ?
541 NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
544 static OM_uint32
545 verify_key_flags(gssspnego_ctx ctx, int make_checksum)
547 return (ctx->flags.local ^ make_checksum) ?
548 NEGOEX_SIGN_KEY : NEGOEX_VERIFY_KEY;
551 static OM_uint32
552 verify_checksum(OM_uint32 *minor,
553 gssspnego_ctx ctx,
554 struct negoex_message *messages,
555 size_t nmessages,
556 gss_const_buffer_t input_token,
557 int *send_alert_out)
559 krb5_error_code ret;
560 struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
561 struct verify_message *msg;
562 krb5_context context = _gss_mg_krb5_context();
563 krb5_crypto_iov iov[3];
564 krb5_keyusage usage = verify_keyusage(ctx, FALSE);
566 *send_alert_out = FALSE;
567 heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum");
570 * The other party may not be ready to send a verify token yet, or (in the
571 * first initiator step) may send one for a mechanism we don't support.
573 msg = _gss_negoex_locate_verify_message(messages, nmessages);
574 if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme))
575 return GSS_S_COMPLETE;
578 * Last chance attempt to obtain session key for imported exported partial
579 * contexts (which do not carry the session key at the NegoEx layer).
581 if (mech->verify_crypto == NULL)
582 get_session_keys(minor, context, verify_key_flags(ctx, FALSE), mech);
585 * A recoverable error may cause us to be unable to verify a token from the
586 * other party. In this case we should send an alert.
588 if (mech->verify_crypto == NULL) {
589 *send_alert_out = TRUE;
590 return GSS_S_COMPLETE;
593 if (!krb5_checksum_is_keyed(context, msg->cksum_type)) {
594 *minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM;
595 return GSS_S_BAD_SIG;
599 * Verify the checksum over the existing transcript and the portion of the
600 * input token leading up to the verify message.
602 iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
603 ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data);
604 if (ret) {
605 *minor = ret;
606 return GSS_S_FAILURE;
609 iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
610 iov[1].data.data = input_token->value;
611 iov[1].data.length = msg->offset_in_token;
613 iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
614 iov[2].data.data = (uint8_t *)msg->cksum;
615 iov[2].data.length = msg->cksum_len;
617 ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage,
618 iov, sizeof(iov) / sizeof(iov[0]), NULL);
619 if (ret == 0)
620 mech->verified_checksum = TRUE;
621 else
622 *minor = ret;
624 krb5_data_free(&iov[0].data);
626 return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
629 static OM_uint32
630 make_checksum(OM_uint32 *minor, gssspnego_ctx ctx)
632 krb5_error_code ret;
633 krb5_context context = _gss_mg_krb5_context();
634 krb5_data d;
635 krb5_keyusage usage = verify_keyusage(ctx, TRUE);
636 krb5_checksum cksum;
637 struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
638 OM_uint32 major;
640 heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum");
642 if (mech->crypto == NULL) {
643 if (mech->complete) {
645 * Last chance attempt to obtain session key for imported exported partial
646 * contexts (which do not carry the session key at the NegoEx layer).
648 get_session_keys(minor, context, verify_key_flags(ctx, TRUE), mech);
649 if (mech->crypto == NULL) {
650 *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
651 return GSS_S_UNAVAILABLE;
653 } else {
654 return GSS_S_COMPLETE;
658 ret = krb5_storage_to_data(ctx->negoex_transcript, &d);
659 if (ret) {
660 *minor = ret;
661 return GSS_S_FAILURE;
664 ret = krb5_create_checksum(context, mech->crypto,
665 usage, 0, d.data, d.length, &cksum);
666 krb5_data_free(&d);
667 if (ret) {
668 *minor = ret;
669 return GSS_S_FAILURE;
672 major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme,
673 cksum.cksumtype,
674 cksum.checksum.data,
675 cksum.checksum.length);
676 free_Checksum(&cksum);
678 if (major == GSS_S_COMPLETE)
679 mech->sent_checksum = TRUE;
681 return major;
685 * If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state
686 * on the mechanism so that we send another VERIFY message.
688 static void
689 process_alerts(gssspnego_ctx ctx,
690 struct negoex_message *messages,
691 uint32_t nmessages)
693 struct alert_message *msg;
694 struct negoex_auth_mech *mech;
696 msg = _gss_negoex_locate_alert_message(messages, nmessages);
697 if (msg != NULL && msg->verify_no_key) {
698 mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
699 if (mech != NULL)
700 release_mech_crypto(mech);
704 static OM_uint32
705 make_output_token(OM_uint32 *minor,
706 gssspnego_ctx ctx,
707 gss_buffer_t mech_output_token,
708 int send_alert,
709 gss_buffer_t output_token)
711 OM_uint32 major, tmpMinor;
712 struct negoex_auth_mech *mech;
713 enum message_type type;
714 off_t old_transcript_len;
716 output_token->length = 0;
717 output_token->value = NULL;
719 old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR);
722 * If the mech is complete and we previously sent a checksum, we just
723 * processed the last leg and don't need to send another token.
725 if (mech_output_token->length == 0 &&
726 HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
727 return GSS_S_COMPLETE;
729 if (ctx->negoex_step == 1) {
730 if (ctx->flags.local)
731 major = emit_initiator_nego(minor, ctx);
732 else
733 major = emit_acceptor_nego(minor, ctx);
734 if (major != GSS_S_COMPLETE)
735 return major;
737 type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
738 HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
739 if (mech->metadata.length > 0) {
740 major = _gss_negoex_add_exchange_message(minor, ctx,
741 type, mech->scheme,
742 &mech->metadata);
743 if (major != GSS_S_COMPLETE)
744 return major;
749 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
751 if (mech_output_token->length > 0) {
752 type = ctx->flags.local ? AP_REQUEST : CHALLENGE;
753 major = _gss_negoex_add_exchange_message(minor, ctx,
754 type, mech->scheme,
755 mech_output_token);
756 if (major != GSS_S_COMPLETE)
757 return major;
760 if (send_alert) {
761 major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme);
762 if (major != GSS_S_COMPLETE)
763 return major;
766 /* Try to add a VERIFY message if we haven't already done so. */
767 if (!mech->sent_checksum) {
768 major = make_checksum(minor, ctx);
769 if (major != GSS_S_COMPLETE)
770 return major;
773 heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized");
775 output_token->length =
776 krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len;
777 output_token->value = malloc(output_token->length);
778 if (output_token->value == NULL) {
779 *minor = ENOMEM;
780 return GSS_S_FAILURE;
783 krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET);
785 if (krb5_storage_read(ctx->negoex_transcript,
786 output_token->value,
787 output_token->length) != output_token->length) {
788 *minor = ERANGE;
789 gss_release_buffer(&tmpMinor, output_token);
790 return GSS_S_FAILURE;
793 krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END);
795 return GSS_S_COMPLETE;
798 OM_uint32
799 _gss_negoex_init(OM_uint32 *minor,
800 struct gssspnego_optimistic_ctx *opt,
801 gssspnego_ctx ctx,
802 gss_cred_id_t cred,
803 OM_uint32 req_flags,
804 OM_uint32 time_req,
805 const gss_channel_bindings_t input_chan_bindings,
806 gss_const_buffer_t input_token,
807 gss_buffer_t output_token)
809 OM_uint32 major, tmpMinor;
810 gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
811 struct negoex_message *messages = NULL;
812 struct negoex_auth_mech *mech;
813 size_t nmessages = 0;
814 int send_alert = FALSE, mech_error = FALSE;
816 output_token->length = 0;
817 output_token->value = NULL;
819 if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
820 input_token->length != 0)
821 return GSS_S_DEFECTIVE_TOKEN;
823 major = _gss_negoex_begin(minor, ctx);
824 if (major != GSS_S_COMPLETE)
825 goto cleanup;
827 ctx->negoex_step++;
829 if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
830 major = _gss_negoex_parse_token(minor, ctx, input_token,
831 &messages, &nmessages);
832 if (major != GSS_S_COMPLETE)
833 goto cleanup;
836 process_alerts(ctx, messages, nmessages);
838 if (ctx->negoex_step == 1) {
839 /* Choose a random conversation ID. */
840 krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH);
842 /* Query each mech for its metadata (this may prune the mech list). */
843 query_meta_data(ctx, opt, cred, req_flags);
844 } else if (ctx->negoex_step == 2) {
845 /* See if the mech processed the optimistic token. */
846 check_optimistic_result(ctx, messages, nmessages);
848 /* Pass the acceptor metadata to each mech to prune the list. */
849 exchange_meta_data(ctx, cred, req_flags, messages, nmessages);
851 /* Process the ACCEPTOR_NEGO message. */
852 major = process_acceptor_nego(minor, ctx, messages, nmessages);
853 if (major != GSS_S_COMPLETE)
854 goto cleanup;
858 * Process the input token and/or produce an output token. This may prune
859 * the mech list, but on success there will be at least one mech entry.
861 major = mech_init(minor, opt, ctx, cred, req_flags, time_req,
862 input_chan_bindings, messages, nmessages,
863 &mech_output_token, &mech_error);
864 if (major != GSS_S_COMPLETE)
865 goto cleanup;
866 heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
867 "Invalid empty NegoEx mechanism list");
870 * At this point in step 2 we have performed the metadata exchange and
871 * chosen a mech we can use, so discard any fallback mech entries.
873 if (ctx->negoex_step == 2)
874 _gss_negoex_select_auth_mech(ctx, HEIM_TAILQ_FIRST(&ctx->negoex_mechs));
876 major = verify_checksum(minor, ctx, messages, nmessages, input_token,
877 &send_alert);
878 if (major != GSS_S_COMPLETE)
879 goto cleanup;
881 if (input_token != GSS_C_NO_BUFFER) {
882 if (krb5_storage_write(ctx->negoex_transcript,
883 input_token->value,
884 input_token->length) != input_token->length) {
885 major = GSS_S_FAILURE;
886 *minor = ENOMEM;
887 goto cleanup;
891 major = make_output_token(minor, ctx, &mech_output_token, send_alert,
892 output_token);
893 if (major != GSS_S_COMPLETE)
894 goto cleanup;
896 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
897 major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
898 GSS_S_CONTINUE_NEEDED;
900 cleanup:
901 free(messages);
902 gss_release_buffer(&tmpMinor, &mech_output_token);
903 _gss_negoex_end(ctx);
905 if (GSS_ERROR(major)) {
906 if (!mech_error) {
907 krb5_context context = _gss_mg_krb5_context();
908 char *emsg = krb5_get_error_message(context, *minor);
910 gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
911 major, *minor,
912 "NegoEx failed to initialize security context: %s",
913 emsg);
914 krb5_free_error_message(context, emsg);
917 _gss_negoex_release_context(ctx);
920 return major;
923 OM_uint32
924 _gss_negoex_accept(OM_uint32 *minor,
925 gssspnego_ctx ctx,
926 gss_cred_id_t cred,
927 gss_const_buffer_t input_token,
928 const gss_channel_bindings_t input_chan_bindings,
929 gss_buffer_t output_token,
930 gss_cred_id_t *deleg_cred)
932 OM_uint32 major, tmpMinor;
933 gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
934 struct negoex_message *messages = NULL;
935 struct negoex_auth_mech *mech;
936 size_t nmessages;
937 int send_alert = FALSE, mech_error = FALSE;
939 output_token->length = 0;
940 output_token->value = NULL;
941 if (deleg_cred)
942 *deleg_cred = GSS_C_NO_CREDENTIAL;
944 if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
945 major = GSS_S_DEFECTIVE_TOKEN;
946 goto cleanup;
949 major = _gss_negoex_begin(minor, ctx);
950 if (major != GSS_S_COMPLETE)
951 goto cleanup;
953 ctx->negoex_step++;
955 major = _gss_negoex_parse_token(minor, ctx, input_token,
956 &messages, &nmessages);
957 if (major != GSS_S_COMPLETE)
958 goto cleanup;
960 process_alerts(ctx, messages, nmessages);
962 if (ctx->negoex_step == 1) {
964 * Read the INITIATOR_NEGO message to prune the candidate mech list.
966 major = process_initiator_nego(minor, ctx, messages, nmessages);
967 if (major != GSS_S_COMPLETE)
968 goto cleanup;
971 * Pass the initiator metadata to each mech to prune the list, and
972 * query each mech for its acceptor metadata (which may also prune the
973 * list).
975 exchange_meta_data(ctx, cred, 0, messages, nmessages);
976 query_meta_data(ctx, NULL, cred, 0);
978 if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
979 *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
980 major = GSS_S_FAILURE;
981 goto cleanup;
986 * Process the input token and possibly produce an output token. This may
987 * prune the list to a single mech. Continue on error if an output token
988 * is generated, so that we send the token to the initiator.
990 major = mech_accept(minor, ctx, cred, input_chan_bindings,
991 messages, nmessages, &mech_output_token,
992 deleg_cred, &mech_error);
993 if (major != GSS_S_COMPLETE && mech_output_token.length == 0)
994 goto cleanup;
996 if (major == GSS_S_COMPLETE) {
997 major = verify_checksum(minor, ctx, messages, nmessages, input_token,
998 &send_alert);
999 if (major != GSS_S_COMPLETE)
1000 goto cleanup;
1003 if (krb5_storage_write(ctx->negoex_transcript,
1004 input_token->value,
1005 input_token->length) != input_token->length) {
1006 major = GSS_S_FAILURE;
1007 *minor = ENOMEM;
1008 goto cleanup;
1011 major = make_output_token(minor, ctx, &mech_output_token, send_alert,
1012 output_token);
1013 if (major != GSS_S_COMPLETE)
1014 goto cleanup;
1016 mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
1017 major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
1018 GSS_S_CONTINUE_NEEDED;
1020 cleanup:
1021 free(messages);
1022 gss_release_buffer(&tmpMinor, &mech_output_token);
1023 _gss_negoex_end(ctx);
1025 if (GSS_ERROR(major)) {
1026 if (!mech_error) {
1027 krb5_context context = _gss_mg_krb5_context();
1029 gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
1030 major, *minor,
1031 "NegoEx failed to accept security context: %s",
1032 krb5_get_error_message(context, *minor));
1035 _gss_negoex_release_context(ctx);
1038 return major;