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