1 /*
2 * Copyright (c) 2024 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #undef _POSIX_C_SOURCE
8 #define _POSIX_C_SOURCE 200809L /* For strnlen() */
9
10 #include <zephyr/kernel.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <zephyr/shell/shell.h>
14 #include <zephyr/init.h>
15
16 #include <zephyr/net/net_if.h>
17 #include <zephyr/net/wifi_mgmt.h>
18 #include <zephyr/net/wifi_utils.h>
19 #include <zephyr/net/net_event.h>
20 #include <zephyr/net/net_l2.h>
21 #include <zephyr/net/ethernet.h>
22
23 #include <zephyr/net/wifi_credentials.h>
24
25 #ifdef CONFIG_WIFI_CERTIFICATE_LIB
26 #include <zephyr/net/wifi_certs.h>
27 #endif
28
29 LOG_MODULE_REGISTER(wifi_credentials_shell, CONFIG_WIFI_CREDENTIALS_LOG_LEVEL);
30
31 #define MAX_BANDS_STR_LEN 64
32 #define MACSTR "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
33
print_network_info(void * cb_arg,const char * ssid,size_t ssid_len)34 static void print_network_info(void *cb_arg, const char *ssid, size_t ssid_len)
35 {
36 int ret = 0;
37 struct wifi_credentials_personal creds = {0};
38 const struct shell *sh = (const struct shell *)cb_arg;
39
40 ret = wifi_credentials_get_by_ssid_personal_struct(ssid, ssid_len, &creds);
41 if (ret) {
42 shell_error(sh,
43 "An error occurred when trying to load credentials for network \"%.*s\""
44 ". err: %d",
45 (int)ssid_len, ssid, ret);
46 return;
47 }
48
49 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
50 " network ssid: \"%.*s\", ssid_len: %d, type: %s", (int)ssid_len, ssid,
51 ssid_len, wifi_security_txt(creds.header.type));
52
53 if (creds.header.type == WIFI_SECURITY_TYPE_PSK ||
54 creds.header.type == WIFI_SECURITY_TYPE_PSK_SHA256 ||
55 creds.header.type == WIFI_SECURITY_TYPE_SAE ||
56 creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
57 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
58 ", password: \"%.*s\", password_len: %d", (int)creds.password_len,
59 creds.password, creds.password_len);
60 }
61
62 #ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
63 if (creds.header.type == WIFI_SECURITY_TYPE_EAP_TLS) {
64 if (creds.header.key_passwd_length > 0) {
65 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
66 ", key_passwd: \"%.*s\", key_passwd_len: %d",
67 creds.header.key_passwd_length, creds.header.key_passwd,
68 creds.header.key_passwd_length);
69 }
70 if (creds.header.aid_length > 0) {
71 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT,
72 ", anon_id: \"%.*s\", anon_id_len: %d",
73 creds.header.aid_length, creds.header.anon_id,
74 creds.header.aid_length);
75 }
76 }
77 #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
78
79 if (creds.header.flags & WIFI_CREDENTIALS_FLAG_BSSID) {
80 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", bssid: " MACSTR,
81 creds.header.bssid[0], creds.header.bssid[1], creds.header.bssid[2],
82 creds.header.bssid[3], creds.header.bssid[4], creds.header.bssid[5]);
83 }
84
85 if (creds.header.flags & WIFI_CREDENTIALS_FLAG_2_4GHz) {
86 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", band: 2.4GHz");
87 }
88
89 if (creds.header.flags & WIFI_CREDENTIALS_FLAG_5GHz) {
90 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", band: 5GHz");
91 }
92
93 if (creds.header.channel) {
94 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", channel: %d", creds.header.channel);
95 }
96
97 if (creds.header.flags & WIFI_CREDENTIALS_FLAG_FAVORITE) {
98 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", favorite");
99 }
100
101 if (creds.header.flags & WIFI_CREDENTIALS_FLAG_MFP_REQUIRED) {
102 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_REQUIRED");
103 } else if (creds.header.flags & WIFI_CREDENTIALS_FLAG_MFP_DISABLED) {
104 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_DISABLED");
105 } else {
106 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", MFP_OPTIONAL");
107 }
108
109 if (creds.header.timeout) {
110 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, ", timeout: %d", creds.header.timeout);
111 }
112
113 shell_fprintf(sh, SHELL_VT100_COLOR_DEFAULT, "\n");
114 }
115
cmd_add_network(const struct shell * sh,size_t argc,char * argv[])116 static int cmd_add_network(const struct shell *sh, size_t argc, char *argv[])
117 {
118 int opt;
119 int opt_index = 0;
120 struct getopt_state *state;
121 static const struct option long_options[] = {
122 {"ssid", required_argument, 0, 's'}, {"passphrase", required_argument, 0, 'p'},
123 {"key-mgmt", required_argument, 0, 'k'}, {"ieee-80211w", required_argument, 0, 'w'},
124 {"bssid", required_argument, 0, 'm'}, {"band", required_argument, 0, 'b'},
125 {"channel", required_argument, 0, 'c'}, {"timeout", required_argument, 0, 't'},
126 {"identity", required_argument, 0, 'a'}, {"key-passwd", required_argument, 0, 'K'},
127 {"help", no_argument, 0, 'h'}, {0, 0, 0, 0}};
128 char *endptr;
129 bool secure_connection = false;
130 uint8_t band;
131 struct wifi_credentials_personal creds = {0};
132
133 const uint8_t all_bands[] = {WIFI_FREQ_BAND_2_4_GHZ, WIFI_FREQ_BAND_5_GHZ,
134 WIFI_FREQ_BAND_6_GHZ};
135 bool found = false;
136 char bands_str[MAX_BANDS_STR_LEN] = {0};
137 size_t offset = 0;
138 long channel;
139 long mfp = WIFI_MFP_OPTIONAL;
140
141 while ((opt = getopt_long(argc, argv, "s:p:k:w:b:c:m:t:a:K:h", long_options, &opt_index)) !=
142 -1) {
143 state = getopt_state_get();
144 switch (opt) {
145 case 's':
146 creds.header.ssid_len = strlen(state->optarg);
147 if (creds.header.ssid_len > WIFI_SSID_MAX_LEN) {
148 shell_warn(sh, "SSID too long (max %d characters)\n",
149 WIFI_SSID_MAX_LEN);
150 return -EINVAL;
151 }
152 memcpy(creds.header.ssid, state->optarg, creds.header.ssid_len);
153 break;
154 case 'k':
155 creds.header.type = atoi(state->optarg);
156 if (creds.header.type) {
157 secure_connection = true;
158 }
159 break;
160 case 'p':
161 creds.password_len = strlen(state->optarg);
162 if (creds.password_len < WIFI_PSK_MIN_LEN) {
163 shell_warn(sh, "Passphrase should be minimum %d characters\n",
164 WIFI_PSK_MIN_LEN);
165 return -EINVAL;
166 }
167 if (creds.password_len > WIFI_PSK_MAX_LEN) {
168 shell_warn(sh, "Passphrase too long (max %d characters)\n",
169 WIFI_PSK_MAX_LEN);
170 return -EINVAL;
171 }
172 memcpy(creds.password, state->optarg, creds.password_len);
173 break;
174 case 'c':
175 channel = strtol(state->optarg, &endptr, 10);
176 if (*endptr != '\0') {
177 shell_error(sh, "Invalid channel: %s\n", state->optarg);
178 return -EINVAL;
179 }
180
181 for (band = 0; band < ARRAY_SIZE(all_bands); band++) {
182 offset += snprintf(bands_str + offset, sizeof(bands_str) - offset,
183 "%s%s", band ? "," : "",
184 wifi_band_txt(all_bands[band]));
185 if (offset >= sizeof(bands_str)) {
186 shell_error(sh,
187 "Failed to parse channel: %ld: "
188 "band string too long\n",
189 channel);
190 return -EINVAL;
191 }
192
193 if (wifi_utils_validate_chan(all_bands[band], channel)) {
194 found = true;
195 break;
196 }
197 }
198
199 if (!found) {
200 shell_error(sh, "Invalid channel: %ld, checked bands: %s\n",
201 channel, bands_str);
202 return -EINVAL;
203 }
204
205 creds.header.channel = channel;
206 break;
207 case 'b':
208 switch (atoi(state->optarg)) {
209 case 2:
210 creds.header.flags |= WIFI_CREDENTIALS_FLAG_2_4GHz;
211 break;
212 case 5:
213 creds.header.flags |= WIFI_CREDENTIALS_FLAG_5GHz;
214 break;
215 case 6:
216 creds.header.flags |= WIFI_CREDENTIALS_FLAG_6GHz;
217 break;
218 default:
219 shell_error(sh, "Invalid band: %d\n", atoi(state->optarg));
220 return -EINVAL;
221 }
222 break;
223 case 'w':
224 if (creds.header.type == WIFI_SECURITY_TYPE_NONE ||
225 creds.header.type == WIFI_SECURITY_TYPE_WPA_PSK) {
226 shell_error(sh, "MFP not supported for security type %s",
227 wifi_security_txt(creds.header.type));
228 return -ENOTSUP;
229 }
230 mfp = strtol(state->optarg, &endptr, 10);
231 if (*endptr != '\0') {
232 shell_error(sh, "Invalid IEEE 802.11w value: %s", state->optarg);
233 return -EINVAL;
234 }
235 if (mfp == WIFI_MFP_DISABLE) {
236 creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_DISABLED;
237 } else if (mfp == WIFI_MFP_REQUIRED) {
238 creds.header.flags |= WIFI_CREDENTIALS_FLAG_MFP_REQUIRED;
239 } else if (mfp > 2) {
240 shell_error(sh, "Invalid IEEE 802.11w value: %s",
241 state->optarg);
242 return -EINVAL;
243 }
244 break;
245 case 'm':
246 if (net_bytes_from_str(creds.header.bssid, sizeof(creds.header.bssid),
247 state->optarg) < 0) {
248 shell_warn(sh, "Invalid MAC address\n");
249 return -EINVAL;
250 }
251 creds.header.flags |= WIFI_CREDENTIALS_FLAG_BSSID;
252 break;
253 case 'a':
254 creds.header.aid_length = strlen(state->optarg);
255 if (creds.header.aid_length > WIFI_ENT_IDENTITY_MAX_LEN) {
256 shell_warn(sh, "anon_id too long (max %d characters)\n",
257 WIFI_ENT_IDENTITY_MAX_LEN);
258 return -EINVAL;
259 }
260 memcpy(creds.header.anon_id, state->optarg, creds.header.aid_length);
261 creds.header.flags |= WIFI_CREDENTIALS_FLAG_ANONYMOUS_IDENTITY;
262 break;
263 case 'K':
264 creds.header.key_passwd_length = strlen(state->optarg);
265 if (creds.header.key_passwd_length > WIFI_ENT_PSWD_MAX_LEN) {
266 shell_warn(sh, "key_passwd too long (max %d characters)\n",
267 WIFI_ENT_PSWD_MAX_LEN);
268 return -EINVAL;
269 }
270 memcpy(creds.header.key_passwd, state->optarg,
271 creds.header.key_passwd_length);
272 creds.header.flags |= WIFI_CREDENTIALS_FLAG_KEY_PASSWORD;
273 break;
274 case 'h':
275 shell_help(sh);
276 return -ENOEXEC;
277 default:
278 shell_error(sh, "Invalid option %c\n", state->optopt);
279 return -EINVAL;
280 }
281 }
282 if (creds.password_len > 0 && !secure_connection) {
283 shell_warn(sh, "Passphrase provided without security configuration\n");
284 }
285
286 if (creds.header.ssid_len == 0) {
287 shell_error(sh, "SSID not provided\n");
288 shell_help(sh);
289 return -EINVAL;
290 }
291
292 #ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
293 struct net_if *iface = net_if_get_wifi_sta();
294
295 /* Load the enterprise credentials if needed */
296 if (creds.header.type == WIFI_SECURITY_TYPE_EAP_TLS ||
297 creds.header.type == WIFI_SECURITY_TYPE_EAP_PEAP_MSCHAPV2 ||
298 creds.header.type == WIFI_SECURITY_TYPE_EAP_PEAP_GTC ||
299 creds.header.type == WIFI_SECURITY_TYPE_EAP_TTLS_MSCHAPV2 ||
300 creds.header.type == WIFI_SECURITY_TYPE_EAP_PEAP_TLS) {
301 wifi_set_enterprise_credentials(iface, 0);
302 }
303 #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
304
305 return wifi_credentials_set_personal_struct(&creds);
306 }
307
cmd_delete_network(const struct shell * sh,size_t argc,char * argv[])308 static int cmd_delete_network(const struct shell *sh, size_t argc, char *argv[])
309 {
310 if (argc != 2) {
311 shell_print(sh, "Usage: wifi cred delete \"network name\"");
312 return -EINVAL;
313 }
314
315 if (strnlen(argv[1], WIFI_SSID_MAX_LEN + 1) > WIFI_SSID_MAX_LEN) {
316 shell_error(sh, "SSID too long");
317 return -EINVAL;
318 }
319
320 shell_print(sh, "\tDeleting network ssid: \"%s\", ssid_len: %d", argv[1], strlen(argv[1]));
321
322 #ifdef CONFIG_WIFI_SHELL_RUNTIME_CERTIFICATES
323 /* Clear the certificates */
324 wifi_clear_enterprise_credentials();
325 #endif /* CONFIG_WIFI_SHELL_RUNTIME_CERTIFICATES */
326
327 return wifi_credentials_delete_by_ssid(argv[1], strlen(argv[1]));
328 }
329
cmd_list_networks(const struct shell * sh,size_t argc,char * argv[])330 static int cmd_list_networks(const struct shell *sh, size_t argc, char *argv[])
331 {
332 wifi_credentials_for_each_ssid(print_network_info, (void *)sh);
333 return 0;
334 }
335
336 #if CONFIG_WIFI_CREDENTIALS_CONNECT_STORED
337
cmd_auto_connect(const struct shell * sh,size_t argc,char * argv[])338 static int cmd_auto_connect(const struct shell *sh, size_t argc, char *argv[])
339 {
340 struct net_if *iface = net_if_get_wifi_sta();
341
342 #ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE
343 wifi_set_enterprise_credentials(iface, 0);
344 #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE */
345 int rc = net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0);
346
347 if (rc) {
348 shell_error(sh,
349 "An error occurred when trying to auto-connect to a network. err: %d",
350 rc);
351 }
352
353 return 0;
354 }
355
356 #endif /* CONFIG_WIFI_CREDENTIALS_CONNECT_STORED */
357
358 SHELL_STATIC_SUBCMD_SET_CREATE(sub_wifi_cred,
359 SHELL_CMD_ARG(add, NULL,
360 "Add network to storage.\n"
361 "<-s --ssid \"<SSID>\">: SSID.\n"
362 "[-c --channel]: Channel that needs to be scanned for connection. 0:any channel.\n"
363 "[-b, --band] 0: any band (2:2.4GHz, 5:5GHz, 6:6GHz]\n"
364 "[-p, --passphrase]: Passphrase (valid only for secure SSIDs)\n"
365 "[-k, --key-mgmt]: Key Management type (valid only for secure SSIDs)\n"
366 "0:None, 1:WPA2-PSK, 2:WPA2-PSK-256, 3:SAE-HNP, 4:SAE-H2E, 5:SAE-AUTO, 6:WAPI,"
367 " 7:EAP-TLS, 8:WEP, 9: WPA-PSK, 10: WPA-Auto-Personal, 11: DPP\n"
368 "12: EAP-PEAP-MSCHAPv2, 13: EAP-PEAP-GTC, 14: EAP-TTLS-MSCHAPv2,\n"
369 "15: EAP-PEAP-TLS, 20: SAE-EXT-KEY\n"
370 "[-w, --ieee-80211w]: MFP (optional: needs security type to be specified)\n"
371 ": 0:Disable, 1:Optional, 2:Required.\n"
372 "[-m, --bssid]: MAC address of the AP (BSSID).\n"
373 "[-t, --timeout]: Timeout for the connection attempt (in seconds).\n"
374 "[-a, --anon-id]: Anonymous identity for enterprise mode.\n"
375 "[-K, --key1-pwd for eap phase1 or --key2-pwd for eap phase2]:\n"
376 "Private key passwd for enterprise mode. Default no password for private key.\n"
377 "[-S, --wpa3-enterprise]: WPA3 enterprise mode:\n"
378 "Default 0: Not WPA3 enterprise mode.\n"
379 "1:Suite-b mode, 2:Suite-b-192-bit mode, 3:WPA3-enterprise-only mode.\n"
380 "[-T, --TLS-cipher]: 0:TLS-NONE, 1:TLS-ECC-P384, 2:TLS-RSA-3K.\n"
381 "[-V, --eap-version]: 0 or 1. Default 1: eap version 1.\n"
382 "[-I, --eap-id1]: Client Identity. Default no eap identity.\n"
383 "[-P, --eap-pwd1]: Client Password.\n"
384 "Default no password for eap user.\n"
385 "[-R, --ieee-80211r]: Use IEEE80211R fast BSS transition connect."
386 "[-h, --help]: Print out the help for the add network command.\n",
387 cmd_add_network,
388 2, 12),
389 SHELL_CMD_ARG(delete, NULL,
390 "Delete network from storage.\n",
391 cmd_delete_network,
392 0, 0),
393 SHELL_CMD_ARG(list, NULL,
394 "List stored networks.\n",
395 cmd_list_networks,
396 0, 0),
397
398 #if CONFIG_WIFI_CREDENTIALS_CONNECT_STORED
399 SHELL_CMD_ARG(auto_connect, NULL,
400 "Connect to any stored network.\n",
401 cmd_auto_connect,
402 0, 0),
403 #endif /* CONFIG_WIFI_CREDENTIALS_CONNECT_STORED */
404
405 SHELL_SUBCMD_SET_END
406 );
407
408 SHELL_SUBCMD_ADD((wifi), cred, &sub_wifi_cred,
409 "Wifi credentials management.\n",
410 NULL,
411 0, 0);
412