3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-10-05 19:08:52 +02:00
iwd/src/sae.c
Daniel DE ALMEIDA BRAGA 211f7dde6e sae: Fix a side channel leak on the password
Use a constant control flow in the derivation loop, avoiding leakage
in the iteration succesfuly converting the password.
Increase number of iterations (20 to 30) to avoid issues with
passwords needing more iterations.
2020-08-03 16:12:50 -05:00

1185 lines
30 KiB
C

/*
*
* 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 <config.h>
#endif
#include <ell/ell.h>
#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/sae.h"
#include "src/auth-proto.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 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;
uint8_t group_retry;
const unsigned int *ecc_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[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;
/* remote peer */
uint8_t peer[6];
sae_tx_authenticate_func_t tx_auth;
sae_tx_associate_func_t tx_assoc;
void *user_data;
};
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 bool sae_cn(const uint8_t *kck, 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)
{
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];
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 *) 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, 32);
l_checksum_free(hmac);
return (ret == 32);
}
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_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP) {
l_put_u16(sm->group, ptr);
ptr += 2;
}
sm->tx_auth(reject, ptr - reject, sm->user_data);
}
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 bool sae_compute_pwe(struct sae_sm *sm, 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};
/* create qr/qnr prior to beginning hunting-and-pecking loop */
qr = sae_new_residue(sm->curve, true);
qnr = sae_new_residue(sm->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(sm->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(sm->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 false;
}
sm->pwe = l_ecc_point_from_data(sm->curve, !is_odd + 2, x, sizeof(x));
if (!sm->pwe) {
l_error("computing y failed, was x quadratic residue?");
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)
{
struct l_ecc_scalar *mask;
uint8_t *ptr = commit;
struct l_ecc_scalar *order;
if (retry)
goto old_commit;
if (!sm->handshake->passphrase) {
l_error("no handshake passphrase found");
return false;
}
if (!sae_compute_pwe(sm, sm->handshake->passphrase, addr1, addr2)) {
l_error("could not compute PWE");
return false;
}
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:
/* transaction */
l_put_le16(1, ptr);
ptr += 2;
/* status success */
l_put_le16(0, ptr);
ptr += 2;
/* group */
l_put_le16(sm->group, ptr);
ptr += 2;
if (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);
*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;
/*
* confirm = CN(KCK, send-confirm, commit-scalar, COMMIT-ELEMENT,
* peer-commit-scalar, PEER-COMMIT-ELEMENT)
*/
sae_cn(sm->kck, sm->sc, sm->scalar, sm->element, sm->p_scalar,
sm->p_element, confirm);
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, 32);
ptr += 32;
sm->state = SAE_STATE_CONFIRMED;
sm->tx_auth(body, 38, sm->user_data);
}
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;
uint8_t k[L_ECC_SCALAR_MAX_BYTES];
struct l_ecc_point *k_point;
uint8_t zero_key[32] = { 0 };
uint8_t keyseed[32];
uint8_t kck_and_pmk[2][32];
uint8_t tmp[L_ECC_SCALAR_MAX_BYTES];
struct l_ecc_scalar *tmp_scalar;
uint16_t group;
uint16_t reason = MMPDU_REASON_CODE_UNSPECIFIED;
ssize_t klen;
struct l_ecc_scalar *order;
unsigned int nbytes = l_ecc_curve_get_scalar_bytes(sm->curve);
if (sm->state != SAE_STATE_COMMITTED) {
l_error("bad state %u", sm->state);
goto reject;
}
/* Scalar + Point + group */
if (len < nbytes + nbytes * 2 + 2) {
l_error("bad packet length");
goto reject;
}
group = l_get_le16(ptr);
ptr += 2;
if (group != sm->group) {
sae_reject_authentication(sm,
MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP);
return 0;
}
sm->p_scalar = l_ecc_scalar_new(sm->curve, ptr, nbytes);
if (!sm->p_scalar) {
l_error("Server sent invalid P_Scalar during commit");
reason = MMPDU_REASON_CODE_UNSPECIFIED;
goto reject;
}
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");
reason = MMPDU_REASON_CODE_UNSPECIFIED;
goto reject;
}
if (l_ecc_scalars_are_equal(sm->p_scalar, sm->scalar) ||
l_ecc_points_are_equal(sm->p_element, sm->element)) {
/* possible reflection attack, silently discard message */
l_warn("peer scalar or element matched own, discarding frame");
return 0;
}
sm->sc++;
/*
* 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)
goto reject;
/* keyseed = H(<0>32, k) */
hmac_sha256(zero_key, 32, k, klen, keyseed, 32);
/*
* kck_and_pmk = KDF-Hash-512(keyseed, "SAE KCK and PMK",
(commit-scalar + peer-commit-scalar) mod r)
*/
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));
kdf_sha256(keyseed, 32, "SAE KCK and PMK", strlen("SAE KCK and PMK"),
tmp, nbytes, 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)
*/
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);
sae_send_confirm(sm);
return 0;
reject:
sae_reject_authentication(sm, reason);
return -EBADMSG;
}
static bool sae_verify_confirm(struct sae_sm *sm, const uint8_t *frame)
{
uint8_t check[32];
uint16_t rc = l_get_le16(frame);
sae_cn(sm->kck, rc, sm->p_scalar, sm->p_element, sm->scalar,
sm->element, check);
if (memcmp(frame + 2, check, 32)) {
l_error("confirm did not match");
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 (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->state = SAE_STATE_ACCEPTED;
sm->tx_assoc(sm->user_data);
return 0;
reject:
sae_reject_authentication(sm, MMPDU_REASON_CODE_UNSPECIFIED);
return -EBADMSG;
}
static bool sae_send_commit(struct sae_sm *sm, bool retry)
{
struct handshake_state *hs = sm->handshake;
/* regular commit + possible 256 byte token + 6 bytes header */
uint8_t commit[L_ECC_SCALAR_MAX_BYTES + L_ECC_POINT_MAX_BYTES + 262];
size_t len;
if (!sae_build_commit(sm, hs->spa, hs->aa, commit, &len, retry))
return false;
sm->state = SAE_STATE_COMMITTED;
sm->tx_auth(commit, len, sm->user_data);
return true;
}
static bool sae_timeout(struct auth_proto *ap)
{
struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap);
/* regardless of state, reject if sync exceeds max */
if (sm->sync > SAE_SYNC_MAX) {
sae_reject_authentication(sm, MMPDU_REASON_CODE_UNSPECIFIED);
return false;
}
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 false;
}
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 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"
*
* 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 < 3 || len > 258) {
l_error("anti-clogging token size invalid %zu", 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 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) {
sae_reject_authentication(sm,
MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP);
return -EBADMSG;
}
return 0;
}
static void sae_reset_state(struct sae_sm *sm)
{
l_free(sm->token);
sm->token = NULL;
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;
}
/*
* 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)
{
/*
* 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 -EBADMSG;
sm->sync++;
sae_send_commit(sm, true);
return -EAGAIN;
}
switch (status) {
case MMPDU_STATUS_CODE_ANTI_CLOGGING_TOKEN_REQ:
sae_process_anti_clogging(sm, frame, len);
return -EAGAIN;
case 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 -EBADMSG;
sm->group_retry++;
if (sm->ecc_groups[sm->group_retry] == 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;
}
/*
* "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"
*/
sae_reset_state(sm);
sm->group = sm->ecc_groups[sm->group_retry];
sm->curve = l_ecc_curve_get_ike_group(sm->group);
sm->sync = 0;
sae_send_commit(sm, false);
return -EAGAIN;
case 0:
if (len < 2)
return -EBADMSG;
if (l_get_le16(frame) == sm->group)
return 0;
if (!l_ecc_curve_get_ike_group(l_get_le16(frame))) {
if (sm->sync > SAE_SYNC_MAX)
return -EBADMSG;
sm->sync++;
goto reject_unsupp_group;
}
/*
* If we get here we know that the groups do not match, but the
* group provided in the frame is supported. From section
* 12.4.8.6.4 we see:
*
* "If the group is supported but does not match that used when
* the protocol instance constructed its SAE Commit message,
* DiffGrp shall be set and the local identity and peer identity
* shall be checked"
*/
if (memcmp(sm->handshake->spa, sm->handshake->aa, 6) > 0) {
/*
* "The mesh STA, with the numerically greater of the two
* MAC addresses, drops the received SAE Commit message,
* retransmits its last SAE Commit message, and shall
* set the t0 (retransmission) timer and remain in
* Committed state"
*/
sae_send_commit(sm, true);
return 0;
}
/*
* "The mesh STA, with the numerically lesser of the two
* MAC addresses, zeros Sync, shall increment Sc, choose
* the group from the received SAE Commit message,
* generate new PWE and new secret values according to
* 12.4.5.2, process the received SAE Commit message
* according to 12.4.5.4, generate a new SAE Commit
* message and SAE Confirm message, and shall transmit
* the new Commit and Confirm to the peer. It shall then
* transition to Confirmed state."
*/
sm->sync = 0;
sm->sc++;
sm->group = l_get_le16(frame);
sm->curve = l_ecc_curve_get_ike_group(sm->group);
sae_send_commit(sm, false);
sm->state = SAE_STATE_CONFIRMED;
/*
* The processing and sending of the confirm message
* will happen after we return. Since we have set the
* state to CONFIRMED, our confirm handler will get
* called.
*/
return 0;
default:
/*
* If the Status is some other nonzero value, the frame shall
* be silently discarded...
*/
return 0;
}
reject_unsupp_group:
sae_reject_authentication(sm,
MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP);
return 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)
return 0;
/*
* If the Status is nonzero, the frame shall be silently discarded...
*/
if (status != 0)
return 0;
/*
* 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 -EBADMSG;
if (len < 2)
return -EBADMSG;
/* frame shall be silently discarded */
if (l_get_le16(frame) != sm->group)
return 0;
/*
* 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 0;
}
/*
* 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;
/* 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 -EBADMSG;
}
if (sm->sync > SAE_SYNC_MAX)
return -EBADMSG;
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;
sae_send_confirm(sm);
return 0;
}
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 -1;
}
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 = mpdu_validate(frame, len);
const struct mmpdu_authentication *auth;
int ret;
if (!hdr) {
l_debug("Auth frame header did not validate");
goto reject;
}
auth = mmpdu_body(hdr);
len -= mmpdu_header_len(hdr);
ret = sae_verify_packet(sm, L_LE16_TO_CPU(auth->transaction_sequence),
L_LE16_TO_CPU(auth->status),
auth->ies, len - 6);
if (ret != 0)
return ret;
switch (L_LE16_TO_CPU(auth->transaction_sequence)) {
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",
L_LE16_TO_CPU(auth->transaction_sequence));
}
reject:
sae_reject_authentication(sm, MMPDU_REASON_CODE_UNSPECIFIED);
return -EBADMSG;
}
static int sae_rx_associate(struct auth_proto *ap, const uint8_t *frame,
size_t len)
{
const struct mmpdu_header *mpdu = NULL;
const struct mmpdu_association_response *body;
mpdu = mpdu_validate(frame, len);
if (!mpdu) {
l_error("could not process frame");
return -EBADMSG;
}
body = mmpdu_body(mpdu);
if (body->status_code != 0)
return L_LE16_TO_CPU(body->status_code);
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);
return sae_send_commit(sm, false);
}
static void sae_free(struct auth_proto *ap)
{
struct sae_sm *sm = l_container_of(ap, struct sae_sm, ap);
sae_reset_state(sm);
/* 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;
sm = l_new(struct sae_sm, 1);
if (!sm)
return NULL;
sm->tx_auth = tx_auth;
sm->tx_assoc = tx_assoc;
sm->user_data = user_data;
sm->handshake = hs;
sm->state = SAE_STATE_NOTHING;
sm->ecc_groups = l_ecc_curve_get_supported_ike_groups();
sm->group = sm->ecc_groups[sm->group_retry];
sm->curve = l_ecc_curve_get_ike_group(sm->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.auth_timeout = sae_timeout;
return &sm->ap;
}