1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 #include <zephyr/kernel.h>
9 #include <zephyr/init.h>
10 #include <zephyr/net/wifi_credentials.h>
11 #include <sys/types.h>
12 
13 #include "wifi_credentials_internal.h"
14 
15 LOG_MODULE_REGISTER(wifi_credentials, CONFIG_WIFI_CREDENTIALS_LOG_LEVEL);
16 
17 static K_MUTEX_DEFINE(wifi_credentials_mutex);
18 
19 /* SSID cache: maps SSIDs to their storage indices */
20 static char ssid_cache[CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES][WIFI_SSID_MAX_LEN];
21 static size_t ssid_cache_lengths[CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES];
22 
23 /**
24  * @brief Finds index of given SSID if it exists.
25  *
26  * @param ssid		SSID to look for (buffer of WIFI_SSID_MAX_LEN length)
27  * @return		index if entry is found, -1 otherwise
28  */
lookup_idx(const uint8_t * ssid,size_t ssid_len)29 static inline ssize_t lookup_idx(const uint8_t *ssid, size_t ssid_len)
30 {
31 	for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
32 		if (ssid_len != ssid_cache_lengths[i]) {
33 			continue;
34 		}
35 
36 		if (strncmp(ssid, ssid_cache[i], ssid_len) == 0) {
37 			return i;
38 		}
39 	}
40 
41 	return -1;
42 }
43 
44 /**
45  * @brief Determine whether an index is currently used for storing network credentials.
46  *
47  * @param idx credential index
48  * @return true if index is used, false otherwise
49  */
is_entry_used(size_t idx)50 static inline bool is_entry_used(size_t idx)
51 {
52 	return ssid_cache_lengths[idx] != 0;
53 }
54 
55 /**
56  * @brief Finds unused index to store new entry at.
57  *
58  * @return		index if empty slot is found, -1 otherwise
59  */
lookup_unused_idx(void)60 static inline ssize_t lookup_unused_idx(void)
61 {
62 	for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
63 		if (!is_entry_used(i)) {
64 			return i;
65 		}
66 	}
67 
68 	return -1;
69 }
70 
init(void)71 static int init(void)
72 {
73 
74 	int ret;
75 
76 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
77 
78 	ret = wifi_credentials_backend_init();
79 	if (ret) {
80 		LOG_ERR("Initializing WiFi credentials storage backend failed, err: %d", ret);
81 	}
82 
83 	k_mutex_unlock(&wifi_credentials_mutex);
84 
85 	return 0;
86 }
87 
wifi_credentials_cache_ssid(size_t idx,const struct wifi_credentials_header * buf)88 void wifi_credentials_cache_ssid(size_t idx, const struct wifi_credentials_header *buf)
89 {
90 	memcpy(ssid_cache[idx], buf->ssid, buf->ssid_len);
91 	ssid_cache_lengths[idx] = buf->ssid_len;
92 }
93 
94 /**
95  * @brief Clear entry in SSID cache.
96  *
97  * @param idx credential index
98  */
wifi_credentials_uncache_ssid(size_t idx)99 void wifi_credentials_uncache_ssid(size_t idx)
100 {
101 	ssid_cache_lengths[idx] = 0;
102 }
103 
wifi_credentials_get_by_ssid_personal_struct(const char * ssid,size_t ssid_len,struct wifi_credentials_personal * buf)104 int wifi_credentials_get_by_ssid_personal_struct(const char *ssid, size_t ssid_len,
105 						 struct wifi_credentials_personal *buf)
106 {
107 	ssize_t idx;
108 	int ret;
109 
110 	if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
111 		LOG_ERR("Cannot %s WiFi credentials, %s", "retrieve", "SSID has invalid format");
112 		return -EINVAL;
113 	}
114 
115 	if (buf == NULL) {
116 		LOG_ERR("Cannot %s WiFi credentials, %s", "retrieve", "destination is NULL");
117 		return -EINVAL;
118 	}
119 
120 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
121 
122 	idx = lookup_idx(ssid, ssid_len);
123 	if (idx == -1) {
124 		LOG_DBG("Cannot %s WiFi credentials, %s", "retrieve", "SSID not found");
125 		ret = -ENOENT;
126 		goto exit;
127 	}
128 
129 	ret = wifi_credentials_load_entry(idx, buf, sizeof(struct wifi_credentials_personal));
130 	if (ret) {
131 		LOG_ERR("Failed to %s WiFi credentials at index %d, err: %d", "load", idx, ret);
132 		goto exit;
133 	}
134 
135 	if (buf->header.type < 0 || buf->header.type > WIFI_SECURITY_TYPE_MAX) {
136 		LOG_ERR("Requested WiFi credentials entry is corrupted");
137 		ret = -EPROTO;
138 		goto exit;
139 	}
140 
141 exit:
142 	k_mutex_unlock(&wifi_credentials_mutex);
143 
144 	return ret;
145 }
146 
wifi_credentials_set_personal_struct(const struct wifi_credentials_personal * creds)147 int wifi_credentials_set_personal_struct(const struct wifi_credentials_personal *creds)
148 {
149 	ssize_t idx;
150 	int ret;
151 
152 	if (creds == NULL) {
153 		LOG_ERR("Cannot %s WiFi credentials, %s", "set", "credential not set");
154 		return -EINVAL;
155 	}
156 
157 	if (creds->header.ssid_len > WIFI_SSID_MAX_LEN || creds->header.ssid_len == 0) {
158 		LOG_ERR("Cannot %s WiFi credentials, %s", "set", "SSID has invalid format");
159 		return -EINVAL;
160 	}
161 
162 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
163 
164 	idx = lookup_idx(creds->header.ssid, creds->header.ssid_len);
165 	if (idx == -1) {
166 		idx = lookup_unused_idx();
167 		if (idx == -1) {
168 			LOG_ERR("Cannot %s WiFi credentials, %s", "store", "no space left");
169 			ret = -ENOBUFS;
170 			goto exit;
171 		}
172 	}
173 
174 	ret = wifi_credentials_store_entry(idx, creds, sizeof(struct wifi_credentials_personal));
175 	if (ret) {
176 		LOG_ERR("Failed to %s WiFi credentials at index %d, err: %d", "store", idx, ret);
177 		goto exit;
178 	}
179 
180 	wifi_credentials_cache_ssid(idx, &creds->header);
181 
182 exit:
183 	k_mutex_unlock(&wifi_credentials_mutex);
184 
185 	return ret;
186 }
187 
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)188 int wifi_credentials_set_personal(const char *ssid, size_t ssid_len, enum wifi_security_type type,
189 				  const uint8_t *bssid, size_t bssid_len, const char *password,
190 				  size_t password_len, uint32_t flags, uint8_t channel,
191 				  uint32_t timeout)
192 {
193 	int ret = 0;
194 	uint8_t buf[ENTRY_MAX_LEN] = {0};
195 	struct wifi_credentials_header *header;
196 
197 	if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
198 		LOG_ERR("Cannot %s WiFi credentials, %s", "set", "SSID has invalid format");
199 		return -EINVAL;
200 	}
201 
202 	if (flags & WIFI_CREDENTIALS_FLAG_BSSID &&
203 	    (bssid_len != WIFI_MAC_ADDR_LEN || bssid == NULL)) {
204 		LOG_ERR("Cannot %s WiFi credentials, %s", "set",
205 			"provided flags indicated BSSID but no BSSID provided");
206 		return -EINVAL;
207 	}
208 
209 	if ((type != WIFI_SECURITY_TYPE_NONE && (password_len == 0 || password == NULL)) ||
210 	    (password_len > WIFI_CREDENTIALS_MAX_PASSWORD_LEN)) {
211 		LOG_ERR("Cannot %s WiFi credentials, %s", "set",
212 			"password not provided or invalid");
213 		return -EINVAL;
214 	}
215 
216 	/* pack entry */
217 	header = (struct wifi_credentials_header *)buf;
218 
219 	header->type = type;
220 	memcpy(header->ssid, ssid, ssid_len);
221 	header->ssid_len = ssid_len;
222 	header->flags = flags;
223 	header->channel = channel;
224 	header->timeout = timeout;
225 
226 	if (flags & WIFI_CREDENTIALS_FLAG_BSSID) {
227 		memcpy(header->bssid, bssid, WIFI_MAC_ADDR_LEN);
228 	}
229 
230 	switch (type) {
231 	case WIFI_SECURITY_TYPE_NONE:
232 		break;
233 	case WIFI_SECURITY_TYPE_PSK:
234 	case WIFI_SECURITY_TYPE_PSK_SHA256:
235 	case WIFI_SECURITY_TYPE_WPA_PSK:
236 	case WIFI_SECURITY_TYPE_SAE: {
237 		struct wifi_credentials_personal *header_personal =
238 			(struct wifi_credentials_personal *)buf;
239 
240 		memcpy(header_personal->password, password, password_len);
241 		header_personal->password_len = password_len;
242 		break;
243 	}
244 	default:
245 		LOG_ERR("Cannot %s WiFi credentials, provided security type %d is unsupported",
246 			"set", type);
247 		return -ENOTSUP;
248 	}
249 
250 	/* store entry */
251 	ret = wifi_credentials_set_personal_struct((struct wifi_credentials_personal *)buf);
252 
253 	return ret;
254 }
255 
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)256 int wifi_credentials_get_by_ssid_personal(const char *ssid, size_t ssid_len,
257 					  enum wifi_security_type *type, uint8_t *bssid_buf,
258 					  size_t bssid_buf_len, char *password_buf,
259 					  size_t password_buf_len, size_t *password_len,
260 					  uint32_t *flags, uint8_t *channel, uint32_t *timeout)
261 {
262 	int ret = 0;
263 	uint8_t buf[ENTRY_MAX_LEN] = {0};
264 	struct wifi_credentials_header *header;
265 
266 	if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
267 		LOG_ERR("Cannot %s WiFi credentials, %s", "retrieve", "SSID has invalid format");
268 		return -EINVAL;
269 	}
270 
271 	if (bssid_buf_len != WIFI_MAC_ADDR_LEN || bssid_buf == NULL) {
272 		LOG_ERR("%s buffer needs to be provided", "BSSID");
273 		return -EINVAL;
274 	}
275 
276 	if (password_buf == NULL || password_buf_len > WIFI_CREDENTIALS_MAX_PASSWORD_LEN ||
277 	    password_buf_len == 0) {
278 		LOG_ERR("%s buffer needs to be provided", "WiFi password");
279 		return -EINVAL;
280 	}
281 
282 	/* load entry */
283 	ret = wifi_credentials_get_by_ssid_personal_struct(ssid, ssid_len,
284 							   (struct wifi_credentials_personal *)buf);
285 	if (ret) {
286 		return ret;
287 	}
288 
289 	/* unpack entry*/
290 	header = (struct wifi_credentials_header *)buf;
291 
292 	*type = header->type;
293 	*flags = header->flags;
294 	*channel = header->channel;
295 	*timeout = header->timeout;
296 
297 	if (header->flags & WIFI_CREDENTIALS_FLAG_BSSID) {
298 		memcpy(bssid_buf, header->bssid, WIFI_MAC_ADDR_LEN);
299 	}
300 
301 	switch (header->type) {
302 	case WIFI_SECURITY_TYPE_NONE:
303 		break;
304 	case WIFI_SECURITY_TYPE_PSK:
305 	case WIFI_SECURITY_TYPE_PSK_SHA256:
306 	case WIFI_SECURITY_TYPE_WPA_PSK:
307 	case WIFI_SECURITY_TYPE_SAE: {
308 		struct wifi_credentials_personal *header_personal =
309 			(struct wifi_credentials_personal *)buf;
310 
311 		memcpy(password_buf, header_personal->password, header_personal->password_len);
312 		*password_len = header_personal->password_len;
313 		break;
314 	}
315 	default:
316 		LOG_ERR("Cannot %s WiFi credentials, %s", "get",
317 			"the requested credentials have invalid WIFI_SECURITY_TYPE");
318 		ret = -EPROTO;
319 	}
320 	return ret;
321 }
322 
wifi_credentials_delete_by_ssid(const char * ssid,size_t ssid_len)323 int wifi_credentials_delete_by_ssid(const char *ssid, size_t ssid_len)
324 {
325 	ssize_t idx;
326 	int ret = 0;
327 
328 	if (ssid == NULL || ssid_len > WIFI_SSID_MAX_LEN || ssid_len == 0) {
329 		LOG_ERR("Cannot %s WiFi credentials, %s", "delete", "SSID has invalid format");
330 		return -EINVAL;
331 	}
332 
333 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
334 
335 	idx = lookup_idx(ssid, ssid_len);
336 	if (idx == -1) {
337 		LOG_DBG("WiFi credentials entry was not found");
338 		goto exit;
339 	}
340 
341 	ret = wifi_credentials_delete_entry(idx);
342 	if (ret) {
343 		LOG_ERR("Failed to %s WiFi credentials index %d, err: %d", "delete", idx, ret);
344 		goto exit;
345 	}
346 
347 	wifi_credentials_uncache_ssid(idx);
348 
349 exit:
350 	k_mutex_unlock(&wifi_credentials_mutex);
351 	return ret;
352 }
353 
wifi_credentials_for_each_ssid(wifi_credentials_ssid_cb cb,void * cb_arg)354 void wifi_credentials_for_each_ssid(wifi_credentials_ssid_cb cb, void *cb_arg)
355 {
356 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
357 
358 	for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
359 		if (is_entry_used(i)) {
360 			cb(cb_arg, ssid_cache[i], ssid_cache_lengths[i]);
361 		}
362 	}
363 
364 	k_mutex_unlock(&wifi_credentials_mutex);
365 }
366 
wifi_credentials_is_empty(void)367 bool wifi_credentials_is_empty(void)
368 {
369 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
370 
371 	for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
372 		if (is_entry_used(i)) {
373 			k_mutex_unlock(&wifi_credentials_mutex);
374 			return false;
375 		}
376 	}
377 
378 	k_mutex_unlock(&wifi_credentials_mutex);
379 	return true;
380 }
381 
wifi_credentials_delete_all(void)382 int wifi_credentials_delete_all(void)
383 {
384 	int ret = 0;
385 
386 	k_mutex_lock(&wifi_credentials_mutex, K_FOREVER);
387 	for (size_t i = 0; i < CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES; ++i) {
388 		if (is_entry_used(i)) {
389 			ret = wifi_credentials_delete_entry(i);
390 			if (ret) {
391 				LOG_ERR("Failed to %s WiFi credentials index %d, err: %d",
392 					"delete", i, ret);
393 				break;
394 			}
395 
396 			wifi_credentials_uncache_ssid(i);
397 		}
398 	}
399 
400 	k_mutex_unlock(&wifi_credentials_mutex);
401 	return ret;
402 }
403 
404 SYS_INIT(init, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY);
405