mirror of https://github.com/wolfSSL/wolfBoot.git
Merge pull request #828 from bigbrett/remove-custom-tlv-limit
support for large and file-backed custom TLVspull/833/head
commit
464f1eeae5
|
|
@ -48,3 +48,51 @@ jobs:
|
|||
- name: Run get_tlv simulator test
|
||||
run: |
|
||||
[ x`./wolfboot.elf get_tlv 2>/dev/null| tail -1` = xAABBCCDDEEFF0011223344 ]
|
||||
|
||||
custom_tlv_large_simulator_tests:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/wolfssl/wolfboot-ci-sim:v1.0
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Trust workspace
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: make clean
|
||||
run: |
|
||||
make distclean
|
||||
|
||||
- name: Select config
|
||||
run: |
|
||||
cp config/examples/sim.config .config
|
||||
|
||||
- name: Build tools
|
||||
run: |
|
||||
make -C tools/keytools && make -C tools/bin-assemble
|
||||
|
||||
- name: Build wolfboot.elf and test-app/image.elf with a 2KB header
|
||||
run: |
|
||||
make clean && make IMAGE_HEADER_SIZE=2048
|
||||
|
||||
- name: Sign the image with 300-byte buffer and file custom TLVs
|
||||
run: |
|
||||
VAL=`printf 'A5%.0s' $(seq 1 300)`
|
||||
head -c 300 /dev/zero | tr '\0' '\245' > large_tlv.bin
|
||||
IMAGE_HEADER_SIZE=2048 tools/keytools/sign --ed25519 --custom-tlv-buffer 0x0034 $VAL --custom-tlv-file 0x0035 large_tlv.bin test-app/image.elf wolfboot_signing_private_key.der 1
|
||||
|
||||
- name: Re-assemble the internal_flash.dd image file
|
||||
run: |
|
||||
make assemble_internal_flash.dd IMAGE_HEADER_SIZE=2048
|
||||
|
||||
- name: Run get_tlv simulator test on the 300-byte buffer TLV
|
||||
run: |
|
||||
[ x`./wolfboot.elf get_tlv 2>/dev/null| tail -1` = x`printf 'A5%.0s' $(seq 1 300)` ]
|
||||
|
||||
- name: Run get_tlv simulator test on the 300-byte file TLV (tag 0x35)
|
||||
run: |
|
||||
[ x`./wolfboot.elf get_tlv=53 2>/dev/null| tail -1` = x`printf 'A5%.0s' $(seq 1 300)` ]
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ tools/unit-tests/unit-update-ram-enc
|
|||
tools/unit-tests/unit-update-ram-enc-nopart
|
||||
tools/unit-tests/unit-va416x0-fram
|
||||
tools/unit-tests/unit-wolfhsm_flash_hal
|
||||
tools/unit-tests/__pycache__/*
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -255,17 +255,33 @@ Provides a value to be set with a custom tag
|
|||
* `--custom-tlv-buffer tag value`: Adds a TLV entry with arbitrary length to the manifest
|
||||
header, corresponding to the type identified by `tag`, and assigns the value `value`. The
|
||||
tag is a 16-bit number. Valid tags are in the range between 0x0030 and 0xFEFE. The length
|
||||
is implicit, and is the length of the value.
|
||||
is implicit, and is the length of the value. The maximum length is 65524 bytes.
|
||||
Value argument is in the form of a hex string, e.g. `--custom-tlv-buffer 0x0030 AABBCCDDEE`
|
||||
will add a TLV entry with tag 0x0030, length 5 and value 0xAABBCCDDEE.
|
||||
|
||||
* `--custom-tlv-string tag ascii-string`: Adds a TLV entry with arbitrary length to the manifest
|
||||
header, corresponding to the type identified by `tag`, and assigns the value of `ascii-string`. The
|
||||
tag is a 16-bit number. Valid tags are in the range between 0x0030 and 0xFEFE. The length
|
||||
is implicit, and is the length of the `ascii-string`. `ascii-string` argument is in the form of a string,
|
||||
is implicit, and is the length of the `ascii-string`. The maximum length is 65524 bytes.
|
||||
`ascii-string` argument is in the form of a string,
|
||||
e.g. `--custom-tlv-string 0x0030 "Version-1"` will add a TLV entry with tag 0x0030,
|
||||
length 9 and value Version-1.
|
||||
|
||||
* `--custom-tlv-file tag filename`: Adds a TLV entry with arbitrary length to the manifest
|
||||
header, corresponding to the type identified by `tag`, with the value read as raw bytes
|
||||
from the file `filename`. The tag is a 16-bit number. Valid tags are in the range between
|
||||
0x0030 and 0xFEFE. The length is implicit, and is the size of the file. The maximum length
|
||||
is 65524 bytes. Unlike `--custom-tlv-buffer`, the value is not passed on the command line,
|
||||
so large binary values are not subject to the OS argument length limits.
|
||||
|
||||
The 65524-byte maximum is the largest TLV value the wolfBoot header parser can walk
|
||||
past when locating the fields that follow it, such as the signature.
|
||||
|
||||
If the custom TLVs do not fit in the configured header size, the sign tool automatically
|
||||
increases the size of the manifest header, rounding up to the next power of two. wolfBoot
|
||||
must be built with a matching `IMAGE_HEADER_SIZE`, or it will fail to locate the firmware
|
||||
image at boot.
|
||||
|
||||
#### Three-steps signing using external provisioning tools
|
||||
|
||||
If the private key is not accessible, while it's possible to sign payloads using
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ static inline int fp_truncate(FILE *f, size_t len)
|
|||
#define MAX_CUSTOM_TLVS (16)
|
||||
#endif
|
||||
|
||||
/* wolfBoot and this tool stop parsing at any header field larger than
|
||||
* (uint16_t)(header size - IMAGE_HEADER_OFFSET), at most 65528 including the
|
||||
* 4-byte tag and length, so the largest usable value is 65524. A longer
|
||||
* field would hide every field after it, including the signature. */
|
||||
#define MAX_TLV_LEN (65524)
|
||||
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#include <wolfssl/wolfcrypt/asn.h>
|
||||
#include <wolfssl/wolfcrypt/aes.h>
|
||||
|
|
@ -1351,7 +1357,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
uint8_t *base_hash, uint32_t base_hash_sz)
|
||||
{
|
||||
uint32_t header_idx;
|
||||
uint8_t *header;
|
||||
uint8_t *header = NULL;
|
||||
FILE *f = NULL, *f2 = NULL, *fek = NULL, *fef = NULL;
|
||||
uint32_t fw_version32;
|
||||
struct stat attrib;
|
||||
|
|
@ -1377,42 +1383,56 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
|
||||
/* Check certificate chain file size before allocating header, and adjust
|
||||
* header size if needed */
|
||||
if (CMD.cert_chain_file != NULL) {
|
||||
struct stat file_stat;
|
||||
if ((CMD.cert_chain_file != NULL) || (CMD.custom_tlvs > 0)) {
|
||||
uint32_t hdr_cert_chain_sz = 0;
|
||||
uint32_t required_space;
|
||||
|
||||
/* Get the file size */
|
||||
if (stat(CMD.cert_chain_file, &file_stat) == 0) {
|
||||
off_t chain_file_sz = file_stat.st_size;
|
||||
uint32_t required_space;
|
||||
|
||||
if ((chain_file_sz < 0) ||
|
||||
((uintmax_t)chain_file_sz > (uintmax_t)UINT32_MAX)) {
|
||||
printf("Warning: certificate chain file size is invalid (%jd)\n",
|
||||
(intmax_t)chain_file_sz);
|
||||
}
|
||||
else {
|
||||
required_space = header_required_size(is_diff,
|
||||
(uint32_t)chain_file_sz, secondary_key_sz);
|
||||
|
||||
/* 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;
|
||||
if (CMD.cert_chain_file != NULL) {
|
||||
struct stat file_stat;
|
||||
if (stat(CMD.cert_chain_file, &file_stat) == 0) {
|
||||
off_t chain_file_sz = file_stat.st_size;
|
||||
if (chain_file_sz < 0) {
|
||||
printf("Warning: certificate chain file size is invalid "
|
||||
"(%jd)\n", (intmax_t)chain_file_sz);
|
||||
}
|
||||
else if ((uintmax_t)chain_file_sz > (uintmax_t)MAX_TLV_LEN) {
|
||||
printf("Error: Certificate chain too large for TLV encoding "
|
||||
"(%ju > %u)\n", (uintmax_t)chain_file_sz, MAX_TLV_LEN);
|
||||
goto failure;
|
||||
}
|
||||
else {
|
||||
hdr_cert_chain_sz = (uint32_t)chain_file_sz;
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("Warning: Could not stat certificate chain file %s: %s\n",
|
||||
CMD.cert_chain_file, strerror(errno));
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("Warning: Could not stat certificate chain file %s: %s\n",
|
||||
CMD.cert_chain_file, strerror(errno));
|
||||
|
||||
required_space =
|
||||
header_required_size(is_diff, hdr_cert_chain_sz, secondary_key_sz);
|
||||
|
||||
/* 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 all fields */
|
||||
const uint32_t min_header_size = 256;
|
||||
uint32_t new_size = min_header_size;
|
||||
while (new_size < required_space) {
|
||||
if (new_size > (UINT32_MAX / 2U)) {
|
||||
printf("Error: Header size overflow while sizing "
|
||||
"manifest header\n");
|
||||
goto failure;
|
||||
}
|
||||
new_size *= 2;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Warning: increasing header size from %u to %u "
|
||||
"bytes to fit manifest header fields.\n"
|
||||
"Warning: wolfBoot must be built with IMAGE_HEADER_SIZE=%u "
|
||||
"or it will not find the firmware image.\n",
|
||||
CMD.header_sz, new_size, new_size);
|
||||
CMD.header_sz = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1572,10 +1592,10 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
}
|
||||
cert_chain_sz = (uint32_t)file_stat.st_size;
|
||||
|
||||
if (cert_chain_sz > (uint32_t)UINT16_MAX) {
|
||||
if (cert_chain_sz > (uint32_t)MAX_TLV_LEN) {
|
||||
printf("Error: Certificate chain too large for TLV encoding "
|
||||
"(%u > %u)\n",
|
||||
cert_chain_sz, (unsigned int)UINT16_MAX);
|
||||
cert_chain_sz, (unsigned int)MAX_TLV_LEN);
|
||||
fclose(f);
|
||||
f = NULL;
|
||||
goto failure;
|
||||
|
|
@ -2530,36 +2550,43 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in
|
|||
len3++;
|
||||
}
|
||||
/* make_header_delta() below calls make_header_ex(is_diff=1), which may grow
|
||||
* CMD.header_sz to fit the delta TLVs plus certificate chain. Resolve that
|
||||
* expansion here, using the same logic, so patch_inv_off reflects the header
|
||||
* size actually written; otherwise HDR_IMG_DELTA_INVERSE would encode a
|
||||
* stale, too-small offset and break inverse-patch rollback. */
|
||||
if (CMD.cert_chain_file != NULL) {
|
||||
struct stat cc_stat;
|
||||
if ((stat(CMD.cert_chain_file, &cc_stat) == 0) &&
|
||||
(cc_stat.st_size >= 0)) {
|
||||
if ((uintmax_t)cc_stat.st_size > (uintmax_t)UINT16_MAX) {
|
||||
printf("Error: Certificate chain too large for TLV encoding "
|
||||
"(%ju > %u)\n", (uintmax_t)cc_stat.st_size, UINT16_MAX);
|
||||
goto cleanup;
|
||||
}
|
||||
else {
|
||||
uint32_t required_space = header_required_size(1,
|
||||
(uint32_t)cc_stat.st_size, 0);
|
||||
if (CMD.header_sz < required_space) {
|
||||
uint32_t new_size = 256;
|
||||
while (new_size < required_space) {
|
||||
if (new_size > (UINT32_MAX / 2U)) {
|
||||
printf("Error: Header size overflow while sizing "
|
||||
"certificate chain\n");
|
||||
goto cleanup;
|
||||
}
|
||||
new_size *= 2;
|
||||
}
|
||||
CMD.header_sz = new_size;
|
||||
* CMD.header_sz to fit the delta TLVs, custom TLVs and certificate chain.
|
||||
* Resolve that expansion here, using the same logic, so patch_inv_off
|
||||
* reflects the header size actually written; otherwise HDR_IMG_DELTA_INVERSE
|
||||
* would encode a stale, too-small offset and break inverse-patch rollback. */
|
||||
if ((CMD.cert_chain_file != NULL) || (CMD.custom_tlvs > 0)) {
|
||||
uint32_t cert_chain_sz = 0;
|
||||
uint32_t required_space;
|
||||
if (CMD.cert_chain_file != NULL) {
|
||||
struct stat cc_stat;
|
||||
if ((stat(CMD.cert_chain_file, &cc_stat) == 0) &&
|
||||
(cc_stat.st_size >= 0)) {
|
||||
if ((uintmax_t)cc_stat.st_size > (uintmax_t)MAX_TLV_LEN) {
|
||||
printf("Error: Certificate chain too large for TLV encoding "
|
||||
"(%ju > %u)\n", (uintmax_t)cc_stat.st_size, MAX_TLV_LEN);
|
||||
goto cleanup;
|
||||
}
|
||||
cert_chain_sz = (uint32_t)cc_stat.st_size;
|
||||
}
|
||||
}
|
||||
required_space = header_required_size(1, cert_chain_sz, 0);
|
||||
if (CMD.header_sz < required_space) {
|
||||
uint32_t new_size = 256;
|
||||
while (new_size < required_space) {
|
||||
if (new_size > (UINT32_MAX / 2U)) {
|
||||
printf("Error: Header size overflow while sizing "
|
||||
"manifest header\n");
|
||||
goto cleanup;
|
||||
}
|
||||
new_size *= 2;
|
||||
}
|
||||
fprintf(stderr, "Warning: increasing header size from %u to %u "
|
||||
"bytes to fit manifest header fields.\n"
|
||||
"Warning: wolfBoot must be built with IMAGE_HEADER_SIZE=%u "
|
||||
"or it will not find the firmware image.\n",
|
||||
CMD.header_sz, new_size, new_size);
|
||||
CMD.header_sz = new_size;
|
||||
}
|
||||
}
|
||||
patch_inv_off = (uint32_t)len3 + CMD.header_sz;
|
||||
patch_inv_sz = 0;
|
||||
|
|
@ -3171,7 +3198,7 @@ int main(int argc, char** argv)
|
|||
fprintf(stderr, "Too many custom TLVs.\n");
|
||||
exit(16);
|
||||
}
|
||||
if (argc < (i + 3)) {
|
||||
if (argc < (i + 4)) {
|
||||
fprintf(stderr, "Invalid custom TLV fields. \n");
|
||||
exit(16);
|
||||
}
|
||||
|
|
@ -3202,17 +3229,18 @@ int main(int argc, char** argv)
|
|||
} else if (strcmp(argv[i], "--custom-tlv-buffer") == 0) {
|
||||
int p = CMD.custom_tlvs;
|
||||
uint16_t tag, len;
|
||||
size_t slen;
|
||||
uint32_t j;
|
||||
if (p >= MAX_CUSTOM_TLVS) {
|
||||
fprintf(stderr, "Too many custom TLVs.\n");
|
||||
exit(16);
|
||||
}
|
||||
if (argc < (i + 2)) {
|
||||
if (argc < (i + 3)) {
|
||||
fprintf(stderr, "Invalid custom TLV fields. \n");
|
||||
exit(16);
|
||||
}
|
||||
tag = (uint16_t)arg2num(argv[i + 1], 2);
|
||||
len = (uint16_t)strlen(argv[i + 2]) / 2;
|
||||
slen = strlen(argv[i + 2]);
|
||||
if (tag < 0x0030) {
|
||||
fprintf(stderr, "Invalid custom tag: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
|
|
@ -3221,10 +3249,18 @@ int main(int argc, char** argv)
|
|||
fprintf(stderr, "Invalid custom tag: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
}
|
||||
if (len > 255) {
|
||||
fprintf(stderr, "custom tlv buffer size too big: %s\n", argv[i + 2]);
|
||||
if ((slen / 2) > MAX_TLV_LEN) {
|
||||
fprintf(stderr, "custom tlv buffer size too big: "
|
||||
"%lu bytes (max %u)\n", (unsigned long)(slen / 2),
|
||||
MAX_TLV_LEN);
|
||||
exit(16);
|
||||
}
|
||||
if ((slen % 2) != 0) {
|
||||
fprintf(stderr, "custom tlv buffer hex string must have an "
|
||||
"even number of digits: %s\n", argv[i + 2]);
|
||||
exit(16);
|
||||
}
|
||||
len = (uint16_t)(slen / 2);
|
||||
CMD.custom_tlv[p].tag = tag;
|
||||
CMD.custom_tlv[p].len = len;
|
||||
CMD.custom_tlv[p].buffer = malloc(len);
|
||||
|
|
@ -3241,17 +3277,18 @@ int main(int argc, char** argv)
|
|||
} else if (strcmp(argv[i], "--custom-tlv-string") == 0) {
|
||||
int p = CMD.custom_tlvs;
|
||||
uint16_t tag, len;
|
||||
size_t slen;
|
||||
uint32_t j;
|
||||
if (p >= MAX_CUSTOM_TLVS) {
|
||||
fprintf(stderr, "Too many custom TLVs.\n");
|
||||
exit(16);
|
||||
}
|
||||
if (argc < (i + 2)) {
|
||||
if (argc < (i + 3)) {
|
||||
fprintf(stderr, "Invalid custom TLV fields. \n");
|
||||
exit(16);
|
||||
}
|
||||
tag = (uint16_t)arg2num(argv[i + 1], 2);
|
||||
len = (uint16_t)strlen(argv[i + 2]);
|
||||
slen = strlen(argv[i + 2]);
|
||||
if (tag < 0x0030) {
|
||||
fprintf(stderr, "Invalid custom tag: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
|
|
@ -3260,10 +3297,12 @@ int main(int argc, char** argv)
|
|||
fprintf(stderr, "Invalid custom tag: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
}
|
||||
if (len > 255) {
|
||||
fprintf(stderr, "custom tlv buffer size too big: %s\n", argv[i + 2]);
|
||||
if (slen > MAX_TLV_LEN) {
|
||||
fprintf(stderr, "custom tlv string size too big: "
|
||||
"%lu bytes (max %u)\n", (unsigned long)slen, MAX_TLV_LEN);
|
||||
exit(16);
|
||||
}
|
||||
len = (uint16_t)slen;
|
||||
CMD.custom_tlv[p].tag = tag;
|
||||
CMD.custom_tlv[p].len = len;
|
||||
CMD.custom_tlv[p].buffer = malloc(len);
|
||||
|
|
@ -3276,6 +3315,68 @@ int main(int argc, char** argv)
|
|||
}
|
||||
CMD.custom_tlvs++;
|
||||
i += 2;
|
||||
} else if (strcmp(argv[i], "--custom-tlv-file") == 0) {
|
||||
int p = CMD.custom_tlvs;
|
||||
uint16_t tag;
|
||||
FILE *f;
|
||||
long fsz;
|
||||
size_t rd;
|
||||
if (p >= MAX_CUSTOM_TLVS) {
|
||||
fprintf(stderr, "Too many custom TLVs.\n");
|
||||
exit(16);
|
||||
}
|
||||
if (argc < (i + 3)) {
|
||||
fprintf(stderr, "Invalid custom TLV fields. \n");
|
||||
exit(16);
|
||||
}
|
||||
tag = (uint16_t)arg2num(argv[i + 1], 2);
|
||||
if (tag < 0x0030) {
|
||||
fprintf(stderr, "Invalid custom tag: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
}
|
||||
if ( ((tag & 0xFF00) == 0xFF00) || ((tag & 0xFF) == 0xFF) ) {
|
||||
fprintf(stderr, "Invalid custom tag: %s\n", argv[i + 1]);
|
||||
exit(16);
|
||||
}
|
||||
f = fopen(argv[i + 2], "rb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "Cannot open custom tlv file %s: %s\n",
|
||||
argv[i + 2], strerror(errno));
|
||||
exit(16);
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
fsz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (fsz <= 0) {
|
||||
fprintf(stderr, "custom tlv file is empty or unreadable: %s\n",
|
||||
argv[i + 2]);
|
||||
fclose(f);
|
||||
exit(16);
|
||||
}
|
||||
if (fsz > (long)MAX_TLV_LEN) {
|
||||
fprintf(stderr, "custom tlv file too big: %ld bytes "
|
||||
"(max %u): %s\n", fsz, MAX_TLV_LEN, argv[i + 2]);
|
||||
fclose(f);
|
||||
exit(16);
|
||||
}
|
||||
CMD.custom_tlv[p].tag = tag;
|
||||
CMD.custom_tlv[p].len = (uint16_t)fsz;
|
||||
CMD.custom_tlv[p].buffer = malloc((size_t)fsz);
|
||||
if (CMD.custom_tlv[p].buffer == NULL) {
|
||||
fprintf(stderr, "Error malloc for custom tlv buffer %ld\n",
|
||||
fsz);
|
||||
fclose(f);
|
||||
exit(16);
|
||||
}
|
||||
rd = fread(CMD.custom_tlv[p].buffer, 1, (size_t)fsz, f);
|
||||
fclose(f);
|
||||
if (rd != (size_t)fsz) {
|
||||
fprintf(stderr, "Error reading custom tlv file %s\n",
|
||||
argv[i + 2]);
|
||||
exit(16);
|
||||
}
|
||||
CMD.custom_tlvs++;
|
||||
i += 2;
|
||||
}
|
||||
else if (strcmp(argv[i], "--cert-chain") == 0) {
|
||||
if (argc <= (i + 1)) {
|
||||
|
|
@ -3395,11 +3496,19 @@ int main(int argc, char** argv)
|
|||
printf("TLV %u\n", i);
|
||||
printf("----\n");
|
||||
if (CMD.custom_tlv[i].buffer) {
|
||||
uint16_t print_len = CMD.custom_tlv[i].len;
|
||||
if (print_len > 256) {
|
||||
print_len = 256;
|
||||
}
|
||||
printf("Tag: %04X Len: %hu Val: ", CMD.custom_tlv[i].tag,
|
||||
CMD.custom_tlv[i].len);
|
||||
for (j = 0; j < CMD.custom_tlv[i].len; j++) {
|
||||
for (j = 0; j < print_len; j++) {
|
||||
printf("%02X", CMD.custom_tlv[i].buffer[j]);
|
||||
}
|
||||
if (print_len < CMD.custom_tlv[i].len) {
|
||||
printf("... (truncated, %hu bytes total)",
|
||||
CMD.custom_tlv[i].len);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ run: $(TESTS)
|
|||
python3 unit-sign-delta-tlv.py || exit 1
|
||||
python3 unit-sign-delta-cert-inv-off.py || exit 1
|
||||
python3 unit-sign-custom-tlv-le.py || exit 1
|
||||
python3 unit-sign-custom-tlv-large.py || exit 1
|
||||
|
||||
|
||||
WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha.c \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,376 @@
|
|||
#!/usr/bin/env python3
|
||||
# unit-sign-custom-tlv-large.py
|
||||
#
|
||||
# Tests large custom TLVs in the C sign tool (tools/keytools/sign.c):
|
||||
# values up to the 65524-byte maximum via --custom-tlv-buffer,
|
||||
# --custom-tlv-string and --custom-tlv-file, automatic power-of-two growth
|
||||
# of the manifest header, rejection of oversized, empty or missing values,
|
||||
# and delta images keeping HDR_IMG_DELTA_INVERSE consistent with the grown
|
||||
# header.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HDR_PADDING = 0xFF
|
||||
HDR_IMG_DELTA_SIZE = 0x06
|
||||
HDR_IMG_DELTA_INVERSE = 0x15
|
||||
HDR_IMG_DELTA_INVERSE_SIZE = 0x16
|
||||
|
||||
TAG_BUFFER = 0x0030
|
||||
TAG_STRING = 0x0031
|
||||
TAG_FILE = 0x0032
|
||||
|
||||
SECTOR_SIZE = 0x1000
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(THIS_DIR, "..", ".."))
|
||||
SIGN = os.path.join(ROOT, "tools", "keytools", "sign")
|
||||
|
||||
failures = []
|
||||
|
||||
|
||||
def skip(msg):
|
||||
print("SKIP unit-sign-custom-tlv-large: " + msg)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def fail(msg):
|
||||
failures.append(msg)
|
||||
|
||||
|
||||
def parse_tlvs(data, scan_end):
|
||||
"""Walk the header like wolfBoot_find_header(): {tag: value bytes}."""
|
||||
tlvs = {}
|
||||
p = 8 # skip 4-byte magic + 4-byte image size
|
||||
while p + 4 <= scan_end:
|
||||
htype = data[p] | (data[p + 1] << 8)
|
||||
if htype == 0:
|
||||
break
|
||||
if data[p] == HDR_PADDING or (p & 1) != 0:
|
||||
p += 1
|
||||
continue
|
||||
length = data[p + 2] | (data[p + 3] << 8)
|
||||
# wolfBoot_find_header() stops at any field larger than the header
|
||||
# capacity truncated to uint16_t, so this walk must stop too or the
|
||||
# test would accept images wolfBoot cannot read
|
||||
if 4 + length > (scan_end - 8) & 0xFFFF:
|
||||
break
|
||||
if p + 4 + length > scan_end:
|
||||
break
|
||||
if htype not in tlvs:
|
||||
tlvs[htype] = bytes(data[p + 4:p + 4 + length])
|
||||
p += 4 + length
|
||||
return tlvs
|
||||
|
||||
|
||||
def tlv_u32(tlvs, tag):
|
||||
val = tlvs.get(tag)
|
||||
if val is None or len(val) != 4:
|
||||
return None
|
||||
return struct.unpack("<I", val)[0]
|
||||
|
||||
|
||||
def ensure_sign():
|
||||
if os.path.exists(SIGN):
|
||||
return True
|
||||
try:
|
||||
subprocess.run(["make", "sign"],
|
||||
cwd=os.path.join(ROOT, "tools", "keytools"),
|
||||
check=True, capture_output=True, text=True)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return False
|
||||
return os.path.exists(SIGN)
|
||||
|
||||
|
||||
def make_ed25519_key(path):
|
||||
"""Write a 64-byte raw ed25519 key (seed + public) as expected by sign."""
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import \
|
||||
Ed25519PrivateKey
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
except Exception:
|
||||
return False
|
||||
seed = b"\x42" * 32
|
||||
sk = Ed25519PrivateKey.from_private_bytes(seed)
|
||||
pub = sk.public_key().public_bytes(serialization.Encoding.Raw,
|
||||
serialization.PublicFormat.Raw)
|
||||
with open(path, "wb") as f:
|
||||
f.write(seed + pub)
|
||||
return True
|
||||
|
||||
|
||||
def run_sign(args, image, key, version, env=None):
|
||||
cmd = [SIGN, "--ed25519", "--sha256"] + args + [image, key, version]
|
||||
run_env = dict(os.environ)
|
||||
if env:
|
||||
run_env.update(env)
|
||||
return subprocess.run(cmd, cwd=ROOT, env=run_env,
|
||||
capture_output=True, text=True)
|
||||
|
||||
|
||||
def signed_name(image, version):
|
||||
return image.replace(".bin", "_v%s_signed.bin" % version)
|
||||
|
||||
|
||||
def make_image(path, payload):
|
||||
with open(path, "wb") as f:
|
||||
f.write(payload)
|
||||
|
||||
|
||||
def check_signed_layout(name, signed, payload):
|
||||
"""Return (data, header_size) or None; header must be a power of two
|
||||
holding the payload right after it."""
|
||||
with open(signed, "rb") as f:
|
||||
data = f.read()
|
||||
hdr = len(data) - len(payload)
|
||||
if hdr <= 0 or (hdr & (hdr - 1)) != 0:
|
||||
fail("%s: file size %d - payload %d = %d is not a power-of-two "
|
||||
"header" % (name, len(data), len(payload), hdr))
|
||||
return None
|
||||
if data[hdr:] != payload:
|
||||
fail("%s: payload is not stored at header size %d" % (name, hdr))
|
||||
return None
|
||||
if struct.unpack("<I", data[4:8])[0] != len(payload):
|
||||
fail("%s: image size field does not match payload size" % name)
|
||||
return None
|
||||
return data, hdr
|
||||
|
||||
|
||||
def check_value(name, tlvs, tag, expected):
|
||||
got = tlvs.get(tag)
|
||||
if got is None:
|
||||
fail("%s: tag 0x%04x not found in header" % (name, tag))
|
||||
elif got != expected:
|
||||
fail("%s: tag 0x%04x value mismatch (%d bytes, want %d)" %
|
||||
(name, tag, len(got), len(expected)))
|
||||
|
||||
|
||||
def expect_reject(name, args, image, key, version, needle):
|
||||
signed = signed_name(image, version)
|
||||
if os.path.exists(signed):
|
||||
os.unlink(signed)
|
||||
r = run_sign(args, image, key, version)
|
||||
if r.returncode == 0:
|
||||
fail("%s: sign succeeded, expected rejection" % name)
|
||||
return
|
||||
if needle not in r.stderr:
|
||||
fail("%s: expected '%s' in stderr, got: %s" %
|
||||
(name, needle, r.stderr.strip()[:200]))
|
||||
if os.path.exists(signed):
|
||||
fail("%s: rejected sign still produced an output image" % name)
|
||||
|
||||
|
||||
def main():
|
||||
if not ensure_sign():
|
||||
skip("could not build tools/keytools/sign")
|
||||
|
||||
with tempfile.TemporaryDirectory() as work:
|
||||
key = os.path.join(work, "priv.der")
|
||||
if not make_ed25519_key(key):
|
||||
skip("python cryptography module not available")
|
||||
|
||||
payload = bytes((i * 7) & 0xFF for i in range(2048))
|
||||
|
||||
# Control: a plain sign must work, or the environment is broken and
|
||||
# every other failure below would be misleading.
|
||||
control = os.path.join(work, "control.bin")
|
||||
make_image(control, payload)
|
||||
r = run_sign([], control, key, "1")
|
||||
if r.returncode != 0 or not os.path.exists(signed_name(control, "1")):
|
||||
skip("control sign failed: " + r.stderr.strip())
|
||||
|
||||
# Large buffer, string and file TLVs in one image.
|
||||
buf_val = bytes((i * 13 + 5) & 0xFF for i in range(1000))
|
||||
str_val = "".join(chr(65 + (i % 26)) for i in range(300))
|
||||
file_val = bytes((i * 31 + 7) & 0xFF for i in range(40000))
|
||||
tlv_file = os.path.join(work, "tlv.bin")
|
||||
with open(tlv_file, "wb") as f:
|
||||
f.write(file_val)
|
||||
|
||||
image = os.path.join(work, "large.bin")
|
||||
make_image(image, payload)
|
||||
r = run_sign(["--custom-tlv-buffer", hex(TAG_BUFFER), buf_val.hex(),
|
||||
"--custom-tlv-string", hex(TAG_STRING), str_val,
|
||||
"--custom-tlv-file", hex(TAG_FILE), tlv_file],
|
||||
image, key, "1")
|
||||
if r.returncode != 0:
|
||||
fail("large: sign failed: " + r.stderr.strip()[:200])
|
||||
else:
|
||||
res = check_signed_layout("large", signed_name(image, "1"),
|
||||
payload)
|
||||
if res:
|
||||
data, hdr = res
|
||||
if hdr <= 256:
|
||||
fail("large: header did not grow (size %d)" % hdr)
|
||||
tlvs = parse_tlvs(data, hdr)
|
||||
check_value("large", tlvs, TAG_BUFFER, buf_val)
|
||||
check_value("large", tlvs, TAG_STRING,
|
||||
str_val.encode("ascii"))
|
||||
check_value("large", tlvs, TAG_FILE, file_val)
|
||||
|
||||
# Largest value the header parsers can walk past (see parse_tlvs).
|
||||
max_val = bytes((i * 3 + 1) & 0xFF for i in range(65524))
|
||||
max_file = os.path.join(work, "max.bin")
|
||||
with open(max_file, "wb") as f:
|
||||
f.write(max_val)
|
||||
image = os.path.join(work, "max_img.bin")
|
||||
make_image(image, payload)
|
||||
r = run_sign(["--custom-tlv-file", hex(TAG_FILE), max_file],
|
||||
image, key, "1")
|
||||
if r.returncode != 0:
|
||||
fail("max: sign failed for 65524-byte TLV: " +
|
||||
r.stderr.strip()[:200])
|
||||
else:
|
||||
res = check_signed_layout("max", signed_name(image, "1"), payload)
|
||||
if res:
|
||||
data, hdr = res
|
||||
check_value("max", parse_tlvs(data, hdr), TAG_FILE, max_val)
|
||||
|
||||
# Rejections.
|
||||
image = os.path.join(work, "rej.bin")
|
||||
make_image(image, payload)
|
||||
|
||||
over_file = os.path.join(work, "over.bin")
|
||||
with open(over_file, "wb") as f:
|
||||
f.write(b"\x00" * 65525)
|
||||
expect_reject("reject-file-65525",
|
||||
["--custom-tlv-file", hex(TAG_FILE), over_file],
|
||||
image, key, "1", "too big")
|
||||
|
||||
expect_reject("reject-string-65525",
|
||||
["--custom-tlv-string", hex(TAG_STRING), "X" * 65525],
|
||||
image, key, "1", "too big")
|
||||
|
||||
empty_file = os.path.join(work, "empty.bin")
|
||||
open(empty_file, "wb").close()
|
||||
expect_reject("reject-empty-file",
|
||||
["--custom-tlv-file", hex(TAG_FILE), empty_file],
|
||||
image, key, "1", "empty")
|
||||
|
||||
expect_reject("reject-missing-file",
|
||||
["--custom-tlv-file", hex(TAG_FILE),
|
||||
os.path.join(work, "does-not-exist.bin")],
|
||||
image, key, "1", "Cannot open")
|
||||
|
||||
# Delta: a large custom TLV must not desync HDR_IMG_DELTA_INVERSE
|
||||
# from the grown header.
|
||||
env = {"WOLFBOOT_SECTOR_SIZE": str(SECTOR_SIZE)}
|
||||
base = os.path.join(work, "delta.bin")
|
||||
make_image(base, payload)
|
||||
r = run_sign([], base, key, "1", env=env)
|
||||
if r.returncode != 0:
|
||||
fail("delta: base sign failed: " + r.stderr.strip()[:200])
|
||||
else:
|
||||
upd = os.path.join(work, "delta2.bin")
|
||||
make_image(upd, payload[:512] + b"PATCHED!" + payload[520:])
|
||||
r = run_sign(["--delta", signed_name(base, "1"),
|
||||
"--custom-tlv-file", hex(TAG_FILE), tlv_file],
|
||||
upd, key, "2", env=env)
|
||||
diff = upd.replace(".bin", "_v2_signed_diff.bin")
|
||||
if r.returncode != 0 or not os.path.exists(diff):
|
||||
fail("delta: delta sign failed: " + r.stderr.strip()[:200])
|
||||
else:
|
||||
# The full v2 image from the same run reveals the header
|
||||
# size the tool settled on.
|
||||
res = check_signed_layout("delta-full",
|
||||
signed_name(upd, "2"), payload[:512]
|
||||
+ b"PATCHED!" + payload[520:])
|
||||
if res:
|
||||
hdr = res[1]
|
||||
with open(diff, "rb") as f:
|
||||
ddata = f.read()
|
||||
tlvs = parse_tlvs(ddata, hdr)
|
||||
check_value("delta-diff", tlvs, TAG_FILE, file_val)
|
||||
inv_off = tlv_u32(tlvs, HDR_IMG_DELTA_INVERSE)
|
||||
inv_sz = tlv_u32(tlvs, HDR_IMG_DELTA_INVERSE_SIZE)
|
||||
fwd_sz = tlv_u32(tlvs, HDR_IMG_DELTA_SIZE)
|
||||
if inv_off is None or inv_sz is None or fwd_sz is None:
|
||||
fail("delta-diff: delta TLVs missing from header")
|
||||
else:
|
||||
if inv_off + inv_sz != len(ddata):
|
||||
fail("delta-diff: HDR_IMG_DELTA_INVERSE=%d + "
|
||||
"size=%d != filesize %d (stale header "
|
||||
"size in patch_inv_off)" %
|
||||
(inv_off, inv_sz, len(ddata)))
|
||||
if inv_off < hdr + fwd_sz:
|
||||
fail("delta-diff: inverse patch at %d overlaps "
|
||||
"forward patch (header %d + %d)" %
|
||||
(inv_off, hdr, fwd_sz))
|
||||
|
||||
# Boundary delta: size the TLV so the header fits a power of two
|
||||
# without the delta TLVs but not with them. base_diff() must grow
|
||||
# the header before capturing patch_inv_off, or HDR_IMG_DELTA_INVERSE
|
||||
# points a full header step short of the trailing inverse patch.
|
||||
bnd_val = bytes((i * 11 + 3) & 0xFF for i in range(800))
|
||||
bnd_file = os.path.join(work, "bnd.bin")
|
||||
with open(bnd_file, "wb") as f:
|
||||
f.write(bnd_val)
|
||||
base = os.path.join(work, "bdelta.bin")
|
||||
make_image(base, payload)
|
||||
r = run_sign([], base, key, "1", env=env)
|
||||
if r.returncode != 0:
|
||||
fail("bdelta: base sign failed: " + r.stderr.strip()[:200])
|
||||
else:
|
||||
upd = os.path.join(work, "bdelta2.bin")
|
||||
make_image(upd, payload[:512] + b"PATCHED!" + payload[520:])
|
||||
r = run_sign(["--delta", signed_name(base, "1"),
|
||||
"--custom-tlv-file", hex(TAG_FILE), bnd_file],
|
||||
upd, key, "2", env=env)
|
||||
diff = upd.replace(".bin", "_v2_signed_diff.bin")
|
||||
if r.returncode != 0 or not os.path.exists(diff):
|
||||
fail("bdelta: delta sign failed: " + r.stderr.strip()[:200])
|
||||
else:
|
||||
res = check_signed_layout("bdelta-full",
|
||||
signed_name(upd, "2"), payload[:512]
|
||||
+ b"PATCHED!" + payload[520:])
|
||||
if res:
|
||||
h_full = res[1]
|
||||
with open(diff, "rb") as f:
|
||||
ddata = f.read()
|
||||
tlvs = parse_tlvs(ddata,
|
||||
min(4 * h_full, len(ddata)))
|
||||
check_value("bdelta-diff", tlvs, TAG_FILE, bnd_val)
|
||||
inv_off = tlv_u32(tlvs, HDR_IMG_DELTA_INVERSE)
|
||||
inv_sz = tlv_u32(tlvs, HDR_IMG_DELTA_INVERSE_SIZE)
|
||||
if inv_off is None or inv_sz is None:
|
||||
fail("bdelta-diff: delta TLVs missing from header")
|
||||
else:
|
||||
if inv_off < 2 * h_full:
|
||||
fail("bdelta-diff: test setup no longer "
|
||||
"straddles a header size boundary "
|
||||
"(inv_off=%d, full header=%d); re-tune "
|
||||
"the 800-byte TLV size" % (inv_off, h_full))
|
||||
if inv_off + inv_sz != len(ddata):
|
||||
fail("bdelta-diff: HDR_IMG_DELTA_INVERSE=%d + "
|
||||
"size=%d != filesize %d (stale header "
|
||||
"size in patch_inv_off)" %
|
||||
(inv_off, inv_sz, len(ddata)))
|
||||
|
||||
if failures:
|
||||
for msg in failures:
|
||||
print("FAIL unit-sign-custom-tlv-large: " + msg)
|
||||
sys.exit(1)
|
||||
|
||||
print("unit-sign-custom-tlv-large: OK")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue