mirror of
https://github.com/polhenarejos/pico-openpgp.git
synced 2026-08-29 16:11:08 +01:00
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:
@@ -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
Submodule pico-keys-sdk updated: 50481a7671...749534369a
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user