2 * Copyright (c) 2011, JANET(UK)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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
34 * Context establishment state machine.
37 #include "gssapiP_eap.h"
40 #define SM_FLAG_TRANSITED 0x80000000
42 #define SM_ASSERT_VALID(ctx, status) do { \
43 GSSEAP_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)); \
50 gssEapStateToString(enum gss_eap_state state)
55 case GSSEAP_STATE_INITIAL:
58 case GSSEAP_STATE_AUTHENTICATE:
61 case GSSEAP_STATE_INITIATOR_EXTS:
64 case GSSEAP_STATE_ACCEPTOR_EXTS:
67 #ifdef GSSEAP_ENABLE_REAUTH
68 case GSSEAP_STATE_REAUTHENTICATE:
72 case GSSEAP_STATE_ESTABLISHED:
84 gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state)
86 GSSEAP_ASSERT(state >= GSSEAP_STATE_INITIAL);
87 GSSEAP_ASSERT(state <= GSSEAP_STATE_ESTABLISHED);
89 fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
90 gssEapStateToString(GSSEAP_SM_STATE(ctx)),
91 gssEapStateToString(state));
95 #endif /* GSSEAP_DEBUG */
98 makeErrorToken(OM_uint32 *minor,
99 OM_uint32 majorStatus,
100 OM_uint32 minorStatus,
101 struct gss_eap_token_buffer_set *token)
103 OM_uint32 major, tmpMinor;
104 unsigned char errorData[8];
105 gss_buffer_desc errorBuffer;
107 GSSEAP_ASSERT(GSS_ERROR(majorStatus));
110 * Only return error codes that the initiator could have caused,
111 * to avoid information leakage.
113 if (IS_RADIUS_ERROR(minorStatus)) {
114 /* Squash RADIUS error codes */
115 minorStatus = GSSEAP_RADIUS_PROT_FAILURE;
116 } else if (!IS_WIRE_ERROR(minorStatus)) {
117 /* Don't return non-wire error codes */
121 if (minorStatus != 0)
122 minorStatus -= ERROR_TABLE_BASE_eapg;
124 store_uint32_be(majorStatus, &errorData[0]);
125 store_uint32_be(minorStatus, &errorData[4]);
127 major = gssEapAllocInnerTokens(&tmpMinor, 1, token);
128 if (GSS_ERROR(major)) {
133 errorBuffer.length = sizeof(errorData);
134 errorBuffer.value = errorData;
136 major = duplicateBuffer(&tmpMinor, &errorBuffer, &token->buffers.elements[0]);
137 if (GSS_ERROR(major)) {
138 gssEapReleaseInnerTokens(&tmpMinor, token, 1);
143 token->buffers.count = 1;
144 token->types[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
147 return GSS_S_COMPLETE;
151 gssEapSmStep(OM_uint32 *minor,
154 gss_const_name_t target,
158 gss_channel_bindings_t chanBindings,
159 gss_buffer_t inputToken,
160 gss_buffer_t outputToken,
161 struct gss_eap_sm *sm, /* ordered by state */
164 OM_uint32 major, tmpMajor, tmpMinor;
165 struct gss_eap_token_buffer_set inputTokens = { { 0, GSS_C_NO_BUFFER }, NULL };
166 struct gss_eap_token_buffer_set outputTokens = { { 0, GSS_C_NO_BUFFER }, NULL };
167 gss_buffer_desc unwrappedInputToken = GSS_C_EMPTY_BUFFER;
168 gss_buffer_desc unwrappedOutputToken = GSS_C_EMPTY_BUFFER;
169 unsigned int smFlags = 0;
171 int initialContextToken = 0;
172 enum gss_eap_token_type tokType;
174 GSSEAP_ASSERT(smCount > 0);
178 outputToken->length = 0;
179 outputToken->value = NULL;
181 if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) {
182 major = gssEapVerifyToken(minor, ctx, inputToken, &tokType,
183 &unwrappedInputToken);
184 if (GSS_ERROR(major))
187 if (tokType != (CTX_IS_INITIATOR(ctx)
188 ? TOK_TYPE_ACCEPTOR_CONTEXT : TOK_TYPE_INITIATOR_CONTEXT)) {
189 major = GSS_S_DEFECTIVE_TOKEN;
190 *minor = GSSEAP_WRONG_TOK_ID;
193 } else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) {
194 major = GSS_S_DEFECTIVE_TOKEN;
195 *minor = GSSEAP_WRONG_SIZE;
198 initialContextToken = 1;
201 if (CTX_IS_ESTABLISHED(ctx)) {
202 major = GSS_S_BAD_STATUS;
203 *minor = GSSEAP_CONTEXT_ESTABLISHED;
207 GSSEAP_ASSERT(ctx->state < GSSEAP_STATE_ESTABLISHED);
209 major = gssEapDecodeInnerTokens(minor, &unwrappedInputToken, &inputTokens);
210 if (GSS_ERROR(major))
213 major = gssEapAllocInnerTokens(minor, smCount, &outputTokens);
214 if (GSS_ERROR(major))
217 ctx->inputTokens = &inputTokens;
218 ctx->outputTokens = &outputTokens;
220 /* Process all the tokens that are valid for the current state. */
221 for (i = 0; i < smCount; i++) {
222 struct gss_eap_sm *smp = &sm[i];
223 int processToken = 0;
224 gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
225 OM_uint32 *inputTokenType = NULL;
226 gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
228 if ((smp->validStates & ctx->state) == 0)
232 * We special case the first call to gss_init_sec_context so that
233 * all token providers have the opportunity to generate an initial
234 * context token. Providers where inputTokenType is ITOK_TYPE_NONE
235 * are always called and generally act on state transition boundaries,
236 * for example to advance the state after a series of optional tokens
237 * (as is the case with the extension token exchange) or to generate
238 * a new token after the state was advanced by a provider which did
241 if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
243 } else if ((smFlags & SM_FLAG_TRANSITED) == 0) {
244 /* Don't regurgitate a token which belonds to a previous state. */
245 for (j = 0; j < inputTokens.buffers.count; j++) {
246 if ((inputTokens.types[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
248 /* Check for duplicate inner tokens */
249 major = GSS_S_DEFECTIVE_TOKEN;
250 *minor = GSSEAP_DUPLICATE_ITOK;
254 innerInputToken = &inputTokens.buffers.elements[j];
255 inputTokenType = &inputTokens.types[j];
258 if (GSS_ERROR(major))
263 enum gss_eap_state oldState = ctx->state;
266 if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL))
267 smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL;
269 major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
270 timeReq, chanBindings, innerInputToken,
271 &innerOutputToken, &smFlags);
272 if (GSS_ERROR(major))
275 if (inputTokenType != NULL)
276 *inputTokenType |= ITOK_FLAG_VERIFIED;
277 if (ctx->state < oldState)
279 else if (ctx->state != oldState)
280 smFlags |= SM_FLAG_TRANSITED;
282 if (innerOutputToken.value != NULL) {
283 outputTokens.buffers.elements[outputTokens.buffers.count] = innerOutputToken;
284 GSSEAP_ASSERT(smp->outputTokenType != ITOK_TYPE_NONE);
285 outputTokens.types[outputTokens.buffers.count] = smp->outputTokenType;
286 if (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL)
287 outputTokens.types[outputTokens.buffers.count] |= ITOK_FLAG_CRITICAL;
288 outputTokens.buffers.count++;
291 * Break out if we made a state transition and have some tokens to send.
293 if ((smFlags & SM_FLAG_TRANSITED) &&
294 ((smFlags & SM_FLAG_FORCE_SEND_TOKEN) || outputTokens.buffers.count != 0)) {
295 SM_ASSERT_VALID(ctx, major);
298 } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
299 smp->inputTokenType != ITOK_TYPE_NONE) {
300 /* Check for required inner tokens */
301 major = GSS_S_DEFECTIVE_TOKEN;
302 *minor = GSSEAP_MISSING_REQUIRED_ITOK;
307 GSSEAP_ASSERT(outputTokens.buffers.count <= smCount);
309 /* Check we understood all critical tokens sent by peer */
310 if (!GSS_ERROR(major)) {
311 for (j = 0; j < inputTokens.buffers.count; j++) {
312 if ((inputTokens.types[j] & ITOK_FLAG_CRITICAL) &&
313 (inputTokens.types[j] & ITOK_FLAG_VERIFIED) == 0) {
314 major = GSS_S_UNAVAILABLE;
315 *minor = GSSEAP_CRIT_ITOK_UNAVAILABLE;
321 /* Optionaly emit an error token if we are the acceptor */
322 if (GSS_ERROR(major)) {
323 if (CTX_IS_INITIATOR(ctx))
324 goto cleanup; /* return error directly to caller */
326 /* replace any emitted tokens with error token */
327 gssEapReleaseInnerTokens(&tmpMinor, &outputTokens, 1);
329 tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &outputTokens);
330 if (GSS_ERROR(tmpMajor)) {
337 /* Format output token from inner tokens */
338 if (outputTokens.buffers.count != 0 || /* inner tokens to send */
339 !CTX_IS_INITIATOR(ctx) || /* any leg acceptor */
340 !CTX_IS_ESTABLISHED(ctx)) { /* non-last leg initiator */
341 tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, &outputTokens, &unwrappedOutputToken);
342 if (tmpMajor == GSS_S_COMPLETE) {
343 if (CTX_IS_INITIATOR(ctx))
344 tokType = TOK_TYPE_INITIATOR_CONTEXT;
346 tokType = TOK_TYPE_ACCEPTOR_CONTEXT;
348 tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &unwrappedOutputToken,
349 tokType, outputToken);
350 if (GSS_ERROR(tmpMajor)) {
358 /* If the context is established, empty tokens only to be emitted by initiator */
359 GSSEAP_ASSERT(!CTX_IS_ESTABLISHED(ctx) || ((outputToken->length == 0) == CTX_IS_INITIATOR(ctx)));
361 SM_ASSERT_VALID(ctx, major);
364 gssEapReleaseInnerTokens(&tmpMinor, &inputTokens, 0);
365 gssEapReleaseInnerTokens(&tmpMinor, &inputTokens, 1);
367 gss_release_buffer(&tmpMinor, &unwrappedOutputToken);
369 ctx->inputTokens = NULL;
370 ctx->outputTokens = NULL;