diff mbox series

[6/9] dpp-common: add TX/RX handlers to dpp_sm

Message ID 20240327151957.1446149-6-prestwoj@gmail.com (mailing list archive)
State New
Headers show
Series [1/9] dpp: prep for moving AAD within dpp_append_wrapped_data | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
prestwoj/iwd-ci-gitlint success GitLint

Commit Message

James Prestwood March 27, 2024, 3:19 p.m. UTC
This copies the bulk of the protocol handling from dpp.c into
dpp-common.c. There are no logical changes but some adaptations
needed to be made in order to isolate the encapsulating protocol
from the common protocol.

 - The DPP header is now reduced to not include the mmpdu header
   or catagory byte.
 - A channel switch must now notify the encapsulating protocol
   since DPP common has no concept of frequency/channels
 - When a peer is accepted its up to the encapsulating protocol
   to filter frames only from this peer for the remainder of the
   protocol. With PKEX the peer/own MACs actually do need to be
   communicated to dpp-common due to some of the key generation.
 - When a PKEX key is not set into the SM its up to the
   encapsulating protocol to provide the key after a
   PKEX_KEY_REQUESTED event. Once the key is obtained the SM
   can resume the PKEX protocol.

Other changes were made for readability. Sending/handler function
names were made consistent. The DPP config header is now built
and parsed with common functions (as opposed to manually). Extra
validation here and there was added as it was noticed (checks for
required attributes, return value checks, etc).
---
 src/dpp-common.c | 2246 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 2242 insertions(+), 4 deletions(-)

Comments

Denis Kenzior April 2, 2024, 3:27 p.m. UTC | #1
Hi James,

On 3/27/24 10:19, James Prestwood wrote:
> This copies the bulk of the protocol handling from dpp.c into
> dpp-common.c. There are no logical changes but some adaptations
> needed to be made in order to isolate the encapsulating protocol
> from the common protocol.
> 
>   - The DPP header is now reduced to not include the mmpdu header
>     or catagory byte.
>   - A channel switch must now notify the encapsulating protocol
>     since DPP common has no concept of frequency/channels
>   - When a peer is accepted its up to the encapsulating protocol
>     to filter frames only from this peer for the remainder of the
>     protocol. With PKEX the peer/own MACs actually do need to be
>     communicated to dpp-common due to some of the key generation.
>   - When a PKEX key is not set into the SM its up to the
>     encapsulating protocol to provide the key after a
>     PKEX_KEY_REQUESTED event. Once the key is obtained the SM
>     can resume the PKEX protocol.
>

There's a lot going on here, I think you're going to need to come up with a 
different strategy and break this patch up into digestible chunks.

> Other changes were made for readability. Sending/handler function
> names were made consistent. The DPP config header is now built
> and parsed with common functions (as opposed to manually). Extra
> validation here and there was added as it was noticed (checks for
> required attributes, return value checks, etc).

Much of this can likely be done in separate steps.

> ---
>   src/dpp-common.c | 2246 +++++++++++++++++++++++++++++++++++++++++++++-

I'm not taking or reviewing a 2kloc patch :)

>   1 file changed, 2242 insertions(+), 4 deletions(-)
> 

Regards,
-Denis
James Prestwood April 15, 2024, 2:05 p.m. UTC | #2
On 4/2/24 8:27 AM, Denis Kenzior wrote:
> Hi James,
>
> On 3/27/24 10:19, James Prestwood wrote:
>> This copies the bulk of the protocol handling from dpp.c into
>> dpp-common.c. There are no logical changes but some adaptations
>> needed to be made in order to isolate the encapsulating protocol
>> from the common protocol.
>>
>>   - The DPP header is now reduced to not include the mmpdu header
>>     or catagory byte.
>>   - A channel switch must now notify the encapsulating protocol
>>     since DPP common has no concept of frequency/channels
>>   - When a peer is accepted its up to the encapsulating protocol
>>     to filter frames only from this peer for the remainder of the
>>     protocol. With PKEX the peer/own MACs actually do need to be
>>     communicated to dpp-common due to some of the key generation.
>>   - When a PKEX key is not set into the SM its up to the
>>     encapsulating protocol to provide the key after a
>>     PKEX_KEY_REQUESTED event. Once the key is obtained the SM
>>     can resume the PKEX protocol.
>>
>
> There's a lot going on here, I think you're going to need to come up 
> with a different strategy and break this patch up into digestible chunks.
>
Sure, I tried, but its just a nasty build breaking change :)

I can try and make some of the improvements ahead of time, add frame 
handlers separately (auth/pkex/config/etc), then add the different events.

Thanks,

James
diff mbox series

Patch

diff --git a/src/dpp-common.c b/src/dpp-common.c
index 45099d12..2d4bbb31 100644
--- a/src/dpp-common.c
+++ b/src/dpp-common.c
@@ -145,6 +145,70 @@  struct dpp_sm {
 	bool initiator : 1;
 };
 
+/*
+ * Builds only the core DPP header. This is shared between DPP over 80211 and
+ * over TCP. Those protocols are required to encapsulate the core message as
+ * required by the spec.
+ */
+static size_t dpp_build_header(enum dpp_frame_type type, uint8_t *buf)
+{
+	uint8_t *ptr = buf;
+
+	*ptr++ = DPP_ACTION_VENDOR_SPECIFIC;
+	memcpy(ptr, wifi_alliance_oui, 3);
+	ptr += 3;
+	*ptr++ = 0x1a;			/* WiFi Alliance DPP OI type */
+	*ptr++ = 1;			/* Cryptosuite */
+	*ptr++ = type;
+
+	return ptr - buf;
+}
+
+/*
+ * For some reason the DPP spec decided to use GAS frame formats only for the
+ * configuration request and response. There is no explicit DPP frame type for
+ * these messages, hence the boolean 'request' parameter. The header will be
+ * formatted either for a configuration request or configuration response.
+ *
+ * See Section 8.3 DPP Generic Advertisement Service (GAS) frames
+ *
+ * As with the more generic header above, the specific protocol handlers will
+ * be required to add extra header information (80211 or TCP).
+ */
+static size_t dpp_build_config_header(bool request, uint8_t diag_token,
+					uint8_t *buf)
+{
+	uint8_t *ptr = buf;
+
+	*ptr++ = request ? DPP_ACTION_GAS_REQUEST: DPP_ACTION_GAS_RESPONSE;
+	*ptr++ = diag_token;
+
+	if (!request) {
+		/* Status */
+		l_put_le16(0, ptr);
+		ptr += 2;
+		/* Not fragmented */
+		l_put_le16(0, ptr);
+		ptr += 2;
+	}
+
+	*ptr++ = IE_TYPE_ADVERTISEMENT_PROTOCOL;
+	*ptr++ = 8; /* len */
+	if (request)
+		*ptr++ = 0x7f;
+	else
+		*ptr++ = 0x00;
+
+	*ptr++ = IE_TYPE_VENDOR_SPECIFIC;
+	*ptr++ = 5;
+	memcpy(ptr, wifi_alliance_oui, 3);
+	ptr += 3;
+	*ptr++ = 0x1a;
+	*ptr++ = 1;
+
+	return ptr - buf;
+}
+
 static void dpp_failed(struct dpp_sm *dpp)
 {
 	if (dpp->event_cb)
@@ -169,6 +233,503 @@  static void dpp_reset_protocol_timer(struct dpp_sm *dpp, uint64_t time)
 						dpp, NULL);
 }
 
+static bool dpp_check_roles(struct dpp_sm *dpp, uint8_t peer_capa)
+{
+	if (dpp->role == DPP_CAPABILITY_ENROLLEE &&
+			!(peer_capa & DPP_CAPABILITY_CONFIGURATOR))
+		return false;
+	else if (dpp->role == DPP_CAPABILITY_CONFIGURATOR &&
+			!(peer_capa & DPP_CAPABILITY_ENROLLEE))
+		return false;
+
+	return true;
+}
+
+static void dpp_try_write(struct dpp_sm *dpp, const uint8_t *frame,
+				size_t frame_len)
+{
+	if (!dpp->write) {
+		if (dpp->frame_pending) {
+			l_free(dpp->frame_pending);
+			dpp->frame_pending = NULL;
+		}
+
+		l_debug("No write handler, waiting to send");
+		dpp->frame_pending = l_memdup(frame, frame_len);
+		dpp->frame_len = frame_len;
+		return;
+	}
+
+	dpp->write(frame, frame_len, dpp->user_data);
+}
+
+static void dpp_send_presence(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 64];
+	uint8_t *ptr = frame;
+
+	ptr += dpp_build_header(DPP_FRAME_PRESENCE_ANNOUNCEMENT, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+				dpp->own_chirp_hash,
+				sizeof(dpp->own_chirp_hash));
+
+	dpp->state = DPP_STATE_PRESENCE;
+
+	l_debug("sending presence announcement");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_auth_request(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint8_t version = 2;
+
+	ptr += dpp_build_header(DPP_FRAME_AUTHENTICATION_REQUEST, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+				dpp->peer_boot_hash, 32);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_BOOT_KEY_HASH,
+				dpp->own_boot_hash, 32);
+	ptr += dpp_append_point(ptr, DPP_ATTR_INITIATOR_PROTOCOL_KEY,
+				dpp->own_proto_public);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
+
+	if (dpp->role == DPP_CAPABILITY_CONFIGURATOR && dpp->channel[0])
+		ptr += dpp_append_attr(ptr, DPP_ATTR_CHANNEL, dpp->channel, 2);
+
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->k1, dpp->key_len, 2,
+			DPP_ATTR_INITIATOR_NONCE, dpp->nonce_len, dpp->i_nonce,
+			DPP_ATTR_INITIATOR_CAPABILITIES,
+			(size_t) 1, &dpp->role);
+
+	dpp->state = DPP_STATE_AUTHENTICATING;
+
+	l_debug("sending auth request");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_auth_response(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 512];
+	uint8_t *ptr = frame;
+	uint8_t status = DPP_STATUS_OK;
+	uint8_t version = 2;
+	uint8_t wrapped2_plaintext[dpp->key_len + 4];
+	uint8_t wrapped2[dpp->key_len + 16 + 8];
+	size_t wrapped2_len;
+
+	ptr += dpp_build_header(DPP_FRAME_AUTHENTICATION_RESPONSE, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+				dpp->own_boot_hash, 32);
+	if (dpp->mutual_auth)
+		ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_BOOT_KEY_HASH,
+				dpp->peer_boot_hash, 32);
+	ptr += dpp_append_point(ptr, DPP_ATTR_RESPONDER_PROTOCOL_KEY,
+				dpp->own_proto_public);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
+
+	/* Wrap up secondary data (R-Auth) */
+	wrapped2_len = dpp_append_attr(wrapped2_plaintext,
+					DPP_ATTR_RESPONDER_AUTH_TAG,
+					dpp->auth_tag, dpp->key_len);
+	/*
+	 * "Invocations of AES-SIV in the DPP Authentication protocol that
+	 * produce ciphertext that is part of an additional AES-SIV invocation
+	 * do not use AAD; in other words, the number of AAD components is set
+	 * to zero."
+	 */
+	if (!aes_siv_encrypt(dpp->ke, dpp->key_len, wrapped2_plaintext,
+					dpp->key_len + 4, NULL, 0, wrapped2)) {
+		l_error("Failed to encrypt wrapped data");
+		return;
+	}
+
+	wrapped2_len += 16;
+
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->k2, dpp->key_len, 4,
+			DPP_ATTR_RESPONDER_NONCE, dpp->nonce_len, dpp->r_nonce,
+			DPP_ATTR_INITIATOR_NONCE, dpp->nonce_len, dpp->i_nonce,
+			DPP_ATTR_RESPONDER_CAPABILITIES, (size_t) 1, &dpp->role,
+			DPP_ATTR_WRAPPED_DATA, wrapped2_len, wrapped2);
+
+	l_debug("send auth response");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_auth_request_failed(struct dpp_sm *dpp,
+					enum dpp_status status,
+					void *k1)
+{
+	uint8_t frame[DPP_HDR_LEN + 128];
+	uint8_t *ptr = frame;
+	uint8_t version = 2;
+	uint8_t s = status;
+
+	ptr += dpp_build_header(DPP_FRAME_AUTHENTICATION_RESPONSE, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &s, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+				dpp->own_boot_hash, 32);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			k1, dpp->key_len, 2,
+			DPP_ATTR_INITIATOR_NONCE, dpp->nonce_len, dpp->i_nonce,
+			DPP_ATTR_RESPONDER_CAPABILITIES,
+			(size_t) 1, &dpp->role);
+
+	l_debug("sending auth request failed");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_auth_confirm(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint8_t zero = 0;
+
+	ptr += dpp_build_header(DPP_FRAME_AUTHENTICATION_CONFIRM, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &zero, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+					dpp->peer_boot_hash, 32);
+	if (dpp->mutual_auth)
+		ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_BOOT_KEY_HASH,
+					dpp->own_boot_hash, 32);
+
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->ke, dpp->key_len, 1,
+			DPP_ATTR_INITIATOR_AUTH_TAG, dpp->key_len,
+			dpp->auth_tag);
+
+	l_debug("sending auth confirm");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_config_request(struct dpp_sm *dpp)
+{
+	const char *json = "{\"name\":\"IWD\",\"wi-fi_tech\":\"infra\","
+				"\"netRole\":\"sta\"}";
+	size_t json_len = strlen(json);
+	uint8_t frame[DPP_HDR_LEN + 512];
+	uint8_t *ptr = frame;
+	uint8_t *lptr;
+
+	l_getrandom(&dpp->diag_token, 1);
+	l_getrandom(dpp->e_nonce, dpp->nonce_len);
+
+	ptr += dpp_build_config_header(true, dpp->diag_token, ptr);
+
+	/* Save length location */
+	lptr = ptr;
+	ptr += 2;
+
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->ke, dpp->key_len, 2,
+			DPP_ATTR_ENROLLEE_NONCE, dpp->nonce_len, dpp->e_nonce,
+			DPP_ATTR_CONFIGURATION_REQUEST, json_len, json);
+
+	l_put_le16(ptr - lptr - 2, lptr);
+
+	dpp->state = DPP_STATE_CONFIGURING;
+
+	l_debug("sending config request");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_config_response(struct dpp_sm *dpp, uint8_t status)
+{
+	_auto_(l_free) char *json = NULL;
+	uint8_t frame[512];
+	size_t json_len;
+	uint8_t *ptr = frame;
+	uint8_t *lptr;
+
+	ptr += dpp_build_config_header(false, dpp->diag_token, ptr);
+
+	/* Save length location */
+	lptr = ptr;
+	ptr += 2;
+
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
+
+	/*
+	 * There are several failure status codes that can be used (defined in
+	 * 6.4.3.1), each with their own set of attributes that should be
+	 * included. For now IWD's basic DPP implementation will assume
+	 * STATUS_CONFIGURE_FAILURE which only includes the E-Nonce.
+	 */
+	if (status == DPP_STATUS_OK) {
+		json = dpp_configuration_to_json(dpp->config);
+		json_len = strlen(json);
+
+		ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+						dpp->ke, dpp->key_len, 2,
+						DPP_ATTR_ENROLLEE_NONCE,
+						dpp->nonce_len, dpp->e_nonce,
+						DPP_ATTR_CONFIGURATION_OBJECT,
+						json_len, json);
+	} else
+		ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+						dpp->ke, dpp->key_len, 2,
+						DPP_ATTR_ENROLLEE_NONCE,
+						dpp->nonce_len, dpp->e_nonce);
+
+	l_put_le16(ptr - lptr - 2, lptr);
+
+	l_debug("sending config response");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_config_result(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint8_t zero = 0;
+
+	memset(frame, 0, sizeof(frame));
+
+	ptr += dpp_build_header(DPP_FRAME_CONFIGURATION_RESULT, ptr);
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->ke, dpp->key_len, 2,
+			DPP_ATTR_STATUS, (size_t) 1, &zero,
+			DPP_ATTR_ENROLLEE_NONCE, dpp->nonce_len, dpp->e_nonce);
+
+	l_debug("sending config result");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_pkex_exchange_request(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint16_t group;
+
+	memset(frame, 0, sizeof(frame));
+
+	l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group);
+
+	ptr += dpp_build_header(DPP_FRAME_PKEX_VERSION1_XCHG_REQUEST, ptr);
+
+	ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION,
+				&dpp->pkex_version, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_FINITE_CYCLIC_GROUP,
+				&group, 2);
+
+	if (dpp->pkex_id)
+		ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER,
+					dpp->pkex_id, strlen(dpp->pkex_id));
+
+	ptr += dpp_append_point(ptr, DPP_ATTR_ENCRYPTED_KEY, dpp->pkex_m);
+
+	dpp->state = DPP_STATE_PKEX_EXCHANGE;
+
+	l_debug("sending PKEX v1 exchange request");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_pkex_exchange_response(struct dpp_sm *dpp,
+						struct l_ecc_point *n)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint16_t group;
+	uint8_t status = DPP_STATUS_OK;
+
+	memset(frame, 0, sizeof(frame));
+
+	l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group);
+
+	ptr += dpp_build_header(DPP_FRAME_PKEX_XCHG_RESPONSE, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
+
+	if (dpp->pkex_id)
+		ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER,
+					dpp->pkex_id, strlen(dpp->pkex_id));
+
+	ptr += dpp_append_point(ptr, DPP_ATTR_ENCRYPTED_KEY, n);
+
+	dpp->state = DPP_STATE_PKEX_COMMIT_REVEAL;
+
+	l_debug("sending PKEX exchange response");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_pkex_bad_group(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint16_t group;
+	uint8_t status = DPP_STATUS_BAD_GROUP;
+
+	memset(frame, 0, sizeof(frame));
+
+	l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group);
+
+	ptr += dpp_build_header(DPP_FRAME_PKEX_XCHG_RESPONSE, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION,
+				&dpp->pkex_version, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_FINITE_CYCLIC_GROUP, &group, 2);
+
+	l_debug("sending PKEX bad group");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_pkex_bad_code(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 256];
+	uint8_t *ptr = frame;
+	uint8_t status = DPP_STATUS_BAD_CODE;
+
+	memset(frame, 0, sizeof(frame));
+
+	ptr += dpp_build_header(DPP_FRAME_PKEX_XCHG_RESPONSE, ptr);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
+	ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION,
+				&dpp->pkex_version, 1);
+	if (dpp->pkex_id)
+		ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER,
+					dpp->pkex_id, strlen(dpp->pkex_id));
+
+	l_debug("sending PKEX bad code");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_pkex_commit_reveal_request(struct dpp_sm *dpp)
+{
+	uint8_t frame[DPP_HDR_LEN + 512];
+	uint8_t *ptr = frame;
+	uint8_t a_pub[L_ECC_POINT_MAX_BYTES];
+	ssize_t a_len;
+
+	memset(frame, 0, sizeof(frame));
+
+	a_len = l_ecc_point_get_data(dpp->boot_public, a_pub, sizeof(a_pub));
+
+	ptr += dpp_build_header(DPP_FRAME_PKEX_COMMIT_REVEAL_REQUEST, ptr);
+
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->z, dpp->z_len, 2,
+			DPP_ATTR_BOOTSTRAPPING_KEY, a_len, a_pub,
+			DPP_ATTR_INITIATOR_AUTH_TAG, dpp->u_len, dpp->u);
+
+	dpp->state = DPP_STATE_PKEX_COMMIT_REVEAL;
+
+	l_debug("sending PKEX commit reveal request");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_send_pkex_commit_reveal_response(struct dpp_sm *dpp,
+						const uint8_t *v, size_t v_len)
+{
+	uint8_t frame[DPP_HDR_LEN + 512];
+	uint8_t *ptr = frame;
+	uint8_t b_pub[L_ECC_POINT_MAX_BYTES];
+	size_t b_len;
+
+	memset(frame, 0, sizeof(frame));
+
+	b_len = l_ecc_point_get_data(dpp->boot_public, b_pub, sizeof(b_pub));
+
+	ptr += dpp_build_header(DPP_FRAME_PKEX_COMMIT_REVEAL_RESPONSE, ptr);
+	ptr += dpp_append_wrapped_data(frame, sizeof(frame), ptr,
+			dpp->z, dpp->z_len, 2,
+			DPP_ATTR_BOOTSTRAPPING_KEY, b_len, b_pub,
+			DPP_ATTR_RESPONDER_AUTH_TAG, v_len, v);
+
+	l_debug("sending PKEX commit reveal response");
+
+	dpp_try_write(dpp, frame, ptr - frame);
+}
+
+static void dpp_process_pkex_exchange_request(struct dpp_sm *dpp,
+						struct l_ecc_point *m)
+{
+	_auto_(l_ecc_point_free) struct l_ecc_point *n = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *qi = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *k = NULL;
+
+	l_debug("");
+
+	/* Qi = H(MAC-Initiator | [identifier | ] code) * Pi */
+	qi = dpp_derive_qi(dpp->curve, dpp->pkex_key, dpp->pkex_id,
+				dpp->mac_initiator);
+	if (!qi) {
+		l_debug("could not derive Qi");
+		return;
+	}
+
+	/* X' = M - Qi */
+	dpp->y_or_x = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_inverse(qi);
+	l_ecc_point_add(dpp->y_or_x, m, qi);
+
+	/*
+	 * "The resulting ephemeral key, denoted X’, is checked whether it is
+	 * the point-at-infinity. If it is not valid, the protocol silently
+	 * fails"
+	 */
+	if (l_ecc_point_is_infinity(dpp->y_or_x)) {
+		l_debug("X' is at infinity, ignore message");
+		dpp_failed(dpp);
+		return;
+	}
+
+	qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id,
+				dpp->mac_responder);
+	if (!qr || l_ecc_point_is_infinity(qr)) {
+		l_debug("Qr did not derive");
+		l_ecc_point_free(dpp->y_or_x);
+		dpp->y_or_x = NULL;
+		goto bad_code;
+	}
+
+	/*
+	 * "The Responder then generates a random ephemeral keypair, y/Y,
+	 * encrypts Y with Qr to obtain the result, denoted N."
+	 */
+	l_ecdh_generate_key_pair(dpp->curve, &dpp->pkex_private,
+					&dpp->pkex_public);
+
+	/* N = Y + Qr */
+	n = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_add(n, dpp->pkex_public, qr);
+
+	/* K = y * X' */
+
+	k = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_multiply(k, dpp->pkex_private, dpp->y_or_x);
+
+	/* z = HKDF(<>, info | M.x | N.x | code, K.x) */
+	dpp_derive_z(dpp->mac_initiator, dpp->mac_responder, n, m, k,
+			dpp->pkex_key, dpp->pkex_id, dpp->z, &dpp->z_len);
+
+	dpp_send_pkex_exchange_response(dpp, n);
+
+	return;
+
+bad_code:
+	dpp_send_pkex_bad_code(dpp);
+}
+
 struct dpp_sm *dpp_sm_new(dpp_event_cb_t event,
 				const struct l_ecc_point *boot_public,
 				const struct l_ecc_scalar *boot_private,
@@ -201,7 +762,25 @@  void dpp_sm_set_write_handler(struct dpp_sm *dpp, dpp_write_cb_t write)
 	if (!dpp->write)
 		return;
 
-	/* Handle writing frame */
+	/*
+	 * Presence/exchange are special cases because 802.11 encapsulation
+	 * requires going off channel for specific amounts of time. We cannot
+	 * easily start a timer within the common code and try and remained
+	 * synced with the offchannel callbacks.
+	 * Instead let the offchannel callbacks dictate when these frames
+	 * are sent out. All other write operations are driven by the reception
+	 * of other frames.
+	 */
+	if (dpp->state == DPP_STATE_PRESENCE && !dpp->initiator)
+		dpp_send_presence(dpp);
+	else if ( dpp->state == DPP_STATE_PKEX_EXCHANGE && dpp->initiator)
+		dpp_send_pkex_exchange_request(dpp);
+	else if (dpp->frame_pending) {
+		dpp->write(dpp->frame_pending, dpp->frame_len, dpp->user_data);
+
+		l_free(dpp->frame_pending);
+		dpp->frame_pending = NULL;
+	}
 }
 
 static void dpp_free_auth_data(struct dpp_sm *dpp)
@@ -339,6 +918,9 @@  static bool dpp_start_authentication(struct dpp_sm *dpp)
 
 	dpp->state = DPP_STATE_AUTHENTICATING;
 
+	if (dpp->initiator)
+		dpp_send_auth_request(dpp);
+
 	return true;
 }
 
@@ -413,6 +995,16 @@  bool dpp_sm_start_responder(struct dpp_sm *dpp)
 					&dpp->own_proto_public);
 	dpp->initiator = false;
 
+	/*
+	 * An Enrollee acting in a Responder role uses DPP Presence
+	 * Announcement to signal a potential Configurator that it is
+	 * ready to engage in a DPP exchange
+	 */
+	if (dpp->role == DPP_CAPABILITY_ENROLLEE) {
+		if (!dpp->skip_presence)
+			dpp_send_presence(dpp);
+	}
+
 	dpp->state = DPP_STATE_PRESENCE;
 
 	return true;
@@ -426,6 +1018,10 @@  void dpp_sm_set_pkex_identifier(struct dpp_sm *dpp, const char *identifier)
 void dpp_sm_set_pkex_key(struct dpp_sm *dpp, const char *key)
 {
 	dpp->pkex_key = l_strdup(key);
+
+	/* This was set after the key was requested, send the response now */
+	if (dpp->state == DPP_STATE_PKEX_EXCHANGE)
+		dpp_process_pkex_exchange_request(dpp, dpp->peer_encr_key);
 }
 
 void dpp_sm_set_pkex_own_mac(struct dpp_sm *dpp, const uint8_t *mac)
@@ -488,7 +1084,7 @@  bool dpp_sm_pkex_start_initiator(struct dpp_sm *dpp)
 
 	dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT);
 
-	/* Send exchange request */
+	dpp_send_pkex_exchange_request(dpp);
 
 	return true;
 
@@ -512,7 +1108,1649 @@  bool dpp_sm_pkex_start_responder(struct dpp_sm *dpp)
 	return true;
 }
 
-void dpp_handle_rx(struct dpp_sm *dpp, const uint8_t *data, size_t len)
+static void dpp_handle_presence_announcement(struct dpp_sm *dpp,
+						const uint8_t *frame,
+						size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	const void *r_boot = NULL;
+	size_t r_boot_len = 0;
+	uint8_t hash[32];
+
+	l_debug("Presence announcement");
+
+	/* Must be a configurator, in an initiator role, in PRESENCE state */
+	if (dpp->state != DPP_STATE_PRESENCE)
+		return;
+
+	if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
+			r_boot = data;
+			r_boot_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!r_boot || r_boot_len != 32) {
+		l_debug("No responder boot hash");
+		return;
+	}
+
+	/* Hash what we have for the peer and check its our enrollee */
+	dpp_hash(L_CHECKSUM_SHA256, hash, 2, "chirp", strlen("chirp"),
+			dpp->peer_asn1, dpp->peer_asn1_len);
+
+	if (memcmp(hash, r_boot, sizeof(hash))) {
+		l_debug("Peers boot hash did not match");
+		return;
+	}
+
+	if (dpp->event_cb)
+		dpp->event_cb(DPP_EVENT_PEER_ACCEPTED, NULL, dpp->user_data);
+
+	dpp->state = DPP_STATE_AUTHENTICATING;
+
+	dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT);
+
+	dpp_start_authentication(dpp);
+}
+
+static void dpp_handle_auth_request(struct dpp_sm *dpp,
+					const uint8_t *frame, size_t frame_len)
 {
-	/* Handle frame */
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	const uint8_t *r_boot = NULL;
+	const uint8_t *i_boot = NULL;
+	const uint8_t *i_proto = NULL;
+	const void *wrapped = NULL;
+	const uint8_t *i_nonce = NULL;
+	uint8_t i_capa = 0;
+	size_t r_boot_len = 0, i_proto_len = 0, wrapped_len = 0;
+	size_t i_nonce_len = 0;
+	_auto_(l_free) uint8_t *unwrapped = NULL;
+	_auto_(l_ecc_scalar_free) struct l_ecc_scalar *m = NULL;
+	_auto_(l_ecc_scalar_free) struct l_ecc_scalar *n = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
+	struct l_ecc_point *bi = NULL;
+	uint64_t k1[L_ECC_MAX_DIGITS];
+	const void *ad0 = frame;
+	const void *ad1 = frame + 6;
+
+	if (dpp->state != DPP_STATE_PRESENCE &&
+				dpp->state != DPP_STATE_AUTHENTICATING)
+		return;
+
+	l_debug("authenticate request");
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_INITIATOR_BOOT_KEY_HASH:
+			i_boot = data;
+			/*
+			 * This attribute is required by the spec, but only
+			 * used for mutual authentication.
+			 */
+			break;
+		case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
+			r_boot = data;
+			r_boot_len = len;
+			break;
+		case DPP_ATTR_INITIATOR_PROTOCOL_KEY:
+			i_proto = data;
+			i_proto_len = len;
+			break;
+		case DPP_ATTR_WRAPPED_DATA:
+			/* I-Nonce/I-Capabilities part of wrapped data */
+			wrapped = data;
+			wrapped_len = len;
+			break;
+
+		/* Optional attributes */
+		case DPP_ATTR_PROTOCOL_VERSION:
+			if (l_get_u8(data) != 2) {
+				l_debug("Protocol version did not match");
+				return;
+			}
+
+			break;
+
+		case DPP_ATTR_CHANNEL:
+			if (len != 2)
+				return;
+
+			/*
+			 * Not part of the spec, but IWD puts a requirement on
+			 * enrollees that they must come to the configurators
+			 * channel to preserve performance
+			 */
+			if (dpp->role == DPP_CAPABILITY_CONFIGURATOR)
+				break;
+
+			if (dpp->event_cb)
+				dpp->event_cb(DPP_EVENT_CHANNEL_SWITCH, data,
+						dpp->user_data);
+
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!r_boot || !i_boot || !i_proto || !wrapped) {
+		l_debug("missing attributes");
+		goto auth_request_failed;
+	}
+
+	if (r_boot_len != 32 || memcmp(dpp->own_boot_hash,
+					r_boot, r_boot_len)) {
+		l_debug("Responder boot key hash failed to verify");
+		goto auth_request_failed;
+	}
+
+	dpp->peer_proto_public = l_ecc_point_from_data(dpp->curve,
+						L_ECC_POINT_TYPE_FULL,
+						i_proto, i_proto_len);
+	if (!dpp->peer_proto_public) {
+		l_debug("Initiators protocol key invalid");
+		goto auth_request_failed;
+	}
+
+	m = dpp_derive_k1(dpp->peer_proto_public, dpp->boot_private, k1);
+	if (!m) {
+		l_debug("deriving k1/m failed");
+		goto auth_request_failed;
+	}
+
+	unwrapped = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1,
+			k1, dpp->key_len, wrapped, wrapped_len, &wrapped_len);
+	if (!unwrapped) {
+		l_debug("failed to unwrap data");
+		goto auth_request_failed;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_INITIATOR_NONCE:
+			i_nonce = data;
+			i_nonce_len = len;
+			break;
+		case DPP_ATTR_INITIATOR_CAPABILITIES:
+			/*
+			 * "If the Responder is not capable of supporting the
+			 * role indicated by the Initiator, it shall respond
+			 * with a DPP Authentication Response frame indicating
+			 * failure by adding the DPP Status field set to
+			 * STATUS_NOT_COMPATIBLE"
+			 */
+			i_capa = l_get_u8(data);
+
+			if (!dpp_check_roles(dpp, i_capa)) {
+				l_debug("Peer does not support required role");
+				dpp_send_auth_request_failed(dpp,
+						DPP_STATUS_NOT_COMPATIBLE, k1);
+				goto auth_request_failed;
+			}
+
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (i_nonce_len != dpp->nonce_len) {
+		l_debug("I-Nonce has unexpected length %zu", i_nonce_len);
+		goto auth_request_failed;
+	}
+
+	memcpy(dpp->i_nonce, i_nonce, i_nonce_len);
+
+	if (dpp->mutual_auth) {
+		l = dpp_derive_lr(dpp->boot_private, dpp->proto_private,
+					dpp->peer_boot_public);
+		bi = dpp->peer_boot_public;
+	}
+
+	/* Derive keys k2, ke, and R-Auth for authentication response */
+
+	n = dpp_derive_k2(dpp->peer_proto_public, dpp->proto_private, dpp->k2);
+	if (!n) {
+		l_debug("deriving k2/n failed");
+		goto auth_request_failed;
+	}
+
+	l_getrandom(dpp->r_nonce, dpp->nonce_len);
+
+	if (!dpp_derive_ke(dpp->i_nonce, dpp->r_nonce, m, n, l, dpp->ke)) {
+		l_debug("deriving ke failed");
+		goto auth_request_failed;
+	}
+
+	if (!dpp_derive_r_auth(dpp->i_nonce, dpp->r_nonce, dpp->nonce_len,
+				dpp->peer_proto_public, dpp->own_proto_public,
+				bi, dpp->boot_public, dpp->auth_tag)) {
+		l_debug("deriving R_auth failed");
+		goto auth_request_failed;
+	}
+
+	dpp->state = DPP_STATE_AUTHENTICATING;
+
+	dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT);
+
+	dpp_send_auth_response(dpp);
+
+	return;
+
+auth_request_failed:
+	l_debug("handle auth request failed");
+	dpp->state = DPP_STATE_PRESENCE;
+	dpp_free_auth_data(dpp);
+}
+
+static void dpp_handle_auth_response(struct dpp_sm *dpp,
+					const uint8_t *frame, size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	int status = -1;
+	const void *r_boot_hash = NULL;
+	const void *r_proto = NULL;
+	size_t r_proto_len = 0;
+	const void *wrapped = NULL;
+	size_t wrapped_len;
+	_auto_(l_free) uint8_t *unwrapped1 = NULL;
+	_auto_(l_free) uint8_t *unwrapped2 = NULL;
+	const void *r_nonce = NULL;
+	const void *i_nonce = NULL;
+	const void *r_auth = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *r_proto_key = NULL;
+	_auto_(l_ecc_scalar_free) struct l_ecc_scalar *n = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
+	struct l_ecc_point *bi = NULL;
+	const void *ad0 = frame;
+	const void *ad1 = frame + 6;
+	uint64_t r_auth_derived[L_ECC_MAX_DIGITS];
+
+	l_debug("Authenticate response");
+
+	if (dpp->state != DPP_STATE_AUTHENTICATING)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_STATUS:
+			if (len != 1)
+				return;
+
+			status = l_get_u8(data);
+			break;
+		case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
+			r_boot_hash = data;
+			break;
+		case DPP_ATTR_RESPONDER_PROTOCOL_KEY:
+			r_proto = data;
+			r_proto_len = len;
+			break;
+		case DPP_ATTR_PROTOCOL_VERSION:
+			if (len != 1 || l_get_u8(data) != 2)
+				return;
+			break;
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (status != DPP_STATUS_OK || !r_boot_hash || !r_proto || !wrapped) {
+		l_debug("Auth response bad status or missing attributes");
+		return;
+	}
+
+	r_proto_key = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
+						r_proto, r_proto_len);
+	if (!r_proto_key) {
+		l_debug("Peers protocol key was invalid");
+		return;
+	}
+
+	n = dpp_derive_k2(r_proto_key, dpp->proto_private, dpp->k2);
+
+	unwrapped1 = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1, dpp->k2,
+					dpp->key_len, wrapped, wrapped_len,
+					&wrapped_len);
+	if (!unwrapped1) {
+		l_debug("Failed to unwrap primary data");
+		return;
+	}
+
+	wrapped = NULL;
+
+	dpp_attr_iter_init(&iter, unwrapped1, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_RESPONDER_NONCE:
+			if (len != dpp->nonce_len)
+				return;
+
+			r_nonce = data;
+			break;
+		case DPP_ATTR_INITIATOR_NONCE:
+			if (len != dpp->nonce_len)
+				return;
+
+			i_nonce = data;
+			break;
+		case DPP_ATTR_RESPONDER_CAPABILITIES:
+			break;
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!r_nonce || !i_nonce || !wrapped) {
+		l_debug("Wrapped data missing attributes");
+		return;
+	}
+
+	if (dpp->mutual_auth) {
+		l = dpp_derive_li(dpp->peer_boot_public, r_proto_key,
+					dpp->boot_private);
+		bi = dpp->boot_public;
+	}
+
+	if (!dpp_derive_ke(i_nonce, r_nonce, dpp->m, n, l, dpp->ke)) {
+		l_debug("Failed to derive ke");
+		return;
+	}
+
+	unwrapped2 = dpp_unwrap_attr(NULL, 0, NULL, 0, dpp->ke, dpp->key_len,
+					wrapped, wrapped_len, &wrapped_len);
+	if (!unwrapped2) {
+		l_debug("Failed to unwrap secondary data");
+		return;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped2, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_RESPONDER_AUTH_TAG:
+			if (len != dpp->key_len)
+				return;
+
+			r_auth = data;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!r_auth) {
+		l_debug("R-Auth was not in secondary wrapped data");
+		return;
+	}
+
+	if (!dpp_derive_r_auth(i_nonce, r_nonce, dpp->nonce_len,
+				dpp->own_proto_public, r_proto_key, bi,
+				dpp->peer_boot_public, r_auth_derived)) {
+		l_debug("Failed to derive r_auth");
+		return;
+	}
+
+	if (memcmp(r_auth, r_auth_derived, dpp->key_len)) {
+		l_debug("R-Auth did not verify");
+		return;
+	}
+
+	if (!dpp_derive_i_auth(r_nonce, i_nonce, dpp->nonce_len,
+				r_proto_key, dpp->own_proto_public,
+				dpp->peer_boot_public, bi, dpp->auth_tag)) {
+		l_debug("Could not derive I-Auth");
+		return;
+	}
+
+	dpp_send_auth_confirm(dpp);
+
+	if (dpp->role == DPP_CAPABILITY_ENROLLEE)
+		dpp_send_config_request(dpp);
+}
+
+static void dpp_handle_auth_confirm(struct dpp_sm *dpp,
+					const uint8_t *frame, size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	int status = -1;
+	const uint8_t *r_boot_hash = NULL;
+	const void *wrapped = NULL;
+	const uint8_t *i_auth = NULL;
+	size_t i_auth_len;
+	_auto_(l_free) uint8_t *unwrapped = NULL;
+	size_t wrapped_len = 0;
+	uint64_t i_auth_check[L_ECC_MAX_DIGITS];
+	const void *unwrap_key;
+	const void *ad0 = frame;
+	const void *ad1 = frame + 6;
+	struct l_ecc_point *bi = NULL;
+
+	if (dpp->state != DPP_STATE_AUTHENTICATING)
+		return;
+
+	l_debug("authenticate confirm");
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_STATUS:
+			status = l_get_u8(data);
+			break;
+		case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
+			r_boot_hash = data;
+			/*
+			 * Spec requires this, but does not mention if anything
+			 * is to be done with it.
+			 */
+			break;
+		case DPP_ATTR_INITIATOR_BOOT_KEY_HASH:
+			/* No mutual authentication */
+			break;
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!r_boot_hash || !wrapped) {
+		l_debug("Attributes missing from authenticate confirm");
+		return;
+	}
+
+	/*
+	 * "The Responder obtains the DPP Authentication Confirm frame and
+	 * checks the value of the DPP Status field. If the value of the DPP
+	 * Status field is STATUS_NOT_COMPATIBLE or STATUS_AUTH_FAILURE, the
+	 * Responder unwraps the wrapped data portion of the frame using k2"
+	 */
+	if (status == DPP_STATUS_OK)
+		unwrap_key = dpp->ke;
+	else if (status == DPP_STATUS_NOT_COMPATIBLE ||
+				status == DPP_STATUS_AUTH_FAILURE)
+		unwrap_key = dpp->k2;
+	else
+		goto auth_confirm_failed;
+
+	unwrapped = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1,
+			unwrap_key, dpp->key_len, wrapped, wrapped_len,
+			&wrapped_len);
+	if (!unwrapped)
+		goto auth_confirm_failed;
+
+	if (status != DPP_STATUS_OK) {
+		/*
+		 * "If unwrapping is successful, the Responder should generate
+		 * an alert indicating the reason for the protocol failure."
+		 */
+		l_debug("Authentication failed due to status %s",
+				status == DPP_STATUS_NOT_COMPATIBLE ?
+				"NOT_COMPATIBLE" : "AUTH_FAILURE");
+		goto auth_confirm_failed;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_INITIATOR_AUTH_TAG:
+			i_auth = data;
+			i_auth_len = len;
+			break;
+		case DPP_ATTR_RESPONDER_NONCE:
+			/* Only if error */
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!i_auth || i_auth_len != dpp->key_len) {
+		l_debug("I-Auth missing from wrapped data");
+		goto auth_confirm_failed;
+	}
+
+	if (dpp->mutual_auth)
+		bi = dpp->peer_boot_public;
+
+	dpp_derive_i_auth(dpp->r_nonce, dpp->i_nonce, dpp->nonce_len,
+				dpp->own_proto_public, dpp->peer_proto_public,
+				dpp->boot_public, bi, i_auth_check);
+
+	if (memcmp(i_auth, i_auth_check, i_auth_len)) {
+		l_error("I-Auth did not verify");
+		goto auth_confirm_failed;
+	}
+
+	l_debug("Authentication successful");
+
+	if (dpp->role == DPP_CAPABILITY_ENROLLEE)
+		dpp_send_config_request(dpp);
+
+	return;
+
+auth_confirm_failed:
+	dpp->state = DPP_STATE_PRESENCE;
+	dpp_free_auth_data(dpp);
+}
+
+/* Parses the config header (GAS) and returns the start of the payload */
+static const uint8_t *dpp_parse_config_header(uint8_t action,
+						const uint8_t *data, size_t len,
+						uint8_t *diag_token_out,
+						uint16_t *status_out,
+						uint16_t *comeback_out,
+						size_t *attr_len_out)
+{
+	uint8_t adv_protocol_id[] = { 0xDD, 0x05, 0x50, 0x6F,
+					0x9A, 0x1A, 0x01 };
+	uint8_t diag_token;
+	uint16_t status = 0;
+	uint16_t comeback = 0;
+	uint16_t attr_len;
+	const uint8_t *ptr = data;
+
+	if (len < 1)
+		return NULL;
+
+	diag_token = *ptr++;
+
+	switch (action) {
+	case DPP_ACTION_GAS_REQUEST:
+		if (len < 12)
+			return NULL;
+		break;
+	case DPP_ACTION_GAS_RESPONSE:
+		if (len < 14)
+			return NULL;
+
+		status = l_get_le16(ptr);
+		ptr += 2;
+		comeback = l_get_le16(ptr);
+		ptr += 2;
+
+		break;
+	default:
+		return NULL;
+	}
+
+	if (*ptr++ != IE_TYPE_ADVERTISEMENT_PROTOCOL)
+		return NULL;
+
+	/* Length of advertisement protocol fields */
+	if (*ptr++ != 0x08)
+		return NULL;
+	/*
+	 * Unfortunately wpa_supplicant hard codes 0x7f as the Query Response
+	 * Info so we need to handle both cases.
+	 */
+	if (*ptr != 0x7f && *ptr != 0x00)
+		return NULL;
+	ptr++;
+
+	if (memcmp(ptr, adv_protocol_id, sizeof(adv_protocol_id)))
+		return NULL;
+
+	ptr += sizeof(adv_protocol_id);
+
+	attr_len = l_get_le16(ptr);
+	ptr += 2;
+
+	/* Check the attribute length matches the total length */
+	if (attr_len > len - (ptr - data))
+		return false;
+
+	if (diag_token_out)
+		*diag_token_out = diag_token;
+
+	if (action == DPP_ACTION_GAS_RESPONSE) {
+		if (status_out)
+			*status_out = status;
+		if (comeback_out)
+			*comeback_out = comeback;
+	}
+
+	if (attr_len_out)
+		*attr_len_out = attr_len;
+
+	return ptr;
+}
+
+static void dpp_handle_config_request(struct dpp_sm *dpp, const uint8_t *frame,
+				size_t frame_len)
+{
+	const uint8_t *attrs;
+	size_t attrs_len;
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	const uint8_t *data;
+	size_t len;
+	const char *json = NULL;
+	size_t json_len = 0;
+	struct json_contents *c;
+	const uint8_t *wrapped = NULL;
+	const uint8_t *e_nonce = NULL;
+	size_t wrapped_len = 0;
+	_auto_(l_free) uint8_t *unwrapped = NULL;
+	struct json_iter jsiter;
+	_auto_(l_free) char *tech = NULL;
+	_auto_(l_free) char *role = NULL;
+
+	attrs = dpp_parse_config_header(DPP_ACTION_GAS_REQUEST, frame,
+					frame_len, &dpp->diag_token,
+					NULL, NULL, &attrs_len);
+	if (!attrs)
+		return;
+
+	if (dpp->state != DPP_STATE_AUTHENTICATING) {
+		l_debug("Configuration request in wrong state");
+		return;
+	}
+
+	if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
+		return;
+
+	dpp_attr_iter_init(&iter, attrs, attrs_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			/* Wrapped data should be only attribute */
+			return;
+		}
+	}
+
+	if (!wrapped) {
+		l_debug("Wrapped data missing");
+		return;
+	}
+
+	unwrapped = dpp_unwrap_attr(NULL, 0, NULL, 0, dpp->ke,
+					dpp->key_len, wrapped, wrapped_len,
+					&wrapped_len);
+	if (!unwrapped) {
+		l_debug("Failed to unwrap");
+		return;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_ENROLLEE_NONCE:
+			if (len != dpp->nonce_len)
+				break;
+
+			e_nonce = data;
+			break;
+		case DPP_ATTR_CONFIGURATION_REQUEST:
+			json = (const char *)data;
+			json_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!json || !e_nonce) {
+		l_debug("No configuration object in response");
+		return;
+	}
+
+	c = json_contents_new(json, json_len);
+	if (!c) {
+		json_contents_free(c);
+		return;
+	}
+
+	json_iter_init(&jsiter, c);
+
+	/*
+	 * Check mandatory values (Table 7). There isn't much that can be done
+	 * with these, but the spec requires they be included.
+	 */
+	if (!json_iter_parse(&jsiter,
+			JSON_MANDATORY("name", JSON_STRING, NULL),
+			JSON_MANDATORY("wi-fi_tech", JSON_STRING, &tech),
+			JSON_MANDATORY("netRole", JSON_STRING, &role),
+			JSON_UNDEFINED))
+		goto configure_failure;
+
+	if (strcmp(tech, "infra"))
+		goto configure_failure;
+
+	if (strcmp(role, "sta"))
+		goto configure_failure;
+
+	json_contents_free(c);
+
+	memcpy(dpp->e_nonce, e_nonce, dpp->nonce_len);
+
+	dpp->state = DPP_STATE_CONFIGURING;
+
+	dpp_send_config_response(dpp, DPP_STATUS_OK);
+
+	return;
+
+configure_failure:
+	dpp_send_config_response(dpp, DPP_STATUS_CONFIGURE_FAILURE);
+	/*
+	 * The other peer is still authenticated, and can potentially send
+	 * additional requests so keep this session alive.
+	 */
+}
+
+static void dpp_handle_config_response(struct dpp_sm *dpp, const uint8_t *frame,
+					size_t frame_len)
+{
+	const uint8_t *attrs;
+	size_t attrs_len;
+	uint16_t status;
+	uint8_t diag_token;
+	uint16_t comeback;
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	const char *json = NULL;
+	size_t json_len = 0;
+	int dstatus = -1;
+	const uint8_t *wrapped = NULL;
+	const uint8_t *e_nonce = NULL;
+	size_t wrapped_len = 0;
+	_auto_(l_free) uint8_t *unwrapped = NULL;
+
+	l_debug("config response");
+
+	attrs = dpp_parse_config_header(DPP_ACTION_GAS_RESPONSE, frame,
+					frame_len, &diag_token, &status,
+					&comeback, &attrs_len);
+	if (!attrs || diag_token != dpp->diag_token || status != 0) {
+		l_debug("failed to parse header");
+		return;
+	}
+
+	/* TODO: comeback delay */
+	if (comeback != 0) {
+		l_debug("comeback not zero");
+		return;
+	}
+
+	if (dpp->state != DPP_STATE_CONFIGURING) {
+		l_debug("state not configuring");
+		return;
+	}
+
+
+	dpp_attr_iter_init(&iter, attrs, attrs_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_STATUS:
+			dstatus = l_get_u8(data);
+			break;
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			/*
+			 * TODO: CSR Attribute
+			 */
+			break;
+		}
+	}
+
+	if (dstatus != DPP_STATUS_OK || !wrapped) {
+		l_debug("Bad status or missing attributes");
+		return;
+	}
+
+	unwrapped = dpp_unwrap_attr(attrs, wrapped - attrs - 4, NULL, 0, dpp->ke,
+					dpp->key_len, wrapped, wrapped_len,
+					&wrapped_len);
+	if (!unwrapped) {
+		l_debug("Failed to unwrap");
+		return;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_ENROLLEE_NONCE:
+			if (len != dpp->nonce_len)
+				break;
+
+			if (memcmp(data, dpp->e_nonce, dpp->nonce_len))
+				break;
+
+			e_nonce = data;
+			break;
+		case DPP_ATTR_CONFIGURATION_OBJECT:
+			json = (const char *)data;
+			json_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!json || !e_nonce) {
+		l_debug("No configuration object in response");
+		return;
+	}
+
+	dpp->config = dpp_parse_configuration_object(json, json_len);
+	if (!dpp->config) {
+		l_error("Configuration object did not parse");
+		return;
+	}
+
+	dpp_send_config_result(dpp);
+
+	l_timeout_remove(dpp->timeout);
+	dpp->timeout = NULL;
+
+	if (dpp->event_cb)
+		dpp->event_cb(DPP_EVENT_SUCCESS, NULL, dpp->user_data);
+}
+
+static void dpp_handle_config_result_frame(struct dpp_sm *dpp,
+						const uint8_t *frame,
+						size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	int status = -1;
+	const void *e_nonce = NULL;
+	const void *wrapped = NULL;
+	size_t wrapped_len;
+	_auto_(l_free) void *unwrapped = NULL;
+	const void *ad0 = frame;
+	const void *ad1 = frame + 6;
+
+	if (dpp->state != DPP_STATE_CONFIGURING)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			/* Wrapped data should be only attribute */
+			return;
+		}
+	}
+
+	if (!wrapped) {
+		l_debug("No wrapped data in config result");
+		return;
+	}
+
+	unwrapped = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1,
+					dpp->ke, dpp->key_len, wrapped,
+					wrapped_len, &wrapped_len);
+	if (!unwrapped) {
+		l_debug("Failed to unwrap DPP configuration result");
+		return;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_STATUS:
+			status = l_get_u8(data);
+			break;
+		case DPP_ATTR_ENROLLEE_NONCE:
+			e_nonce = data;
+			break;
+		default:
+			break;
+		}
+	}
+
+	l_timeout_remove(dpp->timeout);
+	dpp->timeout = NULL;
+
+	if (!dpp->event_cb)
+		return;
+
+	if (status != DPP_STATUS_OK || !e_nonce)
+		dpp_failed(dpp);
+	else
+		dpp->event_cb(DPP_EVENT_SUCCESS, NULL, dpp->user_data);
+}
+
+static void dpp_pkex_bad_group(struct dpp_sm *dpp, uint16_t group)
+{
+	uint16_t own_group = l_ecc_curve_get_ike_group(dpp->curve);
+
+	/*
+	 * TODO: The spec allows group negotiation, but it is not yet
+	 *       implemented.
+	 */
+	if (!group)
+		return;
+	/*
+	 * Section 5.6.2
+	 * "If the Responder's offered group offers less security
+	 * than the Initiator's offered group, then the Initiator should
+	 * ignore this message"
+	 */
+	if (group < own_group) {
+		l_debug("Offered group %u is less secure, ignoring",
+				group);
+		return;
+	}
+	/*
+	 * Section 5.6.2
+	 * "If the Responder's offered group offers equivalent or better
+	 * security than the Initiator's offered group, then the
+	 * Initiator may choose to abort its original request and try
+	 * another exchange using the group offered by the Responder"
+	 */
+	if (group >= own_group) {
+		l_debug("Offered group %u is the same or more secure, "
+			" but group negotiation is not supported", group);
+		return;
+	}
+}
+
+static void dpp_pkex_bad_code(struct dpp_sm *dpp)
+{
+	_auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL;
+
+	qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id,
+				dpp->mac_responder);
+	if (!qr || l_ecc_point_is_infinity(qr)) {
+		l_debug("Qr computed to zero, new code should be provisioned");
+		return;
+	}
+
+	l_debug("Qr computed successfully but responder indicated otherwise");
+}
+
+static void dpp_handle_pkex_exchange_response(struct dpp_sm *dpp,
+						const uint8_t *frame,
+						size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	const uint8_t *status = NULL;
+	uint8_t version = 0;
+	const void *identifier = NULL;
+	size_t identifier_len = 0;
+	const void *key = NULL;
+	size_t key_len = 0;
+	uint16_t group = 0;
+	_auto_(l_ecc_point_free) struct l_ecc_point *n = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *j = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *k = NULL;
+
+	l_debug("PKEX exchange response");
+
+	if (dpp->state != DPP_STATE_PKEX_EXCHANGE)
+		return;
+
+	if (dpp->role != DPP_CAPABILITY_ENROLLEE)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_STATUS:
+			if (len != 1)
+				return;
+
+			status = data;
+			break;
+		case DPP_ATTR_PROTOCOL_VERSION:
+			if (len != 1)
+				return;
+
+			version = l_get_u8(data);
+			break;
+		case DPP_ATTR_CODE_IDENTIFIER:
+			identifier = data;
+			identifier_len = len;
+			break;
+		case DPP_ATTR_ENCRYPTED_KEY:
+			if (len != dpp->key_len * 2)
+				return;
+
+			key = data;
+			key_len = len;
+			break;
+		case DPP_ATTR_FINITE_CYCLIC_GROUP:
+			if (len != 2)
+				return;
+
+			group = l_get_le16(data);
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!status) {
+		l_debug("No status attribute, ignoring");
+		return;
+	}
+
+	if (!key) {
+		l_debug("No encrypted key, ignoring");
+		return;
+	}
+
+	if (*status != DPP_STATUS_OK)
+		goto handle_status;
+
+	if (dpp->pkex_id) {
+		if (!identifier || identifier_len != strlen(dpp->pkex_id) ||
+					memcmp(dpp->pkex_id, identifier,
+						identifier_len)) {
+			l_debug("mismatch identifier, ignoring");
+			return;
+		}
+	}
+
+	if (version && version != dpp->pkex_version) {
+		l_debug("PKEX version does not match, igoring");
+		return;
+	}
+
+	n = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
+					key, key_len);
+	if (!n) {
+		l_debug("failed to parse peers encrypted key");
+		goto failed;
+	}
+
+	/*
+	 * TODO: PKEX protocol version 2 does not require the initiator or
+	 *       responder MAC addresses. If using protocol version 2 there will
+	 *       be no requirement for the encapsulating protocol to set this.
+	 *
+	 * Until then (and always for protocol 1) the encapsulating protocol
+	 * must set the peers address in the PEER_ACCEPTED callback for PKEX.
+	 */
+	if (dpp->event_cb)
+		dpp->event_cb(DPP_EVENT_PEER_ACCEPTED, NULL, dpp->user_data);
+
+	if (L_WARN_ON(!dpp->mac_responder))
+		goto failed;
+
+	qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id,
+				dpp->mac_responder);
+	if (!qr)
+		goto failed;
+
+	dpp->y_or_x = l_ecc_point_new(dpp->curve);
+
+	/* Y' = N - Qr */
+	l_ecc_point_inverse(qr);
+	l_ecc_point_add(dpp->y_or_x, n, qr);
+
+	/*
+	 * "The resulting ephemeral key, denoted Y’, is then checked whether it
+	 * is the point-at-infinity. If it is not valid, the protocol ends
+	 * unsuccessfully"
+	 */
+	if (l_ecc_point_is_infinity(dpp->y_or_x)) {
+		l_debug("Y' computed to infinity, failing");
+		goto failed;
+	}
+
+	k = l_ecc_point_new(dpp->curve);
+
+	/* K = Y' * x */
+	l_ecc_point_multiply(k, dpp->pkex_private, dpp->y_or_x);
+
+	dpp_derive_z(dpp->mac_initiator, dpp->mac_responder, n, dpp->pkex_m, k,
+				dpp->pkex_key, dpp->pkex_id,
+				dpp->z, &dpp->z_len);
+
+	/* J = a * Y' */
+	j = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_multiply(j, dpp->boot_private, dpp->y_or_x);
+
+	if (!dpp_derive_u(j, dpp->mac_initiator, dpp->boot_public, dpp->y_or_x,
+				dpp->pkex_public, dpp->u, &dpp->u_len)) {
+		l_debug("failed to compute u");
+		goto failed;
+	}
+
+	dpp_send_pkex_commit_reveal_request(dpp);
+
+	return;
+
+handle_status:
+	switch (*status) {
+	case DPP_STATUS_BAD_GROUP:
+		dpp_pkex_bad_group(dpp, group);
+		break;
+	case DPP_STATUS_BAD_CODE:
+		dpp_pkex_bad_code(dpp);
+		break;
+	default:
+		l_debug("Unhandled status %u", *status);
+		break;
+	}
+
+failed:
+	dpp_failed(dpp);
+}
+
+static void dpp_handle_pkex_v1_exchange_request(struct dpp_sm *dpp,
+					const uint8_t *frame, size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	uint8_t version = 0;
+	uint16_t group = 0;
+	const void *id = NULL;
+	size_t id_len = 0;
+	const void *key = NULL;
+	size_t key_len = 0;
+	_auto_(l_ecc_point_free) struct l_ecc_point *m = NULL;
+
+	l_debug("PKEX exchange request");
+
+	if (dpp->state != DPP_STATE_PKEX_EXCHANGE)
+		return;
+
+	if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_PROTOCOL_VERSION:
+			if (len != 1)
+				return;
+
+			version = l_get_u8(data);
+			break;
+		case DPP_ATTR_FINITE_CYCLIC_GROUP:
+			if (len != 2)
+				return;
+
+			group = l_get_le16(data);
+			break;
+		case DPP_ATTR_CODE_IDENTIFIER:
+			id = data;
+			id_len = len;
+			break;
+		case DPP_ATTR_ENCRYPTED_KEY:
+			key = data;
+			key_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!key || !group) {
+		l_debug("initiator did not provide group or key, ignoring");
+		return;
+	}
+
+	if (group != l_ecc_curve_get_ike_group(dpp->curve)) {
+		l_debug("initiator is not using the same group");
+		goto bad_group;
+	}
+
+	/*
+	 * If the group isn't the same the key length won't match, so check
+	 * this here after we've determined the groups are equal
+	 */
+	if (key_len != dpp->key_len * 2) {
+		l_debug("Unexpected encrypted key length");
+		return;
+	}
+
+	if (version && version != dpp->pkex_version) {
+		l_debug("initiator is not using the same version, ignoring");
+		return;
+	}
+
+	if (dpp->pkex_id) {
+		if (!id || id_len != strlen(dpp->pkex_id) ||
+				memcmp(dpp->pkex_id, id, id_len)) {
+			l_debug("mismatch identifier, ignoring");
+			return;
+		}
+	}
+
+	m = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
+					key, key_len);
+	if (!m) {
+		l_debug("could not parse key from initiator, ignoring");
+		return;
+	}
+
+	if (dpp->event_cb)
+		dpp->event_cb(DPP_EVENT_PEER_ACCEPTED, NULL, dpp->user_data);
+
+	if (!dpp->pkex_key) {
+		/*
+		 * "If an optional code identifier is used, it shall be a UTF-8
+		 *  string not greater than eighty (80) octets"
+		 */
+		if (!id || id_len > 80 || !l_utf8_validate(id, id_len, NULL)) {
+			l_debug("Configurator started with agent but enrollee "
+				"sent invalid or no identifier, ignoring");
+			return;
+		}
+
+		dpp->pkex_id = l_strndup(id, id_len);
+
+		if (dpp->event_cb)
+			dpp->event_cb(DPP_EVENT_PKEX_KEY_REQUESTED,
+					dpp->pkex_id, dpp->user_data);
+
+		/* Save the encrypted key/identifier for the agent callback */
+		dpp->peer_encr_key = l_steal_ptr(m);
+
+		return;
+	}
+
+	dpp_process_pkex_exchange_request(dpp, m);
+
+	return;
+
+bad_group:
+	dpp_send_pkex_bad_group(dpp);
+}
+
+static void dpp_handle_pkex_commit_reveal_request(struct dpp_sm *dpp,
+					const uint8_t *frame, size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	const void *wrapped = NULL;
+	size_t wrapped_len = 0;
+	_auto_(l_free) uint8_t *unwrapped = NULL;
+	size_t unwrapped_len;
+	uint8_t zero = 0;
+	const void *key = 0;
+	size_t key_len = 0;
+	const void *i_auth = NULL;
+	size_t i_auth_len = 0;
+	_auto_(l_ecc_point_free) struct l_ecc_point *j = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *peer_public = NULL;
+	uint8_t u[L_ECC_SCALAR_MAX_BYTES];
+	size_t u_len = 0;
+	uint8_t v[L_ECC_SCALAR_MAX_BYTES];
+	size_t v_len = 0;
+
+	l_debug("PKEX commit-reveal request");
+
+	if (dpp->state != DPP_STATE_PKEX_COMMIT_REVEAL)
+		return;
+
+	if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!wrapped) {
+		l_debug("No wrapped data");
+		return;
+	}
+
+	unwrapped = dpp_unwrap_attr(frame, 6, &zero, 1, dpp->z, dpp->z_len,
+					wrapped, wrapped_len, &unwrapped_len);
+	if (!unwrapped) {
+		l_debug("Failed to unwrap attributes");
+		return;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, unwrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_BOOTSTRAPPING_KEY:
+			if (len != dpp->key_len * 2)
+				return;
+
+			key = data;
+			key_len = len;
+			break;
+		case DPP_ATTR_INITIATOR_AUTH_TAG:
+			if (len != 32)
+				return;
+
+			i_auth = data;
+			i_auth_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!key || !i_auth) {
+		l_debug("missing attributes");
+		return;
+	}
+
+	peer_public = l_ecc_point_from_data(dpp->curve,
+					L_ECC_POINT_TYPE_FULL, key, key_len);
+	if (!peer_public) {
+		l_debug("peers boostrapping key did not validate");
+		goto failed;
+	}
+
+	/* J' = y * A' */
+	j = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_multiply(j, dpp->pkex_private, peer_public);
+
+	if (!dpp_derive_u(j, dpp->mac_initiator, peer_public,
+			dpp->pkex_public, dpp->y_or_x, u, &u_len)) {
+		l_debug("Failed to derive u");
+		goto failed;
+	}
+
+	if (memcmp(u, i_auth, i_auth_len)) {
+		l_debug("Initiator auth tag did not verify");
+		goto failed;
+	}
+
+	/* L' = x * B' */
+	l = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_multiply(l, dpp->boot_private, dpp->y_or_x);
+
+	if (!dpp_derive_v(l, dpp->mac_responder, dpp->boot_public, dpp->y_or_x,
+				dpp->pkex_public, v, &v_len)) {
+		l_debug("Failed to derive v");
+		goto failed;
+	}
+
+	dpp_sm_set_peer_bootstrap(dpp, peer_public);
+
+	dpp_send_pkex_commit_reveal_response(dpp, v, v_len);
+
+	dpp->mutual_auth = true;
+	dpp_start_authentication(dpp);
+
+	return;
+
+failed:
+	dpp_failed(dpp);
+}
+
+static void dpp_handle_pkex_commit_reveal_response(struct dpp_sm *dpp,
+					const uint8_t *frame, size_t frame_len)
+{
+	struct dpp_attr_iter iter;
+	enum dpp_attribute_type type;
+	size_t len;
+	const uint8_t *data;
+	const uint8_t *wrapped = NULL;
+	size_t wrapped_len = 0;
+	uint8_t one = 1;
+	_auto_(l_free) uint8_t *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	const uint8_t *boot_key = NULL;
+	size_t boot_key_len = 0;
+	const uint8_t *r_auth = NULL;
+	uint8_t v[L_ECC_SCALAR_MAX_BYTES];
+	size_t v_len;
+	_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
+	_auto_(l_ecc_point_free) struct l_ecc_point *peer_boot = NULL;
+
+	l_debug("PKEX commit reveal response");
+
+	if (dpp->state != DPP_STATE_PKEX_COMMIT_REVEAL)
+		return;
+
+	if (dpp->role != DPP_CAPABILITY_ENROLLEE)
+		return;
+
+	dpp_attr_iter_init(&iter, frame + 6, frame_len - 6);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_WRAPPED_DATA:
+			wrapped = data;
+			wrapped_len = len;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!wrapped) {
+		l_debug("No wrapped data");
+		return;
+	}
+
+	unwrapped = dpp_unwrap_attr(frame, 6, &one, 1, dpp->z, dpp->z_len,
+					wrapped, wrapped_len, &unwrapped_len);
+	if (!unwrapped) {
+		l_debug("Failed to unwrap Reveal-Commit message");
+		return;
+	}
+
+	dpp_attr_iter_init(&iter, unwrapped, unwrapped_len);
+
+	while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+		switch (type) {
+		case DPP_ATTR_BOOTSTRAPPING_KEY:
+			if (len != dpp->key_len * 2)
+				return;
+
+			boot_key = data;
+			boot_key_len = len;
+			break;
+		case DPP_ATTR_RESPONDER_AUTH_TAG:
+			if (len != 32)
+				return;
+
+			r_auth = data;
+			break;
+		default:
+			break;
+		}
+	}
+
+	peer_boot = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
+							boot_key, boot_key_len);
+	if (!peer_boot) {
+		l_debug("Peer public bootstrapping key was invalid");
+		goto failed;
+	}
+
+	/* L = b * X' */
+	l = l_ecc_point_new(dpp->curve);
+
+	l_ecc_point_multiply(l, dpp->pkex_private, peer_boot);
+
+	if (!dpp_derive_v(l, dpp->mac_responder, peer_boot,
+				dpp->pkex_public, dpp->y_or_x, v, &v_len)) {
+		l_debug("Failed to derive v");
+		goto failed;
+	}
+
+	if (memcmp(v, r_auth, v_len)) {
+		l_debug("Bootstrapping data did not verify");
+		goto failed;
+	}
+
+	dpp_sm_set_peer_bootstrap(dpp, peer_boot);
+
+	dpp->mutual_auth = true;
+	dpp_start_authentication(dpp);
+
+	return;
+
+failed:
+	dpp_failed(dpp);
+}
+
+static bool dpp_validate_header(const uint8_t *data, size_t len,
+					enum dpp_frame_type *type)
+{
+	/* Ensure header + message type */
+	if (len < 6)
+		return false;
+
+	if (memcmp(data, wifi_alliance_oui, 3))
+		return false;
+
+	/* WiFi Allicance DPP OI type */
+	if (data[3] != 0x1a)
+		return false;
+
+	/* Cryptosuite */
+	if (data[4] != 1)
+		return false;
+
+	if (type)
+		*type = data[5];
+
+	return true;
+}
+
+static void dpp_handle_frame(struct dpp_sm *dpp,
+				const uint8_t *data, size_t len)
+{
+	enum dpp_frame_type type;
+
+	if (!dpp_validate_header(data, len, &type)) {
+		l_debug("header did not validate");
+		return;
+	}
+
+	switch (type) {
+	case DPP_FRAME_AUTHENTICATION_REQUEST:
+		dpp_handle_auth_request(dpp, data, len);
+		break;
+	case DPP_FRAME_AUTHENTICATION_RESPONSE:
+		dpp_handle_auth_response(dpp, data, len);
+		break;
+	case DPP_FRAME_AUTHENTICATION_CONFIRM:
+		dpp_handle_auth_confirm(dpp, data, len);
+		break;
+	case DPP_FRAME_CONFIGURATION_RESULT:
+		dpp_handle_config_result_frame(dpp, data, len);
+		break;
+	case DPP_FRAME_PRESENCE_ANNOUNCEMENT:
+		dpp_handle_presence_announcement(dpp, data, len);
+		break;
+	case DPP_FRAME_PKEX_XCHG_RESPONSE:
+		dpp_handle_pkex_exchange_response(dpp, data, len);
+		break;
+	case DPP_FRAME_PKEX_COMMIT_REVEAL_RESPONSE:
+		dpp_handle_pkex_commit_reveal_response(dpp, data, len);
+		break;
+	case DPP_FRAME_PKEX_VERSION1_XCHG_REQUEST:
+		dpp_handle_pkex_v1_exchange_request(dpp, data, len);
+		break;
+	case DPP_FRAME_PKEX_COMMIT_REVEAL_REQUEST:
+		dpp_handle_pkex_commit_reveal_request(dpp, data, len);
+		break;
+	default:
+		l_debug("Unhandled DPP frame %u", type);
+		break;
+	}
+}
+
+void dpp_handle_rx(struct dpp_sm *dpp, const uint8_t *data, size_t len)
+{
+	if (len < 1)
+		return;
+
+	switch (*data) {
+	case DPP_ACTION_VENDOR_SPECIFIC:
+		dpp_handle_frame(dpp, data + 1, len - 1);
+		break;
+	/* For the GAS frames save the action byte to validate the frame */
+	case DPP_ACTION_GAS_REQUEST:
+		dpp_handle_config_request(dpp, data + 1, len - 1);
+		break;
+	case DPP_ACTION_GAS_RESPONSE:
+		dpp_handle_config_response(dpp, data + 1, len - 1);
+		break;
+	default:
+		break;
+	}
 }