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 <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2026-07-27 00:50:26 +02:00
parent 695cb90a78
commit 96a4acf84e
16 changed files with 1334 additions and 83 deletions
+20
View File
@@ -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()
+1 -1
View File
@@ -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;
+2 -10
View File
@@ -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();
}
+16 -10
View File
@@ -18,6 +18,7 @@
#include <stdio.h>
#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();
+1 -1
View File
@@ -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) {
+3 -3
View File
@@ -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;
+4 -4
View File
@@ -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];
+404
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#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);
}
+37
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#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_
+121
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#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);
}
+30
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#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_
+78 -27
View File
@@ -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;
+6 -3
View File
@@ -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
+94 -23
View File
@@ -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();
}
+516
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#include "picokeys.h"
#include "key_container.h"
#include "object_provider.h"
#include "openpgp.h"
#include <assert.h>
#include <setjmp.h>
#include <stdio.h>
#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;
}