/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 | } |