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