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