Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 246 additions & 5 deletions subsys/net/lib/tls_credentials/tls_credentials_shell.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
#include <strings.h>
#include <ctype.h>

#if defined(CONFIG_PSA_CRYPTO)
#include <psa/crypto.h>
#include <psa/crypto_extra.h>
#endif /* defined(CONFIG_PSA_CRYPTO)*/

#if defined(MBEDTLS_PEM_WRITE_C)
#include <mbedtls/pem.h>
#endif /* defined(MBEDTLS_PEM_WRITE_C)*/

enum cred_storage_fmt {
/* Credential is stored as a string and will be passed between the shell and storage
* unmodified.
Expand Down Expand Up @@ -357,6 +366,19 @@
bool keep_copy = false;
int ref_slot = -1;

if (argc < 5) {
shell_fprintf(sh, SHELL_ERROR,
"Usage: cred add <sectag> <type> <backend> <format> [data]\n");
shell_fprintf(sh, SHELL_ERROR, " <sectag> : Security tag\n");
shell_fprintf(sh, SHELL_ERROR,
" <type> : CA_CERT, SERVER_CERT, PRIVATE_KEY, PSK, PSK_ID\n");
shell_fprintf(sh, SHELL_ERROR, " <backend> : default\n");
shell_fprintf(sh, SHELL_ERROR, " <format> : str, strt, bin, bint\n");
shell_fprintf(sh, SHELL_ERROR,
" [data] : Optional credential data (or use 'cred buf' first)\n");
return -EINVAL;
}

/* Lock credentials so that we can interact with them directly.
* Mainly this is required by credential_get.
*/
Expand Down Expand Up @@ -571,6 +593,17 @@
/* Buffers credential data into the credential buffer. */
static int tls_cred_cmd_buf(const struct shell *sh, size_t argc, char *argv[])
{
if (argc < 2) {
shell_fprintf(sh, SHELL_ERROR, "Usage: cred buf <data>\n");
shell_fprintf(sh, SHELL_ERROR,
" <data> : Base64 encoded credential data to buffer\n");
shell_fprintf(sh, SHELL_ERROR,
"Or use: cred buf clear - Clear the credential buffer\n");
shell_fprintf(sh, SHELL_ERROR,
"Or use: cred buf load - Load credential interactively\n");
return -EINVAL;
}

/* Otherwise, assume provided arg is base64 and attempt to write it into the credential
* buffer.
*/
Expand All @@ -586,6 +619,14 @@
struct tls_credential *cred = NULL;
int ref_slot = -1;

if (argc < 3) {
shell_fprintf(sh, SHELL_ERROR, "Usage: cred del <sectag> <type>\n");
shell_fprintf(sh, SHELL_ERROR, " <sectag> : Security tag\n");
shell_fprintf(sh, SHELL_ERROR,
" <type> : CA_CERT, SERVER_CERT, PRIVATE_KEY, PSK, PSK_ID\n");
return -EINVAL;
}

/* Lock credentials so that we can safely use internal access functions. */
credentials_lock();

Expand Down Expand Up @@ -641,7 +682,7 @@
}

/* Retrieves credential data from credential store. */
static int tls_cred_cmd_get(const struct shell *sh, size_t argc, char *argv[])

Check failure on line 685 in subsys/net/lib/tls_credentials/tls_credentials_shell.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 26 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&issues=AZsECICDIXrebpB_scBE&open=AZsECICDIXrebpB_scBE&pullRequest=100760
{
int i;
int remaining;
Expand All @@ -655,6 +696,15 @@

size_t line_length;

if (argc < 4) {
shell_fprintf(sh, SHELL_ERROR, "Usage: cred get <sectag> <type> <format>\n");
shell_fprintf(sh, SHELL_ERROR, " <sectag> : Security tag\n");
shell_fprintf(sh, SHELL_ERROR,
" <type> : CA_CERT, SERVER_CERT, PRIVATE_KEY, PSK, PSK_ID\n");
shell_fprintf(sh, SHELL_ERROR, " <format> : str, strt, bin, bint\n");
return -EINVAL;
}

/* Lock credentials so that we can safely use internal access functions. */
credentials_lock();

Expand Down Expand Up @@ -767,7 +817,7 @@
}

/* Lists credentials in credential store. */
static int tls_cred_cmd_list(const struct shell *sh, size_t argc, char *argv[])

Check failure on line 820 in subsys/net/lib/tls_credentials/tls_credentials_shell.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&issues=AZsECICDIXrebpB_scBF&open=AZsECICDIXrebpB_scBF&pullRequest=100760
{
int err = 0;
size_t digest_size;
Expand All @@ -778,6 +828,21 @@
sec_tag_t sectag_filter = TLS_SEC_TAG_NONE;
enum tls_credential_type type_filter = TLS_CREDENTIAL_NONE;

/* Show usage on explicit help request */
if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
shell_fprintf(sh, SHELL_NORMAL, "Usage: cred list [sectag] [type]\n");
shell_fprintf(sh, SHELL_NORMAL,
" [sectag] : Filter by security tag (or 'any' for all)\n");
shell_fprintf(sh, SHELL_NORMAL,
" [type] : Filter by credential type (or 'any' for all)\n");
shell_fprintf(
sh, SHELL_NORMAL,
" Types: CA_CERT, SERVER_CERT, PRIVATE_KEY, PSK, PSK_ID\n");
shell_fprintf(sh, SHELL_NORMAL,
"\nOutput format: <sectag>,<type>,<digest>,<error>\n");
return 0;
}

/* Lock credentials so that we can safely use internal access functions. */
credentials_lock();

Expand Down Expand Up @@ -840,6 +905,179 @@
return 0;
}

#if defined(CONFIG_PSA_CRYPTO)
/* Key type parameter for generate_ecdsa_keypair */
enum ecdsa_key_type {
ECDSA_P256 = 0, /* secp256r1 / P-256 */
};

/* Generate ECDSA keypair with specified key type.
* Returns 0 on success or negative error code.
*/
static int generate_ecdsa_keypair(psa_key_id_t *user_keypair_id,
enum ecdsa_key_type key_type)
{
if (user_keypair_id != NULL) {
psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_status_t status;
psa_ecc_family_t ecc_family;
size_t key_bits;

/* Initialize PSA Crypto (Can be done multiple times) */
status = psa_crypto_init();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to add a mutex lock.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

psa_crypto_init can be called any number of times and will just return if already initialized. No mutex necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it's more safe in multi-thread environment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? only one thread can access this function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other threads may call psa_crypto_init(), not only generate_ecdsa_keypair.
Anyway, as the doc said it can be called in multiple times, it's just a suggestion.

if (status != PSA_SUCCESS) {
LOG_ERR("psa_crypto_init() failed! (Error: %d)", status);
return -EFAULT;
}

/* Configure key parameters based on key type */
switch (key_type) {

Check warning on line 934 in subsys/net/lib/tls_credentials/tls_credentials_shell.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this "switch" statement by "if" statements to increase readability.

See more on https://sonarcloud.io/project/issues?id=zephyrproject-rtos_zephyr&issues=AZsECICDIXrebpB_scBG&open=AZsECICDIXrebpB_scBG&pullRequest=100760
case ECDSA_P256:
ecc_family = PSA_ECC_FAMILY_SECP_R1;
key_bits = 256;
break;
default:
LOG_ERR("Unsupported key type: %d", key_type);
return -EINVAL;
}

/* Configure the key attributes */
psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_EXPORT);
psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE);
psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(ecc_family));
psa_set_key_bits(&key_attributes, key_bits);

/* Generate the keypair */
status = psa_generate_key(&key_attributes, user_keypair_id);
if (status != PSA_SUCCESS) {
LOG_ERR("psa_generate_key() failed! (Error: %d)", status);
return -EFAULT;
}

psa_reset_key_attributes(&key_attributes);

return 0;
}
return -EINVAL;
}
#endif /* defined(CONFIG_PSA_CRYPTO)*/

/* Generate a private key on device using ECDSA and store it as a credential
* Returns 0 on success or negative error code.
*/
static int tls_cred_cmd_generate_key(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_PSA_CRYPTO)
int err = 0;
psa_key_id_t keypair_id = 0;
sec_tag_t sectag;
enum ecdsa_key_type key_type;
size_t key_der_len;
size_t key_pem_len;
uint8_t key_der[256]; /* Buffer for DER-formatted key */
uint8_t key_pem[512]; /* Buffer for PEM-formatted key */

if (argc < 2) {
shell_fprintf(sh, SHELL_ERROR, "Usage: generate_key <sectag> [key_type]\n");
shell_fprintf(sh, SHELL_ERROR, "Supported key types: p256 (default)\n");
return -EINVAL;
}

/* Parse sectag */
err = shell_parse_cred_sectag(sh, argv[1], &sectag, false);
if (err) {
return err;
}

/* Parse key type if provided, default to P256 */
key_type = ECDSA_P256;
if (argc >= 3) {
if (strcasecmp(argv[2], "p256") == 0) {
key_type = ECDSA_P256;
} else {
shell_fprintf(sh, SHELL_ERROR, "Unknown key type: %s\n", argv[2]);
shell_fprintf(sh, SHELL_ERROR, "Supported types: p256\n");
return -EINVAL;
}
}

/* Lock credentials to prevent concurrent access */
credentials_lock();

/* Check if credential already exists */
if (credential_get(sectag, TLS_CREDENTIAL_PRIVATE_KEY)) {
shell_fprintf(sh, SHELL_ERROR,
"TLS credential with sectag %d and type PK already exists.\n",
sectag);
err = -EEXIST;
goto cleanup;
}

/* Generate the keypair */
err = generate_ecdsa_keypair(&keypair_id, key_type);
if (err) {
shell_fprintf(sh, SHELL_ERROR, "Failed to generate ECDSA keypair (Error: %d)\n",
err);
goto cleanup;
}

/* Export the private key in DER format */
psa_status_t status = psa_export_key(keypair_id, key_der, sizeof(key_der), &key_der_len);

if (status != PSA_SUCCESS) {
shell_fprintf(sh, SHELL_ERROR, "Failed to export key (Error: %d)\n", status);
err = (int)status;
goto cleanup;
}

#if defined(MBEDTLS_PEM_WRITE_C)
/* Convert DER to PEM format */
err = mbedtls_pem_write_buffer("-----BEGIN EC PRIVATE KEY-----\n",
"-----END EC PRIVATE KEY-----\n", key_der, key_der_len,
key_pem, sizeof(key_pem), &key_pem_len);
if (err != 0) {
shell_fprintf(sh, SHELL_ERROR, "Failed to convert key to PEM format (Error: %d)\n",
err);
goto cleanup;
}

/* Store the PEM-formatted key as a TLS credential */
err = tls_credential_add(sectag, TLS_CREDENTIAL_PRIVATE_KEY, key_pem, key_pem_len);
if (err) {
shell_fprintf(sh, SHELL_ERROR,
"Failed to store private key credential with sectag %d (Error: %d)\n",
sectag, err);
goto cleanup;
}

shell_fprintf(sh, SHELL_NORMAL,
"Successfully generated and stored ECDSA private key at sectag %d\n", sectag);
shell_fprintf(sh, SHELL_NORMAL, "Key type: %s, Format: PEM, Size: %d bytes\n",
(key_type == ECDSA_P256) ? "P-256" : "unknown", key_pem_len);
#else
shell_fprintf(sh, SHELL_ERROR,
"MBEDTLS_PEM_WRITE_C is not enabled. Cannot convert to PEM.\n");
err = -ENOTSUP;
goto cleanup;
#endif /* MBEDTLS_PEM_WRITE_C */

cleanup:
/* Destroy the volatile key handle */
if (keypair_id != 0) {
psa_destroy_key(keypair_id);
}

/* Unlock credentials */
credentials_unlock();

return err;
#else
shell_fprintf(sh, SHELL_ERROR, "PSA_CRYPTO is not enabled. Cannot generate keypair.\n");
return -ENOTSUP;
#endif /* defined(CONFIG_PSA_CRYPTO)*/
}

SHELL_STATIC_SUBCMD_SET_CREATE(tls_cred_buf_cmds,
SHELL_CMD(clear, NULL, "Clear the credential buffer", tls_cred_cmd_buf_clear),
SHELL_CMD(load, NULL, "Load credential directly to buffer so it can be added.",
Expand All @@ -849,16 +1087,19 @@

SHELL_STATIC_SUBCMD_SET_CREATE(tls_cred_cmds,
SHELL_CMD_ARG(buf, &tls_cred_buf_cmds, "Buffer in credential data so it can be added.",
tls_cred_cmd_buf, 2, 0),
tls_cred_cmd_buf, 0, 0),
SHELL_CMD_ARG(add, NULL, "Add a TLS credential.",
tls_cred_cmd_add, 5, 1),
tls_cred_cmd_add, 0, 0),
SHELL_CMD_ARG(del, NULL, "Delete a TLS credential.",
tls_cred_cmd_del, 3, 0),
tls_cred_cmd_del, 0, 0),
SHELL_CMD_ARG(get, NULL, "Retrieve the contents of a TLS credential",
tls_cred_cmd_get, 4, 0),
tls_cred_cmd_get, 0, 0),
SHELL_CMD_ARG(list, NULL, "List stored TLS credentials, optionally filtering by type "
"or sectag.",
tls_cred_cmd_list, 1, 2),
tls_cred_cmd_list, 0, 0),
SHELL_CMD_ARG(generate_key, NULL,
"Generate an ECDSA keypair on device and store as private key credential",
tls_cred_cmd_generate_key, 0, 0),
SHELL_SUBCMD_SET_END
);

Expand Down