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 for (i = 0; i < count; i++) {
257 extension_type = load_32_le(p + i * EXTENSION_LENGTH);
258 if (extension_type & EXTENSION_FLAG_CRITICAL) {
259 *minor = ERR_NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION;
260 return GSS_S_UNAVAILABLE;
261 }
262 }
263
264 return GSS_S_COMPLETE;
265 }
266
267 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)268 parse_exchange_message(OM_uint32 *minor, struct k5input *in,
269 const uint8_t *msg_base, size_t msg_len,
270 struct exchange_message *msg)
271 {
272 const uint8_t *p;
273 size_t offset, len;
274
275 p = k5_input_get_bytes(in, GUID_LENGTH);
276 if (p != NULL)
277 memcpy(msg->scheme, p, GUID_LENGTH);
278
279 offset = k5_input_get_uint32_le(in);
280 len = k5_input_get_uint32_le(in);
281 p = vector_base(offset, len, 1, msg_base, msg_len);
282 if (p == NULL) {
283 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
284 return GSS_S_DEFECTIVE_TOKEN;
285 }
286 msg->token.value = (void *)p;
287 msg->token.length = len;
288
289 return GSS_S_COMPLETE;
290 }
291
292 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)293 parse_verify_message(OM_uint32 *minor, struct k5input *in,
294 const uint8_t *msg_base, size_t msg_len,
295 size_t token_offset, struct verify_message *msg)
296 {
297 const uint8_t *p;
298 size_t offset, len;
299 uint32_t hdrlen, cksum_scheme;
300
301 p = k5_input_get_bytes(in, GUID_LENGTH);
302 if (p != NULL)
303 memcpy(msg->scheme, p, GUID_LENGTH);
304
305 hdrlen = k5_input_get_uint32_le(in);
306 if (hdrlen != CHECKSUM_HEADER_LENGTH) {
307 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
308 return GSS_S_DEFECTIVE_TOKEN;
309 }
310 cksum_scheme = k5_input_get_uint32_le(in);
311 if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) {
312 *minor = ERR_NEGOEX_UNKNOWN_CHECKSUM_SCHEME;
313 return GSS_S_UNAVAILABLE;
314 }
315 msg->cksum_type = k5_input_get_uint32_le(in);
316
317 offset = k5_input_get_uint32_le(in);
318 len = k5_input_get_uint32_le(in);
319 msg->cksum = vector_base(offset, len, 1, msg_base, msg_len);
320 msg->cksum_len = len;
321 if (msg->cksum == NULL) {
322 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
323 return GSS_S_DEFECTIVE_TOKEN;
324 }
325
326 msg->offset_in_token = token_offset;
327 return GSS_S_COMPLETE;
328 }
329
330 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)331 parse_alert_message(OM_uint32 *minor, struct k5input *in,
332 const uint8_t *msg_base, size_t msg_len,
333 struct alert_message *msg)
334 {
335 const uint8_t *p;
336 uint32_t atype, reason;
337 size_t alerts_offset, nalerts, value_offset, value_len, i;
338 struct k5input alerts_in, pulse_in;
339
340 p = k5_input_get_bytes(in, GUID_LENGTH);
341 if (p != NULL)
342 memcpy(msg->scheme, p, GUID_LENGTH);
343 (void)k5_input_get_uint32_le(in); /* skip over ErrorCode */
344 alerts_offset = k5_input_get_uint32_le(in);
345 nalerts = k5_input_get_uint32_le(in);
346 p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len);
347 if (p == NULL) {
348 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
349 return GSS_S_DEFECTIVE_TOKEN;
350 }
351
352 /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */
353 msg->verify_no_key = FALSE;
354 k5_input_init(&alerts_in, p, nalerts * ALERT_LENGTH);
355 for (i = 0; i < nalerts; i++) {
356 atype = k5_input_get_uint32_le(&alerts_in);
357 value_offset = k5_input_get_uint32_le(&alerts_in);
358 value_len = k5_input_get_uint32_le(&alerts_in);
359 p = vector_base(value_offset, value_len, 1, msg_base, msg_len);
360 if (p == NULL) {
361 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
362 return GSS_S_DEFECTIVE_TOKEN;
363 }
364
365 if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) {
366 k5_input_init(&pulse_in, p, value_len);
367 (void)k5_input_get_uint32_le(&pulse_in); /* skip header length */
368 reason = k5_input_get_uint32_le(&pulse_in);
369 if (reason == ALERT_VERIFY_NO_KEY)
370 msg->verify_no_key = TRUE;
371 }
372 }
373
374 return GSS_S_COMPLETE;
375 }
376
377 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)378 parse_message(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, struct k5input *in,
379 const uint8_t *token_base, struct negoex_message *msg)
380 {
381 OM_uint32 major;
382 const uint8_t *msg_base = in->ptr, *conv_id;
383 size_t token_remaining = in->len, header_len, msg_len;
384 uint64_t signature;
385 uint32_t type, seqnum;
386
387 signature = k5_input_get_uint64_le(in);
388 type = k5_input_get_uint32_le(in);
389 seqnum = k5_input_get_uint32_le(in);
390 header_len = k5_input_get_uint32_le(in);
391 msg_len = k5_input_get_uint32_le(in);
392 conv_id = k5_input_get_bytes(in, GUID_LENGTH);
393
394 if (in->status || msg_len > token_remaining || header_len > msg_len) {
395 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
396 return GSS_S_DEFECTIVE_TOKEN;
397 }
398 if (signature != MESSAGE_SIGNATURE) {
399 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIGNATURE;
400 return GSS_S_DEFECTIVE_TOKEN;
401 }
402 if (seqnum != ctx->negoex_seqnum) {
403 *minor = ERR_NEGOEX_MESSAGE_OUT_OF_SEQUENCE;
404 return GSS_S_DEFECTIVE_TOKEN;
405 }
406 if (seqnum == 0) {
407 memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH);
408 } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) {
409 *minor = ERR_NEGOEX_INVALID_CONVERSATION_ID;
410 return GSS_S_DEFECTIVE_TOKEN;
411 }
412
413 /* Restrict the input region to the header. */
414 in->len = header_len - (in->ptr - msg_base);
415
416 msg->type = type;
417 if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) {
418 major = parse_nego_message(minor, in, msg_base, msg_len, &msg->u.n);
419 } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
420 type == CHALLENGE || type == AP_REQUEST) {
421 major = parse_exchange_message(minor, in, msg_base, msg_len,
422 &msg->u.e);
423 } else if (type == VERIFY) {
424 major = parse_verify_message(minor, in, msg_base, msg_len,
425 msg_base - token_base, &msg->u.v);
426 } else if (type == ALERT) {
427 major = parse_alert_message(minor, in, msg_base, msg_len, &msg->u.a);
428 } else {
429 *minor = ERR_NEGOEX_INVALID_MESSAGE_TYPE;
430 return GSS_S_DEFECTIVE_TOKEN;
431 }
432 if (major != GSS_S_COMPLETE)
433 return major;
434
435 /* Reset the input buffer to the remainder of the token. */
436 if (!in->status)
437 k5_input_init(in, msg_base + msg_len, token_remaining - msg_len);
438
439 ctx->negoex_seqnum++;
440 trace_received_message(ctx, msg);
441 return GSS_S_COMPLETE;
442 }
443
444 /*
445 * Parse token into an array of negoex_message structures. All pointer fields
446 * within the parsed messages are aliases into token, so the result can be
447 * freed with free(). An unknown protocol version, a critical extension, or an
448 * unknown checksum scheme will cause a parsing failure. Increment the
449 * sequence number in ctx for each message, and record and check the
450 * conversation ID in ctx as appropriate.
451 */
452 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)453 negoex_parse_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
454 gss_const_buffer_t token,
455 struct negoex_message **messages_out, size_t *count_out)
456 {
457 OM_uint32 major = GSS_S_COMPLETE;
458 size_t count = 0;
459 struct k5input in;
460 struct negoex_message *messages = NULL, *newptr;
461
462 *messages_out = NULL;
463 *count_out = 0;
464 assert(token != GSS_C_NO_BUFFER);
465 k5_input_init(&in, token->value, token->length);
466
467 while (in.status == 0 && in.len > 0) {
468 newptr = realloc(messages, (count + 1) * sizeof(*newptr));
469 if (newptr == NULL) {
470 free(messages);
471 *minor = ENOMEM;
472 return GSS_S_FAILURE;
473 }
474 messages = newptr;
475
476 major = parse_message(minor, ctx, &in, token->value, &messages[count]);
477 if (major != GSS_S_COMPLETE)
478 break;
479
480 count++;
481 }
482
483 if (in.status) {
484 *minor = ERR_NEGOEX_INVALID_MESSAGE_SIZE;
485 major = GSS_S_DEFECTIVE_TOKEN;
486 }
487 if (major != GSS_S_COMPLETE) {
488 free(messages);
489 return major;
490 }
491
492 *messages_out = messages;
493 *count_out = count;
494 return GSS_S_COMPLETE;
495 }
496
497 static struct negoex_message *
locate_message(struct negoex_message * messages,size_t nmessages,enum message_type type)498 locate_message(struct negoex_message *messages, size_t nmessages,
499 enum message_type type)
500 {
501 uint32_t i;
502
503 for (i = 0; i < nmessages; i++) {
504 if (messages[i].type == type)
505 return &messages[i];
506 }
507
508 return NULL;
509 }
510
511 struct nego_message *
negoex_locate_nego_message(struct negoex_message * messages,size_t nmessages,enum message_type type)512 negoex_locate_nego_message(struct negoex_message *messages, size_t nmessages,
513 enum message_type type)
514 {
515 struct negoex_message *msg = locate_message(messages, nmessages, type);
516
517 return (msg == NULL) ? NULL : &msg->u.n;
518 }
519
520 struct exchange_message *
negoex_locate_exchange_message(struct negoex_message * messages,size_t nmessages,enum message_type type)521 negoex_locate_exchange_message(struct negoex_message *messages,
522 size_t nmessages, enum message_type type)
523 {
524 struct negoex_message *msg = locate_message(messages, nmessages, type);
525
526 return (msg == NULL) ? NULL : &msg->u.e;
527 }
528
529 struct verify_message *
negoex_locate_verify_message(struct negoex_message * messages,size_t nmessages)530 negoex_locate_verify_message(struct negoex_message *messages,
531 size_t nmessages)
532 {
533 struct negoex_message *msg = locate_message(messages, nmessages, VERIFY);
534
535 return (msg == NULL) ? NULL : &msg->u.v;
536 }
537
538 struct alert_message *
negoex_locate_alert_message(struct negoex_message * messages,size_t nmessages)539 negoex_locate_alert_message(struct negoex_message *messages, size_t nmessages)
540 {
541 struct negoex_message *msg = locate_message(messages, nmessages, ALERT);
542
543 return (msg == NULL) ? NULL : &msg->u.a;
544 }
545
546 /*
547 * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of
548 * bytes of the payload following the full header. Increment the sequence
549 * number in ctx. Set *payload_start_out to the position of the payload within
550 * the message.
551 */
552 static void
put_message_header(spnego_gss_ctx_id_t ctx,enum message_type type,uint32_t payload_len,uint32_t * payload_start_out)553 put_message_header(spnego_gss_ctx_id_t ctx, enum message_type type,
554 uint32_t payload_len, uint32_t *payload_start_out)
555 {
556 size_t header_len;
557
558 if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO)
559 header_len = NEGO_MESSAGE_HEADER_LENGTH;
560 else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
561 type == CHALLENGE || type == AP_REQUEST)
562 header_len = EXCHANGE_MESSAGE_HEADER_LENGTH;
563 else if (type == VERIFY)
564 header_len = VERIFY_MESSAGE_HEADER_LENGTH;
565 else if (type == ALERT)
566 header_len = ALERT_MESSAGE_HEADER_LENGTH;
567 else
568 abort();
569
570 k5_buf_add_uint64_le(&ctx->negoex_transcript, MESSAGE_SIGNATURE);
571 k5_buf_add_uint32_le(&ctx->negoex_transcript, type);
572 k5_buf_add_uint32_le(&ctx->negoex_transcript, ctx->negoex_seqnum++);
573 k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len);
574 k5_buf_add_uint32_le(&ctx->negoex_transcript, header_len + payload_len);
575 k5_buf_add_len(&ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH);
576
577 *payload_start_out = header_len;
578 }
579
580 void
negoex_add_nego_message(spnego_gss_ctx_id_t ctx,enum message_type type,uint8_t random[32])581 negoex_add_nego_message(spnego_gss_ctx_id_t ctx, enum message_type type,
582 uint8_t random[32])
583 {
584 struct negoex_auth_mech *mech;
585 uint32_t payload_start, seqnum = ctx->negoex_seqnum;
586 uint16_t nschemes;
587 struct k5buf buf;
588
589 nschemes = 0;
590 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
591 nschemes++;
592
593 put_message_header(ctx, type, nschemes * GUID_LENGTH, &payload_start);
594 k5_buf_add_len(&ctx->negoex_transcript, random, 32);
595 /* ProtocolVersion */
596 k5_buf_add_uint64_le(&ctx->negoex_transcript, 0);
597 /* AuthSchemes vector */
598 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
599 k5_buf_add_uint16_le(&ctx->negoex_transcript, nschemes);
600 /* Extensions vector */
601 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
602 k5_buf_add_uint16_le(&ctx->negoex_transcript, 0);
603 /* Four bytes of padding to reach a multiple of 8 bytes. */
604 k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
605
606 /* Payload (auth schemes); also build guid string for tracing. */
607 k5_buf_init_dynamic(&buf);
608 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
609 k5_buf_add_len(&ctx->negoex_transcript, mech->scheme, GUID_LENGTH);
610 add_guid(&buf, mech->scheme);
611 k5_buf_add(&buf, " ");
612 }
613
614 if (buf.len > 0) {
615 k5_buf_truncate(&buf, buf.len - 1);
616 TRACE_NEGOEX_OUTGOING(ctx->kctx, seqnum, typestr(type),
617 k5_buf_cstring(&buf));
618 k5_buf_free(&buf);
619 }
620 }
621
622 void
negoex_add_exchange_message(spnego_gss_ctx_id_t ctx,enum message_type type,const auth_scheme scheme,gss_buffer_t token)623 negoex_add_exchange_message(spnego_gss_ctx_id_t ctx, enum message_type type,
624 const auth_scheme scheme, gss_buffer_t token)
625 {
626 uint32_t payload_start;
627
628 put_message_header(ctx, type, token->length, &payload_start);
629 k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
630 /* Exchange byte vector */
631 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
632 k5_buf_add_uint32_le(&ctx->negoex_transcript, token->length);
633 /* Payload (token) */
634 k5_buf_add_len(&ctx->negoex_transcript, token->value, token->length);
635
636 trace_outgoing_message(ctx, type, scheme);
637 }
638
639 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)640 negoex_add_verify_message(spnego_gss_ctx_id_t ctx, const auth_scheme scheme,
641 uint32_t cksum_type, const uint8_t *cksum,
642 uint32_t cksum_len)
643 {
644 uint32_t payload_start;
645
646 put_message_header(ctx, VERIFY, cksum_len, &payload_start);
647 k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
648 k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH);
649 k5_buf_add_uint32_le(&ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961);
650 k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_type);
651 /* ChecksumValue vector */
652 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
653 k5_buf_add_uint32_le(&ctx->negoex_transcript, cksum_len);
654 /* Four bytes of padding to reach a multiple of 8 bytes. */
655 k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0", 4);
656 /* Payload (checksum contents) */
657 k5_buf_add_len(&ctx->negoex_transcript, cksum, cksum_len);
658
659 trace_outgoing_message(ctx, VERIFY, scheme);
660 }
661
662 /* Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the
663 * reason ALERT_VERIFY_NO_KEY. */
664 void
negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,const auth_scheme scheme)665 negoex_add_verify_no_key_alert(spnego_gss_ctx_id_t ctx,
666 const auth_scheme scheme)
667 {
668 uint32_t payload_start;
669
670 put_message_header(ctx, ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH,
671 &payload_start);
672 k5_buf_add_len(&ctx->negoex_transcript, scheme, GUID_LENGTH);
673 /* ErrorCode */
674 k5_buf_add_uint32_le(&ctx->negoex_transcript, 0);
675 /* Alerts vector */
676 k5_buf_add_uint32_le(&ctx->negoex_transcript, payload_start);
677 k5_buf_add_uint16_le(&ctx->negoex_transcript, 1);
678 /* Six bytes of padding to reach a multiple of 8 bytes. */
679 k5_buf_add_len(&ctx->negoex_transcript, "\0\0\0\0\0\0", 6);
680 /* Payload part 1: a single ALERT element */
681 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_TYPE_PULSE);
682 k5_buf_add_uint32_le(&ctx->negoex_transcript,
683 payload_start + ALERT_LENGTH);
684 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
685 /* Payload part 2: ALERT_PULSE */
686 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_PULSE_LENGTH);
687 k5_buf_add_uint32_le(&ctx->negoex_transcript, ALERT_VERIFY_NO_KEY);
688
689 trace_outgoing_message(ctx, ALERT, scheme);
690 }
691
692 static void
release_auth_mech(struct negoex_auth_mech * mech)693 release_auth_mech(struct negoex_auth_mech *mech)
694 {
695 OM_uint32 tmpmin;
696
697 if (mech == NULL)
698 return;
699
700 gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
701 generic_gss_release_oid(&tmpmin, &mech->oid);
702 gss_release_buffer(&tmpmin, &mech->metadata);
703 krb5_free_keyblock_contents(NULL, &mech->key);
704 krb5_free_keyblock_contents(NULL, &mech->verify_key);
705
706 free(mech);
707 }
708
709 void
negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,struct negoex_auth_mech * mech)710 negoex_delete_auth_mech(spnego_gss_ctx_id_t ctx,
711 struct negoex_auth_mech *mech)
712 {
713 K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
714 release_auth_mech(mech);
715 }
716
717 /* Remove all auth mech entries except for mech from ctx->mechs. */
718 void
negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,struct negoex_auth_mech * mech)719 negoex_select_auth_mech(spnego_gss_ctx_id_t ctx,
720 struct negoex_auth_mech *mech)
721 {
722 assert(mech != NULL);
723 K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
724 release_all_mechs(ctx);
725 K5_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links);
726 }
727
728 OM_uint32
negoex_add_auth_mech(OM_uint32 * minor,spnego_gss_ctx_id_t ctx,gss_const_OID oid,auth_scheme scheme)729 negoex_add_auth_mech(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
730 gss_const_OID oid, auth_scheme scheme)
731 {
732 OM_uint32 major;
733 struct negoex_auth_mech *mech;
734
735 mech = calloc(1, sizeof(*mech));
736 if (mech == NULL) {
737 *minor = ENOMEM;
738 return GSS_S_FAILURE;
739 }
740
741 major = generic_gss_copy_oid(minor, (gss_OID)oid, &mech->oid);
742 if (major != GSS_S_COMPLETE) {
743 free(mech);
744 return major;
745 }
746
747 memcpy(mech->scheme, scheme, GUID_LENGTH);
748
749 K5_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links);
750
751 *minor = 0;
752 return GSS_S_COMPLETE;
753 }
754
755 struct negoex_auth_mech *
negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx,const auth_scheme scheme)756 negoex_locate_auth_scheme(spnego_gss_ctx_id_t ctx, const auth_scheme scheme)
757 {
758 struct negoex_auth_mech *mech;
759
760 K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
761 if (GUID_EQ(mech->scheme, scheme))
762 return mech;
763 }
764
765 return NULL;
766 }
767
768 /* Prune ctx->mechs to the schemes present in schemes, and reorder them to
769 * match its order. */
770 void
negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,const uint8_t * schemes,uint16_t nschemes)771 negoex_common_auth_schemes(spnego_gss_ctx_id_t ctx,
772 const uint8_t *schemes, uint16_t nschemes)
773 {
774 struct negoex_mech_list list;
775 struct negoex_auth_mech *mech;
776 uint16_t i;
777
778 /* Construct a new list in the order of schemes. */
779 K5_TAILQ_INIT(&list);
780 for (i = 0; i < nschemes; i++) {
781 mech = negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH);
782 if (mech == NULL)
783 continue;
784 K5_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
785 K5_TAILQ_INSERT_TAIL(&list, mech, links);
786 }
787
788 /* Release any leftover entries and replace the context list. */
789 release_all_mechs(ctx);
790 K5_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links);
791 }
792
793 /* Prune ctx->mechs to the schemes present in schemes, but do not change
794 * their order. */
795 void
negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,const uint8_t * schemes,uint16_t nschemes)796 negoex_restrict_auth_schemes(spnego_gss_ctx_id_t ctx,
797 const uint8_t *schemes, uint16_t nschemes)
798 {
799 struct negoex_auth_mech *mech, *next;
800 uint16_t i;
801 int found;
802
803 K5_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
804 found = FALSE;
805 for (i = 0; i < nschemes && !found; i++) {
806 if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH))
807 found = TRUE;
808 }
809
810 if (!found)
811 negoex_delete_auth_mech(ctx, mech);
812 }
813 }
814