Fixes for Heimdal (macOS) builds from Stefan.
[mech_eap.git] / mech_eap / util_sm.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 /*
34  * Context establishment state machine.
35  */
36
37 #include "gssapiP_eap.h"
38
39 /* private flags */
40 #define SM_FLAG_TRANSITED                   0x80000000
41
42 #define SM_ASSERT_VALID(ctx, status)        do { \
43         GSSEAP_ASSERT(GSS_ERROR((status)) || \
44                ((status) == GSS_S_CONTINUE_NEEDED && ((ctx)->state > GSSEAP_STATE_INITIAL && (ctx)->state < GSSEAP_STATE_ESTABLISHED)) || \
45                ((status) == GSS_S_COMPLETE && (ctx)->state == GSSEAP_STATE_ESTABLISHED)); \
46     } while (0)
47
48 #ifdef GSSEAP_DEBUG
49 static const char *
50 gssEapStateToString(enum gss_eap_state state)
51 {
52     const char *s;
53
54     switch (state) {
55     case GSSEAP_STATE_INITIAL:
56         s = "INITIAL";
57         break;
58     case GSSEAP_STATE_AUTHENTICATE:
59         s = "AUTHENTICATE";
60         break;
61     case GSSEAP_STATE_INITIATOR_EXTS:
62         s = "INITIATOR_EXTS";
63         break;
64     case GSSEAP_STATE_ACCEPTOR_EXTS:
65         s = "ACCEPTOR_EXTS";
66         break;
67 #ifdef GSSEAP_ENABLE_REAUTH
68     case GSSEAP_STATE_REAUTHENTICATE:
69         s = "REAUTHENTICATE";
70         break;
71 #endif
72     case GSSEAP_STATE_ESTABLISHED:
73         s = "ESTABLISHED";
74         break;
75     default:
76         s = "INVALID";
77         break;
78     }
79
80     return s;
81 }
82
83 void
84 gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state)
85 {
86     GSSEAP_ASSERT(state >= GSSEAP_STATE_INITIAL);
87     GSSEAP_ASSERT(state <= GSSEAP_STATE_ESTABLISHED);
88
89     fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
90             gssEapStateToString(GSSEAP_SM_STATE(ctx)),
91             gssEapStateToString(state));
92
93     ctx->state = state;
94 }
95 #endif /* GSSEAP_DEBUG */
96
97 static OM_uint32
98 makeErrorToken(OM_uint32 *minor,
99                OM_uint32 majorStatus,
100                OM_uint32 minorStatus,
101                struct gss_eap_token_buffer_set *token)
102 {
103     OM_uint32 major, tmpMinor;
104     unsigned char errorData[8];
105     gss_buffer_desc errorBuffer;
106
107     GSSEAP_ASSERT(GSS_ERROR(majorStatus));
108
109     /*
110      * Only return error codes that the initiator could have caused,
111      * to avoid information leakage.
112      */
113     if (IS_RADIUS_ERROR(minorStatus)) {
114         /* Squash RADIUS error codes */
115         minorStatus = GSSEAP_RADIUS_PROT_FAILURE;
116     } else if (!IS_WIRE_ERROR(minorStatus)) {
117         /* Don't return non-wire error codes */
118       minorStatus = 0;
119     }
120
121     if (minorStatus != 0)
122       minorStatus -= ERROR_TABLE_BASE_eapg;
123
124     store_uint32_be(majorStatus, &errorData[0]);
125     store_uint32_be(minorStatus, &errorData[4]);
126
127     major = gssEapAllocInnerTokens(&tmpMinor, 1, token);
128     if (GSS_ERROR(major)) {
129         *minor = tmpMinor;
130         return major;
131     }
132
133     errorBuffer.length = sizeof(errorData);
134     errorBuffer.value = errorData;
135
136     major = duplicateBuffer(&tmpMinor, &errorBuffer, &token->buffers.elements[0]);
137     if (GSS_ERROR(major)) {
138         gssEapReleaseInnerTokens(&tmpMinor, token, 1);
139         *minor = tmpMinor;
140         return major;
141     }
142
143     token->buffers.count = 1;
144     token->types[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
145
146     *minor = 0;
147     return GSS_S_COMPLETE;
148 }
149
150 OM_uint32
151 gssEapSmStep(OM_uint32 *minor,
152              gss_cred_id_t cred,
153              gss_ctx_id_t ctx,
154              gss_const_name_t target,
155              gss_OID mech,
156              OM_uint32 reqFlags,
157              OM_uint32 timeReq,
158              gss_channel_bindings_t chanBindings,
159              gss_buffer_t inputToken,
160              gss_buffer_t outputToken,
161              struct gss_eap_sm *sm, /* ordered by state */
162              size_t smCount)
163 {
164     OM_uint32 major, tmpMajor, tmpMinor;
165     struct gss_eap_token_buffer_set inputTokens = { { 0, GSS_C_NO_BUFFER }, NULL };
166     struct gss_eap_token_buffer_set outputTokens = { { 0, GSS_C_NO_BUFFER }, NULL };
167     gss_buffer_desc unwrappedInputToken = GSS_C_EMPTY_BUFFER;
168     gss_buffer_desc unwrappedOutputToken = GSS_C_EMPTY_BUFFER;
169     unsigned int smFlags = 0;
170     size_t i, j;
171     int initialContextToken = 0;
172     enum gss_eap_token_type tokType;
173
174     GSSEAP_ASSERT(smCount > 0);
175
176     *minor = 0;
177
178     outputToken->length = 0;
179     outputToken->value = NULL;
180
181     if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) {
182         major = gssEapVerifyToken(minor, ctx, inputToken, &tokType,
183                                   &unwrappedInputToken);
184         if (GSS_ERROR(major))
185             goto cleanup;
186
187         if (tokType != (CTX_IS_INITIATOR(ctx)
188                     ? TOK_TYPE_ACCEPTOR_CONTEXT : TOK_TYPE_INITIATOR_CONTEXT)) {
189             major = GSS_S_DEFECTIVE_TOKEN;
190             *minor = GSSEAP_WRONG_TOK_ID;
191             goto cleanup;
192         }
193     } else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) {
194         major = GSS_S_DEFECTIVE_TOKEN;
195         *minor = GSSEAP_WRONG_SIZE;
196         goto cleanup;
197     } else {
198         initialContextToken = 1;
199     }
200
201     if (CTX_IS_ESTABLISHED(ctx)) {
202         major = GSS_S_BAD_STATUS;
203         *minor = GSSEAP_CONTEXT_ESTABLISHED;
204         goto cleanup;
205     }
206
207     GSSEAP_ASSERT(ctx->state < GSSEAP_STATE_ESTABLISHED);
208
209     major = gssEapDecodeInnerTokens(minor, &unwrappedInputToken, &inputTokens);
210     if (GSS_ERROR(major))
211         goto cleanup;
212
213     major = gssEapAllocInnerTokens(minor, smCount, &outputTokens);
214     if (GSS_ERROR(major))
215         goto cleanup;
216
217     ctx->inputTokens = &inputTokens;
218     ctx->outputTokens = &outputTokens;
219
220     /* Process all the tokens that are valid for the current state. */
221     for (i = 0; i < smCount; i++) {
222         struct gss_eap_sm *smp = &sm[i];
223         int processToken = 0;
224         gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
225         OM_uint32 *inputTokenType = NULL;
226         gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
227
228         if ((smp->validStates & ctx->state) == 0)
229             continue;
230
231         /*
232          * We special case the first call to gss_init_sec_context so that
233          * all token providers have the opportunity to generate an initial
234          * context token. Providers where inputTokenType is ITOK_TYPE_NONE
235          * are always called and generally act on state transition boundaries,
236          * for example to advance the state after a series of optional tokens
237          * (as is the case with the extension token exchange) or to generate
238          * a new token after the state was advanced by a provider which did
239          * not emit a token.
240          */
241         if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
242             processToken = 1;
243         } else if ((smFlags & SM_FLAG_TRANSITED) == 0) {
244             /* Don't regurgitate a token which belonds to a previous state. */
245             for (j = 0; j < inputTokens.buffers.count; j++) {
246                 if ((inputTokens.types[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
247                     if (processToken) {
248                         /* Check for duplicate inner tokens */
249                         major = GSS_S_DEFECTIVE_TOKEN;
250                         *minor = GSSEAP_DUPLICATE_ITOK;
251                         break;
252                     }
253                     processToken = 1;
254                     innerInputToken = &inputTokens.buffers.elements[j];
255                     inputTokenType = &inputTokens.types[j];
256                 }
257             }
258             if (GSS_ERROR(major))
259                 break;
260         }
261
262         if (processToken) {
263             enum gss_eap_state oldState = ctx->state;
264
265             smFlags = 0;
266             if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL))
267                 smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL;
268
269             major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
270                                       timeReq, chanBindings, innerInputToken,
271                                       &innerOutputToken, &smFlags);
272             if (GSS_ERROR(major))
273                 break;
274
275             if (inputTokenType != NULL)
276                 *inputTokenType |= ITOK_FLAG_VERIFIED;
277             if (ctx->state < oldState)
278                 i = 0; /* restart */
279             else if (ctx->state != oldState)
280                 smFlags |= SM_FLAG_TRANSITED;
281
282             if (innerOutputToken.value != NULL) {
283                 outputTokens.buffers.elements[outputTokens.buffers.count] = innerOutputToken;
284                 GSSEAP_ASSERT(smp->outputTokenType != ITOK_TYPE_NONE);
285                 outputTokens.types[outputTokens.buffers.count] = smp->outputTokenType;
286                 if (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL)
287                     outputTokens.types[outputTokens.buffers.count] |= ITOK_FLAG_CRITICAL;
288                 outputTokens.buffers.count++;
289             }
290             /*
291              * Break out if we made a state transition and have some tokens to send.
292              */
293             if ((smFlags & SM_FLAG_TRANSITED) &&
294                  ((smFlags & SM_FLAG_FORCE_SEND_TOKEN) || outputTokens.buffers.count != 0)) {
295                 SM_ASSERT_VALID(ctx, major);
296                 break;
297             }
298         } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
299             smp->inputTokenType != ITOK_TYPE_NONE) {
300             /* Check for required inner tokens */
301             major = GSS_S_DEFECTIVE_TOKEN;
302             *minor = GSSEAP_MISSING_REQUIRED_ITOK;
303             break;
304         }
305     }
306
307     GSSEAP_ASSERT(outputTokens.buffers.count <= smCount);
308
309     /* Check we understood all critical tokens sent by peer */
310     if (!GSS_ERROR(major)) {
311         for (j = 0; j < inputTokens.buffers.count; j++) {
312             if ((inputTokens.types[j] & ITOK_FLAG_CRITICAL) &&
313                 (inputTokens.types[j] & ITOK_FLAG_VERIFIED) == 0) {
314                 major = GSS_S_UNAVAILABLE;
315                 *minor = GSSEAP_CRIT_ITOK_UNAVAILABLE;
316                 goto cleanup;
317             }
318         }
319     }
320
321     /* Optionaly emit an error token if we are the acceptor */
322     if (GSS_ERROR(major)) {
323         if (CTX_IS_INITIATOR(ctx))
324             goto cleanup; /* return error directly to caller */
325
326         /* replace any emitted tokens with error token */
327         gssEapReleaseInnerTokens(&tmpMinor, &outputTokens, 1);
328
329         tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &outputTokens);
330         if (GSS_ERROR(tmpMajor)) {
331             major = tmpMajor;
332             *minor = tmpMinor;
333             goto cleanup;
334         }
335     }
336
337     /* Format output token from inner tokens */
338     if (outputTokens.buffers.count != 0 ||            /* inner tokens to send */
339         !CTX_IS_INITIATOR(ctx) ||                   /* any leg acceptor */
340         !CTX_IS_ESTABLISHED(ctx)) {                 /* non-last leg initiator */
341         tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, &outputTokens, &unwrappedOutputToken);
342         if (tmpMajor == GSS_S_COMPLETE) {
343             if (CTX_IS_INITIATOR(ctx))
344                 tokType = TOK_TYPE_INITIATOR_CONTEXT;
345             else
346                 tokType = TOK_TYPE_ACCEPTOR_CONTEXT;
347
348             tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &unwrappedOutputToken,
349                                        tokType, outputToken);
350             if (GSS_ERROR(tmpMajor)) {
351                 major = tmpMajor;
352                 *minor = tmpMinor;
353                 goto cleanup;
354             }
355         }
356     }
357
358     /* If the context is established, empty tokens only to be emitted by initiator */
359     GSSEAP_ASSERT(!CTX_IS_ESTABLISHED(ctx) || ((outputToken->length == 0) == CTX_IS_INITIATOR(ctx)));
360
361     SM_ASSERT_VALID(ctx, major);
362
363 cleanup:
364     gssEapReleaseInnerTokens(&tmpMinor, &inputTokens, 0);
365     gssEapReleaseInnerTokens(&tmpMinor, &inputTokens, 1);
366
367     gss_release_buffer(&tmpMinor, &unwrappedOutputToken);
368
369     ctx->inputTokens = NULL;
370     ctx->outputTokens = NULL;
371
372     return major;
373 }