diff --git a/Makefile.am b/Makefile.am index 8ed560cb..a56f5bf4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -64,6 +64,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h \ src/crypto.h src/crypto.c \ src/mpdu.h src/mpdu.c \ src/eapol.h src/eapol.c \ + src/handshake.h src/handshake.c \ src/scan.h src/scan.c \ src/util.h src/util.c \ src/common.h src/common.c \ @@ -98,6 +99,7 @@ monitor_iwmon_SOURCES = monitor/main.c linux/nl80211.h \ src/util.h src/util.c \ src/crypto.h src/crypto.c \ src/eapol.h src/eapol.c \ + src/handshake.h src/handshake.c \ src/eap.h src/eap.c \ src/eap-tls.c src/eap-ttls.c monitor_iwmon_LDADD = ell/libell-internal.la @@ -173,6 +175,7 @@ unit_test_eapol_SOURCES = unit/test-eapol.c \ src/crypto.h src/crypto.c \ src/ie.h src/ie.c \ src/eapol.h src/eapol.c \ + src/handshake.h src/handshake.c \ src/eap.h src/eap.c \ src/eap-tls.c src/eap-ttls.c unit_test_eapol_LDADD = ell/libell-internal.la @@ -189,6 +192,7 @@ unit_test_wsc_SOURCES = unit/test-wsc.c src/wscutil.h src/wscutil.c \ src/crypto.h src/crypto.c \ src/ie.h src/ie.c \ src/eapol.h src/eapol.c \ + src/handshake.h src/handshake.c \ src/eap.h src/eap.c \ src/util.h src/util.c \ src/eap-wsc.h src/eap-wsc.c diff --git a/src/handshake.c b/src/handshake.c new file mode 100644 index 00000000..582693e4 --- /dev/null +++ b/src/handshake.c @@ -0,0 +1,496 @@ +/* + * + * 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 +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "ie.h" +#include "util.h" +#include "handshake.h" + +static bool handshake_get_nonce(uint8_t nonce[]) +{ + return l_getrandom(nonce, 32); +} + +static handshake_get_nonce_func_t get_nonce = handshake_get_nonce; +static handshake_install_tk_func_t install_tk = NULL; +static handshake_install_gtk_func_t install_gtk = NULL; +static handshake_install_igtk_func_t install_igtk = NULL; + +void __handshake_set_get_nonce_func(handshake_get_nonce_func_t func) +{ + get_nonce = func; +} + +void __handshake_set_install_tk_func(handshake_install_tk_func_t func) +{ + install_tk = func; +} + +void __handshake_set_install_gtk_func(handshake_install_gtk_func_t func) +{ + install_gtk = func; +} + +void __handshake_set_install_igtk_func(handshake_install_igtk_func_t func) +{ + install_igtk = func; +} + +struct handshake_state *handshake_state_new(uint32_t ifindex) +{ + struct handshake_state *s; + + s = l_new(struct handshake_state, 1); + + s->ifindex = ifindex; + + return s; +} + +void handshake_state_free(struct handshake_state *s) +{ + l_free(s->ap_ie); + l_free(s->own_ie); + l_free(s->mde); + l_free(s->fte); + + l_free(s); +} + +void handshake_state_set_supplicant_address(struct handshake_state *s, + const uint8_t *spa) +{ + memcpy(s->spa, spa, sizeof(s->spa)); +} + +void handshake_state_set_authenticator_address(struct handshake_state *s, + const uint8_t *aa) +{ + memcpy(s->aa, aa, sizeof(s->aa)); +} + +void handshake_state_set_pmk(struct handshake_state *s, const uint8_t *pmk) +{ + memcpy(s->pmk, pmk, sizeof(s->pmk)); + s->have_pmk = true; +} + +void handshake_state_set_8021x_config(struct handshake_state *s, + struct l_settings *settings) +{ + s->settings_8021x = settings; +} + +static void handshake_state_set_ap_ie(struct handshake_state *s, + const uint8_t *ie, bool is_wpa) +{ + l_free(s->ap_ie); + s->ap_ie = l_memdup(ie, ie[1] + 2u); + s->wpa_ie = is_wpa; +} + +static void handshake_state_set_own_ie(struct handshake_state *s, + const uint8_t *ie, bool is_wpa) +{ + l_free(s->own_ie); + s->own_ie = l_memdup(ie, ie[1] + 2u); + s->wpa_ie = is_wpa; +} + +void handshake_state_set_ap_rsn(struct handshake_state *s, + const uint8_t *rsn_ie) +{ + handshake_state_set_ap_ie(s, rsn_ie, false); +} + +static bool handshake_state_setup_own_ciphers(struct handshake_state *s, + const struct ie_rsn_info *info) +{ + if (__builtin_popcount(info->pairwise_ciphers) != 1) + return false; + + if (__builtin_popcount(info->akm_suites) != 1) + return false; + + s->akm_suite = info->akm_suites; + s->pairwise_cipher = info->pairwise_ciphers; + s->group_cipher = info->group_cipher; + s->group_management_cipher = info->group_management_cipher; + s->mfp = info->mfpc; + + return true; +} + +bool handshake_state_set_own_rsn(struct handshake_state *s, + const uint8_t *rsn_ie) +{ + struct ie_rsn_info info; + + handshake_state_set_own_ie(s, rsn_ie, false); + + if (ie_parse_rsne_from_data(rsn_ie, rsn_ie[1] + 2, &info) < 0) + return false; + + return handshake_state_setup_own_ciphers(s, &info); +} + +void handshake_state_set_ap_wpa(struct handshake_state *s, + const uint8_t *wpa_ie) +{ + handshake_state_set_ap_ie(s, wpa_ie, true); +} + +bool handshake_state_set_own_wpa(struct handshake_state *s, + const uint8_t *wpa_ie) +{ + struct ie_rsn_info info; + + handshake_state_set_own_ie(s, wpa_ie, true); + + if (ie_parse_wpa_from_data(wpa_ie, wpa_ie[1] + 2, &info) < 0) + return false; + + return handshake_state_setup_own_ciphers(s, &info); +} + +void handshake_state_set_user_data(struct handshake_state *s, void *user_data) +{ + s->user_data = user_data; +} + +void handshake_state_set_ssid(struct handshake_state *s, const uint8_t *ssid, + size_t ssid_len) +{ + memcpy(s->ssid, ssid, ssid_len); + s->ssid_len = ssid_len; +} + +void handshake_state_set_mde(struct handshake_state *s, const uint8_t *mde) +{ + if (s->mde) + l_free(s->mde); + + s->mde = mde ? l_memdup(mde, mde[1] + 2) : NULL; +} + +void handshake_state_set_fte(struct handshake_state *s, const uint8_t *fte) +{ + if (s->fte) + l_free(s->fte); + + s->fte = fte ? l_memdup(fte, fte[1] + 2) : NULL; +} + +void handshake_state_set_kh_ids(struct handshake_state *s, + const uint8_t *r0khid, size_t r0khid_len, + const uint8_t *r1khid) +{ + memcpy(s->r0khid, r0khid, r0khid_len); + s->r0khid_len = r0khid_len; + + memcpy(s->r1khid, r1khid, 6); +} + +void handshake_state_new_snonce(struct handshake_state *s) +{ + get_nonce(s->snonce); + + s->have_snonce = true; +} + +void handshake_state_set_anonce(struct handshake_state *s, + const uint8_t *anonce) +{ + memcpy(s->anonce, anonce, 32); +} + +bool handshake_state_derive_ptk(struct handshake_state *s) +{ + struct crypto_ptk *ptk = (struct crypto_ptk *) s->ptk; + enum crypto_cipher cipher; + size_t ptk_size; + bool use_sha256; + + if (!s->have_snonce || !s->have_pmk) + return false; + + if ((s->akm_suite & (IE_RSN_AKM_SUITE_FT_OVER_8021X | + IE_RSN_AKM_SUITE_FT_USING_PSK | + IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256)) && + (!s->mde || !s->fte)) + return false; + + s->ptk_complete = false; + + if (s->akm_suite & (IE_RSN_AKM_SUITE_8021X_SHA256 | + IE_RSN_AKM_SUITE_PSK_SHA256 | + IE_RSN_AKM_SUITE_SAE_SHA256 | + IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256)) + use_sha256 = true; + else + use_sha256 = false; + + cipher = ie_rsn_cipher_suite_to_cipher(s->pairwise_cipher); + + ptk_size = sizeof(struct crypto_ptk) + crypto_cipher_key_len(cipher); + + if (s->akm_suite & (IE_RSN_AKM_SUITE_FT_OVER_8021X | + IE_RSN_AKM_SUITE_FT_USING_PSK | + IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256)) { + uint16_t mdid; + uint8_t ptk_name[16]; + + ie_parse_mobility_domain_from_data(s->mde, s->mde[1] + 2, + &mdid, NULL, NULL); + + if (!crypto_derive_pmk_r0(s->pmk, s->ssid, s->ssid_len, mdid, + s->r0khid, s->r0khid_len, + s->spa, + s->pmk_r0, s->pmk_r0_name)) + return false; + + if (!crypto_derive_pmk_r1(s->pmk_r0, s->r1khid, s->spa, + s->pmk_r0_name, + s->pmk_r1, s->pmk_r1_name)) + return false; + + if (!crypto_derive_ft_ptk(s->pmk_r1, s->pmk_r1_name, s->aa, + s->spa, s->snonce, s->anonce, + ptk, ptk_size, ptk_name)) + return false; + } else + if (!crypto_derive_pairwise_ptk(s->pmk, s->spa, s->aa, + s->anonce, s->snonce, + ptk, ptk_size, use_sha256)) + return false; + + return true; +} + +const struct crypto_ptk *handshake_state_get_ptk(struct handshake_state *s) +{ + return (struct crypto_ptk *) s->ptk; +} + +void handshake_state_install_ptk(struct handshake_state *s) +{ + struct crypto_ptk *ptk = (struct crypto_ptk *) s->ptk; + + s->ptk_complete = true; + + if (install_tk) { + uint32_t cipher = ie_rsn_cipher_suite_to_cipher( + s->pairwise_cipher); + + install_tk(s->ifindex, s->aa, ptk->tk, cipher, s->user_data); + } +} + +void handshake_state_install_gtk(struct handshake_state *s, + uint8_t gtk_key_index, + const uint8_t *gtk, size_t gtk_len, + const uint8_t *rsc, uint8_t rsc_len) +{ + if (install_gtk) { + uint32_t cipher = + ie_rsn_cipher_suite_to_cipher(s->group_cipher); + + install_gtk(s->ifindex, gtk_key_index, gtk, gtk_len, + rsc, rsc_len, cipher, s->user_data); + } +} + +void handshake_state_install_igtk(struct handshake_state *s, + uint8_t igtk_key_index, + const uint8_t *igtk, size_t igtk_len) +{ + if (install_igtk) { + uint32_t cipher = + ie_rsn_cipher_suite_to_cipher( + s->group_management_cipher); + + install_igtk(s->ifindex, igtk_key_index, igtk + 6, + igtk_len - 6, igtk, 6, cipher, + s->user_data); + } +} + +void handshake_state_override_pairwise_cipher(struct handshake_state *s, + enum ie_rsn_cipher_suite pairwise) +{ + s->pairwise_cipher = pairwise; +} + +/* + * This function performs a match of the RSN/WPA IE obtained from the scan + * results vs the RSN/WPA IE obtained as part of the 4-way handshake. If they + * don't match, the EAPoL packet must be silently discarded. + */ +bool handshake_util_ap_ie_matches(const uint8_t *msg_ie, + const uint8_t *scan_ie, bool is_wpa) +{ + struct ie_rsn_info msg_info; + struct ie_rsn_info scan_info; + + /* + * First check that the sizes match, if they do, run a bitwise + * comparison. + */ + if (msg_ie[1] == scan_ie[1] && + !memcmp(msg_ie + 2, scan_ie + 2, msg_ie[1])) + return true; + + /* + * Otherwise we have to parse the IEs and compare the individual + * fields + */ + if (!is_wpa) { + if (ie_parse_rsne_from_data(msg_ie, msg_ie[1] + 2, + &msg_info) < 0) + return false; + + if (ie_parse_rsne_from_data(scan_ie, scan_ie[1] + 2, + &scan_info) < 0) + return false; + } else { + if (ie_parse_wpa_from_data(msg_ie, msg_ie[1] + 2, + &msg_info) < 0) + return false; + + if (ie_parse_wpa_from_data(scan_ie, scan_ie[1] + 2, + &scan_info) < 0) + return false; + } + + if (msg_info.group_cipher != scan_info.group_cipher) + return false; + + if (msg_info.pairwise_ciphers != scan_info.pairwise_ciphers) + return false; + + if (msg_info.akm_suites != scan_info.akm_suites) + return false; + + if (msg_info.preauthentication != scan_info.preauthentication) + return false; + + if (msg_info.no_pairwise != scan_info.no_pairwise) + return false; + + if (msg_info.ptksa_replay_counter != scan_info.ptksa_replay_counter) + return false; + + if (msg_info.gtksa_replay_counter != scan_info.gtksa_replay_counter) + return false; + + if (msg_info.mfpr != scan_info.mfpr) + return false; + + if (msg_info.mfpc != scan_info.mfpc) + return false; + + if (msg_info.peerkey_enabled != scan_info.peerkey_enabled) + return false; + + if (msg_info.spp_a_msdu_capable != scan_info.spp_a_msdu_capable) + return false; + + if (msg_info.spp_a_msdu_required != scan_info.spp_a_msdu_required) + return false; + + if (msg_info.pbac != scan_info.pbac) + return false; + + if (msg_info.extended_key_id != scan_info.extended_key_id) + return false; + + /* We don't check the PMKIDs since these might actually be different */ + + if (msg_info.group_management_cipher != + scan_info.group_management_cipher) + return false; + + return true; +} + +static const uint8_t *find_kde(const uint8_t *data, size_t data_len, + size_t *out_len, const unsigned char *oui) +{ + struct ie_tlv_iter iter; + const uint8_t *result; + unsigned int len; + + ie_tlv_iter_init(&iter, data, data_len); + + while (ie_tlv_iter_next(&iter)) { + if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC) + continue; + + len = ie_tlv_iter_get_length(&iter); + if (len < 4) /* Take care of padding */ + return NULL; + + /* Check OUI */ + result = ie_tlv_iter_get_data(&iter); + if (memcmp(result, oui, 4)) + continue; + + if (out_len) + *out_len = len - 4; + + return result + 4; + } + + return NULL; +} + +const uint8_t *handshake_util_find_gtk_kde(const uint8_t *data, size_t data_len, + size_t *out_gtk_len) +{ + static const unsigned char gtk_oui[] = { 0x00, 0x0f, 0xac, 0x01 }; + + return find_kde(data, data_len, out_gtk_len, gtk_oui); +} + +const uint8_t *handshake_util_find_igtk_kde(const uint8_t *data, + size_t data_len, + size_t *out_igtk_len) +{ + static const unsigned char igtk_oui[] = { 0x00, 0x0f, 0xac, 0x09 }; + + return find_kde(data, data_len, out_igtk_len, igtk_oui); +} diff --git a/src/handshake.h b/src/handshake.h new file mode 100644 index 00000000..8f93be42 --- /dev/null +++ b/src/handshake.h @@ -0,0 +1,137 @@ +/* + * + * 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 + +typedef bool (*handshake_get_nonce_func_t)(uint8_t nonce[]); +typedef void (*handshake_install_tk_func_t)(uint32_t ifindex, const uint8_t *aa, + const uint8_t *tk, uint32_t cipher, + void *user_data); +typedef void (*handshake_install_gtk_func_t)(uint32_t ifindex, + uint8_t key_index, + const uint8_t *gtk, uint8_t gtk_len, + const uint8_t *rsc, uint8_t rsc_len, + uint32_t cipher, void *user_data); +typedef void (*handshake_install_igtk_func_t)(uint32_t ifindex, + uint8_t key_index, + const uint8_t *igtk, uint8_t igtk_len, + const uint8_t *ipn, uint8_t ipn_len, + uint32_t cipher, void *user_data); + +void __handshake_set_get_nonce_func(handshake_get_nonce_func_t func); +void __handshake_set_install_tk_func(handshake_install_tk_func_t func); +void __handshake_set_install_gtk_func(handshake_install_gtk_func_t func); +void __handshake_set_install_igtk_func(handshake_install_igtk_func_t func); + +struct handshake_state { + uint32_t ifindex; + uint8_t spa[6]; + uint8_t aa[6]; + uint8_t *ap_ie; + uint8_t *own_ie; + uint8_t *mde; + uint8_t *fte; + enum ie_rsn_cipher_suite pairwise_cipher; + enum ie_rsn_cipher_suite group_cipher; + enum ie_rsn_cipher_suite group_management_cipher; + enum ie_rsn_akm_suite akm_suite; + uint8_t pmk[32]; + uint8_t snonce[32]; + uint8_t anonce[32]; + uint8_t ptk[64]; + uint8_t pmk_r0[32]; + uint8_t pmk_r0_name[16]; + uint8_t pmk_r1[32]; + uint8_t pmk_r1_name[16]; + struct l_settings *settings_8021x; + bool have_snonce : 1; + bool ptk_complete : 1; + bool wpa_ie : 1; + bool have_pmk : 1; + bool mfp : 1; + uint8_t ssid[32]; + size_t ssid_len; + uint8_t r0khid[48]; + size_t r0khid_len; + uint8_t r1khid[6]; + void *user_data; +}; + +struct handshake_state *handshake_state_new(uint32_t ifindex); +void handshake_state_free(struct handshake_state *s); + +void handshake_state_set_supplicant_address(struct handshake_state *s, + const uint8_t *spa); +void handshake_state_set_authenticator_address(struct handshake_state *s, + const uint8_t *aa); +void handshake_state_set_user_data(struct handshake_state *s, void *user_data); +void handshake_state_set_pmk(struct handshake_state *s, const uint8_t *pmk); +void handshake_state_set_8021x_config(struct handshake_state *s, + struct l_settings *settings); +void handshake_state_set_ap_rsn(struct handshake_state *s, + const uint8_t *rsn_ie); +bool handshake_state_set_own_rsn(struct handshake_state *s, + const uint8_t *rsn_ie); +void handshake_state_set_ap_wpa(struct handshake_state *s, + const uint8_t *wpa_ie); +bool handshake_state_set_own_wpa(struct handshake_state *s, + const uint8_t *wpa_ie); +void handshake_state_set_ssid(struct handshake_state *s, + const uint8_t *ssid, size_t ssid_len); +void handshake_state_set_mde(struct handshake_state *s, + const uint8_t *mde); +void handshake_state_set_fte(struct handshake_state *s, const uint8_t *fte); + +void handshake_state_set_kh_ids(struct handshake_state *s, + const uint8_t *r0khid, size_t r0khid_len, + const uint8_t *r1khid); + +void handshake_state_new_snonce(struct handshake_state *s); +void handshake_state_set_anonce(struct handshake_state *s, + const uint8_t *anonce); + +bool handshake_state_derive_ptk(struct handshake_state *s); +const struct crypto_ptk *handshake_state_get_ptk(struct handshake_state *s); +void handshake_state_install_ptk(struct handshake_state *s); + +void handshake_state_install_gtk(struct handshake_state *s, + uint8_t gtk_key_index, + const uint8_t *gtk, size_t gtk_len, + const uint8_t *rsc, uint8_t rsc_len); + +void handshake_state_install_igtk(struct handshake_state *s, + uint8_t igtk_key_index, + const uint8_t *igtk, size_t igtk_len); + +void handshake_state_override_pairwise_cipher(struct handshake_state *s, + enum ie_rsn_cipher_suite pairwise); + +bool handshake_util_ap_ie_matches(const uint8_t *msg_ie, + const uint8_t *scan_ie, bool is_wpa); + +const uint8_t *handshake_util_find_gtk_kde(const uint8_t *data, size_t data_len, + size_t *out_gtk_len); +const uint8_t *handshake_util_find_igtk_kde(const uint8_t *data, + size_t data_len, size_t *out_igtk_len);