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 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 assert(state >= GSSEAP_STATE_INITIAL);
87 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 recordErrorToken(OM_uint32 *minor,
100 OM_uint32 majorStatus,
101 OM_uint32 minorStatus)
103 unsigned char errorData[8];
104 gss_buffer_desc errorBuffer;
106 assert(GSS_ERROR(majorStatus));
109 * Only return error codes that the initiator could have caused,
110 * to avoid information leakage.
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;
120 minorStatus -= ERROR_TABLE_BASE_eapg;
122 store_uint32_be(majorStatus, &errorData[0]);
123 store_uint32_be(minorStatus, &errorData[4]);
125 errorBuffer.length = sizeof(errorData);
126 errorBuffer.value = errorData;
128 return gssEapRecordInnerContextToken(minor, ctx, &errorBuffer,
129 ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL);
133 makeContextToken(OM_uint32 *minor,
136 gss_buffer_t outputToken)
138 size_t tokSize, bodySize;
141 assert(ctx->conversation.length > headerOffset);
143 bodySize = ctx->conversation.length - headerOffset;
144 tokSize = tokenSize(bodySize);
146 outputToken->value = GSSEAP_MALLOC(tokSize);
147 if (outputToken->value == NULL) {
149 return GSS_S_FAILURE;
152 outputToken->length = tokSize;
154 p = (unsigned char *)outputToken->value;
156 makeTokenHeader(bodySize, &p);
157 memcpy(p, (unsigned char *)ctx->conversation.value + headerOffset, bodySize);
160 return GSS_S_COMPLETE;
164 gssEapSmStep(OM_uint32 *minor,
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 */
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;
184 int initialContextToken = 0;
185 enum gss_eap_token_type tokType;
186 size_t headerOffset, firstTokenOffset;
187 size_t innerOutputTokenCount = 0;
193 outputToken->length = 0;
194 outputToken->value = NULL;
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;
200 major = gssEapVerifyContextToken(minor, ctx, inputToken, tokType,
201 &unwrappedInputToken);
202 if (GSS_ERROR(major))
204 } else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) {
205 major = GSS_S_DEFECTIVE_TOKEN;
206 *minor = GSSEAP_WRONG_SIZE;
209 initialContextToken = 1;
212 if (CTX_IS_ESTABLISHED(ctx)) {
213 major = GSS_S_BAD_STATUS;
214 *minor = GSSEAP_CONTEXT_ESTABLISHED;
218 assert(ctx->state < GSSEAP_STATE_ESTABLISHED);
220 major = gssEapDecodeInnerTokens(minor, &unwrappedInputToken,
221 &innerInputTokens, &inputTokenTypes);
222 if (GSS_ERROR(major))
225 headerOffset = ctx->conversation.length;
227 assert(innerInputTokens != GSS_C_NO_BUFFER_SET);
229 /* Get ready to emit an output token */
230 tokType = CTX_IS_INITIATOR(ctx) ?
231 TOK_TYPE_INITIATOR_CONTEXT : TOK_TYPE_ACCEPTOR_CONTEXT;
233 major = gssEapRecordContextTokenHeader(minor, ctx, tokType);
234 if (GSS_ERROR(major))
237 firstTokenOffset = ctx->conversation.length;
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;
247 if ((smp->validStates & ctx->state) == 0)
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
260 if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
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) {
267 /* Check for duplicate inner tokens */
268 major = GSS_S_DEFECTIVE_TOKEN;
269 *minor = GSSEAP_DUPLICATE_ITOK;
273 innerInputToken = &innerInputTokens->elements[j];
274 inputTokenType = &inputTokenTypes[j];
277 if (GSS_ERROR(major))
282 enum gss_eap_state oldState = ctx->state;
285 if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL))
286 smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL;
288 major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
289 timeReq, chanBindings, innerInputToken,
290 &innerOutputToken, &smFlags);
291 if (GSS_ERROR(major))
294 if (inputTokenType != NULL)
295 *inputTokenType |= ITOK_FLAG_VERIFIED;
296 if (ctx->state < oldState)
298 else if (ctx->state != oldState)
299 smFlags |= SM_FLAG_TRANSITED;
301 if (innerOutputToken.value != NULL) {
302 OM_uint32 outputTokenType = smp->outputTokenType;
304 if (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL)
305 outputTokenType |= ITOK_FLAG_CRITICAL;
307 assert(smp->outputTokenType != ITOK_TYPE_NONE);
309 tmpMajor = gssEapRecordInnerContextToken(&tmpMinor, ctx,
312 if (GSS_ERROR(tmpMajor)) {
318 innerOutputTokenCount++;
322 * Break out if we made a state transition and have some tokens to send.
324 if (smFlags & SM_FLAG_SEND_TOKEN) {
325 SM_ASSERT_VALID(ctx, major);
328 } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
329 smp->inputTokenType != ITOK_TYPE_NONE) {
330 /* Check for required inner tokens */
332 fprintf(stderr, "GSS-EAP: missing required token %08X\n",
333 smp->inputTokenType);
335 major = GSS_S_DEFECTIVE_TOKEN;
336 *minor = GSSEAP_MISSING_REQUIRED_ITOK;
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;
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 */
358 /* replace any emitted tokens with error token */
359 ctx->conversation.length = firstTokenOffset;
361 tmpMajor = recordErrorToken(&tmpMinor, ctx, major, *minor);
362 if (GSS_ERROR(tmpMajor)) {
368 innerOutputTokenCount = 1;
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)) {
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)));
386 SM_ASSERT_VALID(ctx, major);
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);