diff --git a/.github/workflows/test-keytools.yml b/.github/workflows/test-keytools.yml index 6e42dba19..d739fd891 100644 --- a/.github/workflows/test-keytools.yml +++ b/.github/workflows/test-keytools.yml @@ -237,4 +237,30 @@ jobs: run: | ./tools/keytools/keygen --id 1,3,5,10,11,13,14 --ecc256 -g wolfboot_signing_private_key.der | grep "mask" | grep "00006c2a" + # Custom TLVs + - name: make clean + run: | + make distclean + + - name: Select config + run: | + cp config/examples/sim.config .config && make include/target.h + + - name: Build tools + run: | + make -C tools/keytools && make -C tools/bin-assemble + + - name: Build wolfboot with ECC256/SHA256 + run: | + make SIGN=ECC256 HASH=SHA256 + + - name: Sign app with custom numeric TLV included + run: | + ./tools/keytools/sign --ecc256 --sha256 --custom-tlv 0x45 4 0x6f616943 test-app/image.elf wolfboot_signing_private_key.der 2 + grep "Ciao" test-app/image_v2_signed.bin + + - name: Sign app with custom buffer TLV included + run: | + ./tools/keytools/sign --ecc256 --sha256 --custom-tlv-buffer 0x46 48656C6C6F20776F726C64 test-app/image.elf wolfboot_signing_private_key.der 3 + grep "Hello world" test-app/image_v3_signed.bin diff --git a/README.md b/README.md index a4fb2ba8c..1165b57b5 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ For more detailed information about firmware update implementation, see [Firmwar ### Additional features - [Remote external flash interface](docs/remote_flash.md) - [External encrypted partitions](docs/encrypted_partitions.md) + - [Delta updates](docs/firmware_update.md#incremental-updates-aka-delta-updates) ## Building diff --git a/docs/Signing.md b/docs/Signing.md index 85e6e4106..cb84444ef 100644 --- a/docs/Signing.md +++ b/docs/Signing.md @@ -177,6 +177,23 @@ Provides a PCR mask and digest to be signed and included in the header. The sign A copy of the final signed policy (including 4 byte PCR mask) will be output to `[inputname].sig`. Note: This may require increasing the `IMAGE_HEADER_SIZE` as two signatures will be stored in the header. +#### Adding custom fields to the manifest header + +Provides a value to be set with a custom tag + + * `--custom-tlv tag len val`: Adds a TLV entry to the manifest header, corresponding + to the type identified by `tag`, with lenght `len` bytes, and assigns the value `val`. + Values can be decimal or hex numbers (prefixed by '0x'). The tag is a 16-bit number. + Valid tags are in the range between 0x0030 and 0xFEFE. + + * `--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. + 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. + + #### Three-steps signing using external provisioning tools If the private key is not accessible, while it's possible to sign payloads using diff --git a/docs/firmware_image.md b/docs/firmware_image.md index 860c9c789..c65ae77fe 100644 --- a/docs/firmware_image.md +++ b/docs/firmware_image.md @@ -42,15 +42,53 @@ Each **Type** has a different meaning, and integrate information about the firmw - A 'version' Tag (type: 0x0001, size: 4 Bytes) indicating the version number for the firmware stored in the image - A 'timestamp' Tag (type: 0x0002, size 8 Bytes) indicating the timestamp in unix seconds for the creation of the firmware - - A 'sha256 digest' Tag (type: 0x0003, size: 32 Bytes) used for integrity check of the firmware + - A 'sha digest' Tag (type: 0x0003, size: digest size (32 Bytes for SHA256)) used for integrity check of the firmware - A 'firmware signature' Tag (type: 0x0020, size: 64 Bytes) used to validate the signature stored with the firmware against a known public key - A 'firmware type' Tag (type: 0x0030, size: 2 Bytes) used to identify the type of firmware, and the authentication mechanism in use. -Optionally, a 'public key hint digest' Tag can be transmitted in the header (type: 0x10, size:32 Bytes). This Tag contains the SHA256 digest of the public key used +A 'public key hint digest' tag is transmitted in the header (type: 0x10, size:32 Bytes). This tag contains the SHA digest of the public key used by the signing tool. The bootloader may use this field to locate the correct public key in case of multiple keys available. wolfBoot will, in all cases, refuse to boot an image that cannot be verified and authenticated using the built-in digital signature authentication mechanism. +### Adding custom fields to the manifest header + +It is possible to add custom fields to the manifest header, by using the `--custom-tlv` option in the signing tool. + +In order for the fields to be secured (checked by wolfBoot for integrity and authenticity), +their value is placed in the manifest header before the signature is calculated. The signing tool takes care of the alignment and padding of the fields. + +The custom fields are identified by a 16-bit tag, and their size is indicated by a 16-bit length field. The tag and length fields are stored in little-endian format. + +At runtime, the values stored in the manifest header can be accessed using the `wolfBoot_find_header` function. + +The syntax for `--custom-tlv` option is also documented in [docs/Signing.md](/docs/Signing.md#adding-custom-fields-to-the-manifest-header). + +### Image header: Example + +This example adds a custom field when the signing tool is used to sign the firmware image: + +```bash +./tools/keytools/sign --ed25519 --custom-tlv 0x34 4 0xAABBCCDD test-app/image.bin wolfboot_signing_private_key.der 4 +``` + +The output image `test-app/image_v4_signed.bin` will contain the custom field with tag `0x34` with length `4` and value `0xAABBCCDD`. + +From the bootloader code, we can then retrieve the value of the custom field using the `wolfBoot_find_header` function: + +```c +uint32_t custom_code34_field; +const uint16_t custom_code34_field_size = 4; +const uint16_t custom_code34_tag = 0x34; +int size; + +size = wolfBoot_find_header(0x34, &custom_code34_field, sizeof(custom_code34_value)); +if (size != custom_code34_field_size) { + /* Error: the field is not present or has the wrong size */ +} + +/* From here, the value 0xAABBCCDD is stored in custom_code34_value */ +``` ### Image signing tool diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index f88412d9f..c362871a1 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -41,6 +41,7 @@ #include #include #include +#include /* target.h is a generated file based on .config (see target.h.in) * Provides: WOLFBOOT_SECTOR_SIZE */ #include @@ -68,6 +69,8 @@ static inline int fp_truncate(FILE *f, size_t len) #define MAX_SRC_SIZE (1 << 24) +#define MAX_CUSTOM_TLVS (16) + #include #include #include @@ -265,6 +268,13 @@ struct cmd_options { uint32_t signature_sz; uint32_t policy_sz; uint8_t partition_id; + uint32_t custom_tlvs; + struct cmd_tlv { + uint16_t tag; + uint16_t len; + uint64_t val; + uint8_t *buffer; + } custom_tlv[MAX_CUSTOM_TLVS]; }; static struct cmd_options CMD = { @@ -273,6 +283,7 @@ static struct cmd_options CMD = { .hash_algo = HASH_SHA256, .header_sz = IMAGE_HEADER_SIZE, .partition_id = HDR_IMG_TYPE_APP + }; static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz, @@ -1067,6 +1078,28 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, &patch_inv_len); } + /* Add custom TLVs */ + if (CMD.custom_tlvs > 0) { + uint32_t i; + for (i = 0; i < CMD.custom_tlvs; i++) { + if (CMD.custom_tlv[i].len == 8) { + /* This field requires 8-byte alignment */ + while((header_idx % 8) != 4) + header_idx++; + } + if (CMD.custom_tlv[i].buffer == NULL) { + header_append_tag(header, &header_idx, CMD.custom_tlv[i].tag, + CMD.custom_tlv[i].len, &CMD.custom_tlv[i].val); + } else { + header_append_tag(header, &header_idx, CMD.custom_tlv[i].tag, + CMD.custom_tlv[i].len, CMD.custom_tlv[i].buffer); + } + } + /* Align for next field */ + while ((header_idx % 4) != 0) + header_idx++; + } + /* Add padding bytes. Sha-3 val field requires 8-byte alignment */ while ((header_idx % 8) != 4) header_idx++; @@ -1789,6 +1822,31 @@ static int base_diff(const char *f_base, uint8_t *pubkey, uint32_t pubkey_sz, in return ret; } +uint64_t arg2num(const char *arg, size_t len) +{ + uint64_t ret = (uint64_t) -1; + if (strncmp(arg, "0x", 2) == 0) { + ret = strtoll(arg + 2, NULL, 16); + } else { + ret = strtoll(arg, NULL, 10); + } + switch (len) { + case 1: + ret &= 0xFF; + break; + case 2: + ret &= 0xFFFF; + break; + case 4: + ret &= 0xFFFFFFFF; + case 8: + break; + default: + ret = (uint64_t) (-1); + } + return ret; +} + int main(int argc, char** argv) { int ret = 0; @@ -1936,6 +1994,81 @@ int main(int argc, char** argv) CMD.policy_sign = 1; CMD.policy_file = argv[++i]; } + else if (strcmp(argv[i], "--custom-tlv") == 0) { + int p = CMD.custom_tlvs; + uint16_t tag, len; + 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); + len = (uint16_t)arg2num(argv[i + 2], 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); + } + + if ((len != 1) && (len != 2) && (len != 4) && (len != 8)) { + fprintf(stderr, "Invalid custom tag len: %s\n", argv[i + 2]); + fprintf(stderr, "Accepted len: 1, 2, 4 or 8\n"); + exit(16); + } + + CMD.custom_tlv[p].tag = tag; + CMD.custom_tlv[p].len = len; + CMD.custom_tlv[p].val = arg2num(argv[i+3], len); + CMD.custom_tlv[p].buffer = NULL; + CMD.custom_tlvs++; + i += 3; + } else if (strcmp(argv[i], "--custom-tlv-buffer") == 0) { + int p = CMD.custom_tlvs; + uint16_t tag, len; + uint32_t j; + if (p >= MAX_CUSTOM_TLVS) { + fprintf(stderr, "Too many custom TLVs.\n"); + exit(16); + } + if (argc < (i + 2)) { + 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; + 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); + } + if (len > 255) { + fprintf(stderr, "custom tlv buffer size too big: %s\n", argv[i + 2]); + exit(16); + } + CMD.custom_tlv[p].tag = tag; + CMD.custom_tlv[p].len = len; + CMD.custom_tlv[p].buffer = malloc(len); + if (CMD.custom_tlv[p].buffer == NULL) { + fprintf(stderr, "Error malloc for custom tlv buffer %d\n", len); + exit(16); + } + for (j = 0; j < len; j++) { + char c[3] = {argv[i + 2][j * 2], argv[i + 2][j * 2 + 1], 0}; + CMD.custom_tlv[p].buffer[j] = (uint8_t)strtol(c, NULL, 16); + } + CMD.custom_tlvs++; + i += 2; + } else { i--; break; @@ -2010,6 +2143,28 @@ int main(int argc, char** argv) printf("(bootloader)"); printf("\n"); + if (CMD.custom_tlvs > 0) { + uint32_t i, j; + printf("Custom TLVS: %u\n", CMD.custom_tlvs); + for (i = 0; i < CMD.custom_tlvs; i++) { + printf("TLV %u\n", i); + printf("----\n"); + if (CMD.custom_tlv[i].buffer) { + 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++) { + printf("%02X", CMD.custom_tlv[i].buffer[j]); + } + printf("\n"); + + } else { + printf("Tag: %04X Len: %hu Val: %" PRIu64 "\n", CMD.custom_tlv[i].tag, + CMD.custom_tlv[i].len, CMD.custom_tlv[i].val); + } + printf("-----\n"); + } + } + /* get header and signature sizes */ if (CMD.sign == SIGN_ED25519) { if (CMD.header_sz < 256)