Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

451 lines
15 KiB

/*
* 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/wifi_utils.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 MAX_BANDS_STR_LEN 64
#define MACSTR "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
static const char ca_cert_test[] = {
#include <wifi_enterprise_test_certs/ca.pem.inc>
'\0'
};
static const char client_cert_test[] = {
#include <wifi_enterprise_test_certs/client.pem.inc>
'\0'
};
static const char client_key_test[] = {
#include <wifi_enterprise_test_certs/client-key.pem.inc>
'\0'
};
static const char ca_cert2_test[] = {
#include <wifi_enterprise_test_certs/ca2.pem.inc>
'\0'};
static const char client_cert2_test[] = {
#include <wifi_enterprise_test_certs/client2.pem.inc>
'\0'};
static const char client_key2_test[] = {
#include <wifi_enterprise_test_certs/client-key2.pem.inc>
'\0'};
#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
#if defined CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
static int cmd_wifi_set_enterprise_creds(const struct shell *sh, struct net_if *iface)
{
struct wifi_enterprise_creds_params params = {0};
params.ca_cert = (uint8_t *)ca_cert_test;
params.ca_cert_len = ARRAY_SIZE(ca_cert_test);
params.client_cert = (uint8_t *)client_cert_test;
params.client_cert_len = ARRAY_SIZE(client_cert_test);
params.client_key = (uint8_t *)client_key_test;
params.client_key_len = ARRAY_SIZE(client_key_test);
params.ca_cert2 = (uint8_t *)ca_cert2_test;
params.ca_cert2_len = ARRAY_SIZE(ca_cert2_test);
params.client_cert2 = (uint8_t *)client_cert2_test;
params.client_cert2_len = ARRAY_SIZE(client_cert2_test);
params.client_key2 = (uint8_t *)client_key2_test;
params.client_key2_len = ARRAY_SIZE(client_key2_test);
if (net_mgmt(NET_REQUEST_WIFI_ENTERPRISE_CREDS, iface, &params, sizeof(params))) {
shell_warn(sh, "Set enterprise credentials failed\n");
return -1;
}
return 0;
}
#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
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",
(int)ssid_len, ssid, ret);
return;
}
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
" network ssid: \"%.*s\", ssid_len: %d, type: %s", (int)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", (int)creds.password_len,
creds.password, creds.password_len);
}
#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
if (creds.header.type == WIFI_SECURITY_TYPE_EAP_TLS) {
if (creds.header.key_passwd_length > 0) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
", key_passwd: \"%.*s\", key_passwd_len: %d",
creds.header.key_passwd_length, creds.header.key_passwd,
creds.header.key_passwd_length);
}
if (creds.header.aid_length > 0) {
shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
", anon_id: \"%.*s\", anon_id_len: %d",
creds.header.aid_length, creds.header.anon_id,
creds.header.aid_length);
}
}
#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
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 int cmd_add_network(const struct shell *sh, size_t argc, char *argv[])
{
int opt;
int opt_index = 0;
struct getopt_state *state;
static const struct option long_options[] = {
{"ssid", required_argument, 0, 's'}, {"passphrase", required_argument, 0, 'p'},
{"key-mgmt", required_argument, 0, 'k'}, {"ieee-80211w", required_argument, 0, 'w'},
{"bssid", required_argument, 0, 'm'}, {"band", required_argument, 0, 'b'},
{"channel", required_argument, 0, 'c'}, {"timeout", required_argument, 0, 't'},
{"identity", required_argument, 0, 'a'}, {"key-passwd", required_argument, 0, 'K'},
{"help", no_argument, 0, 'h'}, {0, 0, 0, 0}};
char *endptr;
bool secure_connection = false;
uint8_t band;
struct wifi_credentials_personal creds = {0};
const uint8_t all_bands[] = {WIFI_FREQ_BAND_2_4_GHZ, WIFI_FREQ_BAND_5_GHZ,
WIFI_FREQ_BAND_6_GHZ};
bool found = false;
char bands_str[MAX_BANDS_STR_LEN] = {0};
size_t offset = 0;
long channel;
long mfp = WIFI_MFP_OPTIONAL;
while ((opt = getopt_long(argc, argv, "s:p:k:w:b:c:m:t:a:K:h", long_options, &opt_index)) !=
-1) {
state = getopt_state_get();
switch (opt) {
case 's':
creds.header.ssid_len = strlen(state->optarg);
if (creds.header.ssid_len > WIFI_SSID_MAX_LEN) {
shell_warn(sh, "SSID too long (max %d characters)\n",
WIFI_SSID_MAX_LEN);
return -EINVAL;
}
memcpy(creds.header.ssid, state->optarg, creds.header.ssid_len);
break;
case 'k':
creds.header.type = atoi(state->optarg);
if (creds.header.type) {
secure_connection = true;
}
break;
case 'p':
creds.password_len = strlen(state->optarg);
if (creds.password_len < WIFI_PSK_MIN_LEN) {
shell_warn(sh, "Passphrase should be minimum %d characters\n",
WIFI_PSK_MIN_LEN);
return -EINVAL;
}
if (creds.password_len > WIFI_PSK_MAX_LEN) {
shell_warn(sh, "Passphrase too long (max %d characters)\n",
WIFI_PSK_MAX_LEN);
return -EINVAL;
}
memcpy(creds.password, state->optarg, creds.password_len);
break;
case 'c':
channel = strtol(state->optarg, &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "Invalid channel: %s\n", state->optarg);
return -EINVAL;
}
for (band = 0; band < ARRAY_SIZE(all_bands); band++) {
offset += snprintf(bands_str + offset, sizeof(bands_str) - offset,
"%s%s", band ? "," : "",
wifi_band_txt(all_bands[band]));
if (offset >= sizeof(bands_str)) {
shell_error(sh,
"Failed to parse channel: %ld: "
"band string too long\n",
channel);
return -EINVAL;
}
if (wifi_utils_validate_chan(all_bands[band], channel)) {
found = true;
break;
}
}
if (!found) {
shell_error(sh, "Invalid channel: %ld, checked bands: %s\n",
channel, bands_str);
return -EINVAL;
}
creds.header.channel = channel;
break;
case 'b':
switch (atoi(state->optarg)) {
case 2:
creds.header.flags |= WIFI_CREDENTIALS_FLAG_2_4GHz;
break;
case 5:
creds.header.flags |= WIFI_CREDENTIALS_FLAG_5GHz;
break;
case 6:
creds.header.flags |= WIFI_CREDENTIALS_FLAG_6GHz;
break;
default:
shell_error(sh, "Invalid band: %d\n", atoi(state->optarg));
return -EINVAL;
}
break;
case 'w':
if (creds.header.type == WIFI_SECURITY_TYPE_NONE ||
creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
shell_error(sh, "MFP not supported for security type %s",
wifi_security_txt(creds.header.type));
return -ENOTSUP;
}
mfp = strtol(state->optarg, &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "Invalid IEEE 802.11w value: %s", state->optarg);
return -EINVAL;
}
if (mfp == WIFI_MFP_DISABLE) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_DISABLED;
} else if (mfp == WIFI_MFP_REQUIRED) {
creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_REQUIRED;
} else if (mfp > 2) {
shell_error(sh, "Invalid IEEE 802.11w value: %s",
state->optarg);
return -EINVAL;
}
break;
case 'm':
if (net_bytes_from_str(creds.header.bssid, sizeof(creds.header.bssid),
state->optarg) < 0) {
shell_warn(sh, "Invalid MAC address\n");
return -EINVAL;
}
creds.header.flags |= WIFI_CREDENTIALS_FLAG_BSSID;
break;
case 'a':
creds.header.aid_length = strlen(state->optarg);
if (creds.header.aid_length > WIFI_ENT_IDENTITY_MAX_LEN) {
shell_warn(sh, "anon_id too long (max %d characters)\n",
WIFI_ENT_IDENTITY_MAX_LEN);
return -EINVAL;
}
memcpy(creds.header.anon_id, state->optarg, creds.header.aid_length);
creds.header.flags |= WIFI_CREDENTIALS_FLAG_ANONYMOUS_IDENTITY;
break;
case 'K':
creds.header.key_passwd_length = strlen(state->optarg);
if (creds.header.key_passwd_length > WIFI_ENT_PSWD_MAX_LEN) {
shell_warn(sh, "key_passwd too long (max %d characters)\n",
WIFI_ENT_PSWD_MAX_LEN);
return -EINVAL;
}
memcpy(creds.header.key_passwd, state->optarg,
creds.header.key_passwd_length);
creds.header.flags |= WIFI_CREDENTIALS_FLAG_KEY_PASSWORD;
break;
case 'h':
shell_help(sh);
return -ENOEXEC;
default:
shell_error(sh, "Invalid option %c\n", state->optopt);
return -EINVAL;
}
}
if (creds.password_len > 0 && !secure_connection) {
shell_warn(sh, "Passphrase provided without security configuration\n");
}
if (creds.header.ssid_len == 0) {
shell_error(sh, "SSID not provided\n");
shell_help(sh);
return -EINVAL;
}
#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
struct net_if *iface = net_if_get_first_by_type(&NET_L2_GET_NAME(ETHERNET));
/* Load the enterprise credentials if needed */
if (creds.header.type == WIFI_SECURITY_TYPE_EAP_TLS ||
creds.header.type == WIFI_SECURITY_TYPE_EAP_PEAP_MSCHAPV2 ||
creds.header.type == WIFI_SECURITY_TYPE_EAP_PEAP_GTC ||
creds.header.type == WIFI_SECURITY_TYPE_EAP_TTLS_MSCHAPV2 ||
creds.header.type == WIFI_SECURITY_TYPE_EAP_PEAP_TLS) {
cmd_wifi_set_enterprise_creds(sh, iface);
}
#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
return wifi_credentials_set_personal_struct(&creds);
}
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;
}
#if CONFIG_WIFI_CREDENTIALS_CONNECT_STORED
static int cmd_auto_connect(const struct shell *sh, size_t argc, char *argv[])
{
struct net_if *iface = net_if_get_first_by_type(&NET_L2_GET_NAME(ETHERNET));
int rc = net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0);
if (rc) {
shell_error(sh,
"An error occurred when trying to auto-connect to a network. err: %d",
rc);
}
return 0;
}
#endif /* CONFIG_WIFI_CREDENTIALS_CONNECT_STORED */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_wifi_cred,
SHELL_CMD_ARG(add, NULL,
"Add network to storage.\n"
"<-s --ssid \"<SSID>\">: SSID.\n"
"[-c --channel]: Channel that needs to be scanned for connection. 0:any channel.\n"
"[-b, --band] 0: any band (2:2.4GHz, 5:5GHz, 6:6GHz]\n"
"[-p, --passphrase]: Passphrase (valid only for secure SSIDs)\n"
"[-k, --key-mgmt]: Key Management type (valid only for secure SSIDs)\n"
"0:None, 1:WPA2-PSK, 2:WPA2-PSK-256, 3:SAE-HNP, 4:SAE-H2E, 5:SAE-AUTO, 6:WAPI,"
" 7:EAP-TLS, 8:WEP, 9: WPA-PSK, 10: WPA-Auto-Personal, 11: DPP\n"
"12: EAP-PEAP-MSCHAPv2, 13: EAP-PEAP-GTC, 14: EAP-TTLS-MSCHAPv2,\n"
"15: EAP-PEAP-TLS, 20: SAE-EXT-KEY\n"
"[-w, --ieee-80211w]: MFP (optional: needs security type to be specified)\n"
": 0:Disable, 1:Optional, 2:Required.\n"
"[-m, --bssid]: MAC address of the AP (BSSID).\n"
"[-t, --timeout]: Timeout for the connection attempt (in seconds).\n"
"[-a, --anon-id]: Anonymous identity for enterprise mode.\n"
"[-K, --key1-pwd for eap phase1 or --key2-pwd for eap phase2]:\n"
"Private key passwd for enterprise mode. Default no password for private key.\n"
"[-S, --wpa3-enterprise]: WPA3 enterprise mode:\n"
"Default 0: Not WPA3 enterprise mode.\n"
"1:Suite-b mode, 2:Suite-b-192-bit mode, 3:WPA3-enterprise-only mode.\n"
"[-T, --TLS-cipher]: 0:TLS-NONE, 1:TLS-ECC-P384, 2:TLS-RSA-3K.\n"
"[-V, --eap-version]: 0 or 1. Default 1: eap version 1.\n"
"[-I, --eap-id1]: Client Identity. Default no eap identity.\n"
"[-P, --eap-pwd1]: Client Password.\n"
"Default no password for eap user.\n"
"[-R, --ieee-80211r]: Use IEEE80211R fast BSS transition connect."
"[-h, --help]: Print out the help for the add network command.\n",
cmd_add_network,
2, 12),
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),
#if CONFIG_WIFI_CREDENTIALS_CONNECT_STORED
SHELL_CMD_ARG(auto_connect, NULL,
"Connect to any stored network.\n",
cmd_auto_connect,
0, 0),
#endif /* CONFIG_WIFI_CREDENTIALS_CONNECT_STORED */
SHELL_SUBCMD_SET_END
);
SHELL_SUBCMD_ADD((wifi), cred, &sub_wifi_cred,
"Wifi credentials management.\n",
NULL,
0, 0);