diff --git a/src/netdev.c b/src/netdev.c index a3492d1e..c1568604 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -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); } diff --git a/src/owe.c b/src/owe.c index 54d8834f..d61dc929 100644 --- a/src/owe.c +++ b/src/owe.c @@ -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;