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 "gssapiP_spnego.h"
33 #include <generic/gssapiP_generic.h>
34 #include "k5-input.h"
35
36 static void
37 release_auth_mech(struct negoex_auth_mech *mech);
38
39 OM_uint32
negoex_random(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,uint8_t * data,size_t length)40 negoex_random(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
41 uint8_t *data, size_t length)
42 {
43 krb5_data d = make_data(data, length);
44
45 *minor = krb5_c_random_make_octets(ctx->kctx, &d);
46 return *minor ? GSS_S_FAILURE : GSS_S_COMPLETE;
47 }
48
49 /*
50 * SPNEGO functions expect to find the active mech context in ctx->ctx_handle,
51 * but the metadata exchange APIs force us to have one mech context per mech
52 * entry. To address this mismatch, move the active mech context (if we have
53 * one) to ctx->ctx_handle at the end of NegoEx processing.
54 */
55 void
negoex_prep_context_for_spnego(spnego_gss_ctx_id_t ctx)56 negoex_prep_context_for_spnego(spnego_gss_ctx_id_t ctx)
57 {
58 struct negoex_auth_mech *mech;
59
60 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
61 if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
62 return;
63
64 assert(ctx->ctx_handle == GSS_C_NO_CONTEXT);
65 ctx->ctx_handle = mech->mech_context;
66 mech->mech_context = GSS_C_NO_CONTEXT;
67 }
68
69 OM_uint32
negoex_prep_context_for_negoex(OM_uint32 * minor,spnego_gss_ctx_id_t ctx)70 negoex_prep_context_for_negoex(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
71 {
72 krb5_error_code ret;
73 struct negoex_auth_mech *mech;
74
75 if (ctx->kctx != NULL) {
76 /* The context is already initialized for NegoEx. Undo what
77 * negoex_prep_for_spnego() did, if applicable. */
78 if (ctx->ctx_handle != GSS_C_NO_CONTEXT) {
79 mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
80 assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT);
81 mech->mech_context = ctx->ctx_handle;
82 ctx->ctx_handle = GSS_C_NO_CONTEXT;
83 }
84 return GSS_S_COMPLETE;
85 }
86
87 /* Initialize the NegoEX context fields. (negoex_mechs is already set up
88 * by SPNEGO.) */
89 ret = krb5_init_context(&ctx->kctx);
90 if (ret) {
91 *minor = ret;
92 return GSS_S_FAILURE;
93 }
94
95 k5_buf_init_dynamic(&ctx->negoex_transcript);
96
97 return GSS_S_COMPLETE;
98 }
99
100 static void
release_all_mechs(spnego_gss_ctx_id_t ctx)101 release_all_mechs(spnego_gss_ctx_id_t ctx)
102 {
103 struct negoex_auth_mech *mech, *next;
104
105 K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next)
106 release_auth_mech(mech);
107 K5_TAILQ_INIT(&ctx->negoex_mechs);
108 }
109
110 void
negoex_release_context(spnego_gss_ctx_id_t ctx)111 negoex_release_context(spnego_gss_ctx_id_t ctx)
112 {
113 k5_buf_free(&ctx->negoex_transcript);
114 release_all_mechs(ctx);
115 krb5_free_context(ctx->kctx);
116 ctx->kctx = NULL;
117 }
118
119 static const char *
typestr(enum message_type type)120 typestr(enum message_type type)
121 {
122 if (type == INITIATOR_NEGO)
123 return "INITIATOR_NEGO";
124 else if (type == ACCEPTOR_NEGO)
125 return "ACCEPTOR_NEGO";
126 else if (type == INITIATOR_META_DATA)
127 return "INITIATOR_META_DATA";
128 else if (type == ACCEPTOR_META_DATA)
129 return "ACCEPTOR_META_DATA";
130 else if (type == CHALLENGE)
131 return "CHALLENGE";
132 else if (type == AP_REQUEST)
133 return "AP_REQUEST";
134 else if (type == VERIFY)
135 return "VERIFY";
136 else if (type == ALERT)
137 return "ALERT";
138 else
139 return "UNKNOWN";
140 }
141
142 static void
add_guid(struct k5buf * buf,const uint8_t guid[GUID_LENGTH])143 add_guid(struct k5buf *buf, const uint8_t guid[GUID_LENGTH])
144 {
145 uint32_t data1 = load_32_le(guid);
146 uint16_t data2 = load_16_le(guid + 4), data3 = load_16_le(guid + 6);
147
148 k5_buf_add_fmt(buf, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
149 data1, data2, data3, guid[8], guid[9], guid[10], guid[11],
150 guid[12], guid[13], guid[14], guid[15]);
151 }
152
153 static char *
guid_to_string(const uint8_t guid[GUID_LENGTH])154 guid_to_string(const uint8_t guid[GUID_LENGTH])
155 {
156 struct k5buf buf;
157
158 k5_buf_init_dynamic(&buf);
159 add_guid(&buf, guid);
160 return k5_buf_cstring(&buf);
161 }
162
163 /* Check that the described vector lies within the message, and return a
164 * pointer to its first element. */
165 static inline const uint8_t *
vector_base(size_t offset,size_t count,size_t width,const uint8_t * msg_base,size_t msg_len)166 vector_base(size_t offset, size_t count, size_t width,
167 const uint8_t *msg_base, size_t msg_len)
168 {
169 if (offset > msg_len || count > (msg_len - offset) / width)
170 return NULL;
171 return msg_base + offset;
172 }
173
174 /* Trace a received message. Call after the context sequence number is
175 * incremented. */
176 static void
trace_received_message(spnego_gss_ctx_id_t ctx,const struct negoex_message * msg)177 trace_received_message(spnego_gss_ctx_id_t ctx,
178 const struct negoex_message *msg)
179 {
180 struct k5buf buf;
181 uint16_t i;
182 char *info = NULL;
183
184 if (msg->type == INITIATOR_NEGO || msg->type == ACCEPTOR_NEGO) {
185 k5_buf_init_dynamic(&buf);
186 for (i = 0; i < msg->u.n.nschemes; i++) {
187 add_guid(&buf, msg->u.n.schemes + i * GUID_LENGTH);
188 if (i + 1 < msg->u.n.nschemes)
189 k5_buf_add(&buf, " ");
190 }
191 info = k5_buf_cstring(&buf);
192 } else if (msg->type == INITIATOR_META_DATA ||
193 msg->type == ACCEPTOR_META_DATA ||
194 msg->type == CHALLENGE || msg->type == AP_REQUEST) {
195 info = guid_to_string(msg->u.e.scheme);
196 } else if (msg->type == VERIFY) {
197 info = guid_to_string(msg->u.v.scheme);
198 } else if (msg->type == ALERT) {
199 info = guid_to_string(msg->u.a.scheme);
200 }
201
202 if (info == NULL)
203 return;
204
205 TRACE_NEGOEX_INCOMING(ctx->kctx, ctx->negoex_seqnum - 1,
206 typestr(msg->type), info);
207 free(info);
208 }
209
210 /* Trace an outgoing message with a GUID info string. Call after the context
211 * sequence number is incremented. */
212 static void
trace_outgoing_message(spnego_gss_ctx_id_t ctx,enum message_type type,const uint8_t guid[GUID_LENGTH])213 trace_outgoing_message(spnego_gss_ctx_id_t ctx, enum message_type type,
214 const uint8_t guid[GUID_LENGTH])
215 {
216 char *info = guid_to_string(guid);
217
218 if (info == NULL)
219 return;
220 TRACE_NEGOEX_OUTGOING(ctx->kctx, ctx->negoex_seqnum - 1, typestr(type),
221 info);
222 free(info);
223 }
224
225 static OM_uint32
parse_nego_message(OM_uint32 * minor,struct k5input * in,const uint8_t * msg_base,size_t msg_len,struct nego_message * msg)226 parse_nego_message(OM_uint32 *minor, struct k5input *in,
227 const uint8_t *msg_base, size_t msg_len,
228 struct nego_message *msg)
229 {
230 const uint8_t *p;
231 uint64_t protocol_version;
232 uint32_t extension_type;
233 size_t offset, count, i;
234
235 p = k5_input_get_bytes(in, sizeof(msg->random));
236 if (p != NULL)
237 memcpy(msg->random, p, sizeof(msg->random));
238 protocol_version = k5_input_get_uint64_le(in);
239 if (protocol_version != 0) {
240 *minor = ERR_NEGOEX_UNSUPPORTED_VERSION;
241 return GSS_S_UNAVAILABLE;
242 }
243
244 offset = k5_input_get_uint32_le(in);
245 count = k5_input_get_uint16_le(in);
246 msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len);
247 msg->nschemes = count;
248 if (msg->schemes == NULL) {
249 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
250 return GSS_S_DEFECTIVE_TOKEN;
251 }
252
253 offset = k5_input_get_uint32_le(in);
254 count = k5_input_get_uint16_le(in);
255 p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len);
256 if (p == NULL) {
257 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
258 return GSS_S_DEFECTIVE_TOKEN;
259 }
260 for (i = 0; i < count; i++) {
261 extension_type = load_32_le(p + i * EXTENSION_LENGTH);
262 if (extension_type & EXTENSION_FLAG_CRITICAL) {
263 *minor = ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION;
264 return GSS_S_UNAVAILABLE;
265 }
266 }
267
268 return GSS_S_COMPLETE;
269 }
270
271 static OM_uint32
parse_exchange_message(OM_uint32 * minor,struct k5input * in,const uint8_t * msg_base,size_t msg_len,struct exchange_message * msg)272 parse_exchange_message(OM_uint32 *minor, struct k5input *in,
273 const uint8_t *msg_base, size_t msg_len,
274 struct exchange_message *msg)
275 {
276 const uint8_t *p;
277 size_t offset, len;
278
279 p = k5_input_get_bytes(in, GUID_LENGTH);
280 if (p != NULL)
281 memcpy(msg->scheme, p, GUID_LENGTH);
282
283 offset = k5_input_get_uint32_le(in);
284 len = k5_input_get_uint32_le(in);
285 p = vector_base(offset, len, 1, msg_base, msg_len);
286 if (p == NULL) {
287 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
288 return GSS_S_DEFECTIVE_TOKEN;
289 }
290 msg->token.value = (void *)p;
291 msg->token.length = len;
292
293 return GSS_S_COMPLETE;
294 }
295
296 static OM_uint32
parse_verify_message(OM_uint32 * minor,struct k5input * in,const uint8_t * msg_base,size_t msg_len,size_t token_offset,struct verify_message * msg)297 parse_verify_message(OM_uint32 *minor, struct k5input *in,
298 const uint8_t *msg_base, size_t msg_len,
299 size_t token_offset, struct verify_message *msg)
300 {
301 const uint8_t *p;
302 size_t offset, len;
303 uint32_t hdrlen, cksum_scheme;
304
305 p = k5_input_get_bytes(in, GUID_LENGTH);
306 if (p != NULL)
307 memcpy(msg->scheme, p, GUID_LENGTH);
308
309 hdrlen = k5_input_get_uint32_le(in);
310 if (hdrlen != CHECKSUM_HEADER_LENGTH) {
311 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
312 return GSS_S_DEFECTIVE_TOKEN;
313 }
314 cksum_scheme = k5_input_get_uint32_le(in);
315 if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) {
316 *minor = ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME;
317 return GSS_S_UNAVAILABLE;
318 }
319 msg->cksum_type = k5_input_get_uint32_le(in);
320
321 offset = k5_input_get_uint32_le(in);
322 len = k5_input_get_uint32_le(in);
323 msg->cksum = vector_base(offset, len, 1, msg_base, msg_len);
324 msg->cksum_len = len;
325 if (msg->cksum == NULL) {
326 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
327 return GSS_S_DEFECTIVE_TOKEN;
328 }
329
330 msg->offset_in_token = token_offset;
331 return GSS_S_COMPLETE;
332 }
333
334 static OM_uint32
parse_alert_message(OM_uint32 * minor,struct k5input * in,const uint8_t * msg_base,size_t msg_len,struct alert_message * msg)335 parse_alert_message(OM_uint32 *minor, struct k5input *in,
336 const uint8_t *msg_base, size_t msg_len,
337 struct alert_message *msg)
338 {
339 const uint8_t *p;
340 uint32_t atype, reason;
341 size_t alerts_offset, nalerts, value_offset, value_len, i;
342 struct k5input alerts_in, pulse_in;
343
344 p = k5_input_get_bytes(in, GUID_LENGTH);
345 if (p != NULL)
346 memcpy(msg->scheme, p, GUID_LENGTH);
347 (void)k5_input_get_uint32_le(in); /* skip over ErrorCode */
348 alerts_offset = k5_input_get_uint32_le(in);
349 nalerts = k5_input_get_uint32_le(in);
350 p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len);
351 if (p == NULL) {
352 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
353 return GSS_S_DEFECTIVE_TOKEN;
354 }
355
356 /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */
357 msg->verify_no_key = FALSE;
358 k5_input_init(&alerts_in, p, nalerts * ALERT_LENGTH);
359 for (i = 0; i < nalerts; i++) {
360 atype = k5_input_get_uint32_le(&alerts_in);
361 value_offset = k5_input_get_uint32_le(&alerts_in);
362 value_len = k5_input_get_uint32_le(&alerts_in);
363 p = vector_base(value_offset, value_len, 1, msg_base, msg_len);
364 if (p == NULL) {
365 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
366 return GSS_S_DEFECTIVE_TOKEN;
367 }
368
369 if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) {
370 k5_input_init(&pulse_in, p, value_len);
371 (void)k5_input_get_uint32_le(&pulse_in); /* skip header length */
372 reason = k5_input_get_uint32_le(&pulse_in);
373 if (reason == ALERT_VERIFY_NO_KEY)
374 msg->verify_no_key = TRUE;
375 }
376 }
377
378 return GSS_S_COMPLETE;
379 }
380
381 static OM_uint32
parse_message(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,struct k5input * in,const uint8_t * token_base,struct negoex_message * msg)382 parse_message(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, struct k5input *in,
383 const uint8_t *token_base, struct negoex_message *msg)
384 {
385 OM_uint32 major;
386 const uint8_t *msg_base = in->ptr, *conv_id;
387 size_t token_remaining = in->len, header_len, msg_len;
388 uint64_t signature;
389 uint32_t type, seqnum;
390
391 signature = k5_input_get_uint64_le(in);
392 type = k5_input_get_uint32_le(in);
393 seqnum = k5_input_get_uint32_le(in);
394 header_len = k5_input_get_uint32_le(in);
395 msg_len = k5_input_get_uint32_le(in);
396 conv_id = k5_input_get_bytes(in, GUID_LENGTH);
397
398 if (in->status || msg_len > token_remaining ||
399 header_len < (size_t)(in->ptr - msg_base) || header_len > msg_len) {
400 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
401 return GSS_S_DEFECTIVE_TOKEN;
402 }
403 if (signature != MESSAGE_SIGNATURE) {
404 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE;
405 return GSS_S_DEFECTIVE_TOKEN;
406 }
407 if (seqnum != ctx->negoex_seqnum) {
408 *minor = ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE;
409 return GSS_S_DEFECTIVE_TOKEN;
410 }
411 if (seqnum == 0) {
412 memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH);
413 } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) {
414 *minor = ERR_NEGOEX_INVALID_CONVERSATION_ID;
415 return GSS_S_DEFECTIVE_TOKEN;
416 }
417
418 /* Restrict the input region to the header. */
419 in->len = header_len - (in->ptr - msg_base);
420
421 msg->type = type;
422 if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) {
423 major = parse_nego_message(minor, in, msg_base, msg_len, &msg->u.n);
424 } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
425 type == CHALLENGE || type == AP_REQUEST) {
426 major = parse_exchange_message(minor, in, msg_base, msg_len,
427 &msg->u.e);
428 } else if (type == VERIFY) {
429 major = parse_verify_message(minor, in, msg_base, msg_len,
430 msg_base - token_base, &msg->u.v);
431 } else if (type == ALERT) {
432 major = parse_alert_message(minor, in, msg_base, msg_len, &msg->u.a);
433 } else {
434 *minor = ERR_NEGOEX_INVALID_MESSAGE_TYPE;
435 return GSS_S_DEFECTIVE_TOKEN;
436 }
437 if (major != GSS_S_COMPLETE)
438 return major;
439
440 /* Reset the input buffer to the remainder of the token. */
441 if (!in->status)
442 k5_input_init(in, msg_base + msg_len, token_remaining - msg_len);
443
444 ctx->negoex_seqnum++;
445 trace_received_message(ctx, msg);
446 return GSS_S_COMPLETE;
447 }
448
449 /*
450 * Parse token into an array of negoex_message structures. All pointer fields
451 * within the parsed messages are aliases into token, so the result can be
452 * freed with free(). An unknown protocol version, a critical extension, or an
453 * unknown checksum scheme will cause a parsing failure. Increment the
454 * sequence number in ctx for each message, and record and check the
455 * conversation ID in ctx as appropriate.
456 */
457 OM_uint32
negoex_parse_token(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_const_buffer_t token,struct negoex_message ** messages_out,size_t * count_out)458 negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
459 gss_const_buffer_t token,
460 struct negoex_message **messages_out, size_t *count_out)
461 {
462 OM_uint32 major = GSS_S_COMPLETE;
463 size_t count = 0;
464 struct k5input in;
465 struct negoex_message *messages = NULL, *newptr;
466
467 *messages_out = NULL;
468 *count_out = 0;
469 assert(token != GSS_C_NO_BUFFER);
470 k5_input_init(&in, token->value, token->length);
471
472 while (in.status == 0 && in.len > 0) {
473 newptr = realloc(messages, (count + 1) * sizeof(*newptr));
474 if (newptr == NULL) {
475 free(messages);
476 *minor = ENOMEM;
477 return GSS_S_FAILURE;
478 }
479 messages = newptr;
480
481 major = parse_message(minor, ctx, &in, token->value, &messages[count]);
482 if (major != GSS_S_COMPLETE)
483 break;
484
485 count++;
486 }
487
488 if (in.status) {
489 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
490 major = GSS_S_DEFECTIVE_TOKEN;
491 }
492 if (major != GSS_S_COMPLETE) {
493 free(messages);
494 return major;
495 }
496
497 *messages_out = messages;
498 *count_out = count;
499 return GSS_S_COMPLETE;
500 }
501
502 static struct negoex_message *
locate_message(struct negoex_message * messages,size_t nmessages,enum message_type type)503 locate_message(struct negoex_message *messages, size_t nmessages,
504 enum message_type type)
505 {
506 uint32_t i;
507
508 for (i = 0; i < nmessages; i++) {
509 if (messages[i].type == type)
510 return &messages[i];
511 }
512
513 return NULL;
514 }
515
516 struct nego_message *
negoex_locate_nego_message(struct negoex_message * messages,size_t nmessages,enum message_type type)517 negoex_locate_nego_message(struct negoex_message *messages, size_t nmessages,
518 enum message_type type)
519 {
520 struct negoex_message *msg = locate_message(messages, nmessages, type);
521
522 return (msg == NULL) ? NULL : &msg->u.n;
523 }
524
525 struct exchange_message *
negoex_locate_exchange_message(struct negoex_message * messages,size_t nmessages,enum message_type type)526 negoex_locate_exchange_message(struct negoex_message *messages,
527 size_t nmessages, enum message_type type)
528 {
529 struct negoex_message *msg = locate_message(messages, nmessages, type);
530
531 return (msg == NULL) ? NULL : &msg->u.e;
532 }
533
534 struct verify_message *
negoex_locate_verify_message(struct negoex_message * messages,size_t nmessages)535 negoex_locate_verify_message(struct negoex_message *messages,
536 size_t nmessages)
537 {
538 struct negoex_message *msg = locate_message(messages, nmessages, VERIFY);
539
540 return (msg == NULL) ? NULL : &msg->u.v;
541 }
542
543 struct alert_message *
negoex_locate_alert_message(struct negoex_message * messages,size_t nmessages)544 negoex_locate_alert_message(struct negoex_message *messages, size_t nmessages)
545 {
546 struct negoex_message *msg = locate_message(messages, nmessages, ALERT);
547
548 return (msg == NULL) ? NULL : &msg->u.a;
549 }
550
551 /*
552 * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of
553 * bytes of the payload following the full header. Increment the sequence
554 * number in ctx. Set *payload_start_out to the position of the payload within
555 * the message.
556 */
557 static void
put_message_header(spnego_gss_ctx_id_t ctx,enum message_type type,uint32_t payload_len,uint32_t * payload_start_out)558 put_message_header(spnego_gss_ctx_id_t ctx, enum message_type type,
559 uint32_t payload_len, uint32_t *payload_start_out)
560 {
561 size_t header_len;
562
563 if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO)
564 header_len = NEGO_MESSAGE_HEADER_LENGTH;
565 else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
566 type == CHALLENGE || type == AP_REQUEST)
567 header_len = EXCHANGE_MESSAGE_HEADER_LENGTH;
568 else if (type == VERIFY)
569 header_len = VERIFY_MESSAGE_HEADER_LENGTH;
570 else if (type == ALERT)
571 header_len = ALERT_MESSAGE_HEADER_LENGTH;
572 else
573 abort();
574
575 k5_buf_add_uint64_le(&ctx->negoex_transcript, MESSAGE_SIGNATURE);
576 k5_buf_add_uint32_le(&ctx->negoex_transcript, type);
577 k5_buf_add_uint32_le(&ctx->negoex_transcript, ctx->negoex_seqnum++);
578 k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len);
579 k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len + payload_len);
580 k5_buf_add_len(&ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH);
581
582 *payload_start_out = header_len;
583 }
584
585 void
negoex_add_nego_message(spnego_gss_ctx_id_t ctx,enum message_type type,uint8_t random[32])586 negoex_add_nego_message(spnego_gss_ctx_id_t ctx, enum message_type type,
587 uint8_t random[32])
588 {
589 struct negoex_auth_mech *mech;
590 uint32_t payload_start, seqnum = ctx->negoex_seqnum;
591 uint16_t nschemes;
592 struct k5buf buf;
593
594 nschemes = 0;
595 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
596 nschemes++;
597
598 put_message_header(ctx, type, nschemes * GUID_LENGTH, &payload_start);
599 k5_buf_add_len(&ctx->negoex_transcript, random, 32);
600 /* ProtocolVersion */
601 k5_buf_add_uint64_le(&ctx->negoex_transcript, 0);
602 /* AuthSchemes vector */
603 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
604 k5_buf_add_uint16_le(&ctx->negoex_transcript, nschemes);
605 /* Extensions vector */
606 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
607 k5_buf_add_uint16_le(&ctx->negoex_transcript, 0);
608 /* Four bytes of padding to reach a multiple of 8 bytes. */
609 k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
610
611 /* Payload (auth schemes); also build guid string for tracing. */
612 k5_buf_init_dynamic(&buf);
613 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
614 k5_buf_add_len(&ctx->negoex_transcript, mech->scheme, GUID_LENGTH);
615 add_guid(&buf, mech->scheme);
616 k5_buf_add(&buf, " ");
617 }
618
619 if (buf.len > 0) {
620 k5_buf_truncate(&buf, buf.len - 1);
621 TRACE_NEGOEX_OUTGOING(ctx->kctx, seqnum, typestr(type),
622 k5_buf_cstring(&buf));
623 k5_buf_free(&buf);
624 }
625 }
626
627 void
negoex_add_exchange_message(spnego_gss_ctx_id_t ctx,enum message_type type,const auth_scheme scheme,gss_buffer_t token)628 negoex_add_exchange_message(spnego_gss_ctx_id_t ctx, enum message_type type,
629 const auth_scheme scheme, gss_buffer_t token)
630 {
631 uint32_t payload_start;
632
633 put_message_header(ctx, type, token->length, &payload_start);
634 k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
635 /* Exchange byte vector */
636 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
637 k5_buf_add_uint32_le(&ctx->negoex_transcript, token->length);
638 /* Payload (token) */
639 k5_buf_add_len(&ctx->negoex_transcript, token->value, token->length);
640
641 trace_outgoing_message(ctx, type, scheme);
642 }
643
644 void
negoex_add_verify_message(spnego_gss_ctx_id_t ctx,const auth_scheme scheme,uint32_t cksum_type,const uint8_t * cksum,uint32_t cksum_len)645 negoex_add_verify_message(spnego_gss_ctx_id_t ctx, const auth_scheme scheme,
646 uint32_t cksum_type, const uint8_t *cksum,
647 uint32_t cksum_len)
648 {
649 uint32_t payload_start;
650
651 put_message_header(ctx, VERIFY, cksum_len, &payload_start);
652 k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
653 k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH);
654 k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961);
655 k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_type);
656 /* ChecksumValue vector */
657 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
658 k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_len);
659 /* Four bytes of padding to reach a multiple of 8 bytes. */
660 k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
661 /* Payload (checksum contents) */
662 k5_buf_add_len(&ctx->negoex_transcript, cksum, cksum_len);
663
664 trace_outgoing_message(ctx, VERIFY, scheme);
665 }
666
667 /* Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the
668 * reason ALERT_VERIFY_NO_KEY. */
669 void
negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,const auth_scheme scheme)670 negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,
671 const auth_scheme scheme)
672 {
673 uint32_t payload_start;
674
675 put_message_header(ctx, ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH,
676 &payload_start);
677 k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
678 /* ErrorCode */
679 k5_buf_add_uint32_le(&ctx->negoex_transcript, 0);
680 /* Alerts vector */
681 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
682 k5_buf_add_uint16_le(&ctx->negoex_transcript, 1);
683 /* Six bytes of padding to reach a multiple of 8 bytes. */
684 k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0\0\0", 6);
685 /* Payload part 1: a single ALERT element */
686 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_TYPE_PULSE);
687 k5_buf_add_uint32_le(&ctx->negoex_transcript,
688 payload_start + ALERT_LENGTH);
689 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
690 /* Payload part 2: ALERT_PULSE */
691 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
692 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_VERIFY_NO_KEY);
693
694 trace_outgoing_message(ctx, ALERT, scheme);
695 }
696
697 static void
release_auth_mech(struct negoex_auth_mech * mech)698 release_auth_mech(struct negoex_auth_mech *mech)
699 {
700 OM_uint32 tmpmin;
701
702 if (mech == NULL)
703 return;
704
705 gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
706 generic_gss_release_oid(&tmpmin, &mech->oid);
707 gss_release_buffer(&tmpmin, &mech->metadata);
708 krb5_free_keyblock_contents(NULL, &mech->key);
709 krb5_free_keyblock_contents(NULL, &mech->verify_key);
710
711 free(mech);
712 }
713
714 void
negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,struct negoex_auth_mech * mech)715 negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,
716 struct negoex_auth_mech *mech)
717 {
718 K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
719 release_auth_mech(mech);
720 }
721
722 /* Remove all auth mech entries except for mech from ctx->mechs. */
723 void
negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,struct negoex_auth_mech * mech)724 negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,
725 struct negoex_auth_mech *mech)
726 {
727 assert(mech != NULL);
728 K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
729 release_all_mechs(ctx);
730 K5_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links);
731 }
732
733 OM_uint32
negoex_add_auth_mech(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_const_OID oid,auth_scheme scheme)734 negoex_add_auth_mech(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
735 gss_const_OID oid, auth_scheme scheme)
736 {
737 OM_uint32 major;
738 struct negoex_auth_mech *mech;
739
740 mech = calloc(1, sizeof(*mech));
741 if (mech == NULL) {
742 *minor = ENOMEM;
743 return GSS_S_FAILURE;
744 }
745
746 major = generic_gss_copy_oid(minor, (gss_OID)oid, &mech->oid);
747 if (major != GSS_S_COMPLETE) {
748 free(mech);
749 return major;
750 }
751
752 memcpy(mech->scheme, scheme, GUID_LENGTH);
753
754 K5_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links);
755
756 *minor = 0;
757 return GSS_S_COMPLETE;
758 }
759
760 struct negoex_auth_mech *
negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx,const auth_scheme scheme)761 negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx, const auth_scheme scheme)
762 {
763 struct negoex_auth_mech *mech;
764
765 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
766 if (GUID_EQ(mech->scheme, scheme))
767 return mech;
768 }
769
770 return NULL;
771 }
772
773 /* Prune ctx->mechs to the schemes present in schemes, and reorder them to
774 * match its order. */
775 void
negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,const uint8_t * schemes,uint16_t nschemes)776 negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,
777 const uint8_t *schemes, uint16_t nschemes)
778 {
779 struct negoex_mech_list list;
780 struct negoex_auth_mech *mech;
781 uint16_t i;
782
783 /* Construct a new list in the order of schemes. */
784 K5_TAILQ_INIT(&list);
785 for (i = 0; i < nschemes; i++) {
786 mech = negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH);
787 if (mech == NULL)
788 continue;
789 K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
790 K5_TAILQ_INSERT_TAIL(&list, mech, links);
791 }
792
793 /* Release any leftover entries and replace the context list. */
794 release_all_mechs(ctx);
795 K5_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links);
796 }
797
798 /* Prune ctx->mechs to the schemes present in schemes, but do not change
799 * their order. */
800 void
negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,const uint8_t * schemes,uint16_t nschemes)801 negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,
802 const uint8_t *schemes, uint16_t nschemes)
803 {
804 struct negoex_auth_mech *mech, *next;
805 uint16_t i;
806 int found;
807
808 K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
809 found = FALSE;
810 for (i = 0; i < nschemes && !found; i++) {
811 if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH))
812 found = TRUE;
813 }
814
815 if (!found)
816 negoex_delete_auth_mech(ctx, mech);
817 }
818 }
819