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