diff mbox series

[08/12] MLD STA: Add support for processing EAPOL 3/4 frame

Message ID 20220825055311.3327147-9-quic_vjakkam@quicinc.com
State Changes Requested
Headers show
Series MLD STA: Add support for four-way handshake and SAE external authentication | expand

Commit Message

Veerendranath Jakkam Aug. 25, 2022, 5:53 a.m. UTC
Process EAPOL 3/4 frame and plumb PTK and per-link GTK/IGTK/BIGTK keys
to driver.

Signed-off-by: Veerendranath Jakkam <quic_vjakkam@quicinc.com>
---
 src/rsn_supp/wpa.c   | 494 ++++++++++++++++++++++++++++++++++++++++++-
 src/rsn_supp/wpa_i.h |   6 +
 2 files changed, 498 insertions(+), 2 deletions(-)

Comments

Peer, Ilan Sept. 13, 2022, 8:12 a.m. UTC | #1
Hi,

> -----Original Message-----
> From: Hostap <hostap-bounces@lists.infradead.org> On Behalf Of
> Veerendranath Jakkam
> Sent: Thursday, August 25, 2022 08:53
> To: hostap@lists.infradead.org
> Cc: quic_vjakkam@quicinc.com
> Subject: [PATCH 08/12] MLD STA: Add support for processing EAPOL 3/4
> frame
> 
> Process EAPOL 3/4 frame and plumb PTK and per-link GTK/IGTK/BIGTK keys
> to driver.
> 
> Signed-off-by: Veerendranath Jakkam <quic_vjakkam@quicinc.com>
> ---
>  src/rsn_supp/wpa.c   | 494
> ++++++++++++++++++++++++++++++++++++++++++-
>  src/rsn_supp/wpa_i.h |   6 +
>  2 files changed, 498 insertions(+), 2 deletions(-)
> 
> diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index
> 8ac22eac9..96adc4817 100644
> --- a/src/rsn_supp/wpa.c
> +++ b/src/rsn_supp/wpa.c
> @@ -1203,6 +1203,76 @@ static int wpa_supplicant_install_gtk(struct
> wpa_sm *sm,  }
> 
> 
> +static int wpa_supplicant_install_mlo_gtk(struct wpa_sm *sm, u8 link_id,
> +					  const struct wpa_gtk_data *gd,
> +					  const u8 *key_rsc, int wnm_sleep) {
> +	const u8 *_gtk = gd->gtk;
> +	u8 gtk_buf[32];
> +
> +	/* Detect possible key reinstallation */
> +	if ((sm->links[link_id].gtk.gtk_len == (size_t) gd->gtk_len &&
> +	     os_memcmp(sm->links[link_id].gtk.gtk, gd->gtk,
> +		       sm->links[link_id].gtk.gtk_len) == 0) ||
> +	    (sm->links[link_id].gtk_wnm_sleep.gtk_len == (size_t) gd-
> >gtk_len &&
> +	     os_memcmp(sm->links[link_id].gtk_wnm_sleep.gtk, gd->gtk,
> +		       sm->links[link_id].gtk_wnm_sleep.gtk_len) == 0)) {
> +		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +			"WPA MLO: Not reinstalling already in-use GTK to the
> driver (keyidx=%d tx=%d len=%d)",
> +			gd->keyidx, gd->tx, gd->gtk_len);

Also print the link ID?

> +		return 0;
> +	}
> +
> +	wpa_hexdump_key(MSG_DEBUG, "WPA MLO: Group Key", gd->gtk,
> gd->gtk_len);
> +	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +		"WPA MLO: Installing GTK to the driver (keyidx=%d tx=%d
> len=%d)",
> +		gd->keyidx, gd->tx, gd->gtk_len);
> +	wpa_hexdump(MSG_DEBUG, "WPA MLO: RSC", key_rsc, gd-
> >key_rsc_len);

Same in the above prints. I think it would be useful to print the link ID.

> +	if (sm->group_cipher == WPA_CIPHER_TKIP) {
> +		/* Swap Tx/Rx keys for Michael MIC */
> +		os_memcpy(gtk_buf, gd->gtk, 16);
> +		os_memcpy(gtk_buf + 16, gd->gtk + 24, 8);
> +		os_memcpy(gtk_buf + 24, gd->gtk + 16, 8);
> +		_gtk = gtk_buf;
> +	}

As noted earlier not sure we need to allow TKIP.

> +	if (sm->pairwise_cipher == WPA_CIPHER_NONE) {
> +		if (wpa_sm_mlo_set_key(sm, link_id, gd->alg, NULL, gd-
> >keyidx,
> +				       1, key_rsc, gd->key_rsc_len, _gtk,
> +				       gd->gtk_len,
> +				       KEY_FLAG_GROUP_RX_TX_DEFAULT) < 0)
> {
> +			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
> +				"WPA MLO: Failed to set GTK to the driver "
> +				"(Group only)");
> +			forced_memzero(gtk_buf, sizeof(gtk_buf));
> +			return -1;
> +		}

This also seems something that we do not to deal with in a MLO connection. But really not sure.

> +	} else if (wpa_sm_mlo_set_key(sm, link_id, gd->alg,
> +				      broadcast_ether_addr, gd->keyidx,
> +				      gd->tx, key_rsc, gd->key_rsc_len, _gtk,
> +				      gd->gtk_len, KEY_FLAG_GROUP_RX) < 0) {
> +		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
> +			"WPA MLO: Failed to set GTK to "
> +			"the driver (alg=%d keylen=%d keyidx=%d)",
> +			gd->alg, gd->gtk_len, gd->keyidx);
> +		forced_memzero(gtk_buf, sizeof(gtk_buf));
> +		return -1;
> +	}
> +	forced_memzero(gtk_buf, sizeof(gtk_buf));
> +
> +	if (wnm_sleep) {
> +		sm->links[link_id].gtk_wnm_sleep.gtk_len = gd->gtk_len;
> +		os_memcpy(sm->links[link_id].gtk_wnm_sleep.gtk, gd->gtk,
> +			  sm->links[link_id].gtk_wnm_sleep.gtk_len);
> +	} else {
> +		sm->links[link_id].gtk.gtk_len = gd->gtk_len;
> +		os_memcpy(sm->links[link_id].gtk.gtk, gd->gtk,
> +			  sm->links[link_id].gtk.gtk_len);
> +	}
> +
> +	return 0;
> +}
> +
> +
>  static int wpa_supplicant_gtk_tx_bit_workaround(const struct wpa_sm
> *sm,
>  						int tx)
>  {
> @@ -1251,6 +1321,82 @@ static int wpa_supplicant_rsc_relaxation(const
> struct wpa_sm *sm,  }
> 
> 
> +static int _wpa_supplicant_pairwise_mlo_gtk(struct wpa_sm *sm, u8

Why use pairwise in the function name?

> link_id,
> +					    const u8 *gtk, size_t gtk_len,
> +					    int key_info)
> +{
> +	struct wpa_gtk_data gd;
> +	const u8 *key_rsc;
> +
> +	/*
> +	 * MLO GTK KDE format:
> +	 * KeyID[bits 0-1], Tx [bit 2], Reserved [bit 3], link id [4-7]
> +	 * PN
> +	 * GTK
> +	 */
> +
> +	os_memset(&gd, 0, sizeof(gd));
> +	wpa_hexdump_key(MSG_DEBUG, "MLO RSN: received GTK in
> pairwise handshake",
> +			gtk, gtk_len);
> +
> +	if (gtk_len < 7 || gtk_len - 7 > sizeof(gd.gtk))
> +		return -1;
> +

Can use WPA_MLO_GTK_KDE_PREFIX_LENGTH instead of 7.

> +	gd.keyidx = gtk[0] & 0x3;
> +	gtk += 1;
> +	gtk_len -= 1;
> +
> +	key_rsc = gtk;
> +
> +	gtk += 6;
> +	gtk_len -= 6;
> +
> +	os_memcpy(gd.gtk, gtk, gtk_len);
> +	gd.gtk_len = gtk_len;
> +
> +	if (sm->group_cipher != WPA_CIPHER_GTK_NOT_USED &&
> +	    (wpa_supplicant_check_group_cipher(sm, sm->group_cipher,
> +					       gtk_len, gtk_len,
> +					       &gd.key_rsc_len, &gd.alg) ||
> +	     wpa_supplicant_install_mlo_gtk(sm, link_id, &gd, key_rsc, 0))) {
> +		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +			"MLO RSN: Failed to install GTK");
> +		forced_memzero(&gd, sizeof(gd));
> +		return -1;
> +	}
> +	forced_memzero(&gd, sizeof(gd));

The code can be refactored so "forced_memzero(&gd, sizeof(gd));" would be called once.

> +
> +	return 0;
> +}
> +
> +
> +static int wpa_supplicant_pairwise_mlo_gtk(struct wpa_sm *sm,
> +					   const struct wpa_eapol_key *key,
> +					   struct wpa_eapol_ie_parse *ie,
> +					   int key_info)
> +{
> +	u8 i;
> +
> +	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
> +		if (!(sm->valid_links & BIT(i)))
> +			continue;

As noted earlier, I think that this should also handle the case of rejected links, for which the MLD AP
Is not expected to provide GTK/IGTK/BIGTK.

> +
> +		if (!ie->mlo_gtk[i]) {
> +			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
> +				"MLO RSN: GTK not found for link ID %u", i);
> +			return -1;
> +		}
> +
> +		if (_wpa_supplicant_pairwise_mlo_gtk(sm, i, ie->mlo_gtk[i],
> +						    ie->mlo_gtk_len[i],
> +						    key_info))
> +			return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +
>  static int wpa_supplicant_pairwise_gtk(struct wpa_sm *sm,
>  				       const struct wpa_eapol_key *key,
>  				       const u8 *gtk, size_t gtk_len, @@ -1421,6
> +1567,170 @@ static int wpa_supplicant_install_bigtk(struct wpa_sm *sm,
>  	return 0;
>  }
> 
> +static int wpa_supplicant_install_mlo_igtk(struct wpa_sm *sm, u8 link_id,
> +					   const struct wpa_mlo_igtk_kde
> *igtk,
> +					   int wnm_sleep)
> +{
> +	size_t len = wpa_cipher_key_len(sm->mgmt_group_cipher);
> +	u16 keyidx = WPA_GET_LE16(igtk->hdr.keyid);
> +
> +	/* Detect possible key reinstallation */
> +	if ((sm->links[link_id].igtk.igtk_len == len &&
> +	     os_memcmp(sm->links[link_id].igtk.igtk, igtk->igtk,
> +		       sm->links[link_id].igtk.igtk_len) == 0) ||
> +	    (sm->links[link_id].igtk_wnm_sleep.igtk_len == len &&
> +	     os_memcmp(sm->links[link_id].igtk_wnm_sleep.igtk, igtk->igtk,
> +		       sm->links[link_id].igtk_wnm_sleep.igtk_len) == 0)) {
> +		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +			"WPA: Not reinstalling already in-use IGTK to the
> driver (keyidx=%d)",
> +			keyidx);

Same here: print link ID.

> +		return  0;
> +	}
> +
> +	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +		"WPA: IGTK keyid %d pn " COMPACT_MACSTR,
> +		keyidx, MAC2STR(igtk->hdr.pn));
> +	wpa_hexdump_key(MSG_DEBUG, "WPA: IGTK", igtk->igtk, len);
> +	if (keyidx > 4095) {
> +		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
> +			"WPA: Invalid IGTK KeyID %d", keyidx);
> +		return -1;
> +	}
> +	if (wpa_sm_mlo_set_key(sm, link_id,
> +			       wpa_cipher_to_alg(sm->mgmt_group_cipher),
> +			       broadcast_ether_addr, keyidx, 0, igtk->hdr.pn,
> +			       sizeof(igtk->hdr.pn), igtk->igtk, len,
> +			       KEY_FLAG_GROUP_RX) < 0) {
> +		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
> +			"WPA: Failed to configure IGTK to the driver");
> +		return -1;
> +	}
> +
> +	if (wnm_sleep) {
> +		sm->links[link_id].igtk_wnm_sleep.igtk_len = len;
> +		os_memcpy(sm->links[link_id].igtk_wnm_sleep.igtk,
> +			  igtk->igtk,
> +			  sm->links[link_id].igtk_wnm_sleep.igtk_len);
> +	} else {
> +		sm->links[link_id].igtk.igtk_len = len;
> +		os_memcpy(sm->links[link_id].igtk.igtk, igtk->igtk,
> +			  sm->links[link_id].igtk.igtk_len);
> +	}
> +
> +	return 0;
> +}
> +
> +
> +static int
> +wpa_supplicant_install_mlo_bigtk(struct wpa_sm *sm, u8 link_id,
> +				 const struct wpa_mlo_bigtk_kde *bigtk,
> +				 int wnm_sleep)
> +{
> +	size_t len = wpa_cipher_key_len(sm->mgmt_group_cipher);
> +	u16 keyidx = WPA_GET_LE16(bigtk->hdr.keyid);
> +
> +	/* Detect possible key reinstallation */
> +	if ((sm->links[link_id].bigtk.bigtk_len == len &&
> +	     os_memcmp(sm->links[link_id].bigtk.bigtk, bigtk->bigtk,
> +		       sm->links[link_id].bigtk.bigtk_len) == 0) ||
> +	    (sm->links[link_id].bigtk_wnm_sleep.bigtk_len == len &&
> +	     os_memcmp(sm->links[link_id].bigtk_wnm_sleep.bigtk, bigtk-
> >bigtk,
> +		       sm->links[link_id].bigtk_wnm_sleep.bigtk_len) == 0)) {
> +		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +			"WPA: Not reinstalling already in-use BIGTK to the
> driver (keyidx=%d)",
> +			keyidx);
> +		return  0;
> +	}
> +
> +	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
> +		"WPA: BIGTK keyid %d pn " COMPACT_MACSTR,
> +		keyidx, MAC2STR(bigtk->hdr.pn));
> +	wpa_hexdump_key(MSG_DEBUG, "WPA: BIGTK", bigtk->bigtk, len);
> +	if (keyidx < 6 || keyidx > 7) {
> +		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
> +			"WPA: Invalid BIGTK KeyID %d", keyidx);
> +		return -1;
> +	}
> +	if (wpa_sm_mlo_set_key(sm, link_id,
> +			       wpa_cipher_to_alg(sm->mgmt_group_cipher),
> +			       broadcast_ether_addr, keyidx, 0, bigtk->hdr.pn,
> +			       sizeof(bigtk->hdr.pn), bigtk->bigtk, len,
> +			       KEY_FLAG_GROUP_RX) < 0) {
> +		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
> +			"WPA: Failed to configure BIGTK to the driver");
> +		return -1;
> +	}
> +
> +	if (wnm_sleep) {
> +		sm->links[link_id].bigtk_wnm_sleep.bigtk_len = len;
> +		os_memcpy(sm->links[link_id].bigtk_wnm_sleep.bigtk,
> +			  bigtk->bigtk,
> +			  sm->links[link_id].bigtk_wnm_sleep.bigtk_len);
> +	} else {
> +		sm->links[link_id].bigtk.bigtk_len = len;
> +		os_memcpy(sm->links[link_id].bigtk.bigtk, bigtk->bigtk,
> +			  sm->links[link_id].bigtk.bigtk_len);
> +	}
> +
> +	return 0;
> +}
> +
> +
> +static int _mlo_ieee80211w_set_keys(struct wpa_sm *sm, u8 link_id,
> +				    struct wpa_eapol_ie_parse *ie)
> +{
> +	size_t len;
> +
> +	if (!wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) ||
> +	    sm->mgmt_group_cipher == WPA_CIPHER_GTK_NOT_USED)
> +		return 0;
> +

I do not think that WPA_CIPHER_GTK_NOT_USED is valid for MLO.

> +	if (ie->mlo_igtk[link_id]) {
> +		const struct wpa_mlo_igtk_kde *igtk;
> +
> +		len = wpa_cipher_key_len(sm->mgmt_group_cipher);
> +		if (ie->mlo_igtk_len[link_id] !=
> +		    sizeof(struct wpa_mlo_igtk_hdr) + len)
> +			return -1;
> +
> +		igtk = (const struct wpa_mlo_igtk_kde *) ie-
> >mlo_igtk[link_id];
> +		if (wpa_supplicant_install_mlo_igtk(sm, link_id, igtk, 0) < 0)
> +			return -1;
> +	}
> +
> +	if (ie->mlo_bigtk[link_id] && sm->beacon_prot) {
> +		const struct wpa_mlo_bigtk_kde *bigtk;
> +
> +		len = wpa_cipher_key_len(sm->mgmt_group_cipher);
> +		if (ie->mlo_bigtk_len[link_id] !=
> +		    sizeof(struct wpa_mlo_bigtk_hdr) + len)
> +			return -1;
> +
> +		bigtk = (const struct wpa_mlo_bigtk_kde *) ie-
> >mlo_bigtk[link_id];
> +		if (wpa_supplicant_install_mlo_bigtk(sm, link_id, bigtk, 0) <
> 0)
> +			return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +
> +static int mlo_ieee80211w_set_keys(struct wpa_sm *sm,
> +				   struct wpa_eapol_ie_parse *ie)
> +{
> +	u8 i;
> +
> +	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
> +		if (!(sm->valid_links & BIT(i)))
> +			continue;
> +

Also here, consider links that were rejected during negotiation.

Regards,

Ilan.
diff mbox series

Patch

diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 8ac22eac9..96adc4817 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -1203,6 +1203,76 @@  static int wpa_supplicant_install_gtk(struct wpa_sm *sm,
 }
 
 
+static int wpa_supplicant_install_mlo_gtk(struct wpa_sm *sm, u8 link_id,
+					  const struct wpa_gtk_data *gd,
+					  const u8 *key_rsc, int wnm_sleep)
+{
+	const u8 *_gtk = gd->gtk;
+	u8 gtk_buf[32];
+
+	/* Detect possible key reinstallation */
+	if ((sm->links[link_id].gtk.gtk_len == (size_t) gd->gtk_len &&
+	     os_memcmp(sm->links[link_id].gtk.gtk, gd->gtk,
+		       sm->links[link_id].gtk.gtk_len) == 0) ||
+	    (sm->links[link_id].gtk_wnm_sleep.gtk_len == (size_t) gd->gtk_len &&
+	     os_memcmp(sm->links[link_id].gtk_wnm_sleep.gtk, gd->gtk,
+		       sm->links[link_id].gtk_wnm_sleep.gtk_len) == 0)) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"WPA MLO: Not reinstalling already in-use GTK to the driver (keyidx=%d tx=%d len=%d)",
+			gd->keyidx, gd->tx, gd->gtk_len);
+		return 0;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "WPA MLO: Group Key", gd->gtk, gd->gtk_len);
+	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+		"WPA MLO: Installing GTK to the driver (keyidx=%d tx=%d len=%d)",
+		gd->keyidx, gd->tx, gd->gtk_len);
+	wpa_hexdump(MSG_DEBUG, "WPA MLO: RSC", key_rsc, gd->key_rsc_len);
+	if (sm->group_cipher == WPA_CIPHER_TKIP) {
+		/* Swap Tx/Rx keys for Michael MIC */
+		os_memcpy(gtk_buf, gd->gtk, 16);
+		os_memcpy(gtk_buf + 16, gd->gtk + 24, 8);
+		os_memcpy(gtk_buf + 24, gd->gtk + 16, 8);
+		_gtk = gtk_buf;
+	}
+	if (sm->pairwise_cipher == WPA_CIPHER_NONE) {
+		if (wpa_sm_mlo_set_key(sm, link_id, gd->alg, NULL, gd->keyidx,
+				       1, key_rsc, gd->key_rsc_len, _gtk,
+				       gd->gtk_len,
+				       KEY_FLAG_GROUP_RX_TX_DEFAULT) < 0) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+				"WPA MLO: Failed to set GTK to the driver "
+				"(Group only)");
+			forced_memzero(gtk_buf, sizeof(gtk_buf));
+			return -1;
+		}
+	} else if (wpa_sm_mlo_set_key(sm, link_id, gd->alg,
+				      broadcast_ether_addr, gd->keyidx,
+				      gd->tx, key_rsc, gd->key_rsc_len, _gtk,
+				      gd->gtk_len, KEY_FLAG_GROUP_RX) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA MLO: Failed to set GTK to "
+			"the driver (alg=%d keylen=%d keyidx=%d)",
+			gd->alg, gd->gtk_len, gd->keyidx);
+		forced_memzero(gtk_buf, sizeof(gtk_buf));
+		return -1;
+	}
+	forced_memzero(gtk_buf, sizeof(gtk_buf));
+
+	if (wnm_sleep) {
+		sm->links[link_id].gtk_wnm_sleep.gtk_len = gd->gtk_len;
+		os_memcpy(sm->links[link_id].gtk_wnm_sleep.gtk, gd->gtk,
+			  sm->links[link_id].gtk_wnm_sleep.gtk_len);
+	} else {
+		sm->links[link_id].gtk.gtk_len = gd->gtk_len;
+		os_memcpy(sm->links[link_id].gtk.gtk, gd->gtk,
+			  sm->links[link_id].gtk.gtk_len);
+	}
+
+	return 0;
+}
+
+
 static int wpa_supplicant_gtk_tx_bit_workaround(const struct wpa_sm *sm,
 						int tx)
 {
@@ -1251,6 +1321,82 @@  static int wpa_supplicant_rsc_relaxation(const struct wpa_sm *sm,
 }
 
 
+static int _wpa_supplicant_pairwise_mlo_gtk(struct wpa_sm *sm, u8 link_id,
+					    const u8 *gtk, size_t gtk_len,
+					    int key_info)
+{
+	struct wpa_gtk_data gd;
+	const u8 *key_rsc;
+
+	/*
+	 * MLO GTK KDE format:
+	 * KeyID[bits 0-1], Tx [bit 2], Reserved [bit 3], link id [4-7]
+	 * PN
+	 * GTK
+	 */
+
+	os_memset(&gd, 0, sizeof(gd));
+	wpa_hexdump_key(MSG_DEBUG, "MLO RSN: received GTK in pairwise handshake",
+			gtk, gtk_len);
+
+	if (gtk_len < 7 || gtk_len - 7 > sizeof(gd.gtk))
+		return -1;
+
+	gd.keyidx = gtk[0] & 0x3;
+	gtk += 1;
+	gtk_len -= 1;
+
+	key_rsc = gtk;
+
+	gtk += 6;
+	gtk_len -= 6;
+
+	os_memcpy(gd.gtk, gtk, gtk_len);
+	gd.gtk_len = gtk_len;
+
+	if (sm->group_cipher != WPA_CIPHER_GTK_NOT_USED &&
+	    (wpa_supplicant_check_group_cipher(sm, sm->group_cipher,
+					       gtk_len, gtk_len,
+					       &gd.key_rsc_len, &gd.alg) ||
+	     wpa_supplicant_install_mlo_gtk(sm, link_id, &gd, key_rsc, 0))) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"MLO RSN: Failed to install GTK");
+		forced_memzero(&gd, sizeof(gd));
+		return -1;
+	}
+	forced_memzero(&gd, sizeof(gd));
+
+	return 0;
+}
+
+
+static int wpa_supplicant_pairwise_mlo_gtk(struct wpa_sm *sm,
+					   const struct wpa_eapol_key *key,
+					   struct wpa_eapol_ie_parse *ie,
+					   int key_info)
+{
+	u8 i;
+
+	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
+		if (!(sm->valid_links & BIT(i)))
+			continue;
+
+		if (!ie->mlo_gtk[i]) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+				"MLO RSN: GTK not found for link ID %u", i);
+			return -1;
+		}
+
+		if (_wpa_supplicant_pairwise_mlo_gtk(sm, i, ie->mlo_gtk[i],
+						    ie->mlo_gtk_len[i],
+						    key_info))
+			return -1;
+	}
+
+	return 0;
+}
+
+
 static int wpa_supplicant_pairwise_gtk(struct wpa_sm *sm,
 				       const struct wpa_eapol_key *key,
 				       const u8 *gtk, size_t gtk_len,
@@ -1421,6 +1567,170 @@  static int wpa_supplicant_install_bigtk(struct wpa_sm *sm,
 	return 0;
 }
 
+static int wpa_supplicant_install_mlo_igtk(struct wpa_sm *sm, u8 link_id,
+					   const struct wpa_mlo_igtk_kde *igtk,
+					   int wnm_sleep)
+{
+	size_t len = wpa_cipher_key_len(sm->mgmt_group_cipher);
+	u16 keyidx = WPA_GET_LE16(igtk->hdr.keyid);
+
+	/* Detect possible key reinstallation */
+	if ((sm->links[link_id].igtk.igtk_len == len &&
+	     os_memcmp(sm->links[link_id].igtk.igtk, igtk->igtk,
+		       sm->links[link_id].igtk.igtk_len) == 0) ||
+	    (sm->links[link_id].igtk_wnm_sleep.igtk_len == len &&
+	     os_memcmp(sm->links[link_id].igtk_wnm_sleep.igtk, igtk->igtk,
+		       sm->links[link_id].igtk_wnm_sleep.igtk_len) == 0)) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"WPA: Not reinstalling already in-use IGTK to the driver (keyidx=%d)",
+			keyidx);
+		return  0;
+	}
+
+	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+		"WPA: IGTK keyid %d pn " COMPACT_MACSTR,
+		keyidx, MAC2STR(igtk->hdr.pn));
+	wpa_hexdump_key(MSG_DEBUG, "WPA: IGTK", igtk->igtk, len);
+	if (keyidx > 4095) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA: Invalid IGTK KeyID %d", keyidx);
+		return -1;
+	}
+	if (wpa_sm_mlo_set_key(sm, link_id,
+			       wpa_cipher_to_alg(sm->mgmt_group_cipher),
+			       broadcast_ether_addr, keyidx, 0, igtk->hdr.pn,
+			       sizeof(igtk->hdr.pn), igtk->igtk, len,
+			       KEY_FLAG_GROUP_RX) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA: Failed to configure IGTK to the driver");
+		return -1;
+	}
+
+	if (wnm_sleep) {
+		sm->links[link_id].igtk_wnm_sleep.igtk_len = len;
+		os_memcpy(sm->links[link_id].igtk_wnm_sleep.igtk,
+			  igtk->igtk,
+			  sm->links[link_id].igtk_wnm_sleep.igtk_len);
+	} else {
+		sm->links[link_id].igtk.igtk_len = len;
+		os_memcpy(sm->links[link_id].igtk.igtk, igtk->igtk,
+			  sm->links[link_id].igtk.igtk_len);
+	}
+
+	return 0;
+}
+
+
+static int
+wpa_supplicant_install_mlo_bigtk(struct wpa_sm *sm, u8 link_id,
+				 const struct wpa_mlo_bigtk_kde *bigtk,
+				 int wnm_sleep)
+{
+	size_t len = wpa_cipher_key_len(sm->mgmt_group_cipher);
+	u16 keyidx = WPA_GET_LE16(bigtk->hdr.keyid);
+
+	/* Detect possible key reinstallation */
+	if ((sm->links[link_id].bigtk.bigtk_len == len &&
+	     os_memcmp(sm->links[link_id].bigtk.bigtk, bigtk->bigtk,
+		       sm->links[link_id].bigtk.bigtk_len) == 0) ||
+	    (sm->links[link_id].bigtk_wnm_sleep.bigtk_len == len &&
+	     os_memcmp(sm->links[link_id].bigtk_wnm_sleep.bigtk, bigtk->bigtk,
+		       sm->links[link_id].bigtk_wnm_sleep.bigtk_len) == 0)) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"WPA: Not reinstalling already in-use BIGTK to the driver (keyidx=%d)",
+			keyidx);
+		return  0;
+	}
+
+	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+		"WPA: BIGTK keyid %d pn " COMPACT_MACSTR,
+		keyidx, MAC2STR(bigtk->hdr.pn));
+	wpa_hexdump_key(MSG_DEBUG, "WPA: BIGTK", bigtk->bigtk, len);
+	if (keyidx < 6 || keyidx > 7) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA: Invalid BIGTK KeyID %d", keyidx);
+		return -1;
+	}
+	if (wpa_sm_mlo_set_key(sm, link_id,
+			       wpa_cipher_to_alg(sm->mgmt_group_cipher),
+			       broadcast_ether_addr, keyidx, 0, bigtk->hdr.pn,
+			       sizeof(bigtk->hdr.pn), bigtk->bigtk, len,
+			       KEY_FLAG_GROUP_RX) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA: Failed to configure BIGTK to the driver");
+		return -1;
+	}
+
+	if (wnm_sleep) {
+		sm->links[link_id].bigtk_wnm_sleep.bigtk_len = len;
+		os_memcpy(sm->links[link_id].bigtk_wnm_sleep.bigtk,
+			  bigtk->bigtk,
+			  sm->links[link_id].bigtk_wnm_sleep.bigtk_len);
+	} else {
+		sm->links[link_id].bigtk.bigtk_len = len;
+		os_memcpy(sm->links[link_id].bigtk.bigtk, bigtk->bigtk,
+			  sm->links[link_id].bigtk.bigtk_len);
+	}
+
+	return 0;
+}
+
+
+static int _mlo_ieee80211w_set_keys(struct wpa_sm *sm, u8 link_id,
+				    struct wpa_eapol_ie_parse *ie)
+{
+	size_t len;
+
+	if (!wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) ||
+	    sm->mgmt_group_cipher == WPA_CIPHER_GTK_NOT_USED)
+		return 0;
+
+	if (ie->mlo_igtk[link_id]) {
+		const struct wpa_mlo_igtk_kde *igtk;
+
+		len = wpa_cipher_key_len(sm->mgmt_group_cipher);
+		if (ie->mlo_igtk_len[link_id] !=
+		    sizeof(struct wpa_mlo_igtk_hdr) + len)
+			return -1;
+
+		igtk = (const struct wpa_mlo_igtk_kde *) ie->mlo_igtk[link_id];
+		if (wpa_supplicant_install_mlo_igtk(sm, link_id, igtk, 0) < 0)
+			return -1;
+	}
+
+	if (ie->mlo_bigtk[link_id] && sm->beacon_prot) {
+		const struct wpa_mlo_bigtk_kde *bigtk;
+
+		len = wpa_cipher_key_len(sm->mgmt_group_cipher);
+		if (ie->mlo_bigtk_len[link_id] !=
+		    sizeof(struct wpa_mlo_bigtk_hdr) + len)
+			return -1;
+
+		bigtk = (const struct wpa_mlo_bigtk_kde *) ie->mlo_bigtk[link_id];
+		if (wpa_supplicant_install_mlo_bigtk(sm, link_id, bigtk, 0) < 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+
+static int mlo_ieee80211w_set_keys(struct wpa_sm *sm,
+				   struct wpa_eapol_ie_parse *ie)
+{
+	u8 i;
+
+	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
+		if (!(sm->valid_links & BIT(i)))
+			continue;
+
+		if (_mlo_ieee80211w_set_keys(sm, i, ie))
+			return -1;
+	}
+
+	return 0;
+}
+
 
 static int ieee80211w_set_keys(struct wpa_sm *sm,
 			       struct wpa_eapol_ie_parse *ie)
@@ -1782,6 +2092,162 @@  int wpa_supplicant_send_4_of_4(struct wpa_sm *sm, const unsigned char *dst,
 }
 
 
+static void wpa_supplicant_process_mlo_3_of_4(struct wpa_sm *sm,
+					      const struct wpa_eapol_key *key,
+					      u16 ver, const u8 *key_data,
+					      size_t key_data_len)
+{
+	u16 key_info, keylen;
+	struct wpa_eapol_ie_parse ie;
+
+	wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);
+	wpa_dbg(sm->ctx->msg_ctx, MSG_INFO, "WPA MLO: RX message 3 of 4-Way "
+		"Handshake from " MACSTR " (ver=%d)", MAC2STR(sm->bssid), ver);
+
+	key_info = WPA_GET_BE16(key->key_info);
+
+	wpa_hexdump(MSG_DEBUG, "WPA MLO: IE KeyData", key_data, key_data_len);
+	if (wpa_supplicant_parse_ies(key_data, key_data_len, &ie) < 0)
+		goto failed;
+
+	if (ie.mlo_gtk_found && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA MLO: GTK IE in unencrypted key data");
+		goto failed;
+	}
+	if (ie.mlo_igtk_found && !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA MLO: IGTK KDE in unencrypted key data");
+		goto failed;
+	}
+
+
+#ifdef CONFIG_IEEE80211R
+	if (wpa_key_mgmt_ft(sm->key_mgmt) &&
+	    wpa_supplicant_validate_ie_ft(sm, sm->bssid, &ie) < 0)
+		goto failed;
+#endif /* CONFIG_IEEE80211R */
+
+	if (os_memcmp(sm->anonce, key->key_nonce, WPA_NONCE_LEN) != 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA MLO: ANonce from message 1 of 4-Way Handshake "
+			"differs from 3 of 4-Way Handshake - drop packet (src="
+			MACSTR ")", MAC2STR(sm->bssid));
+		goto failed;
+	}
+
+	keylen = WPA_GET_BE16(key->key_length);
+	if (keylen != wpa_cipher_key_len(sm->pairwise_cipher)) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+			"WPA MLO: Invalid %s key length %d (src=" MACSTR
+			")", wpa_cipher_txt(sm->pairwise_cipher), keylen,
+			MAC2STR(sm->bssid));
+		goto failed;
+	}
+
+#ifdef CONFIG_OCV
+	if (wpa_sm_ocv_enabled(sm)) {
+		struct wpa_channel_info ci;
+
+		if (wpa_sm_channel_info(sm, &ci) != 0) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
+				"MLO: Failed to get channel info to validate received OCI in EAPOL-Key 3/4");
+			return;
+		}
+
+		if (ocv_verify_tx_params(ie.oci, ie.oci_len, &ci,
+					 channel_width_to_int(ci.chanwidth),
+					 ci.seg1_idx) != OCI_SUCCESS) {
+			wpa_msg(sm->ctx->msg_ctx, MSG_INFO, OCV_FAILURE
+				"addr=" MACSTR " frame=eapol-key-m3 error=%s",
+				MAC2STR(sm->bssid), ocv_errorstr);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
+
+	if (wpa_supplicant_send_4_of_4(sm, sm->bssid, key, ver, key_info,
+				       &sm->ptk) < 0)
+		goto failed;
+
+	/* SNonce was successfully used in msg 3/4, so mark it to be renewed
+	 * for the next 4-Way Handshake. If msg 3 is received again, the old
+	 * SNonce will still be used to avoid changing PTK. */
+	sm->renew_snonce = 1;
+
+	if (key_info & WPA_KEY_INFO_INSTALL) {
+		int res;
+
+		if (sm->use_ext_key_id)
+			res = wpa_supplicant_activate_ptk(sm);
+		else
+			res = wpa_supplicant_install_ptk(sm, key,
+							 KEY_FLAG_RX_TX);
+		if (res)
+			goto failed;
+	}
+
+	if (key_info & WPA_KEY_INFO_SECURE) {
+		wpa_sm_mlme_setprotection(
+			sm, sm->bssid, MLME_SETPROTECTION_PROTECT_TYPE_RX,
+			MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
+		eapol_sm_notify_portValid(sm->eapol, true);
+	}
+	wpa_sm_set_state(sm, WPA_GROUP_HANDSHAKE);
+
+	if (sm->group_cipher == WPA_CIPHER_GTK_NOT_USED) {
+		/* No GTK to be set to the driver */
+	} else if (!ie.mlo_gtk_found && sm->proto == WPA_PROTO_RSN) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"MLO RSN: No GTK KDE included in EAPOL-Key msg 3/4");
+		goto failed;
+	} else if (ie.mlo_gtk_found &&
+	    wpa_supplicant_pairwise_mlo_gtk(sm, key, &ie, key_info) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"MLO RSN: Failed to configure MLO GTKs");
+		goto failed;
+	}
+
+	if (mlo_ieee80211w_set_keys(sm, &ie) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"MLO RSN: Failed to configure IGTK");
+		goto failed;
+	}
+
+	if (sm->group_cipher == WPA_CIPHER_GTK_NOT_USED || ie.mlo_gtk_found)
+		wpa_supplicant_key_neg_complete(sm, sm->bssid,
+						key_info & WPA_KEY_INFO_SECURE);
+
+	if (ie.mlo_gtk_found)
+		wpa_sm_set_rekey_offload(sm);
+
+	/* Add PMKSA cache entry for Suite B AKMs here since PMKID can be
+	 * calculated only after KCK has been derived. Though, do not replace an
+	 * existing PMKSA entry after each 4-way handshake (i.e., new KCK/PMKID)
+	 * to avoid unnecessary changes of PMKID while continuing to use the
+	 * same PMK. */
+	if (sm->proto == WPA_PROTO_RSN && wpa_key_mgmt_suite_b(sm->key_mgmt) &&
+	    !sm->cur_pmksa) {
+		struct rsn_pmksa_cache_entry *sa;
+
+		sa = pmksa_cache_add(sm->pmksa, sm->pmk, sm->pmk_len, NULL,
+				     sm->ptk.kck, sm->ptk.kck_len,
+				     sm->bssid, sm->own_addr,
+				     sm->network_ctx, sm->key_mgmt, NULL);
+		if (!sm->cur_pmksa)
+			sm->cur_pmksa = sa;
+	}
+
+	if (ie.transition_disable)
+		wpa_sm_transition_disable(sm, ie.transition_disable[0]);
+	sm->msg_3_of_4_ok = 1;
+	return;
+
+failed:
+	wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
+}
+
+
 static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 					  const struct wpa_eapol_key *key,
 					  u16 ver, const u8 *key_data,
@@ -2844,8 +3310,13 @@  int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr,
 		if (key_info & (WPA_KEY_INFO_MIC |
 				WPA_KEY_INFO_ENCR_KEY_DATA)) {
 			/* 3/4 4-Way Handshake */
-			wpa_supplicant_process_3_of_4(sm, key, ver, key_data,
-						      key_data_len);
+			if (sm->valid_links)
+				wpa_supplicant_process_mlo_3_of_4(
+					sm, key, ver, key_data, key_data_len);
+			else
+				wpa_supplicant_process_3_of_4(sm, key, ver,
+							      key_data,
+							      key_data_len);
 		} else {
 			/* 1/4 4-Way Handshake */
 			wpa_supplicant_process_1_of_4(sm, src_addr, key,
@@ -3136,6 +3607,7 @@  void wpa_sm_deinit(struct wpa_sm *sm)
 void wpa_sm_notify_assoc(struct wpa_sm *sm, const u8 *bssid)
 {
 	int clear_keys = 1;
+	int i;
 
 	if (sm == NULL)
 		return;
@@ -3193,6 +3665,14 @@  void wpa_sm_notify_assoc(struct wpa_sm *sm, const u8 *bssid)
 		os_memset(&sm->igtk, 0, sizeof(sm->igtk));
 		os_memset(&sm->igtk_wnm_sleep, 0, sizeof(sm->igtk_wnm_sleep));
 		sm->tk_set = false;
+		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+			os_memset(&sm->links[i].gtk, 0, sizeof(sm->gtk));
+			os_memset(&sm->links[i].gtk_wnm_sleep, 0,
+				  sizeof(sm->gtk_wnm_sleep));
+			os_memset(&sm->links[i].igtk, 0, sizeof(sm->igtk));
+			os_memset(&sm->links[i].igtk_wnm_sleep, 0,
+				  sizeof(sm->igtk_wnm_sleep));
+		}
 	}
 
 #ifdef CONFIG_TDLS
@@ -4053,6 +4533,8 @@  struct rsn_pmksa_cache_entry * wpa_sm_pmksa_cache_get(struct wpa_sm *sm,
 
 void wpa_sm_drop_sa(struct wpa_sm *sm)
 {
+	int i;
+
 	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Clear old PMK and PTK");
 	sm->ptk_set = 0;
 	sm->tptk_set = 0;
@@ -4065,6 +4547,14 @@  void wpa_sm_drop_sa(struct wpa_sm *sm)
 	os_memset(&sm->gtk_wnm_sleep, 0, sizeof(sm->gtk_wnm_sleep));
 	os_memset(&sm->igtk, 0, sizeof(sm->igtk));
 	os_memset(&sm->igtk_wnm_sleep, 0, sizeof(sm->igtk_wnm_sleep));
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		os_memset(&sm->links[i].gtk, 0, sizeof(sm->gtk));
+		os_memset(&sm->links[i].gtk_wnm_sleep, 0,
+			  sizeof(sm->gtk_wnm_sleep));
+		os_memset(&sm->links[i].igtk, 0, sizeof(sm->igtk));
+		os_memset(&sm->links[i].igtk_wnm_sleep, 0,
+			  sizeof(sm->igtk_wnm_sleep));
+	}
 #ifdef CONFIG_IEEE80211R
 	os_memset(sm->xxkey, 0, sizeof(sm->xxkey));
 	sm->xxkey_len = 0;
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index f60616352..abac0a2d3 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -226,6 +226,12 @@  struct wpa_sm {
 		u8 bssid[ETH_ALEN];
 		u8 *ap_wpa_ie, *ap_rsn_ie, *ap_rsnxe;
 		size_t ap_wpa_ie_len, ap_rsn_ie_len, ap_rsnxe_len;
+		struct wpa_gtk gtk;
+		struct wpa_gtk gtk_wnm_sleep;
+		struct wpa_igtk igtk;
+		struct wpa_igtk igtk_wnm_sleep;
+		struct wpa_bigtk bigtk;
+		struct wpa_bigtk bigtk_wnm_sleep;
 	} links[MAX_NUM_MLD_LINKS];
 };