2012-12-30 21:06:11 +01:00
|
|
|
/*
|
|
|
|
* Simultaneous authentication of equals
|
2016-03-27 20:43:24 +02:00
|
|
|
* Copyright (c) 2012-2016, Jouni Malinen <j@w1.fi>
|
2012-12-30 21:06:11 +01:00
|
|
|
*
|
|
|
|
* This software may be distributed under the terms of the BSD license.
|
|
|
|
* See README for more details.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "includes.h"
|
|
|
|
|
|
|
|
#include "common.h"
|
2019-02-26 12:05:09 +01:00
|
|
|
#include "utils/const_time.h"
|
2012-12-31 18:41:21 +01:00
|
|
|
#include "crypto/crypto.h"
|
2012-12-30 21:06:11 +01:00
|
|
|
#include "crypto/sha256.h"
|
2019-08-27 15:33:15 +02:00
|
|
|
#include "crypto/sha384.h"
|
|
|
|
#include "crypto/sha512.h"
|
2012-12-30 21:06:11 +01:00
|
|
|
#include "crypto/random.h"
|
2013-01-05 20:22:00 +01:00
|
|
|
#include "crypto/dh_groups.h"
|
2012-12-30 21:16:18 +01:00
|
|
|
#include "ieee802_11_defs.h"
|
2019-04-25 18:45:27 +02:00
|
|
|
#include "dragonfly.h"
|
2012-12-30 21:06:11 +01:00
|
|
|
#include "sae.h"
|
|
|
|
|
|
|
|
|
2013-01-01 10:29:53 +01:00
|
|
|
int sae_set_group(struct sae_data *sae, int group)
|
|
|
|
{
|
2013-01-06 18:26:27 +01:00
|
|
|
struct sae_temporary_data *tmp;
|
|
|
|
|
2019-04-25 18:45:27 +02:00
|
|
|
#ifdef CONFIG_TESTING_OPTIONS
|
|
|
|
/* Allow all groups for testing purposes in non-production builds. */
|
|
|
|
#else /* CONFIG_TESTING_OPTIONS */
|
|
|
|
if (!dragonfly_suitable_group(group, 0)) {
|
2019-04-08 17:01:07 +02:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Reject unsuitable group %d", group);
|
|
|
|
return -1;
|
|
|
|
}
|
2019-04-25 18:45:27 +02:00
|
|
|
#endif /* CONFIG_TESTING_OPTIONS */
|
2019-04-08 17:01:07 +02:00
|
|
|
|
2013-01-05 20:22:00 +01:00
|
|
|
sae_clear_data(sae);
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp = sae->tmp = os_zalloc(sizeof(*tmp));
|
|
|
|
if (tmp == NULL)
|
|
|
|
return -1;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
|
|
|
/* First, check if this is an ECC group */
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->ec = crypto_ec_init(group);
|
|
|
|
if (tmp->ec) {
|
2018-03-02 11:29:30 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Selecting supported ECC group %d",
|
|
|
|
group);
|
2013-01-05 20:22:00 +01:00
|
|
|
sae->group = group;
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->prime_len = crypto_ec_prime_len(tmp->ec);
|
|
|
|
tmp->prime = crypto_ec_get_prime(tmp->ec);
|
SAE: Fix KCK, PMK, and PMKID derivation for groups 22, 23, 24
IEEE Std 802.11-2016 is not exactly clear on the encoding of the bit
string that is needed for KCK, PMK, and PMKID derivation, but it seems
to make most sense to encode the (commit-scalar + peer-commit-scalar)
mod r part as a bit string by zero padding it from left to the length of
the order (in full octets).
The previous implementation used the length of the prime (in full
octets). This would work for KCK/PMK, but this results in deriving all
zero PMKIDs for the groups where the size of the order is smaller than
the size of the prime. This is the case for groups 22, 23, and 24.
However, those groups have been marked as being unsuitable for use with
SAE, so this fix should not really have a practical impact anymore.
Anyway, better fix it and document this clearly in the implementation
taken into account the unclarity of the standard in this area.
Signed-off-by: Jouni Malinen <j@w1.fi>
2019-08-03 16:00:39 +02:00
|
|
|
tmp->order_len = crypto_ec_order_len(tmp->ec);
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->order = crypto_ec_get_order(tmp->ec);
|
2013-01-05 20:22:00 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2013-01-01 10:29:53 +01:00
|
|
|
|
2013-01-05 20:22:00 +01:00
|
|
|
/* Not an ECC group, check FFC */
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->dh = dh_groups_get(group);
|
|
|
|
if (tmp->dh) {
|
2018-03-02 11:29:30 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Selecting supported FFC group %d",
|
|
|
|
group);
|
2013-01-05 20:22:00 +01:00
|
|
|
sae->group = group;
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->prime_len = tmp->dh->prime_len;
|
|
|
|
if (tmp->prime_len > SAE_MAX_PRIME_LEN) {
|
2013-01-05 20:22:00 +01:00
|
|
|
sae_clear_data(sae);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-01-01 10:29:53 +01:00
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->prime_buf = crypto_bignum_init_set(tmp->dh->prime,
|
|
|
|
tmp->prime_len);
|
|
|
|
if (tmp->prime_buf == NULL) {
|
2013-01-05 20:22:00 +01:00
|
|
|
sae_clear_data(sae);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->prime = tmp->prime_buf;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-08-05 15:52:20 +02:00
|
|
|
tmp->order_len = tmp->dh->order_len;
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->order_buf = crypto_bignum_init_set(tmp->dh->order,
|
|
|
|
tmp->dh->order_len);
|
|
|
|
if (tmp->order_buf == NULL) {
|
2013-01-05 20:22:00 +01:00
|
|
|
sae_clear_data(sae);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp->order = tmp->order_buf;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unsupported group */
|
2018-03-02 11:29:30 +01:00
|
|
|
wpa_printf(MSG_DEBUG,
|
|
|
|
"SAE: Group %d not supported by the crypto library", group);
|
2013-01-05 20:22:00 +01:00
|
|
|
return -1;
|
2013-01-01 10:29:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 18:06:59 +01:00
|
|
|
void sae_clear_temp_data(struct sae_data *sae)
|
2013-01-01 10:29:53 +01:00
|
|
|
{
|
2013-01-06 18:26:27 +01:00
|
|
|
struct sae_temporary_data *tmp;
|
|
|
|
if (sae == NULL || sae->tmp == NULL)
|
2013-01-01 10:29:53 +01:00
|
|
|
return;
|
2013-01-06 18:26:27 +01:00
|
|
|
tmp = sae->tmp;
|
|
|
|
crypto_ec_deinit(tmp->ec);
|
|
|
|
crypto_bignum_deinit(tmp->prime_buf, 0);
|
|
|
|
crypto_bignum_deinit(tmp->order_buf, 0);
|
|
|
|
crypto_bignum_deinit(tmp->sae_rand, 1);
|
|
|
|
crypto_bignum_deinit(tmp->pwe_ffc, 1);
|
|
|
|
crypto_bignum_deinit(tmp->own_commit_scalar, 0);
|
|
|
|
crypto_bignum_deinit(tmp->own_commit_element_ffc, 0);
|
|
|
|
crypto_bignum_deinit(tmp->peer_commit_element_ffc, 0);
|
|
|
|
crypto_ec_point_deinit(tmp->pwe_ecc, 1);
|
|
|
|
crypto_ec_point_deinit(tmp->own_commit_element_ecc, 0);
|
|
|
|
crypto_ec_point_deinit(tmp->peer_commit_element_ecc, 0);
|
2014-11-25 03:04:40 +01:00
|
|
|
wpabuf_free(tmp->anti_clogging_token);
|
2019-08-27 15:33:15 +02:00
|
|
|
wpabuf_free(tmp->own_rejected_groups);
|
2019-09-07 17:38:29 +02:00
|
|
|
wpabuf_free(tmp->peer_rejected_groups);
|
2018-05-19 16:28:01 +02:00
|
|
|
os_free(tmp->pw_id);
|
2014-12-29 17:40:10 +01:00
|
|
|
bin_clear_free(tmp, sizeof(*tmp));
|
2013-01-06 18:26:27 +01:00
|
|
|
sae->tmp = NULL;
|
2013-01-06 18:06:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void sae_clear_data(struct sae_data *sae)
|
|
|
|
{
|
|
|
|
if (sae == NULL)
|
|
|
|
return;
|
|
|
|
sae_clear_temp_data(sae);
|
|
|
|
crypto_bignum_deinit(sae->peer_commit_scalar, 0);
|
2013-01-01 10:29:53 +01:00
|
|
|
os_memset(sae, 0, sizeof(*sae));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-30 21:06:11 +01:00
|
|
|
static void sae_pwd_seed_key(const u8 *addr1, const u8 *addr2, u8 *key)
|
|
|
|
{
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: PWE derivation - addr1=" MACSTR
|
|
|
|
" addr2=" MACSTR, MAC2STR(addr1), MAC2STR(addr2));
|
|
|
|
if (os_memcmp(addr1, addr2, ETH_ALEN) > 0) {
|
|
|
|
os_memcpy(key, addr1, ETH_ALEN);
|
|
|
|
os_memcpy(key + ETH_ALEN, addr2, ETH_ALEN);
|
|
|
|
} else {
|
|
|
|
os_memcpy(key, addr2, ETH_ALEN);
|
|
|
|
os_memcpy(key + ETH_ALEN, addr1, ETH_ALEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 17:30:11 +01:00
|
|
|
static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
|
2019-02-26 18:34:38 +01:00
|
|
|
const u8 *prime, const u8 *qr, const u8 *qnr,
|
2019-02-26 12:05:09 +01:00
|
|
|
u8 *pwd_value)
|
2012-12-30 21:06:11 +01:00
|
|
|
{
|
2015-06-25 10:35:39 +02:00
|
|
|
struct crypto_bignum *y_sqr, *x_cand;
|
|
|
|
int res;
|
2013-01-01 13:00:40 +01:00
|
|
|
size_t bits;
|
2019-06-24 22:01:06 +02:00
|
|
|
int cmp_prime;
|
|
|
|
unsigned int in_range;
|
2012-12-30 21:06:11 +01:00
|
|
|
|
2013-01-01 10:52:49 +01:00
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
|
2012-12-30 21:06:11 +01:00
|
|
|
|
|
|
|
/* pwd-value = KDF-z(pwd-seed, "SAE Hunting and Pecking", p) */
|
2013-01-06 18:26:27 +01:00
|
|
|
bits = crypto_ec_prime_len_bits(sae->tmp->ec);
|
2016-03-27 20:43:24 +02:00
|
|
|
if (sha256_prf_bits(pwd_seed, SHA256_MAC_LEN, "SAE Hunting and Pecking",
|
|
|
|
prime, sae->tmp->prime_len, pwd_value, bits) < 0)
|
|
|
|
return -1;
|
2013-01-01 13:00:40 +01:00
|
|
|
if (bits % 8)
|
2019-02-26 12:05:09 +01:00
|
|
|
buf_shift_right(pwd_value, sae->tmp->prime_len, 8 - bits % 8);
|
2012-12-30 21:06:11 +01:00
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value",
|
2013-01-06 18:26:27 +01:00
|
|
|
pwd_value, sae->tmp->prime_len);
|
2012-12-30 21:06:11 +01:00
|
|
|
|
2019-06-24 22:01:06 +02:00
|
|
|
cmp_prime = const_time_memcmp(pwd_value, prime, sae->tmp->prime_len);
|
|
|
|
/* Create a const_time mask for selection based on prf result
|
|
|
|
* being smaller than prime. */
|
|
|
|
in_range = const_time_fill_msb((unsigned int) cmp_prime);
|
|
|
|
/* The algorithm description would skip the next steps if
|
|
|
|
* cmp_prime >= 0 (reutnr 0 here), but go through them regardless to
|
|
|
|
* minimize externally observable differences in behavior. */
|
2012-12-30 21:06:11 +01:00
|
|
|
|
2015-06-25 10:35:39 +02:00
|
|
|
x_cand = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
|
|
|
|
if (!x_cand)
|
|
|
|
return -1;
|
|
|
|
y_sqr = crypto_ec_point_compute_y_sqr(sae->tmp->ec, x_cand);
|
2019-02-26 12:05:09 +01:00
|
|
|
crypto_bignum_deinit(x_cand, 1);
|
|
|
|
if (!y_sqr)
|
2012-12-30 21:06:11 +01:00
|
|
|
return -1;
|
|
|
|
|
2019-04-25 21:35:14 +02:00
|
|
|
res = dragonfly_is_quadratic_residue_blind(sae->tmp->ec, qr, qnr,
|
|
|
|
y_sqr);
|
2015-06-25 10:35:39 +02:00
|
|
|
crypto_bignum_deinit(y_sqr, 1);
|
2019-06-24 22:01:06 +02:00
|
|
|
if (res < 0)
|
|
|
|
return res;
|
|
|
|
return const_time_select_int(in_range, res, 0);
|
2012-12-30 21:06:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
/* Returns -1 on fatal failure, 0 if PWE cannot be derived from the provided
|
|
|
|
* pwd-seed, or 1 if a valid PWE was derived from pwd-seed. */
|
2013-01-06 17:30:11 +01:00
|
|
|
static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed,
|
|
|
|
struct crypto_bignum *pwe)
|
2013-01-05 20:22:00 +01:00
|
|
|
{
|
2013-01-06 17:38:17 +01:00
|
|
|
u8 pwd_value[SAE_MAX_PRIME_LEN];
|
2013-01-06 18:26:27 +01:00
|
|
|
size_t bits = sae->tmp->prime_len * 8;
|
2013-01-05 20:22:00 +01:00
|
|
|
u8 exp[1];
|
2019-03-02 15:05:56 +01:00
|
|
|
struct crypto_bignum *a, *b = NULL;
|
|
|
|
int res, is_val;
|
|
|
|
u8 pwd_value_valid;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
|
|
|
|
|
|
|
|
/* pwd-value = KDF-z(pwd-seed, "SAE Hunting and Pecking", p) */
|
2016-03-27 20:43:24 +02:00
|
|
|
if (sha256_prf_bits(pwd_seed, SHA256_MAC_LEN, "SAE Hunting and Pecking",
|
|
|
|
sae->tmp->dh->prime, sae->tmp->prime_len, pwd_value,
|
|
|
|
bits) < 0)
|
|
|
|
return -1;
|
2013-01-06 18:26:27 +01:00
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value", pwd_value,
|
|
|
|
sae->tmp->prime_len);
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
/* Check whether pwd-value < p */
|
|
|
|
res = const_time_memcmp(pwd_value, sae->tmp->dh->prime,
|
|
|
|
sae->tmp->prime_len);
|
|
|
|
/* pwd-value >= p is invalid, so res is < 0 for the valid cases and
|
|
|
|
* the negative sign can be used to fill the mask for constant time
|
|
|
|
* selection */
|
|
|
|
pwd_value_valid = const_time_fill_msb(res);
|
|
|
|
|
|
|
|
/* If pwd-value >= p, force pwd-value to be < p and perform the
|
|
|
|
* calculations anyway to hide timing difference. The derived PWE will
|
|
|
|
* be ignored in that case. */
|
|
|
|
pwd_value[0] = const_time_select_u8(pwd_value_valid, pwd_value[0], 0);
|
2013-01-05 20:22:00 +01:00
|
|
|
|
|
|
|
/* PWE = pwd-value^((p-1)/r) modulo p */
|
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
res = -1;
|
2013-01-06 18:26:27 +01:00
|
|
|
a = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
|
2019-03-02 15:05:56 +01:00
|
|
|
if (!a)
|
|
|
|
goto fail;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
/* This is an optimization based on the used group that does not depend
|
|
|
|
* on the password in any way, so it is fine to use separate branches
|
|
|
|
* for this step without constant time operations. */
|
2013-01-06 18:26:27 +01:00
|
|
|
if (sae->tmp->dh->safe_prime) {
|
2013-01-05 20:22:00 +01:00
|
|
|
/*
|
|
|
|
* r = (p-1)/2 for the group used here, so this becomes:
|
|
|
|
* PWE = pwd-value^2 modulo p
|
|
|
|
*/
|
|
|
|
exp[0] = 2;
|
|
|
|
b = crypto_bignum_init_set(exp, sizeof(exp));
|
|
|
|
} else {
|
2013-01-06 17:56:46 +01:00
|
|
|
/* Calculate exponent: (p-1)/r */
|
2013-01-05 20:22:00 +01:00
|
|
|
exp[0] = 1;
|
|
|
|
b = crypto_bignum_init_set(exp, sizeof(exp));
|
2013-01-06 17:56:46 +01:00
|
|
|
if (b == NULL ||
|
2013-01-06 18:26:27 +01:00
|
|
|
crypto_bignum_sub(sae->tmp->prime, b, b) < 0 ||
|
2019-03-02 15:05:56 +01:00
|
|
|
crypto_bignum_div(b, sae->tmp->order, b) < 0)
|
|
|
|
goto fail;
|
2013-01-05 20:22:00 +01:00
|
|
|
}
|
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
if (!b)
|
|
|
|
goto fail;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
res = crypto_bignum_exptmod(a, b, sae->tmp->prime, pwe);
|
|
|
|
if (res < 0)
|
|
|
|
goto fail;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
/* There were no fatal errors in calculations, so determine the return
|
|
|
|
* value using constant time operations. We get here for number of
|
|
|
|
* invalid cases which are cleared here after having performed all the
|
|
|
|
* computation. PWE is valid if pwd-value was less than prime and
|
|
|
|
* PWE > 1. Start with pwd-value check first and then use constant time
|
|
|
|
* operations to clear res to 0 if PWE is 0 or 1.
|
|
|
|
*/
|
|
|
|
res = const_time_select_u8(pwd_value_valid, 1, 0);
|
|
|
|
is_val = crypto_bignum_is_zero(pwe);
|
|
|
|
res = const_time_select_u8(const_time_is_zero(is_val), res, 0);
|
|
|
|
is_val = crypto_bignum_is_one(pwe);
|
|
|
|
res = const_time_select_u8(const_time_is_zero(is_val), res, 0);
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-03-02 15:05:56 +01:00
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(a, 1);
|
|
|
|
crypto_bignum_deinit(b, 1);
|
|
|
|
return res;
|
2013-01-05 20:22:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 17:30:11 +01:00
|
|
|
static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
|
|
|
const u8 *addr2, const u8 *password,
|
2018-05-19 16:28:01 +02:00
|
|
|
size_t password_len, const char *identifier)
|
2013-01-06 17:30:11 +01:00
|
|
|
{
|
2019-07-23 20:21:30 +02:00
|
|
|
u8 counter, k;
|
2013-01-06 17:30:11 +01:00
|
|
|
u8 addrs[2 * ETH_ALEN];
|
2018-05-19 16:28:01 +02:00
|
|
|
const u8 *addr[3];
|
|
|
|
size_t len[3];
|
|
|
|
size_t num_elem;
|
2019-02-26 12:05:09 +01:00
|
|
|
u8 *dummy_password, *tmp_password;
|
2015-06-25 10:35:39 +02:00
|
|
|
int pwd_seed_odd = 0;
|
|
|
|
u8 prime[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
size_t prime_len;
|
2019-02-26 12:05:09 +01:00
|
|
|
struct crypto_bignum *x = NULL, *qr = NULL, *qnr = NULL;
|
|
|
|
u8 x_bin[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
u8 x_cand_bin[SAE_MAX_ECC_PRIME_LEN];
|
2019-02-26 18:34:38 +01:00
|
|
|
u8 qr_bin[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
u8 qnr_bin[SAE_MAX_ECC_PRIME_LEN];
|
2019-02-26 12:05:09 +01:00
|
|
|
int res = -1;
|
|
|
|
u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
|
|
|
|
* mask */
|
2013-01-06 17:30:11 +01:00
|
|
|
|
2019-02-26 12:05:09 +01:00
|
|
|
os_memset(x_bin, 0, sizeof(x_bin));
|
|
|
|
|
|
|
|
dummy_password = os_malloc(password_len);
|
|
|
|
tmp_password = os_malloc(password_len);
|
|
|
|
if (!dummy_password || !tmp_password ||
|
|
|
|
random_get_bytes(dummy_password, password_len) < 0)
|
|
|
|
goto fail;
|
2015-06-26 10:44:22 +02:00
|
|
|
|
2015-06-25 10:35:39 +02:00
|
|
|
prime_len = sae->tmp->prime_len;
|
|
|
|
if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
|
|
|
prime_len) < 0)
|
2019-02-26 12:05:09 +01:00
|
|
|
goto fail;
|
2015-06-25 10:35:39 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Create a random quadratic residue (qr) and quadratic non-residue
|
|
|
|
* (qnr) modulo p for blinding purposes during the loop.
|
|
|
|
*/
|
2019-04-25 19:18:27 +02:00
|
|
|
if (dragonfly_get_random_qr_qnr(sae->tmp->prime, &qr, &qnr) < 0 ||
|
2019-02-26 18:34:38 +01:00
|
|
|
crypto_bignum_to_bin(qr, qr_bin, sizeof(qr_bin), prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(qnr, qnr_bin, sizeof(qnr_bin), prime_len) < 0)
|
2019-02-26 12:05:09 +01:00
|
|
|
goto fail;
|
2013-01-06 17:30:11 +01:00
|
|
|
|
|
|
|
wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
|
|
|
password, password_len);
|
2018-05-19 16:28:01 +02:00
|
|
|
if (identifier)
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: password identifier: %s",
|
|
|
|
identifier);
|
2013-01-06 17:30:11 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* H(salt, ikm) = HMAC-SHA256(salt, ikm)
|
2018-05-19 16:28:01 +02:00
|
|
|
* base = password [|| identifier]
|
2013-01-06 17:30:11 +01:00
|
|
|
* pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC),
|
2015-06-26 10:44:22 +02:00
|
|
|
* base || counter)
|
2013-01-06 17:30:11 +01:00
|
|
|
*/
|
|
|
|
sae_pwd_seed_key(addr1, addr2, addrs);
|
|
|
|
|
2019-02-26 12:05:09 +01:00
|
|
|
addr[0] = tmp_password;
|
2013-01-06 17:30:11 +01:00
|
|
|
len[0] = password_len;
|
2018-05-19 16:28:01 +02:00
|
|
|
num_elem = 1;
|
|
|
|
if (identifier) {
|
|
|
|
addr[num_elem] = (const u8 *) identifier;
|
|
|
|
len[num_elem] = os_strlen(identifier);
|
|
|
|
num_elem++;
|
|
|
|
}
|
|
|
|
addr[num_elem] = &counter;
|
|
|
|
len[num_elem] = sizeof(counter);
|
|
|
|
num_elem++;
|
2013-01-06 17:30:11 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Continue for at least k iterations to protect against side-channel
|
|
|
|
* attacks that attempt to determine the number of iterations required
|
|
|
|
* in the loop.
|
|
|
|
*/
|
2019-07-23 20:21:30 +02:00
|
|
|
k = dragonfly_min_pwe_loop_iter(sae->group);
|
|
|
|
|
2019-02-26 12:05:09 +01:00
|
|
|
for (counter = 1; counter <= k || !found; counter++) {
|
2013-01-06 17:30:11 +01:00
|
|
|
u8 pwd_seed[SHA256_MAC_LEN];
|
|
|
|
|
|
|
|
if (counter > 200) {
|
|
|
|
/* This should not happen in practice */
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to derive PWE");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-02-26 12:05:09 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: counter = %03u", counter);
|
|
|
|
const_time_select_bin(found, dummy_password, password,
|
|
|
|
password_len, tmp_password);
|
2018-05-19 16:28:01 +02:00
|
|
|
if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
|
|
|
|
addr, len, pwd_seed) < 0)
|
2013-01-06 17:30:11 +01:00
|
|
|
break;
|
2015-06-25 10:35:39 +02:00
|
|
|
|
2013-01-06 17:30:11 +01:00
|
|
|
res = sae_test_pwd_seed_ecc(sae, pwd_seed,
|
2019-02-26 18:34:38 +01:00
|
|
|
prime, qr_bin, qnr_bin, x_cand_bin);
|
2019-02-26 12:05:09 +01:00
|
|
|
const_time_select_bin(found, x_bin, x_cand_bin, prime_len,
|
|
|
|
x_bin);
|
|
|
|
pwd_seed_odd = const_time_select_u8(
|
|
|
|
found, pwd_seed_odd,
|
|
|
|
pwd_seed[SHA256_MAC_LEN - 1] & 0x01);
|
|
|
|
os_memset(pwd_seed, 0, sizeof(pwd_seed));
|
2013-01-06 17:30:11 +01:00
|
|
|
if (res < 0)
|
2015-06-25 10:35:39 +02:00
|
|
|
goto fail;
|
2019-02-26 12:05:09 +01:00
|
|
|
/* Need to minimize differences in handling res == 0 and 1 here
|
|
|
|
* to avoid differences in timing and instruction cache access,
|
|
|
|
* so use const_time_select_*() to make local copies of the
|
|
|
|
* values based on whether this loop iteration was the one that
|
|
|
|
* found the pwd-seed/x. */
|
|
|
|
|
|
|
|
/* found is 0 or 0xff here and res is 0 or 1. Bitwise OR of them
|
|
|
|
* (with res converted to 0/0xff) handles this in constant time.
|
|
|
|
*/
|
|
|
|
found |= res * 0xff;
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: pwd-seed result %d found=0x%02x",
|
|
|
|
res, found);
|
2013-01-06 17:30:11 +01:00
|
|
|
}
|
|
|
|
|
2019-02-26 12:05:09 +01:00
|
|
|
if (!found) {
|
2015-06-25 10:35:39 +02:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
|
|
|
|
res = -1;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2019-02-26 12:05:09 +01:00
|
|
|
x = crypto_bignum_init_set(x_bin, prime_len);
|
|
|
|
if (!x) {
|
|
|
|
res = -1;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2015-06-25 10:35:39 +02:00
|
|
|
if (!sae->tmp->pwe_ecc)
|
|
|
|
sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec);
|
|
|
|
if (!sae->tmp->pwe_ecc)
|
|
|
|
res = -1;
|
|
|
|
else
|
|
|
|
res = crypto_ec_point_solve_y_coord(sae->tmp->ec,
|
|
|
|
sae->tmp->pwe_ecc, x,
|
|
|
|
pwd_seed_odd);
|
|
|
|
if (res < 0) {
|
|
|
|
/*
|
|
|
|
* This should not happen since we already checked that there
|
|
|
|
* is a result.
|
|
|
|
*/
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not solve y");
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(qr, 0);
|
|
|
|
crypto_bignum_deinit(qnr, 0);
|
2019-02-26 12:05:09 +01:00
|
|
|
os_free(dummy_password);
|
|
|
|
bin_clear_free(tmp_password, password_len);
|
|
|
|
crypto_bignum_deinit(x, 1);
|
|
|
|
os_memset(x_bin, 0, sizeof(x_bin));
|
|
|
|
os_memset(x_cand_bin, 0, sizeof(x_cand_bin));
|
2013-01-06 17:30:11 +01:00
|
|
|
|
2015-06-25 10:35:39 +02:00
|
|
|
return res;
|
2013-01-06 17:30:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1,
|
|
|
|
const u8 *addr2, const u8 *password,
|
2018-05-19 16:28:01 +02:00
|
|
|
size_t password_len, const char *identifier)
|
2013-01-05 20:22:00 +01:00
|
|
|
{
|
2019-03-02 11:45:33 +01:00
|
|
|
u8 counter, k, sel_counter = 0;
|
2013-01-05 20:22:00 +01:00
|
|
|
u8 addrs[2 * ETH_ALEN];
|
2018-05-19 16:28:01 +02:00
|
|
|
const u8 *addr[3];
|
|
|
|
size_t len[3];
|
|
|
|
size_t num_elem;
|
2019-03-02 11:45:33 +01:00
|
|
|
u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
|
|
|
|
* mask */
|
|
|
|
u8 mask;
|
|
|
|
struct crypto_bignum *pwe;
|
|
|
|
size_t prime_len = sae->tmp->prime_len * 8;
|
|
|
|
u8 *pwe_buf;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-03-02 11:24:09 +01:00
|
|
|
crypto_bignum_deinit(sae->tmp->pwe_ffc, 1);
|
|
|
|
sae->tmp->pwe_ffc = NULL;
|
2013-01-06 15:10:48 +01:00
|
|
|
|
2019-03-02 11:45:33 +01:00
|
|
|
/* Allocate a buffer to maintain selected and candidate PWE for constant
|
|
|
|
* time selection. */
|
|
|
|
pwe_buf = os_zalloc(prime_len * 2);
|
|
|
|
pwe = crypto_bignum_init();
|
|
|
|
if (!pwe_buf || !pwe)
|
|
|
|
goto fail;
|
|
|
|
|
2013-01-05 20:22:00 +01:00
|
|
|
wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
|
|
|
password, password_len);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* H(salt, ikm) = HMAC-SHA256(salt, ikm)
|
|
|
|
* pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC),
|
2018-05-19 16:28:01 +02:00
|
|
|
* password [|| identifier] || counter)
|
2013-01-05 20:22:00 +01:00
|
|
|
*/
|
|
|
|
sae_pwd_seed_key(addr1, addr2, addrs);
|
|
|
|
|
|
|
|
addr[0] = password;
|
|
|
|
len[0] = password_len;
|
2018-05-19 16:28:01 +02:00
|
|
|
num_elem = 1;
|
|
|
|
if (identifier) {
|
|
|
|
addr[num_elem] = (const u8 *) identifier;
|
|
|
|
len[num_elem] = os_strlen(identifier);
|
|
|
|
num_elem++;
|
|
|
|
}
|
|
|
|
addr[num_elem] = &counter;
|
|
|
|
len[num_elem] = sizeof(counter);
|
|
|
|
num_elem++;
|
2013-01-05 20:22:00 +01:00
|
|
|
|
2019-07-23 20:21:30 +02:00
|
|
|
k = dragonfly_min_pwe_loop_iter(sae->group);
|
2019-03-02 11:24:09 +01:00
|
|
|
|
|
|
|
for (counter = 1; counter <= k || !found; counter++) {
|
2013-01-05 20:22:00 +01:00
|
|
|
u8 pwd_seed[SHA256_MAC_LEN];
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (counter > 200) {
|
|
|
|
/* This should not happen in practice */
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to derive PWE");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-02 11:24:09 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: counter = %02u", counter);
|
2018-05-19 16:28:01 +02:00
|
|
|
if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
|
|
|
|
addr, len, pwd_seed) < 0)
|
2013-01-05 20:22:00 +01:00
|
|
|
break;
|
2019-03-02 11:24:09 +01:00
|
|
|
res = sae_test_pwd_seed_ffc(sae, pwd_seed, pwe);
|
2019-03-02 11:45:33 +01:00
|
|
|
/* res is -1 for fatal failure, 0 if a valid PWE was not found,
|
|
|
|
* or 1 if a valid PWE was found. */
|
2013-01-05 20:22:00 +01:00
|
|
|
if (res < 0)
|
|
|
|
break;
|
2019-03-02 11:45:33 +01:00
|
|
|
/* Store the candidate PWE into the second half of pwe_buf and
|
|
|
|
* the selected PWE in the beginning of pwe_buf using constant
|
|
|
|
* time selection. */
|
|
|
|
if (crypto_bignum_to_bin(pwe, pwe_buf + prime_len, prime_len,
|
|
|
|
prime_len) < 0)
|
|
|
|
break;
|
|
|
|
const_time_select_bin(found, pwe_buf, pwe_buf + prime_len,
|
|
|
|
prime_len, pwe_buf);
|
|
|
|
sel_counter = const_time_select_u8(found, sel_counter, counter);
|
|
|
|
mask = const_time_eq_u8(res, 1);
|
|
|
|
found = const_time_select_u8(found, found, mask);
|
2013-01-05 20:22:00 +01:00
|
|
|
}
|
|
|
|
|
2019-03-02 11:45:33 +01:00
|
|
|
if (!found)
|
|
|
|
goto fail;
|
2019-03-02 11:24:09 +01:00
|
|
|
|
2019-03-02 11:45:33 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Use PWE from counter = %02u", sel_counter);
|
|
|
|
sae->tmp->pwe_ffc = crypto_bignum_init_set(pwe_buf, prime_len);
|
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(pwe, 1);
|
|
|
|
bin_clear_free(pwe_buf, prime_len * 2);
|
|
|
|
return sae->tmp->pwe_ffc ? 0 : -1;
|
2013-01-05 20:22:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
static int hkdf_extract(size_t hash_len, const u8 *salt, size_t salt_len,
|
|
|
|
size_t num_elem, const u8 *addr[], const size_t len[],
|
|
|
|
u8 *prk)
|
|
|
|
{
|
|
|
|
if (hash_len == 32)
|
|
|
|
return hmac_sha256_vector(salt, salt_len, num_elem, addr, len,
|
|
|
|
prk);
|
|
|
|
#ifdef CONFIG_SHA384
|
|
|
|
if (hash_len == 48)
|
|
|
|
return hmac_sha384_vector(salt, salt_len, num_elem, addr, len,
|
|
|
|
prk);
|
|
|
|
#endif /* CONFIG_SHA384 */
|
|
|
|
#ifdef CONFIG_SHA512
|
|
|
|
if (hash_len == 64)
|
|
|
|
return hmac_sha512_vector(salt, salt_len, num_elem, addr, len,
|
|
|
|
prk);
|
|
|
|
#endif /* CONFIG_SHA512 */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int hkdf_expand(size_t hash_len, const u8 *prk, size_t prk_len,
|
|
|
|
const char *info, u8 *okm, size_t okm_len)
|
|
|
|
{
|
|
|
|
size_t info_len = os_strlen(info);
|
|
|
|
|
|
|
|
if (hash_len == 32)
|
|
|
|
return hmac_sha256_kdf(prk, prk_len, NULL,
|
|
|
|
(const u8 *) info, info_len,
|
|
|
|
okm, okm_len);
|
|
|
|
#ifdef CONFIG_SHA384
|
|
|
|
if (hash_len == 48)
|
|
|
|
return hmac_sha384_kdf(prk, prk_len, NULL,
|
|
|
|
(const u8 *) info, info_len,
|
|
|
|
okm, okm_len);
|
|
|
|
#endif /* CONFIG_SHA384 */
|
|
|
|
#ifdef CONFIG_SHA512
|
|
|
|
if (hash_len == 64)
|
|
|
|
return hmac_sha512_kdf(prk, prk_len, NULL,
|
|
|
|
(const u8 *) info, info_len,
|
|
|
|
okm, okm_len);
|
|
|
|
#endif /* CONFIG_SHA512 */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sswu_curve_param(int group, int *z)
|
|
|
|
{
|
|
|
|
switch (group) {
|
|
|
|
case 19:
|
2019-11-13 12:05:37 +01:00
|
|
|
*z = -10;
|
|
|
|
return 0;
|
2019-08-27 15:33:15 +02:00
|
|
|
case 20:
|
2019-11-13 12:05:37 +01:00
|
|
|
*z = -12;
|
|
|
|
return 0;
|
2019-08-27 15:33:15 +02:00
|
|
|
case 21:
|
2019-11-13 12:05:37 +01:00
|
|
|
*z = -4;
|
2019-08-27 15:33:15 +02:00
|
|
|
return 0;
|
|
|
|
case 25:
|
|
|
|
case 29:
|
|
|
|
*z = -5;
|
|
|
|
return 0;
|
|
|
|
case 26:
|
2019-11-13 12:05:37 +01:00
|
|
|
*z = 31;
|
|
|
|
return 0;
|
|
|
|
case 28:
|
|
|
|
*z = -2;
|
2019-08-27 15:33:15 +02:00
|
|
|
return 0;
|
|
|
|
case 30:
|
2019-11-13 12:05:37 +01:00
|
|
|
*z = 7;
|
2019-08-27 15:33:15 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void debug_print_bignum(const char *title, const struct crypto_bignum *a,
|
|
|
|
size_t prime_len)
|
|
|
|
{
|
|
|
|
u8 *bin;
|
|
|
|
|
|
|
|
bin = os_malloc(prime_len);
|
|
|
|
if (bin && crypto_bignum_to_bin(a, bin, prime_len, prime_len) >= 0)
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, title, bin, prime_len);
|
|
|
|
else
|
|
|
|
wpa_printf(MSG_DEBUG, "Could not print bignum (%s)", title);
|
|
|
|
bin_clear_free(bin, prime_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct crypto_ec_point * sswu(struct crypto_ec *ec, int group,
|
|
|
|
const struct crypto_bignum *u)
|
|
|
|
{
|
|
|
|
int z_int;
|
|
|
|
const struct crypto_bignum *a, *b, *prime;
|
|
|
|
struct crypto_bignum *u2, *t1, *t2, *z, *t, *zero, *one, *two, *three,
|
|
|
|
*x1a, *x1b, *y = NULL;
|
|
|
|
struct crypto_bignum *x1 = NULL, *x2, *gx1, *gx2, *v = NULL;
|
|
|
|
unsigned int m_is_zero, is_qr, is_eq;
|
|
|
|
size_t prime_len;
|
|
|
|
u8 bin[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
u8 bin1[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
u8 bin2[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
u8 x_y[2 * SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
struct crypto_ec_point *p = NULL;
|
|
|
|
|
|
|
|
if (sswu_curve_param(group, &z_int) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
prime = crypto_ec_get_prime(ec);
|
|
|
|
prime_len = crypto_ec_prime_len(ec);
|
|
|
|
a = crypto_ec_get_a(ec);
|
|
|
|
b = crypto_ec_get_b(ec);
|
|
|
|
|
|
|
|
u2 = crypto_bignum_init();
|
|
|
|
t1 = crypto_bignum_init();
|
|
|
|
t2 = crypto_bignum_init();
|
|
|
|
z = crypto_bignum_init_uint(abs(z_int));
|
|
|
|
t = crypto_bignum_init();
|
|
|
|
zero = crypto_bignum_init_uint(0);
|
|
|
|
one = crypto_bignum_init_uint(1);
|
|
|
|
two = crypto_bignum_init_uint(2);
|
|
|
|
three = crypto_bignum_init_uint(3);
|
|
|
|
x1a = crypto_bignum_init();
|
|
|
|
x1b = crypto_bignum_init();
|
|
|
|
x2 = crypto_bignum_init();
|
|
|
|
gx1 = crypto_bignum_init();
|
|
|
|
gx2 = crypto_bignum_init();
|
|
|
|
if (!u2 || !t1 || !t2 || !z || !t || !zero || !one || !two || !three ||
|
|
|
|
!x1a || !x1b || !x2 || !gx1 || !gx2)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (z_int < 0 && crypto_bignum_sub(prime, z, z) < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* m = z^2 * u^4 + z * u^2 */
|
|
|
|
/* --> tmp = z * u^2, m = tmp^2 + tmp */
|
|
|
|
|
|
|
|
/* u2 = u^2
|
|
|
|
* t1 = z * u2
|
|
|
|
* t2 = t1^2
|
|
|
|
* m = t1 = t1 + t2 */
|
|
|
|
if (crypto_bignum_sqrmod(u, prime, u2) < 0 ||
|
|
|
|
crypto_bignum_mulmod(z, u2, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_sqrmod(t1, prime, t2) < 0 ||
|
|
|
|
crypto_bignum_addmod(t1, t2, prime, t1) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: m", t1, prime_len);
|
|
|
|
|
|
|
|
/* l = CEQ(m, 0)
|
|
|
|
* t = CSEL(l, 0, inverse(m); where inverse(x) is calculated as
|
|
|
|
* x^(p-2) modulo p which will handle m == 0 case correctly */
|
|
|
|
/* TODO: Make sure crypto_bignum_is_zero() is constant time */
|
|
|
|
m_is_zero = const_time_eq(crypto_bignum_is_zero(t1), 1);
|
|
|
|
/* t = m^(p-2) modulo p */
|
|
|
|
if (crypto_bignum_sub(prime, two, t2) < 0 ||
|
|
|
|
crypto_bignum_exptmod(t1, t2, prime, t) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: t", t, prime_len);
|
|
|
|
|
|
|
|
/* b / (z * a) */
|
|
|
|
if (crypto_bignum_mulmod(z, a, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_inverse(t1, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_mulmod(b, t1, prime, x1a) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: x1a = b / (z * a)", x1a, prime_len);
|
|
|
|
|
|
|
|
/* (-b/a) * (1 + t) */
|
|
|
|
if (crypto_bignum_sub(prime, b, t1) < 0 ||
|
|
|
|
crypto_bignum_inverse(a, prime, t2) < 0 ||
|
|
|
|
crypto_bignum_mulmod(t1, t2, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_addmod(one, t, prime, t2) < 0 ||
|
|
|
|
crypto_bignum_mulmod(t1, t2, prime, x1b) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: x1b = (-b/a) * (1 + t)", x1b, prime_len);
|
|
|
|
|
|
|
|
/* x1 = CSEL(CEQ(m, 0), x1a, x1b) */
|
|
|
|
if (crypto_bignum_to_bin(x1a, bin1, sizeof(bin1), prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(x1b, bin2, sizeof(bin2), prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
const_time_select_bin(m_is_zero, bin1, bin2, prime_len, bin);
|
|
|
|
x1 = crypto_bignum_init_set(bin, prime_len);
|
|
|
|
debug_print_bignum("SSWU: x1 = CSEL(l, x1a, x1b)", x1, prime_len);
|
|
|
|
|
|
|
|
/* gx1 = x1^3 + a * x1 + b */
|
|
|
|
if (crypto_bignum_exptmod(x1, three, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_mulmod(a, x1, prime, t2) < 0 ||
|
|
|
|
crypto_bignum_addmod(t1, t2, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_addmod(t1, b, prime, gx1) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: gx1 = x1^3 + a * x1 + b", gx1, prime_len);
|
|
|
|
|
|
|
|
/* x2 = z * u^2 * x1 */
|
|
|
|
if (crypto_bignum_mulmod(z, u2, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_mulmod(t1, x1, prime, x2) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: x2 = z * u^2 * x1", x2, prime_len);
|
|
|
|
|
|
|
|
/* gx2 = x2^3 + a * x2 + b */
|
|
|
|
if (crypto_bignum_exptmod(x2, three, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_mulmod(a, x2, prime, t2) < 0 ||
|
|
|
|
crypto_bignum_addmod(t1, t2, prime, t1) < 0 ||
|
|
|
|
crypto_bignum_addmod(t1, b, prime, gx2) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: gx2 = x2^3 + a * x2 + b", gx2, prime_len);
|
|
|
|
|
|
|
|
/* l = gx1 is a quadratic residue modulo p
|
|
|
|
* --> gx1^((p-1)/2) modulo p is zero or one */
|
|
|
|
if (crypto_bignum_sub(prime, one, t1) < 0 ||
|
|
|
|
crypto_bignum_rshift(t1, 1, t1) < 0 ||
|
|
|
|
crypto_bignum_exptmod(gx1, t1, prime, t1) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: gx1^((p-1)/2) modulo p", t1, prime_len);
|
|
|
|
is_qr = const_time_eq(crypto_bignum_is_zero(t1) |
|
|
|
|
crypto_bignum_is_one(t1), 1);
|
|
|
|
|
|
|
|
/* v = CSEL(l, gx1, gx2) */
|
|
|
|
if (crypto_bignum_to_bin(gx1, bin1, sizeof(bin1), prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(gx2, bin2, sizeof(bin2), prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
const_time_select_bin(is_qr, bin1, bin2, prime_len, bin);
|
|
|
|
v = crypto_bignum_init_set(bin, prime_len);
|
|
|
|
debug_print_bignum("SSWU: v = CSEL(l, gx1, gx2)", v, prime_len);
|
|
|
|
|
|
|
|
/* x = CSEL(l, x1, x2) */
|
|
|
|
if (crypto_bignum_to_bin(x1, bin1, sizeof(bin1), prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(x2, bin2, sizeof(bin2), prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
const_time_select_bin(is_qr, bin1, bin2, prime_len, x_y);
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SSWU: x = CSEL(l, x1, x2)", x_y, prime_len);
|
|
|
|
|
2019-10-25 13:32:05 +02:00
|
|
|
/* y = sqrt(v)
|
|
|
|
* For prime p such that p = 3 mod 4 --> v^((p+1)/4) */
|
2019-08-27 15:33:15 +02:00
|
|
|
if (crypto_bignum_to_bin(prime, bin1, sizeof(bin1), prime_len) < 0)
|
|
|
|
goto fail;
|
2019-10-25 13:32:05 +02:00
|
|
|
if ((bin1[prime_len - 1] & 0x03) != 3) {
|
2019-08-27 15:33:15 +02:00
|
|
|
wpa_printf(MSG_DEBUG, "SSWU: prime does not have p = 3 mod 4");
|
2019-10-25 13:32:05 +02:00
|
|
|
goto fail;
|
2019-08-27 15:33:15 +02:00
|
|
|
}
|
2019-10-25 13:32:05 +02:00
|
|
|
y = crypto_bignum_init();
|
|
|
|
if (!y ||
|
|
|
|
crypto_bignum_add(prime, one, t1) < 0 ||
|
|
|
|
crypto_bignum_rshift(t1, 2, t1) < 0 ||
|
|
|
|
crypto_bignum_exptmod(v, t1, prime, y) < 0)
|
|
|
|
goto fail;
|
2019-08-27 15:33:15 +02:00
|
|
|
debug_print_bignum("SSWU: y = sqrt(v)", y, prime_len);
|
|
|
|
|
|
|
|
/* l = CEQ(LSB(u), LSB(y)) */
|
|
|
|
if (crypto_bignum_to_bin(u, bin1, sizeof(bin1), prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(y, bin2, sizeof(bin2), prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
is_eq = const_time_eq(bin1[prime_len - 1] & 0x01,
|
|
|
|
bin2[prime_len - 1] & 0x01);
|
|
|
|
|
|
|
|
/* P = CSEL(l, (x,y), (x, p-y)) */
|
|
|
|
if (crypto_bignum_sub(prime, y, t1) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SSWU: p - y", t1, prime_len);
|
|
|
|
if (crypto_bignum_to_bin(y, bin1, sizeof(bin1), prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(t1, bin2, sizeof(bin2), prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
const_time_select_bin(is_eq, bin1, bin2, prime_len, &x_y[prime_len]);
|
|
|
|
|
|
|
|
/* output P */
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SSWU: P.x", x_y, prime_len);
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SSWU: P.y", &x_y[prime_len], prime_len);
|
|
|
|
p = crypto_ec_point_from_bin(ec, x_y);
|
|
|
|
|
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(u2, 1);
|
|
|
|
crypto_bignum_deinit(t1, 1);
|
|
|
|
crypto_bignum_deinit(t2, 1);
|
|
|
|
crypto_bignum_deinit(z, 0);
|
|
|
|
crypto_bignum_deinit(t, 1);
|
|
|
|
crypto_bignum_deinit(x1a, 1);
|
|
|
|
crypto_bignum_deinit(x1b, 1);
|
|
|
|
crypto_bignum_deinit(x1, 1);
|
|
|
|
crypto_bignum_deinit(x2, 1);
|
|
|
|
crypto_bignum_deinit(gx1, 1);
|
|
|
|
crypto_bignum_deinit(gx2, 1);
|
|
|
|
crypto_bignum_deinit(y, 1);
|
|
|
|
crypto_bignum_deinit(v, 1);
|
|
|
|
crypto_bignum_deinit(zero, 0);
|
|
|
|
crypto_bignum_deinit(one, 0);
|
|
|
|
crypto_bignum_deinit(two, 0);
|
|
|
|
crypto_bignum_deinit(three, 0);
|
|
|
|
forced_memzero(bin, sizeof(bin));
|
|
|
|
forced_memzero(bin1, sizeof(bin1));
|
|
|
|
forced_memzero(bin2, sizeof(bin2));
|
|
|
|
forced_memzero(x_y, sizeof(x_y));
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sae_pwd_seed(size_t hash_len, const u8 *ssid, size_t ssid_len,
|
|
|
|
const u8 *password, size_t password_len,
|
|
|
|
const char *identifier, u8 *pwd_seed)
|
|
|
|
{
|
|
|
|
const u8 *addr[2];
|
|
|
|
size_t len[2];
|
|
|
|
size_t num_elem;
|
|
|
|
|
|
|
|
/* pwd-seed = HKDF-Extract(ssid, password [ || identifier ]) */
|
|
|
|
addr[0] = password;
|
|
|
|
len[0] = password_len;
|
|
|
|
num_elem = 1;
|
|
|
|
wpa_hexdump_ascii(MSG_DEBUG, "SAE: SSID", ssid, ssid_len);
|
|
|
|
wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
|
|
|
password, password_len);
|
|
|
|
if (identifier) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: password identifier: %s",
|
|
|
|
identifier);
|
|
|
|
addr[num_elem] = (const u8 *) identifier;
|
|
|
|
len[num_elem] = os_strlen(identifier);
|
|
|
|
num_elem++;
|
|
|
|
}
|
|
|
|
if (hkdf_extract(hash_len, ssid, ssid_len, num_elem, addr, len,
|
|
|
|
pwd_seed) < 0)
|
|
|
|
return -1;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, hash_len);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t sae_ecc_prime_len_2_hash_len(size_t prime_len)
|
|
|
|
{
|
|
|
|
if (prime_len <= 256 / 8)
|
|
|
|
return 32;
|
|
|
|
if (prime_len <= 384 / 8)
|
|
|
|
return 48;
|
|
|
|
return 64;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct crypto_ec_point *
|
|
|
|
sae_derive_pt_ecc(struct crypto_ec *ec, int group,
|
|
|
|
const u8 *ssid, size_t ssid_len,
|
|
|
|
const u8 *password, size_t password_len,
|
|
|
|
const char *identifier)
|
|
|
|
{
|
|
|
|
u8 pwd_seed[64];
|
|
|
|
u8 pwd_value[SAE_MAX_ECC_PRIME_LEN * 2];
|
|
|
|
size_t pwd_value_len, hash_len, prime_len;
|
|
|
|
const struct crypto_bignum *prime;
|
|
|
|
struct crypto_bignum *bn = NULL;
|
|
|
|
struct crypto_ec_point *p1 = NULL, *p2 = NULL, *pt = NULL;
|
|
|
|
|
|
|
|
prime = crypto_ec_get_prime(ec);
|
|
|
|
prime_len = crypto_ec_prime_len(ec);
|
|
|
|
if (prime_len > SAE_MAX_ECC_PRIME_LEN)
|
|
|
|
goto fail;
|
|
|
|
hash_len = sae_ecc_prime_len_2_hash_len(prime_len);
|
|
|
|
|
|
|
|
/* len = olen(p) + ceil(olen(p)/2) */
|
|
|
|
pwd_value_len = prime_len + (prime_len + 1) / 2;
|
|
|
|
|
|
|
|
if (sae_pwd_seed(hash_len, ssid, ssid_len, password, password_len,
|
|
|
|
identifier, pwd_seed) < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* pwd-value = HKDF-Expand(pwd-seed, "SAE Hash to Element u1 P1", len)
|
|
|
|
*/
|
|
|
|
if (hkdf_expand(hash_len, pwd_seed, hash_len,
|
|
|
|
"SAE Hash to Element u1 P1", pwd_value, pwd_value_len) <
|
|
|
|
0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value (u1 P1)",
|
|
|
|
pwd_value, pwd_value_len);
|
|
|
|
|
|
|
|
/* u1 = pwd-value modulo p */
|
|
|
|
bn = crypto_bignum_init_set(pwd_value, pwd_value_len);
|
|
|
|
if (!bn || crypto_bignum_mod(bn, prime, bn) < 0 ||
|
|
|
|
crypto_bignum_to_bin(bn, pwd_value, sizeof(pwd_value),
|
|
|
|
prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: u1", pwd_value, prime_len);
|
|
|
|
|
|
|
|
/* P1 = SSWU(u1) */
|
|
|
|
p1 = sswu(ec, group, bn);
|
|
|
|
if (!p1)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* pwd-value = HKDF-Expand(pwd-seed, "SAE Hash to Element u2 P2", len)
|
|
|
|
*/
|
|
|
|
if (hkdf_expand(hash_len, pwd_seed, hash_len,
|
|
|
|
"SAE Hash to Element u2 P2", pwd_value,
|
|
|
|
pwd_value_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value (u2 P2)",
|
|
|
|
pwd_value, pwd_value_len);
|
|
|
|
|
|
|
|
/* u2 = pwd-value modulo p */
|
|
|
|
crypto_bignum_deinit(bn, 1);
|
|
|
|
bn = crypto_bignum_init_set(pwd_value, pwd_value_len);
|
|
|
|
if (!bn || crypto_bignum_mod(bn, prime, bn) < 0 ||
|
|
|
|
crypto_bignum_to_bin(bn, pwd_value, sizeof(pwd_value),
|
|
|
|
prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: u2", pwd_value, prime_len);
|
|
|
|
|
|
|
|
/* P2 = SSWU(u2) */
|
|
|
|
p2 = sswu(ec, group, bn);
|
|
|
|
if (!p2)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* PT = elem-op(P1, P2) */
|
|
|
|
pt = crypto_ec_point_init(ec);
|
|
|
|
if (!pt)
|
|
|
|
goto fail;
|
|
|
|
if (crypto_ec_point_add(ec, p1, p2, pt) < 0) {
|
|
|
|
crypto_ec_point_deinit(pt, 1);
|
|
|
|
pt = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
forced_memzero(pwd_seed, sizeof(pwd_seed));
|
|
|
|
forced_memzero(pwd_value, sizeof(pwd_value));
|
|
|
|
crypto_bignum_deinit(bn, 1);
|
|
|
|
crypto_ec_point_deinit(p1, 1);
|
|
|
|
crypto_ec_point_deinit(p2, 1);
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t sae_ffc_prime_len_2_hash_len(size_t prime_len)
|
|
|
|
{
|
|
|
|
if (prime_len <= 2048 / 8)
|
|
|
|
return 32;
|
|
|
|
if (prime_len <= 3072 / 8)
|
|
|
|
return 48;
|
|
|
|
return 64;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct crypto_bignum *
|
|
|
|
sae_derive_pt_ffc(const struct dh_group *dh, int group,
|
|
|
|
const u8 *ssid, size_t ssid_len,
|
|
|
|
const u8 *password, size_t password_len,
|
|
|
|
const char *identifier)
|
|
|
|
{
|
|
|
|
size_t hash_len, prime_len, pwd_value_len;
|
|
|
|
struct crypto_bignum *prime, *order;
|
|
|
|
struct crypto_bignum *one = NULL, *two = NULL, *bn = NULL, *tmp = NULL,
|
|
|
|
*pt = NULL;
|
|
|
|
u8 pwd_seed[64];
|
|
|
|
u8 pwd_value[SAE_MAX_PRIME_LEN + SAE_MAX_PRIME_LEN / 2];
|
|
|
|
|
|
|
|
prime = crypto_bignum_init_set(dh->prime, dh->prime_len);
|
|
|
|
order = crypto_bignum_init_set(dh->order, dh->order_len);
|
|
|
|
if (!prime || !order)
|
|
|
|
goto fail;
|
|
|
|
prime_len = dh->prime_len;
|
|
|
|
if (prime_len > SAE_MAX_PRIME_LEN)
|
|
|
|
goto fail;
|
|
|
|
hash_len = sae_ffc_prime_len_2_hash_len(prime_len);
|
|
|
|
|
|
|
|
/* len = olen(p) + ceil(olen(p)/2) */
|
|
|
|
pwd_value_len = prime_len + (prime_len + 1) / 2;
|
|
|
|
if (pwd_value_len > sizeof(pwd_value))
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (sae_pwd_seed(hash_len, ssid, ssid_len, password, password_len,
|
|
|
|
identifier, pwd_seed) < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* pwd-value = HKDF-Expand(pwd-seed, "SAE Hash to Element", len) */
|
|
|
|
if (hkdf_expand(hash_len, pwd_seed, hash_len,
|
|
|
|
"SAE Hash to Element", pwd_value, pwd_value_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value",
|
|
|
|
pwd_value, pwd_value_len);
|
|
|
|
|
|
|
|
/* pwd-value = (pwd-value modulo (p-2)) + 2 */
|
|
|
|
bn = crypto_bignum_init_set(pwd_value, pwd_value_len);
|
|
|
|
one = crypto_bignum_init_uint(1);
|
|
|
|
two = crypto_bignum_init_uint(2);
|
|
|
|
tmp = crypto_bignum_init();
|
|
|
|
if (!bn || !one || !two || !tmp ||
|
|
|
|
crypto_bignum_sub(prime, two, tmp) < 0 ||
|
|
|
|
crypto_bignum_mod(bn, tmp, bn) < 0 ||
|
|
|
|
crypto_bignum_add(bn, two, bn) < 0 ||
|
|
|
|
crypto_bignum_to_bin(bn, pwd_value, sizeof(pwd_value),
|
|
|
|
prime_len) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value(reduced)",
|
|
|
|
pwd_value, prime_len);
|
|
|
|
|
|
|
|
/* PT = pwd-value^((p-1)/q) modulo p */
|
|
|
|
pt = crypto_bignum_init();
|
|
|
|
if (!pt ||
|
|
|
|
crypto_bignum_sub(prime, one, tmp) < 0 ||
|
|
|
|
crypto_bignum_div(tmp, order, tmp) < 0 ||
|
|
|
|
crypto_bignum_exptmod(bn, tmp, prime, pt) < 0) {
|
|
|
|
crypto_bignum_deinit(pt, 1);
|
|
|
|
pt = NULL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
debug_print_bignum("SAE: PT", pt, prime_len);
|
|
|
|
|
|
|
|
fail:
|
|
|
|
forced_memzero(pwd_seed, sizeof(pwd_seed));
|
|
|
|
forced_memzero(pwd_value, sizeof(pwd_value));
|
|
|
|
crypto_bignum_deinit(bn, 1);
|
|
|
|
crypto_bignum_deinit(tmp, 1);
|
|
|
|
crypto_bignum_deinit(one, 0);
|
|
|
|
crypto_bignum_deinit(two, 0);
|
|
|
|
crypto_bignum_deinit(prime, 0);
|
|
|
|
crypto_bignum_deinit(order, 0);
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct sae_pt *
|
|
|
|
sae_derive_pt_group(int group, const u8 *ssid, size_t ssid_len,
|
|
|
|
const u8 *password, size_t password_len,
|
|
|
|
const char *identifier)
|
|
|
|
{
|
|
|
|
struct sae_pt *pt;
|
|
|
|
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Derive PT - group %d", group);
|
|
|
|
|
|
|
|
pt = os_zalloc(sizeof(*pt));
|
|
|
|
if (!pt)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
pt->group = group;
|
|
|
|
pt->ec = crypto_ec_init(group);
|
|
|
|
if (pt->ec) {
|
|
|
|
pt->ecc_pt = sae_derive_pt_ecc(pt->ec, group, ssid, ssid_len,
|
|
|
|
password, password_len,
|
|
|
|
identifier);
|
|
|
|
if (!pt->ecc_pt) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to derive PT");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
pt->dh = dh_groups_get(group);
|
|
|
|
if (!pt->dh) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Unsupported group %d", group);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
pt->ffc_pt = sae_derive_pt_ffc(pt->dh, group, ssid, ssid_len,
|
|
|
|
password, password_len, identifier);
|
|
|
|
if (!pt->ffc_pt) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to derive PT");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pt;
|
|
|
|
fail:
|
|
|
|
sae_deinit_pt(pt);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct sae_pt * sae_derive_pt(int *groups, const u8 *ssid, size_t ssid_len,
|
|
|
|
const u8 *password, size_t password_len,
|
|
|
|
const char *identifier)
|
|
|
|
{
|
|
|
|
struct sae_pt *pt = NULL, *last = NULL, *tmp;
|
|
|
|
int default_groups[] = { 19, 0 };
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!groups)
|
|
|
|
groups = default_groups;
|
|
|
|
for (i = 0; groups[i] > 0; i++) {
|
|
|
|
tmp = sae_derive_pt_group(groups[i], ssid, ssid_len, password,
|
|
|
|
password_len, identifier);
|
|
|
|
if (!tmp)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (last)
|
|
|
|
last->next = tmp;
|
|
|
|
else
|
|
|
|
pt = tmp;
|
|
|
|
last = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void sae_max_min_addr(const u8 *addr[], size_t len[],
|
|
|
|
const u8 *addr1, const u8 *addr2)
|
|
|
|
{
|
|
|
|
len[0] = ETH_ALEN;
|
|
|
|
len[1] = ETH_ALEN;
|
|
|
|
if (os_memcmp(addr1, addr2, ETH_ALEN) > 0) {
|
|
|
|
addr[0] = addr1;
|
|
|
|
addr[1] = addr2;
|
|
|
|
} else {
|
|
|
|
addr[0] = addr2;
|
|
|
|
addr[1] = addr1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct crypto_ec_point *
|
|
|
|
sae_derive_pwe_from_pt_ecc(const struct sae_pt *pt,
|
|
|
|
const u8 *addr1, const u8 *addr2)
|
|
|
|
{
|
|
|
|
u8 bin[SAE_MAX_ECC_PRIME_LEN * 2];
|
|
|
|
size_t prime_len;
|
|
|
|
const u8 *addr[2];
|
|
|
|
size_t len[2];
|
|
|
|
u8 salt[64], hash[64];
|
|
|
|
size_t hash_len;
|
|
|
|
const struct crypto_bignum *order;
|
|
|
|
struct crypto_bignum *tmp = NULL, *val = NULL, *one = NULL;
|
|
|
|
struct crypto_ec_point *pwe = NULL;
|
|
|
|
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Derive PWE from PT");
|
|
|
|
prime_len = crypto_ec_prime_len(pt->ec);
|
|
|
|
if (crypto_ec_point_to_bin(pt->ec, pt->ecc_pt,
|
|
|
|
bin, bin + prime_len) < 0)
|
|
|
|
return NULL;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: PT.x", bin, prime_len);
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: PT.y", bin + prime_len, prime_len);
|
|
|
|
|
|
|
|
sae_max_min_addr(addr, len, addr1, addr2);
|
|
|
|
|
|
|
|
/* val = H(0^n,
|
|
|
|
* MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC)) */
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: val = H(0^n, MAX(addrs) || MIN(addrs))");
|
|
|
|
hash_len = sae_ecc_prime_len_2_hash_len(prime_len);
|
|
|
|
os_memset(salt, 0, hash_len);
|
|
|
|
if (hkdf_extract(hash_len, salt, hash_len, 2, addr, len, hash) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: val", hash, hash_len);
|
|
|
|
|
|
|
|
/* val = val modulo (q - 1) + 1 */
|
|
|
|
order = crypto_ec_get_order(pt->ec);
|
|
|
|
tmp = crypto_bignum_init();
|
|
|
|
val = crypto_bignum_init_set(hash, hash_len);
|
|
|
|
one = crypto_bignum_init_uint(1);
|
|
|
|
if (!tmp || !val || !one ||
|
|
|
|
crypto_bignum_sub(order, one, tmp) < 0 ||
|
|
|
|
crypto_bignum_mod(val, tmp, val) < 0 ||
|
|
|
|
crypto_bignum_add(val, one, val) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SAE: val(reduced to 1..q-1)", val, prime_len);
|
|
|
|
|
|
|
|
/* PWE = scalar-op(val, PT) */
|
|
|
|
pwe = crypto_ec_point_init(pt->ec);
|
|
|
|
if (!pwe ||
|
|
|
|
crypto_ec_point_mul(pt->ec, pt->ecc_pt, val, pwe) < 0 ||
|
|
|
|
crypto_ec_point_to_bin(pt->ec, pwe, bin, bin + prime_len) < 0) {
|
|
|
|
crypto_ec_point_deinit(pwe, 1);
|
|
|
|
pwe = NULL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: PWE.x", bin, prime_len);
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: PWE.y", bin + prime_len, prime_len);
|
|
|
|
|
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(tmp, 1);
|
|
|
|
crypto_bignum_deinit(val, 1);
|
|
|
|
crypto_bignum_deinit(one, 0);
|
|
|
|
return pwe;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct crypto_bignum *
|
|
|
|
sae_derive_pwe_from_pt_ffc(const struct sae_pt *pt,
|
|
|
|
const u8 *addr1, const u8 *addr2)
|
|
|
|
{
|
|
|
|
size_t prime_len;
|
|
|
|
const u8 *addr[2];
|
|
|
|
size_t len[2];
|
|
|
|
u8 salt[64], hash[64];
|
|
|
|
size_t hash_len;
|
|
|
|
struct crypto_bignum *tmp = NULL, *val = NULL, *one = NULL;
|
|
|
|
struct crypto_bignum *pwe = NULL, *order = NULL, *prime = NULL;
|
|
|
|
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Derive PWE from PT");
|
|
|
|
prime = crypto_bignum_init_set(pt->dh->prime, pt->dh->prime_len);
|
|
|
|
order = crypto_bignum_init_set(pt->dh->order, pt->dh->order_len);
|
|
|
|
if (!prime || !order)
|
|
|
|
goto fail;
|
|
|
|
prime_len = pt->dh->prime_len;
|
|
|
|
|
|
|
|
sae_max_min_addr(addr, len, addr1, addr2);
|
|
|
|
|
|
|
|
/* val = H(0^n,
|
|
|
|
* MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC)) */
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: val = H(0^n, MAX(addrs) || MIN(addrs))");
|
|
|
|
hash_len = sae_ffc_prime_len_2_hash_len(prime_len);
|
|
|
|
os_memset(salt, 0, hash_len);
|
|
|
|
if (hkdf_extract(hash_len, salt, hash_len, 2, addr, len, hash) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: val", hash, hash_len);
|
|
|
|
|
|
|
|
/* val = val modulo (q - 1) + 1 */
|
|
|
|
tmp = crypto_bignum_init();
|
|
|
|
val = crypto_bignum_init_set(hash, hash_len);
|
|
|
|
one = crypto_bignum_init_uint(1);
|
|
|
|
if (!tmp || !val || !one ||
|
|
|
|
crypto_bignum_sub(order, one, tmp) < 0 ||
|
|
|
|
crypto_bignum_mod(val, tmp, val) < 0 ||
|
|
|
|
crypto_bignum_add(val, one, val) < 0)
|
|
|
|
goto fail;
|
|
|
|
debug_print_bignum("SAE: val(reduced to 1..q-1)", val, prime_len);
|
|
|
|
|
|
|
|
/* PWE = scalar-op(val, PT) */
|
|
|
|
pwe = crypto_bignum_init();
|
|
|
|
if (!pwe || crypto_bignum_exptmod(pt->ffc_pt, val, prime, pwe) < 0) {
|
|
|
|
crypto_bignum_deinit(pwe, 1);
|
|
|
|
pwe = NULL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
debug_print_bignum("SAE: PWE", pwe, prime_len);
|
|
|
|
|
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(tmp, 1);
|
|
|
|
crypto_bignum_deinit(val, 1);
|
|
|
|
crypto_bignum_deinit(one, 0);
|
|
|
|
crypto_bignum_deinit(prime, 0);
|
|
|
|
crypto_bignum_deinit(order, 0);
|
|
|
|
return pwe;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void sae_deinit_pt(struct sae_pt *pt)
|
|
|
|
{
|
|
|
|
struct sae_pt *prev;
|
|
|
|
|
|
|
|
while (pt) {
|
|
|
|
crypto_ec_point_deinit(pt->ecc_pt, 1);
|
|
|
|
crypto_bignum_deinit(pt->ffc_pt, 1);
|
|
|
|
crypto_ec_deinit(pt->ec);
|
|
|
|
prev = pt;
|
|
|
|
pt = pt->next;
|
|
|
|
os_free(prev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 17:47:52 +01:00
|
|
|
static int sae_derive_commit_element_ecc(struct sae_data *sae,
|
|
|
|
struct crypto_bignum *mask)
|
2013-01-06 17:30:11 +01:00
|
|
|
{
|
|
|
|
/* COMMIT-ELEMENT = inverse(scalar-op(mask, PWE)) */
|
2013-01-06 18:26:27 +01:00
|
|
|
if (!sae->tmp->own_commit_element_ecc) {
|
|
|
|
sae->tmp->own_commit_element_ecc =
|
|
|
|
crypto_ec_point_init(sae->tmp->ec);
|
|
|
|
if (!sae->tmp->own_commit_element_ecc)
|
2013-01-06 17:47:52 +01:00
|
|
|
return -1;
|
2013-01-06 17:30:11 +01:00
|
|
|
}
|
2013-01-06 17:47:52 +01:00
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (crypto_ec_point_mul(sae->tmp->ec, sae->tmp->pwe_ecc, mask,
|
|
|
|
sae->tmp->own_commit_element_ecc) < 0 ||
|
|
|
|
crypto_ec_point_invert(sae->tmp->ec,
|
|
|
|
sae->tmp->own_commit_element_ecc) < 0) {
|
2013-01-06 17:30:11 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not compute commit-element");
|
2013-01-06 17:47:52 +01:00
|
|
|
return -1;
|
2013-01-06 17:30:11 +01:00
|
|
|
}
|
|
|
|
|
2013-01-06 17:47:52 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sae_derive_commit_element_ffc(struct sae_data *sae,
|
|
|
|
struct crypto_bignum *mask)
|
|
|
|
{
|
|
|
|
/* COMMIT-ELEMENT = inverse(scalar-op(mask, PWE)) */
|
2013-01-06 18:26:27 +01:00
|
|
|
if (!sae->tmp->own_commit_element_ffc) {
|
|
|
|
sae->tmp->own_commit_element_ffc = crypto_bignum_init();
|
|
|
|
if (!sae->tmp->own_commit_element_ffc)
|
2013-01-06 17:47:52 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (crypto_bignum_exptmod(sae->tmp->pwe_ffc, mask, sae->tmp->prime,
|
|
|
|
sae->tmp->own_commit_element_ffc) < 0 ||
|
|
|
|
crypto_bignum_inverse(sae->tmp->own_commit_element_ffc,
|
|
|
|
sae->tmp->prime,
|
|
|
|
sae->tmp->own_commit_element_ffc) < 0) {
|
2013-01-06 17:47:52 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not compute commit-element");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2013-01-06 17:30:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 17:47:52 +01:00
|
|
|
static int sae_derive_commit(struct sae_data *sae)
|
2013-01-05 20:22:00 +01:00
|
|
|
{
|
2013-01-06 17:12:58 +01:00
|
|
|
struct crypto_bignum *mask;
|
2019-04-26 16:33:44 +02:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
mask = crypto_bignum_init();
|
|
|
|
if (!sae->tmp->sae_rand)
|
|
|
|
sae->tmp->sae_rand = crypto_bignum_init();
|
|
|
|
if (!sae->tmp->own_commit_scalar)
|
|
|
|
sae->tmp->own_commit_scalar = crypto_bignum_init();
|
|
|
|
ret = !mask || !sae->tmp->sae_rand || !sae->tmp->own_commit_scalar ||
|
|
|
|
dragonfly_generate_scalar(sae->tmp->order, sae->tmp->sae_rand,
|
|
|
|
mask,
|
|
|
|
sae->tmp->own_commit_scalar) < 0 ||
|
|
|
|
(sae->tmp->ec &&
|
|
|
|
sae_derive_commit_element_ecc(sae, mask) < 0) ||
|
|
|
|
(sae->tmp->dh &&
|
|
|
|
sae_derive_commit_element_ffc(sae, mask) < 0);
|
2013-01-05 20:22:00 +01:00
|
|
|
crypto_bignum_deinit(mask, 1);
|
2019-04-26 16:33:44 +02:00
|
|
|
return ret ? -1 : 0;
|
2013-01-05 20:22:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int sae_prepare_commit(const u8 *addr1, const u8 *addr2,
|
|
|
|
const u8 *password, size_t password_len,
|
2018-05-19 16:28:01 +02:00
|
|
|
const char *identifier, struct sae_data *sae)
|
2013-01-05 20:22:00 +01:00
|
|
|
{
|
2015-06-21 23:38:02 +02:00
|
|
|
if (sae->tmp == NULL ||
|
|
|
|
(sae->tmp->ec && sae_derive_pwe_ecc(sae, addr1, addr2, password,
|
2018-05-19 16:28:01 +02:00
|
|
|
password_len,
|
|
|
|
identifier) < 0) ||
|
2015-06-21 23:38:02 +02:00
|
|
|
(sae->tmp->dh && sae_derive_pwe_ffc(sae, addr1, addr2, password,
|
2018-05-19 16:28:01 +02:00
|
|
|
password_len,
|
2019-08-27 15:33:15 +02:00
|
|
|
identifier) < 0))
|
2013-01-06 17:47:52 +01:00
|
|
|
return -1;
|
2019-08-27 15:33:15 +02:00
|
|
|
|
|
|
|
sae->tmp->h2e = 0;
|
|
|
|
return sae_derive_commit(sae);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int sae_prepare_commit_pt(struct sae_data *sae, const struct sae_pt *pt,
|
|
|
|
const u8 *addr1, const u8 *addr2,
|
|
|
|
int *rejected_groups)
|
|
|
|
{
|
|
|
|
if (!sae->tmp)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
while (pt) {
|
|
|
|
if (pt->group == sae->group)
|
|
|
|
break;
|
|
|
|
pt = pt->next;
|
|
|
|
}
|
|
|
|
if (!pt) {
|
|
|
|
wpa_printf(MSG_INFO, "SAE: Could not find PT for group %u",
|
|
|
|
sae->group);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sae->tmp->own_addr_higher = os_memcmp(addr1, addr2, ETH_ALEN) > 0;
|
|
|
|
wpabuf_free(sae->tmp->own_rejected_groups);
|
|
|
|
sae->tmp->own_rejected_groups = NULL;
|
|
|
|
if (rejected_groups) {
|
|
|
|
int count, i;
|
|
|
|
struct wpabuf *groups;
|
|
|
|
|
|
|
|
count = int_array_len(rejected_groups);
|
|
|
|
groups = wpabuf_alloc(count * 2);
|
|
|
|
if (!groups)
|
|
|
|
return -1;
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
wpabuf_put_le16(groups, rejected_groups[i]);
|
|
|
|
sae->tmp->own_rejected_groups = groups;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pt->ec) {
|
|
|
|
crypto_ec_point_deinit(sae->tmp->pwe_ecc, 1);
|
|
|
|
sae->tmp->pwe_ecc = sae_derive_pwe_from_pt_ecc(pt, addr1,
|
|
|
|
addr2);
|
|
|
|
if (!sae->tmp->pwe_ecc)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pt->dh) {
|
|
|
|
crypto_bignum_deinit(sae->tmp->pwe_ffc, 1);
|
|
|
|
sae->tmp->pwe_ffc = sae_derive_pwe_from_pt_ffc(pt, addr1,
|
|
|
|
addr2);
|
|
|
|
if (!sae->tmp->pwe_ffc)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sae->tmp->h2e = 1;
|
|
|
|
return sae_derive_commit(sae);
|
2013-01-05 20:22:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 17:30:11 +01:00
|
|
|
static int sae_derive_k_ecc(struct sae_data *sae, u8 *k)
|
2012-12-30 21:16:18 +01:00
|
|
|
{
|
2013-01-06 17:12:58 +01:00
|
|
|
struct crypto_ec_point *K;
|
2012-12-30 21:16:18 +01:00
|
|
|
int ret = -1;
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
K = crypto_ec_point_init(sae->tmp->ec);
|
2013-01-06 17:12:58 +01:00
|
|
|
if (K == NULL)
|
2012-12-30 21:16:18 +01:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* K = scalar-op(rand, (elem-op(scalar-op(peer-commit-scalar, PWE),
|
|
|
|
* PEER-COMMIT-ELEMENT)))
|
|
|
|
* If K is identity element (point-at-infinity), reject
|
|
|
|
* k = F(K) (= x coordinate)
|
|
|
|
*/
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (crypto_ec_point_mul(sae->tmp->ec, sae->tmp->pwe_ecc,
|
|
|
|
sae->peer_commit_scalar, K) < 0 ||
|
|
|
|
crypto_ec_point_add(sae->tmp->ec, K,
|
|
|
|
sae->tmp->peer_commit_element_ecc, K) < 0 ||
|
|
|
|
crypto_ec_point_mul(sae->tmp->ec, K, sae->tmp->sae_rand, K) < 0 ||
|
|
|
|
crypto_ec_point_is_at_infinity(sae->tmp->ec, K) ||
|
|
|
|
crypto_ec_point_to_bin(sae->tmp->ec, K, k, NULL) < 0) {
|
2012-12-30 21:16:18 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to calculate K and k");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: k", k, sae->tmp->prime_len);
|
2012-12-30 21:16:18 +01:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
fail:
|
2012-12-31 18:41:21 +01:00
|
|
|
crypto_ec_point_deinit(K, 1);
|
2012-12-30 21:16:18 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 17:30:11 +01:00
|
|
|
static int sae_derive_k_ffc(struct sae_data *sae, u8 *k)
|
2013-01-05 20:22:00 +01:00
|
|
|
{
|
2013-01-06 17:12:58 +01:00
|
|
|
struct crypto_bignum *K;
|
2013-01-05 20:22:00 +01:00
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
K = crypto_bignum_init();
|
2013-01-06 17:12:58 +01:00
|
|
|
if (K == NULL)
|
2013-01-05 20:22:00 +01:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* K = scalar-op(rand, (elem-op(scalar-op(peer-commit-scalar, PWE),
|
|
|
|
* PEER-COMMIT-ELEMENT)))
|
|
|
|
* If K is identity element (one), reject.
|
|
|
|
* k = F(K) (= x coordinate)
|
|
|
|
*/
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (crypto_bignum_exptmod(sae->tmp->pwe_ffc, sae->peer_commit_scalar,
|
|
|
|
sae->tmp->prime, K) < 0 ||
|
|
|
|
crypto_bignum_mulmod(K, sae->tmp->peer_commit_element_ffc,
|
|
|
|
sae->tmp->prime, K) < 0 ||
|
|
|
|
crypto_bignum_exptmod(K, sae->tmp->sae_rand, sae->tmp->prime, K) < 0
|
|
|
|
||
|
2013-01-06 17:38:17 +01:00
|
|
|
crypto_bignum_is_one(K) ||
|
2013-01-06 18:26:27 +01:00
|
|
|
crypto_bignum_to_bin(K, k, SAE_MAX_PRIME_LEN, sae->tmp->prime_len) <
|
|
|
|
0) {
|
2013-01-05 20:22:00 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to calculate K and k");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: k", k, sae->tmp->prime_len);
|
2013-01-05 20:22:00 +01:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
fail:
|
|
|
|
crypto_bignum_deinit(K, 1);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
static int sae_kdf_hash(size_t hash_len, const u8 *k, const char *label,
|
|
|
|
const u8 *context, size_t context_len,
|
|
|
|
u8 *out, size_t out_len)
|
|
|
|
{
|
|
|
|
if (hash_len == 32)
|
|
|
|
return sha256_prf(k, hash_len, label,
|
|
|
|
context, context_len, out, out_len);
|
|
|
|
#ifdef CONFIG_SHA384
|
|
|
|
if (hash_len == 48)
|
|
|
|
return sha384_prf(k, hash_len, label,
|
|
|
|
context, context_len, out, out_len);
|
|
|
|
#endif /* CONFIG_SHA384 */
|
|
|
|
#ifdef CONFIG_SHA512
|
|
|
|
if (hash_len == 64)
|
|
|
|
return sha512_prf(k, hash_len, label,
|
|
|
|
context, context_len, out, out_len);
|
|
|
|
#endif /* CONFIG_SHA512 */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-31 18:41:21 +01:00
|
|
|
static int sae_derive_keys(struct sae_data *sae, const u8 *k)
|
2012-12-30 21:16:18 +01:00
|
|
|
{
|
2019-08-27 15:33:15 +02:00
|
|
|
u8 zero[SAE_MAX_HASH_LEN], val[SAE_MAX_PRIME_LEN];
|
|
|
|
const u8 *salt;
|
|
|
|
struct wpabuf *rejected_groups = NULL;
|
|
|
|
u8 keyseed[SAE_MAX_HASH_LEN];
|
|
|
|
u8 keys[SAE_MAX_HASH_LEN + SAE_PMK_LEN];
|
2013-01-06 15:57:53 +01:00
|
|
|
struct crypto_bignum *tmp;
|
2012-12-30 21:16:18 +01:00
|
|
|
int ret = -1;
|
2019-08-27 15:33:15 +02:00
|
|
|
size_t hash_len, salt_len, prime_len = sae->tmp->prime_len;
|
|
|
|
const u8 *addr[1];
|
|
|
|
size_t len[1];
|
2012-12-30 21:16:18 +01:00
|
|
|
|
2012-12-31 18:41:21 +01:00
|
|
|
tmp = crypto_bignum_init();
|
2013-01-06 15:57:53 +01:00
|
|
|
if (tmp == NULL)
|
2012-12-30 21:16:18 +01:00
|
|
|
goto fail;
|
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
/* keyseed = H(salt, k)
|
|
|
|
* KCK || PMK = KDF-Hash-Length(keyseed, "SAE KCK and PMK",
|
2012-12-30 21:16:18 +01:00
|
|
|
* (commit-scalar + peer-commit-scalar) modulo r)
|
|
|
|
* PMKID = L((commit-scalar + peer-commit-scalar) modulo r, 0, 128)
|
|
|
|
*/
|
2019-08-27 15:33:15 +02:00
|
|
|
if (!sae->tmp->h2e)
|
|
|
|
hash_len = SHA256_MAC_LEN;
|
|
|
|
else if (sae->tmp->dh)
|
|
|
|
hash_len = sae_ffc_prime_len_2_hash_len(prime_len);
|
|
|
|
else
|
|
|
|
hash_len = sae_ecc_prime_len_2_hash_len(prime_len);
|
|
|
|
if (sae->tmp->h2e && (sae->tmp->own_rejected_groups ||
|
|
|
|
sae->tmp->peer_rejected_groups)) {
|
|
|
|
struct wpabuf *own, *peer;
|
|
|
|
|
|
|
|
own = sae->tmp->own_rejected_groups;
|
|
|
|
peer = sae->tmp->peer_rejected_groups;
|
|
|
|
salt_len = 0;
|
|
|
|
if (own)
|
|
|
|
salt_len += wpabuf_len(own);
|
|
|
|
if (peer)
|
|
|
|
salt_len += wpabuf_len(peer);
|
|
|
|
rejected_groups = wpabuf_alloc(salt_len);
|
|
|
|
if (!rejected_groups)
|
|
|
|
goto fail;
|
|
|
|
if (sae->tmp->own_addr_higher) {
|
|
|
|
if (own)
|
|
|
|
wpabuf_put_buf(rejected_groups, own);
|
|
|
|
if (peer)
|
|
|
|
wpabuf_put_buf(rejected_groups, peer);
|
|
|
|
} else {
|
|
|
|
if (peer)
|
|
|
|
wpabuf_put_buf(rejected_groups, peer);
|
|
|
|
if (own)
|
|
|
|
wpabuf_put_buf(rejected_groups, own);
|
|
|
|
}
|
|
|
|
salt = wpabuf_head(rejected_groups);
|
|
|
|
salt_len = wpabuf_len(rejected_groups);
|
|
|
|
} else {
|
|
|
|
os_memset(zero, 0, hash_len);
|
|
|
|
salt = zero;
|
|
|
|
salt_len = hash_len;
|
|
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: salt for keyseed derivation",
|
|
|
|
salt, salt_len);
|
|
|
|
addr[0] = k;
|
|
|
|
len[0] = prime_len;
|
|
|
|
if (hkdf_extract(hash_len, salt, salt_len, 1, addr, len, keyseed) < 0)
|
|
|
|
goto fail;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: keyseed", keyseed, hash_len);
|
2012-12-30 21:16:18 +01:00
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
if (crypto_bignum_add(sae->tmp->own_commit_scalar,
|
|
|
|
sae->peer_commit_scalar, tmp) < 0 ||
|
|
|
|
crypto_bignum_mod(tmp, sae->tmp->order, tmp) < 0)
|
|
|
|
goto fail;
|
SAE: Fix KCK, PMK, and PMKID derivation for groups 22, 23, 24
IEEE Std 802.11-2016 is not exactly clear on the encoding of the bit
string that is needed for KCK, PMK, and PMKID derivation, but it seems
to make most sense to encode the (commit-scalar + peer-commit-scalar)
mod r part as a bit string by zero padding it from left to the length of
the order (in full octets).
The previous implementation used the length of the prime (in full
octets). This would work for KCK/PMK, but this results in deriving all
zero PMKIDs for the groups where the size of the order is smaller than
the size of the prime. This is the case for groups 22, 23, and 24.
However, those groups have been marked as being unsuitable for use with
SAE, so this fix should not really have a practical impact anymore.
Anyway, better fix it and document this clearly in the implementation
taken into account the unclarity of the standard in this area.
Signed-off-by: Jouni Malinen <j@w1.fi>
2019-08-03 16:00:39 +02:00
|
|
|
/* IEEE Std 802.11-2016 is not exactly clear on the encoding of the bit
|
|
|
|
* string that is needed for KCK, PMK, and PMKID derivation, but it
|
|
|
|
* seems to make most sense to encode the
|
|
|
|
* (commit-scalar + peer-commit-scalar) mod r part as a bit string by
|
|
|
|
* zero padding it from left to the length of the order (in full
|
|
|
|
* octets). */
|
|
|
|
crypto_bignum_to_bin(tmp, val, sizeof(val), sae->tmp->order_len);
|
2013-01-01 10:52:49 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: PMKID", val, SAE_PMKID_LEN);
|
2019-08-27 15:33:15 +02:00
|
|
|
if (sae_kdf_hash(hash_len, keyseed, "SAE KCK and PMK",
|
|
|
|
val, sae->tmp->order_len,
|
|
|
|
keys, hash_len + SAE_PMK_LEN) < 0)
|
2016-03-27 20:43:24 +02:00
|
|
|
goto fail;
|
2019-08-27 15:33:15 +02:00
|
|
|
forced_memzero(keyseed, sizeof(keyseed));
|
|
|
|
os_memcpy(sae->tmp->kck, keys, hash_len);
|
|
|
|
sae->tmp->kck_len = hash_len;
|
|
|
|
os_memcpy(sae->pmk, keys + hash_len, SAE_PMK_LEN);
|
2015-12-27 03:20:51 +01:00
|
|
|
os_memcpy(sae->pmkid, val, SAE_PMKID_LEN);
|
2019-08-27 15:33:15 +02:00
|
|
|
forced_memzero(keys, sizeof(keys));
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: KCK",
|
|
|
|
sae->tmp->kck, sae->tmp->kck_len);
|
2013-01-01 10:52:49 +01:00
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: PMK", sae->pmk, SAE_PMK_LEN);
|
2012-12-30 21:16:18 +01:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
fail:
|
2019-08-27 15:33:15 +02:00
|
|
|
wpabuf_free(rejected_groups);
|
2012-12-31 18:41:21 +01:00
|
|
|
crypto_bignum_deinit(tmp, 0);
|
2012-12-30 21:16:18 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int sae_process_commit(struct sae_data *sae)
|
|
|
|
{
|
2013-01-01 10:49:01 +01:00
|
|
|
u8 k[SAE_MAX_PRIME_LEN];
|
2014-02-12 16:46:33 +01:00
|
|
|
if (sae->tmp == NULL ||
|
|
|
|
(sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) ||
|
2013-01-06 18:26:27 +01:00
|
|
|
(sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||
|
2013-01-06 17:59:46 +01:00
|
|
|
sae_derive_keys(sae, k) < 0)
|
2012-12-30 21:16:18 +01:00
|
|
|
return -1;
|
2012-12-31 18:41:21 +01:00
|
|
|
return 0;
|
2012-12-30 21:16:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-12-31 15:58:36 +01:00
|
|
|
void sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
|
2018-05-19 16:28:01 +02:00
|
|
|
const struct wpabuf *token, const char *identifier)
|
2012-12-30 21:06:11 +01:00
|
|
|
{
|
2013-01-06 17:12:58 +01:00
|
|
|
u8 *pos;
|
2014-02-12 16:46:33 +01:00
|
|
|
|
|
|
|
if (sae->tmp == NULL)
|
|
|
|
return;
|
|
|
|
|
2013-01-01 10:29:53 +01:00
|
|
|
wpabuf_put_le16(buf, sae->group); /* Finite Cyclic Group */
|
2014-11-25 03:04:40 +01:00
|
|
|
if (token) {
|
2012-12-31 15:58:36 +01:00
|
|
|
wpabuf_put_buf(buf, token);
|
2014-11-25 03:04:40 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Anti-clogging token",
|
|
|
|
wpabuf_head(token), wpabuf_len(token));
|
|
|
|
}
|
2013-01-06 18:26:27 +01:00
|
|
|
pos = wpabuf_put(buf, sae->tmp->prime_len);
|
|
|
|
crypto_bignum_to_bin(sae->tmp->own_commit_scalar, pos,
|
|
|
|
sae->tmp->prime_len, sae->tmp->prime_len);
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-scalar",
|
|
|
|
pos, sae->tmp->prime_len);
|
|
|
|
if (sae->tmp->ec) {
|
|
|
|
pos = wpabuf_put(buf, 2 * sae->tmp->prime_len);
|
|
|
|
crypto_ec_point_to_bin(sae->tmp->ec,
|
|
|
|
sae->tmp->own_commit_element_ecc,
|
|
|
|
pos, pos + sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-element(x)",
|
2013-01-06 18:26:27 +01:00
|
|
|
pos, sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-element(y)",
|
2013-01-06 18:26:27 +01:00
|
|
|
pos + sae->tmp->prime_len, sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
} else {
|
2013-01-06 18:26:27 +01:00
|
|
|
pos = wpabuf_put(buf, sae->tmp->prime_len);
|
|
|
|
crypto_bignum_to_bin(sae->tmp->own_commit_element_ffc, pos,
|
|
|
|
sae->tmp->prime_len, sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-element",
|
2013-01-06 18:26:27 +01:00
|
|
|
pos, sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
}
|
2018-05-19 16:28:01 +02:00
|
|
|
|
|
|
|
if (identifier) {
|
|
|
|
/* Password Identifier element */
|
|
|
|
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
|
|
|
|
wpabuf_put_u8(buf, 1 + os_strlen(identifier));
|
|
|
|
wpabuf_put_u8(buf, WLAN_EID_EXT_PASSWORD_IDENTIFIER);
|
|
|
|
wpabuf_put_str(buf, identifier);
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: own Password Identifier: %s",
|
|
|
|
identifier);
|
|
|
|
}
|
2019-08-27 15:33:15 +02:00
|
|
|
|
|
|
|
if (sae->tmp->h2e && sae->tmp->own_rejected_groups) {
|
|
|
|
wpa_hexdump_buf(MSG_DEBUG, "SAE: own Rejected Groups",
|
|
|
|
sae->tmp->own_rejected_groups);
|
|
|
|
wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
|
|
|
|
wpabuf_put_u8(buf,
|
|
|
|
1 + wpabuf_len(sae->tmp->own_rejected_groups));
|
|
|
|
wpabuf_put_u8(buf, WLAN_EID_EXT_REJECTED_GROUPS);
|
|
|
|
wpabuf_put_buf(buf, sae->tmp->own_rejected_groups);
|
|
|
|
}
|
2012-12-30 21:06:11 +01:00
|
|
|
}
|
2012-12-30 21:16:18 +01:00
|
|
|
|
|
|
|
|
2014-11-25 03:04:41 +01:00
|
|
|
u16 sae_group_allowed(struct sae_data *sae, int *allowed_groups, u16 group)
|
2012-12-30 21:16:18 +01:00
|
|
|
{
|
2013-01-01 15:23:47 +01:00
|
|
|
if (allowed_groups) {
|
|
|
|
int i;
|
2013-11-02 17:07:49 +01:00
|
|
|
for (i = 0; allowed_groups[i] > 0; i++) {
|
2013-01-01 15:23:47 +01:00
|
|
|
if (allowed_groups[i] == group)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (allowed_groups[i] != group) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Proposed group %u not "
|
|
|
|
"enabled in the current configuration",
|
|
|
|
group);
|
|
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
|
|
}
|
|
|
|
}
|
2013-01-06 16:03:09 +01:00
|
|
|
|
2013-01-01 10:29:53 +01:00
|
|
|
if (sae->state == SAE_COMMITTED && group != sae->group) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Do not allow group to be changed");
|
|
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
|
|
}
|
2013-01-06 16:03:09 +01:00
|
|
|
|
2013-01-01 10:29:53 +01:00
|
|
|
if (group != sae->group && sae_set_group(sae, group) < 0) {
|
2012-12-30 21:16:18 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Unsupported Finite Cyclic Group %u",
|
2013-01-01 10:29:53 +01:00
|
|
|
group);
|
2012-12-30 21:16:18 +01:00
|
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
|
|
}
|
2013-01-06 16:03:09 +01:00
|
|
|
|
2013-12-28 12:41:02 +01:00
|
|
|
if (sae->tmp == NULL) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Group information not yet initialized");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (sae->tmp->dh && !allowed_groups) {
|
2013-01-05 20:22:00 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Do not allow FFC group %u without "
|
|
|
|
"explicit configuration enabling it", group);
|
|
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
|
|
}
|
2012-12-30 21:16:18 +01:00
|
|
|
|
2013-01-06 16:03:09 +01:00
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
static int sae_is_password_id_elem(const u8 *pos, const u8 *end)
|
|
|
|
{
|
|
|
|
return end - pos >= 3 &&
|
|
|
|
pos[0] == WLAN_EID_EXTENSION &&
|
|
|
|
pos[1] >= 1 &&
|
|
|
|
end - pos - 2 >= pos[1] &&
|
|
|
|
pos[2] == WLAN_EID_EXT_PASSWORD_IDENTIFIER;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-07 17:38:29 +02:00
|
|
|
static int sae_is_rejected_groups_elem(const u8 *pos, const u8 *end)
|
|
|
|
{
|
|
|
|
return end - pos >= 3 &&
|
|
|
|
pos[0] == WLAN_EID_EXTENSION &&
|
|
|
|
pos[1] >= 2 &&
|
|
|
|
end - pos - 2 >= pos[1] &&
|
|
|
|
pos[2] == WLAN_EID_EXT_REJECTED_GROUPS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 16:03:09 +01:00
|
|
|
static void sae_parse_commit_token(struct sae_data *sae, const u8 **pos,
|
|
|
|
const u8 *end, const u8 **token,
|
2019-09-07 17:38:29 +02:00
|
|
|
size_t *token_len, int h2e)
|
2013-01-06 16:03:09 +01:00
|
|
|
{
|
2018-05-19 16:28:01 +02:00
|
|
|
size_t scalar_elem_len, tlen;
|
|
|
|
const u8 *elem;
|
|
|
|
|
|
|
|
if (token)
|
|
|
|
*token = NULL;
|
|
|
|
if (token_len)
|
|
|
|
*token_len = 0;
|
|
|
|
|
|
|
|
scalar_elem_len = (sae->tmp->ec ? 3 : 2) * sae->tmp->prime_len;
|
|
|
|
if (scalar_elem_len >= (size_t) (end - *pos))
|
|
|
|
return; /* No extra data beyond peer scalar and element */
|
|
|
|
|
|
|
|
/* It is a bit difficult to parse this now that there is an
|
|
|
|
* optional variable length Anti-Clogging Token field and
|
|
|
|
* optional variable length Password Identifier element in the
|
|
|
|
* frame. We are sending out fixed length Anti-Clogging Token
|
|
|
|
* fields, so use that length as a requirement for the received
|
|
|
|
* token and check for the presence of possible Password
|
|
|
|
* Identifier element based on the element header information.
|
2019-09-07 17:38:29 +02:00
|
|
|
* When parsing H2E case, also consider the Rejected Groupd element
|
|
|
|
* similarly.
|
2018-05-19 16:28:01 +02:00
|
|
|
*/
|
|
|
|
tlen = end - (*pos + scalar_elem_len);
|
|
|
|
|
|
|
|
if (tlen < SHA256_MAC_LEN) {
|
|
|
|
wpa_printf(MSG_DEBUG,
|
|
|
|
"SAE: Too short optional data (%u octets) to include our Anti-Clogging Token",
|
|
|
|
(unsigned int) tlen);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
elem = *pos + scalar_elem_len;
|
|
|
|
if (sae_is_password_id_elem(elem, end)) {
|
|
|
|
/* Password Identifier element takes out all available
|
|
|
|
* extra octets, so there can be no Anti-Clogging token in
|
|
|
|
* this frame. */
|
|
|
|
return;
|
2012-12-31 15:58:36 +01:00
|
|
|
}
|
2019-09-07 17:38:29 +02:00
|
|
|
if (h2e && sae_is_rejected_groups_elem(elem, end)) {
|
|
|
|
/* Rejected Groups takes out all available extra octets, so
|
|
|
|
* there can be no Anti-Clogging token in this frame. */
|
|
|
|
return;
|
|
|
|
}
|
2018-05-19 16:28:01 +02:00
|
|
|
|
|
|
|
elem += SHA256_MAC_LEN;
|
|
|
|
if (sae_is_password_id_elem(elem, end)) {
|
|
|
|
/* Password Identifier element is included in the end, so
|
|
|
|
* remove its length from the Anti-Clogging token field. */
|
|
|
|
tlen -= 2 + elem[1];
|
2019-09-07 17:38:29 +02:00
|
|
|
elem += 2 + elem[1];
|
|
|
|
if (h2e && sae_is_rejected_groups_elem(elem, end)) {
|
|
|
|
/* Also remove Rejected Groups element from the
|
|
|
|
* Anti-Clogging token field length */
|
|
|
|
tlen -= 2 + elem[1];
|
|
|
|
}
|
|
|
|
} else if (h2e && sae_is_rejected_groups_elem(elem, end)) {
|
|
|
|
/* Rejected Groups element is included in the end, so
|
|
|
|
* remove its length from the Anti-Clogging token field. */
|
|
|
|
tlen -= 2 + elem[1];
|
2018-05-19 16:28:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Anti-Clogging Token", *pos, tlen);
|
|
|
|
if (token)
|
|
|
|
*token = *pos;
|
|
|
|
if (token_len)
|
|
|
|
*token_len = tlen;
|
|
|
|
*pos += tlen;
|
2013-01-06 16:03:09 +01:00
|
|
|
}
|
|
|
|
|
2012-12-31 15:58:36 +01:00
|
|
|
|
2013-01-06 16:03:09 +01:00
|
|
|
static u16 sae_parse_commit_scalar(struct sae_data *sae, const u8 **pos,
|
|
|
|
const u8 *end)
|
|
|
|
{
|
|
|
|
struct crypto_bignum *peer_scalar;
|
|
|
|
|
2015-10-18 17:49:56 +02:00
|
|
|
if (sae->tmp->prime_len > end - *pos) {
|
2012-12-30 21:16:18 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Not enough data for scalar");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
2012-12-31 10:20:04 +01:00
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
peer_scalar = crypto_bignum_init_set(*pos, sae->tmp->prime_len);
|
2013-01-06 15:57:53 +01:00
|
|
|
if (peer_scalar == NULL)
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
|
2012-12-31 10:20:04 +01:00
|
|
|
/*
|
|
|
|
* IEEE Std 802.11-2012, 11.3.8.6.1: If there is a protocol instance for
|
|
|
|
* the peer and it is in Authenticated state, the new Commit Message
|
|
|
|
* shall be dropped if the peer-scalar is identical to the one used in
|
|
|
|
* the existing protocol instance.
|
|
|
|
*/
|
2013-01-06 15:57:53 +01:00
|
|
|
if (sae->state == SAE_ACCEPTED && sae->peer_commit_scalar &&
|
|
|
|
crypto_bignum_cmp(sae->peer_commit_scalar, peer_scalar) == 0) {
|
2012-12-31 10:20:04 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Do not accept re-use of previous "
|
|
|
|
"peer-commit-scalar");
|
2013-01-06 15:57:53 +01:00
|
|
|
crypto_bignum_deinit(peer_scalar, 0);
|
2012-12-31 10:20:04 +01:00
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
|
2015-06-27 11:41:40 +02:00
|
|
|
/* 1 < scalar < r */
|
2013-01-06 16:34:05 +01:00
|
|
|
if (crypto_bignum_is_zero(peer_scalar) ||
|
2015-06-27 11:41:40 +02:00
|
|
|
crypto_bignum_is_one(peer_scalar) ||
|
2013-01-06 18:26:27 +01:00
|
|
|
crypto_bignum_cmp(peer_scalar, sae->tmp->order) >= 0) {
|
2013-01-06 16:34:05 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid peer scalar");
|
|
|
|
crypto_bignum_deinit(peer_scalar, 0);
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 15:57:53 +01:00
|
|
|
crypto_bignum_deinit(sae->peer_commit_scalar, 0);
|
|
|
|
sae->peer_commit_scalar = peer_scalar;
|
2013-01-06 18:26:27 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-scalar",
|
|
|
|
*pos, sae->tmp->prime_len);
|
|
|
|
*pos += sae->tmp->prime_len;
|
2013-01-06 16:03:09 +01:00
|
|
|
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2012-12-30 21:16:18 +01:00
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 **pos,
|
2013-01-06 16:34:05 +01:00
|
|
|
const u8 *end)
|
|
|
|
{
|
2013-01-06 17:12:58 +01:00
|
|
|
u8 prime[SAE_MAX_ECC_PRIME_LEN];
|
2013-01-06 16:34:05 +01:00
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
if (2 * sae->tmp->prime_len > end - *pos) {
|
2012-12-30 21:16:18 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Not enough data for "
|
|
|
|
"commit-element");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
|
|
|
sae->tmp->prime_len) < 0)
|
2013-01-06 17:12:58 +01:00
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
2013-01-06 16:34:05 +01:00
|
|
|
|
|
|
|
/* element x and y coordinates < p */
|
2018-05-19 16:28:01 +02:00
|
|
|
if (os_memcmp(*pos, prime, sae->tmp->prime_len) >= 0 ||
|
|
|
|
os_memcmp(*pos + sae->tmp->prime_len, prime,
|
2013-01-06 18:26:27 +01:00
|
|
|
sae->tmp->prime_len) >= 0) {
|
2013-01-06 16:34:05 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid coordinates in peer "
|
|
|
|
"element");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
|
2013-01-06 17:12:58 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element(x)",
|
2018-05-19 16:28:01 +02:00
|
|
|
*pos, sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element(y)",
|
2018-05-19 16:28:01 +02:00
|
|
|
*pos + sae->tmp->prime_len, sae->tmp->prime_len);
|
2013-01-06 17:12:58 +01:00
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
crypto_ec_point_deinit(sae->tmp->peer_commit_element_ecc, 0);
|
|
|
|
sae->tmp->peer_commit_element_ecc =
|
2018-05-19 16:28:01 +02:00
|
|
|
crypto_ec_point_from_bin(sae->tmp->ec, *pos);
|
2013-01-06 18:26:27 +01:00
|
|
|
if (sae->tmp->peer_commit_element_ecc == NULL)
|
2013-01-06 17:12:58 +01:00
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
|
2013-03-10 10:26:22 +01:00
|
|
|
if (!crypto_ec_point_is_on_curve(sae->tmp->ec,
|
|
|
|
sae->tmp->peer_commit_element_ecc)) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Peer element is not on curve");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
*pos += 2 * sae->tmp->prime_len;
|
|
|
|
|
2012-12-30 21:16:18 +01:00
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
}
|
2012-12-30 21:28:57 +01:00
|
|
|
|
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
static u16 sae_parse_commit_element_ffc(struct sae_data *sae, const u8 **pos,
|
2013-01-06 17:30:11 +01:00
|
|
|
const u8 *end)
|
|
|
|
{
|
2015-06-27 20:20:14 +02:00
|
|
|
struct crypto_bignum *res, *one;
|
|
|
|
const u8 one_bin[1] = { 0x01 };
|
2013-03-10 10:45:55 +01:00
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
if (sae->tmp->prime_len > end - *pos) {
|
2013-01-06 17:30:11 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Not enough data for "
|
|
|
|
"commit-element");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
2018-05-19 16:28:01 +02:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element", *pos,
|
2013-01-06 18:26:27 +01:00
|
|
|
sae->tmp->prime_len);
|
2013-01-06 17:30:11 +01:00
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
crypto_bignum_deinit(sae->tmp->peer_commit_element_ffc, 0);
|
|
|
|
sae->tmp->peer_commit_element_ffc =
|
2018-05-19 16:28:01 +02:00
|
|
|
crypto_bignum_init_set(*pos, sae->tmp->prime_len);
|
2013-01-06 18:26:27 +01:00
|
|
|
if (sae->tmp->peer_commit_element_ffc == NULL)
|
2013-01-06 17:30:11 +01:00
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
2015-06-27 20:20:14 +02:00
|
|
|
/* 1 < element < p - 1 */
|
|
|
|
res = crypto_bignum_init();
|
|
|
|
one = crypto_bignum_init_set(one_bin, sizeof(one_bin));
|
|
|
|
if (!res || !one ||
|
|
|
|
crypto_bignum_sub(sae->tmp->prime, one, res) ||
|
|
|
|
crypto_bignum_is_zero(sae->tmp->peer_commit_element_ffc) ||
|
2013-01-06 18:26:27 +01:00
|
|
|
crypto_bignum_is_one(sae->tmp->peer_commit_element_ffc) ||
|
2015-06-27 20:20:14 +02:00
|
|
|
crypto_bignum_cmp(sae->tmp->peer_commit_element_ffc, res) >= 0) {
|
|
|
|
crypto_bignum_deinit(res, 0);
|
|
|
|
crypto_bignum_deinit(one, 0);
|
2013-01-06 17:30:11 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid peer element");
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
2015-06-27 20:20:14 +02:00
|
|
|
crypto_bignum_deinit(one, 0);
|
2013-01-06 17:30:11 +01:00
|
|
|
|
2013-03-10 10:45:55 +01:00
|
|
|
/* scalar-op(r, ELEMENT) = 1 modulo p */
|
2015-06-27 20:20:14 +02:00
|
|
|
if (crypto_bignum_exptmod(sae->tmp->peer_commit_element_ffc,
|
2013-03-10 10:45:55 +01:00
|
|
|
sae->tmp->order, sae->tmp->prime, res) < 0 ||
|
|
|
|
!crypto_bignum_is_one(res)) {
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid peer element (scalar-op)");
|
|
|
|
crypto_bignum_deinit(res, 0);
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
}
|
|
|
|
crypto_bignum_deinit(res, 0);
|
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
*pos += sae->tmp->prime_len;
|
|
|
|
|
2013-01-06 17:30:11 +01:00
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
static u16 sae_parse_commit_element(struct sae_data *sae, const u8 **pos,
|
2013-01-06 16:34:05 +01:00
|
|
|
const u8 *end)
|
|
|
|
{
|
2013-01-06 18:26:27 +01:00
|
|
|
if (sae->tmp->dh)
|
2013-01-06 16:34:05 +01:00
|
|
|
return sae_parse_commit_element_ffc(sae, pos, end);
|
|
|
|
return sae_parse_commit_element_ecc(sae, pos, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-19 16:28:01 +02:00
|
|
|
static int sae_parse_password_identifier(struct sae_data *sae,
|
2019-09-07 17:38:29 +02:00
|
|
|
const u8 **pos, const u8 *end)
|
2018-05-19 16:28:01 +02:00
|
|
|
{
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Possible elements at the end of the frame",
|
2019-09-07 17:38:29 +02:00
|
|
|
*pos, end - *pos);
|
|
|
|
if (!sae_is_password_id_elem(*pos, end)) {
|
2018-05-19 16:28:01 +02:00
|
|
|
if (sae->tmp->pw_id) {
|
|
|
|
wpa_printf(MSG_DEBUG,
|
|
|
|
"SAE: No Password Identifier included, but expected one (%s)",
|
|
|
|
sae->tmp->pw_id);
|
|
|
|
return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;
|
|
|
|
}
|
|
|
|
os_free(sae->tmp->pw_id);
|
|
|
|
sae->tmp->pw_id = NULL;
|
|
|
|
return WLAN_STATUS_SUCCESS; /* No Password Identifier */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sae->tmp->pw_id &&
|
2019-09-07 17:38:29 +02:00
|
|
|
((*pos)[1] - 1 != (int) os_strlen(sae->tmp->pw_id) ||
|
|
|
|
os_memcmp(sae->tmp->pw_id, (*pos) + 3, (*pos)[1] - 1) != 0)) {
|
2018-05-19 16:28:01 +02:00
|
|
|
wpa_printf(MSG_DEBUG,
|
|
|
|
"SAE: The included Password Identifier does not match the expected one (%s)",
|
|
|
|
sae->tmp->pw_id);
|
|
|
|
return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;
|
|
|
|
}
|
|
|
|
|
|
|
|
os_free(sae->tmp->pw_id);
|
2019-09-07 17:38:29 +02:00
|
|
|
sae->tmp->pw_id = os_malloc((*pos)[1]);
|
2018-05-19 16:28:01 +02:00
|
|
|
if (!sae->tmp->pw_id)
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
2019-09-07 17:38:29 +02:00
|
|
|
os_memcpy(sae->tmp->pw_id, (*pos) + 3, (*pos)[1] - 1);
|
|
|
|
sae->tmp->pw_id[(*pos)[1] - 1] = '\0';
|
2018-05-19 16:28:01 +02:00
|
|
|
wpa_hexdump_ascii(MSG_DEBUG, "SAE: Received Password Identifier",
|
2019-09-07 17:38:29 +02:00
|
|
|
sae->tmp->pw_id, (*pos)[1] - 1);
|
|
|
|
*pos = *pos + 2 + (*pos)[1];
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sae_parse_rejected_groups(struct sae_data *sae,
|
|
|
|
const u8 *pos, const u8 *end)
|
|
|
|
{
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Possible elements at the end of the frame",
|
|
|
|
pos, end - pos);
|
|
|
|
if (!sae_is_rejected_groups_elem(pos, end))
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
wpabuf_free(sae->tmp->peer_rejected_groups);
|
|
|
|
sae->tmp->peer_rejected_groups = wpabuf_alloc(pos[1] - 1);
|
|
|
|
if (!sae->tmp->peer_rejected_groups)
|
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
wpabuf_put_data(sae->tmp->peer_rejected_groups, pos + 3, pos[1] - 1);
|
|
|
|
wpa_hexdump_buf(MSG_DEBUG, "SAE: Received Rejected Groups list",
|
|
|
|
sae->tmp->peer_rejected_groups);
|
2018-05-19 16:28:01 +02:00
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 16:03:09 +01:00
|
|
|
u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len,
|
2019-09-07 17:33:02 +02:00
|
|
|
const u8 **token, size_t *token_len, int *allowed_groups,
|
|
|
|
int h2e)
|
2013-01-06 16:03:09 +01:00
|
|
|
{
|
|
|
|
const u8 *pos = data, *end = data + len;
|
|
|
|
u16 res;
|
|
|
|
|
|
|
|
/* Check Finite Cyclic Group */
|
2015-10-18 17:49:56 +02:00
|
|
|
if (end - pos < 2)
|
2013-01-06 16:03:09 +01:00
|
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
res = sae_group_allowed(sae, allowed_groups, WPA_GET_LE16(pos));
|
|
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
|
|
return res;
|
|
|
|
pos += 2;
|
|
|
|
|
|
|
|
/* Optional Anti-Clogging Token */
|
2019-09-07 17:38:29 +02:00
|
|
|
sae_parse_commit_token(sae, &pos, end, token, token_len, h2e);
|
2013-01-06 16:03:09 +01:00
|
|
|
|
|
|
|
/* commit-scalar */
|
|
|
|
res = sae_parse_commit_scalar(sae, &pos, end);
|
|
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
/* commit-element */
|
2018-05-19 16:28:01 +02:00
|
|
|
res = sae_parse_commit_element(sae, &pos, end);
|
|
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
/* Optional Password Identifier element */
|
2019-09-07 17:38:29 +02:00
|
|
|
res = sae_parse_password_identifier(sae, &pos, end);
|
2015-06-23 21:30:15 +02:00
|
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
|
|
return res;
|
|
|
|
|
2019-09-07 17:38:29 +02:00
|
|
|
/* Conditional Rejected Groups element */
|
|
|
|
if (h2e) {
|
|
|
|
res = sae_parse_rejected_groups(sae, pos, end);
|
|
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2015-06-23 21:30:15 +02:00
|
|
|
/*
|
|
|
|
* Check whether peer-commit-scalar and PEER-COMMIT-ELEMENT are same as
|
|
|
|
* the values we sent which would be evidence of a reflection attack.
|
|
|
|
*/
|
|
|
|
if (!sae->tmp->own_commit_scalar ||
|
|
|
|
crypto_bignum_cmp(sae->tmp->own_commit_scalar,
|
|
|
|
sae->peer_commit_scalar) != 0 ||
|
|
|
|
(sae->tmp->dh &&
|
|
|
|
(!sae->tmp->own_commit_element_ffc ||
|
|
|
|
crypto_bignum_cmp(sae->tmp->own_commit_element_ffc,
|
|
|
|
sae->tmp->peer_commit_element_ffc) != 0)) ||
|
|
|
|
(sae->tmp->ec &&
|
|
|
|
(!sae->tmp->own_commit_element_ecc ||
|
|
|
|
crypto_ec_point_cmp(sae->tmp->ec,
|
|
|
|
sae->tmp->own_commit_element_ecc,
|
|
|
|
sae->tmp->peer_commit_element_ecc) != 0)))
|
|
|
|
return WLAN_STATUS_SUCCESS; /* scalars/elements are different */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a reflection attack - return special value to trigger caller
|
|
|
|
* to silently discard the frame instead of replying with a specific
|
|
|
|
* status code.
|
|
|
|
*/
|
|
|
|
return SAE_SILENTLY_DISCARD;
|
2013-01-06 16:03:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-07 17:47:48 +02:00
|
|
|
static int sae_cn_confirm(struct sae_data *sae, const u8 *sc,
|
|
|
|
const struct crypto_bignum *scalar1,
|
|
|
|
const u8 *element1, size_t element1_len,
|
|
|
|
const struct crypto_bignum *scalar2,
|
|
|
|
const u8 *element2, size_t element2_len,
|
|
|
|
u8 *confirm)
|
2012-12-30 21:28:57 +01:00
|
|
|
{
|
|
|
|
const u8 *addr[5];
|
|
|
|
size_t len[5];
|
2013-01-06 15:57:53 +01:00
|
|
|
u8 scalar_b1[SAE_MAX_PRIME_LEN], scalar_b2[SAE_MAX_PRIME_LEN];
|
2012-12-30 21:28:57 +01:00
|
|
|
|
|
|
|
/* Confirm
|
|
|
|
* CN(key, X, Y, Z, ...) =
|
|
|
|
* HMAC-SHA256(key, D2OS(X) || D2OS(Y) || D2OS(Z) | ...)
|
|
|
|
* confirm = CN(KCK, send-confirm, commit-scalar, COMMIT-ELEMENT,
|
|
|
|
* peer-commit-scalar, PEER-COMMIT-ELEMENT)
|
2013-01-06 15:57:53 +01:00
|
|
|
* verifier = CN(KCK, peer-send-confirm, peer-commit-scalar,
|
|
|
|
* PEER-COMMIT-ELEMENT, commit-scalar, COMMIT-ELEMENT)
|
2012-12-30 21:28:57 +01:00
|
|
|
*/
|
2019-09-07 17:47:48 +02:00
|
|
|
if (crypto_bignum_to_bin(scalar1, scalar_b1, sizeof(scalar_b1),
|
|
|
|
sae->tmp->prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(scalar2, scalar_b2, sizeof(scalar_b2),
|
|
|
|
sae->tmp->prime_len) < 0)
|
|
|
|
return -1;
|
2012-12-30 21:28:57 +01:00
|
|
|
addr[0] = sc;
|
|
|
|
len[0] = 2;
|
2013-01-06 15:57:53 +01:00
|
|
|
addr[1] = scalar_b1;
|
2013-01-06 18:26:27 +01:00
|
|
|
len[1] = sae->tmp->prime_len;
|
2013-01-06 15:57:53 +01:00
|
|
|
addr[2] = element1;
|
2013-01-06 17:12:58 +01:00
|
|
|
len[2] = element1_len;
|
2013-01-06 15:57:53 +01:00
|
|
|
addr[3] = scalar_b2;
|
2013-01-06 18:26:27 +01:00
|
|
|
len[3] = sae->tmp->prime_len;
|
2013-01-06 15:57:53 +01:00
|
|
|
addr[4] = element2;
|
2013-01-06 17:12:58 +01:00
|
|
|
len[4] = element2_len;
|
2019-08-27 15:33:15 +02:00
|
|
|
return hkdf_extract(sae->tmp->kck_len, sae->tmp->kck, sae->tmp->kck_len,
|
|
|
|
5, addr, len, confirm);
|
2013-01-06 15:57:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-07 17:47:48 +02:00
|
|
|
static int sae_cn_confirm_ecc(struct sae_data *sae, const u8 *sc,
|
|
|
|
const struct crypto_bignum *scalar1,
|
|
|
|
const struct crypto_ec_point *element1,
|
|
|
|
const struct crypto_bignum *scalar2,
|
|
|
|
const struct crypto_ec_point *element2,
|
|
|
|
u8 *confirm)
|
2013-01-06 17:12:58 +01:00
|
|
|
{
|
|
|
|
u8 element_b1[2 * SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
u8 element_b2[2 * SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
|
2019-09-07 17:47:48 +02:00
|
|
|
if (crypto_ec_point_to_bin(sae->tmp->ec, element1, element_b1,
|
|
|
|
element_b1 + sae->tmp->prime_len) < 0 ||
|
|
|
|
crypto_ec_point_to_bin(sae->tmp->ec, element2, element_b2,
|
|
|
|
element_b2 + sae->tmp->prime_len) < 0 ||
|
|
|
|
sae_cn_confirm(sae, sc, scalar1, element_b1,
|
|
|
|
2 * sae->tmp->prime_len,
|
|
|
|
scalar2, element_b2, 2 * sae->tmp->prime_len,
|
|
|
|
confirm) < 0)
|
|
|
|
return -1;
|
|
|
|
return 0;
|
2013-01-06 17:12:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-07 17:47:48 +02:00
|
|
|
static int sae_cn_confirm_ffc(struct sae_data *sae, const u8 *sc,
|
|
|
|
const struct crypto_bignum *scalar1,
|
|
|
|
const struct crypto_bignum *element1,
|
|
|
|
const struct crypto_bignum *scalar2,
|
|
|
|
const struct crypto_bignum *element2,
|
|
|
|
u8 *confirm)
|
2013-01-06 17:12:58 +01:00
|
|
|
{
|
|
|
|
u8 element_b1[SAE_MAX_PRIME_LEN];
|
|
|
|
u8 element_b2[SAE_MAX_PRIME_LEN];
|
|
|
|
|
2019-09-07 17:47:48 +02:00
|
|
|
if (crypto_bignum_to_bin(element1, element_b1, sizeof(element_b1),
|
|
|
|
sae->tmp->prime_len) < 0 ||
|
|
|
|
crypto_bignum_to_bin(element2, element_b2, sizeof(element_b2),
|
|
|
|
sae->tmp->prime_len) < 0 ||
|
|
|
|
sae_cn_confirm(sae, sc, scalar1, element_b1, sae->tmp->prime_len,
|
|
|
|
scalar2, element_b2, sae->tmp->prime_len,
|
|
|
|
confirm) < 0)
|
|
|
|
return -1;
|
|
|
|
return 0;
|
2013-01-06 17:12:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-06 15:57:53 +01:00
|
|
|
void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf)
|
|
|
|
{
|
|
|
|
const u8 *sc;
|
2019-08-27 15:33:15 +02:00
|
|
|
size_t hash_len;
|
2013-01-06 15:57:53 +01:00
|
|
|
|
2014-02-12 16:46:33 +01:00
|
|
|
if (sae->tmp == NULL)
|
|
|
|
return;
|
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
hash_len = sae->tmp->kck_len;
|
|
|
|
|
2013-01-06 15:57:53 +01:00
|
|
|
/* Send-Confirm */
|
|
|
|
sc = wpabuf_put(buf, 0);
|
|
|
|
wpabuf_put_le16(buf, sae->send_confirm);
|
2017-12-27 11:17:44 +01:00
|
|
|
if (sae->send_confirm < 0xffff)
|
|
|
|
sae->send_confirm++;
|
2013-01-06 15:57:53 +01:00
|
|
|
|
2013-01-06 18:26:27 +01:00
|
|
|
if (sae->tmp->ec)
|
|
|
|
sae_cn_confirm_ecc(sae, sc, sae->tmp->own_commit_scalar,
|
|
|
|
sae->tmp->own_commit_element_ecc,
|
2013-01-06 17:12:58 +01:00
|
|
|
sae->peer_commit_scalar,
|
2013-01-06 18:26:27 +01:00
|
|
|
sae->tmp->peer_commit_element_ecc,
|
2019-08-27 15:33:15 +02:00
|
|
|
wpabuf_put(buf, hash_len));
|
2013-01-06 17:12:58 +01:00
|
|
|
else
|
2013-01-06 18:26:27 +01:00
|
|
|
sae_cn_confirm_ffc(sae, sc, sae->tmp->own_commit_scalar,
|
|
|
|
sae->tmp->own_commit_element_ffc,
|
2013-01-06 17:12:58 +01:00
|
|
|
sae->peer_commit_scalar,
|
2013-01-06 18:26:27 +01:00
|
|
|
sae->tmp->peer_commit_element_ffc,
|
2019-08-27 15:33:15 +02:00
|
|
|
wpabuf_put(buf, hash_len));
|
2012-12-30 21:28:57 +01:00
|
|
|
}
|
2012-12-30 21:31:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len)
|
|
|
|
{
|
2019-08-27 15:33:15 +02:00
|
|
|
u8 verifier[SAE_MAX_HASH_LEN];
|
|
|
|
size_t hash_len;
|
|
|
|
|
|
|
|
if (!sae->tmp)
|
|
|
|
return -1;
|
2012-12-30 21:31:19 +01:00
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
hash_len = sae->tmp->kck_len;
|
|
|
|
if (len < 2 + hash_len) {
|
2012-12-30 21:31:19 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Too short confirm message");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-01-06 15:57:53 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: peer-send-confirm %u", WPA_GET_LE16(data));
|
2012-12-30 21:31:19 +01:00
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
if (!sae->peer_commit_scalar || !sae->tmp->own_commit_scalar) {
|
2014-02-12 16:46:33 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Temporary data not yet available");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-03-05 22:43:25 +01:00
|
|
|
if (sae->tmp->ec) {
|
|
|
|
if (!sae->tmp->peer_commit_element_ecc ||
|
2019-09-07 17:47:48 +02:00
|
|
|
!sae->tmp->own_commit_element_ecc ||
|
|
|
|
sae_cn_confirm_ecc(sae, data, sae->peer_commit_scalar,
|
|
|
|
sae->tmp->peer_commit_element_ecc,
|
|
|
|
sae->tmp->own_commit_scalar,
|
|
|
|
sae->tmp->own_commit_element_ecc,
|
|
|
|
verifier) < 0)
|
2019-03-05 22:43:25 +01:00
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
if (!sae->tmp->peer_commit_element_ffc ||
|
2019-09-07 17:47:48 +02:00
|
|
|
!sae->tmp->own_commit_element_ffc ||
|
|
|
|
sae_cn_confirm_ffc(sae, data, sae->peer_commit_scalar,
|
|
|
|
sae->tmp->peer_commit_element_ffc,
|
|
|
|
sae->tmp->own_commit_scalar,
|
|
|
|
sae->tmp->own_commit_element_ffc,
|
|
|
|
verifier) < 0)
|
2019-03-05 22:43:25 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2012-12-30 21:31:19 +01:00
|
|
|
|
2019-08-27 15:33:15 +02:00
|
|
|
if (os_memcmp_const(verifier, data + 2, hash_len) != 0) {
|
2012-12-30 21:31:19 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "SAE: Confirm mismatch");
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Received confirm",
|
2019-08-27 15:33:15 +02:00
|
|
|
data + 2, hash_len);
|
2012-12-30 21:31:19 +01:00
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Calculated verifier",
|
2019-08-27 15:33:15 +02:00
|
|
|
verifier, hash_len);
|
2012-12-30 21:31:19 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-12-26 23:07:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
const char * sae_state_txt(enum sae_state state)
|
|
|
|
{
|
|
|
|
switch (state) {
|
|
|
|
case SAE_NOTHING:
|
|
|
|
return "Nothing";
|
|
|
|
case SAE_COMMITTED:
|
|
|
|
return "Committed";
|
|
|
|
case SAE_CONFIRMED:
|
|
|
|
return "Confirmed";
|
|
|
|
case SAE_ACCEPTED:
|
|
|
|
return "Accepted";
|
|
|
|
}
|
|
|
|
return "?";
|
|
|
|
}
|