/* * * Wireless daemon for Linux * * Copyright (C) 2018-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 #endif #define _GNU_SOURCE #include #include #include "src/missing.h" #include "src/util.h" #include "src/ie.h" #include "src/handshake.h" #include "src/crypto.h" #include "src/mpdu.h" #include "src/auth-proto.h" #include "src/sae.h" #include "src/module.h" static bool debug; /* SHA-512 is the highest supported hashing function as of 802.11-2020 */ #define SAE_MAX_HASH_LEN 64 #define SAE_RETRANSMIT_TIMEOUT 2 #define SAE_SYNC_MAX 3 #define SAE_MAX_ASSOC_RETRY 3 #define sae_debug(fmat, ...) \ ({ \ if (debug) \ l_info("[SAE]: "fmat, ##__VA_ARGS__); \ }) enum sae_state { SAE_STATE_NOTHING = 0, SAE_STATE_COMMITTED = 1, SAE_STATE_CONFIRMED = 2, SAE_STATE_ACCEPTED = 3, }; struct sae_sm { struct auth_proto ap; struct handshake_state *handshake; struct l_ecc_point *pwe; enum sae_state state; const struct l_ecc_curve *curve; unsigned int group; int group_retry; uint16_t *rejected_groups; struct l_ecc_scalar *rand; struct l_ecc_scalar *scalar; struct l_ecc_scalar *p_scalar; struct l_ecc_point *element; struct l_ecc_point *p_element; uint16_t send_confirm; uint8_t kck[SAE_MAX_HASH_LEN]; 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; /* remote peer */ uint8_t peer[6]; uint8_t assoc_retry; sae_tx_authenticate_func_t tx_auth; sae_tx_associate_func_t tx_assoc; void *user_data; enum crypto_sae sae_type; bool force_default_group : 1; }; static enum mmpdu_status_code sae_status_code(struct sae_sm *sm) { switch (sm->sae_type) { case CRYPTO_SAE_LOOPING: return MMPDU_STATUS_CODE_SUCCESS; case CRYPTO_SAE_HASH_TO_ELEMENT: return MMPDU_STATUS_CODE_SAE_HASH_TO_ELEMENT; } return MMPDU_STATUS_CODE_UNSPECIFIED; } static void sae_rejected_groups_append(struct sae_sm *sm, uint16_t group) { uint16_t i; if (!sm->rejected_groups) { sm->rejected_groups = reallocarray(NULL, 2, sizeof(uint16_t)); sm->rejected_groups[0] = 1; sm->rejected_groups[1] = group; return; } for (i = 1; i <= sm->rejected_groups[0]; i++) if (sm->rejected_groups[i] == group) return; sm->rejected_groups = reallocarray(sm->rejected_groups, i + 1, sizeof(uint16_t)); sm->rejected_groups[0] += 1; sm->rejected_groups[i] = group; } static void sae_reset_state(struct sae_sm *sm) { l_ecc_scalar_free(sm->scalar); sm->scalar = NULL; l_ecc_scalar_free(sm->p_scalar); sm->p_scalar = NULL; l_ecc_scalar_free(sm->rand); sm->rand = NULL; l_ecc_point_free(sm->element); sm->element = NULL; l_ecc_point_free(sm->p_element); sm->p_element = NULL; l_ecc_point_free(sm->pwe); sm->pwe = NULL; } static int sae_choose_next_group(struct sae_sm *sm) { const unsigned int *ecc_groups = l_ecc_supported_ike_groups(); bool reset = sm->group_retry >= 0; /* * If this is a buggy AP in which group negotiation is broken use the * default group 19 and fail if this is a retry. */ if (sm->sae_type == CRYPTO_SAE_LOOPING && sm->force_default_group) { if (sm->group_retry != -1) { l_warn("Forced default group but was rejected!"); return -ENOENT; } sae_debug("Forcing default SAE group 19"); sm->group_retry++; sm->group = 19; goto get_curve; } do { sm->group_retry++; if (ecc_groups[sm->group_retry] == 0) return -ENOENT; } while (sm->sae_type != CRYPTO_SAE_LOOPING && !sm->handshake->ecc_sae_pts[sm->group_retry]); if (reset) sae_reset_state(sm); sm->group = ecc_groups[sm->group_retry]; get_curve: sae_debug("Using group %u", sm->group); sm->curve = l_ecc_curve_from_ike_group(sm->group); return 0; } static int sae_valid_group(struct sae_sm *sm, unsigned int group) { const unsigned int *ecc_groups = l_ecc_supported_ike_groups(); unsigned int i; for (i = sm->group_retry; ecc_groups[i]; i++) { if (ecc_groups[i] != group) continue; if (sm->sae_type != CRYPTO_SAE_LOOPING && !sm->handshake->ecc_sae_pts[i]) continue; return i; } return -ENOENT; } 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 hkdf_extract(L_CHECKSUM_SHA256, key, 12, 2, out, base, base_len, &counter, (size_t) 1); } /* * Computes KDF-256(pwd_seed, "SAE Hunting and Pecking", p). If the output is * greater than p, the output is set to qnr, a quadratic non-residue. * Since this happens with very low probability, using the same qnr is fine. */ static struct l_ecc_scalar *sae_pwd_value(const struct l_ecc_curve *curve, uint8_t *pwd_seed, uint8_t *qnr) { uint8_t pwd_value[L_ECC_SCALAR_MAX_BYTES]; uint8_t prime[L_ECC_SCALAR_MAX_BYTES]; ssize_t len; int is_in_range; struct l_ecc_scalar *p = l_ecc_curve_get_prime(curve); len = l_ecc_scalar_get_data(p, prime, sizeof(prime)); l_ecc_scalar_free(p); if (!kdf_sha256(pwd_seed, 32, "SAE Hunting and Pecking", strlen("SAE Hunting and Pecking"), prime, len, pwd_value, len)) return NULL; /* * If pwd_value >= prime, this iteration should fail. We need a smooth * control flow, so we need to continue anyway. */ is_in_range = l_secure_memcmp(pwd_value, prime, len); /* * We only consider is_in_range == -1 as valid, meaning the value of the * MSB defines the mask. */ is_in_range = util_secure_fill_with_msb(is_in_range); /* * libell has public Legendre symbol only for l_ecc_scalar, but they * cannot be created if the coordinate is greater than the p. Hence, * to avoid control flow dependencies, we replace pwd_value by a dummy * quadratic non residue if we generate a value >= prime. */ util_secure_select((uint8_t) is_in_range, pwd_value, qnr, pwd_value, sizeof(pwd_value)); return l_ecc_scalar_new(curve, pwd_value, sizeof(pwd_value)); } /* IEEE 802.11-2016 - Section 12.4.2 Assumptions on SAE */ static ssize_t sae_cn(struct sae_sm *sm, uint16_t send_confirm, struct l_ecc_scalar *scalar1, struct l_ecc_point *element1, struct l_ecc_scalar *scalar2, struct l_ecc_point *element2, uint8_t *confirm) { enum l_checksum_type hash = crypto_sae_hash_from_ecc_prime_len(sm->sae_type, l_ecc_curve_get_scalar_bytes(sm->curve)); size_t hash_len = l_checksum_digest_length(hash); uint8_t s1[L_ECC_SCALAR_MAX_BYTES]; uint8_t s2[L_ECC_SCALAR_MAX_BYTES]; uint8_t e1[L_ECC_POINT_MAX_BYTES]; uint8_t e2[L_ECC_POINT_MAX_BYTES]; struct l_checksum *hmac; struct iovec iov[5]; ssize_t ret; hmac = l_checksum_new_hmac(hash, sm->kck, hash_len); if (!hmac) return false; send_confirm = L_CPU_TO_LE16(send_confirm); iov[0].iov_base = &send_confirm; iov[0].iov_len = 2; iov[1].iov_base = (void *) s1; iov[1].iov_len = l_ecc_scalar_get_data(scalar1, s1, sizeof(s1)); iov[2].iov_base = (void *) e1; iov[2].iov_len = l_ecc_point_get_data(element1, e1, sizeof(e1)); iov[3].iov_base = (void *) s2; iov[3].iov_len = l_ecc_scalar_get_data(scalar2, s2, sizeof(s2)); iov[4].iov_base = (void *) e2; iov[4].iov_len = l_ecc_point_get_data(element2, e2, sizeof(e2)); l_checksum_updatev(hmac, iov, 5); ret = l_checksum_get_digest(hmac, confirm, hash_len); l_checksum_free(hmac); return ret; } static int sae_reject(struct sae_sm *sm, uint16_t transaction, uint16_t status) { uint8_t reject[6]; uint8_t *ptr = reject; if (!sm->handshake->authenticator) return -EPROTO; /* transaction */ l_put_u16(transaction, ptr); ptr += 2; l_put_u16(status, ptr); ptr += 2; if (status == MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP) { l_put_u16(sm->group, ptr); ptr += 2; } sae_debug("Rejecting exchange transaction=%u status=%u", transaction, status); sm->tx_auth(reject, ptr - reject, sm->user_data); return status; } static struct l_ecc_scalar *sae_new_residue(const struct l_ecc_curve *curve, bool residue) { struct l_ecc_scalar *s = l_ecc_scalar_new_random(curve); while (l_ecc_scalar_legendre(s) != ((residue) ? -1 : 1)) { l_ecc_scalar_free(s); s = l_ecc_scalar_new_random(curve); } return s; } static uint8_t sae_is_quadradic_residue(const struct l_ecc_curve *curve, struct l_ecc_scalar *value, struct l_ecc_scalar *qr, struct l_ecc_scalar *qnr) { uint64_t rbuf[L_ECC_MAX_DIGITS]; struct l_ecc_scalar *y_sqr = l_ecc_scalar_new(curve, NULL, 0); struct l_ecc_scalar *r = l_ecc_scalar_new_random(curve); struct l_ecc_scalar *num = l_ecc_scalar_new(curve, NULL, 0); size_t bytes; l_ecc_scalar_sum_x(y_sqr, value); l_ecc_scalar_multiply(num, y_sqr, r); l_ecc_scalar_multiply(num, num, r); l_ecc_scalar_free(y_sqr); bytes = l_ecc_scalar_get_data(r, rbuf, sizeof(rbuf)); l_ecc_scalar_free(r); if (bytes <= 0) { l_ecc_scalar_free(num); return 0; } if (rbuf[bytes / 8 - 1] & 1) { l_ecc_scalar_multiply(num, num, qr); if (l_ecc_scalar_legendre(num) == -1) { l_ecc_scalar_free(num); return 1; } } else { l_ecc_scalar_multiply(num, num, qnr); if (l_ecc_scalar_legendre(num) == 1) { l_ecc_scalar_free(num); return 1; } } l_ecc_scalar_free(num); return 0; } /* * IEEE 802.11-2016 Section 12.4.4.2.2 * Generation of the password element with ECC groups */ static struct l_ecc_point *sae_compute_pwe(const struct l_ecc_curve *curve, const char *password, const uint8_t *addr1, const uint8_t *addr2) { uint8_t found = 0; uint8_t is_residue; uint8_t is_odd = 0; uint8_t counter; uint8_t pwd_seed[32]; uint8_t x[L_ECC_SCALAR_MAX_BYTES]; uint8_t x_cand[L_ECC_SCALAR_MAX_BYTES]; struct l_ecc_scalar *pwd_value; uint8_t *dummy; uint8_t *base; size_t base_len; struct l_ecc_scalar *qr; struct l_ecc_scalar *qnr; uint8_t qnr_bin[L_ECC_SCALAR_MAX_BYTES] = {0}; struct l_ecc_point *pwe; unsigned int bytes = l_ecc_curve_get_scalar_bytes(curve); /* create qr/qnr prior to beginning hunting-and-pecking loop */ qr = sae_new_residue(curve, true); qnr = sae_new_residue(curve, false); l_ecc_scalar_get_data(qnr, qnr_bin, sizeof(qnr_bin)); /* * Allocate memory for the base, and set a random dummy to be used in * additional iterations, once a valid value is found */ base_len = strlen(password); base = l_malloc(base_len * sizeof(*base)); dummy = l_malloc(base_len * sizeof(*dummy)); l_getrandom(dummy, base_len); /* * Loop with constant time and memory access * We do 30 iterations instead of the 40 recommended to achieve a * resonnable security/complexity trade-off. */ for (counter = 1; counter <= 30; counter++) { /* * Set base to either dummy or password, depending on found's * value. * A non-secure version would be: * base = (found ? dummy : password); */ util_secure_select(found, dummy, (uint8_t *)password, base, base_len); /* * 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); /* * The case pwd_value > prime is handled inside, so that * execution can continue whatever the result is, without * changing the outcome. */ pwd_value = sae_pwd_value(curve, pwd_seed, qnr_bin); /* * Check if the candidate is a valid x-coordinate on our curve, * and convert it from scalar to binary. */ is_residue = sae_is_quadradic_residue(curve, pwd_value, qr, qnr); l_ecc_scalar_get_data(pwd_value, x_cand, sizeof(x_cand)); /* * If we already found the point, we overwrite x with itself. * Otherwise, we copy the new candidate into x. */ util_secure_select(found, x, x_cand, x, sizeof(x)); is_odd = util_secure_select_byte(found, is_odd, pwd_seed[31] & 0x01); /* * found is 0 or 0xff here and is_residue is 0 or 1. Bitwise OR * of them (with is_residue converted to 0/0xff) handles this * in constant time. */ found |= is_residue * 0xff; memset(pwd_seed, 0, sizeof(pwd_seed)); l_ecc_scalar_free(pwd_value); } l_ecc_scalar_free(qr); l_ecc_scalar_free(qnr); l_free(dummy); l_free(base); if (!found) { l_error("max PWE iterations reached!"); return NULL; } /* * The 802.11 spec requires the point be solved unambiguously (since * solving for Y results in two solutions). The correct Y value * is chosen based on the LSB of the pwd-seed: * * if (LSB(y) == LSB(pwd-seed)) * then * PWE = (x, y) * else * PWE = (x, p-y) * * The ELL API (somewhat hidden from view here) automatically * performs a subtraction (P - Y) when: * - Y is even and BIT1 * - Y is odd and BIT0 * * So we choose the point type which matches the parity of * pwd-seed. This means a subtraction will be performed (P - Y) * if the parity of pwd-seed and the computed Y do not match. */ pwe = l_ecc_point_from_data(curve, is_odd ? L_ECC_POINT_TYPE_COMPRESSED_BIT1 : L_ECC_POINT_TYPE_COMPRESSED_BIT0, x, bytes); if (!pwe) l_error("computing y failed, was x quadratic residue?"); return pwe; } static int sae_build_commit(struct sae_sm *sm, const uint8_t *addr1, const uint8_t *addr2, uint8_t *commit, size_t len, bool retry) { struct l_ecc_scalar *mask; uint8_t *ptr = commit; struct l_ecc_scalar *order; struct ie_tlv_builder builder; if (retry) goto old_commit; switch (sm->sae_type) { case CRYPTO_SAE_HASH_TO_ELEMENT: { const struct l_ecc_point *pt = sm->handshake->ecc_sae_pts[sm->group_retry]; sm->pwe = crypto_derive_sae_pwe_from_pt_ecc(addr1, addr2, pt); break; } case CRYPTO_SAE_LOOPING: sm->pwe = sae_compute_pwe(sm->curve, sm->handshake->passphrase, addr1, addr2); break; } if (!sm->pwe) { l_error("could not compute PWE"); return -EIO; } sm->scalar = l_ecc_scalar_new(sm->curve, NULL, 0); sm->rand = l_ecc_scalar_new_random(sm->curve); mask = l_ecc_scalar_new_random(sm->curve); order = l_ecc_curve_get_order(sm->curve); /* commit-scalar = (rand + mask) mod r */ l_ecc_scalar_add(sm->scalar, sm->rand, mask, order); l_ecc_scalar_free(order); /* commit-element = inv(mask * PWE) */ sm->element = l_ecc_point_new(sm->curve); l_ecc_point_multiply(sm->element, mask, sm->pwe); l_ecc_point_inverse(sm->element); l_ecc_scalar_free(mask); /* * Several cases require retransmitting the same commit message. The * anti-clogging code path requires this as well as the retransmission * timeout. */ old_commit: /* * 12.4.7.4 Encoding and decoding of SAE Commit messages * Refer to Table 9-40 for order and Table 9-41 for presence * of elements */ /* "a Transaction Sequence Number of 1" */ l_put_le16(1, ptr); ptr += 2; /* "a Status Code of SUCCESS or SAE_HASH_TO_ELEMENT" */ l_put_le16(sae_status_code(sm), ptr); ptr += 2; /* group */ l_put_le16(sm->group, ptr); ptr += 2; if (sm->sae_type == CRYPTO_SAE_LOOPING && sm->token) { memcpy(ptr, sm->token, sm->token_len); ptr += sm->token_len; } ptr += l_ecc_scalar_get_data(sm->scalar, ptr, L_ECC_SCALAR_MAX_BYTES); ptr += l_ecc_point_get_data(sm->element, ptr, L_ECC_POINT_MAX_BYTES); ie_tlv_builder_init(&builder, ptr, len - (ptr - commit)); if (sm->sae_type != CRYPTO_SAE_LOOPING && sm->rejected_groups) { ie_tlv_builder_next(&builder, IE_TYPE_REJECTED_GROUPS); ie_tlv_builder_set_data(&builder, sm->rejected_groups + 1, sm->rejected_groups[0] * sizeof(uint16_t)); } if (sm->sae_type != CRYPTO_SAE_LOOPING && sm->token) { ie_tlv_builder_next(&builder, IE_TYPE_ANTI_CLOGGING_TOKEN_CONTAINER); ie_tlv_builder_set_data(&builder, sm->token, sm->token_len); } if (sm->sae_type == CRYPTO_SAE_HASH_TO_ELEMENT && sm->handshake->password_identifier) { ie_tlv_builder_next(&builder, IE_TYPE_PASSWORD_IDENTIFIER); ie_tlv_builder_set_data(&builder, sm->handshake->password_identifier, strlen(sm->handshake->password_identifier)); } ie_tlv_builder_finalize(&builder, &len); return ptr - commit + len; } static bool sae_send_confirm(struct sae_sm *sm) { uint8_t confirm[SAE_MAX_HASH_LEN]; uint8_t body[sizeof(confirm) + 6]; uint8_t *ptr = body; ssize_t r; /* * confirm = CN(KCK, send-confirm, commit-scalar, COMMIT-ELEMENT, * peer-commit-scalar, PEER-COMMIT-ELEMENT) */ r = sae_cn(sm, sm->sc, sm->scalar, sm->element, sm->p_scalar, sm->p_element, confirm); if (r < 0) return false; l_put_le16(2, ptr); ptr += 2; l_put_le16(0, ptr); ptr += 2; l_put_le16(sm->sc, ptr); ptr += 2; memcpy(ptr, confirm, r); ptr += r; sae_debug("Sending Confirm to "MAC" sc=%u", MAC_STR(sm->handshake->aa), sm->sc); sm->tx_auth(body, ptr - body, sm->user_data); return true; } static int sae_calculate_keys(struct sae_sm *sm) { unsigned int nbytes = l_ecc_curve_get_scalar_bytes(sm->curve); enum l_checksum_type hash = crypto_sae_hash_from_ecc_prime_len(sm->sae_type, nbytes); size_t hash_len = l_checksum_digest_length(hash); struct l_ecc_point *k_point; uint8_t k[L_ECC_SCALAR_MAX_BYTES]; ssize_t klen; const void *salt = NULL; size_t salt_len = 0; uint8_t keyseed[SAE_MAX_HASH_LEN]; uint8_t kck_and_pmk[SAE_MAX_HASH_LEN + 32]; uint8_t tmp[L_ECC_SCALAR_MAX_BYTES]; struct l_ecc_scalar *tmp_scalar; struct l_ecc_scalar *order; /* * K = scalar-op(rand, (element-op(scalar-op(peer-commit-scalar, PWE), * PEER-COMMIT-ELEMENT))) */ k_point = l_ecc_point_new(sm->curve); /* k_point = scalar-op(peer-commit-scalar, PWE) */ l_ecc_point_multiply(k_point, sm->p_scalar, sm->pwe); /* k_point = element-op(k_point, PEER-COMMIT-ELEMENT) */ l_ecc_point_add(k_point, k_point, sm->p_element); /* k_point = scalar-op(rand, k_point) */ l_ecc_point_multiply(k_point, sm->rand, k_point); /* * 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. */ klen = l_ecc_point_get_x(k_point, k, sizeof(k)); l_ecc_point_free(k_point); if (klen < 0) return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSPECIFIED); /* * keyseed = H(salt, k) * * 802.11-2020 12.4.5.4: * Hash to Element case: * "... a salt consisting of the concatenation of the rejected groups * from each peer's Rejected Groups element shall be * passed to the KDF; those of the peer with the highest MAC address go * first (if only one sent a Rejected Groups element then the salt will * consist of that list). " * * Looping case: * "...the salt shall consist of a series of octets of the value zero * whose length equals the length of the digest of the hash function * used to instantiate H()." * * NOTE: We use hkdf_extract here since it is just an hmac invocation * and it handles the case of the zero key for us. */ if (sm->sae_type != CRYPTO_SAE_LOOPING && sm->rejected_groups) { salt = sm->rejected_groups + 1; salt_len = sm->rejected_groups[0] * sizeof(uint16_t); } hkdf_extract(hash, salt, salt_len, 1, keyseed, k, klen); /* * context = (commit-scalar + peer-commit-scalar) mod r * Length = Q + 256 * kck_and_pmk = KDF-Hash-Length(keyseed, "SAE KCK and PMK", context) * KCK = L(kck_and_pmk, 0, Q) * PMK = L(kck_and_pmk, Q, 256) * * Q is the length of the digest of the H(), the hash function used */ tmp_scalar = l_ecc_scalar_new(sm->curve, NULL, 0); order = l_ecc_curve_get_order(sm->curve); l_ecc_scalar_add(tmp_scalar, sm->p_scalar, sm->scalar, order); l_ecc_scalar_get_data(tmp_scalar, tmp, sizeof(tmp)); crypto_kdf(hash, keyseed, hash_len, "SAE KCK and PMK", strlen("SAE KCK and PMK"), tmp, nbytes, kck_and_pmk, hash_len + 32); memcpy(sm->kck, kck_and_pmk, hash_len); memcpy(sm->pmk, kck_and_pmk + hash_len, 32); /* * PMKID = L((commit-scalar + peer-commit-scalar) mod r, 0, 128) */ l_ecc_scalar_add(tmp_scalar, sm->scalar, sm->p_scalar, order); l_ecc_scalar_get_data(tmp_scalar, tmp, sizeof(tmp)); l_ecc_scalar_free(order); l_ecc_scalar_free(tmp_scalar); /* don't set the handshakes pmkid until confirm is verified */ memcpy(sm->pmkid, tmp, 16); return 0; } static int sae_process_commit(struct sae_sm *sm, const uint8_t *from, const uint8_t *frame, size_t len) { uint8_t *ptr = (uint8_t *) frame; unsigned int nbytes = l_ecc_curve_get_scalar_bytes(sm->curve); int r; ptr += 2; sm->p_scalar = l_ecc_scalar_new(sm->curve, ptr, nbytes); if (!sm->p_scalar) { l_error("Server sent invalid P_Scalar during commit"); return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP); } ptr += nbytes; sm->p_element = l_ecc_point_from_data(sm->curve, L_ECC_POINT_TYPE_FULL, ptr, nbytes * 2); if (!sm->p_element) { l_error("Server sent invalid P_Element during commit"); return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP); } /* * If they match those sent as part of the protocol instance's own * SAE Commit message, the frame shall be silently discarded (because * it is evidence of a reflection attack) and the t0 (retransmission) * timer shall be set. */ if (l_ecc_scalars_are_equal(sm->p_scalar, sm->scalar) || l_ecc_points_are_equal(sm->p_element, sm->element)) { l_warn("peer scalar or element matched own, discarding frame"); return -ENOMSG; } sm->sc++; r = sae_calculate_keys(sm); if (r != 0) return r; if (!sae_send_confirm(sm)) return -EPROTO; sm->state = SAE_STATE_CONFIRMED; return 0; } static bool sae_verify_confirm(struct sae_sm *sm, const uint8_t *frame) { uint8_t check[SAE_MAX_HASH_LEN]; uint16_t rc = l_get_le16(frame); ssize_t r; r = sae_cn(sm, rc, sm->p_scalar, sm->p_element, sm->scalar, sm->element, check); if (r < 0) return false; if (memcmp(frame + 2, check, r)) return false; sm->rc = rc; return true; } static int sae_process_confirm(struct sae_sm *sm, const uint8_t *from, const uint8_t *frame, size_t len) { const uint8_t *ptr = frame; /* * If processing is unsuccessful and the SAE Confirm message is not * verified, protocol instance shall remain in Confirmed state. * * NOTE: We diverge from the protocol here and bail out early */ if (!sae_verify_confirm(sm, ptr)) { l_error("SAE: Confirm could not be verified"); return sae_reject(sm, SAE_STATE_CONFIRMED, MMPDU_STATUS_CODE_UNSPECIFIED); } /* 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->state = SAE_STATE_ACCEPTED; sae_debug("Sending Associate to "MAC, MAC_STR(sm->handshake->aa)); sm->tx_assoc(sm->user_data); return 0; } static bool sae_send_commit(struct sae_sm *sm, bool retry) { struct handshake_state *hs = sm->handshake; /* regular commit + 3x IEs (257 bytes) + 6 bytes header */ uint8_t commit[L_ECC_SCALAR_MAX_BYTES + L_ECC_POINT_MAX_BYTES + 777]; int r; r = sae_build_commit(sm, hs->spa, hs->aa, commit, sizeof(commit), retry); if (r < 0) return false; sae_debug("Sending Commit to "MAC, MAC_STR(hs->aa)); sm->tx_auth(commit, r, sm->user_data); return true; } static bool sae_assoc_timeout(struct auth_proto *ap) { struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap); if (sm->assoc_retry >= SAE_MAX_ASSOC_RETRY) return false; sm->assoc_retry++; sae_debug("Retry Associate to "MAC, MAC_STR(sm->handshake->aa)); sm->tx_assoc(sm->user_data); return true; } /* * 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 int sae_process_anti_clogging(struct sae_sm *sm, const uint8_t *ptr, size_t len) { /* * 802.11 doesn't talk about validating the group of the Anti-Clogging * Request message. We assume here that the group is something that * we would have potentially sent */ if (len < 2) return -EBADMSG; if (sae_valid_group(sm, l_get_le16(ptr)) < 0) return -EBADMSG; len -= 2; ptr += 2; /* * 802.11-2020, Table 9-41: * When the hash-to-element method is used to derive the PWE, the * Anti-Clogging Token Container element is present if the * Status Code field is ANTI_CLOGGING_TOKEN_REQUIRED */ if (sm->sae_type != CRYPTO_SAE_LOOPING) { if (len < 3) return -EBADMSG; if (ptr[0] != IE_TYPE_EXTENSION || ptr[2] != 93 || ptr[1] < 2 || len < ptr[1] + 2u) return -EBADMSG; len = ptr[1] - 1; ptr += 3; } /* * IEEE 802.11-2016 - Section 12.4.6 Anti-clogging tokens * * "It is suggested that an Anti-Clogging Token not exceed 256 octets" * * Also ensure the token is at least 1 byte. The packet passed in will * contain the group number, meaning the anti-clogging token length is * going to be 2 bytes less than the passed in length. This is why we * are checking 3 > len > 258. */ if (len < 1 || len > 256) { l_error("anti-clogging token size invalid %zu", len); return -EBADMSG; } sae_debug("Processed anti-clogging token"); l_free(sm->token); sm->token = l_memdup(ptr, len); sm->token_len = len; sm->sync = 0; sae_send_commit(sm, true); return -EAGAIN; } /* * 802.11-2016 - 12.4.8.6.3 Protocol instance behavior - Nothing state */ static int 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 -EBADMSG; /* frame shall be silently discarded and Del event sent */ if (status != 0) return -EBADMSG; if (len < 2) return -EBADMSG; /* reject with unsupported group */ if (l_get_le16(frame) != sm->group) return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP); return 0; } /* * 802.11-2016 - 12.4.8.6.4 Protocol instance behavior - Committed state */ static int sae_verify_committed(struct sae_sm *sm, uint16_t transaction, uint16_t status, const uint8_t *frame, size_t len) { unsigned int skip; struct ie_tlv_iter iter; /* * 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) return -ETIMEDOUT; sm->sync++; sae_send_commit(sm, true); return -EAGAIN; } if (status == MMPDU_STATUS_CODE_ANTI_CLOGGING_TOKEN_REQ) return sae_process_anti_clogging(sm, frame, len); if (status == MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP) { /* * TODO: hostapd in its current state does not include the * group number as it should. This is a violation of the spec, * but there isn't much we can do about it. We simply treat this * response as if its rejecting our last commit message (which * it most likely is). If/When this is fixed we should be * checking that the group matches here, e.g. * * if (l_get_le16(frame) != sm->group) * return false; * * According to 802.11 Section 12.4.8.6.4: * * "If the rejected group does not match the last offered group * the protocol instance shall silently discard the message and * set the t0 (retransmission) timer" */ if (len == 0) l_warn("AP did not include group number in response!"); else if (len >= 2 && (l_get_le16(frame) != sm->group)) return -ENOMSG; sae_rejected_groups_append(sm, L_CPU_TO_LE16(sm->group)); /* * "If the rejected group matches the last offered group, the * protocol instance shall choose a different group and generate * the PWE and the secret values according to 12.4.5.2; it then * generates and transmits a new SAE Commit message to the peer, * zeros Sync, sets the t0 (retransmission) timer, and remains * in Committed state" */ if (sae_choose_next_group(sm) < 0) { /* * "If there are no other groups to choose, the protocol * instance shall send a Del event to the parent process * and transitions back to Nothing state" */ sm->state = SAE_STATE_NOTHING; goto reject_unsupp_group; } sae_debug("AP rejected group, trying again with group %u", sm->group); sm->sync = 0; sae_send_commit(sm, false); return -EAGAIN; } /* * If the Status is some other nonzero value, the frame shall be * silently discarded and the t0 (retransmission) timer shall be set. */ switch (status) { case 0: case MMPDU_STATUS_CODE_SAE_HASH_TO_ELEMENT: if (status != sae_status_code(sm)) return -EBADMSG; break; case MMPDU_STATUS_CODE_UNKNOWN_PASSWORD_IDENTIFIER: sae_debug("Incorrect password identifier, check " "[Security].PasswordIdentifier"); /* fall through */ default: return -ENOMSG; } if (len < 2) return -EBADMSG; if (l_get_le16(frame) != sm->group) { l_error("SAE: Peer tried to change group -- Reject"); goto reject_unsupp_group; } len -= 2; frame += 2; skip = l_ecc_curve_get_scalar_bytes(sm->curve) * 3; if (len < skip) return -EBADMSG; /* If H2E isn't being used, there should be no IEs in use */ if (status == 0) return 0; len -= skip; frame += skip; ie_tlv_iter_init(&iter, frame, len); while (ie_tlv_iter_next(&iter)) { switch (ie_tlv_iter_get_tag(&iter)) { /* * If the peer's SAE Commit message contains a Rejected Groups * element, the list of rejected groups shall be checked to * ensure that all of the groups in the list are groups that * would be rejected. If any groups in the list would not be * rejected then processing of the SAE Commit message * terminates and the STA shall reject the peer's * authentication. * * NOTE: We currently only support the Initiator role, and so * do not reject any groups. We should never receive this * element */ case IE_TYPE_REJECTED_GROUPS: l_error("SAE: Unexpected Rejected Groups IE"); return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSPECIFIED); /* We don't request tokens, so we shouldn't get any */ case IE_TYPE_ANTI_CLOGGING_TOKEN_CONTAINER: l_error("SAE: Unexpected Anti-Clogging Container IE"); return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSPECIFIED); } } return 0; reject_unsupp_group: return sae_reject(sm, SAE_STATE_COMMITTED, MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP); } /* * 802.11-2016 - 12.4.8.6.5 Protocol instance behavior - Confirmed state */ static int 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) { enum l_checksum_type hash = crypto_sae_hash_from_ecc_prime_len(sm->sae_type, l_ecc_curve_get_scalar_bytes(sm->curve)); size_t hash_len = l_checksum_digest_length(hash); /* Most likely the password is wrong */ if (status == MMPDU_STATUS_CODE_UNSPECIFIED && len == 0) return -ENOKEY; if (status != MMPDU_STATUS_CODE_SUCCESS) return -EPROTO; if (len < hash_len + 2) { l_error("SAE: Confirm packet too short"); return -EBADMSG; } return 0; } /* * Upon receipt of a Com event, the t0 (retransmission) timer shall be * canceled. If the Status is nonzero, the frame shall be silently * discarded, the t0 (retransmission) timer set, and the protocol * instance shall remain in the Confirmed state. */ if (status != 0) return -ENOMSG; /* * 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) return -ETIMEDOUT; if (len < 2) return -EBADMSG; /* frame shall be silently discarded */ if (l_get_le16(frame) != sm->group) return -EBADMSG; /* * Because of kernel retransmit behavior on missed ACKs plus hostapd's * incorrect handling of confirm packets while in accepted state the * following can happen: * * 1. Client sends commit, not acked (committed state) * 2. AP receives commit, sends commit reply (committed state) * 3. Client retransmits original commit * 4. Client receives AP's commit, sends confirm (confirmed state) * 5. AP receives clients retransmitted commit, sends only commit * 6. AP receives clients confirm and accepts (accepted state) * 7. Client receives AP's commit and sends both commit + confirm * (the code below). * 8. AP receives clients commit while in accepted state, and deauths * * Due to this, any commit received while in a confirmed state will be * ignored by IWD since it is probably caused by this retransmission * and sending the commit/confirm below would likely cause hostapd to * deauth us. * * As for non-sta (currently not used) we want to keep with the spec. */ if (!sm->handshake->authenticator) return -EBADMSG; /* * 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); if (!sae_send_confirm(sm)) return -EPROTO; return -EAGAIN; } /* * 802.11-2016 - 12.4.8.6.6 Protocol instance behavior - Accepted state */ static int sae_verify_accepted(struct sae_sm *sm, uint16_t trans, uint16_t status, const uint8_t *frame, size_t len) { uint16_t sc; /* * 12.4.8.6.1 Parent process behavior * * "Upon receipt of an SAE Commit message... and it is in Accepted * state, the scalar in the received frame is checked against the * peer-scalar used in authentication of the existing protocol instance * (in Accepted state). If it is identical, the frame shall be dropped" */ if (trans == SAE_STATE_COMMITTED) { bool drop; unsigned int nbytes = l_ecc_curve_get_scalar_bytes(sm->curve); struct l_ecc_scalar *p_scalar; if (len < nbytes + 2) return -EMSGSIZE; p_scalar = l_ecc_scalar_new(sm->curve, frame + 2, nbytes); drop = l_ecc_scalars_are_equal(sm->p_scalar, p_scalar); l_ecc_scalar_free(p_scalar); if (drop) return -EBADMSG; l_error("received transaction %u in accepted state", trans); return -EPROTO; } if (sm->sync > SAE_SYNC_MAX) return -ETIMEDOUT; if (len < 2) return -EBADMSG; sc = l_get_le16(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 -EBADMSG; /* * If the verification fails, the received frame shall be silently * discarded. */ if (!sae_verify_confirm(sm, frame)) return -EBADMSG; /* * 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; if (!sae_send_confirm(sm)) return -EPROTO; return -EAGAIN; } static const char *sae_state_to_str(enum sae_state state) { switch (state) { case SAE_STATE_NOTHING: return "nothing"; case SAE_STATE_COMMITTED: return "committed"; case SAE_STATE_CONFIRMED: return "confirmed"; case SAE_STATE_ACCEPTED: return "accepted"; } return "unknown"; } static int 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 -EBADMSG; 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 -EPROTO; } static int sae_rx_authenticate(struct auth_proto *ap, const uint8_t *frame, size_t len) { struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap); const struct mmpdu_header *hdr = (const struct mmpdu_header *) frame; const struct mmpdu_authentication *auth = mmpdu_body(hdr); int ret; uint16_t transaction = L_LE16_TO_CPU(auth->transaction_sequence); uint16_t status = L_LE16_TO_CPU(auth->status); sae_debug("Received frame transaction=%u status=%u state=%s", transaction, status, sae_state_to_str(sm->state)); len -= mmpdu_header_len(hdr); ret = sae_verify_packet(sm, transaction, status, auth->ies, len - 6); if (ret != 0) { if (ret < 0 && ret != -EAGAIN) sae_debug("Frame did not verify (%s)", strerror(-ret)); return ret; } switch (transaction) { case SAE_STATE_COMMITTED: return sae_process_commit(sm, hdr->address_2, auth->ies, len - 6); case SAE_STATE_CONFIRMED: return sae_process_confirm(sm, hdr->address_2, auth->ies, len - 6); default: l_error("invalid transaction sequence %u", transaction); } /* should never get here */ return -EPROTO; } static int sae_rx_associate(struct auth_proto *ap, const uint8_t *frame, size_t len) { const struct mmpdu_header *mpdu = (const struct mmpdu_header *)frame; const struct mmpdu_association_response *body = mmpdu_body(mpdu); if (body->status_code != 0) return -EPROTO; return 0; } static bool sae_start(struct auth_proto *ap) { struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap); if (sm->handshake->authenticator) memcpy(sm->peer, sm->handshake->spa, 6); else memcpy(sm->peer, sm->handshake->aa, 6); if (sm->sae_type == CRYPTO_SAE_LOOPING && !sm->handshake->passphrase) { l_error("SAE: No passphrase set"); return false; } if (sae_choose_next_group(sm) < 0) return false; sm->state = SAE_STATE_COMMITTED; return sae_send_commit(sm, false); } bool sae_sm_is_h2e(struct auth_proto *ap) { struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap); return sm->sae_type != CRYPTO_SAE_LOOPING; } static void sae_free(struct auth_proto *ap) { struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap); sae_reset_state(sm); l_free(sm->token); sm->token = NULL; if (sm->rejected_groups) free(sm->rejected_groups); /* zero out whole structure, including keys */ explicit_bzero(sm, sizeof(struct sae_sm)); l_free(sm); } struct auth_proto *sae_sm_new(struct handshake_state *hs, sae_tx_authenticate_func_t tx_auth, sae_tx_associate_func_t tx_assoc, void *user_data) { struct sae_sm *sm; const void *rsnxe; sm = l_new(struct sae_sm, 1); sm->group_retry = -1; sm->tx_auth = tx_auth; sm->tx_assoc = tx_assoc; sm->user_data = user_data; sm->handshake = hs; sm->state = SAE_STATE_NOTHING; sm->force_default_group = hs->force_default_ecc_group; sm->ap.start = sae_start; sm->ap.free = sae_free; sm->ap.rx_authenticate = sae_rx_authenticate; sm->ap.rx_associate = sae_rx_associate; sm->ap.assoc_timeout = sae_assoc_timeout; rsnxe = hs->authenticator ? hs->supplicant_rsnxe : hs->authenticator_rsnxe; if (ie_rsnxe_capable(rsnxe, IE_RSNX_SAE_H2E) && hs->ecc_sae_pts) { sae_debug("Using SAE H2E"); sm->sae_type = CRYPTO_SAE_HASH_TO_ELEMENT; } else { sae_debug("Using SAE Hunting and Pecking"); sm->sae_type = CRYPTO_SAE_LOOPING; } return &sm->ap; } static int sae_init(void) { if (getenv("IWD_SAE_DEBUG")) debug = true; return 0; } static void sae_exit(void) { } IWD_MODULE(sae, sae_init, sae_exit);