Cumulative patch from commit 3d6953288b592704484864f41791f8c67ff9aa5a

3d6953288 Extend RESEND_* test commands to allow forcing plaintext TX
4be5bc98a DPP: Update AES-SIV AD for PKEX frames
dc4d271c6 DPP: Update AES-SIV AD for DPP Authentication frames
6338c99ef FILS: Send updated connection parameters to drivers if needed
d2ba0d719 Move assoc param setting into a helper function
084131c85 FILS: Allow eap_peer_get_erp_info() to be called without config
b0a21e228 FILS: Update replay counter from roam info
693eafb15 nl80211: Update FILS roam info from vendor roam event
3c67e977d nl80211: Add support to send updated connection parameters
cddfda789 Add attributes to support roam+auth vendor event for FILS
c0fe5f125 Clear BSSID information in supplicant state machine on disconnection
006fb845b nl80211: Use NL80211_BSS_LAST_SEEN_BOOTTIME if available
a6ea66530 Additional consistentcy checks for PTK component lengths
6f234c1e2 Optional AP side workaround for key reinstallation attacks
daa409608 Allow last (Re)Association Request frame to be replayed for testing
751f5b293 Allow EAPOL-Key Request to be sent through control interface
bb06748f4 Make last received ANonce available through control interface
143b81bad Allow arbitrary key configuration for testing
d8afdb210 Allow EAPOL-Key messages 1/4 and 3/4 to be retransmitted for testing
6bc2f00f4 Allow group key handshake message 1/2 to be retransmitted for testing
16579769f Add testing functionality for resetting PN/IPN for configured keys
b488a1294 Clear PMK length and check for this when deriving PTK
00583ef11 Add debug prints on PMK configuration in WPA supplicant
a00e946c1 WPA: Extra defense against PTK reinstalls in 4-way handshake
a0bf1b68c Remove all PeerKey functionality
e76085117 FILS: Do not allow multiple (Re)Association Response frames
2f1357fb6 FILS: Accept another (Re)Association Request frame during an association
df9490620 Add MGMT_TX_STATUS_PROCESS command for testing purposes
c53eb9461 OWE: Remove forgotten developer debug prints
a34ca59e4 SAE: Allow SAE password to be configured separately (STA)
2377c1cae SAE: Allow SAE password to be configured separately (AP)
c5aeb4343 P2P: Do not mark DFS channel as invalid if DFS is offloaded to driver
58efbcbcd DPP: Fix static analyzer warnings in key generation and JWK construction
f51609022 P2P: Prefer 5/60 GHz band over 2.4 GHz during GO configuration
91cc34bf3 OWE: Allow set of enabled DH groups to be limited on AP
265bda344 OWE: Allow DH Parameters element to be overridden for testing purposes
8c19ea3f2 DPP: Add the crypto suite field to the frames
c77e2ff09 DPP: Remove C-sign-key expiry
6254045a5 DPP: Explicitly delete the PKEX secret element K upon generation of z
0e6709a4e DPP: Rename PKEX secret element from Z to K
657317179 DPP: Verify that PKEX Qi is not the point-at-infinity
a89138818 OWE: Transition mode information based on BSS ifname
109704657 OWE: Support station SME-in-driver case
5a78c3619 OWE: PMKSA caching in station mode
d90f10fa4 OWE: PMKSA caching in AP mode
8b5579e17 DPP: Fix EAPOL-Key Key MIC calculation
ec9f48377 OWE: Support DH groups 20 (NIST P-384) and 21 (NIST P-521) in station
7a12edd16 OWE: Support DH groups 20 (NIST P-384) and 21 (NIST P-521) in AP mode
6c4726189 OWE: Extend shared helper functions to support other DH curves
d8c8d8575 OWE: Include RSNE in (Re)Association Response frame
e8b964901 OWE: Transition mode support on station side
675112df1 OWE: Set PMK length properly on supplicant side
ea079153f OWE: Add AP support for transition mode
4a3746341 hostapd: Update HE capabilities and HE operation definition
63bc0ab0e P2P: Allow GO to advertise Interworking element
3567641eb Add TX/RX rate info and signal strength into STA output
fa4b605a0 WPS: Do not increment wildcard_uuid when pin is locked
e37cea308 OCE: Update default scan IEs when OCE is enabled/disabled
ee522d27c Vendor flags for 11ax channel property flags for use with external ACS
61a56c148 Add group_mgmt network parameter for PMF cipher selection
0ad5893a2 PAE: Validate input before pointer
fd35ed5bb AP: Remove unneeded check for 'added_unassociated'
d55b17460 FILS: Vendor attribute to disable driver FILS features
a7297ae5c Fix hostapd debug messages on wpa_pairwise and rsn_pairwise parsing
29c940e7a TDLS: Update the comments related to TPK derivation
3de1566db FILS: Check req_ies for NULL pointer in hostapd_notif_assoc()
1c9663cf6 OpenSSL: Force RSA 3072-bit DH prime size limit for Suite B
2ed70c758 OpenSSL: Add option to disable ECDHE with Suite B RSA
4eb8cfe06 OpenSSL: Force RSA 3072-bit key size limit for Suite B
6418400db Add hostapd tls_flags parameter
60ed2f24e Suite B: Add tls_suiteb=1 parameter for RSA 3k key case
5030d7d9f DPP: Allow raw hex PSK to be used for legacy configuration
039ab15fd DPP: Add DPP-CONFOBJ-PASS/PSK events for hostapd legacy configuration
9824de57a Fix EAPOL-Key version check for a corner case with Suite B AKM
3c7863f81 wpa_supplicant: Support dynamic update of wowlan_triggers
4cada9dcc FILS: Add DHss into FILS-Key-Data derivation when using FILS SK+PFS
41b819148 FILS: Update PMKID derivation rules for ERP key hierarchy establishment
303113398 mesh: Move writing of mesh_rssi_threshold inside CONFIG_MESH
2efc67207 Fix RSN pre-authentication regression with pre-connection scan results
73b3de01c macsec_linux: Exit early when missing macsec kernel module
7612e65b9 mka: Add error handling for secy_init_macsec() calls
2c66c7d11 wpa_supplicant: Check length when building ext_capability in assoc_cb
fdbfb63e4 nl80211: Fix bridge name print while removing interface from bridge
333517ac1 crypto: Fix undefined behavior in random number generator
84fccc724 Send Client-Error when AT_KDF attributes from the server are incorrect
446600c35 Add AT_KDF attributes to Synchronization-Failure in EAP-AKA'
155bf1108 PMKSA: Fix use-after-free in pmksa_cache_clone_entry()
cdf250149 dbus: Add new interface property to get mesh group
190f6f117 dbus: Add new interface property to get connected mesh peers
a9de99b1c dbus: Add MeshPeerDisconnected signal
9b0701fbf dbus: Add MeshPeerConnected signal
a39b040b4 dbus: Add MeshGroupRemoved signal
89e9cd25d dbus: Add MeshGroupStarted signal
49e6a5553 FILS: Add a space before MAC address to a HLP debug message
359166ed2 Remove the completely unused FT parameters in driver association data
3db2a82df Add SHA-384 routines to libcrypto.a
a0f19e9c7 SAE: Allow commit fields to be overridden for testing purposes (STA)
3648d8a18 SAE: Allow commit fields to be overridden for testing purposes
e75335384 SAE: Add testing code for reflection attack
e61fea6b4 SAE: Fix PMKSA caching behavior in AP mode
a6f238f21 DPP: Add base64 dependency in makefiles
c2d4f2eb5 DPP: Derive PMKID using SHA256() for all curves
64a0a75b5 nl80211: Fix auth_alg selection with FILS in the connect command
7475e80f1 FILS: Fix wpa_supplicant AP build without CONFIG_IEEE80211W
85fd8263a DPP: Use Transaction ID in Peer Discovery Request/Response frames
a28675da2 hs20-osu-client: Fix build with new OpenSSL and BoringSSL
cf39475b4 Introduce QCA_NL80211_VENDOR_SUBCMD_HANG
17385fba2 tests: JSON module tests for additional array parsing
d4488b9da JSON: Fix parsing of arrays of numbers, strings, literals
a4bf00787 DPP: Remove devices object from the connector
e77d13ef9 QCA vendor attribute to configure beacon miss penalize count for BTC
7bd88aaf3 QCA vendor attribute to configure beacon miss count
505554bbf QCA vendor attribute to enable/disable scan
ae048257c WPS: Interpret zero length ap_pin hostapd.conf parameter as "unset"
2bdbace63 Remove some obsolete information from hostapd README file
b0fc2ef3a hw_features: Fix check of supported 802.11ac channel width
b5bf84ba3 WNM: Differentiate between WNM for station and for AP in build
922dcf1b4 RRM: Remove duplicate frequencies from beacon report scan request
705e2909c RRM: Send response when Beacon report request is not supported/refused
3756acfd4 RRM: Send Radio Measurement response when beacon report scan fails
b3c148e9f RRM: Send reject/refuse response only to unicast measurement request
51143af7e wpa_cli: Fix global control interface for STA-FIRST/STA-NEXT
809c67502 DPP: Fix build with OpenSSL 1.1.0
89971d8b1 OpenSSL: Clear default_passwd_cb more thoroughly
f665c93e1 OpenSSL: Fix private key password handling with OpenSSL >= 1.1.0f
2b9891bd6 OpenSSL: Add build option to select default ciphers
65833d71a OCE: Add hostapd mode OCE capability indication if enabled
332aadb8a STA: Add OCE capability indication attribute
fb718f94d nl80211: Check if driver supports OCE specific features
46b15e470 Add vendor flags for OCE feature support indication
b377ec258 FILS: Fix issuing FILS connect to a non-FILS AP in driver-FILS case
9f44f7f3b Introduce a vendor attribute to represent the PNO/EPNO Request ID
881a92e8b FILS: Fix compilation with CONFIG_NO_WPA
1f2ae8cff EAP-TTLS: Fix a memory leak on error paths
83e003a91 EAP-TTLS: Fix possible memory leak in eap_ttls_phase2_request_mschap()
422570eec MBO: Fix possible memory leak in anqp_send_req()
96e595a9f EAP-LEAP: Fix possible memory leak in eap_leap_process_request()
23eead4d7 RRM: Filter scan results by parent TSF only if driver supports it
3f8e3a548 ap: Fix invalid HT40 channel pair fallback
6d3e24d3e ap: Fix return value in hostapd_drv_switch_channel()
d02e4c8ac P2P: Clear get_pref_freq_list_override on P2P Device
bfbc41eac DPP: Fix compilation without openssl
cc6088463 P2P: Allow auto GO on DFS channels if driver supports this
fe3e0bac1 FILS: Advertize FILS capability based on driver capability
5579c11c3 Fix a typo in vendor attribute documentation
8b5ddda5f FILS: Add HLP support with driver-based AP SME
31ec556ce FILS: Fix the IP header protocol field in HLP DHCP response
b3e567c89 FILS: ERP-based PMKSA cache addition on AP
bfe448331 FILS: Fix a frame name in a debug print
6d49aeb76 MBO: Whitespace cleanup
f2cdb41b8 OCE: Define OCE attributes and other related macros
267fc0dd3 Add wpa_supplicant ctrl iface support to scan for a specific BSSID
f522bb237 DPP: Add DPP_CONFIGURATOR_SIGN to generate own connector
dc7fc09cc DPP: Add control interface commands into hostapd_cli
a86fb43ca DPP: DPP_BOOTSTRAP_INFO for hostapd
484788b87 DPP: Share bootstrap type to string helper function
623f95685 DPP: Allow wpa_cli DPP_CONFIGURATOR_ADD without arguments
888502325 Add new key_mgmt values for wpa_supplicant STATUS command
3a5954ef9 Add mgmt_group_cipher to wpa_supplicant STATUS command
90f837b0b Update default wpa_group_rekey to once-per-day when using CCMP/GCMP
787615b38 DPP: Set PMKSA expiration based on peer connector
6b140f0fa DPP: Update hostapd configurator parameters to match wpa_supplicant
2605405aa DPP: Configurator in hostapd
efeada91a DPP: PKEX in hostapd
6095b4790 DPP: Check JWS protected header alg against C-sign-key curve
b65b22d60 DPP: Configurator parameters in responder role
31f03cb00 DPP: Update JWS algorithm strings for Brainpool curves
e0d3d3fce DPP: Rename Brainpool curve names for JSON
500ed7f00 DPP: PKEX bootstrapping
b9d47b484 DPP: Add helper functions for running hash operations
0c7cf1f50 DPP: Increase hostapd_cli buffer limits
44d6b272c DPP: Fix configuration item list
43fbb8db5 DPP: More debug for own connector configuration errors
f1f4fa797 DPP: Fix JWK debug prints
b04854cef nl80211/MBO: Set temporary disallowed BSSID list to driver
2a71673e2 ERP: Derive ERP key only after successful EAP authentication
528b65578 Add Set Wi-Fi Configuration vendor attribute to configure LRO
944f359e1 Introduce a vendor command to specify the active Type Of Service
b6ea76425 nl80211: Make KCK attribute optional in rekey data
35f064212 DPP: Allow passphrase to be set for Configurator
68cb6dcec DPP: Allow SSID to be set for Configurator
186f20489 JSON: Fix \u escaping
a82349347 DPP: Add an example python script for QR Code operations
6a7182a9c DPP: Add DPP_BOOTSTRAP_INFO command
8528994e2 DPP: Automatic network profile creation
da143f7fb FILS: Fix EVENT_ASSOC processing checks for driver-SME
3c0daa13d Make wpa_config_read_blob() easier for static analyzers
a0d5c56f8 DPP: Network Introduction protocol for wpa_supplicant
4ff89c2eb DPP: Network Introduction protocol for hostapd
650a70a72 DPP: Network Introduction protocol
56c754958 DPP: AP parameters for DPP AKM
b979caae5 DPP: Network profile parameters for DPP AKM
0c52953b0 DPP: Allow PMKSA cache entries to be added through hostapd ctrl_iface
567da5bbd DPP: Add new AKM
9c2b8204e DPP: Integration for hostapd
9beb2892d DPP: Add wpa_cli commands for DPP operations
461d39af4 DPP: Configuration exchange
30d27b048 DPP: Authentication exchange
be27e185b DPP: Bootstrap information management
d4d76d983 Fix offchannel TX done handling for sequence of TX frames
00b02149e nl80211: Register to receive DPP Public Action frames
4e19eb88a tests: Module tests for JSON parser
005be3daa Add JavaScript Object Notation (JSON) parser (RFC7159)
5b52e1adc tests: Update base64 OOM test cases to match implementation changes
0ffdc8b19 Add base64url encoding/decoding per RFC 4648
77f273c82 Extend SHA-384 and SHA-512 support to match SHA-256
2c9d92497 P2P: Debug print P2P_FIND rejection reason
618aa2290 P2P: Fix p2p_in_provisioning clearing in failure case
b5db6e5dc eap_proxy: Support multiple SIMs in get_imsi()
002087651 eap_proxy: Build realm from IMSI for proxy based EAP methods
5e0c20ff3 nl80211: Do not notify interface as re-enabled if initialization fails
8696e6170 eap_proxy: Add support for deriving ERP information
115d5e222 hostapd: Fix handling a 20/40 BSS Coexistence Management frame
2d18ab408 Add a config parameter to exclude DFS channels from ACS
3c2bd55f0 P2P: wpas_p2p_select_go_freq() to check for supported pref_freq
8d968351a Interworking: Add NULL checking for EAP name in phase2/autheap parameter
9ddba3a30 Rename vendor attribute DISABLE_OFFCHANNEL to RESTRICT_OFFCHANNEL
d506c35ef Set Wi-Fi Configuration attribute to restrict offchannel operations
4aa329298 ERP: Do not generate ERP keys when domain name is not specified
34ee12c55 Do not flush PMKSA on bssid_hint change
290834df6 nl80211: Fix race condition in detecting MAC change
04f667fcd DFS: Allow switch to DFS channel after radar detection in ETSI
aa56e36d6 driver: Make DFS domain information available to core
e8e430fe7 Vendor attributes to retain connection on a roam request failure
43a356b26 Provide option to configure BSSID hint for a network
33117656e Define a QCA vendor attribute to update the listen interval
85cff4b0d OpenSSL: Try SHA256 hash for OCSP certificate matching
d264c2e39 HTTP (curl): Try SHA256 hash for OCSP certificate matching
95818ec17 Fix compiler warning with CONFIG_IEEE80211R no-CONFIG_FILS build
613639454 Make CONFIG_MACSEC depend on IEEE8021X_EAPOL
31a856a12 mesh: Make NL80211_MESHCONF_RSSI_THRESHOLD configurable
1f3c49d41 Fix 160 MHz opclass channel to frequency conversion
9f4947466 dbus: Add method to disable channel switching with a TDLS peer
2a57b4b82 dbus: Add method to enable channel switching with a TDLS peer
193950541 dbus: Add AbortScan method to abort ongoing scan
8fed47e01 FILS: Derive FT key hierarchy on authenticator side for FILS+FT
7d440a3bc FILS: Derive FT key hierarchy on supplicant side for FILS+FT
215eaa748 FILS: Implement FILS-FT derivation
80ddf5d99 FILS: Fix Key-Auth derivation for SK+PFS for authenticator side
e6b623133 FILS: Fix Key-Auth derivation for SK+PFS for supplicant side
4d0a61c50 FILS: Debug print inputs to Key-Auth derivation
be1ece46f wpa_supplicant: Add GET_CAPABILITY for P2P redirection
853cfa873 Detect endianness when building for RTEMS
35bb8a9a5 Android: Define CONFIG_TESTING_OPTIONS if enabled in config
178553b70 MBO: Add support to set ignore assoc disallow to driver
3a46cf93d FT: Add support for wildcard R0KH/R1KH
eefe86301 FT RRB: Add msg replay and msg delay protection
245fc96e5 FT: New RRB message format
50bd8e0a9 FT: Replace inter-AP protocol with use of OUI Extended Ethertype
f2a04874c MBO: Fix possible NULL pointer dereference on candidate handling
01dd2b105 ERP: Silence static analyzer warning
d912953e3 atheros: Get rid of static analyzer warnings on 0-length memcpy
470f08b4f Enable CONFIG_WNM=y automatically for CONFIG_MBO=y builds
f54114825 Fix GAS server ifdef block use
8b49b530b Fix CONFIG_INTERWORKING=y build without CONFIG_HS20=y
0661163ef Do not blacklist the current AP on DISABLE_NETWORK
ec27b04e6 hostapd: Select a valid secondary channel if both enabled
da6a28ba6 FILS: Specify if FILS HLP was sent in connect
a38090b16 FILS: Add HLP to Connect IEs
1e6780bda Allocate dynamic memory for connect IEs
9f894823f PAE: Silence static analyzer warning about NULL pointer dereference
9a72bfe9a Add control interface command to enable/disable roaming
fa61bff6a FILS: Handle authentication/association in partial driver AP SME
5cee22ca4 FILS: Make handle_auth_fils() re-usable for driver-based AP SME
5e5f8c816 FILS: Move authentication response handling into a helper function
b8a3453ac FILS: Pass only IE area to handle_auth_fils()
9392859d7 FILS: Move AssocResp construction to a helper function
bd5993532 FILS: Move Key Confirm element validation to a helper function
087631b98 FILS: Move Session element validation to a helper function
cc20edc9f FILS: Add FILS auth_alg to driver-based AP SME association handling
957bff83c FILS: Add driver-AP SME callback to set TK after association
8acbf85fa FILS: Add FILS AEAD parameters for sta_auth() calls
f46c154c5 atheros: Add FILS AAD parameters in sta_auth() handler
6b128fb2a driver: Move sta_auth() arguments to a struct
d7cff1d87 atheros: Enable raw management frame receive for FILS builds
2b7a8ec47 atheros: Read driver FILS capability
d5444aac4 FILS: Add FILS Indication element into Beacon/Probe Response template
8befe8a99 Define a QCA attribute to specify the PCL policy for external ACS
183d3924c WPS: Add option for using random UUID
b44d1efd2 FILS: Fix key info in GTK rekey EAPOL-Key msg 2/2
04243740c FILS: Fix GTK rekey by accepting EAPOL-Key msg 1/2 with FILS AKM
bbe7969d6 FILS: Update cache identifier on association
f705f41b7 FILS: Update PMKSA cache with FILS shared key offload
01ef320f1 FILS: Update ERP next sequence number with driver offload
5538fc930 FILS: Track completion with FILS shared key authentication offload
8b0a6dba8 FILS: Connect request for offloaded FILS shared key authentication
79f3121bb FILS: Set cache identifier in current PMKSA entry for driver-SME case
15def72fa ERP: External control of ERP key information
42e69bda2 FILS: Add support for Cache Identifier in add/remove PMKSA
061a3d3d5 nl80211: Add support for FILS Cache Identifier in add/remove_pmkid()
6fbb54140 driver: Move add_pmkid() and remove_pmkid() arguments into a struct
ad295f3b8 nl80211: Add support for FILS shared key offload
199eb3a4e FILS: Add support to write FILS key_mgmt values in network blocks
16217e13d QCA vendor commands and attributes for spectral scan
5db997e34 FILS: Add FTE into FILS Authentication frame from AP when using FILS+FT
af3e362fa FILS: Add MDE into Authentication frame for FILS+FT
5aa08153a FT: Add selection of FT+FILS AKMs
c10e0ccc9 Hide *PMKSA_ADD parameters from debug log
2971da270 P2P: Do not use wait_time for SD Response TX for last fragmentation
c5fee1604 FT: Schedule wpa_ft_rrb_rx() through eloop in intra-process communication
469677367 Sync with mac80211-next.git include/uapi/linux/nl80211.h
775e986d5 hostapd: Fix crash on consecutive channel switch failures
3d5f0e916 wpa_supplicant: Avoid associating to temp disabled SSID in ap_scan=2
fbba28f8c P2P: Suppress warning on non-P2P config
127595887 QCA vendor command: Add TA max duration attribute for OCB configure
2a9ec7c69 Define attributes for QCA vendor OCB commands
6a4363f5f MBO: Fix reject reason codes
0119d4424 FILS: Fix wpa_supplicant compilation errors
4cc6574d0 FILS: Fix fils_cache_id check
a5269dc20 wpa_helpers: Ignore link-local IPv4 address while waiting for DHCP
e2f00bb5f xml: Add Value node in TNDS node conversion for empty value case
a34317b52 GAS: Handle no-ACK TX status for GAS request frames
5db86df6a macsec_linux: Fix NULL pointer dereference on error cases
e50df5d2a mka: Fix use-after-free when transmit secure channels are deleted
529d6ed72 mka: Fix use-after-free when receive secure channels are deleted
6c2056abe QCA vendor attributes to extend antenna diversity functionality
b4ae5f04d Add vendor attribute to config propagation delay's absolute value
7f5f4e46a Fix QCA_ATTR_NUD_STATS_IS_DAD value
2c0ac6d61 P2P: Run full P2P_FIND scan after pending scan completes
d3bb082a7 P2P: Continue scanning specified channel with P2P_FIND freq argument
31e130f82 FILS: Add FILS-SK-PFS capability into "GET_CAPABILITY fils" command
649835167 FILS: Check FILS Indication element against local network profile
76e20f4fa FILS: Add FILS SK auth PFS support in STA mode
1764559ee FILS: Add FILS SK auth PFS support in AP mode
cad291d67 FILS: Define authentication algorithm for FILS SK auth with PFS
611523849 OWE: Add CONFIG_OWE=y build option
07a5fe823 OWE: Use AKM 00-0F-AC:11 style parameters for EAPOL-Key frames
ef9627cbc Print the algorithms used for EAPOL-Key professing in log
ef2383859 Return success/failure result from sha384_prf()
0a6147991 OWE: Process Diffie-Hellman Parameter element in STA mode
09368515d OWE: Process Diffie-Hellman Parameter element in AP mode
f9561868e OWE: Add driver capability flag for OWE AKM
a1ea1b452 OWE: Define and parse OWE AKM selector
9c7aac738 OWE: Define and parse Diffie-Hellman Parameter element
e73244c24 tests: Extract-and-Expand HKDF (RFC 5869)
4ec833daf Extend hmac_sha256_kdf() to support HKDF-Expand() as defined in RFC 5869
ae1ec1aaf OpenSSL: Add wrapper functions for ECDH
b07ff9cb0 wpa_supplicant: Allow disabling HT in AP mode without HT overrides
2124a615e wpa_supplicant: Allow explicit wide channel configuration for AP mode
57ee04dc7 wpa_cli: Execute action file in case of WPS_EVENT_TIMEOUT
6252b981d wpa_cli: Execute action file in case of WPS_EVENT_ACTIVE
acdf50219 WPS: Notify about WPS PBC event in Enrollee mode
15e5ee0b7 wpa_supplicant: events: Don't bounce timeout reason through a buffer
e97d15b73 wpa_cli: Update wnm_bss_query auto complete message
15ab61eda WNM: Add option to configure candidates for BTM query candidate list
e044a9d1e common: Add candidate list parsing helper function
13bf18eda WNM: Use a dynamically allocated buffer for BTM query and response
34f285190 MBO: Parse MBO ANQP-element on STA
941caed98 MBO: Add MBO ANQP-element processing on AP
8ecf2231f ANQP: Extend ANQP_GET command to request without IEEE 802.11 elements
2316cb358 MBO: Add option to add MBO query list to ANQP query
7cbb5f1a4 DFS: Handle pre-CAC expired event
62c8c7f72 nl80211: Handle pre-CAC expired event from the driver
caaaee072 Sync with mac80211-next.git include/uapi/linux/nl80211.h
3dcd735c1 DFS: Handle CAC completion event from other radio
d0330d57f nl80211: Add option to delay start of schedule scan plans
b696f791a RRM: Fix wpas_rrm_send_msr_report() loop handling
891aa65b8 RRM: Use dynamically allocated buffer for beacon report
a1f11e34c Use os_memdup()
dbdda355d Introduce os_memdup()
af8bc24da MBO: Add support for transition reject reason code
3ab484928 nl80211: Driver command for checking BTM accept/reject
23cddd751 wpa_supplicant: Fix non_pref_chan example
b9fd3c244 tests: Add TEST_FAIL() to radius_msg_add_attr()
de01f254a RADIUS server: Fix error paths in new session creation
4c803dfcd ACS: Fix memory leak if interface is disabled during scan
29be2c090 ACS: Simplify code paths
fa07d2d46 tests: Add TEST_FAIL() checks in l2_packet
d4359923e Fix DHCP/NDISC snoop deinit followed by failing re-init
160dca078 Add QCA vendor command/attr for BRP antenna limit control
57d3c5913 Clear scan_res_handler on no-retry failure
e9518ae74 WFD: Add WFD R2 Subelements
21ac78279 QCA nl80211 vendor attribute for specific sub-20 MHz channel width
ff936bc75 Make the third octet of Country String configurable
511831983 trace: Look up start to cope with ASLR
206516e8c af_alg: Crypto wrappers for Linux kernel crypto (AF_ALG)
b41d3e0a7 crypto: Process des_encrypt() error returns in callers
5f0e165e8 crypto: Add return value to DES and AES encrypt/decrypt
dca4b503f MBO: Fix minimum length check on non_pref_chan configuration
5b9f46df0 hostapd: Get channel number from frequency based on other modes as well
4c8836f13 FILS: Fix fils_hlp.c build with older netinet/udp.h definitions
c4bb39707 Fix AES-SIV build dependencies
694a3a0d6 mesh: Fix CONFIG_MESH=y build without CONFIG_IEEE80211W=y
88a447556 Fix SELECT_NETWORK freq parameter
d02989f2e D-Bus: Notify mesh capability if driver supports it
57a2aacab Add option to disable broadcast deauth in hostapd on AP start/stop
21ed24f5a hostapd: Fix potential mesh-related change from impacting non-mesh cases
b7286c1b5 FILS: External management of PMKSA cache entry with Cache Identifier
869af3072 FILS: Use FILS Cache Identifier to extend PMKSA applicability
6aea02e57 SME: Clear portValid on starting authentication to fix FILS
ba9774bd7 FILS: Fix BSSID in reassociation case
7eace3787 FILS: Find PMKSA cache entries on AP based on FILS Cache Identifier

Bug: 68042382
Test: Device boots up and connects to wifi networks.
Test: Regression tests.

Change-Id: I6710d39e00c489288f8afe855868ad28aeba0100
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/src/common/dpp.c b/src/common/dpp.c
new file mode 100644
index 0000000..5107950
--- /dev/null
+++ b/src/common/dpp.c
@@ -0,0 +1,5998 @@
+/*
+ * DPP functionality shared between hostapd and wpa_supplicant
+ * Copyright (c) 2017, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include <openssl/opensslv.h>
+#include <openssl/err.h>
+
+#include "utils/common.h"
+#include "utils/base64.h"
+#include "utils/json.h"
+#include "common/ieee802_11_common.h"
+#include "common/ieee802_11_defs.h"
+#include "common/wpa_ctrl.h"
+#include "crypto/crypto.h"
+#include "crypto/random.h"
+#include "crypto/aes.h"
+#include "crypto/aes_siv.h"
+#include "crypto/sha384.h"
+#include "crypto/sha512.h"
+#include "dpp.h"
+
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+/* Compatibility wrappers for older versions. */
+
+static int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+{
+	sig->r = r;
+	sig->s = s;
+	return 1;
+}
+
+
+static void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr,
+			   const BIGNUM **ps)
+{
+	if (pr)
+		*pr = sig->r;
+	if (ps)
+		*ps = sig->s;
+}
+
+#endif
+
+
+static const struct dpp_curve_params dpp_curves[] = {
+	/* The mandatory to support and the default NIST P-256 curve needs to
+	 * be the first entry on this list. */
+	{ "prime256v1", 32, 32, 16, 32, "P-256", 19, "ES256" },
+	{ "secp384r1", 48, 48, 24, 48, "P-384", 20, "ES384" },
+	{ "secp521r1", 64, 64, 32, 66, "P-521", 21, "ES512" },
+	{ "brainpoolP256r1", 32, 32, 16, 32, "BP-256", 28, "BS256" },
+	{ "brainpoolP384r1", 48, 48, 24, 48, "BP-384", 29, "BS384" },
+	{ "brainpoolP512r1", 64, 64, 32, 64, "BP-512", 30, "BS512" },
+	{ NULL, 0, 0, 0, 0, NULL, 0, NULL }
+};
+
+
+/* Role-specific elements for PKEX */
+
+/* NIST P-256 */
+static const u8 pkex_init_x_p256[32] = {
+	0x56, 0x26, 0x12, 0xcf, 0x36, 0x48, 0xfe, 0x0b,
+	0x07, 0x04, 0xbb, 0x12, 0x22, 0x50, 0xb2, 0x54,
+	0xb1, 0x94, 0x64, 0x7e, 0x54, 0xce, 0x08, 0x07,
+	0x2e, 0xec, 0xca, 0x74, 0x5b, 0x61, 0x2d, 0x25
+ };
+static const u8 pkex_init_y_p256[32] = {
+	0x3e, 0x44, 0xc7, 0xc9, 0x8c, 0x1c, 0xa1, 0x0b,
+	0x20, 0x09, 0x93, 0xb2, 0xfd, 0xe5, 0x69, 0xdc,
+	0x75, 0xbc, 0xad, 0x33, 0xc1, 0xe7, 0xc6, 0x45,
+	0x4d, 0x10, 0x1e, 0x6a, 0x3d, 0x84, 0x3c, 0xa4
+ };
+static const u8 pkex_resp_x_p256[32] = {
+	0x1e, 0xa4, 0x8a, 0xb1, 0xa4, 0xe8, 0x42, 0x39,
+	0xad, 0x73, 0x07, 0xf2, 0x34, 0xdf, 0x57, 0x4f,
+	0xc0, 0x9d, 0x54, 0xbe, 0x36, 0x1b, 0x31, 0x0f,
+	0x59, 0x91, 0x52, 0x33, 0xac, 0x19, 0x9d, 0x76
+};
+static const u8 pkex_resp_y_p256[32] = {
+	0x26, 0x04, 0x09, 0x45, 0x0a, 0x05, 0x20, 0xe7,
+	0xa7, 0x27, 0xc1, 0x36, 0x76, 0x85, 0xca, 0x3e,
+	0x42, 0x16, 0xf4, 0x89, 0x85, 0x34, 0x6e, 0xd5,
+	0x17, 0xde, 0xc0, 0xb8, 0xad, 0xfd, 0xb2, 0x98
+};
+
+/* NIST P-384 */
+static const u8 pkex_init_x_p384[48] = {
+	0x95, 0x3f, 0x42, 0x9e, 0x50, 0x7f, 0xf9, 0xaa,
+	0xac, 0x1a, 0xf2, 0x85, 0x2e, 0x64, 0x91, 0x68,
+	0x64, 0xc4, 0x3c, 0xb7, 0x5c, 0xf8, 0xc9, 0x53,
+	0x6e, 0x58, 0x4c, 0x7f, 0xc4, 0x64, 0x61, 0xac,
+	0x51, 0x8a, 0x6f, 0xfe, 0xab, 0x74, 0xe6, 0x12,
+	0x81, 0xac, 0x38, 0x5d, 0x41, 0xe6, 0xb9, 0xa3
+};
+static const u8 pkex_init_y_p384[48] = {
+	0x89, 0xd0, 0x97, 0x7b, 0x59, 0x4f, 0xa6, 0xd6,
+	0x7c, 0x5d, 0x93, 0x5b, 0x93, 0xc4, 0x07, 0xa9,
+	0x89, 0xee, 0xd5, 0xcd, 0x6f, 0x42, 0xf8, 0x38,
+	0xc8, 0xc6, 0x62, 0x24, 0x69, 0x0c, 0xd4, 0x48,
+	0xd8, 0x44, 0xd6, 0xc2, 0xe8, 0xcc, 0x62, 0x6b,
+	0x3c, 0x25, 0x53, 0xba, 0x4f, 0x71, 0xf8, 0xe7
+};
+static const u8 pkex_resp_x_p384[48] = {
+	0xad, 0xbe, 0xd7, 0x1d, 0x3a, 0x71, 0x64, 0x98,
+	0x5f, 0xb4, 0xd6, 0x4b, 0x50, 0xd0, 0x84, 0x97,
+	0x4b, 0x7e, 0x57, 0x70, 0xd2, 0xd9, 0xf4, 0x92,
+	0x2a, 0x3f, 0xce, 0x99, 0xc5, 0x77, 0x33, 0x44,
+	0x14, 0x56, 0x92, 0xcb, 0xae, 0x46, 0x64, 0xdf,
+	0xe0, 0xbb, 0xd7, 0xb1, 0x29, 0x20, 0x72, 0xdf
+};
+static const u8 pkex_resp_y_p384[48] = {
+	0x54, 0x58, 0x20, 0xad, 0x55, 0x1d, 0xca, 0xf3,
+	0x1c, 0x8a, 0xcd, 0x19, 0x40, 0xf9, 0x37, 0x83,
+	0xc7, 0xd6, 0xb3, 0x13, 0x7d, 0x53, 0x28, 0x5c,
+	0xf6, 0x2d, 0xf1, 0xdd, 0xa5, 0x8b, 0xad, 0x5d,
+	0x81, 0xab, 0xb1, 0x00, 0x39, 0xd6, 0xcc, 0x9c,
+	0xea, 0x1e, 0x84, 0x1d, 0xbf, 0xe3, 0x35, 0xf9
+};
+
+/* NIST P-521 */
+static const u8 pkex_init_x_p521[66] = {
+	0x00, 0x16, 0x20, 0x45, 0x19, 0x50, 0x95, 0x23,
+	0x0d, 0x24, 0xbe, 0x00, 0x87, 0xdc, 0xfa, 0xf0,
+	0x58, 0x9a, 0x01, 0x60, 0x07, 0x7a, 0xca, 0x76,
+	0x01, 0xab, 0x2d, 0x5a, 0x46, 0xcd, 0x2c, 0xb5,
+	0x11, 0x9a, 0xff, 0xaa, 0x48, 0x04, 0x91, 0x38,
+	0xcf, 0x86, 0xfc, 0xa4, 0xa5, 0x0f, 0x47, 0x01,
+	0x80, 0x1b, 0x30, 0xa3, 0xae, 0xe8, 0x1c, 0x2e,
+	0xea, 0xcc, 0xf0, 0x03, 0x9f, 0x77, 0x4c, 0x8d,
+	0x97, 0x76
+};
+static const u8 pkex_init_y_p521[66] = {
+	0x01, 0x4c, 0x71, 0xfd, 0x1b, 0xd5, 0x9c, 0xa6,
+	0xed, 0x39, 0xef, 0x45, 0xc5, 0x06, 0xfd, 0x66,
+	0xc0, 0xeb, 0x0f, 0xbf, 0x21, 0xa3, 0x36, 0x74,
+	0xfd, 0xaa, 0x05, 0x6e, 0x4e, 0x33, 0x95, 0x42,
+	0x1a, 0x9d, 0x3f, 0x3a, 0x1c, 0x5e, 0xa8, 0x60,
+	0xf7, 0xe5, 0x59, 0x1d, 0x07, 0xaa, 0x6f, 0x40,
+	0x0a, 0x59, 0x3c, 0x27, 0xad, 0xe0, 0x48, 0xfd,
+	0xd1, 0x83, 0x37, 0x4c, 0xdf, 0xe1, 0x86, 0x72,
+	0xfc, 0x57
+};
+static const u8 pkex_resp_x_p521[66] = {
+	0x00, 0x79, 0xe4, 0x4d, 0x6b, 0x5e, 0x12, 0x0a,
+	0x18, 0x2c, 0xb3, 0x05, 0x77, 0x0f, 0xc3, 0x44,
+	0x1a, 0xcd, 0x78, 0x46, 0x14, 0xee, 0x46, 0x3f,
+	0xab, 0xc9, 0x59, 0x7c, 0x85, 0xa0, 0xc2, 0xfb,
+	0x02, 0x32, 0x99, 0xde, 0x5d, 0xe1, 0x0d, 0x48,
+	0x2d, 0x71, 0x7d, 0x8d, 0x3f, 0x61, 0x67, 0x9e,
+	0x2b, 0x8b, 0x12, 0xde, 0x10, 0x21, 0x55, 0x0a,
+	0x5b, 0x2d, 0xe8, 0x05, 0x09, 0xf6, 0x20, 0x97,
+	0x84, 0xb4
+};
+static const u8 pkex_resp_y_p521[66] = {
+	0x01, 0xb9, 0x9c, 0xc6, 0x41, 0x32, 0x5b, 0xd2,
+	0x35, 0xd8, 0x8b, 0x2b, 0xe4, 0x6e, 0xcc, 0xdf,
+	0x7c, 0x38, 0xc4, 0x5b, 0xf6, 0x74, 0x71, 0x5c,
+	0x77, 0x16, 0x8a, 0x80, 0xa9, 0x84, 0xc7, 0x7b,
+	0x9d, 0xfd, 0x83, 0x6f, 0xae, 0xf8, 0x24, 0x16,
+	0x2f, 0x21, 0x25, 0x65, 0xa2, 0x1a, 0x6b, 0x2d,
+	0x30, 0x62, 0xb3, 0xcc, 0x6e, 0x59, 0x3c, 0x7f,
+	0x58, 0x91, 0x81, 0x72, 0x07, 0x8c, 0x91, 0xac,
+	0x31, 0x1e
+};
+
+/* Brainpool P-256r1 */
+static const u8 pkex_init_x_bp_p256r1[32] = {
+	0x46, 0x98, 0x18, 0x6c, 0x27, 0xcd, 0x4b, 0x10,
+	0x7d, 0x55, 0xa3, 0xdd, 0x89, 0x1f, 0x9f, 0xca,
+	0xc7, 0x42, 0x5b, 0x8a, 0x23, 0xed, 0xf8, 0x75,
+	0xac, 0xc7, 0xe9, 0x8d, 0xc2, 0x6f, 0xec, 0xd8
+};
+static const u8 pkex_init_y_bp_p256r1[32] = {
+	0x16, 0x30, 0x68, 0x32, 0x3b, 0xb0, 0x21, 0xee,
+	0xeb, 0xf7, 0xb6, 0x7c, 0xae, 0x52, 0x26, 0x42,
+	0x59, 0x28, 0x58, 0xb6, 0x14, 0x90, 0xed, 0x69,
+	0xd0, 0x67, 0xea, 0x25, 0x60, 0x0f, 0xa9, 0x6c
+};
+static const u8 pkex_resp_x_bp_p256r1[32] = {
+	0x90, 0x18, 0x84, 0xc9, 0xdc, 0xcc, 0xb5, 0x2f,
+	0x4a, 0x3f, 0x4f, 0x18, 0x0a, 0x22, 0x56, 0x6a,
+	0xa9, 0xef, 0xd4, 0xe6, 0xc3, 0x53, 0xc2, 0x1a,
+	0x23, 0x54, 0xdd, 0x08, 0x7e, 0x10, 0xd8, 0xe3
+};
+static const u8 pkex_resp_y_bp_p256r1[32] = {
+	0x2a, 0xfa, 0x98, 0x9b, 0xe3, 0xda, 0x30, 0xfd,
+	0x32, 0x28, 0xcb, 0x66, 0xfb, 0x40, 0x7f, 0xf2,
+	0xb2, 0x25, 0x80, 0x82, 0x44, 0x85, 0x13, 0x7e,
+	0x4b, 0xb5, 0x06, 0xc0, 0x03, 0x69, 0x23, 0x64
+};
+
+/* Brainpool P-384r1 */
+static const u8 pkex_init_x_bp_p384r1[48] = {
+	0x0a, 0x2c, 0xeb, 0x49, 0x5e, 0xb7, 0x23, 0xbd,
+	0x20, 0x5b, 0xe0, 0x49, 0xdf, 0xcf, 0xcf, 0x19,
+	0x37, 0x36, 0xe1, 0x2f, 0x59, 0xdb, 0x07, 0x06,
+	0xb5, 0xeb, 0x2d, 0xae, 0xc2, 0xb2, 0x38, 0x62,
+	0xa6, 0x73, 0x09, 0xa0, 0x6c, 0x0a, 0xa2, 0x30,
+	0x99, 0xeb, 0xf7, 0x1e, 0x47, 0xb9, 0x5e, 0xbe
+};
+static const u8 pkex_init_y_bp_p384r1[48] = {
+	0x54, 0x76, 0x61, 0x65, 0x75, 0x5a, 0x2f, 0x99,
+	0x39, 0x73, 0xca, 0x6c, 0xf9, 0xf7, 0x12, 0x86,
+	0x54, 0xd5, 0xd4, 0xad, 0x45, 0x7b, 0xbf, 0x32,
+	0xee, 0x62, 0x8b, 0x9f, 0x52, 0xe8, 0xa0, 0xc9,
+	0xb7, 0x9d, 0xd1, 0x09, 0xb4, 0x79, 0x1c, 0x3e,
+	0x1a, 0xbf, 0x21, 0x45, 0x66, 0x6b, 0x02, 0x52
+};
+static const u8 pkex_resp_x_bp_p384r1[48] = {
+	0x03, 0xa2, 0x57, 0xef, 0xe8, 0x51, 0x21, 0xa0,
+	0xc8, 0x9e, 0x21, 0x02, 0xb5, 0x9a, 0x36, 0x25,
+	0x74, 0x22, 0xd1, 0xf2, 0x1b, 0xa8, 0x9a, 0x9b,
+	0x97, 0xbc, 0x5a, 0xeb, 0x26, 0x15, 0x09, 0x71,
+	0x77, 0x59, 0xec, 0x8b, 0xb7, 0xe1, 0xe8, 0xce,
+	0x65, 0xb8, 0xaf, 0xf8, 0x80, 0xae, 0x74, 0x6c
+};
+static const u8 pkex_resp_y_bp_p384r1[48] = {
+	0x2f, 0xd9, 0x6a, 0xc7, 0x3e, 0xec, 0x76, 0x65,
+	0x2d, 0x38, 0x7f, 0xec, 0x63, 0x26, 0x3f, 0x04,
+	0xd8, 0x4e, 0xff, 0xe1, 0x0a, 0x51, 0x74, 0x70,
+	0xe5, 0x46, 0x63, 0x7f, 0x5c, 0xc0, 0xd1, 0x7c,
+	0xfb, 0x2f, 0xea, 0xe2, 0xd8, 0x0f, 0x84, 0xcb,
+	0xe9, 0x39, 0x5c, 0x64, 0xfe, 0xcb, 0x2f, 0xf1
+};
+
+/* Brainpool P-512r1 */
+static const u8 pkex_init_x_bp_p512r1[64] = {
+	0x4c, 0xe9, 0xb6, 0x1c, 0xe2, 0x00, 0x3c, 0x9c,
+	0xa9, 0xc8, 0x56, 0x52, 0xaf, 0x87, 0x3e, 0x51,
+	0x9c, 0xbb, 0x15, 0x31, 0x1e, 0xc1, 0x05, 0xfc,
+	0x7c, 0x77, 0xd7, 0x37, 0x61, 0x27, 0xd0, 0x95,
+	0x98, 0xee, 0x5d, 0xa4, 0x3d, 0x09, 0xdb, 0x3d,
+	0xfa, 0x89, 0x9e, 0x7f, 0xa6, 0xa6, 0x9c, 0xff,
+	0x83, 0x5c, 0x21, 0x6c, 0x3e, 0xf2, 0xfe, 0xdc,
+	0x63, 0xe4, 0xd1, 0x0e, 0x75, 0x45, 0x69, 0x0f
+};
+static const u8 pkex_init_y_bp_p512r1[64] = {
+	0x5a, 0x28, 0x01, 0xbe, 0x96, 0x82, 0x4e, 0xf6,
+	0xfa, 0xed, 0x7d, 0xfd, 0x48, 0x8b, 0x48, 0x4e,
+	0xd1, 0x97, 0x87, 0xc4, 0x05, 0x5d, 0x15, 0x2a,
+	0xf4, 0x91, 0x4b, 0x75, 0x90, 0xd9, 0x34, 0x2c,
+	0x3c, 0x12, 0xf2, 0xf5, 0x25, 0x94, 0x24, 0x34,
+	0xa7, 0x6d, 0x66, 0xbc, 0x27, 0xa4, 0xa0, 0x8d,
+	0xd5, 0xe1, 0x54, 0xa3, 0x55, 0x26, 0xd4, 0x14,
+	0x17, 0x0f, 0xc1, 0xc7, 0x3d, 0x68, 0x7f, 0x5a
+};
+static const u8 pkex_resp_x_bp_p512r1[64] = {
+	0x2a, 0x60, 0x32, 0x27, 0xa1, 0xe6, 0x94, 0x72,
+	0x1c, 0x48, 0xbe, 0xc5, 0x77, 0x14, 0x30, 0x76,
+	0xe4, 0xbf, 0xf7, 0x7b, 0xc5, 0xfd, 0xdf, 0x19,
+	0x1e, 0x0f, 0xdf, 0x1c, 0x40, 0xfa, 0x34, 0x9e,
+	0x1f, 0x42, 0x24, 0xa3, 0x2c, 0xd5, 0xc7, 0xc9,
+	0x7b, 0x47, 0x78, 0x96, 0xf1, 0x37, 0x0e, 0x88,
+	0xcb, 0xa6, 0x52, 0x29, 0xd7, 0xa8, 0x38, 0x29,
+	0x8e, 0x6e, 0x23, 0x47, 0xd4, 0x4b, 0x70, 0x3e
+};
+static const u8 pkex_resp_y_bp_p512r1[64] = {
+	0x2a, 0xbe, 0x59, 0xe6, 0xc4, 0xb3, 0xd8, 0x09,
+	0x66, 0x89, 0x0a, 0x2d, 0x19, 0xf0, 0x9c, 0x9f,
+	0xb4, 0xab, 0x8f, 0x50, 0x68, 0x3c, 0x74, 0x64,
+	0x4e, 0x19, 0x55, 0x81, 0x9b, 0x48, 0x5c, 0xf4,
+	0x12, 0x8d, 0xb9, 0xd8, 0x02, 0x5b, 0xe1, 0x26,
+	0x7e, 0x19, 0x5c, 0xfd, 0x70, 0xf7, 0x4b, 0xdc,
+	0xb5, 0x5d, 0xc1, 0x7a, 0xe9, 0xd1, 0x05, 0x2e,
+	0xd1, 0xfd, 0x2f, 0xce, 0x63, 0x77, 0x48, 0x2c
+};
+
+
+static int dpp_hash_vector(const struct dpp_curve_params *curve,
+			   size_t num_elem, const u8 *addr[], const size_t *len,
+			   u8 *mac)
+{
+	if (curve->hash_len == 32)
+		return sha256_vector(num_elem, addr, len, mac);
+	if (curve->hash_len == 48)
+		return sha384_vector(num_elem, addr, len, mac);
+	if (curve->hash_len == 64)
+		return sha512_vector(num_elem, addr, len, mac);
+	return -1;
+}
+
+
+static int dpp_hkdf_expand(size_t hash_len, const u8 *secret, size_t secret_len,
+			   const char *label, u8 *out, size_t outlen)
+{
+	if (hash_len == 32)
+		return hmac_sha256_kdf(secret, secret_len, NULL,
+				       (const u8 *) label, os_strlen(label),
+				       out, outlen);
+	if (hash_len == 48)
+		return hmac_sha384_kdf(secret, secret_len, NULL,
+				       (const u8 *) label, os_strlen(label),
+				       out, outlen);
+	if (hash_len == 64)
+		return hmac_sha512_kdf(secret, secret_len, NULL,
+				       (const u8 *) label, os_strlen(label),
+				       out, outlen);
+	return -1;
+}
+
+
+static int dpp_hmac_vector(size_t hash_len, const u8 *key, size_t key_len,
+			   size_t num_elem, const u8 *addr[],
+			   const size_t *len, u8 *mac)
+{
+	if (hash_len == 32)
+		return hmac_sha256_vector(key, key_len, num_elem, addr, len,
+					  mac);
+	if (hash_len == 48)
+		return hmac_sha384_vector(key, key_len, num_elem, addr, len,
+					  mac);
+	if (hash_len == 64)
+		return hmac_sha512_vector(key, key_len, num_elem, addr, len,
+					  mac);
+	return -1;
+}
+
+
+static int dpp_hmac(size_t hash_len, const u8 *key, size_t key_len,
+		    const u8 *data, size_t data_len, u8 *mac)
+{
+	if (hash_len == 32)
+		return hmac_sha256(key, key_len, data, data_len, mac);
+	if (hash_len == 48)
+		return hmac_sha384(key, key_len, data, data_len, mac);
+	if (hash_len == 64)
+		return hmac_sha512(key, key_len, data, data_len, mac);
+	return -1;
+}
+
+
+static struct wpabuf * dpp_get_pubkey_point(EVP_PKEY *pkey, int prefix)
+{
+	int len, res;
+	EC_KEY *eckey;
+	struct wpabuf *buf;
+	unsigned char *pos;
+
+	eckey = EVP_PKEY_get1_EC_KEY(pkey);
+	if (!eckey)
+		return NULL;
+	EC_KEY_set_conv_form(eckey, POINT_CONVERSION_UNCOMPRESSED);
+	len = i2o_ECPublicKey(eckey, NULL);
+	if (len <= 0) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to determine public key encoding length");
+		EC_KEY_free(eckey);
+		return NULL;
+	}
+
+	buf = wpabuf_alloc(len);
+	if (!buf) {
+		EC_KEY_free(eckey);
+		return NULL;
+	}
+
+	pos = wpabuf_put(buf, len);
+	res = i2o_ECPublicKey(eckey, &pos);
+	EC_KEY_free(eckey);
+	if (res != len) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to encode public key (res=%d/%d)",
+			   res, len);
+		wpabuf_free(buf);
+		return NULL;
+	}
+
+	if (!prefix) {
+		/* Remove 0x04 prefix to match DPP definition */
+		pos = wpabuf_mhead(buf);
+		os_memmove(pos, pos + 1, len - 1);
+		buf->used--;
+	}
+
+	return buf;
+}
+
+
+static EVP_PKEY * dpp_set_pubkey_point_group(const EC_GROUP *group,
+					     const u8 *buf_x, const u8 *buf_y,
+					     size_t len)
+{
+	EC_KEY *eckey = NULL;
+	BN_CTX *ctx;
+	EC_POINT *point = NULL;
+	BIGNUM *x = NULL, *y = NULL;
+	EVP_PKEY *pkey = NULL;
+
+	ctx = BN_CTX_new();
+	if (!ctx) {
+		wpa_printf(MSG_ERROR, "DPP: Out of memory");
+		return NULL;
+	}
+
+	point = EC_POINT_new(group);
+	x = BN_bin2bn(buf_x, len, NULL);
+	y = BN_bin2bn(buf_y, len, NULL);
+	if (!point || !x || !y) {
+		wpa_printf(MSG_ERROR, "DPP: Out of memory");
+		goto fail;
+	}
+
+	if (!EC_POINT_set_affine_coordinates_GFp(group, point, x, y, ctx)) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: OpenSSL: EC_POINT_set_affine_coordinates_GFp failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	if (!EC_POINT_is_on_curve(group, point, ctx) ||
+	    EC_POINT_is_at_infinity(group, point)) {
+		wpa_printf(MSG_ERROR, "DPP: Invalid point");
+		goto fail;
+	}
+
+	eckey = EC_KEY_new();
+	if (!eckey ||
+	    EC_KEY_set_group(eckey, group) != 1 ||
+	    EC_KEY_set_public_key(eckey, point) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to set EC_KEY: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
+
+	pkey = EVP_PKEY_new();
+	if (!pkey || EVP_PKEY_set1_EC_KEY(pkey, eckey) != 1) {
+		wpa_printf(MSG_ERROR, "DPP: Could not create EVP_PKEY");
+		goto fail;
+	}
+
+out:
+	BN_free(x);
+	BN_free(y);
+	EC_KEY_free(eckey);
+	EC_POINT_free(point);
+	BN_CTX_free(ctx);
+	return pkey;
+fail:
+	EVP_PKEY_free(pkey);
+	pkey = NULL;
+	goto out;
+}
+
+
+static EVP_PKEY * dpp_set_pubkey_point(EVP_PKEY *group_key,
+				       const u8 *buf, size_t len)
+{
+	EC_KEY *eckey;
+	const EC_GROUP *group;
+	EVP_PKEY *pkey = NULL;
+
+	if (len & 1)
+		return NULL;
+
+	eckey = EVP_PKEY_get1_EC_KEY(group_key);
+	if (!eckey) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Could not get EC_KEY from group_key");
+		return NULL;
+	}
+
+	group = EC_KEY_get0_group(eckey);
+	if (group)
+		pkey = dpp_set_pubkey_point_group(group, buf, buf + len / 2,
+						  len / 2);
+	else
+		wpa_printf(MSG_ERROR, "DPP: Could not get EC group");
+
+	EC_KEY_free(eckey);
+	return pkey;
+}
+
+
+struct wpabuf * dpp_alloc_msg(enum dpp_public_action_frame_type type,
+			      size_t len)
+{
+	struct wpabuf *msg;
+
+	msg = wpabuf_alloc(8 + len);
+	if (!msg)
+		return NULL;
+	wpabuf_put_u8(msg, WLAN_ACTION_PUBLIC);
+	wpabuf_put_u8(msg, WLAN_PA_VENDOR_SPECIFIC);
+	wpabuf_put_be24(msg, OUI_WFA);
+	wpabuf_put_u8(msg, DPP_OUI_TYPE);
+	wpabuf_put_u8(msg, 1); /* Crypto Suite */
+	wpabuf_put_u8(msg, type);
+	return msg;
+}
+
+
+const u8 * dpp_get_attr(const u8 *buf, size_t len, u16 req_id, u16 *ret_len)
+{
+	u16 id, alen;
+	const u8 *pos = buf, *end = buf + len;
+
+	while (end - pos >= 4) {
+		id = WPA_GET_LE16(pos);
+		pos += 2;
+		alen = WPA_GET_LE16(pos);
+		pos += 2;
+		if (alen > end - pos)
+			return NULL;
+		if (id == req_id) {
+			*ret_len = alen;
+			return pos;
+		}
+		pos += alen;
+	}
+
+	return NULL;
+}
+
+
+int dpp_check_attrs(const u8 *buf, size_t len)
+{
+	const u8 *pos, *end;
+
+	pos = buf;
+	end = buf + len;
+	while (end - pos >= 4) {
+		u16 id, alen;
+
+		id = WPA_GET_LE16(pos);
+		pos += 2;
+		alen = WPA_GET_LE16(pos);
+		pos += 2;
+		wpa_printf(MSG_MSGDUMP, "DPP: Attribute ID %04x len %u",
+			   id, alen);
+		if (alen > end - pos) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Truncated message - not enough room for the attribute - dropped");
+			return -1;
+		}
+		pos += alen;
+	}
+
+	if (end != pos) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected octets (%d) after the last attribute",
+			   (int) (end - pos));
+		return -1;
+	}
+
+	return 0;
+}
+
+
+void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info)
+{
+	if (!info)
+		return;
+	os_free(info->uri);
+	os_free(info->info);
+	EVP_PKEY_free(info->pubkey);
+	os_free(info);
+}
+
+
+const char * dpp_bootstrap_type_txt(enum dpp_bootstrap_type type)
+{
+	switch (type) {
+	case DPP_BOOTSTRAP_QR_CODE:
+		return "QRCODE";
+	case DPP_BOOTSTRAP_PKEX:
+		return "PKEX";
+	}
+	return "??";
+}
+
+
+static int dpp_uri_valid_info(const char *info)
+{
+	while (*info) {
+		unsigned char val = *info++;
+
+		if (val < 0x20 || val > 0x7e || val == 0x3b)
+			return 0;
+	}
+
+	return 1;
+}
+
+
+static int dpp_clone_uri(struct dpp_bootstrap_info *bi, const char *uri)
+{
+	bi->uri = os_strdup(uri);
+	return bi->uri ? 0 : -1;
+}
+
+
+int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi,
+			    const char *chan_list)
+{
+	const char *pos = chan_list;
+	int opclass, channel, freq;
+
+	while (pos && *pos && *pos != ';') {
+		opclass = atoi(pos);
+		if (opclass <= 0)
+			goto fail;
+		pos = os_strchr(pos, '/');
+		if (!pos)
+			goto fail;
+		pos++;
+		channel = atoi(pos);
+		if (channel <= 0)
+			goto fail;
+		while (*pos >= '0' && *pos <= '9')
+			pos++;
+		freq = ieee80211_chan_to_freq(NULL, opclass, channel);
+		wpa_printf(MSG_DEBUG,
+			   "DPP: URI channel-list: opclass=%d channel=%d ==> freq=%d",
+			   opclass, channel, freq);
+		if (freq < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Ignore unknown URI channel-list channel (opclass=%d channel=%d)",
+				   opclass, channel);
+		} else if (bi->num_freq == DPP_BOOTSTRAP_MAX_FREQ) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Too many channels in URI channel-list - ignore list");
+			bi->num_freq = 0;
+			break;
+		} else {
+			bi->freq[bi->num_freq++] = freq;
+		}
+
+		if (*pos == ';' || *pos == '\0')
+			break;
+		if (*pos != ',')
+			goto fail;
+		pos++;
+	}
+
+	return 0;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: Invalid URI channel-list");
+	return -1;
+}
+
+
+int dpp_parse_uri_mac(struct dpp_bootstrap_info *bi, const char *mac)
+{
+	if (!mac)
+		return 0;
+
+	if (hwaddr_aton2(mac, bi->mac_addr) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Invalid URI mac");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: URI mac: " MACSTR, MAC2STR(bi->mac_addr));
+
+	return 0;
+}
+
+
+int dpp_parse_uri_info(struct dpp_bootstrap_info *bi, const char *info)
+{
+	const char *end;
+
+	if (!info)
+		return 0;
+
+	end = os_strchr(info, ';');
+	if (!end)
+		end = info + os_strlen(info);
+	bi->info = os_malloc(end - info + 1);
+	if (!bi->info)
+		return -1;
+	os_memcpy(bi->info, info, end - info);
+	bi->info[end - info] = '\0';
+	wpa_printf(MSG_DEBUG, "DPP: URI(information): %s", bi->info);
+	if (!dpp_uri_valid_info(bi->info)) {
+		wpa_printf(MSG_DEBUG, "DPP: Invalid URI information payload");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static const struct dpp_curve_params *
+dpp_get_curve_oid(const ASN1_OBJECT *poid)
+{
+	ASN1_OBJECT *oid;
+	int i;
+
+	for (i = 0; dpp_curves[i].name; i++) {
+		oid = OBJ_txt2obj(dpp_curves[i].name, 0);
+		if (oid && OBJ_cmp(poid, oid) == 0)
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static const struct dpp_curve_params * dpp_get_curve_nid(int nid)
+{
+	int i, tmp;
+
+	if (!nid)
+		return NULL;
+	for (i = 0; dpp_curves[i].name; i++) {
+		tmp = OBJ_txt2nid(dpp_curves[i].name);
+		if (tmp == nid)
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static int dpp_parse_uri_pk(struct dpp_bootstrap_info *bi, const char *info)
+{
+	const char *end;
+	u8 *data;
+	size_t data_len;
+	EVP_PKEY *pkey;
+	const unsigned char *p;
+	int res;
+	X509_PUBKEY *pub = NULL;
+	ASN1_OBJECT *ppkalg;
+	const unsigned char *pk;
+	int ppklen;
+	X509_ALGOR *pa;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+	ASN1_OBJECT *pa_oid;
+#else
+	const ASN1_OBJECT *pa_oid;
+#endif
+	const void *pval;
+	int ptype;
+	const ASN1_OBJECT *poid;
+	char buf[100];
+
+	end = os_strchr(info, ';');
+	if (!end)
+		return -1;
+
+	data = base64_decode((const unsigned char *) info, end - info,
+			     &data_len);
+	if (!data) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid base64 encoding on URI public-key");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Base64 decoded URI public-key",
+		    data, data_len);
+
+	if (sha256_vector(1, (const u8 **) &data, &data_len,
+			  bi->pubkey_hash) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Public key hash",
+		    bi->pubkey_hash, SHA256_MAC_LEN);
+
+	/* DER encoded ASN.1 SubjectPublicKeyInfo
+	 *
+	 * SubjectPublicKeyInfo  ::=  SEQUENCE  {
+	 *      algorithm            AlgorithmIdentifier,
+	 *      subjectPublicKey     BIT STRING  }
+	 *
+	 * AlgorithmIdentifier  ::=  SEQUENCE  {
+	 *      algorithm               OBJECT IDENTIFIER,
+	 *      parameters              ANY DEFINED BY algorithm OPTIONAL  }
+	 *
+	 * subjectPublicKey = compressed format public key per ANSI X9.63
+	 * algorithm = ecPublicKey (1.2.840.10045.2.1)
+	 * parameters = shall be present and shall be OBJECT IDENTIFIER; e.g.,
+	 *       prime256v1 (1.2.840.10045.3.1.7)
+	 */
+
+	p = data;
+	pkey = d2i_PUBKEY(NULL, &p, data_len);
+	os_free(data);
+
+	if (!pkey) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not parse URI public-key SubjectPublicKeyInfo");
+		return -1;
+	}
+
+	if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: SubjectPublicKeyInfo does not describe an EC key");
+		EVP_PKEY_free(pkey);
+		return -1;
+	}
+
+	res = X509_PUBKEY_set(&pub, pkey);
+	if (res != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not set pubkey");
+		goto fail;
+	}
+
+	res = X509_PUBKEY_get0_param(&ppkalg, &pk, &ppklen, &pa, pub);
+	if (res != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not extract SubjectPublicKeyInfo parameters");
+		goto fail;
+	}
+	res = OBJ_obj2txt(buf, sizeof(buf), ppkalg, 0);
+	if (res < 0 || (size_t) res >= sizeof(buf)) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not extract SubjectPublicKeyInfo algorithm");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: URI subjectPublicKey algorithm: %s", buf);
+	if (os_strcmp(buf, "id-ecPublicKey") != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported SubjectPublicKeyInfo algorithm");
+		goto fail;
+	}
+
+	X509_ALGOR_get0(&pa_oid, &ptype, (void *) &pval, pa);
+	if (ptype != V_ASN1_OBJECT) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: SubjectPublicKeyInfo parameters did not contain an OID");
+		goto fail;
+	}
+	poid = pval;
+	res = OBJ_obj2txt(buf, sizeof(buf), poid, 0);
+	if (res < 0 || (size_t) res >= sizeof(buf)) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Could not extract SubjectPublicKeyInfo parameters OID");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: URI subjectPublicKey parameters: %s", buf);
+	bi->curve = dpp_get_curve_oid(poid);
+	if (!bi->curve) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported SubjectPublicKeyInfo curve: %s",
+			   buf);
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: URI subjectPublicKey", pk, ppklen);
+
+	X509_PUBKEY_free(pub);
+	bi->pubkey = pkey;
+	return 0;
+fail:
+	X509_PUBKEY_free(pub);
+	EVP_PKEY_free(pkey);
+	return -1;
+}
+
+
+static struct dpp_bootstrap_info * dpp_parse_uri(const char *uri)
+{
+	const char *pos = uri;
+	const char *end;
+	const char *chan_list = NULL, *mac = NULL, *info = NULL, *pk = NULL;
+	struct dpp_bootstrap_info *bi;
+
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: URI", uri, os_strlen(uri));
+
+	if (os_strncmp(pos, "DPP:", 4) != 0) {
+		wpa_printf(MSG_INFO, "DPP: Not a DPP URI");
+		return NULL;
+	}
+	pos += 4;
+
+	for (;;) {
+		end = os_strchr(pos, ';');
+		if (!end)
+			break;
+
+		if (end == pos) {
+			/* Handle terminating ";;" and ignore unexpected ";"
+			 * for parsing robustness. */
+			pos++;
+			continue;
+		}
+
+		if (pos[0] == 'C' && pos[1] == ':' && !chan_list)
+			chan_list = pos + 2;
+		else if (pos[0] == 'M' && pos[1] == ':' && !mac)
+			mac = pos + 2;
+		else if (pos[0] == 'I' && pos[1] == ':' && !info)
+			info = pos + 2;
+		else if (pos[0] == 'K' && pos[1] == ':' && !pk)
+			pk = pos + 2;
+		else
+			wpa_hexdump_ascii(MSG_DEBUG,
+					  "DPP: Ignore unrecognized URI parameter",
+					  pos, end - pos);
+		pos = end + 1;
+	}
+
+	if (!pk) {
+		wpa_printf(MSG_INFO, "DPP: URI missing public-key");
+		return NULL;
+	}
+
+	bi = os_zalloc(sizeof(*bi));
+	if (!bi)
+		return NULL;
+
+	if (dpp_clone_uri(bi, uri) < 0 ||
+	    dpp_parse_uri_chan_list(bi, chan_list) < 0 ||
+	    dpp_parse_uri_mac(bi, mac) < 0 ||
+	    dpp_parse_uri_info(bi, info) < 0 ||
+	    dpp_parse_uri_pk(bi, pk) < 0) {
+		dpp_bootstrap_info_free(bi);
+		bi = NULL;
+	}
+
+	return bi;
+}
+
+
+struct dpp_bootstrap_info * dpp_parse_qr_code(const char *uri)
+{
+	struct dpp_bootstrap_info *bi;
+
+	bi = dpp_parse_uri(uri);
+	if (bi)
+		bi->type = DPP_BOOTSTRAP_QR_CODE;
+	return bi;
+}
+
+
+static void dpp_debug_print_key(const char *title, EVP_PKEY *key)
+{
+	EC_KEY *eckey;
+	BIO *out;
+	size_t rlen;
+	char *txt;
+	int res;
+	unsigned char *der = NULL;
+	int der_len;
+
+	out = BIO_new(BIO_s_mem());
+	if (!out)
+		return;
+
+	EVP_PKEY_print_private(out, key, 0, NULL);
+	rlen = BIO_ctrl_pending(out);
+	txt = os_malloc(rlen + 1);
+	if (txt) {
+		res = BIO_read(out, txt, rlen);
+		if (res > 0) {
+			txt[res] = '\0';
+			wpa_printf(MSG_DEBUG, "%s: %s", title, txt);
+		}
+		os_free(txt);
+	}
+	BIO_free(out);
+
+	eckey = EVP_PKEY_get1_EC_KEY(key);
+	if (!eckey)
+		return;
+
+	der_len = i2d_ECPrivateKey(eckey, &der);
+	if (der_len > 0)
+		wpa_hexdump_key(MSG_DEBUG, "DPP: ECPrivateKey", der, der_len);
+	OPENSSL_free(der);
+	if (der_len <= 0) {
+		der = NULL;
+		der_len = i2d_EC_PUBKEY(eckey, &der);
+		if (der_len > 0)
+			wpa_hexdump(MSG_DEBUG, "DPP: EC_PUBKEY", der, der_len);
+		OPENSSL_free(der);
+	}
+
+	EC_KEY_free(eckey);
+}
+
+
+static EVP_PKEY * dpp_gen_keypair(const struct dpp_curve_params *curve)
+{
+#ifdef OPENSSL_IS_BORINGSSL
+	EVP_PKEY_CTX *kctx = NULL;
+	const EC_GROUP *group;
+	EC_KEY *ec_params;
+#else
+	EVP_PKEY_CTX *pctx, *kctx = NULL;
+#endif
+	EVP_PKEY *params = NULL, *key = NULL;
+	int nid;
+
+	wpa_printf(MSG_DEBUG, "DPP: Generating a keypair");
+
+	nid = OBJ_txt2nid(curve->name);
+	if (nid == NID_undef) {
+		wpa_printf(MSG_INFO, "DPP: Unsupported curve %s", curve->name);
+		return NULL;
+	}
+#ifdef OPENSSL_IS_BORINGSSL
+	group = EC_GROUP_new_by_curve_name(nid);
+	ec_params = EC_KEY_new();
+	if (!ec_params || EC_KEY_set_group(ec_params, group) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to generate EC_KEY parameters");
+		goto fail;
+	}
+	EC_KEY_set_asn1_flag(ec_params, OPENSSL_EC_NAMED_CURVE);
+	params = EVP_PKEY_new();
+	if (!params || EVP_PKEY_set1_EC_KEY(params, ec_params) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to generate EVP_PKEY parameters");
+		goto fail;
+	}
+#else
+	pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+	if (!pctx ||
+	    EVP_PKEY_paramgen_init(pctx) != 1 ||
+	    EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) != 1 ||
+	    EVP_PKEY_CTX_set_ec_param_enc(pctx, OPENSSL_EC_NAMED_CURVE) != 1 ||
+	    EVP_PKEY_paramgen(pctx, &params) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to generate EVP_PKEY parameters");
+		EVP_PKEY_CTX_free(pctx);
+		goto fail;
+	}
+	EVP_PKEY_CTX_free(pctx);
+#endif
+
+	kctx = EVP_PKEY_CTX_new(params, NULL);
+	if (!kctx ||
+	    EVP_PKEY_keygen_init(kctx) != 1 ||
+	    EVP_PKEY_keygen(kctx, &key) != 1) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate EC key");
+		goto fail;
+	}
+
+	if (wpa_debug_show_keys)
+		dpp_debug_print_key("Own generated key", key);
+
+	EVP_PKEY_free(params);
+	EVP_PKEY_CTX_free(kctx);
+	return key;
+fail:
+	EVP_PKEY_CTX_free(kctx);
+	EVP_PKEY_free(params);
+	return NULL;
+}
+
+
+static const struct dpp_curve_params *
+dpp_get_curve_name(const char *name)
+{
+	int i;
+
+	for (i = 0; dpp_curves[i].name; i++) {
+		if (os_strcmp(name, dpp_curves[i].name) == 0 ||
+		    (dpp_curves[i].jwk_crv &&
+		     os_strcmp(name, dpp_curves[i].jwk_crv) == 0))
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static const struct dpp_curve_params *
+dpp_get_curve_jwk_crv(const char *name)
+{
+	int i;
+
+	for (i = 0; dpp_curves[i].name; i++) {
+		if (dpp_curves[i].jwk_crv &&
+		    os_strcmp(name, dpp_curves[i].jwk_crv) == 0)
+			return &dpp_curves[i];
+	}
+	return NULL;
+}
+
+
+static EVP_PKEY * dpp_set_keypair(const struct dpp_curve_params **curve,
+				  const u8 *privkey, size_t privkey_len)
+{
+	EVP_PKEY *pkey;
+	EC_KEY *eckey;
+	const EC_GROUP *group;
+	int nid;
+
+	pkey = EVP_PKEY_new();
+	if (!pkey)
+		return NULL;
+	eckey = d2i_ECPrivateKey(NULL, &privkey, privkey_len);
+	if (!eckey) {
+		wpa_printf(MSG_INFO,
+			   "DPP: OpenSSL: d2i_ECPrivateKey() failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+	group = EC_KEY_get0_group(eckey);
+	if (!group) {
+		EC_KEY_free(eckey);
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+	nid = EC_GROUP_get_curve_name(group);
+	*curve = dpp_get_curve_nid(nid);
+	if (!*curve) {
+		wpa_printf(MSG_INFO,
+			   "DPP: Unsupported curve (nid=%d) in pre-assigned key",
+			   nid);
+		EC_KEY_free(eckey);
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+
+	if (EVP_PKEY_assign_EC_KEY(pkey, eckey) != 1) {
+		EC_KEY_free(eckey);
+		EVP_PKEY_free(pkey);
+		return NULL;
+	}
+	return pkey;
+}
+
+
+int dpp_bootstrap_key_hash(struct dpp_bootstrap_info *bi)
+{
+	unsigned char *der = NULL;
+	int der_len;
+	EC_KEY *eckey;
+	int res;
+	size_t len;
+
+	/* Need to get the compressed form of the public key through EC_KEY, so
+	 * cannot use the simpler i2d_PUBKEY() here. */
+	eckey = EVP_PKEY_get1_EC_KEY(bi->pubkey);
+	if (!eckey)
+		return -1;
+	EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED);
+	der_len = i2d_EC_PUBKEY(eckey, &der);
+	EC_KEY_free(eckey);
+	if (der_len <= 0) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to build DER encoded public key");
+		OPENSSL_free(der);
+		return -1;
+	}
+
+	len = der_len;
+	res = sha256_vector(1, (const u8 **) &der, &len, bi->pubkey_hash);
+	OPENSSL_free(der);
+	if (res < 0)
+		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+	return res;
+}
+
+
+char * dpp_keygen(struct dpp_bootstrap_info *bi, const char *curve,
+		  const u8 *privkey, size_t privkey_len)
+{
+	unsigned char *base64 = NULL;
+	char *pos, *end;
+	size_t len;
+	unsigned char *der = NULL;
+	int der_len;
+	EC_KEY *eckey;
+
+	if (!curve) {
+		bi->curve = &dpp_curves[0];
+	} else {
+		bi->curve = dpp_get_curve_name(curve);
+		if (!bi->curve) {
+			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
+				   curve);
+			return NULL;
+		}
+	}
+	if (privkey)
+		bi->pubkey = dpp_set_keypair(&bi->curve, privkey, privkey_len);
+	else
+		bi->pubkey = dpp_gen_keypair(bi->curve);
+	if (!bi->pubkey)
+		goto fail;
+	bi->own = 1;
+
+	/* Need to get the compressed form of the public key through EC_KEY, so
+	 * cannot use the simpler i2d_PUBKEY() here. */
+	eckey = EVP_PKEY_get1_EC_KEY(bi->pubkey);
+	if (!eckey)
+		goto fail;
+	EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED);
+	der_len = i2d_EC_PUBKEY(eckey, &der);
+	EC_KEY_free(eckey);
+	if (der_len <= 0) {
+		wpa_printf(MSG_ERROR,
+			   "DDP: Failed to build DER encoded public key");
+		goto fail;
+	}
+
+	len = der_len;
+	if (sha256_vector(1, (const u8 **) &der, &len, bi->pubkey_hash) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+		goto fail;
+	}
+
+	base64 = base64_encode(der, der_len, &len);
+	OPENSSL_free(der);
+	der = NULL;
+	if (!base64)
+		goto fail;
+	pos = (char *) base64;
+	end = pos + len;
+	for (;;) {
+		pos = os_strchr(pos, '\n');
+		if (!pos)
+			break;
+		os_memmove(pos, pos + 1, end - pos);
+	}
+	return (char *) base64;
+fail:
+	os_free(base64);
+	OPENSSL_free(der);
+	return NULL;
+}
+
+
+static int dpp_derive_k1(const u8 *Mx, size_t Mx_len, u8 *k1,
+			 unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	const char *info = "first intermediate key";
+	int res;
+
+	/* k1 = HKDF(<>, "first intermediate key", M.x) */
+
+	/* HKDF-Extract(<>, M.x) */
+	os_memset(salt, 0, hash_len);
+	if (dpp_hmac(hash_len, salt, hash_len, Mx, Mx_len, prk) < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=M.x)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info, k1, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: k1 = HKDF-Expand(PRK, info, L)",
+			k1, hash_len);
+	return 0;
+}
+
+
+static int dpp_derive_k2(const u8 *Nx, size_t Nx_len, u8 *k2,
+			 unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	const char *info = "second intermediate key";
+	int res;
+
+	/* k2 = HKDF(<>, "second intermediate key", N.x) */
+
+	/* HKDF-Extract(<>, N.x) */
+	os_memset(salt, 0, hash_len);
+	res = dpp_hmac(hash_len, salt, hash_len, Nx, Nx_len, prk);
+	if (res < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=N.x)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info, k2, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: k2 = HKDF-Expand(PRK, info, L)",
+			k2, hash_len);
+	return 0;
+}
+
+
+static int dpp_derive_ke(struct dpp_authentication *auth, u8 *ke,
+			 unsigned int hash_len)
+{
+	size_t nonce_len;
+	u8 nonces[2 * DPP_MAX_NONCE_LEN];
+	const char *info_ke = "DPP Key";
+	u8 prk[DPP_MAX_HASH_LEN];
+	int res;
+	const u8 *addr[3];
+	size_t len[3];
+	size_t num_elem = 0;
+
+	/* ke = HKDF(I-nonce | R-nonce, "DPP Key", M.x | N.x [| L.x]) */
+
+	/* HKDF-Extract(I-nonce | R-nonce, M.x | N.x [| L.x]) */
+	nonce_len = auth->curve->nonce_len;
+	os_memcpy(nonces, auth->i_nonce, nonce_len);
+	os_memcpy(&nonces[nonce_len], auth->r_nonce, nonce_len);
+	addr[num_elem] = auth->Mx;
+	len[num_elem] = auth->secret_len;
+	num_elem++;
+	addr[num_elem] = auth->Nx;
+	len[num_elem] = auth->secret_len;
+	num_elem++;
+	if (auth->peer_bi && auth->own_bi) {
+		addr[num_elem] = auth->Lx;
+		len[num_elem] = auth->secret_len;
+		num_elem++;
+	}
+	res = dpp_hmac_vector(hash_len, nonces, 2 * nonce_len,
+			      num_elem, addr, len, prk);
+	if (res < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info_ke, ke, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ke = HKDF-Expand(PRK, info, L)",
+			ke, hash_len);
+	return 0;
+}
+
+
+struct dpp_authentication * dpp_auth_init(void *msg_ctx,
+					  struct dpp_bootstrap_info *peer_bi,
+					  struct dpp_bootstrap_info *own_bi,
+					  int configurator)
+{
+	struct dpp_authentication *auth;
+	size_t nonce_len;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	struct wpabuf *msg, *pi = NULL;
+	u8 clear[4 + DPP_MAX_NONCE_LEN + 4 + 1];
+	u8 wrapped_data[4 + DPP_MAX_NONCE_LEN + 4 + 1 + AES_BLOCK_SIZE];
+	u8 *pos;
+	const u8 *addr[2];
+	size_t len[2], siv_len, attr_len;
+	u8 *attr_start, *attr_end;
+
+	auth = os_zalloc(sizeof(*auth));
+	if (!auth)
+		return NULL;
+	auth->msg_ctx = msg_ctx;
+	auth->initiator = 1;
+	auth->configurator = configurator;
+	auth->peer_bi = peer_bi;
+	auth->own_bi = own_bi;
+	auth->curve = peer_bi->curve;
+
+	nonce_len = auth->curve->nonce_len;
+	if (random_get_bytes(auth->i_nonce, nonce_len)) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", auth->i_nonce, nonce_len);
+
+	auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+	if (!auth->own_protocol_key)
+		goto fail;
+
+	pi = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+	if (!pi)
+		goto fail;
+
+	/* ECDH: M = pI * BR */
+	ctx = EVP_PKEY_CTX_new(auth->own_protocol_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, auth->peer_bi->pubkey) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Mx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	auth->secret_len = secret_len;
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (M.x)",
+			auth->Mx, auth->secret_len);
+
+	if (dpp_derive_k1(auth->Mx, auth->secret_len, auth->k1,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	/* Build DPP Authentication Request frame attributes */
+	attr_len = 2 * (4 + SHA256_MAC_LEN) + 4 + wpabuf_len(pi) +
+		4 + sizeof(wrapped_data);
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_REQ, attr_len);
+	if (!msg)
+		goto fail;
+	auth->req_msg = msg;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	/* Responder Bootstrapping Key Hash */
+	wpabuf_put_le16(msg, DPP_ATTR_R_BOOTSTRAP_KEY_HASH);
+	wpabuf_put_le16(msg, SHA256_MAC_LEN);
+	wpabuf_put_data(msg, auth->peer_bi->pubkey_hash, SHA256_MAC_LEN);
+
+	/* Initiator Bootstrapping Key Hash */
+	wpabuf_put_le16(msg, DPP_ATTR_I_BOOTSTRAP_KEY_HASH);
+	wpabuf_put_le16(msg, SHA256_MAC_LEN);
+	if (auth->own_bi)
+		wpabuf_put_data(msg, auth->own_bi->pubkey_hash, SHA256_MAC_LEN);
+	else
+		os_memset(wpabuf_put(msg, SHA256_MAC_LEN), 0, SHA256_MAC_LEN);
+
+	/* Initiator Protocol Key */
+	wpabuf_put_le16(msg, DPP_ATTR_I_PROTOCOL_KEY);
+	wpabuf_put_le16(msg, wpabuf_len(pi));
+	wpabuf_put_buf(msg, pi);
+	wpabuf_free(pi);
+	pi = NULL;
+
+	/* Wrapped data ({I-nonce, I-capabilities}k1) */
+	pos = clear;
+	/* I-nonce */
+	WPA_PUT_LE16(pos, DPP_ATTR_I_NONCE);
+	pos += 2;
+	WPA_PUT_LE16(pos, nonce_len);
+	pos += 2;
+	os_memcpy(pos, auth->i_nonce, nonce_len);
+	pos += nonce_len;
+	/* I-capabilities */
+	WPA_PUT_LE16(pos, DPP_ATTR_I_CAPABILITIES);
+	pos += 2;
+	WPA_PUT_LE16(pos, 1);
+	pos += 2;
+	auth->i_capab = configurator ? DPP_CAPAB_CONFIGURATOR :
+		DPP_CAPAB_ENROLLEE;
+	*pos++ = auth->i_capab;
+
+	attr_end = wpabuf_put(msg, 0);
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	siv_len = pos - clear;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext", clear, siv_len);
+	if (aes_siv_encrypt(auth->k1, auth->curve->hash_len, clear, siv_len,
+			    2, addr, len, wrapped_data) < 0)
+		goto fail;
+	siv_len += AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, siv_len);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, siv_len);
+	wpabuf_put_data(msg, wrapped_data, siv_len);
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Request frame attributes", msg);
+
+	return auth;
+fail:
+	wpabuf_free(pi);
+	EVP_PKEY_CTX_free(ctx);
+	dpp_auth_deinit(auth);
+	return NULL;
+}
+
+
+struct wpabuf * dpp_build_conf_req(struct dpp_authentication *auth,
+				   const char *json)
+{
+	size_t nonce_len;
+	size_t json_len, clear_len;
+	struct wpabuf *clear = NULL, *msg = NULL;
+	u8 *wrapped;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build configuration request");
+
+	nonce_len = auth->curve->nonce_len;
+	if (random_get_bytes(auth->e_nonce, nonce_len)) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate E-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: E-nonce", auth->e_nonce, nonce_len);
+	json_len = os_strlen(json);
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: configAttr JSON", json, json_len);
+
+	/* { E-nonce, configAttrib }ke */
+	clear_len = 4 + nonce_len + 4 + json_len;
+	clear = wpabuf_alloc(clear_len);
+	msg = wpabuf_alloc(4 + clear_len + AES_BLOCK_SIZE);
+	if (!clear || !msg)
+		goto fail;
+
+	/* E-nonce */
+	wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE);
+	wpabuf_put_le16(clear, nonce_len);
+	wpabuf_put_data(clear, auth->e_nonce, nonce_len);
+
+	/* configAttrib */
+	wpabuf_put_le16(clear, DPP_ATTR_CONFIG_ATTR_OBJ);
+	wpabuf_put_le16(clear, json_len);
+	wpabuf_put_data(clear, json, json_len);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	/* No AES-SIV AD */
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    0, NULL, NULL, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Configuration Request frame attributes", msg);
+	wpabuf_free(clear);
+	return msg;
+
+fail:
+	wpabuf_free(clear);
+	wpabuf_free(msg);
+	return NULL;
+}
+
+
+static void dpp_auth_success(struct dpp_authentication *auth)
+{
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Authentication success - clear temporary keys");
+	os_memset(auth->Mx, 0, sizeof(auth->Mx));
+	os_memset(auth->Nx, 0, sizeof(auth->Nx));
+	os_memset(auth->Lx, 0, sizeof(auth->Lx));
+	os_memset(auth->k1, 0, sizeof(auth->k1));
+	os_memset(auth->k2, 0, sizeof(auth->k2));
+
+	auth->auth_success = 1;
+}
+
+
+static int dpp_gen_r_auth(struct dpp_authentication *auth, u8 *r_auth)
+{
+	struct wpabuf *pix, *prx, *bix, *brx;
+	const u8 *addr[7];
+	size_t len[7];
+	size_t i, num_elem = 0;
+	size_t nonce_len;
+	u8 zero = 0;
+	int res = -1;
+
+	/* R-auth = H(I-nonce | R-nonce | PI.x | PR.x | [BI.x |] BR.x | 0) */
+	nonce_len = auth->curve->nonce_len;
+
+	if (auth->initiator) {
+		pix = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		if (auth->own_bi)
+			bix = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+		else
+			bix = NULL;
+		brx = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+	} else {
+		pix = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		if (auth->peer_bi)
+			bix = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+		else
+			bix = NULL;
+		brx = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+	}
+	if (!pix || !prx || !brx)
+		goto fail;
+
+	addr[num_elem] = auth->i_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = auth->r_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(pix);
+	len[num_elem] = wpabuf_len(pix) / 2;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(prx);
+	len[num_elem] = wpabuf_len(prx) / 2;
+	num_elem++;
+
+	if (bix) {
+		addr[num_elem] = wpabuf_head(bix);
+		len[num_elem] = wpabuf_len(bix) / 2;
+		num_elem++;
+	}
+
+	addr[num_elem] = wpabuf_head(brx);
+	len[num_elem] = wpabuf_len(brx) / 2;
+	num_elem++;
+
+	addr[num_elem] = &zero;
+	len[num_elem] = 1;
+	num_elem++;
+
+	wpa_printf(MSG_DEBUG, "DPP: R-auth hash components");
+	for (i = 0; i < num_elem; i++)
+		wpa_hexdump(MSG_DEBUG, "DPP: hash component", addr[i], len[i]);
+	res = dpp_hash_vector(auth->curve, num_elem, addr, len, r_auth);
+	if (res == 0)
+		wpa_hexdump(MSG_DEBUG, "DPP: R-auth", r_auth,
+			    auth->curve->hash_len);
+fail:
+	wpabuf_free(pix);
+	wpabuf_free(prx);
+	wpabuf_free(bix);
+	wpabuf_free(brx);
+	return res;
+}
+
+
+static int dpp_gen_i_auth(struct dpp_authentication *auth, u8 *i_auth)
+{
+	struct wpabuf *pix = NULL, *prx = NULL, *bix = NULL, *brx = NULL;
+	const u8 *addr[7];
+	size_t len[7];
+	size_t i, num_elem = 0;
+	size_t nonce_len;
+	u8 one = 1;
+	int res = -1;
+
+	/* I-auth = H(R-nonce | I-nonce | PR.x | PI.x | BR.x | [BI.x |] 1) */
+	nonce_len = auth->curve->nonce_len;
+
+	if (auth->initiator) {
+		pix = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		if (auth->own_bi)
+			bix = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+		else
+			bix = NULL;
+		if (!auth->peer_bi)
+			goto fail;
+		brx = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+	} else {
+		pix = dpp_get_pubkey_point(auth->peer_protocol_key, 0);
+		prx = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+		if (auth->peer_bi)
+			bix = dpp_get_pubkey_point(auth->peer_bi->pubkey, 0);
+		else
+			bix = NULL;
+		if (!auth->own_bi)
+			goto fail;
+		brx = dpp_get_pubkey_point(auth->own_bi->pubkey, 0);
+	}
+	if (!pix || !prx || !brx)
+		goto fail;
+
+	addr[num_elem] = auth->r_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = auth->i_nonce;
+	len[num_elem] = nonce_len;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(prx);
+	len[num_elem] = wpabuf_len(prx) / 2;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(pix);
+	len[num_elem] = wpabuf_len(pix) / 2;
+	num_elem++;
+
+	addr[num_elem] = wpabuf_head(brx);
+	len[num_elem] = wpabuf_len(brx) / 2;
+	num_elem++;
+
+	if (bix) {
+		addr[num_elem] = wpabuf_head(bix);
+		len[num_elem] = wpabuf_len(bix) / 2;
+		num_elem++;
+	}
+
+	addr[num_elem] = &one;
+	len[num_elem] = 1;
+	num_elem++;
+
+	wpa_printf(MSG_DEBUG, "DPP: I-auth hash components");
+	for (i = 0; i < num_elem; i++)
+		wpa_hexdump(MSG_DEBUG, "DPP: hash component", addr[i], len[i]);
+	res = dpp_hash_vector(auth->curve, num_elem, addr, len, i_auth);
+	if (res == 0)
+		wpa_hexdump(MSG_DEBUG, "DPP: I-auth", i_auth,
+			    auth->curve->hash_len);
+fail:
+	wpabuf_free(pix);
+	wpabuf_free(prx);
+	wpabuf_free(bix);
+	wpabuf_free(brx);
+	return res;
+}
+
+
+static int dpp_auth_derive_l_responder(struct dpp_authentication *auth)
+{
+	const EC_GROUP *group;
+	EC_POINT *l = NULL;
+	EC_KEY *BI = NULL, *bR = NULL, *pR = NULL;
+	const EC_POINT *BI_point;
+	BN_CTX *bnctx;
+	BIGNUM *lx, *sum, *q;
+	const BIGNUM *bR_bn, *pR_bn;
+	int ret = -1;
+	int num_bytes, offset;
+
+	/* L = ((bR + pR) modulo q) * BI */
+
+	bnctx = BN_CTX_new();
+	sum = BN_new();
+	q = BN_new();
+	lx = BN_new();
+	if (!bnctx || !sum || !q || !lx)
+		goto fail;
+	BI = EVP_PKEY_get1_EC_KEY(auth->peer_bi->pubkey);
+	if (!BI)
+		goto fail;
+	BI_point = EC_KEY_get0_public_key(BI);
+	group = EC_KEY_get0_group(BI);
+	if (!group)
+		goto fail;
+
+	bR = EVP_PKEY_get1_EC_KEY(auth->own_bi->pubkey);
+	pR = EVP_PKEY_get1_EC_KEY(auth->own_protocol_key);
+	if (!bR || !pR)
+		goto fail;
+	bR_bn = EC_KEY_get0_private_key(bR);
+	pR_bn = EC_KEY_get0_private_key(pR);
+	if (!bR_bn || !pR_bn)
+		goto fail;
+	if (EC_GROUP_get_order(group, q, bnctx) != 1 ||
+	    BN_mod_add(sum, bR_bn, pR_bn, q, bnctx) != 1)
+		goto fail;
+	l = EC_POINT_new(group);
+	if (!l ||
+	    EC_POINT_mul(group, l, NULL, BI_point, sum, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, l, lx, NULL,
+						bnctx) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "OpenSSL: failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	num_bytes = BN_num_bytes(lx);
+	if ((size_t) num_bytes > auth->secret_len)
+		goto fail;
+	if (auth->secret_len > (size_t) num_bytes)
+		offset = auth->secret_len - num_bytes;
+	else
+		offset = 0;
+
+	os_memset(auth->Lx, 0, offset);
+	BN_bn2bin(lx, auth->Lx + offset);
+	wpa_hexdump_key(MSG_DEBUG, "DPP: L.x", auth->Lx, auth->secret_len);
+	ret = 0;
+fail:
+	EC_POINT_clear_free(l);
+	EC_KEY_free(BI);
+	EC_KEY_free(bR);
+	EC_KEY_free(pR);
+	BN_clear_free(lx);
+	BN_clear_free(sum);
+	BN_free(q);
+	BN_CTX_free(bnctx);
+	return ret;
+}
+
+
+static int dpp_auth_derive_l_initiator(struct dpp_authentication *auth)
+{
+	const EC_GROUP *group;
+	EC_POINT *l = NULL, *sum = NULL;
+	EC_KEY *bI = NULL, *BR = NULL, *PR = NULL;
+	const EC_POINT *BR_point, *PR_point;
+	BN_CTX *bnctx;
+	BIGNUM *lx;
+	const BIGNUM *bI_bn;
+	int ret = -1;
+	int num_bytes, offset;
+
+	/* L = bI * (BR + PR) */
+
+	bnctx = BN_CTX_new();
+	lx = BN_new();
+	if (!bnctx || !lx)
+		goto fail;
+	BR = EVP_PKEY_get1_EC_KEY(auth->peer_bi->pubkey);
+	PR = EVP_PKEY_get1_EC_KEY(auth->peer_protocol_key);
+	if (!BR || !PR)
+		goto fail;
+	BR_point = EC_KEY_get0_public_key(BR);
+	PR_point = EC_KEY_get0_public_key(PR);
+
+	bI = EVP_PKEY_get1_EC_KEY(auth->own_bi->pubkey);
+	if (!bI)
+		goto fail;
+	group = EC_KEY_get0_group(bI);
+	bI_bn = EC_KEY_get0_private_key(bI);
+	if (!group || !bI_bn)
+		goto fail;
+	sum = EC_POINT_new(group);
+	l = EC_POINT_new(group);
+	if (!sum || !l ||
+	    EC_POINT_add(group, sum, BR_point, PR_point, bnctx) != 1 ||
+	    EC_POINT_mul(group, l, NULL, sum, bI_bn, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, l, lx, NULL,
+						bnctx) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "OpenSSL: failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	num_bytes = BN_num_bytes(lx);
+	if ((size_t) num_bytes > auth->secret_len)
+		goto fail;
+	if (auth->secret_len > (size_t) num_bytes)
+		offset = auth->secret_len - num_bytes;
+	else
+		offset = 0;
+
+	os_memset(auth->Lx, 0, offset);
+	BN_bn2bin(lx, auth->Lx + offset);
+	wpa_hexdump_key(MSG_DEBUG, "DPP: L.x", auth->Lx, auth->secret_len);
+	ret = 0;
+fail:
+	EC_POINT_clear_free(l);
+	EC_KEY_free(bI);
+	EC_KEY_free(BR);
+	EC_KEY_free(PR);
+	BN_clear_free(lx);
+	BN_CTX_free(bnctx);
+	return ret;
+}
+
+
+static int dpp_auth_build_resp(struct dpp_authentication *auth)
+{
+	size_t nonce_len;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	struct wpabuf *msg, *pr = NULL;
+	u8 r_auth[4 + DPP_MAX_HASH_LEN];
+	u8 wrapped_r_auth[4 + DPP_MAX_HASH_LEN + AES_BLOCK_SIZE];
+#define DPP_AUTH_RESP_CLEAR_LEN 2 * (4 + DPP_MAX_NONCE_LEN) + 4 + 1 + \
+		4 + sizeof(wrapped_r_auth)
+	size_t wrapped_r_auth_len;
+	u8 clear[DPP_AUTH_RESP_CLEAR_LEN];
+	u8 wrapped_data[DPP_AUTH_RESP_CLEAR_LEN + AES_BLOCK_SIZE];
+	u8 *pos;
+	const u8 *addr[2];
+	size_t len[2], siv_len, attr_len;
+	u8 *attr_start, *attr_end;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build Authentication Response");
+
+	nonce_len = auth->curve->nonce_len;
+	if (random_get_bytes(auth->r_nonce, nonce_len)) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to generate R-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: R-nonce", auth->r_nonce, nonce_len);
+
+	auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+	if (!auth->own_protocol_key)
+		goto fail;
+
+	pr = dpp_get_pubkey_point(auth->own_protocol_key, 0);
+	if (!pr)
+		goto fail;
+
+	/* ECDH: N = pR * PI */
+	ctx = EVP_PKEY_CTX_new(auth->own_protocol_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, auth->peer_protocol_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Nx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)",
+			auth->Nx, auth->secret_len);
+
+	if (dpp_derive_k2(auth->Nx, auth->secret_len, auth->k2,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	if (auth->own_bi && auth->peer_bi) {
+		/* Mutual authentication */
+		if (dpp_auth_derive_l_responder(auth) < 0)
+			goto fail;
+	}
+
+	if (dpp_derive_ke(auth, auth->ke, auth->curve->hash_len) < 0)
+		goto fail;
+
+	/* R-auth = H(I-nonce | R-nonce | PI.x | PR.x | [BI.x |] BR.x | 0) */
+	WPA_PUT_LE16(r_auth, DPP_ATTR_R_AUTH_TAG);
+	WPA_PUT_LE16(&r_auth[2], auth->curve->hash_len);
+	if (dpp_gen_r_auth(auth, r_auth + 4) < 0 ||
+	    aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    r_auth, 4 + auth->curve->hash_len,
+			    0, NULL, NULL, wrapped_r_auth) < 0)
+		goto fail;
+	wrapped_r_auth_len = 4 + auth->curve->hash_len + AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: {R-auth}ke",
+		    wrapped_r_auth, wrapped_r_auth_len);
+
+	/* Build DPP Authentication Response frame attributes */
+	attr_len = 4 + 1 + 2 * (4 + SHA256_MAC_LEN) +
+		4 + wpabuf_len(pr) + 4 + sizeof(wrapped_data);
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_RESP, attr_len);
+	if (!msg)
+		goto fail;
+	wpabuf_free(auth->resp_msg);
+	auth->resp_msg = msg;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	/* DPP Status */
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, DPP_STATUS_OK);
+
+	/* Responder Bootstrapping Key Hash */
+	wpabuf_put_le16(msg, DPP_ATTR_R_BOOTSTRAP_KEY_HASH);
+	wpabuf_put_le16(msg, SHA256_MAC_LEN);
+	wpabuf_put_data(msg, auth->own_bi->pubkey_hash, SHA256_MAC_LEN);
+
+	if (auth->peer_bi) {
+		/* Mutual authentication */
+		/* Initiator Bootstrapping Key Hash */
+		wpabuf_put_le16(msg, DPP_ATTR_I_BOOTSTRAP_KEY_HASH);
+		wpabuf_put_le16(msg, SHA256_MAC_LEN);
+		wpabuf_put_data(msg, auth->peer_bi->pubkey_hash,
+				SHA256_MAC_LEN);
+	}
+
+	/* Responder Protocol Key */
+	wpabuf_put_le16(msg, DPP_ATTR_R_PROTOCOL_KEY);
+	wpabuf_put_le16(msg, wpabuf_len(pr));
+	wpabuf_put_buf(msg, pr);
+	wpabuf_free(pr);
+	pr = NULL;
+
+	attr_end = wpabuf_put(msg, 0);
+
+	/* Wrapped data ({R-nonce, I-nonce, R-capabilities, {R-auth}ke}k2) */
+	pos = clear;
+	/* R-nonce */
+	WPA_PUT_LE16(pos, DPP_ATTR_R_NONCE);
+	pos += 2;
+	WPA_PUT_LE16(pos, nonce_len);
+	pos += 2;
+	os_memcpy(pos, auth->r_nonce, nonce_len);
+	pos += nonce_len;
+	/* I-nonce */
+	WPA_PUT_LE16(pos, DPP_ATTR_I_NONCE);
+	pos += 2;
+	WPA_PUT_LE16(pos, nonce_len);
+	pos += 2;
+	os_memcpy(pos, auth->i_nonce, nonce_len);
+	pos += nonce_len;
+	/* R-capabilities */
+	WPA_PUT_LE16(pos, DPP_ATTR_R_CAPABILITIES);
+	pos += 2;
+	WPA_PUT_LE16(pos, 1);
+	pos += 2;
+	auth->r_capab = auth->configurator ? DPP_CAPAB_CONFIGURATOR :
+		DPP_CAPAB_ENROLLEE;
+	*pos++ = auth->r_capab;
+	/* {R-auth}ke */
+	WPA_PUT_LE16(pos, DPP_ATTR_WRAPPED_DATA);
+	pos += 2;
+	WPA_PUT_LE16(pos, wrapped_r_auth_len);
+	pos += 2;
+	os_memcpy(pos, wrapped_r_auth, wrapped_r_auth_len);
+	pos += wrapped_r_auth_len;
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	siv_len = pos - clear;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext", clear, siv_len);
+	if (aes_siv_encrypt(auth->k2, auth->curve->hash_len, clear, siv_len,
+			    2, addr, len, wrapped_data) < 0)
+		goto fail;
+	siv_len += AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, siv_len);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, siv_len);
+	wpabuf_put_data(msg, wrapped_data, siv_len);
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Response frame attributes", msg);
+
+	return 0;
+
+fail:
+	wpabuf_free(pr);
+	return -1;
+}
+
+
+static int dpp_auth_build_resp_status(struct dpp_authentication *auth,
+				      enum dpp_status_error status)
+{
+	size_t nonce_len;
+	struct wpabuf *msg;
+#define DPP_AUTH_RESP_CLEAR_LEN2 4 + DPP_MAX_NONCE_LEN + 4 + 1
+	u8 clear[DPP_AUTH_RESP_CLEAR_LEN2];
+	u8 wrapped_data[DPP_AUTH_RESP_CLEAR_LEN2 + AES_BLOCK_SIZE];
+	u8 *pos;
+	const u8 *addr[2];
+	size_t len[2], siv_len, attr_len;
+	u8 *attr_start, *attr_end;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build Authentication Response");
+
+	/* Build DPP Authentication Response frame attributes */
+	attr_len = 4 + 1 + 2 * (4 + SHA256_MAC_LEN) + 4 + sizeof(wrapped_data);
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_RESP, attr_len);
+	if (!msg)
+		goto fail;
+	wpabuf_free(auth->resp_msg);
+	auth->resp_msg = msg;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	/* DPP Status */
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, status);
+
+	/* Responder Bootstrapping Key Hash */
+	wpabuf_put_le16(msg, DPP_ATTR_R_BOOTSTRAP_KEY_HASH);
+	wpabuf_put_le16(msg, SHA256_MAC_LEN);
+	wpabuf_put_data(msg, auth->own_bi->pubkey_hash, SHA256_MAC_LEN);
+
+	if (auth->peer_bi) {
+		/* Mutual authentication */
+		/* Initiator Bootstrapping Key Hash */
+		wpabuf_put_le16(msg, DPP_ATTR_I_BOOTSTRAP_KEY_HASH);
+		wpabuf_put_le16(msg, SHA256_MAC_LEN);
+		wpabuf_put_data(msg, auth->peer_bi->pubkey_hash,
+				SHA256_MAC_LEN);
+	}
+
+	attr_end = wpabuf_put(msg, 0);
+
+	/* Wrapped data ({I-nonce, R-capabilities}k1) */
+	pos = clear;
+	/* I-nonce */
+	nonce_len = auth->curve->nonce_len;
+	WPA_PUT_LE16(pos, DPP_ATTR_I_NONCE);
+	pos += 2;
+	WPA_PUT_LE16(pos, nonce_len);
+	pos += 2;
+	os_memcpy(pos, auth->i_nonce, nonce_len);
+	pos += nonce_len;
+	/* R-capabilities */
+	WPA_PUT_LE16(pos, DPP_ATTR_R_CAPABILITIES);
+	pos += 2;
+	WPA_PUT_LE16(pos, 1);
+	pos += 2;
+	auth->r_capab = auth->configurator ? DPP_CAPAB_CONFIGURATOR :
+		DPP_CAPAB_ENROLLEE;
+	*pos++ = auth->r_capab;
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	siv_len = pos - clear;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext", clear, siv_len);
+	if (aes_siv_encrypt(auth->k1, auth->curve->hash_len, clear, siv_len,
+			    2, addr, len, wrapped_data) < 0)
+		goto fail;
+	siv_len += AES_BLOCK_SIZE;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, siv_len);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, siv_len);
+	wpabuf_put_data(msg, wrapped_data, siv_len);
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Response frame attributes", msg);
+
+	return 0;
+
+fail:
+	return -1;
+}
+
+
+struct dpp_authentication *
+dpp_auth_req_rx(void *msg_ctx, u8 dpp_allowed_roles, int qr_mutual,
+		struct dpp_bootstrap_info *peer_bi,
+		struct dpp_bootstrap_info *own_bi,
+		unsigned int freq, const u8 *hdr, const u8 *attr_start,
+		const u8 *wrapped_data,	u16 wrapped_data_len)
+{
+	EVP_PKEY *pi = NULL;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	const u8 *i_proto, *i_nonce, *i_capab, *i_bootstrap;
+	u16 i_proto_len, i_nonce_len, i_capab_len, i_bootstrap_len;
+	struct dpp_authentication *auth = NULL;
+	size_t attr_len;
+
+	if (wrapped_data_len < AES_BLOCK_SIZE)
+		return NULL;
+
+	attr_len = wrapped_data - 4 - attr_start;
+
+	auth = os_zalloc(sizeof(*auth));
+	if (!auth)
+		goto fail;
+	auth->msg_ctx = msg_ctx;
+	auth->peer_bi = peer_bi;
+	auth->own_bi = own_bi;
+	auth->curve = own_bi->curve;
+	auth->curr_freq = freq;
+
+	i_proto = dpp_get_attr(attr_start, attr_len, DPP_ATTR_I_PROTOCOL_KEY,
+			       &i_proto_len);
+	if (!i_proto) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing required Initiator Protocol Key attribute");
+		goto fail;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Protocol Key",
+		    i_proto, i_proto_len);
+
+	/* M = bR * PI */
+	pi = dpp_set_pubkey_point(own_bi->pubkey, i_proto, i_proto_len);
+	if (!pi) {
+		wpa_printf(MSG_DEBUG, "DPP: Invalid Initiator Protocol Key");
+		goto fail;
+	}
+	dpp_debug_print_key("Peer (Initiator) Protocol Key", pi);
+
+	ctx = EVP_PKEY_CTX_new(own_bi->pubkey, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pi) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Mx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	auth->secret_len = secret_len;
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (M.x)",
+			auth->Mx, auth->secret_len);
+
+	if (dpp_derive_k1(auth->Mx, auth->secret_len, auth->k1,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+	if (aes_siv_decrypt(auth->k1, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	i_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_NONCE,
+			       &i_nonce_len);
+	if (!i_nonce || i_nonce_len != auth->curve->nonce_len) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", i_nonce, i_nonce_len);
+	os_memcpy(auth->i_nonce, i_nonce, i_nonce_len);
+
+	i_capab = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_I_CAPABILITIES,
+			       &i_capab_len);
+	if (!i_capab || i_capab_len < 1) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid I-capabilities");
+		goto fail;
+	}
+	auth->i_capab = i_capab[0];
+	wpa_printf(MSG_DEBUG, "DPP: I-capabilities: 0x%02x", auth->i_capab);
+
+	bin_clear_free(unwrapped, unwrapped_len);
+	unwrapped = NULL;
+
+	switch (auth->i_capab & DPP_CAPAB_ROLE_MASK) {
+	case DPP_CAPAB_ENROLLEE:
+		if (!(dpp_allowed_roles & DPP_CAPAB_CONFIGURATOR)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Local policy does not allow Configurator role");
+			goto not_compatible;
+		}
+		wpa_printf(MSG_DEBUG, "DPP: Acting as Configurator");
+		auth->configurator = 1;
+		break;
+	case DPP_CAPAB_CONFIGURATOR:
+		if (!(dpp_allowed_roles & DPP_CAPAB_ENROLLEE)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Local policy does not allow Enrollee role");
+			goto not_compatible;
+		}
+		wpa_printf(MSG_DEBUG, "DPP: Acting as Enrollee");
+		auth->configurator = 0;
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "DPP: Unexpected role in I-capabilities");
+		goto not_compatible;
+	}
+
+	auth->peer_protocol_key = pi;
+	pi = NULL;
+	if (qr_mutual && !peer_bi && own_bi->type == DPP_BOOTSTRAP_QR_CODE) {
+		char hex[SHA256_MAC_LEN * 2 + 1];
+
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mutual authentication required with QR Codes, but peer info is not yet available - request more time");
+		if (dpp_auth_build_resp_status(auth,
+					       DPP_STATUS_RESPONSE_PENDING) < 0)
+			goto fail;
+		i_bootstrap = dpp_get_attr(attr_start, attr_len,
+					   DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+					   &i_bootstrap_len);
+		if (i_bootstrap && i_bootstrap_len == SHA256_MAC_LEN) {
+			auth->response_pending = 1;
+			os_memcpy(auth->waiting_pubkey_hash,
+				  i_bootstrap, i_bootstrap_len);
+			wpa_snprintf_hex(hex, sizeof(hex), i_bootstrap,
+					 i_bootstrap_len);
+		} else {
+			hex[0] = '\0';
+		}
+
+		wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_SCAN_PEER_QR_CODE
+			"%s", hex);
+		return auth;
+	}
+	if (dpp_auth_build_resp(auth) < 0)
+		goto fail;
+
+	return auth;
+
+not_compatible:
+	wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_NOT_COMPATIBLE
+		"i-capab=0x%02x", auth->i_capab);
+	if (dpp_allowed_roles & DPP_CAPAB_CONFIGURATOR)
+		auth->configurator = 1;
+	else
+		auth->configurator = 0;
+	auth->peer_protocol_key = pi;
+	pi = NULL;
+	if (dpp_auth_build_resp_status(auth, DPP_STATUS_NOT_COMPATIBLE) < 0)
+		goto fail;
+
+	auth->remove_on_tx_status = 1;
+	return auth;
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	EVP_PKEY_free(pi);
+	EVP_PKEY_CTX_free(ctx);
+	dpp_auth_deinit(auth);
+	return NULL;
+}
+
+
+int dpp_notify_new_qr_code(struct dpp_authentication *auth,
+			   struct dpp_bootstrap_info *peer_bi)
+{
+	if (!auth || !auth->response_pending ||
+	    os_memcmp(auth->waiting_pubkey_hash, peer_bi->pubkey_hash,
+		      SHA256_MAC_LEN) != 0)
+		return 0;
+
+	wpa_printf(MSG_DEBUG,
+		   "DPP: New scanned QR Code has matching public key that was needed to continue DPP Authentication exchange with "
+		   MACSTR, MAC2STR(auth->peer_mac_addr));
+	auth->peer_bi = peer_bi;
+
+	if (dpp_auth_build_resp(auth) < 0)
+		return -1;
+
+	return 1;
+}
+
+
+static struct wpabuf * dpp_auth_build_conf(struct dpp_authentication *auth)
+{
+	struct wpabuf *msg;
+	u8 i_auth[4 + DPP_MAX_HASH_LEN];
+	size_t i_auth_len;
+	const u8 *addr[2];
+	size_t len[2], attr_len;
+	u8 *wrapped_i_auth;
+	u8 *attr_start, *attr_end;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build Authentication Confirmation");
+
+	i_auth_len = 4 + auth->curve->hash_len;
+	/* Build DPP Authentication Confirmation frame attributes */
+	attr_len = 4 + 1 + 2 * (4 + SHA256_MAC_LEN) +
+		4 + i_auth_len + AES_BLOCK_SIZE;
+	msg = dpp_alloc_msg(DPP_PA_AUTHENTICATION_CONF, attr_len);
+	if (!msg)
+		goto fail;
+
+	attr_start = wpabuf_put(msg, 0);
+
+	/* DPP Status */
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, DPP_STATUS_OK);
+
+	/* Responder Bootstrapping Key Hash */
+	wpabuf_put_le16(msg, DPP_ATTR_R_BOOTSTRAP_KEY_HASH);
+	wpabuf_put_le16(msg, SHA256_MAC_LEN);
+	wpabuf_put_data(msg, auth->peer_bi->pubkey_hash, SHA256_MAC_LEN);
+
+	if (auth->own_bi) {
+		/* Mutual authentication */
+		/* Initiator Bootstrapping Key Hash */
+		wpabuf_put_le16(msg, DPP_ATTR_I_BOOTSTRAP_KEY_HASH);
+		wpabuf_put_le16(msg, SHA256_MAC_LEN);
+		wpabuf_put_data(msg, auth->own_bi->pubkey_hash, SHA256_MAC_LEN);
+	}
+
+	attr_end = wpabuf_put(msg, 0);
+
+	/* OUI, OUI type, Crypto Suite, DPP frame type */
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = 3 + 1 + 1 + 1;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+
+	/* Attributes before Wrapped Data */
+	addr[1] = attr_start;
+	len[1] = attr_end - attr_start;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, i_auth_len + AES_BLOCK_SIZE);
+	wrapped_i_auth = wpabuf_put(msg, i_auth_len + AES_BLOCK_SIZE);
+	/* I-auth = H(R-nonce | I-nonce | PR.x | PI.x | BR.x | [BI.x |] 1) */
+	WPA_PUT_LE16(i_auth, DPP_ATTR_I_AUTH_TAG);
+	WPA_PUT_LE16(&i_auth[2], auth->curve->hash_len);
+	if (dpp_gen_i_auth(auth, i_auth + 4) < 0 ||
+	    aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    i_auth, i_auth_len,
+			    2, addr, len, wrapped_i_auth) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: {I-auth}ke",
+		    wrapped_i_auth, i_auth_len + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Authentication Confirmation frame attributes",
+			msg);
+	dpp_auth_success(auth);
+
+	return msg;
+
+fail:
+	return NULL;
+}
+
+
+static void
+dpp_auth_resp_rx_status(struct dpp_authentication *auth, const u8 *hdr,
+			const u8 *attr_start, size_t attr_len,
+			const u8 *wrapped_data, u16 wrapped_data_len,
+			enum dpp_status_error status)
+{
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	const u8 *i_nonce, *r_capab;
+	u16 i_nonce_len, r_capab_len;
+
+	if (status == DPP_STATUS_NOT_COMPATIBLE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder reported incompatible roles");
+	} else if (status == DPP_STATUS_RESPONSE_PENDING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder reported more time needed");
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Responder reported failure (status %d)",
+			   status);
+		return;
+	}
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+	if (aes_siv_decrypt(auth->k1, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	i_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_NONCE,
+			       &i_nonce_len);
+	if (!i_nonce || i_nonce_len != auth->curve->nonce_len) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", i_nonce, i_nonce_len);
+	if (os_memcmp(auth->i_nonce, i_nonce, i_nonce_len) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: I-nonce mismatch");
+		goto fail;
+	}
+
+	r_capab = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_R_CAPABILITIES,
+			       &r_capab_len);
+	if (!r_capab || r_capab_len < 1) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid R-capabilities");
+		goto fail;
+	}
+	auth->r_capab = r_capab[0];
+	wpa_printf(MSG_DEBUG, "DPP: R-capabilities: 0x%02x", auth->r_capab);
+	if (status == DPP_STATUS_NOT_COMPATIBLE) {
+		wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_NOT_COMPATIBLE
+			"r-capab=0x%02x", auth->r_capab);
+	} else if (status == DPP_STATUS_RESPONSE_PENDING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Continue waiting for full DPP Authentication Response");
+		wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_RESPONSE_PENDING);
+	}
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+}
+
+
+struct wpabuf *
+dpp_auth_resp_rx(struct dpp_authentication *auth, const u8 *hdr,
+		 const u8 *attr_start, size_t attr_len)
+{
+	EVP_PKEY *pr;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t secret_len;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL, *unwrapped2 = NULL;
+	size_t unwrapped_len = 0, unwrapped2_len = 0;
+	const u8 *r_bootstrap, *i_bootstrap, *wrapped_data, *status, *r_proto,
+		*r_nonce, *i_nonce, *r_capab, *wrapped2, *r_auth;
+	u16 r_bootstrap_len, i_bootstrap_len, wrapped_data_len, status_len,
+		r_proto_len, r_nonce_len, i_nonce_len, r_capab_len,
+		wrapped2_len, r_auth_len;
+	u8 r_auth2[DPP_MAX_HASH_LEN];
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing required Wrapped data attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Wrapped data",
+		    wrapped_data, wrapped_data_len);
+
+	if (wrapped_data_len < AES_BLOCK_SIZE)
+		return NULL;
+
+	attr_len = wrapped_data - 4 - attr_start;
+
+	r_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
+				   &r_bootstrap_len);
+	if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required Responder Bootstrapping Key Hash attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Responder Bootstrapping Key Hash",
+		    r_bootstrap, r_bootstrap_len);
+	if (os_memcmp(r_bootstrap, auth->peer_bi->pubkey_hash,
+		      SHA256_MAC_LEN) != 0) {
+		wpa_hexdump(MSG_DEBUG,
+			    "DPP: Expected Responder Bootstrapping Key Hash",
+			    auth->peer_bi->pubkey_hash, SHA256_MAC_LEN);
+		return NULL;
+	}
+
+	i_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+				   &i_bootstrap_len);
+	if (i_bootstrap) {
+		if (i_bootstrap_len != SHA256_MAC_LEN) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Invalid Initiator Bootstrapping Key Hash attribute");
+			return NULL;
+		}
+		wpa_hexdump(MSG_MSGDUMP,
+			    "DPP: Initiator Bootstrapping Key Hash",
+			    i_bootstrap, i_bootstrap_len);
+		if (!auth->own_bi ||
+		    os_memcmp(i_bootstrap, auth->own_bi->pubkey_hash,
+			      SHA256_MAC_LEN) != 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Initiator Bootstrapping Key Hash attribute did not match");
+			return NULL;
+		}
+	}
+
+	status = dpp_get_attr(attr_start, attr_len, DPP_ATTR_STATUS,
+			      &status_len);
+	if (!status || status_len < 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required DPP Status attribute");
+		return NULL;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]);
+	auth->auth_resp_status = status[0];
+	if (status[0] != DPP_STATUS_OK) {
+		dpp_auth_resp_rx_status(auth, hdr, attr_start,
+					attr_len, wrapped_data,
+					wrapped_data_len, status[0]);
+		return NULL;
+	}
+
+	r_proto = dpp_get_attr(attr_start, attr_len, DPP_ATTR_R_PROTOCOL_KEY,
+			       &r_proto_len);
+	if (!r_proto) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing required Responder Protocol Key attribute");
+		return NULL;
+	}
+	wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Protocol Key",
+		    r_proto, r_proto_len);
+
+	/* N = pI * PR */
+	pr = dpp_set_pubkey_point(auth->own_protocol_key, r_proto, r_proto_len);
+	if (!pr) {
+		wpa_printf(MSG_DEBUG, "DPP: Invalid Responder Protocol Key");
+		return NULL;
+	}
+	dpp_debug_print_key("Peer (Responder) Protocol Key", pr);
+
+	ctx = EVP_PKEY_CTX_new(auth->own_protocol_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pr) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &secret_len) != 1 ||
+	    secret_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, auth->Nx, &secret_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	EVP_PKEY_CTX_free(ctx);
+	ctx = NULL;
+	auth->peer_protocol_key = pr;
+	pr = NULL;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)",
+			auth->Nx, auth->secret_len);
+
+	if (dpp_derive_k2(auth->Nx, auth->secret_len, auth->k2,
+			  auth->curve->hash_len) < 0)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+	if (aes_siv_decrypt(auth->k2, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	r_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_R_NONCE,
+			       &r_nonce_len);
+	if (!r_nonce || r_nonce_len != auth->curve->nonce_len) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid R-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: R-nonce", r_nonce, r_nonce_len);
+	os_memcpy(auth->r_nonce, r_nonce, r_nonce_len);
+
+	i_nonce = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_NONCE,
+			       &i_nonce_len);
+	if (!i_nonce || i_nonce_len != auth->curve->nonce_len) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid I-nonce");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: I-nonce", i_nonce, i_nonce_len);
+	if (os_memcmp(auth->i_nonce, i_nonce, i_nonce_len) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: I-nonce mismatch");
+		goto fail;
+	}
+
+	if (auth->own_bi && auth->peer_bi) {
+		/* Mutual authentication */
+		if (dpp_auth_derive_l_initiator(auth) < 0)
+			goto fail;
+	}
+
+	if (dpp_derive_ke(auth, auth->ke, auth->curve->hash_len) < 0)
+		goto fail;
+
+	r_capab = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_R_CAPABILITIES,
+			       &r_capab_len);
+	if (!r_capab || r_capab_len < 1) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing or invalid R-capabilities");
+		goto fail;
+	}
+	auth->r_capab = r_capab[0];
+	wpa_printf(MSG_DEBUG, "DPP: R-capabilities: 0x%02x", auth->r_capab);
+	if ((auth->configurator && (auth->r_capab & DPP_CAPAB_CONFIGURATOR)) ||
+	    (!auth->configurator && (auth->r_capab & DPP_CAPAB_ENROLLEE))) {
+		wpa_printf(MSG_DEBUG, "DPP: Incompatible role selection");
+		goto fail;
+	}
+
+	wrapped2 = dpp_get_attr(unwrapped, unwrapped_len,
+				DPP_ATTR_WRAPPED_DATA, &wrapped2_len);
+	if (!wrapped2 || wrapped2_len < AES_BLOCK_SIZE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Secondary Wrapped Data");
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped2, wrapped2_len);
+	unwrapped2_len = wrapped2_len - AES_BLOCK_SIZE;
+	unwrapped2 = os_malloc(unwrapped2_len);
+	if (!unwrapped2)
+		goto fail;
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped2, wrapped2_len,
+			    0, NULL, NULL, unwrapped2) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped2, unwrapped2_len);
+
+	if (dpp_check_attrs(unwrapped2, unwrapped2_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in secondary unwrapped data");
+		goto fail;
+	}
+
+	r_auth = dpp_get_attr(unwrapped2, unwrapped2_len, DPP_ATTR_R_AUTH_TAG,
+			       &r_auth_len);
+	if (!r_auth || r_auth_len != auth->curve->hash_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Responder Authenticating Tag");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Received Responder Authenticating Tag",
+		    r_auth, r_auth_len);
+	/* R-auth' = H(I-nonce | R-nonce | PI.x | PR.x | [BI.x |] BR.x | 0) */
+	if (dpp_gen_r_auth(auth, r_auth2) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: Calculated Responder Authenticating Tag",
+		    r_auth2, r_auth_len);
+	if (os_memcmp(r_auth, r_auth2, r_auth_len) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mismatching Responder Authenticating Tag");
+		goto fail;
+	}
+
+	bin_clear_free(unwrapped, unwrapped_len);
+	bin_clear_free(unwrapped2, unwrapped2_len);
+
+	return dpp_auth_build_conf(auth);
+
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	bin_clear_free(unwrapped2, unwrapped2_len);
+	EVP_PKEY_free(pr);
+	EVP_PKEY_CTX_free(ctx);
+	return NULL;
+}
+
+
+int dpp_auth_conf_rx(struct dpp_authentication *auth, const u8 *hdr,
+		     const u8 *attr_start, size_t attr_len)
+{
+	const u8 *r_bootstrap, *i_bootstrap, *wrapped_data, *status, *i_auth;
+	u16 r_bootstrap_len, i_bootstrap_len, wrapped_data_len, status_len,
+		i_auth_len;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	u8 i_auth2[DPP_MAX_HASH_LEN];
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing required Wrapped data attribute");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Wrapped data",
+		    wrapped_data, wrapped_data_len);
+
+	if (wrapped_data_len < AES_BLOCK_SIZE)
+		return -1;
+
+	attr_len = wrapped_data - 4 - attr_start;
+
+	r_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
+				   &r_bootstrap_len);
+	if (!r_bootstrap || r_bootstrap > wrapped_data ||
+	    r_bootstrap_len != SHA256_MAC_LEN) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required Responder Bootstrapping Key Hash attribute");
+		return -1;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Responder Bootstrapping Key Hash",
+		    r_bootstrap, r_bootstrap_len);
+	if (os_memcmp(r_bootstrap, auth->own_bi->pubkey_hash,
+		      SHA256_MAC_LEN) != 0) {
+		wpa_hexdump(MSG_DEBUG,
+			    "DPP: Expected Responder Bootstrapping Key Hash",
+			    auth->peer_bi->pubkey_hash, SHA256_MAC_LEN);
+		return -1;
+	}
+
+	i_bootstrap = dpp_get_attr(attr_start, attr_len,
+				   DPP_ATTR_I_BOOTSTRAP_KEY_HASH,
+				   &i_bootstrap_len);
+	if (i_bootstrap) {
+		if (i_bootstrap > wrapped_data ||
+		    i_bootstrap_len != SHA256_MAC_LEN) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Invalid Initiator Bootstrapping Key Hash attribute");
+			return -1;
+		}
+		wpa_hexdump(MSG_MSGDUMP,
+			    "DPP: Initiator Bootstrapping Key Hash",
+			    i_bootstrap, i_bootstrap_len);
+		if (!auth->peer_bi ||
+		    os_memcmp(i_bootstrap, auth->peer_bi->pubkey_hash,
+			      SHA256_MAC_LEN) != 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Initiator Bootstrapping Key Hash attribute did not match");
+			return -1;
+		}
+	}
+
+	status = dpp_get_attr(attr_start, attr_len, DPP_ATTR_STATUS,
+			      &status_len);
+	if (!status || status_len < 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required DPP Status attribute");
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]);
+	if (status[0] != DPP_STATUS_OK) {
+		wpa_printf(MSG_DEBUG, "DPP: Authentication failed");
+		return -1;
+	}
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	addr[1] = attr_start;
+	len[1] = attr_len;
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		return -1;
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	i_auth = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_AUTH_TAG,
+			      &i_auth_len);
+	if (!i_auth || i_auth_len != auth->curve->hash_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Initiator Authenticating Tag");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Received Initiator Authenticating Tag",
+		    i_auth, i_auth_len);
+	/* I-auth' = H(R-nonce | I-nonce | PR.x | PI.x | BR.x | [BI.x |] 1) */
+	if (dpp_gen_i_auth(auth, i_auth2) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: Calculated Initiator Authenticating Tag",
+		    i_auth2, i_auth_len);
+	if (os_memcmp(i_auth, i_auth2, i_auth_len) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mismatching Initiator Authenticating Tag");
+		goto fail;
+	}
+
+	bin_clear_free(unwrapped, unwrapped_len);
+	dpp_auth_success(auth);
+	return 0;
+fail:
+	bin_clear_free(unwrapped, unwrapped_len);
+	return -1;
+}
+
+
+void dpp_configuration_free(struct dpp_configuration *conf)
+{
+	if (!conf)
+		return;
+	str_clear_free(conf->passphrase);
+	bin_clear_free(conf, sizeof(*conf));
+}
+
+
+void dpp_auth_deinit(struct dpp_authentication *auth)
+{
+	if (!auth)
+		return;
+	dpp_configuration_free(auth->conf_ap);
+	dpp_configuration_free(auth->conf_sta);
+	EVP_PKEY_free(auth->own_protocol_key);
+	EVP_PKEY_free(auth->peer_protocol_key);
+	wpabuf_free(auth->req_msg);
+	wpabuf_free(auth->resp_msg);
+	wpabuf_free(auth->conf_req);
+	os_free(auth->connector);
+	wpabuf_free(auth->net_access_key);
+	wpabuf_free(auth->c_sign_key);
+#ifdef CONFIG_TESTING_OPTIONS
+	os_free(auth->config_obj_override);
+	os_free(auth->discovery_override);
+	os_free(auth->groups_override);
+#endif /* CONFIG_TESTING_OPTIONS */
+	bin_clear_free(auth, sizeof(*auth));
+}
+
+
+static struct wpabuf *
+dpp_build_conf_start(struct dpp_authentication *auth,
+		     struct dpp_configuration *conf, size_t tailroom)
+{
+	struct wpabuf *buf;
+	char ssid[6 * sizeof(conf->ssid) + 1];
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->discovery_override)
+		tailroom += os_strlen(auth->discovery_override);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	buf = wpabuf_alloc(200 + tailroom);
+	if (!buf)
+		return NULL;
+	wpabuf_put_str(buf, "{\"wi-fi_tech\":\"infra\",\"discovery\":");
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->discovery_override) {
+		wpa_printf(MSG_DEBUG, "DPP: TESTING - discovery override: '%s'",
+			   auth->discovery_override);
+		wpabuf_put_str(buf, auth->discovery_override);
+		wpabuf_put_u8(buf, ',');
+		return buf;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	wpabuf_put_str(buf, "{\"ssid\":\"");
+	json_escape_string(ssid, sizeof(ssid),
+			   (const char *) conf->ssid, conf->ssid_len);
+	wpabuf_put_str(buf, ssid);
+	wpabuf_put_str(buf, "\"");
+	/* TODO: optional channel information */
+	wpabuf_put_str(buf, "},");
+
+	return buf;
+}
+
+
+static int dpp_bn2bin_pad(const BIGNUM *bn, u8 *pos, size_t len)
+{
+	int num_bytes, offset;
+
+	num_bytes = BN_num_bytes(bn);
+	if ((size_t) num_bytes > len)
+		return -1;
+	offset = len - num_bytes;
+	os_memset(pos, 0, offset);
+	BN_bn2bin(bn, pos + offset);
+	return 0;
+}
+
+
+static int dpp_build_jwk(struct wpabuf *buf, const char *name, EVP_PKEY *key,
+			 const char *kid, const struct dpp_curve_params *curve)
+{
+	struct wpabuf *pub;
+	const u8 *pos;
+	char *x = NULL, *y = NULL;
+	int ret = -1;
+
+	pub = dpp_get_pubkey_point(key, 0);
+	if (!pub)
+		goto fail;
+	pos = wpabuf_head(pub);
+	x = (char *) base64_url_encode(pos, curve->prime_len, NULL, 0);
+	pos += curve->prime_len;
+	y = (char *) base64_url_encode(pos, curve->prime_len, NULL, 0);
+	if (!x || !y)
+		goto fail;
+
+	wpabuf_put_str(buf, "\"");
+	wpabuf_put_str(buf, name);
+	wpabuf_put_str(buf, "\":{\"kty\":\"EC\",\"crv\":\"");
+	wpabuf_put_str(buf, curve->jwk_crv);
+	wpabuf_put_str(buf, "\",\"x\":\"");
+	wpabuf_put_str(buf, x);
+	wpabuf_put_str(buf, "\",\"y\":\"");
+	wpabuf_put_str(buf, y);
+	if (kid) {
+		wpabuf_put_str(buf, "\",\"kid\":\"");
+		wpabuf_put_str(buf, kid);
+	}
+	wpabuf_put_str(buf, "\"}");
+	ret = 0;
+fail:
+	wpabuf_free(pub);
+	os_free(x);
+	os_free(y);
+	return ret;
+}
+
+
+static struct wpabuf *
+dpp_build_conf_obj_dpp(struct dpp_authentication *auth, int ap,
+		       struct dpp_configuration *conf)
+{
+	struct wpabuf *buf = NULL;
+	char *signed1 = NULL, *signed2 = NULL, *signed3 = NULL;
+	size_t tailroom;
+	const struct dpp_curve_params *curve;
+	char jws_prot_hdr[100];
+	size_t signed1_len, signed2_len, signed3_len;
+	struct wpabuf *dppcon = NULL;
+	unsigned char *signature = NULL;
+	const unsigned char *p;
+	size_t signature_len;
+	EVP_MD_CTX *md_ctx = NULL;
+	ECDSA_SIG *sig = NULL;
+	char *dot = ".";
+	const EVP_MD *sign_md;
+	const BIGNUM *r, *s;
+	size_t extra_len = 1000;
+
+	if (!auth->conf) {
+		wpa_printf(MSG_INFO,
+			   "DPP: No configurator specified - cannot generate DPP config object");
+		goto fail;
+	}
+	curve = auth->conf->curve;
+	if (curve->hash_len == SHA256_MAC_LEN) {
+		sign_md = EVP_sha256();
+	} else if (curve->hash_len == SHA384_MAC_LEN) {
+		sign_md = EVP_sha384();
+	} else if (curve->hash_len == SHA512_MAC_LEN) {
+		sign_md = EVP_sha512();
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: Unknown signature algorithm");
+		goto fail;
+	}
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->groups_override)
+		extra_len += os_strlen(auth->groups_override);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	/* Connector (JSON dppCon object) */
+	dppcon = wpabuf_alloc(extra_len + 2 * auth->curve->prime_len * 4 / 3);
+	if (!dppcon)
+		goto fail;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->groups_override) {
+		wpabuf_put_u8(dppcon, '{');
+		if (auth->groups_override) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: TESTING - groups override: '%s'",
+				   auth->groups_override);
+			wpabuf_put_str(dppcon, "\"groups\":");
+			wpabuf_put_str(dppcon, auth->groups_override);
+			wpabuf_put_u8(dppcon, ',');
+		}
+		goto skip_groups;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+	wpabuf_put_str(dppcon, "{\"groups\":[{\"groupId\":\"*\",");
+	wpabuf_printf(dppcon, "\"netRole\":\"%s\"}],", ap ? "ap" : "sta");
+#ifdef CONFIG_TESTING_OPTIONS
+skip_groups:
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (dpp_build_jwk(dppcon, "netAccessKey", auth->peer_protocol_key, NULL,
+			  auth->curve) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to build netAccessKey JWK");
+		goto fail;
+	}
+	if (conf->netaccesskey_expiry) {
+		struct os_tm tm;
+
+		if (os_gmtime(conf->netaccesskey_expiry, &tm) < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Failed to generate expiry string");
+			goto fail;
+		}
+		wpabuf_printf(dppcon,
+			      ",\"expiry\":\"%04u-%02u-%02uT%02u:%02u:%02uZ\"",
+			      tm.year, tm.month, tm.day,
+			      tm.hour, tm.min, tm.sec);
+	}
+	wpabuf_put_u8(dppcon, '}');
+	wpa_printf(MSG_DEBUG, "DPP: dppCon: %s",
+		   (const char *) wpabuf_head(dppcon));
+
+	os_snprintf(jws_prot_hdr, sizeof(jws_prot_hdr),
+		    "{\"typ\":\"dppCon\",\"kid\":\"%s\",\"alg\":\"%s\"}",
+		    auth->conf->kid, curve->jws_alg);
+	signed1 = (char *) base64_url_encode((unsigned char *) jws_prot_hdr,
+					     os_strlen(jws_prot_hdr),
+					     &signed1_len, 0);
+	signed2 = (char *) base64_url_encode(wpabuf_head(dppcon),
+					     wpabuf_len(dppcon),
+					     &signed2_len, 0);
+	if (!signed1 || !signed2)
+		goto fail;
+
+	md_ctx = EVP_MD_CTX_create();
+	if (!md_ctx)
+		goto fail;
+
+	ERR_clear_error();
+	if (EVP_DigestSignInit(md_ctx, NULL, sign_md, NULL,
+			       auth->conf->csign) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignInit failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	if (EVP_DigestSignUpdate(md_ctx, signed1, signed1_len) != 1 ||
+	    EVP_DigestSignUpdate(md_ctx, dot, 1) != 1 ||
+	    EVP_DigestSignUpdate(md_ctx, signed2, signed2_len) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignUpdate failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	if (EVP_DigestSignFinal(md_ctx, NULL, &signature_len) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignFinal failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	signature = os_malloc(signature_len);
+	if (!signature)
+		goto fail;
+	if (EVP_DigestSignFinal(md_ctx, signature, &signature_len) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestSignFinal failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: signedConnector ECDSA signature (DER)",
+		    signature, signature_len);
+	/* Convert to raw coordinates r,s */
+	p = signature;
+	sig = d2i_ECDSA_SIG(NULL, &p, signature_len);
+	if (!sig)
+		goto fail;
+	ECDSA_SIG_get0(sig, &r, &s);
+	if (dpp_bn2bin_pad(r, signature, curve->prime_len) < 0 ||
+	    dpp_bn2bin_pad(s, signature + curve->prime_len,
+			   curve->prime_len) < 0)
+		goto fail;
+	signature_len = 2 * curve->prime_len;
+	wpa_hexdump(MSG_DEBUG, "DPP: signedConnector ECDSA signature (raw r,s)",
+		    signature, signature_len);
+	signed3 = (char *) base64_url_encode(signature, signature_len,
+					     &signed3_len, 0);
+	if (!signed3)
+		goto fail;
+
+	tailroom = 1000;
+	tailroom += 2 * curve->prime_len * 4 / 3 + os_strlen(auth->conf->kid);
+	tailroom += signed1_len + signed2_len + signed3_len;
+	buf = dpp_build_conf_start(auth, conf, tailroom);
+	if (!buf)
+		return NULL;
+
+	wpabuf_put_str(buf, "\"cred\":{\"akm\":\"dpp\",\"signedConnector\":\"");
+	wpabuf_put_str(buf, signed1);
+	wpabuf_put_u8(buf, '.');
+	wpabuf_put_str(buf, signed2);
+	wpabuf_put_u8(buf, '.');
+	wpabuf_put_str(buf, signed3);
+	wpabuf_put_str(buf, "\",");
+	if (dpp_build_jwk(buf, "csign", auth->conf->csign, auth->conf->kid,
+			  curve) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to build csign JWK");
+		goto fail;
+	}
+
+	wpabuf_put_str(buf, "}}");
+
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Configuration Object",
+			      wpabuf_head(buf), wpabuf_len(buf));
+
+out:
+	EVP_MD_CTX_destroy(md_ctx);
+	ECDSA_SIG_free(sig);
+	os_free(signed1);
+	os_free(signed2);
+	os_free(signed3);
+	os_free(signature);
+	wpabuf_free(dppcon);
+	return buf;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: Failed to build configuration object");
+	wpabuf_free(buf);
+	buf = NULL;
+	goto out;
+}
+
+
+static struct wpabuf *
+dpp_build_conf_obj_legacy(struct dpp_authentication *auth, int ap,
+			  struct dpp_configuration *conf)
+{
+	struct wpabuf *buf;
+
+	buf = dpp_build_conf_start(auth, conf, 1000);
+	if (!buf)
+		return NULL;
+
+	wpabuf_put_str(buf, "\"cred\":{\"akm\":\"psk\",");
+	if (conf->passphrase) {
+		char pass[63 * 6 + 1];
+
+		if (os_strlen(conf->passphrase) > 63) {
+			wpabuf_free(buf);
+			return NULL;
+		}
+
+		json_escape_string(pass, sizeof(pass), conf->passphrase,
+				   os_strlen(conf->passphrase));
+		wpabuf_put_str(buf, "\"pass\":\"");
+		wpabuf_put_str(buf, pass);
+		wpabuf_put_str(buf, "\"");
+	} else {
+		char psk[2 * sizeof(conf->psk) + 1];
+
+		wpa_snprintf_hex(psk, sizeof(psk),
+				 conf->psk, sizeof(conf->psk));
+		wpabuf_put_str(buf, "\"psk_hex\":\"");
+		wpabuf_put_str(buf, psk);
+		wpabuf_put_str(buf, "\"");
+	}
+	wpabuf_put_str(buf, "}}");
+
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Configuration Object (legacy)",
+			      wpabuf_head(buf), wpabuf_len(buf));
+
+	return buf;
+}
+
+
+static struct wpabuf *
+dpp_build_conf_obj(struct dpp_authentication *auth, int ap)
+{
+	struct dpp_configuration *conf;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (auth->config_obj_override) {
+		wpa_printf(MSG_DEBUG, "DPP: Testing - Config Object override");
+		return wpabuf_alloc_copy(auth->config_obj_override,
+					 os_strlen(auth->config_obj_override));
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	conf = ap ? auth->conf_ap : auth->conf_sta;
+	if (!conf) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No configuration available for Enrollee(%s) - reject configuration request",
+			   ap ? "ap" : "sta");
+		return NULL;
+	}
+
+	if (conf->dpp)
+		return dpp_build_conf_obj_dpp(auth, ap, conf);
+	return dpp_build_conf_obj_legacy(auth, ap, conf);
+}
+
+
+static struct wpabuf *
+dpp_build_conf_resp(struct dpp_authentication *auth, const u8 *e_nonce,
+		    u16 e_nonce_len, int ap)
+{
+	struct wpabuf *conf;
+	size_t clear_len;
+	struct wpabuf *clear = NULL, *msg = NULL;
+	u8 *wrapped;
+	const u8 *addr[1];
+	size_t len[1];
+	enum dpp_status_error status;
+
+	conf = dpp_build_conf_obj(auth, ap);
+	if (conf) {
+		wpa_hexdump_ascii(MSG_DEBUG, "DPP: configurationObject JSON",
+				  wpabuf_head(conf), wpabuf_len(conf));
+	}
+	status = conf ? DPP_STATUS_OK : DPP_STATUS_CONFIGURE_FAILURE;
+
+	/* { E-nonce, configurationObject}ke */
+	clear_len = 4 + e_nonce_len;
+	if (conf)
+		clear_len += 4 + wpabuf_len(conf);
+	clear = wpabuf_alloc(clear_len);
+	msg = wpabuf_alloc(4 + 1 + 4 + clear_len + AES_BLOCK_SIZE);
+	if (!clear || !msg)
+		goto fail;
+
+	/* E-nonce */
+	wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE);
+	wpabuf_put_le16(clear, e_nonce_len);
+	wpabuf_put_data(clear, e_nonce, e_nonce_len);
+
+	if (conf) {
+		wpabuf_put_le16(clear, DPP_ATTR_CONFIG_OBJ);
+		wpabuf_put_le16(clear, wpabuf_len(conf));
+		wpabuf_put_buf(clear, conf);
+		wpabuf_free(conf);
+		conf = NULL;
+	}
+
+	/* DPP Status */
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, status);
+
+	addr[0] = wpabuf_head(msg);
+	len[0] = wpabuf_len(msg);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD", addr[0], len[0]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(auth->ke, auth->curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    1, addr, len, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wpabuf_free(clear);
+	clear = NULL;
+
+	wpa_hexdump_buf(MSG_DEBUG,
+			"DPP: Configuration Response attributes", msg);
+	return msg;
+fail:
+	wpabuf_free(conf);
+	wpabuf_free(clear);
+	wpabuf_free(msg);
+	return NULL;
+}
+
+
+struct wpabuf *
+dpp_conf_req_rx(struct dpp_authentication *auth, const u8 *attr_start,
+		size_t attr_len)
+{
+	const u8 *wrapped_data, *e_nonce, *config_attr;
+	u16 wrapped_data_len, e_nonce_len, config_attr_len;
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	struct wpabuf *resp = NULL;
+	struct json_token *root = NULL, *token;
+	int ap;
+
+	if (dpp_check_attrs(attr_start, attr_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in config request");
+		return NULL;
+	}
+
+	wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required Wrapped data attribute");
+		return NULL;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		return NULL;
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    0, NULL, NULL, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	e_nonce = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_ENROLLEE_NONCE,
+			       &e_nonce_len);
+	if (!e_nonce || e_nonce_len != auth->curve->nonce_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Enrollee Nonce attribute");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Enrollee Nonce", e_nonce, e_nonce_len);
+
+	config_attr = dpp_get_attr(unwrapped, unwrapped_len,
+				   DPP_ATTR_CONFIG_ATTR_OBJ,
+				   &config_attr_len);
+	if (!config_attr) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Config Attributes attribute");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: Config Attributes",
+			  config_attr, config_attr_len);
+
+	root = json_parse((const char *) config_attr, config_attr_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not parse Config Attributes");
+		goto fail;
+	}
+
+	token = json_get_member(root, "name");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No Config Attributes - name");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Enrollee name = '%s'", token->string);
+
+	token = json_get_member(root, "wi-fi_tech");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No Config Attributes - wi-fi_tech");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: wi-fi_tech = '%s'", token->string);
+	if (os_strcmp(token->string, "infra") != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported wi-fi_tech '%s'",
+			   token->string);
+		goto fail;
+	}
+
+	token = json_get_member(root, "netRole");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No Config Attributes - netRole");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: netRole = '%s'", token->string);
+	if (os_strcmp(token->string, "sta") == 0) {
+		ap = 0;
+	} else if (os_strcmp(token->string, "ap") == 0) {
+		ap = 1;
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported netRole '%s'",
+			   token->string);
+		goto fail;
+	}
+
+	resp = dpp_build_conf_resp(auth, e_nonce, e_nonce_len, ap);
+
+fail:
+	json_free(root);
+	os_free(unwrapped);
+	return resp;
+}
+
+
+static struct wpabuf *
+dpp_parse_jws_prot_hdr(const struct dpp_curve_params *curve,
+		       const u8 *prot_hdr, u16 prot_hdr_len,
+		       const EVP_MD **ret_md)
+{
+	struct json_token *root, *token;
+	struct wpabuf *kid = NULL;
+
+	root = json_parse((const char *) prot_hdr, prot_hdr_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: JSON parsing failed for JWS Protected Header");
+		goto fail;
+	}
+
+	if (root->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: JWS Protected Header root is not an object");
+		goto fail;
+	}
+
+	token = json_get_member(root, "typ");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No typ string value found");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: JWS Protected Header typ=%s",
+		   token->string);
+	if (os_strcmp(token->string, "dppCon") != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported JWS Protected Header typ=%s",
+			   token->string);
+		goto fail;
+	}
+
+	token = json_get_member(root, "alg");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No alg string value found");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: JWS Protected Header alg=%s",
+		   token->string);
+	if (os_strcmp(token->string, curve->jws_alg) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected JWS Protected Header alg=%s (expected %s based on C-sign-key)",
+			   token->string, curve->jws_alg);
+		goto fail;
+	}
+	if (os_strcmp(token->string, "ES256") == 0 ||
+	    os_strcmp(token->string, "BS256") == 0)
+		*ret_md = EVP_sha256();
+	else if (os_strcmp(token->string, "ES384") == 0 ||
+		 os_strcmp(token->string, "BS384") == 0)
+		*ret_md = EVP_sha384();
+	else if (os_strcmp(token->string, "ES512") == 0 ||
+		 os_strcmp(token->string, "BS512") == 0)
+		*ret_md = EVP_sha512();
+	else
+		*ret_md = NULL;
+	if (!*ret_md) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unsupported JWS Protected Header alg=%s",
+			   token->string);
+		goto fail;
+	}
+
+	kid = json_get_member_base64url(root, "kid");
+	if (!kid) {
+		wpa_printf(MSG_DEBUG, "DPP: No kid string value found");
+		goto fail;
+	}
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: JWS Protected Header kid (decoded)",
+			kid);
+
+fail:
+	json_free(root);
+	return kid;
+}
+
+
+static int dpp_parse_cred_legacy(struct dpp_authentication *auth,
+				 struct json_token *cred)
+{
+	struct json_token *pass, *psk_hex;
+
+	wpa_printf(MSG_DEBUG, "DPP: Legacy akm=psk credential");
+
+	pass = json_get_member(cred, "pass");
+	psk_hex = json_get_member(cred, "psk_hex");
+
+	if (pass && pass->type == JSON_STRING) {
+		size_t len = os_strlen(pass->string);
+
+		wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Legacy passphrase",
+				      pass->string, len);
+		if (len < 8 || len > 63)
+			return -1;
+		os_strlcpy(auth->passphrase, pass->string,
+			   sizeof(auth->passphrase));
+	} else if (psk_hex && psk_hex->type == JSON_STRING) {
+		if (os_strlen(psk_hex->string) != PMK_LEN * 2 ||
+		    hexstr2bin(psk_hex->string, auth->psk, PMK_LEN) < 0) {
+			wpa_printf(MSG_DEBUG, "DPP: Invalid psk_hex encoding");
+			return -1;
+		}
+		wpa_hexdump_key(MSG_DEBUG, "DPP: Legacy PSK",
+				auth->psk, PMK_LEN);
+		auth->psk_set = 1;
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: No pass or psk_hex strings found");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static EVP_PKEY * dpp_parse_jwk(struct json_token *jwk,
+				const struct dpp_curve_params **key_curve)
+{
+	struct json_token *token;
+	const struct dpp_curve_params *curve;
+	struct wpabuf *x = NULL, *y = NULL;
+	EC_GROUP *group;
+	EVP_PKEY *pkey = NULL;
+
+	token = json_get_member(jwk, "kty");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No kty in JWK");
+		goto fail;
+	}
+	if (os_strcmp(token->string, "EC") != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Unexpected JWK kty '%s",
+			   token->string);
+		goto fail;
+	}
+
+	token = json_get_member(jwk, "crv");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No crv in JWK");
+		goto fail;
+	}
+	curve = dpp_get_curve_jwk_crv(token->string);
+	if (!curve) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported JWK crv '%s'",
+			   token->string);
+		goto fail;
+	}
+
+	x = json_get_member_base64url(jwk, "x");
+	if (!x) {
+		wpa_printf(MSG_DEBUG, "DPP: No x in JWK");
+		goto fail;
+	}
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: JWK x", x);
+	if (wpabuf_len(x) != curve->prime_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected JWK x length %u (expected %u for curve %s)",
+			   (unsigned int) wpabuf_len(x),
+			   (unsigned int) curve->prime_len, curve->name);
+		goto fail;
+	}
+
+	y = json_get_member_base64url(jwk, "y");
+	if (!y) {
+		wpa_printf(MSG_DEBUG, "DPP: No y in JWK");
+		goto fail;
+	}
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: JWK y", y);
+	if (wpabuf_len(y) != curve->prime_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected JWK y length %u (expected %u for curve %s)",
+			   (unsigned int) wpabuf_len(y),
+			   (unsigned int) curve->prime_len, curve->name);
+		goto fail;
+	}
+
+	group = EC_GROUP_new_by_curve_name(OBJ_txt2nid(curve->name));
+	if (!group) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not prepare group for JWK");
+		goto fail;
+	}
+
+	pkey = dpp_set_pubkey_point_group(group, wpabuf_head(x), wpabuf_head(y),
+					  wpabuf_len(x));
+	*key_curve = curve;
+
+fail:
+	wpabuf_free(x);
+	wpabuf_free(y);
+
+	return pkey;
+}
+
+
+int dpp_key_expired(const char *timestamp, os_time_t *expiry)
+{
+	struct os_time now;
+	unsigned int year, month, day, hour, min, sec;
+	os_time_t utime;
+	const char *pos;
+
+	/* ISO 8601 date and time:
+	 * <date>T<time>
+	 * YYYY-MM-DDTHH:MM:SSZ
+	 * YYYY-MM-DDTHH:MM:SS+03:00
+	 */
+	if (os_strlen(timestamp) < 19) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Too short timestamp - assume expired key");
+		return 1;
+	}
+	if (sscanf(timestamp, "%04u-%02u-%02uT%02u:%02u:%02u",
+		   &year, &month, &day, &hour, &min, &sec) != 6) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to parse expiration day - assume expired key");
+		return 1;
+	}
+
+	if (os_mktime(year, month, day, hour, min, sec, &utime) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid date/time information - assume expired key");
+		return 1;
+	}
+
+	pos = timestamp + 19;
+	if (*pos == 'Z' || *pos == '\0') {
+		/* In UTC - no need to adjust */
+	} else if (*pos == '-' || *pos == '+') {
+		int items;
+
+		/* Adjust local time to UTC */
+		items = sscanf(pos + 1, "%02u:%02u", &hour, &min);
+		if (items < 1) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Invalid time zone designator (%s) - assume expired key",
+				   pos);
+			return 1;
+		}
+		if (*pos == '-')
+			utime += 3600 * hour;
+		if (*pos == '+')
+			utime -= 3600 * hour;
+		if (items > 1) {
+			if (*pos == '-')
+				utime += 60 * min;
+			if (*pos == '+')
+				utime -= 60 * min;
+		}
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid time zone designator (%s) - assume expired key",
+			   pos);
+		return 1;
+	}
+	if (expiry)
+		*expiry = utime;
+
+	if (os_get_time(&now) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Cannot get current time - assume expired key");
+		return 1;
+	}
+
+	if (now.sec > utime) {
+		wpa_printf(MSG_DEBUG, "DPP: Key has expired (%lu < %lu)",
+			   utime, now.sec);
+		return 1;
+	}
+
+	return 0;
+}
+
+
+static int dpp_parse_connector(struct dpp_authentication *auth,
+			       const unsigned char *payload,
+			       u16 payload_len)
+{
+	struct json_token *root, *groups, *netkey, *token;
+	int ret = -1;
+	EVP_PKEY *key = NULL;
+	const struct dpp_curve_params *curve;
+	unsigned int rules = 0;
+
+	root = json_parse((const char *) payload, payload_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG, "DPP: JSON parsing of connector failed");
+		goto fail;
+	}
+
+	groups = json_get_member(root, "groups");
+	if (!groups || groups->type != JSON_ARRAY) {
+		wpa_printf(MSG_DEBUG, "DPP: No groups array found");
+		goto skip_groups;
+	}
+	for (token = groups->child; token; token = token->sibling) {
+		struct json_token *id, *role;
+
+		id = json_get_member(token, "groupId");
+		if (!id || id->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG, "DPP: Missing groupId string");
+			goto fail;
+		}
+
+		role = json_get_member(token, "netRole");
+		if (!role || role->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG, "DPP: Missing netRole string");
+			goto fail;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "DPP: connector group: groupId='%s' netRole='%s'",
+			   id->string, role->string);
+		rules++;
+	}
+skip_groups:
+
+	if (!rules) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Connector includes no groups");
+		goto fail;
+	}
+
+	token = json_get_member(root, "expiry");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No expiry string found - connector does not expire");
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: expiry = %s", token->string);
+		if (dpp_key_expired(token->string,
+				    &auth->net_access_key_expiry)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Connector (netAccessKey) has expired");
+			goto fail;
+		}
+	}
+
+	netkey = json_get_member(root, "netAccessKey");
+	if (!netkey || netkey->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No netAccessKey object found");
+		goto fail;
+	}
+
+	key = dpp_parse_jwk(netkey, &curve);
+	if (!key)
+		goto fail;
+	dpp_debug_print_key("DPP: Received netAccessKey", key);
+
+	if (EVP_PKEY_cmp(key, auth->own_protocol_key) != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: netAccessKey in connector does not match own protocol key");
+#ifdef CONFIG_TESTING_OPTIONS
+		if (auth->ignore_netaccesskey_mismatch) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: TESTING - skip netAccessKey mismatch");
+		} else {
+			goto fail;
+		}
+#else /* CONFIG_TESTING_OPTIONS */
+		goto fail;
+#endif /* CONFIG_TESTING_OPTIONS */
+	}
+
+	ret = 0;
+fail:
+	EVP_PKEY_free(key);
+	json_free(root);
+	return ret;
+}
+
+
+static int dpp_check_pubkey_match(EVP_PKEY *pub, struct wpabuf *r_hash)
+{
+	struct wpabuf *uncomp;
+	int res;
+	u8 hash[SHA256_MAC_LEN];
+	const u8 *addr[1];
+	size_t len[1];
+
+	if (wpabuf_len(r_hash) != SHA256_MAC_LEN)
+		return -1;
+	uncomp = dpp_get_pubkey_point(pub, 1);
+	if (!uncomp)
+		return -1;
+	addr[0] = wpabuf_head(uncomp);
+	len[0] = wpabuf_len(uncomp);
+	wpa_hexdump(MSG_DEBUG, "DPP: Uncompressed public key",
+		    addr[0], len[0]);
+	res = sha256_vector(1, addr, len, hash);
+	wpabuf_free(uncomp);
+	if (res < 0)
+		return -1;
+	if (os_memcmp(hash, wpabuf_head(r_hash), SHA256_MAC_LEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Received hash value does not match calculated public key hash value");
+		wpa_hexdump(MSG_DEBUG, "DPP: Calculated hash",
+			    hash, SHA256_MAC_LEN);
+		return -1;
+	}
+	return 0;
+}
+
+
+static void dpp_copy_csign(struct dpp_authentication *auth, EVP_PKEY *csign)
+{
+	unsigned char *der = NULL;
+	int der_len;
+
+	der_len = i2d_PUBKEY(csign, &der);
+	if (der_len <= 0)
+		return;
+	wpabuf_free(auth->c_sign_key);
+	auth->c_sign_key = wpabuf_alloc_copy(der, der_len);
+	OPENSSL_free(der);
+}
+
+
+static void dpp_copy_netaccesskey(struct dpp_authentication *auth)
+{
+	unsigned char *der = NULL;
+	int der_len;
+	EC_KEY *eckey;
+
+	eckey = EVP_PKEY_get1_EC_KEY(auth->own_protocol_key);
+	if (!eckey)
+		return;
+
+	der_len = i2d_ECPrivateKey(eckey, &der);
+	if (der_len <= 0) {
+		EC_KEY_free(eckey);
+		return;
+	}
+	wpabuf_free(auth->net_access_key);
+	auth->net_access_key = wpabuf_alloc_copy(der, der_len);
+	OPENSSL_free(der);
+	EC_KEY_free(eckey);
+}
+
+
+struct dpp_signed_connector_info {
+	unsigned char *payload;
+	size_t payload_len;
+};
+
+static int
+dpp_process_signed_connector(struct dpp_signed_connector_info *info,
+			     EVP_PKEY *csign_pub, const char *connector)
+{
+	int ret = -1;
+	const char *pos, *end, *signed_start, *signed_end;
+	struct wpabuf *kid = NULL;
+	unsigned char *prot_hdr = NULL, *signature = NULL;
+	size_t prot_hdr_len = 0, signature_len = 0;
+	const EVP_MD *sign_md = NULL;
+	unsigned char *der = NULL;
+	int der_len;
+	int res;
+	EVP_MD_CTX *md_ctx = NULL;
+	ECDSA_SIG *sig = NULL;
+	BIGNUM *r = NULL, *s = NULL;
+	const struct dpp_curve_params *curve;
+	EC_KEY *eckey;
+	const EC_GROUP *group;
+	int nid;
+
+	eckey = EVP_PKEY_get1_EC_KEY(csign_pub);
+	if (!eckey)
+		goto fail;
+	group = EC_KEY_get0_group(eckey);
+	if (!group)
+		goto fail;
+	nid = EC_GROUP_get_curve_name(group);
+	curve = dpp_get_curve_nid(nid);
+	if (!curve)
+		goto fail;
+	wpa_printf(MSG_DEBUG, "DPP: C-sign-key group: %s", curve->jwk_crv);
+	os_memset(info, 0, sizeof(*info));
+
+	signed_start = pos = connector;
+	end = os_strchr(pos, '.');
+	if (!end) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing dot(1) in signedConnector");
+		goto fail;
+	}
+	prot_hdr = base64_url_decode((const unsigned char *) pos,
+				     end - pos, &prot_hdr_len);
+	if (!prot_hdr) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode signedConnector JWS Protected Header");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG,
+			  "DPP: signedConnector - JWS Protected Header",
+			  prot_hdr, prot_hdr_len);
+	kid = dpp_parse_jws_prot_hdr(curve, prot_hdr, prot_hdr_len, &sign_md);
+	if (!kid)
+		goto fail;
+	if (wpabuf_len(kid) != SHA256_MAC_LEN) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected signedConnector JWS Protected Header kid length: %u (expected %u)",
+			   (unsigned int) wpabuf_len(kid), SHA256_MAC_LEN);
+		goto fail;
+	}
+
+	pos = end + 1;
+	end = os_strchr(pos, '.');
+	if (!end) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing dot(2) in signedConnector");
+		goto fail;
+	}
+	signed_end = end - 1;
+	info->payload = base64_url_decode((const unsigned char *) pos,
+					  end - pos, &info->payload_len);
+	if (!info->payload) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode signedConnector JWS Payload");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG,
+			  "DPP: signedConnector - JWS Payload",
+			  info->payload, info->payload_len);
+	pos = end + 1;
+	signature = base64_url_decode((const unsigned char *) pos,
+				      os_strlen(pos), &signature_len);
+	if (!signature) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode signedConnector signature");
+		goto fail;
+		}
+	wpa_hexdump(MSG_DEBUG, "DPP: signedConnector - signature",
+		    signature, signature_len);
+
+	if (dpp_check_pubkey_match(csign_pub, kid) < 0)
+		goto fail;
+
+	if (signature_len & 0x01) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected signedConnector signature length (%d)",
+			   (int) signature_len);
+		goto fail;
+	}
+
+	/* JWS Signature encodes the signature (r,s) as two octet strings. Need
+	 * to convert that to DER encoded ECDSA_SIG for OpenSSL EVP routines. */
+	r = BN_bin2bn(signature, signature_len / 2, NULL);
+	s = BN_bin2bn(signature + signature_len / 2, signature_len / 2, NULL);
+	sig = ECDSA_SIG_new();
+	if (!r || !s || !sig || ECDSA_SIG_set0(sig, r, s) != 1)
+		goto fail;
+	r = NULL;
+	s = NULL;
+
+	der_len = i2d_ECDSA_SIG(sig, &der);
+	if (der_len <= 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Could not DER encode signature");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: DER encoded signature", der, der_len);
+	md_ctx = EVP_MD_CTX_create();
+	if (!md_ctx)
+		goto fail;
+
+	ERR_clear_error();
+	if (EVP_DigestVerifyInit(md_ctx, NULL, sign_md, NULL, csign_pub) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestVerifyInit failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	if (EVP_DigestVerifyUpdate(md_ctx, signed_start,
+				   signed_end - signed_start + 1) != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: EVP_DigestVerifyUpdate failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+	res = EVP_DigestVerifyFinal(md_ctx, der, der_len);
+	if (res != 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: EVP_DigestVerifyFinal failed (res=%d): %s",
+			   res, ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	ret = 0;
+fail:
+	EC_KEY_free(eckey);
+	EVP_MD_CTX_destroy(md_ctx);
+	os_free(prot_hdr);
+	wpabuf_free(kid);
+	os_free(signature);
+	ECDSA_SIG_free(sig);
+	BN_free(r);
+	BN_free(s);
+	OPENSSL_free(der);
+	return ret;
+}
+
+
+static int dpp_parse_cred_dpp(struct dpp_authentication *auth,
+			      struct json_token *cred)
+{
+	struct dpp_signed_connector_info info;
+	struct json_token *token, *csign;
+	int ret = -1;
+	EVP_PKEY *csign_pub = NULL;
+	const struct dpp_curve_params *key_curve = NULL;
+	const char *signed_connector;
+
+	os_memset(&info, 0, sizeof(info));
+
+	wpa_printf(MSG_DEBUG, "DPP: Connector credential");
+
+	csign = json_get_member(cred, "csign");
+	if (!csign || csign->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No csign JWK in JSON");
+		goto fail;
+	}
+
+	csign_pub = dpp_parse_jwk(csign, &key_curve);
+	if (!csign_pub) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to parse csign JWK");
+		goto fail;
+	}
+	dpp_debug_print_key("DPP: Received C-sign-key", csign_pub);
+
+	token = json_get_member(cred, "signedConnector");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No signedConnector string found");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: signedConnector",
+			  token->string, os_strlen(token->string));
+	signed_connector = token->string;
+
+	if (os_strchr(signed_connector, '"') ||
+	    os_strchr(signed_connector, '\n')) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Unexpected character in signedConnector");
+		goto fail;
+	}
+
+	if (dpp_process_signed_connector(&info, csign_pub,
+					 signed_connector) < 0)
+		goto fail;
+
+	if (dpp_parse_connector(auth, info.payload, info.payload_len) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to parse connector");
+		goto fail;
+	}
+
+	os_free(auth->connector);
+	auth->connector = os_strdup(signed_connector);
+
+	dpp_copy_csign(auth, csign_pub);
+	dpp_copy_netaccesskey(auth);
+
+	ret = 0;
+fail:
+	EVP_PKEY_free(csign_pub);
+	os_free(info.payload);
+	return ret;
+}
+
+
+static int dpp_parse_conf_obj(struct dpp_authentication *auth,
+			      const u8 *conf_obj, u16 conf_obj_len)
+{
+	int ret = -1;
+	struct json_token *root, *token, *discovery, *cred;
+
+	root = json_parse((const char *) conf_obj, conf_obj_len);
+	if (!root)
+		return -1;
+	if (root->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: JSON root is not an object");
+		goto fail;
+	}
+
+	token = json_get_member(root, "wi-fi_tech");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG, "DPP: No wi-fi_tech string value found");
+		goto fail;
+	}
+	if (os_strcmp(token->string, "infra") != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported wi-fi_tech value: '%s'",
+			   token->string);
+		goto fail;
+	}
+
+	discovery = json_get_member(root, "discovery");
+	if (!discovery || discovery->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No discovery object in JSON");
+		goto fail;
+	}
+
+	token = json_get_member(discovery, "ssid");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No discovery::ssid string value found");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: discovery::ssid",
+			  token->string, os_strlen(token->string));
+	if (os_strlen(token->string) > SSID_MAX_LEN) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Too long discovery::ssid string value");
+		goto fail;
+	}
+	auth->ssid_len = os_strlen(token->string);
+	os_memcpy(auth->ssid, token->string, auth->ssid_len);
+
+	cred = json_get_member(root, "cred");
+	if (!cred || cred->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No cred object in JSON");
+		goto fail;
+	}
+
+	token = json_get_member(cred, "akm");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No cred::akm string value found");
+		goto fail;
+	}
+	if (os_strcmp(token->string, "psk") == 0) {
+		if (dpp_parse_cred_legacy(auth, cred) < 0)
+			goto fail;
+	} else if (os_strcmp(token->string, "dpp") == 0) {
+		if (dpp_parse_cred_dpp(auth, cred) < 0)
+			goto fail;
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: Unsupported akm: %s",
+			   token->string);
+		goto fail;
+	}
+
+	wpa_printf(MSG_DEBUG, "DPP: JSON parsing completed successfully");
+	ret = 0;
+fail:
+	json_free(root);
+	return ret;
+}
+
+
+int dpp_conf_resp_rx(struct dpp_authentication *auth,
+		     const struct wpabuf *resp)
+{
+	const u8 *wrapped_data, *e_nonce, *status, *conf_obj;
+	u16 wrapped_data_len, e_nonce_len, status_len, conf_obj_len;
+	const u8 *addr[1];
+	size_t len[1];
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	int ret = -1;
+
+	if (dpp_check_attrs(wpabuf_head(resp), wpabuf_len(resp)) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in config response");
+		return -1;
+	}
+
+	wrapped_data = dpp_get_attr(wpabuf_head(resp), wpabuf_len(resp),
+				    DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required Wrapped data attribute");
+		return -1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		return -1;
+
+	addr[0] = wpabuf_head(resp);
+	len[0] = wrapped_data - 4 - (const u8 *) wpabuf_head(resp);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD", addr[0], len[0]);
+
+	if (aes_siv_decrypt(auth->ke, auth->curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    1, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	e_nonce = dpp_get_attr(unwrapped, unwrapped_len,
+			       DPP_ATTR_ENROLLEE_NONCE,
+			       &e_nonce_len);
+	if (!e_nonce || e_nonce_len != auth->curve->nonce_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Enrollee Nonce attribute");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: Enrollee Nonce", e_nonce, e_nonce_len);
+	if (os_memcmp(e_nonce, auth->e_nonce, e_nonce_len) != 0) {
+		wpa_printf(MSG_DEBUG, "Enrollee Nonce mismatch");
+		goto fail;
+	}
+
+	status = dpp_get_attr(wpabuf_head(resp), wpabuf_len(resp),
+			      DPP_ATTR_STATUS, &status_len);
+	if (!status || status_len < 1) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required DPP Status attribute");
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", status[0]);
+	if (status[0] != DPP_STATUS_OK) {
+		wpa_printf(MSG_DEBUG, "DPP: Configuration failed");
+		goto fail;
+	}
+
+	conf_obj = dpp_get_attr(unwrapped, unwrapped_len,
+				DPP_ATTR_CONFIG_OBJ, &conf_obj_len);
+	if (!conf_obj) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing required Configuration Object attribute");
+		goto fail;
+	}
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: configurationObject JSON",
+			  conf_obj, conf_obj_len);
+	if (dpp_parse_conf_obj(auth, conf_obj, conf_obj_len) < 0)
+		goto fail;
+
+	ret = 0;
+
+fail:
+	os_free(unwrapped);
+	return ret;
+}
+
+
+void dpp_configurator_free(struct dpp_configurator *conf)
+{
+	if (!conf)
+		return;
+	EVP_PKEY_free(conf->csign);
+	os_free(conf->kid);
+	os_free(conf);
+}
+
+
+struct dpp_configurator *
+dpp_keygen_configurator(const char *curve, const u8 *privkey,
+			size_t privkey_len)
+{
+	struct dpp_configurator *conf;
+	struct wpabuf *csign_pub = NULL;
+	u8 kid_hash[SHA256_MAC_LEN];
+	const u8 *addr[1];
+	size_t len[1];
+
+	conf = os_zalloc(sizeof(*conf));
+	if (!conf)
+		return NULL;
+
+	if (!curve) {
+		conf->curve = &dpp_curves[0];
+	} else {
+		conf->curve = dpp_get_curve_name(curve);
+		if (!conf->curve) {
+			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
+				   curve);
+			return NULL;
+		}
+	}
+	if (privkey)
+		conf->csign = dpp_set_keypair(&conf->curve, privkey,
+					      privkey_len);
+	else
+		conf->csign = dpp_gen_keypair(conf->curve);
+	if (!conf->csign)
+		goto fail;
+	conf->own = 1;
+
+	csign_pub = dpp_get_pubkey_point(conf->csign, 1);
+	if (!csign_pub) {
+		wpa_printf(MSG_INFO, "DPP: Failed to extract C-sign-key");
+		goto fail;
+	}
+
+	/* kid = SHA256(ANSI X9.63 uncompressed C-sign-key) */
+	addr[0] = wpabuf_head(csign_pub);
+	len[0] = wpabuf_len(csign_pub);
+	if (sha256_vector(1, addr, len, kid_hash) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to derive kid for C-sign-key");
+		goto fail;
+	}
+
+	conf->kid = (char *) base64_url_encode(kid_hash, sizeof(kid_hash),
+					       NULL, 0);
+	if (!conf->kid)
+		goto fail;
+out:
+	wpabuf_free(csign_pub);
+	return conf;
+fail:
+	dpp_configurator_free(conf);
+	conf = NULL;
+	goto out;
+}
+
+
+int dpp_configurator_own_config(struct dpp_authentication *auth,
+				const char *curve)
+{
+	struct wpabuf *conf_obj;
+	int ret = -1;
+
+	if (!auth->conf) {
+		wpa_printf(MSG_DEBUG, "DPP: No configurator specified");
+		return -1;
+	}
+
+	if (!curve) {
+		auth->curve = &dpp_curves[0];
+	} else {
+		auth->curve = dpp_get_curve_name(curve);
+		if (!auth->curve) {
+			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
+				   curve);
+			return -1;
+		}
+	}
+	wpa_printf(MSG_DEBUG,
+		   "DPP: Building own configuration/connector with curve %s",
+		   auth->curve->name);
+
+	auth->own_protocol_key = dpp_gen_keypair(auth->curve);
+	if (!auth->own_protocol_key)
+		return -1;
+	dpp_copy_netaccesskey(auth);
+	auth->peer_protocol_key = auth->own_protocol_key;
+	dpp_copy_csign(auth, auth->conf->csign);
+
+	conf_obj = dpp_build_conf_obj(auth, 0);
+	if (!conf_obj)
+		goto fail;
+	ret = dpp_parse_conf_obj(auth, wpabuf_head(conf_obj),
+				 wpabuf_len(conf_obj));
+fail:
+	wpabuf_free(conf_obj);
+	auth->peer_protocol_key = NULL;
+	return ret;
+}
+
+
+static int dpp_compatible_netrole(const char *role1, const char *role2)
+{
+	return (os_strcmp(role1, "sta") == 0 && os_strcmp(role2, "ap") == 0) ||
+		(os_strcmp(role1, "ap") == 0 && os_strcmp(role2, "sta") == 0);
+}
+
+
+static int dpp_connector_compatible_group(struct json_token *root,
+					  const char *group_id,
+					  const char *net_role)
+{
+	struct json_token *groups, *token;
+
+	groups = json_get_member(root, "groups");
+	if (!groups || groups->type != JSON_ARRAY)
+		return 0;
+
+	for (token = groups->child; token; token = token->sibling) {
+		struct json_token *id, *role;
+
+		id = json_get_member(token, "groupId");
+		if (!id || id->type != JSON_STRING)
+			continue;
+
+		role = json_get_member(token, "netRole");
+		if (!role || role->type != JSON_STRING)
+			continue;
+
+		if (os_strcmp(id->string, "*") != 0 &&
+		    os_strcmp(group_id, "*") != 0 &&
+		    os_strcmp(id->string, group_id) != 0)
+			continue;
+
+		if (dpp_compatible_netrole(role->string, net_role))
+			return 1;
+	}
+
+	return 0;
+}
+
+
+static int dpp_connector_match_groups(struct json_token *own_root,
+				      struct json_token *peer_root)
+{
+	struct json_token *groups, *token;
+
+	groups = json_get_member(peer_root, "groups");
+	if (!groups || groups->type != JSON_ARRAY) {
+		wpa_printf(MSG_DEBUG, "DPP: No peer groups array found");
+		return 0;
+	}
+
+	for (token = groups->child; token; token = token->sibling) {
+		struct json_token *id, *role;
+
+		id = json_get_member(token, "groupId");
+		if (!id || id->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Missing peer groupId string");
+			continue;
+		}
+
+		role = json_get_member(token, "netRole");
+		if (!role || role->type != JSON_STRING) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Missing peer groups::netRole string");
+			continue;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "DPP: peer connector group: groupId='%s' netRole='%s'",
+			   id->string, role->string);
+		if (dpp_connector_compatible_group(own_root, id->string,
+						   role->string)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Compatible group/netRole in own connector");
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+
+static int dpp_derive_pmk(const u8 *Nx, size_t Nx_len, u8 *pmk,
+			  unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	const char *info = "DPP PMK";
+	int res;
+
+	/* PMK = HKDF(<>, "DPP PMK", N.x) */
+
+	/* HKDF-Extract(<>, N.x) */
+	os_memset(salt, 0, hash_len);
+	if (dpp_hmac(hash_len, salt, hash_len, Nx, Nx_len, prk) < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=N.x)",
+			prk, hash_len);
+
+	/* HKDF-Expand(PRK, info, L) */
+	res = dpp_hkdf_expand(hash_len, prk, hash_len, info, pmk, hash_len);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PMK = HKDF-Expand(PRK, info, L)",
+			pmk, hash_len);
+	return 0;
+}
+
+
+static int dpp_derive_pmkid(const struct dpp_curve_params *curve,
+			    EVP_PKEY *own_key, EVP_PKEY *peer_key, u8 *pmkid)
+{
+	struct wpabuf *nkx, *pkx;
+	int ret = -1, res;
+	const u8 *addr[2];
+	size_t len[2];
+	u8 hash[SHA256_MAC_LEN];
+
+	/* PMKID = Truncate-128(H(min(NK.x, PK.x) | max(NK.x, PK.x))) */
+	nkx = dpp_get_pubkey_point(own_key, 0);
+	pkx = dpp_get_pubkey_point(peer_key, 0);
+	if (!nkx || !pkx)
+		goto fail;
+	addr[0] = wpabuf_head(nkx);
+	len[0] = wpabuf_len(nkx) / 2;
+	addr[1] = wpabuf_head(pkx);
+	len[1] = wpabuf_len(pkx) / 2;
+	if (len[0] != len[1])
+		goto fail;
+	if (os_memcmp(addr[0], addr[1], len[0]) > 0) {
+		addr[0] = wpabuf_head(pkx);
+		addr[1] = wpabuf_head(nkx);
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash payload 1", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash payload 2", addr[1], len[1]);
+	res = sha256_vector(2, addr, len, hash);
+	if (res < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID hash output", hash, SHA256_MAC_LEN);
+	os_memcpy(pmkid, hash, PMKID_LEN);
+	wpa_hexdump(MSG_DEBUG, "DPP: PMKID", pmkid, PMKID_LEN);
+	ret = 0;
+fail:
+	wpabuf_free(nkx);
+	wpabuf_free(pkx);
+	return ret;
+}
+
+
+int dpp_peer_intro(struct dpp_introduction *intro, const char *own_connector,
+		   const u8 *net_access_key, size_t net_access_key_len,
+		   const u8 *csign_key, size_t csign_key_len,
+		   const u8 *peer_connector, size_t peer_connector_len,
+		   os_time_t *expiry)
+{
+	struct json_token *root = NULL, *netkey, *token;
+	struct json_token *own_root = NULL;
+	int ret = -1;
+	EVP_PKEY *own_key = NULL, *peer_key = NULL;
+	struct wpabuf *own_key_pub = NULL;
+	const struct dpp_curve_params *curve, *own_curve;
+	struct dpp_signed_connector_info info;
+	const unsigned char *p;
+	EVP_PKEY *csign = NULL;
+	char *signed_connector = NULL;
+	const char *pos, *end;
+	unsigned char *own_conn = NULL;
+	size_t own_conn_len;
+	EVP_PKEY_CTX *ctx = NULL;
+	size_t Nx_len;
+	u8 Nx[DPP_MAX_SHARED_SECRET_LEN];
+
+	os_memset(intro, 0, sizeof(*intro));
+	os_memset(&info, 0, sizeof(info));
+	if (expiry)
+		*expiry = 0;
+
+	p = csign_key;
+	csign = d2i_PUBKEY(NULL, &p, csign_key_len);
+	if (!csign) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to parse local C-sign-key information");
+		goto fail;
+	}
+
+	own_key = dpp_set_keypair(&own_curve, net_access_key,
+				  net_access_key_len);
+	if (!own_key) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to parse own netAccessKey");
+		goto fail;
+	}
+
+	pos = os_strchr(own_connector, '.');
+	if (!pos) {
+		wpa_printf(MSG_DEBUG, "DPP: Own connector is missing the first dot (.)");
+		goto fail;
+	}
+	pos++;
+	end = os_strchr(pos, '.');
+	if (!end) {
+		wpa_printf(MSG_DEBUG, "DPP: Own connector is missing the second dot (.)");
+		goto fail;
+	}
+	own_conn = base64_url_decode((const unsigned char *) pos,
+				     end - pos, &own_conn_len);
+	if (!own_conn) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Failed to base64url decode own signedConnector JWS Payload");
+		goto fail;
+	}
+
+	own_root = json_parse((const char *) own_conn, own_conn_len);
+	if (!own_root) {
+		wpa_printf(MSG_DEBUG, "DPP: Failed to parse local connector");
+		goto fail;
+	}
+
+	wpa_hexdump_ascii(MSG_DEBUG, "DPP: Peer signedConnector",
+			  peer_connector, peer_connector_len);
+	signed_connector = os_malloc(peer_connector_len + 1);
+	if (!signed_connector)
+		goto fail;
+	os_memcpy(signed_connector, peer_connector, peer_connector_len);
+	signed_connector[peer_connector_len] = '\0';
+
+	if (dpp_process_signed_connector(&info, csign, signed_connector) < 0)
+		goto fail;
+
+	root = json_parse((const char *) info.payload, info.payload_len);
+	if (!root) {
+		wpa_printf(MSG_DEBUG, "DPP: JSON parsing of connector failed");
+		goto fail;
+	}
+
+	if (!dpp_connector_match_groups(own_root, root)) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Peer connector does not include compatible group netrole with own connector");
+		goto fail;
+	}
+
+	token = json_get_member(root, "expiry");
+	if (!token || token->type != JSON_STRING) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No expiry string found - connector does not expire");
+	} else {
+		wpa_printf(MSG_DEBUG, "DPP: expiry = %s", token->string);
+		if (dpp_key_expired(token->string, expiry)) {
+			wpa_printf(MSG_DEBUG,
+				   "DPP: Connector (netAccessKey) has expired");
+			goto fail;
+		}
+	}
+
+	netkey = json_get_member(root, "netAccessKey");
+	if (!netkey || netkey->type != JSON_OBJECT) {
+		wpa_printf(MSG_DEBUG, "DPP: No netAccessKey object found");
+		goto fail;
+	}
+
+	peer_key = dpp_parse_jwk(netkey, &curve);
+	if (!peer_key)
+		goto fail;
+	dpp_debug_print_key("DPP: Received netAccessKey", peer_key);
+
+	if (own_curve != curve) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mismatching netAccessKey curves (%s != %s)",
+			   own_curve->name, curve->name);
+		goto fail;
+	}
+
+	/* ECDH: N = nk * PK */
+	ctx = EVP_PKEY_CTX_new(own_key, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, peer_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Nx_len) != 1 ||
+	    Nx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Nx, &Nx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (N.x)",
+			Nx, Nx_len);
+
+	/* PMK = HKDF(<>, "DPP PMK", N.x) */
+	if (dpp_derive_pmk(Nx, Nx_len, intro->pmk, curve->hash_len) < 0) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to derive PMK");
+		goto fail;
+	}
+	intro->pmk_len = curve->hash_len;
+
+	/* PMKID = Truncate-128(H(min(NK.x, PK.x) | max(NK.x, PK.x))) */
+	if (dpp_derive_pmkid(curve, own_key, peer_key, intro->pmkid) < 0) {
+		wpa_printf(MSG_ERROR, "DPP: Failed to derive PMKID");
+		goto fail;
+	}
+
+	ret = 0;
+fail:
+	if (ret < 0)
+		os_memset(intro, 0, sizeof(*intro));
+	os_memset(Nx, 0, sizeof(Nx));
+	EVP_PKEY_CTX_free(ctx);
+	os_free(own_conn);
+	os_free(signed_connector);
+	os_free(info.payload);
+	EVP_PKEY_free(own_key);
+	wpabuf_free(own_key_pub);
+	EVP_PKEY_free(peer_key);
+	EVP_PKEY_free(csign);
+	json_free(root);
+	json_free(own_root);
+	return ret;
+}
+
+
+static EVP_PKEY * dpp_pkex_get_role_elem(const struct dpp_curve_params *curve,
+					 int init)
+{
+	EC_GROUP *group;
+	size_t len = curve->prime_len;
+	const u8 *x, *y;
+
+	switch (curve->ike_group) {
+	case 19:
+		x = init ? pkex_init_x_p256 : pkex_resp_x_p256;
+		y = init ? pkex_init_y_p256 : pkex_resp_y_p256;
+		break;
+	case 20:
+		x = init ? pkex_init_x_p384 : pkex_resp_x_p384;
+		y = init ? pkex_init_y_p384 : pkex_resp_y_p384;
+		break;
+	case 21:
+		x = init ? pkex_init_x_p521 : pkex_resp_x_p521;
+		y = init ? pkex_init_y_p521 : pkex_resp_y_p521;
+		break;
+	case 28:
+		x = init ? pkex_init_x_bp_p256r1 : pkex_resp_x_bp_p256r1;
+		y = init ? pkex_init_y_bp_p256r1 : pkex_resp_y_bp_p256r1;
+		break;
+	case 29:
+		x = init ? pkex_init_x_bp_p384r1 : pkex_resp_x_bp_p384r1;
+		y = init ? pkex_init_y_bp_p384r1 : pkex_resp_y_bp_p384r1;
+		break;
+	case 30:
+		x = init ? pkex_init_x_bp_p512r1 : pkex_resp_x_bp_p512r1;
+		y = init ? pkex_init_y_bp_p512r1 : pkex_resp_y_bp_p512r1;
+		break;
+	default:
+		return NULL;
+	}
+
+	group = EC_GROUP_new_by_curve_name(OBJ_txt2nid(curve->name));
+	if (!group)
+		return NULL;
+	return dpp_set_pubkey_point_group(group, x, y, len);
+}
+
+
+static EC_POINT * dpp_pkex_derive_Qi(const struct dpp_curve_params *curve,
+				     const u8 *mac_init, const char *code,
+				     const char *identifier, BN_CTX *bnctx,
+				     const EC_GROUP **ret_group)
+{
+	u8 hash[DPP_MAX_HASH_LEN];
+	const u8 *addr[3];
+	size_t len[3];
+	unsigned int num_elem = 0;
+	EC_POINT *Qi = NULL;
+	EVP_PKEY *Pi = NULL;
+	EC_KEY *Pi_ec = NULL;
+	const EC_POINT *Pi_point;
+	BIGNUM *hash_bn = NULL;
+	const EC_GROUP *group = NULL;
+	EC_GROUP *group2 = NULL;
+
+	/* Qi = H(MAC-Initiator | [identifier |] code) * Pi */
+
+	wpa_printf(MSG_DEBUG, "DPP: MAC-Initiator: " MACSTR, MAC2STR(mac_init));
+	addr[num_elem] = mac_init;
+	len[num_elem] = ETH_ALEN;
+	num_elem++;
+	if (identifier) {
+		wpa_printf(MSG_DEBUG, "DPP: code identifier: %s",
+			   identifier);
+		addr[num_elem] = (const u8 *) identifier;
+		len[num_elem] = os_strlen(identifier);
+		num_elem++;
+	}
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: code", code, os_strlen(code));
+	addr[num_elem] = (const u8 *) code;
+	len[num_elem] = os_strlen(code);
+	num_elem++;
+	if (dpp_hash_vector(curve, num_elem, addr, len, hash) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG,
+			"DPP: H(MAC-Initiator | [identifier |] code)",
+			hash, curve->hash_len);
+	Pi = dpp_pkex_get_role_elem(curve, 1);
+	if (!Pi)
+		goto fail;
+	dpp_debug_print_key("DPP: Pi", Pi);
+	Pi_ec = EVP_PKEY_get1_EC_KEY(Pi);
+	if (!Pi_ec)
+		goto fail;
+	Pi_point = EC_KEY_get0_public_key(Pi_ec);
+
+	group = EC_KEY_get0_group(Pi_ec);
+	if (!group)
+		goto fail;
+	group2 = EC_GROUP_dup(group);
+	if (!group2)
+		goto fail;
+	Qi = EC_POINT_new(group2);
+	if (!Qi) {
+		EC_GROUP_free(group2);
+		goto fail;
+	}
+	hash_bn = BN_bin2bn(hash, curve->hash_len, NULL);
+	if (!hash_bn ||
+	    EC_POINT_mul(group2, Qi, NULL, Pi_point, hash_bn, bnctx) != 1)
+		goto fail;
+	if (EC_POINT_is_at_infinity(group, Qi)) {
+		wpa_printf(MSG_INFO, "PDP: Qi is the point-at-infinity");
+		goto fail;
+	}
+out:
+	EC_KEY_free(Pi_ec);
+	EVP_PKEY_free(Pi);
+	BN_clear_free(hash_bn);
+	if (ret_group)
+		*ret_group = group2;
+	return Qi;
+fail:
+	EC_POINT_free(Qi);
+	Qi = NULL;
+	goto out;
+}
+
+
+static EC_POINT * dpp_pkex_derive_Qr(const struct dpp_curve_params *curve,
+				     const u8 *mac_resp, const char *code,
+				     const char *identifier, BN_CTX *bnctx,
+				     const EC_GROUP **ret_group)
+{
+	u8 hash[DPP_MAX_HASH_LEN];
+	const u8 *addr[3];
+	size_t len[3];
+	unsigned int num_elem = 0;
+	EC_POINT *Qr = NULL;
+	EVP_PKEY *Pr = NULL;
+	EC_KEY *Pr_ec = NULL;
+	const EC_POINT *Pr_point;
+	BIGNUM *hash_bn = NULL;
+	const EC_GROUP *group = NULL;
+	EC_GROUP *group2 = NULL;
+
+	/* Qr = H(MAC-Responder | | [identifier | ] code) * Pr */
+
+	wpa_printf(MSG_DEBUG, "DPP: MAC-Responder: " MACSTR, MAC2STR(mac_resp));
+	addr[num_elem] = mac_resp;
+	len[num_elem] = ETH_ALEN;
+	num_elem++;
+	if (identifier) {
+		wpa_printf(MSG_DEBUG, "DPP: code identifier: %s",
+			   identifier);
+		addr[num_elem] = (const u8 *) identifier;
+		len[num_elem] = os_strlen(identifier);
+		num_elem++;
+	}
+	wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: code", code, os_strlen(code));
+	addr[num_elem] = (const u8 *) code;
+	len[num_elem] = os_strlen(code);
+	num_elem++;
+	if (dpp_hash_vector(curve, num_elem, addr, len, hash) < 0)
+		goto fail;
+	wpa_hexdump_key(MSG_DEBUG,
+			"DPP: H(MAC-Responder | [identifier |] code)",
+			hash, curve->hash_len);
+	Pr = dpp_pkex_get_role_elem(curve, 0);
+	if (!Pr)
+		goto fail;
+	dpp_debug_print_key("DPP: Pr", Pr);
+	Pr_ec = EVP_PKEY_get1_EC_KEY(Pr);
+	if (!Pr_ec)
+		goto fail;
+	Pr_point = EC_KEY_get0_public_key(Pr_ec);
+
+	group = EC_KEY_get0_group(Pr_ec);
+	if (!group)
+		goto fail;
+	group2 = EC_GROUP_dup(group);
+	if (!group2)
+		goto fail;
+	Qr = EC_POINT_new(group2);
+	if (!Qr) {
+		EC_GROUP_free(group2);
+		goto fail;
+	}
+	hash_bn = BN_bin2bn(hash, curve->hash_len, NULL);
+	if (!hash_bn ||
+	    EC_POINT_mul(group2, Qr, NULL, Pr_point, hash_bn, bnctx) != 1)
+		goto fail;
+out:
+	EC_KEY_free(Pr_ec);
+	EVP_PKEY_free(Pr);
+	BN_clear_free(hash_bn);
+	if (ret_group)
+		*ret_group = group2;
+	return Qr;
+fail:
+	EC_POINT_free(Qr);
+	Qr = NULL;
+	goto out;
+}
+
+
+static struct wpabuf * dpp_pkex_build_exchange_req(struct dpp_pkex *pkex)
+{
+	EC_KEY *X_ec = NULL;
+	const EC_POINT *X_point;
+	BN_CTX *bnctx = NULL;
+	const EC_GROUP *group;
+	EC_POINT *Qi = NULL, *M = NULL;
+	struct wpabuf *M_buf = NULL;
+	BIGNUM *Mx = NULL, *My = NULL;
+	struct wpabuf *msg = NULL;
+	size_t attr_len;
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	int num_bytes, offset;
+
+	wpa_printf(MSG_DEBUG, "DPP: Build PKEX Exchange Request");
+
+	/* Qi = H(MAC-Initiator | [identifier |] code) * Pi */
+	bnctx = BN_CTX_new();
+	if (!bnctx)
+		goto fail;
+	Qi = dpp_pkex_derive_Qi(curve, pkex->own_mac, pkex->code,
+				pkex->identifier, bnctx, &group);
+	if (!Qi)
+		goto fail;
+
+	/* Generate a random ephemeral keypair x/X */
+	pkex->x = dpp_gen_keypair(curve);
+	if (!pkex->x)
+		goto fail;
+
+	/* M = X + Qi */
+	X_ec = EVP_PKEY_get1_EC_KEY(pkex->x);
+	if (!X_ec)
+		goto fail;
+	X_point = EC_KEY_get0_public_key(X_ec);
+	if (!X_point)
+		goto fail;
+	M = EC_POINT_new(group);
+	Mx = BN_new();
+	My = BN_new();
+	if (!M || !Mx || !My ||
+	    EC_POINT_add(group, M, X_point, Qi, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, M, Mx, My, bnctx) != 1)
+		goto fail;
+
+	/* Initiator -> Responder: group, [identifier,] M */
+	attr_len = 4 + 2;
+	if (pkex->identifier)
+		attr_len += 4 + os_strlen(pkex->identifier);
+	attr_len += 4 + 2 * curve->prime_len;
+	msg = dpp_alloc_msg(DPP_PA_PKEX_EXCHANGE_REQ, attr_len);
+	if (!msg)
+		goto fail;
+
+	/* Finite Cyclic Group attribute */
+	wpabuf_put_le16(msg, DPP_ATTR_FINITE_CYCLIC_GROUP);
+	wpabuf_put_le16(msg, 2);
+	wpabuf_put_le16(msg, curve->ike_group);
+
+	/* Code Identifier attribute */
+	if (pkex->identifier) {
+		wpabuf_put_le16(msg, DPP_ATTR_CODE_IDENTIFIER);
+		wpabuf_put_le16(msg, os_strlen(pkex->identifier));
+		wpabuf_put_str(msg, pkex->identifier);
+	}
+
+	/* M in Encrypted Key attribute */
+	wpabuf_put_le16(msg, DPP_ATTR_ENCRYPTED_KEY);
+	wpabuf_put_le16(msg, 2 * curve->prime_len);
+
+	num_bytes = BN_num_bytes(Mx);
+	if ((size_t) num_bytes > curve->prime_len)
+		goto fail;
+	if (curve->prime_len > (size_t) num_bytes)
+		offset = curve->prime_len - num_bytes;
+	else
+		offset = 0;
+	os_memset(wpabuf_put(msg, offset), 0, offset);
+	BN_bn2bin(Mx, wpabuf_put(msg, num_bytes));
+	os_memset(pkex->Mx, 0, offset);
+	BN_bn2bin(Mx, pkex->Mx + offset);
+
+	num_bytes = BN_num_bytes(My);
+	if ((size_t) num_bytes > curve->prime_len)
+		goto fail;
+	if (curve->prime_len > (size_t) num_bytes)
+		offset = curve->prime_len - num_bytes;
+	else
+		offset = 0;
+	os_memset(wpabuf_put(msg, offset), 0, offset);
+	BN_bn2bin(My, wpabuf_put(msg, num_bytes));
+
+out:
+	wpabuf_free(M_buf);
+	EC_KEY_free(X_ec);
+	EC_POINT_free(M);
+	EC_POINT_free(Qi);
+	BN_clear_free(Mx);
+	BN_clear_free(My);
+	BN_CTX_free(bnctx);
+	return msg;
+fail:
+	wpa_printf(MSG_INFO, "DPP: Failed to build PKEX Exchange Request");
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+struct dpp_pkex * dpp_pkex_init(struct dpp_bootstrap_info *bi,
+				const u8 *own_mac,
+				const char *identifier,
+				const char *code)
+{
+	struct dpp_pkex *pkex;
+
+	pkex = os_zalloc(sizeof(*pkex));
+	if (!pkex)
+		return NULL;
+	pkex->initiator = 1;
+	pkex->own_bi = bi;
+	os_memcpy(pkex->own_mac, own_mac, ETH_ALEN);
+	if (identifier) {
+		pkex->identifier = os_strdup(identifier);
+		if (!pkex->identifier)
+			goto fail;
+	}
+	pkex->code = os_strdup(code);
+	if (!pkex->code)
+		goto fail;
+	pkex->exchange_req = dpp_pkex_build_exchange_req(pkex);
+	if (!pkex->exchange_req)
+		goto fail;
+	return pkex;
+fail:
+	dpp_pkex_free(pkex);
+	return NULL;
+}
+
+
+struct dpp_pkex * dpp_pkex_rx_exchange_req(struct dpp_bootstrap_info *bi,
+					   const u8 *own_mac,
+					   const u8 *peer_mac,
+					   const char *identifier,
+					   const char *code,
+					   const u8 *buf, size_t len)
+{
+	const u8 *attr_group, *attr_id, *attr_key;
+	u16 attr_group_len, attr_id_len, attr_key_len;
+	const struct dpp_curve_params *curve = bi->curve;
+	u16 ike_group;
+	struct dpp_pkex *pkex = NULL;
+	EC_POINT *Qi = NULL, *Qr = NULL, *M = NULL, *X = NULL, *N = NULL;
+	BN_CTX *bnctx = NULL;
+	const EC_GROUP *group;
+	BIGNUM *Mx = NULL, *My = NULL;
+	EC_KEY *Y_ec = NULL, *X_ec = NULL;;
+	const EC_POINT *Y_point;
+	BIGNUM *Nx = NULL, *Ny = NULL;
+	struct wpabuf *msg = NULL;
+	size_t attr_len;
+	int num_bytes, offset;
+
+	attr_id = dpp_get_attr(buf, len, DPP_ATTR_CODE_IDENTIFIER,
+			       &attr_id_len);
+	if (!attr_id && identifier) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No PKEX code identifier received, but expected one");
+		return NULL;
+	}
+	if (attr_id && identifier &&
+	    (os_strlen(identifier) != attr_id_len ||
+	     os_memcmp(identifier, attr_id, attr_id_len) != 0)) {
+		wpa_printf(MSG_DEBUG, "DPP: PKEX code identifier mismatch");
+		return NULL;
+	}
+
+	attr_group = dpp_get_attr(buf, len, DPP_ATTR_FINITE_CYCLIC_GROUP,
+				  &attr_group_len);
+	if (!attr_group || attr_group_len != 2) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid Finite Cyclic Group attribute");
+		return NULL;
+	}
+	ike_group = WPA_GET_LE16(attr_group);
+	if (ike_group != curve->ike_group) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Mismatching PKEX curve: peer=%u own=%u",
+			   ike_group, curve->ike_group);
+		/* TODO: error response with suggested curve:
+		 * DPP Status, group */
+		return NULL;
+	}
+
+	/* M in Encrypted Key attribute */
+	attr_key = dpp_get_attr(buf, len, DPP_ATTR_ENCRYPTED_KEY,
+				&attr_key_len);
+	if (!attr_key || attr_key_len & 0x01 || attr_key_len < 2 ||
+	    attr_key_len / 2 > DPP_MAX_SHARED_SECRET_LEN) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing Encrypted Key attribute");
+		return NULL;
+	}
+
+	/* Qi = H(MAC-Initiator | [identifier |] code) * Pi */
+	bnctx = BN_CTX_new();
+	if (!bnctx)
+		goto fail;
+	Qi = dpp_pkex_derive_Qi(curve, peer_mac, code, identifier, bnctx,
+				&group);
+	if (!Qi)
+		goto fail;
+
+	/* X' = M - Qi */
+	X = EC_POINT_new(group);
+	M = EC_POINT_new(group);
+	Mx = BN_bin2bn(attr_key, attr_key_len / 2, NULL);
+	My = BN_bin2bn(attr_key + attr_key_len / 2, attr_key_len / 2, NULL);
+	if (!X || !M || !Mx || !My ||
+	    EC_POINT_set_affine_coordinates_GFp(group, M, Mx, My, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, M) ||
+	    !EC_POINT_is_on_curve(group, M, bnctx) ||
+	    EC_POINT_invert(group, Qi, bnctx) != 1 ||
+	    EC_POINT_add(group, X, M, Qi, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, X) ||
+	    !EC_POINT_is_on_curve(group, X, bnctx))
+		goto fail;
+
+	pkex = os_zalloc(sizeof(*pkex));
+	if (!pkex)
+		goto fail;
+	pkex->own_bi = bi;
+	os_memcpy(pkex->own_mac, own_mac, ETH_ALEN);
+	os_memcpy(pkex->peer_mac, peer_mac, ETH_ALEN);
+	if (identifier) {
+		pkex->identifier = os_strdup(identifier);
+		if (!pkex->identifier)
+			goto fail;
+	}
+	pkex->code = os_strdup(code);
+	if (!pkex->code)
+		goto fail;
+
+	os_memcpy(pkex->Mx, attr_key, attr_key_len / 2);
+
+	X_ec = EC_KEY_new();
+	if (!X_ec ||
+	    EC_KEY_set_group(X_ec, group) != 1 ||
+	    EC_KEY_set_public_key(X_ec, X) != 1)
+		goto fail;
+	pkex->x = EVP_PKEY_new();
+	if (!pkex->x ||
+	    EVP_PKEY_set1_EC_KEY(pkex->x, X_ec) != 1)
+		goto fail;
+
+	/* Qr = H(MAC-Responder | | [identifier | ] code) * Pr */
+	Qr = dpp_pkex_derive_Qr(curve, own_mac, code, identifier, bnctx, NULL);
+	if (!Qr)
+		goto fail;
+
+	/* Generate a random ephemeral keypair y/Y */
+	pkex->y = dpp_gen_keypair(curve);
+	if (!pkex->y)
+		goto fail;
+
+	/* N = Y + Qr */
+	Y_ec = EVP_PKEY_get1_EC_KEY(pkex->y);
+	if (!Y_ec)
+		goto fail;
+	Y_point = EC_KEY_get0_public_key(Y_ec);
+	if (!Y_point)
+		goto fail;
+	N = EC_POINT_new(group);
+	Nx = BN_new();
+	Ny = BN_new();
+	if (!N || !Nx || !Ny ||
+	    EC_POINT_add(group, N, Y_point, Qr, bnctx) != 1 ||
+	    EC_POINT_get_affine_coordinates_GFp(group, N, Nx, Ny, bnctx) != 1)
+		goto fail;
+
+	/* Initiator -> Responder: DPP Status, [identifier,] N */
+	attr_len = 4 + 1;
+	if (identifier)
+		attr_len += 4 + os_strlen(identifier);
+	attr_len += 4 + 2 * curve->prime_len;
+	msg = dpp_alloc_msg(DPP_PA_PKEX_EXCHANGE_RESP, attr_len);
+	if (!msg)
+		goto fail;
+
+	/* DPP Status */
+	wpabuf_put_le16(msg, DPP_ATTR_STATUS);
+	wpabuf_put_le16(msg, 1);
+	wpabuf_put_u8(msg, DPP_STATUS_OK);
+
+	/* Code Identifier attribute */
+	if (pkex->identifier) {
+		wpabuf_put_le16(msg, DPP_ATTR_CODE_IDENTIFIER);
+		wpabuf_put_le16(msg, os_strlen(pkex->identifier));
+		wpabuf_put_str(msg, pkex->identifier);
+	}
+
+	/* N in Encrypted Key attribute */
+	wpabuf_put_le16(msg, DPP_ATTR_ENCRYPTED_KEY);
+	wpabuf_put_le16(msg, 2 * curve->prime_len);
+
+	num_bytes = BN_num_bytes(Nx);
+	if ((size_t) num_bytes > curve->prime_len)
+		goto fail;
+	if (curve->prime_len > (size_t) num_bytes)
+		offset = curve->prime_len - num_bytes;
+	else
+		offset = 0;
+	os_memset(wpabuf_put(msg, offset), 0, offset);
+	BN_bn2bin(Nx, wpabuf_put(msg, num_bytes));
+	os_memset(pkex->Nx, 0, offset);
+	BN_bn2bin(Nx, pkex->Nx + offset);
+
+	num_bytes = BN_num_bytes(Ny);
+	if ((size_t) num_bytes > curve->prime_len)
+		goto fail;
+	if (curve->prime_len > (size_t) num_bytes)
+		offset = curve->prime_len - num_bytes;
+	else
+		offset = 0;
+	os_memset(wpabuf_put(msg, offset), 0, offset);
+	BN_bn2bin(Ny, wpabuf_put(msg, num_bytes));
+
+	pkex->exchange_resp = msg;
+	msg = NULL;
+	pkex->exchange_done = 1;
+
+out:
+	wpabuf_free(msg);
+	BN_CTX_free(bnctx);
+	EC_POINT_free(Qi);
+	EC_POINT_free(Qr);
+	BN_free(Mx);
+	BN_free(My);
+	BN_free(Nx);
+	BN_free(Ny);
+	EC_POINT_free(M);
+	EC_POINT_free(N);
+	EC_POINT_free(X);
+	EC_KEY_free(X_ec);
+	EC_KEY_free(Y_ec);
+	return pkex;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Request processing faileed");
+	dpp_pkex_free(pkex);
+	pkex = NULL;
+	goto out;
+}
+
+
+static int dpp_pkex_derive_z(const u8 *mac_init, const u8 *mac_resp,
+			     const u8 *Mx, size_t Mx_len,
+			     const u8 *Nx, size_t Nx_len,
+			     const char *code,
+			     const u8 *Kx, size_t Kx_len,
+			     u8 *z, unsigned int hash_len)
+{
+	u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];
+	int res;
+	u8 *info, *pos;
+	size_t info_len;
+
+	/* z = HKDF(<>, MAC-Initiator | MAC-Responder | M.x | N.x | code, K.x)
+	 */
+
+	/* HKDF-Extract(<>, IKM=K.x) */
+	os_memset(salt, 0, hash_len);
+	if (dpp_hmac(hash_len, salt, hash_len, Kx, Kx_len, prk) < 0)
+		return -1;
+	wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM)",
+			prk, hash_len);
+	info_len = 2 * ETH_ALEN + Mx_len + Nx_len + os_strlen(code);
+	info = os_malloc(info_len);
+	if (!info)
+		return -1;
+	pos = info;
+	os_memcpy(pos, mac_init, ETH_ALEN);
+	pos += ETH_ALEN;
+	os_memcpy(pos, mac_resp, ETH_ALEN);
+	pos += ETH_ALEN;
+	os_memcpy(pos, Mx, Mx_len);
+	pos += Mx_len;
+	os_memcpy(pos, Nx, Nx_len);
+	pos += Nx_len;
+	os_memcpy(pos, code, os_strlen(code));
+
+	/* HKDF-Expand(PRK, info, L) */
+	if (hash_len == 32)
+		res = hmac_sha256_kdf(prk, hash_len, NULL, info, info_len,
+				      z, hash_len);
+	else if (hash_len == 48)
+		res = hmac_sha384_kdf(prk, hash_len, NULL, info, info_len,
+				      z, hash_len);
+	else if (hash_len == 64)
+		res = hmac_sha512_kdf(prk, hash_len, NULL, info, info_len,
+				      z, hash_len);
+	else
+		res = -1;
+	os_free(info);
+	os_memset(prk, 0, hash_len);
+	if (res < 0)
+		return -1;
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: z = HKDF-Expand(PRK, info, L)",
+			z, hash_len);
+	return 0;
+}
+
+
+struct wpabuf * dpp_pkex_rx_exchange_resp(struct dpp_pkex *pkex,
+					  const u8 *buf, size_t buflen)
+{
+	const u8 *attr_status, *attr_id, *attr_key;
+	u16 attr_status_len, attr_id_len, attr_key_len;
+	const EC_GROUP *group;
+	BN_CTX *bnctx = NULL;
+	size_t clear_len;
+	struct wpabuf *clear = NULL;
+	u8 *wrapped;
+	struct wpabuf *msg = NULL, *A_pub = NULL, *X_pub = NULL, *Y_pub = NULL;
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	EC_POINT *Qr = NULL, *Y = NULL, *N = NULL;
+	BIGNUM *Nx = NULL, *Ny = NULL;
+	EVP_PKEY_CTX *ctx = NULL;
+	EC_KEY *Y_ec = NULL;
+	size_t Jx_len, Kx_len;
+	u8 Jx[DPP_MAX_SHARED_SECRET_LEN], Kx[DPP_MAX_SHARED_SECRET_LEN];
+	const u8 *addr[4];
+	size_t len[4];
+	u8 u[DPP_MAX_HASH_LEN];
+	u8 octet;
+	int res;
+
+	attr_status = dpp_get_attr(buf, buflen, DPP_ATTR_STATUS,
+				   &attr_status_len);
+	if (!attr_status || attr_status_len != 1) {
+		wpa_printf(MSG_DEBUG, "DPP: No DPP Status attribute");
+		return NULL;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Status %u", attr_status[0]);
+	if (attr_status[0] != DPP_STATUS_OK) {
+		wpa_printf(MSG_DEBUG, "DPP: PKEX failed");
+		return NULL;
+	}
+
+	attr_id = dpp_get_attr(buf, buflen, DPP_ATTR_CODE_IDENTIFIER,
+			       &attr_id_len);
+	if (!attr_id && pkex->identifier) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No PKEX code identifier received, but expected one");
+		return NULL;
+	}
+	if (attr_id && pkex->identifier &&
+	    (os_strlen(pkex->identifier) != attr_id_len ||
+	     os_memcmp(pkex->identifier, attr_id, attr_id_len) != 0)) {
+		wpa_printf(MSG_DEBUG, "DPP: PKEX code identifier mismatch");
+		return NULL;
+	}
+
+	/* N in Encrypted Key attribute */
+	attr_key = dpp_get_attr(buf, buflen, DPP_ATTR_ENCRYPTED_KEY,
+				&attr_key_len);
+	if (!attr_key || attr_key_len & 0x01 || attr_key_len < 2) {
+		wpa_printf(MSG_DEBUG, "DPP: Missing Encrypted Key attribute");
+		return NULL;
+	}
+
+	/* Qr = H(MAC-Responder | [identifier |] code) * Pr */
+	bnctx = BN_CTX_new();
+	if (!bnctx)
+		goto fail;
+	Qr = dpp_pkex_derive_Qr(curve, pkex->peer_mac, pkex->code,
+				pkex->identifier, bnctx, &group);
+	if (!Qr)
+		goto fail;
+
+	/* Y' = N - Qr */
+	Y = EC_POINT_new(group);
+	N = EC_POINT_new(group);
+	Nx = BN_bin2bn(attr_key, attr_key_len / 2, NULL);
+	Ny = BN_bin2bn(attr_key + attr_key_len / 2, attr_key_len / 2, NULL);
+	if (!Y || !N || !Nx || !Ny ||
+	    EC_POINT_set_affine_coordinates_GFp(group, N, Nx, Ny, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, N) ||
+	    !EC_POINT_is_on_curve(group, N, bnctx) ||
+	    EC_POINT_invert(group, Qr, bnctx) != 1 ||
+	    EC_POINT_add(group, Y, N, Qr, bnctx) != 1 ||
+	    EC_POINT_is_at_infinity(group, Y) ||
+	    !EC_POINT_is_on_curve(group, Y, bnctx))
+		goto fail;
+
+	pkex->exchange_done = 1;
+
+	/* ECDH: J = a * Y’ */
+	Y_ec = EC_KEY_new();
+	if (!Y_ec ||
+	    EC_KEY_set_group(Y_ec, group) != 1 ||
+	    EC_KEY_set_public_key(Y_ec, Y) != 1)
+		goto fail;
+	pkex->y = EVP_PKEY_new();
+	if (!pkex->y ||
+	    EVP_PKEY_set1_EC_KEY(pkex->y, Y_ec) != 1)
+		goto fail;
+	ctx = EVP_PKEY_CTX_new(pkex->own_bi->pubkey, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->y) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Jx_len) != 1 ||
+	    Jx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Jx, &Jx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (J.x)",
+			Jx, Jx_len);
+
+	/* u = HMAC(J.x,  MAC-Initiator | A.x | Y’.x | X.x ) */
+	A_pub = dpp_get_pubkey_point(pkex->own_bi->pubkey, 0);
+	Y_pub = dpp_get_pubkey_point(pkex->y, 0);
+	X_pub = dpp_get_pubkey_point(pkex->x, 0);
+	if (!A_pub || !Y_pub || !X_pub)
+		goto fail;
+	addr[0] = pkex->own_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(A_pub);
+	len[1] = wpabuf_len(A_pub) / 2;
+	addr[2] = wpabuf_head(Y_pub);
+	len[2] = wpabuf_len(Y_pub) / 2;
+	addr[3] = wpabuf_head(X_pub);
+	len[3] = wpabuf_len(X_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Jx, Jx_len, 4, addr, len, u) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: u", u, curve->hash_len);
+
+	/* K = x * Y’ */
+	EVP_PKEY_CTX_free(ctx);
+	ctx = EVP_PKEY_CTX_new(pkex->x, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->y) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Kx_len) != 1 ||
+	    Kx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Kx, &Kx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (K.x)",
+			Kx, Kx_len);
+
+	/* z = HKDF(<>, MAC-Initiator | MAC-Responder | M.x | N.x | code, K.x)
+	 */
+	res = dpp_pkex_derive_z(pkex->own_mac, pkex->peer_mac,
+				pkex->Mx, curve->prime_len,
+				attr_key /* N.x */, attr_key_len / 2,
+				pkex->code, Kx, Kx_len,
+				pkex->z, curve->hash_len);
+	os_memset(Kx, 0, Kx_len);
+	if (res < 0)
+		goto fail;
+
+	/* {A, u, [bootstrapping info]}z */
+	clear_len = 4 + 2 * curve->prime_len + 4 + curve->hash_len;
+	clear = wpabuf_alloc(clear_len);
+	msg = dpp_alloc_msg(DPP_PA_PKEX_COMMIT_REVEAL_REQ,
+			    4 + clear_len + AES_BLOCK_SIZE);
+	if (!clear || !msg)
+		goto fail;
+
+	/* A in Bootstrap Key attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_BOOTSTRAP_KEY);
+	wpabuf_put_le16(clear, wpabuf_len(A_pub));
+	wpabuf_put_buf(clear, A_pub);
+
+	/* u in I-Auth tag attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_I_AUTH_TAG);
+	wpabuf_put_le16(clear, curve->hash_len);
+	wpabuf_put_data(clear, u, curve->hash_len);
+
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = DPP_HDR_LEN;
+	octet = 0;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(pkex->z, curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    2, addr, len, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+out:
+	wpabuf_free(clear);
+	wpabuf_free(A_pub);
+	wpabuf_free(X_pub);
+	wpabuf_free(Y_pub);
+	EC_POINT_free(Qr);
+	EC_POINT_free(Y);
+	EC_POINT_free(N);
+	BN_free(Nx);
+	BN_free(Ny);
+	EC_KEY_free(Y_ec);
+	EVP_PKEY_CTX_free(ctx);
+	BN_CTX_free(bnctx);
+	return msg;
+fail:
+	wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Response processing faileed");
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+struct wpabuf * dpp_pkex_rx_commit_reveal_req(struct dpp_pkex *pkex,
+					      const u8 *hdr,
+					      const u8 *buf, size_t buflen)
+{
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	EVP_PKEY_CTX *ctx;
+	size_t Jx_len, Kx_len, Lx_len;
+	u8 Jx[DPP_MAX_SHARED_SECRET_LEN], Kx[DPP_MAX_SHARED_SECRET_LEN];
+	u8 Lx[DPP_MAX_SHARED_SECRET_LEN];
+	const u8 *wrapped_data, *b_key, *peer_u;
+	u16 wrapped_data_len, b_key_len, peer_u_len = 0;
+	const u8 *addr[4];
+	size_t len[4];
+	u8 octet;
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	struct wpabuf *msg = NULL, *A_pub = NULL, *X_pub = NULL, *Y_pub = NULL;
+	struct wpabuf *B_pub = NULL;
+	u8 u[DPP_MAX_HASH_LEN], v[DPP_MAX_HASH_LEN];
+	size_t clear_len;
+	struct wpabuf *clear = NULL;
+	u8 *wrapped;
+	int res;
+
+	/* K = y * X' */
+	ctx = EVP_PKEY_CTX_new(pkex->y, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->x) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Kx_len) != 1 ||
+	    Kx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Kx, &Kx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (K.x)",
+			Kx, Kx_len);
+
+	/* z = HKDF(<>, MAC-Initiator | MAC-Responder | M.x | N.x | code, K.x)
+	 */
+	res = dpp_pkex_derive_z(pkex->peer_mac, pkex->own_mac,
+				pkex->Mx, curve->prime_len,
+				pkex->Nx, curve->prime_len, pkex->code,
+				Kx, Kx_len, pkex->z, curve->hash_len);
+	os_memset(Kx, 0, Kx_len);
+	if (res < 0)
+		goto fail;
+
+	wrapped_data = dpp_get_attr(buf, buflen, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required Wrapped data attribute");
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	octet = 0;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	if (aes_siv_decrypt(pkex->z, curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	b_key = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_BOOTSTRAP_KEY,
+			     &b_key_len);
+	if (!b_key || b_key_len != 2 * curve->prime_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No valid peer bootstrapping key found");
+		goto fail;
+	}
+	pkex->peer_bootstrap_key = dpp_set_pubkey_point(pkex->x, b_key,
+							b_key_len);
+	if (!pkex->peer_bootstrap_key)
+		goto fail;
+	dpp_debug_print_key("DPP: Peer bootstrap public key",
+			    pkex->peer_bootstrap_key);
+
+	/* ECDH: J' = y * A' */
+	EVP_PKEY_CTX_free(ctx);
+	ctx = EVP_PKEY_CTX_new(pkex->y, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->peer_bootstrap_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Jx_len) != 1 ||
+	    Jx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Jx, &Jx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (J.x)",
+			Jx, Jx_len);
+
+	/* u' = HMAC(J'.x, MAC-Initiator | A'.x | Y.x | X'.x) */
+	A_pub = dpp_get_pubkey_point(pkex->peer_bootstrap_key, 0);
+	Y_pub = dpp_get_pubkey_point(pkex->y, 0);
+	X_pub = dpp_get_pubkey_point(pkex->x, 0);
+	if (!A_pub || !Y_pub || !X_pub)
+		goto fail;
+	addr[0] = pkex->peer_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(A_pub);
+	len[1] = wpabuf_len(A_pub) / 2;
+	addr[2] = wpabuf_head(Y_pub);
+	len[2] = wpabuf_len(Y_pub) / 2;
+	addr[3] = wpabuf_head(X_pub);
+	len[3] = wpabuf_len(X_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Jx, Jx_len, 4, addr, len, u) < 0)
+		goto fail;
+
+	peer_u = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_I_AUTH_TAG,
+			      &peer_u_len);
+	if (!peer_u || peer_u_len != curve->hash_len ||
+	    os_memcmp(peer_u, u, curve->hash_len) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: No valid u (I-Auth tag) found");
+		wpa_hexdump(MSG_DEBUG, "DPP: Calculated u'",
+			    u, curve->hash_len);
+		wpa_hexdump(MSG_DEBUG, "DPP: Received u", peer_u, peer_u_len);
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Valid u (I-Auth tag) received");
+
+	/* ECDH: L = b * X' */
+	EVP_PKEY_CTX_free(ctx);
+	ctx = EVP_PKEY_CTX_new(pkex->own_bi->pubkey, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->x) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Lx_len) != 1 ||
+	    Lx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Lx, &Lx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (L.x)",
+			Lx, Lx_len);
+
+	/* v = HMAC(L.x, MAC-Responder | B.x | X'.x | Y.x) */
+	B_pub = dpp_get_pubkey_point(pkex->own_bi->pubkey, 0);
+	if (!B_pub)
+		goto fail;
+	addr[0] = pkex->own_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(B_pub);
+	len[1] = wpabuf_len(B_pub) / 2;
+	addr[2] = wpabuf_head(X_pub);
+	len[2] = wpabuf_len(X_pub) / 2;
+	addr[3] = wpabuf_head(Y_pub);
+	len[3] = wpabuf_len(Y_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Lx, Lx_len, 4, addr, len, v) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: v", v, curve->hash_len);
+
+	/* {B, v [bootstrapping info]}z */
+	clear_len = 4 + 2 * curve->prime_len + 4 + curve->hash_len;
+	clear = wpabuf_alloc(clear_len);
+	msg = dpp_alloc_msg(DPP_PA_PKEX_COMMIT_REVEAL_RESP,
+			    4 + clear_len + AES_BLOCK_SIZE);
+	if (!clear || !msg)
+		goto fail;
+
+	/* A in Bootstrap Key attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_BOOTSTRAP_KEY);
+	wpabuf_put_le16(clear, wpabuf_len(B_pub));
+	wpabuf_put_buf(clear, B_pub);
+
+	/* v in R-Auth tag attribute */
+	wpabuf_put_le16(clear, DPP_ATTR_R_AUTH_TAG);
+	wpabuf_put_le16(clear, curve->hash_len);
+	wpabuf_put_data(clear, v, curve->hash_len);
+
+	addr[0] = wpabuf_head_u8(msg) + 2;
+	len[0] = DPP_HDR_LEN;
+	octet = 1;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA);
+	wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+	wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE);
+
+	wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear);
+	if (aes_siv_encrypt(pkex->z, curve->hash_len,
+			    wpabuf_head(clear), wpabuf_len(clear),
+			    2, addr, len, wrapped) < 0)
+		goto fail;
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE);
+out:
+	EVP_PKEY_CTX_free(ctx);
+	os_free(unwrapped);
+	wpabuf_free(A_pub);
+	wpabuf_free(B_pub);
+	wpabuf_free(X_pub);
+	wpabuf_free(Y_pub);
+	wpabuf_free(clear);
+	return msg;
+fail:
+	wpabuf_free(msg);
+	msg = NULL;
+	goto out;
+}
+
+
+int dpp_pkex_rx_commit_reveal_resp(struct dpp_pkex *pkex, const u8 *hdr,
+				   const u8 *buf, size_t buflen)
+{
+	const struct dpp_curve_params *curve = pkex->own_bi->curve;
+	const u8 *wrapped_data, *b_key, *peer_v;
+	u16 wrapped_data_len, b_key_len, peer_v_len = 0;
+	const u8 *addr[4];
+	size_t len[4];
+	u8 octet;
+	u8 *unwrapped = NULL;
+	size_t unwrapped_len = 0;
+	int ret = -1;
+	u8 v[DPP_MAX_HASH_LEN];
+	size_t Lx_len;
+	u8 Lx[DPP_MAX_SHARED_SECRET_LEN];
+	EVP_PKEY_CTX *ctx = NULL;
+	struct wpabuf *B_pub = NULL, *X_pub = NULL, *Y_pub = NULL;
+
+	wrapped_data = dpp_get_attr(buf, buflen, DPP_ATTR_WRAPPED_DATA,
+				    &wrapped_data_len);
+	if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Missing or invalid required Wrapped data attribute");
+		goto fail;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext",
+		    wrapped_data, wrapped_data_len);
+	unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE;
+	unwrapped = os_malloc(unwrapped_len);
+	if (!unwrapped)
+		goto fail;
+
+	addr[0] = hdr;
+	len[0] = DPP_HDR_LEN;
+	octet = 1;
+	addr[1] = &octet;
+	len[1] = sizeof(octet);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[0]", addr[0], len[0]);
+	wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD[1]", addr[1], len[1]);
+
+	if (aes_siv_decrypt(pkex->z, curve->hash_len,
+			    wrapped_data, wrapped_data_len,
+			    2, addr, len, unwrapped) < 0) {
+		wpa_printf(MSG_DEBUG, "DPP: AES-SIV decryption failed");
+		goto fail;
+	}
+	wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext",
+		    unwrapped, unwrapped_len);
+
+	if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: Invalid attribute in unwrapped data");
+		goto fail;
+	}
+
+	b_key = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_BOOTSTRAP_KEY,
+			     &b_key_len);
+	if (!b_key || b_key_len != 2 * curve->prime_len) {
+		wpa_printf(MSG_DEBUG,
+			   "DPP: No valid peer bootstrapping key found");
+		goto fail;
+	}
+	pkex->peer_bootstrap_key = dpp_set_pubkey_point(pkex->x, b_key,
+							b_key_len);
+	if (!pkex->peer_bootstrap_key)
+		goto fail;
+	dpp_debug_print_key("DPP: Peer bootstrap public key",
+			    pkex->peer_bootstrap_key);
+
+	/* ECDH: L' = x * B' */
+	ctx = EVP_PKEY_CTX_new(pkex->x, NULL);
+	if (!ctx ||
+	    EVP_PKEY_derive_init(ctx) != 1 ||
+	    EVP_PKEY_derive_set_peer(ctx, pkex->peer_bootstrap_key) != 1 ||
+	    EVP_PKEY_derive(ctx, NULL, &Lx_len) != 1 ||
+	    Lx_len > DPP_MAX_SHARED_SECRET_LEN ||
+	    EVP_PKEY_derive(ctx, Lx, &Lx_len) != 1) {
+		wpa_printf(MSG_ERROR,
+			   "DPP: Failed to derive ECDH shared secret: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto fail;
+	}
+
+	wpa_hexdump_key(MSG_DEBUG, "DPP: ECDH shared secret (L.x)",
+			Lx, Lx_len);
+
+	/* v' = HMAC(L.x, MAC-Responder | B'.x | X.x | Y'.x) */
+	B_pub = dpp_get_pubkey_point(pkex->peer_bootstrap_key, 0);
+	X_pub = dpp_get_pubkey_point(pkex->x, 0);
+	Y_pub = dpp_get_pubkey_point(pkex->y, 0);
+	if (!B_pub || !X_pub || !Y_pub)
+		goto fail;
+	addr[0] = pkex->peer_mac;
+	len[0] = ETH_ALEN;
+	addr[1] = wpabuf_head(B_pub);
+	len[1] = wpabuf_len(B_pub) / 2;
+	addr[2] = wpabuf_head(X_pub);
+	len[2] = wpabuf_len(X_pub) / 2;
+	addr[3] = wpabuf_head(Y_pub);
+	len[3] = wpabuf_len(Y_pub) / 2;
+	if (dpp_hmac_vector(curve->hash_len, Lx, Lx_len, 4, addr, len, v) < 0)
+		goto fail;
+
+	peer_v = dpp_get_attr(unwrapped, unwrapped_len, DPP_ATTR_R_AUTH_TAG,
+			      &peer_v_len);
+	if (!peer_v || peer_v_len != curve->hash_len ||
+	    os_memcmp(peer_v, v, curve->hash_len) != 0) {
+		wpa_printf(MSG_DEBUG, "DPP: No valid v (R-Auth tag) found");
+		wpa_hexdump(MSG_DEBUG, "DPP: Calculated v'",
+			    v, curve->hash_len);
+		wpa_hexdump(MSG_DEBUG, "DPP: Received v", peer_v, peer_v_len);
+		goto fail;
+	}
+	wpa_printf(MSG_DEBUG, "DPP: Valid v (R-Auth tag) received");
+
+	ret = 0;
+out:
+	wpabuf_free(B_pub);
+	wpabuf_free(X_pub);
+	wpabuf_free(Y_pub);
+	EVP_PKEY_CTX_free(ctx);
+	os_free(unwrapped);
+	return ret;
+fail:
+	goto out;
+}
+
+
+void dpp_pkex_free(struct dpp_pkex *pkex)
+{
+	if (!pkex)
+		return;
+
+	os_free(pkex->identifier);
+	os_free(pkex->code);
+	EVP_PKEY_free(pkex->x);
+	EVP_PKEY_free(pkex->y);
+	EVP_PKEY_free(pkex->peer_bootstrap_key);
+	wpabuf_free(pkex->exchange_req);
+	wpabuf_free(pkex->exchange_resp);
+	os_free(pkex);
+}