iwd/src/owe.c

360 lines
8.2 KiB
C
Raw Normal View History

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2018-2019 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
2019-04-03 18:33:36 +02:00
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ell/ell.h>
2019-04-03 18:33:36 +02:00
#include "src/crypto.h"
#include "src/ie.h"
#include "src/handshake.h"
#include "src/owe.h"
#include "src/mpdu.h"
#include "src/auth-proto.h"
struct owe_sm {
struct auth_proto ap;
struct handshake_state *hs;
2018-12-12 18:14:17 +01:00
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;
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;
}
static void owe_free(struct auth_proto *ap)
{
struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap);
2018-12-12 18:14:17 +01:00
l_ecc_scalar_free(owe->private);
l_ecc_point_free(owe->public_key);
l_free(owe);
}
static bool owe_start(struct auth_proto *ap)
{
struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap);
owe->auth_tx(owe->user_data);
return true;
}
static int owe_rx_authenticate(struct auth_proto *ap, const uint8_t *frame,
size_t frame_len)
{
struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap);
2018-12-12 18:14:17 +01:00
uint8_t buf[5 + L_ECC_SCALAR_MAX_BYTES];
struct iovec iov[3];
int iov_elems = 0;
2018-12-12 18:14:17 +01:00
size_t len;
/*
* RFC 8110 Section 4.3
* A client wishing to do OWE MUST indicate the OWE AKM in the RSN
* element portion of the 802.11 association request ...
*/
iov[iov_elems].iov_base = owe->hs->supplicant_ie;
iov[iov_elems].iov_len = owe->hs->supplicant_ie[1] + 2;
iov_elems++;
/*
* ... and MUST include a Diffie-Hellman Parameter element to its
* 802.11 association request.
*/
buf[0] = IE_TYPE_EXTENSION;
buf[2] = IE_TYPE_OWE_DH_PARAM - 256;
l_put_le16(owe->group, buf + 3); /* group */
2018-12-12 18:14:17 +01:00
len = l_ecc_point_get_x(owe->public_key, buf + 5,
L_ECC_SCALAR_MAX_BYTES);
buf[1] = 3 + len; /* length */
iov[iov_elems].iov_base = (void *) buf;
iov[iov_elems].iov_len = buf[1] + 2;
iov_elems++;
owe->assoc_tx(iov, iov_elems, owe->user_data);
return 0;
}
/*
* RFC 8110 Section 4.4 Post Association
*/
static bool owe_compute_keys(struct owe_sm *owe, const void *public_key,
size_t pub_len)
{
2018-12-12 18:14:17 +01:00
struct l_ecc_scalar *shared_secret;
uint8_t ss_buf[L_ECC_SCALAR_MAX_BYTES];
uint8_t prk[L_ECC_SCALAR_MAX_BYTES];
uint8_t pmk[L_ECC_SCALAR_MAX_BYTES];
uint8_t pmkid[16];
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;
2018-12-12 18:14:17 +01:00
struct l_ecc_point *other_public;
ssize_t nbytes;
enum l_checksum_type type;
2018-12-12 18:14:17 +01:00
other_public = l_ecc_point_from_data(owe->curve,
L_ECC_POINT_TYPE_COMPLIANT,
public_key, pub_len);
if (!other_public) {
l_error("AP public key was not valid");
return false;
}
if (!l_ecdh_generate_shared_secret(owe->private, other_public,
&shared_secret)) {
2019-10-17 01:14:17 +02:00
l_ecc_point_free(other_public);
return false;
2018-12-12 18:14:17 +01:00
}
2018-12-12 18:14:17 +01:00
l_ecc_point_free(other_public);
nbytes = l_ecc_scalar_get_data(shared_secret, ss_buf, sizeof(ss_buf));
2018-12-12 18:14:17 +01:00
l_ecc_scalar_free(shared_secret);
if (nbytes < 0)
return false;
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(type, key, ptr - key, 1, prk, ss_buf, nbytes))
goto failed;
/* PMK = HKDF-expand(prk, "OWE Key Generation", n) */
if (!hkdf_expand(type, prk, nbytes, "OWE Key Generation",
strlen("OWE Key Generation"), pmk, nbytes))
goto failed;
sha = l_checksum_new(type);
/* PMKID = Truncate-128(Hash(C | A)) */
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 = nbytes;
l_checksum_updatev(sha, iov, 2);
l_checksum_get_digest(sha, pmkid, 16);
l_checksum_free(sha);
handshake_state_set_pmk(owe->hs, pmk, nbytes);
handshake_state_set_pmkid(owe->hs, pmkid);
return true;
failed:
2018-12-12 18:14:17 +01:00
memset(ss_buf, 0, sizeof(ss_buf));
return false;
}
static bool owe_retry(struct owe_sm *owe)
{
/* retry with another group, if possible */
owe->retry++;
if (!owe_reset(owe))
return false;
l_debug("OWE retrying with group %u", owe->group);
owe_rx_authenticate(&owe->ap, NULL, 0);
return true;
}
static int owe_rx_associate(struct auth_proto *ap, const uint8_t *frame,
size_t len)
{
struct owe_sm *owe = l_container_of(ap, struct owe_sm, ap);
const struct mmpdu_header *mpdu = NULL;
const struct mmpdu_association_response *body;
struct ie_tlv_iter iter;
size_t owe_dh_len = 0;
const uint8_t *owe_dh = NULL;
struct ie_rsn_info info;
bool akm_found = false;
const void *data;
mpdu = mpdu_validate(frame, len);
if (!mpdu) {
l_error("could not process frame");
return -EBADMSG;
}
body = mmpdu_body(mpdu);
if (L_LE16_TO_CPU(body->status_code) ==
MMPDU_STATUS_CODE_UNSUPP_FINITE_CYCLIC_GROUP) {
if (!owe_retry(owe))
goto owe_bad_status;
return -EAGAIN;
} else if (body->status_code)
goto owe_bad_status;
ie_tlv_iter_init(&iter, body->ies, (const uint8_t *) mpdu + len -
body->ies);
while (ie_tlv_iter_next(&iter)) {
uint16_t tag = ie_tlv_iter_get_tag(&iter);
data = ie_tlv_iter_get_data(&iter);
len = ie_tlv_iter_get_length(&iter);
switch (tag) {
case IE_TYPE_OWE_DH_PARAM:
owe_dh = data;
owe_dh_len = len;
break;
case IE_TYPE_RSN:
if (ie_parse_rsne(&iter, &info) < 0) {
l_error("could not parse RSN IE");
goto invalid_ies;
}
/*
* RFC 8110 Section 4.2
* An AP agreeing to do OWE MUST include the OWE AKM in
* the RSN element portion of the 802.11 association
* response.
*/
if (info.akm_suites != IE_RSN_AKM_SUITE_OWE) {
l_error("OWE AKM not included");
goto invalid_ies;
}
akm_found = true;
break;
default:
continue;
}
}
if (!owe_dh || owe_dh_len < 34 || !akm_found) {
l_error("associate response did not include proper OWE IE's");
goto invalid_ies;
}
if (l_get_le16(owe_dh) != owe->group) {
l_error("associate response contained unsupported group %u",
l_get_le16(owe_dh));
return -EBADMSG;
}
if (!owe_compute_keys(owe, owe_dh + 2, owe_dh_len - 2)) {
l_error("could not compute OWE keys");
return -EBADMSG;
}
return 0;
invalid_ies:
return MMPDU_STATUS_CODE_INVALID_ELEMENT;
owe_bad_status:
return L_LE16_TO_CPU(body->status_code);
}
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
struct auth_proto *owe_sm_new(struct handshake_state *hs,
owe_tx_authenticate_func_t auth,
owe_tx_associate_func_t assoc,
void *user_data)
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
{
struct owe_sm *owe = l_new(struct owe_sm, 1);
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
owe->hs = hs;
owe->auth_tx = auth;
owe->assoc_tx = assoc;
owe->user_data = user_data;
owe->ecc_groups = l_ecc_curve_get_supported_ike_groups();
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
owe->ap.start = owe_start;
owe->ap.free = owe_free;
owe->ap.rx_authenticate = owe_rx_authenticate;
owe->ap.rx_associate = owe_rx_associate;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
if (!owe_reset(owe)) {
l_free(owe);
return NULL;
}
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
return &owe->ap;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
}