Fixes for Heimdal (macOS) builds from Stefan.
[mech_eap.git] / mech_eap / wrap_iov.c
1 /*
2  * Copyright (c) 2011, 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 /*
57  * Message protection services: wrap with scatter-gather API.
58  */
59
60 #include "gssapiP_eap.h"
61
62 unsigned char
63 rfc4121Flags(gss_const_ctx_id_t ctx, int receiving)
64 {
65     unsigned char flags;
66     int isAcceptor;
67
68     isAcceptor = !CTX_IS_INITIATOR(ctx);
69     if (receiving)
70         isAcceptor = !isAcceptor;
71
72     flags = 0;
73     if (isAcceptor)
74         flags |= TOK_FLAG_SENDER_IS_ACCEPTOR;
75
76     if ((ctx->flags & CTX_FLAG_KRB_REAUTH) &&
77         (ctx->gssFlags & GSS_C_MUTUAL_FLAG))
78         flags |= TOK_FLAG_ACCEPTOR_SUBKEY;
79
80     return flags;
81 }
82
83 OM_uint32
84 gssEapWrapOrGetMIC(OM_uint32 *minor,
85                    gss_ctx_id_t ctx,
86                    int conf_req_flag,
87                    int *conf_state,
88                    gss_iov_buffer_desc *iov,
89                    int iov_count,
90                    enum gss_eap_token_type toktype)
91 {
92     krb5_error_code code = 0;
93     gss_iov_buffer_t header;
94     gss_iov_buffer_t padding;
95     gss_iov_buffer_t trailer;
96     unsigned char flags;
97     unsigned char *outbuf = NULL;
98     unsigned char *tbuf = NULL;
99     int keyUsage;
100     size_t rrc = 0;
101     size_t gssHeaderLen, gssTrailerLen;
102     size_t dataLen, assocDataLen;
103     krb5_context krbContext;
104 #ifdef HAVE_HEIMDAL_VERSION
105     krb5_crypto krbCrypto = NULL;
106 #endif
107
108     if (ctx->encryptionType == ENCTYPE_NULL) {
109         *minor = GSSEAP_KEY_UNAVAILABLE;
110         return GSS_S_UNAVAILABLE;
111     }
112
113     GSSEAP_KRB_INIT(&krbContext);
114
115     flags = rfc4121Flags(ctx, FALSE);
116
117     if (toktype == TOK_TYPE_WRAP) {
118         keyUsage = CTX_IS_INITIATOR(ctx)
119                    ? KEY_USAGE_INITIATOR_SEAL
120                    : KEY_USAGE_ACCEPTOR_SEAL;
121     } else {
122         keyUsage = CTX_IS_INITIATOR(ctx)
123                    ? KEY_USAGE_INITIATOR_SIGN
124                    : KEY_USAGE_ACCEPTOR_SIGN;
125     }
126
127     gssEapIovMessageLength(iov, iov_count, &dataLen, &assocDataLen);
128
129     header = gssEapLocateHeaderIov(iov, iov_count, toktype);
130     if (header == NULL) {
131         *minor = GSSEAP_MISSING_IOV;
132         return GSS_S_FAILURE;
133     }
134
135     padding = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_PADDING);
136     if (padding != NULL)
137         padding->buffer.length = 0;
138
139     trailer = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER);
140
141 #ifdef HAVE_HEIMDAL_VERSION
142     code = krb5_crypto_init(krbContext, &ctx->rfc3961Key, ETYPE_NULL, &krbCrypto);
143     if (code != 0)
144         goto cleanup;
145 #endif
146
147     if (toktype == TOK_TYPE_WRAP && conf_req_flag) {
148         size_t krbHeaderLen, krbTrailerLen, krbPadLen;
149         size_t ec = 0, confDataLen = dataLen - assocDataLen;
150
151         code = krbCryptoLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
152                                KRB5_CRYPTO_TYPE_HEADER, &krbHeaderLen);
153         if (code != 0)
154             goto cleanup;
155
156         code = krbPaddingLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
157                                 confDataLen + 16 /* E(Header) */,
158                                 &krbPadLen);
159         if (code != 0)
160             goto cleanup;
161
162         if (krbPadLen == 0 && (ctx->gssFlags & GSS_C_DCE_STYLE)) {
163             /* Windows rejects AEAD tokens with non-zero EC */
164             code = krbBlockSize(krbContext, KRB_CRYPTO_CONTEXT(ctx), &ec);
165             if (code != 0)
166                 goto cleanup;
167         } else
168             ec = krbPadLen;
169
170         code = krbCryptoLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
171                                KRB5_CRYPTO_TYPE_TRAILER, &krbTrailerLen);
172         if (code != 0)
173             goto cleanup;
174
175         gssHeaderLen = 16 /* Header */ + krbHeaderLen;
176         gssTrailerLen = ec + 16 /* E(Header) */ + krbTrailerLen;
177
178         if (trailer == NULL) {
179             rrc = gssTrailerLen;
180             /* Workaround for Windows bug where it rotates by EC + RRC */
181             if (ctx->gssFlags & GSS_C_DCE_STYLE)
182                 rrc -= ec;
183             gssHeaderLen += gssTrailerLen;
184         }
185
186         if (header->type & GSS_IOV_BUFFER_FLAG_ALLOCATE) {
187             code = gssEapAllocIov(header, (size_t)gssHeaderLen);
188         } else if (header->buffer.length < gssHeaderLen)
189             code = GSSEAP_WRONG_SIZE;
190         if (code != 0)
191             goto cleanup;
192         outbuf = (unsigned char *)header->buffer.value;
193         header->buffer.length = (size_t)gssHeaderLen;
194
195         if (trailer != NULL) {
196             if (trailer->type & GSS_IOV_BUFFER_FLAG_ALLOCATE)
197                 code = gssEapAllocIov(trailer, (size_t)gssTrailerLen);
198             else if (trailer->buffer.length < gssTrailerLen)
199                 code = GSSEAP_WRONG_SIZE;
200             if (code != 0)
201                 goto cleanup;
202             trailer->buffer.length = (size_t)gssTrailerLen;
203         }
204
205         /* TOK_ID */
206         store_uint16_be((uint16_t)toktype, outbuf);
207         /* flags */
208         outbuf[2] = flags
209                      | (conf_req_flag ? TOK_FLAG_WRAP_CONFIDENTIAL : 0);
210         /* filler */
211         outbuf[3] = 0xFF;
212         /* EC */
213         store_uint16_be(ec, outbuf + 4);
214         /* RRC */
215         store_uint16_be(0, outbuf + 6);
216         store_uint64_be(ctx->sendSeq, outbuf + 8);
217
218         /*
219          * EC | copy of header to be encrypted, located in
220          * (possibly rotated) trailer
221          */
222         if (trailer == NULL)
223             tbuf = (unsigned char *)header->buffer.value + 16; /* Header */
224         else
225             tbuf = (unsigned char *)trailer->buffer.value;
226
227         memset(tbuf, 0xFF, ec);
228         memcpy(tbuf + ec, header->buffer.value, 16);
229
230         code = gssEapEncrypt(krbContext,
231                              ((ctx->gssFlags & GSS_C_DCE_STYLE) != 0),
232                              ec, rrc, KRB_CRYPTO_CONTEXT(ctx),
233                              keyUsage, iov, iov_count);
234         if (code != 0)
235             goto cleanup;
236
237         /* RRC */
238         store_uint16_be(rrc, outbuf + 6);
239
240         ctx->sendSeq++;
241     } else if (toktype == TOK_TYPE_WRAP && !conf_req_flag) {
242     wrap_with_checksum:
243
244         gssHeaderLen = 16;
245
246         code = krbCryptoLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
247                                KRB5_CRYPTO_TYPE_CHECKSUM, &gssTrailerLen);
248         if (code != 0)
249             goto cleanup;
250
251         GSSEAP_ASSERT(gssTrailerLen <= 0xFFFF);
252
253         if (trailer == NULL) {
254             rrc = gssTrailerLen;
255             gssHeaderLen += gssTrailerLen;
256         }
257
258         if (header->type & GSS_IOV_BUFFER_FLAG_ALLOCATE)
259             code = gssEapAllocIov(header, (size_t)gssHeaderLen);
260         else if (header->buffer.length < gssHeaderLen)
261             code = GSSEAP_WRONG_SIZE;
262         if (code != 0)
263             goto cleanup;
264         outbuf = (unsigned char *)header->buffer.value;
265         header->buffer.length = (size_t)gssHeaderLen;
266
267         if (trailer != NULL) {
268             if (trailer->type & GSS_IOV_BUFFER_FLAG_ALLOCATE)
269                 code = gssEapAllocIov(trailer, (size_t)gssTrailerLen);
270             else if (trailer->buffer.length < gssTrailerLen)
271                 code = GSSEAP_WRONG_SIZE;
272             if (code != 0)
273                 goto cleanup;
274             trailer->buffer.length = (size_t)gssTrailerLen;
275         }
276
277         /* TOK_ID */
278         store_uint16_be((uint16_t)toktype, outbuf);
279         /* flags */
280         outbuf[2] = flags;
281         /* filler */
282         outbuf[3] = 0xFF;
283         if (toktype == TOK_TYPE_WRAP) {
284             /* Use 0 for checksum calculation, substitute
285              * checksum length later.
286              */
287             /* EC */
288             store_uint16_be(0, outbuf + 4);
289             /* RRC */
290             store_uint16_be(0, outbuf + 6);
291         } else {
292             /* MIC and DEL store 0xFF in EC and RRC */
293             store_uint16_be(0xFFFF, outbuf + 4);
294             store_uint16_be(0xFFFF, outbuf + 6);
295         }
296         store_uint64_be(ctx->sendSeq, outbuf + 8);
297
298         code = gssEapSign(krbContext, ctx->checksumType, rrc,
299                           KRB_CRYPTO_CONTEXT(ctx), keyUsage,
300                           iov, iov_count, toktype);
301         if (code != 0)
302             goto cleanup;
303
304         ctx->sendSeq++;
305
306         if (toktype == TOK_TYPE_WRAP) {
307             /* Fix up EC field */
308             store_uint16_be(gssTrailerLen, outbuf + 4);
309             /* Fix up RRC field */
310             store_uint16_be(rrc, outbuf + 6);
311         }
312     } else if (toktype == TOK_TYPE_MIC) {
313         trailer = NULL;
314         goto wrap_with_checksum;
315     } else if (toktype == TOK_TYPE_DELETE_CONTEXT) {
316         trailer = NULL;
317         goto wrap_with_checksum;
318     } else {
319         abort();
320     }
321
322     code = 0;
323     if (conf_state != NULL)
324         *conf_state = conf_req_flag;
325
326 cleanup:
327     if (code != 0)
328         gssEapReleaseIov(iov, iov_count);
329 #ifdef HAVE_HEIMDAL_VERSION
330     if (krbCrypto != NULL)
331         krb5_crypto_destroy(krbContext, krbCrypto);
332 #endif
333
334     *minor = code;
335
336     return (code == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
337 }
338
339 OM_uint32 GSSAPI_CALLCONV
340 gss_wrap_iov(OM_uint32 *minor,
341              gss_ctx_id_t ctx,
342              int conf_req_flag,
343              gss_qop_t qop_req,
344              int *conf_state,
345              gss_iov_buffer_desc *iov,
346              int iov_count)
347 {
348     OM_uint32 major;
349
350     if (ctx == GSS_C_NO_CONTEXT) {
351         *minor = EINVAL;
352         return GSS_S_CALL_INACCESSIBLE_READ | GSS_S_NO_CONTEXT;
353     }
354
355     if (qop_req != GSS_C_QOP_DEFAULT) {
356         *minor = GSSEAP_UNKNOWN_QOP;
357         return GSS_S_UNAVAILABLE;
358     }
359
360     *minor = 0;
361
362     GSSEAP_MUTEX_LOCK(&((gss_ctx_id_t)ctx)->mutex);
363
364     if (!CTX_IS_ESTABLISHED(ctx)) {
365         major = GSS_S_NO_CONTEXT;
366         *minor = GSSEAP_CONTEXT_INCOMPLETE;
367         goto cleanup;
368     }
369
370     major = gssEapWrapOrGetMIC(minor, (gss_ctx_id_t)ctx, conf_req_flag, conf_state,
371                                iov, iov_count, TOK_TYPE_WRAP);
372     if (GSS_ERROR(major))
373         goto cleanup;
374
375 cleanup:
376     GSSEAP_MUTEX_UNLOCK(&((gss_ctx_id_t)ctx)->mutex);
377
378     return major;
379 }
380