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