Implement gssEapDeriveRFC3961Key
[mech_eap.git] / unwrap_iov.c
1 /*
2  * Copyright (c) 2010, JANET(UK)
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  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 /*
33  * Copyright 2008 by the Massachusetts Institute of Technology.
34  * All Rights Reserved.
35  *
36  * Export of this software from the United States of America may
37  *   require a specific license from the United States Government.
38  *   It is the responsibility of any person or organization contemplating
39  *   export to obtain such a license before exporting.
40  *
41  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
42  * distribute this software and its documentation for any purpose and
43  * without fee is hereby granted, provided that the above copyright
44  * notice appear in all copies and that both that copyright notice and
45  * this permission notice appear in supporting documentation, and that
46  * the name of M.I.T. not be used in advertising or publicity pertaining
47  * to distribution of the software without specific, written prior
48  * permission.  Furthermore if you modify this software you must label
49  * your software as modified software and not distribute it in such a
50  * fashion that it might be confused with the original M.I.T. software.
51  * M.I.T. makes no representations about the suitability of
52  * this software for any purpose.  It is provided "as is" without express
53  * or implied warranty.
54  */
55
56 #include "gssapiP_eap.h"
57
58 /*
59  * Caller must provide TOKEN | DATA | PADDING | TRAILER, except
60  * for DCE in which case it can just provide TOKEN | DATA (must
61  * guarantee that DATA is padded)
62  */
63 OM_uint32
64 unwrapToken(OM_uint32 *minor,
65             gss_ctx_id_t ctx,
66             int *conf_state,
67             gss_qop_t *qop_state,
68             gss_iov_buffer_desc *iov,
69             int iov_count,
70             enum gss_eap_token_type toktype)
71 {
72     OM_uint32 code;
73     gss_iov_buffer_t header;
74     gss_iov_buffer_t padding;
75     gss_iov_buffer_t trailer;
76     unsigned char acceptorFlag;
77     unsigned char *ptr = NULL;
78     int keyUsage;
79     size_t rrc, ec;
80     size_t dataLen, assocDataLen;
81     uint64_t seqnum;
82     int valid = 0;
83     krb5_cksumtype cksumtype;
84     int conf_flag = 0;
85     krb5_context krbContext;
86
87     GSSEAP_KRB_INIT(&krbContext);
88
89     *minor = 0;
90
91     if (qop_state != NULL)
92         *qop_state = GSS_C_QOP_DEFAULT;
93
94     header = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_HEADER);
95     assert(header != NULL);
96
97     padding = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_PADDING);
98     if (padding != NULL && padding->buffer.length != 0)
99         return GSS_S_DEFECTIVE_TOKEN;
100
101     trailer = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER);
102
103     acceptorFlag = CTX_IS_INITIATOR(ctx) ? TOK_FLAG_SENDER_IS_ACCEPTOR : 0;
104     keyUsage = (toktype == TOK_TYPE_WRAP
105                 ? (!CTX_IS_INITIATOR(ctx)
106                    ? KEY_USAGE_INITIATOR_SEAL
107                    : KEY_USAGE_ACCEPTOR_SEAL)
108                 : (!CTX_IS_INITIATOR(ctx)
109                    ? KEY_USAGE_INITIATOR_SIGN
110                    : KEY_USAGE_ACCEPTOR_SIGN));
111
112     gssEapIovMessageLength(iov, iov_count, &dataLen, &assocDataLen);
113
114     ptr = (unsigned char *)header->buffer.value;
115
116     if (header->buffer.length < 16) {
117         *minor = 0;
118         return GSS_S_DEFECTIVE_TOKEN;
119     }
120
121     if ((ptr[2] & TOK_FLAG_SENDER_IS_ACCEPTOR) != acceptorFlag) {
122         return GSS_S_BAD_SIG;
123     }
124
125     if (ptr[2] & TOK_FLAG_ACCEPTOR_SUBKEY) {
126         return GSS_S_BAD_SIG;
127     }
128
129     if (toktype == TOK_TYPE_WRAP) {
130         unsigned int krbTrailerLen;
131
132         if (load_uint16_be(ptr) != TOK_TYPE_WRAP)
133             goto defective;
134         conf_flag = ((ptr[2] & TOK_FLAG_WRAP_CONFIDENTIAL) != 0);
135         if (ptr[3] != 0xFF)
136             goto defective;
137         ec = load_uint16_be(ptr + 4);
138         rrc = load_uint16_be(ptr + 6);
139         seqnum = load_uint64_be(ptr + 8);
140
141         code = krb5_c_crypto_length(krbContext,
142                                     ctx->encryptionType,
143                                     conf_flag ? KRB5_CRYPTO_TYPE_TRAILER :
144                                     KRB5_CRYPTO_TYPE_CHECKSUM,
145                                     &krbTrailerLen);
146         if (code != 0) {
147             *minor = code;
148             return GSS_S_FAILURE;
149         }
150
151         /* Deal with RRC */
152         if (trailer == NULL) {
153             size_t desired_rrc = krbTrailerLen;
154
155             if (conf_flag) {
156                 desired_rrc += 16; /* E(Header) */
157
158                 if ((ctx->gssFlags & GSS_C_DCE_STYLE) == 0)
159                     desired_rrc += ec;
160             }
161
162             /* According to MS, we only need to deal with a fixed RRC for DCE */
163             if (rrc != desired_rrc)
164                 goto defective;
165         } else if (rrc != 0) {
166             goto defective;
167         }
168
169         if (conf_flag) {
170             unsigned char *althdr;
171
172             /* Decrypt */
173             code = gssEapDecrypt(krbContext,
174                                  ((ctx->gssFlags & GSS_C_DCE_STYLE) != 0),
175                                  ec, rrc, &ctx->rfc3961Key,
176                                  keyUsage, 0, iov, iov_count);
177             if (code != 0) {
178                 *minor = code;
179                 return GSS_S_BAD_SIG;
180             }
181
182             /* Validate header integrity */
183             if (trailer == NULL)
184                 althdr = (unsigned char *)header->buffer.value + 16 + ec;
185             else
186                 althdr = (unsigned char *)trailer->buffer.value + ec;
187
188             if (load_uint16_be(althdr) != TOK_TYPE_WRAP
189                 || althdr[2] != ptr[2]
190                 || althdr[3] != ptr[3]
191                 || memcmp(althdr + 8, ptr + 8, 8) != 0) {
192                 *minor = 0;
193                 return GSS_S_BAD_SIG;
194             }
195         } else {
196             /* Verify checksum: note EC is checksum size here, not padding */
197             if (ec != krbTrailerLen)
198                 goto defective;
199
200             /* Zero EC, RRC before computing checksum */
201             store_uint16_be(0, ptr + 4);
202             store_uint16_be(0, ptr + 6);
203
204             code = gssEapVerify(krbContext, cksumtype, rrc,
205                                 &ctx->rfc3961Key, keyUsage,
206                                 iov, iov_count, &valid);
207             if (code != 0 || valid == FALSE) {
208                 *minor = code;
209                 return GSS_S_BAD_SIG;
210             }
211         }
212
213         code = sequenceCheck(&ctx->seqState, seqnum);
214     } else if (toktype == TOK_TYPE_MIC) {
215         if (load_uint16_be(ptr) != TOK_TYPE_MIC)
216             goto defective;
217
218     verify_mic_1:
219         if (ptr[3] != 0xFF)
220             goto defective;
221         seqnum = load_uint64_be(ptr + 8);
222
223         code = gssEapVerify(krbContext, cksumtype, 0,
224                             &ctx->rfc3961Key, keyUsage,
225                             iov, iov_count, &valid);
226         if (code != 0 || valid == FALSE) {
227             *minor = code;
228             return GSS_S_BAD_SIG;
229         }
230         code = sequenceCheck(&ctx->seqState, seqnum);
231     } else if (toktype == TOK_TYPE_DELETE) {
232         if (load_uint16_be(ptr) != TOK_TYPE_DELETE)
233             goto defective;
234         goto verify_mic_1;
235     } else {
236         goto defective;
237     }
238
239     *minor = 0;
240
241     if (conf_state != NULL)
242         *conf_state = conf_flag;
243
244     return code;
245
246 defective:
247     *minor = 0;
248
249     return GSS_S_DEFECTIVE_TOKEN;
250 }
251
252 int
253 rotateLeft(void *ptr, size_t bufsiz, size_t rc)
254 {
255     void *tbuf;
256
257     if (bufsiz == 0)
258         return 0;
259     rc = rc % bufsiz;
260     if (rc == 0)
261         return 0;
262
263     tbuf = GSSEAP_MALLOC(rc);
264     if (tbuf == NULL)
265         return ENOMEM;
266
267     memcpy(tbuf, ptr, rc);
268     memmove(ptr, (char *)ptr + rc, bufsiz - rc);
269     memcpy((char *)ptr + bufsiz - rc, tbuf, rc);
270     GSSEAP_FREE(tbuf);
271
272     return 0;
273 }
274
275 /*
276  * Split a STREAM | SIGN_DATA | DATA into
277  *         HEADER | SIGN_DATA | DATA | PADDING | TRAILER
278  */
279 static OM_uint32
280 unwrapStream(OM_uint32 *minor,
281              gss_ctx_id_t ctx,
282              int *conf_state,
283              gss_qop_t *qop_state,
284              gss_iov_buffer_desc *iov,
285              int iov_count,
286              enum gss_eap_token_type toktype)
287 {
288     unsigned char *ptr;
289     OM_uint32 code = 0, major = GSS_S_FAILURE;
290     krb5_context krbContext;
291     int conf_req_flag, toktype2;
292     int i = 0, j;
293     gss_iov_buffer_desc *tiov = NULL;
294     gss_iov_buffer_t stream, data = NULL;
295     gss_iov_buffer_t theader, tdata = NULL, tpadding, ttrailer;
296
297     GSSEAP_KRB_INIT(&krbContext);
298
299     assert(toktype == TOK_TYPE_WRAP);
300
301     if (toktype != TOK_TYPE_WRAP || (ctx->gssFlags & GSS_C_DCE_STYLE)) {
302         code = EINVAL;
303         goto cleanup;
304     }
305
306     stream = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM);
307     assert(stream != NULL);
308
309     if (stream->buffer.length < 16) {
310         major = GSS_S_DEFECTIVE_TOKEN;
311         goto cleanup;
312     }
313
314     ptr = (unsigned char *)stream->buffer.value;
315     toktype2 = load_uint16_be(ptr);
316     ptr += 2;
317
318     tiov = (gss_iov_buffer_desc *)GSSEAP_CALLOC((size_t)iov_count + 2,
319                                                 sizeof(gss_iov_buffer_desc));
320     if (tiov == NULL) {
321         code = ENOMEM;
322         goto cleanup;
323     }
324
325     /* HEADER */
326     theader = &tiov[i++];
327     theader->type = GSS_IOV_BUFFER_TYPE_HEADER;
328     theader->buffer.value = stream->buffer.value;
329     theader->buffer.length = 16;
330
331     /* n[SIGN_DATA] | DATA | m[SIGN_DATA] */
332     for (j = 0; j < iov_count; j++) {
333         OM_uint32 type = GSS_IOV_BUFFER_TYPE(iov[j].type);
334
335         if (type == GSS_IOV_BUFFER_TYPE_DATA) {
336             if (data != NULL) {
337                 /* only a single DATA buffer can appear */
338                 code = EINVAL;
339                 goto cleanup;
340             }
341
342             data = &iov[j];
343             tdata = &tiov[i];
344         }
345         if (type == GSS_IOV_BUFFER_TYPE_DATA ||
346             type == GSS_IOV_BUFFER_TYPE_SIGN_ONLY)
347             tiov[i++] = iov[j];
348     }
349
350     if (data == NULL) {
351         /* a single DATA buffer must be present */
352         code = EINVAL;
353         goto cleanup;
354     }
355
356     /* PADDING | TRAILER */
357     tpadding = &tiov[i++];
358     tpadding->type = GSS_IOV_BUFFER_TYPE_PADDING;
359     tpadding->buffer.length = 0;
360     tpadding->buffer.value = NULL;
361
362     ttrailer = &tiov[i++];
363     ttrailer->type = GSS_IOV_BUFFER_TYPE_TRAILER;
364
365     {
366         size_t ec, rrc;
367         unsigned int krbHeaderLen = 0;
368         unsigned int krbTrailerLen = 0;
369
370         conf_req_flag = ((ptr[0] & TOK_FLAG_WRAP_CONFIDENTIAL) != 0);
371         ec = conf_req_flag ? load_uint16_be(ptr + 2) : 0;
372         rrc = load_uint16_be(ptr + 4);
373
374         if (rrc != 0) {
375             code = rotateLeft((unsigned char *)stream->buffer.value + 16,
376                               stream->buffer.length - 16, rrc);
377             if (code != 0)
378                 goto cleanup;
379             store_uint16_be(0, ptr + 4); /* set RRC to zero */
380         }
381
382         if (conf_req_flag) {
383             code = krb5_c_crypto_length(krbContext, ctx->encryptionType,
384                                         KRB5_CRYPTO_TYPE_HEADER, &krbHeaderLen);
385             if (code != 0)
386                 goto cleanup;
387             theader->buffer.length += krbHeaderLen; /* length validated later */
388         }
389
390         /* no PADDING for CFX, EC is used instead */
391         code = krb5_c_crypto_length(krbContext, ctx->encryptionType,
392                                     conf_req_flag
393                                       ? KRB5_CRYPTO_TYPE_TRAILER
394                                       : KRB5_CRYPTO_TYPE_CHECKSUM,
395                                     &krbTrailerLen);
396         if (code != 0)
397             goto cleanup;
398
399         ttrailer->buffer.length = ec + (conf_req_flag ? 16 : 0 /* E(Header) */) +
400                                   krbTrailerLen;
401         ttrailer->buffer.value = (unsigned char *)stream->buffer.value +
402             stream->buffer.length - ttrailer->buffer.length;
403     }
404
405     /* IOV: -----------0-------------+---1---+--2--+----------------3--------------*/
406     /* Old: GSS-Header | Conf        | Data  | Pad |                               */
407     /* CFX: GSS-Header | Kerb-Header | Data  |     | EC | E(Header) | Kerb-Trailer */
408     /* GSS: -------GSS-HEADER--------+-DATA--+-PAD-+----------GSS-TRAILER----------*/
409
410     /* validate lengths */
411     if (stream->buffer.length < theader->buffer.length +
412         tpadding->buffer.length +
413         ttrailer->buffer.length) {
414         code = KRB5_BAD_MSIZE;
415         major = GSS_S_DEFECTIVE_TOKEN;
416         goto cleanup;
417     }
418
419     /* setup data */
420     tdata->buffer.length = stream->buffer.length - ttrailer->buffer.length -
421         tpadding->buffer.length - theader->buffer.length;
422
423     assert(data != NULL);
424
425     if (data->type & GSS_IOV_BUFFER_FLAG_ALLOCATE) {
426         code = gssEapAllocIov(tdata, tdata->buffer.length);
427         if (code != 0)
428             goto cleanup;
429
430         memcpy(tdata->buffer.value,
431                (unsigned char *)stream->buffer.value + theader->buffer.length,
432                tdata->buffer.length);
433     } else {
434         tdata->buffer.value = (unsigned char *)stream->buffer.value +
435                               theader->buffer.length;
436     }
437
438     assert(i <= iov_count + 2);
439
440     major = unwrapToken(&code, ctx, conf_state, qop_state,
441                         tiov, i, toktype);
442     if (major == GSS_S_COMPLETE) {
443         *data = *tdata;
444     } else if (tdata->type & GSS_IOV_BUFFER_FLAG_ALLOCATED) {
445         OM_uint32 tmp;
446
447         gss_release_buffer(&tmp, &tdata->buffer);
448         tdata->type &= ~(GSS_IOV_BUFFER_FLAG_ALLOCATED);
449     }
450
451 cleanup:
452     if (tiov != NULL)
453         GSSEAP_FREE(tiov);
454
455     *minor = code;
456
457     return major;
458 }
459
460 OM_uint32
461 gssEapUnwrapOrVerifyMIC(OM_uint32 *minor,
462                         gss_ctx_id_t ctx,
463                         int *conf_state,
464                         gss_qop_t *qop_state,
465                         gss_iov_buffer_desc *iov,
466                         int iov_count,
467                         enum gss_eap_token_type toktype)
468 {
469     OM_uint32 major;
470
471     if (!CTX_IS_ESTABLISHED(ctx))
472         return GSS_S_NO_CONTEXT;
473
474     if (gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM) != NULL) {
475         major = unwrapStream(minor, ctx, conf_state, qop_state,
476                              iov, iov_count, toktype);
477     } else {
478         major = unwrapToken(minor, ctx, conf_state, qop_state,
479                             iov, iov_count, toktype);
480     }
481
482     return major;
483 }
484
485 OM_uint32
486 gss_unwrap_iov(OM_uint32 *minor,
487                gss_ctx_id_t ctx,
488                int *conf_state,
489                gss_qop_t *qop_state,
490                gss_iov_buffer_desc *iov,
491                int iov_count)
492 {
493     return gssEapUnwrapOrVerifyMIC(minor, ctx, conf_state, qop_state,
494                                    iov, iov_count, TOK_TYPE_WRAP);
495 }