diff --git a/Makefile.am b/Makefile.am index c2d3d1b7..78cbe763 100644 --- a/Makefile.am +++ b/Makefile.am @@ -111,6 +111,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h \ src/eap-pwd.c \ src/ecc.h src/ecc.c \ src/adhoc.h src/adhoc.c \ + src/sae.h src/sae.c \ $(builtin_sources) src_iwd_LDADD = ell/libell-internal.la -ldl diff --git a/src/sae.c b/src/sae.c new file mode 100644 index 00000000..5777510d --- /dev/null +++ b/src/sae.c @@ -0,0 +1,1016 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2018 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 "util.h" +#include "ie.h" +#include "handshake.h" +#include "crypto.h" +#include "mpdu.h" +#include "ecc.h" +#include "sae.h" + +#define SAE_RETRANSMIT_TIMEOUT 2 +#define SAE_SYNC_MAX 3 + +enum sae_state { + SAE_STATE_NOTHING = 0, + SAE_STATE_COMMITTED = 1, + SAE_STATE_CONFIRMED = 2, + SAE_STATE_ACCEPTED = 3, +}; + +struct sae_sm { + struct handshake_state *handshake; + struct ecc_point pwe; + enum sae_state state; + uint64_t rand[NUM_ECC_DIGITS]; + uint64_t scalar[NUM_ECC_DIGITS]; + uint64_t p_scalar[NUM_ECC_DIGITS]; + struct ecc_point element; + struct ecc_point p_element; + uint16_t send_confirm; + uint8_t kck[32]; + uint8_t pmk[32]; + uint8_t pmkid[16]; + uint8_t *token; + size_t token_len; + /* number of state resyncs that have occurred */ + uint16_t sync; + /* number of SAE confirm messages that have been sent */ + uint16_t sc; + /* received value of the send-confirm counter */ + uint16_t rc; + + sae_tx_packet_func_t tx; + sae_complete_func_t complete; + void *user_data; +}; + +static uint64_t curve_p[NUM_ECC_DIGITS] = CURVE_P_32; +static uint64_t curve_n[NUM_ECC_DIGITS] = CURVE_N_32; + +static bool H(const uint8_t *key, size_t key_len, uint8_t num_args, + uint8_t *out, ...) +{ + struct l_checksum *hmac; + struct iovec iov[num_args]; + va_list va; + int i; + int ret; + + va_start(va, out); + + hmac = l_checksum_new_hmac(L_CHECKSUM_SHA256, key, key_len); + if (!hmac) + return false; + + for (i = 0; i < num_args; i++) { + iov[i].iov_base = va_arg(va, void *); + iov[i].iov_len = va_arg(va, size_t); + } + + if (!l_checksum_updatev(hmac, iov, num_args)) + return false; + + ret = l_checksum_get_digest(hmac, out, 32); + l_checksum_free(hmac); + + return (ret == 32); +} + +/* calculate random quadratic residue */ +static void sae_get_qr(uint64_t *qr) +{ + l_getrandom(qr, 32); + + while (vli_legendre(qr, curve_p) != -1) + l_getrandom(qr, 32); +} + +/* calculate random quadratic non-residue */ +static void sae_get_qnr(uint64_t *qnr) +{ + l_getrandom(qnr, 32); + + while (vli_legendre(qnr, curve_p) != 1) + l_getrandom(qnr, 32); +} + +/* blinding technique to determine if 'value' is quadratic residue */ +static bool sae_is_quadratic_residue(uint64_t *value, uint64_t *qr, + uint64_t *qnr) +{ + uint64_t y_sqr[NUM_ECC_DIGITS]; + uint64_t r[NUM_ECC_DIGITS]; + uint64_t num[NUM_ECC_DIGITS]; + + ecc_compute_y_sqr(y_sqr, value); + + l_getrandom(r, 32); + + while (vli_cmp(r, curve_p) >= 0) + l_getrandom(r, 32); + + vli_mod_mult_fast(num, y_sqr, r); + vli_mod_mult_fast(num, num, r); + + if (r[0] & 1) { + vli_mod_mult_fast(num, num, qr); + + if (vli_legendre(num, curve_p) == -1) + return true; + } else { + vli_mod_mult_fast(num, num, qnr); + + if (vli_legendre(num, curve_p) == 1) + return true; + } + + return false; +} + +static bool sae_pwd_seed(const uint8_t *addr1, const uint8_t *addr2, + uint8_t *base, size_t base_len, + uint8_t counter, uint8_t *out) +{ + uint8_t key[12]; + + if (memcmp(addr1, addr2, 6) > 0) { + memcpy(key, addr1, 6); + memcpy(key + 6, addr2, 6); + } else { + memcpy(key, addr2, 6); + memcpy(key + 6, addr1, 6); + } + + return H(key, 12, 2, out, base, base_len, &counter, 1); +} + +static bool sae_pwd_value(uint8_t *pwd_seed, uint64_t *pwd_value) +{ + uint64_t prime[NUM_ECC_DIGITS]; + + memcpy(prime, curve_p, 32); + + ecc_be2native(prime); + + return kdf_sha256(pwd_seed, 32, "SAE Hunting and Pecking", + strlen("SAE Hunting and Pecking"), prime, 32, + pwd_value, 32); +} + +/* IEEE 802.11-2016 - Section 12.4.2 Assumptions on SAE */ +static bool sae_cn(const uint8_t *kck, uint16_t send_confirm, + const uint64_t *scalar1, const uint64_t *element1, + const uint64_t *scalar2, const uint64_t *element2, + uint8_t *confirm) +{ + struct l_checksum *hmac; + struct iovec iov[5]; + int ret; + + hmac = l_checksum_new_hmac(L_CHECKSUM_SHA256, kck, 32); + if (!hmac) + return false; + + iov[0].iov_base = &send_confirm; + iov[0].iov_len = 2; + iov[1].iov_base = (void *) scalar1; + iov[1].iov_len = 32; + iov[2].iov_base = (void *) element1; + iov[2].iov_len = 64; + iov[3].iov_base = (void *) scalar2; + iov[3].iov_len = 32; + iov[4].iov_base = (void *) element2; + iov[4].iov_len = 64; + + l_checksum_updatev(hmac, iov, 5); + + ret = l_checksum_get_digest(hmac, confirm, 32); + + l_checksum_free(hmac); + + return (ret == 32); +} + +static void sae_authentication_failed(struct sae_sm *sm, uint16_t reason) +{ + sm->complete(reason, sm->user_data); + + sae_sm_free(sm); +} + +static void sae_reject_authentication(struct sae_sm *sm, uint16_t reason) +{ + uint8_t reject[6]; + uint8_t *ptr = reject; + + /* transaction */ + l_put_u16(1, ptr); + ptr += 2; + /* status success */ + l_put_u16(reason, ptr); + ptr += 2; + + if (reason == MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP) { + l_put_u16(19, ptr); + ptr += 2; + } + + sm->tx(sm->handshake->aa, reject, ptr - reject, sm->user_data); + + sae_authentication_failed(sm, reason); +} + +/* + * IEEE 802.11-2016 Section 12.4.4.2.2 + * Generation of the password element with ECC groups + */ +static bool sae_compute_pwe(char *password, const uint8_t *addr1, + const uint8_t *addr2, struct ecc_point *pwe) +{ + bool found = false; + uint8_t counter = 1; + uint8_t k = 20; + uint8_t pwd_seed[32]; + uint64_t pwd_value[NUM_ECC_DIGITS]; + uint8_t random[32]; + uint8_t *base = (uint8_t *) password; + size_t base_len = strlen(password); + uint8_t save[32] = { 0 }; + uint64_t qr[NUM_ECC_DIGITS]; + uint64_t qnr[NUM_ECC_DIGITS]; + + /* create qr/qnr prior to beginning hunting-and-pecking loop */ + sae_get_qr(qr); + sae_get_qnr(qnr); + + do { + /* pwd-seed = H(max(addr1, addr2) || min(addr1, addr2), + * base || counter) + * pwd-value = KDF-256(pwd-seed, "SAE Hunting and Pecking", p) + */ + sae_pwd_seed(addr1, addr2, base, base_len, counter, pwd_seed); + + sae_pwd_value(pwd_seed, pwd_value); + + ecc_be2native(pwd_value); + + /* if (pwd-value < p) { */ + if (vli_cmp(pwd_value, curve_p) < 0) { + if (sae_is_quadratic_residue(pwd_value, qr, qnr)) { + if (found == false) { + memcpy(pwe->x, pwd_value, 32); + memcpy(save, pwd_seed, 32); + + l_getrandom(random, 32); + base = random; + base_len = 32; + + found = true; + } + } + } + + counter++; + + } while ((counter <= k) || (found == false)); + + if (!found) { + l_error("max PWE iterations reached!"); + return false; + } + + if (!ecc_compute_y(pwe->y, pwe->x)) { + /* should always return true */ + l_error("computing y failed, was x quadratic residue?"); + return false; + } + + if ((pwe->y[0] & 1) != (save[31] & 1)) + vli_mod_sub(pwe->y, curve_p, pwe->y, curve_p); + + return true; +} + +/* commit-scalar = (rand + mask) mod r */ +static void sae_get_commit_scalar(uint64_t *scalar, uint64_t *mask, + uint64_t *rand) +{ + uint64_t _1[NUM_ECC_DIGITS] = { 1ull }; + + l_getrandom(rand, ECC_BYTES); + + /* ensure 1 < p_rand < r */ + while (!((vli_cmp(rand, _1) > 0) && + (vli_cmp(rand, curve_n) < 0))) + l_getrandom(rand, ECC_BYTES); + + l_getrandom(mask, ECC_BYTES); + + /* ensure 1 < p_mask < r */ + while (!((vli_cmp(mask, _1) > 0) && + (vli_cmp(mask, curve_n) < 0))) + l_getrandom(mask, ECC_BYTES); + + /* (rand + mask) mod r */ + vli_mod_add(scalar, rand, mask, curve_n); +} + +/* commit-element = inv(mask * PWE) */ +static bool sae_get_commit_element(struct ecc_point *element, + struct ecc_point *pwe, uint64_t *mask) +{ + /* p_mask * PWE */ + ecc_point_mult(element, pwe, mask, NULL, vli_num_bits(mask)); + + if (!ecc_valid_point(element)) + return false; + + /* inv(p_mask * PWE) */ + vli_sub(element->y, curve_p, element->y); + + if (!ecc_valid_point(element)) + return false; + + return true; +} + +static bool sae_build_commit(struct sae_sm *sm, const uint8_t *addr1, + const uint8_t *addr2, uint8_t *commit, + size_t *len, bool retry) +{ + uint64_t scalar[NUM_ECC_DIGITS]; + uint64_t mask[NUM_ECC_DIGITS]; + struct ecc_point element; + uint8_t *ptr = commit; + + if (retry) + goto old_commit; + + if (!sm->handshake->passphrase) { + l_error("no handshake passphrase found"); + return false; + } + + if (!sae_compute_pwe(sm->handshake->passphrase, addr1, + addr2, &sm->pwe)) { + l_error("could not compute PWE"); + return false; + } + + sae_get_commit_scalar(sm->scalar, mask, sm->rand); + + if (!sae_get_commit_element(&sm->element, &sm->pwe, mask)) { + l_error("error calculating commit element"); + return false; + } + + /* + * Several cases require retransmitting the same commit message. The + * anti-clogging code path requires this as well as the retransmition + * timeout. + */ +old_commit: + memcpy(scalar, sm->scalar, 32); + memcpy(element.x, sm->element.x, 32); + memcpy(element.y, sm->element.y, 32); + + ecc_native2be(scalar); + ecc_native2be(element.x); + ecc_native2be(element.y); + + /* transaction */ + l_put_u16(1, ptr); + ptr += 2; + /* status success */ + l_put_u16(0, ptr); + ptr += 2; + /* group */ + l_put_u16(19, ptr); + ptr += 2; + + if (sm->token) { + memcpy(ptr, sm->token, sm->token_len); + ptr += sm->token_len; + } + + memcpy(ptr, scalar, 32); + ptr += 32; + memcpy(ptr, element.x, 32); + ptr += 32; + memcpy(ptr, element.y, 32); + ptr += 32; + + *len = ptr - commit; + + return true; +} + +static void sae_send_confirm(struct sae_sm *sm) +{ + uint8_t confirm[32]; + uint8_t body[38]; + uint8_t *ptr = body; + + ecc_native2be(sm->scalar); + ecc_native2be(sm->element.x); + ecc_native2be(sm->element.y); + ecc_native2be(sm->p_scalar); + ecc_native2be(sm->p_element.x); + ecc_native2be(sm->p_element.y); + + /* + * confirm = CN(KCK, send-confirm, commit-scalar, COMMIT-ELEMENT, + * peer-commit-scalar, PEER-COMMIT-ELEMENT) + */ + sae_cn(sm->kck, sm->sc, sm->scalar, (uint64_t *) &sm->element, + sm->p_scalar, (uint64_t *) &sm->p_element, confirm); + + /* + * in case of retransmition, we will need to reuse these values, so + * go back to native endianness for consistency. + */ + ecc_be2native(sm->scalar); + ecc_be2native(sm->element.x); + ecc_be2native(sm->element.y); + ecc_be2native(sm->p_scalar); + ecc_be2native(sm->p_element.x); + ecc_be2native(sm->p_element.y); + + l_put_u16(2, ptr); + ptr += 2; + l_put_u16(0, ptr); + ptr += 2; + l_put_u16(sm->sc, ptr); + ptr += 2; + memcpy(ptr, confirm, 32); + ptr += 32; + + sm->sc++; + + sm->state = SAE_STATE_CONFIRMED; + + sm->tx(sm->handshake->aa, body, 38, sm->user_data); +} + +static void sae_process_commit(struct sae_sm *sm, const uint8_t *from, + const uint8_t *frame, size_t len) +{ + uint8_t *ptr = (uint8_t *) frame; + uint64_t k[NUM_ECC_DIGITS]; + struct ecc_point k_point; + uint8_t zero_key[32] = { 0 }; + uint8_t keyseed[32]; + uint8_t kck_and_pmk[2][32]; + uint64_t tmp[NUM_ECC_DIGITS]; + uint16_t group; + uint16_t reason = MMPDU_REASON_CODE_UNSPECIFIED; + + if (sm->state != SAE_STATE_COMMITTED) { + l_error("bad state %u", sm->state); + goto reject; + } + + if (len < 98) { + l_error("bad packet length"); + goto reject; + } + + group = l_get_u16(ptr); + ptr += 2; + + if (group != 19) { + l_error("unsupported group: %u", group); + reason = MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP; + goto reject; + } + + memcpy(sm->p_scalar, ptr, 32); + ptr += 32; + memcpy(sm->p_element.x, ptr, 32); + ptr += 32; + memcpy(sm->p_element.y, ptr, 32); + + ecc_be2native(sm->p_scalar); + ecc_be2native(sm->p_element.x); + ecc_be2native(sm->p_element.y); + + if (!memcmp(sm->p_scalar, sm->scalar, 32) || + !memcmp(sm->p_element.x, sm->element.x, 32) || + !memcmp(sm->p_element.y, sm->element.y, 32)) { + /* possible reflection attack, silently discard message */ + l_warn("peer scalar or element matched own, discarding frame"); + + return; + } + + sm->sc++; + + /* + * K = scalar-op(rand, (element-op(scalar-op(peer-commit-scalar, PWE), + * PEER-COMMIT-ELEMENT))) + */ + + /* k_point = scalar-op(peer-commit-scalar, PWE) */ + ecc_point_mult(&k_point, &sm->pwe, sm->p_scalar, NULL, + vli_num_bits(sm->p_scalar)); + + /* k_point = element-op(k_point, PEER-COMMIT-ELEMENT) */ + ecc_point_add(&k_point, &k_point, &sm->p_element); + + /* k_point = scalar-op(rand, k_point) */ + ecc_point_mult(&k_point, &k_point, sm->rand, NULL, + vli_num_bits(sm->rand)); + + /* + * IEEE 802.11-2016 - Section 12.4.4.2.1 ECC group definition + * ECC groups make use of a mapping function, F, that maps a + * point (x, y) that satisfies the curve equation to its x-coordinate— + * i.e., if P = (x, y) then F(P) = x. + */ + memcpy(k, k_point.x, 32); + + ecc_native2be(k); + + /* keyseed = H(<0>32, k) */ + hmac_sha256(zero_key, 32, k, 32, keyseed, 32); + + /* + * kck_and_pmk = KDF-Hash-512(keyseed, "SAE KCK and PMK", + (commit-scalar + peer-commit-scalar) mod r) + */ + vli_mod_add(tmp, sm->p_scalar, sm->scalar, curve_n); + + ecc_native2be(tmp); + + kdf_sha256(keyseed, 32, "SAE KCK and PMK", strlen("SAE KCK and PMK"), + tmp, 32, kck_and_pmk, 64); + + memcpy(sm->kck, kck_and_pmk[0], 32); + memcpy(sm->pmk, kck_and_pmk[1], 32); + + /* + * PMKID = L((commit-scalar + peer-commit-scalar) mod r, 0, 128) + */ + vli_mod_add(tmp, sm->scalar, sm->p_scalar, curve_n); + ecc_native2be(tmp); + /* don't set the handshakes pmkid until confirm is verified */ + memcpy(sm->pmkid, tmp, 16); + + sae_send_confirm(sm); + + return; + +reject: + sae_reject_authentication(sm, reason); +} + +static bool sae_verify_confirm(struct sae_sm *sm, const uint8_t *frame) +{ + uint8_t check[32]; + uint16_t rc = l_get_u16(frame); + + ecc_native2be(sm->scalar); + ecc_native2be(sm->element.x); + ecc_native2be(sm->element.y); + ecc_native2be(sm->p_scalar); + ecc_native2be(sm->p_element.x); + ecc_native2be(sm->p_element.y); + + sae_cn(sm->kck, rc, sm->p_scalar, + (const uint64_t *) &sm->p_element, sm->scalar, + (const uint64_t *) &sm->element, check); + + ecc_be2native(sm->scalar); + ecc_be2native(sm->element.x); + ecc_be2native(sm->element.y); + ecc_be2native(sm->p_scalar); + ecc_be2native(sm->p_element.x); + ecc_be2native(sm->p_element.y); + + if (memcmp(frame + 2, check, 32)) { + l_error("confirm did not match"); + return false; + } + + sm->rc = rc; + + return true; +} + +static void sae_process_confirm(struct sae_sm *sm, const uint8_t *from, + const uint8_t *frame, size_t len) +{ + const uint8_t *ptr = frame; + + if (sm->state != SAE_STATE_CONFIRMED) { + l_error("bad state %u", sm->state); + goto reject; + } + + if (len < 34) { + l_error("bad length"); + goto reject; + } + + if (!sae_verify_confirm(sm, ptr)) + goto reject; + + /* Sc shall be set to the value 2^16 - 1 */ + sm->sc = 0xffff; + + handshake_state_set_pmkid(sm->handshake, sm->pmkid); + handshake_state_set_pmk(sm->handshake, sm->pmk, 32); + + sm->complete(0, sm->user_data); + + sm->state = SAE_STATE_ACCEPTED; + + return; + +reject: + sae_reject_authentication(sm, MMPDU_REASON_CODE_UNSPECIFIED); +} + +static void sae_send_commit(struct sae_sm *sm, bool retry) +{ + struct handshake_state *hs = sm->handshake; + /* regular commit + possible 256 byte token */ + uint8_t commit[358]; + size_t len; + + if (!sae_build_commit(sm, hs->spa, hs->aa, commit, &len, retry)) + return; + + sm->state = SAE_STATE_COMMITTED; + + sm->tx(hs->aa, commit, len, sm->user_data); +} + +void sae_timeout(struct sae_sm *sm) +{ + /* regardless of state, reject if sync exceeds max */ + if (sm->sync > SAE_SYNC_MAX) { + sae_reject_authentication(sm, MMPDU_REASON_CODE_UNSPECIFIED); + return; + } + + sm->sync++; + + switch (sm->state) { + case SAE_STATE_COMMITTED: + sae_send_commit(sm, true); + break; + case SAE_STATE_CONFIRMED: + sm->sc++; + sae_send_confirm(sm); + break; + default: + /* should never happen */ + l_error("SAE timeout in bad state %u", sm->state); + return; + } +} + +/* + * 802.11-2016 - Section 12.4.8.6.4 + * If the Status code is ANTI_CLOGGING_TOKEN_REQUIRED, a new SAE Commit message + * shall be constructed with the Anti-Clogging Token from the received + * Authentication frame, and the commit-scalar and COMMIT-ELEMENT previously + * sent. The new SAE Commit message shall be transmitted to the peer, Sync shall + * be zeroed, and the t0 (retransmission) timer shall be set. + */ +static void sae_process_anti_clogging(struct sae_sm *sm, const uint8_t *ptr, + size_t len) +{ + /* + * IEEE 802.11-2016 - Section 12.4.6 Anti-clogging tokens + * + * It is suggested that an Anti-Clogging Token not exceed 256 octets + */ + if (len > 256) { + l_error("anti-clogging token size %ld too large, 256 max", len); + return; + } + + sm->token = l_memdup(ptr + 2, len - 2); + sm->token_len = len - 2; + sm->sync = 0; + + sae_send_commit(sm, true); +} + +/* + * 802.11-2016 - 12.4.8.6.3 Protocol instance behavior - Nothing state + */ +static bool sae_verify_nothing(struct sae_sm *sm, uint16_t transaction, + uint16_t status, const uint8_t *frame, + size_t len) +{ + /* + * TODO: This does not handle the transition from NOTHING -> CONFIRMED + * as this is only relevant to the AP or in Mesh mode which is not + * yet supported. + */ + if (transaction != SAE_STATE_COMMITTED) + return false; + + /* frame shall be silently discarded and Del event sent */ + if (status != 0) { + sae_authentication_failed(sm, MMPDU_REASON_CODE_UNSPECIFIED); + return false; + } + + /* reject with unsupported group */ + if (l_get_u16(frame) != 19) { + sae_reject_authentication(sm, + MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP); + return false; + } + + return true; +} + +/* + * 802.11-2016 - 12.4.8.6.4 Protocol instance behavior - Committed state + */ +static bool sae_verify_committed(struct sae_sm *sm, uint16_t transaction, + uint16_t status, const uint8_t *frame, + size_t len) +{ + /* + * Upon receipt of a Con event... + * Then the protocol instance checks the value of Sync. If it + * is greater than dot11RSNASAESync, the protocol instance shall send a + * Del event to the parent process and transition back to Nothing state. + * If Sync is not greater than dot11RSNASAESync, the protocol instance + * shall increment Sync, transmit the last SAE Commit message sent to + * the peer... + */ + if (transaction == SAE_STATE_CONFIRMED) { + if (sm->sync > SAE_SYNC_MAX) { + sae_authentication_failed(sm, + MMPDU_REASON_CODE_UNSPECIFIED); + return false; + } + + sm->sync++; + + sae_send_commit(sm, true); + + return false; + } + + switch (status) { + case MMPDU_REASON_CODE_ANTI_CLOGGING_TOKEN_REQ: + sae_process_anti_clogging(sm, frame, len); + return false; + case MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP: + l_error("AP requested unsupported FCC group %d", + l_get_u16(frame)); + + goto reject_unsupp_group; + case 0: + if (l_get_u16(frame) != 19) { + if (sm->sync > SAE_SYNC_MAX) { + sae_authentication_failed(sm, + MMPDU_REASON_CODE_UNSPECIFIED); + return false; + } + + sm->sync++; + + goto reject_unsupp_group; + } + + return true; + default: + /* + * If the Status is some other nonzero value, the frame shall + * be silently discarded... + */ + return false; + } + +reject_unsupp_group: + sae_reject_authentication(sm, + MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP); + return false; +} + +/* + * 802.11-2016 - 12.4.8.6.5 Protocol instance behavior - Confirmed state + */ +static bool sae_verify_confirmed(struct sae_sm *sm, uint16_t trans, + uint16_t status, const uint8_t *frame, + size_t len) +{ + if (trans == SAE_STATE_CONFIRMED) + return true; + + /* + * If the Status is nonzero, the frame shall be silently discarded... + */ + if (status != 0) + return false; + + /* + * If Sync is greater than dot11RSNASAESync, the protocol instance + * shall send the parent process a Del event and transitions back to + * Nothing state. + */ + if (sm->sync > SAE_SYNC_MAX) { + sae_authentication_failed(sm, MMPDU_REASON_CODE_UNSPECIFIED); + return false; + } + + /* frame shall be silently discarded */ + if (l_get_u16(frame) != 19) + return false; + + /* + * the protocol instance shall increment Sync, increment Sc, and + * transmit its Commit and Confirm (with the new Sc value) messages. + */ + sm->sync++; + sm->sc++; + + sae_send_commit(sm, true); + sae_send_confirm(sm); + + return false; +} + +/* + * 802.11-2016 - 12.4.8.6.6 Protocol instance behavior - Accepted state + */ +static bool sae_verify_accepted(struct sae_sm *sm, uint16_t trans, + uint16_t status, const uint8_t *frame, + size_t len) +{ + uint16_t sc; + + /* spec does not specify what to do here, so print and discard */ + if (trans != SAE_STATE_CONFIRMED) { + l_error("received transaction %u in accepted state", trans); + return false; + } + + if (sm->sync > SAE_SYNC_MAX) { + sae_authentication_failed(sm, MMPDU_REASON_CODE_UNSPECIFIED); + return false; + } + + sc = l_get_u16(frame); + + /* + * ... the value of send-confirm shall be checked. If the value is not + * greater than Rc or is equal to 2^16 - 1, the received frame shall be + * silently discarded. + */ + if (sc <= sm->rc || sc == 0xffff) + return false; + + /* + * If the verification fails, the received frame shall be silently + * discarded. + */ + if (!sae_verify_confirm(sm, frame)) + return false; + + /* + * If the verification succeeds, the Rc variable shall be set to the + * send-confirm portion of the frame, the Sync shall be incremented and + * a new SAE Confirm message shall be constructed (with Sc set to + * 2^16 - 1) and sent to the peer. + */ + sm->sync++; + sm->sc = 0xffff; + + sae_send_confirm(sm); + + /* + * Since the confirmed needed special processing because of accepted + * state we don't want the standard code path to execute. + */ + return false; +} + +static bool sae_verify_packet(struct sae_sm *sm, uint16_t trans, + uint16_t status, const uint8_t *frame, + size_t len) +{ + if (trans != SAE_STATE_COMMITTED && trans != SAE_STATE_CONFIRMED) + return false; + + switch (sm->state) { + case SAE_STATE_NOTHING: + return sae_verify_nothing(sm, trans, status, frame, len); + case SAE_STATE_COMMITTED: + return sae_verify_committed(sm, trans, status, frame, len); + case SAE_STATE_CONFIRMED: + return sae_verify_confirmed(sm, trans, status, frame, len); + case SAE_STATE_ACCEPTED: + return sae_verify_accepted(sm, trans, status, frame, len); + } + + /* should never get here */ + return false; +} + +void sae_rx_packet(struct sae_sm *sm, const uint8_t *from, const uint8_t *frame, + size_t len) +{ + uint16_t transaction; + uint16_t status; + const uint8_t *ptr = frame; + + if (len < 4) { + l_error("bad packet length"); + goto reject; + } + + transaction = l_get_u16(ptr); + ptr += 2; + status = l_get_u16(ptr); + ptr += 2; + + /* AP rejected authentication */ + if (len == 4) { + sae_authentication_failed(sm, status); + return; + } + + if (!sae_verify_packet(sm, transaction, status, ptr, len - 4)) + return; + + switch (transaction) { + case SAE_STATE_COMMITTED: + sae_process_commit(sm, from, ptr, len - 4); + return; + case SAE_STATE_CONFIRMED: + sae_process_confirm(sm, from, ptr, len - 4); + return; + default: + l_error("invalid transaction sequence %u", transaction); + } + +reject: + sae_reject_authentication(sm, MMPDU_REASON_CODE_UNSPECIFIED); +} + +void sae_start(struct sae_sm *sm) +{ + sae_send_commit(sm, false); +} + +struct sae_sm *sae_sm_new(struct handshake_state *hs, sae_tx_packet_func_t tx, + sae_complete_func_t complete, void *user_data) +{ + struct sae_sm *sm; + + sm = l_new(struct sae_sm, 1); + + if (!sm) + return NULL; + + sm->tx = tx; + sm->complete = complete; + sm->user_data = user_data; + sm->handshake = hs; + sm->state = SAE_STATE_NOTHING; + + return sm; +} + +void sae_sm_free(struct sae_sm *sm) +{ + l_free(sm->token); + + /* zero out whole structure, including keys */ + memset(sm, 0, sizeof(struct sae_sm)); + + l_free(sm); +} diff --git a/src/sae.h b/src/sae.h new file mode 100644 index 00000000..f6c42f61 --- /dev/null +++ b/src/sae.h @@ -0,0 +1,39 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2018 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 + * + */ + +struct sae_sm; +struct handshake_state; + +typedef int (*sae_tx_packet_func_t)(const uint8_t *dest, const uint8_t *frame, + size_t len, void *user_data); + +typedef void (*sae_complete_func_t)(uint16_t status, void *user_data); + +struct sae_sm *sae_sm_new(struct handshake_state *hs, sae_tx_packet_func_t tx, + sae_complete_func_t complete, void *user_data); +void sae_sm_free(struct sae_sm *sm); + +void sae_rx_packet(struct sae_sm *sm, const uint8_t *src, + const uint8_t *frame, size_t len); +void sae_timeout(struct sae_sm *sm); + +void sae_start(struct sae_sm *sm);