mirror of
				https://git.kernel.org/pub/scm/network/wireless/iwd.git
				synced 2025-11-04 00:37:22 +01:00 
			
		
		
		
	Using mmpdu_associate_response structure members marked __le requires the relevant endianness conversion.
		
			
				
	
	
		
			358 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *
 | 
						|
 *  Wireless daemon for Linux
 | 
						|
 *
 | 
						|
 *  Copyright (C) 2018  Intel Corporation. All rights reserved.
 | 
						|
 *
 | 
						|
 *  This library is free software; you can redistribute it and/or
 | 
						|
 *  modify it under the terms of the GNU Lesser General Public
 | 
						|
 *  License as published by the Free Software Foundation; either
 | 
						|
 *  version 2.1 of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 *  This library is distributed in the hope that it will be useful,
 | 
						|
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
 *  Lesser General Public License for more details.
 | 
						|
 *
 | 
						|
 *  You should have received a copy of the GNU Lesser General Public
 | 
						|
 *  License along with this library; if not, write to the Free Software
 | 
						|
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#ifdef HAVE_CONFIG_H
 | 
						|
#include <config.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include <ell/ell.h>
 | 
						|
 | 
						|
#include "src/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;
 | 
						|
	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);
 | 
						|
 | 
						|
	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);
 | 
						|
 | 
						|
	uint8_t buf[5 + L_ECC_SCALAR_MAX_BYTES];
 | 
						|
	struct iovec iov[3];
 | 
						|
	int iov_elems = 0;
 | 
						|
	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 */
 | 
						|
	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)
 | 
						|
{
 | 
						|
	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;
 | 
						|
	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,
 | 
						|
						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)) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	l_ecc_point_free(other_public);
 | 
						|
 | 
						|
	nbytes = l_ecc_scalar_get_data(shared_secret, ss_buf, sizeof(ss_buf));
 | 
						|
 | 
						|
	l_ecc_scalar_free(shared_secret);
 | 
						|
 | 
						|
	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:
 | 
						|
	memset(ss_buf, 0, sizeof(ss_buf));
 | 
						|
	l_ecc_scalar_free(shared_secret);
 | 
						|
	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;
 | 
						|
	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);
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
{
 | 
						|
	struct owe_sm *owe = l_new(struct owe_sm, 1);
 | 
						|
 | 
						|
	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();
 | 
						|
 | 
						|
	owe->ap.start = owe_start;
 | 
						|
	owe->ap.free = owe_free;
 | 
						|
	owe->ap.rx_authenticate = owe_rx_authenticate;
 | 
						|
	owe->ap.rx_associate = owe_rx_associate;
 | 
						|
 | 
						|
	if (!owe_reset(owe)) {
 | 
						|
		l_free(owe);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	return &owe->ap;
 | 
						|
}
 |