This implements the DPP protocol used to authenticate to a
DPP configurator.
Note this is not a full implementation of the protocol and
there are a few missing features which will be added as
needed:
- Mutual authentication (needed for BLE bootstrapping)
- Configurator support
- Initiator role
---
src/dpp.c | 571 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 571 insertions(+)
diff --git a/src/dpp.c b/src/dpp.c
index ddc7c3c9..78074fd2 100644
--- a/src/dpp.c
+++ b/src/dpp.c
@@ -42,6 +42,9 @@
#include "src/frame-xchg.h"
#include "src/iwd.h"
#include "src/util.h"
+#include "src/crypto.h"
+#include "src/mpdu.h"
+#include "ell/useful.h"
static uint32_t netdev_watch;
static struct l_genl_family *nl80211;
@@ -50,6 +53,7 @@ static uint8_t broadcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
enum dpp_state {
DPP_STATE_NOTHING,
DPP_STATE_PRESENCE,
+ DPP_STATE_AUTHENTICATING,
};
struct dpp_sm {
@@ -77,6 +81,18 @@ struct dpp_sm {
struct scan_freq_set *presence_list;
uint32_t offchannel_id;
+
+ uint8_t auth_addr[6];
+ uint8_t r_nonce[32];
+ uint8_t i_nonce[32];
+
+ uint64_t ke[L_ECC_MAX_DIGITS];
+ uint64_t k2[L_ECC_MAX_DIGITS];
+
+ struct l_ecc_scalar *proto_private;
+ struct l_ecc_point *proto_public;
+
+ struct l_ecc_point *i_proto_public;
};
static void dpp_send_frame_cb(struct l_genl_msg *msg, void *user_data)
@@ -102,6 +118,34 @@ static void dpp_send_frame(uint64_t wdev_id, struct iovec *iov,
size_t iov_len,
l_error("Could not send CMD_FRAME");
}
+static uint8_t *dpp_unwrap_attr(enum dpp_frame_type type, const void *start,
+ const void *key, size_t key_len,
+ const uint8_t *wrapped, size_t wrapped_len,
+ size_t *unwrapped_len)
+{
+ uint8_t ad0[] = { 0x50, 0x6f, 0x9a, 0x1a, 0x01, type };
+ struct iovec ad[2];
+ uint8_t *unwrapped;
+
+ ad[0].iov_base = ad0;
+ ad[0].iov_len = sizeof(ad0);
+
+ ad[1].iov_base = (void *)start;
+ ad[1].iov_len = ((wrapped - 4) - ((const uint8_t *)start));
+
+ unwrapped = l_malloc(wrapped_len - 16);
+
+ if (!aes_siv_decrypt(key, key_len, wrapped, wrapped_len, ad, 2,
+ unwrapped)) {
+ l_free(unwrapped);
+ return NULL;
+ }
+
+ *unwrapped_len = wrapped_len - 16;
+
+ return unwrapped;
+}
+
static size_t dpp_append_attr(uint8_t *to, enum dpp_attribute_type type,
void *attr, size_t attr_len)
{
@@ -112,6 +156,93 @@ static size_t dpp_append_attr(uint8_t *to, enum dpp_attribute_type
type,
return attr_len + 4;
}
+/*
+ * Encrypt DPP attributes encapsulated in DPP wrapped data.
+ *
+ * hdr - pointer to start of OUI, or NULL
+ * wrap_start - pointer to the start of wrapped data attribute
+ * to - buffer to encrypt data.
+ * to_len - size of 'to'
+ * key - key used to encrypt
+ * key_len - size of 'key'
+ * num_attrs - number of attributes listed (type, length, data triplets)
+ * ... - List of attributes, Type, Length, and data
+ */
+static size_t dpp_append_wrapped_data(uint8_t *hdr, uint8_t *wrap_start,
+ uint8_t *to, size_t to_len,
+ const void *key, size_t key_len,
+ size_t num_attrs, ...)
+{
+ size_t i;
+ size_t attrs_len = 0;
+ _auto_(l_free) uint8_t *plaintext = NULL;
+ uint8_t *ptr;
+ struct iovec ad[2];
+ size_t ad_size = 0;
+ va_list va;
+
+ if (hdr) {
+ ad[0].iov_base = hdr;
+ ad[0].iov_len = 6;
+ ad_size++;
+ }
+
+ va_start(va, num_attrs);
+
+ /* Count up total attributes length */
+ for (i = 0; i < num_attrs; i++) {
+ va_arg(va, enum dpp_attribute_type);
+ attrs_len += va_arg(va, size_t) + 4;
+ va_arg(va, void*);
+ }
+
+ if (to_len < attrs_len + 4 + 16)
+ return false;
+
+ plaintext = l_malloc(attrs_len);
+
+ ptr = plaintext;
+
+ va_end(va);
+
+ va_start(va, num_attrs);
+
+ /* Build up plaintext attributes */
+ for (i = 0; i < num_attrs; i++) {
+ enum dpp_attribute_type type = va_arg(va,
+ enum dpp_attribute_type);
+ size_t l = va_arg(va, size_t);
+ void *p = va_arg(va, void *);
+
+ l_put_le16(type, ptr);
+ ptr += 2;
+ l_put_le16(l, ptr);
+ ptr += 2;
+ memcpy(ptr, p, l);
+ ptr += l;
+ }
+
+ va_end(va);
+
+ ptr = to;
+
+ l_put_le16(DPP_ATTR_WRAPPED_DATA, ptr);
+ ptr += 2;
+ l_put_le16(attrs_len + 16, ptr);
+ ptr += 2;
+
+ if (wrap_start) {
+ ad[1].iov_base = wrap_start;
+ ad[1].iov_len = ptr - wrap_start - 4;
+ ad_size++;
+ }
+
+ aes_siv_encrypt(key, key_len, plaintext, attrs_len,
+ ad, ad_size, ptr);
+
+ return attrs_len + 4 + 16;
+}
+
static size_t dpp_build_header(const uint8_t *src, const uint8_t *dest,
enum dpp_frame_type type,
uint8_t buf[static 32])
@@ -136,6 +267,439 @@ static size_t dpp_build_header(const uint8_t *src, const uint8_t
*dest,
return ptr - buf;
}
+static void dpp_free_auth_data(struct dpp_sm *dpp)
+{
+ if (dpp->proto_public) {
+ l_ecc_point_free(dpp->proto_public);
+ dpp->proto_public = NULL;
+ }
+
+ if (dpp->proto_private) {
+ l_ecc_scalar_free(dpp->proto_private);
+ dpp->proto_private = NULL;
+ }
+
+ if (dpp->i_proto_public) {
+ l_ecc_point_free(dpp->i_proto_public);
+ dpp->i_proto_public = NULL;
+ }
+}
+
+static void send_authenticate_response(struct dpp_sm *dpp, void *r_auth)
+{
+ 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;
+ uint8_t r_capabilities = 0x01;
+ 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->proto_public, r_proto_key,
+ sizeof(r_proto_key));
+
+ iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
+ dpp->auth_addr,
+ DPP_FRAME_AUTHENTICATION_RESPONSE, hdr);
+ iov[0].iov_base = hdr;
+
+ ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &status, 1);
+ ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+ dpp->pub_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,
+ r_auth, dpp->key_len);
+ aes_siv_encrypt(dpp->ke, dpp->key_len, wrapped2_plaintext,
+ dpp->key_len + 4, NULL, 0, wrapped2);
+
+ wrapped2_len += 16;
+
+ /* Wrap up primary data */
+ ptr += dpp_append_wrapped_data(hdr + 26, attrs,
+ ptr, 512, 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, 1, &r_capabilities,
+ DPP_ATTR_WRAPPED_DATA, wrapped2_len, wrapped2);
+
+ iov[1].iov_base = attrs;
+ iov[1].iov_len = ptr - attrs;
+ iov[2].iov_base = NULL;
+
+ dpp_send_frame(netdev_get_wdev_id(dpp->netdev), iov, 2,
+ dpp->current_freq);
+}
+
+static void authenticate_confirm(struct dpp_sm *dpp, const uint8_t *from,
+ const uint8_t *attrs, size_t attrs_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, *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;
+
+ if (dpp->state != DPP_STATE_AUTHENTICATING)
+ return;
+
+ if (memcmp(from, dpp->auth_addr, 6))
+ return;
+
+ l_debug("authenticate confirm");
+
+ dpp_attr_iter_init(&iter, attrs, attrs_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_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(DPP_FRAME_AUTHENTICATION_CONFIRM, attrs,
+ unwrap_key, dpp->key_len, wrapped, wrapped_len,
+ &wrapped_len);
+ if (!unwrapped)
+ goto auth_confirm_failed;
+
+ if (status != DPP_STATUS_OK) {
+ /*
+ * "If unwrapping is successful, the Responder should generate
+ * an alert indicating the reason for the protocol failure."
+ */
+ l_debug("Authentication failed due to status %s",
+ status == DPP_STATUS_NOT_COMPATIBLE ?
+ "NOT_COMPATIBLE" : "AUTH_FAILURE");
+ goto auth_confirm_failed;
+ }
+
+ dpp_attr_iter_init(&iter, unwrapped, wrapped_len);
+
+ while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
+ switch (type) {
+ case DPP_ATTR_INITIATOR_AUTH_TAG:
+ i_auth = data;
+ i_auth_len = len;
+ break;
+ case DPP_ATTR_RESPONDER_NONCE:
+ /* Only if error */
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!i_auth || i_auth_len != dpp->key_len) {
+ l_debug("I-Auth missing from wrapped data");
+ goto auth_confirm_failed;
+ }
+
+ dpp_derive_i_auth(dpp->r_nonce, dpp->i_nonce, dpp->nonce_len,
+ dpp->proto_public, dpp->i_proto_public,
+ dpp->boot_public, i_auth_check);
+
+ if (memcmp(i_auth, i_auth_check, i_auth_len)) {
+ l_error("I-Auth did not verify");
+ goto auth_confirm_failed;
+ }
+
+ l_debug("Authentication successful");
+
+ 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 r_capabilities = 0x01;
+ uint8_t s = status;
+ struct iovec iov[3];
+
+ iov[0].iov_len = dpp_build_header(netdev_get_address(dpp->netdev),
+ dpp->auth_addr,
+ DPP_FRAME_AUTHENTICATION_RESPONSE, hdr);
+ iov[0].iov_base = hdr;
+
+ ptr += dpp_append_attr(ptr, DPP_ATTR_STATUS, &s, 1);
+ ptr += dpp_append_attr(ptr, DPP_ATTR_RESPONDER_BOOT_KEY_HASH,
+ dpp->pub_boot_hash, 32);
+
+ ptr += dpp_append_attr(ptr, DPP_ATTR_PROTOCOL_VERSION, &version, 1);
+
+ ptr += dpp_append_wrapped_data(hdr + 26, 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, 1, &r_capabilities);
+
+ iov[1].iov_base = attrs;
+ iov[1].iov_len = ptr - attrs;
+ iov[2].iov_base = NULL;
+
+ dpp_send_frame(netdev_get_wdev_id(dpp->netdev), iov, 2,
+ dpp->current_freq);
+}
+
+static void authenticate_request(struct dpp_sm *dpp, const uint8_t *from,
+ const uint8_t *attrs, size_t attrs_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 uint8_t *wrapped = NULL;
+ const uint8_t *i_nonce = NULL;
+ size_t r_boot_len = 0, i_proto_len = 0, wrapped_len = 0;
+ size_t i_nonce_len = 0;
+ _auto_(l_free) uint8_t *unwrapped = NULL;
+ _auto_(l_ecc_scalar_free) struct l_ecc_scalar *m = NULL;
+ _auto_(l_ecc_scalar_free) struct l_ecc_scalar *n = NULL;
+ uint64_t k1[L_ECC_MAX_DIGITS];
+ uint64_t r_auth[L_ECC_MAX_DIGITS];
+
+ if (dpp->state != DPP_STATE_PRESENCE)
+ return;
+
+ l_debug("authenticate request");
+
+ dpp_attr_iter_init(&iter, attrs, attrs_len);
+
+ 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;
+ /*
+ * TODO: Go on this channel for remainder of auth protocol.
+ *
+ * "the Responder determines whether it can use the requested
+ * channel for the following exchanges. If so, it sends the DPP
+ * Authentication Response frame on that channel. If not, it
+ * discards the DPP Authentication Request frame without
+ * replying to it."
+ *
+ * For the time being this feature is not being implemented and
+ * the frame will be dropped.
+ */
+ case DPP_ATTR_CHANNEL:
+ return;
+ default:
+ break;
+ }
+ }
+
+ if (!r_boot || !i_boot || !i_proto || !wrapped)
+ goto auth_request_failed;
+
+ if (r_boot_len != 32 || memcmp(dpp->pub_boot_hash,
+ r_boot, r_boot_len)) {
+ l_debug("Responder boot key hash failed to verify");
+ goto auth_request_failed;
+ }
+
+ dpp->i_proto_public = l_ecc_point_from_data(dpp->curve,
+ L_ECC_POINT_TYPE_FULL,
+ i_proto, i_proto_len);
+ if (!dpp->i_proto_public) {
+ l_debug("Initiators protocol key invalid");
+ goto auth_request_failed;
+ }
+
+ m = dpp_derive_k1(dpp->i_proto_public, dpp->boot_private, k1);
+ if (!m)
+ goto auth_request_failed;
+
+ unwrapped = dpp_unwrap_attr(DPP_FRAME_AUTHENTICATION_REQUEST, attrs, 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"
+ */
+ if (!(l_get_u8(data) & 0x2)) {
+ l_debug("Initiator is not configurator");
+
+ 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 %lu", i_nonce_len);
+ goto auth_request_failed;
+ }
+
+ memcpy(dpp->i_nonce, i_nonce, i_nonce_len);
+
+ /* Derive keys k2, ke, and R-Auth for authentication response */
+
+ l_ecdh_generate_key_pair(dpp->curve, &dpp->proto_private,
+ &dpp->proto_public);
+
+ n = dpp_derive_k2(dpp->i_proto_public, dpp->proto_private, dpp->k2);
+ if (!n)
+ goto auth_request_failed;
+
+ l_getrandom(dpp->r_nonce, dpp->nonce_len);
+
+ if (!dpp_derive_ke(dpp->i_nonce, dpp->r_nonce, m, n, dpp->ke))
+ goto auth_request_failed;
+
+ if (!dpp_derive_r_auth(dpp->i_nonce, dpp->r_nonce, dpp->nonce_len,
+ dpp->i_proto_public, dpp->proto_public,
+ dpp->boot_public, r_auth))
+ goto auth_request_failed;
+
+ memcpy(dpp->auth_addr, from, 6);
+
+ dpp->state = DPP_STATE_AUTHENTICATING;
+
+ send_authenticate_response(dpp, r_auth);
+
+ return;
+
+auth_request_failed:
+ dpp->state = DPP_STATE_PRESENCE;
+ dpp_free_auth_data(dpp);
+}
+
+static void dpp_handle_auth_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;
+
+ if (body_len < 8)
+ return;
+
+ ptr = body + 7;
+ body_len -= 7;
+
+ switch (*ptr) {
+ case DPP_FRAME_AUTHENTICATION_REQUEST:
+ authenticate_request(dpp, frame->address_2, ptr + 1,
+ body_len - 1);
+ break;
+ case DPP_FRAME_AUTHENTICATION_CONFIRM:
+ authenticate_confirm(dpp, frame->address_2, ptr + 1,
+ body_len - 1);
+ break;
+ default:
+ l_debug("Unhandled DPP frame %u", *ptr);
+ break;
+ }
+}
+
static void dpp_presence_announce(struct dpp_sm *dpp)
{
struct netdev *netdev = dpp->netdev;
@@ -176,6 +740,7 @@ static void dpp_create(struct netdev *netdev)
{
struct l_dbus *dbus = dbus_get_bus();
struct dpp_sm *dpp = l_new(struct dpp_sm, 1);
+ uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 };
dpp->netdev = netdev;
dpp->state = DPP_STATE_NOTHING;
@@ -183,6 +748,10 @@ static void dpp_create(struct netdev *netdev)
l_dbus_object_add_interface(dbus, netdev_get_path(netdev),
IWD_DPP_INTERFACE, dpp);
+
+ frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0, dpp_prefix,
+ sizeof(dpp_prefix), dpp_handle_auth_frame,
+ dpp, NULL);
}
static void dpp_reset(struct dpp_sm *dpp)
@@ -222,6 +791,8 @@ static void dpp_free(struct dpp_sm *dpp)
dpp->offchannel_id = 0;
}
+ dpp_free_auth_data(dpp);
+
l_free(dpp);
}
--
2.31.1