Coverage Report

Created: 2026-06-13 06:07

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