Line data Source code
1 : /*
2 : * Copyright (C) 2011-2021 PADL Software Pty Ltd.
3 : * All rights reserved.
4 : *
5 : * Redistribution and use in source and binary forms, with or without
6 : * modification, are permitted provided that the following conditions
7 : * are met:
8 : *
9 : * * Redistributions of source code must retain the above copyright
10 : * notice, this list of conditions and the following disclaimer.
11 : *
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.
16 : *
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.
29 : */
30 :
31 : #include "spnego_locl.h"
32 :
33 : /*
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.
37 : *
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.
42 : *
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.
46 : *
47 : * Once the context token exchange is complete, VERIFY messages are sent to
48 : * authenticate the entire exchange.
49 : */
50 :
51 : static OM_uint32
52 0 : buffer_set_to_crypto(OM_uint32 *minor,
53 : krb5_context context,
54 : gss_buffer_set_t buffers,
55 : krb5_crypto *crypto)
56 : {
57 0 : krb5_error_code ret;
58 0 : krb5_keyblock keyblock;
59 0 : OM_uint32 tmp;
60 :
61 : /*
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.
65 : */
66 0 : if (buffers->count != 2 ||
67 0 : buffers->elements[1].length != sizeof(tmp)) {
68 0 : *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
69 0 : return GSS_S_FAILURE;
70 : }
71 :
72 0 : if (*crypto != NULL) {
73 0 : krb5_crypto_destroy(context, *crypto);
74 0 : *crypto = NULL;
75 : }
76 :
77 0 : keyblock.keyvalue.data = buffers->elements[0].value;
78 0 : keyblock.keyvalue.length = buffers->elements[0].length;
79 0 : _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp);
80 0 : keyblock.keytype = tmp;
81 :
82 0 : ret = krb5_crypto_init(context, &keyblock, 0, crypto);
83 0 : if (ret) {
84 0 : *minor = ret;
85 0 : return GSS_S_FAILURE;
86 : }
87 :
88 0 : return GSS_S_COMPLETE;
89 : }
90 :
91 : #define NEGOEX_SIGN_KEY 1
92 : #define NEGOEX_VERIFY_KEY 2
93 : #define NEGOEX_BOTH_KEYS (NEGOEX_SIGN_KEY|NEGOEX_VERIFY_KEY)
94 :
95 : static OM_uint32
96 0 : get_session_keys(OM_uint32 *minor,
97 : krb5_context context,
98 : OM_uint32 flags,
99 : struct negoex_auth_mech *mech)
100 : {
101 0 : OM_uint32 major, tmpMinor;
102 0 : gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
103 :
104 0 : if (flags & NEGOEX_SIGN_KEY) {
105 0 : major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
106 : GSS_C_INQ_NEGOEX_KEY, &buffers);
107 0 : if (major == GSS_S_COMPLETE) {
108 0 : major = buffer_set_to_crypto(minor, context,
109 : buffers, &mech->crypto);
110 0 : _gss_secure_release_buffer_set(&tmpMinor, &buffers);
111 0 : if (major != GSS_S_COMPLETE)
112 0 : return major;
113 : }
114 : }
115 :
116 0 : if (flags & NEGOEX_VERIFY_KEY) {
117 0 : major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context,
118 : GSS_C_INQ_NEGOEX_VERIFY_KEY,
119 : &buffers);
120 0 : if (major == GSS_S_COMPLETE) {
121 0 : major = buffer_set_to_crypto(minor, context,
122 : buffers, &mech->verify_crypto);
123 0 : _gss_secure_release_buffer_set(&tmpMinor, &buffers);
124 0 : if (major != GSS_S_COMPLETE)
125 0 : return major;
126 : }
127 : }
128 :
129 0 : return GSS_S_COMPLETE;
130 : }
131 :
132 : static OM_uint32
133 0 : emit_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx)
134 : {
135 0 : uint8_t random[32];
136 0 : struct negoex_auth_mech *mech;
137 0 : size_t i = 0;
138 :
139 0 : krb5_generate_random_block(random, sizeof(random));
140 :
141 0 : HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
142 0 : _gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme);
143 :
144 0 : return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random);
145 : }
146 :
147 : static OM_uint32
148 0 : process_initiator_nego(OM_uint32 *minor,
149 : gssspnego_ctx ctx,
150 : struct negoex_message *messages,
151 : size_t nmessages)
152 : {
153 0 : struct nego_message *msg;
154 0 : size_t i;
155 :
156 0 : heim_assert(!ctx->flags.local && ctx->negoex_step == 1,
157 : "NegoEx INITIATOR_NEGO token received after first leg");
158 :
159 0 : msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
160 0 : if (msg == NULL) {
161 0 : *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
162 0 : return GSS_S_DEFECTIVE_TOKEN;
163 : }
164 :
165 0 : for (i = 0; i < msg->nschemes; i++)
166 0 : _gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]);
167 :
168 0 : _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
169 :
170 0 : return GSS_S_COMPLETE;
171 : }
172 :
173 : static OM_uint32
174 0 : emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx)
175 : {
176 0 : uint8_t random[32];
177 :
178 0 : krb5_generate_random_block(random, 32);
179 :
180 0 : return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random);
181 : }
182 :
183 : static OM_uint32
184 0 : process_acceptor_nego(OM_uint32 *minor,
185 : gssspnego_ctx ctx,
186 : struct negoex_message *messages,
187 : size_t nmessages)
188 : {
189 0 : struct nego_message *msg;
190 :
191 0 : msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
192 0 : if (msg == NULL) {
193 0 : *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE;
194 0 : return GSS_S_DEFECTIVE_TOKEN;
195 : }
196 :
197 : /*
198 : * Reorder and prune our mech list to match the acceptor's list (or a
199 : * subset of it).
200 : */
201 0 : _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
202 :
203 0 : return GSS_S_COMPLETE;
204 : }
205 :
206 : static void
207 0 : query_meta_data(gssspnego_ctx ctx,
208 : struct gssspnego_optimistic_ctx *opt,
209 : gss_cred_id_t cred,
210 : OM_uint32 req_flags)
211 : {
212 0 : OM_uint32 major, minor;
213 0 : struct negoex_auth_mech *p, *next;
214 :
215 : /*
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.
220 : */
221 0 : HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
222 0 : if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0)
223 0 : p->mech_context = opt->gssctx;;
224 :
225 0 : major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
226 0 : ctx->target_name, req_flags, &p->metadata);
227 : /* GSS_Query_meta_data failure removes mechanism from list. */
228 0 : if (major != GSS_S_COMPLETE)
229 0 : _gss_negoex_delete_auth_mech(ctx, p);
230 : }
231 0 : }
232 :
233 : static void
234 0 : 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)
239 : {
240 0 : OM_uint32 major, minor;
241 0 : struct negoex_auth_mech *mech;
242 0 : enum message_type type;
243 0 : struct exchange_message *msg;
244 0 : uint32_t i;
245 :
246 0 : type = ctx->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
247 :
248 0 : for (i = 0; i < nmessages; i++) {
249 0 : if (messages[i].type != type)
250 0 : continue;
251 0 : msg = &messages[i].u.e;
252 :
253 0 : mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
254 0 : if (mech == NULL)
255 0 : continue;
256 :
257 0 : major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
258 : &mech->mech_context,
259 0 : ctx->target_name,
260 0 : req_flags, &msg->token);
261 : /* GSS_Exchange_meta_data failure removes mechanism from list. */
262 0 : if (major != GSS_S_COMPLETE)
263 0 : _gss_negoex_delete_auth_mech(ctx, mech);
264 : }
265 0 : }
266 :
267 : static void
268 0 : release_mech_crypto(struct negoex_auth_mech *mech)
269 : {
270 0 : krb5_context context = NULL;
271 :
272 0 : if (mech->crypto || mech->verify_crypto)
273 0 : context = _gss_mg_krb5_context();
274 :
275 0 : if (mech->crypto) {
276 0 : krb5_crypto_destroy(context, mech->crypto);
277 0 : mech->crypto = NULL;
278 : }
279 :
280 0 : if (mech->verify_crypto) {
281 0 : krb5_crypto_destroy(context, mech->verify_crypto);
282 0 : mech->verify_crypto = NULL;
283 : }
284 :
285 0 : mech->sent_checksum = FALSE;
286 0 : }
287 :
288 : /*
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.
292 : */
293 : static void
294 0 : check_optimistic_result(gssspnego_ctx ctx,
295 : struct negoex_message *messages,
296 : size_t nmessages)
297 : {
298 0 : struct negoex_auth_mech *mech;
299 0 : OM_uint32 tmpMinor;
300 :
301 0 : heim_assert(ctx->flags.local && ctx->negoex_step == 2,
302 : "NegoEx optimistic result should only be checked in second leg");
303 :
304 : /* Do nothing if we didn't make an optimistic context. */
305 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
306 0 : if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
307 0 : return;
308 :
309 : /*
310 : * If the acceptor used the optimistic token, it will send an acceptor
311 : * token or a checksum (or both) in its first reply.
312 : */
313 0 : if (_gss_negoex_locate_exchange_message(messages, nmessages,
314 0 : CHALLENGE) != NULL ||
315 0 : _gss_negoex_locate_verify_message(messages, nmessages) != NULL) {
316 : /*
317 : * The acceptor continued the optimistic mech, and metadata exchange
318 : * didn't remove it. Commit to this mechanism.
319 : */
320 0 : _gss_negoex_select_auth_mech(ctx, mech);
321 : } else {
322 : /*
323 : * The acceptor ignored the optimistic token. Restart the mech.
324 : */
325 0 : gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
326 0 : release_mech_crypto(mech);
327 0 : mech->complete = FALSE;
328 : }
329 : }
330 :
331 : /* Perform an initiator step of the underlying mechanism exchange. */
332 : static OM_uint32
333 0 : 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)
344 : {
345 0 : OM_uint32 major, first_major = GSS_S_COMPLETE, first_minor = 0;
346 0 : struct negoex_auth_mech *mech = NULL;
347 0 : gss_buffer_t input_token = GSS_C_NO_BUFFER;
348 0 : struct exchange_message *msg;
349 0 : int first_mech;
350 0 : krb5_context context = _gss_mg_krb5_context();
351 :
352 0 : output_token->value = NULL;
353 0 : output_token->length = 0;
354 :
355 0 : *mech_error = FALSE;
356 :
357 : /* Allow disabling of optimistic token for testing. */
358 0 : if (ctx->negoex_step == 1 &&
359 0 : secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
360 0 : return GSS_S_COMPLETE;
361 :
362 0 : if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
363 0 : *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
364 0 : return GSS_S_FAILURE;
365 : }
366 :
367 : /*
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.
371 : */
372 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
373 0 : msg = _gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE);
374 0 : if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme))
375 0 : input_token = &msg->token;
376 :
377 0 : if (mech->complete)
378 0 : return GSS_S_COMPLETE;
379 :
380 0 : first_mech = TRUE;
381 0 : major = GSS_S_BAD_MECH;
382 :
383 0 : while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
384 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
385 :
386 : /*
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();
390 : */
391 0 : if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) {
392 0 : heim_assert(ctx->negoex_step == 1,
393 : "SPNEGO optimistic token only valid for NegoEx first leg");
394 :
395 0 : major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token);
396 0 : if (GSS_ERROR(major))
397 0 : return major;
398 :
399 0 : ctx->negotiated_mech_type = opt->negotiated_mech_type;
400 0 : ctx->mech_flags = opt->optimistic_flags;
401 0 : ctx->mech_time_rec = opt->optimistic_time_rec;
402 :
403 0 : mech->mech_context = opt->gssctx;
404 0 : opt->gssctx = NULL; /* steal it */
405 :
406 0 : mech->complete = opt->complete;
407 0 : major = GSS_S_COMPLETE;
408 : } else {
409 0 : major = gss_init_sec_context(minor, cred, &mech->mech_context,
410 0 : 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 0 : if (major == GSS_S_COMPLETE)
416 0 : mech->complete = 1;
417 0 : else if (GSS_ERROR(major)) {
418 0 : gss_mg_collect_error(mech->oid, major, *minor);
419 0 : *mech_error = TRUE;
420 : }
421 : }
422 0 : if (!GSS_ERROR(major))
423 0 : return get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech);
424 :
425 : /* Remember the error we got from the first mech. */
426 0 : if (first_mech) {
427 0 : first_major = major;
428 0 : first_minor = *minor;
429 : }
430 :
431 : /* If we still have multiple mechs to try, move on to the next one. */
432 0 : _gss_negoex_delete_auth_mech(ctx, mech);
433 0 : first_mech = FALSE;
434 0 : input_token = GSS_C_NO_BUFFER;
435 : }
436 :
437 0 : if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
438 0 : major = first_major;
439 0 : *minor = first_minor;
440 : }
441 :
442 0 : return major;
443 : }
444 :
445 : /* Perform an acceptor step of the underlying mechanism exchange. */
446 : static OM_uint32
447 0 : 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)
456 : {
457 0 : OM_uint32 major, tmpMinor;
458 0 : struct negoex_auth_mech *mech;
459 0 : struct exchange_message *msg;
460 0 : krb5_context context = _gss_mg_krb5_context();
461 :
462 0 : heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
463 : "Acceptor NegoEx function called in wrong sequence");
464 :
465 0 : *mech_error = FALSE;
466 :
467 0 : msg = _gss_negoex_locate_exchange_message(messages, nmessages, AP_REQUEST);
468 0 : if (msg == NULL) {
469 : /*
470 : * No input token is okay on the first request or if the mech is
471 : * complete.
472 : */
473 0 : if (ctx->negoex_step == 1 ||
474 0 : HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
475 0 : return GSS_S_COMPLETE;
476 0 : *minor = (OM_uint32)NEGOEX_MISSING_AP_REQUEST_MESSAGE;
477 0 : return GSS_S_DEFECTIVE_TOKEN;
478 : }
479 :
480 0 : if (ctx->negoex_step == 1) {
481 : /*
482 : * Ignore the optimistic token if it isn't for our most preferred
483 : * mech.
484 : */
485 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
486 0 : if (!GUID_EQ(msg->scheme, mech->scheme)) {
487 0 : _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech");
488 0 : return GSS_S_COMPLETE;
489 : }
490 : } else {
491 : /* The initiator has selected a mech; discard other entries. */
492 0 : mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
493 0 : if (mech == NULL) {
494 0 : *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
495 0 : return GSS_S_FAILURE;
496 : }
497 0 : _gss_negoex_select_auth_mech(ctx, mech);
498 : }
499 :
500 0 : if (mech->complete)
501 0 : return GSS_S_COMPLETE;
502 :
503 0 : if (ctx->mech_src_name != GSS_C_NO_NAME)
504 0 : gss_release_name(&tmpMinor, &ctx->mech_src_name);
505 0 : if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL)
506 0 : gss_release_cred(&tmpMinor, deleg_cred);
507 :
508 0 : major = gss_accept_sec_context(minor, &mech->mech_context, cred,
509 0 : &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 0 : if (major == GSS_S_COMPLETE)
514 0 : mech->complete = 1;
515 :
516 0 : if (!GSS_ERROR(major)) {
517 0 : if (major == GSS_S_COMPLETE &&
518 0 : !gss_oid_equal(ctx->negotiated_mech_type, mech->oid))
519 0 : _gss_mg_log(1, "negoex client didn't send the mech they said they would");
520 :
521 0 : major = get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech);
522 0 : } else if (ctx->negoex_step == 1) {
523 0 : gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor);
524 0 : *mech_error = TRUE;
525 :
526 : /* This was an optimistic token; pretend this never happened. */
527 0 : major = GSS_S_COMPLETE;
528 0 : *minor = 0;
529 0 : gss_release_buffer(&tmpMinor, output_token);
530 0 : gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER);
531 : }
532 :
533 0 : return major;
534 : }
535 :
536 : static krb5_keyusage
537 0 : verify_keyusage(gssspnego_ctx ctx, int make_checksum)
538 : {
539 : /* Of course, these are the wrong way around in the spec. */
540 0 : return (ctx->flags.local ^ !make_checksum) ?
541 0 : NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
542 : }
543 :
544 : static OM_uint32
545 0 : verify_key_flags(gssspnego_ctx ctx, int make_checksum)
546 : {
547 0 : return (ctx->flags.local ^ make_checksum) ?
548 0 : NEGOEX_SIGN_KEY : NEGOEX_VERIFY_KEY;
549 : }
550 :
551 : static OM_uint32
552 0 : 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)
558 : {
559 0 : krb5_error_code ret;
560 0 : struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
561 0 : struct verify_message *msg;
562 0 : krb5_context context = _gss_mg_krb5_context();
563 0 : krb5_crypto_iov iov[3];
564 0 : krb5_keyusage usage = verify_keyusage(ctx, FALSE);
565 :
566 0 : *send_alert_out = FALSE;
567 0 : heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum");
568 :
569 : /*
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.
572 : */
573 0 : msg = _gss_negoex_locate_verify_message(messages, nmessages);
574 0 : if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme))
575 0 : return GSS_S_COMPLETE;
576 :
577 : /*
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).
580 : */
581 0 : if (mech->verify_crypto == NULL)
582 0 : get_session_keys(minor, context, verify_key_flags(ctx, FALSE), mech);
583 :
584 : /*
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.
587 : */
588 0 : if (mech->verify_crypto == NULL) {
589 0 : *send_alert_out = TRUE;
590 0 : return GSS_S_COMPLETE;
591 : }
592 :
593 0 : if (!krb5_checksum_is_keyed(context, msg->cksum_type)) {
594 0 : *minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM;
595 0 : return GSS_S_BAD_SIG;
596 : }
597 :
598 : /*
599 : * Verify the checksum over the existing transcript and the portion of the
600 : * input token leading up to the verify message.
601 : */
602 0 : iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
603 0 : ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data);
604 0 : if (ret) {
605 0 : *minor = ret;
606 0 : return GSS_S_FAILURE;
607 : }
608 :
609 0 : iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
610 0 : iov[1].data.data = input_token->value;
611 0 : iov[1].data.length = msg->offset_in_token;
612 :
613 0 : iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
614 0 : iov[2].data.data = (uint8_t *)msg->cksum;
615 0 : iov[2].data.length = msg->cksum_len;
616 :
617 0 : ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage,
618 : iov, sizeof(iov) / sizeof(iov[0]), NULL);
619 0 : if (ret == 0)
620 0 : mech->verified_checksum = TRUE;
621 : else
622 0 : *minor = ret;
623 :
624 0 : krb5_data_free(&iov[0].data);
625 :
626 0 : return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
627 : }
628 :
629 : static OM_uint32
630 0 : make_checksum(OM_uint32 *minor, gssspnego_ctx ctx)
631 : {
632 0 : krb5_error_code ret;
633 0 : krb5_context context = _gss_mg_krb5_context();
634 0 : krb5_data d;
635 0 : krb5_keyusage usage = verify_keyusage(ctx, TRUE);
636 0 : krb5_checksum cksum;
637 0 : struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
638 0 : OM_uint32 major;
639 :
640 0 : heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum");
641 :
642 0 : if (mech->crypto == NULL) {
643 0 : if (mech->complete) {
644 : /*
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).
647 : */
648 0 : get_session_keys(minor, context, verify_key_flags(ctx, TRUE), mech);
649 0 : if (mech->crypto == NULL) {
650 0 : *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY;
651 0 : return GSS_S_UNAVAILABLE;
652 : }
653 : } else {
654 0 : return GSS_S_COMPLETE;
655 : }
656 : }
657 :
658 0 : ret = krb5_storage_to_data(ctx->negoex_transcript, &d);
659 0 : if (ret) {
660 0 : *minor = ret;
661 0 : return GSS_S_FAILURE;
662 : }
663 :
664 0 : ret = krb5_create_checksum(context, mech->crypto,
665 : usage, 0, d.data, d.length, &cksum);
666 0 : krb5_data_free(&d);
667 0 : if (ret) {
668 0 : *minor = ret;
669 0 : return GSS_S_FAILURE;
670 : }
671 :
672 0 : major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme,
673 0 : cksum.cksumtype,
674 0 : cksum.checksum.data,
675 0 : cksum.checksum.length);
676 0 : free_Checksum(&cksum);
677 :
678 0 : if (major == GSS_S_COMPLETE)
679 0 : mech->sent_checksum = TRUE;
680 :
681 0 : return major;
682 : }
683 :
684 : /*
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.
687 : */
688 : static void
689 0 : process_alerts(gssspnego_ctx ctx,
690 : struct negoex_message *messages,
691 : uint32_t nmessages)
692 : {
693 0 : struct alert_message *msg;
694 0 : struct negoex_auth_mech *mech;
695 :
696 0 : msg = _gss_negoex_locate_alert_message(messages, nmessages);
697 0 : if (msg != NULL && msg->verify_no_key) {
698 0 : mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme);
699 0 : if (mech != NULL)
700 0 : release_mech_crypto(mech);
701 : }
702 0 : }
703 :
704 : static OM_uint32
705 0 : 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)
710 : {
711 0 : OM_uint32 major, tmpMinor;
712 0 : struct negoex_auth_mech *mech;
713 0 : enum message_type type;
714 0 : off_t old_transcript_len;
715 :
716 0 : output_token->length = 0;
717 0 : output_token->value = NULL;
718 :
719 0 : old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR);
720 :
721 : /*
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.
724 : */
725 0 : if (mech_output_token->length == 0 &&
726 0 : HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
727 0 : return GSS_S_COMPLETE;
728 :
729 0 : if (ctx->negoex_step == 1) {
730 0 : if (ctx->flags.local)
731 0 : major = emit_initiator_nego(minor, ctx);
732 : else
733 0 : major = emit_acceptor_nego(minor, ctx);
734 0 : if (major != GSS_S_COMPLETE)
735 0 : return major;
736 :
737 0 : type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
738 0 : HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
739 0 : if (mech->metadata.length > 0) {
740 0 : major = _gss_negoex_add_exchange_message(minor, ctx,
741 0 : type, mech->scheme,
742 0 : &mech->metadata);
743 0 : if (major != GSS_S_COMPLETE)
744 0 : return major;
745 : }
746 : }
747 : }
748 :
749 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
750 :
751 0 : if (mech_output_token->length > 0) {
752 0 : type = ctx->flags.local ? AP_REQUEST : CHALLENGE;
753 0 : major = _gss_negoex_add_exchange_message(minor, ctx,
754 0 : type, mech->scheme,
755 : mech_output_token);
756 0 : if (major != GSS_S_COMPLETE)
757 0 : return major;
758 : }
759 :
760 0 : if (send_alert) {
761 0 : major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme);
762 0 : if (major != GSS_S_COMPLETE)
763 0 : return major;
764 : }
765 :
766 : /* Try to add a VERIFY message if we haven't already done so. */
767 0 : if (!mech->sent_checksum) {
768 0 : major = make_checksum(minor, ctx);
769 0 : if (major != GSS_S_COMPLETE)
770 0 : return major;
771 : }
772 :
773 0 : heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized");
774 :
775 0 : output_token->length =
776 0 : krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len;
777 0 : output_token->value = malloc(output_token->length);
778 0 : if (output_token->value == NULL) {
779 0 : *minor = ENOMEM;
780 0 : return GSS_S_FAILURE;
781 : }
782 :
783 0 : krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET);
784 :
785 0 : if (krb5_storage_read(ctx->negoex_transcript,
786 : output_token->value,
787 0 : output_token->length) != output_token->length) {
788 0 : *minor = ERANGE;
789 0 : gss_release_buffer(&tmpMinor, output_token);
790 0 : return GSS_S_FAILURE;
791 : }
792 :
793 0 : krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END);
794 :
795 0 : return GSS_S_COMPLETE;
796 : }
797 :
798 : OM_uint32
799 0 : _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)
808 : {
809 0 : OM_uint32 major, tmpMinor;
810 0 : gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
811 0 : struct negoex_message *messages = NULL;
812 0 : struct negoex_auth_mech *mech;
813 0 : size_t nmessages = 0;
814 0 : int send_alert = FALSE, mech_error = FALSE;
815 :
816 0 : output_token->length = 0;
817 0 : output_token->value = NULL;
818 :
819 0 : if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
820 0 : input_token->length != 0)
821 0 : return GSS_S_DEFECTIVE_TOKEN;
822 :
823 0 : major = _gss_negoex_begin(minor, ctx);
824 0 : if (major != GSS_S_COMPLETE)
825 0 : goto cleanup;
826 :
827 0 : ctx->negoex_step++;
828 :
829 0 : if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
830 0 : major = _gss_negoex_parse_token(minor, ctx, input_token,
831 : &messages, &nmessages);
832 0 : if (major != GSS_S_COMPLETE)
833 0 : goto cleanup;
834 : }
835 :
836 0 : process_alerts(ctx, messages, nmessages);
837 :
838 0 : if (ctx->negoex_step == 1) {
839 : /* Choose a random conversation ID. */
840 0 : krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH);
841 :
842 : /* Query each mech for its metadata (this may prune the mech list). */
843 0 : query_meta_data(ctx, opt, cred, req_flags);
844 0 : } else if (ctx->negoex_step == 2) {
845 : /* See if the mech processed the optimistic token. */
846 0 : check_optimistic_result(ctx, messages, nmessages);
847 :
848 : /* Pass the acceptor metadata to each mech to prune the list. */
849 0 : exchange_meta_data(ctx, cred, req_flags, messages, nmessages);
850 :
851 : /* Process the ACCEPTOR_NEGO message. */
852 0 : major = process_acceptor_nego(minor, ctx, messages, nmessages);
853 0 : if (major != GSS_S_COMPLETE)
854 0 : goto cleanup;
855 : }
856 :
857 : /*
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.
860 : */
861 0 : major = mech_init(minor, opt, ctx, cred, req_flags, time_req,
862 : input_chan_bindings, messages, nmessages,
863 : &mech_output_token, &mech_error);
864 0 : if (major != GSS_S_COMPLETE)
865 0 : goto cleanup;
866 0 : heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs),
867 : "Invalid empty NegoEx mechanism list");
868 :
869 : /*
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.
872 : */
873 0 : if (ctx->negoex_step == 2)
874 0 : _gss_negoex_select_auth_mech(ctx, HEIM_TAILQ_FIRST(&ctx->negoex_mechs));
875 :
876 0 : major = verify_checksum(minor, ctx, messages, nmessages, input_token,
877 : &send_alert);
878 0 : if (major != GSS_S_COMPLETE)
879 0 : goto cleanup;
880 :
881 0 : if (input_token != GSS_C_NO_BUFFER) {
882 0 : if (krb5_storage_write(ctx->negoex_transcript,
883 0 : input_token->value,
884 0 : input_token->length) != input_token->length) {
885 0 : major = GSS_S_FAILURE;
886 0 : *minor = ENOMEM;
887 0 : goto cleanup;
888 : }
889 : }
890 :
891 0 : major = make_output_token(minor, ctx, &mech_output_token, send_alert,
892 : output_token);
893 0 : if (major != GSS_S_COMPLETE)
894 0 : goto cleanup;
895 :
896 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
897 0 : major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
898 : GSS_S_CONTINUE_NEEDED;
899 :
900 0 : cleanup:
901 0 : free(messages);
902 0 : gss_release_buffer(&tmpMinor, &mech_output_token);
903 0 : _gss_negoex_end(ctx);
904 :
905 0 : if (GSS_ERROR(major)) {
906 0 : if (!mech_error) {
907 0 : krb5_context context = _gss_mg_krb5_context();
908 0 : const char *emsg = krb5_get_error_message(context, *minor);
909 :
910 0 : gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
911 : major, *minor,
912 : "NegoEx failed to initialize security context: %s",
913 : emsg);
914 0 : krb5_free_error_message(context, emsg);
915 : }
916 :
917 0 : _gss_negoex_release_context(ctx);
918 : }
919 :
920 0 : return major;
921 : }
922 :
923 : OM_uint32
924 0 : _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)
931 : {
932 0 : OM_uint32 major, tmpMinor;
933 0 : gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER;
934 0 : struct negoex_message *messages = NULL;
935 0 : struct negoex_auth_mech *mech;
936 0 : size_t nmessages;
937 0 : int send_alert = FALSE, mech_error = FALSE;
938 :
939 0 : output_token->length = 0;
940 0 : output_token->value = NULL;
941 0 : if (deleg_cred)
942 0 : *deleg_cred = GSS_C_NO_CREDENTIAL;
943 :
944 0 : if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
945 0 : major = GSS_S_DEFECTIVE_TOKEN;
946 0 : goto cleanup;
947 : }
948 :
949 0 : major = _gss_negoex_begin(minor, ctx);
950 0 : if (major != GSS_S_COMPLETE)
951 0 : goto cleanup;
952 :
953 0 : ctx->negoex_step++;
954 :
955 0 : major = _gss_negoex_parse_token(minor, ctx, input_token,
956 : &messages, &nmessages);
957 0 : if (major != GSS_S_COMPLETE)
958 0 : goto cleanup;
959 :
960 0 : process_alerts(ctx, messages, nmessages);
961 :
962 0 : if (ctx->negoex_step == 1) {
963 : /*
964 : * Read the INITIATOR_NEGO message to prune the candidate mech list.
965 : */
966 0 : major = process_initiator_nego(minor, ctx, messages, nmessages);
967 0 : if (major != GSS_S_COMPLETE)
968 0 : goto cleanup;
969 :
970 : /*
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).
974 : */
975 0 : exchange_meta_data(ctx, cred, 0, messages, nmessages);
976 0 : query_meta_data(ctx, NULL, cred, 0);
977 :
978 0 : if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) {
979 0 : *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS;
980 0 : major = GSS_S_FAILURE;
981 0 : goto cleanup;
982 : }
983 : }
984 :
985 : /*
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.
989 : */
990 0 : major = mech_accept(minor, ctx, cred, input_chan_bindings,
991 : messages, nmessages, &mech_output_token,
992 : deleg_cred, &mech_error);
993 0 : if (major != GSS_S_COMPLETE && mech_output_token.length == 0)
994 0 : goto cleanup;
995 :
996 0 : if (major == GSS_S_COMPLETE) {
997 0 : major = verify_checksum(minor, ctx, messages, nmessages, input_token,
998 : &send_alert);
999 0 : if (major != GSS_S_COMPLETE)
1000 0 : goto cleanup;
1001 : }
1002 :
1003 0 : if (krb5_storage_write(ctx->negoex_transcript,
1004 0 : input_token->value,
1005 0 : input_token->length) != input_token->length) {
1006 0 : major = GSS_S_FAILURE;
1007 0 : *minor = ENOMEM;
1008 0 : goto cleanup;
1009 : }
1010 :
1011 0 : major = make_output_token(minor, ctx, &mech_output_token, send_alert,
1012 : output_token);
1013 0 : if (major != GSS_S_COMPLETE)
1014 0 : goto cleanup;
1015 :
1016 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
1017 0 : major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
1018 : GSS_S_CONTINUE_NEEDED;
1019 :
1020 0 : cleanup:
1021 0 : free(messages);
1022 0 : gss_release_buffer(&tmpMinor, &mech_output_token);
1023 0 : _gss_negoex_end(ctx);
1024 :
1025 0 : if (GSS_ERROR(major)) {
1026 0 : if (!mech_error) {
1027 0 : krb5_context context = _gss_mg_krb5_context();
1028 0 : const char *emsg = krb5_get_error_message(context, *minor);
1029 :
1030 0 : gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
1031 : major, *minor,
1032 : "NegoEx failed to accept security context: %s",
1033 : emsg);
1034 0 : krb5_free_error_message(context, emsg);
1035 : }
1036 :
1037 0 : _gss_negoex_release_context(ctx);
1038 : }
1039 :
1040 0 : return major;
1041 : }
|