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
zero_and_release_buffer_set(gss_buffer_set_t * pbuffers)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
buffer_set_to_key(OM_uint32 * minor,gss_buffer_set_t buffers,krb5_keyblock * key)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
get_session_keys(OM_uint32 * minor,struct negoex_auth_mech * mech)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
emit_initiator_nego(OM_uint32 * minor,spnego_gss_ctx_id_t ctx)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
process_initiator_nego(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,struct negoex_message * messages,size_t nmessages)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
emit_acceptor_nego(OM_uint32 * minor,spnego_gss_ctx_id_t ctx)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
process_acceptor_nego(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,struct negoex_message * messages,size_t nmessages)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
query_meta_data(spnego_gss_ctx_id_t ctx,gss_cred_id_t cred,gss_name_t target,OM_uint32 req_flags)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
exchange_meta_data(spnego_gss_ctx_id_t ctx,gss_cred_id_t cred,gss_name_t target,OM_uint32 req_flags,struct negoex_message * messages,size_t nmessages)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
check_optimistic_result(spnego_gss_ctx_id_t ctx,struct negoex_message * messages,size_t nmessages)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
mech_init(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_cred_id_t cred,gss_name_t target,OM_uint32 req_flags,OM_uint32 time_req,struct negoex_message * messages,size_t nmessages,gss_channel_bindings_t bindings,gss_buffer_t output_token,OM_uint32 * time_rec)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
mech_accept(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_cred_id_t cred,struct negoex_message * messages,size_t nmessages,gss_channel_bindings_t bindings,gss_buffer_t output_token,OM_uint32 * time_rec)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
verify_keyusage(spnego_gss_ctx_id_t ctx,int make_checksum)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
verify_checksum(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,struct negoex_message * messages,size_t nmessages,gss_buffer_t input_token,int * send_alert_out)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
make_checksum(OM_uint32 * minor,spnego_gss_ctx_id_t ctx)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
process_alerts(spnego_gss_ctx_id_t ctx,struct negoex_message * messages,uint32_t nmessages)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
make_output_token(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_buffer_t mech_output_token,int send_alert,gss_buffer_t output_token)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
negoex_init(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_cred_id_t cred,gss_name_t target_name,OM_uint32 req_flags,OM_uint32 time_req,gss_buffer_t input_token,gss_channel_bindings_t bindings,gss_buffer_t output_token,OM_uint32 * time_rec)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
negoex_accept(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_cred_id_t cred,gss_buffer_t input_token,gss_channel_bindings_t bindings,gss_buffer_t output_token,OM_uint32 * time_rec)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