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_PUBLIC_CERTIFICATE},
44 	{"CLIENT_CERT",		TLS_CREDENTIAL_PUBLIC_CERTIFICATE},
45 	{"SELF_CERT",		TLS_CREDENTIAL_PUBLIC_CERTIFICATE},
46 	{"SELF",		TLS_CREDENTIAL_PUBLIC_CERTIFICATE},
47 	{"CLIENT",		TLS_CREDENTIAL_PUBLIC_CERTIFICATE},
48 	{"SERV",		TLS_CREDENTIAL_PUBLIC_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,size_t chunk_len)155 static int cred_buf_write(char *chunk, size_t chunk_len)
156 {
157 	char *writehead = cred_buf + cred_written;
158 
159 	/* Verify that there is room for the incoming chunk */
160 	if ((writehead + chunk_len) >= (cred_buf + sizeof(cred_buf) - 1)) {
161 		return -ENOMEM;
162 	}
163 
164 	/* Append chunk to the credential buffer.
165 	 * Deliberately do not copy NULL terminator.
166 	 */
167 	memcpy(writehead, chunk, chunk_len);
168 	cred_written += chunk_len;
169 
170 	return chunk_len;
171 }
172 
173 /* Get the human-readable name of a TLS credential type */
cred_type_name(enum tls_credential_type type)174 static const char *cred_type_name(enum tls_credential_type type)
175 {
176 	/* Scan over predefined type strings, and return the name
177 	 * of the first one of matching type.
178 	 */
179 	for (int i = 0; i < ARRAY_SIZE(type_strings); i++) {
180 		if (type_strings[i].type == type) {
181 			return type_strings[i].name;
182 		}
183 	}
184 
185 	/* No matches found, it's invalid. */
186 	return "INVALID";
187 }
188 
189 /* Get the compact name of a TLS credential type*/
cred_type_name_compact(enum tls_credential_type type)190 static const char *cred_type_name_compact(enum tls_credential_type type)
191 {
192 	/* Scan over predefined type strings, and return the name
193 	 * of the last one of matching type.
194 	 */
195 	for (int i = ARRAY_SIZE(type_strings) - 1; i >= 0; i--) {
196 		if (type_strings[i].type == type) {
197 			return type_strings[i].name;
198 		}
199 	}
200 
201 	/* No matches found, it's invalid. */
202 	return "INV";
203 }
204 
205 /* Shell interface routines */
206 
207 /* Attempt to parse a command line argument into a sectag.
208  * TLS_SEC_TAG_NONE is returned if ANY_KEYWORD is provided.
209  */
shell_parse_cred_sectag(const struct shell * sh,char * arg,sec_tag_t * out,bool allow_any)210 static int shell_parse_cred_sectag(const struct shell *sh, char *arg, sec_tag_t *out,
211 				   bool allow_any)
212 {
213 	unsigned long sectag_value;
214 	int err = 0;
215 
216 	/* Check for "ANY" special keyword if desired. */
217 	if (allow_any && strcasecmp(arg, ANY_KEYWORD) == 0) {
218 		*out = TLS_SEC_TAG_NONE;
219 		return 0;
220 	}
221 
222 	/* Otherwise, verify that the sectag is purely numeric */
223 	if (!check_numeric(arg)) {
224 		err = -EINVAL;
225 		goto error;
226 	}
227 
228 	/* Use strtoul because it has nicer validation features than atoi */
229 	sectag_value = shell_strtoul(arg, 10, &err);
230 
231 	if (!err) {
232 		*out = (sec_tag_t)sectag_value;
233 		return 0;
234 	}
235 
236 error:
237 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid sectag.\n", arg);
238 	return err;
239 }
240 
241 /* Attempt to parse a command line argument into a credential type.
242  * TLS_CREDENTIAL_NONE is returned if ANY_KEYWORD is provided.
243  */
shell_parse_cred_type(const struct shell * sh,char * arg,enum tls_credential_type * out,bool allow_any)244 static int shell_parse_cred_type(const struct shell *sh, char *arg, enum tls_credential_type *out,
245 				 bool allow_any)
246 {
247 	/* Check for "ANY" special keyword if desired. */
248 	if (allow_any && strcasecmp(arg, ANY_KEYWORD) == 0) {
249 		*out = TLS_CREDENTIAL_NONE;
250 		return 0;
251 	}
252 
253 	/* Otherwise, scan over predefined type strings, and return the corresponding
254 	 * credential type if one is found
255 	 */
256 	for (int i = 0; i < ARRAY_SIZE(type_strings); i++) {
257 		if (strcasecmp(arg, type_strings[i].name) == 0) {
258 			*out = type_strings[i].type;
259 			return 0;
260 		}
261 	}
262 
263 	/* No matches found, it's invalid. */
264 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid credential type.\n", arg);
265 
266 	return -EINVAL;
267 }
268 
269 /* Parse a backend specifier argument
270  * Right now, only a single backend is supported, so this is serving simply as a reserved argument.
271  * As such, the only valid input is "default"
272  */
shell_parse_cred_backend(const struct shell * sh,char * arg)273 static int shell_parse_cred_backend(const struct shell *sh, char *arg)
274 {
275 	if (strcasecmp(arg, "default") == 0) {
276 		return 0;
277 	}
278 
279 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid backend.\n", arg);
280 
281 	return -EINVAL;
282 }
283 
284 /* Parse an input type specifier */
shell_parse_cred_storage_format(const struct shell * sh,char * arg,enum cred_storage_fmt * out,bool * terminated)285 static int shell_parse_cred_storage_format(const struct shell *sh, char *arg,
286 					   enum cred_storage_fmt *out, bool *terminated)
287 {
288 	if (strcasecmp(arg, "bin") == 0) {
289 		*out = CRED_STORAGE_FMT_BINARY;
290 		*terminated = false;
291 		return 0;
292 	}
293 
294 	if (strcasecmp(arg, "bint") == 0) {
295 		*out = CRED_STORAGE_FMT_BINARY;
296 		*terminated = true;
297 		return 0;
298 	}
299 
300 	if (strcasecmp(arg, "str") == 0) {
301 		*out = CRED_STORAGE_FMT_STRING;
302 		*terminated = false;
303 		return 0;
304 	}
305 
306 	if (strcasecmp(arg, "strt") == 0) {
307 		*out = CRED_STORAGE_FMT_STRING;
308 		*terminated = true;
309 		return 0;
310 	}
311 
312 	shell_fprintf(sh, SHELL_ERROR, "%s is not a valid storage format.\n", arg);
313 
314 	return -EINVAL;
315 }
316 
317 /* Clear credential buffer, with shell feedback */
shell_clear_cred_buf(const struct shell * sh)318 static void shell_clear_cred_buf(const struct shell *sh)
319 {
320 	/* We will only print a message if some data was actually wiped. */
321 	if (cred_buf_clear()) {
322 		shell_fprintf(sh, SHELL_NORMAL, "Credential buffer cleared.\n");
323 	}
324 }
325 
326 /* Write data into the credential buffer, with shell feedback. */
shell_write_cred_buf(const struct shell * sh,char * chunk)327 static int shell_write_cred_buf(const struct shell *sh, char *chunk)
328 {
329 	size_t chunk_len = strlen(chunk);
330 	int res = cred_buf_write(chunk, chunk_len);
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 #define ASCII_CTRL_C 0x03
519 
tls_cred_cmd_load_bypass(const struct shell * sh,uint8_t * data,size_t len)520 static void tls_cred_cmd_load_bypass(const struct shell *sh, uint8_t *data, size_t len)
521 {
522 	bool terminate = false;
523 	int res;
524 	size_t write_len = len;
525 
526 	for (size_t i = 0; i < len; i++) {
527 		if (data[i] == ASCII_CTRL_C) {
528 			write_len = i;
529 			terminate = true;
530 			break;
531 		}
532 	}
533 
534 	res = cred_buf_write(data, write_len);
535 	if (res == -ENOMEM) {
536 		shell_set_bypass(sh, NULL);
537 		shell_fprintf(sh, SHELL_ERROR, "Not enough room in credential buffer for "
538 					       "provided data. Increase "
539 					       "CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE.\n");
540 		shell_clear_cred_buf(sh);
541 		return;
542 	}
543 
544 	if (terminate) {
545 		shell_set_bypass(sh, NULL);
546 		shell_fprintf(sh, SHELL_NORMAL, "Stored %d bytes.\n", cred_written);
547 	}
548 }
549 
tls_cred_cmd_buf_clear(const struct shell * sh,size_t argc,char * argv[])550 static int tls_cred_cmd_buf_clear(const struct shell *sh, size_t argc, char *argv[])
551 {
552 	/* If the "clear" keyword is provided, clear the buffer rather than write to it. */
553 	(void)cred_buf_clear();
554 	shell_fprintf(sh, SHELL_NORMAL, "Credential buffer cleared.\n");
555 
556 	return 0;
557 }
558 
tls_cred_cmd_buf_load(const struct shell * sh,size_t argc,char * argv[])559 static int tls_cred_cmd_buf_load(const struct shell *sh, size_t argc, char *argv[])
560 {
561 	shell_clear_cred_buf(sh);
562 
563 	shell_fprintf(sh, SHELL_NORMAL, "Input credential, finish with CTRL+C.\n");
564 	shell_set_bypass(sh, tls_cred_cmd_load_bypass);
565 	return 0;
566 }
567 
568 /* Buffers credential data into the credential buffer. */
tls_cred_cmd_buf(const struct shell * sh,size_t argc,char * argv[])569 static int tls_cred_cmd_buf(const struct shell *sh, size_t argc, char *argv[])
570 {
571 	/* Otherwise, assume provided arg is base64 and attempt to write it into the credential
572 	 * buffer.
573 	 */
574 	return shell_write_cred_buf(sh, argv[1]);
575 }
576 
577 /* Deletes a credential from the credential store */
tls_cred_cmd_del(const struct shell * sh,size_t argc,char * argv[])578 static int tls_cred_cmd_del(const struct shell *sh, size_t argc, char *argv[])
579 {
580 	int err = 0;
581 	sec_tag_t sectag;
582 	enum tls_credential_type type;
583 	struct tls_credential *cred = NULL;
584 	int ref_slot = -1;
585 
586 	/* Lock credentials so that we can safely use internal access functions. */
587 	credentials_lock();
588 
589 	err = shell_parse_cred_sectag(sh, argv[1], &sectag, false);
590 	if (err) {
591 		goto cleanup;
592 	}
593 
594 	err = shell_parse_cred_type(sh, argv[2], &type, false);
595 	if (err) {
596 		goto cleanup;
597 	}
598 
599 	/* Check whether a credential of this type and sectag actually exists. */
600 	cred = credential_get(sectag, type);
601 	if (!cred) {
602 		shell_fprintf(sh, SHELL_ERROR, "There is no TLS credential with sectag %d and "
603 					       "type %s.\n", sectag, cred_type_name(type));
604 		err = -ENOENT;
605 		goto cleanup;
606 	}
607 
608 	ref_slot = cred->buf != NULL ? find_ref_slot(cred->buf) : -1;
609 	if (ref_slot >= 0) {
610 		/* This was a credential we copied to heap. Clear and free it. */
611 		memset(&cred_refs[ref_slot], 0, cred->len);
612 		k_free((void *)cred_refs[ref_slot]);
613 		cred->buf = NULL;
614 
615 		/* Clear the reference slot so it can be used again. */
616 		cred_refs[ref_slot] = NULL;
617 
618 		shell_fprintf(sh, SHELL_NORMAL, "Stored credential freed.\n");
619 	}
620 
621 	/* Attempt to delete. */
622 	err = tls_credential_delete(sectag, type);
623 	if (err) {
624 		shell_fprintf(sh, SHELL_ERROR, "Deleting TLS credential with sectag %d and "
625 					       "type %s failed with error: %d.\n", sectag,
626 					       cred_type_name(type), err);
627 		goto cleanup;
628 	}
629 
630 	shell_fprintf(sh, SHELL_NORMAL, "Deleted TLS credential with sectag %d and type %s.\n",
631 					sectag, cred_type_name(type));
632 
633 cleanup:
634 	/* Unlock credentials since we are done interacting with internal state. */
635 	credentials_unlock();
636 
637 	return err;
638 }
639 
640 /* Retrieves credential data from credential store. */
tls_cred_cmd_get(const struct shell * sh,size_t argc,char * argv[])641 static int tls_cred_cmd_get(const struct shell *sh, size_t argc, char *argv[])
642 {
643 	int i;
644 	int remaining;
645 	int written;
646 	int err = 0;
647 	size_t cred_len;
648 	sec_tag_t sectag;
649 	enum tls_credential_type type;
650 	enum cred_storage_fmt format;
651 	bool terminated;
652 
653 	size_t line_length;
654 
655 	/* Lock credentials so that we can safely use internal access functions. */
656 	credentials_lock();
657 
658 	err = shell_parse_cred_sectag(sh, argv[1], &sectag, false);
659 	if (err) {
660 		goto cleanup;
661 	}
662 
663 	err = shell_parse_cred_type(sh, argv[2], &type, false);
664 	if (err) {
665 		goto cleanup;
666 	}
667 
668 	err = shell_parse_cred_storage_format(sh, argv[3], &format, &terminated);
669 	if (err) {
670 		goto cleanup;
671 	}
672 
673 	line_length = CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH;
674 
675 	/* If the credential is stored as binary, adjust line length so that the output
676 	 * base64 has width CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH
677 	 */
678 	if (format == CRED_STORAGE_FMT_BINARY) {
679 		line_length = CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH / 4 * 3;
680 	}
681 
682 	/* Check whether a credential of this type and sectag actually exists. */
683 	if (!credential_get(sectag, type)) {
684 		shell_fprintf(sh, SHELL_ERROR, "There is no TLS credential with sectag %d and "
685 					       "type %s.\n", sectag, cred_type_name(type));
686 		err = -ENOENT;
687 		goto cleanup;
688 	}
689 
690 	/* Clear the credential buffer before use. */
691 	shell_clear_cred_buf(sh);
692 
693 	/* Load the credential into the credential buffer */
694 	cred_len = sizeof(cred_buf);
695 	err = tls_credential_get(sectag, type, cred_buf, &cred_len);
696 	if (err == -EFBIG) {
697 		shell_fprintf(sh, SHELL_ERROR, "Not enough room in the credential buffer to "
698 					       "retrieve credential with sectag %d and type %s. "
699 					       "Increase TLS_CREDENTIALS_SHELL_MAX_CRED_LEN.\n",
700 					       sectag, cred_type_name(type));
701 		err = -ENOMEM;
702 		goto cleanup;
703 	} else if (err) {
704 		shell_fprintf(sh, SHELL_ERROR, "Could not retrieve TLS credential with sectag %d "
705 					       "and type %s due to error: %d.\n", sectag,
706 					       cred_type_name(type), err);
707 		goto cleanup;
708 	}
709 
710 	/* Update the credential buffer writehead.
711 	 * Keeping this accurate ensures that a "Buffer Cleared" message is eventually printed.
712 	 */
713 	cred_written = cred_len;
714 
715 	/* If the stored credential is NULL-terminated, do not include NULL termination in output */
716 	if (terminated) {
717 		if (cred_buf[cred_written - 1] != 0) {
718 			shell_fprintf(sh, SHELL_ERROR, "The stored credential isn't "
719 						       "NULL-terminated, but a NULL-terminated "
720 						       "format was specified.\n");
721 
722 			err = -EINVAL;
723 			goto cleanup;
724 		}
725 		cred_written -= 1;
726 	}
727 
728 	/* Print the credential out in lines. */
729 	for (i = 0; i < cred_written; i += line_length) {
730 		/* Print either a full line, or however much credential data is left. */
731 		remaining = MIN(line_length, cred_written - i);
732 
733 		/* Read out a line of data. */
734 		memset(cred_out_buf, 0, sizeof(cred_out_buf));
735 		if (format == CRED_STORAGE_FMT_BINARY) {
736 			(void)base64_encode(cred_out_buf, sizeof(cred_out_buf),
737 					    &written, &cred_buf[i], remaining);
738 		} else if (format == CRED_STORAGE_FMT_STRING) {
739 			memcpy(cred_out_buf, &cred_buf[i], remaining);
740 			if (filter_nonprint(cred_out_buf, remaining, '?')) {
741 				err = -EBADF;
742 			}
743 		}
744 
745 		/* Print the line. */
746 		shell_fprintf(sh, SHELL_NORMAL, "%s\n", cred_out_buf);
747 	}
748 
749 	if (err) {
750 		shell_fprintf(sh, SHELL_WARNING, "Non-printable characters were included in the "
751 						 "output and filtered. Have you selected the "
752 						 "correct storage format?\n");
753 	}
754 
755 cleanup:
756 	/* Unlock credentials since we are done interacting with internal state. */
757 	credentials_unlock();
758 
759 	/* Clear buffers when done. */
760 	memset(cred_out_buf, 0, sizeof(cred_out_buf));
761 	shell_clear_cred_buf(sh);
762 
763 	return err;
764 }
765 
766 /* Lists credentials in credential store. */
tls_cred_cmd_list(const struct shell * sh,size_t argc,char * argv[])767 static int tls_cred_cmd_list(const struct shell *sh, size_t argc, char *argv[])
768 {
769 	int err = 0;
770 	size_t digest_size;
771 	sec_tag_t sectag = TLS_SEC_TAG_NONE;
772 	struct tls_credential *cred;
773 	int count = 0;
774 
775 	sec_tag_t sectag_filter = TLS_SEC_TAG_NONE;
776 	enum tls_credential_type type_filter = TLS_CREDENTIAL_NONE;
777 
778 	/* Lock credentials so that we can safely use internal access functions. */
779 	credentials_lock();
780 
781 	/* Sectag filter was provided, parse it. */
782 	if (argc >= 2) {
783 		err = shell_parse_cred_sectag(sh, argv[1], &sectag_filter, true);
784 		if (err) {
785 			goto cleanup;
786 		}
787 	}
788 
789 	/* Credential type filter was provided, parse it. */
790 	if (argc >= 3) {
791 		err = shell_parse_cred_type(sh, argv[2], &type_filter, true);
792 		if (err) {
793 			goto cleanup;
794 		}
795 	}
796 
797 	/* Scan through all occupied sectags */
798 	while ((sectag = credential_next_tag_get(sectag)) != TLS_SEC_TAG_NONE) {
799 		/* Filter by sectag if requested. */
800 		if (sectag_filter != TLS_SEC_TAG_NONE && sectag != sectag_filter) {
801 			continue;
802 		}
803 
804 		cred = NULL;
805 		/* Scan through all credentials within each sectag */
806 		while ((cred = credential_next_get(sectag, cred)) != NULL) {
807 			/* Filter by credential type if requested. */
808 			if (type_filter != TLS_CREDENTIAL_NONE && cred->type != type_filter) {
809 				continue;
810 			}
811 			count++;
812 
813 			/* Generate a digest of the credential */
814 			memset(cred_digest_buf, 0, sizeof(cred_digest_buf));
815 			strcpy(cred_digest_buf, "N/A");
816 			digest_size = sizeof(cred_digest_buf);
817 			err = credential_digest(cred, cred_digest_buf, &digest_size);
818 
819 			/* Print digest and sectag/type info */
820 			shell_fprintf(sh, err ? SHELL_ERROR : SHELL_NORMAL, "%d,%s,%s,%d\n",
821 				      sectag, cred_type_name_compact(cred->type),
822 				      err ? "ERROR" : cred_digest_buf, err);
823 
824 			err = 0;
825 		}
826 	};
827 
828 	shell_fprintf(sh, SHELL_NORMAL, "%d credentials found.\n", count);
829 
830 cleanup:
831 	/* Unlock credentials since we are done interacting with internal state. */
832 	credentials_unlock();
833 
834 	/* Clear digest buffer afterwards for good measure. */
835 	memset(cred_digest_buf, 0, sizeof(cred_digest_buf));
836 
837 	return 0;
838 }
839 
840 SHELL_STATIC_SUBCMD_SET_CREATE(tls_cred_buf_cmds,
841 	SHELL_CMD(clear, NULL, "Clear the credential buffer", tls_cred_cmd_buf_clear),
842 	SHELL_CMD(load, NULL, "Load credential directly to buffer so it can be added.",
843 		      tls_cred_cmd_buf_load),
844 	SHELL_SUBCMD_SET_END
845 );
846 
847 SHELL_STATIC_SUBCMD_SET_CREATE(tls_cred_cmds,
848 	SHELL_CMD_ARG(buf, &tls_cred_buf_cmds, "Buffer in credential data so it can be added.",
849 		      tls_cred_cmd_buf, 2, 0),
850 	SHELL_CMD_ARG(add, NULL, "Add a TLS credential.",
851 		      tls_cred_cmd_add, 5, 1),
852 	SHELL_CMD_ARG(del, NULL, "Delete a TLS credential.",
853 		      tls_cred_cmd_del, 3, 0),
854 	SHELL_CMD_ARG(get, NULL, "Retrieve the contents of a TLS credential",
855 		      tls_cred_cmd_get, 4, 0),
856 	SHELL_CMD_ARG(list, NULL, "List stored TLS credentials, optionally filtering by type "
857 				  "or sectag.",
858 		      tls_cred_cmd_list, 1, 2),
859 	SHELL_SUBCMD_SET_END
860 );
861 
862 SHELL_CMD_REGISTER(cred, &tls_cred_cmds, "TLS Credentials Commands", NULL);
863