3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2026-02-18 22:47:59 +01:00
iwd/src/dpp.c
James Prestwood b080854d3e dpp: fix incorrect offchannel usage as configurator
When acting as a configurator the enrollee can start on a different
channel than IWD is connected to. IWD will begin the auth process
on this channel but tell the enrollee to transition to the current
channel after the auth request. Since a configurator must be
connected (a requirement IWD enforces) we can assume a channel
transition will always be to the currently connected channel. This
allows us to simply cancel the offchannel request and wait for a
response (rather than start another offchannel).

Doing this improves the DPP performance and reduces the potential
for a lost frame during the channel transition.

This patch also addresses the comment that we should wait for the
auth request ACK before canceling the offchannel. Now a flag is
set and IWD will cancel the offchannel once the ACK is received.
2023-04-30 13:03:48 -05:00

2773 lines
68 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2021 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <ell/ell.h>
#include "linux/nl80211.h"
#include "src/missing.h"
#include "src/dbus.h"
#include "src/netdev.h"
#include "src/module.h"
#include "src/dpp-util.h"
#include "src/band.h"
#include "src/frame-xchg.h"
#include "src/offchannel.h"
#include "src/wiphy.h"
#include "src/ie.h"
#include "src/iwd.h"
#include "src/util.h"
#include "src/crypto.h"
#include "src/mpdu.h"
#include "ell/useful.h"
#include "src/common.h"
#include "src/json.h"
#include "src/storage.h"
#include "src/station.h"
#include "src/scan.h"
#include "src/network.h"
#include "src/handshake.h"
#include "src/nl80211util.h"
#define DPP_FRAME_MAX_RETRIES 5
#define DPP_FRAME_RETRY_TIMEOUT 1
static uint32_t netdev_watch;
static struct l_genl_family *nl80211;
static uint8_t broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
static struct l_queue *dpp_list;
static uint32_t mlme_watch;
static uint32_t unicast_watch;
static uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 };
enum dpp_state {
DPP_STATE_NOTHING,
DPP_STATE_PRESENCE,
DPP_STATE_AUTHENTICATING,
DPP_STATE_CONFIGURING,
};
enum dpp_capability {
DPP_CAPABILITY_ENROLLEE = 0x01,
DPP_CAPABILITY_CONFIGURATOR = 0x02,
};
struct dpp_sm {
struct netdev *netdev;
char *uri;
uint8_t role;
uint64_t wdev_id;
uint8_t *own_asn1;
size_t own_asn1_len;
uint8_t *peer_asn1;
size_t peer_asn1_len;
uint8_t own_boot_hash[32];
uint8_t peer_boot_hash[32];
const struct l_ecc_curve *curve;
size_t key_len;
size_t nonce_len;
struct l_ecc_scalar *boot_private;
struct l_ecc_point *boot_public;
struct l_ecc_point *peer_boot_public;
enum dpp_state state;
/*
* List of frequencies to jump between. The presence of this list is
* also used to signify that a configurator is an initiator vs responder
*/
uint32_t *freqs;
size_t freqs_len;
size_t freqs_idx;
uint32_t dwell;
uint32_t current_freq;
uint32_t new_freq;
struct scan_freq_set *presence_list;
uint32_t offchannel_id;
uint8_t auth_addr[6];
uint8_t r_nonce[32];
uint8_t i_nonce[32];
uint8_t e_nonce[32];
struct l_ecc_scalar *m;
uint64_t ke[L_ECC_MAX_DIGITS];
uint64_t k1[L_ECC_MAX_DIGITS];
uint64_t k2[L_ECC_MAX_DIGITS];
uint64_t auth_tag[L_ECC_MAX_DIGITS];
struct l_ecc_scalar *proto_private;
struct l_ecc_point *own_proto_public;
struct l_ecc_point *peer_proto_public;
uint8_t diag_token;
/* Timeout of either auth/config protocol */
struct l_timeout *timeout;
struct dpp_configuration *config;
uint32_t connect_scan_id;
uint64_t frame_cookie;
uint8_t frame_retry;
void *frame_pending;
size_t frame_size;
struct l_timeout *retry_timeout;
struct l_dbus_message *pending;
bool mcast_support : 1;
bool roc_started : 1;
bool channel_switch : 1;
};
static bool dpp_get_started(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct dpp_sm *dpp = user_data;
bool started = (dpp->state != DPP_STATE_NOTHING);
l_dbus_message_builder_append_basic(builder, 'b', &started);
return true;
}
static bool dpp_get_role(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct dpp_sm *dpp = user_data;
const char *role;
if (dpp->state == DPP_STATE_NOTHING)
return false;
switch (dpp->role) {
case DPP_CAPABILITY_ENROLLEE:
role = "enrollee";
break;
case DPP_CAPABILITY_CONFIGURATOR:
role = "configurator";
break;
default:
return false;
}
l_dbus_message_builder_append_basic(builder, 's', role);
return true;
}
static bool dpp_get_uri(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct dpp_sm *dpp = user_data;
if (dpp->state == DPP_STATE_NOTHING)
return false;
l_dbus_message_builder_append_basic(builder, 's', dpp->uri);
return true;
}
static void dpp_property_changed_notify(struct dpp_sm *dpp)
{
const char *path = netdev_get_path(dpp->netdev);
l_dbus_property_changed(dbus_get_bus(), path, IWD_DPP_INTERFACE,
"Started");
l_dbus_property_changed(dbus_get_bus(), path, IWD_DPP_INTERFACE,
"Role");
l_dbus_property_changed(dbus_get_bus(), path, IWD_DPP_INTERFACE,
"URI");
}
static void *dpp_serialize_iovec(struct iovec *iov, size_t iov_len,
size_t *out_len)
{
unsigned int i;
size_t size = 0;
uint8_t *ret;
for (i = 0; i < iov_len; i++)
size += iov[i].iov_len;
ret = l_malloc(size);
size = 0;
for (i = 0; i < iov_len; i++) {
memcpy(ret + size, iov[i].iov_base, iov[i].iov_len);
size += iov[i].iov_len;
}
if (out_len)
*out_len = size;
return ret;
}
static void dpp_free_auth_data(struct dpp_sm *dpp)
{
if (dpp->own_proto_public) {
l_ecc_point_free(dpp->own_proto_public);
dpp->own_proto_public = NULL;
}
if (dpp->proto_private) {
l_ecc_scalar_free(dpp->proto_private);
dpp->proto_private = NULL;
}
if (dpp->peer_proto_public) {
l_ecc_point_free(dpp->peer_proto_public);
dpp->peer_proto_public = NULL;
}
if (dpp->peer_boot_public) {
l_ecc_point_free(dpp->peer_boot_public);
dpp->peer_boot_public = NULL;
}
if (dpp->m) {
l_ecc_scalar_free(dpp->m);
dpp->m = NULL;
}
}
static void dpp_reset(struct dpp_sm *dpp)
{
if (dpp->uri) {
l_free(dpp->uri);
dpp->uri = NULL;
}
if (dpp->freqs) {
l_free(dpp->freqs);
dpp->freqs = NULL;
}
if (dpp->offchannel_id) {
offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
dpp->offchannel_id = 0;
}
if (dpp->timeout) {
l_timeout_remove(dpp->timeout);
dpp->timeout = NULL;
}
if (dpp->config) {
dpp_configuration_free(dpp->config);
dpp->config = NULL;
}
if (dpp->connect_scan_id) {
scan_cancel(dpp->wdev_id, dpp->connect_scan_id);
dpp->connect_scan_id = 0;
}
if (dpp->peer_asn1) {
l_free(dpp->peer_asn1);
dpp->peer_asn1 = NULL;
}
if (dpp->frame_pending) {
l_free(dpp->frame_pending);
dpp->frame_pending = NULL;
}
if (dpp->retry_timeout) {
l_timeout_remove(dpp->retry_timeout);
dpp->retry_timeout = NULL;
}
dpp->state = DPP_STATE_NOTHING;
dpp->new_freq = 0;
dpp->frame_retry = 0;
dpp->frame_cookie = 0;
explicit_bzero(dpp->r_nonce, dpp->nonce_len);
explicit_bzero(dpp->i_nonce, dpp->nonce_len);
explicit_bzero(dpp->e_nonce, dpp->nonce_len);
explicit_bzero(dpp->ke, dpp->key_len);
explicit_bzero(dpp->k1, dpp->key_len);
explicit_bzero(dpp->k2, dpp->key_len);
explicit_bzero(dpp->auth_tag, dpp->key_len);
dpp_free_auth_data(dpp);
dpp_property_changed_notify(dpp);
}
static void dpp_free(struct dpp_sm *dpp)
{
dpp_reset(dpp);
if (dpp->own_asn1) {
l_free(dpp->own_asn1);
dpp->own_asn1 = NULL;
}
if (dpp->boot_public) {
l_ecc_point_free(dpp->boot_public);
dpp->boot_public = NULL;
}
if (dpp->boot_private) {
l_ecc_scalar_free(dpp->boot_private);
dpp->boot_private = NULL;
}
l_free(dpp);
}
static void dpp_send_frame_cb(struct l_genl_msg *msg, void *user_data)
{
struct dpp_sm *dpp = user_data;
int err = l_genl_msg_get_error(msg);
if (err < 0) {
l_error("Error sending frame (%d)", err);
return;
}
if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &dpp->frame_cookie,
NL80211_ATTR_UNSPEC) < 0)
l_error("Error parsing frame cookie");
}
static void dpp_send_frame(struct dpp_sm *dpp,
struct iovec *iov, size_t iov_len,
uint32_t freq)
{
struct l_genl_msg *msg;
/*
* A received frame could potentially come in after the ROC session has
* ended. In this case the frame needs to be stored until ROC is started
* and sent at that time. The offchannel_id is also checked since
* this is not applicable when DPP is in a responder role waiting
* on the currently connected channel i.e. offchannel is never used.
*/
if (!dpp->roc_started && dpp->offchannel_id) {
dpp->frame_pending = dpp_serialize_iovec(iov, iov_len,
&dpp->frame_size);
return;
}
msg = l_genl_msg_new_sized(NL80211_CMD_FRAME, 512);
l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &dpp->wdev_id);
l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq);
l_genl_msg_append_attr(msg, NL80211_ATTR_OFFCHANNEL_TX_OK, 0, NULL);
l_genl_msg_append_attrv(msg, NL80211_ATTR_FRAME, iov, iov_len);
l_debug("Sending frame on frequency %u", freq);
if (!l_genl_family_send(nl80211, msg, dpp_send_frame_cb, dpp, NULL)) {
l_error("Could not send CMD_FRAME");
l_genl_msg_unref(msg);
}
}
static void dpp_frame_retry(struct dpp_sm *dpp)
{
struct iovec iov;
iov.iov_base = dpp->frame_pending;
iov.iov_len = dpp->frame_size;
dpp_send_frame(dpp, &iov, 1, dpp->current_freq);
l_free(dpp->frame_pending);
dpp->frame_pending = NULL;
}
static size_t dpp_build_header(const uint8_t *src, const uint8_t *dest,
enum dpp_frame_type type,
uint8_t buf[static 32])
{
uint8_t *ptr = buf + 24;
memset(buf, 0, 32);
l_put_le16(0x00d0, buf);
memcpy(buf + 4, dest, 6);
memcpy(buf + 10, src, 6);
memcpy(buf + 16, broadcast, 6);
*ptr++ = 0x04; /* Category: Public */
*ptr++ = 0x09; /* Action: Vendor specific usage */
memcpy(ptr, wifi_alliance_oui, 3);
ptr += 3;
*ptr++ = 0x1a; /* WiFi Alliance DPP OI type */
*ptr++ = 1; /* Cryptosuite */
*ptr++ = type;
return ptr - buf;
}
static size_t dpp_build_config_header(const uint8_t *src, const uint8_t *dest,
uint8_t diag_token,
uint8_t buf[static 37])
{
uint8_t *ptr = buf + 24;
memset(buf, 0, 37);
l_put_le16(0x00d0, buf);
memcpy(buf + 4, dest, 6);
memcpy(buf + 10, src, 6);
memcpy(buf + 16, broadcast, 6);
*ptr++ = 0x04; /* Public */
*ptr++ = 0x0a; /* Action */
*ptr++ = diag_token;
*ptr++ = IE_TYPE_ADVERTISEMENT_PROTOCOL;
*ptr++ = 8; /* len */
*ptr++ = 0x00;
*ptr++ = IE_TYPE_VENDOR_SPECIFIC;
*ptr++ = 5;
memcpy(ptr, wifi_alliance_oui, 3);
ptr += 3;
*ptr++ = 0x1a;
*ptr++ = 1;
return ptr - buf;
}
static void dpp_protocol_timeout(struct l_timeout *timeout, void *user_data)
{
struct dpp_sm *dpp = user_data;
l_debug("DPP timed out");
dpp_reset(dpp);
}
static void dpp_reset_protocol_timer(struct dpp_sm *dpp)
{
if (dpp->timeout)
l_timeout_modify(dpp->timeout, 10);
else
dpp->timeout = l_timeout_create(10, dpp_protocol_timeout,
dpp, NULL);
}
/*
* The configuration protocols use of AD components is somewhat confusing
* since the request/response frames are of a different format than the rest.
* In addition there are situations where the components length is zero yet it
* is still passed as such to AES-SIV.
*
* For the configuration request/response frames:
*
* "AAD for use with AES-SIV for protected messages in the DPP Configuration
* protocol shall consist of all octets in the Query Request and Query Response
* fields up to the first octet of the Wrapped Data attribute, which is the last
* attribute in a DPP Configuration frame. When the number of octets of AAD is
* zero, the number of components of AAD passed to AES-SIV is zero."
*
* - For configuration requests the optional query request field is not
* included, therefore no AAD data is passed. (dpp_configuration_start)
*
* - The configuration response does contain a query response field which is
* 5 bytes. (dpp_handle_config_response_frame)
*
* For the configuration result/status, the same rules are used as the
* authentication protocol. This is reiterated in section 6.4.1.
*
* - For the configuration result there is some confusion as to exactly how the
* second AAD component should be passed (since the spec specifically
* mentions using two components). There are no attributes prior to the
* wrapped data component meaning the length would be zero.
* Hostapd/wpa_supplicant pass a zero length AAD component to AES-SIV which
* does effect the resulting encryption/decryption so this is also what IWD
* will do to remain compliant with it.
*/
static void dpp_configuration_start(struct dpp_sm *dpp, const uint8_t *addr)
{
const char *json = "{\"name\":\"IWD\",\"wi-fi_tech\":\"infra\","
"\"netRole\":\"sta\"}";
struct iovec iov[3];
uint8_t hdr[37];
uint8_t attrs[512];
size_t json_len = strlen(json);
uint8_t *ptr = attrs;
l_getrandom(&dpp->diag_token, 1);
iov[0].iov_len = dpp_build_config_header(
netdev_get_address(dpp->netdev),
addr, dpp->diag_token, hdr);
iov[0].iov_base = hdr;
l_getrandom(dpp->e_nonce, dpp->nonce_len);
/* length */
ptr += 2;
/*
* "AAD for use with AES-SIV for protected messages in the DPP
* Configuration protocol shall consist of all octets in the Query
* Request and Query Response fields up to the first octet of the
* Wrapped Data attribute"
*
* In this case there is no query request/response fields, nor any
* attributes besides wrapped data meaning zero AD components.
*/
ptr += dpp_append_wrapped_data(NULL, 0, NULL, 0, ptr, sizeof(attrs),
dpp->ke, dpp->key_len, 2,
DPP_ATTR_ENROLLEE_NONCE, dpp->nonce_len, dpp->e_nonce,
DPP_ATTR_CONFIGURATION_REQUEST, json_len, json);
l_put_le16(ptr - attrs - 2, attrs);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp->state = DPP_STATE_CONFIGURING;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static void send_config_result(struct dpp_sm *dpp, const uint8_t *to)
{
uint8_t hdr[32];
struct iovec iov[2];
uint8_t attrs[256];
uint8_t *ptr = attrs;
uint8_t zero = 0;
iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev), to,
DPP_FRAME_CONFIGURATION_RESULT, hdr);
iov[0].iov_base = hdr;
ptr += dpp_append_wrapped_data(hdr + 26, 6, attrs, 0, ptr,
sizeof(attrs), dpp->ke, dpp->key_len, 2,
DPP_ATTR_STATUS, (size_t) 1, &zero,
DPP_ATTR_ENROLLEE_NONCE, dpp->nonce_len, dpp->e_nonce);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static void dpp_write_config(struct dpp_configuration *config,
struct network *network)
{
char ssid[33];
_auto_(l_settings_free) struct l_settings *settings = l_settings_new();
_auto_(l_free) char *path;
_auto_(l_free) uint8_t *psk = NULL;
size_t psk_len;
memcpy(ssid, config->ssid, config->ssid_len);
ssid[config->ssid_len] = '\0';
path = storage_get_network_file_path(SECURITY_PSK, ssid);
if (l_settings_load_from_file(settings, path)) {
/* Remove any existing Security keys */
l_settings_remove_group(settings, "Security");
}
if (config->passphrase) {
l_settings_set_string(settings, "Security", "Passphrase",
config->passphrase);
if (network)
network_set_passphrase(network, config->passphrase);
} else if (config->psk) {
l_settings_set_string(settings, "Security", "PreSharedKey",
config->psk);
psk = l_util_from_hexstring(config->psk, &psk_len);
if (network)
network_set_psk(network, psk);
}
l_debug("Storing credential for '%s(%s)'", ssid,
security_to_str(SECURITY_PSK));
storage_network_sync(SECURITY_PSK, ssid, settings);
}
static void dpp_scan_triggered(int err, void *user_data)
{
/* Not much can be done in this case */
if (err < 0)
l_error("Failed to trigger DPP scan");
}
static bool dpp_scan_results(int err, struct l_queue *bss_list,
const struct scan_freq_set *freqs,
void *userdata)
{
struct dpp_sm *dpp = userdata;
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
if (err < 0)
return false;
station_set_scan_results(station, bss_list, freqs, true);
return true;
}
static void dpp_scan_destroy(void *userdata)
{
struct dpp_sm *dpp = userdata;
dpp->connect_scan_id = 0;
dpp_reset(dpp);
}
static void dpp_handle_config_response_frame(const struct mmpdu_header *frame,
const void *body, size_t body_len,
int rssi, void *user_data)
{
struct dpp_sm *dpp = user_data;
const uint8_t *ptr = body;
uint16_t status;
uint16_t fragmented; /* Fragmented/Comeback delay field */
uint8_t adv_protocol_element[] = { 0x6C, 0x08, 0x7F };
uint8_t adv_protocol_id[] = { 0xDD, 0x05, 0x50, 0x6F,
0x9A, 0x1A, 0x01 };
uint16_t query_len;
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
const char *json = NULL;
size_t json_len = 0;
int dstatus = -1;
const uint8_t *wrapped = NULL;
const uint8_t *e_nonce = NULL;
size_t wrapped_len = 0;
_auto_(l_free) uint8_t *unwrapped = NULL;
struct dpp_configuration *config;
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
struct network *network = NULL;
struct scan_bss *bss = NULL;
char ssid[33];
if (dpp->state != DPP_STATE_CONFIGURING)
return;
/*
* Can a configuration request come from someone other than who you
* authenticated to?
*/
if (memcmp(dpp->auth_addr, frame->address_2, 6))
return;
if (body_len < 19)
return;
ptr += 2;
if (*ptr++ != dpp->diag_token)
return;
status = l_get_le16(ptr);
ptr += 2;
if (status != 0) {
l_debug("Bad configuration status %u", status);
return;
}
fragmented = l_get_le16(ptr);
ptr += 2;
/*
* TODO: handle 0x0001 (fragmented), as well as comeback delay.
*/
if (fragmented != 0) {
l_debug("Fragmented messages not currently supported");
return;
}
if (memcmp(ptr, adv_protocol_element, sizeof(adv_protocol_element))) {
l_debug("Invalid Advertisement protocol element");
return;
}
ptr += sizeof(adv_protocol_element);
if (memcmp(ptr, adv_protocol_id, sizeof(adv_protocol_id))) {
l_debug("Invalid Advertisement protocol ID");
return;
}
ptr += sizeof(adv_protocol_id);
query_len = l_get_le16(ptr);
ptr += 2;
if (query_len > body_len - 19)
return;
dpp_attr_iter_init(&iter, ptr, query_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_STATUS:
dstatus = l_get_u8(data);
break;
case DPP_ATTR_WRAPPED_DATA:
wrapped = data;
wrapped_len = len;
break;
default:
/*
* TODO: CSR Attribute
*/
break;
}
}
if (dstatus != DPP_STATUS_OK || !wrapped) {
l_debug("Bad status or missing attributes");
return;
}
unwrapped = dpp_unwrap_attr(ptr, wrapped - ptr - 4, NULL, 0, dpp->ke,
dpp->key_len, wrapped, wrapped_len,
&wrapped_len);
if (!unwrapped) {
l_debug("Failed to unwrap");
return;
}
dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_ENROLLEE_NONCE:
if (len != dpp->nonce_len)
break;
if (memcmp(data, dpp->e_nonce, dpp->nonce_len))
break;
e_nonce = data;
break;
case DPP_ATTR_CONFIGURATION_OBJECT:
json = (const char *)data;
json_len = len;
break;
default:
break;
}
}
if (!json || !e_nonce) {
l_debug("No configuration object in response");
return;
}
config = dpp_parse_configuration_object(json, json_len);
if (!config) {
l_error("Configuration object did not parse");
return;
}
/*
* We should have a station device, but if not DPP can write the
* credentials out and be done
*/
if (station) {
memcpy(ssid, config->ssid, config->ssid_len);
ssid[config->ssid_len] = '\0';
network = station_network_find(station, ssid, SECURITY_PSK);
if (network)
bss = network_bss_select(network, true);
}
dpp_write_config(config, network);
dpp_configuration_free(config);
send_config_result(dpp, dpp->auth_addr);
offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
if (network && bss)
__station_connect_network(station, network, bss);
else if (station) {
dpp->connect_scan_id = scan_active(dpp->wdev_id, NULL, 0,
dpp_scan_triggered,
dpp_scan_results, dpp,
dpp_scan_destroy);
if (dpp->connect_scan_id)
return;
}
dpp_reset(dpp);
}
static void dpp_send_config_response(struct dpp_sm *dpp, uint8_t status)
{
_auto_(l_free) char *json = NULL;
struct iovec iov[3];
uint8_t hdr[41];
uint8_t attrs[512];
size_t json_len;
uint8_t *ptr = hdr + 24;
memset(hdr, 0, sizeof(hdr));
l_put_le16(0x00d0, hdr);
memcpy(hdr + 4, dpp->auth_addr, 6);
memcpy(hdr + 10, netdev_get_address(dpp->netdev), 6);
memcpy(hdr + 16, broadcast, 6);
*ptr++ = 0x04;
*ptr++ = 0x0b;
*ptr++ = dpp->diag_token;
l_put_le16(0, ptr); /* status */
ptr += 2;
l_put_le16(0, ptr); /* fragmented (no) */
ptr += 2;
*ptr++ = IE_TYPE_ADVERTISEMENT_PROTOCOL;
*ptr++ = 0x08;
*ptr++ = 0x7f;
*ptr++ = IE_TYPE_VENDOR_SPECIFIC;
*ptr++ = 5;
memcpy(ptr, wifi_alliance_oui, sizeof(wifi_alliance_oui));
ptr += sizeof(wifi_alliance_oui);
*ptr++ = 0x1a;
*ptr++ = 1;
iov[0].iov_base = hdr;
iov[0].iov_len = ptr - hdr;
ptr = attrs;
ptr += 2; /* length */
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
/*
* There are several failure status codes that can be used (defined in
* 6.4.3.1), each with their own set of attributes that should be
* included. For now IWD's basic DPP implementation will assume
* STATUS_CONFIGURE_FAILURE which only includes the E-Nonce.
*/
if (status == DPP_STATUS_OK) {
json = dpp_configuration_to_json(dpp->config);
json_len = strlen(json);
ptr += dpp_append_wrapped_data(attrs + 2, ptr - attrs - 2,
NULL, 0, ptr, sizeof(attrs),
dpp->ke, dpp->key_len, 2,
DPP_ATTR_ENROLLEE_NONCE,
dpp->nonce_len, dpp->e_nonce,
DPP_ATTR_CONFIGURATION_OBJECT,
json_len, json);
} else
ptr += dpp_append_wrapped_data(attrs + 2, ptr - attrs - 2,
NULL, 0, ptr, sizeof(attrs),
dpp->ke, dpp->key_len, 2,
DPP_ATTR_ENROLLEE_NONCE,
dpp->nonce_len, dpp->e_nonce);
l_put_le16(ptr - attrs - 2, attrs);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static void dpp_handle_config_request_frame(const struct mmpdu_header *frame,
const void *body, size_t body_len,
int rssi, void *user_data)
{
struct dpp_sm *dpp = user_data;
const uint8_t *ptr = body;
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
const char *json = NULL;
size_t json_len = 0;
struct json_contents *c;
const uint8_t *wrapped = NULL;
const uint8_t *e_nonce = NULL;
size_t wrapped_len = 0;
_auto_(l_free) uint8_t *unwrapped = NULL;
uint8_t hdr_check[] = { IE_TYPE_ADVERTISEMENT_PROTOCOL, 0x08, 0x7f,
IE_TYPE_VENDOR_SPECIFIC, 5 };
struct json_iter jsiter;
_auto_(l_free) char *tech = NULL;
_auto_(l_free) char *role = NULL;
if (dpp->state != DPP_STATE_AUTHENTICATING) {
l_debug("Configuration request in wrong state");
return;
}
if (memcmp(dpp->auth_addr, frame->address_2, 6)) {
l_debug("Configuration request not from authenticated peer");
return;
}
if (body_len < 15) {
l_debug("Configuration request data not long enough");
return;
}
ptr += 2;
dpp->diag_token = *ptr++;
if (memcmp(ptr, hdr_check, sizeof(hdr_check)))
return;
ptr += sizeof(hdr_check);
if (memcmp(ptr, wifi_alliance_oui, sizeof(wifi_alliance_oui)))
return;
ptr += sizeof(wifi_alliance_oui);
if (*ptr != 0x1a && *(ptr + 1) != 1)
return;
ptr += 2;
len = l_get_le16(ptr);
ptr += 2;
if (len > body_len - 15)
return;
dpp_attr_iter_init(&iter, ptr, len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_WRAPPED_DATA:
wrapped = data;
wrapped_len = len;
break;
default:
/* Wrapped data should be only attribute */
return;
}
}
if (!wrapped) {
l_debug("Wrapped data missing");
return;
}
unwrapped = dpp_unwrap_attr(NULL, 0, NULL, 0, dpp->ke,
dpp->key_len, wrapped, wrapped_len,
&wrapped_len);
if (!unwrapped) {
l_debug("Failed to unwrap");
return;
}
dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_ENROLLEE_NONCE:
if (len != dpp->nonce_len)
break;
e_nonce = data;
break;
case DPP_ATTR_CONFIGURATION_REQUEST:
json = (const char *)data;
json_len = len;
break;
default:
break;
}
}
if (!json || !e_nonce) {
l_debug("No configuration object in response");
return;
}
c = json_contents_new(json, json_len);
if (!c) {
json_contents_free(c);
return;
}
json_iter_init(&jsiter, c);
/*
* Check mandatory values (Table 7). There isn't much that can be done
* with these, but the spec requires they be included.
*/
if (!json_iter_parse(&jsiter,
JSON_MANDATORY("name", JSON_STRING, NULL),
JSON_MANDATORY("wi-fi_tech", JSON_STRING, &tech),
JSON_MANDATORY("netRole", JSON_STRING, &role),
JSON_UNDEFINED))
goto configure_failure;
if (strcmp(tech, "infra"))
goto configure_failure;
if (strcmp(role, "sta"))
goto configure_failure;
json_contents_free(c);
memcpy(dpp->e_nonce, e_nonce, dpp->nonce_len);
dpp->state = DPP_STATE_CONFIGURING;
dpp_send_config_response(dpp, DPP_STATUS_OK);
return;
configure_failure:
dpp_send_config_response(dpp, DPP_STATUS_CONFIGURE_FAILURE);
/*
* The other peer is still authenticated, and can potentially send
* additional requests so keep this session alive.
*/
}
static void dpp_handle_config_result_frame(struct dpp_sm *dpp,
const uint8_t *from, const void *body,
size_t body_len)
{
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
int status = -1;
const void *e_nonce = NULL;
const void *wrapped = NULL;
size_t wrapped_len;
_auto_(l_free) void *unwrapped = NULL;
if (dpp->state != DPP_STATE_CONFIGURING)
return;
dpp_attr_iter_init(&iter, body + 8, body_len - 8);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_WRAPPED_DATA:
wrapped = data;
wrapped_len = len;
break;
default:
/* Wrapped data should be only attribute */
return;
}
}
if (!wrapped)
return;
unwrapped = dpp_unwrap_attr(body + 2, wrapped - body - 6, wrapped, 0,
dpp->ke, dpp->key_len, wrapped,
wrapped_len, &wrapped_len);
if (!unwrapped) {
l_debug("Failed to unwrap DPP configuration result");
return;
}
dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_STATUS:
status = l_get_u8(data);
break;
case DPP_ATTR_ENROLLEE_NONCE:
e_nonce = data;
break;
default:
break;
}
}
if (status != DPP_STATUS_OK || !e_nonce)
l_debug("Enrollee signaled a failed configuration");
else
l_debug("Configuration success");
dpp_reset(dpp);
}
/*
* The Authentication protocol has a consistent use of AD components, and this
* use is defined in 6.3.1.4:
*
* "Invocations of AES-SIV in the DPP Authentication protocol that produce
* ciphertext that is part of an additional AES-SIV invocation do not use AAD;
* in other words, the number of AAD components is set to zero. All other
* invocations of AES-SIV in the DPP Authentication protocol shall pass a vector
* of AAD having two components of AAD in the following order: (1) the DPP
* header, as defined in Table 30, from the OUI field (inclusive) to the DPP
* Frame Type field (inclusive); and (2) all octets in a DPP Public Action frame
* after the DPP Frame Type field up to and including the last octet of the last
* attribute before the Wrapped Data attribute"
*
* In practice you see this as AD0 being some offset in the frame (offset to the
* OUI). For outgoing packets this is 26 bytes offset since the header is built
* manually. For incoming packets the offset is 2 bytes. The length is always
* 6 bytes for AD0.
*
* The AD1 data is always the start of the attributes, and length is the number
* of bytes from these attributes to wrapped data. e.g.
*
* ad1 = attrs
* ad1_len = ptr - attrs
*/
static void send_authenticate_response(struct dpp_sm *dpp)
{
uint8_t hdr[32];
uint8_t attrs[256];
uint8_t *ptr = attrs;
uint8_t status = DPP_STATUS_OK;
uint64_t r_proto_key[L_ECC_MAX_DIGITS * 2];
uint8_t version = 2;
struct iovec iov[3];
uint8_t wrapped2_plaintext[dpp->key_len + 4];
uint8_t wrapped2[dpp->key_len + 16 + 8];
size_t wrapped2_len;
l_ecc_point_get_data(dpp->own_proto_public, r_proto_key,
sizeof(r_proto_key));
iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
dpp->auth_addr,
DPP_FRAME_AUTHENTICATION_RESPONSE, hdr);
iov[0].iov_base = hdr;
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
dpp->own_boot_hash, 32);
ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_PROTOCOL_KEY,
r_proto_key, dpp->key_len * 2);
ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
/* Wrap up secondary data (R-Auth) */
wrapped2_len = dpp_append_attr(wrapped2_plaintext,
DPP_ATTR_RESPONDER_AUTH_TAG,
dpp->auth_tag, dpp->key_len);
/*
* "Invocations of AES-SIV in the DPP Authentication protocol that
* produce ciphertext that is part of an additional AES-SIV invocation
* do not use AAD; in other words, the number of AAD components is set
* to zero.""
*/
if (!aes_siv_encrypt(dpp->ke, dpp->key_len, wrapped2_plaintext,
dpp->key_len + 4, NULL, 0, wrapped2)) {
l_error("Failed to encrypt wrapped data");
return;
}
wrapped2_len += 16;
ptr += dpp_append_wrapped_data(hdr + 26, 6, attrs, ptr - attrs,
ptr, sizeof(attrs), dpp->k2, dpp->key_len, 4,
DPP_ATTR_RESPONDER_NONCE, dpp->nonce_len, dpp->r_nonce,
DPP_ATTR_INITIATOR_NONCE, dpp->nonce_len, dpp->i_nonce,
DPP_ATTR_RESPONDER_CAPABILITIES, (size_t) 1, &dpp->role,
DPP_ATTR_WRAPPED_DATA, wrapped2_len, wrapped2);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static void authenticate_confirm(struct dpp_sm *dpp, const uint8_t *from,
const uint8_t *body, size_t body_len)
{
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
int status = -1;
const uint8_t *r_boot_hash = NULL;
const void *wrapped = NULL;
const uint8_t *i_auth = NULL;
size_t i_auth_len;
_auto_(l_free) uint8_t *unwrapped = NULL;
size_t wrapped_len = 0;
uint64_t i_auth_check[L_ECC_MAX_DIGITS];
const void *unwrap_key;
const void *ad0 = body + 2;
const void *ad1 = body + 8;
if (dpp->state != DPP_STATE_AUTHENTICATING)
return;
if (memcmp(from, dpp->auth_addr, 6))
return;
l_debug("authenticate confirm");
dpp_attr_iter_init(&iter, body + 8, body_len - 8);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_STATUS:
status = l_get_u8(data);
break;
case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
r_boot_hash = data;
/*
* Spec requires this, but does not mention if anything
* is to be done with it.
*/
break;
case DPP_ATTR_INITIATOR_BOOT_KEY_HASH:
/* No mutual authentication */
break;
case DPP_ATTR_WRAPPED_DATA:
wrapped = data;
wrapped_len = len;
break;
default:
break;
}
}
if (!r_boot_hash || !wrapped) {
l_debug("Attributes missing from authenticate confirm");
return;
}
/*
* "The Responder obtains the DPP Authentication Confirm frame and
* checks the value of the DPP Status field. If the value of the DPP
* Status field is STATUS_NOT_COMPATIBLE or STATUS_AUTH_FAILURE, the
* Responder unwraps the wrapped data portion of the frame using k2"
*/
if (status == DPP_STATUS_OK)
unwrap_key = dpp->ke;
else if (status == DPP_STATUS_NOT_COMPATIBLE ||
status == DPP_STATUS_AUTH_FAILURE)
unwrap_key = dpp->k2;
else
goto auth_confirm_failed;
unwrapped = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1,
unwrap_key, dpp->key_len, wrapped, wrapped_len,
&wrapped_len);
if (!unwrapped)
goto auth_confirm_failed;
if (status != DPP_STATUS_OK) {
/*
* "If unwrapping is successful, the Responder should generate
* an alert indicating the reason for the protocol failure."
*/
l_debug("Authentication failed due to status %s",
status == DPP_STATUS_NOT_COMPATIBLE ?
"NOT_COMPATIBLE" : "AUTH_FAILURE");
goto auth_confirm_failed;
}
dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_INITIATOR_AUTH_TAG:
i_auth = data;
i_auth_len = len;
break;
case DPP_ATTR_RESPONDER_NONCE:
/* Only if error */
break;
default:
break;
}
}
if (!i_auth || i_auth_len != dpp->key_len) {
l_debug("I-Auth missing from wrapped data");
goto auth_confirm_failed;
}
dpp_derive_i_auth(dpp->r_nonce, dpp->i_nonce, dpp->nonce_len,
dpp->own_proto_public, dpp->peer_proto_public,
dpp->boot_public, i_auth_check);
if (memcmp(i_auth, i_auth_check, i_auth_len)) {
l_error("I-Auth did not verify");
goto auth_confirm_failed;
}
l_debug("Authentication successful");
dpp_reset_protocol_timer(dpp);
if (dpp->role == DPP_CAPABILITY_ENROLLEE)
dpp_configuration_start(dpp, from);
return;
auth_confirm_failed:
dpp->state = DPP_STATE_PRESENCE;
dpp_free_auth_data(dpp);
}
static void dpp_auth_request_failed(struct dpp_sm *dpp,
enum dpp_status status,
void *k1)
{
uint8_t hdr[32];
uint8_t attrs[128];
uint8_t *ptr = attrs;
uint8_t version = 2;
uint8_t s = status;
struct iovec iov[2];
iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
dpp->auth_addr,
DPP_FRAME_AUTHENTICATION_RESPONSE, hdr);
iov[0].iov_base = hdr;
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &s, 1);
ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
dpp->own_boot_hash, 32);
ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
ptr += dpp_append_wrapped_data(hdr + 26, 6, attrs, ptr - attrs,
ptr, sizeof(attrs) - (ptr - attrs), k1, dpp->key_len, 2,
DPP_ATTR_INITIATOR_NONCE, dpp->nonce_len, dpp->i_nonce,
DPP_ATTR_RESPONDER_CAPABILITIES,
(size_t) 1, &dpp->role);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static bool dpp_check_roles(struct dpp_sm *dpp, uint8_t peer_capa)
{
if (dpp->role == DPP_CAPABILITY_ENROLLEE &&
!(peer_capa & DPP_CAPABILITY_CONFIGURATOR))
return false;
else if (dpp->role == DPP_CAPABILITY_CONFIGURATOR &&
!(peer_capa & DPP_CAPABILITY_ENROLLEE))
return false;
return true;
}
static void dpp_presence_announce(struct dpp_sm *dpp)
{
struct netdev *netdev = dpp->netdev;
uint8_t hdr[32];
uint8_t attrs[32 + 4];
uint8_t hash[32];
uint8_t *ptr = attrs;
const uint8_t *addr = netdev_get_address(netdev);
struct iovec iov[2];
iov[0].iov_len = dpp_build_header(addr, broadcast,
DPP_FRAME_PRESENCE_ANNOUNCEMENT, hdr);
iov[0].iov_base = hdr;
dpp_hash(L_CHECKSUM_SHA256, hash, 2, "chirp", strlen("chirp"),
dpp->own_asn1, dpp->own_asn1_len);
ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH, hash, 32);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
l_debug("Sending presence announcement on frequency %u and waiting %u",
dpp->current_freq, dpp->dwell);
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static bool dpp_send_authenticate_request(struct dpp_sm *dpp)
{
uint8_t hdr[32];
uint8_t attrs[256];
uint8_t *ptr = attrs;
uint64_t i_proto_key[L_ECC_MAX_DIGITS * 2];
uint8_t version = 2;
struct iovec iov[2];
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
struct scan_bss *bss = station_get_connected_bss(station);
/* Got disconnected by the time the peer was discovered */
if (!bss) {
dpp_reset(dpp);
return false;
}
l_ecc_point_get_data(dpp->own_proto_public, i_proto_key,
sizeof(i_proto_key));
iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
dpp->auth_addr,
DPP_FRAME_AUTHENTICATION_REQUEST, hdr);
iov[0].iov_base = hdr;
ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
dpp->peer_boot_hash, 32);
ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_BOOT_KEY_HASH,
dpp->own_boot_hash, 32);
ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_PROTOCOL_KEY,
i_proto_key, dpp->key_len * 2);
ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
if (dpp->current_freq != bss->frequency) {
uint8_t pair[2] = { 81,
band_freq_to_channel(bss->frequency, NULL) };
ptr += dpp_append_attr(ptr, DPP_ATTR_CHANNEL, pair, 2);
}
ptr += dpp_append_wrapped_data(hdr + 26, 6, attrs, ptr - attrs,
ptr, sizeof(attrs), dpp->k1, dpp->key_len, 2,
DPP_ATTR_INITIATOR_NONCE, dpp->nonce_len, dpp->i_nonce,
DPP_ATTR_INITIATOR_CAPABILITIES,
(size_t) 1, &dpp->role);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
return true;
}
static void dpp_roc_started(void *user_data)
{
struct dpp_sm *dpp = user_data;
/*
* - If a configurator, nothing to do but wait for a request
* (unless multicast frame registration is unsupported in which case
* send an authenticate request now)
* - If in the presence state continue sending announcements.
* - If authenticating, and this is a result of a channel switch send
* the authenticate response now.
*/
dpp->roc_started = true;
/*
* The retry timer indicates a frame was not acked in which case we
* should not change any state or send any frames until that expires.
*/
if (dpp->retry_timeout)
return;
if (dpp->frame_pending) {
dpp_frame_retry(dpp);
return;
}
switch (dpp->state) {
case DPP_STATE_PRESENCE:
if (dpp->role == DPP_CAPABILITY_CONFIGURATOR)
return;
if (dpp->pending) {
struct l_dbus_message *reply =
l_dbus_message_new_method_return(dpp->pending);
l_dbus_message_set_arguments(reply, "s", dpp->uri);
dbus_pending_reply(&dpp->pending, reply);
}
dpp_presence_announce(dpp);
break;
case DPP_STATE_AUTHENTICATING:
/*
* No multicast frame registration support, jump right into
* sending auth frames. This diverges from the 2.0 spec, but in
* reality the the main path nearly all drivers will hit.
*/
if (dpp->role == DPP_CAPABILITY_CONFIGURATOR) {
if (dpp->mcast_support)
return;
dpp_send_authenticate_request(dpp);
return;
}
if (dpp->new_freq) {
dpp->current_freq = dpp->new_freq;
dpp->new_freq = 0;
send_authenticate_response(dpp);
}
break;
default:
break;
}
}
static void dpp_start_offchannel(struct dpp_sm *dpp, uint32_t freq);
static void dpp_presence_timeout(int error, void *user_data)
{
struct dpp_sm *dpp = user_data;
dpp->offchannel_id = 0;
dpp->roc_started = false;
/*
* If cancelled this is likely due to netdev going down or from Stop().
* Otherwise there was some other problem which is probably not
* recoverable.
*/
if (error == -ECANCELED)
return;
else if (error == -EIO)
goto next_roc;
else if (error < 0)
goto protocol_failed;
switch (dpp->state) {
case DPP_STATE_PRESENCE:
break;
case DPP_STATE_NOTHING:
/* Protocol already terminated */
return;
case DPP_STATE_AUTHENTICATING:
case DPP_STATE_CONFIGURING:
goto next_roc;
}
dpp->freqs_idx++;
if (dpp->freqs_idx >= dpp->freqs_len) {
l_debug("Max retries on presence announcements");
dpp->freqs_idx = 0;
}
dpp->current_freq = dpp->freqs[dpp->freqs_idx];
l_debug("Presence timeout, moving to next frequency %u, duration %u",
dpp->current_freq, dpp->dwell);
next_roc:
dpp_start_offchannel(dpp, dpp->current_freq);
return;
protocol_failed:
dpp_reset(dpp);
}
static void dpp_start_offchannel(struct dpp_sm *dpp, uint32_t freq)
{
/*
* This needs to be handled carefully for a few reasons:
*
* First, the next offchannel operation needs to be started prior to
* canceling an existing one. This is so the offchannel work can
* continue uninterrupted without any other work items starting in
* between canceling and starting the next (e.g. if a scan request is
* sitting in the queue).
*
* Second, dpp_presence_timeout resets dpp->offchannel_id to zero which
* is why the new ID is saved and only set to dpp->offchannel_id once
* the previous offchannel work is cancelled (i.e. destroy() has been
* called).
*/
uint32_t id = offchannel_start(netdev_get_wdev_id(dpp->netdev),
WIPHY_WORK_PRIORITY_OFFCHANNEL,
freq, dpp->dwell, dpp_roc_started,
dpp, dpp_presence_timeout);
if (dpp->offchannel_id)
offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
dpp->offchannel_id = id;
}
static void authenticate_request(struct dpp_sm *dpp, const uint8_t *from,
const uint8_t *body, size_t body_len)
{
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
const uint8_t *r_boot = NULL;
const uint8_t *i_boot = NULL;
const uint8_t *i_proto = NULL;
const void *wrapped = NULL;
const uint8_t *i_nonce = NULL;
uint8_t i_capa = 0;
size_t r_boot_len = 0, i_proto_len = 0, wrapped_len = 0;
size_t i_nonce_len = 0;
_auto_(l_free) uint8_t *unwrapped = NULL;
_auto_(l_ecc_scalar_free) struct l_ecc_scalar *m = NULL;
_auto_(l_ecc_scalar_free) struct l_ecc_scalar *n = NULL;
uint64_t k1[L_ECC_MAX_DIGITS];
const void *ad0 = body + 2;
const void *ad1 = body + 8;
uint32_t freq;
if (util_is_broadcast_address(from))
return;
if (dpp->state != DPP_STATE_PRESENCE)
return;
l_debug("authenticate request");
dpp_attr_iter_init(&iter, body + 8, body_len - 8);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_INITIATOR_BOOT_KEY_HASH:
i_boot = data;
/*
* This attribute is required by the spec, but only
* used for mutual authentication.
*/
break;
case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
r_boot = data;
r_boot_len = len;
break;
case DPP_ATTR_INITIATOR_PROTOCOL_KEY:
i_proto = data;
i_proto_len = len;
break;
case DPP_ATTR_WRAPPED_DATA:
/* I-Nonce/I-Capabilities part of wrapped data */
wrapped = data;
wrapped_len = len;
break;
/* Optional attributes */
case DPP_ATTR_PROTOCOL_VERSION:
if (l_get_u8(data) != 2) {
l_debug("Protocol version did not match");
return;
}
break;
case DPP_ATTR_CHANNEL:
if (len != 2)
return;
freq = oci_to_frequency(l_get_u8(data),
l_get_u8(data + 1));
if (freq == dpp->current_freq)
break;
/*
* Configurators are already connected to a network, so
* to preserve wireless performance the enrollee will
* be required to be on this channel, not a channel it
* requests.
*/
if (dpp->role == DPP_CAPABILITY_CONFIGURATOR)
return;
/*
* Otherwise, as an enrollee, we can jump to whatever
* channel the configurator requests
*/
dpp->new_freq = freq;
l_debug("Configurator requested a new frequency %u",
dpp->new_freq);
dpp_start_offchannel(dpp, dpp->new_freq);
break;
default:
break;
}
}
if (!r_boot || !i_boot || !i_proto || !wrapped)
goto auth_request_failed;
if (r_boot_len != 32 || memcmp(dpp->own_boot_hash,
r_boot, r_boot_len)) {
l_debug("Responder boot key hash failed to verify");
goto auth_request_failed;
}
dpp->peer_proto_public = l_ecc_point_from_data(dpp->curve,
L_ECC_POINT_TYPE_FULL,
i_proto, i_proto_len);
if (!dpp->peer_proto_public) {
l_debug("Initiators protocol key invalid");
goto auth_request_failed;
}
m = dpp_derive_k1(dpp->peer_proto_public, dpp->boot_private, k1);
if (!m)
goto auth_request_failed;
unwrapped = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1,
k1, dpp->key_len, wrapped, wrapped_len, &wrapped_len);
if (!unwrapped)
goto auth_request_failed;
dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_INITIATOR_NONCE:
i_nonce = data;
i_nonce_len = len;
break;
case DPP_ATTR_INITIATOR_CAPABILITIES:
/*
* "If the Responder is not capable of supporting the
* role indicated by the Initiator, it shall respond
* with a DPP Authentication Response frame indicating
* failure by adding the DPP Status field set to
* STATUS_NOT_COMPATIBLE"
*/
i_capa = l_get_u8(data);
if (!dpp_check_roles(dpp, i_capa)) {
l_debug("Peer does not support required role");
dpp_auth_request_failed(dpp,
DPP_STATUS_NOT_COMPATIBLE, k1);
goto auth_request_failed;
}
break;
default:
break;
}
}
if (i_nonce_len != dpp->nonce_len) {
l_debug("I-Nonce has unexpected length %zu", i_nonce_len);
goto auth_request_failed;
}
memcpy(dpp->i_nonce, i_nonce, i_nonce_len);
/* Derive keys k2, ke, and R-Auth for authentication response */
n = dpp_derive_k2(dpp->peer_proto_public, dpp->proto_private, dpp->k2);
if (!n)
goto auth_request_failed;
l_getrandom(dpp->r_nonce, dpp->nonce_len);
if (!dpp_derive_ke(dpp->i_nonce, dpp->r_nonce, m, n, dpp->ke))
goto auth_request_failed;
if (!dpp_derive_r_auth(dpp->i_nonce, dpp->r_nonce, dpp->nonce_len,
dpp->peer_proto_public, dpp->own_proto_public,
dpp->boot_public, dpp->auth_tag))
goto auth_request_failed;
memcpy(dpp->auth_addr, from, 6);
dpp->state = DPP_STATE_AUTHENTICATING;
dpp_reset_protocol_timer(dpp);
/* Don't send if the frequency is changing */
if (!dpp->new_freq)
send_authenticate_response(dpp);
return;
auth_request_failed:
dpp->state = DPP_STATE_PRESENCE;
dpp_free_auth_data(dpp);
}
static void dpp_send_authenticate_confirm(struct dpp_sm *dpp)
{
uint8_t hdr[32];
struct iovec iov[2];
uint8_t attrs[256];
uint8_t *ptr = attrs;
uint8_t zero = 0;
iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
dpp->auth_addr,
DPP_FRAME_AUTHENTICATION_CONFIRM, hdr);
iov[0].iov_base = hdr;
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &zero, 1);
ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
dpp->peer_boot_hash, 32);
ptr += dpp_append_wrapped_data(hdr + 26, 6, attrs, ptr - attrs, ptr,
sizeof(attrs), dpp->ke, dpp->key_len, 1,
DPP_ATTR_INITIATOR_AUTH_TAG, dpp->key_len,
dpp->auth_tag);
iov[1].iov_base = attrs;
iov[1].iov_len = ptr - attrs;
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
}
static void authenticate_response(struct dpp_sm *dpp, const uint8_t *from,
const uint8_t *body, size_t body_len)
{
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
int status = -1;
const void *r_boot_hash = NULL;
const void *r_proto = NULL;
size_t r_proto_len = 0;
const void *wrapped = NULL;
size_t wrapped_len;
_auto_(l_free) uint8_t *unwrapped1 = NULL;
_auto_(l_free) uint8_t *unwrapped2 = NULL;
const void *r_nonce = NULL;
const void *i_nonce = NULL;
const void *r_auth = NULL;
_auto_(l_ecc_point_free) struct l_ecc_point *r_proto_key = NULL;
_auto_(l_ecc_scalar_free) struct l_ecc_scalar *n = NULL;
const void *ad0 = body + 2;
const void *ad1 = body + 8;
uint64_t r_auth_derived[L_ECC_MAX_DIGITS];
l_debug("Authenticate response");
if (dpp->state != DPP_STATE_AUTHENTICATING)
return;
if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
return;
if (!dpp->freqs)
return;
if (memcmp(from, dpp->auth_addr, 6))
return;
dpp_attr_iter_init(&iter, body + 8, body_len - 8);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_STATUS:
if (len != 1)
return;
status = l_get_u8(data);
break;
case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
r_boot_hash = data;
break;
case DPP_ATTR_RESPONDER_PROTOCOL_KEY:
r_proto = data;
r_proto_len = len;
break;
case DPP_ATTR_PROTOCOL_VERSION:
if (len != 1 || l_get_u8(data) != 2)
return;
break;
case DPP_ATTR_WRAPPED_DATA:
wrapped = data;
wrapped_len = len;
break;
default:
break;
}
}
if (status != DPP_STATUS_OK || !r_boot_hash || !r_proto ) {
l_debug("Auth response bad status or missing attributes");
return;
}
r_proto_key = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
r_proto, r_proto_len);
if (!r_proto_key) {
l_debug("Peers protocol key was invalid");
return;
}
n = dpp_derive_k2(r_proto_key, dpp->proto_private, dpp->k2);
unwrapped1 = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1, dpp->k2,
dpp->key_len, wrapped, wrapped_len,
&wrapped_len);
if (!unwrapped1) {
l_debug("Failed to unwrap primary data");
return;
}
wrapped = NULL;
dpp_attr_iter_init(&iter, unwrapped1, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_RESPONDER_NONCE:
if (len != dpp->nonce_len)
return;
r_nonce = data;
break;
case DPP_ATTR_INITIATOR_NONCE:
if (len != dpp->nonce_len)
return;
i_nonce = data;
break;
case DPP_ATTR_RESPONDER_CAPABILITIES:
break;
case DPP_ATTR_WRAPPED_DATA:
wrapped = data;
wrapped_len = len;
break;
default:
break;
}
}
if (!r_nonce || !i_nonce || !wrapped) {
l_debug("Wrapped data missing attributes");
return;
}
if (!dpp_derive_ke(i_nonce, r_nonce, dpp->m, n, dpp->ke)) {
l_debug("Failed to derive ke");
return;
}
unwrapped2 = dpp_unwrap_attr(NULL, 0, NULL, 0, dpp->ke, dpp->key_len,
wrapped, wrapped_len, &wrapped_len);
if (!unwrapped2) {
l_debug("Failed to unwrap secondary data");
return;
}
dpp_attr_iter_init(&iter, unwrapped2, wrapped_len);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_RESPONDER_AUTH_TAG:
if (len != dpp->key_len)
return;
r_auth = data;
break;
default:
break;
}
}
if (!r_auth) {
l_debug("R-Auth was not in secondary wrapped data");
return;
}
if (!dpp_derive_r_auth(i_nonce, r_nonce, dpp->nonce_len,
dpp->own_proto_public, r_proto_key,
dpp->peer_boot_public, r_auth_derived)) {
l_debug("Failed to derive r_auth");
return;
}
if (memcmp(r_auth, r_auth_derived, dpp->key_len)) {
l_debug("R-Auth did not verify");
return;
}
if (!dpp_derive_i_auth(r_nonce, i_nonce, dpp->nonce_len,
r_proto_key, dpp->own_proto_public,
dpp->peer_boot_public, dpp->auth_tag)) {
l_debug("Could not derive I-Auth");
return;
}
dpp->channel_switch = false;
dpp->current_freq = dpp->new_freq;
dpp_send_authenticate_confirm(dpp);
}
static void dpp_handle_presence_announcement(struct dpp_sm *dpp,
const uint8_t *from,
const uint8_t *body,
size_t body_len)
{
struct dpp_attr_iter iter;
enum dpp_attribute_type type;
size_t len;
const uint8_t *data;
const void *r_boot = NULL;
size_t r_boot_len = 0;
uint8_t hash[32];
l_debug("Presence announcement "MAC, MAC_STR(from));
/* Must be a configurator, in an initiator role, in PRESENCE state */
if (dpp->state != DPP_STATE_PRESENCE)
return;
if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
return;
if (!dpp->freqs)
return;
/*
* The URI may not have contained a MAC address, if this announcement
* verifies set auth_addr then.
*/
if (!l_memeqzero(dpp->auth_addr, 6) &&
memcmp(from, dpp->auth_addr, 6)) {
l_debug("Unexpected source "MAC" expected "MAC, MAC_STR(from),
MAC_STR(dpp->auth_addr));
return;
}
dpp_attr_iter_init(&iter, body + 8, body_len - 8);
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
switch (type) {
case DPP_ATTR_RESPONDER_BOOT_KEY_HASH:
r_boot = data;
r_boot_len = len;
break;
default:
break;
}
}
if (!r_boot || r_boot_len != 32) {
l_debug("No responder boot hash");
return;
}
/* Hash what we have for the peer and check its our enrollee */
dpp_hash(L_CHECKSUM_SHA256, hash, 2, "chirp", strlen("chirp"),
dpp->peer_asn1, dpp->peer_asn1_len);
if (memcmp(hash, r_boot, sizeof(hash))) {
l_debug("Peers boot hash did not match");
return;
}
/*
* This is the peer we expected, save away the address and derive the
* initial keys.
*/
memcpy(dpp->auth_addr, from, 6);
dpp->state = DPP_STATE_AUTHENTICATING;
if (!dpp_send_authenticate_request(dpp))
return;
/*
* Requested the peer to move to another channel for the remainder of
* the protocol. IWD's current logic prohibits a configurator from
* running while not connected, so we can assume here that the new
* frequency is the same of the connected BSS. Wait until an ACK is
* received for the auth request then cancel the offchannel request.
*/
if (dpp->current_freq != dpp->new_freq)
dpp->channel_switch = true;
}
static void dpp_handle_frame(struct dpp_sm *dpp,
const struct mmpdu_header *frame,
const void *body, size_t body_len)
{
const uint8_t *ptr;
/*
* Both handlers offset by 8 bytes to reach the beginning of the DPP
* attributes. Easier checking this in one place, which also covers the
* frame type byte.
*/
if (body_len < 8)
return;
ptr = body + 7;
switch (*ptr) {
case DPP_FRAME_AUTHENTICATION_REQUEST:
authenticate_request(dpp, frame->address_2, body, body_len);
break;
case DPP_FRAME_AUTHENTICATION_RESPONSE:
authenticate_response(dpp, frame->address_2, body, body_len);
break;
case DPP_FRAME_AUTHENTICATION_CONFIRM:
authenticate_confirm(dpp, frame->address_2, body, body_len);
break;
case DPP_FRAME_CONFIGURATION_RESULT:
dpp_handle_config_result_frame(dpp, frame->address_2,
body, body_len);
break;
case DPP_FRAME_PRESENCE_ANNOUNCEMENT:
dpp_handle_presence_announcement(dpp, frame->address_2,
body, body_len);
break;
default:
l_debug("Unhandled DPP frame %u", *ptr);
break;
}
}
static bool match_wdev(const void *a, const void *b)
{
const struct dpp_sm *dpp = a;
const uint64_t *wdev_id = b;
return *wdev_id == dpp->wdev_id;
}
static void dpp_frame_timeout(struct l_timeout *timeout, void *user_data)
{
struct dpp_sm *dpp = user_data;
l_timeout_remove(timeout);
dpp->retry_timeout = NULL;
/*
* ROC has not yet started (in between an ROC timeout and starting a
* new session), this will most likely result in the frame failing to
* send. Just bail out now and the roc_started callback will take care
* of sending this out.
*/
if (!dpp->roc_started)
return;
dpp_frame_retry(dpp);
}
static void dpp_mlme_notify(struct l_genl_msg *msg, void *user_data)
{
struct dpp_sm *dpp;
uint64_t wdev_id = 0;
uint64_t cookie = 0;
bool ack = false;
struct iovec iov;
uint8_t cmd = l_genl_msg_get_command(msg);
if (cmd != NL80211_CMD_FRAME_TX_STATUS)
return;
if (nl80211_parse_attrs(msg, NL80211_ATTR_WDEV, &wdev_id,
NL80211_ATTR_COOKIE, &cookie,
NL80211_ATTR_ACK, &ack,
NL80211_ATTR_FRAME, &iov,
NL80211_ATTR_UNSPEC) < 0)
return;
dpp = l_queue_find(dpp_list, match_wdev, &wdev_id);
if (!dpp)
return;
if (dpp->state <= DPP_STATE_PRESENCE)
return;
if (dpp->frame_cookie != cookie)
return;
/*
* Only want to handle the no-ACK case. Re-transmitting an ACKed
* frame likely wont do any good, at least in the case of DPP.
*/
if (!ack)
goto retransmit;
/*
* Special handling for a channel transition when acting as a
* configurator. The auth request was sent offchannel so we need to
* wait for the ACK before going back to the connected channel.
*/
if (dpp->channel_switch) {
if (dpp->offchannel_id) {
offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
dpp->offchannel_id = 0;
}
dpp->channel_switch = false;
}
return;
retransmit:
if (dpp->frame_retry > DPP_FRAME_MAX_RETRIES) {
dpp_reset(dpp);
return;
}
/* This should never happen */
if (L_WARN_ON(dpp->frame_pending))
return;
l_debug("No ACK from peer, re-transmitting in %us",
DPP_FRAME_RETRY_TIMEOUT);
dpp->frame_retry++;
dpp->frame_pending = l_memdup(iov.iov_base, iov.iov_len);
dpp->frame_size = iov.iov_len;
dpp->retry_timeout = l_timeout_create(DPP_FRAME_RETRY_TIMEOUT,
dpp_frame_timeout, dpp, NULL);
}
static void dpp_unicast_notify(struct l_genl_msg *msg, void *user_data)
{
struct dpp_sm *dpp;
const uint64_t *wdev_id = NULL;
struct l_genl_attr attr;
uint16_t type, len, frame_len;
const void *data;
const struct mmpdu_header *mpdu = NULL;
const uint8_t *body;
size_t body_len;
if (l_genl_msg_get_command(msg) != NL80211_CMD_FRAME)
return;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_WDEV:
if (len != 8)
break;
wdev_id = data;
break;
case NL80211_ATTR_FRAME:
mpdu = mpdu_validate(data, len);
if (!mpdu) {
l_warn("Frame didn't validate as MMPDU");
return;
}
frame_len = len;
break;
}
}
if (!wdev_id) {
l_warn("Bad wdev attribute");
return;
}
dpp = l_queue_find(dpp_list, match_wdev, wdev_id);
if (!dpp)
return;
if (!mpdu) {
l_warn("Missing frame data");
return;
}
body = mmpdu_body(mpdu);
body_len = (const uint8_t *) mpdu + frame_len - body;
if (body_len < sizeof(dpp_prefix) ||
memcmp(body, dpp_prefix, sizeof(dpp_prefix)) != 0)
return;
dpp_handle_frame(dpp, mpdu, body, body_len);
}
static void dpp_frame_watch_cb(struct l_genl_msg *msg, void *user_data)
{
if (l_genl_msg_get_error(msg) < 0)
l_error("Could not register frame watch type %04x: %i",
L_PTR_TO_UINT(user_data), l_genl_msg_get_error(msg));
}
/*
* Special case the frame watch which includes the presence frames since they
* require multicast support. This is only supported by ath9k, so adding
* general support to frame-xchg isn't desireable.
*/
static void dpp_frame_watch(struct dpp_sm *dpp, uint16_t frame_type,
const uint8_t *prefix, size_t prefix_len)
{
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_REGISTER_FRAME, 32 + prefix_len);
l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &dpp->wdev_id);
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_TYPE, 2, &frame_type);
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_MATCH,
prefix_len, prefix);
if (dpp->mcast_support)
l_genl_msg_append_attr(msg, NL80211_ATTR_RECEIVE_MULTICAST,
0, NULL);
l_genl_family_send(nl80211, msg, dpp_frame_watch_cb,
L_UINT_TO_PTR(frame_type), NULL);
}
static void dpp_create(struct netdev *netdev)
{
struct l_dbus *dbus = dbus_get_bus();
struct dpp_sm *dpp = l_new(struct dpp_sm, 1);
uint8_t dpp_conf_response_prefix[] = { 0x04, 0x0b };
uint8_t dpp_conf_request_prefix[] = { 0x04, 0x0a };
dpp->netdev = netdev;
dpp->state = DPP_STATE_NOTHING;
dpp->wdev_id = netdev_get_wdev_id(netdev);
dpp->curve = l_ecc_curve_from_ike_group(19);
dpp->key_len = l_ecc_curve_get_scalar_bytes(dpp->curve);
dpp->nonce_len = dpp_nonce_len_from_key_len(dpp->key_len);
dpp->mcast_support = wiphy_has_ext_feature(
wiphy_find_by_wdev(dpp->wdev_id),
NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS);
l_ecdh_generate_key_pair(dpp->curve, &dpp->boot_private,
&dpp->boot_public);
dpp->own_asn1 = dpp_point_to_asn1(dpp->boot_public, &dpp->own_asn1_len);
dpp_hash(L_CHECKSUM_SHA256, dpp->own_boot_hash, 1,
dpp->own_asn1, dpp->own_asn1_len);
l_dbus_object_add_interface(dbus, netdev_get_path(netdev),
IWD_DPP_INTERFACE, dpp);
dpp_frame_watch(dpp, 0x00d0, dpp_prefix, sizeof(dpp_prefix));
frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0,
dpp_conf_response_prefix,
sizeof(dpp_conf_response_prefix),
dpp_handle_config_response_frame, dpp, NULL);
frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0,
dpp_conf_request_prefix,
sizeof(dpp_conf_request_prefix),
dpp_handle_config_request_frame, dpp, NULL);
l_queue_push_tail(dpp_list, dpp);
}
static void dpp_netdev_watch(struct netdev *netdev,
enum netdev_watch_event event, void *userdata)
{
switch (event) {
case NETDEV_WATCH_EVENT_NEW:
case NETDEV_WATCH_EVENT_UP:
if (netdev_get_iftype(netdev) == NETDEV_IFTYPE_STATION &&
netdev_get_is_up(netdev))
dpp_create(netdev);
break;
case NETDEV_WATCH_EVENT_DEL:
case NETDEV_WATCH_EVENT_DOWN:
l_dbus_object_remove_interface(dbus_get_bus(),
netdev_get_path(netdev),
IWD_DPP_INTERFACE);
break;
default:
break;
}
}
/*
* EasyConnect 2.0 - 6.2.2
*/
static uint32_t *dpp_add_default_channels(struct dpp_sm *dpp, size_t *len_out)
{
struct wiphy *wiphy = wiphy_find_by_wdev(
netdev_get_wdev_id(dpp->netdev));
const struct scan_freq_set *list = wiphy_get_supported_freqs(wiphy);
uint32_t freq;
if (!dpp->presence_list)
dpp->presence_list = scan_freq_set_new();
scan_freq_set_add(dpp->presence_list, band_channel_to_freq(6,
BAND_FREQ_2_4_GHZ));
/*
* "5 GHz: Channel 44 (5.220 GHz) if local regulations permit operation
* only in the 5.150 - 5.250 GHz band and Channel 149 (5.745 GHz)
* otherwise"
*/
freq = band_channel_to_freq(149, BAND_FREQ_5_GHZ);
if (scan_freq_set_contains(list, freq))
scan_freq_set_add(dpp->presence_list, freq);
else
scan_freq_set_add(dpp->presence_list,
band_channel_to_freq(44, BAND_FREQ_5_GHZ));
/* TODO: 60GHz: Channel 2 */
return scan_freq_set_to_fixed_array(dpp->presence_list, len_out);
}
/*
* TODO: There is an entire procedure defined in the spec where you increase
* the ROC timeout with each unsuccessful iteration of channels, wait on channel
* for long periods of time etc. Due to offchannel issues in the kernel this
* procedure is not being fully implemented. In reality doing this would result
* in quite terrible DPP performance anyways.
*/
static void dpp_start_presence(struct dpp_sm *dpp, uint32_t *limit_freqs,
size_t limit_len)
{
uint32_t max_roc = wiphy_get_max_roc_duration(
wiphy_find_by_wdev(dpp->wdev_id));
if (2000 < max_roc)
max_roc = 2000;
if (limit_freqs) {
dpp->freqs = l_memdup(limit_freqs, sizeof(uint32_t) * limit_len);
dpp->freqs_len = limit_len;
} else
dpp->freqs = dpp_add_default_channels(dpp, &dpp->freqs_len);
dpp->dwell = max_roc;
dpp->freqs_idx = 0;
dpp->current_freq = dpp->freqs[0];
dpp_start_offchannel(dpp, dpp->current_freq);
}
static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct dpp_sm *dpp = user_data;
uint32_t freq = band_channel_to_freq(6, BAND_FREQ_2_4_GHZ);
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
if (dpp->state != DPP_STATE_NOTHING)
return dbus_error_busy(message);
/*
* Station isn't actually required for DPP itself, although this will
* prevent connecting to the network once configured.
*/
if (station && station_get_connected_network(station)) {
l_warn("cannot be enrollee while connected, please disconnect");
return dbus_error_busy(message);
} else if (!station)
l_debug("No station device, continuing anyways...");
dpp->uri = dpp_generate_uri(dpp->own_asn1, dpp->own_asn1_len, 2,
netdev_get_address(dpp->netdev), &freq,
1, NULL, NULL);
dpp->state = DPP_STATE_PRESENCE;
dpp->role = DPP_CAPABILITY_ENROLLEE;
l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
&dpp->own_proto_public);
l_debug("DPP Start Enrollee: %s", dpp->uri);
dpp->pending = l_dbus_message_ref(message);
/*
* Going off spec here. Select a single channel to send presence
* announcements on. This will be advertised in the URI. The full
* presence procedure can be implemented if it is ever needed.
*/
dpp_start_presence(dpp, &freq, 1);
scan_periodic_stop(dpp->wdev_id);
dpp_property_changed_notify(dpp);
return NULL;
}
/*
* Set up the configurator for an initiator role. The configurator
* will go offchannel to frequencies advertised by the enrollees URI or,
* if no channels are provided, use a default channel list.
*/
static bool dpp_configurator_start_presence(struct dpp_sm *dpp, const char *uri)
{
_auto_(l_free) uint32_t *freqs = NULL;
size_t freqs_len = 0;
struct dpp_uri_info *info;
info = dpp_parse_uri(uri);
if (!info)
return false;
/*
* Very few drivers actually support registration of multicast frames.
* This renders the presence procedure impossible on most drivers.
* But not all is lost. If the URI contains the MAC and channel
* info we an start going through channels sending auth requests which
* is basically DPP 1.0. Otherwise DPP cannot start.
*/
if (!dpp->mcast_support &&
(l_memeqzero(info->mac, 6) || !info->freqs)) {
l_error("No multicast registration support, URI must contain "
"MAC and channel information");
dpp_free_uri_info(info);
return false;
}
if (!l_memeqzero(info->mac, 6))
memcpy(dpp->auth_addr, info->mac, 6);
if (info->freqs)
freqs = scan_freq_set_to_fixed_array(info->freqs, &freqs_len);
dpp->peer_boot_public = l_ecc_point_clone(info->boot_public);
dpp->peer_asn1 = dpp_point_to_asn1(info->boot_public,
&dpp->peer_asn1_len);
dpp_free_uri_info(info);
if (!dpp->peer_asn1) {
l_debug("Peer boot key did not convert to asn1");
return false;
}
dpp_hash(L_CHECKSUM_SHA256, dpp->peer_boot_hash, 1, dpp->peer_asn1,
dpp->peer_asn1_len);
dpp_start_presence(dpp, freqs, freqs_len);
return true;
}
static struct l_dbus_message *dpp_start_configurator_common(
struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data,
bool responder)
{
struct dpp_sm *dpp = user_data;
struct l_dbus_message *reply;
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
struct scan_bss *bss;
struct network *network;
struct l_settings *settings;
struct handshake_state *hs = netdev_get_handshake(dpp->netdev);
const char *uri;
/*
* For now limit the configurator to only configuring enrollees to the
* currently connected network.
*/
if (!station)
return dbus_error_not_available(message);
bss = station_get_connected_bss(station);
network = station_get_connected_network(station);
if (!bss || !network)
return dbus_error_not_connected(message);
settings = network_get_settings(network);
if (!settings)
return dbus_error_not_configured(message);
if (network_get_security(network) != SECURITY_PSK)
return dbus_error_not_supported(message);
if (dpp->state != DPP_STATE_NOTHING)
return dbus_error_busy(message);
l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
&dpp->own_proto_public);
dpp->state = DPP_STATE_PRESENCE;
if (!responder) {
if (!l_dbus_message_get_arguments(message, "s", &uri))
return dbus_error_invalid_args(message);
if (!dpp_configurator_start_presence(dpp, uri))
return dbus_error_invalid_args(message);
/* Since we have the peer's URI generate the keys now */
l_getrandom(dpp->i_nonce, dpp->nonce_len);
dpp->m = dpp_derive_k1(dpp->peer_boot_public,
dpp->proto_private, dpp->k1);
if (!dpp->mcast_support)
dpp->state = DPP_STATE_AUTHENTICATING;
dpp->new_freq = bss->frequency;
} else
dpp->current_freq = bss->frequency;
dpp->uri = dpp_generate_uri(dpp->own_asn1, dpp->own_asn1_len, 2,
netdev_get_address(dpp->netdev),
&bss->frequency, 1, NULL, NULL);
dpp->role = DPP_CAPABILITY_CONFIGURATOR;
dpp->config = dpp_configuration_new(settings,
network_get_ssid(network),
hs->akm_suite);
scan_periodic_stop(dpp->wdev_id);
dpp_property_changed_notify(dpp);
l_debug("DPP Start Configurator: %s", dpp->uri);
reply = l_dbus_message_new_method_return(message);
l_dbus_message_set_arguments(reply, "s", dpp->uri);
return reply;
}
static struct l_dbus_message *dpp_dbus_start_configurator(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
return dpp_start_configurator_common(dbus, message, user_data, true);
}
static struct l_dbus_message *dpp_dbus_configure_enrollee(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
return dpp_start_configurator_common(dbus, message, user_data, false);
}
static struct l_dbus_message *dpp_dbus_stop(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct dpp_sm *dpp = user_data;
dpp_reset(dpp);
return l_dbus_message_new_method_return(message);
}
static void dpp_setup_interface(struct l_dbus_interface *interface)
{
l_dbus_interface_method(interface, "StartEnrollee", 0,
dpp_dbus_start_enrollee, "s", "", "uri");
l_dbus_interface_method(interface, "StartConfigurator", 0,
dpp_dbus_start_configurator, "s", "", "uri");
l_dbus_interface_method(interface, "ConfigureEnrollee", 0,
dpp_dbus_configure_enrollee, "", "s", "uri");
l_dbus_interface_method(interface, "Stop", 0,
dpp_dbus_stop, "", "");
l_dbus_interface_property(interface, "Started", 0, "b", dpp_get_started,
NULL);
l_dbus_interface_property(interface, "Role", 0, "s", dpp_get_role,
NULL);
l_dbus_interface_property(interface, "URI", 0, "s", dpp_get_uri, NULL);
}
static void dpp_destroy_interface(void *user_data)
{
struct dpp_sm *dpp = user_data;
l_queue_remove(dpp_list, dpp);
dpp_free(dpp);
}
static int dpp_init(void)
{
nl80211 = l_genl_family_new(iwd_get_genl(), NL80211_GENL_NAME);
if (!nl80211) {
l_error("Failed to obtain nl80211");
return -EIO;
}
netdev_watch = netdev_watch_add(dpp_netdev_watch, NULL, NULL);
l_dbus_register_interface(dbus_get_bus(), IWD_DPP_INTERFACE,
dpp_setup_interface,
dpp_destroy_interface, false);
mlme_watch = l_genl_family_register(nl80211, "mlme", dpp_mlme_notify,
NULL, NULL);
unicast_watch = l_genl_add_unicast_watch(iwd_get_genl(),
NL80211_GENL_NAME,
dpp_unicast_notify,
NULL, NULL);
dpp_list = l_queue_new();
return 0;
}
static void dpp_exit(void)
{
l_debug("");
l_dbus_unregister_interface(dbus_get_bus(), IWD_DPP_INTERFACE);
netdev_watch_remove(netdev_watch);
l_genl_remove_unicast_watch(iwd_get_genl(), unicast_watch);
l_genl_family_unregister(nl80211, mlme_watch);
mlme_watch = 0;
l_genl_family_free(nl80211);
nl80211 = NULL;
l_queue_destroy(dpp_list, (l_queue_destroy_func_t) dpp_free);
}
IWD_MODULE(dpp, dpp_init, dpp_exit);
IWD_MODULE_DEPENDS(dpp, netdev);