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