2 * Copyright (C) 2011-2021 PADL Software Pty Ltd.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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
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
47 * Once the context token exchange is complete, VERIFY messages are sent to
48 * authenticate the entire exchange.
52 buffer_set_to_crypto(OM_uint32
*minor
,
54 gss_buffer_set_t buffers
,
58 krb5_keyblock keyblock
;
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
66 if (buffers
->count
!= 2 ||
67 buffers
->elements
[1].length
!= sizeof(tmp
)) {
68 *minor
= (OM_uint32
)NEGOEX_NO_VERIFY_KEY
;
72 if (*crypto
!= NULL
) {
73 krb5_crypto_destroy(context
, *crypto
);
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
);
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)
96 get_session_keys(OM_uint32
*minor
,
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
)
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
,
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
)
129 return GSS_S_COMPLETE
;
133 emit_initiator_nego(OM_uint32
*minor
, gssspnego_ctx ctx
)
136 struct negoex_auth_mech
*mech
;
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
);
148 process_initiator_nego(OM_uint32
*minor
,
150 struct negoex_message
*messages
,
153 struct nego_message
*msg
;
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
);
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
;
174 emit_acceptor_nego(OM_uint32
*minor
, gssspnego_ctx ctx
)
178 krb5_generate_random_block(random
, 32);
180 return _gss_negoex_add_nego_message(minor
, ctx
, ACCEPTOR_NEGO
, random
);
184 process_acceptor_nego(OM_uint32
*minor
,
186 struct negoex_message
*messages
,
189 struct nego_message
*msg
;
191 msg
= _gss_negoex_locate_nego_message(messages
, nmessages
, ACCEPTOR_NEGO
);
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
201 _gss_negoex_common_auth_schemes(ctx
, msg
->schemes
, msg
->nschemes
);
203 return GSS_S_COMPLETE
;
207 query_meta_data(gssspnego_ctx ctx
,
208 struct gssspnego_optimistic_ctx
*opt
,
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
);
234 exchange_meta_data(gssspnego_ctx ctx
,
237 struct negoex_message
*messages
,
240 OM_uint32 major
, minor
;
241 struct negoex_auth_mech
*mech
;
242 enum message_type type
;
243 struct exchange_message
*msg
;
246 type
= ctx
->flags
.local
? ACCEPTOR_META_DATA
: INITIATOR_META_DATA
;
248 for (i
= 0; i
< nmessages
; i
++) {
249 if (messages
[i
].type
!= type
)
251 msg
= &messages
[i
].u
.e
;
253 mech
= _gss_negoex_locate_auth_scheme(ctx
, msg
->scheme
);
257 major
= gssspi_exchange_meta_data(&minor
, mech
->oid
, cred
,
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
);
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();
276 krb5_crypto_destroy(context
, mech
->crypto
);
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.
294 check_optimistic_result(gssspnego_ctx ctx
,
295 struct negoex_message
*messages
,
298 struct negoex_auth_mech
*mech
;
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
)
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
);
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. */
333 mech_init(OM_uint32
*minor
,
334 struct gssspnego_optimistic_ctx
*opt
,
339 const gss_channel_bindings_t input_chan_bindings
,
340 struct negoex_message
*messages
,
342 gss_buffer_t output_token
,
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
;
350 krb5_context context
= _gss_mg_krb5_context();
352 output_token
->value
= NULL
;
353 output_token
->length
= 0;
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
;
378 return GSS_S_COMPLETE
;
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
))
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
;
409 major
= gss_init_sec_context(minor
, cred
, &mech
->mech_context
,
410 ctx
->target_name
, mech
->oid
,
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
)
417 else if (GSS_ERROR(major
)) {
418 gss_mg_collect_error(mech
->oid
, major
, *minor
);
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. */
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
);
434 input_token
= GSS_C_NO_BUFFER
;
437 if (HEIM_TAILQ_EMPTY(&ctx
->negoex_mechs
)) {
439 *minor
= first_minor
;
445 /* Perform an acceptor step of the underlying mechanism exchange. */
447 mech_accept(OM_uint32
*minor
,
450 const gss_channel_bindings_t input_chan_bindings
,
451 struct negoex_message
*messages
,
453 gss_buffer_t output_token
,
454 gss_cred_id_t
*deleg_cred
,
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");
467 msg
= _gss_negoex_locate_exchange_message(messages
, nmessages
, AP_REQUEST
);
470 * No input token is okay on the first request or if the mech is
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
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
;
491 /* The initiator has selected a mech; discard other entries. */
492 mech
= _gss_negoex_locate_auth_scheme(ctx
, msg
->scheme
);
494 *minor
= (OM_uint32
)NEGOEX_NO_AVAILABLE_MECHS
;
495 return GSS_S_FAILURE
;
497 _gss_negoex_select_auth_mech(ctx
, mech
);
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
)
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
);
526 /* This was an optimistic token; pretend this never happened. */
527 major
= GSS_S_COMPLETE
;
529 gss_release_buffer(&tmpMinor
, output_token
);
530 gss_delete_sec_context(&tmpMinor
, &mech
->mech_context
, GSS_C_NO_BUFFER
);
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
;
545 verify_key_flags(gssspnego_ctx ctx
, int make_checksum
)
547 return (ctx
->flags
.local
^ make_checksum
) ?
548 NEGOEX_SIGN_KEY
: NEGOEX_VERIFY_KEY
;
552 verify_checksum(OM_uint32
*minor
,
554 struct negoex_message
*messages
,
556 gss_const_buffer_t input_token
,
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
);
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
);
620 mech
->verified_checksum
= TRUE
;
624 krb5_data_free(&iov
[0].data
);
626 return (ret
== 0) ? GSS_S_COMPLETE
: GSS_S_FAILURE
;
630 make_checksum(OM_uint32
*minor
, gssspnego_ctx ctx
)
633 krb5_context context
= _gss_mg_krb5_context();
635 krb5_keyusage usage
= verify_keyusage(ctx
, TRUE
);
637 struct negoex_auth_mech
*mech
= HEIM_TAILQ_FIRST(&ctx
->negoex_mechs
);
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
;
654 return GSS_S_COMPLETE
;
658 ret
= krb5_storage_to_data(ctx
->negoex_transcript
, &d
);
661 return GSS_S_FAILURE
;
664 ret
= krb5_create_checksum(context
, mech
->crypto
,
665 usage
, 0, d
.data
, d
.length
, &cksum
);
669 return GSS_S_FAILURE
;
672 major
= _gss_negoex_add_verify_message(minor
, ctx
, mech
->scheme
,
675 cksum
.checksum
.length
);
676 free_Checksum(&cksum
);
678 if (major
== GSS_S_COMPLETE
)
679 mech
->sent_checksum
= TRUE
;
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.
689 process_alerts(gssspnego_ctx ctx
,
690 struct negoex_message
*messages
,
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
);
700 release_mech_crypto(mech
);
705 make_output_token(OM_uint32
*minor
,
707 gss_buffer_t mech_output_token
,
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
);
733 major
= emit_acceptor_nego(minor
, ctx
);
734 if (major
!= GSS_S_COMPLETE
)
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
,
743 if (major
!= GSS_S_COMPLETE
)
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
,
756 if (major
!= GSS_S_COMPLETE
)
761 major
= _gss_negoex_add_verify_no_key_alert(minor
, ctx
, mech
->scheme
);
762 if (major
!= GSS_S_COMPLETE
)
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
)
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
) {
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
,
787 output_token
->length
) != output_token
->length
) {
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
;
799 _gss_negoex_init(OM_uint32
*minor
,
800 struct gssspnego_optimistic_ctx
*opt
,
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
)
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
)
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
)
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
)
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
,
878 if (major
!= GSS_S_COMPLETE
)
881 if (input_token
!= GSS_C_NO_BUFFER
) {
882 if (krb5_storage_write(ctx
->negoex_transcript
,
884 input_token
->length
) != input_token
->length
) {
885 major
= GSS_S_FAILURE
;
891 major
= make_output_token(minor
, ctx
, &mech_output_token
, send_alert
,
893 if (major
!= GSS_S_COMPLETE
)
896 mech
= HEIM_TAILQ_FIRST(&ctx
->negoex_mechs
);
897 major
= (mech
->complete
&& mech
->verified_checksum
) ? GSS_S_COMPLETE
:
898 GSS_S_CONTINUE_NEEDED
;
902 gss_release_buffer(&tmpMinor
, &mech_output_token
);
903 _gss_negoex_end(ctx
);
905 if (GSS_ERROR(major
)) {
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
,
912 "NegoEx failed to initialize security context: %s",
914 krb5_free_error_message(context
, emsg
);
917 _gss_negoex_release_context(ctx
);
924 _gss_negoex_accept(OM_uint32
*minor
,
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
;
937 int send_alert
= FALSE
, mech_error
= FALSE
;
939 output_token
->length
= 0;
940 output_token
->value
= NULL
;
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
;
949 major
= _gss_negoex_begin(minor
, ctx
);
950 if (major
!= GSS_S_COMPLETE
)
955 major
= _gss_negoex_parse_token(minor
, ctx
, input_token
,
956 &messages
, &nmessages
);
957 if (major
!= GSS_S_COMPLETE
)
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
)
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
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
;
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)
996 if (major
== GSS_S_COMPLETE
) {
997 major
= verify_checksum(minor
, ctx
, messages
, nmessages
, input_token
,
999 if (major
!= GSS_S_COMPLETE
)
1003 if (krb5_storage_write(ctx
->negoex_transcript
,
1005 input_token
->length
) != input_token
->length
) {
1006 major
= GSS_S_FAILURE
;
1011 major
= make_output_token(minor
, ctx
, &mech_output_token
, send_alert
,
1013 if (major
!= GSS_S_COMPLETE
)
1016 mech
= HEIM_TAILQ_FIRST(&ctx
->negoex_mechs
);
1017 major
= (mech
->complete
&& mech
->verified_checksum
) ? GSS_S_COMPLETE
:
1018 GSS_S_CONTINUE_NEEDED
;
1022 gss_release_buffer(&tmpMinor
, &mech_output_token
);
1023 _gss_negoex_end(ctx
);
1025 if (GSS_ERROR(major
)) {
1027 krb5_context context
= _gss_mg_krb5_context();
1029 gss_mg_set_error_string(GSS_SPNEGO_MECHANISM
,
1031 "NegoEx failed to accept security context: %s",
1032 krb5_get_error_message(context
, *minor
));
1035 _gss_negoex_release_context(ctx
);