diff --git a/Makefile.am b/Makefile.am index fb123c63..d90b77d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -69,6 +69,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h linux/kdbus.h \ src/network.h src/network.c \ src/wscutil.h src/wscutil.c \ src/wsc.h src/wsc.c \ + src/eap.h src/eap.c \ src/iwd.h src_iwd_LDADD = ell/libell-internal.la @@ -86,7 +87,8 @@ monitor_iwmon_SOURCES = monitor/main.c linux/nl80211.h \ src/util.h src/util.c \ src/sha1.h src/sha1.c \ src/crypto.h src/crypto.c \ - src/eapol.h src/eapol.c + src/eapol.h src/eapol.c \ + src/eap.h src/eap.c monitor_iwmon_LDADD = ell/libell-internal.la noinst_PROGRAMS = tools/hwsim @@ -153,7 +155,8 @@ unit_test_eapol_SOURCES = unit/test-eapol.c \ src/sha1.h src/sha1.c \ src/crypto.h src/crypto.c \ src/ie.h src/ie.c \ - src/eapol.h src/eapol.c + src/eapol.h src/eapol.c \ + src/eap.h src/eap.c unit_test_eapol_LDADD = ell/libell-internal.la unit_test_ssid_to_utf8_SOURCES = src/util.h src/util.c \ diff --git a/src/eap.c b/src/eap.c new file mode 100644 index 00000000..35090cdb --- /dev/null +++ b/src/eap.c @@ -0,0 +1,409 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "eap.h" + +struct l_queue *eap_methods; + +struct eap_state { + eap_tx_packet_func_t tx_packet; + eap_key_material_func_t set_key_material; + eap_complete_func_t complete; + void *user_data; + size_t mtu; + + struct eap_method *method; + char *identity; + + int last_id; + void *method_state; + bool method_success; + struct l_timeout *complete_timeout; +}; + +struct eap_state *eap_new(eap_tx_packet_func_t tx_packet, + eap_complete_func_t complete, void *user_data) +{ + struct eap_state *eap; + + eap = l_new(struct eap_state, 1); + + eap->last_id = -1; + eap->mtu = 1024; + + eap->tx_packet = tx_packet; + eap->complete = complete; + eap->user_data = user_data; + + return eap; +} + +/* + * Setting a non-NULL set_key_material callback for this EAP instance will + * disable the legacy methods that don't generate key material, such + * as EAP-MD5. + */ +void eap_set_key_material_func(struct eap_state *eap, + eap_key_material_func_t func) +{ + eap->set_key_material = func; +} + +void eap_free(struct eap_state *eap) +{ + if (eap->method_state) + eap->method->remove(eap); + + if (eap->identity) + l_free(eap->identity); + + l_timeout_remove(eap->complete_timeout); + + l_free(eap); +} + +/* Note: callers must check for a minimum value */ +void eap_set_mtu(struct eap_state *eap, size_t mtu) +{ + eap->mtu = mtu; +} + +size_t eap_get_mtu(struct eap_state *eap) +{ + return eap->mtu; +} + +void eap_send_response(struct eap_state *eap, + enum eap_request_type request_type, + uint8_t *buf, size_t len) +{ + buf[0] = EAP_CODE_RESPONSE; + buf[1] = eap->last_id; + l_put_be16(len, &buf[2]); + buf[4] = request_type; + + eap->tx_packet(buf, len, eap->user_data); +} + +static void eap_complete_timeout(struct l_timeout *timeout, void *user_data) +{ + struct eap_state *eap = user_data; + + eap->complete_timeout = NULL; + + eap->complete(eap->method_success ? EAP_RESULT_SUCCESS : + EAP_RESULT_TIMEOUT, eap->user_data); +} + +void eap_start_complete_timeout(struct eap_state *eap) +{ + if (eap->complete_timeout) + l_timeout_remove(eap->complete_timeout); + + eap->complete_timeout = l_timeout_create(5, eap_complete_timeout, + eap, NULL); +} + +static void eap_send_identity_response(struct eap_state *eap, char *identity) +{ + int len = identity ? strlen(identity) : 0; + uint8_t buf[5 + len]; + + if (!identity) + identity = ""; + + memcpy(buf + 5, identity, len); + + eap_send_response(eap, EAP_TYPE_IDENTITY, buf, len + 5); +} + +static void eap_handle_request(struct eap_state *eap, + const uint8_t *pkt, size_t len) +{ + enum eap_request_type type; + uint8_t buf[10]; + int buf_len; + + if (len < 1) + /* Invalid packets to be ignored */ + return; + + type = pkt[0]; + + if (type >= __EAP_TYPE_MIN_METHOD) { + if (!eap->method || type != eap->method->request_type) { + l_warn("EAP server tried method %i while client was " + "configured for method %i", + type, eap->method->request_type); + + goto unsupported_method; + } + + eap->method->handle_request(eap, pkt + 1, len - 1); + + return; + } + + switch (type) { + case EAP_TYPE_IDENTITY: + if (len >= 2) + l_warn("EAP identity prompt: \"%.*s\"", + (int) len - 1, buf + 1); + + eap_send_identity_response(eap, eap->identity); + + return; + + case EAP_TYPE_NOTIFICATION: + if (len < 2) + /* Invalid packets to be ignored */ + return; + + l_warn("EAP notification: \"%.*s\"", (int) len - 1, buf + 1); + + eap_send_response(eap, EAP_TYPE_NOTIFICATION, buf, 5); + + return; + + default: + unsupported_method: + /* Send a legacy NAK response */ + buf_len = 5; + + buf[buf_len++] = eap->method ? eap->method->request_type : 0; + + eap_send_response(eap, EAP_TYPE_NAK, buf, buf_len); + return; + } +} + +void eap_rx_packet(struct eap_state *eap, const uint8_t *pkt, size_t len) +{ + uint8_t code, id; + uint16_t eap_len; + + if (len < 4 || l_get_be16(&pkt[2]) < 4 || len < l_get_be16(&pkt[2])) + /* Invalid packets to be silently discarded */ + return; + + code = pkt[0]; + id = pkt[1]; + eap_len = l_get_be16(&pkt[2]); + + switch ((enum eap_pkt_code) code) { + case EAP_CODE_REQUEST: + if (id == eap->last_id) { + /* + * We should resend the last response if this ever + * happens and if one was sent already. This can + * happen if the request_credentials callback needs + * to wait for user input. We can assume a reliable + * lower layer but the retransmission behaviour is + * decided by the authenticator (Section 4.3). + */ + + return; + } + + eap->last_id = id; + + eap_handle_request(eap, pkt + 4, eap_len - 4); + return; + + case EAP_CODE_FAILURE: + case EAP_CODE_SUCCESS: + l_timeout_remove(eap->complete_timeout); + eap->complete_timeout = NULL; + + /* Section 4.2 */ + + if (id != eap->last_id) + return; + + if (eap_len != 4) + /* Invalid packets to be silently discarded */ + return; + + if (code == EAP_CODE_SUCCESS && !eap->method_success) + /* "Canned" success packets to be discarded */ + return; + + if (code == EAP_CODE_FAILURE && eap->method_success) + /* + * "On the peer, after success result indications have + * been exchanged by both sides, a Failure packet MUST + * be silently discarded." + * + * "Where the peer authenticates successfully to the + * authenticator, but the authenticator does not send + * a result indication, the authenticator MAY deny + * access by sending a Failure packet where the peer + * is not currently authorized for network access." + * -- eap->method_success implies we've received + * a full result indication. + */ + return; + + if (eap->method_state) + eap->method->remove(eap); + + eap->method = NULL; + + eap->complete(code == EAP_CODE_SUCCESS ? EAP_RESULT_SUCCESS : + EAP_RESULT_FAIL, eap->user_data); + return; + + default: + /* Invalid packets to be silently discarded */ + return; + } +} + +bool eap_load_settings(struct eap_state *eap, struct l_settings *settings, + const char *prefix) +{ + char setting[64]; + const char *method_name; + const struct l_queue_entry *entry; + struct eap_method *method; + + snprintf(setting, sizeof(setting), "%sMethod", prefix); + method_name = l_settings_get_value(settings, "Security", setting); + + if (!method_name) + return false; + + for (entry = l_queue_get_entries(eap_methods); entry; + entry = entry->next) { + method = entry->data; + + if (method->probe(eap, method_name) == 0) { + eap->method = method; + + break; + } + } + + if (!eap->method) + return false; + + /* Check if selected method is suitable for 802.1x */ + if (eap->set_key_material && !eap->method->exports_msk) { + l_error("EAP method \"%s\" doesn't export key material", + method_name); + + goto err; + } + + snprintf(setting, sizeof(setting), "%sIdentity", prefix); + eap->identity = l_strdup(l_settings_get_value(settings, + "Security", setting)); + if (!eap->identity) { + l_error("EAP Identity is missing"); + + goto err; + } + + if (!eap->method->load_settings) + return true; + + if (!eap->method->load_settings(eap, settings, prefix)) + goto err; + + return true; + +err: + if (eap->method->remove) + eap->method->remove(eap); + + eap->method = NULL; + + return false; +} + +void eap_set_data(struct eap_state *eap, void *data) +{ + eap->method_state = data; +} + +void *eap_get_data(struct eap_state *eap) +{ + return eap->method_state; +} + +void eap_set_key_material(struct eap_state *eap, + const uint8_t *msk_data, size_t msk_len, + const uint8_t *emsk_data, size_t emsk_len, + const uint8_t *iv, size_t iv_len) +{ + if (!eap->set_key_material) + return; + + eap->set_key_material(msk_data, msk_len, emsk_data, emsk_len, + iv, iv_len, eap->user_data); +} + +void eap_method_success(struct eap_state *eap) +{ + eap->method_success = true; +} + +void eap_method_error(struct eap_state *eap) +{ + /* + * It looks like neither EAP nor EAP-TLS specify the error handling + * behavior. + */ + eap->complete(EAP_RESULT_FAIL, eap->user_data); +} + +void eap_save_last_id(struct eap_state *eap, uint8_t *last_id) +{ + *last_id = eap->last_id; +} + +void eap_restore_last_id(struct eap_state *eap, uint8_t last_id) +{ + eap->last_id = last_id; +} + +static void eap_register_method(struct eap_method *method) +{ + l_queue_push_head(eap_methods, method); +} + +void eap_init(void) { + eap_methods = l_queue_new(); +} + +void eap_exit(void) { + l_queue_destroy(eap_methods, NULL); +} diff --git a/src/eap.h b/src/eap.h new file mode 100644 index 00000000..e64ac32c --- /dev/null +++ b/src/eap.h @@ -0,0 +1,116 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2013-2014 Intel Corporation. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include + +struct eap_state; + +enum eap_result { + EAP_RESULT_SUCCESS, + EAP_RESULT_FAIL, + EAP_RESULT_TIMEOUT, +}; + +typedef void (*eap_tx_packet_func_t)(const uint8_t *eap_data, size_t len, + void *user_data); +typedef void (*eap_key_material_func_t)(const uint8_t *msk_data, size_t msk_len, + const uint8_t *emsk_data, size_t emsk_len, + const uint8_t *iv, size_t iv_len, + void *user_data); +typedef void (*eap_complete_func_t)(enum eap_result result, void *user_data); + +struct eap_state *eap_new(eap_tx_packet_func_t tx_packet, + eap_complete_func_t complete, void *user_data); +void eap_free(struct eap_state *eap); + +bool eap_load_settings(struct eap_state *eap, struct l_settings *settings, + const char *prefix); + +void eap_set_key_material_func(struct eap_state *eap, + eap_key_material_func_t func); + +void eap_set_mtu(struct eap_state *eap, size_t mtu); +size_t eap_get_mtu(struct eap_state *eap); + +void eap_rx_packet(struct eap_state *eap, const uint8_t *pkt, size_t len); + +void eap_init(void); +void eap_exit(void); + +/* EAP method API */ + +enum eap_request_type { + EAP_TYPE_IDENTITY = 1, + EAP_TYPE_NOTIFICATION = 2, + EAP_TYPE_NAK = 3, + __EAP_TYPE_MIN_METHOD = 4, + EAP_TYPE_MD5_CHALLENGE = 4, + EAP_TYPE_TLS_EAP = 13, + EAP_TYPE_TTLS = 21, + EAP_TYPE_EXPANDED = 254, +}; + +enum eap_pkt_code { + EAP_CODE_REQUEST = 1, + EAP_CODE_RESPONSE = 2, + EAP_CODE_SUCCESS = 3, + EAP_CODE_FAILURE = 4, +}; + +struct eap_method { + enum eap_request_type request_type; + bool exports_msk; + const char *name; + + int (*probe)(struct eap_state *eap, const char *method_string); + void (*remove)(struct eap_state *eap); + + bool (*load_settings)(struct eap_state *eap, + struct l_settings *settings, + const char *prefix); + + void (*handle_request)(struct eap_state *eap, + const uint8_t *pkt, size_t len); +}; + +void eap_set_data(struct eap_state *eap, void *data); +void *eap_get_data(struct eap_state *eap); + +void eap_send_response(struct eap_state *eap, + enum eap_request_type request_type, + uint8_t *buf, size_t len); + +void eap_set_key_material(struct eap_state *eap, + const uint8_t *msk_data, size_t msk_len, + const uint8_t *emsk_data, size_t emsk_len, + const uint8_t *iv, size_t iv_len); + +void eap_start_complete_timeout(struct eap_state *eap); + +void eap_method_success(struct eap_state *eap); +void eap_method_error(struct eap_state *eap); + +void eap_save_last_id(struct eap_state *eap, uint8_t *last_id); +void eap_restore_last_id(struct eap_state *eap, uint8_t last_id);