Add wolfHSM cert chain verification for ECC and RSA

pull/574/head
Brett Nicholas 2025-05-11 13:00:16 -06:00
parent 2b996f8280
commit 7660bf66f8
17 changed files with 750 additions and 70 deletions

View File

@ -18,6 +18,8 @@ jobs:
file: "config/examples/sim-wolfHSM.config" file: "config/examples/sim-wolfHSM.config"
- name: "wolfHSM ML-DSA" - name: "wolfHSM ML-DSA"
file: "config/examples/sim-wolfHSM-mldsa.config" file: "config/examples/sim-wolfHSM-mldsa.config"
- name: "wolfHSM cert chain verify"
file: "config/examples/sim-wolfHSM-certchain.config"
fail-fast: false fail-fast: false
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -54,8 +56,7 @@ jobs:
with: with:
repository: wolfssl/wolfHSM-examples repository: wolfssl/wolfHSM-examples
# Make sure to update this when the wolfHSM submodule is updated! # Make sure to update this when the wolfHSM submodule is updated!
#ref: wolfHSM-v1.1.0 ref: wolfHSM-examples-v1.2.0
ref: 3e03bd4d4a8439ed4a8a9577823c89e4c37eb9be
path: wolfHSM-examples path: wolfHSM-examples
- name: Build example POSIX TCP server - name: Build example POSIX TCP server
@ -65,7 +66,13 @@ jobs:
- name: Run POSIX TCP server - name: Run POSIX TCP server
run: | run: |
cd wolfHSM-examples/posix/tcp/wh_server_tcp cd wolfHSM-examples/posix/tcp/wh_server_tcp
./Build/wh_server_tcp.elf --client 12 --id 255 --key ../../../../wolfboot_signing_private_key_pub.der & if [ "${{ matrix.config.name }}" = "wolfHSM cert chain verify" ]; then
tmpfile=$(mktemp)
echo "obj 1 0xFFFF 0x0000 \"cert CA\" ../../../../test-dummy-ca/root-cert.der" >> $tmpfile
./Build/wh_server_tcp.elf --nvminit $tmpfile &
else
./Build/wh_server_tcp.elf --client 12 --id 255 --key ../../../../wolfboot_signing_private_key_pub.der --cert ../../../../wolfboot_signing_cert_chain.der &
fi
TCP_SERVER_PID=$! TCP_SERVER_PID=$!
echo "TCP_SERVER_PID=$TCP_SERVER_PID" >> $GITHUB_ENV echo "TCP_SERVER_PID=$TCP_SERVER_PID" >> $GITHUB_ENV

1
.gitignore vendored
View File

@ -99,6 +99,7 @@ include/target.h
.wolfboot-partition-size .wolfboot-partition-size
.bootloader-partition-size .bootloader-partition-size
MPLabX/wolfBoot-SAME51.X/.generated_files/ MPLabX/wolfBoot-SAME51.X/.generated_files/
test-dummy-ca/**
# Test tools # Test tools
tools/check_config/check_config tools/check_config/check_config

View File

@ -229,6 +229,7 @@ $(PRIVATE_KEY):
$(Q)(test $(SIGN) = NONE) || ($(SIGN_ENV) "$(KEYGEN_TOOL)" $(KEYGEN_OPTIONS) -g $(PRIVATE_KEY)) || true $(Q)(test $(SIGN) = NONE) || ($(SIGN_ENV) "$(KEYGEN_TOOL)" $(KEYGEN_OPTIONS) -g $(PRIVATE_KEY)) || true
$(Q)(test $(SIGN) = NONE) && (echo "// SIGN=NONE" > src/keystore.c) || true $(Q)(test $(SIGN) = NONE) && (echo "// SIGN=NONE" > src/keystore.c) || true
$(Q)(test "$(FLASH_OTP_KEYSTORE)" = "1") && (make -C tools/keytools/otp) || true $(Q)(test "$(FLASH_OTP_KEYSTORE)" = "1") && (make -C tools/keytools/otp) || true
$(Q)(test $(SIGN) = NONE) || (test "$(CERT_CHAIN_VERIFY)" = "") || (test "$(CERT_CHAIN_GEN)" = "") || (tools/scripts/sim-gen-dummy-chain.sh --algo $(CERT_CHAIN_GEN_ALGO) --leaf $(PRIVATE_KEY))
$(SECONDARY_PRIVATE_KEY): $(PRIVATE_KEY) keystore.der $(SECONDARY_PRIVATE_KEY): $(PRIVATE_KEY) keystore.der
$(Q)$(MAKE) keytools_check $(Q)$(MAKE) keytools_check
@ -390,6 +391,7 @@ utilsclean: clean
keysclean: clean keysclean: clean
$(Q)rm -f *.pem *.der tags ./src/*_pub_key.c ./src/keystore.c include/target.h $(Q)rm -f *.pem *.der tags ./src/*_pub_key.c ./src/keystore.c include/target.h
$(Q)(test "$(CERT_CHAIN_GEN)" = "") || rm -rf test-dummy-ca || true
distclean: clean keysclean utilsclean distclean: clean keysclean utilsclean
$(Q)rm -f *.bin *.elf $(Q)rm -f *.bin *.elf

View File

@ -0,0 +1,39 @@
ARCH=sim
TARGET=sim
SIGN?=ECC256
HASH?=SHA256
WOLFBOOT_SMALL_STACK?=0
SPI_FLASH=0
DEBUG=1
# Cert chain options
CERT_CHAIN_VERIFY=1
CERT_CHAIN_GEN=1
# Ensure header is large enough to hold the cert chain (check sign tool output)
# for actual length
IMAGE_HEADER_SIZE=2048
# If SIGN=RSA4096, use the below options
#WOLFBOOT_HUGE_STACK=1
#IMAGE_HEADER_SIZE=4096
# wolfHSM options
WOLFHSM_CLIENT=1
# sizes should be multiple of system page size
#WOLFBOOT_PARTITION_SIZE=0x40000
WOLFBOOT_PARTITION_SIZE=0x100000
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
# if on external flash, it should be multiple of system page size
#WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
#WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x180000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x280000
# required for keytools
WOLFBOOT_FIXED_PARTITIONS=1
# For debugging XMALLOC/XFREE
#CFLAGS_EXTRA+=-DWOLFBOOT_DEBUG_MALLOC

View File

@ -119,6 +119,14 @@ If none of the following is used, '--sha256' is assumed by default.
* `--sha3` Use sha3-384 for digest calculation on binary images and public keys. * `--sha3` Use sha3-384 for digest calculation on binary images and public keys.
#### Certificate Chain Options
wolfBoot also supports verifying firmware images using certificate chains instead of raw public keys. In this mode of operation, a certificate chain is included in the image manifest header, and the image is signed with the private key corresponding to the leaf certificate identity (signer cert). On boot, wolfBoot verifies the trust of the certificate chain (and therefore the signer cert) against a trusted root CA stored in the wolfHSM server, and if the chain is trusted, verifies the authenticity of the firmware image using the public key from the image signer certificate.
To generate an image for use with this mode, pass the `--cert-chain CERT_CHAIN.der` option to the sign tool, where `CERT_CHAIN.der` is a der encoded certificate chain containing one or more certificates in SSL order (leaf/signer cert last). Note that the sign tool still expects a signing private key to be provided as described above, and assumes that the public key of the signer cert in the chain corresponds to the signing private key.
Certificate chain verification of images is currently limited to use in conjuction with wolfHSM. See [wolfHSM.md](wolfHSM.md) for more details.
#### Target partition id (Multiple partition images, "self-update" feature) #### Target partition id (Multiple partition images, "self-update" feature)
If none of the following is used, "--id=1" is assumed by default. On systems If none of the following is used, "--id=1" is assumed by default. On systems
@ -257,7 +265,7 @@ For a real-life example, see the section below.
./tools/keytools/sign --rsa2048 --sha256 test-app/image.bin wolfboot_signing_private_key.der 1 ./tools/keytools/sign --rsa2048 --sha256 test-app/image.bin wolfboot_signing_private_key.der 1
``` ```
Note: The last argument is the “version” number. Note: The last argument is the "version" number.
### Signing Firmware with External Private Key (HSM) ### Signing Firmware with External Private Key (HSM)

View File

@ -221,3 +221,26 @@ Note: When using scattered ELF images, ensure that:
- The ELF file adheres to the ELF file specification and was generated by a toolchain supporting the target architecture - The ELF file adheres to the ELF file specification and was generated by a toolchain supporting the target architecture
- All section addresses are within valid executable memory regions and **do not overlap with the wolfBoot image, nor the BOOT, UPDATE and SWAP partitions**. - All section addresses are within valid executable memory regions and **do not overlap with the wolfBoot image, nor the BOOT, UPDATE and SWAP partitions**.
## Certificate Verification
wolfBoot supports authenticating images using certificate chains instead of raw public keys. in this mode of operation, a certificate chain is included in the image manifest header, and the image is signed with the private key corresponding to the leaf certificate identity (signer cert). On boot, wolfBoot verifies the trust of the certificate chain (and therefore the signer cert) against a trusted root CA stored in the wolfHSM server, and if the chain is trusted, verifies the authenticity of the firmware image using the public key from the image signer certificate.
To use this feature:
1. Enable the feature in your wolfBoot configuration by defining `WOLFBOOT_CERT_CHAIN_VERIFY`
2. When signing firmware, include the certificate chain using the `--cert-chain` option:
```sh
./tools/keytools/sign --rsa2048 --sha256 --cert-chain cert_chain.der test-app/image.bin private_key.der 1
```
When verifying firmware, wolfBoot will:
1. Extract the certificate chain from the firmware header
2. Verify the chain using the pre-provisioned root certificate
3. Use the public key from the leaf certificate to verify the firmware signature
This feature is particularly useful in scenarios where you want to rotate signing keys without updating the bootloader, as you can simply resign the image with a new key, create a new certificate chain, then update the certificate chain in the firmware header.
Note: Currently, support for certificate verification is limited to use in conjuction with wolfHSM. Fore more information see [wolfHSM.md](wolfHSM.md).

View File

@ -31,6 +31,34 @@ wolfBoot supports using wolfHSM for the following algorithms:
Encrypted images with wolfHSM is not yet supported in wolfBoot. Note that every HAL target may not support all of these algorithms. Consult the platform-specific wolfBoot documentation for details. Encrypted images with wolfHSM is not yet supported in wolfBoot. Note that every HAL target may not support all of these algorithms. Consult the platform-specific wolfBoot documentation for details.
## Additional Features
wolfBoot with wolfHSM also supports the following features:
### Certificate Verification
wolfBoot with wolfHSM supports certificate chain verification for firmware images. In this mode, instead of using raw public keys for signature verification, wolfBoot verifies firmware images using wolfHSM with a public key embedded in a certificate chain that is included in the image manifest header.
The certificate verification process with wolfHSM works as follows:
1. A root CA is created serving as the root of trust for the entire PKI system
2. A signing keypair and corresponding identity certificate is created for signing firmware images
3. The firmware image is signed with the signing private key
4. A certificate chain is created consisting of the signing identity certificate and an optional number of intermediate certificates, where trust is chained back to the root CA.
5. During the signing process, the image is signed with the signer private key and the certificate chain is embedded in the firmware image header.
6. During boot, wolfBoot extracts the certificate chain from the firmware header
7. wolfBoot uses the wolfHSM server to verify the certificate chain against a pre-provisioned root CA certificate stored on the HSM and caches the public key of the leaf certificate if the chain verifies as trusted
8. If the chain is trusted, wolfBoot uses the cached public key from the leaf certificate to verify the firmware signature on the wolfHSM server
To use certificate verification with wolfHSM:
1. Enable `WOLFBOOT_CERT_CHAIN_VERIFY` in your wolfBoot configuration
2. Ensure the wolfHSM server is configured with certificate manager support (`WOLFHSM_CFG_CERTIFICATE_MANAGER`)
3. Pre-provision the root CA certificate on the wolfHSM server at the NVM ID specified by the HAL `hsmClientNvmIdCertRootCA`
4. Sign firmware images with the `--cert-chain` option, providing a DER-encoded certificate chain
To build the simulator using wolfHSM for certificate verification, use [config/examples/sim-wolfHSM-certchain.config](config/examples/sim-wolfHSM-certchain.config).
## Configuration Options ## Configuration Options
This section describes the configuration options available for wolfHSM client integration. Note that these options should be configured automatically by the build system for each supported platform when wolfHSM support is enabled. Consult the platform-specific documentation for details on enabling wolfHSM support. This section describes the configuration options available for wolfHSM client integration. Note that these options should be configured automatically by the build system for each supported platform when wolfHSM support is enabled. Consult the platform-specific documentation for details on enabling wolfHSM support.

View File

@ -100,6 +100,9 @@ const int hsmClientKeyIdPubKey = 0xFF;
const int hsmClientDevIdCrypt = WH_DEV_ID; const int hsmClientDevIdCrypt = WH_DEV_ID;
const int hsmClientKeyIdCrypt = 0xFF; const int hsmClientKeyIdCrypt = 0xFF;
#endif #endif
#ifdef WOLFBOOT_CERT_CHAIN_VERIFY
const int hsmClientNvmIdCertRootCA = 1;
#endif
int hal_hsm_init_connect(void); int hal_hsm_init_connect(void);
int hal_hsm_disconnect(void); int hal_hsm_disconnect(void);

View File

@ -161,6 +161,10 @@ extern const int hsmClientKeyIdPubKey; /* KeyId for public key operations */
#ifdef EXT_ENCRYPTED #ifdef EXT_ENCRYPTED
extern const int hsmClientKeyIdCrypt; /* KeyId for image (enc/dec)ryption */ extern const int hsmClientKeyIdCrypt; /* KeyId for image (enc/dec)ryption */
#endif #endif
#ifdef WOLFBOOT_CERT_CHAIN_VERIFY
/* NvmId for trusted root CA certificate */
extern const whNvmId hsmClientNvmIdCertRootCA;
#endif
/* Implementation of functions provided by HAL */ /* Implementation of functions provided by HAL */
int hal_hsm_init_connect(void); int hal_hsm_init_connect(void);

View File

@ -79,6 +79,7 @@ extern "C" {
#define HDR_SIGNATURE 0x20 #define HDR_SIGNATURE 0x20
#define HDR_POLICY_SIGNATURE 0x21 #define HDR_POLICY_SIGNATURE 0x21
#define HDR_SECONDARY_SIGNATURE 0x22 #define HDR_SECONDARY_SIGNATURE 0x22
#define HDR_CERT_CHAIN 0x23
#define HDR_PADDING 0xFF #define HDR_PADDING 0xFF
/* Auth Key types */ /* Auth Key types */

@ -1 +1 @@
Subproject commit ea4c3db1e05b878f39c107b375c4c57ac93ab35a Subproject commit f76701294bb8be47c7a9364a1061483c9ed7b3af

@ -1 +1 @@
Subproject commit b077c81eb635392e694ccedbab8b644297ec0285 Subproject commit 2151a1b8a1f8f81c4dba985429d50b76db7307e5

View File

@ -907,5 +907,43 @@ ifeq ($(WOLFHSM_CLIENT),1)
ifneq ($(WOLFHSM_CLIENT_LOCAL_KEYS),1) ifneq ($(WOLFHSM_CLIENT_LOCAL_KEYS),1)
KEYGEN_OPTIONS += --nolocalkeys KEYGEN_OPTIONS += --nolocalkeys
CFLAGS += -DWOLFBOOT_USE_WOLFHSM_PUBKEY_ID CFLAGS += -DWOLFBOOT_USE_WOLFHSM_PUBKEY_ID
# big enough for cert chain
CFLAGS += -DWOLFHSM_CFG_COMM_DATA_LEN=5000
endif
# Ensure wolfHSM is configured to use certificate manager if we are
# doing cert chain verification
ifneq ($(CERT_CHAIN_VERIFY),)
WOLFHSM_CLIENT_OBJS += \
$(LIBDIR)/wolfHSM/src/wh_client_cert.o \
$(LIBDIR)/wolfHSM/src/wh_message_cert.o
CFLAGS += -DWOLFHSM_CFG_CERTIFICATE_MANAGER
endif endif
endif endif
# Cert chain verification options
ifneq ($(CERT_CHAIN_VERIFY),)
CFLAGS += -DWOLFBOOT_CERT_CHAIN_VERIFY
# export the private key in DER format so it can be used with certificates
KEYGEN_OPTIONS += --der
ifneq ($(CERT_CHAIN_GEN),)
# Use dummy cert chain file if not provided (needs to be generated when keys are generated)
CERT_CHAIN_FILE = test-dummy-ca/raw_chain.der
# Set appropriate cert gen options based on sigalg
ifeq ($(SIGN),ECC256)
CERT_CHAIN_GEN_ALGO+=ecc256
endif
ifeq ($(SIGN),RSA2048)
CERT_CHAIN_GEN_ALGO+=rsa2048
endif
ifeq ($(SIGN),RSA4096)
CERT_CHAIN_GEN_ALGO+=rsa4096
endif
else
ifeq ($(CERT_CHAIN_FILE),)
$(error CERT_CHAIN_FILE must be specified when CERT_CHAIN_VERIFY is enabled and not using CERT_CHAIN_GEN)
endif
endif
SIGN_OPTIONS += --cert-chain $(CERT_CHAIN_FILE)
endif

View File

@ -55,6 +55,12 @@
/* Globals */ /* Globals */
static uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE]; static uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE];
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY) && \
defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT)
static whKeyId g_certLeafKeyId = WH_KEYID_ERASED;
static int g_leafKeyIdValid = 0;
#endif
/* TPM based verify */ /* TPM based verify */
#if defined(WOLFBOOT_TPM) && defined(WOLFBOOT_TPM_VERIFY) #if defined(WOLFBOOT_TPM) && defined(WOLFBOOT_TPM_VERIFY)
#ifdef ECC_IMAGE_SIGNATURE_SIZE #ifdef ECC_IMAGE_SIGNATURE_SIZE
@ -241,7 +247,23 @@ static void wolfBoot_verify_signature_ecc(uint8_t key_slot,
const int point_sz = ECC_IMAGE_SIGNATURE_SIZE / 2; const int point_sz = ECC_IMAGE_SIGNATURE_SIZE / 2;
/* Use the public key ID to verify the signature */ /* Use the public key ID to verify the signature */
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY)
/* If using certificate chain verification and we have a verified leaf
* key ID */
if (g_leafKeyIdValid) {
/* Use the leaf key ID from certificate verification */
ret = wh_Client_EccSetKeyId(&ecc, g_certLeafKeyId);
wolfBoot_printf(
"Using leaf cert public key (ID: %08x) for ECC verification\n",
(unsigned int)g_certLeafKeyId);
}
else {
/* Default behavior: use the pre-configured public key ID */
ret = wh_Client_EccSetKeyId(&ecc, hsmClientKeyIdPubKey);
}
#else
ret = wh_Client_EccSetKeyId(&ecc, hsmClientKeyIdPubKey); ret = wh_Client_EccSetKeyId(&ecc, hsmClientKeyIdPubKey);
#endif
if (ret != 0) { if (ret != 0) {
return; return;
} }
@ -273,6 +295,12 @@ static void wolfBoot_verify_signature_ecc(uint8_t key_slot,
img->sha_hash, WOLFBOOT_SHA_DIGEST_SIZE, &verify_res, img->sha_hash, WOLFBOOT_SHA_DIGEST_SIZE, &verify_res,
&ecc); &ecc);
} }
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY)
if (g_leafKeyIdValid) {
(void)wh_Client_KeyEvict(&hsmClientCtx, g_certLeafKeyId);
g_leafKeyIdValid = 0;
}
#endif
#else #else
/* Import public key */ /* Import public key */
ret = wc_ecc_import_unsigned(&ecc, pubkey, pubkey + point_sz, NULL, ret = wc_ecc_import_unsigned(&ecc, pubkey, pubkey + point_sz, NULL,
@ -400,7 +428,23 @@ static void wolfBoot_verify_signature_rsa(uint8_t key_slot,
#if defined(WOLFBOOT_USE_WOLFHSM_PUBKEY_ID) #if defined(WOLFBOOT_USE_WOLFHSM_PUBKEY_ID)
(void)key_slot; (void)key_slot;
/* public key is stored on server at hsmClientKeyIdPubKey*/ /* public key is stored on server at hsmClientKeyIdPubKey*/
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY)
/* If using certificate chain verification and we have a verified leaf key
* ID */
if (g_leafKeyIdValid) {
/* Use the leaf key ID from certificate verification */
ret = wh_Client_RsaSetKeyId(&rsa, g_certLeafKeyId);
wolfBoot_printf(
"Using leaf cert public key (ID: %08x) for RSA verification\n",
(unsigned int)g_certLeafKeyId);
}
else {
/* Default behavior: use the pre-configured public key ID */
ret = wh_Client_RsaSetKeyId(&rsa, hsmClientKeyIdPubKey);
}
#else
ret = wh_Client_RsaSetKeyId(&rsa, hsmClientKeyIdPubKey); ret = wh_Client_RsaSetKeyId(&rsa, hsmClientKeyIdPubKey);
#endif
if (ret != 0) { if (ret != 0) {
return; return;
} }
@ -426,6 +470,11 @@ static void wolfBoot_verify_signature_rsa(uint8_t key_slot,
if (WH_ERROR_OK != wh_Client_KeyEvict(&hsmClientCtx, hsmKeyId)) { if (WH_ERROR_OK != wh_Client_KeyEvict(&hsmClientCtx, hsmKeyId)) {
return; return;
} }
#elif defined(WOLFBOOT_CERT_CHAIN_VERIFY)
if (g_leafKeyIdValid) {
(void)wh_Client_KeyEvict(&hsmClientCtx, g_certLeafKeyId);
g_leafKeyIdValid = 0;
}
#endif /* !WOLFBOOT_USE_WOLFHSM_PUBKEY_ID */ #endif /* !WOLFBOOT_USE_WOLFHSM_PUBKEY_ID */
#else #else
/* wolfCrypt software RSA verify */ /* wolfCrypt software RSA verify */
@ -644,7 +693,23 @@ static void wolfBoot_verify_signature_ml_dsa(uint8_t key_slot,
#if defined WOLFBOOT_ENABLE_WOLFHSM_CLIENT && \ #if defined WOLFBOOT_ENABLE_WOLFHSM_CLIENT && \
defined(WOLFBOOT_USE_WOLFHSM_PUBKEY_ID) defined(WOLFBOOT_USE_WOLFHSM_PUBKEY_ID)
/* Use key slot ID directly with wolfHSM */ /* Use key slot ID directly with wolfHSM */
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY)
/* If using certificate chain verification and we have a verified leaf key
* ID */
if (g_leafKeyIdValid) {
/* Use the leaf key ID from certificate verification */
ret = wh_Client_MlDsaSetKeyId(&ml_dsa, g_certLeafKeyId);
wolfBoot_printf(
"Using leaf cert public key (ID: %08x) for ML-DSA verification\n",
(unsigned int)g_certLeafKeyId);
}
else {
/* Default behavior: use the pre-configured public key ID */
ret = wh_Client_MlDsaSetKeyId(&ml_dsa, hsmClientKeyIdPubKey);
}
#else
ret = wh_Client_MlDsaSetKeyId(&ml_dsa, hsmClientKeyIdPubKey); ret = wh_Client_MlDsaSetKeyId(&ml_dsa, hsmClientKeyIdPubKey);
#endif
if (ret != 0) { if (ret != 0) {
wolfBoot_printf("error: wh_Client_MlDsaSetKeyId returned %d\n", ret); wolfBoot_printf("error: wh_Client_MlDsaSetKeyId returned %d\n", ret);
} }
@ -1901,6 +1966,16 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img)
uint32_t key_mask = 0U; uint32_t key_mask = 0U;
uint32_t image_part = 1U; uint32_t image_part = 1U;
int key_slot; int key_slot;
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY) && \
defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT)
uint8_t* cert_chain;
uint16_t cert_chain_size;
int32_t cert_verify_result;
int hsm_ret;
/* Reset certificate chain usage for this verification */
g_leafKeyIdValid = 0;
#endif
stored_signature_size = get_header(img, HDR_SIGNATURE, &stored_signature); stored_signature_size = get_header(img, HDR_SIGNATURE, &stored_signature);
pubkey_hint_size = get_header(img, HDR_PUBKEY, &pubkey_hint); pubkey_hint_size = get_header(img, HDR_PUBKEY, &pubkey_hint);
@ -1955,6 +2030,37 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img)
CONFIRM_MASK_VALID(image_part, key_mask); CONFIRM_MASK_VALID(image_part, key_mask);
#if defined(WOLFBOOT_CERT_CHAIN_VERIFY) && \
defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT)
/* Check for certificate chain in the image header */
cert_chain_size = get_header(img, HDR_CERT_CHAIN, &cert_chain);
if (cert_chain_size > 0) {
wolfBoot_printf("Found certificate chain (%d bytes)\n",
cert_chain_size);
/* Verify certificate chain using wolfHSM's verification API */
hsm_ret = wh_Client_CertVerifyAndCacheLeafPubKey(
&hsmClientCtx, cert_chain, cert_chain_size,
hsmClientNvmIdCertRootCA, &g_certLeafKeyId, &cert_verify_result);
/* Error or verification failure results in standard auth check failure
* path */
if (hsm_ret != 0 || cert_verify_result != 0) {
wolfBoot_printf("Certificate chain verification failed: "
"hsm_ret=%d, verify_result=%d\n",
hsm_ret, cert_verify_result);
return -1;
}
wolfBoot_printf("Certificate chain verified, using leaf key ID: %08x\n",
(unsigned int)g_certLeafKeyId);
/* Set flag to use the leaf certificate's public key for signature
* verification later */
g_leafKeyIdValid = 1;
}
#endif
/* wolfBoot_verify_signature_ecc() does not return the result directly. /* wolfBoot_verify_signature_ecc() does not return the result directly.
* A call to wolfBoot_image_confirm_signature_ok() is required in order to * A call to wolfBoot_image_confirm_signature_ok() is required in order to
* confirm that the signature verification is OK. * confirm that the signature verification is OK.

View File

@ -158,6 +158,7 @@ static inline int fp_truncate(FILE *f, size_t len)
#define HDR_SIGNATURE 0x20 #define HDR_SIGNATURE 0x20
#define HDR_POLICY_SIGNATURE 0x21 #define HDR_POLICY_SIGNATURE 0x21
#define HDR_SECONDARY_SIGNATURE 0x22 #define HDR_SECONDARY_SIGNATURE 0x22
#define HDR_CERT_CHAIN 0x23
#define HDR_SHA256_LEN 32 #define HDR_SHA256_LEN 32
@ -265,6 +266,7 @@ struct cmd_options {
const char *policy_file; const char *policy_file;
const char *encrypt_key_file; const char *encrypt_key_file;
const char *delta_base_file; const char *delta_base_file;
const char *cert_chain_file;
int no_base_sha; int no_base_sha;
char output_image_file[PATH_MAX]; char output_image_file[PATH_MAX];
char output_diff_file[PATH_MAX]; char output_diff_file[PATH_MAX];
@ -356,6 +358,10 @@ static int load_key_ecc(int sign_type, uint32_t curve_sz, int curve_id,
*pubkey_sz = curve_sz * 2; *pubkey_sz = curve_sz * 2;
*pubkey = malloc(*pubkey_sz); /* assume malloc works */ *pubkey = malloc(*pubkey_sz); /* assume malloc works */
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
return -1;
}
initRet = ret = wc_ecc_init(&key.ecc); initRet = ret = wc_ecc_init(&key.ecc);
if (CMD.manual_sign || CMD.sha_only) { if (CMD.manual_sign || CMD.sha_only) {
/* raw (public x + public y) */ /* raw (public x + public y) */
@ -425,8 +431,10 @@ static int load_key_ecc(int sign_type, uint32_t curve_sz, int curve_id,
if (ret != 0 && initRet == 0) { if (ret != 0 && initRet == 0) {
wc_ecc_free(&key.ecc); wc_ecc_free(&key.ecc);
} }
if (ret != 0) if (ret != 0) {
free(*pubkey); free(*pubkey);
*pubkey = NULL;
}
if (ret == 0 || CMD.sign != SIGN_AUTO) { if (ret == 0 || CMD.sign != SIGN_AUTO) {
if (CMD.header_sz < header_sz) if (CMD.header_sz < header_sz)
@ -455,9 +463,14 @@ static int load_key_rsa(int sign_type, uint32_t rsa_keysz, uint32_t rsa_pubkeysz
uint32_t keySzOut = 0; uint32_t keySzOut = 0;
if (CMD.manual_sign || CMD.sha_only) { if (CMD.manual_sign || CMD.sha_only) {
/* use public key directly */ /* Allocate and copy pubkey instead of using key_buffer directly */
*pubkey = *key_buffer;
*pubkey_sz = *key_buffer_sz; *pubkey_sz = *key_buffer_sz;
*pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
return -1;
}
memcpy(*pubkey, *key_buffer, *pubkey_sz);
if (*pubkey_sz <= rsa_pubkeysz) { if (*pubkey_sz <= rsa_pubkeysz) {
CMD.header_sz = header_sz; CMD.header_sz = header_sz;
@ -484,8 +497,18 @@ static int load_key_rsa(int sign_type, uint32_t rsa_keysz, uint32_t rsa_pubkeysz
} }
if (ret > 0) { if (ret > 0) {
*pubkey = *key_buffer; /* Allocate and copy pubkey instead of using key_buffer directly */
*pubkey_sz = ret; *pubkey_sz = ret;
*pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
ret = -1;
if (initRet == 0) {
wc_FreeRsaKey(&key.rsa);
}
return -1;
}
memcpy(*pubkey, *key_buffer, *pubkey_sz);
ret = 0; ret = 0;
} }
@ -565,6 +588,10 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
initRet = -1; initRet = -1;
*pubkey_sz = ED25519_PUB_KEY_SIZE; *pubkey_sz = ED25519_PUB_KEY_SIZE;
*pubkey = malloc(*pubkey_sz); *pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
if (CMD.manual_sign || CMD.sha_only) { if (CMD.manual_sign || CMD.sha_only) {
/* raw */ /* raw */
@ -628,6 +655,10 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
initRet = -1; initRet = -1;
*pubkey_sz = ED448_PUB_KEY_SIZE; *pubkey_sz = ED448_PUB_KEY_SIZE;
*pubkey = malloc(*pubkey_sz); *pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
if (CMD.manual_sign || CMD.sha_only) { if (CMD.manual_sign || CMD.sha_only) {
/* raw */ /* raw */
@ -743,16 +774,26 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
if (*key_buffer_sz == (HSS_MAX_PRIVATE_KEY_LEN + if (*key_buffer_sz == (HSS_MAX_PRIVATE_KEY_LEN +
KEYSTORE_PUBKEY_SIZE_LMS)) { KEYSTORE_PUBKEY_SIZE_LMS)) {
/* priv + pub */ /* priv + pub */
*pubkey = (*key_buffer) + HSS_MAX_PRIVATE_KEY_LEN; *pubkey_sz = KEYSTORE_PUBKEY_SIZE_LMS;
*pubkey_sz = (*key_buffer_sz) - HSS_MAX_PRIVATE_KEY_LEN; *pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
memcpy(*pubkey, (*key_buffer) + HSS_MAX_PRIVATE_KEY_LEN, *pubkey_sz);
ret = 0; ret = 0;
printf("Found LMS key\n"); printf("Found LMS key\n");
break; break;
} }
else if (*key_buffer_sz == KEYSTORE_PUBKEY_SIZE_LMS) { else if (*key_buffer_sz == KEYSTORE_PUBKEY_SIZE_LMS) {
/* pub only */ /* pub only */
*pubkey = (*key_buffer);
*pubkey_sz = KEYSTORE_PUBKEY_SIZE_LMS; *pubkey_sz = KEYSTORE_PUBKEY_SIZE_LMS;
*pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
memcpy(*pubkey, *key_buffer, *pubkey_sz);
ret = 0; ret = 0;
printf("Found LMS public only key\n"); printf("Found LMS public only key\n");
break; break;
@ -791,16 +832,26 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
if (*key_buffer_sz == (priv_sz + KEYSTORE_PUBKEY_SIZE_XMSS)) { if (*key_buffer_sz == (priv_sz + KEYSTORE_PUBKEY_SIZE_XMSS)) {
/* priv + pub */ /* priv + pub */
*pubkey = (*key_buffer) + priv_sz; *pubkey_sz = KEYSTORE_PUBKEY_SIZE_XMSS;
*pubkey_sz = (*key_buffer_sz) - priv_sz; *pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
memcpy(*pubkey, (*key_buffer) + priv_sz, *pubkey_sz);
ret = 0; ret = 0;
printf("Found XMSS key\n"); printf("Found XMSS key\n");
break; break;
} }
else if (*key_buffer_sz == KEYSTORE_PUBKEY_SIZE_XMSS) { else if (*key_buffer_sz == KEYSTORE_PUBKEY_SIZE_XMSS) {
/* pub only */ /* pub only */
*pubkey = (*key_buffer);
*pubkey_sz = KEYSTORE_PUBKEY_SIZE_XMSS; *pubkey_sz = KEYSTORE_PUBKEY_SIZE_XMSS;
*pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
memcpy(*pubkey, *key_buffer, *pubkey_sz);
ret = 0; ret = 0;
printf("Found XMSS public only key\n"); printf("Found XMSS public only key\n");
break; break;
@ -844,16 +895,26 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz,
/* priv + pub */ /* priv + pub */
ret = wc_MlDsaKey_ImportPrivRaw(&key.ml_dsa, *key_buffer, ret = wc_MlDsaKey_ImportPrivRaw(&key.ml_dsa, *key_buffer,
priv_sz); priv_sz);
*pubkey = (*key_buffer) + priv_sz; *pubkey_sz = pub_sz;
*pubkey_sz = (*key_buffer_sz) - priv_sz; *pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
memcpy(*pubkey, (*key_buffer) + priv_sz, *pubkey_sz);
ret = 0; ret = 0;
printf("Found ml-dsa key\n"); printf("Found ml-dsa key\n");
break; break;
} }
else if (*key_buffer_sz == pub_sz) { else if (*key_buffer_sz == pub_sz) {
/* pub only */ /* pub only */
*pubkey = (*key_buffer);
*pubkey_sz = pub_sz; *pubkey_sz = pub_sz;
*pubkey = malloc(*pubkey_sz);
if (*pubkey == NULL) {
printf("Pubkey malloc error!\n");
goto failure;
}
memcpy(*pubkey, *key_buffer, *pubkey_sz);
ret = 0; ret = 0;
printf("Found ml-dsa public only key\n"); printf("Found ml-dsa public only key\n");
break; break;
@ -1066,6 +1127,47 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
uint32_t digest_sz = 0; uint32_t digest_sz = 0;
uint32_t image_sz = 0; uint32_t image_sz = 0;
int io_sz; int io_sz;
uint8_t* cert_chain = NULL;
uint32_t cert_chain_sz = 0;
/* Check certificate chain file size before allocating header, and adjust
* header size if needed */
if (CMD.cert_chain_file != NULL) {
struct stat file_stat;
/* Get the file size */
if (stat(CMD.cert_chain_file, &file_stat) == 0) {
cert_chain_sz = file_stat.st_size;
/* 2 bytes for tag + 2 bytes for length field */
const uint32_t tag_len_size = 4;
/* Maximum alignment padding that might be needed */
const uint32_t max_alignment = 8;
/* Required space = tag(2) + length(2) + data + potential alignment
* * padding */
const uint32_t required_space =
tag_len_size + cert_chain_sz + max_alignment;
/* If the current header size is too small, increase it */
if (CMD.header_sz < required_space) {
/* Round up to nearest power of 2 that can hold the chain */
const uint32_t min_header_size = 256;
uint32_t new_size = min_header_size;
while (new_size < required_space) {
new_size *= 2;
}
printf("Increasing header size from %u to %u bytes to fit "
"certificate chain\n",
CMD.header_sz, new_size);
CMD.header_sz = new_size;
}
}
else {
printf("Warning: Could not stat certificate chain file %s: %s\n",
CMD.cert_chain_file, strerror(errno));
}
}
header_idx = 0; header_idx = 0;
header = malloc(CMD.header_sz); header = malloc(CMD.header_sz);
@ -1183,6 +1285,64 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
} }
} }
/* Read certificate chain if provided */
if (CMD.cert_chain_file != NULL) {
const size_t cert_chain_tlv_hdr_sz = 4;
struct stat file_stat;
f = fopen(CMD.cert_chain_file, "rb");
if (f == NULL) {
printf("Open certificate chain file %s failed: %s\n",
CMD.cert_chain_file, strerror(errno));
goto failure;
}
/* Get the file size */
if (stat(CMD.cert_chain_file, &file_stat) != 0) {
printf("Could not get certificate chain file size: %s\n",
strerror(errno));
fclose(f);
goto failure;
}
cert_chain_sz = file_stat.st_size;
/* Verify that the chain will fit in our header */
if (header_idx + cert_chain_tlv_hdr_sz + cert_chain_sz >
CMD.header_sz) {
printf("Error: Certificate chain too large for header (%u bytes "
"needed, %u available)\n",
(unsigned int)(header_idx + cert_chain_tlv_hdr_sz +
cert_chain_sz),
CMD.header_sz);
fclose(f);
goto failure;
}
cert_chain = malloc(cert_chain_sz);
if (cert_chain == NULL) {
printf("Certificate chain buffer malloc error!\n");
fclose(f);
goto failure;
}
/* Read the entire file into the buffer */
io_sz = (int)fread(cert_chain, 1, cert_chain_sz, f);
fclose(f);
if (io_sz != (int)cert_chain_sz) {
printf("Error reading certificate chain file: %s\n",
strerror(errno));
goto failure;
}
/* Append the certificate chain TLV - require 8-byte alignment */
ALIGN_8(header_idx);
header_append_tag(header, &header_idx, HDR_CERT_CHAIN, cert_chain_sz,
cert_chain);
printf("Added certificate chain (%d bytes)\n", cert_chain_sz);
}
/* Add padding bytes. Sha-3 val field requires 8-byte alignment */ /* Add padding bytes. Sha-3 val field requires 8-byte alignment */
/* The offset '4' takes into account 2B Tag + 2B Len, so that the Value /* The offset '4' takes into account 2B Tag + 2B Len, so that the Value
* starts at (addr % 8 == 0) position. * starts at (addr % 8 == 0) position.
@ -1693,10 +1853,16 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
fclose(f2); fclose(f2);
fclose(f); fclose(f);
failure: failure:
if (cert_chain)
free(cert_chain);
if (policy) if (policy)
free(policy); free(policy);
if (header) if (header)
free(header); free(header);
if (signature)
free(signature);
if (secondary_signature)
free(secondary_signature);
return ret; return ret;
} }
@ -2587,6 +2753,13 @@ int main(int argc, char** argv)
CMD.custom_tlvs++; CMD.custom_tlvs++;
i += 2; i += 2;
} }
else if (strcmp(argv[i], "--cert-chain") == 0) {
if (argc <= (i + 1)) {
fprintf(stderr, "Missing certificate chain file argument\n");
exit(16);
}
CMD.cert_chain_file = argv[++i];
}
else { else {
i--; i--;
break; break;
@ -2746,6 +2919,8 @@ int main(int argc, char** argv)
DEBUG_PRINT("Header size: %u\n", CMD.header_sz); DEBUG_PRINT("Header size: %u\n", CMD.header_sz);
if (kbuf2) if (kbuf2)
free(kbuf2); free(kbuf2);
if (pubkey2)
free(pubkey2);
} else { } else {
make_header(pubkey, pubkey_sz, CMD.image_file, CMD.output_image_file); make_header(pubkey, pubkey_sz, CMD.image_file, CMD.output_image_file);
} }
@ -2758,6 +2933,9 @@ int main(int argc, char** argv)
ret = base_diff(CMD.delta_base_file, pubkey, pubkey_sz, 16); ret = base_diff(CMD.delta_base_file, pubkey, pubkey_sz, 16);
} }
/* Add pubkey cleanup */
if (pubkey)
free(pubkey);
if (kbuf) if (kbuf)
free(kbuf); free(kbuf);

View File

@ -0,0 +1,267 @@
#!/bin/bash
# Certificate Chain Generation Script (ECC P256 or RSA)
# Creates a certificate chain with root, intermediate, and leaf
# Outputs DER format files plus C arrays for embedding
# Optional: Use existing leaf private key with --leaf <file> argument
set -e # Exit on any error
# Default output directory and algorithm
OUTPUT_DIR="test-dummy-ca"
ALGO="ecc256" # Default to ECC P-256 keys
# Helper functions for key operations
generate_private_key() {
local output_file=$1
if [[ "$ALGO" == "ecc256" ]]; then
openssl ecparam -genkey -name prime256v1 -noout -out "$output_file"
elif [[ "$ALGO" == "rsa2048" ]]; then
openssl genrsa -out "$output_file" 2048
elif [[ "$ALGO" == "rsa4096" ]]; then
openssl genrsa -out "$output_file" 4096
fi
}
convert_key_to_der() {
local input_file=$1
local output_file=$2
if [[ "$ALGO" == "ecc256" ]]; then
openssl ec -in "$input_file" -outform DER -out "$output_file"
elif [[ "$ALGO" == "rsa2048" || "$ALGO" == "rsa4096" ]]; then
openssl rsa -in "$input_file" -outform DER -out "$output_file"
fi
}
extract_public_key() {
local cert_file=$1
local pubkey_pem=$2
local pubkey_der=$3
# Extract public key from certificate (same for both algos)
openssl x509 -in "$cert_file" -pubkey -noout > "$pubkey_pem"
# Convert public key to DER format
if [[ "$ALGO" == "ecc256" ]]; then
openssl ec -pubin -in "$pubkey_pem" -outform DER -out "$pubkey_der"
elif [[ "$ALGO" == "rsa2048" || "$ALGO" == "rsa4096" ]]; then
openssl rsa -pubin -in "$pubkey_pem" -outform DER -out "$pubkey_der"
fi
}
validate_key_format() {
local key_file=$1
if [[ "$ALGO" == "ecc256" ]]; then
openssl ec -in "$key_file" -noout
elif [[ "$ALGO" == "rsa2048" || "$ALGO" == "rsa4096" ]]; then
openssl rsa -in "$key_file" -noout
fi
}
# Parse command line arguments
LEAF_KEY_FILE=""
while [[ $# -gt 0 ]]; do
case $1 in
--leaf)
LEAF_KEY_FILE="$2"
shift 2
;;
--outdir)
OUTPUT_DIR="$2"
shift 2
;;
--algo)
ALGO="$2"
if [[ "$ALGO" != "ecc256" && "$ALGO" != "rsa2048" && "$ALGO" != "rsa4096" ]]; then
echo "Invalid algorithm: $ALGO. Use 'ecc256', 'rsa2048', or 'rsa4096'"
exit 1
fi
shift 2
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--leaf <private_key_file>] [--outdir <output_directory>] [--algo <ecc256|rsa2048|rsa4096>]"
exit 1
;;
esac
done
# Configuration
ROOT_SUBJECT="/C=US/ST=California/L=San Francisco/O=MyOrganization/OU=Root CA/CN=My Root CA"
INTERMEDIATE_SUBJECT="/C=US/ST=California/L=San Francisco/O=MyOrganization/OU=Intermediate CA/CN=My Intermediate CA"
LEAF_SUBJECT="/C=US/ST=California/L=San Francisco/O=MyOrganization/OU=Services/CN=service.example.com"
# Create directory structure
echo "Creating directory structure..."
mkdir -p ${OUTPUT_DIR}/temp
##################
# GENERATE CHAIN
##################
echo "Generating Certificate Chain using $ALGO..."
# Step 1: Generate Root key and certificate
echo "Generating Root CA..."
generate_private_key "${OUTPUT_DIR}/temp/root.key.pem"
# Create PEM format root certificate (temporary)
openssl req -new -x509 -days 3650 -sha256 \
-key ${OUTPUT_DIR}/temp/root.key.pem \
-out ${OUTPUT_DIR}/temp/root.crt.pem \
-subj "$ROOT_SUBJECT" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign,digitalSignature"
# Convert root key and certificate to DER format
convert_key_to_der "${OUTPUT_DIR}/temp/root.key.pem" "${OUTPUT_DIR}/root-prvkey.der"
openssl x509 -in ${OUTPUT_DIR}/temp/root.crt.pem -outform DER -out ${OUTPUT_DIR}/root-cert.der
# Step 2: Generate Intermediate key and CSR
echo "Generating Intermediate CA..."
generate_private_key "${OUTPUT_DIR}/temp/intermediate.key.pem"
openssl req -new -sha256 \
-key ${OUTPUT_DIR}/temp/intermediate.key.pem \
-out ${OUTPUT_DIR}/temp/intermediate.csr \
-subj "$INTERMEDIATE_SUBJECT"
# Step 3: Sign Intermediate certificate with Root
openssl x509 -req -days 1825 -sha256 \
-in ${OUTPUT_DIR}/temp/intermediate.csr \
-out ${OUTPUT_DIR}/temp/intermediate.crt.pem \
-CA ${OUTPUT_DIR}/temp/root.crt.pem \
-CAkey ${OUTPUT_DIR}/temp/root.key.pem \
-CAcreateserial \
-extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign,digitalSignature")
# Convert intermediate key and certificate to DER format
convert_key_to_der "${OUTPUT_DIR}/temp/intermediate.key.pem" "${OUTPUT_DIR}/intermediate-prvkey.der"
openssl x509 -in ${OUTPUT_DIR}/temp/intermediate.crt.pem -outform DER -out ${OUTPUT_DIR}/intermediate-cert.der
# Step 4: Handle Leaf key (generate or use existing)
echo "Handling Leaf Certificate..."
if [ -z "$LEAF_KEY_FILE" ]; then
echo "Generating new leaf private key..."
generate_private_key "${OUTPUT_DIR}/temp/leaf.key.pem"
else
echo "Using provided leaf private key: $LEAF_KEY_FILE"
cp "$LEAF_KEY_FILE" ${OUTPUT_DIR}/temp/leaf.key.pem
# Ensure the key file is in the right format
validate_key_format "${OUTPUT_DIR}/temp/leaf.key.pem"
fi
# Create CSR for leaf certificate
openssl req -new -sha256 \
-key ${OUTPUT_DIR}/temp/leaf.key.pem \
-out ${OUTPUT_DIR}/temp/leaf.csr \
-subj "$LEAF_SUBJECT"
# Step 5: Sign Leaf certificate with Intermediate
openssl x509 -req -days 365 -sha256 \
-in ${OUTPUT_DIR}/temp/leaf.csr \
-out ${OUTPUT_DIR}/temp/leaf.crt.pem \
-CA ${OUTPUT_DIR}/temp/intermediate.crt.pem \
-CAkey ${OUTPUT_DIR}/temp/intermediate.key.pem \
-CAcreateserial \
-extfile <(printf "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth")
# Convert leaf key and certificate to DER format
convert_key_to_der "${OUTPUT_DIR}/temp/leaf.key.pem" "${OUTPUT_DIR}/leaf-prvkey.der"
openssl x509 -in ${OUTPUT_DIR}/temp/leaf.crt.pem -outform DER -out ${OUTPUT_DIR}/leaf-cert.der
# Extract the public key from leaf certificate in DER format
echo "Extracting public key from leaf certificate..."
extract_public_key "${OUTPUT_DIR}/temp/leaf.crt.pem" "${OUTPUT_DIR}/temp/leaf_pubkey.pem" "${OUTPUT_DIR}/leaf-pubkey.der"
# Create raw DER format certificate chain
cat ${OUTPUT_DIR}/intermediate-cert.der ${OUTPUT_DIR}/leaf-cert.der > ${OUTPUT_DIR}/raw_chain.der
##################################
# GENERATE C ARRAYS FOR EMBEDDING
##################################
echo "Generating C arrays for embedding in programs..."
# Create a header file for certificates
HEADER_FILE="${OUTPUT_DIR}/gen_certificates.h"
# Initialize the header file with header guards and includes
cat > "${HEADER_FILE}" << 'EOT'
/*
* Certificate arrays for embedded SSL/TLS applications
* Generated by OpenSSL certificate chain script
*/
#ifndef GEN_CERTIFICATES_H
#define GEN_CERTIFICATES_H
#include <stddef.h>
EOT
# Function to append a certificate array to the header file
append_cert_array() {
local infile=$1
local arrayname=$2
local description=$3
echo "/* ${description} */" >> "${HEADER_FILE}"
echo "const unsigned char ${arrayname}[] = {" >> "${HEADER_FILE}"
# Use xxd instead of hexdump for more reliable output
xxd -i < "${infile}" | grep -v "unsigned char" | grep -v "unsigned int" | \
sed 's/ 0x/0x/g' >> "${HEADER_FILE}"
echo "};" >> "${HEADER_FILE}"
echo "const size_t ${arrayname}_len = sizeof(${arrayname});" >> "${HEADER_FILE}"
echo "" >> "${HEADER_FILE}"
}
### Add certificates to the header file
echo "/* Certificates */" >> "${HEADER_FILE}"
append_cert_array "${OUTPUT_DIR}/root-cert.der" "ROOT_CERT" "Root CA Certificate (DER format)"
append_cert_array "${OUTPUT_DIR}/intermediate-cert.der" "INTERMEDIATE_CERT" "Intermediate CA Certificate (DER format)"
append_cert_array "${OUTPUT_DIR}/leaf-cert.der" "LEAF_CERT" "Leaf/Server Certificate (DER format)"
append_cert_array "${OUTPUT_DIR}/raw_chain.der" "RAW_CERT_CHAIN" "Raw Certificate Chain (Intermediate+Leaf) (DER format)"
# Add leaf certificate public key
append_cert_array "${OUTPUT_DIR}/leaf-pubkey.der" "LEAF_PUBKEY" "Leaf Certificate Public Key (DER format)"
# Close the header guard
echo "#endif /* GEN_CERTIFICATES_H */" >> "${HEADER_FILE}"
echo "Generated C header file with certificate arrays: ${HEADER_FILE}"
# Display verification information
echo ""
echo "=== Certificate Chain Generation Complete ==="
echo ""
# Verify Chain
echo "=== Verifying Certificate Chain ==="
echo "Verifying intermediate certificate against root:"
openssl verify -CAfile ${OUTPUT_DIR}/temp/root.crt.pem ${OUTPUT_DIR}/temp/intermediate.crt.pem
echo ""
echo "Verifying leaf certificate against intermediate and root:"
openssl verify -CAfile ${OUTPUT_DIR}/temp/root.crt.pem -untrusted ${OUTPUT_DIR}/temp/intermediate.crt.pem ${OUTPUT_DIR}/temp/leaf.crt.pem
# Display generated files summary
echo ""
echo "=== Generated Files Summary ==="
echo ""
echo "DER Format (Algorithm: $ALGO):"
echo " Root CA certificate: ${OUTPUT_DIR}/root-cert.der"
echo " Root CA key: ${OUTPUT_DIR}/root-prvkey.der"
echo " Intermediate certificate: ${OUTPUT_DIR}/intermediate-cert.der"
echo " Intermediate key: ${OUTPUT_DIR}/intermediate-prvkey.der"
echo " Leaf certificate: ${OUTPUT_DIR}/leaf-cert.der"
echo " Leaf key: ${OUTPUT_DIR}/leaf-prvkey.der"
echo " Raw chain: ${OUTPUT_DIR}/raw_chain.der"
echo " Leaf public key: ${OUTPUT_DIR}/leaf-pubkey.der"
echo ""
echo "C Header file:"
echo " Certificate arrays: ${OUTPUT_DIR}/gen_certificates.h"
# Clean up temporary files
rm -rf ${OUTPUT_DIR}/temp ${OUTPUT_DIR}/root.srl ${OUTPUT_DIR}/intermediate.srl

View File

@ -5,7 +5,6 @@ EXPVER_CMD=$(EXPVER) /dev/ttyAMA0
BINASSEMBLE=tools/bin-assemble/bin-assemble BINASSEMBLE=tools/bin-assemble/bin-assemble
SPI_CHIP=SST25VF080B SPI_CHIP=SST25VF080B
SPI_OPTIONS=SPI_FLASH=1 WOLFBOOT_PARTITION_SIZE=0x80000 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00000 WOLFBOOT_PARTITION_SWAP_ADDRESS=0x80000 SPI_OPTIONS=SPI_FLASH=1 WOLFBOOT_PARTITION_SIZE=0x80000 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00000 WOLFBOOT_PARTITION_SWAP_ADDRESS=0x80000
SIGN_ARGS=
SIGN_ENC_ARGS= SIGN_ENC_ARGS=
DELTA_DATA_SIZE?=2000 DELTA_DATA_SIZE?=2000
@ -21,49 +20,6 @@ else
SIGN_TOOL="$(WOLFBOOT_ROOT)/tools/keytools/sign" SIGN_TOOL="$(WOLFBOOT_ROOT)/tools/keytools/sign"
endif endif
# Make sign algorithm argument
ifeq ($(SIGN),NONE)
SIGN_ARGS+=--no-sign
endif
ifeq ($(SIGN),ED25519)
SIGN_ARGS+= --ed25519
endif
ifeq ($(SIGN),ED448)
SIGN_ARGS+= --ed448
endif
ifeq ($(SIGN),ECC256)
SIGN_ARGS+= --ecc256
endif
ifeq ($(SIGN),RSA2048)
SIGN_ARGS+= --rsa2048
endif
ifeq ($(SIGN),RSA3072)
SIGN_ARGS+= --rsa3072
endif
ifeq ($(SIGN),RSA4096)
SIGN_ARGS+= --rsa4096
endif
ifeq ($(SIGN),LMS)
SIGN_ARGS+= --lms
endif
ifeq ($(SIGN),XMSS)
SIGN_ARGS+= --xmss
endif
ifeq ($(SIGN),ML_DSA)
SIGN_ARGS+= --ml_dsa
endif
# Make sign hash argument
ifeq ($(HASH),SHA256)
SIGN_ARGS+= --sha256
endif
ifeq ($(HASH),SHA384)
SIGN_ARGS+= --sha384
endif
ifeq ($(HASH),SHA3)
SIGN_ARGS+= --sha3
endif
ifeq ($(FLAGS_INVERT),1) ifeq ($(FLAGS_INVERT),1)
INVERSION= INVERSION=
else else
@ -138,7 +94,7 @@ test-spi-off: FORCE
test-update: test-app/image.bin FORCE test-update: test-app/image.bin FORCE
@dd if=/dev/zero bs=131067 count=1 2>/dev/null $(INVERSION) > test-update.bin @dd if=/dev/zero bs=131067 count=1 2>/dev/null $(INVERSION) > test-update.bin
@$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) test-app/image.bin $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) @$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.bin $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
@dd if=test-app/image_v$(TEST_UPDATE_VERSION)_signed.bin of=test-update.bin bs=1 conv=notrunc @dd if=test-app/image_v$(TEST_UPDATE_VERSION)_signed.bin of=test-update.bin bs=1 conv=notrunc
@printf "pBOOT" >> test-update.bin @printf "pBOOT" >> test-update.bin
@make test-reset @make test-reset
@ -174,7 +130,7 @@ test-sim-external-flash-with-enc-delta-update-extradata: wolfboot.bin test-app/i
$(Q)make -C test-app delta-extra-data DELTA_DATA_SIZE=$(DELTA_DATA_SIZE) $(Q)make -C test-app delta-extra-data DELTA_DATA_SIZE=$(DELTA_DATA_SIZE)
$(Q)cp test-app/image_v1_signed.bak test-app/image_v1_signed.bin $(Q)cp test-app/image_v1_signed.bak test-app/image_v1_signed.bin
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(SIGN_ENC_ARGS) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(SIGN_ENC_ARGS) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) $(DELTA_UPDATE_OPTIONS) $(SIGN_ENC_ARGS) \ $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(DELTA_UPDATE_OPTIONS) $(SIGN_ENC_ARGS) \
test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null $(INVERSION) > v1_part.dd $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_PARTITION_SIZE))) count=1 2>/dev/null $(INVERSION) > v1_part.dd
$(Q)dd if=test-app/image_v1_signed.bin bs=256 of=v1_part.dd conv=notrunc $(Q)dd if=test-app/image_v1_signed.bin bs=256 of=v1_part.dd conv=notrunc
@ -192,11 +148,16 @@ test-sim-external-flash-with-enc-update: wolfboot.bin test-app/image.elf FORCE
$(Q)cp test-app/image.elf test-app/image.bak.elf $(Q)cp test-app/image.elf test-app/image.bak.elf
$(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc $(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc
@printf "0123456789abcdef0123456789abcdef0123456789abcdef" > /tmp/enc_key.der @printf "0123456789abcdef0123456789abcdef0123456789abcdef" > /tmp/enc_key.der
# First sign command: Create version 1 of the encrypted application (base image)
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(SIGN_ENC_ARGS) test-app/image.elf $(PRIVATE_KEY) 1 $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(SIGN_ENC_ARGS) test-app/image.elf $(PRIVATE_KEY) 1
$(Q)cp test-app/image.bak.elf test-app/image.elf $(Q)cp test-app/image.bak.elf test-app/image.elf
$(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc $(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc
# Second sign command: Create a full encrypted update (version 2 by default)
# This produces image_v2_signed_and_encrypted.bin which is needed for the first flash assembly step
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(SIGN_ENC_ARGS) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(SIGN_ENC_ARGS) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) $(DELTA_UPDATE_OPTIONS) $(SIGN_ENC_ARGS) \ # Third sign command: Create update with delta option (if specified), producing image_v2_signed_diff_encrypted.bin
# This file is used by the test-sim-external-flash-with-enc-delta-update target
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(DELTA_UPDATE_OPTIONS) $(SIGN_ENC_ARGS) \
test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
# Assembling internal flash image # Assembling internal flash image
# #
@ -210,6 +171,9 @@ test-sim-external-flash-with-enc-update: wolfboot.bin test-app/image.elf FORCE
$(WOLFBOOT_PARTITION_SIZE) erased_sec.dd $(WOLFBOOT_PARTITION_SIZE) erased_sec.dd
test-sim-external-flash-with-enc-delta-update: test-sim-external-flash-with-enc-delta-update:
# This target first calls test-sim-external-flash-with-enc-update to generate both
# image_v2_signed_and_encrypted.bin (full update) and image_v2_signed_diff_encrypted.bin (delta update)
# Then it rebuilds the external flash image using the delta update version
make test-sim-external-flash-with-enc-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin" make test-sim-external-flash-with-enc-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin"
$(Q)$(BINASSEMBLE) external_flash.dd 0 test-app/image_v$(TEST_UPDATE_VERSION)_signed_diff_encrypted.bin \ $(Q)$(BINASSEMBLE) external_flash.dd 0 test-app/image_v$(TEST_UPDATE_VERSION)_signed_diff_encrypted.bin \
$(WOLFBOOT_PARTITION_SIZE) erased_sec.dd $(WOLFBOOT_PARTITION_SIZE) erased_sec.dd
@ -217,12 +181,17 @@ test-sim-external-flash-with-enc-delta-update:
test-sim-internal-flash-with-update: wolfboot.bin test-app/image.elf FORCE test-sim-internal-flash-with-update: wolfboot.bin test-app/image.elf FORCE
$(Q)cp test-app/image.elf test-app/image.bak.elf $(Q)cp test-app/image.elf test-app/image.bak.elf
$(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc $(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc
# Create version 1 of the application (base image)
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.elf $(PRIVATE_KEY) 1 $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.elf $(PRIVATE_KEY) 1
$(Q)cp test-app/image.bak.elf test-app/image.elf $(Q)cp test-app/image.bak.elf test-app/image.elf
$(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc $(Q)dd if=/dev/urandom of=test-app/image.elf bs=1k count=16 oflag=append conv=notrunc
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) $(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
$(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null $(INVERSION) > erased_sec.dd $(Q)dd if=/dev/zero bs=$$(($(WOLFBOOT_SECTOR_SIZE))) count=1 2>/dev/null $(INVERSION) > erased_sec.dd
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) $(DELTA_UPDATE_OPTIONS) \ # Sign the update image (version 2 by default)
# This command handles both standard and delta update modes based on DELTA_UPDATE_OPTIONS
# empty DELTA_UPDATE_OPTIONS (Without --delta): Produces image_v2_signed.bin
# DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin": Produces image_v2_signed_diff.bin
$(Q)$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) $(DELTA_UPDATE_OPTIONS) \
test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) test-app/image.elf $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
$(Q)$(BINASSEMBLE) internal_flash.dd \ $(Q)$(BINASSEMBLE) internal_flash.dd \
0 wolfboot.bin \ 0 wolfboot.bin \
@ -231,6 +200,9 @@ test-sim-internal-flash-with-update: wolfboot.bin test-app/image.elf FORCE
$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) erased_sec.dd $$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) erased_sec.dd
test-sim-internal-flash-with-delta-update: test-sim-internal-flash-with-delta-update:
# This target calls test-sim-internal-flash-with-update with the delta option
# The delta option causes the sign tool to produce image_v2_signed_diff.bin instead of image_v2_signed.bin
# Then it rebuilds the internal flash image using the delta update version
make test-sim-internal-flash-with-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin" make test-sim-internal-flash-with-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin"
$(Q)$(BINASSEMBLE) internal_flash.dd \ $(Q)$(BINASSEMBLE) internal_flash.dd \
0 wolfboot.bin \ 0 wolfboot.bin \
@ -247,6 +219,9 @@ test-sim-internal-flash-with-delta-update-no-base-sha:
$$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) erased_sec.dd $$(($(WOLFBOOT_PARTITION_SWAP_ADDRESS)-$(ARCH_FLASH_OFFSET))) erased_sec.dd
test-sim-internal-flash-with-wrong-delta-update: test-sim-internal-flash-with-wrong-delta-update:
# This target tests the bootloader's ability to reject delta updates with wrong base hashes
# First it creates a delta update based on v1, then creates a different delta update based on v2
# The final image contains v1 as the base image but a delta update that expects v2 as its base
make test-sim-internal-flash-with-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin" make test-sim-internal-flash-with-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v1_signed.bin"
make test-sim-internal-flash-with-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v2_signed.bin" TEST_UPDATE_VERSION=3 make test-sim-internal-flash-with-update DELTA_UPDATE_OPTIONS="--delta test-app/image_v2_signed.bin" TEST_UPDATE_VERSION=3
$(Q)$(BINASSEMBLE) internal_flash.dd \ $(Q)$(BINASSEMBLE) internal_flash.dd \
@ -268,12 +243,12 @@ test-sim-rollback-flash: wolfboot.elf test-sim-internal-flash-with-update FORCE
test-self-update: FORCE test-self-update: FORCE
@mv $(PRIVATE_KEY) private_key.old @mv $(PRIVATE_KEY) private_key.old
@make clean factory.bin RAM_CODE=1 WOLFBOOT_VERSION=1 SIGN=$(SIGN) @make clean factory.bin RAM_CODE=1 WOLFBOOT_VERSION=1 SIGN=$(SIGN)
@$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) test-app/image.bin $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) @$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.bin $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
@st-flash --reset write test-app/image_v2_signed.bin 0x08020000 || \ @st-flash --reset write test-app/image_v2_signed.bin 0x08020000 || \
(make test-reset && sleep 1 && st-flash --reset write test-app/image_v2_signed.bin 0x08020000) || \ (make test-reset && sleep 1 && st-flash --reset write test-app/image_v2_signed.bin 0x08020000) || \
(make test-reset && sleep 1 && st-flash --reset write test-app/image_v2_signed.bin 0x08020000) (make test-reset && sleep 1 && st-flash --reset write test-app/image_v2_signed.bin 0x08020000)
@dd if=/dev/zero bs=131067 count=1 2>/dev/null $(INVERSION) > test-self-update.bin @dd if=/dev/zero bs=131067 count=1 2>/dev/null $(INVERSION) > test-self-update.bin
@$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) --wolfboot-update wolfboot.bin private_key.old $(WOLFBOOT_VERSION) @$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) --wolfboot-update wolfboot.bin private_key.old $(WOLFBOOT_VERSION)
@dd if=wolfboot_v$(WOLFBOOT_VERSION)_signed.bin of=test-self-update.bin bs=1 conv=notrunc @dd if=wolfboot_v$(WOLFBOOT_VERSION)_signed.bin of=test-self-update.bin bs=1 conv=notrunc
@printf "pBOOT" >> test-self-update.bin @printf "pBOOT" >> test-self-update.bin
@st-flash --reset write test-self-update.bin 0x08040000 || \ @st-flash --reset write test-self-update.bin 0x08040000 || \
@ -281,7 +256,7 @@ test-self-update: FORCE
(make test-reset && sleep 1 && st-flash --reset write test-self-update.bin 0x08040000) (make test-reset && sleep 1 && st-flash --reset write test-self-update.bin 0x08040000)
test-update-ext: test-app/image.bin FORCE test-update-ext: test-app/image.bin FORCE
@$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_ARGS) test-app/image.bin $(PRIVATE_KEY) $(TEST_UPDATE_VERSION) @$(SIGN_ENV) $(SIGN_TOOL) $(SIGN_OPTIONS) test-app/image.bin $(PRIVATE_KEY) $(TEST_UPDATE_VERSION)
@(dd if=/dev/zero bs=1M count=1 | tr '\000' '\377' > test-update.rom) @(dd if=/dev/zero bs=1M count=1 | tr '\000' '\377' > test-update.rom)
@dd if=test-app/image_v$(TEST_UPDATE_VERSION)_signed.bin of=test-update.rom bs=1 count=524283 conv=notrunc @dd if=test-app/image_v$(TEST_UPDATE_VERSION)_signed.bin of=test-update.rom bs=1 count=524283 conv=notrunc
@printf "pBOOT" | dd of=test-update.rom obs=1 seek=524283 count=5 conv=notrunc @printf "pBOOT" | dd of=test-update.rom obs=1 seek=524283 count=5 conv=notrunc