EAP Channel binding support
[mech_eap.git] / libeap / src / eap_peer / eap_ttls.c
index 2573780..70827e4 100644 (file)
@@ -15,6 +15,7 @@
 #include "includes.h"
 
 #include "common.h"
+#include "radius/radius.h"
 #include "crypto/ms_funcs.h"
 #include "crypto/sha1.h"
 #include "crypto/tls.h"
@@ -73,7 +74,9 @@ struct eap_ttls_data {
        u8 *key_data;
 
        struct wpabuf *pending_phase2_req;
-
+       int chbind_req_sent; /* channel binding request was sent */
+       int done_butfor_cb; /*we turned METHOD_DONE into METHOD_MAY_CONT to receive cb*/
+  EapDecision cbDecision;
 #ifdef EAP_TNC
        int ready_for_tnc;
        int tnc_started;
@@ -81,6 +84,23 @@ struct eap_ttls_data {
 };
 
 
+/* draft-ietf-emu-chbind-13 section 5.3 */
+
+#ifdef _MSC_VER
+#pragma pack(push, 1)
+#endif /* _MSC_VER */
+
+struct chbind_hdr {
+       u16 len;
+       u8 nsid;
+} STRUCT_PACKED;
+
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif /* _MSC_VER */
+
+
+
 static void * eap_ttls_init(struct eap_sm *sm)
 {
        struct eap_ttls_data *data;
@@ -245,6 +265,48 @@ static int eap_ttls_avp_encapsulate(struct wpabuf **resp, u32 avp_code,
        return 0;
 }
 
+/* chop up resp into multiple vsa's as necessary*/
+static int eap_ttls_avp_radius_vsa_encapsulate(struct wpabuf **resp, u32 vendor,
+                                       u8 attr, int mandatory)
+{
+       struct wpabuf *msg;
+       u8 *avp, *pos, *src, *final;
+       size_t size = wpabuf_len(*resp);
+       size_t num_msgs = 1 + (size / 248);
+       size_t msg_wrapper_size = sizeof(struct ttls_avp_vendor) + 6;
+       size_t allocated_total = num_msgs * (4 + msg_wrapper_size) + size;
+
+       msg = wpabuf_alloc(allocated_total);
+       if (msg == NULL) {
+               wpabuf_free(*resp);
+               *resp = NULL;
+               return -1;
+       }
+       src = wpabuf_mhead(*resp);
+       avp = wpabuf_mhead(msg);
+       while (size > 0) {
+               int avp_size = size > 248 ? 248 : size;
+               size -= avp_size;
+               pos = eap_ttls_avp_hdr(avp, RADIUS_ATTR_VENDOR_SPECIFIC, 0, mandatory,
+                                      avp_size+6);
+               wpabuf_put(msg, pos-avp);
+               wpabuf_put_be32(msg, vendor);
+               wpabuf_put_u8(msg, (u8) attr);
+               wpabuf_put_u8(msg, (u8) avp_size+2);
+               wpabuf_put_data(msg, src, avp_size);
+               src += avp_size;
+               pos = wpabuf_mhead_u8(msg) + wpabuf_len(msg);
+               final = pos; /*keep pos so we know how much padding is added*/
+               AVP_PAD(avp, final); /*final modified*/
+               if (final > pos)
+                       wpabuf_put(msg, final-pos);
+               avp = final;
+       }
+       /* check avp-wpabuf_mhead(msg) < allocated_total */
+       wpabuf_free(*resp);
+       *resp = msg;
+       return 0;
+}
 
 #if EAP_TTLS_VERSION > 0
 static int eap_ttls_ia_permute_inner_secret(struct eap_sm *sm,
@@ -1061,6 +1123,8 @@ struct ttls_parse_avp {
        u8 *mschapv2;
        u8 *eapdata;
        size_t eap_len;
+       u8 *chbind_data;
+       size_t chbind_len;
        int mschapv2_error;
 };
 
@@ -1094,6 +1158,38 @@ static int eap_ttls_parse_attr_eap(const u8 *dpos, size_t dlen,
 }
 
 
+static int eap_ttls_parse_attr_chbind(const u8 *dpos, size_t dlen,
+                                  struct ttls_parse_avp *parse)
+{
+       wpa_printf(MSG_DEBUG, "EAP-TTLS: AVP - Channel Binding Message");
+
+       if (parse->chbind_data == NULL) {
+               parse->chbind_data = os_malloc(dlen);
+               if (parse->chbind_data == NULL) {
+                       wpa_printf(MSG_WARNING, "EAP-TTLS: Failed to allocate "
+                                  "memory for Phase 2 channel binding data");
+                       return -1;
+               }
+               os_memcpy(parse->chbind_data, dpos, dlen);
+               parse->chbind_len = dlen;
+       } else {
+        /* TODO: can this really happen?  maybe just make this an error? */
+               u8 *newchbind = os_realloc(parse->chbind_data,
+                                          parse->chbind_len + dlen);
+               if (newchbind == NULL) {
+                       wpa_printf(MSG_WARNING, "EAP-TTLS: Failed to allocate "
+                                  "memory for Phase 2 channel binding data");
+                       return -1;
+               }
+               os_memcpy(newchbind + parse->chbind_len, dpos, dlen);
+               parse->chbind_data = newchbind;
+               parse->chbind_len += dlen;
+       }
+
+       return 0;
+}
+
+
 static int eap_ttls_parse_avp(u8 *pos, size_t left,
                              struct ttls_parse_avp *parse)
 {
@@ -1144,6 +1240,11 @@ static int eap_ttls_parse_avp(u8 *pos, size_t left,
        if (vendor_id == 0 && avp_code == RADIUS_ATTR_EAP_MESSAGE) {
                if (eap_ttls_parse_attr_eap(dpos, dlen, parse) < 0)
                        return -1;
+       } else if (vendor_id == RADIUS_VENDOR_ID_UKERNA &&
+                  avp_code == RADIUS_ATTR_UKERNA_CHBIND) {
+               /* message containing channel binding data */
+               if (eap_ttls_parse_attr_chbind(dpos, dlen, parse) < 0)
+                       return -1;
        } else if (vendor_id == 0 && avp_code == RADIUS_ATTR_REPLY_MESSAGE) {
                /* This is an optional message that can be displayed to
                 * the user. */
@@ -1265,6 +1366,100 @@ static int eap_ttls_encrypt_response(struct eap_sm *sm,
        return 0;
 }
 
+static int eap_ttls_add_chbind_request(struct eap_sm *sm,
+                                      struct eap_ttls_data *data,
+                                      struct wpabuf **resp)
+{
+       struct wpabuf *chbind_req, *res;
+       int length = 1, i;
+       struct eap_peer_config *config = eap_get_config(sm);
+
+       if (!config->chbind_config || config->chbind_config_len <= 0)
+               return -1;
+
+       for (i=0; i<config->chbind_config_len; i++) {
+               length += 3 + config->chbind_config[i].req_data_len;
+       }
+
+       chbind_req = wpabuf_alloc(length);
+       if (!chbind_req)
+               return -1;
+
+       wpabuf_put_u8(chbind_req, CHBIND_CODE_REQUEST);
+       for (i=0; i<config->chbind_config_len; i++) {
+               struct eap_peer_chbind_config *chbind_config =
+                       &config->chbind_config[i];
+               wpabuf_put_be16(chbind_req, chbind_config->req_data_len);
+               wpabuf_put_u8(chbind_req, chbind_config->nsid);
+               wpabuf_put_data(chbind_req, chbind_config->req_data,
+                               chbind_config->req_data_len);
+       }
+       if (eap_ttls_avp_radius_vsa_encapsulate(&chbind_req,
+                                        RADIUS_VENDOR_ID_UKERNA,
+                                        RADIUS_ATTR_UKERNA_CHBIND, 0) < 0)
+               return -1;
+
+       /* bleh. This will free *resp regardless of whether combined buffer
+          alloc succeeds, which is not consistent with the other error
+          condition behavior in this function */
+       *resp = wpabuf_concat(chbind_req, *resp);
+
+       return (*resp) ? 0 : -1;
+}
+
+
+static int eap_ttls_process_chbind(struct eap_sm *sm,
+                                  struct eap_ttls_data *data,
+                                  struct eap_method_ret *ret,
+                                  struct ttls_parse_avp *parse,
+                                  struct wpabuf **resp)
+{
+       size_t pos=0;
+       u8 code;
+       u16 len;
+       struct chbind_hdr *hdr;
+       struct eap_peer_config *config = eap_get_config(sm);
+
+       if (parse->chbind_data == NULL) {
+               wpa_printf(MSG_WARNING, "EAP-TTLS: No channel binding message "
+                          "in the packet - dropped");
+               return -1;
+       }
+       if (parse->chbind_len < 1 + sizeof(*hdr)) {
+               wpa_printf(MSG_WARNING, "EAP-TTLS: bad channel binding response "
+                               "frame (len=%lu, expected %lu or more) - dropped",
+                               (unsigned long) parse->chbind_len,
+                               (unsigned long) sizeof(*hdr));
+               return -1;
+       }
+       code = parse->chbind_data[pos++];
+       while (pos+sizeof(*hdr) < parse->chbind_len) {
+               hdr = (struct chbind_hdr *)(&parse->chbind_data[pos]);
+               pos += sizeof(*hdr);
+               len = be_to_host16(hdr->len);
+               if (pos + len <= parse->chbind_len) {
+                       int i;
+                       for (i=0; i<config->chbind_config_len; i++) {
+                               struct eap_peer_chbind_config *chbind_config =
+                                       &config->chbind_config[i];
+                               if (chbind_config->nsid == hdr->nsid)
+                                       chbind_config->response_cb(
+                                               chbind_config->ctx,
+                                               code, hdr->nsid,
+                                               &parse->chbind_data[pos], len);
+                       }
+               }
+               pos += len;
+       }
+       if (pos != parse->chbind_len) {
+               wpa_printf(MSG_WARNING, "EAP-TTLS: bad channel binding response "
+                               "frame (parsed len=%lu, expected %lu) - dropped",
+                               (unsigned long) pos,
+                               (unsigned long) parse->chbind_len);
+               return -1;
+       }
+       return 0;
+}
 
 static int eap_ttls_process_phase2_eap(struct eap_sm *sm,
                                       struct eap_ttls_data *data,
@@ -1438,6 +1633,19 @@ static int eap_ttls_process_decrypted(struct eap_sm *sm,
                phase2_type = EAP_TTLS_PHASE2_EAP;
 #endif /* EAP_TNC */
 
+       /* handle channel binding response here */
+       if (parse->chbind_data) {
+               /* received channel binding repsonse */
+               if (eap_ttls_process_chbind(sm, data, ret, parse, &resp) < 0)
+                       return -1;
+               if (data->done_butfor_cb) {
+                       ret->methodState = METHOD_DONE;
+                       ret->decision = data->cbDecision;
+                       data->phase2_success = 1;
+                       return 1; /*request ack*/
+               }
+       }
+
        switch (phase2_type) {
        case EAP_TTLS_PHASE2_EAP:
                if (eap_ttls_process_phase2_eap(sm, data, ret, parse, &resp) <
@@ -1477,16 +1685,32 @@ static int eap_ttls_process_decrypted(struct eap_sm *sm,
 #endif /* EAP_TNC */
        }
 
+       if (!resp && (config->pending_req_identity ||
+                       config->pending_req_password ||
+                       config->pending_req_otp ||
+                       config->pending_req_new_password)) {
+               wpabuf_free(data->pending_phase2_req);
+               data->pending_phase2_req = wpabuf_dup(in_decrypted);
+               return 0;
+       }
+
+       /* issue channel binding request when appropriate */
+       if (config->chbind_config && config->chbind_config_len > 0 &&
+               !data->chbind_req_sent) {
+               if (eap_ttls_add_chbind_request(sm, data, &resp) < 0)
+                       return -1;
+               data->chbind_req_sent = 1;
+               if (ret->methodState == METHOD_DONE) {
+                       data->done_butfor_cb = 1;
+                       data->cbDecision = ret->decision;
+                       ret->methodState = METHOD_MAY_CONT;
+               }
+       }
+
        if (resp) {
                if (eap_ttls_encrypt_response(sm, data, resp, identifier,
                                              out_data) < 0)
                        return -1;
-       } else if (config->pending_req_identity ||
-                  config->pending_req_password ||
-                  config->pending_req_otp ||
-                  config->pending_req_new_password) {
-               wpabuf_free(data->pending_phase2_req);
-               data->pending_phase2_req = wpabuf_dup(in_decrypted);
        }
 
        return 0;