new file mode 100644
@@ -0,0 +1,517 @@
+/*
+ *
+ * Wireless daemon for Linux
+ *
+ * Copyright (C) 2024 Locus Robotics
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <ell/ell.h>
+
+#include "linux/nl80211.h"
+
+#include "src/missing.h"
+#include "src/dbus.h"
+#include "src/netdev.h"
+#include "src/module.h"
+#include "src/dpp-util.h"
+#include "src/band.h"
+#include "src/frame-xchg.h"
+#include "src/offchannel.h"
+#include "src/wiphy.h"
+#include "src/ie.h"
+#include "src/iwd.h"
+#include "src/util.h"
+#include "src/crypto.h"
+#include "src/mpdu.h"
+#include "ell/useful.h"
+#include "src/common.h"
+#include "src/json.h"
+#include "src/storage.h"
+#include "src/station.h"
+#include "src/scan.h"
+#include "src/network.h"
+#include "src/handshake.h"
+#include "src/nl80211util.h"
+#include "src/knownnetworks.h"
+#include "src/dpp-common.h"
+
+#define DPP_HDR_LEN 6
+#define DPP_ACTION_VENDOR_SPECIFIC 0x09
+#define DPP_ACTION_GAS_REQUEST 0x0a
+#define DPP_ACTION_GAS_RESPONSE 0x0b
+#define DPP_AUTH_PROTO_TIMEOUT 10
+#define DPP_PKEX_PROTO_TIMEOUT 120
+
+struct dpp_sm {
+ char *uri;
+ uint8_t role;
+
+ 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];
+ uint8_t own_chirp_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;
+
+ 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 auth/config/PKEX protocols */
+ uint64_t proto_timeout;
+ struct l_timeout *timeout;
+
+ struct dpp_configuration *config;
+
+ /* 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;
+ uint8_t pkex_own_mac[6];
+ uint8_t pkex_peer_mac[6];
+ /* Set to either own/peer mac depending on configuration */
+ const uint8_t *mac_initiator;
+ const uint8_t *mac_responder;
+
+ /*
+ * Since the authenticate frame may request a channel switch we do need
+ * to expose this detail within the common code.
+ */
+ uint8_t channel[2];
+
+ uint8_t *frame_pending;
+ size_t frame_len;
+ dpp_write_cb_t write;
+ dpp_event_cb_t event_cb;
+ void *user_data;
+
+ bool skip_presence : 1;
+ bool mutual_auth : 1;
+ bool initiator : 1;
+};
+
+static void dpp_failed(struct dpp_sm *dpp)
+{
+ if (dpp->event_cb)
+ dpp->event_cb(DPP_EVENT_FAILED, NULL, dpp->user_data);
+}
+
+static void dpp_protocol_timeout(struct l_timeout *timeout, void *user_data)
+{
+ struct dpp_sm *dpp = user_data;
+
+ l_debug("DPP timed out");
+
+ dpp_failed(dpp);
+}
+
+static void dpp_reset_protocol_timer(struct dpp_sm *dpp, uint64_t time)
+{
+ if (dpp->timeout)
+ l_timeout_modify(dpp->timeout, time);
+ else
+ dpp->timeout = l_timeout_create(time, dpp_protocol_timeout,
+ dpp, NULL);
+}
+
+struct dpp_sm *dpp_sm_new(dpp_event_cb_t event,
+ const struct l_ecc_point *boot_public,
+ const struct l_ecc_scalar *boot_private,
+ void *user_data)
+{
+ struct dpp_sm *dpp = l_new(struct dpp_sm, 1);
+
+ dpp->state = DPP_STATE_NOTHING;
+ dpp->curve = l_ecc_point_get_curve(boot_public);
+ dpp->key_len = l_ecc_curve_get_scalar_bytes(dpp->curve);
+ dpp->nonce_len = dpp_nonce_len_from_key_len(dpp->key_len);
+ dpp->boot_public = l_ecc_point_clone(boot_public);
+ dpp->boot_private = l_ecc_scalar_clone(boot_private);
+ dpp->own_asn1 = dpp_point_to_asn1(dpp->boot_public, &dpp->own_asn1_len);
+ dpp->event_cb = event;
+ dpp->user_data = user_data;
+
+ dpp_hash(L_CHECKSUM_SHA256, dpp->own_boot_hash, 1, dpp->own_asn1,
+ dpp->own_asn1_len);
+ dpp_hash(L_CHECKSUM_SHA256, dpp->own_chirp_hash, 2, "chirp",
+ strlen("chirp"), dpp->own_asn1, dpp->own_asn1_len);
+
+ return dpp;
+}
+
+void dpp_sm_set_write_handler(struct dpp_sm *dpp, dpp_write_cb_t write)
+{
+ dpp->write = write;
+
+ if (!dpp->write)
+ return;
+
+ /* Handle writing frame */
+}
+
+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_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;
+ }
+
+ if (dpp->peer_encr_key) {
+ l_ecc_point_free(dpp->peer_encr_key);
+ dpp->peer_encr_key = NULL;
+ }
+}
+
+void dpp_sm_free(struct dpp_sm *dpp)
+{
+ 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;
+ }
+
+ if (dpp->timeout) {
+ l_timeout_remove(dpp->timeout);
+ dpp->timeout = NULL;
+ }
+
+ if (dpp->config) {
+ dpp_configuration_free(dpp->config);
+ dpp->config = NULL;
+ }
+
+ if (dpp->peer_asn1) {
+ l_free(dpp->peer_asn1);
+ dpp->peer_asn1 = NULL;
+ }
+
+ if (dpp->own_asn1) {
+ l_free(dpp->own_asn1);
+ dpp->own_asn1 = NULL;
+ }
+
+ if (dpp->frame_pending) {
+ l_free(dpp->frame_pending);
+ dpp->frame_pending = NULL;
+ }
+
+ dpp->state = DPP_STATE_NOTHING;
+ 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_free_pending_pkex_data(dpp);
+
+ dpp_free_auth_data(dpp);
+
+ l_free(dpp);
+}
+
+void dpp_sm_set_peer_bootstrap(struct dpp_sm *dpp,
+ struct l_ecc_point *public)
+{
+ dpp->peer_boot_public = public;
+ dpp->peer_asn1 = dpp_point_to_asn1(public, &dpp->peer_asn1_len);
+
+ dpp_hash(L_CHECKSUM_SHA256, dpp->peer_boot_hash, 1, dpp->peer_asn1,
+ dpp->peer_asn1_len);
+}
+
+const uint8_t *dpp_sm_get_own_asn1(struct dpp_sm *dpp, size_t *len)
+{
+ *len = dpp->own_asn1_len;
+
+ return dpp->own_asn1;
+}
+
+void dpp_sm_set_configuration(struct dpp_sm *dpp,
+ struct dpp_configuration *config)
+{
+ dpp->config = config;
+}
+
+const struct dpp_configuration *dpp_sm_get_configuration(struct dpp_sm *dpp)
+{
+ return dpp->config;
+}
+
+void dpp_sm_set_skip_presence(struct dpp_sm *dpp, bool skip)
+{
+ dpp->skip_presence = skip;
+}
+
+void dpp_sm_set_channel(struct dpp_sm *dpp, uint8_t oper_class, uint8_t channel)
+{
+ dpp->channel[0] = oper_class;
+ dpp->channel[1] = channel;
+}
+
+enum dpp_state dpp_sm_get_state(struct dpp_sm *dpp)
+{
+ return dpp->state;
+}
+
+bool dpp_sm_start_initiator(struct dpp_sm *dpp)
+{
+ if (L_WARN_ON(!dpp->peer_boot_public))
+ return false;
+
+ dpp->initiator = true;
+
+ /* Generate the keys for this protocol run */
+ l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
+ &dpp->own_proto_public);
+
+ l_getrandom(dpp->i_nonce, dpp->nonce_len);
+
+ dpp->m = dpp_derive_k1(dpp->peer_boot_public,
+ dpp->proto_private, dpp->k1);
+
+ if (dpp->config)
+ dpp->role = DPP_CAPABILITY_CONFIGURATOR;
+ else
+ dpp->role = DPP_CAPABILITY_ENROLLEE;
+
+ dpp->state = DPP_STATE_PRESENCE;
+
+ return true;
+}
+
+bool dpp_sm_start_responder(struct dpp_sm *dpp)
+{
+ l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
+ &dpp->own_proto_public);
+ dpp->initiator = false;
+
+ if (dpp->config)
+ dpp->role = DPP_CAPABILITY_CONFIGURATOR;
+ else
+ dpp->role = DPP_CAPABILITY_ENROLLEE;
+
+ dpp->state = DPP_STATE_PRESENCE;
+
+ return true;
+}
+
+void dpp_sm_set_pkex_identifier(struct dpp_sm *dpp, const char *identifier)
+{
+ dpp->pkex_id = l_strdup(identifier);
+}
+
+void dpp_sm_set_pkex_key(struct dpp_sm *dpp, const char *key)
+{
+ dpp->pkex_key = l_strdup(key);
+}
+
+void dpp_sm_set_pkex_own_mac(struct dpp_sm *dpp, const uint8_t *mac)
+{
+ memcpy(dpp->pkex_own_mac, mac, 6);
+}
+
+void dpp_sm_set_pkex_peer_mac(struct dpp_sm *dpp, const uint8_t *mac)
+{
+ memcpy(dpp->pkex_peer_mac, mac, 6);
+
+ if (dpp->initiator) {
+ dpp->mac_responder = dpp->pkex_peer_mac;
+ dpp->mac_initiator = dpp->pkex_own_mac;
+ } else {
+ dpp->mac_initiator = dpp->pkex_peer_mac;
+ dpp->mac_responder = dpp->pkex_own_mac;
+ }
+}
+
+bool dpp_sm_pkex_start_initiator(struct dpp_sm *dpp)
+{
+ _auto_(l_ecc_point_free) struct l_ecc_point *qi = NULL;
+
+ if (!dpp->pkex_key)
+ return false;
+
+ dpp->role = dpp->config ? DPP_CAPABILITY_CONFIGURATOR :
+ DPP_CAPABILITY_ENROLLEE;
+
+ /*
+ * "DPP R2 devices are expected to use PKEXv1 by default"
+ *
+ * TODO: Support setting version (v2 required for TCP encapsulation)
+ */
+ 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,
+ dpp->pkex_own_mac);
+ if (!qi || l_ecc_point_is_infinity(qi)) {
+ l_debug("Cannot derive Qi, provision a new code");
+ goto failed;
+ }
+
+ dpp->pkex_m = l_ecc_point_new(dpp->curve);
+
+ if (!l_ecc_point_add(dpp->pkex_m, dpp->pkex_public, qi))
+ goto failed;
+
+ dpp->initiator = true;
+
+ dpp->mac_initiator = dpp->pkex_own_mac;
+ /* Won't know until we receive a response */
+ dpp->mac_responder = NULL;
+
+ dpp_reset_protocol_timer(dpp, DPP_AUTH_PROTO_TIMEOUT);
+
+ /* Send exchange request */
+
+ return true;
+
+failed:
+ return false;
+}
+
+bool dpp_sm_pkex_start_responder(struct dpp_sm *dpp)
+{
+ dpp->initiator = false;
+
+ dpp->mac_responder = dpp->pkex_own_mac;
+ /* Won't know until we receive the first frame */
+ dpp->mac_initiator = NULL;
+
+ dpp->role = dpp->config ? DPP_CAPABILITY_CONFIGURATOR :
+ DPP_CAPABILITY_ENROLLEE;
+ dpp->state = DPP_STATE_PKEX_EXCHANGE;
+ dpp->pkex_version = 1;
+
+ dpp_reset_protocol_timer(dpp, DPP_PKEX_PROTO_TIMEOUT);
+
+ return true;
+}
+
+void dpp_handle_rx(struct dpp_sm *dpp, const uint8_t *data, size_t len)
+{
+ /* Handle frame */
+}
new file mode 100644
@@ -0,0 +1,109 @@
+/*
+ *
+ * Wireless daemon for Linux
+ *
+ * Copyright (C) 2024 Locus Robotics
+ *
+ * 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
+ *
+ */
+
+struct dpp_sm;
+struct l_ecc_point;
+struct l_ecc_scalar;
+
+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_event {
+ /*
+ * PEER_ACCEPTED indicates an initial DPP frame has been received and
+ * accepted. This could be either a PKEX or an auth frame when DPP is
+ * running as either a configurator or enrollee. This should be
+ * used by the encapsulating protocol to note the peer address (MAC
+ * or IP) and from then on only accept frames from this peer until DPP
+ * has completed.
+ */
+ DPP_EVENT_PEER_ACCEPTED,
+ /*
+ * The authenticate request frame included a channel attribute. The
+ * encapsulating protocol must switch to this channel to continue the
+ * protocol (only applicable to 802.11 encapsulation). Event data is
+ * two bytes: [oper_class, channel]
+ */
+ DPP_EVENT_CHANNEL_SWITCH,
+ /*
+ * A key corresponding to an identifier (set in event_data) is now
+ * required. The encapsulating protocol must retrieve the key and
+ * notify using dpp_sm_set_pkex_key().
+ */
+ DPP_EVENT_PKEX_KEY_REQUESTED,
+ DPP_EVENT_SUCCESS,
+ DPP_EVENT_FAILED,
+};
+
+typedef void (*dpp_event_cb_t)(enum dpp_event event, const void *event_data,
+ void *user_data);
+
+typedef void (*dpp_write_cb_t)(const uint8_t *data, size_t len,
+ void *user_data);
+
+struct dpp_sm *dpp_sm_new(dpp_event_cb_t event,
+ const struct l_ecc_point *boot_public,
+ const struct l_ecc_scalar *boot_private,
+ void *user_data);
+
+void dpp_sm_free(struct dpp_sm *dpp);
+
+void dpp_handle_rx(struct dpp_sm *dpp, const uint8_t *data, size_t len);
+void dpp_sm_set_write_handler(struct dpp_sm *dpp, dpp_write_cb_t write);
+
+void dpp_sm_set_peer_bootstrap(struct dpp_sm *dpp,
+ struct l_ecc_point *public);
+void dpp_sm_set_own_bootstrap(struct dpp_sm *dpp, struct l_ecc_point *public,
+ struct l_ecc_scalar *private);
+const uint8_t *dpp_sm_get_own_asn1(struct dpp_sm *dpp, size_t *len);
+
+void dpp_sm_set_configuration(struct dpp_sm *dpp,
+ struct dpp_configuration *config);
+const struct dpp_configuration *dpp_sm_get_configuration(struct dpp_sm *dpp);
+
+void dpp_sm_set_skip_presence(struct dpp_sm *dpp, bool skip);
+
+void dpp_sm_set_channel(struct dpp_sm *dpp, uint8_t oper_class,
+ uint8_t channel);
+enum dpp_state dpp_sm_get_state(struct dpp_sm *dpp);
+
+bool dpp_sm_start_initiator(struct dpp_sm *dpp);
+bool dpp_sm_start_responder(struct dpp_sm *dpp);
+
+void dpp_sm_set_pkex_identifier(struct dpp_sm *dpp, const char *identifier);
+void dpp_sm_set_pkex_key(struct dpp_sm *dpp, const char *key);
+void dpp_sm_set_pkex_own_mac(struct dpp_sm *dpp, const uint8_t *mac);
+void dpp_sm_set_pkex_peer_mac(struct dpp_sm *dpp, const uint8_t *mac);
+
+bool dpp_sm_pkex_start_initiator(struct dpp_sm *dpp);
+bool dpp_sm_pkex_start_responder(struct dpp_sm *dpp);