Blame SOURCES/Refactor-krb5-GSS-checksum-handling.patch

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