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], §ag, 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], §ag, 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], §ag, 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], §ag_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