// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2018-2020, Linaro Limited
*/
#include <assert.h>
#include <pkcs11_ta.h>
#include <string.h>
#include <string_ext.h>
#include <tee_internal_api_extensions.h>
#include <util.h>
#include "pkcs11_token.h"
#include "pkcs11_helpers.h"
#define PERSISTENT_OBJECT_ID_LEN 32
/*
* Token persistent objects
*
* The persistent objects are each identified by a UUID.
* The persistent object database stores the list of the UUIDs registered. For
* each it is expected that a file of ID "UUID" is stored in the TA secure
* storage.
*/
static TEE_Result get_db_file_name(struct ck_token *token,
char *name, size_t size)
{
int n = snprintf(name, size, "token.db.%u", get_token_id(token));
if (n < 0 || (size_t)n >= size)
return TEE_ERROR_SECURITY;
else
return TEE_SUCCESS;
}
static TEE_Result open_db_file(struct ck_token *token,
TEE_ObjectHandle *out_hdl)
{
char file[PERSISTENT_OBJECT_ID_LEN] = { };
TEE_Result res = TEE_ERROR_GENERIC;
res = get_db_file_name(token, file, sizeof(file));
if (res)
return res;
return TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, file, sizeof(file),
TEE_DATA_FLAG_ACCESS_READ |
TEE_DATA_FLAG_ACCESS_WRITE,
out_hdl);
}
void update_persistent_db(struct ck_token *token)
{
TEE_Result res = TEE_ERROR_GENERIC;
TEE_ObjectHandle db_hdl = TEE_HANDLE_NULL;
res = open_db_file(token, &db_hdl);
if (res) {
EMSG("Failed to open token persistent db: %#"PRIx32, res);
TEE_Panic(0);
}
res = TEE_WriteObjectData(db_hdl, token->db_main,
sizeof(*token->db_main));
if (res) {
EMSG("Failed to write to token persistent db: %#"PRIx32, res);
TEE_Panic(0);
}
TEE_CloseObject(db_hdl);
}
static enum pkcs11_rc do_hash(uint32_t user, const uint8_t *pin,
size_t pin_size, uint32_t salt,
uint8_t hash[TEE_MAX_HASH_SIZE])
{
TEE_Result res = TEE_SUCCESS;
TEE_OperationHandle oh = TEE_HANDLE_NULL;
uint32_t sz = TEE_MAX_HASH_SIZE;
res = TEE_AllocateOperation(&oh, TEE_ALG_SHA256, TEE_MODE_DIGEST, 0);
if (res)
return tee2pkcs_error(res);
TEE_DigestUpdate(oh, &user, sizeof(user));
TEE_DigestUpdate(oh, &salt, sizeof(salt));
res = TEE_DigestDoFinal(oh, pin, pin_size, hash, &sz);
TEE_FreeOperation(oh);
if (res)
return PKCS11_CKR_GENERAL_ERROR;
memset(hash + sz, 0, TEE_MAX_HASH_SIZE - sz);
return PKCS11_CKR_OK;
}
enum pkcs11_rc hash_pin(enum pkcs11_user_type user, const uint8_t *pin,
size_t pin_size, uint32_t *salt,
uint8_t hash[TEE_MAX_HASH_SIZE])
{
enum pkcs11_rc rc = PKCS11_CKR_OK;
uint32_t s = 0;
TEE_GenerateRandom(&s, sizeof(s));
if (!s)
s++;
rc = do_hash(user, pin, pin_size, s, hash);
if (!rc)
*salt = s;
return rc;
}
enum pkcs11_rc verify_pin(enum pkcs11_user_type user, const uint8_t *pin,
size_t pin_size, uint32_t salt,
const uint8_t hash[TEE_MAX_HASH_SIZE])
{
uint8_t tmp_hash[TEE_MAX_HASH_SIZE] = { 0 };
enum pkcs11_rc rc = PKCS11_CKR_OK;
rc = do_hash(user, pin, pin_size, salt, tmp_hash);
if (rc)
return rc;
if (buf_compare_ct(tmp_hash, hash, TEE_MAX_HASH_SIZE))
rc = PKCS11_CKR_PIN_INCORRECT;
return rc;
}
/*
* Release resources relate to persistent database
*/
void close_persistent_db(struct ck_token *token __unused)
{
}
static int get_persistent_obj_idx(struct ck_token *token, TEE_UUID *uuid)
{
size_t i = 0;
if (!uuid)
return -1;
for (i = 0; i < token->db_objs->count; i++)
if (!TEE_MemCompare(token->db_objs->uuids + i,
uuid, sizeof(TEE_UUID)))
return i;
return -1;
}
/* UUID for persistent object */
enum pkcs11_rc create_object_uuid(struct ck_token *token,
struct pkcs11_object *obj)
{
assert(!obj->uuid);
obj->uuid = TEE_Malloc(sizeof(TEE_UUID),
TEE_USER_MEM_HINT_NO_FILL_ZERO);
if (!obj->uuid)
return PKCS11_CKR_DEVICE_MEMORY;
do {
TEE_GenerateRandom(obj->uuid, sizeof(TEE_UUID));
} while (get_persistent_obj_idx(token, obj->uuid) >= 0);
return PKCS11_CKR_OK;
}
void destroy_object_uuid(struct ck_token *token __maybe_unused,
struct pkcs11_object *obj)
{
assert(get_persistent_obj_idx(token, obj->uuid) < 0);
TEE_Free(obj->uuid);
obj->uuid = NULL;
}
enum pkcs11_rc get_persistent_objects_list(struct ck_token *token,
TEE_UUID *array, size_t *size)
{
size_t out_size = *size;
*size = token->db_objs->count * sizeof(TEE_UUID);
if (out_size < *size)
return PKCS11_CKR_BUFFER_TOO_SMALL;
if (array)
TEE_MemMove(array, token->db_objs->uuids, *size);
return PKCS11_CKR_OK;
}
enum pkcs11_rc unregister_persistent_object(struct ck_token *token,
TEE_UUID *uuid)
{
TEE_ObjectHandle db_hdl = TEE_HANDLE_NULL;
struct token_persistent_objs *ptr = NULL;
TEE_Result res = TEE_ERROR_GENERIC;
int count = 0;
int idx = 0;
if (!uuid)
return PKCS11_CKR_OK;
idx = get_persistent_obj_idx(token, uuid);
if (idx < 0) {
DMSG("Cannot unregister an invalid persistent object");
return PKCS11_RV_NOT_FOUND;
}
ptr = TEE_Malloc(sizeof(struct token_persistent_objs) +
((token->db_objs->count - 1) * sizeof(TEE_UUID)),
TEE_USER_MEM_HINT_NO_FILL_ZERO);
if (!ptr)
return PKCS11_CKR_DEVICE_MEMORY;
res = open_db_file(token, &db_hdl);
if (res)
goto out;
res = TEE_SeekObjectData(db_hdl, sizeof(struct token_persistent_main),
TEE_DATA_SEEK_SET);
if (res) {
DMSG("Failed to read database");
goto out;
}
TEE_MemMove(ptr, token->db_objs,
sizeof(struct token_persistent_objs) +
idx * sizeof(TEE_UUID));
ptr->count--;
count = ptr->count - idx;
TEE_MemMove(&ptr->uuids[idx],
&token->db_objs->uuids[idx + 1],
count * sizeof(TEE_UUID));
res = TEE_WriteObjectData(db_hdl, ptr,
sizeof(struct token_persistent_objs) +
ptr->count * sizeof(TEE_UUID));
if (res)
DMSG("Failed to update database");
TEE_Free(token->db_objs);
token->db_objs = ptr;
ptr = NULL;
out:
TEE_CloseObject(db_hdl);
TEE_Free(ptr);
return tee2pkcs_error(res);
}
enum pkcs11_rc register_persistent_object(struct ck_token *token,
TEE_UUID *uuid)
{
TEE_ObjectHandle db_hdl = TEE_HANDLE_NULL;
TEE_Result res = TEE_ERROR_GENERIC;
void *ptr = NULL;
size_t size = 0;
int count = 0;
if (get_persistent_obj_idx(token, uuid) >= 0)
TEE_Panic(0);
count = token->db_objs->count;
ptr = TEE_Realloc(token->db_objs,
sizeof(struct token_persistent_objs) +
((count + 1) * sizeof(TEE_UUID)));
if (!ptr)
return PKCS11_CKR_DEVICE_MEMORY;
token->db_objs = ptr;
TEE_MemMove(token->db_objs->uuids + count, uuid, sizeof(TEE_UUID));
size = sizeof(struct token_persistent_main) +
sizeof(struct token_persistent_objs) +
count * sizeof(TEE_UUID);
res = open_db_file(token, &db_hdl);
if (res)
goto out;
res = TEE_TruncateObjectData(db_hdl, size + sizeof(TEE_UUID));
if (res)
goto out;
res = TEE_SeekObjectData(db_hdl, sizeof(struct token_persistent_main),
TEE_DATA_SEEK_SET);
if (res)
goto out;
token->db_objs->count++;
res = TEE_WriteObjectData(db_hdl, token->db_objs,
sizeof(struct token_persistent_objs) +
token->db_objs->count * sizeof(TEE_UUID));
if (res)
token->db_objs->count--;
out:
TEE_CloseObject(db_hdl);
return tee2pkcs_error(res);
}
/*
* Return the token instance, either initialized from reset or initialized
* from the token persistent state if found.
*/
struct ck_token *init_persistent_db(unsigned int token_id)
{
struct ck_token *token = get_token(token_id);
TEE_Result res = TEE_ERROR_GENERIC;
TEE_ObjectHandle db_hdl = TEE_HANDLE_NULL;
/* Copy persistent database: main db and object db */
struct token_persistent_main *db_main = NULL;
struct token_persistent_objs *db_objs = NULL;
void *ptr = NULL;
if (!token)
return NULL;
LIST_INIT(&token->object_list);
db_main = TEE_Malloc(sizeof(*db_main), TEE_MALLOC_FILL_ZERO);
db_objs = TEE_Malloc(sizeof(*db_objs), TEE_MALLOC_FILL_ZERO);
if (!db_main || !db_objs)
goto error;
res = open_db_file(token, &db_hdl);
if (res == TEE_SUCCESS) {
uint32_t size = 0;
size_t idx = 0;
IMSG("PKCS11 token %u: load db", token_id);
size = sizeof(*db_main);
res = TEE_ReadObjectData(db_hdl, db_main, size, &size);
if (res || size != sizeof(*db_main))
TEE_Panic(0);
size = sizeof(*db_objs);
res = TEE_ReadObjectData(db_hdl, db_objs, size, &size);
if (res || size != sizeof(*db_objs))
TEE_Panic(0);
size += db_objs->count * sizeof(TEE_UUID);
ptr = TEE_Realloc(db_objs, size);
if (!ptr)
goto error;
db_objs = ptr;
size -= sizeof(struct token_persistent_objs);
res = TEE_ReadObjectData(db_hdl, db_objs->uuids, size, &size);
if (res || size != (db_objs->count * sizeof(TEE_UUID)))
TEE_Panic(0);
for (idx = 0; idx < db_objs->count; idx++) {
/* Create an empty object instance */
struct pkcs11_object *obj = NULL;
TEE_UUID *uuid = NULL;
uuid = TEE_Malloc(sizeof(TEE_UUID),
TEE_USER_MEM_HINT_NO_FILL_ZERO);
if (!uuid)
goto error;
TEE_MemMove(uuid, &db_objs->uuids[idx], sizeof(*uuid));
obj = create_token_object(NULL, uuid);
if (!obj)
TEE_Panic(0);
LIST_INSERT_HEAD(&token->object_list, obj, link);
}
} else if (res == TEE_ERROR_ITEM_NOT_FOUND) {
char file[PERSISTENT_OBJECT_ID_LEN] = { };
IMSG("PKCS11 token %u: init db", token_id);
TEE_MemFill(db_main, 0, sizeof(*db_main));
TEE_MemFill(db_main->label, '*', sizeof(db_main->label));
db_main->flags = PKCS11_CKFT_SO_PIN_TO_BE_CHANGED |
PKCS11_CKFT_USER_PIN_TO_BE_CHANGED |
PKCS11_CKFT_RNG |
PKCS11_CKFT_DUAL_CRYPTO_OPERATIONS |
PKCS11_CKFT_LOGIN_REQUIRED;
res = get_db_file_name(token, file, sizeof(file));
if (res)
TEE_Panic(0);
/*
* Object stores persistent state + persistent object
* references.
*/
res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE,
file, sizeof(file),
TEE_DATA_FLAG_ACCESS_READ |
TEE_DATA_FLAG_ACCESS_WRITE,
TEE_HANDLE_NULL,
db_main, sizeof(*db_main),
&db_hdl);
if (res) {
EMSG("Failed to create db: %"PRIx32, res);
goto error;
}
res = TEE_TruncateObjectData(db_hdl, sizeof(*db_main) +
sizeof(*db_objs));
if (res)
TEE_Panic(0);
res = TEE_SeekObjectData(db_hdl, sizeof(*db_main),
TEE_DATA_SEEK_SET);
if (res)
TEE_Panic(0);
db_objs->count = 0;
res = TEE_WriteObjectData(db_hdl, db_objs, sizeof(*db_objs));
if (res)
TEE_Panic(0);
} else {
goto error;
}
token->db_main = db_main;
token->db_objs = db_objs;
TEE_CloseObject(db_hdl);
return token;
error:
TEE_Free(db_main);
TEE_Free(db_objs);
if (db_hdl != TEE_HANDLE_NULL)
TEE_CloseObject(db_hdl);
return NULL;
}