From 3e139a96a0be2caa9d0e14a8d700ba55017aa7f0 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Thu, 6 Oct 2011 09:44:51 +1100 Subject: [PATCH] use RFC3961 checksums for CB/exts MIC --- moonshot/mech_eap/accept_sec_context.c | 66 ++++++++------ moonshot/mech_eap/gssapiP_eap.h | 5 ++ moonshot/mech_eap/gsseap_err.et | 1 + moonshot/mech_eap/init_sec_context.c | 44 ++++++--- moonshot/mech_eap/util.h | 20 +++++ moonshot/mech_eap/util_context.c | 158 ++++++++++++++++++++++++--------- moonshot/mech_eap/util_krb.c | 6 +- 7 files changed, 220 insertions(+), 80 deletions(-) diff --git a/moonshot/mech_eap/accept_sec_context.c b/moonshot/mech_eap/accept_sec_context.c index b089bae..9a77714 100644 --- a/moonshot/mech_eap/accept_sec_context.c +++ b/moonshot/mech_eap/accept_sec_context.c @@ -639,39 +639,41 @@ eapGssSmAcceptGssChannelBindings(OM_uint32 *minor, gss_buffer_t outputToken GSSEAP_UNUSED, OM_uint32 *smFlags GSSEAP_UNUSED) { - OM_uint32 major; - gss_iov_buffer_desc iov[2]; + krb5_error_code code; + krb5_context krbContext; + krb5_data data; + krb5_checksum cksum; + krb5_boolean valid = FALSE; - iov[0].type = GSS_IOV_BUFFER_TYPE_DATA | GSS_IOV_BUFFER_FLAG_ALLOCATE; - iov[0].buffer.length = 0; - iov[0].buffer.value = NULL; + if (chanBindings == GSS_C_NO_CHANNEL_BINDINGS || + chanBindings->application_data.length == 0) + return GSS_S_CONTINUE_NEEDED; - iov[1].type = GSS_IOV_BUFFER_TYPE_STREAM | GSS_IOV_BUFFER_FLAG_ALLOCATED; + GSSEAP_KRB_INIT(&krbContext); - /* XXX necessary because decrypted in place and we verify it later */ - major = duplicateBuffer(minor, inputToken, &iov[1].buffer); - if (GSS_ERROR(major)) - return major; + KRB_DATA_INIT(&data); - major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL, - iov, 2, TOK_TYPE_WRAP); - if (GSS_ERROR(major)) { - gssEapReleaseIov(iov, 2); - return major; + gssBufferToKrbData(&chanBindings->application_data, &data); + + KRB_CHECKSUM_INIT(&cksum, ctx->checksumType, inputToken); + + code = krb5_c_verify_checksum(krbContext, &ctx->rfc3961Key, + KEY_USAGE_GSSEAP_CHBIND_MIC, + &data, &cksum, &valid); + if (code != 0) { + *minor = code; + return GSS_S_FAILURE; } - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS && - !bufferEqual(&iov[0].buffer, &chanBindings->application_data)) { - major = GSS_S_BAD_BINDINGS; + if (valid == FALSE) { *minor = GSSEAP_BINDINGS_MISMATCH; - } else { - major = GSS_S_CONTINUE_NEEDED; - *minor = 0; + return GSS_S_BAD_BINDINGS; } - gssEapReleaseIov(iov, 2); + ctx->flags |= CTX_FLAG_CHANNEL_BINDINGS_VERIFIED; - return major; + *minor = 0; + return GSS_S_CONTINUE_NEEDED; } static OM_uint32 @@ -682,13 +684,27 @@ eapGssSmAcceptInitiatorMIC(OM_uint32 *minor, gss_OID mech GSSEAP_UNUSED, OM_uint32 reqFlags GSSEAP_UNUSED, OM_uint32 timeReq GSSEAP_UNUSED, - gss_channel_bindings_t chanBindings GSSEAP_UNUSED, + gss_channel_bindings_t chanBindings, gss_buffer_t inputToken, gss_buffer_t outputToken GSSEAP_UNUSED, OM_uint32 *smFlags GSSEAP_UNUSED) { OM_uint32 major; + /* + * The channel binding token is optional, however if the caller indicated + * bindings we must raise an error if it was absent. + * + * In the future, we might use a context option to allow the caller to + * indicate that missing bindings are acceptable. + */ + if (chanBindings != NULL && + chanBindings->application_data.length != 0 && + (ctx->flags & CTX_FLAG_CHANNEL_BINDINGS_VERIFIED) == 0) { + *minor = GSSEAP_MISSING_BINDINGS; + return GSS_S_BAD_BINDINGS; + } + major = gssEapVerifyTokenMIC(minor, ctx, inputToken); if (GSS_ERROR(major)) return major; @@ -807,7 +823,7 @@ static struct gss_eap_sm eapGssAcceptorSm[] = { ITOK_TYPE_GSS_CHANNEL_BINDINGS, ITOK_TYPE_NONE, GSSEAP_STATE_INITIATOR_EXTS, - SM_ITOK_FLAG_REQUIRED, + 0, eapGssSmAcceptGssChannelBindings, }, { diff --git a/moonshot/mech_eap/gssapiP_eap.h b/moonshot/mech_eap/gssapiP_eap.h index f42f7be..164dabd 100644 --- a/moonshot/mech_eap/gssapiP_eap.h +++ b/moonshot/mech_eap/gssapiP_eap.h @@ -178,6 +178,7 @@ struct gss_cred_id_struct #define CTX_FLAG_INITIATOR 0x00000001 #define CTX_FLAG_KRB_REAUTH 0x00000002 +#define CTX_FLAG_CHANNEL_BINDINGS_VERIFIED 0x00000004 #define CTX_IS_INITIATOR(ctx) (((ctx)->flags & CTX_FLAG_INITIATOR) != 0) @@ -257,6 +258,10 @@ struct gss_ctx_id_struct #define KEY_USAGE_INITIATOR_SEAL 24 #define KEY_USAGE_INITIATOR_SIGN 25 +#define KEY_USAGE_GSSEAP_CHBIND_MIC 60 +#define KEY_USAGE_GSSEAP_ACCTOKEN_MIC 61 +#define KEY_USAGE_GSSEAP_INITOKEN_MIC 62 + /* accept_sec_context.c */ OM_uint32 gssEapAcceptSecContext(OM_uint32 *minor, diff --git a/moonshot/mech_eap/gsseap_err.et b/moonshot/mech_eap/gsseap_err.et index d60c2c7..c126424 100644 --- a/moonshot/mech_eap/gsseap_err.et +++ b/moonshot/mech_eap/gsseap_err.et @@ -156,6 +156,7 @@ error_code GSSEAP_SHIB_LISTENER_FAILURE, "Failed to communicate with loca # Extensions # error_code GSSEAP_BINDINGS_MISMATCH, "Channel bindings do not match" +error_code GSSEAP_MISSING_BINDINGS, "Channel binding token missing" error_code GSSEAP_NO_MECHGLUE_SYMBOL, "Could not find symbol in mechanism glue" error_code GSSEAP_BAD_INVOCATION, "Bad mechanism invoke OID" diff --git a/moonshot/mech_eap/init_sec_context.c b/moonshot/mech_eap/init_sec_context.c index e99b479..a123626 100644 --- a/moonshot/mech_eap/init_sec_context.c +++ b/moonshot/mech_eap/init_sec_context.c @@ -759,21 +759,45 @@ eapGssSmInitGssChannelBindings(OM_uint32 *minor, OM_uint32 *smFlags) { OM_uint32 major; - gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + krb5_error_code code; + krb5_context krbContext; + krb5_data data; + krb5_checksum cksum; + gss_buffer_desc cksumBuffer; - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS) - buffer = chanBindings->application_data; + if (chanBindings == GSS_C_NO_CHANNEL_BINDINGS || + chanBindings->application_data.length == 0) + return GSS_S_CONTINUE_NEEDED; - major = gssEapWrap(minor, ctx, TRUE, GSS_C_QOP_DEFAULT, - &buffer, NULL, outputToken); - if (GSS_ERROR(major)) - return major; + GSSEAP_KRB_INIT(&krbContext); + + KRB_DATA_INIT(&data); + + gssBufferToKrbData(&chanBindings->application_data, &data); + + code = krb5_c_make_checksum(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + KEY_USAGE_GSSEAP_CHBIND_MIC, + &data, &cksum); + if (code != 0) { + *minor = code; + return GSS_S_FAILURE; + } + + cksumBuffer.length = KRB_CHECKSUM_LENGTH(&cksum); + cksumBuffer.value = KRB_CHECKSUM_DATA(&cksum); - GSSEAP_ASSERT(outputToken->value != NULL); + major = duplicateBuffer(minor, &cksumBuffer, outputToken); + if (GSS_ERROR(major)) { + krb5_free_checksum_contents(krbContext, &cksum); + return major; + } *minor = 0; *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; + krb5_free_checksum_contents(krbContext, &cksum); + return GSS_S_CONTINUE_NEEDED; } @@ -803,7 +827,7 @@ eapGssSmInitInitiatorMIC(OM_uint32 *minor, return GSS_S_CONTINUE_NEEDED; } - + #ifdef GSSEAP_ENABLE_REAUTH static OM_uint32 eapGssSmInitReauthCreds(OM_uint32 *minor, @@ -918,7 +942,7 @@ static struct gss_eap_sm eapGssInitiatorSm[] = { ITOK_TYPE_NONE, ITOK_TYPE_GSS_CHANNEL_BINDINGS, GSSEAP_STATE_INITIATOR_EXTS, - SM_ITOK_FLAG_REQUIRED, + 0, eapGssSmInitGssChannelBindings }, { diff --git a/moonshot/mech_eap/util.h b/moonshot/mech_eap/util.h index 4f54d41..bd6888e 100644 --- a/moonshot/mech_eap/util.h +++ b/moonshot/mech_eap/util.h @@ -379,6 +379,16 @@ gssEapDeriveRfc3961Key(OM_uint32 *minor, #define KRB_DATA_INIT(d) krb5_data_zero((d)) +#define KRB_CHECKSUM_TYPE(c) ((c)->cksumtype) +#define KRB_CHECKSUM_LENGTH(c) ((c)->checksum.length) +#define KRB_CHECKSUM_DATA(c) ((c)->checksum.data) + +#define KRB_CHECKSUM_INIT(cksum, type, d) do { \ + (cksum)->cksumtype = (type); \ + (cksum)->checksum.length = (d)->length; \ + (cksum)->checksum.data = (d)->value; \ + } while (0) + #else #define KRB_TIME_FOREVER KRB5_INT32_MAX @@ -403,6 +413,16 @@ gssEapDeriveRfc3961Key(OM_uint32 *minor, (d)->data = NULL; \ } while (0) +#define KRB_CHECKSUM_TYPE(c) ((c)->checksum_type) +#define KRB_CHECKSUM_LENGTH(c) ((c)->length) +#define KRB_CHECKSUM_DATA(c) ((c)->contents) + +#define KRB_CHECKSUM_INIT(cksum, type, d) do { \ + (cksum)->checksum_type = (type); \ + (cksum)->length = (d)->length; \ + (cksum)->contents = (d)->value; \ + } while (0) + #endif /* HAVE_HEIMDAL_VERSION */ #define KRB_KEY_INIT(key) do { \ diff --git a/moonshot/mech_eap/util_context.c b/moonshot/mech_eap/util_context.c index e18edc5..f1720f2 100644 --- a/moonshot/mech_eap/util_context.c +++ b/moonshot/mech_eap/util_context.c @@ -243,20 +243,45 @@ gssEapMakeOrVerifyTokenMIC(OM_uint32 *minor, int verifyMIC) { OM_uint32 major; - gss_iov_buffer_desc *iov = NULL; size_t i = 0, j; enum gss_eap_token_type tokType; OM_uint32 micTokType; unsigned char wireTokType[2]; unsigned char *innerTokTypes = NULL, *innerTokLengths = NULL; const struct gss_eap_token_buffer_set *tokens; + ssize_t checksumIndex = -1; + + krb5_keyusage usage; + krb5_error_code code; + krb5_context krbContext; + krb5_crypto_iov *kiov = NULL; +#ifdef HAVE_HEIMDAL_VERSION + krb5_crypto krbCrypto = NULL; + krb5_cksumtype cksumType; +#endif + size_t kiovCount; + + GSSEAP_KRB_INIT(&krbContext); tokens = verifyMIC ? ctx->inputTokens : ctx->outputTokens; GSSEAP_ASSERT(tokens != NULL); - iov = GSSEAP_CALLOC(2 + (3 * tokens->buffers.count) + 1, sizeof(*iov)); - if (iov == NULL) { +#ifdef HAVE_HEIMDAL_VERSION + code = krb5_crypto_init(krbContext, &ctx->rfc3961Key, ETYPE_NULL, &krbCrypto); + if (code != 0) + goto cleanup; +#endif + + kiovCount = 2 + (3 * tokens->buffers.count) + 1; + + if (verifyMIC) { + assert(tokens->buffers.count != 0); + kiovCount -= 3; + } + + kiov = GSSEAP_CALLOC(kiovCount, sizeof(*kiov)); + if (kiov == NULL) { major = GSS_S_FAILURE; *minor = ENOMEM; goto cleanup; @@ -278,76 +303,134 @@ gssEapMakeOrVerifyTokenMIC(OM_uint32 *minor, /* Mechanism OID */ GSSEAP_ASSERT(ctx->mechanismUsed != GSS_C_NO_OID); - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = ctx->mechanismUsed->length; - iov[i].buffer.value = ctx->mechanismUsed->elements; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = ctx->mechanismUsed->length; + kiov[i].data.data = ctx->mechanismUsed->elements; i++; /* Token type */ if (CTX_IS_INITIATOR(ctx) ^ verifyMIC) { tokType = TOK_TYPE_INITIATOR_CONTEXT; micTokType = ITOK_TYPE_INITIATOR_MIC; + usage = KEY_USAGE_GSSEAP_INITOKEN_MIC; } else { tokType = TOK_TYPE_ACCEPTOR_CONTEXT; micTokType = ITOK_TYPE_ACCEPTOR_MIC; + usage = KEY_USAGE_GSSEAP_ACCTOKEN_MIC; } store_uint16_be(tokType, wireTokType); - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = sizeof(wireTokType); - iov[i].buffer.value = wireTokType; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = sizeof(wireTokType); + kiov[i].data.data = (char *)wireTokType; i++; for (j = 0; j < tokens->buffers.count; j++) { if (verifyMIC && - (tokens->types[j] & ITOK_TYPE_MASK) == micTokType) - continue; /* will use this slot for trailer */ + (tokens->types[j] & ITOK_TYPE_MASK) == micTokType) { + continue; + } - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = 4; - iov[i].buffer.value = &innerTokTypes[j * 4]; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = 4; + kiov[i].data.data = (char *)&innerTokTypes[j * 4]; store_uint32_be(tokens->types[j] & ~(ITOK_FLAG_VERIFIED), - iov[i].buffer.value); + kiov[i].data.data); i++; - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = 4; - iov[i].buffer.value = &innerTokLengths[j * 4]; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = 4; + kiov[i].data.data = (char *)&innerTokLengths[j * 4]; store_uint32_be(tokens->buffers.elements[j].length, - iov[i].buffer.value); + kiov[i].data.data); i++; - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer = tokens->buffers.elements[j]; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + gssBufferToKrbData(&tokens->buffers.elements[j], &kiov[i].data); i++; } + kiov[i].flags = KRB5_CRYPTO_TYPE_CHECKSUM; if (verifyMIC) { - GSSEAP_ASSERT(tokenMIC->length >= 16); + gssBufferToKrbData(tokenMIC, &kiov[i].data); + } else { + size_t checksumSize; - GSSEAP_ASSERT(i < 2 + (3 * tokens->buffers.count)); + code = krb5_c_checksum_length(krbContext, ctx->checksumType, + &checksumSize); + if (code != 0) + goto cleanup; - iov[i].type = GSS_IOV_BUFFER_TYPE_HEADER; - iov[i].buffer = *tokenMIC; - i++; + kiov[i].data.data = GSSEAP_MALLOC(checksumSize); + if (kiov[i].data.data == NULL) { + code = ENOMEM; + goto cleanup; + } + kiov[i].data.length = checksumSize; + checksumIndex = i; + } + i++; + GSSEAP_ASSERT(i == kiovCount); - major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL, - iov, i, TOK_TYPE_MIC); +#ifdef HAVE_HEIMDAL_VERSION + cksumType = ctx->checksumType; + + if (verifyMIC) { + code = krb5_verify_checksum_iov(krbContext, krbCrypto, usage, + kiov, i, &cksumType); + } else { + code = krb5_create_checksum_iov(krbContext, krbCrypto, usage, + kiov, i, &cksumType); + } +#else + if (verifyMIC) { + krb5_boolean kvalid = FALSE; + + code = krb5_c_verify_checksum_iov(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + usage, kiov, i, &kvalid); + if (code == 0 && !kvalid) { + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } } else { - iov[i++].type = GSS_IOV_BUFFER_TYPE_HEADER | GSS_IOV_BUFFER_FLAG_ALLOCATE; - major = gssEapWrapOrGetMIC(minor, ctx, FALSE, NULL, - iov, i, TOK_TYPE_MIC); - if (!GSS_ERROR(major)) - *tokenMIC = iov[i - 1].buffer; + code = krb5_c_make_checksum_iov(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + usage, kiov, i); + } +#endif /* HAVE_HEIMDAL_VERSION */ + + if (code == 0 && !verifyMIC) { + krbDataToGssBuffer(&kiov[checksumIndex].data, tokenMIC); + checksumIndex = -1; } cleanup: - if (iov != NULL) - gssEapReleaseIov(iov, tokens->buffers.count); + if (checksumIndex != -1) + GSSEAP_FREE(kiov[checksumIndex].data.data); + if (kiov != NULL) + GSSEAP_FREE(kiov); if (innerTokTypes != NULL) GSSEAP_FREE(innerTokTypes); if (innerTokLengths != NULL) GSSEAP_FREE(innerTokLengths); +#ifdef HAVE_HEIMDAL_VERSION + if (krbCrypto != NULL) + krb5_crypto_destroy(krbContext, krbCrypto); +#endif + + *minor = code; + + switch (code) { + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + major = GSS_S_BAD_SIG; + break; + case 0: + major = GSS_S_COMPLETE; + break; + default: + major = GSS_S_FAILURE; + break; + } return major; } @@ -368,10 +451,5 @@ gssEapVerifyTokenMIC(OM_uint32 *minor, gss_ctx_id_t ctx, const gss_buffer_t tokenMIC) { - if (tokenMIC->length < 16) { - *minor = GSSEAP_TOK_TRUNC; - return GSS_S_BAD_SIG; - } - return gssEapMakeOrVerifyTokenMIC(minor, ctx, tokenMIC, TRUE); } diff --git a/moonshot/mech_eap/util_krb.c b/moonshot/mech_eap/util_krb.c index 5eaa31e..32e28b4 100644 --- a/moonshot/mech_eap/util_krb.c +++ b/moonshot/mech_eap/util_krb.c @@ -283,11 +283,7 @@ rfc3961ChecksumTypeForKey(OM_uint32 *minor, if (*minor != 0) return GSS_S_FAILURE; -#ifdef HAVE_HEIMDAL_VERSION - *cksumtype = cksum.cksumtype; -#else - *cksumtype = cksum.checksum_type; -#endif + *cksumtype = KRB_CHECKSUM_TYPE(&cksum); krb5_free_checksum_contents(krbContext, &cksum); #endif /* HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE */ -- 2.1.4