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