#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)
+{
+ const char *s;
+
+ switch (state) {
+ case GSSEAP_STATE_INITIAL:
+ s = "INITIAL";
+ break;
+ case GSSEAP_STATE_AUTHENTICATE:
+ s = "AUTHENTICATE";
+ break;
+ case GSSEAP_STATE_INITIATOR_EXTS:
+ s = "INITIATOR_EXTS";
+ break;
+ case GSSEAP_STATE_ACCEPTOR_EXTS:
+ s = "ACCEPTOR_EXTS";
+ break;
+ case GSSEAP_STATE_REAUTHENTICATE:
+ s = "REAUTHENTICATE";
+ break;
+ case GSSEAP_STATE_ESTABLISHED:
+ s = "ESTABLISHED";
+ break;
+ default:
+ s = "INVALID";
+ break;
+ }
+
+ return s;
+}
+
+void
+gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state)
+{
+ 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)
{
+ OM_uint32 major;
unsigned char errorData[8];
gss_buffer_desc errorBuffer;
assert(GSS_ERROR(majorStatus));
+ major = gss_create_empty_buffer_set(minor, outputToken);
+ if (GSS_ERROR(major))
+ return major;
+
/*
* Only return error codes that the initiator could have caused,
* to avoid information leakage.
errorBuffer.length = sizeof(errorData);
errorBuffer.value = errorData;
- return gss_add_buffer_set_member(minor, &errorBuffer, outputToken);
+ major = gss_add_buffer_set_member(minor, &errorBuffer, outputToken);
+ if (GSS_ERROR(major))
+ return major;
+
+ return GSS_S_COMPLETE;
+}
+
+static OM_uint32
+allocInnerTokens(OM_uint32 *minor,
+ size_t count,
+ gss_buffer_set_t *pTokens,
+ OM_uint32 **pTokenTypes)
+{
+ OM_uint32 major, tmpMinor;
+ gss_buffer_set_t tokens = GSS_C_NO_BUFFER_SET;
+ OM_uint32 *tokenTypes = NULL;
+
+ major = gss_create_empty_buffer_set(minor, &tokens);
+ if (GSS_ERROR(major))
+ goto cleanup;
+
+ assert(tokens->count == 0);
+ assert(tokens->elements == NULL);
+
+ tokens->elements = (gss_buffer_desc *)GSSEAP_CALLOC(count, sizeof(gss_buffer_desc));
+ if (tokens->elements == NULL) {
+ major = GSS_S_FAILURE;
+ *minor = ENOMEM;
+ goto cleanup;
+ }
+
+ tokenTypes = (OM_uint32 *)GSSEAP_CALLOC(count, sizeof(OM_uint32));
+ if (tokenTypes == NULL) {
+ major = GSS_S_FAILURE;
+ *minor = ENOMEM;
+ goto cleanup;
+ }
+
+ major = GSS_S_COMPLETE;
+ *minor = 0;
+
+cleanup:
+ if (GSS_ERROR(major)) {
+ gss_release_buffer_set(&tmpMinor, &tokens);
+ tokens = GSS_C_NO_BUFFER_SET;
+ if (tokenTypes != NULL) {
+ GSSEAP_FREE(tokenTypes);
+ tokenTypes = NULL;
+ }
+ }
+
+ *pTokens = tokens;
+ *pTokenTypes = tokenTypes;
+
+ return major;
}
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_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;
- enum gss_eap_state inputState = ctx->state;
+ int initialContextToken = 0;
assert(smCount > 0);
major = GSS_S_DEFECTIVE_TOKEN;
*minor = GSSEAP_WRONG_SIZE;
goto cleanup;
+ } else {
+ 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;
- 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);
-
- 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 = allocInnerTokens(minor, smCount, &innerOutputTokens, &outputTokenTypes);
+ 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 {
- int transitionState = 0;
-
- 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 (innerInputTokens == GSS_C_NO_BUFFER_SET) {
- processToken = ((smp->validStates & GSSEAP_STATE_INITIAL) != 0);
- } else if (inputState != ctx->state) {
- processToken = (smp->inputTokenType == ITOK_TYPE_NONE);
- } else {
- for (j = 0; j < innerInputTokens->count; j++) {
- processToken = (smp->inputTokenType == inputTokenTypes[j]);
- if (innerInputToken != GSS_C_NO_BUFFER && processToken) {
+ /* 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];
}
}
-
-#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) {
- major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
- timeReq, chanBindings, innerInputToken,
- &innerOutputToken, &transitionState);
- if (GSS_ERROR(major))
- break;
-
- if (inputTokenType != NULL)
- *inputTokenType |= ITOK_FLAG_VERIFIED;
-
- 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 (transitionState)
- break;
- } else if (smp->required && smp->inputTokenType != ITOK_TYPE_NONE) {
- major = GSS_S_DEFECTIVE_TOKEN;
- *minor = GSSEAP_MISSING_REQUIRED_ITOK;
+ if (GSS_ERROR(major))
break;
- }
}
- if (GSS_ERROR(major) || !transitionState)
- break;
+ if (processToken) {
+ enum gss_eap_state oldState = ctx->state;
- assert(ctx->state < GSSEAP_STATE_ESTABLISHED);
+ smFlags = 0;
+ if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL))
+ smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL;
- ctx->state = GSSEAP_STATE_NEXT(ctx->state);
+ major = smp->processToken(minor, cred, ctx, target, mech, reqFlags,
+ timeReq, chanBindings, innerInputToken,
+ &innerOutputToken, &smFlags);
+ if (GSS_ERROR(major))
+ break;
- if (innerOutputTokens->count != 0) {
- assert(major == GSS_S_CONTINUE_NEEDED);
- break; /* send any tokens if we have them */
+ if (inputTokenType != NULL)
+ *inputTokenType |= ITOK_FLAG_VERIFIED;
+ if (smFlags & SM_FLAG_RESTART) {
+ assert(ctx->state < oldState);
+ i = 0;
+ } 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 (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL)
+ outputTokenTypes[innerOutputTokens->count] |= ITOK_FLAG_CRITICAL;
+ innerOutputTokens->count++;
+ }
+ /*
+ * Break out if explicitly requested, or if we made a state transition
+ * and have some tokens to send.
+ */
+ if ((smFlags & SM_FLAG_STOP_EVAL) ||
+ ((smFlags & SM_FLAG_TRANSITED) &&
+ ((smFlags & SM_FLAG_FORCE_SEND_TOKEN) || innerOutputTokens->count != 0))) {
+ SM_ASSERT_VALID(ctx, major);
+ break;
+ }
+ } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) &&
+ smp->inputTokenType != ITOK_TYPE_NONE) {
+ /* Check for required inner tokens */
+ 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 */
- if (!GSS_ERROR(major) && innerInputTokens != GSS_C_NO_BUFFER_SET) {
+ /* 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) &&
(inputTokenTypes[j] & ITOK_FLAG_VERIFIED) == 0) {
}
}
- /* 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 */
goto cleanup;
}
- outputTokenTypes[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
+ if (innerOutputTokens->count != 0)
+ outputTokenTypes[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL;
}
-#ifdef GSSEAP_DEBUG
- for (i = 0; i < innerOutputTokens->count; i++) {
- fprintf(stderr, "GSS-EAP: type %d length %zd value %p\n",
- outputTokenTypes[i],
- innerOutputTokens->elements[i].length,
- innerOutputTokens->elements[i].value);
- }
-#endif
-
- /* Format composite output token */
+ /* Format output token from inner tokens */
if (innerOutputTokens->count != 0 || /* inner tokens to send */
!CTX_IS_INITIATOR(ctx) || /* any leg acceptor */
- ctx->state != GSSEAP_STATE_ESTABLISHED) { /* non-last leg initiator */
+ !CTX_IS_ESTABLISHED(ctx)) { /* non-last leg initiator */
tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, innerOutputTokens,
outputTokenTypes, &unwrappedOutputToken);
if (tmpMajor == GSS_S_COMPLETE) {
}
}
- assert(GSS_ERROR(major) ||
- (major == GSS_S_CONTINUE_NEEDED && 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);