owe: allow group 20 + group negotiation

ELL ECC supports group 20 (P384) so OWE can also support it. This also
adds group negotiation, where OWE can choose a different group than the
default if the AP requests it.

A check needed to be added in netdev in order for the negotiation to work.
The RFC says that if a group is not supported association should be rejected
with code 77 (unsupported finite cyclic group) and association should be
started again. This rejection was causing a connect event to be emitted by
the kernel (in addition to an associate event) which would result in netdev
terminating the connection, which we didn't want. Since OWE receives the
rejected associate event it can intelligently decide whether it really wants
to terminate (out of supported groups) or try the next available group.

This also utilizes the new MIC/KEK/KCK length changes, since OWE dictates
the lengths of those keys.
This commit is contained in:
James Prestwood 2019-01-17 12:25:38 -08:00 committed by Denis Kenzior
parent d79b2b28ec
commit 922506105e
2 changed files with 91 additions and 27 deletions

View File

@ -1830,6 +1830,19 @@ static void netdev_connect_event(struct l_genl_msg *msg, struct netdev *netdev)
return;
error:
/*
* RFC 8110 Section 4.3 - OWE Association
* A client that receives an 802.11 association response with a status
* code of seventy-seven SHOULD retry OWE with a different supported
* group...
*
* Note: OWE (should have) received this already in an associate
* response and will handle it.
*/
if (netdev->owe && *status_code ==
MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP)
return;
netdev->result = NETDEV_RESULT_ASSOCIATION_FAILED;
netdev_connect_failed(NULL, netdev);
}

105
src/owe.c
View File

@ -28,18 +28,14 @@
#include "owe.h"
#include "mpdu.h"
/*
* TODO: Once other groups are added, this will need to be dynamic. OWE does
* support retries with different groups, but this is not yet implemented since
* only group 19 is supported.
*/
#define OWE_DEFAULT_GROUP 19
struct owe_sm {
struct handshake_state *hs;
const struct l_ecc_curve *curve;
struct l_ecc_scalar *private;
struct l_ecc_point *public_key;
uint8_t retry;
uint16_t group;
const unsigned int *ecc_groups;
owe_tx_authenticate_func_t auth_tx;
owe_tx_associate_func_t assoc_tx;
@ -47,6 +43,30 @@ struct owe_sm {
void *user_data;
};
static bool owe_reset(struct owe_sm *owe)
{
/*
* Reset OWE with a different curve group and generate a new key pair
*/
if (owe->ecc_groups[owe->retry] == 0)
return false;
owe->group = owe->ecc_groups[owe->retry];
owe->curve = l_ecc_curve_get_ike_group(owe->group);
if (owe->private)
l_ecc_scalar_free(owe->private);
if (owe->public_key)
l_ecc_point_free(owe->public_key);
if (!l_ecdh_generate_key_pair(owe->curve, &owe->private,
&owe->public_key))
return false;
return true;
}
struct owe_sm *owe_sm_new(struct handshake_state *hs,
owe_tx_authenticate_func_t auth,
owe_tx_associate_func_t assoc,
@ -59,10 +79,9 @@ struct owe_sm *owe_sm_new(struct handshake_state *hs,
owe->assoc_tx = assoc;
owe->user_data = user_data;
owe->complete = complete;
owe->curve = l_ecc_curve_get_ike_group(OWE_DEFAULT_GROUP);
owe->ecc_groups = l_ecc_curve_get_supported_ike_groups();
if (!l_ecdh_generate_key_pair(owe->curve, &owe->private,
&owe->public_key)) {
if (!owe_reset(owe)) {
l_free(owe);
return NULL;
}
@ -105,7 +124,7 @@ void owe_rx_authenticate(struct owe_sm *owe)
*/
buf[0] = IE_TYPE_EXTENSION;
buf[2] = IE_TYPE_OWE_DH_PARAM - 256;
l_put_le16(OWE_DEFAULT_GROUP, buf + 3); /* group */
l_put_le16(owe->group, buf + 3); /* group */
len = l_ecc_point_get_x(owe->public_key, buf + 5,
L_ECC_SCALAR_MAX_BYTES);
buf[1] = 3 + len; /* length */
@ -125,13 +144,16 @@ static bool owe_compute_keys(struct owe_sm *owe, const void *public_key,
{
struct l_ecc_scalar *shared_secret;
uint8_t ss_buf[L_ECC_SCALAR_MAX_BYTES];
uint8_t prk[32];
uint8_t pmk[32];
uint8_t prk[L_ECC_SCALAR_MAX_BYTES];
uint8_t pmk[L_ECC_SCALAR_MAX_BYTES];
uint8_t pmkid[16];
uint8_t key[32 + 32 + 2];
uint8_t key[L_ECC_SCALAR_MAX_BYTES + L_ECC_SCALAR_MAX_BYTES + 2];
uint8_t *ptr = key;
struct iovec iov[2];
struct l_checksum *sha;
struct l_ecc_point *other_public;
ssize_t nbytes;
enum l_checksum_type type;
other_public = l_ecc_point_from_data(owe->curve,
L_ECC_POINT_TYPE_COMPLIANT,
@ -148,30 +170,43 @@ static bool owe_compute_keys(struct owe_sm *owe, const void *public_key,
l_ecc_point_free(other_public);
l_ecc_scalar_get_data(shared_secret, ss_buf, sizeof(ss_buf));
nbytes = l_ecc_scalar_get_data(shared_secret, ss_buf, sizeof(ss_buf));
l_ecc_scalar_free(shared_secret);
l_ecc_point_get_x(owe->public_key, key, sizeof(key));
memcpy(key + 32, public_key, 32);
l_put_le16(OWE_DEFAULT_GROUP, key + 64);
ptr += l_ecc_point_get_x(owe->public_key, ptr, sizeof(key));
memcpy(ptr, public_key, nbytes);
ptr += nbytes;
l_put_le16(owe->group, ptr);
ptr += 2;
switch (owe->group) {
case 19:
type = L_CHECKSUM_SHA256;
break;
case 20:
type = L_CHECKSUM_SHA384;
break;
default:
goto failed;
}
/* prk = HKDF-extract(C | A | group, z) */
if (!hkdf_extract(L_CHECKSUM_SHA256, key, 66, 1, prk, ss_buf, 32))
if (!hkdf_extract(type, key, ptr - key, 1, prk, ss_buf, nbytes))
goto failed;
/* PMK = HKDF-expand(prk, "OWE Key Generation", n) */
if (!hkdf_expand(L_CHECKSUM_SHA256, prk, 32, "OWE Key Generation",
strlen("OWE Key Generation"), pmk, 32))
if (!hkdf_expand(type, prk, nbytes, "OWE Key Generation",
strlen("OWE Key Generation"), pmk, nbytes))
goto failed;
sha = l_checksum_new(L_CHECKSUM_SHA256);
sha = l_checksum_new(type);
/* PMKID = Truncate-128(Hash(C | A)) */
iov[0].iov_base = key; /* first 32 bytes of key are owe->public_key */
iov[0].iov_len = 32;
iov[0].iov_base = key; /* first nbytes of key are owe->public_key */
iov[0].iov_len = nbytes;
iov[1].iov_base = (void *) public_key;
iov[1].iov_len = 32;
iov[1].iov_len = nbytes;
l_checksum_updatev(sha, iov, 2);
@ -179,7 +214,7 @@ static bool owe_compute_keys(struct owe_sm *owe, const void *public_key,
l_checksum_free(sha);
handshake_state_set_pmk(owe->hs, pmk, 32);
handshake_state_set_pmk(owe->hs, pmk, nbytes);
handshake_state_set_pmkid(owe->hs, pmkid);
return true;
@ -209,6 +244,22 @@ void owe_rx_associate(struct owe_sm *owe, const uint8_t *frame, size_t len)
body = mmpdu_body(mpdu);
if (body->status_code == MMPDU_REASON_CODE_UNSUPP_FINITE_CYCLIC_GROUP) {
/* retry with another group, if possible */
owe->retry++;
if (!owe_reset(owe)) {
owe->complete(body->status_code, owe->user_data);
return;
}
l_debug("OWE retrying with group %u", owe->group);
owe_start(owe);
return;
}
ie_tlv_iter_init(&iter, body->ies, (const uint8_t *) mpdu + len -
body->ies);
@ -254,7 +305,7 @@ void owe_rx_associate(struct owe_sm *owe, const uint8_t *frame, size_t len)
goto owe_failed;
}
if (l_get_le16(owe_dh) != 19) {
if (l_get_le16(owe_dh) != owe->group) {
l_error("associate response contained unsupported group %u",
l_get_le16(owe_dh));
goto owe_failed;