mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-12-23 06:02:37 +01:00
dpp: add support for configuration protocol
This is a minimal implementation only supporting legacy network configuration, i.e. only SSID and PSK/passphrase are supported. Missing features include: - Fragmentation/comeback delay support - DPP AKM support - 8021x/PKEX support
This commit is contained in:
parent
59a5cf3de4
commit
926c8bb9bd
334
src/dpp.c
334
src/dpp.c
@ -44,6 +44,8 @@
|
|||||||
#include "src/crypto.h"
|
#include "src/crypto.h"
|
||||||
#include "src/mpdu.h"
|
#include "src/mpdu.h"
|
||||||
#include "ell/useful.h"
|
#include "ell/useful.h"
|
||||||
|
#include "src/common.h"
|
||||||
|
#include "src/storage.h"
|
||||||
|
|
||||||
static uint32_t netdev_watch;
|
static uint32_t netdev_watch;
|
||||||
static struct l_genl_family *nl80211;
|
static struct l_genl_family *nl80211;
|
||||||
@ -53,6 +55,7 @@ enum dpp_state {
|
|||||||
DPP_STATE_NOTHING,
|
DPP_STATE_NOTHING,
|
||||||
DPP_STATE_PRESENCE,
|
DPP_STATE_PRESENCE,
|
||||||
DPP_STATE_AUTHENTICATING,
|
DPP_STATE_AUTHENTICATING,
|
||||||
|
DPP_STATE_CONFIGURING,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dpp_sm {
|
struct dpp_sm {
|
||||||
@ -84,6 +87,7 @@ struct dpp_sm {
|
|||||||
uint8_t auth_addr[6];
|
uint8_t auth_addr[6];
|
||||||
uint8_t r_nonce[32];
|
uint8_t r_nonce[32];
|
||||||
uint8_t i_nonce[32];
|
uint8_t i_nonce[32];
|
||||||
|
uint8_t e_nonce[32];
|
||||||
|
|
||||||
uint64_t ke[L_ECC_MAX_DIGITS];
|
uint64_t ke[L_ECC_MAX_DIGITS];
|
||||||
uint64_t k2[L_ECC_MAX_DIGITS];
|
uint64_t k2[L_ECC_MAX_DIGITS];
|
||||||
@ -92,6 +96,8 @@ struct dpp_sm {
|
|||||||
struct l_ecc_point *proto_public;
|
struct l_ecc_point *proto_public;
|
||||||
|
|
||||||
struct l_ecc_point *i_proto_public;
|
struct l_ecc_point *i_proto_public;
|
||||||
|
|
||||||
|
uint8_t diag_token;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void dpp_free_auth_data(struct dpp_sm *dpp)
|
static void dpp_free_auth_data(struct dpp_sm *dpp)
|
||||||
@ -203,6 +209,327 @@ static size_t dpp_build_header(const uint8_t *src, const uint8_t *dest,
|
|||||||
return ptr - buf;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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->wdev_id, 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, 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->wdev_id, iov, 2, dpp->current_freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dpp_write_config(struct dpp_configuration *config)
|
||||||
|
{
|
||||||
|
_auto_(l_free) char *ssid = l_malloc(config->ssid_len + 1);
|
||||||
|
_auto_(l_settings_free) struct l_settings *settings = l_settings_new();
|
||||||
|
_auto_(l_free) char *path;
|
||||||
|
|
||||||
|
memcpy(ssid, config->ssid, config->ssid_len);
|
||||||
|
ssid[config->ssid_len] = '\0';
|
||||||
|
|
||||||
|
path = storage_get_network_file_path(SECURITY_PSK, ssid);
|
||||||
|
|
||||||
|
if (l_settings_load_from_file(settings, path)) {
|
||||||
|
/* Remove any existing Security keys */
|
||||||
|
l_settings_remove_group(settings, "Security");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->passphrase)
|
||||||
|
l_settings_set_string(settings, "Security", "Passphrase",
|
||||||
|
config->passphrase);
|
||||||
|
else if (config->psk)
|
||||||
|
l_settings_set_string(settings, "Security", "PreSharedKey",
|
||||||
|
config->psk);
|
||||||
|
|
||||||
|
l_debug("Storing credential for '%s(%s)'", ssid,
|
||||||
|
security_to_str(SECURITY_PSK));
|
||||||
|
storage_network_sync(SECURITY_PSK, ssid, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
uint8_t ad0[] = { 0x00, 0x10, 0x01, 0x00, 0x05 };
|
||||||
|
|
||||||
|
if (dpp->state != DPP_STATE_CONFIGURING)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Can a configuration request come from someone other than who you
|
||||||
|
* authenticated to?
|
||||||
|
*/
|
||||||
|
if (memcmp(dpp->auth_addr, frame->address_2, 6))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (body_len < 19)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
if (*ptr++ != dpp->diag_token)
|
||||||
|
return;
|
||||||
|
|
||||||
|
status = l_get_le16(ptr);
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
if (status != 0) {
|
||||||
|
l_debug("Bad configuration status %u", status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragmented = l_get_le16(ptr);
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: handle 0x0001 (fragmented), as well as comeback delay.
|
||||||
|
*/
|
||||||
|
if (fragmented != 0) {
|
||||||
|
l_debug("Fragmented messages not currently supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(ptr, adv_protocol_element, sizeof(adv_protocol_element))) {
|
||||||
|
l_debug("Invalid Advertisement protocol element");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += sizeof(adv_protocol_element);
|
||||||
|
|
||||||
|
if (memcmp(ptr, adv_protocol_id, sizeof(adv_protocol_id))) {
|
||||||
|
l_debug("Invalid Advertisement protocol ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += sizeof(adv_protocol_id);
|
||||||
|
|
||||||
|
query_len = l_get_le16(ptr);
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
if (query_len > body_len - 19)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dpp_attr_iter_init(&iter, ptr, query_len);
|
||||||
|
|
||||||
|
while (dpp_attr_iter_next(&iter, &type, &len, &data)) {
|
||||||
|
switch (type) {
|
||||||
|
case DPP_ATTR_STATUS:
|
||||||
|
dstatus = l_get_u8(data);
|
||||||
|
break;
|
||||||
|
case DPP_ATTR_WRAPPED_DATA:
|
||||||
|
wrapped = data;
|
||||||
|
wrapped_len = len;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
* TODO: CSR Attribute
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dstatus != DPP_STATUS_OK || !wrapped) {
|
||||||
|
l_debug("Bad status or missing attributes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unwrapped = dpp_unwrap_attr(ad0, sizeof(ad0), 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
dpp_write_config(config);
|
||||||
|
/*
|
||||||
|
* TODO: Depending on the info included in the configuration object a
|
||||||
|
* limited scan could be issued to get autoconnect to trigger faster.
|
||||||
|
* In addition this network may already be in past scan results and
|
||||||
|
* could be joined immediately.
|
||||||
|
*
|
||||||
|
* For now just wait for autoconnect.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dpp_configuration_free(config);
|
||||||
|
|
||||||
|
send_config_result(dpp, dpp->auth_addr);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The Authentication protocol has a consistent use of AD components, and this
|
* The Authentication protocol has a consistent use of AD components, and this
|
||||||
* use is defined in 6.3.1.4:
|
* use is defined in 6.3.1.4:
|
||||||
@ -407,6 +734,8 @@ static void authenticate_confirm(struct dpp_sm *dpp, const uint8_t *from,
|
|||||||
|
|
||||||
l_debug("Authentication successful");
|
l_debug("Authentication successful");
|
||||||
|
|
||||||
|
dpp_configuration_start(dpp, from);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auth_confirm_failed:
|
auth_confirm_failed:
|
||||||
@ -693,6 +1022,7 @@ static void dpp_create(struct netdev *netdev)
|
|||||||
struct l_dbus *dbus = dbus_get_bus();
|
struct l_dbus *dbus = dbus_get_bus();
|
||||||
struct dpp_sm *dpp = l_new(struct dpp_sm, 1);
|
struct dpp_sm *dpp = l_new(struct dpp_sm, 1);
|
||||||
uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 };
|
uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 };
|
||||||
|
uint8_t dpp_conf_response_prefix[] = { 0x04, 0x0b };
|
||||||
|
|
||||||
dpp->netdev = netdev;
|
dpp->netdev = netdev;
|
||||||
dpp->state = DPP_STATE_NOTHING;
|
dpp->state = DPP_STATE_NOTHING;
|
||||||
@ -715,6 +1045,10 @@ static void dpp_create(struct netdev *netdev)
|
|||||||
frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0, dpp_prefix,
|
frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0, dpp_prefix,
|
||||||
sizeof(dpp_prefix), dpp_handle_auth_frame,
|
sizeof(dpp_prefix), dpp_handle_auth_frame,
|
||||||
dpp, NULL);
|
dpp, NULL);
|
||||||
|
frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0,
|
||||||
|
dpp_conf_response_prefix,
|
||||||
|
sizeof(dpp_conf_response_prefix),
|
||||||
|
dpp_handle_config_response_frame, dpp, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dpp_netdev_watch(struct netdev *netdev,
|
static void dpp_netdev_watch(struct netdev *netdev,
|
||||||
|
Loading…
Reference in New Issue
Block a user