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