Merge pull request #828 from bigbrett/remove-custom-tlv-limit

support for large and file-backed custom TLVs
pull/833/head
David Garske 2026-07-23 08:48:30 -07:00 committed by GitHub
commit 464f1eeae5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 625 additions and 74 deletions

View File

@ -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)` ]

1
.gitignore vendored
View File

@ -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__/*

View File

@ -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

View File

@ -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 {

View File

@ -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 \

View File

@ -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()