#include "gssapiP_eap.h"
+/* private flags */
+#define SM_FLAG_TRANSITED 0x80000000
+
+#define SM_ASSERT_VALID(ctx, status) do { \
+ assert(GSS_ERROR((status)) || \
+ ((status) == GSS_S_CONTINUE_NEEDED && ((ctx)->state > GSSEAP_STATE_INITIAL && (ctx)->state < GSSEAP_STATE_ESTABLISHED)) || \
+ ((status) == GSS_S_COMPLETE && (ctx)->state == GSSEAP_STATE_ESTABLISHED)); \
+ } while (0)
+
+#ifdef GSSEAP_DEBUG
static const char *
gssEapStateToString(enum gss_eap_state state)
{
case GSSEAP_STATE_ACCEPTOR_EXTS:
s = "ACCEPTOR_EXTS";
break;
+#ifdef GSSEAP_ENABLE_REAUTH
+ case GSSEAP_STATE_REAUTHENTICATE:
+ s = "REAUTHENTICATE";
+ break;
+#endif
case GSSEAP_STATE_ESTABLISHED:
s = "ESTABLISHED";
break;
return s;
}
+void
+gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state)
+{
+ assert(state >= GSSEAP_STATE_INITIAL);
+ assert(state <= GSSEAP_STATE_ESTABLISHED);
+
+ fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
+ gssEapStateToString(GSSEAP_SM_STATE(ctx)),
+ gssEapStateToString(state));
+
+ ctx->state = state;
+}
+#endif /* GSSEAP_DEBUG */
+
static OM_uint32
-makeErrorToken(OM_uint32 *minor,
- OM_uint32 majorStatus,
- OM_uint32 minorStatus,
- gss_buffer_set_t *outputToken)
+recordErrorToken(OM_uint32 *minor,
+ gss_ctx_id_t ctx,
+ OM_uint32 majorStatus,
+ OM_uint32 minorStatus)
{
unsigned char errorData[8];
gss_buffer_desc errorBuffer;
errorBuffer.length = sizeof(errorData);
errorBuffer.value = errorData;
- return gss_add_buffer_set_member(minor, &errorBuffer, outputToken);
+ return gssEapRecordInnerContextToken(minor, ctx, &errorBuffer,
+ ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL);
+}
+
+static OM_uint32
+makeContextToken(OM_uint32 *minor,
+ gss_ctx_id_t ctx,
+ size_t headerOffset,
+ gss_buffer_t outputToken)
+{
+ size_t tokSize, bodySize;
+ unsigned char *p;
+
+ assert(ctx->conversation.length > headerOffset);
+
+ bodySize = ctx->conversation.length - headerOffset;
+ tokSize = tokenSize(bodySize);
+
+ outputToken->value = GSSEAP_MALLOC(tokSize);
+ if (outputToken->value == NULL) {
+ *minor = ENOMEM;
+ return GSS_S_FAILURE;
+ }
+
+ outputToken->length = tokSize;
+
+ p = (unsigned char *)outputToken->value;
+
+ makeTokenHeader(bodySize, &p);
+ memcpy(p, (unsigned char *)ctx->conversation.value + headerOffset, bodySize);
+
+ *minor = 0;
+ return GSS_S_COMPLETE;
}
OM_uint32
gss_channel_bindings_t chanBindings,
gss_buffer_t inputToken,
gss_buffer_t outputToken,
- struct gss_eap_sm *sm,
+ struct gss_eap_sm *sm, /* ordered by state */
size_t smCount)
{
OM_uint32 major, tmpMajor, tmpMinor;
gss_buffer_desc unwrappedInputToken = GSS_C_EMPTY_BUFFER;
gss_buffer_desc unwrappedOutputToken = GSS_C_EMPTY_BUFFER;
gss_buffer_set_t innerInputTokens = GSS_C_NO_BUFFER_SET;
- gss_buffer_set_t innerOutputTokens = GSS_C_NO_BUFFER_SET;
OM_uint32 *inputTokenTypes = NULL, *outputTokenTypes = NULL;
unsigned int smFlags = 0;
size_t i, j;
int initialContextToken = 0;
+ enum gss_eap_token_type tokType;
+ size_t headerOffset, firstTokenOffset;
+ size_t innerOutputTokenCount = 0;
assert(smCount > 0);
outputToken->value = NULL;
if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) {
- enum gss_eap_token_type tokType;
+ tokType = CTX_IS_INITIATOR(ctx) ?
+ TOK_TYPE_ACCEPTOR_CONTEXT : TOK_TYPE_INITIATOR_CONTEXT;
- major = gssEapVerifyToken(minor, ctx, inputToken, &tokType,
- &unwrappedInputToken);
+ major = gssEapVerifyContextToken(minor, ctx, inputToken, tokType,
+ &unwrappedInputToken);
if (GSS_ERROR(major))
goto cleanup;
-
- if (tokType != TOK_TYPE_ESTABLISH_CONTEXT) {
- major = GSS_S_DEFECTIVE_TOKEN;
- *minor = GSSEAP_WRONG_TOK_ID;
- goto cleanup;
- }
} else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) {
major = GSS_S_DEFECTIVE_TOKEN;
*minor = GSSEAP_WRONG_SIZE;
initialContextToken = 1;
}
- if (ctx->state == GSSEAP_STATE_ESTABLISHED) {
+ if (CTX_IS_ESTABLISHED(ctx)) {
major = GSS_S_BAD_STATUS;
*minor = GSSEAP_CONTEXT_ESTABLISHED;
goto cleanup;
if (GSS_ERROR(major))
goto cleanup;
- assert(innerInputTokens != GSS_C_NO_BUFFER_SET);
+ headerOffset = ctx->conversation.length;
- major = gss_create_empty_buffer_set(minor, &innerOutputTokens);
- if (GSS_ERROR(major))
- goto cleanup;
+ assert(innerInputTokens != GSS_C_NO_BUFFER_SET);
- assert(innerOutputTokens->count == 0);
- assert(innerOutputTokens->elements == NULL);
+ /* Get ready to emit an output token */
+ tokType = CTX_IS_INITIATOR(ctx) ?
+ TOK_TYPE_INITIATOR_CONTEXT : TOK_TYPE_ACCEPTOR_CONTEXT;
- innerOutputTokens->elements = (gss_buffer_desc *)GSSEAP_CALLOC(smCount,
- sizeof(gss_buffer_desc));
- if (innerOutputTokens->elements == NULL) {
- major = GSS_S_FAILURE;
- *minor = ENOMEM;
- goto cleanup;
- }
-
- outputTokenTypes = (OM_uint32 *)GSSEAP_CALLOC(smCount, sizeof(OM_uint32));
- if (outputTokenTypes == NULL) {
- major = GSS_S_FAILURE;
- *minor = ENOMEM;
+ major = gssEapRecordContextTokenHeader(minor, ctx, tokType);
+ if (GSS_ERROR(major))
goto cleanup;
- }
- /*
- * Process all the tokens that are valid for the current state. If
- * the processToken function returns GSS_S_COMPLETE, the state is
- * advanced until there is a token to send or the ESTABLISHED state
- * is reached.
- */
- do {
- major = GSS_S_COMPLETE;
-
- for (i = 0; i < smCount; i++) {
- struct gss_eap_sm *smp = &sm[i];
- int processToken = 0;
- gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
- OM_uint32 *inputTokenType = NULL;
- gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
-
- if ((smp->validStates & ctx->state) == 0)
- continue;
-
- if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
- processToken = 1;
- } else if ((smFlags & SM_FLAG_TRANSITION) == 0) {
- for (j = 0; j < innerInputTokens->count; j++) {
- if ((inputTokenTypes[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
- processToken = 1;
- if (innerInputToken != GSS_C_NO_BUFFER) {
- major = GSS_S_DEFECTIVE_TOKEN;
- *minor = GSSEAP_DUPLICATE_ITOK;
- break;
- }
+ firstTokenOffset = ctx->conversation.length;
+
+ /* Process all the tokens that are valid for the current state. */
+ for (i = 0; i < smCount; i++) {
+ struct gss_eap_sm *smp = &sm[i];
+ int processToken = 0;
+ gss_buffer_t innerInputToken = GSS_C_NO_BUFFER;
+ OM_uint32 *inputTokenType = NULL;
+ gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER;
+
+ if ((smp->validStates & ctx->state) == 0)
+ continue;
+
+ /*
+ * We special case the first call to gss_init_sec_context so that
+ * all token providers have the opportunity to generate an initial
+ * context token. Providers where inputTokenType is ITOK_TYPE_NONE
+ * are always called and generally act on state transition boundaries,
+ * for example to advance the state after a series of optional tokens
+ * (as is the case with the extension token exchange) or to generate
+ * a new token after the state was advanced by a provider which did
+ * not emit a token.
+ */
+ if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) {
+ processToken = 1;
+ } else if ((smFlags & SM_FLAG_TRANSITED) == 0) {
+ /* Don't regurgitate a token which belonds to a previous state. */
+ for (j = 0; j < innerInputTokens->count; j++) {
+ if ((inputTokenTypes[j] & ITOK_TYPE_MASK) == smp->inputTokenType) {
+ if (processToken) {
+ /* Check for duplicate inner tokens */
+ major = GSS_S_DEFECTIVE_TOKEN;
+ *minor = GSSEAP_DUPLICATE_ITOK;
+ break;
}
+ processToken = 1;
innerInputToken = &innerInputTokens->elements[j];
inputTokenType = &inputTokenTypes[j];
}
}
+ if (GSS_ERROR(major))
+ break;
+ }
-#ifdef GSSEAP_DEBUG
- fprintf(stderr, "GSS-EAP: state %d processToken %d inputTokenType %08x "
- "innerInputToken %p innerOutputTokensCount %zd\n",
- ctx->state, processToken, smp->inputTokenType,
- innerInputToken, innerOutputTokens->count);
-#endif
+ if (processToken) {
+ enum gss_eap_state oldState = ctx->state;
- if (processToken) {
- smFlags = 0;
+ smFlags = 0;
+ if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL))
+ smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL;
- major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
- timeReq, chanBindings, innerInputToken,
- &innerOutputToken, &smFlags);
- if (GSS_ERROR(major))
- break;
+ major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
+ timeReq, chanBindings, innerInputToken,
+ &innerOutputToken, &smFlags);
+ if (GSS_ERROR(major))
+ break;
- if (inputTokenType != NULL)
- *inputTokenType |= ITOK_FLAG_VERIFIED;
+ if (inputTokenType != NULL)
+ *inputTokenType |= ITOK_FLAG_VERIFIED;
+ if (ctx->state < oldState)
+ i = 0; /* restart */
+ else if (ctx->state != oldState)
+ smFlags |= SM_FLAG_TRANSITED;
- if (innerOutputToken.value != NULL) {
- innerOutputTokens->elements[innerOutputTokens->count] = innerOutputToken;
- assert(smp->outputTokenType != ITOK_TYPE_NONE);
- outputTokenTypes[innerOutputTokens->count] = smp->outputTokenType;
- if (smp->critical)
- outputTokenTypes[innerOutputTokens->count] |= ITOK_FLAG_CRITICAL;
- innerOutputTokens->count++;
- }
- if (smFlags & SM_FLAG_STOP_EVAL)
- break;
- } else if (smp->required && smp->inputTokenType != ITOK_TYPE_NONE) {
- major = GSS_S_DEFECTIVE_TOKEN;
- *minor = GSSEAP_MISSING_REQUIRED_ITOK;
- break;
- }
- }
+ if (innerOutputToken.value != NULL) {
+ OM_uint32 outputTokenType = smp->outputTokenType;
- if (GSS_ERROR(major) || (smFlags & SM_FLAG_TRANSITION) == 0)
- break;
+ if (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL)
+ outputTokenType |= ITOK_FLAG_CRITICAL;
- assert(ctx->state < GSSEAP_STATE_ESTABLISHED);
+ assert(smp->outputTokenType != ITOK_TYPE_NONE);
-#ifdef GSSEAP_DEBUG
- fprintf(stderr, "GSS-EAP: state transition %s->%s\n",
- gssEapStateToString(ctx->state),
- gssEapStateToString(GSSEAP_STATE_NEXT(ctx->state)));
-#endif
+ tmpMajor = gssEapRecordInnerContextToken(&tmpMinor, ctx,
+ &innerOutputToken,
+ outputTokenType);
+ if (GSS_ERROR(tmpMajor)) {
+ major = tmpMajor;
+ *minor = tmpMinor;
+ break;
+ }
- ctx->state = GSSEAP_STATE_NEXT(ctx->state);
+ innerOutputTokenCount++;
+ }
- if (innerOutputTokens->count != 0 || (smFlags & SM_FLAG_FORCE_SEND_TOKEN)) {
- assert(major == GSS_S_CONTINUE_NEEDED || ctx->state == GSSEAP_STATE_ESTABLISHED);
- break; /* send any tokens if we have them */
+ /*
+ * Break out if we made a state transition and have some tokens to send.
+ */
+ if (smFlags & SM_FLAG_SEND_TOKEN) {
+ SM_ASSERT_VALID(ctx, major);
+ break;
+ }
+ } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
+ smp->inputTokenType != ITOK_TYPE_NONE) {
+ /* Check for required inner tokens */
+#ifdef GSSEAP_DEBUG
+ fprintf(stderr, "GSS-EAP: missing required token %08X\n",
+ smp->inputTokenType);
+#endif
+ major = GSS_S_DEFECTIVE_TOKEN;
+ *minor = GSSEAP_MISSING_REQUIRED_ITOK;
+ break;
}
- } while (ctx->state != GSSEAP_STATE_ESTABLISHED);
-
- assert(innerOutputTokens->count <= smCount);
+ }
- /* Check we understood all critical tokens */
+ /* Check we understood all critical tokens sent by peer */
if (!GSS_ERROR(major)) {
for (j = 0; j < innerInputTokens->count; j++) {
if ((inputTokenTypes[j] & ITOK_FLAG_CRITICAL) &&
}
}
- /* Emit an error token if we are the acceptor */
+ /* Optionaly emit an error token if we are the acceptor */
if (GSS_ERROR(major)) {
if (CTX_IS_INITIATOR(ctx))
goto cleanup; /* return error directly to caller */
/* replace any emitted tokens with error token */
- gss_release_buffer_set(&tmpMinor, &innerOutputTokens);
+ ctx->conversation.length = firstTokenOffset;
- tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &innerOutputTokens);
+ tmpMajor = recordErrorToken(&tmpMinor, ctx, major, *minor);
if (GSS_ERROR(tmpMajor)) {
major = tmpMajor;
*minor = tmpMinor;
goto cleanup;
}
- outputTokenTypes[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
+ innerOutputTokenCount = 1;
}
-#ifdef GSSEAP_DEBUG
- for (i = 0; i < innerOutputTokens->count; i++) {
- fprintf(stderr, "GSS-EAP: type %08x length %zd value %p\n",
- outputTokenTypes[i],
- innerOutputTokens->elements[i].length,
- innerOutputTokens->elements[i].value);
- }
-#endif
-
- /* Format composite output token */
- if (innerOutputTokens->count != 0 || /* inner tokens to send */
+ /* Format output token from inner tokens */
+ if (innerOutputTokenCount != 0 || /* inner tokens to send */
!CTX_IS_INITIATOR(ctx) || /* any leg acceptor */
- ctx->state != GSSEAP_STATE_ESTABLISHED) { /* non-last leg initiator */
- tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, innerOutputTokens,
- outputTokenTypes, &unwrappedOutputToken);
- if (tmpMajor == GSS_S_COMPLETE) {
- tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &unwrappedOutputToken,
- TOK_TYPE_ESTABLISH_CONTEXT, outputToken);
- if (GSS_ERROR(tmpMajor)) {
- major = tmpMajor;
- *minor = tmpMinor;
- goto cleanup;
- }
+ !CTX_IS_ESTABLISHED(ctx)) { /* non-last leg initiator */
+ tmpMajor = makeContextToken(&tmpMinor, ctx, headerOffset, outputToken);
+ if (GSS_ERROR(tmpMajor)) {
+ major = tmpMajor;
+ *minor = tmpMinor;
+ goto cleanup;
}
}
- assert(GSS_ERROR(major) ||
- (major == GSS_S_CONTINUE_NEEDED && (ctx->state > GSSEAP_STATE_INITIAL && ctx->state < GSSEAP_STATE_ESTABLISHED)) ||
- (major == GSS_S_COMPLETE && ctx->state == GSSEAP_STATE_ESTABLISHED));
+ /* If the context is established, empty tokens only to be emitted by initiator */
+ assert(!CTX_IS_ESTABLISHED(ctx) || ((outputToken->length == 0) == CTX_IS_INITIATOR(ctx)));
+
+ SM_ASSERT_VALID(ctx, major);
cleanup:
gss_release_buffer_set(&tmpMinor, &innerInputTokens);
- gss_release_buffer_set(&tmpMinor, &innerOutputTokens);
if (inputTokenTypes != NULL)
GSSEAP_FREE(inputTokenTypes);
if (outputTokenTypes != NULL)