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