mirror of https://github.com/wolfSSL/wolfBoot.git
Merge pull request #842 from danielinux/fenrir-fixes-2026-08-04
Fenrir fixes 2026 08 04pull/844/head
commit
817289ed2c
|
|
@ -203,6 +203,7 @@ tools/unit-tests/unit-mpusize
|
|||
tools/unit-tests/unit-otp-keystore
|
||||
tools/unit-tests/unit-otp-keystore-gen-zeroize
|
||||
tools/unit-tests/unit-tpm-api-names
|
||||
tools/unit-tests/unit-tpm-nsc-cert
|
||||
tools/unit-tests/unit-elf-bss-guard
|
||||
tools/unit-tests/unit-fit-fpga
|
||||
tools/unit-tests/unit-flash-erase-c0
|
||||
|
|
|
|||
|
|
@ -395,7 +395,8 @@ static psa_status_t wolfboot_crypto_dispatch(const psa_invec *in_vec,
|
|||
return psa_generate_random((uint8_t *)out_vec[0].base, out_vec[0].len);
|
||||
|
||||
case ARM_TEE_CRYPTO_OPEN_KEY_SID:
|
||||
if (out_vec == NULL || out_len < 1) {
|
||||
if (out_vec == NULL || out_len < 1 || out_vec[0].base == NULL ||
|
||||
out_vec[0].len < sizeof(psa_key_id_t)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
return wolfboot_psa_open_key(iov->key_id,
|
||||
|
|
@ -405,7 +406,9 @@ static psa_status_t wolfboot_crypto_dispatch(const psa_invec *in_vec,
|
|||
return wolfboot_psa_close_key(iov->key_id);
|
||||
|
||||
case ARM_TEE_CRYPTO_IMPORT_KEY_SID:
|
||||
if (in_len < 3 || out_vec == NULL || out_len < 1) {
|
||||
if (in_len < 3 || out_vec == NULL || out_len < 1 ||
|
||||
out_vec[0].base == NULL ||
|
||||
out_vec[0].len < sizeof(psa_key_id_t)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (in_vec[1].base == NULL ||
|
||||
|
|
@ -425,7 +428,9 @@ static psa_status_t wolfboot_crypto_dispatch(const psa_invec *in_vec,
|
|||
}
|
||||
|
||||
case ARM_TEE_CRYPTO_GENERATE_KEY_SID:
|
||||
if (in_len < 2 || out_vec == NULL || out_len < 1) {
|
||||
if (in_len < 2 || out_vec == NULL || out_len < 1 ||
|
||||
out_vec[0].base == NULL ||
|
||||
out_vec[0].len < sizeof(psa_key_id_t)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (in_vec[1].base == NULL ||
|
||||
|
|
@ -478,7 +483,8 @@ static psa_status_t wolfboot_crypto_dispatch(const psa_invec *in_vec,
|
|||
}
|
||||
|
||||
case ARM_TEE_CRYPTO_GET_KEY_ATTRIBUTES_SID:
|
||||
if (out_vec == NULL || out_len < 1) {
|
||||
if (out_vec == NULL || out_len < 1 || out_vec[0].base == NULL ||
|
||||
out_vec[0].len < sizeof(psa_key_attributes_t)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
return psa_get_key_attributes(iov->key_id,
|
||||
|
|
@ -1006,11 +1012,13 @@ static int32_t arm_tee_psa_ps_dispatch(int32_t type, const psa_invec *in_vec,
|
|||
return PSA_SUCCESS;
|
||||
}
|
||||
if (type == ARM_TEE_PS_GET_SUPPORT) {
|
||||
if (out_vec != NULL && out_len >= 1 && out_vec[0].base != NULL) {
|
||||
uint32_t support = 0;
|
||||
XMEMCPY(out_vec[0].base, &support, sizeof(support));
|
||||
out_vec[0].len = sizeof(support);
|
||||
uint32_t support = 0;
|
||||
if (out_vec == NULL || out_len < 1 || out_vec[0].base == NULL ||
|
||||
out_vec[0].len < sizeof(support)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
XMEMCPY(out_vec[0].base, &support, sizeof(support));
|
||||
out_vec[0].len = sizeof(support);
|
||||
return PSA_SUCCESS;
|
||||
}
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
|
@ -1067,13 +1075,19 @@ int32_t arm_tee_psa_call(psa_handle_t handle, int32_t type,
|
|||
out_vec_s[i] = out_vec[i];
|
||||
}
|
||||
|
||||
/* Every non-NULL .base must pass the non-secure attribution check, even
|
||||
* when the declared .len is zero: a descriptor is not guaranteed to be
|
||||
* accessed only within .len, so a zero-length descriptor would otherwise
|
||||
* smuggle a Secure pointer past validation. At least one byte is always
|
||||
* checked. */
|
||||
for (i = 0; i < in_len; i++) {
|
||||
if (in_vec_s[i].len > 0 && in_vec_s[i].base == NULL) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (in_vec_s[i].len > 0 &&
|
||||
if (in_vec_s[i].base != NULL &&
|
||||
cmse_check_address_range((void *)in_vec_s[i].base,
|
||||
in_vec_s[i].len,
|
||||
in_vec_s[i].len > 0 ?
|
||||
in_vec_s[i].len : 1,
|
||||
CMSE_NONSECURE) == NULL) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
|
@ -1082,9 +1096,10 @@ int32_t arm_tee_psa_call(psa_handle_t handle, int32_t type,
|
|||
if (out_vec_s[i].len > 0 && out_vec_s[i].base == NULL) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (out_vec_s[i].len > 0 &&
|
||||
if (out_vec_s[i].base != NULL &&
|
||||
cmse_check_address_range(out_vec_s[i].base,
|
||||
out_vec_s[i].len,
|
||||
out_vec_s[i].len > 0 ?
|
||||
out_vec_s[i].len : 1,
|
||||
CMSE_NONSECURE) == NULL) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <arm_cmse.h>
|
||||
#include <stddef.h> /* offsetof */
|
||||
#include <wolfssl/wolfcrypt/types.h> /* XMALLOC/XFREE/XMEMCPY, DYNAMIC_TYPE_* */
|
||||
#include <wolfssl/wolfcrypt/memory.h> /* wc_ForceZero */
|
||||
|
||||
/*
|
||||
* TrustZone-M PKCS#11 non-secure-callable (NSC) layer with pointer
|
||||
|
|
@ -130,6 +131,7 @@ static int ns_outlen_begin(const volatile void *pBuf, CK_ULONG_PTR pulLen,
|
|||
struct nsc_mech {
|
||||
CK_MECHANISM mech; /* secure mechanism passed to wolfPKCS11 */
|
||||
void *alloc[NSC_MECH_MAX_ALLOC];
|
||||
CK_ULONG allocLen[NSC_MECH_MAX_ALLOC];
|
||||
int nAlloc;
|
||||
struct {
|
||||
void *dst; /* NS destination */
|
||||
|
|
@ -148,8 +150,10 @@ static void *nsc_alloc(struct nsc_mech *m, CK_ULONG len)
|
|||
if (m->nAlloc >= NSC_MECH_MAX_ALLOC)
|
||||
return NULL;
|
||||
p = XMALLOC((size_t)len, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
if (p != NULL)
|
||||
if (p != NULL) {
|
||||
m->allocLen[m->nAlloc] = len;
|
||||
m->alloc[m->nAlloc++] = p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
@ -205,13 +209,17 @@ static CK_RV nsc_inout(struct nsc_mech *m, CK_VOID_PTR dst, CK_ULONG len,
|
|||
return CKR_OK;
|
||||
}
|
||||
|
||||
/* Free all secure allocations without copying anything back (error path). */
|
||||
/* Free all secure allocations without copying anything back (error path).
|
||||
* Parameter blobs can carry secrets (CKM_PKCS5_PBKD2 pPassword, HKDF salt,
|
||||
* ...), so scrub every block before it goes back to the secure heap. */
|
||||
static void nsc_mech_free(struct nsc_mech *m)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < m->nAlloc; i++)
|
||||
for (i = 0; i < m->nAlloc; i++) {
|
||||
wc_ForceZero(m->alloc[i], (size_t)m->allocLen[i]);
|
||||
XFREE(m->alloc[i], NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
m->nAlloc = 0;
|
||||
m->nCback = 0;
|
||||
}
|
||||
|
|
@ -551,8 +559,16 @@ static void nsc_tmpl_free(struct nsc_tmpl *t)
|
|||
|
||||
if (t->work != NULL) {
|
||||
for (i = 0; i < t->count; i++) {
|
||||
if (t->work[i].pValue != NULL)
|
||||
if (t->work[i].pValue != NULL) {
|
||||
/* Value buffers hold imported key material (CKA_VALUE, the RSA
|
||||
* private components, ...). Scrub before releasing, using the
|
||||
* snapshot length: that is what was allocated, and wolfPKCS11
|
||||
* rewrites work[].ulValueLen on the C_GetAttributeValue path. */
|
||||
if (t->snap != NULL)
|
||||
wc_ForceZero(t->work[i].pValue,
|
||||
(size_t)t->snap[i].ulValueLen);
|
||||
XFREE(t->work[i].pValue, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
}
|
||||
XFREE(t->work, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
t->work = NULL;
|
||||
|
|
|
|||
17
src/tpm.c
17
src/tpm.c
|
|
@ -1334,14 +1334,27 @@ int CSME_NSE_API wolfBoot_tpm2_read_pcr(uint8_t pcrIndex, uint8_t* digest, int*
|
|||
|
||||
int CSME_NSE_API wolfBoot_tpm2_read_cert(uint32_t handle, uint8_t* cert, uint32_t* certSz)
|
||||
{
|
||||
uint32_t certCapacity;
|
||||
int rc;
|
||||
|
||||
if (WOLFBOOT_TPM_NS_RW(certSz, sizeof(*certSz)) == NULL) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
if (WOLFBOOT_TPM_NS_RW(cert, *certSz) == NULL) {
|
||||
/* single-fetch *certSz so it cannot be re-read after validation: wolfTPM
|
||||
* checks the capacity again before filling 'cert', and a racing non-secure
|
||||
* agent would otherwise enlarge it in between to reopen the write past the
|
||||
* range validated here */
|
||||
certCapacity = *(volatile const uint32_t*)certSz;
|
||||
if (certCapacity == 0) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
if (WOLFBOOT_TPM_NS_RW(cert, certCapacity) == NULL) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
wolfTPM2_SetAuthPassword(&wolftpm_dev, 0, NULL);
|
||||
return wolfTPM2_NVReadCert(&wolftpm_dev, handle, cert, certSz);
|
||||
rc = wolfTPM2_NVReadCert(&wolftpm_dev, handle, cert, &certCapacity);
|
||||
*certSz = certCapacity;
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef WOLFTPM_MFG_IDENTITY
|
||||
|
|
|
|||
|
|
@ -639,6 +639,8 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
|
|||
|
||||
/* open and load key buffer */
|
||||
*key_buffer = NULL;
|
||||
*pubkey = NULL;
|
||||
*pubkey_sz = 0;
|
||||
if (secondary) {
|
||||
key_file = CMD.secondary_key_file;
|
||||
sign = CMD.secondary_sign;
|
||||
|
|
@ -722,8 +724,10 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
|
|||
wc_ed25519_free(&key.ed);
|
||||
}
|
||||
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
free(*pubkey);
|
||||
*pubkey = NULL;
|
||||
}
|
||||
|
||||
/* break if we succeed or are not using auto */
|
||||
if (ret == 0 || sign != SIGN_AUTO) {
|
||||
|
|
@ -789,8 +793,10 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
|
|||
wc_ed448_free(&key.ed4);
|
||||
}
|
||||
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
free(*pubkey);
|
||||
*pubkey = NULL;
|
||||
}
|
||||
|
||||
/* break if we succeed or are not using auto */
|
||||
if (ret == 0 || sign != SIGN_AUTO) {
|
||||
|
|
@ -1051,6 +1057,11 @@ failure:
|
|||
zero_and_free(*key_buffer, *key_buffer_sz);
|
||||
*key_buffer = NULL;
|
||||
}
|
||||
if (*pubkey != NULL) {
|
||||
free(*pubkey);
|
||||
*pubkey = NULL;
|
||||
}
|
||||
*pubkey_sz = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -3728,12 +3739,15 @@ int main(int argc, char** argv)
|
|||
if (CMD.hybrid) {
|
||||
uint8_t *kbuf2 = NULL;
|
||||
uint8_t *pubkey2 = NULL;
|
||||
uint32_t pubkey_sz2;
|
||||
uint32_t pubkey_sz2 = 0;
|
||||
DEBUG_PRINT("Loading secondary key\n");
|
||||
kbuf2 = load_key(&key_buffer2, &key_buffer_sz2, &pubkey2, &pubkey_sz2, 1);
|
||||
if (!kbuf2) {
|
||||
exit(1);
|
||||
}
|
||||
printf("Creating hybrid signature\n");
|
||||
make_hybrid_header(pubkey, pubkey_sz, CMD.image_file, CMD.output_image_file,
|
||||
pubkey2, pubkey_sz2);
|
||||
ret = make_hybrid_header(pubkey, pubkey_sz, CMD.image_file,
|
||||
CMD.output_image_file, pubkey2, pubkey_sz2);
|
||||
DEBUG_PRINT("Signature size: %u\n", CMD.signature_sz);
|
||||
DEBUG_PRINT("Secondary signature size: %u\n", CMD.secondary_signature_sz);
|
||||
DEBUG_PRINT("Header size: %u\n", CMD.header_sz);
|
||||
|
|
@ -3742,11 +3756,13 @@ int main(int argc, char** argv)
|
|||
if (pubkey2)
|
||||
free(pubkey2);
|
||||
} else {
|
||||
make_header(pubkey, pubkey_sz, CMD.image_file, CMD.output_image_file);
|
||||
ret = make_header(pubkey, pubkey_sz, CMD.image_file,
|
||||
CMD.output_image_file);
|
||||
}
|
||||
|
||||
|
||||
if (CMD.delta) {
|
||||
/* Skip the delta step and propagate the failure to the caller if the
|
||||
* signed image could not be created. */
|
||||
if ((ret == 0) && CMD.delta) {
|
||||
if (CMD.encrypt)
|
||||
ret = base_diff(CMD.delta_base_file, pubkey, pubkey_sz, 64);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -61,9 +61,13 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128
|
|||
unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \
|
||||
unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \
|
||||
unit-sdhci-disk-unaligned unit-sign-encrypted-output \
|
||||
unit-sign-hybrid-keyload \
|
||||
unit-sign-header-failure \
|
||||
unit-keygen-xmss-params
|
||||
TESTS+=unit-tpm-check-rot-auth
|
||||
TESTS+=unit-tpm-api-names
|
||||
TESTS+=unit-tpm-nsc-cert
|
||||
TESTS+=unit-pkcs11-nsc-zeroize
|
||||
TESTS+=unit-diagnostics
|
||||
TESTS+=unit-diagnostics-256
|
||||
TESTS+=unit-fit-gzip unit-fit-nogzip
|
||||
|
|
@ -280,6 +284,23 @@ unit-tpm-api-names: ../../include/target.h unit-tpm-api-names.c ../../src/string
|
|||
-DWOLFBOOT_HASH_SHA256 \
|
||||
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-tpm-nsc-cert: ../../include/target.h unit-tpm-nsc-cert.c ../../src/string.c
|
||||
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \
|
||||
-DWOLFTPM_USER_SETTINGS -DWOLFBOOT_SIGN_RSA2048 \
|
||||
-DWOLFBOOT_HASH_SHA256 -D__ARM_FEATURE_CMSE=3U -DCSME_NSE_API= \
|
||||
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
# The PKCS#11 NSC veneers are exercised here through C_CreateObject_nsc_call
|
||||
# and C_DeriveKey_nsc_call only; --gc-sections drops the remaining veneers so
|
||||
# just those two wolfPKCS11 entry points need a stub.
|
||||
unit-pkcs11-nsc-zeroize: ../../include/target.h unit-pkcs11-nsc-zeroize.c
|
||||
gcc -o $@ unit-pkcs11-nsc-zeroize.c \
|
||||
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c \
|
||||
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/misc.c \
|
||||
$(CFLAGS) -I$(WOLFBOOT_LIB_WOLFPKCS11) -DSECURE_PKCS11 \
|
||||
-DWOLFPKCS11_USER_SETTINGS -DWOLFCRYPT_SECURE_MODE \
|
||||
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-fwtpm-stub: ../../include/target.h unit-fwtpm-stub.c
|
||||
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) \
|
||||
-DWOLFTPM_USER_SETTINGS -ffunction-sections -fdata-sections \
|
||||
|
|
@ -316,6 +337,22 @@ unit-sign-encrypted-output: ../../include/target.h unit-sign-encrypted-output.c
|
|||
-ffunction-sections -fdata-sections \
|
||||
$(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-sign-hybrid-keyload: ../../include/target.h unit-sign-hybrid-keyload.c \
|
||||
$(KEYTOOLS_SIGN_SRCS)
|
||||
gcc -o $@ $^ -I../keytools $(CFLAGS) -DML_DSA_LEVEL=2 -DDELTA_UPDATES \
|
||||
-D"LMS_LEVELS=1" -D"LMS_HEIGHT=10" -D"LMS_WINTERNITZ=8" \
|
||||
-DWOLFBOOT_XMSS_PARAMS=\"XMSS-SHA2_10_256\" \
|
||||
-ffunction-sections -fdata-sections \
|
||||
$(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-sign-header-failure: ../../include/target.h unit-sign-header-failure.c \
|
||||
$(KEYTOOLS_SIGN_SRCS)
|
||||
gcc -o $@ $^ -I../keytools $(CFLAGS) -DML_DSA_LEVEL=2 -DDELTA_UPDATES \
|
||||
-D"LMS_LEVELS=1" -D"LMS_HEIGHT=10" -D"LMS_WINTERNITZ=8" \
|
||||
-DWOLFBOOT_XMSS_PARAMS=\"XMSS-SHA2_10_256\" \
|
||||
-ffunction-sections -fdata-sections \
|
||||
$(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-keygen-xmss-params: ../../include/target.h unit-keygen-xmss-params.c
|
||||
gcc -o $@ $^ -I../keytools $(CFLAGS) -DML_DSA_LEVEL=2 \
|
||||
-D"LMS_LEVELS=1" -D"LMS_HEIGHT=10" -D"LMS_WINTERNITZ=8" \
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
#ifndef UNIT_TEST_ARM_CMSE_H
|
||||
#define UNIT_TEST_ARM_CMSE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define CMSE_NONSECURE 0
|
||||
#define cmse_check_address_range(ptr, size, flags) \
|
||||
((void *)(uintptr_t)(ptr))
|
||||
|
||||
/* Provided by the unit test, so it can model a Secure region that must never
|
||||
* pass a non-secure attribution check. */
|
||||
void *cmse_check_address_range(void *ptr, size_t size, int flags);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -17,8 +17,99 @@ void wc_ForceZero(void *mem, size_t len)
|
|||
ForceZero(mem, len);
|
||||
}
|
||||
|
||||
/* Simulated Secure SRAM: any pointer landing in here is rejected by the CMSE
|
||||
* stub below, exactly like a real Secure address fails CMSE_NONSECURE. */
|
||||
static uint8_t secure_mem[64];
|
||||
|
||||
void *cmse_check_address_range(void *ptr, size_t size, int flags)
|
||||
{
|
||||
uint8_t *start = (uint8_t *)ptr;
|
||||
uint8_t *end;
|
||||
|
||||
(void)flags;
|
||||
if (size == 0) {
|
||||
size = 1;
|
||||
}
|
||||
end = start + size;
|
||||
if (end > secure_mem && start < secure_mem + sizeof(secure_mem)) {
|
||||
return NULL;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
#include "../../src/arm_tee_psa_ipc.c"
|
||||
|
||||
/* Backend stubs: the tests below only exercise the IPC argument validation,
|
||||
* never the crypto/attestation back ends. */
|
||||
psa_status_t psa_crypto_init(void) { return PSA_SUCCESS; }
|
||||
psa_status_t psa_generate_random(uint8_t *o, size_t s)
|
||||
{ (void)o; (void)s; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_get_key_attributes(psa_key_id_t k, psa_key_attributes_t *a)
|
||||
{ (void)k; (void)a; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
void psa_reset_key_attributes(psa_key_attributes_t *a) { (void)a; }
|
||||
psa_status_t psa_destroy_key(psa_key_id_t k)
|
||||
{ (void)k; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_import_key(const psa_key_attributes_t *a, const uint8_t *d,
|
||||
size_t dl, psa_key_id_t *k)
|
||||
{ (void)a; (void)d; (void)dl; (void)k; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_generate_key(const psa_key_attributes_t *a, psa_key_id_t *k)
|
||||
{ (void)a; (void)k; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_export_key(psa_key_id_t k, uint8_t *d, size_t ds, size_t *dl)
|
||||
{ (void)k; (void)d; (void)ds; (void)dl; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_export_public_key(psa_key_id_t k, uint8_t *d, size_t ds,
|
||||
size_t *dl)
|
||||
{ (void)k; (void)d; (void)ds; (void)dl; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_hash_compute(psa_algorithm_t alg, const uint8_t *i, size_t il,
|
||||
uint8_t *h, size_t hs, size_t *hl)
|
||||
{ (void)alg; (void)i; (void)il; (void)h; (void)hs; (void)hl;
|
||||
return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_hash_setup(psa_hash_operation_t *op, psa_algorithm_t alg)
|
||||
{ (void)op; (void)alg; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_hash_update(psa_hash_operation_t *op, const uint8_t *i,
|
||||
size_t il)
|
||||
{ (void)op; (void)i; (void)il; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_hash_finish(psa_hash_operation_t *op, uint8_t *h, size_t hs,
|
||||
size_t *hl)
|
||||
{ (void)op; (void)h; (void)hs; (void)hl; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_hash_clone(const psa_hash_operation_t *s,
|
||||
psa_hash_operation_t *t)
|
||||
{ (void)s; (void)t; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_hash_abort(psa_hash_operation_t *op)
|
||||
{ (void)op; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_cipher_encrypt_setup(psa_cipher_operation_t *op,
|
||||
psa_key_id_t k, psa_algorithm_t alg)
|
||||
{ (void)op; (void)k; (void)alg; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_cipher_decrypt_setup(psa_cipher_operation_t *op,
|
||||
psa_key_id_t k, psa_algorithm_t alg)
|
||||
{ (void)op; (void)k; (void)alg; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_cipher_set_iv(psa_cipher_operation_t *op, const uint8_t *iv,
|
||||
size_t ivl)
|
||||
{ (void)op; (void)iv; (void)ivl; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_cipher_update(psa_cipher_operation_t *op, const uint8_t *i,
|
||||
size_t il, uint8_t *o, size_t os, size_t *ol)
|
||||
{ (void)op; (void)i; (void)il; (void)o; (void)os; (void)ol;
|
||||
return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_cipher_finish(psa_cipher_operation_t *op, uint8_t *o,
|
||||
size_t os, size_t *ol)
|
||||
{ (void)op; (void)o; (void)os; (void)ol; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_cipher_abort(psa_cipher_operation_t *op)
|
||||
{ (void)op; return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_sign_hash(psa_key_id_t k, psa_algorithm_t alg,
|
||||
const uint8_t *h, size_t hl, uint8_t *s, size_t ss, size_t *sl)
|
||||
{ (void)k; (void)alg; (void)h; (void)hl; (void)s; (void)ss; (void)sl;
|
||||
return PSA_ERROR_NOT_SUPPORTED; }
|
||||
psa_status_t psa_verify_hash(psa_key_id_t k, psa_algorithm_t alg,
|
||||
const uint8_t *h, size_t hl, const uint8_t *s, size_t sl)
|
||||
{ (void)k; (void)alg; (void)h; (void)hl; (void)s; (void)sl;
|
||||
return PSA_ERROR_NOT_SUPPORTED; }
|
||||
int wolfBoot_dice_get_token(const uint8_t *c, size_t cs, uint8_t *t, size_t ts,
|
||||
size_t *tl)
|
||||
{ (void)c; (void)cs; (void)t; (void)ts; (void)tl; return -1; }
|
||||
int wolfBoot_dice_get_token_size(size_t cs, size_t *ts)
|
||||
{ (void)cs; (void)ts; return -1; }
|
||||
int wolfBoot_dice_get_attest_pubkey(uint8_t *b, size_t *l)
|
||||
{ (void)b; (void)l; return -1; }
|
||||
|
||||
static void reset_ps_state(void)
|
||||
{
|
||||
memset(g_ps_entries, 0, sizeof(g_ps_entries));
|
||||
|
|
@ -164,6 +255,68 @@ START_TEST(test_ps_set_get_info_remove_success_path)
|
|||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_psa_call_rejects_secure_zero_len_outvec)
|
||||
{
|
||||
psa_outvec out_vec[1];
|
||||
size_t i;
|
||||
|
||||
reset_ps_state();
|
||||
memset(secure_mem, 0xA5, sizeof(secure_mem));
|
||||
|
||||
/* A zero-length descriptor pointing at Secure memory must not pass
|
||||
* validation: ARM_TEE_PS_GET_SUPPORT writes through .base regardless of
|
||||
* the declared length. */
|
||||
out_vec[0].base = secure_mem;
|
||||
out_vec[0].len = 0;
|
||||
|
||||
ck_assert_int_eq(
|
||||
arm_tee_psa_call((psa_handle_t)ARM_TEE_PROTECTED_STORAGE_HANDLE,
|
||||
ARM_TEE_PS_GET_SUPPORT, NULL, 0, out_vec, 1),
|
||||
PSA_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
for (i = 0; i < sizeof(secure_mem); i++) {
|
||||
ck_assert_uint_eq(secure_mem[i], 0xA5);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_ps_get_support_rejects_short_outvec)
|
||||
{
|
||||
uint8_t buf[sizeof(uint32_t)];
|
||||
psa_outvec out_vec[1];
|
||||
|
||||
reset_ps_state();
|
||||
memset(buf, 0xA5, sizeof(buf));
|
||||
|
||||
out_vec[0].base = buf;
|
||||
out_vec[0].len = sizeof(uint32_t) - 1;
|
||||
|
||||
ck_assert_int_eq(
|
||||
arm_tee_psa_test_ps_dispatch(ARM_TEE_PS_GET_SUPPORT, NULL, 0,
|
||||
out_vec, 1),
|
||||
PSA_ERROR_INVALID_ARGUMENT);
|
||||
ck_assert_uint_eq(buf[0], 0xA5);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_ps_get_support_success_path)
|
||||
{
|
||||
uint32_t support = 0xFFFFFFFFU;
|
||||
psa_outvec out_vec[1];
|
||||
|
||||
reset_ps_state();
|
||||
out_vec[0].base = &support;
|
||||
out_vec[0].len = sizeof(support);
|
||||
|
||||
ck_assert_int_eq(
|
||||
arm_tee_psa_call((psa_handle_t)ARM_TEE_PROTECTED_STORAGE_HANDLE,
|
||||
ARM_TEE_PS_GET_SUPPORT, NULL, 0, out_vec, 1),
|
||||
PSA_SUCCESS);
|
||||
ck_assert_uint_eq(support, 0);
|
||||
ck_assert_uint_eq(out_vec[0].len, sizeof(support));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *arm_tee_psa_ipc_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("arm-tee-psa-ipc");
|
||||
|
|
@ -174,6 +327,9 @@ Suite *arm_tee_psa_ipc_suite(void)
|
|||
tcase_add_test(tc, test_ps_get_info_rejects_short_uid_vector);
|
||||
tcase_add_test(tc, test_ps_remove_rejects_short_uid_vector);
|
||||
tcase_add_test(tc, test_ps_set_get_info_remove_success_path);
|
||||
tcase_add_test(tc, test_psa_call_rejects_secure_zero_len_outvec);
|
||||
tcase_add_test(tc, test_ps_get_support_rejects_short_outvec);
|
||||
tcase_add_test(tc, test_ps_get_support_success_path);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
/* unit-pkcs11-nsc-zeroize.c
|
||||
*
|
||||
* Unit test for the PKCS#11 non-secure-callable bounce buffers.
|
||||
*
|
||||
* The NSC veneers deep-copy every non-secure CK_ATTRIBUTE value and every
|
||||
* CK_MECHANISM parameter into secure-world heap before calling wolfPKCS11.
|
||||
* For key import (C_CreateObject with CKA_VALUE, or the RSA private
|
||||
* components) and for password-based derivation (CKM_PKCS5_PBKD2 pPassword)
|
||||
* those bounce buffers hold plaintext secrets, so they must be scrubbed before
|
||||
* being released back to the secure heap.
|
||||
*
|
||||
* Copyright (C) 2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfBoot.
|
||||
*
|
||||
* wolfBoot is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfBoot 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Secure-world heap stand-in: a bump allocator whose backing store is never
|
||||
* reused or cleared, so whatever a veneer leaves behind on XFREE() is still
|
||||
* observable afterwards. That is exactly what a released heap block looks like
|
||||
* to the next allocation that recycles it, to a secure-world debugger, or to a
|
||||
* cold-boot dump.
|
||||
*/
|
||||
static uint8_t sec_pool[4096];
|
||||
static size_t sec_pool_used;
|
||||
|
||||
static void *sec_malloc(size_t n)
|
||||
{
|
||||
void *p;
|
||||
|
||||
if (n == 0)
|
||||
n = 1;
|
||||
n = (n + 7U) & ~(size_t)7U; /* keep allocations aligned */
|
||||
if (sec_pool_used + n > sizeof(sec_pool))
|
||||
return NULL;
|
||||
p = &sec_pool[sec_pool_used];
|
||||
sec_pool_used += n;
|
||||
return p;
|
||||
}
|
||||
|
||||
#define XMALLOC_OVERRIDE
|
||||
#define XMALLOC(n, h, t) sec_malloc((size_t)(n))
|
||||
#define XFREE(p, h, t) do { (void)(p); } while (0)
|
||||
#define XREALLOC(p, n, h, t) NULL
|
||||
|
||||
#include "user_settings.h"
|
||||
#include "wolfboot/wc_secure.h"
|
||||
#include "wolfpkcs11/pkcs11.h"
|
||||
#include "wolfboot/wcs_pkcs11.h"
|
||||
|
||||
/*
|
||||
* Simulated non-secure RAM. Only pointers fully inside this object pass the
|
||||
* CMSE attribution stub, like real NS memory on a TrustZone-M part.
|
||||
*/
|
||||
static union {
|
||||
uint8_t bytes[1024];
|
||||
CK_ATTRIBUTE tmpl[8];
|
||||
struct {
|
||||
CK_MECHANISM mech;
|
||||
CK_PKCS5_PBKD2_PARAMS2 params;
|
||||
struct C_DeriveKey_nsc_args args;
|
||||
CK_OBJECT_HANDLE hKey;
|
||||
CK_UTF8CHAR password[16];
|
||||
} derive;
|
||||
} ns_mem;
|
||||
|
||||
void *cmse_check_address_range(void *ptr, size_t size, int flags)
|
||||
{
|
||||
uint8_t *start = (uint8_t *)ptr;
|
||||
|
||||
(void)flags;
|
||||
if (start == NULL)
|
||||
return NULL;
|
||||
if (size == 0)
|
||||
size = 1;
|
||||
if (start < ns_mem.bytes ||
|
||||
start + size > ns_mem.bytes + sizeof(ns_mem.bytes))
|
||||
return NULL;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* The 32-byte AES key the non-secure client imports. */
|
||||
static const uint8_t secret_key[32] = {
|
||||
0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x4b, 0x30,
|
||||
0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x4b, 0x31,
|
||||
0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x4b, 0x32,
|
||||
0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x4b, 0x33
|
||||
};
|
||||
|
||||
/* The PBKDF2 password the non-secure client derives from. */
|
||||
static const uint8_t secret_pwd[16] = {
|
||||
0x50, 0x41, 0x53, 0x53, 0x77, 0x30, 0x72, 0x64,
|
||||
0x50, 0x41, 0x53, 0x53, 0x77, 0x30, 0x72, 0x65
|
||||
};
|
||||
|
||||
/* Set when the wolfPKCS11 stub actually saw the secret in the secure copy. */
|
||||
static int stub_saw_secret;
|
||||
|
||||
CK_RV C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate,
|
||||
CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phObject)
|
||||
{
|
||||
CK_ULONG i;
|
||||
|
||||
(void)hSession;
|
||||
for (i = 0; i < ulCount; i++) {
|
||||
if (pTemplate[i].type == CKA_VALUE &&
|
||||
pTemplate[i].ulValueLen == sizeof(secret_key) &&
|
||||
memcmp(pTemplate[i].pValue, secret_key,
|
||||
sizeof(secret_key)) == 0) {
|
||||
stub_saw_secret = 1;
|
||||
}
|
||||
}
|
||||
if (phObject != NULL)
|
||||
*phObject = 1;
|
||||
return CKR_OK;
|
||||
}
|
||||
|
||||
CK_RV C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
|
||||
CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate,
|
||||
CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey)
|
||||
{
|
||||
CK_PKCS5_PBKD2_PARAMS2 *p;
|
||||
|
||||
(void)hSession;
|
||||
(void)hBaseKey;
|
||||
(void)pTemplate;
|
||||
(void)ulAttributeCount;
|
||||
p = (CK_PKCS5_PBKD2_PARAMS2 *)pMechanism->pParameter;
|
||||
if (p->ulPasswordLen == sizeof(secret_pwd) &&
|
||||
memcmp(p->pPassword, secret_pwd, sizeof(secret_pwd)) == 0) {
|
||||
stub_saw_secret = 1;
|
||||
}
|
||||
if (phKey != NULL)
|
||||
*phKey = 1;
|
||||
return CKR_OK;
|
||||
}
|
||||
|
||||
#include "../../src/pkcs11_callable.c"
|
||||
|
||||
static void reset_state(void)
|
||||
{
|
||||
memset(sec_pool, 0, sizeof(sec_pool));
|
||||
sec_pool_used = 0;
|
||||
stub_saw_secret = 0;
|
||||
memset(&ns_mem, 0, sizeof(ns_mem));
|
||||
}
|
||||
|
||||
/* Return 1 if the released secure heap still contains 'secret'. */
|
||||
static int pool_has(const uint8_t *secret, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + len <= sec_pool_used; i++) {
|
||||
if (memcmp(&sec_pool[i], secret, len) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* A non-secure client imports a symmetric key with C_CreateObject. The veneer
|
||||
* bounce-buffers CKA_VALUE into the secure heap; once the call completes that
|
||||
* buffer is freed and must no longer hold the key bytes.
|
||||
*/
|
||||
START_TEST(test_create_object_value_zeroized)
|
||||
{
|
||||
CK_ATTRIBUTE *tmpl = ns_mem.tmpl;
|
||||
uint8_t *nsKey = ns_mem.bytes + 3 * sizeof(CK_ATTRIBUTE);
|
||||
CK_OBJECT_HANDLE *nsHandle;
|
||||
CK_OBJECT_CLASS *nsClass;
|
||||
CK_KEY_TYPE *nsType;
|
||||
CK_RV rv;
|
||||
|
||||
reset_state();
|
||||
memcpy(nsKey, secret_key, sizeof(secret_key));
|
||||
nsClass = (CK_OBJECT_CLASS *)(nsKey + sizeof(secret_key));
|
||||
nsType = (CK_KEY_TYPE *)(nsClass + 1);
|
||||
nsHandle = (CK_OBJECT_HANDLE *)(nsType + 1);
|
||||
*nsClass = CKO_SECRET_KEY;
|
||||
*nsType = CKK_AES;
|
||||
|
||||
tmpl[0].type = CKA_CLASS;
|
||||
tmpl[0].pValue = nsClass;
|
||||
tmpl[0].ulValueLen = sizeof(*nsClass);
|
||||
tmpl[1].type = CKA_KEY_TYPE;
|
||||
tmpl[1].pValue = nsType;
|
||||
tmpl[1].ulValueLen = sizeof(*nsType);
|
||||
tmpl[2].type = CKA_VALUE;
|
||||
tmpl[2].pValue = nsKey;
|
||||
tmpl[2].ulValueLen = sizeof(secret_key);
|
||||
|
||||
rv = C_CreateObject_nsc_call(1, tmpl, 3, nsHandle);
|
||||
ck_assert_int_eq((int)rv, (int)CKR_OK);
|
||||
/* The secure copy really was made, so the pool did hold the key... */
|
||||
ck_assert_int_eq(stub_saw_secret, 1);
|
||||
/* ...and it must not survive the free. */
|
||||
ck_assert_int_eq(pool_has(secret_key, sizeof(secret_key)), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/*
|
||||
* Same for the mechanism parameter path: CKM_PKCS5_PBKD2 carries the caller's
|
||||
* password, which nsc_mech_prepare() copies into its own secure buffer.
|
||||
*/
|
||||
START_TEST(test_mech_password_zeroized)
|
||||
{
|
||||
CK_RV rv;
|
||||
|
||||
reset_state();
|
||||
memcpy(ns_mem.derive.password, secret_pwd, sizeof(secret_pwd));
|
||||
ns_mem.derive.params.saltSource = CKZ_DATA_SPECIFIED;
|
||||
ns_mem.derive.params.iterations = 1000;
|
||||
ns_mem.derive.params.prf = CKP_PKCS5_PBKD2_HMAC_SHA256;
|
||||
ns_mem.derive.params.pPassword = ns_mem.derive.password;
|
||||
ns_mem.derive.params.ulPasswordLen = sizeof(secret_pwd);
|
||||
ns_mem.derive.mech.mechanism = CKM_PKCS5_PBKD2;
|
||||
ns_mem.derive.mech.pParameter = &ns_mem.derive.params;
|
||||
ns_mem.derive.mech.ulParameterLen = sizeof(ns_mem.derive.params);
|
||||
ns_mem.derive.args.hSession = 1;
|
||||
ns_mem.derive.args.pMechanism = &ns_mem.derive.mech;
|
||||
ns_mem.derive.args.phKey = &ns_mem.derive.hKey;
|
||||
|
||||
rv = C_DeriveKey_nsc_call(&ns_mem.derive.args);
|
||||
ck_assert_int_eq((int)rv, (int)CKR_OK);
|
||||
ck_assert_int_eq(stub_saw_secret, 1);
|
||||
ck_assert_int_eq(pool_has(secret_pwd, sizeof(secret_pwd)), 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *pkcs11_nsc_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("pkcs11-nsc-zeroize");
|
||||
TCase *tc = tcase_create("bounce-buffers");
|
||||
|
||||
tcase_add_test(tc, test_create_object_value_zeroized);
|
||||
tcase_add_test(tc, test_mech_password_zeroized);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fails;
|
||||
SRunner *sr = srunner_create(pkcs11_nsc_suite());
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
fails = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (fails == 0) ? 0 : 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/* unit-sign-header-failure.c
|
||||
*
|
||||
* Unit test for sign tool exit status when the manifest cannot be created.
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define WOLFBOOT_HASH_SHA256
|
||||
#define IMAGE_HEADER_SIZE 512
|
||||
|
||||
#define main wolfboot_sign_main
|
||||
#include "../keytools/sign.c"
|
||||
#undef main
|
||||
|
||||
static const char missing_image[] = "/nonexistent/wolfboot-image.bin";
|
||||
|
||||
static int write_file(const char *path, const void *buf, size_t len)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
size_t written;
|
||||
|
||||
if (f == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = fwrite(buf, 1, len, f);
|
||||
fclose(f);
|
||||
|
||||
return written == len ? 0 : -1;
|
||||
}
|
||||
|
||||
/* The sign tool must report a failure to the caller (make, CI) when
|
||||
* make_header() could not produce the output image. */
|
||||
START_TEST(test_sign_main_fails_when_image_missing)
|
||||
{
|
||||
char tempdir[] = "/tmp/wolfboot-sign-XXXXXX";
|
||||
char key_path[PATH_MAX];
|
||||
uint8_t raw_pubkey[64]; /* ECC256 raw Qx + Qy */
|
||||
char *argv[6];
|
||||
|
||||
ck_assert_ptr_nonnull(mkdtemp(tempdir));
|
||||
snprintf(key_path, sizeof(key_path), "%s/ecc256.raw", tempdir);
|
||||
|
||||
memset(raw_pubkey, 0x11, sizeof(raw_pubkey));
|
||||
ck_assert_int_eq(write_file(key_path, raw_pubkey, sizeof(raw_pubkey)), 0);
|
||||
|
||||
argv[0] = "sign";
|
||||
argv[1] = "--sha-only";
|
||||
argv[2] = "--ecc256";
|
||||
argv[3] = (char *)missing_image;
|
||||
argv[4] = key_path;
|
||||
argv[5] = "1";
|
||||
|
||||
ck_assert_int_ne(wolfboot_sign_main(6, argv), 0);
|
||||
|
||||
unlink(key_path);
|
||||
rmdir(tempdir);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Same contract for the hybrid path, which uses make_hybrid_header(). */
|
||||
START_TEST(test_sign_main_fails_when_image_missing_hybrid)
|
||||
{
|
||||
char tempdir[] = "/tmp/wolfboot-sign-XXXXXX";
|
||||
char key_path[PATH_MAX];
|
||||
char key2_path[PATH_MAX];
|
||||
uint8_t raw_pubkey[64]; /* ECC256 raw Qx + Qy */
|
||||
uint8_t raw_pubkey2[32]; /* ED25519 raw public key */
|
||||
char *argv[8];
|
||||
|
||||
ck_assert_ptr_nonnull(mkdtemp(tempdir));
|
||||
snprintf(key_path, sizeof(key_path), "%s/ecc256.raw", tempdir);
|
||||
snprintf(key2_path, sizeof(key2_path), "%s/ed25519.raw", tempdir);
|
||||
|
||||
memset(raw_pubkey, 0x11, sizeof(raw_pubkey));
|
||||
memset(raw_pubkey2, 0x22, sizeof(raw_pubkey2));
|
||||
ck_assert_int_eq(write_file(key_path, raw_pubkey, sizeof(raw_pubkey)), 0);
|
||||
ck_assert_int_eq(write_file(key2_path, raw_pubkey2, sizeof(raw_pubkey2)),
|
||||
0);
|
||||
|
||||
argv[0] = "sign";
|
||||
argv[1] = "--sha-only";
|
||||
argv[2] = "--ecc256";
|
||||
argv[3] = "--ed25519";
|
||||
argv[4] = (char *)missing_image;
|
||||
argv[5] = key_path;
|
||||
argv[6] = key2_path;
|
||||
argv[7] = "1";
|
||||
|
||||
ck_assert_int_ne(wolfboot_sign_main(8, argv), 0);
|
||||
|
||||
unlink(key_path);
|
||||
unlink(key2_path);
|
||||
rmdir(tempdir);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *wolfboot_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("sign-header-failure");
|
||||
TCase *tcase = tcase_create("make-header");
|
||||
|
||||
tcase_add_test(tcase, test_sign_main_fails_when_image_missing);
|
||||
tcase_add_test(tcase, test_sign_main_fails_when_image_missing_hybrid);
|
||||
suite_add_tcase(s, tcase);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failed;
|
||||
Suite *s = wolfboot_suite();
|
||||
SRunner *runner = srunner_create(s);
|
||||
|
||||
srunner_run_all(runner, CK_NORMAL);
|
||||
failed = srunner_ntests_failed(runner);
|
||||
srunner_free(runner);
|
||||
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/* unit-sign-hybrid-keyload.c
|
||||
*
|
||||
* Unit test for sign tool secondary (hybrid) key load error handling.
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define WOLFBOOT_HASH_SHA256
|
||||
#define IMAGE_HEADER_SIZE 512
|
||||
|
||||
#define main wolfboot_sign_main
|
||||
#include "../keytools/sign.c"
|
||||
#undef main
|
||||
|
||||
static const char missing_key[] = "/nonexistent/wolfboot-secondary-key.der";
|
||||
|
||||
static int write_file(const char *path, const void *buf, size_t len)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
size_t written;
|
||||
|
||||
if (f == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
written = fwrite(buf, 1, len, f);
|
||||
fclose(f);
|
||||
|
||||
return written == len ? 0 : -1;
|
||||
}
|
||||
|
||||
static void reset_cmd_defaults(void)
|
||||
{
|
||||
memset(&CMD, 0, sizeof(CMD));
|
||||
CMD.sign = NO_SIGN;
|
||||
CMD.hash_algo = HASH_SHA256;
|
||||
CMD.partition_id = HDR_IMG_TYPE_APP;
|
||||
CMD.header_sz = IMAGE_HEADER_SIZE;
|
||||
CMD.fw_version = "1";
|
||||
CMD.no_ts = 1;
|
||||
}
|
||||
|
||||
/* load_key() must not hand back an unset public key when the key file
|
||||
* cannot be opened at all. */
|
||||
START_TEST(test_load_key_clears_pubkey_when_file_missing)
|
||||
{
|
||||
uint8_t *key_buffer = NULL;
|
||||
uint32_t key_buffer_sz = 0;
|
||||
uint8_t sentinel = 0xA5;
|
||||
uint8_t *pubkey = &sentinel;
|
||||
uint32_t pubkey_sz = 0xDEADBEEFU;
|
||||
|
||||
reset_cmd_defaults();
|
||||
CMD.hybrid = 1;
|
||||
CMD.secondary_sign = SIGN_ML_DSA;
|
||||
CMD.secondary_key_file = missing_key;
|
||||
|
||||
ck_assert_ptr_null(load_key(&key_buffer, &key_buffer_sz, &pubkey,
|
||||
&pubkey_sz, 1));
|
||||
ck_assert_ptr_null(pubkey);
|
||||
ck_assert_uint_eq(pubkey_sz, 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* load_key() must not hand back a dangling public key pointer when the key
|
||||
* file is readable but cannot be decoded. */
|
||||
START_TEST(test_load_key_clears_pubkey_when_decode_fails)
|
||||
{
|
||||
char tempdir[] = "/tmp/wolfboot-sign-XXXXXX";
|
||||
char key_path[PATH_MAX];
|
||||
uint8_t garbage[7];
|
||||
uint8_t *key_buffer = NULL;
|
||||
uint32_t key_buffer_sz = 0;
|
||||
uint8_t *pubkey = NULL;
|
||||
uint32_t pubkey_sz = 0;
|
||||
|
||||
ck_assert_ptr_nonnull(mkdtemp(tempdir));
|
||||
snprintf(key_path, sizeof(key_path), "%s/secondary.der", tempdir);
|
||||
|
||||
memset(garbage, 0x5A, sizeof(garbage));
|
||||
ck_assert_int_eq(write_file(key_path, garbage, sizeof(garbage)), 0);
|
||||
|
||||
reset_cmd_defaults();
|
||||
CMD.hybrid = 1;
|
||||
CMD.secondary_sign = SIGN_ED25519;
|
||||
CMD.secondary_key_file = key_path;
|
||||
|
||||
ck_assert_ptr_null(load_key(&key_buffer, &key_buffer_sz, &pubkey,
|
||||
&pubkey_sz, 1));
|
||||
ck_assert_ptr_null(pubkey);
|
||||
ck_assert_uint_eq(pubkey_sz, 0);
|
||||
|
||||
unlink(key_path);
|
||||
rmdir(tempdir);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* The sign tool must fail when the hybrid secondary key cannot be loaded,
|
||||
* instead of building a manifest out of an unset secondary public key. */
|
||||
START_TEST(test_sign_main_fails_when_secondary_key_missing)
|
||||
{
|
||||
char tempdir[] = "/tmp/wolfboot-sign-XXXXXX";
|
||||
char image_path[PATH_MAX];
|
||||
char key_path[PATH_MAX];
|
||||
uint8_t image_buf[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
uint8_t raw_pubkey[64]; /* ECC256 raw Qx + Qy */
|
||||
char *argv[8];
|
||||
|
||||
ck_assert_ptr_nonnull(mkdtemp(tempdir));
|
||||
snprintf(image_path, sizeof(image_path), "%s/image.bin", tempdir);
|
||||
snprintf(key_path, sizeof(key_path), "%s/ecc256.raw", tempdir);
|
||||
|
||||
memset(raw_pubkey, 0x11, sizeof(raw_pubkey));
|
||||
ck_assert_int_eq(write_file(image_path, image_buf, sizeof(image_buf)), 0);
|
||||
ck_assert_int_eq(write_file(key_path, raw_pubkey, sizeof(raw_pubkey)), 0);
|
||||
|
||||
argv[0] = "sign";
|
||||
argv[1] = "--sha-only";
|
||||
argv[2] = "--ecc256";
|
||||
argv[3] = "--ml_dsa";
|
||||
argv[4] = image_path;
|
||||
argv[5] = key_path;
|
||||
argv[6] = (char *)missing_key;
|
||||
argv[7] = "1";
|
||||
|
||||
exit(wolfboot_sign_main(8, argv));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *wolfboot_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("sign-hybrid-keyload");
|
||||
TCase *tcase = tcase_create("load-key");
|
||||
|
||||
tcase_add_test(tcase, test_load_key_clears_pubkey_when_file_missing);
|
||||
tcase_add_test(tcase, test_load_key_clears_pubkey_when_decode_fails);
|
||||
tcase_add_exit_test(tcase, test_sign_main_fails_when_secondary_key_missing,
|
||||
1);
|
||||
suite_add_tcase(s, tcase);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failed;
|
||||
Suite *s = wolfboot_suite();
|
||||
SRunner *runner = srunner_create(s);
|
||||
|
||||
srunner_run_all(runner, CK_NORMAL);
|
||||
failed = srunner_ntests_failed(runner);
|
||||
srunner_free(runner);
|
||||
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
/* unit-tpm-nsc-cert.c
|
||||
*
|
||||
* Unit tests for the wolfBoot_tpm2_read_cert() non-secure entry veneer.
|
||||
*
|
||||
* The veneer validates the caller-supplied output buffer against the length
|
||||
* word the non-secure caller points at. That length must be snapshotted into
|
||||
* Secure memory before validation, otherwise a racing non-secure agent (a
|
||||
* second NS thread, an NS interrupt, or NS-programmed DMA) can enlarge it
|
||||
* between the veneer's cmse_check_address_range() and wolfTPM's own capacity
|
||||
* check, and the secure world writes NV data past the validated range.
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef SPI_CS_TPM
|
||||
#define SPI_CS_TPM 1
|
||||
#endif
|
||||
|
||||
#include "tpm.h"
|
||||
|
||||
/* Simulated non-secure region followed by the Secure SRAM adjacent to it. Any
|
||||
* range touching 'secure' is rejected by the CMSE stub below, exactly like a
|
||||
* real Secure address fails CMSE_NONSECURE. */
|
||||
#define NS_CERT_CAP 16
|
||||
static struct {
|
||||
uint8_t cert[NS_CERT_CAP];
|
||||
uint8_t secure[64];
|
||||
} ns_edge;
|
||||
|
||||
/* Non-secure length word handed to the veneer, and the value a racing
|
||||
* non-secure agent stores into it once the veneer has validated it. */
|
||||
static uint32_t ns_cert_sz;
|
||||
static uint32_t ns_race_value;
|
||||
|
||||
/* Size of the certificate the TPM reports for the requested NV index. */
|
||||
static uint32_t nv_cert_size;
|
||||
|
||||
void *cmse_check_address_range(void *ptr, size_t size, int flags)
|
||||
{
|
||||
uint8_t *start = (uint8_t *)ptr;
|
||||
uint8_t *end;
|
||||
|
||||
(void)flags;
|
||||
if (start == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (size == 0) {
|
||||
size = 1;
|
||||
}
|
||||
end = start + size;
|
||||
if (end > ns_edge.secure &&
|
||||
start < ns_edge.secure + sizeof(ns_edge.secure)) {
|
||||
return NULL;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int wolfBoot_printf(const char* fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* TPM2_GetAlgName(TPM_ALG_ID alg)
|
||||
{
|
||||
(void)alg;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* TPM2_GetRCString(int rc)
|
||||
{
|
||||
(void)rc;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int wolfTPM2_SetAuthPassword(WOLFTPM2_DEV* dev, int index,
|
||||
const TPM2B_AUTH* auth)
|
||||
{
|
||||
(void)dev;
|
||||
(void)index;
|
||||
(void)auth;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Faithful stand-in for wolfTPM2_NVReadCert() (lib/wolfTPM/src/tpm2_wrap.c):
|
||||
* '*len' is an input capacity, re-read after the veneer's own validation, and
|
||||
* the NV data is copied into 'buffer' only if it fits. The racing non-secure
|
||||
* write is modelled here because that is exactly the window it occupies. */
|
||||
int wolfTPM2_NVReadCert(WOLFTPM2_DEV* dev, TPM_HANDLE handle,
|
||||
uint8_t* buffer, uint32_t* len)
|
||||
{
|
||||
(void)dev;
|
||||
(void)handle;
|
||||
|
||||
if (len == NULL) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
if (ns_race_value != 0) {
|
||||
ns_cert_sz = ns_race_value; /* NS agent enlarges the length word */
|
||||
}
|
||||
if (nv_cert_size > *len) {
|
||||
return BUFFER_E;
|
||||
}
|
||||
*len = nv_cert_size;
|
||||
memset(buffer, 0xA5, nv_cert_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "../../src/tpm.c"
|
||||
|
||||
static void setup_ns_edge(uint32_t certSz, uint32_t race, uint32_t nvSize)
|
||||
{
|
||||
memset(&ns_edge, 0xEE, sizeof(ns_edge));
|
||||
ns_cert_sz = certSz;
|
||||
ns_race_value = race;
|
||||
nv_cert_size = nvSize;
|
||||
}
|
||||
|
||||
static int secure_untouched(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < sizeof(ns_edge.secure); i++) {
|
||||
if (ns_edge.secure[i] != 0xEE) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* A non-secure caller presents a 16-byte capacity, so only 16 bytes of NS
|
||||
* memory are validated, then enlarges the length word. The 48-byte NV
|
||||
* certificate must not be written, because 32 of those bytes land in the
|
||||
* Secure SRAM following the validated range. */
|
||||
START_TEST(test_read_cert_ns_length_race)
|
||||
{
|
||||
int rc;
|
||||
|
||||
setup_ns_edge(NS_CERT_CAP, sizeof(ns_edge), 48);
|
||||
|
||||
rc = wolfBoot_tpm2_read_cert(0x01C00002, ns_edge.cert, &ns_cert_sz);
|
||||
|
||||
ck_assert_int_eq(secure_untouched(), 1);
|
||||
ck_assert_int_ne(rc, 0);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Without a race the veneer must still behave as documented: the certificate
|
||||
* is copied and the non-secure length word receives the actual size. */
|
||||
START_TEST(test_read_cert_normal)
|
||||
{
|
||||
uint8_t cert[128];
|
||||
uint32_t certSz = (uint32_t)sizeof(cert);
|
||||
int rc;
|
||||
|
||||
setup_ns_edge(0, 0, 48);
|
||||
memset(cert, 0xEE, sizeof(cert));
|
||||
|
||||
rc = wolfBoot_tpm2_read_cert(0x01C00002, cert, &certSz);
|
||||
|
||||
ck_assert_int_eq(rc, 0);
|
||||
ck_assert_uint_eq(certSz, 48);
|
||||
ck_assert_int_eq(cert[0], 0xA5);
|
||||
ck_assert_int_eq(cert[47], 0xA5);
|
||||
ck_assert_int_eq(cert[48], 0xEE);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* A length word pointing into Secure memory must be rejected outright. */
|
||||
START_TEST(test_read_cert_secure_length_pointer)
|
||||
{
|
||||
int rc;
|
||||
|
||||
setup_ns_edge(NS_CERT_CAP, 0, 48);
|
||||
|
||||
rc = wolfBoot_tpm2_read_cert(0x01C00002, ns_edge.cert,
|
||||
(uint32_t*)ns_edge.secure);
|
||||
|
||||
ck_assert_int_eq(rc, BAD_FUNC_ARG);
|
||||
ck_assert_int_eq(secure_untouched(), 1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite* tpm_nsc_cert_suite(void)
|
||||
{
|
||||
Suite* s;
|
||||
TCase* tc;
|
||||
|
||||
s = suite_create("TPM NSC read cert");
|
||||
tc = tcase_create("ns_bounds");
|
||||
tcase_add_test(tc, test_read_cert_ns_length_race);
|
||||
tcase_add_test(tc, test_read_cert_normal);
|
||||
tcase_add_test(tc, test_read_cert_secure_length_pointer);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Suite* s;
|
||||
SRunner* sr;
|
||||
int failed;
|
||||
|
||||
s = tpm_nsc_cert_suite();
|
||||
sr = srunner_create(s);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
Loading…
Reference in New Issue