| /* |
| * |
| * 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 |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <string.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 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); |
| |
| frame_watch_group_remove(dpp->wdev_id, FRAME_GROUP_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, dpp->peer_addr, |
| 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 can't 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, ignoring"); |
| 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); |
| |
| /* |
| * No longer waiting for an arbitrary peer to respond, reduce the |
| * timeout now that we are proceeding to authentication |
| */ |
| dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT); |
| |
| 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 bootstrapping 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(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; |
| |
| /* |
| * 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 won't 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_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); |
| 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_supports_multicast_rx( |
| wiphy_find_by_wdev(dpp->wdev_id)); |
| |
| 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->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_add_frame_watches(struct dpp_sm *dpp, bool multicast_rx) |
| { |
| uint8_t dpp_conf_response_prefix[] = { 0x04, 0x0b }; |
| uint8_t dpp_conf_request_prefix[] = { 0x04, 0x0a }; |
| |
| frame_watch_add(dpp->wdev_id, FRAME_GROUP_DPP, 0x00d0, |
| dpp_prefix, sizeof(dpp_prefix), multicast_rx, |
| dpp_handle_frame, dpp, NULL); |
| |
| frame_watch_add(dpp->wdev_id, FRAME_GROUP_DPP, 0x00d0, |
| dpp_conf_response_prefix, |
| sizeof(dpp_conf_response_prefix), false, |
| dpp_handle_config_response_frame, dpp, NULL); |
| |
| frame_watch_add(dpp->wdev_id, FRAME_GROUP_DPP, 0x00d0, |
| dpp_conf_request_prefix, |
| sizeof(dpp_conf_request_prefix), false, |
| dpp_handle_config_request_frame, dpp, NULL); |
| } |
| |
| 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; |
| |
| dpp_add_frame_watches(dpp, false); |
| |
| 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_add_frame_watches(dpp, responder && dpp->mcast_support); |
| |
| 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) |
| { |
| uint32_t timeout = minsize(DPP_PKEX_PROTO_TIMEOUT, |
| dpp->freqs_len * DPP_PKEX_PROTO_PER_FREQ_TIMEOUT); |
| dpp->current_freq = dpp->freqs[0]; |
| |
| dpp_reset_protocol_timer(dpp, 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; |
| |
| dpp_add_frame_watches(dpp, false); |
| |
| 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_add_frame_watches(dpp, dpp->mcast_support); |
| |
| 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); |
| |
| 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_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); |