mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2025-04-01 16:16:51 +02:00

If the number of frequencies used is very small reduce the timeout to avoid waiting for extended periods of time.
4769 lines
118 KiB
C
4769 lines
118 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"
|
||
#include "src/knownnetworks.h"
|
||
|
||
#define DPP_FRAME_MAX_RETRIES 5
|
||
#define DPP_FRAME_RETRY_TIMEOUT 1
|
||
#define DPP_AUTH_PROTO_TIMEOUT 10
|
||
#define DPP_PKEX_PROTO_TIMEOUT 120
|
||
#define DPP_PKEX_PROTO_PER_FREQ_TIMEOUT 10
|
||
|
||
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_PKEX_EXCHANGE,
|
||
DPP_STATE_PKEX_COMMIT_REVEAL,
|
||
DPP_STATE_AUTHENTICATING,
|
||
DPP_STATE_CONFIGURING,
|
||
};
|
||
|
||
enum dpp_capability {
|
||
DPP_CAPABILITY_ENROLLEE = 0x01,
|
||
DPP_CAPABILITY_CONFIGURATOR = 0x02,
|
||
};
|
||
|
||
enum dpp_interface {
|
||
DPP_INTERFACE_UNBOUND,
|
||
DPP_INTERFACE_DPP,
|
||
DPP_INTERFACE_PKEX,
|
||
};
|
||
|
||
struct pkex_agent {
|
||
char *owner;
|
||
char *path;
|
||
unsigned int disconnect_watch;
|
||
uint32_t pending_id;
|
||
};
|
||
|
||
struct dpp_sm {
|
||
struct netdev *netdev;
|
||
char *uri;
|
||
uint8_t role;
|
||
int refcount;
|
||
uint32_t station_watch;
|
||
uint32_t known_network_watch;
|
||
|
||
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;
|
||
enum dpp_interface interface;
|
||
|
||
struct pkex_agent *agent;
|
||
|
||
/*
|
||
* 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 max_roc;
|
||
|
||
uint32_t offchannel_id;
|
||
|
||
uint8_t peer_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;
|
||
|
||
struct l_idle *connect_idle;
|
||
|
||
/* PKEX-specific values */
|
||
char *pkex_id;
|
||
char *pkex_key;
|
||
uint8_t pkex_version;
|
||
struct l_ecc_point *peer_encr_key;
|
||
struct l_ecc_point *pkex_m;
|
||
/* Ephemeral key Y' or X' for enrollee or configurator */
|
||
struct l_ecc_point *y_or_x;
|
||
/* Ephemeral key pair y/Y or x/X */
|
||
struct l_ecc_point *pkex_public;
|
||
struct l_ecc_scalar *pkex_private;
|
||
uint8_t z[L_ECC_SCALAR_MAX_BYTES];
|
||
size_t z_len;
|
||
uint8_t u[L_ECC_SCALAR_MAX_BYTES];
|
||
size_t u_len;
|
||
uint32_t pkex_scan_id;
|
||
|
||
bool mcast_support : 1;
|
||
bool roc_started : 1;
|
||
bool channel_switch : 1;
|
||
bool mutual_auth : 1;
|
||
bool autoconnect : 1;
|
||
};
|
||
|
||
static const char *dpp_role_to_string(enum dpp_capability role)
|
||
{
|
||
switch (role) {
|
||
case DPP_CAPABILITY_ENROLLEE:
|
||
return "enrollee";
|
||
case DPP_CAPABILITY_CONFIGURATOR:
|
||
return "configurator";
|
||
default:
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
static bool dpp_pkex_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 &&
|
||
dpp->interface == DPP_INTERFACE_PKEX);
|
||
|
||
l_dbus_message_builder_append_basic(builder, 'b', &started);
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool dpp_pkex_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 ||
|
||
dpp->interface != DPP_INTERFACE_PKEX)
|
||
return false;
|
||
|
||
role = dpp_role_to_string(dpp->role);
|
||
if (L_WARN_ON(!role))
|
||
return false;
|
||
|
||
l_dbus_message_builder_append_basic(builder, 's', role);
|
||
return true;
|
||
}
|
||
|
||
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 &&
|
||
dpp->interface == DPP_INTERFACE_DPP);
|
||
|
||
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 ||
|
||
dpp->interface != DPP_INTERFACE_DPP)
|
||
return false;
|
||
|
||
role = dpp_role_to_string(dpp->role);
|
||
if (L_WARN_ON(!role))
|
||
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 ||
|
||
dpp->interface != DPP_INTERFACE_DPP)
|
||
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);
|
||
|
||
switch (dpp->interface) {
|
||
case DPP_INTERFACE_DPP:
|
||
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");
|
||
break;
|
||
case DPP_INTERFACE_PKEX:
|
||
l_dbus_property_changed(dbus_get_bus(), path,
|
||
IWD_DPP_PKEX_INTERFACE,
|
||
"Started");
|
||
l_dbus_property_changed(dbus_get_bus(), path,
|
||
IWD_DPP_PKEX_INTERFACE,
|
||
"Role");
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
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_pending_pkex_data(struct dpp_sm *dpp)
|
||
{
|
||
if (dpp->pkex_id) {
|
||
l_free(dpp->pkex_id);
|
||
dpp->pkex_id = NULL;
|
||
}
|
||
|
||
if (dpp->pkex_key) {
|
||
l_free(dpp->pkex_key);
|
||
dpp->pkex_key = NULL;
|
||
}
|
||
|
||
memset(dpp->peer_addr, 0, sizeof(dpp->peer_addr));
|
||
|
||
if (dpp->peer_encr_key) {
|
||
l_ecc_point_free(dpp->peer_encr_key);
|
||
dpp->peer_encr_key = NULL;
|
||
}
|
||
}
|
||
|
||
static void pkex_agent_free(void *data)
|
||
{
|
||
struct pkex_agent *agent = data;
|
||
|
||
l_free(agent->owner);
|
||
l_free(agent->path);
|
||
l_dbus_remove_watch(dbus_get_bus(), agent->disconnect_watch);
|
||
l_free(agent);
|
||
}
|
||
|
||
static void dpp_agent_cancel(struct dpp_sm *dpp)
|
||
{
|
||
struct l_dbus_message *msg;
|
||
|
||
const char *reason = "shutdown";
|
||
|
||
msg = l_dbus_message_new_method_call(dbus_get_bus(),
|
||
dpp->agent->owner,
|
||
dpp->agent->path,
|
||
IWD_SHARED_CODE_AGENT_INTERFACE,
|
||
"Cancel");
|
||
l_dbus_message_set_arguments(msg, "s", reason);
|
||
l_dbus_message_set_no_reply(msg, true);
|
||
l_dbus_send(dbus_get_bus(), msg);
|
||
}
|
||
|
||
static void dpp_agent_release(struct dpp_sm *dpp)
|
||
{
|
||
struct l_dbus_message *msg;
|
||
|
||
msg = l_dbus_message_new_method_call(dbus_get_bus(),
|
||
dpp->agent->owner,
|
||
dpp->agent->path,
|
||
IWD_SHARED_CODE_AGENT_INTERFACE,
|
||
"Release");
|
||
l_dbus_message_set_arguments(msg, "");
|
||
l_dbus_message_set_no_reply(msg, true);
|
||
l_dbus_send(dbus_get_bus(), msg);
|
||
}
|
||
|
||
static void dpp_destroy_agent(struct dpp_sm *dpp)
|
||
{
|
||
if (!dpp->agent)
|
||
return;
|
||
|
||
if (dpp->agent->pending_id) {
|
||
dpp_agent_cancel(dpp);
|
||
l_dbus_cancel(dbus_get_bus(), dpp->agent->pending_id);
|
||
}
|
||
|
||
dpp_agent_release(dpp);
|
||
|
||
l_debug("Released SharedCodeAgent on path %s", dpp->agent->path);
|
||
|
||
pkex_agent_free(dpp->agent);
|
||
dpp->agent = NULL;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
if (dpp->pkex_m) {
|
||
l_ecc_point_free(dpp->pkex_m);
|
||
dpp->pkex_m = NULL;
|
||
}
|
||
|
||
if (dpp->y_or_x) {
|
||
l_ecc_point_free(dpp->y_or_x);
|
||
dpp->y_or_x = NULL;
|
||
}
|
||
|
||
if (dpp->pkex_public) {
|
||
l_ecc_point_free(dpp->pkex_public);
|
||
dpp->pkex_public = NULL;
|
||
}
|
||
|
||
if (dpp->pkex_private) {
|
||
l_ecc_scalar_free(dpp->pkex_private);
|
||
dpp->pkex_private = NULL;
|
||
}
|
||
|
||
}
|
||
|
||
static void dpp_reset(struct dpp_sm *dpp)
|
||
{
|
||
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
|
||
|
||
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;
|
||
}
|
||
|
||
if (dpp->pkex_scan_id) {
|
||
scan_cancel(dpp->wdev_id, dpp->pkex_scan_id);
|
||
dpp->pkex_scan_id = 0;
|
||
}
|
||
|
||
if (dpp->connect_idle) {
|
||
l_idle_remove(dpp->connect_idle);
|
||
dpp->connect_idle = NULL;
|
||
}
|
||
|
||
dpp->state = DPP_STATE_NOTHING;
|
||
dpp->new_freq = 0;
|
||
dpp->frame_retry = 0;
|
||
dpp->frame_cookie = 0;
|
||
dpp->pkex_version = 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);
|
||
explicit_bzero(dpp->z, dpp->key_len);
|
||
explicit_bzero(dpp->u, dpp->u_len);
|
||
|
||
dpp_destroy_agent(dpp);
|
||
|
||
dpp_free_pending_pkex_data(dpp);
|
||
|
||
dpp_free_auth_data(dpp);
|
||
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
dpp->interface = DPP_INTERFACE_UNBOUND;
|
||
|
||
if (station) {
|
||
if (dpp->station_watch)
|
||
station_remove_state_watch(station, dpp->station_watch);
|
||
|
||
/* Set the old autoconnect state back to what it was */
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE)
|
||
station_set_autoconnect(station, dpp->autoconnect);
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
known_networks_watch_remove(dpp->known_network_watch);
|
||
|
||
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, uint32_t time)
|
||
{
|
||
if (dpp->timeout)
|
||
l_timeout_modify(dpp->timeout, time);
|
||
else
|
||
dpp->timeout = l_timeout_create(time, 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)
|
||
{
|
||
_auto_(l_settings_free) struct l_settings *settings = l_settings_new();
|
||
_auto_(l_free) char *path;
|
||
|
||
path = storage_get_network_file_path(SECURITY_PSK, config->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);
|
||
else if (config->psk)
|
||
l_settings_set_string(settings, "Security", "PreSharedKey",
|
||
config->psk);
|
||
|
||
if (config->send_hostname)
|
||
l_settings_set_bool(settings, "IPv4", "SendHostname", true);
|
||
|
||
if (config->hidden)
|
||
l_settings_set_bool(settings, "Settings", "Hidden", true);
|
||
|
||
l_debug("Storing credential for '%s(%s)'", config->ssid,
|
||
security_to_str(SECURITY_PSK));
|
||
storage_network_sync(SECURITY_PSK, config->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 void dpp_start_connect(struct l_idle *idle, void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
|
||
struct scan_bss *bss;
|
||
struct network *network;
|
||
int ret;
|
||
|
||
network = station_network_find(station, dpp->config->ssid,
|
||
SECURITY_PSK);
|
||
|
||
dpp_reset(dpp);
|
||
|
||
if (!network) {
|
||
l_debug("Network was not found!");
|
||
return;
|
||
}
|
||
|
||
l_debug("connecting to %s from DPP", network_get_ssid(network));
|
||
|
||
bss = network_bss_select(network, true);
|
||
ret = network_autoconnect(network, bss);
|
||
if (ret < 0)
|
||
l_warn("failed to connect after DPP (%d) %s", ret,
|
||
strerror(-ret));
|
||
}
|
||
|
||
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)
|
||
goto reset;
|
||
|
||
if (!bss_list || l_queue_length(bss_list) == 0)
|
||
goto reset;
|
||
|
||
/*
|
||
* The station watch _should_ detect this and reset, which cancels the
|
||
* scan. But just in case...
|
||
*/
|
||
if (L_WARN_ON(station_get_connected_network(station)))
|
||
goto reset;
|
||
|
||
station_set_scan_results(station, bss_list, freqs, false);
|
||
|
||
dpp_start_connect(NULL, dpp);
|
||
|
||
return true;
|
||
|
||
reset:
|
||
return false;
|
||
}
|
||
|
||
static void dpp_scan_destroy(void *userdata)
|
||
{
|
||
struct dpp_sm *dpp = userdata;
|
||
|
||
dpp->connect_scan_id = 0;
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
static void dpp_known_network_watch(enum known_networks_event event,
|
||
const struct network_info *info,
|
||
void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
/*
|
||
* Check the following
|
||
* - DPP is enrolling
|
||
* - DPP finished (dpp->config is set)
|
||
* - This is for the network DPP just configured
|
||
* - DPP isn't already trying to connect (e.g. if the profile was
|
||
* immediately modified after DPP synced it).
|
||
* - DPP didn't start a scan for the network.
|
||
*/
|
||
if (dpp->role != DPP_CAPABILITY_ENROLLEE)
|
||
return;
|
||
if (!dpp->config)
|
||
return;
|
||
if (strcmp(info->ssid, dpp->config->ssid))
|
||
return;
|
||
if (dpp->connect_idle)
|
||
return;
|
||
if (dpp->connect_scan_id)
|
||
return;
|
||
|
||
switch (event) {
|
||
case KNOWN_NETWORKS_EVENT_ADDED:
|
||
case KNOWN_NETWORKS_EVENT_UPDATED:
|
||
/*
|
||
* network.c takes care of updating the settings for the
|
||
* network. This callback just tells us to begin the connection.
|
||
* We do have use an idle here because there is no strict
|
||
* guarantee of ordering between known network events, e.g. DPP
|
||
* could have been called into prior to network and the network
|
||
* object isn't updated yet.
|
||
*/
|
||
dpp->connect_idle = l_idle_create(dpp_start_connect, dpp, NULL);
|
||
break;
|
||
case KNOWN_NETWORKS_EVENT_REMOVED:
|
||
l_warn("profile was removed before DPP could connect");
|
||
break;
|
||
}
|
||
}
|
||
|
||
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;
|
||
|
||
if (dpp->state != DPP_STATE_CONFIGURING)
|
||
return;
|
||
|
||
/*
|
||
* Can a configuration request come from someone other than who you
|
||
* authenticated to?
|
||
*/
|
||
if (memcmp(dpp->peer_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) {
|
||
network = station_network_find(station, config->ssid,
|
||
SECURITY_PSK);
|
||
if (network)
|
||
bss = network_bss_select(network, true);
|
||
}
|
||
|
||
dpp_write_config(config, network);
|
||
|
||
send_config_result(dpp, dpp->peer_addr);
|
||
|
||
offchannel_cancel(dpp->wdev_id, dpp->offchannel_id);
|
||
|
||
if (network && bss) {
|
||
l_debug("delaying connect until settings are synced");
|
||
dpp->config = config;
|
||
return;
|
||
} else if (station) {
|
||
struct scan_parameters params = {0};
|
||
|
||
params.ssid = (void *) config->ssid;
|
||
params.ssid_len = config->ssid_len;
|
||
|
||
l_debug("Scanning for %s", config->ssid);
|
||
|
||
dpp->connect_scan_id = scan_active_full(dpp->wdev_id, ¶ms,
|
||
dpp_scan_triggered,
|
||
dpp_scan_results, dpp,
|
||
dpp_scan_destroy);
|
||
if (dpp->connect_scan_id) {
|
||
dpp->config = config;
|
||
return;
|
||
}
|
||
}
|
||
|
||
dpp_configuration_free(config);
|
||
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->peer_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 bool dpp_check_config_header(const uint8_t *ptr)
|
||
{
|
||
/*
|
||
* Table 58. General Format of DPP Configuration Request frame
|
||
*
|
||
* Unfortunately wpa_supplicant hard codes 0x7f as the Query Response
|
||
* Info so we need to handle both cases.
|
||
*/
|
||
return ptr[0] == IE_TYPE_ADVERTISEMENT_PROTOCOL &&
|
||
ptr[1] == 0x08 &&
|
||
(ptr[2] == 0x7f || ptr[2] == 0x00) &&
|
||
ptr[3] == IE_TYPE_VENDOR_SPECIFIC &&
|
||
ptr[4] == 5;
|
||
}
|
||
|
||
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;
|
||
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 (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
|
||
return;
|
||
|
||
if (memcmp(dpp->peer_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 (!dpp_check_config_header(ptr))
|
||
return;
|
||
|
||
ptr += 5;
|
||
|
||
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[512];
|
||
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->peer_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);
|
||
if (dpp->mutual_auth)
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_BOOT_KEY_HASH,
|
||
dpp->peer_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;
|
||
struct l_ecc_point *bi = NULL;
|
||
|
||
if (dpp->state != DPP_STATE_AUTHENTICATING)
|
||
return;
|
||
|
||
if (memcmp(from, dpp->peer_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;
|
||
}
|
||
|
||
if (dpp->mutual_auth)
|
||
bi = dpp->peer_boot_public;
|
||
|
||
dpp_derive_i_auth(dpp->r_nonce, dpp->i_nonce, dpp->nonce_len,
|
||
dpp->own_proto_public, dpp->peer_proto_public,
|
||
dpp->boot_public, bi, i_auth_check);
|
||
|
||
if (memcmp(i_auth, i_auth_check, i_auth_len)) {
|
||
l_error("I-Auth did not verify");
|
||
goto auth_confirm_failed;
|
||
}
|
||
|
||
l_debug("Authentication successful");
|
||
|
||
dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT);
|
||
|
||
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->peer_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 (dpp->role == DPP_CAPABILITY_CONFIGURATOR && !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->peer_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->role == DPP_CAPABILITY_CONFIGURATOR &&
|
||
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_send_pkex_exchange_request(struct dpp_sm *dpp)
|
||
{
|
||
uint8_t hdr[32];
|
||
uint8_t attrs[256];
|
||
uint8_t *ptr = attrs;
|
||
uint64_t m_data[L_ECC_MAX_DIGITS * 2];
|
||
uint16_t group;
|
||
struct iovec iov[2];
|
||
const uint8_t *own_mac = netdev_get_address(dpp->netdev);
|
||
|
||
l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group);
|
||
|
||
iov[0].iov_len = dpp_build_header(own_mac, broadcast,
|
||
DPP_FRAME_PKEX_VERSION1_XCHG_REQUEST, hdr);
|
||
iov[0].iov_base = hdr;
|
||
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION,
|
||
&dpp->pkex_version, 1);
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_FINITE_CYCLIC_GROUP,
|
||
&group, 2);
|
||
|
||
if (dpp->pkex_id)
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER,
|
||
dpp->pkex_id, strlen(dpp->pkex_id));
|
||
|
||
l_ecc_point_get_data(dpp->pkex_m, m_data, sizeof(m_data));
|
||
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_ENCRYPTED_KEY,
|
||
m_data, dpp->key_len * 2);
|
||
|
||
iov[1].iov_base = attrs;
|
||
iov[1].iov_len = ptr - attrs;
|
||
|
||
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
|
||
}
|
||
|
||
static void dpp_send_commit_reveal_request(struct dpp_sm *dpp)
|
||
{
|
||
struct iovec iov[2];
|
||
uint8_t hdr[41];
|
||
uint8_t attrs[512];
|
||
uint8_t *ptr = attrs;
|
||
uint8_t zero = 0;
|
||
uint8_t a_pub[L_ECC_POINT_MAX_BYTES];
|
||
ssize_t a_len;
|
||
|
||
a_len = l_ecc_point_get_data(dpp->boot_public, a_pub, sizeof(a_pub));
|
||
|
||
iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
|
||
dpp->peer_addr,
|
||
DPP_FRAME_PKEX_COMMIT_REVEAL_REQUEST,
|
||
hdr);
|
||
iov[0].iov_base = hdr;
|
||
|
||
ptr += dpp_append_wrapped_data(hdr + 26, 6, &zero, 1, ptr,
|
||
sizeof(attrs), dpp->z, dpp->z_len, 2,
|
||
DPP_ATTR_BOOTSTRAPPING_KEY, a_len, a_pub,
|
||
DPP_ATTR_INITIATOR_AUTH_TAG, dpp->u_len, dpp->u);
|
||
|
||
iov[1].iov_base = attrs;
|
||
iov[1].iov_len = ptr - attrs;
|
||
|
||
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
|
||
}
|
||
|
||
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;
|
||
case DPP_STATE_PKEX_EXCHANGE:
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE)
|
||
dpp_send_pkex_exchange_request(dpp);
|
||
|
||
break;
|
||
case DPP_STATE_PKEX_COMMIT_REVEAL:
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE)
|
||
dpp_send_commit_reveal_request(dpp);
|
||
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void dpp_start_offchannel(struct dpp_sm *dpp, uint32_t freq);
|
||
|
||
static void dpp_offchannel_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_PKEX_EXCHANGE:
|
||
if (dpp->role != DPP_CAPABILITY_CONFIGURATOR || !dpp->agent)
|
||
break;
|
||
|
||
/*
|
||
* We have a pending agent request but it did not arrive in
|
||
* time, we cant assume the enrollee will be waiting around
|
||
* for our response so cancel the request and continue waiting
|
||
* for another request
|
||
*/
|
||
if (dpp->agent->pending_id) {
|
||
dpp_free_pending_pkex_data(dpp);
|
||
dpp_agent_cancel(dpp);
|
||
}
|
||
/* Fall through */
|
||
case DPP_STATE_PRESENCE:
|
||
break;
|
||
case DPP_STATE_NOTHING:
|
||
/* Protocol already terminated */
|
||
return;
|
||
case DPP_STATE_AUTHENTICATING:
|
||
case DPP_STATE_CONFIGURING:
|
||
case DPP_STATE_PKEX_COMMIT_REVEAL:
|
||
goto next_roc;
|
||
}
|
||
|
||
dpp->freqs_idx++;
|
||
|
||
if (dpp->freqs_idx >= dpp->freqs_len) {
|
||
l_debug("Max retries offchannel");
|
||
dpp->freqs_idx = 0;
|
||
}
|
||
|
||
dpp->current_freq = dpp->freqs[dpp->freqs_idx];
|
||
|
||
l_debug("Offchannel 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_offchannel_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_offchannel_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;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
|
||
struct l_ecc_point *bi = NULL;
|
||
uint64_t k1[L_ECC_MAX_DIGITS];
|
||
const void *ad0 = body + 2;
|
||
const void *ad1 = body + 8;
|
||
uint32_t freq;
|
||
|
||
if (util_is_broadcast_address(from))
|
||
return;
|
||
|
||
if (dpp->state != DPP_STATE_PRESENCE &&
|
||
dpp->state != DPP_STATE_AUTHENTICATING)
|
||
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);
|
||
|
||
if (dpp->mutual_auth) {
|
||
l = dpp_derive_lr(dpp->boot_private, dpp->proto_private,
|
||
dpp->peer_boot_public);
|
||
bi = dpp->peer_boot_public;
|
||
}
|
||
|
||
/* Derive keys k2, ke, and R-Auth for authentication response */
|
||
|
||
n = dpp_derive_k2(dpp->peer_proto_public, dpp->proto_private, dpp->k2);
|
||
if (!n)
|
||
goto auth_request_failed;
|
||
|
||
l_getrandom(dpp->r_nonce, dpp->nonce_len);
|
||
|
||
if (!dpp_derive_ke(dpp->i_nonce, dpp->r_nonce, m, n, l, dpp->ke))
|
||
goto auth_request_failed;
|
||
|
||
if (!dpp_derive_r_auth(dpp->i_nonce, dpp->r_nonce, dpp->nonce_len,
|
||
dpp->peer_proto_public, dpp->own_proto_public,
|
||
bi, dpp->boot_public, dpp->auth_tag))
|
||
goto auth_request_failed;
|
||
|
||
memcpy(dpp->peer_addr, from, 6);
|
||
|
||
dpp->state = DPP_STATE_AUTHENTICATING;
|
||
dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT);
|
||
|
||
/* 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->peer_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);
|
||
if (dpp->mutual_auth)
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_INITIATOR_BOOT_KEY_HASH,
|
||
dpp->own_boot_hash, 32);
|
||
|
||
ptr += dpp_append_wrapped_data(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;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
|
||
struct l_ecc_point *bi = 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->freqs)
|
||
return;
|
||
|
||
if (memcmp(from, dpp->peer_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 || !wrapped) {
|
||
l_debug("Auth response bad status or missing attributes");
|
||
return;
|
||
}
|
||
|
||
r_proto_key = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
|
||
r_proto, r_proto_len);
|
||
if (!r_proto_key) {
|
||
l_debug("Peers protocol key was invalid");
|
||
return;
|
||
}
|
||
|
||
n = dpp_derive_k2(r_proto_key, dpp->proto_private, dpp->k2);
|
||
|
||
unwrapped1 = dpp_unwrap_attr(ad0, 6, ad1, wrapped - 4 - ad1, dpp->k2,
|
||
dpp->key_len, wrapped, wrapped_len,
|
||
&wrapped_len);
|
||
if (!unwrapped1) {
|
||
l_debug("Failed to unwrap primary data");
|
||
return;
|
||
}
|
||
|
||
wrapped = NULL;
|
||
|
||
dpp_attr_iter_init(&iter, unwrapped1, wrapped_len);
|
||
|
||
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
|
||
switch (type) {
|
||
case DPP_ATTR_RESPONDER_NONCE:
|
||
if (len != dpp->nonce_len)
|
||
return;
|
||
|
||
r_nonce = data;
|
||
break;
|
||
case DPP_ATTR_INITIATOR_NONCE:
|
||
if (len != dpp->nonce_len)
|
||
return;
|
||
|
||
i_nonce = data;
|
||
break;
|
||
case DPP_ATTR_RESPONDER_CAPABILITIES:
|
||
break;
|
||
case DPP_ATTR_WRAPPED_DATA:
|
||
wrapped = data;
|
||
wrapped_len = len;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!r_nonce || !i_nonce || !wrapped) {
|
||
l_debug("Wrapped data missing attributes");
|
||
return;
|
||
}
|
||
|
||
if (dpp->mutual_auth) {
|
||
l = dpp_derive_li(dpp->peer_boot_public, r_proto_key,
|
||
dpp->boot_private);
|
||
bi = dpp->boot_public;
|
||
}
|
||
|
||
if (!dpp_derive_ke(i_nonce, r_nonce, dpp->m, n, l, dpp->ke)) {
|
||
l_debug("Failed to derive ke");
|
||
return;
|
||
}
|
||
|
||
unwrapped2 = dpp_unwrap_attr(NULL, 0, NULL, 0, dpp->ke, dpp->key_len,
|
||
wrapped, wrapped_len, &wrapped_len);
|
||
if (!unwrapped2) {
|
||
l_debug("Failed to unwrap secondary data");
|
||
return;
|
||
}
|
||
|
||
dpp_attr_iter_init(&iter, unwrapped2, wrapped_len);
|
||
|
||
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
|
||
switch (type) {
|
||
case DPP_ATTR_RESPONDER_AUTH_TAG:
|
||
if (len != dpp->key_len)
|
||
return;
|
||
|
||
r_auth = data;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!r_auth) {
|
||
l_debug("R-Auth was not in secondary wrapped data");
|
||
return;
|
||
}
|
||
|
||
if (!dpp_derive_r_auth(i_nonce, r_nonce, dpp->nonce_len,
|
||
dpp->own_proto_public, r_proto_key, bi,
|
||
dpp->peer_boot_public, r_auth_derived)) {
|
||
l_debug("Failed to derive r_auth");
|
||
return;
|
||
}
|
||
|
||
if (memcmp(r_auth, r_auth_derived, dpp->key_len)) {
|
||
l_debug("R-Auth did not verify");
|
||
return;
|
||
}
|
||
|
||
if (!dpp_derive_i_auth(r_nonce, i_nonce, dpp->nonce_len,
|
||
r_proto_key, dpp->own_proto_public,
|
||
dpp->peer_boot_public, bi, dpp->auth_tag)) {
|
||
l_debug("Could not derive I-Auth");
|
||
return;
|
||
}
|
||
|
||
dpp->channel_switch = false;
|
||
dpp->current_freq = dpp->new_freq;
|
||
|
||
dpp_send_authenticate_confirm(dpp);
|
||
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE)
|
||
dpp_configuration_start(dpp, from);
|
||
|
||
}
|
||
|
||
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 peer_addr then.
|
||
*/
|
||
if (!l_memeqzero(dpp->peer_addr, 6) &&
|
||
memcmp(from, dpp->peer_addr, 6)) {
|
||
l_debug("Unexpected source "MAC" expected "MAC, MAC_STR(from),
|
||
MAC_STR(dpp->peer_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->peer_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_pkex_bad_group(struct dpp_sm *dpp, uint16_t group)
|
||
{
|
||
uint16_t own_group = l_ecc_curve_get_ike_group(dpp->curve);
|
||
|
||
/*
|
||
* TODO: The spec allows group negotiation, but it is not yet
|
||
* implemented.
|
||
*/
|
||
if (!group)
|
||
return;
|
||
/*
|
||
* Section 5.6.2
|
||
* "If the Responder's offered group offers less security
|
||
* than the Initiator's offered group, then the Initiator should
|
||
* ignore this message"
|
||
*/
|
||
if (group < own_group) {
|
||
l_debug("Offered group %u is less secure, ignoring",
|
||
group);
|
||
return;
|
||
}
|
||
/*
|
||
* Section 5.6.2
|
||
* "If the Responder's offered group offers equivalent or better
|
||
* security than the Initiator's offered group, then the
|
||
* Initiator may choose to abort its original request and try
|
||
* another exchange using the group offered by the Responder"
|
||
*/
|
||
if (group >= own_group) {
|
||
l_debug("Offered group %u is the same or more secure, "
|
||
" but group negotiation is not supported", group);
|
||
return;
|
||
}
|
||
}
|
||
|
||
static void dpp_pkex_bad_code(struct dpp_sm *dpp)
|
||
{
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL;
|
||
|
||
qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id,
|
||
netdev_get_address(dpp->netdev));
|
||
if (!qr || l_ecc_point_is_infinity(qr)) {
|
||
l_debug("Qr computed to zero, new code should be provisioned");
|
||
return;
|
||
}
|
||
|
||
l_debug("Qr computed successfully but responder indicated otherwise");
|
||
}
|
||
|
||
static void dpp_handle_pkex_exchange_response(struct dpp_sm *dpp,
|
||
const uint8_t *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 *status = NULL;
|
||
uint8_t version = 0;
|
||
const void *identifier = NULL;
|
||
size_t identifier_len = 0;
|
||
const void *key = NULL;
|
||
size_t key_len = 0;
|
||
uint16_t group = 0;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *n = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *j = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *k = NULL;
|
||
const uint8_t *own_addr = netdev_get_address(dpp->netdev);
|
||
|
||
l_debug("PKEX response "MAC, MAC_STR(from));
|
||
|
||
if (dpp->state != DPP_STATE_PKEX_EXCHANGE)
|
||
return;
|
||
|
||
if (dpp->role != DPP_CAPABILITY_ENROLLEE)
|
||
return;
|
||
|
||
memcpy(dpp->peer_addr, from, 6);
|
||
|
||
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 = data;
|
||
break;
|
||
case DPP_ATTR_PROTOCOL_VERSION:
|
||
if (len != 1)
|
||
return;
|
||
|
||
version = l_get_u8(data);
|
||
break;
|
||
case DPP_ATTR_CODE_IDENTIFIER:
|
||
identifier = data;
|
||
identifier_len = len;
|
||
break;
|
||
case DPP_ATTR_ENCRYPTED_KEY:
|
||
if (len != dpp->key_len * 2)
|
||
return;
|
||
|
||
key = data;
|
||
key_len = len;
|
||
break;
|
||
case DPP_ATTR_FINITE_CYCLIC_GROUP:
|
||
if (len != 2)
|
||
return;
|
||
|
||
group = l_get_le16(data);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!status) {
|
||
l_debug("No status attribute, ignoring");
|
||
return;
|
||
}
|
||
|
||
if (!key) {
|
||
l_debug("No encrypted key, ignoring");
|
||
return;
|
||
}
|
||
|
||
if (*status != DPP_STATUS_OK)
|
||
goto handle_status;
|
||
|
||
if (dpp->pkex_id) {
|
||
if (!identifier || identifier_len != strlen(dpp->pkex_id) ||
|
||
memcmp(dpp->pkex_id, identifier,
|
||
identifier_len)) {
|
||
l_debug("mismatch identifier, ignoring");
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (version && version != dpp->pkex_version) {
|
||
l_debug("PKEX version does not match, igoring");
|
||
return;
|
||
}
|
||
|
||
n = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
|
||
key, key_len);
|
||
if (!n) {
|
||
l_debug("failed to parse peers encrypted key");
|
||
goto failed;
|
||
}
|
||
|
||
qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id,
|
||
dpp->peer_addr);
|
||
if (!qr)
|
||
goto failed;
|
||
|
||
dpp->y_or_x = l_ecc_point_new(dpp->curve);
|
||
|
||
/* Y' = N - Qr */
|
||
l_ecc_point_inverse(qr);
|
||
l_ecc_point_add(dpp->y_or_x, n, qr);
|
||
|
||
/*
|
||
* "The resulting ephemeral key, denoted Y’, is then checked whether it
|
||
* is the point-at-infinity. If it is not valid, the protocol ends
|
||
* unsuccessfully"
|
||
*/
|
||
if (l_ecc_point_is_infinity(dpp->y_or_x)) {
|
||
l_debug("Y' computed to infinity, failing");
|
||
goto failed;
|
||
}
|
||
|
||
k = l_ecc_point_new(dpp->curve);
|
||
|
||
/* K = Y' * x */
|
||
l_ecc_point_multiply(k, dpp->pkex_private, dpp->y_or_x);
|
||
|
||
dpp_derive_z(own_addr, dpp->peer_addr, n, dpp->pkex_m, k,
|
||
dpp->pkex_key, dpp->pkex_id,
|
||
dpp->z, &dpp->z_len);
|
||
|
||
/* J = a * Y' */
|
||
j = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_multiply(j, dpp->boot_private, dpp->y_or_x);
|
||
|
||
if (!dpp_derive_u(j, own_addr, dpp->boot_public, dpp->y_or_x,
|
||
dpp->pkex_public, dpp->u, &dpp->u_len)) {
|
||
l_debug("failed to compute u");
|
||
goto failed;
|
||
}
|
||
|
||
/*
|
||
* Now that a response was successfully received, start another
|
||
* offchannel with more time for the remainder of the protocol. After
|
||
* PKEX, authentication will begin which handles the protocol timeout.
|
||
* If the remainder of PKEX (commit-reveal exchange) cannot complete in
|
||
* this time it will fail.
|
||
*/
|
||
dpp->dwell = (dpp->max_roc < 2000) ? dpp->max_roc : 2000;
|
||
dpp->state = DPP_STATE_PKEX_COMMIT_REVEAL;
|
||
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
dpp_start_offchannel(dpp, dpp->current_freq);
|
||
|
||
return;
|
||
|
||
handle_status:
|
||
switch (*status) {
|
||
case DPP_STATUS_BAD_GROUP:
|
||
dpp_pkex_bad_group(dpp, group);
|
||
break;
|
||
case DPP_STATUS_BAD_CODE:
|
||
dpp_pkex_bad_code(dpp);
|
||
break;
|
||
default:
|
||
l_debug("Unhandled status %u", *status);
|
||
break;
|
||
}
|
||
|
||
failed:
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
static bool dpp_pkex_start_authentication(struct dpp_sm *dpp)
|
||
{
|
||
dpp->uri = dpp_generate_uri(dpp->own_asn1, dpp->own_asn1_len, 2,
|
||
netdev_get_address(dpp->netdev),
|
||
&dpp->current_freq, 1, NULL, NULL);
|
||
|
||
l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
|
||
&dpp->own_proto_public);
|
||
|
||
l_getrandom(dpp->i_nonce, dpp->nonce_len);
|
||
|
||
dpp->peer_asn1 = dpp_point_to_asn1(dpp->peer_boot_public,
|
||
&dpp->peer_asn1_len);
|
||
|
||
dpp->m = dpp_derive_k1(dpp->peer_boot_public, dpp->proto_private,
|
||
dpp->k1);
|
||
|
||
dpp_hash(L_CHECKSUM_SHA256, dpp->peer_boot_hash, 1, dpp->peer_asn1,
|
||
dpp->peer_asn1_len);
|
||
|
||
dpp->state = DPP_STATE_AUTHENTICATING;
|
||
dpp->mutual_auth = true;
|
||
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE) {
|
||
dpp->new_freq = dpp->current_freq;
|
||
|
||
return dpp_send_authenticate_request(dpp);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static void dpp_handle_pkex_commit_reveal_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;
|
||
const uint8_t *wrapped = NULL;
|
||
size_t wrapped_len = 0;
|
||
uint8_t one = 1;
|
||
_auto_(l_free) uint8_t *unwrapped = NULL;
|
||
size_t unwrapped_len = 0;
|
||
const uint8_t *boot_key = NULL;
|
||
size_t boot_key_len = 0;
|
||
const uint8_t *r_auth = NULL;
|
||
uint8_t v[L_ECC_SCALAR_MAX_BYTES];
|
||
size_t v_len;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
|
||
|
||
l_debug("PKEX commit reveal "MAC, MAC_STR(from));
|
||
|
||
if (dpp->state != DPP_STATE_PKEX_COMMIT_REVEAL)
|
||
return;
|
||
|
||
if (dpp->role != DPP_CAPABILITY_ENROLLEE)
|
||
return;
|
||
|
||
/*
|
||
* The URI may not have contained a MAC address, if this announcement
|
||
* verifies set peer_addr then.
|
||
*/
|
||
if (memcmp(from, dpp->peer_addr, 6)) {
|
||
l_debug("Unexpected source "MAC" expected "MAC, MAC_STR(from),
|
||
MAC_STR(dpp->peer_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_WRAPPED_DATA:
|
||
wrapped = data;
|
||
wrapped_len = len;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!wrapped) {
|
||
l_debug("No wrapped data");
|
||
return;
|
||
}
|
||
|
||
unwrapped = dpp_unwrap_attr(body + 2, 6, &one, 1, dpp->z, dpp->z_len,
|
||
wrapped, wrapped_len, &unwrapped_len);
|
||
if (!unwrapped) {
|
||
l_debug("Failed to unwrap Reveal-Commit message");
|
||
return;
|
||
}
|
||
|
||
dpp_attr_iter_init(&iter, unwrapped, unwrapped_len);
|
||
|
||
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
|
||
switch (type) {
|
||
case DPP_ATTR_BOOTSTRAPPING_KEY:
|
||
if (len != dpp->key_len * 2)
|
||
return;
|
||
|
||
boot_key = data;
|
||
boot_key_len = len;
|
||
break;
|
||
case DPP_ATTR_RESPONDER_AUTH_TAG:
|
||
if (len != 32)
|
||
return;
|
||
|
||
r_auth = data;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
dpp->peer_boot_public = l_ecc_point_from_data(dpp->curve,
|
||
L_ECC_POINT_TYPE_FULL,
|
||
boot_key, boot_key_len);
|
||
if (!dpp->peer_boot_public) {
|
||
l_debug("Peer public bootstrapping key was invalid");
|
||
goto failed;
|
||
}
|
||
|
||
/* L = b * X' */
|
||
l = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_multiply(l, dpp->pkex_private, dpp->peer_boot_public);
|
||
|
||
if (!dpp_derive_v(l, dpp->peer_addr, dpp->peer_boot_public,
|
||
dpp->pkex_public, dpp->y_or_x, v, &v_len)) {
|
||
l_debug("Failed to derive v");
|
||
goto failed;
|
||
}
|
||
|
||
if (memcmp(v, r_auth, v_len)) {
|
||
l_debug("Bootstrapping data did not verify");
|
||
goto failed;
|
||
}
|
||
|
||
if (dpp_pkex_start_authentication(dpp))
|
||
return;
|
||
|
||
failed:
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
static void dpp_send_bad_group(struct dpp_sm *dpp, const uint8_t *addr)
|
||
{
|
||
uint8_t hdr[32];
|
||
uint8_t attrs[256];
|
||
uint8_t *ptr = attrs;
|
||
uint16_t group;
|
||
uint8_t status = DPP_STATUS_BAD_GROUP;
|
||
struct iovec iov[2];
|
||
const uint8_t *own_mac = netdev_get_address(dpp->netdev);
|
||
|
||
l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group);
|
||
|
||
iov[0].iov_len = dpp_build_header(own_mac, addr,
|
||
DPP_FRAME_PKEX_XCHG_RESPONSE, hdr);
|
||
iov[0].iov_base = hdr;
|
||
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION,
|
||
&dpp->pkex_version, 1);
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_FINITE_CYCLIC_GROUP, &group, 2);
|
||
|
||
iov[1].iov_base = attrs;
|
||
iov[1].iov_len = ptr - attrs;
|
||
|
||
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
|
||
}
|
||
|
||
static void dpp_send_bad_code(struct dpp_sm *dpp, const uint8_t *addr)
|
||
{
|
||
uint8_t hdr[32];
|
||
uint8_t attrs[256];
|
||
uint8_t *ptr = attrs;
|
||
uint8_t status = DPP_STATUS_BAD_CODE;
|
||
struct iovec iov[2];
|
||
const uint8_t *own_mac = netdev_get_address(dpp->netdev);
|
||
|
||
iov[0].iov_len = dpp_build_header(own_mac, addr,
|
||
DPP_FRAME_PKEX_XCHG_RESPONSE, hdr);
|
||
iov[0].iov_base = hdr;
|
||
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION,
|
||
&dpp->pkex_version, 1);
|
||
if (dpp->pkex_id)
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER,
|
||
dpp->pkex_id, strlen(dpp->pkex_id));
|
||
|
||
iov[1].iov_base = attrs;
|
||
iov[1].iov_len = ptr - attrs;
|
||
|
||
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
|
||
}
|
||
|
||
static void dpp_send_pkex_exchange_response(struct dpp_sm *dpp,
|
||
struct l_ecc_point *n)
|
||
{
|
||
uint8_t hdr[32];
|
||
uint8_t attrs[256];
|
||
uint8_t *ptr = attrs;
|
||
uint64_t n_data[L_ECC_MAX_DIGITS * 2];
|
||
uint16_t group;
|
||
uint8_t status = DPP_STATUS_OK;
|
||
struct iovec iov[2];
|
||
const uint8_t *own_mac = netdev_get_address(dpp->netdev);
|
||
|
||
l_put_le16(l_ecc_curve_get_ike_group(dpp->curve), &group);
|
||
|
||
iov[0].iov_len = dpp_build_header(own_mac, dpp->peer_addr,
|
||
DPP_FRAME_PKEX_XCHG_RESPONSE, hdr);
|
||
iov[0].iov_base = hdr;
|
||
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
|
||
|
||
if (dpp->pkex_id)
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_CODE_IDENTIFIER,
|
||
dpp->pkex_id, strlen(dpp->pkex_id));
|
||
|
||
l_ecc_point_get_data(n, n_data, sizeof(n_data));
|
||
|
||
ptr += dpp_append_attr(ptr, DPP_ATTR_ENCRYPTED_KEY,
|
||
n_data, dpp->key_len * 2);
|
||
|
||
iov[1].iov_base = attrs;
|
||
iov[1].iov_len = ptr - attrs;
|
||
|
||
dpp->state = DPP_STATE_PKEX_COMMIT_REVEAL;
|
||
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
|
||
}
|
||
|
||
static void dpp_process_pkex_exchange_request(struct dpp_sm *dpp,
|
||
struct l_ecc_point *m)
|
||
{
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *n = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *qr = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *qi = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *k = NULL;
|
||
const uint8_t *own_addr = netdev_get_address(dpp->netdev);
|
||
|
||
/* Qi = H(MAC-Initiator | [identifier | ] code) * Pi */
|
||
qi = dpp_derive_qi(dpp->curve, dpp->pkex_key, dpp->pkex_id,
|
||
dpp->peer_addr);
|
||
if (!qi) {
|
||
l_debug("could not derive Qi");
|
||
return;
|
||
}
|
||
|
||
/* X' = M - Qi */
|
||
dpp->y_or_x = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_inverse(qi);
|
||
l_ecc_point_add(dpp->y_or_x, m, qi);
|
||
|
||
/*
|
||
* "The resulting ephemeral key, denoted X’, is checked whether it is
|
||
* the point-at-infinity. If it is not valid, the protocol silently
|
||
* fails"
|
||
*/
|
||
if (l_ecc_point_is_infinity(dpp->y_or_x)) {
|
||
l_debug("X' is at infinity, ignore message");
|
||
dpp_reset(dpp);
|
||
return;
|
||
}
|
||
|
||
qr = dpp_derive_qr(dpp->curve, dpp->pkex_key, dpp->pkex_id, own_addr);
|
||
if (!qr || l_ecc_point_is_infinity(qr)) {
|
||
l_debug("Qr did not derive");
|
||
l_ecc_point_free(dpp->y_or_x);
|
||
dpp->y_or_x = NULL;
|
||
goto bad_code;
|
||
}
|
||
|
||
/*
|
||
* "The Responder then generates a random ephemeral keypair, y/Y,
|
||
* encrypts Y with Qr to obtain the result, denoted N."
|
||
*/
|
||
l_ecdh_generate_key_pair(dpp->curve, &dpp->pkex_private,
|
||
&dpp->pkex_public);
|
||
|
||
/* N = Y + Qr */
|
||
n = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_add(n, dpp->pkex_public, qr);
|
||
|
||
/* K = y * X' */
|
||
|
||
k = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_multiply(k, dpp->pkex_private, dpp->y_or_x);
|
||
|
||
/* z = HKDF(<>, info | M.x | N.x | code, K.x) */
|
||
dpp_derive_z(dpp->peer_addr, own_addr, n, m, k, dpp->pkex_key,
|
||
dpp->pkex_id, dpp->z, &dpp->z_len);
|
||
|
||
dpp_send_pkex_exchange_response(dpp, n);
|
||
|
||
return;
|
||
|
||
bad_code:
|
||
dpp_send_bad_code(dpp, dpp->peer_addr);
|
||
return;
|
||
}
|
||
|
||
static void dpp_pkex_agent_reply(struct l_dbus_message *message,
|
||
void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
const char *error, *text;
|
||
const char *code;
|
||
|
||
dpp->agent->pending_id = 0;
|
||
|
||
l_debug("SharedCodeAgent %s path %s replied", dpp->agent->owner,
|
||
dpp->agent->path);
|
||
|
||
if (l_dbus_message_get_error(message, &error, &text)) {
|
||
l_error("RequestSharedCode(%s) returned %s(\"%s\")",
|
||
dpp->pkex_id, error, text);
|
||
goto reset;
|
||
}
|
||
|
||
if (!l_dbus_message_get_arguments(message, "s", &code)) {
|
||
l_debug("Invalid arguments, check SharedCodeAgent!");
|
||
goto reset;
|
||
}
|
||
|
||
dpp->pkex_key = l_strdup(code);
|
||
dpp_process_pkex_exchange_request(dpp, dpp->peer_encr_key);
|
||
|
||
return;
|
||
|
||
reset:
|
||
dpp_free_pending_pkex_data(dpp);
|
||
}
|
||
|
||
static bool dpp_pkex_agent_request(struct dpp_sm *dpp)
|
||
{
|
||
struct l_dbus_message *msg;
|
||
|
||
if (!dpp->agent)
|
||
return false;
|
||
|
||
if (L_WARN_ON(dpp->agent->pending_id))
|
||
return false;
|
||
|
||
msg = l_dbus_message_new_method_call(dbus_get_bus(),
|
||
dpp->agent->owner,
|
||
dpp->agent->path,
|
||
IWD_SHARED_CODE_AGENT_INTERFACE,
|
||
"RequestSharedCode");
|
||
l_dbus_message_set_arguments(msg, "s", dpp->pkex_id);
|
||
|
||
|
||
dpp->agent->pending_id = l_dbus_send_with_reply(dbus_get_bus(),
|
||
msg,
|
||
dpp_pkex_agent_reply,
|
||
dpp, NULL);
|
||
return dpp->agent->pending_id != 0;
|
||
}
|
||
|
||
static void dpp_handle_pkex_exchange_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;
|
||
uint8_t version = 0;
|
||
uint16_t group = 0;
|
||
const void *id = NULL;
|
||
size_t id_len = 0;
|
||
const void *key = NULL;
|
||
size_t key_len = 0;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *m = NULL;
|
||
|
||
l_debug("PKEX exchange request "MAC, MAC_STR(from));
|
||
|
||
if (dpp->state != DPP_STATE_PKEX_EXCHANGE)
|
||
return;
|
||
|
||
if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
|
||
return;
|
||
|
||
if (!l_memeqzero(dpp->peer_addr, 6)) {
|
||
l_debug("Already configuring enrollee, ignoring");
|
||
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_PROTOCOL_VERSION:
|
||
if (len != 1)
|
||
return;
|
||
|
||
version = l_get_u8(data);
|
||
break;
|
||
case DPP_ATTR_FINITE_CYCLIC_GROUP:
|
||
if (len != 2)
|
||
return;
|
||
|
||
group = l_get_le16(data);
|
||
break;
|
||
case DPP_ATTR_CODE_IDENTIFIER:
|
||
id = data;
|
||
id_len = len;
|
||
break;
|
||
case DPP_ATTR_ENCRYPTED_KEY:
|
||
key = data;
|
||
key_len = len;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!key || !group) {
|
||
l_debug("initiator did not provide group or key, ignoring");
|
||
return;
|
||
}
|
||
|
||
if (group != l_ecc_curve_get_ike_group(dpp->curve)) {
|
||
l_debug("initiator is not using the same group");
|
||
goto bad_group;
|
||
}
|
||
|
||
/*
|
||
* If the group isn't the same the key length won't match, so check
|
||
* this here after we've determined the groups are equal
|
||
*/
|
||
if (key_len != dpp->key_len * 2) {
|
||
l_debug("Unexpected encrypted key length");
|
||
return;
|
||
}
|
||
|
||
if (version && version != dpp->pkex_version) {
|
||
l_debug("initiator is not using the same version, ignoring");
|
||
return;
|
||
}
|
||
|
||
if (dpp->pkex_id) {
|
||
if (!id || id_len != strlen(dpp->pkex_id) ||
|
||
memcmp(dpp->pkex_id, id, id_len)) {
|
||
l_debug("mismatch identifier, ignoring");
|
||
return;
|
||
}
|
||
}
|
||
|
||
m = l_ecc_point_from_data(dpp->curve, L_ECC_POINT_TYPE_FULL,
|
||
key, key_len);
|
||
if (!m) {
|
||
l_debug("could not parse key from initiator, ignoring");
|
||
return;
|
||
}
|
||
|
||
memcpy(dpp->peer_addr, from, 6);
|
||
|
||
if (!dpp->pkex_key) {
|
||
/*
|
||
* "If an optional code identifier is used, it shall be a UTF-8
|
||
* string not greater than eighty (80) octets"
|
||
*/
|
||
if (!id || id_len > 80 || !l_utf8_validate(id, id_len, NULL)) {
|
||
l_debug("Configurator started with agent but enrollee "
|
||
"sent invalid or no identifier, ignoring");
|
||
return;
|
||
}
|
||
|
||
dpp->pkex_id = l_strndup(id, id_len);
|
||
|
||
/* Need to obtain code from agent */
|
||
if (!dpp_pkex_agent_request(dpp)) {
|
||
l_debug("Failed to request code from agent!");
|
||
dpp_free_pending_pkex_data(dpp);
|
||
return;
|
||
}
|
||
|
||
/* Save the encrypted key/identifier for the agent callback */
|
||
|
||
dpp->peer_encr_key = l_steal_ptr(m);
|
||
|
||
return;
|
||
}
|
||
|
||
dpp_process_pkex_exchange_request(dpp, m);
|
||
|
||
return;
|
||
|
||
bad_group:
|
||
dpp_send_bad_group(dpp, from);
|
||
}
|
||
|
||
static void dpp_send_commit_reveal_response(struct dpp_sm *dpp,
|
||
const uint8_t *v, size_t v_len)
|
||
{
|
||
uint8_t hdr[32];
|
||
uint8_t attrs[256];
|
||
uint8_t *ptr = attrs;
|
||
uint8_t one = 1;
|
||
struct iovec iov[2];
|
||
const uint8_t *own_mac = netdev_get_address(dpp->netdev);
|
||
uint8_t b_pub[L_ECC_POINT_MAX_BYTES];
|
||
size_t b_len;
|
||
|
||
b_len = l_ecc_point_get_data(dpp->boot_public, b_pub, sizeof(b_pub));
|
||
|
||
|
||
iov[0].iov_len = dpp_build_header(own_mac, dpp->peer_addr,
|
||
DPP_FRAME_PKEX_COMMIT_REVEAL_RESPONSE, hdr);
|
||
iov[0].iov_base = hdr;
|
||
|
||
ptr += dpp_append_wrapped_data(hdr + 26, 6, &one, 1, ptr,
|
||
sizeof(attrs), dpp->z, dpp->z_len, 2,
|
||
DPP_ATTR_BOOTSTRAPPING_KEY, b_len, b_pub,
|
||
DPP_ATTR_RESPONDER_AUTH_TAG, v_len, v);
|
||
|
||
iov[1].iov_base = attrs;
|
||
iov[1].iov_len = ptr - attrs;
|
||
|
||
dpp_send_frame(dpp, iov, 2, dpp->current_freq);
|
||
}
|
||
|
||
static void dpp_handle_pkex_commit_reveal_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 void *wrapped = NULL;
|
||
size_t wrapped_len = 0;
|
||
_auto_(l_free) uint8_t *unwrapped = NULL;
|
||
size_t unwrapped_len;
|
||
uint8_t zero = 0;
|
||
const void *key = 0;
|
||
size_t key_len = 0;
|
||
const void *i_auth = NULL;
|
||
size_t i_auth_len = 0;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *j = NULL;
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *l = NULL;
|
||
uint8_t u[L_ECC_SCALAR_MAX_BYTES];
|
||
size_t u_len = 0;
|
||
uint8_t v[L_ECC_SCALAR_MAX_BYTES];
|
||
size_t v_len = 0;
|
||
const uint8_t *own_addr = netdev_get_address(dpp->netdev);
|
||
|
||
l_debug("PKEX commit-reveal request "MAC, MAC_STR(from));
|
||
|
||
if (dpp->state != DPP_STATE_PKEX_COMMIT_REVEAL)
|
||
return;
|
||
|
||
if (dpp->role != DPP_CAPABILITY_CONFIGURATOR)
|
||
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:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!wrapped) {
|
||
l_debug("No wrapped data");
|
||
return;
|
||
}
|
||
|
||
unwrapped = dpp_unwrap_attr(body + 2, 6, &zero, 1, dpp->z, dpp->z_len,
|
||
wrapped, wrapped_len, &unwrapped_len);
|
||
if (!unwrapped) {
|
||
l_debug("Failed to unwrap attributes");
|
||
return;
|
||
}
|
||
|
||
dpp_attr_iter_init(&iter, unwrapped, unwrapped_len);
|
||
|
||
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
|
||
switch (type) {
|
||
case DPP_ATTR_BOOTSTRAPPING_KEY:
|
||
if (len != dpp->key_len * 2)
|
||
return;
|
||
|
||
key = data;
|
||
key_len = len;
|
||
break;
|
||
case DPP_ATTR_INITIATOR_AUTH_TAG:
|
||
if (len != 32)
|
||
return;
|
||
|
||
i_auth = data;
|
||
i_auth_len = len;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!key || !i_auth) {
|
||
l_debug("missing attributes");
|
||
return;
|
||
}
|
||
|
||
dpp->peer_boot_public = l_ecc_point_from_data(dpp->curve,
|
||
L_ECC_POINT_TYPE_FULL, key, key_len);
|
||
if (!dpp->peer_boot_public) {
|
||
l_debug("peers boostrapping key did not validate");
|
||
goto failed;
|
||
}
|
||
|
||
/* J' = y * A' */
|
||
j = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_multiply(j, dpp->pkex_private, dpp->peer_boot_public);
|
||
|
||
dpp_derive_u(j, dpp->peer_addr, dpp->peer_boot_public, dpp->pkex_public,
|
||
dpp->y_or_x, u, &u_len);
|
||
|
||
if (memcmp(u, i_auth, i_auth_len)) {
|
||
l_debug("Initiator auth tag did not verify");
|
||
goto failed;
|
||
}
|
||
|
||
/* L' = x * B' */
|
||
l = l_ecc_point_new(dpp->curve);
|
||
|
||
l_ecc_point_multiply(l, dpp->boot_private, dpp->y_or_x);
|
||
|
||
if (!dpp_derive_v(l, own_addr, dpp->boot_public, dpp->y_or_x,
|
||
dpp->pkex_public, v, &v_len)) {
|
||
l_debug("Failed to derive v");
|
||
goto failed;
|
||
}
|
||
|
||
dpp_send_commit_reveal_response(dpp, v, v_len);
|
||
|
||
dpp_pkex_start_authentication(dpp);
|
||
|
||
return;
|
||
|
||
failed:
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
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;
|
||
case DPP_FRAME_PKEX_XCHG_RESPONSE:
|
||
dpp_handle_pkex_exchange_response(dpp, frame->address_2, body,
|
||
body_len);
|
||
break;
|
||
case DPP_FRAME_PKEX_COMMIT_REVEAL_RESPONSE:
|
||
dpp_handle_pkex_commit_reveal_response(dpp, frame->address_2,
|
||
body, body_len);
|
||
break;
|
||
case DPP_FRAME_PKEX_VERSION1_XCHG_REQUEST:
|
||
dpp_handle_pkex_exchange_request(dpp, frame->address_2, body,
|
||
body_len);
|
||
break;
|
||
case DPP_FRAME_PKEX_COMMIT_REVEAL_REQUEST:
|
||
dpp_handle_pkex_commit_reveal_request(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->offchannel_id && !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;
|
||
|
||
/*
|
||
* Don't retransmit for presence or PKEX exchange if an enrollee, both
|
||
* are broadcast frames which don't expect an ack.
|
||
*/
|
||
if (dpp->state == DPP_STATE_NOTHING ||
|
||
dpp->state == DPP_STATE_PRESENCE ||
|
||
(dpp->state == DPP_STATE_PKEX_EXCHANGE &&
|
||
dpp->role == DPP_CAPABILITY_ENROLLEE))
|
||
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_start_enrollee(struct dpp_sm *dpp);
|
||
static bool dpp_start_pkex_enrollee(struct dpp_sm *dpp);
|
||
|
||
/*
|
||
* Station is unaware of DPP's state so we need to handle a few cases here so
|
||
* weird stuff doesn't happen:
|
||
*
|
||
* - While configuring we should stay connected, a disconnection/roam should
|
||
* stop DPP since it would fail regardless due to the hardware going idle
|
||
* or changing channels since configurators assume all comms will be
|
||
* on-channel.
|
||
* - While enrolling we should stay disconnected. If station connects during
|
||
* enrolling it would cause 2x calls to __station_connect_network after
|
||
* DPP finishes.
|
||
*
|
||
* Other conditions shouldn't ever happen i.e. configuring and going into a
|
||
* connecting state or enrolling and going to a roaming state.
|
||
*/
|
||
static void dpp_station_state_watch(enum station_state state, void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
if (dpp->state == DPP_STATE_NOTHING ||
|
||
dpp->interface == DPP_INTERFACE_UNBOUND)
|
||
return;
|
||
|
||
switch (state) {
|
||
case STATION_STATE_DISCONNECTED:
|
||
/* DPP-initiated disconnect, the enrollee can now start */
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE) {
|
||
l_debug("Starting enrollee after disconnect");
|
||
|
||
if (dpp->interface == DPP_INTERFACE_DPP)
|
||
dpp_start_enrollee(dpp);
|
||
else
|
||
dpp_start_pkex_enrollee(dpp);
|
||
|
||
return;
|
||
}
|
||
|
||
break;
|
||
case STATION_STATE_DISCONNECTING:
|
||
/* Normal part of disconnecting prior to enrolling */
|
||
if (dpp->role == DPP_CAPABILITY_ENROLLEE)
|
||
return;
|
||
|
||
/* fall through */
|
||
case STATION_STATE_ROAMING:
|
||
case STATION_STATE_FT_ROAMING:
|
||
case STATION_STATE_FW_ROAMING:
|
||
/*
|
||
* An enrollee will always be disconnected prior to starting
|
||
* so a roaming condition should never happen.
|
||
*/
|
||
if (L_WARN_ON(dpp->role == DPP_CAPABILITY_ENROLLEE))
|
||
goto reset;
|
||
|
||
if (dpp->role == DPP_CAPABILITY_CONFIGURATOR) {
|
||
l_debug("Disconnected while configuring, stopping DPP");
|
||
goto reset;
|
||
}
|
||
|
||
break;
|
||
case STATION_STATE_CONNECTING:
|
||
case STATION_STATE_CONNECTED:
|
||
case STATION_STATE_CONNECTING_AUTO:
|
||
case STATION_STATE_NETCONFIG:
|
||
case STATION_STATE_AUTOCONNECT_FULL:
|
||
case STATION_STATE_AUTOCONNECT_QUICK:
|
||
/*
|
||
* The user could have issued a connection request during
|
||
* enrolling, in which case DPP should be canceled. We should
|
||
* never hit this case as a configurator as a disconnect would
|
||
* need to come prior.
|
||
*/
|
||
L_WARN_ON(dpp->role == DPP_CAPABILITY_CONFIGURATOR);
|
||
|
||
goto reset;
|
||
}
|
||
|
||
reset:
|
||
l_debug("Resetting DPP after station state change (state=%u)", state);
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
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 };
|
||
uint64_t wdev_id = netdev_get_wdev_id(netdev);
|
||
|
||
dpp->netdev = netdev;
|
||
dpp->state = DPP_STATE_NOTHING;
|
||
dpp->interface = DPP_INTERFACE_UNBOUND;
|
||
dpp->wdev_id = wdev_id;
|
||
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->max_roc = wiphy_get_max_roc_duration(wiphy_find_by_wdev(wdev_id));
|
||
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);
|
||
l_dbus_object_add_interface(dbus, netdev_get_path(netdev),
|
||
IWD_DPP_PKEX_INTERFACE, dpp);
|
||
/*
|
||
* Since both interfaces share the dpp_sm set this to 2. Currently both
|
||
* interfaces are added/removed in unison so we _could_ simply omit the
|
||
* destroy callback on one of them. But for consistency and future
|
||
* proofing use a reference count and the final interface being removed
|
||
* will destroy the dpp_sm.
|
||
*/
|
||
dpp->refcount = 2;
|
||
|
||
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);
|
||
|
||
dpp->known_network_watch = known_networks_watch_add(
|
||
dpp_known_network_watch, 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);
|
||
l_dbus_object_remove_interface(dbus_get_bus(),
|
||
netdev_get_path(netdev),
|
||
IWD_DPP_PKEX_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)
|
||
{
|
||
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 = (dpp->max_roc < 2000) ? dpp->max_roc : 2000;
|
||
dpp->freqs_idx = 0;
|
||
dpp->current_freq = dpp->freqs[0];
|
||
|
||
dpp_start_offchannel(dpp, dpp->current_freq);
|
||
}
|
||
|
||
static int dpp_try_disconnect_station(struct dpp_sm *dpp,
|
||
bool *out_disconnect_started)
|
||
{
|
||
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
|
||
int ret;
|
||
bool started = false;
|
||
|
||
/* Unusual case, but an enrollee could still be started */
|
||
if (!station)
|
||
goto done;
|
||
|
||
dpp->autoconnect = station_get_autoconnect(station);
|
||
station_set_autoconnect(station, false);
|
||
|
||
switch (station_get_state(station)) {
|
||
case STATION_STATE_AUTOCONNECT_QUICK:
|
||
case STATION_STATE_AUTOCONNECT_FULL:
|
||
/* Should never happen since we just set autoconnect false */
|
||
l_warn("Still in autoconnect state after setting false!");
|
||
|
||
/* fall through */
|
||
case STATION_STATE_DISCONNECTED:
|
||
break;
|
||
case STATION_STATE_ROAMING:
|
||
case STATION_STATE_FT_ROAMING:
|
||
case STATION_STATE_FW_ROAMING:
|
||
case STATION_STATE_CONNECTING:
|
||
case STATION_STATE_CONNECTING_AUTO:
|
||
case STATION_STATE_CONNECTED:
|
||
case STATION_STATE_NETCONFIG:
|
||
/*
|
||
* For any connected or connection in progress state, start a
|
||
* disconnect
|
||
*/
|
||
ret = station_disconnect(station);
|
||
if (ret < 0) {
|
||
l_warn("failed to start disconnecting (%d)", ret);
|
||
station_set_autoconnect(station, dpp->autoconnect);
|
||
|
||
return ret;
|
||
}
|
||
/* fall through */
|
||
case STATION_STATE_DISCONNECTING:
|
||
l_debug("Delaying DPP start until after disconnect");
|
||
started = true;
|
||
|
||
break;
|
||
}
|
||
|
||
dpp->station_watch = station_add_state_watch(station,
|
||
dpp_station_state_watch,
|
||
dpp, NULL);
|
||
done:
|
||
*out_disconnect_started = started;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void dpp_start_enrollee(struct dpp_sm *dpp)
|
||
{
|
||
uint32_t freq = band_channel_to_freq(6, BAND_FREQ_2_4_GHZ);
|
||
|
||
dpp->uri = dpp_generate_uri(dpp->own_asn1, dpp->own_asn1_len, 2,
|
||
netdev_get_address(dpp->netdev), &freq,
|
||
1, NULL, NULL);
|
||
|
||
l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
|
||
&dpp->own_proto_public);
|
||
|
||
l_debug("DPP Start Enrollee: %s", dpp->uri);
|
||
|
||
/*
|
||
* 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);
|
||
|
||
dpp_property_changed_notify(dpp);
|
||
}
|
||
|
||
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;
|
||
bool wait_for_disconnect;
|
||
int ret;
|
||
|
||
if (dpp->state != DPP_STATE_NOTHING ||
|
||
dpp->interface != DPP_INTERFACE_UNBOUND)
|
||
return dbus_error_busy(message);
|
||
|
||
dpp->state = DPP_STATE_PRESENCE;
|
||
dpp->role = DPP_CAPABILITY_ENROLLEE;
|
||
dpp->interface = DPP_INTERFACE_DPP;
|
||
|
||
ret = dpp_try_disconnect_station(dpp, &wait_for_disconnect);
|
||
if (ret < 0) {
|
||
dpp_reset(dpp);
|
||
return dbus_error_from_errno(ret, message);
|
||
}
|
||
|
||
if (!wait_for_disconnect)
|
||
dpp_start_enrollee(dpp);
|
||
|
||
/*
|
||
* If a disconnect was started/in progress the enrollee will start once
|
||
* that has finished
|
||
*/
|
||
|
||
dpp->pending = l_dbus_message_ref(message);
|
||
|
||
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->peer_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 ||
|
||
dpp->interface != DPP_INTERFACE_UNBOUND)
|
||
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->interface = DPP_INTERFACE_DPP;
|
||
dpp->config = dpp_configuration_new(settings,
|
||
network_get_ssid(network),
|
||
hs->akm_suite);
|
||
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
dpp->station_watch = station_add_state_watch(station,
|
||
dpp_station_state_watch, dpp, NULL);
|
||
|
||
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;
|
||
|
||
l_debug("");
|
||
|
||
if (dpp->interface != DPP_INTERFACE_DPP)
|
||
return dbus_error_not_found(message);
|
||
|
||
dpp_reset(dpp);
|
||
|
||
return l_dbus_message_new_method_return(message);
|
||
}
|
||
|
||
static void dpp_pkex_scan_trigger(int err, void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
if (err < 0)
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
/*
|
||
* Section 5.6.1
|
||
* In lieu of specific channel information obtained in a manner outside
|
||
* the scope of this specification, PKEX responders shall select one of
|
||
* the following channels:
|
||
* - 2.4 GHz: Channel 6 (2.437 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
|
||
*/
|
||
static uint32_t *dpp_default_freqs(struct dpp_sm *dpp, size_t *out_len)
|
||
{
|
||
struct wiphy *wiphy = wiphy_find_by_wdev(dpp->wdev_id);
|
||
uint32_t default_channels[3] = { 2437, 5220, 5745 };
|
||
uint32_t *freqs_out;
|
||
size_t len = 0;
|
||
|
||
if ((wiphy_get_supported_bands(wiphy) & BAND_FREQ_2_4_GHZ) &&
|
||
scan_get_band_rank_modifier(BAND_FREQ_2_4_GHZ) != 0)
|
||
default_channels[len++] = 2437;
|
||
|
||
if ((wiphy_get_supported_bands(wiphy) & BAND_FREQ_5_GHZ) &&
|
||
scan_get_band_rank_modifier(BAND_FREQ_5_GHZ) != 0) {
|
||
default_channels[len++] = 5220;
|
||
default_channels[len++] = 5745;
|
||
}
|
||
|
||
if (!len) {
|
||
l_warn("No bands are allowed, check BandModifier* settings!");
|
||
return NULL;
|
||
}
|
||
|
||
freqs_out = l_memdup(default_channels, sizeof(uint32_t) * len);
|
||
*out_len = len;
|
||
|
||
return freqs_out;
|
||
}
|
||
|
||
static void __dpp_pkex_start_enrollee(struct dpp_sm *dpp)
|
||
{
|
||
dpp->current_freq = dpp->freqs[0];
|
||
|
||
dpp_reset_protocol_timer(dpp,
|
||
dpp->freqs_len * DPP_PKEX_PROTO_PER_FREQ_TIMEOUT);
|
||
|
||
l_debug("PKEX start enrollee (id=%s)", dpp->pkex_id ?: "unset");
|
||
|
||
dpp_start_offchannel(dpp, dpp->current_freq);
|
||
}
|
||
|
||
static bool dpp_pkex_scan_notify(int err, struct l_queue *bss_list,
|
||
const struct scan_freq_set *freqs,
|
||
void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
const struct l_queue_entry *e;
|
||
_auto_(scan_freq_set_free) struct scan_freq_set *freq_set = NULL;
|
||
|
||
if (err < 0)
|
||
goto failed;
|
||
|
||
freq_set = scan_freq_set_new();
|
||
|
||
if (!bss_list || l_queue_isempty(bss_list)) {
|
||
dpp->freqs = dpp_default_freqs(dpp, &dpp->freqs_len);
|
||
if (!dpp->freqs)
|
||
goto failed;
|
||
|
||
l_debug("No BSS's seen, using default frequency list");
|
||
goto start;
|
||
}
|
||
|
||
for (e = l_queue_get_entries(bss_list); e; e = e->next) {
|
||
const struct scan_bss *bss = e->data;
|
||
|
||
scan_freq_set_add(freq_set, bss->frequency);
|
||
}
|
||
|
||
l_debug("Found %u frequencies to search for configurator",
|
||
l_queue_length(bss_list));
|
||
|
||
dpp->freqs = scan_freq_set_to_fixed_array(freq_set, &dpp->freqs_len);
|
||
|
||
start:
|
||
__dpp_pkex_start_enrollee(dpp);
|
||
|
||
return false;
|
||
|
||
failed:
|
||
dpp_reset(dpp);
|
||
return false;
|
||
}
|
||
|
||
static void dpp_pkex_scan_destroy(void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
dpp->pkex_scan_id = 0;
|
||
}
|
||
|
||
static bool dpp_start_pkex_enrollee(struct dpp_sm *dpp)
|
||
{
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
/* Already have a set (or single) frequency */
|
||
if (dpp->freqs) {
|
||
__dpp_pkex_start_enrollee(dpp);
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* The 'dpp_default_freqs' function returns the default frequencies
|
||
* outlined in section 5.6.1. For 2.4/5GHz this is only 3 frequencies
|
||
* which is unlikely to result in discovery of a configurator. The spec
|
||
* does allow frequencies to be "obtained in a manner outside the scope
|
||
* of this specification" which is what is being done here.
|
||
*
|
||
* This is mainly geared towards IWD-based configurators; banking on the
|
||
* fact that they are currently connected to nearby APs. Scanning lets
|
||
* us see nearby BSS's which should be the same frequencies as our
|
||
* target configurator.
|
||
*/
|
||
l_debug("Performing scan for frequencies to start PKEX");
|
||
|
||
dpp->pkex_scan_id = scan_active(dpp->wdev_id, NULL, 0,
|
||
dpp_pkex_scan_trigger, dpp_pkex_scan_notify,
|
||
dpp, dpp_pkex_scan_destroy);
|
||
if (!dpp->pkex_scan_id)
|
||
goto failed;
|
||
|
||
return true;
|
||
|
||
failed:
|
||
dpp_reset(dpp);
|
||
return false;
|
||
}
|
||
|
||
static bool dpp_parse_pkex_args(struct l_dbus_message *message,
|
||
const char **key_out,
|
||
const char **id_out,
|
||
const char **mac_out,
|
||
uint32_t *freq_out)
|
||
{
|
||
struct l_dbus_message_iter iter;
|
||
struct l_dbus_message_iter variant;
|
||
const char *dict_key;
|
||
const char *key = NULL;
|
||
const char *id = NULL;
|
||
const char *mac = NULL;
|
||
uint32_t freq = 0;
|
||
|
||
if (!l_dbus_message_get_arguments(message, "a{sv}", &iter))
|
||
return false;
|
||
|
||
while (l_dbus_message_iter_next_entry(&iter, &dict_key, &variant)) {
|
||
if (!strcmp(dict_key, "Code")) {
|
||
if (!l_dbus_message_iter_get_variant(&variant, "s",
|
||
&key))
|
||
return false;
|
||
} else if (!strcmp(dict_key, "Identifier")) {
|
||
if (!l_dbus_message_iter_get_variant(&variant, "s",
|
||
&id))
|
||
return false;
|
||
} else if (!strcmp(dict_key, "Address")) {
|
||
if (!l_dbus_message_iter_get_variant(&variant, "s",
|
||
&mac))
|
||
return false;
|
||
} else if (!strcmp(dict_key, "Frequency")) {
|
||
if (!l_dbus_message_iter_get_variant(&variant, "u",
|
||
&freq))
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (!key)
|
||
return false;
|
||
|
||
if (id && strlen(id) > 80)
|
||
return false;
|
||
|
||
*key_out = key;
|
||
*id_out = id;
|
||
|
||
if (mac_out)
|
||
*mac_out = mac;
|
||
|
||
if (freq_out)
|
||
*freq_out = freq;
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool dpp_pkex_derive_keys(struct dpp_sm *dpp)
|
||
{
|
||
_auto_(l_ecc_point_free) struct l_ecc_point *qi = NULL;
|
||
|
||
/*
|
||
* In theory a driver could support a lesser duration than 200ms. This
|
||
* complicates things since we would need to tack on additional
|
||
* offchannel requests to meet the 200ms requirement. This could be done
|
||
* but for now use max_roc or 200ms, whichever is less.
|
||
*/
|
||
dpp->dwell = (dpp->max_roc < 200) ? dpp->max_roc : 200;
|
||
/* "DPP R2 devices are expected to use PKEXv1 by default" */
|
||
dpp->pkex_version = 1;
|
||
|
||
if (!l_ecdh_generate_key_pair(dpp->curve, &dpp->pkex_private,
|
||
&dpp->pkex_public))
|
||
return false;
|
||
|
||
/*
|
||
* "If Qi is the point-at-infinity, the code shall be deleted and the
|
||
* user should be notified to provision a new code"
|
||
*/
|
||
qi = dpp_derive_qi(dpp->curve, dpp->pkex_key, dpp->pkex_id,
|
||
netdev_get_address(dpp->netdev));
|
||
if (!qi || l_ecc_point_is_infinity(qi)) {
|
||
l_debug("Cannot derive Qi, provision a new code");
|
||
return false;
|
||
}
|
||
|
||
dpp->pkex_m = l_ecc_point_new(dpp->curve);
|
||
|
||
return l_ecc_point_add(dpp->pkex_m, dpp->pkex_public, qi);
|
||
}
|
||
|
||
static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus,
|
||
struct l_dbus_message *message,
|
||
void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
const char *key;
|
||
const char *id;
|
||
const char *mac_str;
|
||
uint32_t freq;
|
||
bool wait_for_disconnect;
|
||
int ret;
|
||
|
||
l_debug("");
|
||
|
||
if (dpp->state != DPP_STATE_NOTHING ||
|
||
dpp->interface != DPP_INTERFACE_UNBOUND)
|
||
return dbus_error_busy(message);
|
||
|
||
if (!dpp_parse_pkex_args(message, &key, &id, &mac_str, &freq))
|
||
goto invalid_args;
|
||
|
||
if (mac_str && !util_string_to_address(mac_str, dpp->peer_addr))
|
||
goto invalid_args;
|
||
else if (!mac_str)
|
||
memcpy(dpp->peer_addr, broadcast, 6);
|
||
|
||
if (freq) {
|
||
dpp->freqs = l_new(uint32_t, 1);
|
||
dpp->freqs[0] = freq;
|
||
dpp->freqs_len = 1;
|
||
}
|
||
|
||
dpp->pkex_key = l_strdup(key);
|
||
|
||
if (id)
|
||
dpp->pkex_id = l_strdup(id);
|
||
|
||
dpp->role = DPP_CAPABILITY_ENROLLEE;
|
||
dpp->state = DPP_STATE_PKEX_EXCHANGE;
|
||
dpp->interface = DPP_INTERFACE_PKEX;
|
||
|
||
ret = dpp_try_disconnect_station(dpp, &wait_for_disconnect);
|
||
if (ret < 0) {
|
||
dpp_reset(dpp);
|
||
return dbus_error_from_errno(ret, message);
|
||
}
|
||
|
||
if (!dpp_pkex_derive_keys(dpp)) {
|
||
dpp_reset(dpp);
|
||
return dbus_error_failed(message);
|
||
}
|
||
|
||
if (!wait_for_disconnect && !dpp_start_pkex_enrollee(dpp))
|
||
goto invalid_args;
|
||
|
||
/*
|
||
* If a disconnect was started/in progress the PKEX enrollee will start
|
||
* once that has finished
|
||
*/
|
||
|
||
return l_dbus_message_new_method_return(message);
|
||
|
||
invalid_args:
|
||
return dbus_error_invalid_args(message);
|
||
}
|
||
|
||
static void pkex_agent_disconnect(struct l_dbus *dbus, void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
l_debug("SharedCodeAgent %s disconnected", dpp->agent->path);
|
||
|
||
dpp_reset(dpp);
|
||
}
|
||
|
||
static void dpp_create_agent(struct dpp_sm *dpp, const char *path,
|
||
struct l_dbus_message *message)
|
||
{
|
||
const char *sender = l_dbus_message_get_sender(message);
|
||
|
||
dpp->agent = l_new(struct pkex_agent, 1);
|
||
dpp->agent->owner = l_strdup(sender);
|
||
dpp->agent->path = l_strdup(path);
|
||
dpp->agent->disconnect_watch = l_dbus_add_disconnect_watch(
|
||
dbus_get_bus(),
|
||
sender,
|
||
pkex_agent_disconnect,
|
||
dpp, NULL);
|
||
|
||
l_debug("Registered a SharedCodeAgent on path %s", path);
|
||
}
|
||
|
||
static struct l_dbus_message *dpp_start_pkex_configurator(struct dpp_sm *dpp,
|
||
const char *key, const char *identifier,
|
||
const char *agent_path,
|
||
struct l_dbus_message *message)
|
||
{
|
||
struct handshake_state *hs = netdev_get_handshake(dpp->netdev);
|
||
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
|
||
struct network *network = station_get_connected_network(station);
|
||
struct scan_bss *bss = station_get_connected_bss(station);
|
||
const struct l_settings *settings;
|
||
|
||
if (dpp->state != DPP_STATE_NOTHING ||
|
||
dpp->interface != DPP_INTERFACE_UNBOUND)
|
||
return dbus_error_busy(message);
|
||
|
||
if (!dpp->mcast_support)
|
||
l_debug("Multicast frame registration not supported, only "
|
||
"enrollees sending uncast will be supported");
|
||
|
||
if (!network || !bss)
|
||
return dbus_error_not_connected(message);
|
||
|
||
settings = network_get_settings(network);
|
||
if (!settings) {
|
||
l_debug("No settings for network, is this a known network?");
|
||
return dbus_error_not_configured(message);
|
||
}
|
||
|
||
if (identifier)
|
||
dpp->pkex_id = l_strdup(identifier);
|
||
|
||
if (key)
|
||
dpp->pkex_key = l_strdup(key);
|
||
|
||
if (agent_path)
|
||
dpp_create_agent(dpp, agent_path, message);
|
||
|
||
dpp->role = DPP_CAPABILITY_CONFIGURATOR;
|
||
dpp->state = DPP_STATE_PKEX_EXCHANGE;
|
||
dpp->interface = DPP_INTERFACE_PKEX;
|
||
dpp->current_freq = bss->frequency;
|
||
dpp->pkex_version = 1;
|
||
dpp->config = dpp_configuration_new(network_get_settings(network),
|
||
network_get_ssid(network),
|
||
hs->akm_suite);
|
||
|
||
dpp_reset_protocol_timer(dpp, DPP_PKEX_PROTO_TIMEOUT);
|
||
dpp_property_changed_notify(dpp);
|
||
|
||
dpp->station_watch = station_add_state_watch(station,
|
||
dpp_station_state_watch, dpp, NULL);
|
||
|
||
if (dpp->pkex_key)
|
||
l_debug("Starting PKEX configurator for single enrollee");
|
||
else
|
||
l_debug("Starting PKEX configurator with agent");
|
||
|
||
return l_dbus_message_new_method_return(message);
|
||
}
|
||
|
||
static struct l_dbus_message *dpp_dbus_pkex_configure_enrollee(
|
||
struct l_dbus *dbus,
|
||
struct l_dbus_message *message,
|
||
void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
const char *key;
|
||
const char *id;
|
||
|
||
l_debug("");
|
||
|
||
if (!dpp_parse_pkex_args(message, &key, &id, NULL, NULL))
|
||
return dbus_error_invalid_args(message);
|
||
|
||
return dpp_start_pkex_configurator(dpp, key, id, NULL, message);
|
||
}
|
||
|
||
static struct l_dbus_message *dpp_dbus_pkex_start_configurator(
|
||
struct l_dbus *dbus,
|
||
struct l_dbus_message *message,
|
||
void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
const char *path;
|
||
|
||
if (!l_dbus_message_get_arguments(message, "o", &path))
|
||
return dbus_error_invalid_args(message);
|
||
|
||
return dpp_start_pkex_configurator(dpp, NULL, NULL, path, 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 struct l_dbus_message *dpp_dbus_pkex_stop(struct l_dbus *dbus,
|
||
struct l_dbus_message *message, void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
l_debug("");
|
||
|
||
if (dpp->interface != DPP_INTERFACE_PKEX)
|
||
return dbus_error_not_found(message);
|
||
|
||
dpp_reset(dpp);
|
||
|
||
return l_dbus_message_new_method_return(message);
|
||
}
|
||
|
||
static void dpp_setup_pkex_interface(struct l_dbus_interface *interface)
|
||
{
|
||
l_dbus_interface_method(interface, "StartEnrollee", 0,
|
||
dpp_dbus_pkex_start_enrollee, "", "a{sv}", "args");
|
||
l_dbus_interface_method(interface, "Stop", 0,
|
||
dpp_dbus_pkex_stop, "", "");
|
||
l_dbus_interface_method(interface, "ConfigureEnrollee", 0,
|
||
dpp_dbus_pkex_configure_enrollee, "", "a{sv}", "args");
|
||
l_dbus_interface_method(interface, "StartConfigurator", 0,
|
||
dpp_dbus_pkex_start_configurator, "", "o", "path");
|
||
|
||
l_dbus_interface_property(interface, "Started", 0, "b",
|
||
dpp_pkex_get_started, NULL);
|
||
l_dbus_interface_property(interface, "Role", 0, "s",
|
||
dpp_pkex_get_role, NULL);
|
||
}
|
||
|
||
static void dpp_destroy_interface(void *user_data)
|
||
{
|
||
struct dpp_sm *dpp = user_data;
|
||
|
||
if (--dpp->refcount)
|
||
return;
|
||
|
||
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);
|
||
l_dbus_register_interface(dbus_get_bus(), IWD_DPP_PKEX_INTERFACE,
|
||
dpp_setup_pkex_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);
|
||
l_dbus_unregister_interface(dbus_get_bus(), IWD_DPP_PKEX_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);
|