Blob Blame History Raw
From c4a49f5b42916fdbb34c72a11adb42ff879c50c3 Mon Sep 17 00:00:00 2001
From: Alexander Scheel <ascheel@redhat.com>
Date: Fri, 30 Jun 2017 16:03:01 -0400
Subject: [PATCH] Refactor krb5 GSS checksum handling

Separate out checksum handling from kg_accept_krb5() into a new helper
process_checksum().

[ghudson@mit.edu: simplified checksum processing and made it use
k5-input.h instead of TREAD_ macros; moved more flag handling into
helper]

[iboukris: adjusted helper function arguments, allowing access to the
full authenticator for subsequent changes]

(cherry picked from commit 64d56233f9816a2a93f6e8d3030c8ed6ce397735)
[rharwood@redhat.com: problem with typo fix commit, I think]
(cherry picked from commit a34b7c50e62c19f80d39ece6a72017dac781df64)
---
 src/lib/gssapi/krb5/accept_sec_context.c | 383 +++++++++++------------
 1 file changed, 179 insertions(+), 204 deletions(-)

diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c
index c5bddb1e8..70dd7fc0c 100644
--- a/src/lib/gssapi/krb5/accept_sec_context.c
+++ b/src/lib/gssapi/krb5/accept_sec_context.c
@@ -98,6 +98,7 @@
  */
 
 #include "k5-int.h"
+#include "k5-input.h"
 #include "gssapiP_krb5.h"
 #ifdef HAVE_MEMORY_H
 #include <memory.h>
@@ -413,6 +414,174 @@ kg_process_extension(krb5_context context,
     return code;
 }
 
+/* The length of the MD5 channel bindings in an 0x8003 checksum */
+#define CB_MD5_LEN 16
+
+/* The minimum length of an 0x8003 checksum value (4-byte channel bindings
+ * length, 16-byte channel bindings, 4-byte flags) */
+#define MIN_8003_LEN (4 + CB_MD5_LEN + 4)
+
+/* The flags we accept from the initiator's authenticator checksum. */
+#define INITIATOR_FLAGS (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG |           \
+                         GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG |        \
+                         GSS_C_SEQUENCE_FLAG | GSS_C_DCE_STYLE |        \
+                         GSS_C_IDENTIFY_FLAG | GSS_C_EXTENDED_ERROR_FLAG)
+
+/*
+ * The krb5 GSS mech appropriates the authenticator checksum field from RFC
+ * 4120 to store structured data instead of a checksum, indicated with checksum
+ * type 0x8003 (see RFC 4121 section 4.1.1).  Some implementations instead send
+ * no checksum, or a regular checksum over empty data.
+ *
+ * Interpret the checksum.  Read delegated creds into *deleg_out if it is not
+ * NULL.  Set *flags_out to the allowed subset of token flags, plus
+ * GSS_C_DELEG_FLAG if a delegated credential was present.  Process any
+ * extensions found using exts.  On error, set *code_out to a krb5_error code
+ * for use as a minor status value.
+ */
+static OM_uint32
+process_checksum(OM_uint32 *minor_status, krb5_context context,
+                 gss_channel_bindings_t acceptor_cb,
+                 krb5_auth_context auth_context, krb5_flags ap_req_options,
+                 krb5_authenticator *authenticator, krb5_gss_ctx_ext_t exts,
+                 krb5_gss_cred_id_t *deleg_out, krb5_ui_4 *flags_out,
+                 krb5_error_code *code_out)
+{
+    krb5_error_code code = 0;
+    OM_uint32 status, option_id, token_flags;
+    size_t cb_len, option_len;
+    krb5_boolean valid;
+    krb5_key subkey;
+    krb5_data option, empty = empty_data();
+    krb5_checksum cb_cksum;
+    const uint8_t *token_cb, *option_bytes;
+    struct k5input in;
+    const krb5_checksum *cksum = authenticator->checksum;
+
+    cb_cksum.contents = NULL;
+
+    if (cksum == NULL) {
+        /*
+         * Some SMB client implementations use handcrafted GSSAPI code that
+         * does not provide a checksum.  MS-KILE documents that the Microsoft
+         * implementation considers a missing checksum acceptable; the server
+         * assumes all flags are unset in this case, and does not check channel
+         * bindings.
+         */
+        *flags_out = 0;
+    } else if (cksum->checksum_type != CKSUMTYPE_KG_CB) {
+        /* Samba sends a regular checksum. */
+        code = krb5_auth_con_getkey_k(context, auth_context, &subkey);
+        if (code) {
+            status = GSS_S_FAILURE;
+            goto fail;
+        }
+
+        /* Verifying the checksum ensures that this authenticator wasn't
+         * replayed from one with a checksum over actual data. */
+        code = krb5_k_verify_checksum(context, subkey,
+                                      KRB5_KEYUSAGE_AP_REQ_AUTH_CKSUM, &empty,
+                                      cksum, &valid);
+        krb5_k_free_key(context, subkey);
+        if (code || !valid) {
+            status = GSS_S_BAD_SIG;
+            goto fail;
+        }
+
+        /* Use ap_options from the request to guess the mutual flag. */
+        *flags_out = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG;
+        if (ap_req_options & AP_OPTS_MUTUAL_REQUIRED)
+            *flags_out |= GSS_C_MUTUAL_FLAG;
+    } else {
+        /* The checksum must contain at least a fixed 24-byte part. */
+        if (cksum->length < MIN_8003_LEN) {
+            status = GSS_S_BAD_BINDINGS;
+            goto fail;
+        }
+
+        k5_input_init(&in, cksum->contents, cksum->length);
+        cb_len = k5_input_get_uint32_le(&in);
+        if (cb_len != CB_MD5_LEN) {
+            code = KG_BAD_LENGTH;
+            status = GSS_S_FAILURE;
+            goto fail;
+        }
+
+        token_cb = k5_input_get_bytes(&in, cb_len);
+        if (acceptor_cb != GSS_C_NO_CHANNEL_BINDINGS) {
+            code = kg_checksum_channel_bindings(context, acceptor_cb,
+                                                &cb_cksum);
+            if (code) {
+                status = GSS_S_BAD_BINDINGS;
+                goto fail;
+            }
+            assert(cb_cksum.length == cb_len);
+            if (k5_bcmp(token_cb, cb_cksum.contents, cb_len) != 0) {
+                status = GSS_S_BAD_BINDINGS;
+                goto fail;
+            }
+        }
+
+        /* Read the token flags and accept some of them as context flags. */
+        token_flags = k5_input_get_uint32_le(&in);
+        *flags_out = token_flags & INITIATOR_FLAGS;
+
+        /* Read the delegated credential if present. */
+        if (in.len >= 4 && (token_flags & GSS_C_DELEG_FLAG)) {
+            option_id = k5_input_get_uint16_le(&in);
+            option_len = k5_input_get_uint16_le(&in);
+            option_bytes = k5_input_get_bytes(&in, option_len);
+            option = make_data((uint8_t *)option_bytes, option_len);
+            if (in.status) {
+                code = KG_BAD_LENGTH;
+                status = GSS_S_FAILURE;
+                goto fail;
+            }
+            if (option_id != KRB5_GSS_FOR_CREDS_OPTION) {
+                status = GSS_S_FAILURE;
+                goto fail;
+            }
+
+            /* Store the delegated credential. */
+            code = rd_and_store_for_creds(context, auth_context, &option,
+                                          deleg_out);
+            if (code) {
+                status = GSS_S_FAILURE;
+                goto fail;
+            }
+            *flags_out |= GSS_C_DELEG_FLAG;
+        }
+
+        /* Process any extensions at the end of the checksum.  Extensions use
+         * 4-byte big-endian tag and length instead of 2-byte little-endian. */
+        while (in.len > 0) {
+            option_id = k5_input_get_uint32_be(&in);
+            option_len = k5_input_get_uint32_be(&in);
+            option_bytes = k5_input_get_bytes(&in, option_len);
+            option = make_data((uint8_t *)option_bytes, option_len);
+            if (in.status) {
+                code = KG_BAD_LENGTH;
+                status = GSS_S_FAILURE;
+                goto fail;
+            }
+
+            code = kg_process_extension(context, auth_context, option_id,
+                                        &option, exts);
+            if (code) {
+                status = GSS_S_FAILURE;
+                goto fail;
+            }
+        }
+    }
+
+    status = GSS_S_COMPLETE;
+
+fail:
+    free(cb_cksum.contents);
+    *code_out = code;
+    return status;
+}
+
 static OM_uint32
 kg_accept_krb5(minor_status, context_handle,
                verifier_cred_handle, input_token,
@@ -433,17 +602,13 @@ kg_accept_krb5(minor_status, context_handle,
     krb5_gss_ctx_ext_t exts;
 {
     krb5_context context;
-    unsigned char *ptr, *ptr2;
+    unsigned char *ptr;
     char *sptr;
-    OM_uint32 tmp;
-    size_t md5len;
     krb5_gss_cred_id_t cred = 0;
     krb5_data ap_rep, ap_req;
-    unsigned int i;
     krb5_error_code code;
     krb5_address addr, *paddr;
     krb5_authenticator *authdat = 0;
-    krb5_checksum reqcksum;
     krb5_gss_name_t name = NULL;
     krb5_ui_4 gss_flags = 0;
     krb5_gss_ctx_id_rec *ctx = NULL;
@@ -451,8 +616,6 @@ kg_accept_krb5(minor_status, context_handle,
     gss_buffer_desc token;
     krb5_auth_context auth_context = NULL;
     krb5_ticket * ticket = NULL;
-    int option_id;
-    krb5_data option;
     const gss_OID_desc *mech_used = NULL;
     OM_uint32 major_status = GSS_S_FAILURE;
     OM_uint32 tmp_minor_status;
@@ -463,7 +626,6 @@ kg_accept_krb5(minor_status, context_handle,
     krb5int_access kaccess;
     int cred_rcache = 0;
     int no_encap = 0;
-    int token_deleg_flag = 0;
     krb5_flags ap_req_options = 0;
     krb5_enctype negotiated_etype;
     krb5_authdata_context ad_context = NULL;
@@ -489,7 +651,6 @@ kg_accept_krb5(minor_status, context_handle,
     output_token->length = 0;
     output_token->value = NULL;
     token.value = 0;
-    reqcksum.contents = 0;
     ap_req.data = 0;
     ap_rep.data = 0;
 
@@ -654,195 +815,16 @@ kg_accept_krb5(minor_status, context_handle,
 
     krb5_auth_con_getauthenticator(context, auth_context, &authdat);
 
-    if (authdat->checksum == NULL) {
-        /*
-         * Some SMB client implementations use handcrafted GSSAPI code that
-         * does not provide a checksum.  MS-KILE documents that the Microsoft
-         * implementation considers a missing checksum acceptable; the server
-         * assumes all flags are unset in this case, and does not check channel
-         * bindings.
-         */
-        gss_flags = 0;
-    } else if (authdat->checksum->checksum_type != CKSUMTYPE_KG_CB) {
-        /* Samba does not send 0x8003 GSS-API checksums */
-        krb5_boolean valid;
-        krb5_key subkey;
-        krb5_data zero;
+    major_status = process_checksum(minor_status, context, input_chan_bindings,
+                                    auth_context, ap_req_options,
+                                    authdat, exts,
+                                    delegated_cred_handle ? &deleg_cred : NULL,
+                                    &gss_flags, &code);
 
-        code = krb5_auth_con_getkey_k(context, auth_context, &subkey);
-        if (code) {
-            major_status = GSS_S_FAILURE;
-            goto fail;
-        }
+    if (major_status != GSS_S_COMPLETE)
+        goto fail;
 
-        zero.length = 0;
-        zero.data = "";
-
-        code = krb5_k_verify_checksum(context,
-                                      subkey,
-                                      KRB5_KEYUSAGE_AP_REQ_AUTH_CKSUM,
-                                      &zero,
-                                      authdat->checksum,
-                                      &valid);
-        krb5_k_free_key(context, subkey);
-        if (code || !valid) {
-            major_status = GSS_S_BAD_SIG;
-            goto fail;
-        }
-
-        /* Use ap_options from the request to guess the mutual flag. */
-        gss_flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG;
-        if (ap_req_options & AP_OPTS_MUTUAL_REQUIRED)
-            gss_flags |= GSS_C_MUTUAL_FLAG;
-    } else {
-        /* gss krb5 v1 */
-
-        /* stash this now, for later. */
-        code = krb5_c_checksum_length(context, CKSUMTYPE_RSA_MD5, &md5len);
-        if (code) {
-            major_status = GSS_S_FAILURE;
-            goto fail;
-        }
-
-        /* verify that the checksum is correct */
-
-        /*
-          The checksum may be either exactly 24 bytes, in which case
-          no options are specified, or greater than 24 bytes, in which case
-          one or more options are specified. Currently, the only valid
-          option is KRB5_GSS_FOR_CREDS_OPTION ( = 1 ).
-        */
-
-        if ((authdat->checksum->checksum_type != CKSUMTYPE_KG_CB) ||
-            (authdat->checksum->length < 24)) {
-            code = 0;
-            major_status = GSS_S_BAD_BINDINGS;
-            goto fail;
-        }
-
-        ptr = (unsigned char *) authdat->checksum->contents;
-
-        TREAD_INT(ptr, tmp, 0);
-
-        if (tmp != md5len) {
-            code = KG_BAD_LENGTH;
-            major_status = GSS_S_FAILURE;
-            goto fail;
-        }
-
-        /*
-          The following section of code attempts to implement the
-          optional channel binding facility as described in RFC2743.
-
-          Since this facility is optional channel binding may or may
-          not have been provided by either the client or the server.
-
-          If the server has specified input_chan_bindings equal to
-          GSS_C_NO_CHANNEL_BINDINGS then we skip the check.  If
-          the server does provide channel bindings then we compute
-          a checksum and compare against those provided by the
-          client.         */
-
-        if ((code = kg_checksum_channel_bindings(context,
-                                                 input_chan_bindings,
-                                                 &reqcksum))) {
-            major_status = GSS_S_BAD_BINDINGS;
-            goto fail;
-        }
-
-        /* Always read the clients bindings - eventhough we might ignore them */
-        TREAD_STR(ptr, ptr2, reqcksum.length);
-
-        if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS ) {
-            if (memcmp(ptr2, reqcksum.contents, reqcksum.length) != 0) {
-                xfree(reqcksum.contents);
-                reqcksum.contents = 0;
-                code = 0;
-                major_status = GSS_S_BAD_BINDINGS;
-                goto fail;
-            }
-
-        }
-
-        xfree(reqcksum.contents);
-        reqcksum.contents = 0;
-
-        /* Read the token flags.  Remember if GSS_C_DELEG_FLAG was set, but
-         * mask it out until we actually read a delegated credential. */
-        TREAD_INT(ptr, gss_flags, 0);
-        token_deleg_flag = (gss_flags & GSS_C_DELEG_FLAG);
-        gss_flags &= ~GSS_C_DELEG_FLAG;
-
-        /* if the checksum length > 24, there are options to process */
-
-        i = authdat->checksum->length - 24;
-        if (i && token_deleg_flag) {
-            if (i >= 4) {
-                TREAD_INT16(ptr, option_id, 0);
-                TREAD_INT16(ptr, option.length, 0);
-                i -= 4;
-
-                if (i < option.length) {
-                    code = KG_BAD_LENGTH;
-                    major_status = GSS_S_FAILURE;
-                    goto fail;
-                }
-
-                /* have to use ptr2, since option.data is wrong type and
-                   macro uses ptr as both lvalue and rvalue */
-
-                TREAD_STR(ptr, ptr2, option.length);
-                option.data = (char *) ptr2;
-
-                i -= option.length;
-
-                if (option_id != KRB5_GSS_FOR_CREDS_OPTION) {
-                    major_status = GSS_S_FAILURE;
-                    goto fail;
-                }
-
-                /* store the delegated credential */
-
-                code = rd_and_store_for_creds(context, auth_context, &option,
-                                              (delegated_cred_handle) ?
-                                              &deleg_cred : NULL);
-                if (code) {
-                    major_status = GSS_S_FAILURE;
-                    goto fail;
-                }
-
-                gss_flags |= GSS_C_DELEG_FLAG;
-            } /* if i >= 4 */
-            /* ignore any additional trailing data, for now */
-        }
-        while (i > 0) {
-            /* Process Type-Length-Data options */
-            if (i < 8) {
-                code = KG_BAD_LENGTH;
-                major_status = GSS_S_FAILURE;
-                goto fail;
-            }
-            TREAD_INT(ptr, option_id, 1);
-            TREAD_INT(ptr, option.length, 1);
-            i -= 8;
-            if (i < option.length) {
-                code = KG_BAD_LENGTH;
-                major_status = GSS_S_FAILURE;
-                goto fail;
-            }
-            TREAD_STR(ptr, ptr2, option.length);
-            option.data = (char *)ptr2;
-
-            i -= option.length;
-
-            code = kg_process_extension(context, auth_context,
-                                        option_id, &option, exts);
-            if (code != 0) {
-                major_status = GSS_S_FAILURE;
-                goto fail;
-            }
-        }
-    }
+    major_status = GSS_S_FAILURE;
 
     if (exts->iakerb.conv && !exts->iakerb.verified) {
         major_status = GSS_S_BAD_SIG;
@@ -869,12 +851,7 @@ kg_accept_krb5(minor_status, context_handle,
     ctx->mech_used = (gss_OID) mech_used;
     ctx->auth_context = auth_context;
     ctx->initiate = 0;
-    ctx->gss_flags = (GSS_C_TRANS_FLAG |
-                      ((gss_flags) & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG |
-                                      GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG |
-                                      GSS_C_SEQUENCE_FLAG | GSS_C_DELEG_FLAG |
-                                      GSS_C_DCE_STYLE | GSS_C_IDENTIFY_FLAG |
-                                      GSS_C_EXTENDED_ERROR_FLAG)));
+    ctx->gss_flags = gss_flags | GSS_C_TRANS_FLAG;
     ctx->seed_init = 0;
     ctx->cred_rcache = cred_rcache;
 
@@ -1161,8 +1138,6 @@ fail:
 
         krb5_auth_con_free(context, auth_context);
     }
-    if (reqcksum.contents)
-        xfree(reqcksum.contents);
     if (ap_rep.data)
         krb5_free_data_contents(context, &ap_rep);
     if (major_status == GSS_S_COMPLETE ||