481 lines
18 KiB
Diff
481 lines
18 KiB
Diff
From 4f14a2f48b52e59c472847a5522fd0cf52927755 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 ||
|