Line data Source code
1 : /*
2 : * Copyright (C) 2011-2019 PADL Software Pty Ltd.
3 : * All rights reserved.
4 : *
5 : * Redistribution and use in source and binary forms, with or without
6 : * modification, are permitted provided that the following conditions
7 : * are met:
8 : *
9 : * * Redistributions of source code must retain the above copyright
10 : * notice, this list of conditions and the following disclaimer.
11 : *
12 : * * Redistributions in binary form must reproduce the above copyright
13 : * notice, this list of conditions and the following disclaimer in
14 : * the documentation and/or other materials provided with the
15 : * distribution.
16 : *
17 : * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 : * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 : * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 : * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 : * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 : * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 : * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 : * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 : * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 : * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 : * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 : * OF THE POSSIBILITY OF SUCH DAMAGE.
29 : */
30 :
31 : #include "spnego_locl.h"
32 :
33 : /*
34 : * SPNEGO expects to find the active mech context in ctx->negotiated_ctx_id,
35 : * but the metadata exchange APIs force us to have one mech context per mech
36 : * entry. To address this mismatch, move the active mech context (if we have
37 : * one) to ctx->negotiated_ctx_id at the end of NegoEx processing.
38 : */
39 : void
40 0 : _gss_negoex_end(gssspnego_ctx ctx)
41 : {
42 0 : struct negoex_auth_mech *mech;
43 :
44 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
45 0 : if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT)
46 0 : return;
47 :
48 0 : heim_assert(ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT,
49 : "SPNEGO/NegoEx context mismatch");
50 0 : ctx->negotiated_ctx_id = mech->mech_context;
51 0 : mech->mech_context = GSS_C_NO_CONTEXT;
52 : }
53 :
54 : OM_uint32
55 0 : _gss_negoex_begin(OM_uint32 *minor, gssspnego_ctx ctx)
56 : {
57 0 : struct negoex_auth_mech *mech;
58 :
59 0 : if (ctx->negoex_transcript != NULL) {
60 : /*
61 : * The context is already initialized for NegoEx; undo what
62 : * _gss_negoex_end() did, if applicable.
63 : */
64 0 : if (ctx->negotiated_ctx_id != GSS_C_NO_CONTEXT) {
65 0 : mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
66 0 : heim_assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT,
67 : "NegoEx/SPNEGO context mismatch");
68 0 : mech->mech_context = ctx->negotiated_ctx_id;
69 0 : ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
70 : }
71 0 : return GSS_S_COMPLETE;
72 : }
73 :
74 0 : ctx->negoex_transcript = krb5_storage_emem();
75 0 : if (ctx->negoex_transcript == NULL) {
76 0 : *minor = ENOMEM;
77 0 : return GSS_S_FAILURE;
78 : }
79 :
80 0 : krb5_storage_set_byteorder(ctx->negoex_transcript,
81 : KRB5_STORAGE_BYTEORDER_LE);
82 :
83 0 : return GSS_S_COMPLETE;
84 : }
85 :
86 : static void
87 0 : release_all_mechs(gssspnego_ctx ctx, krb5_context context)
88 : {
89 0 : struct negoex_auth_mech *mech, *next;
90 0 : struct negoex_auth_mech *prev = NULL;
91 :
92 0 : HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
93 0 : if (prev)
94 0 : _gss_negoex_release_auth_mech(context, prev);
95 0 : prev = mech;
96 : }
97 0 : if (prev)
98 0 : _gss_negoex_release_auth_mech(context, mech);
99 :
100 0 : HEIM_TAILQ_INIT(&ctx->negoex_mechs);
101 0 : }
102 :
103 : void
104 0 : _gss_negoex_release_context(gssspnego_ctx ctx)
105 : {
106 0 : krb5_context context = _gss_mg_krb5_context();
107 :
108 0 : if (ctx->negoex_transcript != NULL) {
109 0 : krb5_storage_free(ctx->negoex_transcript);
110 0 : ctx->negoex_transcript = NULL;
111 : }
112 :
113 0 : release_all_mechs(ctx, context);
114 0 : }
115 :
116 : static int
117 0 : guid_to_string(const uint8_t guid[16], char *buffer, size_t bufsiz)
118 : {
119 0 : uint32_t data1;
120 0 : uint16_t data2, data3;
121 :
122 0 : _gss_mg_decode_le_uint32(&guid[0], &data1);
123 0 : _gss_mg_decode_le_uint16(&guid[4], &data2);
124 0 : _gss_mg_decode_le_uint16(&guid[6], &data3);
125 :
126 0 : return snprintf(buffer, bufsiz,
127 : "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
128 0 : data1, data2, data3, guid[8], guid[9], guid[10], guid[11],
129 0 : guid[12], guid[13], guid[14], guid[15]);
130 : }
131 :
132 : void
133 0 : _gss_negoex_log_auth_scheme(int initiator,
134 : int index,
135 : const auth_scheme scheme)
136 : {
137 0 : char scheme_str[37];
138 :
139 0 : guid_to_string(scheme, scheme_str, sizeof(scheme_str));
140 :
141 0 : _gss_mg_log(NEGOEX_LOG_LEVEL,
142 : "negoex: %s authentication scheme %d %s",
143 : initiator ? "proposing" : "received", index, scheme_str);
144 0 : }
145 :
146 : void
147 0 : _gss_negoex_log_message(int direction,
148 : enum message_type type,
149 : const conversation_id conv_id,
150 : unsigned int seqnum,
151 : unsigned int header_len,
152 : unsigned int msg_len)
153 : {
154 0 : char conv_str[37];
155 0 : char *typestr;
156 :
157 0 : if (type == INITIATOR_NEGO)
158 0 : typestr = "INITIATOR_NEGO";
159 0 : else if (type == ACCEPTOR_NEGO)
160 0 : typestr = "ACCEPTOR_NEGO";
161 0 : else if (type == INITIATOR_META_DATA)
162 0 : typestr = "INITIATOR_META_DATA";
163 0 : else if (type == ACCEPTOR_META_DATA)
164 0 : typestr = "ACCEPTOR_META_DATA";
165 0 : else if (type == CHALLENGE)
166 0 : typestr = "CHALLENGE";
167 0 : else if (type == AP_REQUEST)
168 0 : typestr = "AP_REQUEST";
169 0 : else if (type == VERIFY)
170 0 : typestr = "VERIFY";
171 0 : else if (type == ALERT)
172 0 : typestr = "ALERT";
173 : else
174 0 : typestr = "UNKNOWN";
175 :
176 0 : guid_to_string(conv_id, conv_str, sizeof(conv_str));
177 0 : _gss_mg_log(NEGOEX_LOG_LEVEL,
178 : "negoex: %s (%d)%s conversation %s",
179 : direction ? "received" : "sending",
180 : seqnum, typestr, conv_str);
181 0 : }
182 :
183 : /*
184 : * Check that the described vector lies within the message, and return a
185 : * pointer to its first element.
186 : */
187 : static inline const uint8_t *
188 0 : vector_base(size_t offset, size_t count, size_t width,
189 : const uint8_t *msg_base, size_t msg_len)
190 : {
191 0 : if (offset > msg_len || count > (msg_len - offset) / width)
192 0 : return NULL;
193 0 : return msg_base + offset;
194 : }
195 :
196 : static OM_uint32
197 0 : parse_nego_message(OM_uint32 *minor, krb5_storage *sp,
198 : const uint8_t *msg_base, size_t msg_len,
199 : struct nego_message *msg)
200 : {
201 0 : krb5_error_code ret;
202 0 : const uint8_t *p;
203 0 : uint64_t protocol_version;
204 0 : uint32_t extension_type, offset;
205 0 : uint16_t count;
206 0 : size_t i;
207 :
208 0 : if (krb5_storage_read(sp, msg->random,
209 : sizeof(msg->random)) != sizeof(msg->random)) {
210 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
211 0 : return GSS_S_DEFECTIVE_TOKEN;
212 : }
213 :
214 0 : ret = krb5_ret_uint64(sp, &protocol_version);
215 0 : if (ret) {
216 0 : *minor = ret;
217 0 : return GSS_S_DEFECTIVE_TOKEN;
218 : }
219 :
220 0 : if (protocol_version != 0) {
221 0 : *minor = (OM_uint32)NEGOEX_UNSUPPORTED_VERSION;
222 0 : return GSS_S_UNAVAILABLE;
223 : }
224 :
225 0 : ret = krb5_ret_uint32(sp, &offset);
226 0 : if (ret == 0)
227 0 : ret = krb5_ret_uint16(sp, &count);
228 0 : if (ret) {
229 0 : *minor = ret;
230 0 : return GSS_S_DEFECTIVE_TOKEN;
231 : }
232 :
233 0 : msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len);
234 0 : msg->nschemes = count;
235 0 : if (msg->schemes == NULL) {
236 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
237 0 : return GSS_S_DEFECTIVE_TOKEN;
238 : }
239 :
240 0 : ret = krb5_ret_uint32(sp, &offset);
241 0 : if (ret == 0)
242 0 : ret = krb5_ret_uint16(sp, &count);
243 0 : if (ret) {
244 0 : *minor = ret;
245 0 : return GSS_S_DEFECTIVE_TOKEN;
246 : }
247 0 : p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len);
248 0 : for (i = 0; i < count; i++) {
249 0 : _gss_mg_decode_le_uint32(p + i * EXTENSION_LENGTH, &extension_type);
250 0 : if (extension_type & EXTENSION_FLAG_CRITICAL) {
251 0 : *minor = (OM_uint32)NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION;
252 0 : return GSS_S_UNAVAILABLE;
253 : }
254 : }
255 :
256 0 : return GSS_S_COMPLETE;
257 : }
258 :
259 : static OM_uint32
260 0 : parse_exchange_message(OM_uint32 *minor, krb5_storage *sp,
261 : const uint8_t *msg_base, size_t msg_len,
262 : struct exchange_message *msg)
263 : {
264 0 : krb5_error_code ret;
265 0 : const uint8_t *p;
266 0 : uint32_t offset;
267 0 : uint16_t len;
268 :
269 0 : if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) != GUID_LENGTH) {
270 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
271 0 : return GSS_S_DEFECTIVE_TOKEN;
272 : }
273 :
274 0 : ret = krb5_ret_uint32(sp, &offset);
275 0 : if (ret == 0)
276 0 : ret = krb5_ret_uint16(sp, &len);
277 0 : if (ret) {
278 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
279 0 : return GSS_S_DEFECTIVE_TOKEN;
280 : }
281 :
282 0 : p = vector_base(offset, len, 1, msg_base, msg_len);
283 0 : if (p == NULL) {
284 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
285 0 : return GSS_S_DEFECTIVE_TOKEN;
286 : }
287 0 : msg->token.value = (void *)p;
288 0 : msg->token.length = len;
289 :
290 0 : return GSS_S_COMPLETE;
291 : }
292 :
293 : static OM_uint32
294 0 : parse_verify_message(OM_uint32 *minor, krb5_storage *sp,
295 : const uint8_t *msg_base, size_t msg_len,
296 : size_t token_offset, struct verify_message *msg)
297 : {
298 0 : krb5_error_code ret;
299 0 : uint32_t hdrlen, cksum_scheme;
300 0 : uint32_t offset, len;
301 :
302 0 : if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH)
303 0 : ret = 0;
304 : else
305 0 : ret = NEGOEX_INVALID_MESSAGE_SIZE;
306 0 : if (ret == 0)
307 0 : ret = krb5_ret_uint32(sp, &hdrlen);
308 0 : if (ret) {
309 0 : *minor = ret;
310 0 : return GSS_S_DEFECTIVE_TOKEN;
311 : }
312 :
313 0 : if (hdrlen != CHECKSUM_HEADER_LENGTH) {
314 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
315 0 : return GSS_S_DEFECTIVE_TOKEN;
316 : }
317 :
318 0 : ret = krb5_ret_uint32(sp, &cksum_scheme);
319 0 : if (ret == 0)
320 0 : ret = krb5_ret_uint32(sp, &msg->cksum_type);
321 0 : if (ret) {
322 0 : *minor = ret;
323 0 : return GSS_S_DEFECTIVE_TOKEN;
324 : }
325 :
326 0 : if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) {
327 0 : *minor = (OM_uint32)NEGOEX_UNKNOWN_CHECKSUM_SCHEME;
328 0 : return GSS_S_UNAVAILABLE;
329 : }
330 :
331 0 : ret = krb5_ret_uint32(sp, &offset);
332 0 : if (ret == 0)
333 0 : ret = krb5_ret_uint32(sp, &len);
334 0 : if (ret) {
335 0 : *minor = ret;
336 0 : return GSS_S_DEFECTIVE_TOKEN;
337 : }
338 :
339 0 : msg->cksum = vector_base(offset, len, 1, msg_base, msg_len);
340 0 : msg->cksum_len = len;
341 0 : if (msg->cksum == NULL) {
342 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
343 0 : return GSS_S_DEFECTIVE_TOKEN;
344 : }
345 :
346 0 : msg->offset_in_token = token_offset;
347 0 : return GSS_S_COMPLETE;
348 : }
349 :
350 : static OM_uint32
351 0 : storage_from_memory(OM_uint32 *minor,
352 : const uint8_t *data,
353 : size_t length,
354 : krb5_storage **sp)
355 : {
356 0 : *sp = krb5_storage_from_readonly_mem(data, length);
357 0 : if (*sp == NULL) {
358 0 : *minor = ENOMEM;
359 0 : return GSS_S_FAILURE;
360 : }
361 :
362 0 : krb5_storage_set_byteorder(*sp, KRB5_STORAGE_BYTEORDER_LE);
363 0 : krb5_storage_set_eof_code(*sp, NEGOEX_INVALID_MESSAGE_SIZE);
364 :
365 0 : return 0;
366 : }
367 :
368 : static OM_uint32
369 0 : parse_alert_message(OM_uint32 *minor, krb5_storage *sp,
370 : const uint8_t *msg_base, size_t msg_len,
371 : struct alert_message *msg)
372 : {
373 0 : OM_uint32 major;
374 0 : krb5_error_code ret;
375 0 : const uint8_t *p;
376 0 : uint32_t error_code, atype;
377 0 : uint32_t alerts_offset, nalerts, value_offset, value_len;
378 0 : size_t i;
379 0 : krb5_storage *alerts;
380 :
381 0 : if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH)
382 0 : ret = 0;
383 : else
384 0 : ret = NEGOEX_INVALID_MESSAGE_SIZE;
385 0 : if (ret == 0)
386 0 : ret = krb5_ret_uint32(sp, &error_code);
387 0 : if (ret) {
388 0 : *minor = ret;
389 0 : return GSS_S_DEFECTIVE_TOKEN;
390 : }
391 :
392 0 : ret = krb5_ret_uint32(sp, &alerts_offset);
393 0 : if (ret == 0)
394 0 : ret = krb5_ret_uint32(sp, &nalerts);
395 0 : if (ret) {
396 0 : *minor = ret;
397 0 : return GSS_S_DEFECTIVE_TOKEN;
398 : }
399 :
400 0 : p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len);
401 0 : if (p == NULL) {
402 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
403 0 : return GSS_S_DEFECTIVE_TOKEN;
404 : }
405 :
406 : /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */
407 0 : msg->verify_no_key = FALSE;
408 :
409 0 : major = storage_from_memory(minor, p, nalerts * ALERT_LENGTH, &alerts);
410 0 : if (major != GSS_S_COMPLETE)
411 0 : return major;
412 :
413 0 : for (i = 0; i < nalerts; i++) {
414 0 : ret = krb5_ret_uint32(alerts, &atype);
415 0 : if (ret == 0)
416 0 : ret = krb5_ret_uint32(alerts, &value_offset);
417 0 : if (ret == 0)
418 0 : ret = krb5_ret_uint32(alerts, &value_len);
419 0 : if (ret) {
420 0 : *minor = ret;
421 0 : major = GSS_S_DEFECTIVE_TOKEN;
422 0 : break;
423 : }
424 :
425 0 : p = vector_base(value_offset, value_len, 1, msg_base, msg_len);
426 0 : if (p == NULL) {
427 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
428 0 : major = GSS_S_DEFECTIVE_TOKEN;
429 0 : break;
430 : }
431 :
432 0 : if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) {
433 0 : krb5_storage *pulse;
434 0 : uint32_t hdrlen, reason;
435 :
436 0 : major = storage_from_memory(minor, p, value_len, &pulse);
437 0 : if (major != GSS_S_COMPLETE)
438 0 : break;
439 :
440 0 : ret = krb5_ret_uint32(pulse, &hdrlen);
441 0 : if (ret == 0)
442 0 : ret = krb5_ret_uint32(pulse, &reason);
443 0 : krb5_storage_free(pulse);
444 0 : if (ret) {
445 0 : *minor = ret;
446 0 : major = GSS_S_DEFECTIVE_TOKEN;
447 0 : break;
448 : }
449 :
450 0 : if (reason == ALERT_VERIFY_NO_KEY)
451 0 : msg->verify_no_key = TRUE;
452 : }
453 : }
454 :
455 0 : krb5_storage_free(alerts);
456 :
457 0 : return major;
458 : }
459 :
460 : static OM_uint32
461 0 : parse_message(OM_uint32 *minor,
462 : gssspnego_ctx ctx,
463 : gss_const_buffer_t token,
464 : size_t *token_offset,
465 : struct negoex_message *msg)
466 : {
467 0 : OM_uint32 major;
468 0 : krb5_error_code ret;
469 0 : krb5_storage *sp;
470 0 : uint64_t signature;
471 0 : uint32_t header_len, msg_len;
472 0 : uint32_t type, seqnum;
473 0 : conversation_id conv_id;
474 0 : size_t token_remaining = token->length - *token_offset;
475 0 : const uint8_t *msg_base = (uint8_t *)token->value + *token_offset;
476 :
477 0 : major = storage_from_memory(minor, msg_base, token_remaining, &sp);
478 0 : if (major != GSS_S_COMPLETE)
479 0 : return major;
480 :
481 0 : major = GSS_S_DEFECTIVE_TOKEN;
482 :
483 0 : ret = krb5_ret_uint64(sp, &signature);
484 0 : if (ret == 0)
485 0 : ret = krb5_ret_uint32(sp, &type);
486 0 : if (ret == 0)
487 0 : ret = krb5_ret_uint32(sp, &seqnum);
488 0 : if (ret == 0)
489 0 : ret = krb5_ret_uint32(sp, &header_len);
490 0 : if (ret == 0)
491 0 : ret = krb5_ret_uint32(sp, &msg_len);
492 0 : if (ret == 0) {
493 0 : if (krb5_storage_read(sp, conv_id, GUID_LENGTH) != GUID_LENGTH)
494 0 : ret = NEGOEX_INVALID_MESSAGE_SIZE;
495 : }
496 0 : if (ret) {
497 0 : *minor = ret;
498 0 : goto cleanup;
499 : }
500 :
501 0 : if (msg_len > token_remaining || header_len > msg_len) {
502 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
503 0 : goto cleanup;
504 : }
505 0 : if (signature != MESSAGE_SIGNATURE) {
506 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIGNATURE;
507 0 : goto cleanup;
508 : }
509 0 : if (seqnum != ctx->negoex_seqnum) {
510 0 : *minor = (OM_uint32)NEGOEX_MESSAGE_OUT_OF_SEQUENCE;
511 0 : goto cleanup;
512 : }
513 0 : if (seqnum == 0) {
514 0 : memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH);
515 0 : } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) {
516 0 : *minor = (OM_uint32)NEGOEX_INVALID_CONVERSATION_ID;
517 0 : goto cleanup;
518 : }
519 :
520 0 : krb5_storage_truncate(sp, msg_len);
521 :
522 0 : msg->type = type;
523 0 : if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) {
524 0 : major = parse_nego_message(minor, sp, msg_base, msg_len, &msg->u.n);
525 0 : } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
526 0 : type == CHALLENGE || type == AP_REQUEST) {
527 0 : major = parse_exchange_message(minor, sp, msg_base, msg_len,
528 : &msg->u.e);
529 0 : } else if (type == VERIFY) {
530 0 : major = parse_verify_message(minor, sp, msg_base, msg_len,
531 0 : msg_base - (uint8_t *)token->value,
532 : &msg->u.v);
533 0 : } else if (type == ALERT) {
534 0 : major = parse_alert_message(minor, sp, msg_base, msg_len, &msg->u.a);
535 : } else {
536 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_TYPE;
537 0 : goto cleanup;
538 : }
539 :
540 0 : cleanup:
541 0 : krb5_storage_free(sp);
542 :
543 0 : if (major == GSS_S_COMPLETE) {
544 0 : _gss_negoex_log_message(1, msg->type,
545 0 : ctx->negoex_conv_id, ctx->negoex_seqnum,
546 : header_len, msg_len);
547 0 : ctx->negoex_seqnum++;
548 0 : *token_offset += msg_len;
549 : }
550 :
551 0 : return major;
552 : }
553 :
554 : /*
555 : * Parse token into an array of negoex_message structures. All pointer fields
556 : * within the parsed messages are aliases into token, so the result can be
557 : * freed with free(). An unknown protocol version, a critical extension, or an
558 : * unknown checksum scheme will cause a parsing failure. Increment the
559 : * sequence number in ctx for each message, and record and check the
560 : * conversation ID in ctx as appropriate.
561 : */
562 : OM_uint32
563 0 : _gss_negoex_parse_token(OM_uint32 *minor,
564 : gssspnego_ctx ctx,
565 : gss_const_buffer_t token,
566 : struct negoex_message **messages_out,
567 : size_t *count_out)
568 : {
569 0 : OM_uint32 major = GSS_S_DEFECTIVE_TOKEN;
570 0 : size_t count = 0;
571 0 : size_t token_offset = 0;
572 0 : struct negoex_message *messages = NULL, *newptr;
573 :
574 0 : *messages_out = NULL;
575 0 : *count_out = 0;
576 0 : heim_assert(token != GSS_C_NO_BUFFER, "Invalid null NegoEx input token");
577 :
578 0 : while (token_offset < token->length) {
579 0 : newptr = realloc(messages, (count + 1) * sizeof(*newptr));
580 0 : if (newptr == NULL) {
581 0 : free(messages);
582 0 : *minor = ENOMEM;
583 0 : return GSS_S_FAILURE;
584 : }
585 0 : messages = newptr;
586 :
587 0 : major = parse_message(minor, ctx, token, &token_offset,
588 0 : &messages[count]);
589 0 : if (major != GSS_S_COMPLETE)
590 0 : break;
591 :
592 0 : count++;
593 : }
594 :
595 0 : if (token_offset != token->length) {
596 0 : *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE;
597 0 : major = GSS_S_DEFECTIVE_TOKEN;
598 : }
599 0 : if (major != GSS_S_COMPLETE) {
600 0 : free(messages);
601 0 : return major;
602 : }
603 :
604 0 : *messages_out = messages;
605 0 : *count_out = count;
606 0 : return GSS_S_COMPLETE;
607 : }
608 :
609 : static struct negoex_message *
610 0 : locate_message(struct negoex_message *messages, size_t nmessages,
611 : enum message_type type)
612 : {
613 0 : uint32_t i;
614 :
615 0 : for (i = 0; i < nmessages; i++) {
616 0 : if (messages[i].type == type)
617 0 : return &messages[i];
618 : }
619 :
620 0 : return NULL;
621 : }
622 :
623 : struct nego_message *
624 0 : _gss_negoex_locate_nego_message(struct negoex_message *messages,
625 : size_t nmessages,
626 : enum message_type type)
627 : {
628 0 : struct negoex_message *msg = locate_message(messages, nmessages, type);
629 :
630 0 : return (msg == NULL) ? NULL : &msg->u.n;
631 : }
632 :
633 : struct exchange_message *
634 0 : _gss_negoex_locate_exchange_message(struct negoex_message *messages,
635 : size_t nmessages,
636 : enum message_type type)
637 : {
638 0 : struct negoex_message *msg = locate_message(messages, nmessages, type);
639 :
640 0 : return (msg == NULL) ? NULL : &msg->u.e;
641 : }
642 :
643 : struct verify_message *
644 0 : _gss_negoex_locate_verify_message(struct negoex_message *messages,
645 : size_t nmessages)
646 : {
647 0 : struct negoex_message *msg = locate_message(messages, nmessages, VERIFY);
648 :
649 0 : return (msg == NULL) ? NULL : &msg->u.v;
650 : }
651 :
652 : struct alert_message *
653 0 : _gss_negoex_locate_alert_message(struct negoex_message *messages,
654 : size_t nmessages)
655 : {
656 0 : struct negoex_message *msg = locate_message(messages, nmessages, ALERT);
657 :
658 0 : return (msg == NULL) ? NULL : &msg->u.a;
659 : }
660 :
661 : /*
662 : * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of
663 : * bytes of the payload following the full header. Increment the sequence
664 : * number in ctx. Set *payload_start_out to the position of the payload within
665 : * the message.
666 : */
667 : static OM_uint32
668 0 : put_message_header(OM_uint32 *minor, gssspnego_ctx ctx,
669 : enum message_type type, uint32_t payload_len,
670 : uint32_t *payload_start_out)
671 : {
672 0 : krb5_error_code ret;
673 0 : size_t header_len = 0;
674 :
675 0 : if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO)
676 0 : header_len = NEGO_MESSAGE_HEADER_LENGTH;
677 0 : else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA ||
678 0 : type == CHALLENGE || type == AP_REQUEST)
679 0 : header_len = EXCHANGE_MESSAGE_HEADER_LENGTH;
680 0 : else if (type == VERIFY)
681 0 : header_len = VERIFY_MESSAGE_HEADER_LENGTH;
682 0 : else if (type == ALERT)
683 0 : header_len = ALERT_MESSAGE_HEADER_LENGTH;
684 : else
685 0 : heim_assert(0, "Invalid NegoEx message type");
686 :
687 : /* Signature */
688 0 : CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, MESSAGE_SIGNATURE));
689 : /* MessageType */
690 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, type));
691 : /* SequenceNum */
692 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ctx->negoex_seqnum));
693 : /* cbHeaderLength */
694 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len));
695 : /* cbMessageLength */
696 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len + payload_len));
697 : /* ConversationId */
698 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH));
699 :
700 0 : _gss_negoex_log_message(0, type,
701 0 : ctx->negoex_conv_id, ctx->negoex_seqnum,
702 : header_len,
703 : header_len + payload_len);
704 :
705 0 : ctx->negoex_seqnum++;
706 :
707 0 : *payload_start_out = header_len;
708 0 : return GSS_S_COMPLETE;
709 :
710 0 : fail:
711 0 : *minor = ret;
712 0 : return GSS_S_FAILURE;
713 : }
714 :
715 : OM_uint32
716 0 : _gss_negoex_add_nego_message(OM_uint32 *minor,
717 : gssspnego_ctx ctx,
718 : enum message_type type,
719 : uint8_t random[32])
720 : {
721 0 : OM_uint32 major;
722 0 : krb5_error_code ret;
723 0 : struct negoex_auth_mech *mech;
724 0 : uint32_t payload_start;
725 0 : uint16_t nschemes;
726 :
727 0 : nschemes = 0;
728 0 : HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links)
729 0 : nschemes++;
730 :
731 0 : major = put_message_header(minor, ctx, type,
732 0 : nschemes * GUID_LENGTH, &payload_start);
733 0 : if (major != GSS_S_COMPLETE)
734 0 : return major;
735 :
736 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, random, 32));
737 : /* ProtocolVersion */
738 0 : CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, 0));
739 : /* AuthSchemes vector */
740 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
741 0 : CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, nschemes));
742 : /* Extensions vector */
743 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
744 0 : CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 0));
745 : /* Four bytes of padding to reach a multiple of 8 bytes. */
746 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4));
747 :
748 : /* Payload (auth schemes) */
749 0 : HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
750 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, mech->scheme, GUID_LENGTH));
751 : }
752 :
753 0 : return GSS_S_COMPLETE;
754 :
755 0 : fail:
756 0 : *minor = ret;
757 0 : return GSS_S_FAILURE;
758 : }
759 :
760 : OM_uint32
761 0 : _gss_negoex_add_exchange_message(OM_uint32 *minor,
762 : gssspnego_ctx ctx,
763 : enum message_type type,
764 : const auth_scheme scheme,
765 : gss_buffer_t token)
766 : {
767 0 : OM_uint32 major;
768 0 : krb5_error_code ret;
769 0 : uint32_t payload_start;
770 :
771 0 : major = put_message_header(minor, ctx, type, token->length, &payload_start);
772 0 : if (major != GSS_S_COMPLETE)
773 0 : return major;
774 :
775 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH));
776 : /* Exchange byte vector */
777 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
778 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, token->length));
779 : /* Payload (token) */
780 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, token->value, token->length));
781 :
782 0 : return GSS_S_COMPLETE;
783 :
784 0 : fail:
785 0 : *minor = ret;
786 0 : return GSS_S_FAILURE;
787 : }
788 :
789 : OM_uint32
790 0 : _gss_negoex_add_verify_message(OM_uint32 *minor,
791 : gssspnego_ctx ctx,
792 : const auth_scheme scheme,
793 : uint32_t cksum_type,
794 : const uint8_t *cksum,
795 : uint32_t cksum_len)
796 : {
797 0 : OM_uint32 major;
798 0 : krb5_error_code ret;
799 0 : uint32_t payload_start;
800 :
801 0 : major = put_message_header(minor, ctx, VERIFY, cksum_len, &payload_start);
802 0 : if (major != GSS_S_COMPLETE)
803 0 : return major;
804 :
805 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH));
806 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH));
807 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961));
808 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_type));
809 : /* ChecksumValue vector */
810 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
811 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_len));
812 : /* Four bytes of padding to reach a multiple of 8 bytes. */
813 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4));
814 : /* Payload (checksum contents) */
815 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, cksum, cksum_len));
816 :
817 0 : return GSS_S_COMPLETE;
818 :
819 0 : fail:
820 0 : *minor = ret;
821 0 : return GSS_S_FAILURE;
822 : }
823 :
824 : /*
825 : * Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the
826 : * reason ALERT_VERIFY_NO_KEY.
827 : */
828 : OM_uint32
829 0 : _gss_negoex_add_verify_no_key_alert(OM_uint32 *minor,
830 : gssspnego_ctx ctx,
831 : const auth_scheme scheme)
832 : {
833 0 : OM_uint32 major;
834 0 : krb5_error_code ret;
835 0 : uint32_t payload_start;
836 :
837 0 : major = put_message_header(minor, ctx,
838 : ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH,
839 : &payload_start);
840 0 : if (major != GSS_S_COMPLETE)
841 0 : return major;
842 :
843 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH));
844 : /* ErrorCode */
845 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, 0));
846 : /* Alerts vector */
847 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start));
848 0 : CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 1));
849 : /* Six bytes of padding to reach a multiple of 8 bytes. */
850 0 : CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, "\0\0\0\0\0\0", 6));
851 : /* Payload part 1: a single ALERT element */
852 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_TYPE_PULSE));
853 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript,
854 : payload_start + ALERT_LENGTH));
855 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH));
856 : /* Payload part 2: ALERT_PULSE */
857 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH));
858 0 : CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_VERIFY_NO_KEY));
859 :
860 0 : return GSS_S_COMPLETE;
861 :
862 0 : fail:
863 0 : *minor = ret;
864 0 : return GSS_S_FAILURE;
865 : }
866 :
867 :
868 : void
869 0 : _gss_negoex_release_auth_mech(krb5_context context,
870 : struct negoex_auth_mech *mech)
871 : {
872 0 : OM_uint32 tmpmin;
873 :
874 0 : if (mech == NULL)
875 0 : return;
876 :
877 0 : gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
878 0 : gss_release_oid(&tmpmin, &mech->oid);
879 0 : gss_release_buffer(&tmpmin, &mech->metadata);
880 0 : if (mech->crypto)
881 0 : krb5_crypto_destroy(context, mech->crypto);
882 0 : if (mech->verify_crypto)
883 0 : krb5_crypto_destroy(context, mech->verify_crypto);
884 :
885 0 : free(mech);
886 : }
887 :
888 : void
889 0 : _gss_negoex_delete_auth_mech(gssspnego_ctx ctx,
890 : struct negoex_auth_mech *mech)
891 : {
892 0 : krb5_context context = _gss_mg_krb5_context();
893 :
894 0 : HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
895 0 : _gss_negoex_release_auth_mech(context, mech);
896 0 : }
897 :
898 : /* Remove all auth mech entries except for mech from ctx->mechs. */
899 : void
900 0 : _gss_negoex_select_auth_mech(gssspnego_ctx ctx,
901 : struct negoex_auth_mech *mech)
902 : {
903 0 : krb5_context context = _gss_mg_krb5_context();
904 :
905 0 : heim_assert(mech != NULL, "Invalid null NegoEx mech");
906 0 : HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
907 0 : release_all_mechs(ctx, context);
908 0 : HEIM_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links);
909 0 : }
910 :
911 : OM_uint32
912 0 : _gss_negoex_add_auth_mech(OM_uint32 *minor,
913 : gssspnego_ctx ctx,
914 : gss_const_OID oid,
915 : auth_scheme scheme)
916 : {
917 0 : OM_uint32 major;
918 0 : struct negoex_auth_mech *mech;
919 :
920 0 : mech = calloc(1, sizeof(*mech));
921 0 : if (mech == NULL) {
922 0 : *minor = ENOMEM;
923 0 : return GSS_S_FAILURE;
924 : }
925 :
926 0 : major = gss_duplicate_oid(minor, (gss_OID)oid, &mech->oid);
927 0 : if (major != GSS_S_COMPLETE) {
928 0 : free(mech);
929 0 : return major;
930 : }
931 :
932 0 : memcpy(mech->scheme, scheme, GUID_LENGTH);
933 :
934 0 : HEIM_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links);
935 :
936 0 : *minor = 0;
937 0 : return GSS_S_COMPLETE;
938 : }
939 :
940 : struct negoex_auth_mech *
941 0 : _gss_negoex_locate_auth_scheme(gssspnego_ctx ctx,
942 : const auth_scheme scheme)
943 : {
944 0 : struct negoex_auth_mech *mech;
945 :
946 0 : HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
947 0 : if (GUID_EQ(mech->scheme, scheme))
948 0 : return mech;
949 : }
950 :
951 0 : return NULL;
952 : }
953 :
954 : /*
955 : * Prune ctx->mechs to the schemes present in schemes, and reorder them to
956 : * match its order.
957 : */
958 : void
959 0 : _gss_negoex_common_auth_schemes(gssspnego_ctx ctx,
960 : const uint8_t *schemes,
961 : uint16_t nschemes)
962 : {
963 0 : struct negoex_mech_list list;
964 0 : struct negoex_auth_mech *mech;
965 0 : uint16_t i;
966 0 : krb5_context context = _gss_mg_krb5_context();
967 :
968 : /* Construct a new list in the order of schemes. */
969 0 : HEIM_TAILQ_INIT(&list);
970 0 : for (i = 0; i < nschemes; i++) {
971 0 : mech = _gss_negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH);
972 0 : if (mech == NULL)
973 0 : continue;
974 0 : HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links);
975 0 : HEIM_TAILQ_INSERT_TAIL(&list, mech, links);
976 : }
977 :
978 : /* Release any leftover entries and replace the context list. */
979 0 : release_all_mechs(ctx, context);
980 0 : HEIM_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links);
981 0 : }
982 :
983 : /*
984 : * Prune ctx->mechs to the schemes present in schemes, but do not change
985 : * their order.
986 : */
987 : void
988 0 : _gss_negoex_restrict_auth_schemes(gssspnego_ctx ctx,
989 : const uint8_t *schemes,
990 : uint16_t nschemes)
991 : {
992 0 : struct negoex_auth_mech *mech, *next;
993 0 : uint16_t i;
994 0 : int found;
995 :
996 0 : HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) {
997 0 : found = FALSE;
998 0 : for (i = 0; i < nschemes && !found; i++) {
999 0 : if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH))
1000 0 : found = TRUE;
1001 : }
1002 :
1003 0 : if (!found)
1004 0 : _gss_negoex_delete_auth_mech(ctx, mech);
1005 : }
1006 0 : }
1007 :
1008 : /*
1009 : * Return the OID of the current NegoEx mechanism.
1010 : */
1011 : struct negoex_auth_mech *
1012 0 : _gss_negoex_negotiated_mech(gssspnego_ctx ctx)
1013 : {
1014 0 : return HEIM_TAILQ_FIRST(&ctx->negoex_mechs);
1015 : }
1016 :
1017 : /*
1018 : * Returns TRUE if mechanism can be negotiated by both NegoEx and SPNEGO
1019 : */
1020 :
1021 : int
1022 0 : _gss_negoex_and_spnego_mech_p(gss_const_OID mech)
1023 : {
1024 0 : OM_uint32 major, minor;
1025 0 : gss_OID_set attrs = GSS_C_NO_OID_SET;
1026 0 : int negoex_and_spnego = FALSE;
1027 :
1028 0 : major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
1029 0 : if (major == GSS_S_COMPLETE) {
1030 0 : gss_test_oid_set_member(&minor, GSS_C_MA_NEGOEX_AND_SPNEGO,
1031 : attrs, &negoex_and_spnego);
1032 0 : gss_release_oid_set(&minor, &attrs);
1033 : }
1034 :
1035 0 : return negoex_and_spnego;
1036 : }
1037 :
1038 : int
1039 0 : _gss_negoex_mech_p(gss_const_OID mech)
1040 : {
1041 0 : OM_uint32 minor;
1042 0 : auth_scheme scheme;
1043 :
1044 0 : return gssspi_query_mechanism_info(&minor, mech,
1045 0 : scheme) == GSS_S_COMPLETE;
1046 : }
1047 :
|