From 96a4acf84ed7f2623339ece1361c5602f423f110 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 27 Jul 2026 00:50:26 +0200 Subject: [PATCH] Add container-backed storage for OpenPGP and PIV keys Store new key material atomically with legacy EF compatibility and power-loss coverage. Signed-off-by: Pol Henarejos --- CMakeLists.txt | 20 ++ pico-keys-sdk | 2 +- src/openpgp/cmd_get_data.c | 2 +- src/openpgp/cmd_import_data.c | 12 +- src/openpgp/cmd_keypair_gen.c | 26 +- src/openpgp/cmd_put_data.c | 2 +- src/openpgp/do.c | 6 +- src/openpgp/files.c | 8 +- src/openpgp/key_container.c | 404 ++++++++++++++++++++++ src/openpgp/key_container.h | 37 +++ src/openpgp/object_provider.c | 121 +++++++ src/openpgp/object_provider.h | 30 ++ src/openpgp/openpgp.c | 105 ++++-- src/openpgp/openpgp.h | 9 +- src/openpgp/piv.c | 117 +++++-- tests/openpgp_key_container_test.c | 516 +++++++++++++++++++++++++++++ 16 files changed, 1334 insertions(+), 83 deletions(-) create mode 100644 src/openpgp/key_container.c create mode 100644 src/openpgp/key_container.h create mode 100644 src/openpgp/object_provider.c create mode 100644 src/openpgp/object_provider.h create mode 100644 tests/openpgp_key_container_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 292c0cb..b9a98d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,8 @@ if(NOT ESP_PLATFORM) endif() set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/openpgp.c + ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/object_provider.c + ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/key_container.c ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/files.c ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/piv.c ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/management.c @@ -118,6 +120,7 @@ if(NOT ESP_PLATFORM) endif() if(ENABLE_EMULATION) + enable_testing() if(NOT MSVC) target_compile_options(pico_openpgp PUBLIC -fdata-sections @@ -140,6 +143,23 @@ if(NOT ESP_PLATFORM) ) endif(APPLE) target_link_libraries(pico_openpgp PRIVATE picokeys_sdk mbedtls pthread m) + + add_executable(openpgp_key_container_test + ${CMAKE_CURRENT_LIST_DIR}/pico-keys-sdk/src/fs/object_container.c + ${CMAKE_CURRENT_LIST_DIR}/pico-keys-sdk/src/fs/object_container_store.c + ${CMAKE_CURRENT_LIST_DIR}/pico-keys-sdk/src/fs/object_policy.c + ${CMAKE_CURRENT_LIST_DIR}/pico-keys-sdk/src/fs/object_store_txn.c + ${CMAKE_CURRENT_LIST_DIR}/src/openpgp/key_container.c + ${CMAKE_CURRENT_LIST_DIR}/tests/openpgp_key_container_test.c + ) + target_include_directories(openpgp_key_container_test PRIVATE ${INCLUDES}) + if(MSVC) + target_compile_options(openpgp_key_container_test PRIVATE /W4 /WX) + else() + target_compile_options(openpgp_key_container_test PRIVATE -Wall -Wextra -Werror) + endif() + target_link_libraries(openpgp_key_container_test PRIVATE mbedtls) + add_test(NAME openpgp_key_container_test COMMAND openpgp_key_container_test) else() pico_add_extra_outputs(${CMAKE_PROJECT_NAME}) endif() diff --git a/pico-keys-sdk b/pico-keys-sdk index 50481a7..7495343 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 50481a7671c06965faa7d4624aa2937679368a1d +Subproject commit 749534369a4cd0a6799c613fb2247ff238591c5d diff --git a/src/openpgp/cmd_get_data.c b/src/openpgp/cmd_get_data.c index e79b708..c0aff0c 100644 --- a/src/openpgp/cmd_get_data.c +++ b/src/openpgp/cmd_get_data.c @@ -66,7 +66,7 @@ int cmd_get_data(void) { uint16_t fids[] = { 1, ef->fid }; uint16_t data_len = parse_do(fids, 1); data_len = MIN(data_len, res_APDU_size); - if (!(ef->type & FILE_DATA_FLASH)) { + if (!(file_get_type(ef) & FILE_DATA_FLASH)) { uint8_t *p = NULL; uint16_t tg = 0; uint16_t tg_len = 0; diff --git a/src/openpgp/cmd_import_data.c b/src/openpgp/cmd_import_data.c index e946846..a6f2b64 100644 --- a/src/openpgp/cmd_import_data.c +++ b/src/openpgp/cmd_import_data.c @@ -186,8 +186,8 @@ int cmd_import_data(void) { mbedtls_rsa_free(&rsa); return SW_EXEC_ERROR(); } - r = store_keys(&rsa, ALGO_RSA, fid, true); make_rsa_response(&rsa); + r = store_keypair(&rsa, ALGO_RSA, fid, res_APDU, res_APDU_size); mbedtls_rsa_free(&rsa); if (r != PICOKEYS_OK) { return SW_EXEC_ERROR(); @@ -219,8 +219,8 @@ int cmd_import_data(void) { mbedtls_ecp_keypair_free(&ecdsa); return SW_EXEC_ERROR(); } - r = store_keys(&ecdsa, ALGO_ECDSA, fid, true); make_ecdsa_response(&ecdsa); + r = store_keypair(&ecdsa, algo[0], fid, res_APDU, res_APDU_size); mbedtls_ecp_keypair_free(&ecdsa); if (r != PICOKEYS_OK) { return SW_EXEC_ERROR(); @@ -232,14 +232,6 @@ int cmd_import_data(void) { if (fid == EF_PK_SIG) { reset_sig_count(); } - file_t *pbef = file_search_by_fid(fid + 3, NULL, SPECIFY_EF); - if (!pbef) { - return SW_REFERENCE_NOT_FOUND(); - } - r = file_put_data(pbef, res_APDU, res_APDU_size); - if (r != PICOKEYS_OK) { - return SW_EXEC_ERROR(); - } res_APDU_size = 0; //make_*_response sets a response. we need to overwrite return SW_OK(); } diff --git a/src/openpgp/cmd_keypair_gen.c b/src/openpgp/cmd_keypair_gen.c index c7bc63d..4f638fe 100644 --- a/src/openpgp/cmd_keypair_gen.c +++ b/src/openpgp/cmd_keypair_gen.c @@ -18,6 +18,7 @@ #include #include "openpgp.h" #include "do.h" +#include "key_container.h" #include "random.h" int cmd_keypair_gen(void) { @@ -75,8 +76,8 @@ int cmd_keypair_gen(void) { mbedtls_rsa_free(&rsa); return SW_EXEC_ERROR(); } - r = store_keys(&rsa, ALGO_RSA, fid, true); make_rsa_response(&rsa); + r = store_keypair(&rsa, ALGO_RSA, fid, res_APDU, res_APDU_size); mbedtls_rsa_free(&rsa); if (r != PICOKEYS_OK) { return SW_EXEC_ERROR(); @@ -95,8 +96,8 @@ int cmd_keypair_gen(void) { mbedtls_ecp_keypair_free(&ecdsa); return SW_EXEC_ERROR(); } - r = store_keys(&ecdsa, algo[0], fid, true); make_ecdsa_response(&ecdsa); + r = store_keypair(&ecdsa, algo[0], fid, res_APDU, res_APDU_size); mbedtls_ecp_keypair_free(&ecdsa); if (r != PICOKEYS_OK) { return SW_EXEC_ERROR(); @@ -105,14 +106,6 @@ int cmd_keypair_gen(void) { else { return SW_FUNC_NOT_SUPPORTED(); } - file_t *pbef = file_search_by_fid(fid + 3, NULL, SPECIFY_EF); - if (!pbef) { - return SW_REFERENCE_NOT_FOUND(); - } - r = file_put_data(pbef, res_APDU, res_APDU_size); - if (r != PICOKEYS_OK) { - return SW_EXEC_ERROR(); - } if (fid == EF_PK_SIG) { reset_sig_count(); } @@ -132,6 +125,19 @@ int cmd_keypair_gen(void) { return SW_OK(); } else if (P1(apdu) == 0x81) { //read + file_t *private_ef = file_search_by_fid(fid, NULL, SPECIFY_EF); + if (openpgp_key_container_is_marker(private_ef)) { + uint32_t public_size = 0; + if (openpgp_key_container_public_size(fid, &public_size) != PICOKEYS_OK || public_size > OPENPGP_MAX_RESPONSE_SIZE) { + return SW_REFERENCE_NOT_FOUND(); + } + size_t written = 0; + if (openpgp_key_container_read_public(fid, res_APDU, public_size, &written) != PICOKEYS_OK || written != public_size) { + return SW_EXEC_ERROR(); + } + res_APDU_size = (uint16_t)written; + return SW_OK(); + } file_t *ef = file_search_by_fid(fid + 3, NULL, SPECIFY_EF); if (!file_has_data(ef)) { return SW_REFERENCE_NOT_FOUND(); diff --git a/src/openpgp/cmd_put_data.c b/src/openpgp/cmd_put_data.c index 2134a4c..11c25af 100644 --- a/src/openpgp/cmd_put_data.c +++ b/src/openpgp/cmd_put_data.c @@ -69,7 +69,7 @@ int cmd_put_data(void) { else if (currentEF && currentEF->fid == fid) { // previously selected same EF ef = currentEF; } - if (ef->type & FILE_DATA_FLASH) { + if (file_get_type(ef) & FILE_DATA_FLASH) { int r = 0; if (apdu.nc > 0) { if (requested_fid == EF_PW_STATUS) { diff --git a/src/openpgp/do.c b/src/openpgp/do.c index 8789585..177d91a 100644 --- a/src/openpgp/do.c +++ b/src/openpgp/do.c @@ -64,7 +64,7 @@ int parse_do(uint16_t *fids, int mode) { for (int i = 0; i < fids[0]; i++) { if ((ef = file_search_by_fid(fids[i + 1], NULL, SPECIFY_EF))) { uint16_t data_len; - if ((ef->type & FILE_DATA_FUNC) == FILE_DATA_FUNC) { + if ((file_get_type(ef) & FILE_DATA_FUNC) == FILE_DATA_FUNC) { if (mode == 1 && response_remaining() < 16) { break; } @@ -158,7 +158,7 @@ int parse_sec_tpl(const file_t *f, int mode) { memset(res_APDU + res_APDU_size, 0, 3); file_t *ef = file_search_by_fid(EF_SIG_COUNT, NULL, SPECIFY_ANY); if (ef && ef->data) { - uint16_t data_len = MIN(file_get_size(ef), 3); + uint16_t data_len = MIN(file_get_size(ef), 3u); memcpy(res_APDU + res_APDU_size, file_get_data(ef), data_len); } res_APDU_size += 3; @@ -244,7 +244,7 @@ int parse_pw_status(const file_t *f, int mode) { ef = file_search_by_fid(EF_PW_PRIV, NULL, SPECIFY_ANY); memset(res_APDU + res_APDU_size, 0, 7); if (ef && ef->data) { - uint16_t data_len = MIN(file_get_size(ef), 7); + uint16_t data_len = MIN(file_get_size(ef), 7u); memcpy(res_APDU + res_APDU_size, file_get_data(ef), data_len); } res_APDU_size += 7; diff --git a/src/openpgp/files.c b/src/openpgp/files.c index 87cad82..c5c8b30 100644 --- a/src/openpgp/files.c +++ b/src/openpgp/files.c @@ -76,7 +76,7 @@ uint8_t exlen_info[] = { 0x2, 0x2, 0x08, 0x00, }; -file_t file_entries[] = { +file_entry_t file_entries[] = { /* 0 */ { .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = ACL_NONE }, // MF /* 1 */ { .fid = EF_FULL_AID, .parent = 0, .name = openpgp_aid_full, @@ -506,6 +506,6 @@ file_t file_entries[] = { .ef_structure = 0, .acl = ACL_NONE } //end }; -const file_t *MF = &file_entries[0]; -const file_t *file_openpgp = &file_entries[sizeof(file_entries) / sizeof(file_t) - 2]; -const file_t *file_last = &file_entries[sizeof(file_entries) / sizeof(file_t) - 1]; +const file_t *MF = &file_entries[0].file; +const file_t *file_openpgp = &file_entries[sizeof(file_entries) / sizeof(file_entry_t) - 2].file; +const file_entry_t *file_last = &file_entries[sizeof(file_entries) / sizeof(file_entry_t) - 1]; diff --git a/src/openpgp/key_container.c b/src/openpgp/key_container.c new file mode 100644 index 0000000..127e257 --- /dev/null +++ b/src/openpgp/key_container.c @@ -0,0 +1,404 @@ +/* + * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp). + * 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 "key_container.h" +#include "object_container_store.h" +#include "object_provider.h" +#include "openpgp.h" + +#define OPENPGP_KEY_MANIFEST_SLOT_0_PREFIX 0xd0u +#define OPENPGP_KEY_MANIFEST_SLOT_1_PREFIX 0xd1u +#define OPENPGP_KEY_PRIVATE_SLOT_0_PREFIX 0xd2u +#define OPENPGP_KEY_PUBLIC_SLOT_0_PREFIX 0xd3u +#define OPENPGP_KEY_PRIVATE_SLOT_1_PREFIX 0xd4u +#define OPENPGP_KEY_PUBLIC_SLOT_1_PREFIX 0xd5u +#define OPENPGP_KEY_CONTAINER_COMMIT_TIMEOUT_MS 5000u +#define OPENPGP_KEY_CONTAINER_POLICY_ID 0x0500u +#define OPENPGP_KEY_CONTAINER_MARKER_SIZE 10u +#define OPENPGP_KEY_MARKER_VERSION_OFFSET 4u +#define OPENPGP_KEY_MARKER_FID_OFFSET 5u +#define OPENPGP_KEY_MARKER_RESERVED_OFFSET 7u +#define OPENPGP_KEY_MARKER_RESERVED_SIZE 3u +#define OPENPGP_KEY_MARKER_VERSION 1u + +static const uint8_t openpgp_key_container_marker_magic[4] = { 'P', 'K', 'G', '1' }; +static const uint8_t openpgp_key_container_marker_reserved[OPENPGP_KEY_MARKER_RESERVED_SIZE] = { 0 }; +static const uint8_t openpgp_key_internal_policy[] = { + FILE_OBJECT_POLICY_FORMAT_VERSION, 1, + 0x1f, 0xff, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 +}; + +static uint32_t openpgp_key_container_id(uint16_t fid) { + return fid & UINT8_MAX; +} + +static uint16_t openpgp_key_manifest_fid(uint16_t fid, uint8_t slot) { + uint8_t prefix = slot == 0 ? OPENPGP_KEY_MANIFEST_SLOT_0_PREFIX : OPENPGP_KEY_MANIFEST_SLOT_1_PREFIX; + return (uint16_t)((prefix << 8) | openpgp_key_container_id(fid)); +} + +static uint16_t openpgp_key_record_fid(uint16_t fid, uint8_t slot, uint16_t object_type) { + uint8_t prefix = slot == 0 ? OPENPGP_KEY_PRIVATE_SLOT_0_PREFIX : OPENPGP_KEY_PRIVATE_SLOT_1_PREFIX; + if (object_type == OPENPGP_KEY_OBJECT_PUBLIC) { + prefix++; + } + return (uint16_t)((prefix << 8) | openpgp_key_container_id(fid)); +} + +static bool openpgp_key_container_is_piv(uint16_t fid) { + bool retired_range = fid >= EF_PIV_KEY_RETIRED1 && fid <= EF_PIV_KEY_RETIRED17; + bool retired_reserved_gap = fid == EF_PIV_KEY_RETIRED19 || fid == EF_PIV_KEY_RETIRED20 || fid == EF_PIV_KEY_RETIRED18; + bool active = fid >= EF_PIV_KEY_AUTHENTICATION && fid <= EF_PIV_KEY_CARDAUTH; + return retired_range || retired_reserved_gap || active || fid == EF_PIV_KEY_ATTESTATION; +} + +bool openpgp_key_container_supported(uint16_t fid) { + return fid == EF_PK_SIG || fid == EF_PK_DEC || fid == EF_PK_AUT || fid == EF_AES_KEY || openpgp_key_container_is_piv(fid); +} + +static bool openpgp_key_object_type_valid(uint16_t object_type) { + return object_type == OPENPGP_KEY_OBJECT_PRIVATE || object_type == OPENPGP_KEY_OBJECT_PUBLIC; +} + +bool openpgp_key_container_is_marker(const file_t *file) { + if (!file_has_data(file) || file_get_size(file) != OPENPGP_KEY_CONTAINER_MARKER_SIZE) { + return false; + } + + const uint8_t *data = file_get_data(file); + return memcmp(data, openpgp_key_container_marker_magic, sizeof(openpgp_key_container_marker_magic)) == 0 && + data[OPENPGP_KEY_MARKER_VERSION_OFFSET] == OPENPGP_KEY_MARKER_VERSION && + get_uint16_be(data + OPENPGP_KEY_MARKER_FID_OFFSET) == file->fid && + memcmp(data + OPENPGP_KEY_MARKER_RESERVED_OFFSET, openpgp_key_container_marker_reserved, sizeof(openpgp_key_container_marker_reserved)) == 0; +} + +static bool openpgp_key_file_magic(uint16_t fid, const uint8_t magic[4]) { + file_t *file = file_search(fid); + return file_has_data(file) && file_get_size(file) >= 4 && memcmp(file_get_data(file), magic, 4) == 0; +} + +bool openpgp_key_container_physical_fid(uint16_t fid) { + static const uint8_t manifest_magic[4] = { 'P', 'K', 'O', 'C' }; + static const uint8_t record_magic[4] = { 'P', 'K', 'O', 'R' }; + uint8_t prefix = fid >> 8; + + if (prefix == OPENPGP_KEY_MANIFEST_SLOT_0_PREFIX || prefix == OPENPGP_KEY_MANIFEST_SLOT_1_PREFIX) { + return openpgp_key_file_magic(fid, manifest_magic); + } + if (prefix >= OPENPGP_KEY_PRIVATE_SLOT_0_PREFIX && prefix <= OPENPGP_KEY_PUBLIC_SLOT_1_PREFIX) { + return openpgp_key_file_magic(fid, record_magic); + } + return false; +} + +static int openpgp_key_policy_hash(void *ctx, uint16_t policy_id, uint8_t hash[FILE_OBJECT_POLICY_HASH_SIZE]) { + (void)ctx; + + if (policy_id != OPENPGP_KEY_CONTAINER_POLICY_ID) { + return PICOKEYS_WRONG_DATA; + } + return file_object_policy_hash(openpgp_key_internal_policy, sizeof(openpgp_key_internal_policy), hash); +} + +static uint16_t openpgp_key_layout_manifest_fid(void *ctx, uint32_t container_id, uint8_t slot) { + (void)ctx; + + return openpgp_key_manifest_fid((uint16_t)container_id, slot); +} + +static bool openpgp_key_record_id_valid(uint16_t fid, const file_object_descriptor_t *object) { + if (!openpgp_key_object_type_valid(object->object_type) || object->record_id > UINT16_MAX) { + return false; + } + + uint16_t record_fid = (uint16_t)object->record_id; + return record_fid == openpgp_key_record_fid(fid, 0, object->object_type) || record_fid == openpgp_key_record_fid(fid, 1, object->object_type); +} + +static int openpgp_key_layout_record_fid(void *ctx, uint32_t container_id, const file_object_descriptor_t *object, uint16_t *fid) { + (void)ctx; + + if (!object || !fid || !openpgp_key_record_id_valid((uint16_t)container_id, object)) { + return PICOKEYS_WRONG_DATA; + } + *fid = (uint16_t)object->record_id; + return PICOKEYS_OK; +} + +static int openpgp_key_layout_record_allocate(void *ctx, uint32_t container_id, uint8_t target_slot, const file_object_container_write_t *write, const file_object_authenticator_t *auth, uint64_t *record_id, uint16_t *fid) { + (void)ctx; + (void)auth; + + if (!write || !record_id || !fid || !openpgp_key_object_type_valid(write->object_type)) { + return PICOKEYS_ERR_NULL_PARAM; + } + *fid = openpgp_key_record_fid((uint16_t)container_id, target_slot, write->object_type); + *record_id = *fid; + return PICOKEYS_OK; +} + +static bool openpgp_key_layout_write_valid(void *ctx, const file_object_container_write_t *write) { + (void)ctx; + + if (!openpgp_key_object_type_valid(write->object_type) || write->object_tag != 0 || write->policy_id != OPENPGP_KEY_CONTAINER_POLICY_ID || write->key_domain > 1) { + return false; + } + if (write->object_type == OPENPGP_KEY_OBJECT_PRIVATE) { + return write->protection == FILE_OBJECT_PROTECTION_AEAD_SECRET && write->flags == (FILE_OBJECT_FLAG_MUTABLE | FILE_OBJECT_FLAG_NON_EXPORTABLE); + } + return write->protection == FILE_OBJECT_PROTECTION_AUTHENTICATED_PUBLIC && write->flags == (FILE_OBJECT_FLAG_MUTABLE | FILE_OBJECT_FLAG_GENERIC_READABLE); +} + +static bool openpgp_key_layout_descriptor_valid(void *ctx, uint32_t container_id, const file_object_descriptor_t *object) { + (void)ctx; + + uint8_t key_domain = openpgp_key_container_is_piv((uint16_t)container_id) ? 1u : 0u; + return object->object_tag == 0 && object->key_domain == key_domain && openpgp_key_record_id_valid((uint16_t)container_id, object); +} + +static int openpgp_key_marker_write(uint16_t fid) { + file_t *file = file_search_by_fid(fid, NULL, SPECIFY_EF); + if (!file) { + return PICOKEYS_ERR_FILE_NOT_FOUND; + } + + uint8_t marker[OPENPGP_KEY_CONTAINER_MARKER_SIZE] = { 0 }; + memcpy(marker, openpgp_key_container_marker_magic, sizeof(openpgp_key_container_marker_magic)); + marker[OPENPGP_KEY_MARKER_VERSION_OFFSET] = OPENPGP_KEY_MARKER_VERSION; + put_uint16_be(fid, marker + OPENPGP_KEY_MARKER_FID_OFFSET); + int r = file_put_data(file, marker, sizeof(marker)); + if (r != PICOKEYS_OK) { + return r; + } + return flash_commit_sync(OPENPGP_KEY_CONTAINER_COMMIT_TIMEOUT_MS) ? PICOKEYS_OK : PICOKEYS_ERR_MEMORY_FATAL; +} + +static int openpgp_key_layout_activate(void *ctx, uint32_t container_id) { + (void)ctx; + + uint16_t fid = (uint16_t)container_id; + if (openpgp_key_container_is_marker(file_search_by_fid(fid, NULL, SPECIFY_EF))) { + return PICOKEYS_OK; + } + return openpgp_key_marker_write(fid); +} + +static int openpgp_key_layout_deactivate(void *ctx, uint32_t container_id) { + (void)ctx; + + file_t *marker = file_search_by_fid((uint16_t)container_id, NULL, SPECIFY_EF); + if (!marker) { + return PICOKEYS_ERR_FILE_NOT_FOUND; + } + return file_put_data(marker, NULL, 0); +} + +static const file_object_container_layout_t openpgp_key_container_layout = { + .namespace_id = OPENPGP_OBJECT_NAMESPACE, + .container_kind = OPENPGP_KEY_CONTAINER_KIND, + .commit_timeout_ms = OPENPGP_KEY_CONTAINER_COMMIT_TIMEOUT_MS, + .manifest_fid = openpgp_key_layout_manifest_fid, + .record_fid = openpgp_key_layout_record_fid, + .record_allocate = openpgp_key_layout_record_allocate, + .policy_hash = openpgp_key_policy_hash, + .write_valid = openpgp_key_layout_write_valid, + .descriptor_valid = openpgp_key_layout_descriptor_valid, + .activate = openpgp_key_layout_activate, + .deactivate = openpgp_key_layout_deactivate, + .rollback_new_records = true +}; + +static bool openpgp_key_crypto(uint16_t fid, file_object_container_crypto_t *crypto) { + if (openpgp_key_container_is_piv(fid)) { + crypto->auth = openpgp_piv_object_manifest_authenticator(); + crypto->protector = openpgp_piv_object_record_protector(); + } + else { + crypto->auth = openpgp_object_manifest_authenticator(); + crypto->protector = openpgp_object_record_protector(); + } + return crypto->auth && crypto->protector; +} + +bool openpgp_key_container_can_create(uint16_t fid) { + if (!openpgp_key_container_supported(fid)) { + return false; + } + + bool manifest_present = false; + for (uint8_t slot = 0; slot < FILE_OBJECT_CONTAINER_SLOT_COUNT; slot++) { + manifest_present |= file_has_data(file_search(openpgp_key_manifest_fid(fid, slot))); + } + if (manifest_present) { + file_object_container_crypto_t crypto; + file_object_container_state_t state; + if (!openpgp_key_crypto(fid, &crypto) || file_object_container_load(&openpgp_key_container_layout, fid, &crypto, NULL, &state) != PICOKEYS_OK) { + return false; + } + return file_object_container_validate(&openpgp_key_container_layout, fid, &state.candidates[state.current_slot], state.crypto.protector) == PICOKEYS_OK; + } + + static const uint8_t record_magic[4] = { 'P', 'K', 'O', 'R' }; + for (uint8_t slot = 0; slot < FILE_OBJECT_CONTAINER_SLOT_COUNT; slot++) { + for (uint16_t object_type = OPENPGP_KEY_OBJECT_PRIVATE; object_type <= OPENPGP_KEY_OBJECT_PUBLIC; object_type++) { + file_t *record = file_search(openpgp_key_record_fid(fid, slot, object_type)); + if (record && (!file_has_data(record) || file_get_size(record) < sizeof(record_magic) || memcmp(file_get_data(record), record_magic, sizeof(record_magic)) != 0)) { + return false; + } + } + } + return true; +} + +int openpgp_key_container_store(uint16_t fid, const uint8_t *private_data, uint32_t private_size, const uint8_t *public_data, uint32_t public_size, bool internal_firmware) { + if (!openpgp_key_container_supported(fid) || !private_data || private_size == 0 || (!public_data && public_size > 0)) { + return PICOKEYS_WRONG_DATA; + } + if ((!openpgp_key_container_is_piv(fid) && !has_pw3) || (openpgp_key_container_is_piv(fid) && !piv_key_operation_authorized(FILE_OBJECT_OPERATION_UPDATE, internal_firmware))) { + return PICOKEYS_NO_LOGIN; + } + + file_object_container_crypto_t crypto; + if (!openpgp_key_crypto(fid, &crypto)) { + return PICOKEYS_EXEC_ERROR; + } + if (!file_has_data(file_search(openpgp_key_manifest_fid(fid, 0))) && !file_has_data(file_search(openpgp_key_manifest_fid(fid, 1))) && !openpgp_key_container_can_create(fid)) { + return PICOKEYS_WRONG_DATA; + } + + file_object_container_write_t writes[2] = { + { + .object_type = OPENPGP_KEY_OBJECT_PRIVATE, + .data = private_data, + .data_size = private_size, + .policy_id = OPENPGP_KEY_CONTAINER_POLICY_ID, + .key_domain = openpgp_key_container_is_piv(fid) ? 1u : 0u, + .protection = FILE_OBJECT_PROTECTION_AEAD_SECRET, + .flags = FILE_OBJECT_FLAG_MUTABLE | FILE_OBJECT_FLAG_NON_EXPORTABLE + }, + { + .object_type = OPENPGP_KEY_OBJECT_PUBLIC, + .data = public_data, + .data_size = public_size, + .policy_id = OPENPGP_KEY_CONTAINER_POLICY_ID, + .key_domain = openpgp_key_container_is_piv(fid) ? 1u : 0u, + .protection = FILE_OBJECT_PROTECTION_AUTHENTICATED_PUBLIC, + .flags = FILE_OBJECT_FLAG_MUTABLE | FILE_OBJECT_FLAG_GENERIC_READABLE + } + }; + size_t write_count = public_data || public_size > 0 ? 2u : 1u; + return file_object_container_update(&openpgp_key_container_layout, fid, writes, write_count, &crypto, NULL); +} + +static bool openpgp_key_private_operation_authorized(uint16_t fid, uint16_t operation, bool internal_firmware) { + if (openpgp_key_container_is_piv(fid)) { + return piv_key_operation_authorized(operation, internal_firmware); + } + if (operation == FILE_OBJECT_OPERATION_UPDATE || operation == FILE_OBJECT_OPERATION_DELETE || operation == FILE_OBJECT_OPERATION_CHANGE_POLICY) { + return has_pw3; + } + if (fid == EF_PK_SIG && operation == FILE_OBJECT_OPERATION_SIGN) { + return has_pw1 || has_pw3; + } + if ((fid == EF_PK_DEC || fid == EF_AES_KEY) && (operation == FILE_OBJECT_OPERATION_DECRYPT || operation == FILE_OBJECT_OPERATION_DERIVE || operation == FILE_OBJECT_OPERATION_USE)) { + return has_pw2 || has_pw3; + } + if (fid == EF_PK_AUT && (operation == FILE_OBJECT_OPERATION_SIGN || operation == FILE_OBJECT_OPERATION_DECRYPT || operation == FILE_OBJECT_OPERATION_DERIVE || operation == FILE_OBJECT_OPERATION_USE)) { + return has_pw2 || has_pw3; + } + return false; +} + +typedef struct openpgp_key_access_context { + uint16_t fid; + uint16_t operation; + bool internal_firmware; +} openpgp_key_access_context_t; + +static int openpgp_key_object_access(void *ctx, const file_object_descriptor_t *object) { + const openpgp_key_access_context_t *access = (const openpgp_key_access_context_t *)ctx; + if (object->object_type == OPENPGP_KEY_OBJECT_PRIVATE) { + return openpgp_key_private_operation_authorized(access->fid, access->operation, access->internal_firmware) ? PICOKEYS_OK : PICOKEYS_NO_LOGIN; + } + bool readable = access->operation == FILE_OBJECT_OPERATION_READ && object->protection == FILE_OBJECT_PROTECTION_AUTHENTICATED_PUBLIC && (object->flags & FILE_OBJECT_FLAG_GENERIC_READABLE) != 0; + return readable ? PICOKEYS_OK : PICOKEYS_NO_LOGIN; +} + +int openpgp_key_container_read_private(uint16_t fid, uint16_t operation, bool internal_firmware, uint8_t *data, size_t capacity, size_t *written) { + if (!openpgp_key_container_supported(fid)) { + return PICOKEYS_WRONG_DATA; + } + + file_object_container_crypto_t crypto; + if (!openpgp_key_crypto(fid, &crypto)) { + return PICOKEYS_EXEC_ERROR; + } + openpgp_key_access_context_t access = { + .fid = fid, + .operation = operation, + .internal_firmware = internal_firmware + }; + return file_object_container_read(&openpgp_key_container_layout, fid, OPENPGP_KEY_OBJECT_PRIVATE, 0, &crypto, NULL, openpgp_key_object_access, &access, data, capacity, written); +} + +int openpgp_key_container_read_public(uint16_t fid, uint8_t *data, size_t capacity, size_t *written) { + if (!openpgp_key_container_supported(fid)) { + return PICOKEYS_WRONG_DATA; + } + + file_object_container_crypto_t crypto; + if (!openpgp_key_crypto(fid, &crypto)) { + return PICOKEYS_EXEC_ERROR; + } + openpgp_key_access_context_t access = { + .fid = fid, + .operation = FILE_OBJECT_OPERATION_READ + }; + return file_object_container_read(&openpgp_key_container_layout, fid, OPENPGP_KEY_OBJECT_PUBLIC, 0, &crypto, NULL, openpgp_key_object_access, &access, data, capacity, written); +} + +int openpgp_key_container_public_size(uint16_t fid, uint32_t *object_size) { + if (!openpgp_key_container_supported(fid)) { + return PICOKEYS_WRONG_DATA; + } + + file_object_container_crypto_t crypto; + if (!openpgp_key_crypto(fid, &crypto)) { + return PICOKEYS_EXEC_ERROR; + } + openpgp_key_access_context_t access = { + .fid = fid, + .operation = FILE_OBJECT_OPERATION_READ + }; + return file_object_container_object_size(&openpgp_key_container_layout, fid, OPENPGP_KEY_OBJECT_PUBLIC, 0, &crypto, NULL, openpgp_key_object_access, &access, object_size); +} + +int openpgp_key_container_delete(uint16_t fid, bool internal_firmware) { + if (!openpgp_key_container_supported(fid)) { + return PICOKEYS_WRONG_DATA; + } + if ((!openpgp_key_container_is_piv(fid) && !has_pw3) || (openpgp_key_container_is_piv(fid) && !piv_key_operation_authorized(FILE_OBJECT_OPERATION_DELETE, internal_firmware))) { + return PICOKEYS_NO_LOGIN; + } + + file_object_container_crypto_t crypto; + if (!openpgp_key_crypto(fid, &crypto)) { + return PICOKEYS_EXEC_ERROR; + } + return file_object_container_delete(&openpgp_key_container_layout, fid, &crypto, NULL); +} diff --git a/src/openpgp/key_container.h b/src/openpgp/key_container.h new file mode 100644 index 0000000..a6e3864 --- /dev/null +++ b/src/openpgp/key_container.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp). + * 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 _OPENPGP_KEY_CONTAINER_H_ +#define _OPENPGP_KEY_CONTAINER_H_ + +#include "object_policy.h" + +#define OPENPGP_KEY_CONTAINER_KIND 0x0001u +#define OPENPGP_KEY_OBJECT_PRIVATE 0x0001u +#define OPENPGP_KEY_OBJECT_PUBLIC 0x0002u + +bool openpgp_key_container_supported(uint16_t fid); +bool openpgp_key_container_is_marker(const file_t *file); +bool openpgp_key_container_physical_fid(uint16_t fid); +bool openpgp_key_container_can_create(uint16_t fid); +int openpgp_key_container_store(uint16_t fid, const uint8_t *private_data, uint32_t private_size, const uint8_t *public_data, uint32_t public_size, bool internal_firmware); +int openpgp_key_container_read_private(uint16_t fid, uint16_t operation, bool internal_firmware, uint8_t *data, size_t capacity, size_t *written); +int openpgp_key_container_read_public(uint16_t fid, uint8_t *data, size_t capacity, size_t *written); +int openpgp_key_container_public_size(uint16_t fid, uint32_t *object_size); +int openpgp_key_container_delete(uint16_t fid, bool internal_firmware); + +#endif // _OPENPGP_KEY_CONTAINER_H_ diff --git a/src/openpgp/object_provider.c b/src/openpgp/object_provider.c new file mode 100644 index 0000000..7e8fcf3 --- /dev/null +++ b/src/openpgp/object_provider.c @@ -0,0 +1,121 @@ +/* + * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp). + * 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 "crypto_utils.h" +#include "object_crypto_provider.h" +#include "object_provider.h" +#include "openpgp.h" + +static file_object_crypto_provider_t openpgp_object_crypto_provider; +static file_object_crypto_provider_t openpgp_piv_object_crypto_provider; +static bool openpgp_object_crypto_provider_initialized; +static bool openpgp_piv_object_crypto_provider_initialized; + +static int openpgp_object_root_load(void *ctx, uint8_t root[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE]) { + (void)ctx; + + int r = load_dek(); + if (r == PICOKEYS_OK) { + memcpy(root, dek + IV_SIZE, FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE); + } + release_dek(); + return r; +} + +static int openpgp_object_public_root_load(void *ctx, uint8_t root[FILE_OBJECT_CRYPTO_ROOT_KEY_SIZE]) { + (void)ctx; + + derive_kbase(root); + return PICOKEYS_OK; +} + +static bool openpgp_object_identity_valid(void *ctx, const file_object_record_identity_t *identity) { + (void)ctx; + + return identity->key_domain == 0; +} + +static bool openpgp_piv_object_identity_valid(void *ctx, const file_object_record_identity_t *identity) { + (void)ctx; + + return identity->key_domain == 1; +} + +static int openpgp_object_crypto_provider_init(void) { + if (openpgp_object_crypto_provider_initialized) { + return PICOKEYS_OK; + } + + const file_object_crypto_provider_config_t config = { + .namespace_id = OPENPGP_OBJECT_NAMESPACE, + .load_root = openpgp_object_root_load, + .load_public_root = openpgp_object_public_root_load, + .identity_valid = openpgp_object_identity_valid + }; + int r = file_object_crypto_provider_init(&openpgp_object_crypto_provider, &config); + if (r == PICOKEYS_OK) { + openpgp_object_crypto_provider_initialized = true; + } + return r; +} + +static int openpgp_piv_object_crypto_provider_init(void) { + if (openpgp_piv_object_crypto_provider_initialized) { + return PICOKEYS_OK; + } + + const file_object_crypto_provider_config_t config = { + .namespace_id = OPENPGP_OBJECT_NAMESPACE, + .load_root = openpgp_object_public_root_load, + .load_public_root = openpgp_object_public_root_load, + .identity_valid = openpgp_piv_object_identity_valid + }; + int r = file_object_crypto_provider_init(&openpgp_piv_object_crypto_provider, &config); + if (r == PICOKEYS_OK) { + openpgp_piv_object_crypto_provider_initialized = true; + } + return r; +} + +const file_object_authenticator_t *openpgp_object_manifest_authenticator(void) { + if (openpgp_object_crypto_provider_init() != PICOKEYS_OK) { + return NULL; + } + return file_object_crypto_manifest_authenticator(&openpgp_object_crypto_provider); +} + +const file_object_record_protector_t *openpgp_object_record_protector(void) { + if (openpgp_object_crypto_provider_init() != PICOKEYS_OK) { + return NULL; + } + return file_object_crypto_record_protector(&openpgp_object_crypto_provider); +} + +const file_object_authenticator_t *openpgp_piv_object_manifest_authenticator(void) { + if (openpgp_piv_object_crypto_provider_init() != PICOKEYS_OK) { + return NULL; + } + return file_object_crypto_manifest_authenticator(&openpgp_piv_object_crypto_provider); +} + +const file_object_record_protector_t *openpgp_piv_object_record_protector(void) { + if (openpgp_piv_object_crypto_provider_init() != PICOKEYS_OK) { + return NULL; + } + return file_object_crypto_record_protector(&openpgp_piv_object_crypto_provider); +} diff --git a/src/openpgp/object_provider.h b/src/openpgp/object_provider.h new file mode 100644 index 0000000..16f39b5 --- /dev/null +++ b/src/openpgp/object_provider.h @@ -0,0 +1,30 @@ +/* + * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp). + * 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 _OPENPGP_OBJECT_PROVIDER_H_ +#define _OPENPGP_OBJECT_PROVIDER_H_ + +#include "object_container.h" + +#define OPENPGP_OBJECT_NAMESPACE 0x0005u + +const file_object_authenticator_t *openpgp_object_manifest_authenticator(void); +const file_object_record_protector_t *openpgp_object_record_protector(void); +const file_object_authenticator_t *openpgp_piv_object_manifest_authenticator(void); +const file_object_record_protector_t *openpgp_piv_object_record_protector(void); + +#endif // _OPENPGP_OBJECT_PROVIDER_H_ diff --git a/src/openpgp/openpgp.c b/src/openpgp/openpgp.c index 09e9b27..ca65be2 100644 --- a/src/openpgp/openpgp.c +++ b/src/openpgp/openpgp.c @@ -21,6 +21,7 @@ #define MBEDTLS_ALLOW_PRIVATE_ACCESS #endif #include "openpgp.h" +#include "key_container.h" #include "serial.h" #include "version.h" #include "random.h" @@ -57,7 +58,7 @@ enum { ADMINLESS_MODE_KDF_MIGRATION = 3, }; -#define ADMINLESS_MODE_OFFSET 6 +#define ADMINLESS_MODE_OFFSET (6u) #define ADMINLESS_RETRIES_SIZE (ADMINLESS_MODE_OFFSET + 1) static int adminless_set_mode(uint8_t mode) { @@ -292,9 +293,9 @@ void select_file(file_t *pe) { currentDF = (file_t *) MF; currentEF = NULL; } - else if (pe->type & FILE_TYPE_INTERNAL_EF) { + else if (file_get_type(pe) & FILE_TYPE_INTERNAL_EF) { currentEF = pe; - currentDF = &file_entries[pe->parent]; + currentDF = get_parent(pe); } else { currentDF = pe; @@ -484,7 +485,7 @@ void scan_files_openpgp(void) { flash_commit(); } -static void release_dek(void) { +void release_dek(void) { memset(dek, 0, sizeof(dek)); } @@ -692,6 +693,16 @@ int load_key_data(file_t *fkey, uint8_t *out, size_t out_size, size_t *out_len, if (!file_has_data(fkey) || !out || !out_len) { return PICOKEYS_WRONG_DATA; } + if (openpgp_key_container_is_marker(fkey)) { + uint16_t operation = FILE_OBJECT_OPERATION_USE; + if (fkey->fid == EF_PK_SIG) { + operation = FILE_OBJECT_OPERATION_SIGN; + } + else if (fkey->fid == EF_PK_DEC || fkey->fid == EF_AES_KEY) { + operation = FILE_OBJECT_OPERATION_DECRYPT; + } + return openpgp_key_container_read_private(fkey->fid, operation, true, out, out_size, out_len); + } size_t stored_len = file_get_size(fkey); const uint8_t *stored = file_get_data(fkey); @@ -828,7 +839,7 @@ int pin_reset_retries(const file_t *pin, bool force) { if (!pw_status || !pw_retries) { return PICOKEYS_ERR_FILE_NOT_FOUND; } - if (3 + (pin->fid & 0xf) >= file_get_size(pw_status) || (pin->fid & 0xf) >= file_get_size(pw_retries)) { + if (3u + (pin->fid & 0xfu) >= file_get_size(pw_status) || (pin->fid & 0xfu) >= file_get_size(pw_retries)) { return PICOKEYS_ERR_MEMORY_FATAL; } uint8_t p[64]; @@ -1033,45 +1044,85 @@ int reset_sig_count(void) { return PICOKEYS_OK; } -int store_keys(void *key_ctx, int type, uint16_t key_id, bool use_kek) { - int r, key_size = 0; - uint8_t kdata[4096 / 8]; //worst - - //if (!has_pw3) - // return PICOKEYS_NO_LOGIN; - //file_t *pw3 = file_search_by_fid(EF_PW3, NULL, SPECIFY_EF); - //if (!pw3) - // return PICOKEYS_ERR_FILE_NOT_FOUND; - file_t *ef = file_search_by_fid(key_id, NULL, SPECIFY_EF); - if (!ef) { - return PICOKEYS_ERR_FILE_NOT_FOUND; +static int serialize_key(void *key_ctx, int type, uint8_t *kdata, size_t capacity, size_t *key_size) { + if (!key_ctx || !kdata || !key_size) { + return PICOKEYS_ERR_NULL_PARAM; } + + *key_size = 0; if (type == ALGO_RSA) { mbedtls_rsa_context *rsa = (mbedtls_rsa_context *) key_ctx; - key_size = mbedtls_mpi_size(&rsa->P) + mbedtls_mpi_size(&rsa->Q); - mbedtls_mpi_write_binary(&rsa->P, kdata, key_size / 2); - mbedtls_mpi_write_binary(&rsa->Q, kdata + key_size / 2, key_size / 2); + *key_size = mbedtls_mpi_size(&rsa->P) + mbedtls_mpi_size(&rsa->Q); + if (*key_size > capacity || mbedtls_mpi_write_binary(&rsa->P, kdata, *key_size / 2) != 0 || mbedtls_mpi_write_binary(&rsa->Q, kdata + *key_size / 2, *key_size / 2) != 0) { + return PICOKEYS_WRONG_DATA; + } } else if (type == ALGO_ECDSA || type == ALGO_ECDH || type == ALGO_EDDSA) { mbedtls_ecp_keypair *ecdsa = (mbedtls_ecp_keypair *) key_ctx; size_t olen = 0; + if (capacity < 2) { + return PICOKEYS_WRONG_DATA; + } kdata[0] = ecdsa->grp.id & 0xff; - mbedtls_ecp_write_key_ext(ecdsa, &olen, kdata + 1, sizeof(kdata) - 1); - key_size = olen + 1; + if (mbedtls_ecp_write_key_ext(ecdsa, &olen, kdata + 1, capacity - 1) != 0) { + return PICOKEYS_WRONG_DATA; + } + *key_size = olen + 1; } else if (type & ALGO_AES) { if (type == ALGO_AES_128) { - key_size = 16; + *key_size = 16; } else if (type == ALGO_AES_192) { - key_size = 24; + *key_size = 24; } else if (type == ALGO_AES_256) { - key_size = 32; + *key_size = 32; } - memcpy(kdata, key_ctx, key_size); + else { + return PICOKEYS_WRONG_DATA; + } + if (*key_size > capacity) { + return PICOKEYS_WRONG_DATA; + } + memcpy(kdata, key_ctx, *key_size); + } + else { + return PICOKEYS_WRONG_DATA; + } + return PICOKEYS_OK; +} + +int store_keypair(void *key_ctx, int type, uint16_t key_id, const uint8_t *public_data, size_t public_size) { + if (!openpgp_key_container_supported(key_id) || key_id == EF_AES_KEY || !public_data || public_size == 0) { + return PICOKEYS_WRONG_DATA; + } + + uint8_t kdata[4096 / 8]; + size_t key_size = 0; + int r = serialize_key(key_ctx, type, kdata, sizeof(kdata), &key_size); + if (r == PICOKEYS_OK) { + r = openpgp_key_container_store(key_id, kdata, key_size, public_data, (uint32_t)public_size, false); + } + mbedtls_platform_zeroize(kdata, sizeof(kdata)); + return r; +} + +int store_keys(void *key_ctx, int type, uint16_t key_id, bool use_kek) { + file_t *ef = file_search_by_fid(key_id, NULL, SPECIFY_EF); + if (!ef) { + return PICOKEYS_ERR_FILE_NOT_FOUND; + } + + uint8_t kdata[4096 / 8]; + size_t key_size = 0; + int r = serialize_key(key_ctx, type, kdata, sizeof(kdata), &key_size); + if (r == PICOKEYS_OK && openpgp_key_container_supported(key_id)) { + r = openpgp_key_container_store(key_id, kdata, key_size, NULL, 0, !use_kek); + } + else if (r == PICOKEYS_OK) { + r = use_kek ? store_encrypted_key(ef, kdata, key_size) : file_put_data(ef, kdata, key_size); } - r = use_kek ? store_encrypted_key(ef, kdata, key_size) : file_put_data(ef, kdata, key_size); mbedtls_platform_zeroize(kdata, sizeof(kdata)); if (r != PICOKEYS_OK) { return r; diff --git a/src/openpgp/openpgp.h b/src/openpgp/openpgp.h index 5f109aa..67c2207 100644 --- a/src/openpgp/openpgp.h +++ b/src/openpgp/openpgp.h @@ -40,6 +40,7 @@ extern uint8_t session_pw3[32]; extern uint8_t dek[IV_SIZE + 32]; extern int store_keys(void *key_ctx, int type, uint16_t key_id, bool use_kek); +extern int store_keypair(void *key_ctx, int type, uint16_t key_id, const uint8_t *public_data, size_t public_size); extern void make_rsa_response(mbedtls_rsa_context *rsa); extern void make_ecdsa_response(mbedtls_ecdsa_context *ecdsa); extern int ecdsa_sign(mbedtls_ecdsa_context *ctx, const uint8_t *data, size_t data_len, uint8_t *out, size_t *out_len); @@ -60,6 +61,8 @@ extern int pin_reset_retries(const file_t *pin, bool force); extern void select_file(file_t *pe); extern int parse_do(uint16_t *fids, int mode); extern int load_dek(void); +extern void release_dek(void); +extern bool piv_key_operation_authorized(uint16_t operation, bool internal_firmware); extern int check_pin(const file_t *pin, const uint8_t *data, size_t len); extern int openpgp_reset_code_deactivate(void); #ifdef ENABLE_ADMINLESS_MODE @@ -105,8 +108,8 @@ int cmd_get_bulk_data(void); #define DEK_FILE_SIZE_OLD (IV_SIZE + 32 + 32 + 32 + 32) -#define OPENPGP_MAX_ALGORITHM_ATTR_SIZE 16 -#define OPENPGP_MAX_OBJECT_SIZE 2048 -#define OPENPGP_MAX_RESPONSE_SIZE 2048 +#define OPENPGP_MAX_ALGORITHM_ATTR_SIZE 16u +#define OPENPGP_MAX_OBJECT_SIZE 2048u +#define OPENPGP_MAX_RESPONSE_SIZE 2048u #endif diff --git a/src/openpgp/piv.c b/src/openpgp/piv.c index dbc33f8..c8f570b 100644 --- a/src/openpgp/piv.c +++ b/src/openpgp/piv.c @@ -33,6 +33,7 @@ #include "mbedtls/des.h" #include "mbedtls/x509_crt.h" #include "mbedtls/constant_time.h" +#include "key_container.h" #include "openpgp.h" #define PIV_ALGO_3DES 0x03 @@ -60,6 +61,7 @@ #define ORIGIN_GENERATED 0x01 #define ORIGIN_IMPORTED 0x02 +#define PIV_MANAGEMENT_KEY_DEFAULT_SIZE 24u #define IS_RETIRED(x) ((x) >= EF_PIV_KEY_RETIRED1 && (x) <= EF_PIV_KEY_RETIRED20) #define IS_ACTIVE(x) ((x) >= EF_PIV_KEY_AUTHENTICATION && (x) <= EF_PIV_KEY_CARDAUTH) @@ -87,6 +89,21 @@ static uint8_t mgm_challenge[16]; static mgm_challenge_kind_t mgm_challenge_kind = MGM_CHALLENGE_NONE; static uint8_t mgm_challenge_algo = 0; static bool has_mgm = false; +static const uint8_t piv_management_key_default[PIV_MANAGEMENT_KEY_DEFAULT_SIZE] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 +}; + +bool piv_key_operation_authorized(uint16_t operation, bool internal_firmware) { + if (internal_firmware) { + return true; + } + if (operation == FILE_OBJECT_OPERATION_UPDATE || operation == FILE_OBJECT_OPERATION_DELETE || operation == FILE_OBJECT_OPERATION_CHANGE_POLICY) { + return has_mgm; + } + return false; +} static void clear_mgm_challenge(void) { memset(mgm_challenge, 0, sizeof(mgm_challenge)); @@ -228,9 +245,7 @@ static void scan_files_piv(void) { mbedtls_platform_zeroize(session_pwpiv, sizeof(session_pwpiv)); file_put_data(ef, def, sizeof(def)); - uint8_t *key = (uint8_t *)"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"; - file_t *ef_cardmgm = file_search_by_fid(EF_PIV_KEY_CARDMGM, NULL, SPECIFY_ANY); - file_put_data(ef_cardmgm, key, 24); + openpgp_key_container_store(EF_PIV_KEY_CARDMGM, piv_management_key_default, sizeof(piv_management_key_default), NULL, 0, true); uint8_t meta[] = { PIV_ALGO_AES192, PINPOLICY_ALWAYS, TOUCHPOLICY_ALWAYS }; meta_add(EF_PIV_KEY_CARDMGM, meta, sizeof(meta)); @@ -426,7 +441,7 @@ static int cmd_piv_get_data(void) { if ((ef = file_search_by_fid((uint16_t)(fid & 0xFFFF), NULL, SPECIFY_EF))) { uint16_t data_len = 0; res_APDU_size = 2; // Minimum: TAG+LEN - if ((ef->type & FILE_DATA_FUNC) == FILE_DATA_FUNC) { + if ((file_get_type(ef) & FILE_DATA_FUNC) == FILE_DATA_FUNC) { int (*file_data_func)(const file_t *) = NULL; memcpy(&file_data_func, &ef->data, sizeof(file_data_func)); data_len = file_data_func(ef); @@ -556,15 +571,22 @@ static int cmd_get_metadata(void) { int32_t eq = 0; if (key_ref == EF_PIV_PIN) { pin_derive_verifier((const uint8_t *)"\x31\x32\x33\x34\x35\x36\xFF\xFF", 8, dhash); - eq = mbedtls_ct_memcmp(dhash, file_get_data(ef_key) + 1, file_get_size(ef_key) - 1); + eq = file_get_size(ef_key) == 34u && file_get_data(ef_key)[1] == 1u ? mbedtls_ct_memcmp(dhash, file_get_data(ef_key) + 2, sizeof(dhash)) : -1; } else if (key_ref == EF_PIV_PUK) { pin_derive_verifier((const uint8_t *)"\x31\x32\x33\x34\x35\x36\x37\x38", 8, dhash); - eq = mbedtls_ct_memcmp(dhash, file_get_data(ef_key) + 1, file_get_size(ef_key) - 1); + eq = file_get_size(ef_key) == 34u && file_get_data(ef_key)[1] == 1u ? mbedtls_ct_memcmp(dhash, file_get_data(ef_key) + 2, sizeof(dhash)) : -1; } else if (key_ref == EF_PIV_KEY_CARDMGM) { - pin_derive_verifier((const uint8_t *)"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08", 24, dhash); - eq = mbedtls_ct_memcmp(dhash, file_get_data(ef_key), file_get_size(ef_key)); + uint8_t management_key[32] = { 0 }; + size_t management_key_size = 0; + int r = openpgp_key_container_is_marker(ef_key) ? openpgp_key_container_read_private(EF_PIV_KEY_CARDMGM, FILE_OBJECT_OPERATION_USE, true, management_key, sizeof(management_key), &management_key_size) : PICOKEYS_OK; + if (!openpgp_key_container_is_marker(ef_key)) { + management_key_size = MIN(file_get_size(ef_key), sizeof(management_key)); + memcpy(management_key, file_get_data(ef_key), management_key_size); + } + eq = r == PICOKEYS_OK && management_key_size == sizeof(piv_management_key_default) ? mbedtls_ct_memcmp(piv_management_key_default, management_key, management_key_size) : -1; + mbedtls_platform_zeroize(management_key, sizeof(management_key)); } res_APDU[res_APDU_size++] = 0x5; res_APDU[res_APDU_size++] = 1; @@ -587,31 +609,45 @@ static int cmd_get_metadata(void) { } return SW_OK(); } -static int mgm_crypt(uint8_t algo, const file_t *ef_mgm, const uint8_t *input, - uint8_t *output, bool encrypt) { - int r; +static int mgm_crypt(uint8_t algo, const file_t *ef_mgm, const uint8_t *input, uint8_t *output, bool encrypt) { + uint8_t management_key[32] = { 0 }; + size_t key_len = 0; + int r = PICOKEYS_OK; + if (openpgp_key_container_is_marker(ef_mgm)) { + r = openpgp_key_container_read_private(EF_PIV_KEY_CARDMGM, FILE_OBJECT_OPERATION_USE, true, management_key, sizeof(management_key), &key_len); + } + else if (file_has_data(ef_mgm) && file_get_size(ef_mgm) <= sizeof(management_key)) { + key_len = file_get_size(ef_mgm); + memcpy(management_key, file_get_data(ef_mgm), key_len); + } + else { + r = PICOKEYS_WRONG_DATA; + } + if (r != PICOKEYS_OK) { + return r; + } + if (algo == PIV_ALGO_3DES) { mbedtls_des3_context ctx; mbedtls_des3_init(&ctx); - r = encrypt ? mbedtls_des3_set3key_enc(&ctx, file_get_data(ef_mgm)) : - mbedtls_des3_set3key_dec(&ctx, file_get_data(ef_mgm)); + r = key_len == 24 ? (encrypt ? mbedtls_des3_set3key_enc(&ctx, management_key) : mbedtls_des3_set3key_dec(&ctx, management_key)) : PICOKEYS_WRONG_DATA; if (r == 0) { r = mbedtls_des3_crypt_ecb(&ctx, input, output); } mbedtls_des3_free(&ctx); + mbedtls_platform_zeroize(management_key, sizeof(management_key)); return r; } mbedtls_aes_context ctx; mbedtls_aes_init(&ctx); - uint16_t key_len = file_get_size(ef_mgm); - r = encrypt ? mbedtls_aes_setkey_enc(&ctx, file_get_data(ef_mgm), key_len * 8) : - mbedtls_aes_setkey_dec(&ctx, file_get_data(ef_mgm), key_len * 8); + r = encrypt ? mbedtls_aes_setkey_enc(&ctx, management_key, (unsigned int)(key_len * 8u)) : mbedtls_aes_setkey_dec(&ctx, management_key, (unsigned int)(key_len * 8u)); if (r == 0) { r = mbedtls_aes_crypt_ecb(&ctx, encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT, input, output); } mbedtls_aes_free(&ctx); + mbedtls_platform_zeroize(management_key, sizeof(management_key)); return r; } @@ -717,7 +753,16 @@ static int cmd_authenticate(void) { if (!file_has_data(ef_mgm)) { return SW_MEMORY_FAILURE(); } - uint16_t mgm_len = file_get_size(ef_mgm); + uint8_t management_key[32] = { 0 }; + size_t mgm_len = 0; + int r = openpgp_key_container_is_marker(ef_mgm) ? openpgp_key_container_read_private(EF_PIV_KEY_CARDMGM, FILE_OBJECT_OPERATION_USE, true, management_key, sizeof(management_key), &mgm_len) : PICOKEYS_OK; + if (!openpgp_key_container_is_marker(ef_mgm)) { + mgm_len = MIN(file_get_size(ef_mgm), sizeof(management_key)); + } + mbedtls_platform_zeroize(management_key, sizeof(management_key)); + if (r != PICOKEYS_OK) { + return SW_MEMORY_FAILURE(); + } if ((algo == PIV_ALGO_AES128 && mgm_len != 16) || (algo == PIV_ALGO_AES192 && mgm_len != 24) || (algo == PIV_ALGO_AES256 && mgm_len != 32) || (algo == PIV_ALGO_3DES && mgm_len != 24)) { return SW_INCORRECT_P1P2(); } @@ -767,7 +812,7 @@ static int cmd_authenticate(void) { mbedtls_rsa_free(&ctx); return SW_EXEC_ERROR(); } - size_t olen = file_get_size(ef_key); + size_t olen = mbedtls_rsa_get_len(&ctx); if (algo == PIV_ALGO_RSA1024) { memcpy(res_APDU, "\x7C\x81\x00\x82\x81\x00", 6); res_APDU_size = 6; @@ -994,8 +1039,9 @@ static int cmd_set_mgmkey(void) { if (apdu.nc != (uint32_t)pinlen + 3u) { return SW_WRONG_LENGTH(); } - file_t *ef = file_search_by_fid(key_ref, NULL, SPECIFY_ANY); - file_put_data(ef, apdu.data + 3, pinlen); + if (openpgp_key_container_store(key_ref, apdu.data + 3, pinlen, NULL, 0, true) != PICOKEYS_OK) { + return SW_MEMORY_FAILURE(); + } uint8_t *meta = NULL, new_meta[4]; int meta_len = 0; if ((meta_len = meta_find(key_ref, &meta)) <= 0) { @@ -1078,8 +1124,26 @@ static int cmd_move_key(void) { } if (to != 0xFF) { - uint16_t key_len = MIN(file_get_size(efs), OPENPGP_MAX_OBJECT_SIZE); - file_put_data(efd, file_get_data(efs), key_len); + uint8_t key_data[4096 / 8] = { 0 }; + size_t key_len = 0; + int r = PICOKEYS_OK; + if (openpgp_key_container_is_marker(efs)) { + r = openpgp_key_container_read_private(from, FILE_OBJECT_OPERATION_USE, true, key_data, sizeof(key_data), &key_len); + } + else if (file_has_data(efs) && file_get_size(efs) <= sizeof(key_data)) { + key_len = file_get_size(efs); + memcpy(key_data, file_get_data(efs), key_len); + } + else { + r = PICOKEYS_WRONG_DATA; + } + if (r == PICOKEYS_OK) { + r = openpgp_key_container_store(to, key_data, key_len, NULL, 0, true); + } + mbedtls_platform_zeroize(key_data, sizeof(key_data)); + if (r != PICOKEYS_OK) { + return SW_EXEC_ERROR(); + } } file_t *ef_cert_from = file_search_by_fid(cert_from_fid, NULL, SPECIFY_EF); @@ -1117,7 +1181,14 @@ static int cmd_move_key(void) { } } meta_delete(from); - flash_clear_file(efs); + if (openpgp_key_container_is_marker(efs)) { + if (openpgp_key_container_delete(from, true) != PICOKEYS_OK) { + return SW_EXEC_ERROR(); + } + } + else { + flash_clear_file(efs); + } flash_commit(); return SW_OK(); } diff --git a/tests/openpgp_key_container_test.c b/tests/openpgp_key_container_test.c new file mode 100644 index 0000000..b5fca05 --- /dev/null +++ b/tests/openpgp_key_container_test.c @@ -0,0 +1,516 @@ +/* + * This file is part of the Pico OpenPGP distribution (https://github.com/polhenarejos/pico-openpgp). + * 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 "key_container.h" +#include "object_provider.h" +#include "openpgp.h" + +#include +#include +#include + +#define TEST_FILE_COUNT 32u +#define TEST_FILE_CAPACITY 2048u + +typedef struct test_file { + file_t file; + uint8_t storage[TEST_FILE_CAPACITY]; + uint32_t size; + bool allocated; +} test_file_t; + +typedef struct test_file_image { + uint8_t storage[TEST_FILE_CAPACITY]; + uint32_t size; + uint16_t fid; + bool allocated; +} test_file_image_t; + +typedef struct test_auth_context { + uint32_t state[4]; + bool active; +} test_auth_context_t; + +typedef struct test_protector_context { + uint8_t key; +} test_protector_context_t; + +static test_file_t test_files[TEST_FILE_COUNT]; +static test_file_image_t test_durable_files[TEST_FILE_COUNT]; +static test_auth_context_t test_auth_context; +static test_protector_context_t test_protector_context = { .key = 0x5a }; +static jmp_buf test_power_loss_env; +static size_t test_power_loss_event; +static size_t test_power_loss_at = SIZE_MAX; +static bool test_power_loss_armed; + +bool has_pw1; +bool has_pw2; +bool has_pw3; +bool has_rc; +uint8_t session_pw1[32]; +uint8_t session_rc[32]; +uint8_t session_pw3[32]; +uint8_t dek[IV_SIZE + 32]; + +static test_file_t *test_file_from_handle(const file_t *file) { + for (size_t i = 0; i < TEST_FILE_COUNT; i++) { + if (&test_files[i].file == file) { + return &test_files[i]; + } + } + return NULL; +} + +static void test_persist(void) { + for (size_t i = 0; i < TEST_FILE_COUNT; i++) { + memcpy(test_durable_files[i].storage, test_files[i].storage, sizeof(test_durable_files[i].storage)); + test_durable_files[i].size = test_files[i].size; + test_durable_files[i].fid = test_files[i].file.fid; + test_durable_files[i].allocated = test_files[i].allocated; + } +} + +static void test_reboot(void) { + memset(test_files, 0, sizeof(test_files)); + for (size_t i = 0; i < TEST_FILE_COUNT; i++) { + memcpy(test_files[i].storage, test_durable_files[i].storage, sizeof(test_files[i].storage)); + test_files[i].size = test_durable_files[i].size; + test_files[i].file.fid = test_durable_files[i].fid; + test_files[i].allocated = test_durable_files[i].allocated; + test_files[i].file.data = test_files[i].size > 0 ? test_files[i].storage : NULL; + } + test_power_loss_armed = false; +} + +file_t *file_search(uint16_t fid) { + for (size_t i = 0; i < TEST_FILE_COUNT; i++) { + if (test_files[i].allocated && test_files[i].file.fid == fid) { + return &test_files[i].file; + } + } + return NULL; +} + +file_t *file_new(uint16_t fid) { + file_t *existing = file_search(fid); + if (existing) { + return existing; + } + for (size_t i = 0; i < TEST_FILE_COUNT; i++) { + if (!test_files[i].allocated) { + test_files[i].allocated = true; + test_files[i].file.fid = fid; + return &test_files[i].file; + } + } + return NULL; +} + +file_t *file_search_by_fid(const uint16_t fid, const file_t *parent, const uint8_t sp) { + (void)parent; + (void)sp; + return file_search(fid); +} + +bool file_has_data(const file_t *file) { + const test_file_t *test_file = test_file_from_handle(file); + return test_file && test_file->allocated && test_file->file.data && test_file->size > 0; +} + +uint8_t *file_get_data(const file_t *file) { + test_file_t *test_file = test_file_from_handle(file); + return file_has_data(file) ? test_file->storage : NULL; +} + +uint32_t file_get_size(const file_t *file) { + const test_file_t *test_file = test_file_from_handle(file); + return test_file ? test_file->size : 0; +} + +int file_read_at(const file_t *file, uint32_t offset, uint8_t *data, size_t len) { + const test_file_t *test_file = test_file_from_handle(file); + if (!test_file || (!data && len > 0) || offset > test_file->size || len > test_file->size - offset) { + return PICOKEYS_ERR_NULL_PARAM; + } + if (len > 0) { + memcpy(data, test_file->storage + offset, len); + } + return PICOKEYS_OK; +} + +int file_put_data(file_t *file, const uint8_t *data, uint32_t len) { + test_file_t *test_file = test_file_from_handle(file); + if (!test_file || (!data && len > 0) || len > sizeof(test_file->storage)) { + return PICOKEYS_ERR_NO_MEMORY; + } + if (len > 0) { + memcpy(test_file->storage, data, len); + } + else { + memset(test_file->storage, 0, sizeof(test_file->storage)); + } + test_file->size = len; + test_file->file.data = len > 0 ? test_file->storage : NULL; + return PICOKEYS_OK; +} + +int file_delete_no_commit(file_t *file) { + return file_put_data(file, NULL, 0); +} + +static void test_power_loss_point(void) { + test_power_loss_event++; + if (test_power_loss_armed && test_power_loss_event == test_power_loss_at) { + test_power_loss_armed = false; + longjmp(test_power_loss_env, 1); + } + test_persist(); +} + +void flash_commit(void) { + test_power_loss_point(); +} + +bool flash_commit_sync(uint32_t timeout_ms) { + (void)timeout_ms; + test_power_loss_point(); + return true; +} + +static int test_auth_start(void *ctx) { + test_auth_context_t *auth = (test_auth_context_t *)ctx; + auth->state[0] = 0x811c9dc5u; + auth->state[1] = 0x9e3779b9u; + auth->state[2] = 0x85ebca6bu; + auth->state[3] = 0xc2b2ae35u; + auth->active = true; + return PICOKEYS_OK; +} + +static int test_auth_update(void *ctx, const uint8_t *data, size_t len) { + test_auth_context_t *auth = (test_auth_context_t *)ctx; + if (!auth->active || (!data && len > 0)) { + return PICOKEYS_EXEC_ERROR; + } + for (size_t i = 0; i < len; i++) { + for (size_t word = 0; word < 4; word++) { + auth->state[word] ^= data[i] + (uint8_t)word; + auth->state[word] *= 0x01000193u + (uint32_t)(word * 2u); + auth->state[word] = (auth->state[word] << 5) | (auth->state[word] >> 27); + } + } + return PICOKEYS_OK; +} + +static int test_auth_finish(void *ctx, uint8_t tag[FILE_OBJECT_AUTH_TAG_SIZE]) { + test_auth_context_t *auth = (test_auth_context_t *)ctx; + if (!auth->active || !tag) { + return PICOKEYS_EXEC_ERROR; + } + for (size_t i = 0; i < 4; i++) { + put_uint32_be(auth->state[i], tag + i * sizeof(uint32_t)); + } + memset(auth, 0, sizeof(*auth)); + return PICOKEYS_OK; +} + +static void test_auth_abort(void *ctx) { + memset(ctx, 0, sizeof(test_auth_context_t)); +} + +static const file_object_authenticator_t test_auth = { + .ctx = &test_auth_context, + .start = test_auth_start, + .update = test_auth_update, + .finish = test_auth_finish, + .abort = test_auth_abort +}; + +static int test_record_tag(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]) { + int r = test_auth_start(&test_auth_context); + if (r == PICOKEYS_OK) { + r = test_auth_update(&test_auth_context, &test_protector_context.key, sizeof(test_protector_context.key)); + } + if (r == PICOKEYS_OK) { + r = test_auth_update(&test_auth_context, nonce, FILE_OBJECT_RECORD_NONCE_SIZE); + } + if (r == PICOKEYS_OK) { + r = test_auth_update(&test_auth_context, aad, FILE_OBJECT_RECORD_AAD_SIZE); + } + if (r == PICOKEYS_OK) { + r = test_auth_update(&test_auth_context, stored, len); + } + if (r == PICOKEYS_OK) { + r = test_auth_finish(&test_auth_context, tag); + } + return r; +} + +static int test_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]) { + const test_protector_context_t *protector = (const test_protector_context_t *)ctx; + for (size_t i = 0; i < len; i++) { + stored[i] = identity->protection == FILE_OBJECT_PROTECTION_AEAD_SECRET ? plaintext[i] ^ protector->key ^ nonce[i % FILE_OBJECT_RECORD_NONCE_SIZE] : plaintext[i]; + } + return test_record_tag(nonce, aad, stored, len, tag); +} + +static int test_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) { + const test_protector_context_t *protector = (const test_protector_context_t *)ctx; + uint8_t calculated[FILE_OBJECT_AUTH_TAG_SIZE]; + int r = test_record_tag(nonce, aad, stored, len, calculated); + if (r == PICOKEYS_OK && memcmp(calculated, tag, sizeof(calculated)) != 0) { + r = PICOKEYS_WRONG_SIGNATURE; + } + if (r == PICOKEYS_OK) { + for (size_t i = 0; i < len; i++) { + plaintext[i] = identity->protection == FILE_OBJECT_PROTECTION_AEAD_SECRET ? stored[i] ^ protector->key ^ nonce[i % FILE_OBJECT_RECORD_NONCE_SIZE] : stored[i]; + } + } + memset(calculated, 0, sizeof(calculated)); + return r; +} + +static const file_object_record_protector_t test_protector = { + .ctx = &test_protector_context, + .seal = test_record_seal, + .unseal = test_record_unseal +}; + +const file_object_authenticator_t *openpgp_object_manifest_authenticator(void) { + return &test_auth; +} + +const file_object_record_protector_t *openpgp_object_record_protector(void) { + return &test_protector; +} + +const file_object_authenticator_t *openpgp_piv_object_manifest_authenticator(void) { + return &test_auth; +} + +const file_object_record_protector_t *openpgp_piv_object_record_protector(void) { + return &test_protector; +} + +bool piv_key_operation_authorized(uint16_t operation, bool internal_firmware) { + (void)operation; + return internal_firmware; +} + +static void test_reset(void) { + memset(test_files, 0, sizeof(test_files)); + memset(test_durable_files, 0, sizeof(test_durable_files)); + memset(&test_auth_context, 0, sizeof(test_auth_context)); + test_power_loss_event = 0; + test_power_loss_at = SIZE_MAX; + test_power_loss_armed = false; + has_pw1 = false; + has_pw2 = false; + has_pw3 = true; + file_new(EF_PK_SIG); + file_new(EF_PK_DEC); + file_new(EF_PK_AUT); + file_new(EF_AES_KEY); + file_new(EF_PIV_KEY_AUTHENTICATION); + test_persist(); +} + +static void test_read_pair(uint16_t fid, const uint8_t *private_data, size_t private_size, const uint8_t *public_data, size_t public_size) { + uint8_t output[128] = { 0 }; + size_t written = 0; + uint16_t operation = fid == EF_PK_SIG ? FILE_OBJECT_OPERATION_SIGN : FILE_OBJECT_OPERATION_USE; + + assert(private_size <= sizeof(output)); + assert(openpgp_key_container_read_private(fid, operation, false, output, sizeof(output), &written) == PICOKEYS_OK); + assert(written == private_size); + assert(memcmp(output, private_data, private_size) == 0); + + memset(output, 0, sizeof(output)); + written = 0; + assert(public_size <= sizeof(output)); + assert(openpgp_key_container_read_public(fid, output, sizeof(output), &written) == PICOKEYS_OK); + assert(written == public_size); + assert(memcmp(output, public_data, public_size) == 0); +} + +static void test_lifecycle_and_authorization(void) { + static const uint8_t private_first[] = { 1, 2, 3, 4 }; + static const uint8_t public_first[] = { 0x7f, 0x49, 1, 2, 3 }; + static const uint8_t private_second[] = { 5, 6, 7 }; + static const uint8_t public_second[] = { 0x7f, 0x49, 4, 5 }; + uint8_t output[16]; + size_t written = 0; + + test_reset(); + has_pw3 = false; + assert(openpgp_key_container_store(EF_PK_SIG, private_first, sizeof(private_first), public_first, sizeof(public_first), false) == PICOKEYS_NO_LOGIN); + has_pw3 = true; + assert(openpgp_key_container_store(EF_PK_SIG, private_first, sizeof(private_first), public_first, sizeof(public_first), false) == PICOKEYS_OK); + assert(openpgp_key_container_is_marker(file_search(EF_PK_SIG))); + test_read_pair(EF_PK_SIG, private_first, sizeof(private_first), public_first, sizeof(public_first)); + + has_pw3 = false; + assert(openpgp_key_container_read_private(EF_PK_SIG, FILE_OBJECT_OPERATION_SIGN, false, output, sizeof(output), &written) == PICOKEYS_NO_LOGIN); + assert(openpgp_key_container_read_public(EF_PK_SIG, output, sizeof(output), &written) == PICOKEYS_OK); + has_pw1 = true; + assert(openpgp_key_container_read_private(EF_PK_SIG, FILE_OBJECT_OPERATION_SIGN, false, output, sizeof(output), &written) == PICOKEYS_OK); + has_pw1 = false; + has_pw3 = true; + + assert(openpgp_key_container_store(EF_PK_SIG, private_second, sizeof(private_second), public_second, sizeof(public_second), false) == PICOKEYS_OK); + test_read_pair(EF_PK_SIG, private_second, sizeof(private_second), public_second, sizeof(public_second)); + test_reboot(); + test_read_pair(EF_PK_SIG, private_second, sizeof(private_second), public_second, sizeof(public_second)); + + assert(openpgp_key_container_delete(EF_PK_SIG, false) == PICOKEYS_OK); + assert(!file_has_data(file_search(EF_PK_SIG))); + assert(openpgp_key_container_read_private(EF_PK_SIG, FILE_OBJECT_OPERATION_SIGN, false, output, sizeof(output), &written) == PICOKEYS_ERR_FILE_NOT_FOUND); +} + +static void test_corruption_falls_back_to_previous_generation(void) { + static const uint8_t private_first[] = { 0x11, 0x12, 0x13 }; + static const uint8_t public_first[] = { 0x21, 0x22, 0x23 }; + static const uint8_t private_second[] = { 0x31, 0x32, 0x33 }; + static const uint8_t public_second[] = { 0x41, 0x42, 0x43 }; + + test_reset(); + assert(openpgp_key_container_store(EF_PK_DEC, private_first, sizeof(private_first), public_first, sizeof(public_first), false) == PICOKEYS_OK); + assert(openpgp_key_container_store(EF_PK_DEC, private_second, sizeof(private_second), public_second, sizeof(public_second), false) == PICOKEYS_OK); + + file_t *current_private = file_search(0xd4d2u); + assert(file_has_data(current_private)); + file_get_data(current_private)[FILE_OBJECT_RECORD_HEADER_SIZE] ^= 0x80; + test_persist(); + test_reboot(); + + uint8_t private_output[sizeof(private_first)] = { 0 }; + uint8_t public_output[sizeof(public_second)] = { 0 }; + size_t private_written = 0; + size_t public_written = 0; + assert(openpgp_key_container_read_private(EF_PK_DEC, FILE_OBJECT_OPERATION_USE, false, private_output, sizeof(private_output), &private_written) == PICOKEYS_OK); + assert(openpgp_key_container_read_public(EF_PK_DEC, public_output, sizeof(public_output), &public_written) == PICOKEYS_OK); + assert(private_written == sizeof(private_first)); + assert(public_written == sizeof(public_second)); + assert(memcmp(private_output, private_first, sizeof(private_first)) == 0); + assert(memcmp(public_output, public_second, sizeof(public_second)) == 0); +} + +static void test_collision_does_not_replace_legacy_key(void) { + static const uint8_t legacy[] = { 0xaa, 0xbb, 0xcc }; + static const uint8_t collision[] = { 0xde, 0xad }; + static const uint8_t private_data[] = { 1, 2 }; + + test_reset(); + assert(file_put_data(file_search(EF_PK_SIG), legacy, sizeof(legacy)) == PICOKEYS_OK); + assert(file_put_data(file_new(0xd0d1u), collision, sizeof(collision)) == PICOKEYS_OK); + test_persist(); + + assert(!openpgp_key_container_can_create(EF_PK_SIG)); + assert(openpgp_key_container_store(EF_PK_SIG, private_data, sizeof(private_data), NULL, 0, false) != PICOKEYS_OK); + assert(file_get_size(file_search(EF_PK_SIG)) == sizeof(legacy)); + assert(memcmp(file_get_data(file_search(EF_PK_SIG)), legacy, sizeof(legacy)) == 0); + assert(!openpgp_key_container_physical_fid(0xd0d1u)); +} + +static void test_piv_internal_boundary(void) { + static const uint8_t private_data[] = { 0x51, 0x52, 0x53, 0x54 }; + uint8_t output[sizeof(private_data)] = { 0 }; + size_t written = 0; + + test_reset(); + has_pw3 = false; + assert(openpgp_key_container_store(EF_PIV_KEY_AUTHENTICATION, private_data, sizeof(private_data), NULL, 0, false) == PICOKEYS_NO_LOGIN); + assert(openpgp_key_container_store(EF_PIV_KEY_AUTHENTICATION, private_data, sizeof(private_data), NULL, 0, true) == PICOKEYS_OK); + assert(openpgp_key_container_is_marker(file_search(EF_PIV_KEY_AUTHENTICATION))); + assert(openpgp_key_container_read_private(EF_PIV_KEY_AUTHENTICATION, FILE_OBJECT_OPERATION_USE, false, output, sizeof(output), &written) == PICOKEYS_NO_LOGIN); + assert(openpgp_key_container_read_private(EF_PIV_KEY_AUTHENTICATION, FILE_OBJECT_OPERATION_USE, true, output, sizeof(output), &written) == PICOKEYS_OK); + assert(written == sizeof(private_data)); + assert(memcmp(output, private_data, sizeof(private_data)) == 0); + assert(openpgp_key_container_delete(EF_PIV_KEY_AUTHENTICATION, false) == PICOKEYS_NO_LOGIN); + assert(openpgp_key_container_delete(EF_PIV_KEY_AUTHENTICATION, true) == PICOKEYS_OK); +} + +static void test_power_loss_create(void) { + static const uint8_t legacy[] = { 0x91, 0x92, 0x93 }; + static const uint8_t private_data[] = { 1, 3, 5, 7 }; + static const uint8_t public_data[] = { 2, 4, 6, 8 }; + + for (size_t event = 1; event <= 3; event++) { + test_reset(); + assert(file_put_data(file_search(EF_PK_AUT), legacy, sizeof(legacy)) == PICOKEYS_OK); + test_persist(); + test_power_loss_event = 0; + test_power_loss_at = event; + test_power_loss_armed = true; + if (setjmp(test_power_loss_env) == 0) { + (void)openpgp_key_container_store(EF_PK_AUT, private_data, sizeof(private_data), public_data, sizeof(public_data), false); + } + test_reboot(); + + if (openpgp_key_container_is_marker(file_search(EF_PK_AUT))) { + test_read_pair(EF_PK_AUT, private_data, sizeof(private_data), public_data, sizeof(public_data)); + } + else { + assert(file_get_size(file_search(EF_PK_AUT)) == sizeof(legacy)); + assert(memcmp(file_get_data(file_search(EF_PK_AUT)), legacy, sizeof(legacy)) == 0); + assert(openpgp_key_container_store(EF_PK_AUT, private_data, sizeof(private_data), public_data, sizeof(public_data), false) == PICOKEYS_OK); + test_read_pair(EF_PK_AUT, private_data, sizeof(private_data), public_data, sizeof(public_data)); + } + } +} + +static void test_power_loss_update(void) { + static const uint8_t private_first[] = { 1, 1, 1 }; + static const uint8_t public_first[] = { 2, 2, 2 }; + static const uint8_t private_second[] = { 3, 3, 3 }; + static const uint8_t public_second[] = { 4, 4, 4 }; + + for (size_t event = 1; event <= 2; event++) { + test_reset(); + assert(openpgp_key_container_store(EF_PK_SIG, private_first, sizeof(private_first), public_first, sizeof(public_first), false) == PICOKEYS_OK); + test_power_loss_event = 0; + test_power_loss_at = event; + test_power_loss_armed = true; + if (setjmp(test_power_loss_env) == 0) { + (void)openpgp_key_container_store(EF_PK_SIG, private_second, sizeof(private_second), public_second, sizeof(public_second), false); + } + test_reboot(); + + uint8_t private_output[sizeof(private_first)] = { 0 }; + uint8_t public_output[sizeof(public_first)] = { 0 }; + size_t private_written = 0; + size_t public_written = 0; + assert(openpgp_key_container_read_private(EF_PK_SIG, FILE_OBJECT_OPERATION_SIGN, false, private_output, sizeof(private_output), &private_written) == PICOKEYS_OK); + assert(openpgp_key_container_read_public(EF_PK_SIG, public_output, sizeof(public_output), &public_written) == PICOKEYS_OK); + bool old_pair = memcmp(private_output, private_first, sizeof(private_first)) == 0 && memcmp(public_output, public_first, sizeof(public_first)) == 0; + bool new_pair = memcmp(private_output, private_second, sizeof(private_second)) == 0 && memcmp(public_output, public_second, sizeof(public_second)) == 0; + assert(old_pair || new_pair); + } +} + +int main(void) { + test_lifecycle_and_authorization(); + test_corruption_falls_back_to_previous_generation(); + test_collision_does_not_replace_legacy_key(); + test_piv_internal_boundary(); + test_power_loss_create(); + test_power_loss_update(); + puts("openpgp_key_container_test: ok"); + return 0; +}