use RFC3961 checksums for CB/exts MIC rfc3961-mic
authorLuke Howard <lukeh@padl.com>
Wed, 5 Oct 2011 22:44:51 +0000 (09:44 +1100)
committerLuke Howard <lukeh@padl.com>
Fri, 14 Oct 2011 23:18:37 +0000 (10:18 +1100)
moonshot/mech_eap/accept_sec_context.c
moonshot/mech_eap/gssapiP_eap.h
moonshot/mech_eap/gsseap_err.et
moonshot/mech_eap/init_sec_context.c
moonshot/mech_eap/util.h
moonshot/mech_eap/util_context.c
moonshot/mech_eap/util_krb.c

index b089bae..9a77714 100644 (file)
@@ -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,
     },
     {
index f42f7be..164dabd 100644 (file)
@@ -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,
index d60c2c7..c126424 100644 (file)
@@ -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"
 
index e99b479..a123626 100644 (file)
@@ -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
     },
     {
index 4f54d41..bd6888e 100644 (file)
@@ -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 {        \
index e18edc5..f1720f2 100644 (file)
@@ -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);
 }
index 5eaa31e..32e28b4 100644 (file)
@@ -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 */