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