net: lib: add wifi_credentials library

Upstream NCS's library for storing Wi-Fi credentials.
This library allows storage of Wi-Fi credentials
using different backends.
Either the Zephyr settings subsystem
or the PSA secure backend can be used.
For testing purposes, credentials can be defined statically.

Signed-off-by: Ravi Dondaputi <ravi.dondaputi@nordicsemi.no>
Signed-off-by: Kapil Bhatt <kapil.bhatt@nordicsemi.no>
Signed-off-by: Gregers Gram Rygg <gregers.gram.rygg@nordicsemi.no>
Signed-off-by: Kaja Koren <kaja.koren@nordicsemi.no>
Signed-off-by: Simen S. Røstad <simen.rostad@nordicsemi.no>
Signed-off-by: Maximilian Deubel <maximilian.deubel@nordicsemi.no>
This commit is contained in:
Maximilian Deubel 2024-09-05 12:02:01 +02:00 committed by Anas Nashif
parent b758e205b2
commit f6d305a529
12 changed files with 1457 additions and 1 deletions

View file

@ -0,0 +1,218 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef WIFI_CREDENTIALS_H__
#define WIFI_CREDENTIALS_H__
#include <zephyr/types.h>
#include <zephyr/net/wifi.h>
#include <zephyr/kernel.h>
/**
* @defgroup wifi_credentials Wi-Fi credentials library
* @ingroup networking
* @since 4.0
* @version 0.1.0
* @{
* @brief Library that provides a way to store and load Wi-Fi credentials.
*/
#ifdef __cplusplus
extern "C" {
#endif
/* this entry contains a BSSID */
#define WIFI_CREDENTIALS_FLAG_BSSID BIT(0)
/* this entry is to be preferred over others */
#define WIFI_CREDENTIALS_FLAG_FAVORITE BIT(1)
/* this entry can use the 2.4 GHz band */
#define WIFI_CREDENTIALS_FLAG_2_4GHz BIT(2)
/* this entry can use the 5 GHz band */
#define WIFI_CREDENTIALS_FLAG_5GHz BIT(3)
/* this entry requires management frame protection */
#define WIFI_CREDENTIALS_FLAG_MFP_REQUIRED BIT(4)
/* this entry disables management frame protection */
#define WIFI_CREDENTIALS_FLAG_MFP_DISABLED BIT(5)
#define WIFI_CREDENTIALS_MAX_PASSWORD_LEN \
MAX(WIFI_PSK_MAX_LEN, CONFIG_WIFI_CREDENTIALS_SAE_PASSWORD_LENGTH)
/**
* @brief Wi-Fi credentials entry header
* @note Every settings entry starts with this header.
* Depending on the `type` field, the header can be casted to a larger type.
* In addition to SSID (usually a string) and BSSID (a MAC address),
* a `flags` field can be used to control some detail settings.
*
*/
struct wifi_credentials_header {
enum wifi_security_type type; /**< Wi-Fi security type */
char ssid[WIFI_SSID_MAX_LEN]; /**< SSID (Service Set Identifier) */
size_t ssid_len; /**< Length of the SSID */
uint32_t flags; /**< Flags for controlling detail settings */
uint32_t timeout; /**< Timeout for connecting to the network */
uint8_t bssid[WIFI_MAC_ADDR_LEN]; /**< BSSID (Basic Service Set Identifier) */
uint8_t channel; /**< Channel on which the network operates */
};
/**
* @brief Wi-Fi Personal credentials entry
* @note Contains only the header and a password.
* For PSK security, passwords can be up to `WIFI_PSK_MAX_LEN` bytes long
* including NULL termination. For SAE security it can range up to
* `CONFIG_WIFI_CREDENTIALS_SAE_PASSWORD_LENGTH`.
*
*/
struct wifi_credentials_personal {
struct wifi_credentials_header header; /**< Header */
char password[WIFI_CREDENTIALS_MAX_PASSWORD_LEN]; /**< Password/PSK */
size_t password_len; /**< Length of the password */
};
/**
* @brief Wi-Fi Enterprise credentials entry
* @note This functionality is not yet implemented.
*/
struct wifi_credentials_enterprise {
struct wifi_credentials_header header; /**< Header */
size_t identity_len; /**< Length of the identity */
size_t anonymous_identity_len; /**< Length of the anonymous identity */
size_t password_len; /**< Length of the password */
size_t ca_cert_len; /**< Length of the CA certificate */
size_t client_cert_len; /**< Length of the client certificate */
size_t private_key_len; /**< Length of the private key */
size_t private_key_pw_len; /**< Length of the private key password */
};
/**
* @brief Get credentials for given SSID.
*
* @param[in] ssid SSID to look for
* @param[in] ssid_len length of SSID
* @param[out] type Wi-Fi security type
* @param[out] bssid_buf buffer to store BSSID if it was fixed
* @param[in] bssid_buf_len length of bssid_buf
* @param[out] password_buf buffer to store password
* @param[in] password_buf_len length of password_buf
* @param[out] password_len length of password
* @param[out] flags flags
* @param[out] channel channel
* @param[out] timeout timeout
*
* @return 0 Success.
* @return -ENOENT No network with this SSID was found.
* @return -EINVAL A required buffer was NULL or invalid SSID length.
* @return -EPROTO The network with this SSID is not a personal network.
*/
int wifi_credentials_get_by_ssid_personal(const char *ssid, size_t ssid_len,
enum wifi_security_type *type, uint8_t *bssid_buf,
size_t bssid_buf_len, char *password_buf,
size_t password_buf_len, size_t *password_len,
uint32_t *flags, uint8_t *channel, uint32_t *timeout);
/**
* @brief Set credentials for given SSID.
*
* @param[in] ssid SSID to look for
* @param[in] ssid_len length of SSID
* @param[in] type Wi-Fi security type
* @param[in] bssid BSSID (may be NULL)
* @param[in] bssid_len length of BSSID buffer (either 0 or WIFI_MAC_ADDR_LEN)
* @param[in] password password
* @param[in] password_len length of password
* @param[in] flags flags
* @param[in] channel Channel
* @param[in] timeout Timeout
*
* @return 0 Success. Credentials are stored in persistent storage.
* @return -EINVAL A required buffer was NULL or security type is not supported.
* @return -ENOTSUP Security type is not supported.
* @return -ENOBUFS All slots are already taken.
*/
int wifi_credentials_set_personal(const char *ssid, size_t ssid_len, enum wifi_security_type type,
const uint8_t *bssid, size_t bssid_len, const char *password,
size_t password_len, uint32_t flags, uint8_t channel,
uint32_t timeout);
/**
* @brief Get credentials for given SSID by struct.
*
* @param[in] ssid SSID to look for
* @param[in] ssid_len length of SSID
* @param[out] buf credentials Pointer to struct where credentials are stored
*
* @return 0 Success.
* @return -ENOENT No network with this SSID was found.
* @return -EINVAL A required buffer was NULL or too small.
* @return -EPROTO The network with this SSID is not a personal network.
*/
int wifi_credentials_get_by_ssid_personal_struct(const char *ssid, size_t ssid_len,
struct wifi_credentials_personal *buf);
/**
* @brief Set credentials for given SSID by struct.
*
* @param[in] creds credentials Pointer to struct from which credentials are loaded
*
* @return 0 Success.
* @return -ENOENT No network with this SSID was found.
* @return -EINVAL A required buffer was NULL or incorrect size.
* @return -ENOBUFS All slots are already taken.
*/
int wifi_credentials_set_personal_struct(const struct wifi_credentials_personal *creds);
/**
* @brief Delete credentials for given SSID.
*
* @param[in] ssid SSID to look for
* @param[in] ssid_len length of SSID
*
* @return -ENOENT if No network with this SSID was found.
* @return 0 on success, otherwise a negative error code
*/
int wifi_credentials_delete_by_ssid(const char *ssid, size_t ssid_len);
/**
* @brief Check if credentials storage is empty.
*
* @return true if credential storage is empty, otherwise false
*/
bool wifi_credentials_is_empty(void);
/**
* @brief Deletes all stored Wi-Fi credentials.
*
* This function deletes all Wi-Fi credentials that have been stored in the system.
* It is typically used when you want to clear all saved networks.
*
* @return 0 on successful, otherwise a negative error code
*/
int wifi_credentials_delete_all(void);
/**
* @brief Callback type for wifi_credentials_for_each_ssid.
* @param[in] cb_arg arguments for the callback function. Appropriate cb_arg is
* transferred by wifi_credentials_for_each_ssid.
* @param[in] ssid SSID
* @param[in] ssid_len length of SSID
*/
typedef void (*wifi_credentials_ssid_cb)(void *cb_arg, const char *ssid, size_t ssid_len);
/**
* @brief Call callback for each registered SSID.
*
* @param cb callback
* @param cb_arg argument for callback function
*/
void wifi_credentials_for_each_ssid(wifi_credentials_ssid_cb cb, void *cb_arg);
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* WIFI_CREDENTIALS_H__ */

View file

@ -42,7 +42,13 @@ tests:
integration_platforms:
- frdm_k64f
sample.net.wifi.nrf70dk:
extra_args: CONFIG_NRF_WIFI_BUILD_ONLY_MODE=y
extra_args:
- CONFIG_NRF_WIFI_BUILD_ONLY_MODE=y
- CONFIG_WIFI_CREDENTIALS=y
- CONFIG_FLASH=y
- CONFIG_FLASH_MAP=y
- CONFIG_NVS=y
- CONFIG_SETTINGS=y
platform_allow:
- nrf7002dk/nrf5340/cpuapp
- nrf7002dk/nrf5340/cpuapp/nrf7001

View file

@ -18,6 +18,7 @@ add_subdirectory_ifdef(CONFIG_NET_SHELL shell)
add_subdirectory_ifdef(CONFIG_NET_TRICKLE trickle)
add_subdirectory_ifdef(CONFIG_NET_DHCPV6 dhcpv6)
add_subdirectory_ifdef(CONFIG_PROMETHEUS prometheus)
add_subdirectory_ifdef(CONFIG_WIFI_CREDENTIALS wifi_credentials)
if (CONFIG_NET_DHCPV4 OR CONFIG_NET_DHCPV4_SERVER)
add_subdirectory(dhcpv4)

View file

@ -51,4 +51,6 @@ source "subsys/net/lib/zperf/Kconfig"
source "subsys/net/lib/prometheus/Kconfig"
source "subsys/net/lib/wifi_credentials/Kconfig"
endmenu

View file

@ -0,0 +1,36 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#
zephyr_library_named(wifi_credentials)
zephyr_library_sources(wifi_credentials.c)
if (CONFIG_WIFI_CREDENTIALS_BACKEND_PSA)
zephyr_library_include_directories(
$<TARGET_PROPERTY:tfm,TFM_BINARY_DIR>/api_ns/interface/include
)
endif()
zephyr_library_sources_ifdef(
CONFIG_WIFI_CREDENTIALS_BACKEND_SETTINGS
wifi_credentials_backend_settings.c)
zephyr_library_sources_ifdef(
CONFIG_WIFI_CREDENTIALS_BACKEND_PSA
wifi_credentials_backend_psa.c)
zephyr_library_sources_ifdef(
CONFIG_WIFI_CREDENTIALS_BACKEND_NONE
wifi_credentials_backend_none.c)
zephyr_library_sources_ifdef(
CONFIG_WIFI_CREDENTIALS_SHELL
wifi_credentials_shell.c)
if(WIFI_CREDENTIALS_STATIC_SSID)
message(WARNING
"Static Wi-Fi configuration is used, please remove before deployment!"
)
endif()

View file

@ -0,0 +1,109 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig WIFI_CREDENTIALS
bool "WIFI credentials management"
select EXPERIMENTAL
help
Enable WiFi credentials management subsystem.
if WIFI_CREDENTIALS
module = WIFI_CREDENTIALS
module-str = wifi_credentials
source "subsys/logging/Kconfig.template.log_config"
choice WIFI_CREDENTIALS_BACKEND
prompt "WiFi credentials backend"
default WIFI_CREDENTIALS_BACKEND_PSA if BUILD_WITH_TFM
default WIFI_CREDENTIALS_BACKEND_SETTINGS
default WIFI_CREDENTIALS_BACKEND_NONE if WIFI_CREDENTIALS_STATIC
help
Selects whether to use PSA Protected Storage or the Zephyr settings subsystem
for credentials storage.
config WIFI_CREDENTIALS_BACKEND_SETTINGS
bool "Zephyr Settings"
depends on SETTINGS
depends on !SETTINGS_NONE
config WIFI_CREDENTIALS_BACKEND_PSA
bool "PSA Protected Storage"
depends on BUILD_WITH_TFM
config WIFI_CREDENTIALS_BACKEND_NONE
bool "No credentials storage"
depends on WIFI_CREDENTIALS_STATIC
endchoice
config WIFI_CREDENTIALS_MAX_ENTRIES
int "Number of supported WiFi credentials"
default 2
help
This detemines how many different WiFi networks can be configured at a time.
config WIFI_CREDENTIALS_SAE_PASSWORD_LENGTH
int "Max. length of SAE password"
default 128
help
There is no official limit on SAE password length,
but for example Linux 6.0 has a hardcoded limit of 128 bytes.
config WIFI_CREDENTIALS_SHELL
bool "Shell commands to manage Wi-Fi credentials"
default y
depends on SHELL
depends on !WIFI_CREDENTIALS_BACKEND_NONE
endif # WIFI_CREDENTIALS
if WIFI_CREDENTIALS_BACKEND_PSA
config WIFI_CREDENTIALS_BACKEND_PSA_OFFSET
int "PSA_KEY_ID range offset"
default 0
help
The PSA specification mandates to set key identifiers for keys
with persistent lifetime. The users of the PSA API are responsible (WIFI credentials
management is user of PSA API) to provide correct and unique identifiers.
endif # WIFI_CREDENTIALS_BACKEND_PSA
config WIFI_CREDENTIALS_STATIC
bool "Static Wi-Fi network configuration"
if WIFI_CREDENTIALS_STATIC
config WIFI_CREDENTIALS_STATIC_SSID
string "SSID of statically configured WiFi network"
config WIFI_CREDENTIALS_STATIC_PASSWORD
string "Password of statically configured Wi-Fi network"
default ""
choice WIFI_CREDENTIALS_STATIC_TYPE
prompt "Static Wi-Fi network security type"
default WIFI_CREDENTIALS_STATIC_TYPE_PSK
config WIFI_CREDENTIALS_STATIC_TYPE_OPEN
bool "OPEN"
config WIFI_CREDENTIALS_STATIC_TYPE_PSK
bool "WPA2-PSK"
config WIFI_CREDENTIALS_STATIC_TYPE_PSK_SHA256
bool "WPA2-PSK-SHA256"
config WIFI_CREDENTIALS_STATIC_TYPE_SAE
bool "SAE"
config WIFI_CREDENTIALS_STATIC_TYPE_WPA_PSK
bool "WPA-PSK"
endchoice
endif

View file

@ -0,0 +1,409 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/net/wifi_credentials.h>
#include <sys/types.h>
#include "wifi_credentials_internal.h"
LOG_MODULE_REGISTER(wifi_credentials, CONFIG_WIFI_CREDENTIALS_LOG_LEVEL);
static K_MUTEX_DEFINE(wifi_credentials_mutex);
/* SSID cache: maps SSIDs to their storage indices */
static char ssid_cache[CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES][WIFI_SSID_MAX_LEN];
static size_t ssid_cache_lengths[CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES];
/**
* @brief Finds index of given SSID if it exists.
*
* @param ssid SSID to look for (buffer of WIFI_SSID_MAX_LEN length)
* @return index if entry is found, -1 otherwise
*/
static inline ssize_t lookup_idx(const uint8_t *ssid, size_t ssid_len)
{
for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
if (ssid_len != ssid_cache_lengths[i]) {
continue;
}
if (strncmp(ssid, ssid_cache[i], ssid_len) == 0) {
return i;
}
}
return -1;
}
/**
* @brief Determine whether an index is currently used for storing network credentials.
*
* @param idx credential index
* @return true if index is used, false otherwise
*/
static inline bool is_entry_used(size_t idx)
{
return ssid_cache_lengths[idx] != 0;
}
/**
* @brief Finds unused index to store new entry at.
*
* @return index if empty slot is found, -1 otherwise
*/
static inline ssize_t lookup_unused_idx(void)
{
for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
if (!is_entry_used(i)) {
return i;
}
}
return -1;
}
static int init(void)
{
int ret;
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
ret = wifi_credentials_backend_init();
if (ret) {
LOG_ERR("Initializing WiFi credentials storage backend failed, err: %d", ret);
}
k_mutex_unlock(&wifi_credentials_mutex);
return 0;
}
void wifi_credentials_cache_ssid(size_t idx, const struct wifi_credentials_header *buf)
{
memcpy(ssid_cache[idx], buf->ssid, buf->ssid_len);
ssid_cache_lengths[idx] = buf->ssid_len;
}
/**
* @brief Clear entry in SSID cache.
*
* @param idx credential index
*/
void wifi_credentials_uncache_ssid(size_t idx)
{
ssid_cache_lengths[idx] = 0;
}
int wifi_credentials_get_by_ssid_personal_struct(const char *ssid, size_t ssid_len,
struct wifi_credentials_personal *buf)
{
int ret;
if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
LOG_ERR("Cannot retrieve WiFi credentials, SSID has invalid format");
return -EINVAL;
}
if (buf == NULL) {
LOG_ERR("Cannot retrieve WiFi credentials, "
"destination struct pointer cannot be NULL");
return -EINVAL;
}
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
ssize_t idx = lookup_idx(ssid, ssid_len);
if (idx == -1) {
LOG_DBG("Cannot retrieve WiFi credentials, no entry found for the provided SSID");
ret = -ENOENT;
goto exit;
}
ret = wifi_credentials_load_entry(idx, buf, sizeof(struct wifi_credentials_personal));
if (ret) {
LOG_ERR("Failed to load WiFi credentials at index %d, err: %d", idx, ret);
goto exit;
}
if (buf->header.type != WIFI_SECURITY_TYPE_NONE &&
buf->header.type != WIFI_SECURITY_TYPE_PSK &&
buf->header.type != WIFI_SECURITY_TYPE_PSK_SHA256 &&
buf->header.type != WIFI_SECURITY_TYPE_SAE &&
buf->header.type != WIFI_SECURITY_TYPE_WPA_PSK) {
LOG_ERR("Requested WiFi credentials entry is corrupted");
ret = -EPROTO;
goto exit;
}
exit:
k_mutex_unlock(&wifi_credentials_mutex);
return ret;
}
int wifi_credentials_set_personal_struct(const struct wifi_credentials_personal *creds)
{
int ret;
if (creds->header.ssid_len > WIFI_SSID_MAX_LEN || creds->header.ssid_len == 0) {
LOG_ERR("Cannot set WiFi credentials, SSID has invalid format");
return -EINVAL;
}
if (creds == NULL) {
LOG_ERR("Cannot set WiFi credentials, provided struct pointer cannot be NULL");
return -EINVAL;
}
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
ssize_t idx = lookup_idx(creds->header.ssid, creds->header.ssid_len);
if (idx == -1) {
idx = lookup_unused_idx();
if (idx == -1) {
LOG_ERR("Cannot store WiFi credentials, no space left");
ret = -ENOBUFS;
goto exit;
}
}
ret = wifi_credentials_store_entry(idx, creds, sizeof(struct wifi_credentials_personal));
if (ret) {
LOG_ERR("Failed to store WiFi credentials at index %d, err: %d", idx, ret);
goto exit;
}
wifi_credentials_cache_ssid(idx, &creds->header);
exit:
k_mutex_unlock(&wifi_credentials_mutex);
return ret;
}
int wifi_credentials_set_personal(const char *ssid, size_t ssid_len, enum wifi_security_type type,
const uint8_t *bssid, size_t bssid_len, const char *password,
size_t password_len, uint32_t flags, uint8_t channel,
uint32_t timeout)
{
int ret = 0;
uint8_t buf[ENTRY_MAX_LEN] = {0};
if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
LOG_ERR("Cannot set WiFi credentials, SSID has invalid format");
return -EINVAL;
}
if (flags & WIFI_CREDENTIALS_FLAG_BSSID &&
(bssid_len != WIFI_MAC_ADDR_LEN || bssid == NULL)) {
LOG_ERR("Cannot set WiFi credentials, "
"provided flags indicated BSSID, but no BSSID provided");
return -EINVAL;
}
if ((type != WIFI_SECURITY_TYPE_NONE && (password_len == 0 || password == NULL)) ||
(password_len > WIFI_CREDENTIALS_MAX_PASSWORD_LEN)) {
LOG_ERR("Cannot set WiFi credentials, password not provided or invalid");
return -EINVAL;
}
/* pack entry */
struct wifi_credentials_header *header = (struct wifi_credentials_header *)buf;
header->type = type;
memcpy(header->ssid, ssid, ssid_len);
header->ssid_len = ssid_len;
header->flags = flags;
header->channel = channel;
header->timeout = timeout;
if (flags & WIFI_CREDENTIALS_FLAG_BSSID) {
memcpy(header->bssid, bssid, WIFI_MAC_ADDR_LEN);
}
switch (type) {
case WIFI_SECURITY_TYPE_NONE:
break;
case WIFI_SECURITY_TYPE_PSK:
case WIFI_SECURITY_TYPE_PSK_SHA256:
case WIFI_SECURITY_TYPE_WPA_PSK:
case WIFI_SECURITY_TYPE_SAE: {
struct wifi_credentials_personal *header_personal =
(struct wifi_credentials_personal *)buf;
memcpy(header_personal->password, password, password_len);
header_personal->password_len = password_len;
break;
}
default:
LOG_ERR("Cannot set WiFi credentials, "
"provided security type %d is unsupported",
type);
return -ENOTSUP;
}
/* store entry */
ret = wifi_credentials_set_personal_struct((struct wifi_credentials_personal *)buf);
return ret;
}
int wifi_credentials_get_by_ssid_personal(const char *ssid, size_t ssid_len,
enum wifi_security_type *type, uint8_t *bssid_buf,
size_t bssid_buf_len, char *password_buf,
size_t password_buf_len, size_t *password_len,
uint32_t *flags, uint8_t *channel, uint32_t *timeout)
{
int ret = 0;
uint8_t buf[ENTRY_MAX_LEN] = {0};
if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
LOG_ERR("Cannot retrieve WiFi credentials, SSID has invalid format");
return -EINVAL;
}
if (bssid_buf_len != WIFI_MAC_ADDR_LEN || bssid_buf == NULL) {
LOG_ERR("BSSID buffer needs to be provided");
return -EINVAL;
}
if (password_buf == NULL || password_buf_len > WIFI_CREDENTIALS_MAX_PASSWORD_LEN ||
password_buf_len == 0) {
LOG_ERR("WiFi password buffer needs to be provided");
return -EINVAL;
}
/* load entry */
ret = wifi_credentials_get_by_ssid_personal_struct(ssid, ssid_len,
(struct wifi_credentials_personal *)buf);
if (ret) {
return ret;
}
/* unpack entry*/
struct wifi_credentials_header *header = (struct wifi_credentials_header *)buf;
*type = header->type;
*flags = header->flags;
*channel = header->channel;
*timeout = header->timeout;
if (header->flags & WIFI_CREDENTIALS_FLAG_BSSID) {
memcpy(bssid_buf, header->bssid, WIFI_MAC_ADDR_LEN);
}
switch (header->type) {
case WIFI_SECURITY_TYPE_NONE:
break;
case WIFI_SECURITY_TYPE_PSK:
case WIFI_SECURITY_TYPE_PSK_SHA256:
case WIFI_SECURITY_TYPE_WPA_PSK:
case WIFI_SECURITY_TYPE_SAE: {
struct wifi_credentials_personal *header_personal =
(struct wifi_credentials_personal *)buf;
memcpy(password_buf, header_personal->password, header_personal->password_len);
*password_len = header_personal->password_len;
break;
}
default:
LOG_ERR("Cannot get WiFi credentials, "
"the requested credentials have invalid WIFI_SECURITY_TYPE");
ret = -EPROTO;
}
return ret;
}
int wifi_credentials_delete_by_ssid(const char *ssid, size_t ssid_len)
{
int ret = 0;
if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
LOG_ERR("Cannot delete WiFi credentials, SSID has invalid format");
return -EINVAL;
}
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
ssize_t idx = lookup_idx(ssid, ssid_len);
if (idx == -1) {
LOG_DBG("WiFi credentials entry was not found");
goto exit;
}
ret = wifi_credentials_delete_entry(idx);
if (ret) {
LOG_ERR("Failed to delete WiFi credentials index %d, err: %d", idx, ret);
goto exit;
}
wifi_credentials_uncache_ssid(idx);
exit:
k_mutex_unlock(&wifi_credentials_mutex);
return ret;
}
void wifi_credentials_for_each_ssid(wifi_credentials_ssid_cb cb, void *cb_arg)
{
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
if (is_entry_used(i)) {
cb(cb_arg, ssid_cache[i], ssid_cache_lengths[i]);
}
}
k_mutex_unlock(&wifi_credentials_mutex);
}
bool wifi_credentials_is_empty(void)
{
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
if (is_entry_used(i)) {
k_mutex_unlock(&wifi_credentials_mutex);
return false;
}
}
k_mutex_unlock(&wifi_credentials_mutex);
return true;
}
int wifi_credentials_delete_all(void)
{
int ret = 0;
k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
if (is_entry_used(i)) {
ret = wifi_credentials_delete_entry(i);
if (ret) {
LOG_ERR("Failed to delete WiFi credentials index %d, err: %d", i,
ret);
break;
}
wifi_credentials_uncache_ssid(i);
}
}
k_mutex_unlock(&wifi_credentials_mutex);
return ret;
}
SYS_INIT(init, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY);

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include "wifi_credentials_internal.h"
int wifi_credentials_store_entry(size_t idx, const void *buf, size_t buf_len)
{
ARG_UNUSED(idx);
ARG_UNUSED(buf);
ARG_UNUSED(buf_len);
return 0;
}
int wifi_credentials_delete_entry(size_t idx)
{
ARG_UNUSED(idx);
return 0;
}
int wifi_credentials_load_entry(size_t idx, void *buf, size_t buf_len)
{
ARG_UNUSED(idx);
ARG_UNUSED(buf);
ARG_UNUSED(buf_len);
return 0;
}
int wifi_credentials_backend_init(void)
{
return 0;
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "psa/crypto.h"
#include "wifi_credentials_internal.h"
LOG_MODULE_REGISTER(wifi_credentials_backend, CONFIG_WIFI_CREDENTIALS_LOG_LEVEL);
#define WIFI_CREDENTIALS_BACKEND_PSA_KEY_ID_USER_MIN \
(PSA_KEY_ID_USER_MIN + CONFIG_WIFI_CREDENTIALS_BACKEND_PSA_OFFSET)
BUILD_ASSERT((WIFI_CREDENTIALS_BACKEND_PSA_KEY_ID_USER_MIN + CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES) <=
PSA_KEY_ID_USER_MAX,
"WIFI credentials management PSA key id range exceeds PSA_KEY_ID_USER_MAX.");
int wifi_credentials_backend_init(void)
{
psa_status_t ret;
uint8_t buf[ENTRY_MAX_LEN];
for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
size_t length_read = 0;
size_t key_id = i + WIFI_CREDENTIALS_BACKEND_PSA_KEY_ID_USER_MIN;
ret = psa_export_key(key_id, buf, ARRAY_SIZE(buf), &length_read);
if (ret == PSA_SUCCESS && length_read == ENTRY_MAX_LEN) {
wifi_credentials_cache_ssid(i, (struct wifi_credentials_header *)buf);
} else if (ret != PSA_ERROR_INVALID_HANDLE) {
LOG_ERR("psa_export_key failed, err: %d", ret);
return -EFAULT;
}
}
return 0;
}
int wifi_credentials_store_entry(size_t idx, const void *buf, size_t buf_len)
{
psa_status_t ret;
psa_key_attributes_t key_attributes = {0};
psa_key_id_t key_id;
psa_set_key_id(&key_attributes, idx + WIFI_CREDENTIALS_BACKEND_PSA_KEY_ID_USER_MIN);
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_EXPORT);
psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_algorithm(&key_attributes, PSA_ALG_NONE);
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_RAW_DATA);
psa_set_key_bits(&key_attributes, buf_len * 8);
ret = psa_import_key(&key_attributes, buf, buf_len, &key_id);
if (ret == PSA_ERROR_ALREADY_EXISTS) {
LOG_ERR("psa_import_key failed, duplicate key: %d", ret);
return -EEXIST;
} else if (ret != PSA_SUCCESS) {
LOG_ERR("psa_import_key failed, err: %d", ret);
return -EFAULT;
}
return 0;
}
int wifi_credentials_delete_entry(size_t idx)
{
psa_status_t ret = psa_destroy_key(idx + WIFI_CREDENTIALS_BACKEND_PSA_KEY_ID_USER_MIN);
if (ret != PSA_SUCCESS) {
LOG_ERR("psa_destroy_key failed, err: %d", ret);
return -EFAULT;
}
return 0;
}
int wifi_credentials_load_entry(size_t idx, void *buf, size_t buf_len)
{
size_t length_read = 0;
size_t key_id = idx + WIFI_CREDENTIALS_BACKEND_PSA_KEY_ID_USER_MIN;
psa_status_t ret;
ret = psa_export_key(key_id, buf, buf_len, &length_read);
if (ret != PSA_SUCCESS) {
LOG_ERR("psa_export_key failed, err: %d", ret);
return -EFAULT;
}
if (buf_len != length_read) {
return -EIO;
}
return 0;
}

View file

@ -0,0 +1,166 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <stdlib.h>
#include <zephyr/settings/settings.h>
#include "wifi_credentials_internal.h"
LOG_MODULE_REGISTER(wifi_credentials_backend, CONFIG_WIFI_CREDENTIALS_LOG_LEVEL);
BUILD_ASSERT(ENTRY_MAX_LEN <= SETTINGS_MAX_VAL_LEN);
#define WIFI_CREDENTIALS_SBE_BASE_KEY "wifi_cred"
#define WIFI_CREDENTIALS_SBE_KEY_SIZE \
sizeof(WIFI_CREDENTIALS_SBE_BASE_KEY "/" STRINGIFY(CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES))
#define WIFI_CREDENTIALS_SBE_KEY_FMT WIFI_CREDENTIALS_SBE_BASE_KEY "/%d"
/* Type of the callback argument used in the function below. */
struct zephyr_settings_backend_load_cb_arg {
uint8_t *buf;
size_t buf_len;
size_t idx;
bool found;
};
/* This callback function is used to retrieve credentials on demand. */
static int zephyr_settings_backend_load_val_cb(const char *key, size_t len,
settings_read_cb read_cb, void *cb_arg, void *param)
{
struct zephyr_settings_backend_load_cb_arg *arg = param;
int idx = atoi(key);
if (arg->idx != idx) {
LOG_DBG("Skipping index [%s]", key);
return 0;
}
if (len != arg->buf_len) {
LOG_ERR("Settings error: invalid settings length");
return -EINVAL;
}
size_t length_read = read_cb(cb_arg, arg->buf, arg->buf_len);
/* value validation */
if (length_read < len) {
LOG_ERR("Settings error: entry incomplete");
return -ENODATA;
}
arg->found = true;
return 0;
}
/* This callback function is used to initialize the SSID cache. */
static int zephyr_settings_backend_load_key_cb(const char *key, size_t len,
settings_read_cb read_cb, void *cb_arg, void *param)
{
ARG_UNUSED(param);
/* key validation */
if (!key) {
LOG_ERR("Settings error: no key");
return -EINVAL;
}
int idx = atoi(key);
if ((idx == 0 && strcmp(key, "0") != 0) || idx >= CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES) {
LOG_ERR("Settings error: index too large");
return -EINVAL;
}
if (len < sizeof(struct wifi_credentials_header)) {
LOG_ERR("Settings error: invalid settings length");
return -EINVAL;
}
uint8_t buf[ENTRY_MAX_LEN];
size_t length_read = read_cb(cb_arg, buf, ARRAY_SIZE(buf));
/* value validation */
if (length_read < len) {
LOG_ERR("Settings error: entry incomplete");
return -ENODATA;
}
wifi_credentials_cache_ssid(idx, (struct wifi_credentials_header *)buf);
return 0;
}
int wifi_credentials_backend_init(void)
{
int ret = settings_subsys_init();
if (ret) {
LOG_ERR("Initializing settings subsystem failed: %d", ret);
return ret;
}
ret = settings_load_subtree_direct(WIFI_CREDENTIALS_SBE_BASE_KEY,
zephyr_settings_backend_load_key_cb, NULL);
return ret;
}
int wifi_credentials_store_entry(size_t idx, const void *buf, size_t buf_len)
{
char settings_name_buf[WIFI_CREDENTIALS_SBE_KEY_SIZE] = {0};
int ret = snprintk(settings_name_buf, ARRAY_SIZE(settings_name_buf),
WIFI_CREDENTIALS_SBE_KEY_FMT, idx);
if (ret < 0 || ret == ARRAY_SIZE(settings_name_buf)) {
LOG_ERR("WiFi credentials settings key could not be generated, idx: %d", idx);
return -EFAULT;
}
return settings_save_one(settings_name_buf, buf, buf_len);
}
int wifi_credentials_delete_entry(size_t idx)
{
char settings_name_buf[WIFI_CREDENTIALS_SBE_KEY_SIZE] = {0};
int ret = snprintk(settings_name_buf, ARRAY_SIZE(settings_name_buf),
WIFI_CREDENTIALS_SBE_KEY_FMT, idx);
if (ret < 0 || ret == ARRAY_SIZE(settings_name_buf)) {
LOG_ERR("WiFi credentials settings key could not be generated, idx: %d", idx);
return -EFAULT;
}
return settings_delete(settings_name_buf);
}
int wifi_credentials_load_entry(size_t idx, void *buf, size_t buf_len)
{
struct zephyr_settings_backend_load_cb_arg arg = {
.buf = buf,
.buf_len = buf_len,
.idx = idx,
.found = false,
};
int ret;
/* Browse through the settings entries with custom callback to load the whole entry. */
ret = settings_load_subtree_direct(WIFI_CREDENTIALS_SBE_BASE_KEY,
zephyr_settings_backend_load_val_cb, &arg);
if (ret) {
return ret;
}
if (!arg.found) {
return -ENOENT;
}
return 0;
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net/wifi_credentials.h>
#define ENTRY_MAX_LEN sizeof(struct wifi_credentials_personal)
/**
* @brief Write entry to SSID cache.
*
* @param idx credential index
* @param buf encoded settings entry
* @param buf_len length of buf
*/
void wifi_credentials_cache_ssid(size_t idx, const struct wifi_credentials_header *buf);
/**
* @brief Clear entry in SSID cache.
*
* @param idx credential index
*/
void wifi_credentials_uncache_ssid(size_t idx);
/**
* @brief Stores settings entry in flash.
*
* @param idx credential index
* @param buf encoded settings entry
* @param buf_len length of buf
* @return 0 on success, otherwise a negative error code
*/
int wifi_credentials_store_entry(size_t idx, const void *buf, size_t buf_len);
/**
* @brief Deletes settings entry from flash.
*
* @param idx credential index
* @return 0 on success, otherwise a negative error code
*/
int wifi_credentials_delete_entry(size_t idx);
/**
* @brief Loads settings entry from flash.
*
* @param idx credential index
* @param buf encoded settings entry
* @param buf_len length of buf
* @return 0 on success, otherwise a negative error code
*/
int wifi_credentials_load_entry(size_t idx, void *buf, size_t buf_len);
/**
* @brief Initialize backend.
* @note Is called by the library on system startup.
*
*/
int wifi_credentials_backend_init(void);

View file

@ -0,0 +1,313 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L /* For strnlen() */
#include <zephyr/kernel.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/shell/shell.h>
#include <zephyr/init.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/net/net_event.h>
#include <zephyr/net/net_l2.h>
#include <zephyr/net/ethernet.h>
#include <zephyr/net/wifi_credentials.h>
#define MACSTR "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
static void print_network_info(void *cb_arg, const char *ssid, size_t ssid_len)
{
int ret = 0;
struct wifi_credentials_personal creds = {0};
const struct shell *sh = (const struct shell *)cb_arg;
ret = wifi_credentials_get_by_ssid_personal_struct(ssid, ssid_len, &creds);
if (ret) {
shell_error(sh,
"An error occurred when trying to load credentials for network \"%.*s\""
". err: %d",
ssid_len, ssid, ret);
return;
}
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
" network ssid: \"%.*s\", ssid_len: %d, type: %s", ssid_len, ssid, ssid_len,
wifi_security_txt(creds.header.type));
if (creds.header.type == WIFI_SECURITY_TYPE_PSK ||
creds.header.type == WIFI_SECURITY_TYPE_PSK_SHA256 ||
creds.header.type == WIFI_SECURITY_TYPE_SAE ||
creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
", password: \"%.*s\", password_len: %d", creds.password_len,
creds.password, creds.password_len);
}
if (creds.header.flags & WIFI_CREDENTIALS_FLAG_BSSID) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", bssid: " MACSTR,
creds.header.bssid[0], creds.header.bssid[1], creds.header.bssid[2],
creds.header.bssid[3], creds.header.bssid[4], creds.header.bssid[5]);
}
if (creds.header.flags & WIFI_CREDENTIALS_FLAG_2_4GHz) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", band: 2.4GHz");
}
if (creds.header.flags & WIFI_CREDENTIALS_FLAG_5GHz) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", band: 5GHz");
}
if (creds.header.channel) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", channel: %d",
creds.header.channel);
}
if (creds.header.flags & WIFI_CREDENTIALS_FLAG_FAVORITE) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", favorite");
}
if (creds.header.flags & WIFI_CREDENTIALS_FLAG_MFP_REQUIRED) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_REQUIRED");
} else if (creds.header.flags & WIFI_CREDENTIALS_FLAG_MFP_DISABLED) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_DISABLED");
} else {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_OPTIONAL");
}
if (creds.header.timeout) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", timeout: %d",
creds.header.timeout);
}
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, "\n");
}
static enum wifi_security_type parse_sec_type(const char *s)
{
if (strcmp("OPEN", s) == 0) {
return WIFI_SECURITY_TYPE_NONE;
}
if (strcmp("WPA2-PSK", s) == 0) {
return WIFI_SECURITY_TYPE_PSK;
}
if (strcmp("WPA2-PSK-SHA256", s) == 0) {
return WIFI_SECURITY_TYPE_PSK_SHA256;
}
if (strcmp("WPA3-SAE", s) == 0) {
return WIFI_SECURITY_TYPE_SAE;
}
if (strcmp("WPA-PSK", s) == 0) {
return WIFI_SECURITY_TYPE_WPA_PSK;
}
return WIFI_SECURITY_TYPE_UNKNOWN;
}
static enum wifi_frequency_bands parse_band(const char *s)
{
if (strcmp("2.4GHz", s) == 0) {
return WIFI_FREQ_BAND_2_4_GHZ;
}
if (strcmp("5GHz", s) == 0) {
return WIFI_FREQ_BAND_5_GHZ;
}
if (strcmp("6GHz", s) == 0) {
return WIFI_FREQ_BAND_6_GHZ;
}
return WIFI_FREQ_BAND_UNKNOWN;
}
static int cmd_add_network(const struct shell *sh, size_t argc, char *argv[])
{
int ret;
if (argc < 3) {
goto help;
}
if (strnlen(argv[1], WIFI_SSID_MAX_LEN + 1) > WIFI_SSID_MAX_LEN) {
shell_error(sh, "SSID too long");
goto help;
}
struct wifi_credentials_personal creds = {
.header.ssid_len = strlen(argv[1]),
.header.type = parse_sec_type(argv[2]),
};
memcpy(creds.header.ssid, argv[1], creds.header.ssid_len);
if (creds.header.type == WIFI_SECURITY_TYPE_UNKNOWN) {
shell_error(sh, "Cannot parse security type");
goto help;
}
size_t arg_idx = 3;
if (creds.header.type == WIFI_SECURITY_TYPE_PSK ||
creds.header.type == WIFI_SECURITY_TYPE_PSK_SHA256 ||
creds.header.type == WIFI_SECURITY_TYPE_SAE ||
creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
/* parse passphrase */
if (argc < 4) {
shell_error(sh, "Missing password");
goto help;
}
creds.password_len = strlen(argv[3]);
if (creds.password_len < WIFI_PSK_MIN_LEN) {
shell_error(sh, "Passphrase should be minimum %d characters",
WIFI_PSK_MIN_LEN);
goto help;
}
if ((creds.password_len > CONFIG_WIFI_CREDENTIALS_SAE_PASSWORD_LENGTH &&
creds.header.type == WIFI_SECURITY_TYPE_SAE) ||
(creds.password_len > WIFI_PSK_MAX_LEN &&
creds.header.type != WIFI_SECURITY_TYPE_SAE)) {
shell_error(sh, "Password is too long for this security type");
goto help;
}
memcpy(creds.password, argv[3], creds.password_len);
++arg_idx;
}
if (arg_idx < argc) {
/* look for bssid */
ret = sscanf(argv[arg_idx], MACSTR, &creds.header.bssid[0], &creds.header.bssid[1],
&creds.header.bssid[2], &creds.header.bssid[3], &creds.header.bssid[4],
&creds.header.bssid[5]);
if (ret == 6) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_BSSID;
++arg_idx;
}
}
if (arg_idx < argc) {
/* look for band */
enum wifi_frequency_bands band = parse_band(argv[arg_idx]);
if (band == WIFI_FREQ_BAND_2_4_GHZ) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_2_4GHz;
++arg_idx;
}
if (band == WIFI_FREQ_BAND_5_GHZ) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_5GHz;
++arg_idx;
}
}
if (arg_idx < argc) {
/* look for channel */
char *end;
creds.header.channel = strtol(argv[arg_idx], &end, 10);
if (*end == '\0') {
++arg_idx;
}
}
if (arg_idx < argc) {
/* look for favorite flag */
if (strncmp("favorite", argv[arg_idx], strlen("favorite")) == 0) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_FAVORITE;
++arg_idx;
}
}
if (arg_idx < argc) {
/* look for mfp_disabled flag */
if (strncmp("mfp_disabled", argv[arg_idx], strlen("mfp_disabled")) == 0) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_DISABLED;
++arg_idx;
} else if (strncmp("mfp_required", argv[arg_idx], strlen("mfp_required")) == 0) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_REQUIRED;
++arg_idx;
}
}
if (arg_idx < argc) {
/* look for timeout */
char *end;
creds.header.timeout = strtol(argv[arg_idx], &end, 10);
if (*end == '\0') {
++arg_idx;
}
}
if (arg_idx != argc) {
for (size_t i = arg_idx; i < argc; ++i) {
shell_warn(sh, "Unparsed arg: [%s]", argv[i]);
}
}
return wifi_credentials_set_personal_struct(&creds);
help:
shell_print(sh, "Usage: wifi_cred add \"network name\""
" {OPEN, WPA2-PSK, WPA2-PSK-SHA256, WPA3-SAE, WPA-PSK}"
" [psk/password]"
" [bssid]"
" [{2.4GHz, 5GHz}]"
" [channel]"
" [favorite]"
" [mfp_disabled|mfp_required]"
" [timeout]");
return -EINVAL;
}
static int cmd_delete_network(const struct shell *sh, size_t argc, char *argv[])
{
if (argc != 2) {
shell_print(sh, "Usage: wifi_cred delete \"network name\"");
return -EINVAL;
}
if (strnlen(argv[1], WIFI_SSID_MAX_LEN + 1) > WIFI_SSID_MAX_LEN) {
shell_error(sh, "SSID too long");
return -EINVAL;
}
shell_print(sh, "\tDeleting network ssid: \"%s\", ssid_len: %d", argv[1],
strlen(argv[1]));
return wifi_credentials_delete_by_ssid(argv[1], strlen(argv[1]));
}
static int cmd_list_networks(const struct shell *sh, size_t argc, char *argv[])
{
wifi_credentials_for_each_ssid(print_network_info, (void *)sh);
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(sub_wifi_cred,
SHELL_CMD_ARG(add, NULL,
"Add network to storage.\n",
cmd_add_network, 0, 0),
SHELL_CMD_ARG(delete, NULL,
"Delete network from storage.\n",
cmd_delete_network,
0, 0),
SHELL_CMD_ARG(list, NULL,
"List stored networks.\n",
cmd_list_networks,
0, 0),
SHELL_SUBCMD_SET_END
);
SHELL_SUBCMD_ADD((wifi), cred, &sub_wifi_cred,
"Wifi credentials management.\n",
NULL,
0, 0);