812 lines
22 KiB
C
812 lines
22 KiB
C
|
/*
|
||
|
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||
|
*
|
||
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
*/
|
||
|
|
||
|
#include <zephyr/logging/log.h>
|
||
|
LOG_MODULE_REGISTER(tls_credentials_shell, CONFIG_TLS_CREDENTIALS_LOG_LEVEL);
|
||
|
|
||
|
#include <zephyr/kernel.h>
|
||
|
#include <zephyr/shell/shell.h>
|
||
|
#include <zephyr/sys/base64.h>
|
||
|
#include <zephyr/net/tls_credentials.h>
|
||
|
#include "tls_internal.h"
|
||
|
#include <string.h>
|
||
|
#include <strings.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
|
enum cred_storage_fmt {
|
||
|
/* Credential is stored as a string and will be passed between the shell and storage
|
||
|
* unmodified.
|
||
|
*/
|
||
|
CRED_STORAGE_FMT_STRING,
|
||
|
|
||
|
/* Credential is stored as raw binary, and is parsed from base64 before storage and encoded
|
||
|
* back into base64 when retrieved via the shell.
|
||
|
*/
|
||
|
CRED_STORAGE_FMT_BINARY,
|
||
|
};
|
||
|
|
||
|
struct cred_type_string {
|
||
|
char *name;
|
||
|
enum tls_credential_type type;
|
||
|
};
|
||
|
|
||
|
/* The first entry in each credential type group will be used for human-readable shell
|
||
|
* output. The last will be used for compact shell output. The rest are accepted synonyms.
|
||
|
*/
|
||
|
static const struct cred_type_string type_strings[] = {
|
||
|
{"CA_CERT", TLS_CREDENTIAL_CA_CERTIFICATE},
|
||
|
{"CA", TLS_CREDENTIAL_CA_CERTIFICATE},
|
||
|
|
||
|
{"SERVER_CERT", TLS_CREDENTIAL_SERVER_CERTIFICATE},
|
||
|
{"CLIENT_CERT", TLS_CREDENTIAL_SERVER_CERTIFICATE},
|
||
|
{"SELF_CERT", TLS_CREDENTIAL_SERVER_CERTIFICATE},
|
||
|
{"SELF", TLS_CREDENTIAL_SERVER_CERTIFICATE},
|
||
|
{"CLIENT", TLS_CREDENTIAL_SERVER_CERTIFICATE},
|
||
|
{"SERV", TLS_CREDENTIAL_SERVER_CERTIFICATE},
|
||
|
|
||
|
{"PRIVATE_KEY", TLS_CREDENTIAL_PRIVATE_KEY},
|
||
|
{"PK", TLS_CREDENTIAL_PRIVATE_KEY},
|
||
|
|
||
|
{"PRE_SHARED_KEY", TLS_CREDENTIAL_PSK},
|
||
|
{"PSK", TLS_CREDENTIAL_PSK},
|
||
|
|
||
|
{"PRE_SHARED_KEY_ID", TLS_CREDENTIAL_PSK_ID},
|
||
|
{"PSK_ID", TLS_CREDENTIAL_PSK_ID}
|
||
|
};
|
||
|
|
||
|
#define ANY_KEYWORD "any"
|
||
|
|
||
|
/* This is so that we can output base64 in chunks of this length if necessary */
|
||
|
BUILD_ASSERT(
|
||
|
(CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH % 4) == 0,
|
||
|
"CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH must be a multiple of 4."
|
||
|
);
|
||
|
|
||
|
/* Output buffers used for printing credentials and digests.
|
||
|
* One extra byte included for NULL termination.
|
||
|
*/
|
||
|
static char cred_out_buf[CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH + 1];
|
||
|
static char cred_digest_buf[CONFIG_TLS_CREDENTIALS_SHELL_DIGEST_BUF_SIZE + 1];
|
||
|
|
||
|
/* Internal buffer used for storing and retrieving credentials.
|
||
|
* +1 byte for potential NULL termination.
|
||
|
*/
|
||
|
static char cred_buf[CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE + 1];
|
||
|
static size_t cred_written;
|
||
|
|
||
|
/* Some backends (namely, the volatile backend) store a reference rather than a copy of passed-in
|
||
|
* credentials. For these backends, we need to copy incoming credentials onto the heap before
|
||
|
* attempting to store them.
|
||
|
*
|
||
|
* Since the backend in use is determined at build time by KConfig, so is this behavior.
|
||
|
* If multi/dynamic-backend support is ever added, this will need to be updated.
|
||
|
*/
|
||
|
#define COPY_CREDENTIALS_TO_HEAP CONFIG_TLS_CREDENTIALS_BACKEND_VOLATILE
|
||
|
|
||
|
/* Used to track credentials that have been copied permanently to the heap, in case they are
|
||
|
* ever deleted and need to be freed.
|
||
|
*/
|
||
|
static void *cred_refs[CONFIG_TLS_MAX_CREDENTIALS_NUMBER];
|
||
|
|
||
|
/* Find an empty slot in the cred_refs array, or return -1 if none exists.
|
||
|
* Pass NULL to find an unused slot.
|
||
|
*/
|
||
|
static int find_ref_slot(const void *const cred)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(cred_refs); i++) {
|
||
|
if (cred_refs[i] == cred) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* Helpers */
|
||
|
|
||
|
/* Filter out non-printable characters from a passed-in string of known length */
|
||
|
static int filter_nonprint(char *buf, size_t len, char inval)
|
||
|
{
|
||
|
int i;
|
||
|
int ret = 0;
|
||
|
|
||
|
for (i = 0; i < len; i++) {
|
||
|
if (!isprint((int)buf[i])) {
|
||
|
buf[i] = inval;
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Verify that a provided string consists only of the characters 0-9*/
|
||
|
static bool check_numeric(char *str)
|
||
|
{
|
||
|
int i;
|
||
|
int len = strlen(str);
|
||
|
|
||
|
for (i = 0; i < len; i++) {
|
||
|
if (!isdigit((int)str[i])) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Clear the credential write buffer, returns true if anything was actually cleared. */
|
||
|
static bool cred_buf_clear(void)
|
||
|
{
|
||
|
bool cleared = cred_written != 0;
|
||
|
|
||
|
(void)memset(cred_buf, 0, sizeof(cred_buf));
|
||
|
cred_written = 0;
|
||
|
|
||
|
return cleared;
|
||
|
}
|
||
|
|
||
|
/* Parse a (possibly incomplete) chunk into the credential buffer */
|
||
|
static int cred_buf_write(char *chunk)
|
||
|
{
|
||
|
char *writehead = cred_buf + cred_written;
|
||
|
size_t chunk_len = strlen(chunk);
|
||
|
|
||
|
/* Verify that there is room for the incoming chunk */
|
||
|
if ((writehead + chunk_len) >= (cred_buf + sizeof(cred_buf) - 1)) {
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
/* Append chunk to the credential buffer.
|
||
|
* Deliberately do not copy NULL terminator.
|
||
|
*/
|
||
|
memcpy(writehead, chunk, chunk_len);
|
||
|
cred_written += chunk_len;
|
||
|
|
||
|
return chunk_len;
|
||
|
}
|
||
|
|
||
|
/* Get the human-readable name of a TLS credential type */
|
||
|
static const char *cred_type_name(enum tls_credential_type type)
|
||
|
{
|
||
|
/* Scan over predefined type strings, and return the name
|
||
|
* of the first one of matching type.
|
||
|
*/
|
||
|
for (int i = 0; i < ARRAY_SIZE(type_strings); i++) {
|
||
|
if (type_strings[i].type == type) {
|
||
|
return type_strings[i].name;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* No matches found, it's invalid. */
|
||
|
return "INVALID";
|
||
|
}
|
||
|
|
||
|
/* Get the compact name of a TLS credential type*/
|
||
|
static const char *cred_type_name_compact(enum tls_credential_type type)
|
||
|
{
|
||
|
/* Scan over predefined type strings, and return the name
|
||
|
* of the last one of matching type.
|
||
|
*/
|
||
|
for (int i = ARRAY_SIZE(type_strings) - 1; i >= 0; i--) {
|
||
|
if (type_strings[i].type == type) {
|
||
|
return type_strings[i].name;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* No matches found, it's invalid. */
|
||
|
return "INV";
|
||
|
}
|
||
|
|
||
|
/* Shell interface routines */
|
||
|
|
||
|
/* Attempt to parse a command line argument into a sectag.
|
||
|
* TLS_SEC_TAG_NONE is returned if ANY_KEYWORD is provided.
|
||
|
*/
|
||
|
static int shell_parse_cred_sectag(const struct shell *sh, char *arg, sec_tag_t *out,
|
||
|
bool allow_any)
|
||
|
{
|
||
|
unsigned long sectag_value;
|
||
|
int err = 0;
|
||
|
|
||
|
/* Check for "ANY" special keyword if desired. */
|
||
|
if (allow_any && strcasecmp(arg, ANY_KEYWORD) == 0) {
|
||
|
*out = TLS_SEC_TAG_NONE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Otherwise, verify that the sectag is purely numeric */
|
||
|
if (!check_numeric(arg)) {
|
||
|
err = -EINVAL;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Use strtoul because it has nicer validation features than atoi */
|
||
|
sectag_value = shell_strtoul(arg, 10, &err);
|
||
|
|
||
|
if (!err) {
|
||
|
*out = (sec_tag_t)sectag_value;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
error:
|
||
|
shell_fprintf(sh, SHELL_ERROR, "%s is not a valid sectag.\n", arg);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* Attempt to parse a command line argument into a credential type.
|
||
|
* TLS_CREDENTIAL_NONE is returned if ANY_KEYWORD is provided.
|
||
|
*/
|
||
|
static int shell_parse_cred_type(const struct shell *sh, char *arg, enum tls_credential_type *out,
|
||
|
bool allow_any)
|
||
|
{
|
||
|
/* Check for "ANY" special keyword if desired. */
|
||
|
if (allow_any && strcasecmp(arg, ANY_KEYWORD) == 0) {
|
||
|
*out = TLS_CREDENTIAL_NONE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Otherwise, scan over predefined type strings, and return the corresponding
|
||
|
* credential type if one is found
|
||
|
*/
|
||
|
for (int i = 0; i < ARRAY_SIZE(type_strings); i++) {
|
||
|
if (strcasecmp(arg, type_strings[i].name) == 0) {
|
||
|
*out = type_strings[i].type;
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* No matches found, it's invalid. */
|
||
|
shell_fprintf(sh, SHELL_ERROR, "%s is not a valid credential type.\n", arg);
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Parse a backend specifier argument
|
||
|
* Right now, only a single backend is supported, so this is serving simply as a reserved argument.
|
||
|
* As such, the only valid input is "default"
|
||
|
*/
|
||
|
static int shell_parse_cred_backend(const struct shell *sh, char *arg)
|
||
|
{
|
||
|
if (strcasecmp(arg, "default") == 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
shell_fprintf(sh, SHELL_ERROR, "%s is not a valid backend.\n", arg);
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Parse an input type specifier */
|
||
|
static int shell_parse_cred_storage_format(const struct shell *sh, char *arg,
|
||
|
enum cred_storage_fmt *out, bool *terminated)
|
||
|
{
|
||
|
if (strcasecmp(arg, "bin") == 0) {
|
||
|
*out = CRED_STORAGE_FMT_BINARY;
|
||
|
*terminated = false;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (strcasecmp(arg, "bint") == 0) {
|
||
|
*out = CRED_STORAGE_FMT_BINARY;
|
||
|
*terminated = true;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (strcasecmp(arg, "str") == 0) {
|
||
|
*out = CRED_STORAGE_FMT_STRING;
|
||
|
*terminated = false;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (strcasecmp(arg, "strt") == 0) {
|
||
|
*out = CRED_STORAGE_FMT_STRING;
|
||
|
*terminated = true;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
shell_fprintf(sh, SHELL_ERROR, "%s is not a valid storage format.\n", arg);
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Clear credential buffer, with shell feedback */
|
||
|
static void shell_clear_cred_buf(const struct shell *sh)
|
||
|
{
|
||
|
/* We will only print a message if some data was actually wiped. */
|
||
|
if (cred_buf_clear()) {
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "Credential buffer cleared.\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Write data into the credential buffer, with shell feedback. */
|
||
|
static int shell_write_cred_buf(const struct shell *sh, char *chunk)
|
||
|
{
|
||
|
int res = cred_buf_write(chunk);
|
||
|
|
||
|
/* Report results. */
|
||
|
|
||
|
if (res == -ENOMEM) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Not enough room in credential buffer for "
|
||
|
"provided data. Increase "
|
||
|
"CONFIG_TLS_CREDENTIALS_SHELL_CRED_BUF_SIZE.\n");
|
||
|
shell_clear_cred_buf(sh);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "Stored %d bytes.\n", res);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Adds a credential to the credential store */
|
||
|
static int tls_cred_cmd_add(const struct shell *sh, size_t argc, char *argv[])
|
||
|
{
|
||
|
int err = 0;
|
||
|
sec_tag_t sectag;
|
||
|
enum cred_storage_fmt format;
|
||
|
bool terminated;
|
||
|
enum tls_credential_type type;
|
||
|
void *cred_copy = NULL;
|
||
|
void *cred_chosen = NULL;
|
||
|
bool keep_copy = false;
|
||
|
int ref_slot = -1;
|
||
|
|
||
|
/* Lock credentials so that we can interact with them directly.
|
||
|
* Mainly this is required by credential_get.
|
||
|
*/
|
||
|
|
||
|
credentials_lock();
|
||
|
|
||
|
err = shell_parse_cred_sectag(sh, argv[1], §ag, false);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = shell_parse_cred_type(sh, argv[2], &type, false);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = shell_parse_cred_backend(sh, argv[3]);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = shell_parse_cred_storage_format(sh, argv[4], &format, &terminated);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
if (argc == 6) {
|
||
|
/* Credential was passed, clear credential buffer and then use the passed-in
|
||
|
* credential.
|
||
|
*/
|
||
|
shell_clear_cred_buf(sh);
|
||
|
err = shell_write_cred_buf(sh, argv[5]);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Make sure the credential buffer isn't empty. */
|
||
|
if (cred_written == 0) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Please provide a credential to add.\n");
|
||
|
err = -ENOENT;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Check whether a credential of this type and sectag already exists. */
|
||
|
if (credential_get(sectag, type)) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "TLS credential with sectag %d and type %s "
|
||
|
"already exists.\n", sectag, cred_type_name(type));
|
||
|
err = -EEXIST;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* If binary format was specified, decode from base64. */
|
||
|
if (format == CRED_STORAGE_FMT_BINARY) {
|
||
|
/* base64_decode can handle in-place operation.
|
||
|
* Pass &cred_written as olen so that it is updated to match the size of the base64
|
||
|
* encoding.
|
||
|
*
|
||
|
* We use sizeof(cred_buf) - 1 since we want to keep room fors a NULL terminator.
|
||
|
* Though, technically, this is not actually needed since the output of
|
||
|
* base64_decode is always shorter than its input.
|
||
|
*/
|
||
|
err = base64_decode(cred_buf, sizeof(cred_buf) - 1, &cred_written,
|
||
|
cred_buf, cred_written);
|
||
|
if (err) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Could not decode input from base64, "
|
||
|
"error: %d\n", err);
|
||
|
err = -EINVAL;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If NULL termination was requested, append one.
|
||
|
* We are always guaranteed to have room in the buffer for this.
|
||
|
*/
|
||
|
if (terminated) {
|
||
|
cred_buf[cred_written] = 0;
|
||
|
cred_written += 1;
|
||
|
}
|
||
|
|
||
|
/* Default to using cred_buf directly. */
|
||
|
cred_chosen = cred_buf;
|
||
|
|
||
|
/* If the currently active TLS Credentials backend stores credentials by reference,
|
||
|
* copy the incoming credentials off to the heap, and then use this copy instead.
|
||
|
*/
|
||
|
if (IS_ENABLED(COPY_CREDENTIALS_TO_HEAP)) {
|
||
|
/* Before copying the credential to heap, make sure we are able to store a
|
||
|
* reference to it so that it can be freed if the credential is ever deleted.
|
||
|
*/
|
||
|
|
||
|
ref_slot = find_ref_slot(NULL);
|
||
|
|
||
|
if (ref_slot < 0) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "No reference slot available, cannot copy "
|
||
|
"credential to heap. Credential will not be "
|
||
|
"stored\n");
|
||
|
err = -ENOMEM;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cred_copy = k_malloc(cred_written);
|
||
|
if (!cred_copy) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Not enough heap for TLS credential of "
|
||
|
"size %d.\n", cred_written);
|
||
|
err = -ENOMEM;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
memset(cred_copy, 0, cred_written);
|
||
|
memcpy(cred_copy, cred_buf, cred_written);
|
||
|
|
||
|
shell_fprintf(sh, SHELL_WARNING, "Credential has been copied to heap. Memory will "
|
||
|
"be leaked if this credential is deleted without "
|
||
|
"using the shell.\n");
|
||
|
|
||
|
cred_chosen = cred_copy;
|
||
|
}
|
||
|
|
||
|
/* Finally, store the credential in whatever credentials backend is active. */
|
||
|
err = tls_credential_add(sectag, type, cred_chosen, cred_written);
|
||
|
if (err) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Failed to add TLS credential with sectag %d and "
|
||
|
"type %s. Error: %d\n.", sectag,
|
||
|
cred_type_name(type), err);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Do not free the copied key during cleanup, since it was successfully written. */
|
||
|
keep_copy = true;
|
||
|
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "Added TLS credential of type %s, sectag %d, and length %d "
|
||
|
"bytes.\n", cred_type_name(type), sectag, cred_written);
|
||
|
|
||
|
cleanup:
|
||
|
/* Unlock credentials since we are done interacting with internal state. */
|
||
|
credentials_unlock();
|
||
|
|
||
|
/* We are also done with the credentials buffer, so clear it for good measure. */
|
||
|
shell_clear_cred_buf(sh);
|
||
|
|
||
|
/* If we copied the credential, make sure it is eventually freed. */
|
||
|
if (cred_copy) {
|
||
|
if (keep_copy) {
|
||
|
/* If the credential was successfully stored, keep a reference to it in case
|
||
|
* it is ever deleted and needs to be freed.
|
||
|
*/
|
||
|
cred_refs[ref_slot] = cred_copy;
|
||
|
} else {
|
||
|
/* Otherwise, clear and free it immediately */
|
||
|
memset(cred_copy, 0, cred_written);
|
||
|
(void)k_free(cred_copy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* Buffers credential data into the credential buffer. */
|
||
|
static int tls_cred_cmd_buf(const struct shell *sh, size_t argc, char *argv[])
|
||
|
{
|
||
|
/* If the "clear" keyword is provided, clear the buffer rather than write to it. */
|
||
|
if (strcmp(argv[1], "clear") == 0) {
|
||
|
shell_clear_cred_buf(sh);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Otherwise, assume provided arg is base64 and attempt to write it into the credential
|
||
|
* buffer.
|
||
|
*/
|
||
|
return shell_write_cred_buf(sh, argv[1]);
|
||
|
}
|
||
|
|
||
|
/* Deletes a credential from the credential store */
|
||
|
static int tls_cred_cmd_del(const struct shell *sh, size_t argc, char *argv[])
|
||
|
{
|
||
|
int err = 0;
|
||
|
sec_tag_t sectag;
|
||
|
enum tls_credential_type type;
|
||
|
struct tls_credential *cred = NULL;
|
||
|
int ref_slot = -1;
|
||
|
|
||
|
/* Lock credentials so that we can safely use internal access functions. */
|
||
|
credentials_lock();
|
||
|
|
||
|
err = shell_parse_cred_sectag(sh, argv[1], §ag, false);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = shell_parse_cred_type(sh, argv[2], &type, false);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Check whether a credential of this type and sectag actually exists. */
|
||
|
cred = credential_get(sectag, type);
|
||
|
if (!cred) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "There is no TLS credential with sectag %d and "
|
||
|
"type %s.\n", sectag, cred_type_name(type));
|
||
|
err = -ENOENT;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
ref_slot = find_ref_slot(cred->buf);
|
||
|
if (ref_slot >= 0) {
|
||
|
/* This was a credential we copied to heap. Clear and free it. */
|
||
|
memset((void *)cred_buf, 0, cred->len);
|
||
|
k_free((void *)cred_buf);
|
||
|
cred->buf = NULL;
|
||
|
|
||
|
/* Clear the reference slot so it can be used again. */
|
||
|
cred_refs[ref_slot] = NULL;
|
||
|
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "Stored credential freed.\n");
|
||
|
}
|
||
|
|
||
|
/* Attempt to delete. */
|
||
|
err = tls_credential_delete(sectag, type);
|
||
|
if (err) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Deleting TLS credential with sectag %d and "
|
||
|
"type %s failed with error: %d.\n", sectag,
|
||
|
cred_type_name(type), err);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "Deleted TLS credential with sectag %d and type %s.\n",
|
||
|
sectag, cred_type_name(type));
|
||
|
|
||
|
cleanup:
|
||
|
/* Unlock credentials since we are done interacting with internal state. */
|
||
|
credentials_unlock();
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* Retrieves credential data from credential store. */
|
||
|
static int tls_cred_cmd_get(const struct shell *sh, size_t argc, char *argv[])
|
||
|
{
|
||
|
int i;
|
||
|
int remaining;
|
||
|
int written;
|
||
|
int err = 0;
|
||
|
size_t cred_len;
|
||
|
sec_tag_t sectag;
|
||
|
enum tls_credential_type type;
|
||
|
enum cred_storage_fmt format;
|
||
|
bool terminated;
|
||
|
|
||
|
size_t line_length;
|
||
|
|
||
|
/* Lock credentials so that we can safely use internal access functions. */
|
||
|
credentials_lock();
|
||
|
|
||
|
err = shell_parse_cred_sectag(sh, argv[1], §ag, false);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = shell_parse_cred_type(sh, argv[2], &type, false);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = shell_parse_cred_storage_format(sh, argv[3], &format, &terminated);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
line_length = CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH;
|
||
|
|
||
|
/* If the credential is stored as binary, adjust line length so that the output
|
||
|
* base64 has width CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH
|
||
|
*/
|
||
|
if (format == CRED_STORAGE_FMT_BINARY) {
|
||
|
line_length = CONFIG_TLS_CREDENTIALS_SHELL_CRED_OUTPUT_WIDTH / 4 * 3;
|
||
|
}
|
||
|
|
||
|
/* Check whether a credential of this type and sectag actually exists. */
|
||
|
if (!credential_get(sectag, type)) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "There is no TLS credential with sectag %d and "
|
||
|
"type %s.\n", sectag, cred_type_name(type));
|
||
|
err = -ENOENT;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Clear the credential buffer before use. */
|
||
|
shell_clear_cred_buf(sh);
|
||
|
|
||
|
/* Load the credential into the credential buffer */
|
||
|
cred_len = sizeof(cred_buf);
|
||
|
err = tls_credential_get(sectag, type, cred_buf, &cred_len);
|
||
|
if (err == -EFBIG) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Not enough room in the credential buffer to "
|
||
|
"retrieve credential with sectag %d and type %s. "
|
||
|
"Increase TLS_CREDENTIALS_SHELL_MAX_CRED_LEN.\n",
|
||
|
sectag, cred_type_name(type));
|
||
|
err = -ENOMEM;
|
||
|
goto cleanup;
|
||
|
} else if (err) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "Could not retrieve TLS credential with sectag %d "
|
||
|
"and type %s due to error: %d.\n", sectag,
|
||
|
cred_type_name(type), err);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Update the credential buffer writehead.
|
||
|
* Keeping this accurate ensures that a "Buffer Cleared" message is eventually printed.
|
||
|
*/
|
||
|
cred_written = cred_len;
|
||
|
|
||
|
/* If the stored credential is NULL-terminated, do not include NULL termination in output */
|
||
|
if (terminated) {
|
||
|
if (cred_buf[cred_written - 1] != 0) {
|
||
|
shell_fprintf(sh, SHELL_ERROR, "The stored credential isn't "
|
||
|
"NULL-terminated, but a NULL-terminated "
|
||
|
"format was specified.\n");
|
||
|
|
||
|
err = -EINVAL;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
cred_written -= 1;
|
||
|
}
|
||
|
|
||
|
/* Print the credential out in lines. */
|
||
|
for (i = 0; i < cred_written; i += line_length) {
|
||
|
/* Print either a full line, or however much credential data is left. */
|
||
|
remaining = MIN(line_length, cred_written - i);
|
||
|
|
||
|
/* Read out a line of data. */
|
||
|
memset(cred_out_buf, 0, sizeof(cred_out_buf));
|
||
|
if (format == CRED_STORAGE_FMT_BINARY) {
|
||
|
(void)base64_encode(cred_out_buf, sizeof(cred_out_buf),
|
||
|
&written, &cred_buf[i], remaining);
|
||
|
} else if (format == CRED_STORAGE_FMT_STRING) {
|
||
|
memcpy(cred_out_buf, &cred_buf[i], remaining);
|
||
|
if (filter_nonprint(cred_out_buf, remaining, '?')) {
|
||
|
err = -EBADF;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Print the line. */
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "%s\n", cred_out_buf);
|
||
|
}
|
||
|
|
||
|
if (err) {
|
||
|
shell_fprintf(sh, SHELL_WARNING, "Non-printable characters were included in the "
|
||
|
"output and filtered. Have you selected the "
|
||
|
"correct storage format?\n");
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
/* Unlock credentials since we are done interacting with internal state. */
|
||
|
credentials_unlock();
|
||
|
|
||
|
/* Clear buffers when done. */
|
||
|
memset(cred_out_buf, 0, sizeof(cred_out_buf));
|
||
|
shell_clear_cred_buf(sh);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* Lists credentials in credential store. */
|
||
|
static int tls_cred_cmd_list(const struct shell *sh, size_t argc, char *argv[])
|
||
|
{
|
||
|
int err = 0;
|
||
|
size_t digest_size;
|
||
|
sec_tag_t sectag = TLS_SEC_TAG_NONE;
|
||
|
struct tls_credential *cred;
|
||
|
int count = 0;
|
||
|
|
||
|
sec_tag_t sectag_filter = TLS_SEC_TAG_NONE;
|
||
|
enum tls_credential_type type_filter = TLS_CREDENTIAL_NONE;
|
||
|
|
||
|
/* Lock credentials so that we can safely use internal access functions. */
|
||
|
credentials_lock();
|
||
|
|
||
|
/* Sectag filter was provided, parse it. */
|
||
|
if (argc >= 2) {
|
||
|
err = shell_parse_cred_sectag(sh, argv[1], §ag_filter, true);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Credential type filter was provided, parse it. */
|
||
|
if (argc >= 3) {
|
||
|
err = shell_parse_cred_type(sh, argv[2], &type_filter, true);
|
||
|
if (err) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Scan through all occupied sectags */
|
||
|
while ((sectag = credential_next_tag_get(sectag)) != TLS_SEC_TAG_NONE) {
|
||
|
/* Filter by sectag if requested. */
|
||
|
if (sectag_filter != TLS_SEC_TAG_NONE && sectag != sectag_filter) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
cred = NULL;
|
||
|
/* Scan through all credentials within each sectag */
|
||
|
while ((cred = credential_next_get(sectag, cred)) != NULL) {
|
||
|
/* Filter by credential type if requested. */
|
||
|
if (type_filter != TLS_CREDENTIAL_NONE && cred->type != type_filter) {
|
||
|
continue;
|
||
|
}
|
||
|
count++;
|
||
|
|
||
|
/* Generate a digest of the credential */
|
||
|
memset(cred_digest_buf, 0, sizeof(cred_digest_buf));
|
||
|
strcpy(cred_digest_buf, "N/A");
|
||
|
digest_size = sizeof(cred_digest_buf);
|
||
|
err = credential_digest(cred, cred_digest_buf, &digest_size);
|
||
|
|
||
|
/* Print digest and sectag/type info */
|
||
|
shell_fprintf(sh, err ? SHELL_ERROR : SHELL_NORMAL, "%d,%s,%s,%d\n",
|
||
|
sectag, cred_type_name_compact(cred->type),
|
||
|
err ? "ERROR" : cred_digest_buf, err);
|
||
|
|
||
|
err = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
shell_fprintf(sh, SHELL_NORMAL, "%d credentials found.\n", count);
|
||
|
|
||
|
cleanup:
|
||
|
/* Unlock credentials since we are done interacting with internal state. */
|
||
|
credentials_unlock();
|
||
|
|
||
|
/* Clear digest buffer afterwards for good measure. */
|
||
|
memset(cred_digest_buf, 0, sizeof(cred_digest_buf));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
SHELL_STATIC_SUBCMD_SET_CREATE(tls_cred_cmds,
|
||
|
SHELL_CMD_ARG(buf, NULL, "Buffer in credential data so it can be added.",
|
||
|
tls_cred_cmd_buf, 2, 0),
|
||
|
SHELL_CMD_ARG(add, NULL, "Add a TLS credential.",
|
||
|
tls_cred_cmd_add, 5, 1),
|
||
|
SHELL_CMD_ARG(del, NULL, "Delete a TLS credential.",
|
||
|
tls_cred_cmd_del, 3, 0),
|
||
|
SHELL_CMD_ARG(get, NULL, "Retrieve the contents of a TLS credential",
|
||
|
tls_cred_cmd_get, 4, 0),
|
||
|
SHELL_CMD_ARG(list, NULL, "List stored TLS credentials, optionally filtering by type "
|
||
|
"or sectag.",
|
||
|
tls_cred_cmd_list, 1, 2),
|
||
|
SHELL_SUBCMD_SET_END
|
||
|
);
|
||
|
|
||
|
SHELL_CMD_REGISTER(cred, &tls_cred_cmds, "TLS Credentials Commands", NULL);
|