fae652888b47304244eef36a9a80bea8d441d6bb
[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 static const char *
40 gssEapStateToString(enum gss_eap_state state)
41 {
42     const char *s;
43
44     switch (state) {
45     case GSSEAP_STATE_INITIAL:
46         s = "INITIAL";
47         break;
48     case GSSEAP_STATE_AUTHENTICATE:
49         s = "AUTHENTICATE";
50         break;
51     case GSSEAP_STATE_INITIATOR_EXTS:
52         s = "INITIATOR_EXTS";
53         break;
54     case GSSEAP_STATE_ACCEPTOR_EXTS:
55         s = "ACCEPTOR_EXTS";
56         break;
57     case GSSEAP_STATE_REAUTHENTICATE:
58         s = "REAUTHENTICATE";
59         break;
60     case GSSEAP_STATE_ESTABLISHED:
61         s = "ESTABLISHED";
62         break;
63     default:
64         s = "INVALID";
65         break;
66     }
67
68     return s;
69 }
70
71 void
72 gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state)
73 {
74     assert(state > ctx->state);
75     assert(state <= GSSEAP_STATE_ESTABLISHED);
76
77 #ifdef GSSEAP_DEBUG
78     fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
79             gssEapStateToString(ctx->state), gssEapStateToString(state));
80 #endif
81
82     ctx->state = state;
83 }
84
85 static OM_uint32
86 makeErrorToken(OM_uint32 *minor,
87                OM_uint32 majorStatus,
88                OM_uint32 minorStatus,
89                gss_buffer_set_t *outputToken)
90 {
91     unsigned char errorData[8];
92     gss_buffer_desc errorBuffer;
93
94     assert(GSS_ERROR(majorStatus));
95
96     /*
97      * Only return error codes that the initiator could have caused,
98      * to avoid information leakage.
99      */
100     if (IS_RADIUS_ERROR(minorStatus)) {
101         /* Squash RADIUS error codes */
102         minorStatus = GSSEAP_RADIUS_PROT_FAILURE;
103     } else if (!IS_WIRE_ERROR(minorStatus)) {
104         /* Don't return non-wire error codes */
105         return GSS_S_COMPLETE;
106     }
107
108     minorStatus -= ERROR_TABLE_BASE_eapg;
109
110     store_uint32_be(majorStatus, &errorData[0]);
111     store_uint32_be(minorStatus, &errorData[4]);
112
113     errorBuffer.length = sizeof(errorData);
114     errorBuffer.value = errorData;
115
116     return gss_add_buffer_set_member(minor, &errorBuffer, outputToken);
117 }
118
119 OM_uint32
120 gssEapSmStep(OM_uint32 *minor,
121              gss_cred_id_t cred,
122              gss_ctx_id_t ctx,
123              gss_name_t target,
124              gss_OID mech,
125              OM_uint32 reqFlags,
126              OM_uint32 timeReq,
127              gss_channel_bindings_t chanBindings,
128              gss_buffer_t inputToken,
129              gss_buffer_t outputToken,
130              struct gss_eap_sm *sm,
131              size_t smCount)
132 {
133     OM_uint32 major, tmpMajor, tmpMinor;
134     gss_buffer_desc unwrappedInputToken = GSS_C_EMPTY_BUFFER;
135     gss_buffer_desc unwrappedOutputToken = GSS_C_EMPTY_BUFFER;
136     gss_buffer_set_t innerInputTokens = GSS_C_NO_BUFFER_SET;
137     gss_buffer_set_t innerOutputTokens = GSS_C_NO_BUFFER_SET;
138     OM_uint32 *inputTokenTypes = NULL, *outputTokenTypes = NULL;
139     unsigned int smFlags = 0;
140     size_t i, j;
141     int initialContextToken = 0;
142
143     assert(smCount > 0);
144
145     *minor = 0;
146
147     outputToken->length = 0;
148     outputToken->value = NULL;
149
150     if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) {
151         enum gss_eap_token_type tokType;
152
153         major = gssEapVerifyToken(minor, ctx, inputToken, &tokType,
154                                   &unwrappedInputToken);
155         if (GSS_ERROR(major))
156             goto cleanup;
157
158         if (tokType != TOK_TYPE_ESTABLISH_CONTEXT) {
159             major = GSS_S_DEFECTIVE_TOKEN;
160             *minor = GSSEAP_WRONG_TOK_ID;
161             goto cleanup;
162         }
163     } else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) {
164         major = GSS_S_DEFECTIVE_TOKEN;
165         *minor = GSSEAP_WRONG_SIZE;
166         goto cleanup;
167     } else {
168         initialContextToken = 1;
169     }
170
171     if (ctx->state == GSSEAP_STATE_ESTABLISHED) {
172         major = GSS_S_BAD_STATUS;
173         *minor = GSSEAP_CONTEXT_ESTABLISHED;
174         goto cleanup;
175     }
176
177     assert(ctx->state < GSSEAP_STATE_ESTABLISHED);
178
179     major = gssEapDecodeInnerTokens(minor, &unwrappedInputToken,
180                                     &innerInputTokens, &inputTokenTypes);
181     if (GSS_ERROR(major))
182         goto cleanup;
183
184     assert(innerInputTokens != GSS_C_NO_BUFFER_SET);
185
186     major = gss_create_empty_buffer_set(minor, &innerOutputTokens);
187     if (GSS_ERROR(major))
188         goto cleanup;
189
190     assert(innerOutputTokens->count == 0);
191     assert(innerOutputTokens->elements == NULL);
192
193     innerOutputTokens->elements = (gss_buffer_desc *)GSSEAP_CALLOC(smCount,
194                                                                    sizeof(gss_buffer_desc));
195     if (innerOutputTokens->elements == NULL) {
196         major = GSS_S_FAILURE;
197         *minor = ENOMEM;
198         goto cleanup;
199     }
200
201     outputTokenTypes = (OM_uint32 *)GSSEAP_CALLOC(smCount, sizeof(OM_uint32));
202     if (outputTokenTypes == NULL) {
203         major = GSS_S_FAILURE;
204         *minor = ENOMEM;
205         goto cleanup;
206     }
207
208     /*
209      * Process all the tokens that are valid for the current state. If
210      * the processToken function returns GSS_S_COMPLETE, the state is
211      * advanced until there is a token to send or the ESTABLISHED state
212      * is reached.
213      */
214     do {
215         major = GSS_S_COMPLETE;
216
217         for (i = 0; i < smCount; i++) {
218             struct gss_eap_sm *smp = &sm[i];
219             int processToken = 0;
220             gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
221             OM_uint32 *inputTokenType = NULL;
222             gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
223
224             if ((smp->validStates & ctx->state) == 0)
225                 continue;
226
227             if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
228                 processToken = 1;
229             } else if ((smFlags & SM_FLAG_TRANSITION) == 0) {
230                 for (j = 0; j < innerInputTokens->count; j++) {
231                     if ((inputTokenTypes[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
232                         processToken = 1;
233                         if (innerInputToken != GSS_C_NO_BUFFER) {
234                             major = GSS_S_DEFECTIVE_TOKEN;
235                             *minor = GSSEAP_DUPLICATE_ITOK;
236                             break;
237                         }
238                     }
239                     innerInputToken = &innerInputTokens->elements[j];
240                     inputTokenType = &inputTokenTypes[j];
241                 }
242             }
243
244 #ifdef GSSEAP_DEBUG
245             fprintf(stderr, "GSS-EAP: state %d processToken %d inputTokenType %08x "
246                     "innerInputToken %p innerOutputTokensCount %zd\n",
247                     ctx->state, processToken, smp->inputTokenType,
248                     innerInputToken, innerOutputTokens->count);
249 #endif
250
251             if (processToken) {
252                 smFlags = 0;
253
254                 major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
255                                          timeReq, chanBindings, innerInputToken,
256                                          &innerOutputToken, &smFlags);
257                 if (GSS_ERROR(major))
258                     break;
259
260                 if (inputTokenType != NULL)
261                     *inputTokenType |= ITOK_FLAG_VERIFIED;
262
263                 if (innerOutputToken.value != NULL) {
264                     innerOutputTokens->elements[innerOutputTokens->count] = innerOutputToken;
265                     assert(smp->outputTokenType != ITOK_TYPE_NONE);
266                     outputTokenTypes[innerOutputTokens->count] = smp->outputTokenType;
267                     if (smp->itokFlags & SM_ITOK_FLAG_CRITICAL)
268                         outputTokenTypes[innerOutputTokens->count] |= ITOK_FLAG_CRITICAL;
269                     innerOutputTokens->count++;
270                 }
271                 if (smFlags & SM_FLAG_STOP_EVAL)
272                     break;
273             } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
274                 smp->inputTokenType != ITOK_TYPE_NONE) {
275                 major = GSS_S_DEFECTIVE_TOKEN;
276                 *minor = GSSEAP_MISSING_REQUIRED_ITOK;
277                 break;
278             }
279         }
280
281         if (GSS_ERROR(major) || (smFlags & SM_FLAG_TRANSITION) == 0)
282             break;
283
284         gssEapSmTransition(ctx, GSSEAP_STATE_NEXT(ctx->state));
285
286         if (innerOutputTokens->count != 0 || (smFlags & SM_FLAG_FORCE_SEND_TOKEN)) {
287             assert(major == GSS_S_CONTINUE_NEEDED || ctx->state == GSSEAP_STATE_ESTABLISHED);
288             break; /* send any tokens if we have them */
289         }
290     } while (ctx->state != GSSEAP_STATE_ESTABLISHED);
291
292     assert(innerOutputTokens->count <= smCount);
293
294     /* Check we understood all critical tokens */
295     if (!GSS_ERROR(major)) {
296         for (j = 0; j < innerInputTokens->count; j++) {
297             if ((inputTokenTypes[j] & ITOK_FLAG_CRITICAL) &&
298                 (inputTokenTypes[j] & ITOK_FLAG_VERIFIED) == 0) {
299                 major = GSS_S_UNAVAILABLE;
300                 *minor = GSSEAP_CRIT_ITOK_UNAVAILABLE;
301                 goto cleanup;
302             }
303         }
304     }
305
306     /* Emit an error token if we are the acceptor */
307     if (GSS_ERROR(major)) {
308         if (CTX_IS_INITIATOR(ctx))
309             goto cleanup; /* return error directly to caller */
310
311         /* replace any emitted tokens with error token */
312         gss_release_buffer_set(&tmpMinor, &innerOutputTokens);
313
314         tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &innerOutputTokens);
315         if (GSS_ERROR(tmpMajor)) {
316             major = tmpMajor;
317             *minor = tmpMinor;
318             goto cleanup;
319         }
320
321         outputTokenTypes[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
322     }
323
324 #ifdef GSSEAP_DEBUG
325     for (i = 0; i < innerOutputTokens->count; i++) {
326         fprintf(stderr, "GSS-EAP: type %08x length %zd value %p\n",
327                 outputTokenTypes[i],
328                 innerOutputTokens->elements[i].length,
329                 innerOutputTokens->elements[i].value);
330     }
331 #endif
332
333     /* Format composite output token */
334     if (innerOutputTokens->count != 0 ||            /* inner tokens to send */
335         !CTX_IS_INITIATOR(ctx) ||                   /* any leg acceptor */
336         ctx->state != GSSEAP_STATE_ESTABLISHED) {   /* non-last leg initiator */
337         tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, innerOutputTokens,
338                                            outputTokenTypes, &unwrappedOutputToken);
339         if (tmpMajor == GSS_S_COMPLETE) {
340             tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &unwrappedOutputToken,
341                                        TOK_TYPE_ESTABLISH_CONTEXT, outputToken);
342             if (GSS_ERROR(tmpMajor)) {
343                 major = tmpMajor;
344                 *minor = tmpMinor;
345                 goto cleanup;
346             }
347         }
348     }
349
350     assert(GSS_ERROR(major) ||
351            (major == GSS_S_CONTINUE_NEEDED && (ctx->state > GSSEAP_STATE_INITIAL && ctx->state < GSSEAP_STATE_ESTABLISHED)) ||
352            (major == GSS_S_COMPLETE && ctx->state == GSSEAP_STATE_ESTABLISHED));
353
354 cleanup:
355     gss_release_buffer_set(&tmpMinor, &innerInputTokens);
356     gss_release_buffer_set(&tmpMinor, &innerOutputTokens);
357     if (inputTokenTypes != NULL)
358         GSSEAP_FREE(inputTokenTypes);
359     if (outputTokenTypes != NULL)
360     gss_release_buffer(&tmpMinor, &unwrappedOutputToken);
361         GSSEAP_FREE(outputTokenTypes);
362
363     return major;
364 }