f5d12102a0
Add (internal) support for sectag iterating. Also officially marks negative sectag values as reserved for internal use. This will allow a prospective TLS credentials shell to iterate over all available credentials. Signed-off-by: Georges Oates_Larsen <georges.larsen@nordicsemi.no>
459 lines
9.6 KiB
C
459 lines
9.6 KiB
C
/*
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <string.h>
|
|
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#include <psa/protected_storage.h>
|
|
|
|
#include "tls_internal.h"
|
|
#include "tls_credentials_digest_raw.h"
|
|
|
|
LOG_MODULE_REGISTER(tls_credentials_trusted,
|
|
CONFIG_TLS_CREDENTIALS_LOG_LEVEL);
|
|
|
|
/* This implementation uses the PSA Protected Storage API to store:
|
|
* - credentials with an UID constructed as:
|
|
* [ C2E0 ] | [ type as uint16_t ] | [ tag as uint32_t ]
|
|
* - credential ToC with an UID constructed as:
|
|
* [ C2E0 ] | [ ffff as uint16_t ] | [ ffffffff as uint32_t ]
|
|
*
|
|
* The ToC contains a list of CONFIG_TLS_MAX_CREDENTIALS_NUMBER UIDs
|
|
* of credentials, can be 0 if slot is free.
|
|
*/
|
|
|
|
#define PSA_PS_CRED_ID 0xC2E0ULL
|
|
|
|
#define CRED_MAX_SLOTS CONFIG_TLS_MAX_CREDENTIALS_NUMBER
|
|
|
|
/* Global temporary pool of credentials to be used by TLS contexts. */
|
|
static struct tls_credential credentials[CRED_MAX_SLOTS];
|
|
|
|
/* Credentials Table Of Content copy of the one stored in Protected Storage */
|
|
static psa_storage_uid_t credentials_toc[CRED_MAX_SLOTS];
|
|
|
|
/* A mutex for protecting access to the credentials array. */
|
|
static struct k_mutex credential_lock;
|
|
|
|
/* Construct PSA PS uid from tag & type */
|
|
static inline psa_storage_uid_t tls_credential_get_uid(uint32_t tag,
|
|
uint16_t type)
|
|
{
|
|
return PSA_PS_CRED_ID << 48 |
|
|
(type & 0xffffULL) << 32 |
|
|
(tag & 0xffffffff);
|
|
}
|
|
|
|
#define PSA_PS_CRED_TOC_ID tls_credential_get_uid(0xffffffff, 0xffff)
|
|
|
|
/* Get the TAG from an UID */
|
|
static inline sec_tag_t tls_credential_uid_to_tag(psa_storage_uid_t uid)
|
|
{
|
|
return (uid & 0xffffffff);
|
|
}
|
|
|
|
/* Get the TYPE from an UID */
|
|
static inline int tls_credential_uid_to_type(psa_storage_uid_t uid)
|
|
{
|
|
return ((uid >> 32) & 0xffff);
|
|
}
|
|
|
|
static int credentials_toc_get(void)
|
|
{
|
|
psa_status_t status;
|
|
size_t len;
|
|
|
|
status = psa_ps_get(PSA_PS_CRED_TOC_ID, 0, sizeof(credentials_toc),
|
|
credentials_toc, &len);
|
|
if (status == PSA_ERROR_DOES_NOT_EXIST) {
|
|
return -ENOENT;
|
|
} else if (status != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int credentials_toc_write(void)
|
|
{
|
|
psa_status_t status;
|
|
|
|
status = psa_ps_set(PSA_PS_CRED_TOC_ID, sizeof(credentials_toc),
|
|
credentials_toc, 0);
|
|
if (status != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int credentials_toc_update(unsigned int slot, psa_storage_uid_t uid)
|
|
{
|
|
int ret;
|
|
|
|
if (slot >= CRED_MAX_SLOTS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
credentials_toc[slot] = uid;
|
|
|
|
ret = credentials_toc_write();
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
return credentials_toc_get();
|
|
}
|
|
|
|
static unsigned int tls_credential_toc_find_slot(psa_storage_uid_t uid)
|
|
{
|
|
unsigned int slot;
|
|
|
|
for (slot = 0; slot < CRED_MAX_SLOTS; ++slot) {
|
|
if (credentials_toc[slot] == uid) {
|
|
return slot;
|
|
}
|
|
}
|
|
|
|
return CRED_MAX_SLOTS;
|
|
}
|
|
|
|
static int credentials_init(void)
|
|
{
|
|
struct psa_storage_info_t info;
|
|
unsigned int sync = 0;
|
|
psa_status_t status;
|
|
unsigned int slot;
|
|
int ret;
|
|
|
|
/* Retrieve Table of Content from storage */
|
|
ret = credentials_toc_get();
|
|
if (ret == -ENOENT) {
|
|
memset(credentials_toc, 0, sizeof(credentials_toc));
|
|
return 0;
|
|
} else if (ret != 0) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* Check validity of ToC */
|
|
for (slot = 0; slot < CRED_MAX_SLOTS; ++slot) {
|
|
if (credentials_toc[slot] == 0) {
|
|
continue;
|
|
}
|
|
|
|
status = psa_ps_get_info(credentials_toc[slot], &info);
|
|
if (status == PSA_ERROR_DOES_NOT_EXIST) {
|
|
LOG_WRN("Credential %d doesn't exist in storage", slot);
|
|
credentials_toc[slot] = 0;
|
|
sync = 1;
|
|
} else if (status != PSA_SUCCESS) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
if (sync != 0) {
|
|
ret = credentials_toc_write();
|
|
if (ret != 0) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
SYS_INIT(credentials_init, POST_KERNEL, 0);
|
|
|
|
static struct tls_credential *unused_credential_get(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(credentials); i++) {
|
|
if (credentials[i].type == TLS_CREDENTIAL_NONE) {
|
|
return &credentials[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Get a credential struct from an UID */
|
|
static struct tls_credential *credential_get_from_uid(psa_storage_uid_t uid)
|
|
{
|
|
struct tls_credential *credential;
|
|
struct psa_storage_info_t info;
|
|
psa_status_t status;
|
|
|
|
if (tls_credential_toc_find_slot(uid) == CRED_MAX_SLOTS) {
|
|
return NULL;
|
|
}
|
|
|
|
credential = unused_credential_get();
|
|
if (credential == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
status = psa_ps_get_info(uid, &info);
|
|
if (status != PSA_SUCCESS) {
|
|
return NULL;
|
|
}
|
|
|
|
credential->buf = k_malloc(info.size);
|
|
if (credential->buf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
status = psa_ps_get(uid, 0, info.size, (void *)credential->buf,
|
|
&credential->len);
|
|
if (status != PSA_SUCCESS) {
|
|
k_free((void *)credential->buf);
|
|
credential->buf = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
credential->tag = tls_credential_uid_to_tag(uid);
|
|
credential->type = tls_credential_uid_to_type(uid);
|
|
|
|
return credential;
|
|
}
|
|
|
|
/* Get a credential struct from a TAG and TYPE values */
|
|
struct tls_credential *credential_get(sec_tag_t tag,
|
|
enum tls_credential_type type)
|
|
{
|
|
return credential_get_from_uid(tls_credential_get_uid(tag, type));
|
|
}
|
|
|
|
|
|
/* Get the following credential filtered by a TAG valud */
|
|
struct tls_credential *credential_next_get(sec_tag_t tag,
|
|
struct tls_credential *iter)
|
|
{
|
|
psa_storage_uid_t uid;
|
|
unsigned int slot;
|
|
|
|
if (!iter) {
|
|
slot = 0;
|
|
} else {
|
|
uid = tls_credential_get_uid(iter->tag, iter->type);
|
|
|
|
slot = tls_credential_toc_find_slot(uid);
|
|
if (slot == CRED_MAX_SLOTS) {
|
|
return NULL;
|
|
}
|
|
|
|
slot++;
|
|
}
|
|
|
|
for (; slot < CRED_MAX_SLOTS; slot++) {
|
|
uid = credentials_toc[slot];
|
|
if (uid == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (tls_credential_uid_to_type(uid) != TLS_CREDENTIAL_NONE &&
|
|
tls_credential_uid_to_tag(uid) == tag) {
|
|
return credential_get_from_uid(uid);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
sec_tag_t credential_next_tag_get(sec_tag_t iter)
|
|
{
|
|
unsigned int slot;
|
|
psa_storage_uid_t uid;
|
|
sec_tag_t lowest_candidate = TLS_SEC_TAG_NONE;
|
|
sec_tag_t candidate;
|
|
|
|
/* Scan all slots and find lowest sectag greater than iter */
|
|
for (slot = 0; slot < CRED_MAX_SLOTS; slot++) {
|
|
uid = credentials_toc[slot];
|
|
|
|
/* Skip empty slots. */
|
|
if (uid == 0) {
|
|
continue;
|
|
}
|
|
if (tls_credential_uid_to_type(uid) == TLS_CREDENTIAL_NONE) {
|
|
continue;
|
|
}
|
|
|
|
candidate = tls_credential_uid_to_tag(uid);
|
|
|
|
/* Skip any slots containing sectags not greater than iter */
|
|
if (candidate <= iter && iter != TLS_SEC_TAG_NONE) {
|
|
continue;
|
|
}
|
|
|
|
/* Find the lowest of such slots */
|
|
if (lowest_candidate == TLS_SEC_TAG_NONE || candidate < lowest_candidate) {
|
|
lowest_candidate = candidate;
|
|
}
|
|
}
|
|
|
|
return lowest_candidate;
|
|
}
|
|
|
|
int credential_digest(struct tls_credential *credential, void *dest, size_t *len)
|
|
{
|
|
return credential_digest_raw(credential, dest, len);
|
|
}
|
|
|
|
void credentials_lock(void)
|
|
{
|
|
k_mutex_lock(&credential_lock, K_FOREVER);
|
|
}
|
|
|
|
void credentials_unlock(void)
|
|
{
|
|
int i;
|
|
|
|
/* Erase & free all retrieved credentials */
|
|
for (i = 0; i < ARRAY_SIZE(credentials); i++) {
|
|
if (credentials[i].buf) {
|
|
k_free((void *)credentials[i].buf);
|
|
}
|
|
memset(&credentials[i], 0, sizeof(credentials[i]));
|
|
}
|
|
|
|
k_mutex_unlock(&credential_lock);
|
|
}
|
|
|
|
int tls_credential_add(sec_tag_t tag, enum tls_credential_type type,
|
|
const void *cred, size_t credlen)
|
|
{
|
|
psa_storage_uid_t uid = tls_credential_get_uid(tag, type);
|
|
psa_storage_create_flags_t create_flags = 0;
|
|
psa_status_t status;
|
|
unsigned int slot;
|
|
int ret = 0;
|
|
|
|
/* tag 0xffffffff type 0xffff are reserved */
|
|
if (tag == 0xffffffff && type == 0xffff) {
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
k_mutex_lock(&credential_lock, K_FOREVER);
|
|
|
|
if (tls_credential_toc_find_slot(uid) != CRED_MAX_SLOTS) {
|
|
ret = -EEXIST;
|
|
goto cleanup;
|
|
}
|
|
|
|
slot = tls_credential_toc_find_slot(0);
|
|
if (slot == CRED_MAX_SLOTS) {
|
|
ret = -ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* TODO: Set create_flags depending on tag value ? */
|
|
|
|
status = psa_ps_set(uid, credlen, cred, create_flags);
|
|
if (status != PSA_SUCCESS) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = credentials_toc_update(slot, uid);
|
|
|
|
cleanup:
|
|
k_mutex_unlock(&credential_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tls_credential_get(sec_tag_t tag, enum tls_credential_type type,
|
|
void *cred, size_t *credlen)
|
|
{
|
|
struct psa_storage_info_t info;
|
|
psa_storage_uid_t uid = tls_credential_get_uid(tag, type);
|
|
psa_status_t status;
|
|
unsigned int slot;
|
|
int ret = 0;
|
|
|
|
/* tag 0xffffffff type 0xffff are reserved */
|
|
if (tag == 0xffffffff && type == 0xffff) {
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
k_mutex_lock(&credential_lock, K_FOREVER);
|
|
|
|
slot = tls_credential_toc_find_slot(uid);
|
|
if (slot == CRED_MAX_SLOTS) {
|
|
ret = -ENOENT;
|
|
goto cleanup;
|
|
}
|
|
|
|
status = psa_ps_get_info(uid, &info);
|
|
if (status == PSA_ERROR_DOES_NOT_EXIST) {
|
|
ret = -ENOENT;
|
|
goto cleanup;
|
|
} else if (status != PSA_SUCCESS) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (info.size > *credlen) {
|
|
ret = -EFBIG;
|
|
goto cleanup;
|
|
}
|
|
|
|
status = psa_ps_get(uid, 0, info.size, cred, credlen);
|
|
if (status != PSA_SUCCESS) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
k_mutex_unlock(&credential_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tls_credential_delete(sec_tag_t tag, enum tls_credential_type type)
|
|
{
|
|
psa_storage_uid_t uid = tls_credential_get_uid(tag, type);
|
|
psa_status_t status;
|
|
unsigned int slot;
|
|
int ret = 0;
|
|
|
|
/* tag 0xffffffff type 0xffff are reserved */
|
|
if (tag == 0xffffffff && type == 0xffff) {
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
k_mutex_lock(&credential_lock, K_FOREVER);
|
|
|
|
slot = tls_credential_toc_find_slot(uid);
|
|
if (slot == CRED_MAX_SLOTS) {
|
|
ret = -ENOENT;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = credentials_toc_update(slot, 0);
|
|
if (ret != 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
status = psa_ps_remove(uid);
|
|
if (status == PSA_ERROR_DOES_NOT_EXIST) {
|
|
ret = -ENOENT;
|
|
goto cleanup;
|
|
} else if (status != PSA_SUCCESS) {
|
|
ret = -EIO;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
k_mutex_unlock(&credential_lock);
|
|
|
|
return ret;
|
|
}
|