From 831b38983fd4c1e8216d0f904c6c525ade0a9eed Mon Sep 17 00:00:00 2001 From: Mattia Moffa Date: Fri, 24 Jul 2026 22:46:30 +0200 Subject: [PATCH 1/2] Add --custom-tlv-pubkey-der option to signing tool Allows extracting a public key from a DER file and adding it in the same format as the keystore: - `X||Y` for ECC - Raw for Ed25519/Ed448 - Public key DER for RSA --- docs/Signing.md | 11 +++ tools/keytools/sign.c | 177 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/docs/Signing.md b/docs/Signing.md index 3e3377b6ae..7c195211f1 100644 --- a/docs/Signing.md +++ b/docs/Signing.md @@ -274,6 +274,17 @@ Provides a value to be set with a custom tag 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. + * `--custom-tlv-pubkey-der tag filename`: Adds a TLV entry to the manifest header, + corresponding to the type identified by `tag`, with the value extracted from the + DER-encoded public key (SubjectPublicKeyInfo) in `filename`. The tag is a 16-bit + number. Valid tags are in the range between 0x0030 and 0xFEFE. The key algorithm is + detected automatically (ECC, Ed25519, Ed448 or RSA) and the stored value uses the + same format as the wolfBoot keystore: the raw `X||Y` point coordinates for ECC, the + raw public key bytes for Ed25519/Ed448, and the DER-encoded public key for RSA. + This avoids a manual ASN.1-stripping step before `--custom-tlv-file` when embedding + an application-level public key (e.g. for verifying payloads at runtime, or for key + rotation) in the signed manifest. + 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. diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index cae6099b64..a0b822a2f3 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -2706,6 +2706,156 @@ uint64_t arg2num(const char *arg, size_t len) return ret; } +/* Load a DER-encoded public key (SubjectPublicKeyInfo), detect the algorithm + * and extract the public key in the same format used by the keystore: + * X||Y for ECC, raw bytes for Ed25519/Ed448, public key DER for RSA. + * On success stores a malloc'd buffer in *out and its size in *out_sz. + */ +static int extract_pubkey_from_der(const char *fname, uint8_t **out, + uint16_t *out_sz) +{ + FILE *f; + long fsz; + size_t rd; + uint8_t *der; + uint8_t *buf = NULL; + int len = -1; + const char *ktype = NULL; + word32 idx; + + f = fopen(fname, "rb"); + if (f == NULL) { + fprintf(stderr, "Cannot open public key DER file %s: %s\n", + fname, strerror(errno)); + return -1; + } + fseek(f, 0, SEEK_END); + fsz = ftell(f); + fseek(f, 0, SEEK_SET); + if ((fsz <= 0) || (fsz > (long)MAX_TLV_LEN)) { + fprintf(stderr, "Invalid public key DER file size %ld: %s\n", + fsz, fname); + fclose(f); + return -1; + } + der = malloc((size_t)fsz); + if (der == NULL) { + fprintf(stderr, "Error malloc for public key DER %ld\n", fsz); + fclose(f); + return -1; + } + rd = fread(der, 1, (size_t)fsz, f); + fclose(f); + if (rd != (size_t)fsz) { + fprintf(stderr, "Error reading public key DER file %s\n", fname); + free(der); + return -1; + } +#ifdef HAVE_ECC + if (len < 0) { + ecc_key ecc; + if (wc_ecc_init(&ecc) == 0) { + idx = 0; + if (wc_EccPublicKeyDecode(der, &idx, &ecc, (word32)fsz) == 0) { + uint8_t qx[MAX_ECC_BYTES], qy[MAX_ECC_BYTES]; + word32 qxsz = sizeof(qx), qysz = sizeof(qy); + if (wc_ecc_export_public_raw(&ecc, qx, &qxsz, qy, &qysz) + == 0) { + buf = malloc(qxsz + qysz); + if (buf != NULL) { + memcpy(buf, qx, qxsz); + memcpy(buf + qxsz, qy, qysz); + len = (int)(qxsz + qysz); + ktype = "ECC"; + } + } + } + wc_ecc_free(&ecc); + } + } +#endif +#ifdef HAVE_ED25519 + if (len < 0) { + ed25519_key ed; + if (wc_ed25519_init(&ed) == 0) { + idx = 0; + if (wc_Ed25519PublicKeyDecode(der, &idx, &ed, (word32)fsz) == 0) { + word32 osz = ED25519_PUB_KEY_SIZE; + buf = malloc(osz); + if (buf != NULL) { + if (wc_ed25519_export_public(&ed, buf, &osz) == 0) { + len = (int)osz; + ktype = "Ed25519"; + } else { + free(buf); + buf = NULL; + } + } + } + wc_ed25519_free(&ed); + } + } +#endif +#ifdef HAVE_ED448 + if (len < 0) { + ed448_key ed4; + if (wc_ed448_init(&ed4) == 0) { + idx = 0; + if (wc_Ed448PublicKeyDecode(der, &idx, &ed4, (word32)fsz) == 0) { + word32 osz = ED448_PUB_KEY_SIZE; + buf = malloc(osz); + if (buf != NULL) { + if (wc_ed448_export_public(&ed4, buf, &osz) == 0) { + len = (int)osz; + ktype = "Ed448"; + } else { + free(buf); + buf = NULL; + } + } + } + wc_ed448_free(&ed4); + } + } +#endif +#ifndef NO_RSA + if (len < 0) { + RsaKey rsa; + if (wc_InitRsaKey(&rsa, NULL) == 0) { + idx = 0; + if (wc_RsaPublicKeyDecode(der, &idx, &rsa, (word32)fsz) == 0) { + int dsz = wc_RsaPublicKeyDerSize(&rsa, 1); + if (dsz > 0) { + buf = malloc((size_t)dsz); + if (buf != NULL) { + len = wc_RsaKeyToPublicDer(&rsa, buf, (word32)dsz); + if (len > 0) { + ktype = "RSA"; + } else { + free(buf); + buf = NULL; + len = -1; + } + } + } + } + wc_FreeRsaKey(&rsa); + } + } +#endif + free(der); + if ((len <= 0) || (buf == NULL)) { + fprintf(stderr, "Unable to parse public key DER file %s " + "(tried ECC, Ed25519, Ed448, RSA)\n", fname); + return -1; + } + printf("Custom TLV: imported %s public key from %s (%d bytes)\n", + ktype, fname, len); + *out = buf; + *out_sz = (uint16_t)len; + return 0; +} + static void set_signature_sizes(int secondary) { uint32_t *sz = &CMD.signature_sz; @@ -3377,6 +3527,33 @@ int main(int argc, char** argv) } CMD.custom_tlvs++; i += 2; + } else if (strcmp(argv[i], "--custom-tlv-pubkey-der") == 0) { + int p = CMD.custom_tlvs; + uint16_t tag; + 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); + } + if (extract_pubkey_from_der(argv[i + 2], + &CMD.custom_tlv[p].buffer, &CMD.custom_tlv[p].len) != 0) { + exit(16); + } + CMD.custom_tlv[p].tag = tag; + CMD.custom_tlvs++; + i += 2; } else if (strcmp(argv[i], "--cert-chain") == 0) { if (argc <= (i + 1)) { From b21154145f39767ebb3593071bd2e3e19cb59133 Mon Sep 17 00:00:00 2001 From: Mattia Moffa Date: Sat, 25 Jul 2026 05:05:00 +0200 Subject: [PATCH 2/2] Check key len for truncation; add unit test --- tools/keytools/sign.c | 6 ++++++ tools/unit-tests/Makefile | 1 + 2 files changed, 7 insertions(+) diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index a0b822a2f3..64a2913089 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -2849,6 +2849,12 @@ static int extract_pubkey_from_der(const char *fname, uint8_t **out, "(tried ECC, Ed25519, Ed448, RSA)\n", fname); return -1; } + if (len > (int)MAX_TLV_LEN) { + fprintf(stderr, "extracted public key too big: %d bytes (max %u): " + "%s\n", len, MAX_TLV_LEN, fname); + free(buf); + return -1; + } printf("Custom TLV: imported %s public key from %s (%d bytes)\n", ktype, fname, len); *out = buf; diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 5374d28a80..5ba9d7efa1 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -130,6 +130,7 @@ run: $(TESTS) 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 + python3 unit-sign-custom-tlv-pubkey-der.py || exit 1 WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha.c \