xref: /freebsd/crypto/krb5/src/lib/gssapi/spnego/negoex_util.c (revision c9dd7bffa58c50b2f7ed9e66ace39197c468d8e6)
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