1 /*
2  * Copyright (c) 2023 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(tls_credentials_shell, CONFIG_TLS_CREDENTIALS_LOG_LEVEL);
9 
10 #include <zephyr/kernel.h>
11 #include <zephyr/shell/shell.h>
12 #include <zephyr/sys/base64.h>
13 #include <zephyr/net/tls_credentials.h>
14 #include "tls_internal.h"
15 #include <string.h>
16 #include <strings.h>
17 #include <ctype.h>
18 
19 enum cred_storage_fmt {
20 	/* Credential is stored as a string and will be passed between the shell and storage
21 	 * unmodified.
22 	 */
23 	CRED_STORAGE_FMT_STRING,
24 
25 	/* Credential is stored as raw binary, and is parsed from base64 before storage and encoded
26 	 * back into base64 when retrieved via the shell.
27 	 */
28 	CRED_STORAGE_FMT_BINARY,
29 };
30 
31 struct cred_type_string {
32 	char *name;
33 	enum tls_credential_type type;
34 };
35 
36 /* The first entry in each credential type group will be used for human-readable shell
37  * output. The last will be used for compact shell output. The rest are accepted synonyms.
38  */
39 static const struct cred_type_string type_strings[] = {
40 	{"CA_CERT",		TLS_CREDENTIAL_CA_CERTIFICATE},
41 	{"CA",			TLS_CREDENTIAL_CA_CERTIFICATE},
42 
43 	{"SERVER_CERT",		TLS_CREDENTIAL_SERVER_CERTIFICATE},
44 	{"CLIENT_CERT",		TLS_CREDENTIAL_SERVER_CERTIFICATE},
45 	{"SELF_CERT",		TLS_CREDENTIAL_SERVER_CERTIFICATE},
46 	{"SELF",		TLS_CREDENTIAL_SERVER_CERTIFICATE},
47 	{"CLIENT",		TLS_CREDENTIAL_SERVER_CERTIFICATE},
48 	{"SERV",		TLS_CREDENTIAL_SERVER_CERTIFICATE},
49 
50 	{"PRIVATE_KEY",		TLS_CREDENTIAL_PRIVATE_KEY},
51 	{"PK",			TLS_CREDENTIAL_PRIVATE_KEY},
52 
53 	{"PRE_SHARED_KEY",	TLS_CREDENTIAL_PSK},
54 	{"PSK",			TLS_CREDENTIAL_PSK},
55 
56 	{"PRE_SHARED_KEY_ID",	TLS_CREDENTIAL_PSK_ID},
57 	{"PSK_ID",		TLS_CREDENTIAL_PSK_ID}
58 };
59 
60 #define ANY_KEYWORD "any"
61 
62 /* This is so that we can output base64 in chunks of this length if necessary */
63 BUILD_ASSERT(
64 	(CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH % 4) == 0,
65 	"CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH must be a multiple of 4."
66 );
67 
68 /* Output buffers used for printing credentials and digests.
69  * One extra byte included for NULL termination.
70  */
71 static char cred_out_buf[CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH + 1];
72 static char cred_digest_buf[CONFIG_TLS_CREDENTIALS_SHELL_DIGEST_BUF_SIZE + 1];
73 
74 /* Internal buffer used for storing and retrieving credentials.
75  * +1 byte for potential NULL termination.
76  */
77 static char cred_buf[CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE + 1];
78 static size_t cred_written;
79 
80 /* Some backends (namely, the volatile backend) store a reference rather than a copy of passed-in
81  * credentials. For these backends, we need to copy incoming credentials onto the heap before
82  * attempting to store them.
83  *
84  * Since the backend in use is determined at build time by KConfig, so is this behavior.
85  * If multi/dynamic-backend support is ever added, this will need to be updated.
86  */
87 #define COPY_CREDENTIALS_TO_HEAP CONFIG_TLS_CREDENTIALS_BACKEND_VOLATILE
88 
89 /* Used to track credentials that have been copied permanently to the heap, in case they are
90  * ever deleted and need to be freed.
91  */
92 static void *cred_refs[CONFIG_TLS_MAX_CREDENTIALS_NUMBER];
93 
94 /* Find an empty slot in the cred_refs array, or return -1 if none exists.
95  * Pass NULL to find an unused slot.
96  */
find_ref_slot(const void * const cred)97 static int find_ref_slot(const void *const cred)
98 {
99 	int i;
100 
101 	for (i = 0; i < ARRAY_SIZE(cred_refs); i++) {
102 		if (cred_refs[i] == cred) {
103 			return i;
104 		}
105 	}
106 
107 	return -1;
108 }
109 
110 /* Helpers */
111 
112 /* Filter out non-printable characters from a passed-in string of known length */
filter_nonprint(char * buf,size_t len,char inval)113 static int filter_nonprint(char *buf, size_t len, char inval)
114 {
115 	int i;
116 	int ret = 0;
117 
118 	for (i = 0; i < len; i++) {
119 		if (!isprint((int)buf[i])) {
120 			buf[i] = inval;
121 			ret = -EINVAL;
122 		}
123 	}
124 
125 	return ret;
126 }
127 
128 /* Verify that a provided string consists only of the characters 0-9*/
check_numeric(char * str)129 static bool check_numeric(char *str)
130 {
131 	int i;
132 	int len = strlen(str);
133 
134 	for (i = 0; i < len; i++) {
135 		if (!isdigit((int)str[i])) {
136 			return false;
137 		}
138 	}
139 
140 	return true;
141 }
142 
143 /* Clear the credential write buffer, returns true if anything was actually cleared. */
cred_buf_clear(void)144 static bool cred_buf_clear(void)
145 {
146 	bool cleared = cred_written != 0;
147 
148 	(void)memset(cred_buf, 0, sizeof(cred_buf));
149 	cred_written = 0;
150 
151 	return cleared;
152 }
153 
154 /* Parse a (possibly incomplete) chunk into the credential buffer */
cred_buf_write(char * chunk)155 static int cred_buf_write(char *chunk)
156 {
157 	char *writehead = cred_buf + cred_written;
158 	size_t chunk_len = strlen(chunk);
159 
160 	/* Verify that there is room for the incoming chunk */
161 	if ((writehead + chunk_len) >= (cred_buf + sizeof(cred_buf) - 1)) {
162 		return -ENOMEM;
163 	}
164 
165 	/* Append chunk to the credential buffer.
166 	 * Deliberately do not copy NULL terminator.
167 	 */
168 	memcpy(writehead, chunk, chunk_len);
169 	cred_written += chunk_len;
170 
171 	return chunk_len;
172 }
173 
174 /* Get the human-readable name of a TLS credential type */
cred_type_name(enum tls_credential_type type)175 static const char *cred_type_name(enum tls_credential_type type)
176 {
177 	/* Scan over predefined type strings, and return the name
178 	 * of the first one of matching type.
179 	 */
180 	for (int i = 0; i < ARRAY_SIZE(type_strings); i++) {
181 		if (type_strings[i].type == type) {
182 			return type_strings[i].name;
183 		}
184 	}
185 
186 	/* No matches found, it's invalid. */
187 	return "INVALID";
188 }
189 
190 /* Get the compact name of a TLS credential type*/
cred_type_name_compact(enum tls_credential_type type)191 static const char *cred_type_name_compact(enum tls_credential_type type)
192 {
193 	/* Scan over predefined type strings, and return the name
194 	 * of the last one of matching type.
195 	 */
196 	for (int i = ARRAY_SIZE(type_strings) - 1; i >= 0; i--) {
197 		if (type_strings[i].type == type) {
198 			return type_strings[i].name;
199 		}
200 	}
201 
202 	/* No matches found, it's invalid. */
203 	return "INV";
204 }
205 
206 /* Shell interface routines */
207 
208 /* Attempt to parse a command line argument into a sectag.
209  * TLS_SEC_TAG_NONE is returned if ANY_KEYWORD is provided.
210  */
shell_parse_cred_sectag(const struct shell * sh,char * arg,sec_tag_t * out,bool allow_any)211 static int shell_parse_cred_sectag(const struct shell *sh, char *arg, sec_tag_t *out,
212 				   bool allow_any)
213 {
214 	unsigned long sectag_value;
215 	int err = 0;
216 
217 	/* Check for "ANY" special keyword if desired. */
218 	if (allow_any && strcasecmp(arg, ANY_KEYWORD) == 0) {
219 		*out = TLS_SEC_TAG_NONE;
220 		return 0;
221 	}
222 
223 	/* Otherwise, verify that the sectag is purely numeric */
224 	if (!check_numeric(arg)) {
225 		err = -EINVAL;
226 		goto error;
227 	}
228 
229 	/* Use strtoul because it has nicer validation features than atoi */
230 	sectag_value = shell_strtoul(arg, 10, &err);
231 
232 	if (!err) {
233 		*out = (sec_tag_t)sectag_value;
234 		return 0;
235 	}
236 
237 error:
238 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid sectag.\n", arg);
239 	return err;
240 }
241 
242 /* Attempt to parse a command line argument into a credential type.
243  * TLS_CREDENTIAL_NONE is returned if ANY_KEYWORD is provided.
244  */
shell_parse_cred_type(const struct shell * sh,char * arg,enum tls_credential_type * out,bool allow_any)245 static int shell_parse_cred_type(const struct shell *sh, char *arg, enum tls_credential_type *out,
246 				 bool allow_any)
247 {
248 	/* Check for "ANY" special keyword if desired. */
249 	if (allow_any && strcasecmp(arg, ANY_KEYWORD) == 0) {
250 		*out = TLS_CREDENTIAL_NONE;
251 		return 0;
252 	}
253 
254 	/* Otherwise, scan over predefined type strings, and return the corresponding
255 	 * credential type if one is found
256 	 */
257 	for (int i = 0; i < ARRAY_SIZE(type_strings); i++) {
258 		if (strcasecmp(arg, type_strings[i].name) == 0) {
259 			*out = type_strings[i].type;
260 			return 0;
261 		}
262 	}
263 
264 	/* No matches found, it's invalid. */
265 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid credential type.\n", arg);
266 
267 	return -EINVAL;
268 }
269 
270 /* Parse a backend specifier argument
271  * Right now, only a single backend is supported, so this is serving simply as a reserved argument.
272  * As such, the only valid input is "default"
273  */
shell_parse_cred_backend(const struct shell * sh,char * arg)274 static int shell_parse_cred_backend(const struct shell *sh, char *arg)
275 {
276 	if (strcasecmp(arg, "default") == 0) {
277 		return 0;
278 	}
279 
280 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid backend.\n", arg);
281 
282 	return -EINVAL;
283 }
284 
285 /* Parse an input type specifier */
shell_parse_cred_storage_format(const struct shell * sh,char * arg,enum cred_storage_fmt * out,bool * terminated)286 static int shell_parse_cred_storage_format(const struct shell *sh, char *arg,
287 					   enum cred_storage_fmt *out, bool *terminated)
288 {
289 	if (strcasecmp(arg, "bin") == 0) {
290 		*out = CRED_STORAGE_FMT_BINARY;
291 		*terminated = false;
292 		return 0;
293 	}
294 
295 	if (strcasecmp(arg, "bint") == 0) {
296 		*out = CRED_STORAGE_FMT_BINARY;
297 		*terminated = true;
298 		return 0;
299 	}
300 
301 	if (strcasecmp(arg, "str") == 0) {
302 		*out = CRED_STORAGE_FMT_STRING;
303 		*terminated = false;
304 		return 0;
305 	}
306 
307 	if (strcasecmp(arg, "strt") == 0) {
308 		*out = CRED_STORAGE_FMT_STRING;
309 		*terminated = true;
310 		return 0;
311 	}
312 
313 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid storage format.\n", arg);
314 
315 	return -EINVAL;
316 }
317 
318 /* Clear credential buffer, with shell feedback */
shell_clear_cred_buf(const struct shell * sh)319 static void shell_clear_cred_buf(const struct shell *sh)
320 {
321 	/* We will only print a message if some data was actually wiped. */
322 	if (cred_buf_clear()) {
323 		shell_fprintf(sh, SHELL_NORMAL, "Credential buffer cleared.\n");
324 	}
325 }
326 
327 /* Write data into the credential buffer, with shell feedback. */
shell_write_cred_buf(const struct shell * sh,char * chunk)328 static int shell_write_cred_buf(const struct shell *sh, char *chunk)
329 {
330 	int res = cred_buf_write(chunk);
331 
332 	/* Report results. */
333 
334 	if (res == -ENOMEM) {
335 		shell_fprintf(sh, SHELL_ERROR, "Not enough room in credential buffer for "
336 					       "provided data. Increase "
337 					       "CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE.\n");
338 		shell_clear_cred_buf(sh);
339 		return -ENOMEM;
340 	}
341 
342 	shell_fprintf(sh, SHELL_NORMAL, "Stored %d bytes.\n", res);
343 
344 	return 0;
345 }
346 
347 /* Adds a credential to the credential store */
tls_cred_cmd_add(const struct shell * sh,size_t argc,char * argv[])348 static int tls_cred_cmd_add(const struct shell *sh, size_t argc, char *argv[])
349 {
350 	int err = 0;
351 	sec_tag_t sectag;
352 	enum cred_storage_fmt format;
353 	bool terminated;
354 	enum tls_credential_type type;
355 	void *cred_copy = NULL;
356 	void *cred_chosen = NULL;
357 	bool keep_copy = false;
358 	int ref_slot = -1;
359 
360 	/* Lock credentials so that we can interact with them directly.
361 	 * Mainly this is required by credential_get.
362 	 */
363 
364 	credentials_lock();
365 
366 	err = shell_parse_cred_sectag(sh, argv[1], &sectag, false);
367 	if (err) {
368 		goto cleanup;
369 	}
370 
371 	err = shell_parse_cred_type(sh, argv[2], &type, false);
372 	if (err) {
373 		goto cleanup;
374 	}
375 
376 	err = shell_parse_cred_backend(sh, argv[3]);
377 	if (err) {
378 		goto cleanup;
379 	}
380 
381 	err = shell_parse_cred_storage_format(sh, argv[4], &format, &terminated);
382 	if (err) {
383 		goto cleanup;
384 	}
385 
386 	if (argc == 6) {
387 		/* Credential was passed, clear credential buffer and then use the passed-in
388 		 * credential.
389 		 */
390 		shell_clear_cred_buf(sh);
391 		err = shell_write_cred_buf(sh, argv[5]);
392 		if (err) {
393 			goto cleanup;
394 		}
395 	}
396 
397 	/* Make sure the credential buffer isn't empty. */
398 	if (cred_written == 0) {
399 		shell_fprintf(sh, SHELL_ERROR, "Please provide a credential to add.\n");
400 		err = -ENOENT;
401 		goto cleanup;
402 	}
403 
404 	/* Check whether a credential of this type and sectag already exists. */
405 	if (credential_get(sectag, type)) {
406 		shell_fprintf(sh, SHELL_ERROR, "TLS credential with sectag %d and type %s "
407 					       "already exists.\n", sectag, cred_type_name(type));
408 		err = -EEXIST;
409 		goto cleanup;
410 	}
411 
412 	/* If binary format was specified, decode from base64. */
413 	if (format == CRED_STORAGE_FMT_BINARY) {
414 		/* base64_decode can handle in-place operation.
415 		 * Pass &cred_written as olen so that it is updated to match the size of the base64
416 		 * encoding.
417 		 *
418 		 * We use sizeof(cred_buf) - 1 since we want to keep room fors a NULL terminator.
419 		 * Though, technically, this is not actually needed since the output of
420 		 * base64_decode is always shorter than its input.
421 		 */
422 		err = base64_decode(cred_buf, sizeof(cred_buf) - 1, &cred_written,
423 				    cred_buf, cred_written);
424 		if (err) {
425 			shell_fprintf(sh, SHELL_ERROR, "Could not decode input from base64, "
426 						       "error: %d\n", err);
427 			err = -EINVAL;
428 			goto cleanup;
429 		}
430 	}
431 
432 	/* If NULL termination was requested, append one.
433 	 * We are always guaranteed to have room in the buffer for this.
434 	 */
435 	if (terminated) {
436 		cred_buf[cred_written] = 0;
437 		cred_written += 1;
438 	}
439 
440 	/* Default to using cred_buf directly. */
441 	cred_chosen = cred_buf;
442 
443 	/* If the currently active TLS Credentials backend stores credentials by reference,
444 	 * copy the incoming credentials off to the heap, and then use this copy instead.
445 	 */
446 	if (IS_ENABLED(COPY_CREDENTIALS_TO_HEAP)) {
447 		/* Before copying the credential to heap, make sure we are able to store a
448 		 * reference to it so that it can be freed if the credential is ever deleted.
449 		 */
450 
451 		ref_slot = find_ref_slot(NULL);
452 
453 		if (ref_slot < 0) {
454 			shell_fprintf(sh, SHELL_ERROR, "No reference slot available, cannot copy "
455 						       "credential to heap. Credential will not be "
456 						       "stored\n");
457 			err = -ENOMEM;
458 			goto cleanup;
459 		}
460 
461 		cred_copy = k_malloc(cred_written);
462 		if (!cred_copy) {
463 			shell_fprintf(sh, SHELL_ERROR, "Not enough heap for TLS credential of "
464 						       "size %d.\n", cred_written);
465 			err = -ENOMEM;
466 			goto cleanup;
467 		}
468 
469 		memset(cred_copy, 0, cred_written);
470 		memcpy(cred_copy, cred_buf, cred_written);
471 
472 		shell_fprintf(sh, SHELL_WARNING, "Credential has been copied to heap. Memory will "
473 						 "be leaked if this credential is deleted without "
474 						 "using the shell.\n");
475 
476 		cred_chosen = cred_copy;
477 	}
478 
479 	/* Finally, store the credential in whatever credentials backend is active. */
480 	err = tls_credential_add(sectag, type, cred_chosen, cred_written);
481 	if (err) {
482 		shell_fprintf(sh, SHELL_ERROR, "Failed to add TLS credential with sectag %d and "
483 					       "type %s. Error: %d\n.", sectag,
484 					       cred_type_name(type), err);
485 		goto cleanup;
486 	}
487 
488 	/* Do not free the copied key during cleanup, since it was successfully written. */
489 	keep_copy = true;
490 
491 	shell_fprintf(sh, SHELL_NORMAL, "Added TLS credential of type %s, sectag %d, and length %d "
492 					"bytes.\n", cred_type_name(type), sectag, cred_written);
493 
494 cleanup:
495 	/* Unlock credentials since we are done interacting with internal state. */
496 	credentials_unlock();
497 
498 	/* We are also done with the credentials buffer, so clear it for good measure. */
499 	shell_clear_cred_buf(sh);
500 
501 	/* If we copied the credential, make sure it is eventually freed. */
502 	if (cred_copy) {
503 		if (keep_copy) {
504 			/* If the credential was successfully stored, keep a reference to it in case
505 			 * it is ever deleted and needs to be freed.
506 			 */
507 			cred_refs[ref_slot] = cred_copy;
508 		} else {
509 			/* Otherwise, clear and free it immediately */
510 			memset(cred_copy, 0, cred_written);
511 			(void)k_free(cred_copy);
512 		}
513 	}
514 
515 	return err;
516 }
517 
518 /* Buffers credential data into the credential buffer. */
tls_cred_cmd_buf(const struct shell * sh,size_t argc,char * argv[])519 static int tls_cred_cmd_buf(const struct shell *sh, size_t argc, char *argv[])
520 {
521 	/* If the "clear" keyword is provided, clear the buffer rather than write to it. */
522 	if (strcmp(argv[1], "clear") == 0) {
523 		shell_clear_cred_buf(sh);
524 		return 0;
525 	}
526 
527 	/* Otherwise, assume provided arg is base64 and attempt to write it into the credential
528 	 * buffer.
529 	 */
530 	return shell_write_cred_buf(sh, argv[1]);
531 }
532 
533 /* Deletes a credential from the credential store */
tls_cred_cmd_del(const struct shell * sh,size_t argc,char * argv[])534 static int tls_cred_cmd_del(const struct shell *sh, size_t argc, char *argv[])
535 {
536 	int err = 0;
537 	sec_tag_t sectag;
538 	enum tls_credential_type type;
539 	struct tls_credential *cred = NULL;
540 	int ref_slot = -1;
541 
542 	/* Lock credentials so that we can safely use internal access functions. */
543 	credentials_lock();
544 
545 	err = shell_parse_cred_sectag(sh, argv[1], &sectag, false);
546 	if (err) {
547 		goto cleanup;
548 	}
549 
550 	err = shell_parse_cred_type(sh, argv[2], &type, false);
551 	if (err) {
552 		goto cleanup;
553 	}
554 
555 	/* Check whether a credential of this type and sectag actually exists. */
556 	cred = credential_get(sectag, type);
557 	if (!cred) {
558 		shell_fprintf(sh, SHELL_ERROR, "There is no TLS credential with sectag %d and "
559 					       "type %s.\n", sectag, cred_type_name(type));
560 		err = -ENOENT;
561 		goto cleanup;
562 	}
563 
564 	ref_slot = find_ref_slot(cred->buf);
565 	if (ref_slot >= 0) {
566 		/* This was a credential we copied to heap. Clear and free it. */
567 		memset((void *)cred_buf, 0, cred->len);
568 		k_free((void *)cred_buf);
569 		cred->buf = NULL;
570 
571 		/* Clear the reference slot so it can be used again. */
572 		cred_refs[ref_slot] = NULL;
573 
574 		shell_fprintf(sh, SHELL_NORMAL, "Stored credential freed.\n");
575 	}
576 
577 	/* Attempt to delete. */
578 	err = tls_credential_delete(sectag, type);
579 	if (err) {
580 		shell_fprintf(sh, SHELL_ERROR, "Deleting TLS credential with sectag %d and "
581 					       "type %s failed with error: %d.\n", sectag,
582 					       cred_type_name(type), err);
583 		goto cleanup;
584 	}
585 
586 	shell_fprintf(sh, SHELL_NORMAL, "Deleted TLS credential with sectag %d and type %s.\n",
587 					sectag, cred_type_name(type));
588 
589 cleanup:
590 	/* Unlock credentials since we are done interacting with internal state. */
591 	credentials_unlock();
592 
593 	return err;
594 }
595 
596 /* Retrieves credential data from credential store. */
tls_cred_cmd_get(const struct shell * sh,size_t argc,char * argv[])597 static int tls_cred_cmd_get(const struct shell *sh, size_t argc, char *argv[])
598 {
599 	int i;
600 	int remaining;
601 	int written;
602 	int err = 0;
603 	size_t cred_len;
604 	sec_tag_t sectag;
605 	enum tls_credential_type type;
606 	enum cred_storage_fmt format;
607 	bool terminated;
608 
609 	size_t line_length;
610 
611 	/* Lock credentials so that we can safely use internal access functions. */
612 	credentials_lock();
613 
614 	err = shell_parse_cred_sectag(sh, argv[1], &sectag, false);
615 	if (err) {
616 		goto cleanup;
617 	}
618 
619 	err = shell_parse_cred_type(sh, argv[2], &type, false);
620 	if (err) {
621 		goto cleanup;
622 	}
623 
624 	err = shell_parse_cred_storage_format(sh, argv[3], &format, &terminated);
625 	if (err) {
626 		goto cleanup;
627 	}
628 
629 	line_length = CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH;
630 
631 	/* If the credential is stored as binary, adjust line length so that the output
632 	 * base64 has width CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH
633 	 */
634 	if (format == CRED_STORAGE_FMT_BINARY) {
635 		line_length = CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH / 4 * 3;
636 	}
637 
638 	/* Check whether a credential of this type and sectag actually exists. */
639 	if (!credential_get(sectag, type)) {
640 		shell_fprintf(sh, SHELL_ERROR, "There is no TLS credential with sectag %d and "
641 					       "type %s.\n", sectag, cred_type_name(type));
642 		err = -ENOENT;
643 		goto cleanup;
644 	}
645 
646 	/* Clear the credential buffer before use. */
647 	shell_clear_cred_buf(sh);
648 
649 	/* Load the credential into the credential buffer */
650 	cred_len = sizeof(cred_buf);
651 	err = tls_credential_get(sectag, type, cred_buf, &cred_len);
652 	if (err == -EFBIG) {
653 		shell_fprintf(sh, SHELL_ERROR, "Not enough room in the credential buffer to "
654 					       "retrieve credential with sectag %d and type %s. "
655 					       "Increase TLS_CREDENTIALS_SHELL_MAX_CRED_LEN.\n",
656 					       sectag, cred_type_name(type));
657 		err = -ENOMEM;
658 		goto cleanup;
659 	} else if (err) {
660 		shell_fprintf(sh, SHELL_ERROR, "Could not retrieve TLS credential with sectag %d "
661 					       "and type %s due to error: %d.\n", sectag,
662 					       cred_type_name(type), err);
663 		goto cleanup;
664 	}
665 
666 	/* Update the credential buffer writehead.
667 	 * Keeping this accurate ensures that a "Buffer Cleared" message is eventually printed.
668 	 */
669 	cred_written = cred_len;
670 
671 	/* If the stored credential is NULL-terminated, do not include NULL termination in output */
672 	if (terminated) {
673 		if (cred_buf[cred_written - 1] != 0) {
674 			shell_fprintf(sh, SHELL_ERROR, "The stored credential isn't "
675 						       "NULL-terminated, but a NULL-terminated "
676 						       "format was specified.\n");
677 
678 			err = -EINVAL;
679 			goto cleanup;
680 		}
681 		cred_written -= 1;
682 	}
683 
684 	/* Print the credential out in lines. */
685 	for (i = 0; i < cred_written; i += line_length) {
686 		/* Print either a full line, or however much credential data is left. */
687 		remaining = MIN(line_length, cred_written - i);
688 
689 		/* Read out a line of data. */
690 		memset(cred_out_buf, 0, sizeof(cred_out_buf));
691 		if (format == CRED_STORAGE_FMT_BINARY) {
692 			(void)base64_encode(cred_out_buf, sizeof(cred_out_buf),
693 					    &written, &cred_buf[i], remaining);
694 		} else if (format == CRED_STORAGE_FMT_STRING) {
695 			memcpy(cred_out_buf, &cred_buf[i], remaining);
696 			if (filter_nonprint(cred_out_buf, remaining, '?')) {
697 				err = -EBADF;
698 			}
699 		}
700 
701 		/* Print the line. */
702 		shell_fprintf(sh, SHELL_NORMAL, "%s\n", cred_out_buf);
703 	}
704 
705 	if (err) {
706 		shell_fprintf(sh, SHELL_WARNING, "Non-printable characters were included in the "
707 						 "output and filtered. Have you selected the "
708 						 "correct storage format?\n");
709 	}
710 
711 cleanup:
712 	/* Unlock credentials since we are done interacting with internal state. */
713 	credentials_unlock();
714 
715 	/* Clear buffers when done. */
716 	memset(cred_out_buf, 0, sizeof(cred_out_buf));
717 	shell_clear_cred_buf(sh);
718 
719 	return err;
720 }
721 
722 /* Lists credentials in credential store. */
tls_cred_cmd_list(const struct shell * sh,size_t argc,char * argv[])723 static int tls_cred_cmd_list(const struct shell *sh, size_t argc, char *argv[])
724 {
725 	int err = 0;
726 	size_t digest_size;
727 	sec_tag_t sectag = TLS_SEC_TAG_NONE;
728 	struct tls_credential *cred;
729 	int count = 0;
730 
731 	sec_tag_t sectag_filter = TLS_SEC_TAG_NONE;
732 	enum tls_credential_type type_filter = TLS_CREDENTIAL_NONE;
733 
734 	/* Lock credentials so that we can safely use internal access functions. */
735 	credentials_lock();
736 
737 	/* Sectag filter was provided, parse it. */
738 	if (argc >= 2) {
739 		err = shell_parse_cred_sectag(sh, argv[1], &sectag_filter, true);
740 		if (err) {
741 			goto cleanup;
742 		}
743 	}
744 
745 	/* Credential type filter was provided, parse it. */
746 	if (argc >= 3) {
747 		err = shell_parse_cred_type(sh, argv[2], &type_filter, true);
748 		if (err) {
749 			goto cleanup;
750 		}
751 	}
752 
753 	/* Scan through all occupied sectags */
754 	while ((sectag = credential_next_tag_get(sectag)) != TLS_SEC_TAG_NONE) {
755 		/* Filter by sectag if requested. */
756 		if (sectag_filter != TLS_SEC_TAG_NONE && sectag != sectag_filter) {
757 			continue;
758 		}
759 
760 		cred = NULL;
761 		/* Scan through all credentials within each sectag */
762 		while ((cred = credential_next_get(sectag, cred)) != NULL) {
763 			/* Filter by credential type if requested. */
764 			if (type_filter != TLS_CREDENTIAL_NONE && cred->type != type_filter) {
765 				continue;
766 			}
767 			count++;
768 
769 			/* Generate a digest of the credential */
770 			memset(cred_digest_buf, 0, sizeof(cred_digest_buf));
771 			strcpy(cred_digest_buf, "N/A");
772 			digest_size = sizeof(cred_digest_buf);
773 			err = credential_digest(cred, cred_digest_buf, &digest_size);
774 
775 			/* Print digest and sectag/type info */
776 			shell_fprintf(sh, err ? SHELL_ERROR : SHELL_NORMAL, "%d,%s,%s,%d\n",
777 				      sectag, cred_type_name_compact(cred->type),
778 				      err ? "ERROR" : cred_digest_buf, err);
779 
780 			err = 0;
781 		}
782 	};
783 
784 	shell_fprintf(sh, SHELL_NORMAL, "%d credentials found.\n", count);
785 
786 cleanup:
787 	/* Unlock credentials since we are done interacting with internal state. */
788 	credentials_unlock();
789 
790 	/* Clear digest buffer afterwards for good measure. */
791 	memset(cred_digest_buf, 0, sizeof(cred_digest_buf));
792 
793 	return 0;
794 }
795 
796 SHELL_STATIC_SUBCMD_SET_CREATE(tls_cred_cmds,
797 	SHELL_CMD_ARG(buf, NULL, "Buffer in credential data so it can be added.",
798 		      tls_cred_cmd_buf, 2, 0),
799 	SHELL_CMD_ARG(add, NULL, "Add a TLS credential.",
800 		      tls_cred_cmd_add, 5, 1),
801 	SHELL_CMD_ARG(del, NULL, "Delete a TLS credential.",
802 		      tls_cred_cmd_del, 3, 0),
803 	SHELL_CMD_ARG(get, NULL, "Retrieve the contents of a TLS credential",
804 		      tls_cred_cmd_get, 4, 0),
805 	SHELL_CMD_ARG(list, NULL, "List stored TLS credentials, optionally filtering by type "
806 				  "or sectag.",
807 		      tls_cred_cmd_list, 1, 2),
808 	SHELL_SUBCMD_SET_END
809 );
810 
811 SHELL_CMD_REGISTER(cred, &tls_cred_cmds, "TLS Credentials Commands", NULL);
812