diff --git a/picokeys_sdk_import.cmake b/picokeys_sdk_import.cmake
index 26934b6..ac47299 100644
--- a/picokeys_sdk_import.cmake
+++ b/picokeys_sdk_import.cmake
@@ -318,6 +318,7 @@ list(APPEND PICOKEYS_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/fs/object_store.c
${CMAKE_CURRENT_LIST_DIR}/src/fs/object_store_txn.c
${CMAKE_CURRENT_LIST_DIR}/src/fs/object_container.c
+ ${CMAKE_CURRENT_LIST_DIR}/src/fs/object_crypto_provider.c
${CMAKE_CURRENT_LIST_DIR}/src/fs/flash.c
${CMAKE_CURRENT_LIST_DIR}/src/fs/low_flash.c
${CMAKE_CURRENT_LIST_DIR}/src/fs/phy.c
diff --git a/src/fs/object_container.c b/src/fs/object_container.c
index 3e41561..2881b1b 100644
--- a/src/fs/object_container.c
+++ b/src/fs/object_container.c
@@ -367,10 +367,27 @@ int file_object_record_header_parse(const uint8_t *record, size_t len, const fil
uint32_t generation = get_uint32_be(record + FILE_OBJECT_RECORD_GENERATION_OFFSET);
uint64_t nonce_record_id = get_uint64_be(record + FILE_OBJECT_RECORD_NONCE_OFFSET);
uint32_t nonce_generation = get_uint32_be(record + FILE_OBJECT_RECORD_NONCE_OFFSET + sizeof(uint64_t));
- if (memcmp(record, file_object_record_magic, sizeof(file_object_record_magic)) != 0 || record[4] != FILE_OBJECT_RECORD_FORMAT_VERSION || record[FILE_OBJECT_RECORD_PROTECTION_OFFSET] != object->protection || get_uint16_be(record + FILE_OBJECT_RECORD_HEADER_SIZE_OFFSET) != FILE_OBJECT_RECORD_HEADER_SIZE || record_id != object->record_id || stored_size != object->stored_size || logical_size != object->logical_size || generation != object->generation || nonce_record_id != record_id || nonce_generation != generation) {
+ if (memcmp(record, file_object_record_magic, sizeof(file_object_record_magic)) != 0
+ || record[4] != FILE_OBJECT_RECORD_FORMAT_VERSION
+ || record[FILE_OBJECT_RECORD_PROTECTION_OFFSET] != object->protection
+ || get_uint16_be(record + FILE_OBJECT_RECORD_HEADER_SIZE_OFFSET) != FILE_OBJECT_RECORD_HEADER_SIZE
+ || record_id != object->record_id
+ || stored_size != object->stored_size
+ || logical_size != object->logical_size
+ || generation != object->generation
+ || nonce_record_id != record_id
+ || nonce_generation != generation) {
return PICOKEYS_WRONG_DATA;
}
- *info = (file_object_record_info_t) { .record_id = record_id, .stored_size = stored_size, .logical_size = logical_size, .generation = generation, .protection = record[FILE_OBJECT_RECORD_PROTECTION_OFFSET], .payload_offset = FILE_OBJECT_RECORD_HEADER_SIZE, .tag_offset = FILE_OBJECT_RECORD_HEADER_SIZE + stored_size };
+ *info = (file_object_record_info_t) {
+ .record_id = record_id,
+ .stored_size = stored_size,
+ .logical_size = logical_size,
+ .generation = generation,
+ .protection = record[FILE_OBJECT_RECORD_PROTECTION_OFFSET],
+ .payload_offset = FILE_OBJECT_RECORD_HEADER_SIZE,
+ .tag_offset = FILE_OBJECT_RECORD_HEADER_SIZE + stored_size
+ };
return PICOKEYS_OK;
}
diff --git a/src/fs/object_crypto_provider.c b/src/fs/object_crypto_provider.c
new file mode 100644
index 0000000..389ea24
--- /dev/null
+++ b/src/fs/object_crypto_provider.c
@@ -0,0 +1,343 @@
+/*
+ * This file is part of the Pico Keys SDK distribution (https://github.com/polhenarejos/pico-keys-sdk).
+ * Copyright (c) 2022 Pol Henarejos.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+#include "picokeys.h"
+#include "object_crypto_provider.h"
+
+#include
+#include
+#include
+
+#define FILE_OBJECT_CRYPTO_HMAC_SIZE 32u
+
+#define FILE_OBJECT_CRYPTO_AAD_NAMESPACE_OFFSET 5u
+#define FILE_OBJECT_CRYPTO_AAD_KIND_OFFSET 7u
+#define FILE_OBJECT_CRYPTO_AAD_CONTAINER_ID_OFFSET 9u
+#define FILE_OBJECT_CRYPTO_AAD_TYPE_OFFSET 13u
+#define FILE_OBJECT_CRYPTO_AAD_TAG_OFFSET 15u
+#define FILE_OBJECT_CRYPTO_AAD_GENERATION_OFFSET 17u
+#define FILE_OBJECT_CRYPTO_AAD_LOGICAL_SIZE_OFFSET 21u
+#define FILE_OBJECT_CRYPTO_AAD_POLICY_ID_OFFSET 25u
+#define FILE_OBJECT_CRYPTO_AAD_POLICY_HASH_OFFSET 27u
+#define FILE_OBJECT_CRYPTO_AAD_KEY_DOMAIN_OFFSET 43u
+#define FILE_OBJECT_CRYPTO_AAD_PROTECTION_OFFSET 44u
+#define FILE_OBJECT_CRYPTO_AAD_FLAGS_OFFSET 45u
+#define FILE_OBJECT_CRYPTO_AAD_RECORD_ID_OFFSET 47u
+
+static const uint8_t file_object_crypto_record_magic[4] = { 'P', 'K', 'O', 'R' };
+static const uint8_t file_object_crypto_domain_label[] = "PKOC/domain/v1";
+static const uint8_t file_object_crypto_key_label[] = "PKOC/object/v1";
+static const uint8_t file_object_crypto_manifest_label[] = "PKOC/manifest/v1";
+
+static const mbedtls_md_info_t *file_object_crypto_sha256(void) {
+ return mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
+}
+
+static bool file_object_crypto_provider_valid(const file_object_crypto_provider_t *provider) {
+ return provider && provider->initialized && provider->config.namespace_id != 0 && provider->config.load_root;
+}
+
+static int file_object_crypto_load_root(file_object_crypto_provider_t *provider, uint8_t root[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE]) {
+ int r = provider->config.load_root(provider->config.ctx, root);
+ if (r != PICOKEYS_OK) {
+ mbedtls_platform_zeroize(root, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE);
+ }
+ return r;
+}
+
+static int file_object_crypto_derive_manifest_key(file_object_crypto_provider_t *provider, uint8_t key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE]) {
+ uint8_t root[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE] = { 0 };
+ uint8_t info[sizeof(uint16_t) + sizeof(file_object_crypto_manifest_label) - 1];
+ put_uint16_be(provider->config.namespace_id, info);
+ memcpy(info + sizeof(uint16_t), file_object_crypto_manifest_label, sizeof(file_object_crypto_manifest_label) - 1);
+
+ int r = file_object_crypto_load_root(provider, root);
+ if (r == PICOKEYS_OK && mbedtls_hkdf(file_object_crypto_sha256(), NULL, 0, root, sizeof(root), info, sizeof(info), key, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE) != 0) {
+ r = PICOKEYS_EXEC_ERROR;
+ }
+ mbedtls_platform_zeroize(root, sizeof(root));
+ mbedtls_platform_zeroize(info, sizeof(info));
+ return r;
+}
+
+static int file_object_crypto_derive_record_key(file_object_crypto_provider_t *provider, const file_object_record_identity_t *identity, const uint8_t aad[FILE_OBJECT_RECORD_AAD_SIZE], uint8_t key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE]) {
+ uint8_t root[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE] = { 0 };
+ uint8_t domain_key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE] = { 0 };
+ uint8_t domain_info[sizeof(uint16_t) + sizeof(uint8_t) + sizeof(file_object_crypto_domain_label) - 1];
+ uint8_t object_info[sizeof(file_object_crypto_key_label) - 1 + FILE_OBJECT_RECORD_AAD_SIZE];
+
+ put_uint16_be(identity->namespace_id, domain_info);
+ domain_info[sizeof(uint16_t)] = identity->key_domain;
+ memcpy(domain_info + sizeof(uint16_t) + sizeof(uint8_t), file_object_crypto_domain_label, sizeof(file_object_crypto_domain_label) - 1);
+ memcpy(object_info, file_object_crypto_key_label, sizeof(file_object_crypto_key_label) - 1);
+ memcpy(object_info + sizeof(file_object_crypto_key_label) - 1, aad, FILE_OBJECT_RECORD_AAD_SIZE);
+
+ int r = file_object_crypto_load_root(provider, root);
+ if (r == PICOKEYS_OK && mbedtls_hkdf(file_object_crypto_sha256(), NULL, 0, root, sizeof(root), domain_info, sizeof(domain_info), domain_key, sizeof(domain_key)) != 0) {
+ r = PICOKEYS_EXEC_ERROR;
+ }
+ if (r == PICOKEYS_OK && mbedtls_hkdf(file_object_crypto_sha256(), NULL, 0, domain_key, sizeof(domain_key), object_info, sizeof(object_info), key, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE) != 0) {
+ r = PICOKEYS_EXEC_ERROR;
+ }
+ mbedtls_platform_zeroize(root, sizeof(root));
+ mbedtls_platform_zeroize(domain_key, sizeof(domain_key));
+ mbedtls_platform_zeroize(domain_info, sizeof(domain_info));
+ mbedtls_platform_zeroize(object_info, sizeof(object_info));
+ return r;
+}
+
+static bool file_object_crypto_identity_valid(file_object_crypto_provider_t *provider, const file_object_record_identity_t *identity, const uint8_t nonce[FILE_OBJECT_RECORD_NONCE_SIZE], const uint8_t aad[FILE_OBJECT_RECORD_AAD_SIZE]) {
+ if (!file_object_crypto_provider_valid(provider) || !identity || !nonce || !aad || identity->namespace_id != provider->config.namespace_id) {
+ return false;
+ }
+ if (identity->generation == 0 || identity->record_id == 0 || identity->protection < FILE_OBJECT_PROTECTION_AUTHENTICATED_PUBLIC || identity->protection > FILE_OBJECT_PROTECTION_FIRMWARE_INTERNAL || (identity->flags & ~FILE_OBJECT_FLAG_MASK) != 0 || (identity->flags & FILE_OBJECT_FLAG_INLINE) != 0) {
+ return false;
+ }
+ if (provider->config.identity_valid && !provider->config.identity_valid(provider->config.ctx, identity)) {
+ return false;
+ }
+ if (memcmp(aad, file_object_crypto_record_magic, sizeof(file_object_crypto_record_magic)) != 0 || aad[4] != FILE_OBJECT_RECORD_FORMAT_VERSION) {
+ return false;
+ }
+ if (get_uint16_be(aad + FILE_OBJECT_CRYPTO_AAD_NAMESPACE_OFFSET) != identity->namespace_id
+ || get_uint16_be(aad + FILE_OBJECT_CRYPTO_AAD_KIND_OFFSET) != identity->container_kind
+ || get_uint32_be(aad + FILE_OBJECT_CRYPTO_AAD_CONTAINER_ID_OFFSET) != identity->container_id
+ || get_uint16_be(aad + FILE_OBJECT_CRYPTO_AAD_TYPE_OFFSET) != identity->object_type
+ || get_uint16_be(aad + FILE_OBJECT_CRYPTO_AAD_TAG_OFFSET) != identity->object_tag
+ || get_uint32_be(aad + FILE_OBJECT_CRYPTO_AAD_GENERATION_OFFSET) != identity->generation
+ || get_uint32_be(aad + FILE_OBJECT_CRYPTO_AAD_LOGICAL_SIZE_OFFSET) != identity->logical_size
+ || get_uint16_be(aad + FILE_OBJECT_CRYPTO_AAD_POLICY_ID_OFFSET) != identity->policy_id
+ || memcmp(aad + FILE_OBJECT_CRYPTO_AAD_POLICY_HASH_OFFSET, identity->policy_hash, FILE_OBJECT_POLICY_HASH_SIZE) != 0
+ || aad[FILE_OBJECT_CRYPTO_AAD_KEY_DOMAIN_OFFSET] != identity->key_domain
+ || aad[FILE_OBJECT_CRYPTO_AAD_PROTECTION_OFFSET] != identity->protection
+ || get_uint16_be(aad + FILE_OBJECT_CRYPTO_AAD_FLAGS_OFFSET) != identity->flags
+ || get_uint64_be(aad + FILE_OBJECT_CRYPTO_AAD_RECORD_ID_OFFSET) != identity->record_id) {
+ return false;
+ }
+ return get_uint64_be(nonce) == identity->record_id && get_uint32_be(nonce + sizeof(uint64_t)) == identity->generation;
+}
+
+static bool file_object_crypto_tag_equal(const uint8_t *left, const uint8_t *right) {
+ uint8_t difference = 0;
+ for (size_t i = 0; i < FILE_OBJECT_AUTH_TAG_SIZE; i++) {
+ difference |= left[i] ^ right[i];
+ }
+ return difference == 0;
+}
+
+static int file_object_crypto_hmac(const uint8_t key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE], const uint8_t nonce[FILE_OBJECT_RECORD_NONCE_SIZE], const uint8_t aad[FILE_OBJECT_RECORD_AAD_SIZE], const uint8_t *stored, size_t len, uint8_t tag[FILE_OBJECT_AUTH_TAG_SIZE]) {
+ mbedtls_md_context_t md;
+ mbedtls_md_init(&md);
+ uint8_t full_tag[FILE_OBJECT_CRYPTO_HMAC_SIZE] = { 0 };
+ int r = mbedtls_md_setup(&md, file_object_crypto_sha256(), 1);
+ if (r == 0) {
+ r = mbedtls_md_hmac_starts(&md, key, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE);
+ }
+ if (r == 0) {
+ r = mbedtls_md_hmac_update(&md, aad, FILE_OBJECT_RECORD_AAD_SIZE);
+ }
+ if (r == 0) {
+ r = mbedtls_md_hmac_update(&md, nonce, FILE_OBJECT_RECORD_NONCE_SIZE);
+ }
+ if (r == 0 && len > 0) {
+ r = mbedtls_md_hmac_update(&md, stored, len);
+ }
+ if (r == 0) {
+ r = mbedtls_md_hmac_finish(&md, full_tag);
+ }
+ if (r == 0) {
+ memcpy(tag, full_tag, FILE_OBJECT_AUTH_TAG_SIZE);
+ }
+ mbedtls_platform_zeroize(full_tag, sizeof(full_tag));
+ mbedtls_md_free(&md);
+ return r == 0 ? PICOKEYS_OK : PICOKEYS_EXEC_ERROR;
+}
+
+static int file_object_crypto_record_seal(void *ctx, const file_object_record_identity_t *identity, const uint8_t nonce[FILE_OBJECT_RECORD_NONCE_SIZE], const uint8_t aad[FILE_OBJECT_RECORD_AAD_SIZE], const uint8_t *plaintext, size_t len, uint8_t *stored, uint8_t tag[FILE_OBJECT_AUTH_TAG_SIZE]) {
+ file_object_crypto_provider_t *provider = (file_object_crypto_provider_t *)ctx;
+ if (!file_object_crypto_identity_valid(provider, identity, nonce, aad) || (!plaintext && len > 0) || !stored || !tag) {
+ return PICOKEYS_WRONG_DATA;
+ }
+
+ uint8_t key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE] = { 0 };
+ int r = file_object_crypto_derive_record_key(provider, identity, aad, key);
+ if (r == PICOKEYS_OK && identity->protection == FILE_OBJECT_PROTECTION_AUTHENTICATED_PUBLIC) {
+ if (len > 0) {
+ memmove(stored, plaintext, len);
+ }
+ r = file_object_crypto_hmac(key, nonce, aad, stored, len, tag);
+ }
+ else if (r == PICOKEYS_OK) {
+ mbedtls_gcm_context gcm;
+ mbedtls_gcm_init(&gcm);
+ uint8_t empty_input = 0;
+ uint8_t empty_output = 0;
+ const uint8_t *gcm_input = len > 0 ? plaintext : &empty_input;
+ uint8_t *gcm_output = len > 0 ? stored : &empty_output;
+ int crypto_result = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE * 8u);
+ if (crypto_result == 0) {
+ crypto_result = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, len, nonce, FILE_OBJECT_RECORD_NONCE_SIZE, aad, FILE_OBJECT_RECORD_AAD_SIZE, gcm_input, gcm_output, FILE_OBJECT_AUTH_TAG_SIZE, tag);
+ }
+ mbedtls_gcm_free(&gcm);
+ r = crypto_result == 0 ? PICOKEYS_OK : PICOKEYS_EXEC_ERROR;
+ }
+ mbedtls_platform_zeroize(key, sizeof(key));
+ return r;
+}
+
+static int file_object_crypto_record_unseal(void *ctx, const file_object_record_identity_t *identity, const uint8_t nonce[FILE_OBJECT_RECORD_NONCE_SIZE], const uint8_t aad[FILE_OBJECT_RECORD_AAD_SIZE], const uint8_t *stored, size_t len, const uint8_t tag[FILE_OBJECT_AUTH_TAG_SIZE], uint8_t *plaintext) {
+ file_object_crypto_provider_t *provider = (file_object_crypto_provider_t *)ctx;
+ if (!file_object_crypto_identity_valid(provider, identity, nonce, aad) || (!stored && len > 0) || !tag || (!plaintext && len > 0)) {
+ return PICOKEYS_WRONG_DATA;
+ }
+
+ uint8_t key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE] = { 0 };
+ int r = file_object_crypto_derive_record_key(provider, identity, aad, key);
+ if (r == PICOKEYS_OK && identity->protection == FILE_OBJECT_PROTECTION_AUTHENTICATED_PUBLIC) {
+ uint8_t calculated_tag[FILE_OBJECT_AUTH_TAG_SIZE] = { 0 };
+ r = file_object_crypto_hmac(key, nonce, aad, stored, len, calculated_tag);
+ if (r == PICOKEYS_OK && !file_object_crypto_tag_equal(calculated_tag, tag)) {
+ r = PICOKEYS_WRONG_SIGNATURE;
+ }
+ if (r == PICOKEYS_OK && len > 0) {
+ memmove(plaintext, stored, len);
+ }
+ mbedtls_platform_zeroize(calculated_tag, sizeof(calculated_tag));
+ }
+ else if (r == PICOKEYS_OK) {
+ mbedtls_gcm_context gcm;
+ mbedtls_gcm_init(&gcm);
+ uint8_t empty_input = 0;
+ uint8_t empty_output = 0;
+ const uint8_t *gcm_input = len > 0 ? stored : &empty_input;
+ uint8_t *gcm_output = len > 0 ? plaintext : &empty_output;
+ int crypto_result = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, key, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE * 8u);
+ if (crypto_result == 0) {
+ crypto_result = mbedtls_gcm_auth_decrypt(&gcm, len, nonce, FILE_OBJECT_RECORD_NONCE_SIZE, aad, FILE_OBJECT_RECORD_AAD_SIZE, tag, FILE_OBJECT_AUTH_TAG_SIZE, gcm_input, gcm_output);
+ r = crypto_result == 0 ? PICOKEYS_OK : PICOKEYS_WRONG_SIGNATURE;
+ }
+ else {
+ r = PICOKEYS_EXEC_ERROR;
+ }
+ mbedtls_gcm_free(&gcm);
+ }
+ if (r != PICOKEYS_OK && plaintext && len > 0) {
+ mbedtls_platform_zeroize(plaintext, len);
+ }
+ mbedtls_platform_zeroize(key, sizeof(key));
+ return r;
+}
+
+static void file_object_crypto_auth_abort(void *ctx) {
+ file_object_crypto_provider_t *provider = (file_object_crypto_provider_t *)ctx;
+ if (!provider || !provider->initialized) {
+ return;
+ }
+ mbedtls_md_free(&provider->manifest_md);
+ mbedtls_md_init(&provider->manifest_md);
+ provider->manifest_active = false;
+}
+
+static int file_object_crypto_auth_start(void *ctx) {
+ file_object_crypto_provider_t *provider = (file_object_crypto_provider_t *)ctx;
+ if (!file_object_crypto_provider_valid(provider)) {
+ return PICOKEYS_WRONG_DATA;
+ }
+ file_object_crypto_auth_abort(provider);
+
+ uint8_t key[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE] = { 0 };
+ int r = file_object_crypto_derive_manifest_key(provider, key);
+ if (r == PICOKEYS_OK && (mbedtls_md_setup(&provider->manifest_md, file_object_crypto_sha256(), 1) != 0 || mbedtls_md_hmac_starts(&provider->manifest_md, key, sizeof(key)) != 0)) {
+ r = PICOKEYS_EXEC_ERROR;
+ }
+ mbedtls_platform_zeroize(key, sizeof(key));
+ if (r != PICOKEYS_OK) {
+ file_object_crypto_auth_abort(provider);
+ return r;
+ }
+ provider->manifest_active = true;
+ return PICOKEYS_OK;
+}
+
+static int file_object_crypto_auth_update(void *ctx, const uint8_t *data, size_t len) {
+ file_object_crypto_provider_t *provider = (file_object_crypto_provider_t *)ctx;
+ if (!file_object_crypto_provider_valid(provider) || !provider->manifest_active || (!data && len > 0)) {
+ return PICOKEYS_WRONG_DATA;
+ }
+ if (len == 0) {
+ return PICOKEYS_OK;
+ }
+ return mbedtls_md_hmac_update(&provider->manifest_md, data, len) == 0 ? PICOKEYS_OK : PICOKEYS_EXEC_ERROR;
+}
+
+static int file_object_crypto_auth_finish(void *ctx, uint8_t tag[FILE_OBJECT_AUTH_TAG_SIZE]) {
+ file_object_crypto_provider_t *provider = (file_object_crypto_provider_t *)ctx;
+ if (!file_object_crypto_provider_valid(provider) || !provider->manifest_active || !tag) {
+ return PICOKEYS_WRONG_DATA;
+ }
+
+ uint8_t full_tag[FILE_OBJECT_CRYPTO_HMAC_SIZE] = { 0 };
+ int r = mbedtls_md_hmac_finish(&provider->manifest_md, full_tag) == 0 ? PICOKEYS_OK : PICOKEYS_EXEC_ERROR;
+ if (r == PICOKEYS_OK) {
+ memcpy(tag, full_tag, FILE_OBJECT_AUTH_TAG_SIZE);
+ }
+ mbedtls_platform_zeroize(full_tag, sizeof(full_tag));
+ file_object_crypto_auth_abort(provider);
+ return r;
+}
+
+int file_object_crypto_provider_init(file_object_crypto_provider_t *provider, const file_object_crypto_provider_config_t *config) {
+ if (!provider || !config || config->namespace_id == 0 || !config->load_root) {
+ return PICOKEYS_ERR_NULL_PARAM;
+ }
+
+ memset(provider, 0, sizeof(*provider));
+ provider->config = *config;
+ provider->manifest_authenticator = (file_object_authenticator_t) {
+ .ctx = provider,
+ .start = file_object_crypto_auth_start,
+ .update = file_object_crypto_auth_update,
+ .finish = file_object_crypto_auth_finish,
+ .abort = file_object_crypto_auth_abort
+ };
+ provider->record_protector = (file_object_record_protector_t) {
+ .ctx = provider,
+ .seal = file_object_crypto_record_seal,
+ .unseal = file_object_crypto_record_unseal
+ };
+ mbedtls_md_init(&provider->manifest_md);
+ provider->initialized = true;
+ return PICOKEYS_OK;
+}
+
+void file_object_crypto_provider_deinit(file_object_crypto_provider_t *provider) {
+ if (!provider) {
+ return;
+ }
+ if (provider->initialized) {
+ file_object_crypto_auth_abort(provider);
+ }
+ mbedtls_platform_zeroize(provider, sizeof(*provider));
+}
+
+const file_object_authenticator_t *file_object_crypto_manifest_authenticator(file_object_crypto_provider_t *provider) {
+ return file_object_crypto_provider_valid(provider) ? &provider->manifest_authenticator : NULL;
+}
+
+const file_object_record_protector_t *file_object_crypto_record_protector(file_object_crypto_provider_t *provider) {
+ return file_object_crypto_provider_valid(provider) ? &provider->record_protector : NULL;
+}
diff --git a/src/fs/object_crypto_provider.h b/src/fs/object_crypto_provider.h
new file mode 100644
index 0000000..953d63b
--- /dev/null
+++ b/src/fs/object_crypto_provider.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of the Pico Keys SDK distribution (https://github.com/polhenarejos/pico-keys-sdk).
+ * Copyright (c) 2022 Pol Henarejos.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _OBJECT_CRYPTO_PROVIDER_H_
+#define _OBJECT_CRYPTO_PROVIDER_H_
+
+#include "object_container.h"
+
+#include
+
+#define FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE 32u
+
+/* The provider zeroizes the root buffer after every callback invocation. */
+typedef int (*file_object_crypto_root_load_t)(void *ctx, uint8_t root[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE]);
+/* This optional callback applies application-specific constraints after namespace validation. */
+typedef bool (*file_object_crypto_identity_valid_t)(void *ctx, const file_object_record_identity_t *identity);
+
+typedef struct file_object_crypto_provider_config {
+ void *ctx;
+ uint16_t namespace_id;
+ file_object_crypto_root_load_t load_root;
+ file_object_crypto_identity_valid_t identity_valid;
+} file_object_crypto_provider_config_t;
+
+/* One manifest authentication may be active per instance. Instances must not be copied after initialization. */
+typedef struct file_object_crypto_provider {
+ file_object_crypto_provider_config_t config;
+ file_object_authenticator_t manifest_authenticator;
+ file_object_record_protector_t record_protector;
+ mbedtls_md_context_t manifest_md;
+ bool manifest_active;
+ bool initialized;
+} file_object_crypto_provider_t;
+
+int file_object_crypto_provider_init(file_object_crypto_provider_t *provider, const file_object_crypto_provider_config_t *config);
+void file_object_crypto_provider_deinit(file_object_crypto_provider_t *provider);
+const file_object_authenticator_t *file_object_crypto_manifest_authenticator(file_object_crypto_provider_t *provider);
+const file_object_record_protector_t *file_object_crypto_record_protector(file_object_crypto_provider_t *provider);
+
+#endif // _OBJECT_CRYPTO_PROVIDER_H_
diff --git a/src/fs/object_store.c b/src/fs/object_store.c
index f1d5082..754c1b1 100644
--- a/src/fs/object_store.c
+++ b/src/fs/object_store.c
@@ -69,7 +69,11 @@ static int file_object_parse(file_t *file, const file_object_id_t *object_id, fi
return PICOKEYS_WRONG_DATA;
}
- *record = (file_object_record_t) { .payload_offset = FILE_OBJECT_HEADER_SIZE, .payload_size = payload_size, .generation = generation };
+ *record = (file_object_record_t) {
+ .payload_offset = FILE_OBJECT_HEADER_SIZE,
+ .payload_size = payload_size,
+ .generation = generation
+ };
return PICOKEYS_OK;
}
@@ -136,7 +140,12 @@ int file_object_open(const file_object_id_t *object_id, file_object_handle_t *ha
for (size_t i = 0; i < FILE_OBJECT_MAX_HANDLES; i++) {
if (file_object_handles[i].token == FILE_OBJECT_INVALID_HANDLE) {
- file_object_handles[i] = (file_object_handle_entry_t) { .object_id = *object_id, .generation = record.generation, .payload_size = record.payload_size, .token = file_object_allocate_token() };
+ file_object_handles[i] = (file_object_handle_entry_t) {
+ .object_id = *object_id,
+ .generation = record.generation,
+ .payload_size = record.payload_size,
+ .token = file_object_allocate_token()
+ };
*handle = file_object_handles[i].token;
return PICOKEYS_OK;
}
@@ -154,7 +163,10 @@ int file_object_get_info(file_object_handle_t handle, file_object_info_t *info)
if (r != PICOKEYS_OK) {
return r;
}
- *info = (file_object_info_t) { .payload_size = record.payload_size, .generation = record.generation };
+ *info = (file_object_info_t) {
+ .payload_size = record.payload_size,
+ .generation = record.generation
+ };
return PICOKEYS_OK;
}
diff --git a/src/fs/object_store_txn.c b/src/fs/object_store_txn.c
index 30b1327..0dc8fa2 100644
--- a/src/fs/object_store_txn.c
+++ b/src/fs/object_store_txn.c
@@ -172,7 +172,19 @@ static int file_object_txn_validate_record(const file_object_txn_layout_t *layou
if (r != PICOKEYS_OK) {
return r;
}
- if (memcmp(header, file_object_txn_record_magic, sizeof(file_object_txn_record_magic)) != 0 || header[FILE_OBJECT_TXN_VERSION_OFFSET] != FILE_OBJECT_TXN_FORMAT_VERSION || header[FILE_OBJECT_TXN_HEADER_SIZE_OFFSET] != FILE_OBJECT_TXN_RECORD_HEADER_SIZE || header[FILE_OBJECT_TXN_SLOT_OFFSET] != slot || header[FILE_OBJECT_TXN_RECORD_RESERVED_OFFSET] != 0 || get_uint16_be(header + FILE_OBJECT_TXN_NAMESPACE_OFFSET) != layout->namespace_id || get_uint16_be(header + FILE_OBJECT_TXN_TYPE_OFFSET) != layout->object_type || get_uint32_be(header + FILE_OBJECT_TXN_OBJECT_ID_OFFSET) != layout->object_id || get_uint32_be(header + FILE_OBJECT_TXN_GENERATION_OFFSET) != generation || get_uint32_be(header + FILE_OBJECT_TXN_RECORD_ID_OFFSET) != record_id || get_uint32_be(header + FILE_OBJECT_TXN_PAYLOAD_SIZE_OFFSET) != payload_size || get_uint16_be(header + FILE_OBJECT_TXN_RECORD_FID_OFFSET) != layout->record_fid[slot] || get_uint16_be(header + FILE_OBJECT_TXN_RECORD_RESERVED_2_OFFSET) != 0) {
+ if (memcmp(header, file_object_txn_record_magic, sizeof(file_object_txn_record_magic)) != 0
+ || header[FILE_OBJECT_TXN_VERSION_OFFSET] != FILE_OBJECT_TXN_FORMAT_VERSION
+ || header[FILE_OBJECT_TXN_HEADER_SIZE_OFFSET] != FILE_OBJECT_TXN_RECORD_HEADER_SIZE
+ || header[FILE_OBJECT_TXN_SLOT_OFFSET] != slot
+ || header[FILE_OBJECT_TXN_RECORD_RESERVED_OFFSET] != 0
+ || get_uint16_be(header + FILE_OBJECT_TXN_NAMESPACE_OFFSET) != layout->namespace_id
+ || get_uint16_be(header + FILE_OBJECT_TXN_TYPE_OFFSET) != layout->object_type
+ || get_uint32_be(header + FILE_OBJECT_TXN_OBJECT_ID_OFFSET) != layout->object_id
+ || get_uint32_be(header + FILE_OBJECT_TXN_GENERATION_OFFSET) != generation
+ || get_uint32_be(header + FILE_OBJECT_TXN_RECORD_ID_OFFSET) != record_id
+ || get_uint32_be(header + FILE_OBJECT_TXN_PAYLOAD_SIZE_OFFSET) != payload_size
+ || get_uint16_be(header + FILE_OBJECT_TXN_RECORD_FID_OFFSET) != layout->record_fid[slot]
+ || get_uint16_be(header + FILE_OBJECT_TXN_RECORD_RESERVED_2_OFFSET) != 0) {
return PICOKEYS_WRONG_DATA;
}
@@ -210,7 +222,16 @@ static int file_object_txn_parse_commit(const file_object_txn_layout_t *layout,
uint32_t payload_size = get_uint32_be(commit + FILE_OBJECT_TXN_COMMIT_PAYLOAD_SIZE_OFFSET);
uint16_t record_fid = get_uint16_be(commit + FILE_OBJECT_TXN_COMMIT_RECORD_FID_OFFSET);
uint8_t record_slot = commit[FILE_OBJECT_TXN_COMMIT_RECORD_SLOT_OFFSET];
- if (memcmp(commit, file_object_txn_commit_magic, sizeof(file_object_txn_commit_magic)) != 0 || commit[FILE_OBJECT_TXN_VERSION_OFFSET] != FILE_OBJECT_TXN_FORMAT_VERSION || commit[FILE_OBJECT_TXN_HEADER_SIZE_OFFSET] != FILE_OBJECT_TXN_COMMIT_HEADER_SIZE || commit[FILE_OBJECT_TXN_SLOT_OFFSET] != slot || commit[FILE_OBJECT_TXN_COMMIT_MARKER_OFFSET] != FILE_OBJECT_TXN_COMMIT_MARKER || get_uint16_be(commit + FILE_OBJECT_TXN_NAMESPACE_OFFSET) != layout->namespace_id || get_uint16_be(commit + FILE_OBJECT_TXN_TYPE_OFFSET) != layout->object_type || get_uint32_be(commit + FILE_OBJECT_TXN_OBJECT_ID_OFFSET) != layout->object_id || generation == 0 || (flags & ~FILE_OBJECT_TXN_FLAG_DELETED) != 0) {
+ if (memcmp(commit, file_object_txn_commit_magic, sizeof(file_object_txn_commit_magic)) != 0
+ || commit[FILE_OBJECT_TXN_VERSION_OFFSET] != FILE_OBJECT_TXN_FORMAT_VERSION
+ || commit[FILE_OBJECT_TXN_HEADER_SIZE_OFFSET] != FILE_OBJECT_TXN_COMMIT_HEADER_SIZE
+ || commit[FILE_OBJECT_TXN_SLOT_OFFSET] != slot
+ || commit[FILE_OBJECT_TXN_COMMIT_MARKER_OFFSET] != FILE_OBJECT_TXN_COMMIT_MARKER
+ || get_uint16_be(commit + FILE_OBJECT_TXN_NAMESPACE_OFFSET) != layout->namespace_id
+ || get_uint16_be(commit + FILE_OBJECT_TXN_TYPE_OFFSET) != layout->object_type
+ || get_uint32_be(commit + FILE_OBJECT_TXN_OBJECT_ID_OFFSET) != layout->object_id
+ || generation == 0
+ || (flags & ~FILE_OBJECT_TXN_FLAG_DELETED) != 0) {
return PICOKEYS_WRONG_DATA;
}
@@ -241,7 +262,14 @@ static int file_object_txn_parse_commit(const file_object_txn_layout_t *layout,
}
}
- *state = (file_object_txn_state_t) { .generation = generation, .record_id = record_id, .payload_size = payload_size, .commit_slot = slot, .record_slot = record_slot, .deleted = deleted };
+ *state = (file_object_txn_state_t) {
+ .generation = generation,
+ .record_id = record_id,
+ .payload_size = payload_size,
+ .commit_slot = slot,
+ .record_slot = record_slot,
+ .deleted = deleted
+ };
return PICOKEYS_OK;
}
@@ -448,7 +476,11 @@ int file_object_txn_open(const file_object_txn_layout_t *layout, const file_obje
for (size_t i = 0; i < FILE_OBJECT_TXN_MAX_HANDLES; i++) {
if (file_object_txn_handles[i].token == FILE_OBJECT_TXN_INVALID_HANDLE) {
- file_object_txn_handles[i] = (file_object_txn_handle_entry_t) { .layout = *layout, .state = state, .token = file_object_txn_allocate_token() };
+ file_object_txn_handles[i] = (file_object_txn_handle_entry_t) {
+ .layout = *layout,
+ .state = state,
+ .token = file_object_txn_allocate_token()
+ };
*handle = file_object_txn_handles[i].token;
return PICOKEYS_OK;
}
@@ -466,7 +498,10 @@ int file_object_txn_get_info(file_object_txn_handle_t handle, const file_object_
if (r != PICOKEYS_OK) {
return r;
}
- *info = (file_object_info_t) { .payload_size = state.payload_size, .generation = state.generation };
+ *info = (file_object_info_t) {
+ .payload_size = state.payload_size,
+ .generation = state.generation
+ };
return PICOKEYS_OK;
}