| /* |
| * |
| * Wireless daemon for Linux |
| * |
| * Copyright (C) 2016-2019 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 <config.h> |
| #endif |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <ell/ell.h> |
| |
| #include "src/missing.h" |
| #include "src/crypto.h" |
| #include "src/eap.h" |
| #include "src/eap-private.h" |
| #include "src/wscutil.h" |
| #include "src/util.h" |
| #include "src/eap-wsc.h" |
| |
| #define EAP_WSC_HEADER_LEN 14 |
| #define EAP_WSC_PDU_MAX_LEN 4096 |
| |
| /* WSC v2.0.5, Section 7.7.1 */ |
| enum wsc_op { |
| WSC_OP_START = 0x01, |
| WSC_OP_ACK = 0x02, |
| WSC_OP_NACK = 0x03, |
| WSC_OP_MSG = 0x04, |
| WSC_OP_DONE = 0x05, |
| WSC_OP_FRAG_ACK = 0x06, |
| }; |
| |
| /* WSC v2.0.5, Section 7.7.1 */ |
| enum wsc_flag { |
| WSC_FLAG_MF = 0x01, |
| WSC_FLAG_LF = 0x02, |
| }; |
| |
| enum state { |
| /* Enrollee states */ |
| STATE_EXPECT_START = 0, |
| STATE_EXPECT_M2, |
| STATE_EXPECT_M4, |
| STATE_EXPECT_M6, |
| STATE_EXPECT_M8, |
| STATE_FINISHED, |
| /* Registrar states */ |
| STATE_EXPECT_IDENTITY, |
| STATE_EXPECT_M1, |
| STATE_EXPECT_M3, |
| STATE_EXPECT_M5, |
| STATE_EXPECT_M7, |
| STATE_EXPECT_DONE, |
| }; |
| |
| static struct l_key *dh5_generator; |
| static struct l_key *dh5_prime; |
| |
| struct eap_wsc_state { |
| bool registrar; |
| struct wsc_m1 *m1; |
| struct wsc_m2 *m2; |
| struct wsc_credential wpa2_cred; |
| struct wsc_credential open_cred; |
| uint8_t *sent_pdu; |
| size_t sent_len; |
| struct l_key *private; |
| char *device_password; |
| uint8_t local_snonce1[16]; |
| uint8_t local_snonce2[16]; |
| uint8_t iv1[16]; |
| uint8_t iv2[16]; |
| uint8_t iv3[16]; |
| uint8_t psk1[16]; |
| uint8_t psk2[16]; |
| uint8_t e_hash1[32]; |
| uint8_t e_hash2[32]; |
| uint8_t r_hash2[32]; |
| enum state state; |
| struct l_checksum *hmac_auth_key; |
| struct l_cipher *aes_cbc_128; |
| uint8_t *rx_pdu_buf; |
| size_t rx_pdu_buf_len; |
| size_t rx_pdu_buf_offset; |
| size_t tx_frag_offset; |
| size_t tx_last_frag_len; |
| }; |
| |
| static inline void eap_wsc_state_set_sent_pdu(struct eap_wsc_state *wsc, |
| uint8_t *pdu, size_t len) |
| { |
| l_free(wsc->sent_pdu); |
| wsc->sent_pdu = pdu; |
| wsc->sent_len = len; |
| } |
| |
| static inline bool authenticator_check(struct eap_wsc_state *wsc, |
| const uint8_t *pdu, size_t len) |
| { |
| uint8_t authenticator[8]; |
| struct iovec iov[2]; |
| |
| iov[0].iov_base = wsc->sent_pdu; |
| iov[0].iov_len = wsc->sent_len; |
| iov[1].iov_base = (void *) pdu; |
| iov[1].iov_len = len - 12; |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 2); |
| l_checksum_get_digest(wsc->hmac_auth_key, authenticator, 8); |
| |
| /* Authenticator is the last 8 bytes of the message */ |
| if (memcmp(authenticator, pdu + len - 8, 8)) |
| return false; |
| |
| return true; |
| } |
| |
| static inline void authenticator_put(struct eap_wsc_state *wsc, |
| const uint8_t *prev_msg, |
| size_t prev_msg_len, |
| uint8_t *cur_msg, size_t cur_msg_len) |
| { |
| struct iovec iov[2]; |
| |
| iov[0].iov_base = (void *) prev_msg; |
| iov[0].iov_len = prev_msg_len; |
| iov[1].iov_base = cur_msg; |
| iov[1].iov_len = cur_msg_len - 12; |
| |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 2); |
| l_checksum_get_digest(wsc->hmac_auth_key, cur_msg + cur_msg_len - 8, 8); |
| } |
| |
| static inline bool keywrap_authenticator_check(struct eap_wsc_state *wsc, |
| const uint8_t *pdu, size_t len) |
| { |
| uint8_t authenticator[8]; |
| |
| /* We omit the included KeyWrapAuthenticator element from the hash */ |
| l_checksum_update(wsc->hmac_auth_key, pdu, len - 12); |
| l_checksum_get_digest(wsc->hmac_auth_key, authenticator, 8); |
| |
| /* KeyWrapAuthenticator is the last 8 bytes of the message */ |
| if (memcmp(authenticator, pdu + len - 8, 8)) |
| return false; |
| |
| return true; |
| } |
| |
| static inline void keywrap_authenticator_put(struct eap_wsc_state *wsc, |
| uint8_t *pdu, size_t len) |
| { |
| l_checksum_update(wsc->hmac_auth_key, pdu, len - 12); |
| l_checksum_get_digest(wsc->hmac_auth_key, pdu + len - 8, 8); |
| } |
| |
| static inline bool x_hash_check(struct eap_wsc_state *wsc, |
| uint8_t *x_snonce, |
| uint8_t *psk, |
| uint8_t *x_hash_expected) |
| { |
| struct iovec iov[4]; |
| uint8_t hash[32]; |
| |
| /* |
| * WSC 2.0.5, Section 7.4: |
| * The Enrollee creates two 128-bit secret nonces, E-S1, E-S2 and |
| * then computes |
| * E-Hash1 = HMACAuthKey(E-S1 || PSK1 || PKE || PKR) |
| * E-Hash2 = HMACAuthKey(E-S2 || PSK2 || PKE || PKR) |
| * The Registrar creates two 128-bit secret nonces, R-S1, R-S2 and |
| * then computes |
| * R-Hash1 = HMACAuthKey(R-S1 || PSK1 || PKE || PKR) |
| * R-Hash2 = HMACAuthKey(R-S2 || PSK2 || PKE || PKR) |
| */ |
| iov[0].iov_base = x_snonce; |
| iov[0].iov_len = 16; |
| iov[1].iov_base = psk; |
| iov[1].iov_len = 16; |
| iov[2].iov_base = wsc->m1->public_key; |
| iov[2].iov_len = sizeof(wsc->m1->public_key); |
| iov[3].iov_base = wsc->m2->public_key; |
| iov[3].iov_len = sizeof(wsc->m2->public_key); |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 4); |
| l_checksum_get_digest(wsc->hmac_auth_key, hash, sizeof(hash)); |
| |
| return !memcmp(hash, x_hash_expected, sizeof(hash)); |
| } |
| |
| static uint8_t *encrypted_settings_decrypt(struct eap_wsc_state *wsc, |
| const uint8_t *pdu, |
| size_t len, |
| size_t *out_len) |
| { |
| size_t encrypted_len; |
| uint8_t *decrypted; |
| unsigned int i; |
| uint8_t pad; |
| |
| /* WSC 2.0.5, Section 12, Encrypted Settings: |
| * "The Data field of the Encrypted Settings attribute includes an |
| * initialization vector (IV) followed by a set of encrypted Wi-Fi |
| * Simple Configuration TLV attributes." |
| * |
| * Account for the IV being in the beginning 16 bytes |
| */ |
| if (len < 16 ) |
| return NULL; |
| |
| encrypted_len = len - 16; |
| if (encrypted_len < 16 || encrypted_len % 16) |
| return NULL; |
| |
| decrypted = l_malloc(encrypted_len); |
| |
| l_cipher_set_iv(wsc->aes_cbc_128, pdu, 16); |
| |
| if (!l_cipher_decrypt(wsc->aes_cbc_128, pdu + 16, |
| decrypted, encrypted_len)) |
| goto fail; |
| |
| /* Check that the pad value is sane */ |
| pad = decrypted[encrypted_len - 1]; |
| if (pad > encrypted_len) |
| goto fail; |
| |
| for (i = 0; i < pad; i++) { |
| if (decrypted[encrypted_len - pad + i] == pad) |
| continue; |
| |
| goto fail; |
| } |
| |
| *out_len = encrypted_len - pad; |
| |
| return decrypted; |
| |
| fail: |
| explicit_bzero(decrypted, encrypted_len); |
| l_free(decrypted); |
| return NULL; |
| } |
| |
| static bool encrypted_settings_encrypt(struct eap_wsc_state *wsc, |
| const uint8_t *iv, |
| const uint8_t *in, |
| size_t in_len, |
| uint8_t *out, |
| size_t *out_len) |
| { |
| size_t len = 0; |
| unsigned int i; |
| uint8_t pad; |
| |
| l_cipher_set_iv(wsc->aes_cbc_128, iv, 16); |
| memcpy(out, iv, 16); |
| len += 16; |
| |
| memcpy(out + len, in, in_len); |
| len += in_len; |
| |
| pad = 16 - in_len % 16; |
| |
| for (i = 0; i < pad; i++) |
| out[len++] = pad; |
| |
| if (!l_cipher_encrypt(wsc->aes_cbc_128, out + 16, out + 16, len - 16)) { |
| explicit_bzero(out + 16, len - 16); |
| return false; |
| } |
| |
| *out_len = len; |
| return true; |
| } |
| |
| static void eap_wsc_state_free(struct eap_wsc_state *wsc) |
| { |
| if (wsc->device_password) { |
| explicit_bzero(wsc->device_password, |
| strlen(wsc->device_password)); |
| l_free(wsc->device_password); |
| } |
| |
| l_key_free(wsc->private); |
| |
| l_free(wsc->sent_pdu); |
| wsc->sent_pdu = NULL; |
| wsc->sent_len = 0; |
| |
| if (wsc->rx_pdu_buf) { |
| l_free(wsc->rx_pdu_buf); |
| wsc->rx_pdu_buf = NULL; |
| wsc->rx_pdu_buf_len = 0; |
| wsc->rx_pdu_buf_offset = 0; |
| } |
| |
| l_checksum_free(wsc->hmac_auth_key); |
| l_cipher_free(wsc->aes_cbc_128); |
| |
| l_free(wsc->m1); |
| l_free(wsc->m2); |
| |
| explicit_bzero(wsc->local_snonce1, 16); |
| explicit_bzero(wsc->local_snonce2, 16); |
| explicit_bzero(wsc->psk1, 16); |
| explicit_bzero(wsc->psk2, 16); |
| explicit_bzero(&wsc->wpa2_cred, sizeof(struct wsc_credential)); |
| explicit_bzero(&wsc->open_cred, sizeof(struct wsc_credential)); |
| |
| l_free(wsc); |
| } |
| |
| static void eap_wsc_free(struct eap_state *eap) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| |
| eap_set_data(eap, NULL); |
| eap_wsc_state_free(wsc); |
| } |
| |
| static void eap_wsc_r_send_start(struct eap_state *eap) |
| { |
| uint8_t buf[EAP_WSC_HEADER_LEN]; |
| |
| buf[12] = WSC_OP_START; |
| buf[13] = 0; |
| |
| eap_method_new_request(eap, buf, EAP_WSC_HEADER_LEN); |
| } |
| |
| static void eap_wsc_send_fragment(struct eap_state *eap) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| size_t mtu = eap_get_mtu(eap); |
| uint8_t buf[mtu]; |
| size_t len = wsc->sent_len - wsc->tx_frag_offset; |
| size_t header_len = EAP_WSC_HEADER_LEN; |
| |
| buf[12] = WSC_OP_MSG; |
| |
| if (len > mtu - EAP_WSC_HEADER_LEN) { |
| len = mtu - EAP_WSC_HEADER_LEN; |
| buf[13] = WSC_FLAG_MF; |
| } else { |
| buf[13] = 0; |
| } |
| |
| if (!wsc->tx_frag_offset) { |
| buf[13] |= WSC_FLAG_LF; |
| |
| l_put_be16(wsc->sent_len, &buf[14]); |
| len -= 2; |
| header_len += 2; |
| } |
| |
| memcpy(buf + header_len, wsc->sent_pdu + wsc->tx_frag_offset, len); |
| |
| if (wsc->registrar) |
| eap_method_new_request(eap, buf, header_len + len); |
| else |
| eap_method_respond(eap, buf, header_len + len); |
| |
| wsc->tx_last_frag_len = len; |
| } |
| |
| static void eap_wsc_send_message(struct eap_state *eap, |
| uint8_t *pdu, size_t pdu_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| size_t msg_len = pdu_len + EAP_WSC_HEADER_LEN; |
| |
| eap_wsc_state_set_sent_pdu(wsc, pdu, pdu_len); |
| |
| if (msg_len <= eap_get_mtu(eap)) { |
| uint8_t *buf = l_malloc(msg_len); |
| |
| buf[12] = WSC_OP_MSG; |
| buf[13] = 0; |
| memcpy(buf + EAP_WSC_HEADER_LEN, pdu, pdu_len); |
| |
| if (wsc->registrar) |
| eap_method_new_request(eap, buf, msg_len); |
| else |
| eap_method_respond(eap, buf, msg_len); |
| |
| l_free(buf); |
| return; |
| } |
| |
| wsc->tx_frag_offset = 0; |
| eap_wsc_send_fragment(eap); |
| } |
| |
| static void eap_wsc_send_nack(struct eap_state *eap, |
| enum wsc_configuration_error error) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_nack nack; |
| uint8_t *pdu; |
| size_t pdu_len; |
| uint8_t buf[256]; |
| |
| /* |
| * WSC 2.0.5, Table 34, Configuration Error 0 states: |
| * "- not valid for WSC_NACK except when a station acts as an External |
| * Registrar (to learn the current AP settings after M7 with |
| * configuration error = 0)" |
| * |
| * However, section 7.7.3 states: |
| * "Once M5 is sent, for example, if anything but M6 is received, |
| * the Enrollee will respond with a NACK message." |
| * |
| * Section 7.1 states: |
| * "If a message is received with either an invalid nonce or an invalid |
| * Authenticator attribute, the recipient shall silently ignore this |
| * message." |
| * |
| * So it is entirely unclear what to do in the situation of an |
| * out-of-order message being sent. To centralize decision making, |
| * callers will call this function with error 0. |
| */ |
| if (error == WSC_CONFIGURATION_ERROR_NO_ERROR) |
| return; |
| |
| nack.version2 = true; |
| if (wsc->state != STATE_EXPECT_M1) |
| memcpy(nack.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(nack.enrollee_nonce)); |
| else |
| memset(nack.enrollee_nonce, 0, sizeof(nack.enrollee_nonce)); |
| |
| if (wsc->m2) |
| memcpy(nack.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(nack.registrar_nonce)); |
| else |
| memset(nack.registrar_nonce, 0, sizeof(nack.registrar_nonce)); |
| |
| nack.configuration_error = error; |
| |
| pdu = wsc_build_wsc_nack(&nack, &pdu_len); |
| if (!pdu) |
| return; |
| |
| buf[12] = WSC_OP_NACK; |
| buf[13] = 0; |
| memcpy(buf + EAP_WSC_HEADER_LEN, pdu, pdu_len); |
| |
| if (wsc->registrar) |
| eap_method_new_request(eap, buf, pdu_len + EAP_WSC_HEADER_LEN); |
| else |
| eap_method_respond(eap, buf, pdu_len + EAP_WSC_HEADER_LEN); |
| |
| l_free(pdu); |
| } |
| |
| static void eap_wsc_send_done(struct eap_state *eap) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_done done; |
| uint8_t *pdu; |
| size_t pdu_len; |
| uint8_t buf[256]; |
| |
| done.version2 = true; |
| memcpy(done.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(done.enrollee_nonce)); |
| memcpy(done.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(done.registrar_nonce)); |
| |
| pdu = wsc_build_wsc_done(&done, &pdu_len); |
| if (!pdu) |
| return; |
| |
| buf[12] = WSC_OP_DONE; |
| buf[13] = 0; |
| memcpy(buf + EAP_WSC_HEADER_LEN, pdu, pdu_len); |
| |
| eap_method_respond(eap, buf, pdu_len + EAP_WSC_HEADER_LEN); |
| l_free(pdu); |
| } |
| |
| static void eap_wsc_send_frag_ack(struct eap_state *eap) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| uint8_t buf[EAP_WSC_HEADER_LEN]; |
| |
| buf[12] = WSC_OP_FRAG_ACK; |
| buf[13] = 0; |
| |
| if (wsc->registrar) |
| eap_method_new_request(eap, buf, EAP_WSC_HEADER_LEN); |
| else |
| eap_method_respond(eap, buf, EAP_WSC_HEADER_LEN); |
| } |
| |
| static void eap_wsc_handle_m8(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m8 m8; |
| struct iovec encrypted; |
| uint8_t *decrypted; |
| size_t decrypted_len; |
| struct wsc_m8_encrypted_settings m8es; |
| struct iovec creds[3]; |
| size_t n_creds; |
| size_t i; |
| bool nack = true; |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m8(pdu, len, &m8, &encrypted) != 0) { |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| } |
| |
| if (memcmp(m8.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(m8.enrollee_nonce))) |
| return; |
| |
| if (!authenticator_check(wsc, pdu, len)) |
| return; |
| |
| decrypted = encrypted_settings_decrypt(wsc, encrypted.iov_base, |
| encrypted.iov_len, |
| &decrypted_len); |
| if (!decrypted) |
| goto send_nack; |
| |
| n_creds = L_ARRAY_SIZE(creds); |
| |
| if (wsc_parse_m8_encrypted_settings(decrypted, decrypted_len, |
| &m8es, creds, &n_creds)) |
| goto cleanup; |
| |
| if (!keywrap_authenticator_check(wsc, decrypted, decrypted_len)) |
| goto cleanup; |
| |
| for (i = 0; i < n_creds; i++) { |
| struct wsc_credential cred; |
| |
| if (wsc_parse_credential(creds[i].iov_base, creds[i].iov_len, |
| &cred) == 0) |
| eap_method_event(eap, EAP_WSC_EVENT_CREDENTIAL_OBTAINED, |
| &cred); |
| |
| explicit_bzero(&cred, sizeof(cred)); |
| } |
| |
| eap_wsc_send_done(eap); |
| wsc->state = STATE_FINISHED; |
| nack = false; |
| |
| cleanup: |
| explicit_bzero(&m8es, sizeof(m8es)); |
| explicit_bzero(decrypted, decrypted_len); |
| l_free(decrypted); |
| |
| if (!nack) |
| return; |
| |
| send_nack: |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE); |
| } |
| |
| static void eap_wsc_send_m7(struct eap_state *eap, |
| const uint8_t *m6_pdu, size_t m6_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m7_encrypted_settings m7es; |
| struct wsc_m7 m7; |
| uint8_t *pdu; |
| size_t pdu_len; |
| /* 20 for SNonce, 12 for Authenticator, 16 for IV + up to 16 pad */ |
| uint8_t encrypted[64]; |
| size_t encrypted_len; |
| bool r; |
| |
| memcpy(m7es.e_snonce2, wsc->local_snonce2, sizeof(wsc->local_snonce2)); |
| pdu = wsc_build_m7_encrypted_settings(&m7es, &pdu_len); |
| explicit_bzero(m7es.e_snonce2, sizeof(wsc->local_snonce2)); |
| if (!pdu) |
| return; |
| |
| keywrap_authenticator_put(wsc, pdu, pdu_len); |
| r = encrypted_settings_encrypt(wsc, wsc->iv2, pdu, pdu_len, |
| encrypted, &encrypted_len); |
| explicit_bzero(pdu, pdu_len); |
| l_free(pdu); |
| |
| if (!r) |
| return; |
| |
| m7.version2 = true; |
| memcpy(m7.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(m7.registrar_nonce)); |
| |
| pdu = wsc_build_m7(&m7, encrypted, encrypted_len, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m6_pdu, m6_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M8; |
| } |
| |
| static void eap_wsc_handle_m6(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m6 m6; |
| struct iovec encrypted; |
| uint8_t *decrypted; |
| size_t decrypted_len; |
| struct wsc_m6_encrypted_settings m6es; |
| enum wsc_configuration_error error = WSC_CONFIGURATION_ERROR_NO_ERROR; |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m6(pdu, len, &m6, &encrypted) != 0) |
| goto send_nack; |
| |
| if (memcmp(m6.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(m6.enrollee_nonce))) |
| return; |
| |
| if (!authenticator_check(wsc, pdu, len)) |
| return; |
| |
| decrypted = encrypted_settings_decrypt(wsc, encrypted.iov_base, |
| encrypted.iov_len, |
| &decrypted_len); |
| if (!decrypted) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto send_nack; |
| } |
| |
| if (wsc_parse_m6_encrypted_settings(decrypted, decrypted_len, &m6es)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| if (!keywrap_authenticator_check(wsc, decrypted, decrypted_len)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| /* We now have R-SNonce2, verify R-Hash2 stored in eap_wsc_handle_m4 */ |
| if (!x_hash_check(wsc, m6es.r_snonce2, wsc->psk2, wsc->r_hash2)) { |
| error = WSC_CONFIGURATION_ERROR_DEVICE_PASSWORD_AUTH_FAILURE; |
| goto cleanup; |
| } |
| |
| eap_wsc_send_m7(eap, pdu, len); |
| |
| cleanup: |
| explicit_bzero(&m6es, sizeof(m6es)); |
| explicit_bzero(decrypted, decrypted_len); |
| l_free(decrypted); |
| |
| if (!error) |
| return; |
| |
| send_nack: |
| eap_wsc_send_nack(eap, error); |
| } |
| |
| static void eap_wsc_send_m5(struct eap_state *eap, |
| const uint8_t *m4_pdu, size_t m4_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m5_encrypted_settings m5es; |
| struct wsc_m5 m5; |
| uint8_t *pdu; |
| size_t pdu_len; |
| /* 20 for SNonce, 12 for Authenticator, 16 for IV + up to 16 pad */ |
| uint8_t encrypted[64]; |
| size_t encrypted_len; |
| bool r; |
| |
| memcpy(m5es.e_snonce1, wsc->local_snonce1, sizeof(wsc->local_snonce1)); |
| pdu = wsc_build_m5_encrypted_settings(&m5es, &pdu_len); |
| explicit_bzero(m5es.e_snonce1, sizeof(wsc->local_snonce1)); |
| if (!pdu) |
| return; |
| |
| keywrap_authenticator_put(wsc, pdu, pdu_len); |
| r = encrypted_settings_encrypt(wsc, wsc->iv1, pdu, pdu_len, |
| encrypted, &encrypted_len); |
| explicit_bzero(pdu, pdu_len); |
| l_free(pdu); |
| |
| if (!r) |
| return; |
| |
| m5.version2 = true; |
| memcpy(m5.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(m5.registrar_nonce)); |
| |
| pdu = wsc_build_m5(&m5, encrypted, encrypted_len, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m4_pdu, m4_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M6; |
| } |
| |
| static void eap_wsc_handle_m4(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m4 m4; |
| struct iovec encrypted; |
| uint8_t *decrypted; |
| size_t decrypted_len; |
| struct wsc_m4_encrypted_settings m4es; |
| enum wsc_configuration_error error = WSC_CONFIGURATION_ERROR_NO_ERROR; |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m4(pdu, len, &m4, &encrypted) != 0) |
| goto send_nack; |
| |
| if (memcmp(m4.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(m4.enrollee_nonce))) |
| return; |
| |
| if (!authenticator_check(wsc, pdu, len)) |
| return; |
| |
| decrypted = encrypted_settings_decrypt(wsc, encrypted.iov_base, |
| encrypted.iov_len, |
| &decrypted_len); |
| if (!decrypted) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto send_nack; |
| } |
| |
| if (wsc_parse_m4_encrypted_settings(decrypted, decrypted_len, &m4es)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| if (!keywrap_authenticator_check(wsc, decrypted, decrypted_len)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| /* Since we have obtained R-SNonce1, we can now verify R-Hash1. */ |
| if (!x_hash_check(wsc, m4es.r_snonce1, wsc->psk1, m4.r_hash1)) { |
| error = WSC_CONFIGURATION_ERROR_DEVICE_PASSWORD_AUTH_FAILURE; |
| goto cleanup; |
| } |
| |
| /* Now store R_Hash2 so we can verify it when we receive M6 */ |
| memcpy(wsc->r_hash2, m4.r_hash2, sizeof(m4.r_hash2)); |
| eap_wsc_send_m5(eap, pdu, len); |
| |
| cleanup: |
| explicit_bzero(&m4es, sizeof(m4es)); |
| explicit_bzero(decrypted, decrypted_len); |
| l_free(decrypted); |
| |
| if (!error) |
| return; |
| |
| send_nack: |
| eap_wsc_send_nack(eap, error); |
| } |
| |
| static void eap_wsc_send_m3(struct eap_state *eap, |
| const uint8_t *m2_pdu, size_t m2_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m2 *m2 = wsc->m2; |
| size_t len; |
| size_t len_half1; |
| struct wsc_m3 m3; |
| struct iovec iov[4]; |
| uint8_t *pdu; |
| size_t pdu_len; |
| |
| len = strlen(wsc->device_password); |
| |
| /* WSC 2.0.5, Section 7.4: |
| * In case the UTF8 representation of the DevicePassword length is an |
| * odd number (N), the first half of DevicePassword will have length |
| * of N/2+1 and the second half of the DevicePassword will have length |
| * of N/2. |
| */ |
| len_half1 = len / 2; |
| if ((len % 2) == 1) |
| len_half1 += 1; |
| |
| l_checksum_update(wsc->hmac_auth_key, wsc->device_password, len_half1); |
| l_checksum_get_digest(wsc->hmac_auth_key, wsc->psk1, sizeof(wsc->psk1)); |
| |
| l_checksum_update(wsc->hmac_auth_key, wsc->device_password + len_half1, |
| len / 2); |
| l_checksum_get_digest(wsc->hmac_auth_key, wsc->psk2, sizeof(wsc->psk2)); |
| |
| m3.version2 = true; |
| memcpy(m3.registrar_nonce, m2->registrar_nonce, |
| sizeof(m3.registrar_nonce)); |
| |
| /* WSC 2.0.5, Section 7.4: |
| * The Enrollee creates two 128-bit secret nonces, E-S1, E-S2 and then |
| * computes: |
| * E-Hash1 = HMACAuthKey(E-S1 || PSK1 || PKE || PKR) |
| * E-Hash2 = HMACAuthKey(E-S2 || PSK2 || PKE || PKR) |
| */ |
| iov[0].iov_base = wsc->local_snonce1; |
| iov[0].iov_len = sizeof(wsc->local_snonce1); |
| iov[1].iov_base = wsc->psk1; |
| iov[1].iov_len = sizeof(wsc->psk1); |
| iov[2].iov_base = wsc->m1->public_key; |
| iov[2].iov_len = sizeof(wsc->m1->public_key); |
| iov[3].iov_base = m2->public_key; |
| iov[3].iov_len = sizeof(m2->public_key); |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 4); |
| l_checksum_get_digest(wsc->hmac_auth_key, |
| m3.e_hash1, sizeof(m3.e_hash1)); |
| |
| iov[0].iov_base = wsc->local_snonce2; |
| iov[0].iov_len = sizeof(wsc->local_snonce2); |
| iov[1].iov_base = wsc->psk2; |
| iov[1].iov_len = sizeof(wsc->psk2); |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 4); |
| l_checksum_get_digest(wsc->hmac_auth_key, |
| m3.e_hash2, sizeof(m3.e_hash2)); |
| |
| pdu = wsc_build_m3(&m3, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m2_pdu, m2_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M4; |
| } |
| |
| static void eap_wsc_handle_m2(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct l_key *remote_public; |
| uint8_t shared_secret[192] = { 0 }; |
| size_t shared_secret_len = sizeof(shared_secret); |
| struct l_checksum *sha256; |
| uint8_t dhkey[32]; |
| struct l_checksum *hmac_sha256; |
| struct iovec iov[3]; |
| uint8_t kdk[32]; |
| struct wsc_session_key keys; |
| bool r; |
| |
| /* TODO: Check to see if message is M2D first */ |
| if (!wsc->m2) |
| wsc->m2 = l_new(struct wsc_m2, 1); |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m2(pdu, len, wsc->m2) != 0) { |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| } |
| |
| if (memcmp(wsc->m2->enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(wsc->m2->enrollee_nonce))) |
| return; |
| |
| if (!l_key_validate_dh_payload(wsc->m2->public_key, |
| sizeof(wsc->m2->public_key), |
| crypto_dh5_prime, |
| crypto_dh5_prime_size)) |
| return; |
| |
| remote_public = l_key_new(L_KEY_RAW, wsc->m2->public_key, |
| sizeof(wsc->m2->public_key)); |
| if (!remote_public) |
| return; |
| |
| r = l_key_compute_dh_secret(remote_public, wsc->private, dh5_prime, |
| shared_secret, &shared_secret_len); |
| l_key_free(remote_public); |
| |
| if (!r) |
| return; |
| |
| sha256 = l_checksum_new(L_CHECKSUM_SHA256); |
| if (!sha256) |
| return; |
| |
| l_checksum_update(sha256, shared_secret, shared_secret_len); |
| l_checksum_get_digest(sha256, dhkey, sizeof(dhkey)); |
| l_checksum_free(sha256); |
| |
| explicit_bzero(shared_secret, shared_secret_len); |
| |
| hmac_sha256 = l_checksum_new_hmac(L_CHECKSUM_SHA256, |
| dhkey, sizeof(dhkey)); |
| explicit_bzero(dhkey, sizeof(dhkey)); |
| |
| if (!hmac_sha256) |
| return; |
| |
| iov[0].iov_base = wsc->m1->enrollee_nonce; |
| iov[0].iov_len = 16; |
| iov[1].iov_base = wsc->m1->addr; |
| iov[1].iov_len = 6; |
| iov[2].iov_base = wsc->m2->registrar_nonce; |
| iov[2].iov_len = 16; |
| |
| l_checksum_updatev(hmac_sha256, iov, 3); |
| l_checksum_get_digest(hmac_sha256, kdk, sizeof(kdk)); |
| l_checksum_free(hmac_sha256); |
| |
| r = wsc_kdf(kdk, &keys, sizeof(keys)); |
| explicit_bzero(kdk, sizeof(kdk)); |
| if (!r) |
| return; |
| |
| wsc->hmac_auth_key = l_checksum_new_hmac(L_CHECKSUM_SHA256, |
| keys.auth_key, |
| sizeof(keys.auth_key)); |
| if (!authenticator_check(wsc, pdu, len)) { |
| l_checksum_free(wsc->hmac_auth_key); |
| wsc->hmac_auth_key = NULL; |
| goto clear_keys; |
| } |
| |
| /* Everything checks out, lets build M3 */ |
| eap_wsc_send_m3(eap, pdu, len); |
| |
| /* |
| * AuthKey is uploaded into the kernel, once we upload KeyWrapKey, |
| * the keys variable is no longer useful. Make sure to wipe it |
| */ |
| wsc->aes_cbc_128 = l_cipher_new(L_CIPHER_AES_CBC, keys.keywrap_key, |
| sizeof(keys.keywrap_key)); |
| |
| clear_keys: |
| explicit_bzero(&keys, sizeof(keys)); |
| } |
| |
| static void eap_wsc_r_send_m8(struct eap_state *eap, |
| const uint8_t *m7_pdu, size_t m7_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m8_encrypted_settings m8es = {}; |
| struct wsc_m8 m8; |
| uint8_t *pdu; |
| size_t pdu_len; |
| /* At least: 2 * credential, 12 for Authenticator, 16 for IV + up to 16 pad */ |
| uint8_t encrypted[1024]; |
| size_t encrypted_len; |
| bool r; |
| struct wsc_credential creds[2]; |
| unsigned int creds_cnt = 0; |
| unsigned int auth_types = |
| wsc->m1->auth_type_flags & wsc->m2->auth_type_flags; |
| |
| if (auth_types & WSC_AUTHENTICATION_TYPE_OPEN) |
| memcpy(&creds[creds_cnt++], &wsc->open_cred, |
| sizeof(struct wsc_credential)); |
| |
| if (auth_types & WSC_AUTHENTICATION_TYPE_WPA2_PERSONAL) |
| memcpy(&creds[creds_cnt++], &wsc->wpa2_cred, |
| sizeof(struct wsc_credential)); |
| |
| pdu = wsc_build_m8_encrypted_settings(&m8es, creds, creds_cnt, |
| &pdu_len); |
| explicit_bzero(creds, creds_cnt * sizeof(struct wsc_credential)); |
| if (!pdu) |
| return; |
| |
| keywrap_authenticator_put(wsc, pdu, pdu_len); |
| r = encrypted_settings_encrypt(wsc, wsc->iv3, pdu, pdu_len, |
| encrypted, &encrypted_len); |
| explicit_bzero(pdu, pdu_len); |
| l_free(pdu); |
| |
| if (!r) |
| return; |
| |
| m8.version2 = true; |
| memcpy(m8.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(m8.enrollee_nonce)); |
| |
| pdu = wsc_build_m8(&m8, encrypted, encrypted_len, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m7_pdu, m7_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_DONE; |
| } |
| |
| static void eap_wsc_r_handle_m7(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m7 m7; |
| struct iovec encrypted; |
| uint8_t *decrypted; |
| size_t decrypted_len; |
| struct wsc_m7_encrypted_settings m7es; |
| enum wsc_configuration_error error = WSC_CONFIGURATION_ERROR_NO_ERROR; |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m7(pdu, len, &m7, &encrypted) != 0) |
| goto send_nack; |
| |
| if (memcmp(m7.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(m7.registrar_nonce))) |
| goto send_nack; |
| |
| if (!authenticator_check(wsc, pdu, len)) |
| return; |
| |
| decrypted = encrypted_settings_decrypt(wsc, encrypted.iov_base, |
| encrypted.iov_len, |
| &decrypted_len); |
| if (!decrypted) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto send_nack; |
| } |
| |
| if (wsc_parse_m7_encrypted_settings(decrypted, decrypted_len, &m7es)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| if (!keywrap_authenticator_check(wsc, decrypted, decrypted_len)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| /* We have E-SNonce2, verify E-Hash2 stored in eap_wsc_r_handle_m3 */ |
| if (!x_hash_check(wsc, m7es.e_snonce2, wsc->psk2, wsc->e_hash2)) { |
| error = WSC_CONFIGURATION_ERROR_DEVICE_PASSWORD_AUTH_FAILURE; |
| goto cleanup; |
| } |
| |
| eap_wsc_r_send_m8(eap, pdu, len); |
| |
| cleanup: |
| explicit_bzero(&m7es, sizeof(m7es)); |
| explicit_bzero(decrypted, decrypted_len); |
| l_free(decrypted); |
| |
| if (!error) |
| return; |
| |
| send_nack: |
| eap_wsc_send_nack(eap, error); |
| } |
| |
| static void eap_wsc_r_send_m6(struct eap_state *eap, |
| const uint8_t *m5_pdu, size_t m5_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m6_encrypted_settings m6es; |
| struct wsc_m6 m6; |
| uint8_t *pdu; |
| size_t pdu_len; |
| /* 20 for SNonce, 12 for Authenticator, 16 for IV + up to 16 pad */ |
| uint8_t encrypted[64]; |
| size_t encrypted_len; |
| bool r; |
| |
| memcpy(m6es.r_snonce2, wsc->local_snonce2, sizeof(wsc->local_snonce2)); |
| pdu = wsc_build_m6_encrypted_settings(&m6es, &pdu_len); |
| explicit_bzero(m6es.r_snonce2, sizeof(wsc->local_snonce2)); |
| if (!pdu) |
| return; |
| |
| keywrap_authenticator_put(wsc, pdu, pdu_len); |
| r = encrypted_settings_encrypt(wsc, wsc->iv2, pdu, pdu_len, |
| encrypted, &encrypted_len); |
| explicit_bzero(pdu, pdu_len); |
| l_free(pdu); |
| |
| if (!r) |
| return; |
| |
| m6.version2 = true; |
| memcpy(m6.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(m6.enrollee_nonce)); |
| |
| pdu = wsc_build_m6(&m6, encrypted, encrypted_len, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m5_pdu, m5_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M7; |
| } |
| |
| static void eap_wsc_r_handle_m5(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m5 m5; |
| struct iovec encrypted; |
| uint8_t *decrypted; |
| size_t decrypted_len; |
| struct wsc_m5_encrypted_settings m5es; |
| enum wsc_configuration_error error = WSC_CONFIGURATION_ERROR_NO_ERROR; |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m5(pdu, len, &m5, &encrypted) != 0) |
| goto send_nack; |
| |
| if (memcmp(m5.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(m5.registrar_nonce))) |
| goto send_nack; |
| |
| if (!authenticator_check(wsc, pdu, len)) |
| return; |
| |
| decrypted = encrypted_settings_decrypt(wsc, encrypted.iov_base, |
| encrypted.iov_len, |
| &decrypted_len); |
| if (!decrypted) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto send_nack; |
| } |
| |
| if (wsc_parse_m5_encrypted_settings(decrypted, decrypted_len, &m5es)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| if (!keywrap_authenticator_check(wsc, decrypted, decrypted_len)) { |
| error = WSC_CONFIGURATION_ERROR_DECRYPTION_CRC_FAILURE; |
| goto cleanup; |
| } |
| |
| /* We have E-SNonce1, verify E-Hash1 stored in eap_wsc_r_handle_m3 */ |
| if (!x_hash_check(wsc, m5es.e_snonce1, wsc->psk1, wsc->e_hash1)) { |
| error = WSC_CONFIGURATION_ERROR_DEVICE_PASSWORD_AUTH_FAILURE; |
| goto cleanup; |
| } |
| |
| eap_wsc_r_send_m6(eap, pdu, len); |
| |
| cleanup: |
| explicit_bzero(&m5es, sizeof(m5es)); |
| explicit_bzero(decrypted, decrypted_len); |
| l_free(decrypted); |
| |
| if (!error) |
| return; |
| |
| send_nack: |
| eap_wsc_send_nack(eap, error); |
| } |
| |
| static void eap_wsc_r_send_m4(struct eap_state *eap, |
| const uint8_t *m3_pdu, size_t m3_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m4_encrypted_settings m4es; |
| struct wsc_m4 m4; |
| uint8_t *pdu; |
| size_t pdu_len; |
| /* 20 for SNonce, 12 for Authenticator, 16 for IV + up to 16 pad */ |
| uint8_t encrypted[64]; |
| size_t encrypted_len; |
| bool r; |
| size_t len; |
| size_t len_half1; |
| struct iovec iov[4]; |
| |
| memcpy(m4es.r_snonce1, wsc->local_snonce1, sizeof(wsc->local_snonce1)); |
| pdu = wsc_build_m4_encrypted_settings(&m4es, &pdu_len); |
| explicit_bzero(m4es.r_snonce1, sizeof(wsc->local_snonce1)); |
| if (!pdu) |
| return; |
| |
| keywrap_authenticator_put(wsc, pdu, pdu_len); |
| r = encrypted_settings_encrypt(wsc, wsc->iv1, pdu, pdu_len, |
| encrypted, &encrypted_len); |
| explicit_bzero(pdu, pdu_len); |
| l_free(pdu); |
| |
| if (!r) |
| return; |
| |
| len = strlen(wsc->device_password); |
| |
| /* WSC 2.0.5, Section 7.4: |
| * In case the UTF8 representation of the DevicePassword length is an |
| * odd number (N), the first half of DevicePassword will have length |
| * of N/2+1 and the second half of the DevicePassword will have length |
| * of N/2. |
| */ |
| len_half1 = (len + 1) / 2; |
| |
| l_checksum_update(wsc->hmac_auth_key, wsc->device_password, len_half1); |
| l_checksum_get_digest(wsc->hmac_auth_key, wsc->psk1, sizeof(wsc->psk1)); |
| |
| l_checksum_update(wsc->hmac_auth_key, wsc->device_password + len_half1, |
| len / 2); |
| l_checksum_get_digest(wsc->hmac_auth_key, wsc->psk2, sizeof(wsc->psk2)); |
| |
| m4.version2 = true; |
| memcpy(m4.enrollee_nonce, wsc->m1->enrollee_nonce, |
| sizeof(m4.enrollee_nonce)); |
| |
| /* WSC 2.0.5, Section 7.4: |
| * The Registrar creates two 128-bit secret nonces, R-S1, R-S2 and then |
| * computes: |
| * R-Hash1 = HMACAuthKey(R-S1 || PSK1 || PKE || PKR) |
| * R-Hash2 = HMACAuthKey(R-S2 || PSK2 || PKE || PKR) |
| */ |
| iov[0].iov_base = wsc->local_snonce1; |
| iov[0].iov_len = sizeof(wsc->local_snonce1); |
| iov[1].iov_base = wsc->psk1; |
| iov[1].iov_len = sizeof(wsc->psk1); |
| iov[2].iov_base = wsc->m1->public_key; |
| iov[2].iov_len = sizeof(wsc->m1->public_key); |
| iov[3].iov_base = wsc->m2->public_key; |
| iov[3].iov_len = sizeof(wsc->m2->public_key); |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 4); |
| l_checksum_get_digest(wsc->hmac_auth_key, |
| m4.r_hash1, sizeof(m4.r_hash1)); |
| |
| iov[0].iov_base = wsc->local_snonce2; |
| iov[0].iov_len = sizeof(wsc->local_snonce2); |
| iov[1].iov_base = wsc->psk2; |
| iov[1].iov_len = sizeof(wsc->psk2); |
| l_checksum_updatev(wsc->hmac_auth_key, iov, 4); |
| l_checksum_get_digest(wsc->hmac_auth_key, |
| m4.r_hash2, sizeof(m4.r_hash2)); |
| |
| pdu = wsc_build_m4(&m4, encrypted, encrypted_len, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m3_pdu, m3_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M5; |
| } |
| |
| static void eap_wsc_r_handle_m3(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_m3 m3; |
| |
| if (wsc_parse_m3(pdu, len, &m3) != 0) |
| goto send_nack; |
| |
| if (memcmp(m3.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(m3.registrar_nonce))) |
| goto send_nack; |
| |
| if (!authenticator_check(wsc, pdu, len)) |
| return; |
| |
| /* We can't verify these until we receive the E-S1/E-S2 in M5/M7 */ |
| memcpy(wsc->e_hash1, m3.e_hash1, sizeof(m3.e_hash1)); |
| memcpy(wsc->e_hash2, m3.e_hash2, sizeof(m3.e_hash2)); |
| |
| eap_wsc_r_send_m4(eap, pdu, len); |
| return; |
| |
| send_nack: |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| } |
| |
| static void eap_wsc_r_send_m2(struct eap_state *eap, |
| const uint8_t *m1_pdu, size_t m1_len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| uint8_t *pdu; |
| size_t pdu_len; |
| |
| wsc->m2->version2 = true; |
| memcpy(wsc->m2->enrollee_nonce, wsc->m1->enrollee_nonce, 16); |
| wsc->m2->association_state = WSC_ASSOCIATION_STATE_NOT_ASSOCIATED; |
| wsc->m2->configuration_error = WSC_CONFIGURATION_ERROR_NO_ERROR; |
| |
| pdu = wsc_build_m2(wsc->m2, &pdu_len); |
| if (!pdu) |
| return; |
| |
| authenticator_put(wsc, m1_pdu, m1_len, pdu, pdu_len); |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M3; |
| } |
| |
| static void eap_wsc_r_handle_m1(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct l_key *remote_public; |
| uint8_t shared_secret[192] = { 0 }; |
| size_t shared_secret_len = sizeof(shared_secret); |
| struct l_checksum *sha256; |
| uint8_t dhkey[32]; |
| struct l_checksum *hmac_sha256; |
| struct iovec iov[3]; |
| uint8_t kdk[32]; |
| struct wsc_session_key keys; |
| bool r; |
| uint8_t expected_enrollee_mac[6]; |
| uint8_t expected_uuid_e[16]; |
| |
| memcpy(expected_enrollee_mac, wsc->m1->addr, 6); |
| memcpy(expected_uuid_e, wsc->m1->uuid_e, 16); |
| |
| /* Spec unclear what to do here, see comments in eap_wsc_send_nack */ |
| if (wsc_parse_m1(pdu, len, wsc->m1) != 0) { |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| } |
| |
| if (memcmp(expected_enrollee_mac, wsc->m1->addr, 6) || |
| memcmp(expected_uuid_e, wsc->m1->uuid_e, 16)) { |
| eap_wsc_send_nack(eap, |
| WSC_CONFIGURATION_ERROR_MULTIPLE_PBC_SESSIONS_DETECTED); |
| eap_method_error(eap); |
| return; |
| } |
| |
| if (wsc->m1->state != WSC_STATE_NOT_CONFIGURED || |
| !(wsc->m1->auth_type_flags & |
| wsc->m2->auth_type_flags) || |
| !(wsc->m1->encryption_type_flags & |
| wsc->m2->encryption_type_flags) || |
| !(wsc->m1->connection_type_flags & |
| wsc->m2->connection_type_flags) || |
| !(wsc->m1->config_methods & |
| wsc->m2->config_methods) || |
| !(wsc->m1->rf_bands & wsc->m2->rf_bands)) { |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| } |
| |
| if (wsc->m1->configuration_error != WSC_CONFIGURATION_ERROR_NO_ERROR || |
| wsc->m1->device_password_id != |
| wsc->m2->device_password_id) { |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| } |
| |
| /* m1->request_to_enroll not checked */ |
| |
| if (!l_key_validate_dh_payload(wsc->m1->public_key, |
| sizeof(wsc->m1->public_key), |
| crypto_dh5_prime, |
| crypto_dh5_prime_size)) |
| return; |
| |
| /* |
| * Done validating the M1, we can now derive the keys for most of |
| * the rest of the registration protocol. |
| */ |
| |
| remote_public = l_key_new(L_KEY_RAW, wsc->m1->public_key, |
| sizeof(wsc->m1->public_key)); |
| if (!remote_public) |
| return; |
| |
| r = l_key_compute_dh_secret(remote_public, wsc->private, dh5_prime, |
| shared_secret, &shared_secret_len); |
| l_key_free(remote_public); |
| |
| if (!r) |
| return; |
| |
| sha256 = l_checksum_new(L_CHECKSUM_SHA256); |
| if (!sha256) |
| return; |
| |
| l_checksum_update(sha256, shared_secret, shared_secret_len); |
| explicit_bzero(shared_secret, shared_secret_len); |
| l_checksum_get_digest(sha256, dhkey, sizeof(dhkey)); |
| l_checksum_free(sha256); |
| |
| hmac_sha256 = l_checksum_new_hmac(L_CHECKSUM_SHA256, |
| dhkey, sizeof(dhkey)); |
| explicit_bzero(dhkey, sizeof(dhkey)); |
| |
| if (!hmac_sha256) |
| return; |
| |
| iov[0].iov_base = wsc->m1->enrollee_nonce; |
| iov[0].iov_len = 16; |
| iov[1].iov_base = wsc->m1->addr; |
| iov[1].iov_len = 6; |
| iov[2].iov_base = wsc->m2->registrar_nonce; |
| iov[2].iov_len = 16; |
| |
| l_checksum_updatev(hmac_sha256, iov, 3); |
| l_checksum_get_digest(hmac_sha256, kdk, sizeof(kdk)); |
| l_checksum_free(hmac_sha256); |
| |
| r = wsc_kdf(kdk, &keys, sizeof(keys)); |
| explicit_bzero(kdk, sizeof(kdk)); |
| if (!r) |
| return; |
| |
| wsc->hmac_auth_key = l_checksum_new_hmac(L_CHECKSUM_SHA256, |
| keys.auth_key, |
| sizeof(keys.auth_key)); |
| |
| /* |
| * AuthKey is uploaded into the kernel, once we upload KeyWrapKey, |
| * the keys variable is no longer useful. Make sure to wipe it |
| */ |
| wsc->aes_cbc_128 = l_cipher_new(L_CIPHER_AES_CBC, keys.keywrap_key, |
| sizeof(keys.keywrap_key)); |
| explicit_bzero(&keys, sizeof(keys)); |
| |
| eap_wsc_r_send_m2(eap, pdu, len); |
| } |
| |
| static void eap_wsc_handle_nack(struct eap_state *eap, |
| const uint8_t *pdu, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| struct wsc_nack nack; |
| |
| if (wsc_parse_wsc_nack(pdu, len, &nack) != 0) |
| return; |
| |
| if (wsc->state != STATE_EXPECT_M1 && memcmp(nack.enrollee_nonce, |
| wsc->m1->enrollee_nonce, |
| sizeof(nack.enrollee_nonce))) |
| return; |
| |
| if (!wsc->m2) |
| return; |
| |
| if (memcmp(nack.registrar_nonce, wsc->m2->registrar_nonce, |
| sizeof(nack.registrar_nonce))) |
| return; |
| |
| /* |
| * The spec is completely unclear what the NACK error should be set |
| * to. Our choice is to reflect back what the request error code is |
| * in the response. |
| */ |
| if (!wsc->registrar) |
| eap_wsc_send_nack(eap, nack.configuration_error); |
| |
| eap_method_error(eap); |
| } |
| |
| static void eap_wsc_handle_message(struct eap_state *eap, |
| const uint8_t *pkt, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| uint8_t op; |
| uint8_t flags; |
| uint8_t *pdu; |
| size_t pdu_len; |
| size_t rx_header_offset = 0; |
| |
| /* |
| * pkt[0:len] is an EAP-Request packet contents if !wsc->registrar |
| * and an EAP-Response otherwise. Most of the parsing is going to |
| * be identical though. |
| * |
| * Since we have disjoint sets of enum state values between |
| * Enrollee and Registrar, most of the time we don't need to look |
| * at wsc->registrar. |
| */ |
| |
| if (len < 2) |
| return; |
| |
| op = pkt[0]; |
| flags = pkt[1]; |
| |
| pkt += 2; |
| len -= 2; |
| |
| switch (op) { |
| case WSC_OP_START: |
| if (len) |
| return; |
| |
| if (wsc->state != STATE_EXPECT_START) |
| return; |
| |
| pdu = wsc_build_m1(wsc->m1, &pdu_len); |
| if (!pdu) |
| return; |
| |
| eap_wsc_send_message(eap, pdu, pdu_len); |
| wsc->state = STATE_EXPECT_M2; |
| return; |
| case WSC_OP_NACK: |
| if (!len) |
| return; |
| |
| eap_wsc_handle_nack(eap, pkt, len); |
| return; |
| case WSC_OP_ACK: |
| /* Not used in the in-band Enrollee Registration scenario */ |
| return; |
| case WSC_OP_DONE: |
| if (wsc->state != STATE_EXPECT_DONE) |
| return; |
| |
| /* |
| * Notify higher layers that the registration protocol has |
| * finished successfully as the EAP result is going to |
| * always be the same. May not be strictly needed as the |
| * higher layers get the real confirmation when the enrollee |
| * uses the credentials on their next connection. |
| */ |
| eap_method_event(eap, EAP_WSC_EVENT_CREDENTIAL_SENT, NULL); |
| eap_method_error(eap); |
| return; |
| case WSC_OP_FRAG_ACK: |
| if (wsc->tx_last_frag_len && |
| (wsc->tx_frag_offset + wsc->tx_last_frag_len) < |
| wsc->sent_len) { |
| wsc->tx_frag_offset += wsc->tx_last_frag_len; |
| wsc->tx_last_frag_len = 0; |
| |
| eap_wsc_send_fragment(eap); |
| } |
| |
| return; |
| case WSC_OP_MSG: |
| if (flags & WSC_FLAG_LF) { |
| if (wsc->rx_pdu_buf || |
| !(flags & WSC_FLAG_MF) || len < 2) |
| goto invalid_frag; |
| |
| wsc->rx_pdu_buf_len = l_get_be16(pkt); |
| |
| if (!wsc->rx_pdu_buf_len || |
| wsc->rx_pdu_buf_len > |
| EAP_WSC_PDU_MAX_LEN) { |
| l_warn("Fragmented pkt size is outside of " |
| "allowed boundaries [1, %u]", |
| EAP_WSC_PDU_MAX_LEN); |
| return; |
| } |
| |
| if (wsc->rx_pdu_buf_len < len) { |
| l_warn("Fragmented pkt size is smaller than " |
| "the received packet"); |
| return; |
| } |
| |
| wsc->rx_pdu_buf = l_malloc(wsc->rx_pdu_buf_len); |
| wsc->rx_pdu_buf_offset = 0; |
| |
| rx_header_offset = 2; |
| } |
| |
| if (wsc->rx_pdu_buf) { |
| pdu_len = len - rx_header_offset; |
| |
| if (wsc->rx_pdu_buf_len < |
| (wsc->rx_pdu_buf_offset + pdu_len)) { |
| l_error("Request fragment pkt size mismatch"); |
| goto invalid_frag; |
| } |
| |
| memcpy(wsc->rx_pdu_buf + wsc->rx_pdu_buf_offset, |
| pkt + rx_header_offset, pdu_len); |
| wsc->rx_pdu_buf_offset += pdu_len; |
| } |
| |
| if (flags & WSC_FLAG_MF) { |
| if (!wsc->rx_pdu_buf) |
| goto invalid_frag; |
| |
| eap_wsc_send_frag_ack(eap); |
| return; |
| } else if (wsc->rx_pdu_buf) { |
| if (wsc->rx_pdu_buf_len != wsc->rx_pdu_buf_offset) { |
| l_error("Message fragment pkt size mismatch"); |
| goto invalid_frag; |
| } |
| |
| pkt = wsc->rx_pdu_buf; |
| len = wsc->rx_pdu_buf_len; |
| } |
| |
| break; |
| } |
| |
| if (!len) |
| return; |
| |
| switch (wsc->state) { |
| /* Enrollee state machine */ |
| case STATE_EXPECT_START: |
| return; |
| case STATE_EXPECT_M2: |
| eap_wsc_handle_m2(eap, pkt, len); |
| break; |
| case STATE_EXPECT_M4: |
| eap_wsc_handle_m4(eap, pkt, len); |
| break; |
| case STATE_EXPECT_M6: |
| eap_wsc_handle_m6(eap, pkt, len); |
| break; |
| case STATE_EXPECT_M8: |
| eap_wsc_handle_m8(eap, pkt, len); |
| break; |
| case STATE_FINISHED: |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| |
| /* Registrar state machine */ |
| case STATE_EXPECT_M1: |
| eap_wsc_r_handle_m1(eap, pkt, len); |
| break; |
| case STATE_EXPECT_M3: |
| eap_wsc_r_handle_m3(eap, pkt, len); |
| break; |
| case STATE_EXPECT_M5: |
| eap_wsc_r_handle_m5(eap, pkt, len); |
| break; |
| case STATE_EXPECT_M7: |
| eap_wsc_r_handle_m7(eap, pkt, len); |
| break; |
| case STATE_EXPECT_IDENTITY: |
| case STATE_EXPECT_DONE: |
| eap_wsc_send_nack(eap, WSC_CONFIGURATION_ERROR_NO_ERROR); |
| return; |
| } |
| |
| if (wsc->rx_pdu_buf) { |
| l_free(wsc->rx_pdu_buf); |
| wsc->rx_pdu_buf = NULL; |
| wsc->rx_pdu_buf_len = 0; |
| wsc->rx_pdu_buf_offset = 0; |
| } |
| |
| return; |
| |
| invalid_frag: |
| eap_method_error(eap); |
| } |
| |
| static void eap_wsc_handle_retransmit(struct eap_state *eap, |
| const uint8_t *pkt, size_t len) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| uint8_t op; |
| uint8_t flags; |
| |
| if (len < 2) |
| return; |
| |
| op = pkt[0]; |
| |
| switch (op) { |
| case WSC_OP_NACK: |
| eap_wsc_handle_nack(eap, pkt + 2, len - 2); |
| return; |
| case WSC_OP_ACK: |
| case WSC_OP_DONE: |
| /* Should never receive these as Enrollee */ |
| return; |
| case WSC_OP_MSG: |
| flags = pkt[1]; |
| |
| if (flags & WSC_FLAG_MF) { |
| if (!wsc->rx_pdu_buf) { |
| eap_method_error(eap); |
| return; |
| } |
| |
| eap_wsc_send_frag_ack(eap); |
| return; |
| } |
| } |
| |
| if (!wsc->sent_pdu || !wsc->sent_len) { |
| eap_method_error(eap); |
| return; |
| } |
| |
| if (wsc->sent_len + EAP_WSC_HEADER_LEN > eap_get_mtu(eap)) { |
| eap_wsc_send_fragment(eap); |
| } else { |
| size_t msg_len = wsc->sent_len + EAP_WSC_HEADER_LEN; |
| uint8_t buf[msg_len]; |
| |
| buf[12] = WSC_OP_MSG; |
| buf[13] = 0; |
| memcpy(buf + EAP_WSC_HEADER_LEN, wsc->sent_pdu, wsc->sent_len); |
| |
| eap_method_respond(eap, buf, msg_len); |
| } |
| } |
| |
| static bool load_hexencoded(struct l_settings *settings, const char *key, |
| uint8_t *to, size_t len) |
| { |
| uint8_t *v; |
| size_t v_len; |
| |
| v = l_settings_get_bytes(settings, "WSC", key, &v_len); |
| if (!v) |
| return false; |
| |
| if (v_len != len) { |
| explicit_bzero(v, v_len); |
| l_free(v); |
| return false; |
| } |
| |
| memcpy(to, v, len); |
| explicit_bzero(v, v_len); |
| l_free(v); |
| |
| return true; |
| } |
| |
| static bool load_primary_device_type(struct l_settings *settings, |
| struct wsc_primary_device_type *pdt) |
| { |
| const char *v; |
| int r; |
| |
| v = l_settings_get_value(settings, "WSC", "PrimaryDeviceType"); |
| if (!v) |
| return false; |
| |
| r = sscanf(v, "%hx-%2hhx%2hhx%2hhx%2hhx-%2hx", &pdt->category, |
| &pdt->oui[0], &pdt->oui[1], &pdt->oui[2], |
| &pdt->oui_type, &pdt->subcategory); |
| if (r != 6) |
| return false; |
| |
| return true; |
| } |
| |
| static bool load_constrained_string(struct l_settings *settings, |
| const char *key, |
| char *out, size_t max) |
| { |
| char *v; |
| size_t tocopy; |
| |
| v = l_settings_get_string(settings, "WSC", key); |
| if (!v) |
| return false; |
| |
| tocopy = strlen(v); |
| if (tocopy >= max) |
| tocopy = max - 1; |
| |
| memcpy(out, v, tocopy); |
| out[max - 1] = '\0'; |
| |
| l_free(v); |
| |
| return true; |
| } |
| |
| static bool load_device_data(struct l_settings *settings, |
| char *manufacturer, |
| char *model_name, |
| char *model_number, |
| char *serial_number, |
| struct wsc_primary_device_type *dev_type, |
| char *device_name, |
| uint8_t *rf_bands, |
| uint32_t *os_version) |
| { |
| struct wsc_m1 *m1; |
| unsigned int u32; |
| |
| if (!load_constrained_string(settings, "Manufacturer", |
| manufacturer, sizeof(m1->manufacturer))) |
| strcpy(manufacturer, " "); |
| |
| if (!load_constrained_string(settings, "ModelName", |
| model_name, sizeof(m1->model_name))) |
| strcpy(model_name, " "); |
| |
| if (!load_constrained_string(settings, "ModelNumber", |
| model_number, sizeof(m1->model_number))) |
| strcpy(model_number, " "); |
| |
| if (!load_constrained_string(settings, "SerialNumber", |
| serial_number, sizeof(m1->serial_number))) |
| strcpy(serial_number, " "); |
| |
| if (!load_primary_device_type(settings, dev_type)) { |
| /* Make ourselves a WFA standard PC by default */ |
| dev_type->category = 1; |
| memcpy(dev_type->oui, wsc_wfa_oui, 3); |
| dev_type->oui_type = 0x04; |
| dev_type->subcategory = 1; |
| } |
| |
| if (!load_constrained_string(settings, "DeviceName", |
| device_name, sizeof(m1->device_name))) |
| strcpy(device_name, " "); |
| |
| if (!l_settings_get_uint(settings, "WSC", "RFBand", &u32)) |
| return false; |
| |
| switch (u32) { |
| case WSC_RF_BAND_2_4_GHZ: |
| case WSC_RF_BAND_5_0_GHZ: |
| case WSC_RF_BAND_60_GHZ: |
| *rf_bands = u32; |
| break; |
| default: |
| return false; |
| } |
| |
| if (!l_settings_get_uint(settings, "WSC", "OSVersion", &u32)) |
| u32 = 0; |
| |
| *os_version = u32 & 0x7fffffff; |
| |
| return true; |
| } |
| |
| static struct eap_wsc_state *eap_wsc_new_common(struct l_settings *settings, |
| bool registrar) |
| { |
| struct eap_wsc_state *wsc; |
| const char *v; |
| uint8_t private_key[192]; |
| size_t len; |
| unsigned int u32; |
| |
| wsc = l_new(struct eap_wsc_state, 1); |
| wsc->registrar = registrar; |
| |
| wsc->m1 = l_new(struct wsc_m1, 1); |
| |
| if (registrar) |
| wsc->m2 = l_new(struct wsc_m2, 1); |
| |
| v = l_settings_get_value(settings, "WSC", "EnrolleeMAC"); |
| if (!v) |
| goto err; |
| |
| if (!util_string_to_address(v, wsc->m1->addr)) |
| goto err; |
| |
| if (!l_settings_get_uint(settings, "WSC", "ConfigurationMethods", &u32)) |
| u32 = WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN; |
| |
| if (!registrar) |
| wsc->m1->config_methods = u32; |
| else |
| wsc->m2->config_methods = u32; |
| |
| if (!l_settings_get_uint(settings, "WSC", "DevicePasswordId", &u32)) |
| u32 = WSC_DEVICE_PASSWORD_ID_PUSH_BUTTON; |
| |
| if (!registrar) |
| wsc->m1->device_password_id = u32; |
| else |
| wsc->m2->device_password_id = u32; |
| |
| wsc->device_password = l_settings_get_string(settings, "WSC", |
| "DevicePassword"); |
| if (wsc->device_password) { |
| int i; |
| |
| for (i = 0; wsc->device_password[i]; i++) { |
| if (!l_ascii_isxdigit(wsc->device_password[i])) |
| goto err; |
| } |
| |
| /* |
| * WSC 2.0.5: Section 7.4: |
| * If an out-of-band mechanism is used as the configuration |
| * method, the device password is expressed in hexadecimal |
| * using ASCII character (two characters per octet, uppercase |
| * letters only). |
| */ |
| for (i = 0; wsc->device_password[i]; i++) { |
| if (wsc->device_password[i] >= 'a' && |
| wsc->device_password[i] <= 'f') |
| wsc->device_password[i] = |
| 'A' + wsc->device_password[i] - 'a'; |
| } |
| } else |
| wsc->device_password = l_strdup("00000000"); |
| |
| /* |
| * The settings we read below probably shouldn't be used except to |
| * eliminate randomness in debugging/tests. |
| */ |
| if (load_hexencoded(settings, "PrivateKey", private_key, 192)) { |
| if (!l_key_validate_dh_payload(private_key, 192, |
| crypto_dh5_prime, |
| crypto_dh5_prime_size)) { |
| explicit_bzero(private_key, 192); |
| goto err; |
| } |
| |
| wsc->private = l_key_new(L_KEY_RAW, private_key, 192); |
| explicit_bzero(private_key, 192); |
| } else |
| wsc->private = l_key_generate_dh_private(crypto_dh5_prime, |
| crypto_dh5_prime_size); |
| |
| if (!wsc->private) |
| goto err; |
| |
| len = sizeof(wsc->m1->public_key); |
| if (!l_key_compute_dh_public(dh5_generator, wsc->private, dh5_prime, |
| registrar ? wsc->m2->public_key : |
| wsc->m1->public_key, &len)) |
| goto err; |
| |
| if (len != sizeof(wsc->m1->public_key)) |
| goto err; |
| |
| if (!load_hexencoded(settings, registrar ? "R-SNonce1" : "E-SNonce1", |
| wsc->local_snonce1, 16)) |
| l_getrandom(wsc->local_snonce1, 16); |
| |
| if (!load_hexencoded(settings, registrar ? "R-SNonce2" : "E-SNonce2", |
| wsc->local_snonce2, 16)) |
| l_getrandom(wsc->local_snonce2, 16); |
| |
| if (!load_hexencoded(settings, "IV1", wsc->iv1, 16)) |
| l_getrandom(wsc->iv1, 16); |
| |
| if (!load_hexencoded(settings, "IV2", wsc->iv2, 16)) |
| l_getrandom(wsc->iv2, 16); |
| |
| return wsc; |
| |
| err: |
| eap_wsc_state_free(wsc); |
| return NULL; |
| } |
| |
| static bool eap_wsc_load_settings(struct eap_state *eap, |
| struct l_settings *settings, |
| const char *prefix) |
| { |
| struct eap_wsc_state *wsc = eap_wsc_new_common(settings, false); |
| |
| if (!wsc) |
| return false; |
| |
| wsc->m1->version2 = true; |
| wsc->m1->state = WSC_STATE_NOT_CONFIGURED; |
| wsc->m1->association_state = WSC_ASSOCIATION_STATE_NOT_ASSOCIATED; |
| wsc->m1->configuration_error = WSC_CONFIGURATION_ERROR_NO_ERROR; |
| |
| wsc->m1->auth_type_flags = WSC_AUTHENTICATION_TYPE_WPA2_PERSONAL | |
| WSC_AUTHENTICATION_TYPE_WPA_PERSONAL | |
| WSC_AUTHENTICATION_TYPE_OPEN; |
| wsc->m1->encryption_type_flags = WSC_ENCRYPTION_TYPE_NONE | |
| WSC_ENCRYPTION_TYPE_AES_TKIP; |
| wsc->m1->connection_type_flags = WSC_CONNECTION_TYPE_ESS; |
| |
| if (!wsc_uuid_from_addr(wsc->m1->addr, wsc->m1->uuid_e)) |
| goto err; |
| |
| if (!load_device_data(settings, |
| wsc->m1->manufacturer, |
| wsc->m1->model_name, |
| wsc->m1->model_number, |
| wsc->m1->serial_number, |
| &wsc->m1->primary_device_type, |
| wsc->m1->device_name, |
| &wsc->m1->rf_bands, |
| &wsc->m1->os_version)) |
| goto err; |
| |
| /* Debug settings */ |
| if (!load_hexencoded(settings, "EnrolleeNonce", |
| wsc->m1->enrollee_nonce, 16)) |
| l_getrandom(wsc->m1->enrollee_nonce, 16); |
| |
| wsc->state = STATE_EXPECT_START; |
| eap_set_data(eap, wsc); |
| |
| return true; |
| |
| err: |
| eap_wsc_state_free(wsc); |
| return false; |
| } |
| |
| static struct eap_method eap_wsc = { |
| .vendor_id = { 0x00, 0x37, 0x2a }, |
| .vendor_type = 0x00000001, |
| .request_type = EAP_TYPE_EXPANDED, |
| .exports_msk = true, |
| .name = "WSC", |
| .free = eap_wsc_free, |
| .handle_request = eap_wsc_handle_message, |
| .handle_retransmit = eap_wsc_handle_retransmit, |
| .load_settings = eap_wsc_load_settings, |
| }; |
| |
| static bool eap_wsc_r_load_settings(struct eap_state *eap, |
| struct l_settings *settings, |
| const char *prefix) |
| { |
| /* |
| * On the Registrar we store some information in wsc->m1 for the |
| * actual M1's validation, and we fill in the elements of M2 |
| * that don't depend on the data received in M1. |
| */ |
| struct eap_wsc_state *wsc = eap_wsc_new_common(settings, true); |
| char *str; |
| |
| if (!wsc) |
| return false; |
| |
| if (!load_hexencoded(settings, "UUID-E", wsc->m1->uuid_e, 16)) |
| goto err; |
| |
| if (!load_hexencoded(settings, "UUID-R", wsc->m2->uuid_r, 16)) |
| goto err; |
| |
| if (!load_device_data(settings, |
| wsc->m2->manufacturer, |
| wsc->m2->model_name, |
| wsc->m2->model_number, |
| wsc->m2->serial_number, |
| &wsc->m2->primary_device_type, |
| wsc->m2->device_name, |
| &wsc->m2->rf_bands, |
| &wsc->m2->os_version)) |
| goto err; |
| |
| wsc->m2->auth_type_flags = 0; |
| |
| str = l_settings_get_string(settings, "WSC", "WPA2-SSID"); |
| if (str) { |
| if (strlen(str) > 32) |
| goto err; |
| |
| wsc->m2->auth_type_flags |= |
| WSC_AUTHENTICATION_TYPE_WPA2_PERSONAL; |
| wsc->m2->encryption_type_flags |= WSC_ENCRYPTION_TYPE_AES_TKIP; |
| wsc->wpa2_cred.auth_type = |
| WSC_AUTHENTICATION_TYPE_WPA2_PERSONAL; |
| wsc->wpa2_cred.encryption_type = WSC_ENCRYPTION_TYPE_AES_TKIP; |
| |
| wsc->wpa2_cred.ssid_len = strlen(str); |
| memcpy(wsc->wpa2_cred.ssid, str, wsc->wpa2_cred.ssid_len); |
| l_free(str); |
| |
| str = l_settings_get_string(settings, "WSC", "WPA2-Passphrase"); |
| if (str) { |
| size_t len = strlen(str); |
| |
| if (len < 8 || len > 63) { |
| explicit_bzero(str, len); |
| goto err; |
| } |
| |
| memcpy(wsc->wpa2_cred.network_key, str, len); |
| wsc->wpa2_cred.network_key_len = len; |
| explicit_bzero(str, len); |
| } else { |
| uint8_t buf[32]; |
| |
| if (!load_hexencoded(settings, "WPA2-PSK", buf, 32)) |
| goto err; |
| |
| str = l_util_hexstring(buf, 32); |
| explicit_bzero(buf, 32); |
| memcpy(wsc->wpa2_cred.network_key, str, 64); |
| explicit_bzero(str, 64); |
| free(str); |
| wsc->wpa2_cred.network_key_len = 64; |
| } |
| |
| memcpy(wsc->wpa2_cred.addr, wsc->m1->addr, 6); |
| } |
| |
| str = l_settings_get_string(settings, "WSC", "Open-SSID"); |
| if (str) { |
| if (strlen(str) > 32) |
| goto err; |
| |
| wsc->m2->auth_type_flags |= WSC_AUTHENTICATION_TYPE_OPEN; |
| wsc->m2->encryption_type_flags |= WSC_ENCRYPTION_TYPE_NONE; |
| wsc->open_cred.auth_type = WSC_AUTHENTICATION_TYPE_OPEN; |
| wsc->open_cred.encryption_type = WSC_ENCRYPTION_TYPE_NONE; |
| |
| wsc->open_cred.ssid_len = strlen(str); |
| memcpy(wsc->open_cred.ssid, str, wsc->open_cred.ssid_len); |
| l_free(str); |
| |
| wsc->open_cred.network_key_len = 0; |
| |
| memcpy(wsc->open_cred.addr, wsc->m1->addr, 6); |
| } |
| |
| if (!wsc->m2->auth_type_flags) |
| goto err; |
| |
| wsc->m2->connection_type_flags = WSC_CONNECTION_TYPE_ESS; |
| |
| /* Debug settings */ |
| if (!load_hexencoded(settings, "RegistrarNonce", |
| wsc->m2->registrar_nonce, 16)) |
| l_getrandom(wsc->m2->registrar_nonce, 16); |
| |
| if (!load_hexencoded(settings, "IV3", wsc->iv3, 16)) |
| l_getrandom(wsc->iv3, 16); |
| |
| wsc->state = STATE_EXPECT_IDENTITY; |
| eap_set_data(eap, wsc); |
| |
| return true; |
| |
| err: |
| eap_wsc_state_free(wsc); |
| return false; |
| } |
| |
| static bool eap_wsc_r_validate_identity(struct eap_state *eap, |
| const char *identity) |
| { |
| struct eap_wsc_state *wsc = eap_get_data(eap); |
| |
| if (strcmp(identity, "WFA-SimpleConfig-Enrollee-1-0")) |
| return false; |
| |
| /* Identity Request/Response done, directly send the WSC_Start */ |
| eap_wsc_r_send_start(eap); |
| wsc->state = STATE_EXPECT_M1; |
| return true; |
| } |
| |
| static struct eap_method eap_wsc_r = { |
| .name = "WSC-R", |
| .request_type = EAP_TYPE_EXPANDED, |
| .vendor_id = { 0x00, 0x37, 0x2a }, |
| .vendor_type = 0x00000001, |
| .exports_msk = true, |
| .free = eap_wsc_free, |
| .load_settings = eap_wsc_r_load_settings, |
| .validate_identity = eap_wsc_r_validate_identity, |
| .handle_response = eap_wsc_handle_message, |
| }; |
| |
| static int eap_wsc_init(void) |
| { |
| int r = -ENOTSUP; |
| |
| l_debug(""); |
| |
| dh5_generator = l_key_new(L_KEY_RAW, crypto_dh5_generator, |
| crypto_dh5_generator_size); |
| if (!dh5_generator) |
| goto fail_generator; |
| |
| dh5_prime = l_key_new(L_KEY_RAW, crypto_dh5_prime, |
| crypto_dh5_prime_size); |
| if (!dh5_prime) |
| goto fail_prime; |
| |
| r = eap_register_method(&eap_wsc); |
| if (r) |
| goto fail_register; |
| |
| r = eap_register_method(&eap_wsc_r); |
| if (!r) |
| return 0; |
| |
| eap_unregister_method(&eap_wsc); |
| |
| fail_register: |
| l_key_free(dh5_prime); |
| dh5_prime = NULL; |
| fail_prime: |
| l_key_free(dh5_generator); |
| dh5_generator = NULL; |
| fail_generator: |
| return r; |
| } |
| |
| static void eap_wsc_exit(void) |
| { |
| l_debug(""); |
| |
| eap_unregister_method(&eap_wsc); |
| eap_unregister_method(&eap_wsc_r); |
| |
| l_key_free(dh5_prime); |
| l_key_free(dh5_generator); |
| } |
| |
| EAP_METHOD_BUILTIN(eap_wsc, eap_wsc_init, eap_wsc_exit) |