From e23d450e459283eb559147585d8c5ad40673aef7 Mon Sep 17 00:00:00 2001 From: jordan Date: Tue, 5 Sep 2023 15:53:54 -0500 Subject: [PATCH] LMS wolfBoot support. --- .github/workflows/test-renode-nrf52.yml | 4 + config/examples/sim-lms.config | 49 +++++++ docs/PQ.md | 85 +++++++++++ include/loader.h | 6 + include/wolfboot/wolfboot.h | 6 + options.mk | 58 ++++++++ src/image.c | 67 +++++++++ tools/config.mk | 4 + tools/keytools/Makefile | 29 +++- tools/keytools/keygen.c | 114 ++++++++++++++- tools/keytools/sign.c | 118 ++++++++++++++- ...-Patch-to-support-wolfBoot-LMS-build.patch | 118 +++++++++++++++ tools/lms/lms_common.h | 136 ++++++++++++++++++ tools/lms/lms_siglen.sh | 79 ++++++++++ tools/scripts/renode-test-update.sh | 18 +++ tools/test-renode.mk | 26 ++++ tools/test.mk | 3 + 17 files changed, 910 insertions(+), 10 deletions(-) create mode 100644 config/examples/sim-lms.config create mode 100644 docs/PQ.md create mode 100644 tools/lms/0001-Patch-to-support-wolfBoot-LMS-build.patch create mode 100644 tools/lms/lms_common.h create mode 100755 tools/lms/lms_siglen.sh diff --git a/.github/workflows/test-renode-nrf52.yml b/.github/workflows/test-renode-nrf52.yml index 833548181..460aaea83 100644 --- a/.github/workflows/test-renode-nrf52.yml +++ b/.github/workflows/test-renode-nrf52.yml @@ -54,6 +54,10 @@ jobs: - name: Renode Tests RSA4096 run: ./tools/renode/docker-test.sh "SIGN=RSA4096" +# LMS TEST + - name: Renode Tests LMS-8-5-5 + run: ./tools/renode/docker-test.sh "SIGN=LMS LMS_LEVELS=2 LMS_HEIGHT=5 LMS_WINTERNITZ=8 WOLFBOOT_SMALL_STACK=0 IMAGE_SIGNATURE_SIZE=2644 IMAGE_HEADER_SIZE=5288" + - name: Upload Output Dir uses: actions/upload-artifact@v2 with: diff --git a/config/examples/sim-lms.config b/config/examples/sim-lms.config new file mode 100644 index 000000000..66c7e2097 --- /dev/null +++ b/config/examples/sim-lms.config @@ -0,0 +1,49 @@ +# LMS/HSS signature example, based on sim.config example. +# +# LMS/HSS is a post-quantum, stateful, hash-based signature scheme. +# +# The acceptable parameter values are those in RFC8554: +# levels = {1..8} +# height = {5, 10, 15, 20, 25} +# winternitz = {1, 2, 4, 8} +# +# The number of available signatures is: +# N = 2 ** (levels * height) +# +# LMS/HSS Signature sizes are directly proportional to the levels value, +# and inversely proportional to the Winternitz value. They grow only +# modestly with the height value. +# +# Key generation time is strongly determined by the height of the first +# level tree. +# +# Use the helper script +# tools/lms/lms_siglen +# to calculate your signature length given the chosen levels, height, +# Winternitz values. +# + +ARCH=sim +TARGET=sim +SIGN?=LMS +HASH?=SHA256 +LMS_LEVELS=2 +LMS_HEIGHT=5 +LMS_WINTERNITZ=8 +WOLFBOOT_SMALL_STACK=0 +SPI_FLASH=0 +DEBUG=0 +DELTA_UPDATES=0 +IMAGE_SIGNATURE_SIZE=2644 +IMAGE_HEADER_SIZE?=5288 + +# sizes should be multiple of system page size +WOLFBOOT_PARTITION_SIZE=0x40000 +WOLFBOOT_SECTOR_SIZE=0x1000 +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x20000 +# if on external flash, it should be multiple of system page size +WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x60000 +WOLFBOOT_PARTITION_SWAP_ADDRESS=0xA0000 + +# required for keytools +WOLFBOOT_FIXED_PARTITIONS=1 diff --git a/docs/PQ.md b/docs/PQ.md new file mode 100644 index 000000000..21bae8e15 --- /dev/null +++ b/docs/PQ.md @@ -0,0 +1,85 @@ +# Post-Quantum Signatures + +wolfBoot is adding support for post-quantum signatures. At present, support +for LMS/HSS signatures has been added. + +## LMS/HSS + +LMS/HSS is a post-quantum stateful hash-based signature scheme (HBS). It +is known for having small public and private keys, but larger signatures. +The signature size is tunable via the different LMS parameters. + +Stateful HBS schemes are based on the security of their underlying hash +functions and Merkle trees, which are not expected to be broken by the advent +of cryptographically relevant quantum computers. + +### Building with LMS Support + +LMS/HSS support in wolfCrypt requires the hash-sigs library ( https://github.com/cisco/hash-sigs ). +Use the following procedure to prepare hash-sigs for building with wolfBoot: + +``` +$ cd lib +$ mkdir hash-sigs +$ls + CMakeLists.txt hash-sigs wolfssl wolfTPM +$ cd hash-sigs +$ mkdir lib +$ git clone https://github.com/cisco/hash-sigs.git src +$ cd src +$ git checkout b0631b8891295bf2929e68761205337b7c031726 +$ git apply ../../../tools/lms/0001-Patch-to-support-wolfBoot-LMS-build.patch +``` + +Nothing more is needed, as wolfBoot will automatically produce the required +hash-sigs build artifacts. + +Note: the hash-sigs project only builds static libraries: +- hss_verify.a: a single-threaded verify-only static lib. +- hss_lib.a: a single-threaded static lib. +- hss_lib_thread.a: a multi-threaded static lib. + +The keytools utility links against `hss_lib.a`, as it needs full +keygen, signing, and verifying functionality. However wolfBoot +links directly with the subset of objects in the `hss_verify.a` +build rule, as it only requires verify functionality. + +### Config + +A new LMS sim example has been added here: +``` +config/examples/sim-lms.config +``` + +The `LMS_LEVELS`, `LMS_HEIGHT`, and `LMS_WINTERNITZ`, `IMAGE_SIGNATURE_SIZE`, +and (optionally) `IMAGE_HEADER_SIZE` must be set: + +``` +SIGN?=LMS +... +LMS_LEVELS=2 +LMS_HEIGHT=5 +LMS_WINTERNITZ=8 +... +IMAGE_SIGNATURE_SIZE=2644 +IMAGE_HEADER_SIZE?=5288 +``` + +In LMS the signature size is a function of the parameters. Use the added helper +script `tools/lms/lms_siglen.sh` to calculate your signature length given your +LMS parameters: +``` +$./tools/lms/lms_siglen.sh +levels: 3 +height: 5 +winternitz: 8 +# +total_len: 3992 +``` + +### More Info + +See these links for more info on LMS and wolfSSL/wolfCrypt: +- https://www.wolfssl.com/documentation/manuals/wolfssl/appendix07.html#post-quantum-stateful-hash-based-signatures +- https://github.com/wolfSSL/wolfssl-examples/tree/master/pq/stateful_hash_sig + diff --git a/include/loader.h b/include/loader.h index d35b530ef..0b6c898e4 100644 --- a/include/loader.h +++ b/include/loader.h @@ -61,6 +61,12 @@ extern "C" { extern const unsigned char rsa4096_pub_key[]; extern unsigned int rsa4096_pub_key_len; # define IMAGE_SIGNATURE_SIZE (512) +#elif defined(WOLFBOOT_SIGN_LMS) + /* Because signature size in LMS is function of + * LMS variables, IMAGE_SIGNATURE_SIZE is set in + * options.mk from the .config file. */ + extern const unsigned char lms_pub_key[]; + extern unsigned int lms_pub_key_len; #elif !defined(WOLFBOOT_NO_SIGN) # error "No public key available for given signing algorithm." #endif /* Algorithm selection */ diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index d4bd64a94..892051be8 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -80,6 +80,7 @@ extern "C" { #define AUTH_KEY_ECC384 0x06 #define AUTH_KEY_ECC521 0x07 #define AUTH_KEY_RSA3072 0x08 +#define AUTH_KEY_LMS 0x09 @@ -99,6 +100,7 @@ extern "C" { #define HDR_IMG_TYPE_AUTH_ECC384 (AUTH_KEY_ECC384 << 8) #define HDR_IMG_TYPE_AUTH_ECC521 (AUTH_KEY_ECC521 << 8) #define HDR_IMG_TYPE_AUTH_RSA3072 (AUTH_KEY_RSA3072 << 8) +#define HDR_IMG_TYPE_AUTH_LMS (AUTH_KEY_LMS << 8) #define HDR_IMG_TYPE_DIFF 0x00D0 @@ -115,6 +117,7 @@ extern "C" { #define KEYSTORE_PUBKEY_SIZE_RSA2048 320 #define KEYSTORE_PUBKEY_SIZE_RSA3072 448 #define KEYSTORE_PUBKEY_SIZE_RSA4096 576 + #define KEYSTORE_PUBKEY_SIZE_LMS 60 /* Mask for key permissions */ #define KEY_VERIFY_ALL (0xFFFFFFFFU) @@ -194,6 +197,9 @@ extern "C" { #elif defined(WOLFBOOT_SIGN_RSA4096) # define HDR_IMG_TYPE_AUTH HDR_IMG_TYPE_AUTH_RSA4096 # define KEYSTORE_PUBKEY_SIZE KEYSTORE_PUBKEY_SIZE_RSA4096 + #elif defined(WOLFBOOT_SIGN_LMS) + # define HDR_IMG_TYPE_AUTH HDR_IMG_TYPE_AUTH_LMS + # define KEYSTORE_PUBKEY_SIZE KEYSTORE_PUBKEY_SIZE_LMS #else # error "No valid authentication mechanism selected. " \ "Please select a valid SIGN= option." diff --git a/options.mk b/options.mk index 27a31722a..714ae41ba 100644 --- a/options.mk +++ b/options.mk @@ -287,6 +287,64 @@ ifeq ($(SIGN),RSA4096) endif endif +ifeq ($(SIGN),LMS) + # For LMS the signature size is a function of the LMS parameters. + # All five of these parms must be set in the LMS .config file: + # LMS_LEVELS, LMS_HEIGHT, LMS_WINTERNITZ, IMAGE_SIGNATURE_SIZE, + # IMAGE_HEADER_SIZE + + ifndef LMS_LEVELS + $(error LMS_LEVELS not set) + endif + + ifndef LMS_HEIGHT + $(error LMS_HEIGHT not set) + endif + + ifndef LMS_WINTERNITZ + $(error LMS_WINTERNITZ not set) + endif + + ifndef IMAGE_SIGNATURE_SIZE + $(error IMAGE_SIGNATURE_SIZE not set) + endif + + ifndef IMAGE_HEADER_SIZE + $(error IMAGE_HEADER_SIZE not set) + endif + + LMSDIR = lib/hash-sigs + KEYGEN_OPTIONS+=--lms + SIGN_OPTIONS+=--lms + WOLFCRYPT_OBJS+= \ + ./$(LMSDIR)/src/hss_verify.o \ + ./$(LMSDIR)/src/hss_verify_inc.o \ + ./$(LMSDIR)/src/hss_common.o \ + ./$(LMSDIR)/src/hss_thread_single.o \ + ./$(LMSDIR)/src/hss_zeroize.o \ + ./$(LMSDIR)/src/lm_common.o \ + ./$(LMSDIR)/src/lm_ots_common.o \ + ./$(LMSDIR)/src/lm_ots_verify.o \ + ./$(LMSDIR)/src/lm_verify.o \ + ./$(LMSDIR)/src/endian.o \ + ./$(LMSDIR)/src/hash.o \ + ./$(LMSDIR)/src/sha256.o \ + ./lib/wolfssl/wolfcrypt/src/ext_lms.o \ + ./lib/wolfssl/wolfcrypt/src/memory.o \ + ./lib/wolfssl/wolfcrypt/src/wc_port.o \ + ./lib/wolfssl/wolfcrypt/src/hash.o + CFLAGS+=-D"WOLFBOOT_SIGN_LMS" -D"WOLFSSL_HAVE_LMS" -D"HAVE_LIBLMS" \ + -D"LMS_LEVELS=$(LMS_LEVELS)" -D"LMS_HEIGHT=$(LMS_HEIGHT)" \ + -D"LMS_WINTERNITZ=$(LMS_WINTERNITZ)" -I$(LMSDIR)/src \ + -D"IMAGE_SIGNATURE_SIZE"=$(IMAGE_SIGNATURE_SIZE) \ + -D"WOLFSSL_LMS_VERIFY_ONLY" + ifeq ($(WOLFBOOT_SMALL_STACK),1) + $(error WOLFBOOT_SMALL_STACK with LMS not supported) + else + STACK_USAGE=18064 + endif +endif + ifeq ($(USE_GCC_HEADLESS),1) CFLAGS+="-Wstack-usage=$(STACK_USAGE)" diff --git a/src/image.c b/src/image.c index e9b0475f7..ab39818b6 100644 --- a/src/image.c +++ b/src/image.c @@ -393,6 +393,73 @@ static void wolfBoot_verify_signature(uint8_t key_slot, #endif /* WOLFBOOT_SIGN_RSA2048 || WOLFBOOT_SIGN_3072 || \ * WOLFBOOT_SIGN_RSA4096 */ +#ifdef WOLFBOOT_SIGN_LMS +#include +#ifdef HAVE_LIBLMS + #include +#endif + +static void wolfBoot_verify_signature(uint8_t key_slot, + struct wolfBoot_image *img, uint8_t *sig) +{ + int ret = 0; + LmsKey lms; + word32 pub_len = 0; + uint8_t * pubkey = NULL; + + wolfBoot_printf("info: LMS wolfBoot_verify_signature\n"); + + pubkey = keystore_get_buffer(key_slot); + if (pubkey == NULL) { + wolfBoot_printf("error: Lms pubkey not found\n"); + return; + } + + ret = wc_LmsKey_Init(&lms, NULL, INVALID_DEVID); + if (ret != 0) { + wolfBoot_printf("error: wc_LmsKey_Init returned %d\n", ret); + return; + } + + /* Set the LMS parameters. */ + ret = wc_LmsKey_SetParameters(&lms, LMS_LEVELS, LMS_HEIGHT, + LMS_WINTERNITZ); + if (ret != 0) { + /* Something is wrong with the pub key or LMS parameters. */ + wolfBoot_printf("error: wc_LmsKey_SetParameters(%d, %d, %d)" \ + " returned %d\n", LMS_LEVELS, LMS_HEIGHT, + LMS_WINTERNITZ, ret); + return; + } + + wolfBoot_printf("info: using LMS parameters: L%d-H%d-W%d\n", LMS_LEVELS, + LMS_HEIGHT, LMS_WINTERNITZ); + + /* Set the public key. */ + ret = wc_LmsKey_ImportPubRaw(&lms, pubkey, KEYSTORE_PUBKEY_SIZE); + if (ret != 0) { + /* Something is wrong with the pub key or LMS parameters. */ + wolfBoot_printf("error: wc_LmsKey_ImportPubRaw" \ + " returned %d\n", ret); + return; + } + + ret = wc_LmsKey_Verify(&lms, sig, IMAGE_SIGNATURE_SIZE, img->sha_hash, + WOLFBOOT_SHA_DIGEST_SIZE); + + if (ret == 0) { + wolfBoot_printf("info: wc_LmsKey_Verify returned OK\n"); + wolfBoot_image_confirm_signature_ok(img); + } + else { + wolfBoot_printf("error: wc_LmsKey_Verify returned %d\n", ret); + } + + wc_LmsKey_Free(&lms); +} +#endif /* WOLFBOOT_SIGN_LMS */ + + /** * @brief Get the specified header type from the external flash image. * diff --git a/tools/config.mk b/tools/config.mk index 5781bc76f..d5870b7bd 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -30,6 +30,9 @@ ifeq ($(ARCH),) DISABLE_BACKUP?=0 WOLFBOOT_VERSION?=0 V?=0 + LMS_LEVELS?=0 + LMS_HEIGHT?=0 + LMS_WINTERNITZ?=0 NO_MPU?=0 ENCRYPT?=0 ENCRYPT_WITH_CHACHA?=0 @@ -78,5 +81,6 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO WOLFBOOT_SMALL_STACK DELTA_UPDATES DELTA_BLOCK_SIZE \ WOLFBOOT_HUGE_STACK FORCE_32BIT\ ENCRYPT_WITH_CHACHA ENCRYPT_WITH_AES128 ENCRYPT_WITH_AES256 ARMORED \ + LMS_LEVELS LMS_HEIGHT LMS_WINTERNITZ \ ELF diff --git a/tools/keytools/Makefile b/tools/keytools/Makefile index c8244066e..e66b8abe7 100644 --- a/tools/keytools/Makefile +++ b/tools/keytools/Makefile @@ -15,6 +15,15 @@ CFLAGS = -Wall -Wextra -Werror CFLAGS += -I. -DWOLFSSL_USER_SETTINGS -I$(WOLFDIR) -I$(WOLFBOOTDIR)/include -DWOLFBOOT_KEYTOOLS LDFLAGS = OBJDIR = ./ +LIBS = + +ifeq ($(SIGN),LMS) + LMSDIR = $(WOLFBOOTDIR)/lib/hash-sigs + LIBS += $(LMSDIR)/lib/hss_lib.a + CFLAGS +=-DWOLFBOOT_SIGN_LMS -DWOLFSSL_HAVE_LMS -DHAVE_LIBLMS -I$(LMSDIR)/src \ + -D"LMS_LEVELS=$(LMS_LEVELS)" -D"LMS_HEIGHT=$(LMS_HEIGHT)" \ + -D"LMS_WINTERNITZ=$(LMS_WINTERNITZ)" +endif # option variables DEBUG_FLAGS = -g -DDEBUG -DDEBUG_SIGNTOOL -DDEBUG_WOLFSSL -DDEBUG_WOLFSSL_VERBOSE -fsanitize=address @@ -63,7 +72,8 @@ OBJS_REAL=\ $(WOLFDIR)/wolfcrypt/src/sha512.o \ $(WOLFDIR)/wolfcrypt/src/tfm.o \ $(WOLFDIR)/wolfcrypt/src/wc_port.o \ - $(WOLFDIR)/wolfcrypt/src/wolfmath.o + $(WOLFDIR)/wolfcrypt/src/wolfmath.o \ + $(WOLFDIR)/wolfcrypt/src/ext_lms.o OBJS_REAL+=\ $(WOLFBOOTDIR)/src/delta.o @@ -103,13 +113,22 @@ $(OBJDIR)/%.o: $(WOLFDIR)/wolfcrypt/src/%.c $(Q)$(CC) $(CFLAGS) -c -o $@ $< # build templates -sign: $(OBJS_VIRT) sign.o +sign: $(OBJS_VIRT) $(LIBS) sign.o @echo "Building signing tool" - $(Q)$(LD) -o $@ $@.o $(OBJS_VIRT) $(LDFLAGS) + $(Q)$(LD) -o $@ $@.o $(OBJS_VIRT) $(LIBS) $(LDFLAGS) -keygen: $(OBJS_VIRT) keygen.o +keygen: $(OBJS_VIRT) $(LIBS) keygen.o @echo "Building keygen tool" - $(Q)$(LD) -o $@ $@.o $(OBJS_VIRT) $(LDFLAGS) + $(Q)$(LD) -o $@ $@.o $(OBJS_VIRT) $(LIBS) $(LDFLAGS) clean: rm -f sign keygen *.o + +# The final make clean is to ensure a subsequent LMS wolfboot +# hash-sigs build is separate from keytools. +$(LMSDIR)/lib/hss_lib.a: + @echo "Building hss_lib.a" + $(Q)@$(MAKE) -C $(LMSDIR)/src/ -s clean + $(Q)@$(MAKE) -C $(LMSDIR)/src/ hss_lib.a + $(Q)cp $(LMSDIR)/src/hss_lib.a $(LMSDIR)/lib/ + $(Q)@$(MAKE) -C $(LMSDIR)/src/ -s clean diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index bcfd7d7a4..600152843 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -61,6 +61,13 @@ #include #endif +#if defined(WOLFSSL_HAVE_LMS) + #include + #ifdef HAVE_LIBLMS + #include + #endif +#endif + #include #include #ifdef DEBUG_SIGNTOOL @@ -82,7 +89,8 @@ #define KEYGEN_ED448 5 #define KEYGEN_ECC384 6 #define KEYGEN_ECC521 7 -#define KEYGEN_RSA3072 8 +#define KEYGEN_RSA3072 8 +#define KEYGEN_LMS 9 /* Globals */ static FILE *fpub, *fpub_image; @@ -218,7 +226,8 @@ const char KType[][17] = { "AUTH_KEY_ED448", "AUTH_KEY_ECC384", "AUTH_KEY_ECC521", - "AUTH_KEY_RSA3072" + "AUTH_KEY_RSA3072", + "AUTH_KEY_LMS" }; const char KSize[][29] = { @@ -230,7 +239,8 @@ const char KSize[][29] = { "KEYSTORE_PUBKEY_SIZE_ED448", "KEYSTORE_PUBKEY_SIZE_ECC384", "KEYSTORE_PUBKEY_SIZE_ECC521", - "KEYSTORE_PUBKEY_SIZE_RSA3072" + "KEYSTORE_PUBKEY_SIZE_RSA3072", + "KEYSTORE_PUBKEY_SIZE_LMS" }; const char KName[][8] = { @@ -242,7 +252,8 @@ const char KName[][8] = { "ED448", "ECC384", "ECC521", - "RSA3072" + "RSA3072", + "LMS" }; static uint32_t get_pubkey_size(uint32_t keyType) @@ -271,6 +282,9 @@ static uint32_t get_pubkey_size(uint32_t keyType) case KEYGEN_RSA4096: size = KEYSTORE_PUBKEY_SIZE_RSA4096; break; + case KEYGEN_LMS: + size = KEYSTORE_PUBKEY_SIZE_LMS; + break; default: size = 0; } @@ -470,6 +484,87 @@ static void keygen_ed448(const char *privkey) } #endif +#if defined(WOLFSSL_HAVE_LMS) +#include "../lms/lms_common.h" + +static void keygen_lms(const char *priv_fname) +{ + FILE * fpriv; + LmsKey key; + int ret; + byte lms_pub[HSS_MAX_PUBLIC_KEY_LEN]; + word32 pub_len = sizeof(lms_pub); + + ret = wc_LmsKey_Init(&key, NULL, INVALID_DEVID); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_Init returned %d\n", ret); + exit(1); + } + + ret = wc_LmsKey_SetParameters(&key, LMS_LEVELS, LMS_HEIGHT, LMS_WINTERNITZ); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetParameters(%d, %d, %d)" \ + " returned %d\n", LMS_LEVELS, LMS_HEIGHT, + LMS_WINTERNITZ, ret); + exit(1); + } + + printf("info: using LMS parameters: L%d-H%d-W%d\n", LMS_LEVELS, + LMS_HEIGHT, LMS_WINTERNITZ); + + ret = wc_LmsKey_SetWriteCb(&key, lms_write_key); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetWriteCb returned %d\n", ret); + exit(1); + } + + ret = wc_LmsKey_SetReadCb(&key, lms_read_key); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetReadCb returned %d\n", ret); + exit(1); + } + + ret = wc_LmsKey_SetContext(&key, (void *) priv_fname); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetContext returned %d\n", ret); + exit(1); + } + + ret = wc_LmsKey_MakeKey(&key, &rng); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_MakeKey returned %d\n", ret); + exit(1); + } + + ret = wc_LmsKey_ExportPubRaw(&key, lms_pub, &pub_len); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_ExportPubRaw returned %d\n", ret); + exit(1); + } + + if (pub_len != sizeof(lms_pub)) { + fprintf(stderr, "error: wc_LmsKey_ExportPubRaw returned pub_len=%d\n" \ + ", expected %zu\n", pub_len, sizeof(lms_pub)); + exit(1); + } + + /* Append the public key to the private keyfile. */ + fpriv = fopen(priv_fname, "r+"); + if (!fpriv) { + fprintf(stderr, "error: fopen(%s, \"r+\") returned %d\n", priv_fname, + ret); + exit(1); + } + + fseek(fpriv, 64, SEEK_SET); + fwrite(lms_pub, KEYSTORE_PUBKEY_SIZE_LMS, 1, fpriv); + fclose(fpriv); + + keystore_add(KEYGEN_LMS, lms_pub, KEYSTORE_PUBKEY_SIZE_LMS, priv_fname); + + wc_LmsKey_Free(&key); +} +#endif /* if defined(WOLFSSL_HAVE_LMS) */ static void key_gen_check(const char *kfilename) { @@ -533,6 +628,12 @@ static void key_generate(uint32_t ktype, const char *kfilename) keygen_rsa(kfilename, 4096); break; #endif + +#ifdef WOLFSSL_HAVE_LMS + case KEYGEN_LMS: + keygen_lms(kfilename); + break; +#endif } /* end switch */ } @@ -656,6 +757,11 @@ int main(int argc, char** argv) else if (strcmp(argv[i], "--rsa4096") == 0) { keytype = KEYGEN_RSA4096; } +#if defined(WOLFSSL_HAVE_LMS) + else if (strcmp(argv[i], "--lms") == 0) { + keytype = KEYGEN_LMS; + } +#endif else if (strcmp(argv[i], "--force") == 0) { force = 1; } diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index f36e2fed9..c4d27fa72 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -100,6 +100,14 @@ static inline int fp_truncate(FILE *f, size_t len) #endif #include #include + +#if defined(WOLFSSL_HAVE_LMS) + #include + #ifdef HAVE_LIBLMS + #include + #endif +#endif + #ifdef DEBUG_SIGNTOOL #include #endif @@ -160,6 +168,7 @@ static inline int fp_truncate(FILE *f, size_t len) #define SIGN_ED448 HDR_IMG_TYPE_AUTH_ED448 #define SIGN_ECC384 HDR_IMG_TYPE_AUTH_ECC384 #define SIGN_ECC521 HDR_IMG_TYPE_AUTH_ECC521 +#define SIGN_LMS HDR_IMG_TYPE_AUTH_LMS #define ENC_OFF 0 @@ -190,6 +199,9 @@ static void header_append_tag(uint8_t* header, uint32_t* idx, uint16_t tag, *idx += len; } +#if defined(WOLFSSL_HAVE_LMS) +#include "../lms/lms_common.h" +#endif /* Globals */ static const char wolfboot_delta_file[] = "/tmp/wolfboot-delta.bin"; @@ -207,6 +219,9 @@ static union { #ifndef NO_RSA RsaKey rsa; #endif +#if defined(WOLFSSL_HAVE_LMS) + LmsKey lms; +#endif } key; struct cmd_options { @@ -676,9 +691,26 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz, break; } } +#if defined(WOLFSSL_HAVE_LMS) + FALL_THROUGH; + case SIGN_LMS: + ret = -1; + if (CMD.sign == SIGN_AUTO) { + printf("error: SIGN_AUTO with LMS is not supported\n"); + } + else { + /* Set the public key only. + * The LMS file callbacks will write/read the private key. + * The first 64 bytes is the private key. + * The next 60 bytes is the public key.*/ + *pubkey = (*key_buffer) + 64; + *pubkey_sz = (*key_buffer_sz) - 64; + ret = 0; + } +#endif /* defined(WOLFSSL_HAVE_LMS) */ break; - } + } /* end switch (CMD.sign) */ if (ret != 0) { printf("Key decode error %d\n", ret); @@ -1055,6 +1087,43 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz, } #endif } + else if (CMD.sign == SIGN_LMS) { +#if defined(WOLFSSL_HAVE_LMS) + /* Set the callbacks, so LMS can update the private key + * while signing. */ + ret = wc_LmsKey_SetWriteCb(&key.lms, lms_write_key); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetWriteCb returned %d\n", ret); + goto failure; + } + + ret = wc_LmsKey_SetReadCb(&key.lms, lms_read_key); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetReadCb returned %d\n", ret); + goto failure; + } + + ret = wc_LmsKey_SetContext(&key.lms, (void *) CMD.key_file); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetContext returned %d\n", ret); + goto failure; + } + + ret = wc_LmsKey_Reload(&key.lms); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_Reload returned %d\n", ret); + goto failure; + } + + ret = wc_LmsKey_Sign(&key.lms, signature, &CMD.signature_sz, digest, + digest_sz); + if (ret != 0) { + fprintf(stderr, "error: wc_LmsKey_Sign returned %d\n", ret); + goto failure; + } +#endif /* defined(WOLFSSL_HAVE_LMS) */ + } + wc_FreeRng(&rng); if (ret != 0) { @@ -1608,6 +1677,12 @@ int main(int argc, char** argv) CMD.sign = SIGN_RSA4096; sign_str = "RSA4096"; } +#if defined(WOLFSSL_HAVE_LMS) + else if (strcmp(argv[i], "--lms") == 0) { + CMD.sign = SIGN_LMS; + sign_str = "LMS"; + } +#endif else if (strcmp(argv[i], "--sha256") == 0) { CMD.hash_algo = HASH_SHA256; hash_str = "SHA256"; @@ -1782,6 +1857,43 @@ int main(int argc, char** argv) CMD.header_sz = 1024; CMD.signature_sz = 512; } +#if defined(WOLFSSL_HAVE_LMS) + else if (CMD.sign == SIGN_LMS) { + int lms_ret = 0; + word32 sig_sz = 0; + + lms_ret = wc_LmsKey_Init(&key.lms, NULL, INVALID_DEVID); + if (lms_ret != 0) { + fprintf(stderr, "error: wc_LmsKey_Init returned %d\n", lms_ret); + exit(1); + } + + lms_ret = wc_LmsKey_SetParameters(&key.lms, LMS_LEVELS, + LMS_HEIGHT, LMS_WINTERNITZ); + if (lms_ret != 0) { + fprintf(stderr, "error: wc_LmsKey_SetParameters(%d, %d, %d)" \ + " returned %d\n", LMS_LEVELS, LMS_HEIGHT, + LMS_WINTERNITZ, ret); + exit(1); + } + + printf("info: using LMS parameters: L%d-H%d-W%d\n", LMS_LEVELS, + LMS_HEIGHT, LMS_WINTERNITZ); + + lms_ret = wc_LmsKey_GetSigLen(&key.lms, &sig_sz); + if (lms_ret != 0) { + fprintf(stderr, "error: wc_LmsKey_GetSigLen returned %d\n", + lms_ret); + exit(1); + } + + printf("info: LMS signature size: %d\n", sig_sz); + + CMD.header_sz = 2 * sig_sz; + CMD.signature_sz = sig_sz; + } +#endif /* defined(WOLFSSL_HAVE_LMS) */ + if (((CMD.sign != NO_SIGN) && (CMD.signature_sz == 0)) || CMD.header_sz == 0) { printf("Invalid hash or signature type!\n"); @@ -1825,6 +1937,10 @@ int main(int argc, char** argv) } else if (CMD.sign == SIGN_RSA4096 || CMD.sign == SIGN_RSA4096) { #ifndef NO_RSA wc_FreeRsaKey(&key.rsa); +#endif + } else if (CMD.sign == SIGN_LMS) { +#if defined(WOLFSSL_HAVE_LMS) + wc_LmsKey_Free(&key.lms); #endif } return ret; diff --git a/tools/lms/0001-Patch-to-support-wolfBoot-LMS-build.patch b/tools/lms/0001-Patch-to-support-wolfBoot-LMS-build.patch new file mode 100644 index 000000000..9416bca4c --- /dev/null +++ b/tools/lms/0001-Patch-to-support-wolfBoot-LMS-build.patch @@ -0,0 +1,118 @@ +From 18c00c8b44f786c0ad3dabd4ec48f25519b944b4 Mon Sep 17 00:00:00 2001 +From: jordan +Date: Thu, 31 Aug 2023 13:38:44 -0500 +Subject: [PATCH 1/1] Patch to support wolfBoot LMS build. + +--- + endian.c | 2 +- + hss_common.c | 1 + + hss_thread_single.c | 6 ++++++ + lm_ots_verify.c | 2 +- + sha256.h | 2 +- + 5 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/endian.c b/endian.c +index acd2d36..29e5f6d 100644 +--- a/endian.c ++++ b/endian.c +@@ -13,7 +13,7 @@ void put_bigendian( void *target, unsigned long long value, size_t bytes ) { + unsigned long long get_bigendian( const void *target, size_t bytes ) { + const unsigned char *b = target; + unsigned long long result = 0; +- int i; ++ size_t i; + + for (i=0; i MAX_HSS_LEVELS) return 0; + + size_t first_pubkey = lm_get_public_key_len(lm_type[0]); +diff --git a/hss_thread_single.c b/hss_thread_single.c +index 44309fb..723ced8 100644 +--- a/hss_thread_single.c ++++ b/hss_thread_single.c +@@ -10,6 +10,7 @@ + * no threads, hence we don't need such an object + */ + struct thread_collection *hss_thread_init(int num_thread) { ++ (void) num_thread; + return 0; + } + +@@ -22,6 +23,7 @@ void hss_thread_issue_work(struct thread_collection *collect, + struct thread_collection *col), + const void *detail, size_t size_detail_structure) { + /* If we were asked to make sure something is done, just do it */ ++ (void) size_detail_structure; + function( detail, collect ); + } + +@@ -31,6 +33,7 @@ void hss_thread_issue_work(struct thread_collection *collect, + * requested, and we never allocated a collection object in the first place + */ + void hss_thread_done(struct thread_collection *collect) { ++ (void) collect; + ; + } + +@@ -40,6 +43,7 @@ void hss_thread_done(struct thread_collection *collect) { + * there is no need to lock + */ + void hss_thread_before_write(struct thread_collection *collect) { ++ (void) collect; + ; + } + +@@ -47,6 +51,7 @@ void hss_thread_before_write(struct thread_collection *collect) { + * This releases the above lock + */ + void hss_thread_after_write(struct thread_collection *collect) { ++ (void) collect; + ; + } + +@@ -55,5 +60,6 @@ void hss_thread_after_write(struct thread_collection *collect) { + * (the main one) + */ + unsigned hss_thread_num_tracks(int num_thread) { ++ (void) num_thread; + return 1; + } +diff --git a/lm_ots_verify.c b/lm_ots_verify.c +index 9706edd..1c79659 100644 +--- a/lm_ots_verify.c ++++ b/lm_ots_verify.c +@@ -89,7 +89,7 @@ bool lm_ots_validate_signature_compute( + PBLC_PREFIX_LEN ); + } + +- int i; ++ size_t i; + unsigned char tmp[ITER_MAX_LEN]; + + /* Preset the parts of tmp that don't change */ +diff --git a/sha256.h b/sha256.h +index e4c8a9c..3282c90 100644 +--- a/sha256.h ++++ b/sha256.h +@@ -5,7 +5,7 @@ + #include EXT_SHA256_H + #else + +-#define USE_OPENSSL 1 /* We use the OpenSSL implementation for SHA-256 */ ++#define USE_OPENSSL 0 /* We use the OpenSSL implementation for SHA-256 */ + /* (which is quite a bit faster than our portable */ + /* C version) */ + +-- +2.40.0 + diff --git a/tools/lms/lms_common.h b/tools/lms/lms_common.h new file mode 100644 index 000000000..9027df4b6 --- /dev/null +++ b/tools/lms/lms_common.h @@ -0,0 +1,136 @@ +/* lms_common.h + * + * Common callback functions used by LMS to write/read the private + * key file. + * + * + * Copyright (C) 2023 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 2 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef LMS_COMMON_H +#define LMS_COMMON_H +static int lms_write_key(const byte * priv, word32 privSz, void * context) +{ + FILE * file = NULL; + const char * filename = NULL; + int n_cmp = 0; + size_t n_read = 0; + size_t n_write = 0; + byte buff[HSS_MAX_PRIVATE_KEY_LEN]; + int err = 0; + + if (priv == NULL || context == NULL || privSz == 0) { + fprintf(stderr, "error: invalid write args\n"); + return WC_LMS_RC_BAD_ARG; + } + + filename = context; + + /* Open file for read and write. */ + file = fopen(filename, "r+"); + if (!file) { + /* Create the file if it didn't exist. */ + file = fopen(filename, "w+"); + if (!file) { + fprintf(stderr, "error: fopen(%s, \"w+\") failed: %d\n", filename, + ferror(file)); + return WC_LMS_RC_WRITE_FAIL; + } + } + + n_write = fwrite(priv, 1, privSz, file); + + if (n_write != privSz) { + fprintf(stderr, "error: wrote %zu, expected %d: %d\n", n_write, privSz, + ferror(file)); + return WC_LMS_RC_WRITE_FAIL; + } + + err = fclose(file); + if (err) { + fprintf(stderr, "error: fclose returned %d\n", err); + return WC_LMS_RC_WRITE_FAIL; + } + + /* Verify private key data has actually been written to persistent + * storage correctly. */ + file = fopen(filename, "r+"); + if (!file) { + fprintf(stderr, "error: fopen(%s, \"r+\") failed: %d\n", filename, + ferror(file)); + return WC_LMS_RC_WRITE_FAIL; + } + + XMEMSET(buff, 0, n_write); + + n_read = fread(buff, 1, n_write, file); + + if (n_read != n_write) { + fprintf(stderr, "error: read %zu, expected %zu: %d\n", n_read, n_write, + ferror(file)); + return WC_LMS_RC_WRITE_FAIL; + } + + n_cmp = XMEMCMP(buff, priv, n_write); + if (n_cmp != 0) { + fprintf(stderr, "error: write data was corrupted: %d\n", n_cmp); + return WC_LMS_RC_WRITE_FAIL; + } + + err = fclose(file); + if (err) { + fprintf(stderr, "error: fclose returned %d\n", err); + return WC_LMS_RC_WRITE_FAIL; + } + + return WC_LMS_RC_SAVED_TO_NV_MEMORY; +} + +static int lms_read_key(byte * priv, word32 privSz, void * context) +{ + FILE * file = NULL; + const char * filename = NULL; + size_t n_read = 0; + + if (priv == NULL || context == NULL || privSz == 0) { + fprintf(stderr, "error: invalid read args\n"); + return WC_LMS_RC_BAD_ARG; + } + + filename = context; + + file = fopen(filename, "rb"); + if (!file) { + fprintf(stderr, "error: fopen(%s, \"rb\") failed\n", filename); + return WC_LMS_RC_READ_FAIL; + } + + n_read = fread(priv, 1, privSz, file); + + if (n_read != privSz) { + fprintf(stderr, "error: read %zu, expected %d: %d\n", n_read, privSz, + ferror(file)); + return WC_LMS_RC_READ_FAIL; + } + + fclose(file); + + return WC_LMS_RC_READ_TO_MEMORY; +} +#endif /* LMS_COMMON_H */ diff --git a/tools/lms/lms_siglen.sh b/tools/lms/lms_siglen.sh new file mode 100755 index 000000000..5846a9bd7 --- /dev/null +++ b/tools/lms/lms_siglen.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# +# Prints the LMS signature length given 3 LMS parameters. +# +# The total signature length is built up from several sub +# signature lengths that need to be calculated. +# +# This assumes the LMS parameters are the same per each level +# tree. +# +# References: +# - https://datatracker.ietf.org/doc/html/rfc8554 +# - https://github.com/cisco/hash-sigs +# - https://eprint.iacr.org/2017/349.pdf + +# User parameters: +# levels = {1..8} +# height = {5, 10, 15, 20, 25} +# winternitz = {1, 2, 4, 8} +levels=3 +height=5 +winternitz=8 + +# Globals + +# The lm_pub_len is 4 less than the HSS pub len of 60. +ots_sig_len=0 +sig_len=0 +total_len=0 +lm_pub_len=56 + +# n and p. +# n == 32 by definition because this LMS implementation uses SHA256. +# p = f(w), it's a function of Winternitz value. +n=32 +p=0 + +# Functions + +# Get p given Winternitz value. +ots_get_p() { + if [[ $winternitz == 1 ]]; then + p=265 + else + p=$(((265 / winternitz) + 1)) + fi +} + +# Get the OTS sig len for given n and p. +ots_get_sig_len() { + ots_sig_len=$((4 + n * (p + 1))) +} + +# Get the merkle tree sig len for given height and ots_sig_len. +get_sig_len() { + sig_len=$((8 + $ots_sig_len + (n * $height))) +} + +# Finally, get the total signature length given the levels and sig_len. +get_total_len() { + total_len=$((4 + (levels * sig_len) + ((levels - 1) * (lm_pub_len)))) +} + +ots_get_p +ots_get_sig_len +get_sig_len +get_total_len + +echo "levels: $levels" +echo "height: $height" +echo "winternitz: $winternitz" +echo "#" +# Extra debugging info +# echo "n: $n" +# echo "p: $p" +# echo "#" +# echo "ots_sig_len: $ots_sig_len" +# echo "sig_len: $sig_len" +echo "total_len: $total_len" diff --git a/tools/scripts/renode-test-update.sh b/tools/scripts/renode-test-update.sh index b1119c4f7..a7736509f 100755 --- a/tools/scripts/renode-test-update.sh +++ b/tools/scripts/renode-test-update.sh @@ -19,6 +19,24 @@ quit_renode() { rm -f $RENODE_UART + +if (echo $TEST_OPTIONS | grep "LMS" &>/dev/null); then + # Need git. + apt install -y git + + # wolfSSL needs to be on latest master for LMS support. Also, we need to + # add the wolfssl module as a safe directory so docker can use it. + git config --global --add safe.directory /workspace/lib/wolfssl || exit 2 + cd lib/wolfssl && git checkout master && git pull && cd ../.. || exit 2 + + # Need to clone the hash-sigs repo, and patch it for wolfBoot build. + mkdir -p lib/hash-sigs/lib || exit 2 + git clone https://github.com/cisco/hash-sigs.git lib/hash-sigs/src || exit 2 + cd lib/hash-sigs/src && git checkout b0631b8891295bf2929e68761205337b7c031726 && \ + git apply ../../../tools/lms/0001-Patch-to-support-wolfBoot-LMS-build.patch &&\ + cd ../../.. || exit 2 +fi + make distclean make -C tools/keytools make -C tools/test-expect-version diff --git a/tools/test-renode.mk b/tools/test-renode.mk index cb06a0d2c..d9c643cb0 100644 --- a/tools/test-renode.mk +++ b/tools/test-renode.mk @@ -17,6 +17,9 @@ EXPVER=tools/test-expect-version/test-expect-version RENODE_EXPVER=$(EXPVER) $(RENODE_UART) RENODE_BINASSEMBLE=tools/bin-assemble/bin-assemble +LMS_OPTS=LMS_LEVELS=2 LMS_HEIGHT=5 LMS_WINTERNITZ=8 WOLFBOOT_SMALL_STACK=0 \ + IMAGE_SIGNATURE_SIZE=2644 IMAGE_HEADER_SIZE=5288 + ifneq ("$(wildcard $(WOLFBOOT_ROOT)/tools/keytools/keygen)","") KEYGEN_TOOL=$(WOLFBOOT_ROOT)/tools/keytools/keygen else @@ -90,6 +93,10 @@ ifeq ($(SIGN),RSA4096) SIGN_ARGS+= --rsa4096 endif +ifeq ($(SIGN),LMS) + SIGN_ARGS+= --lms +endif + ifeq ($(HASH),SHA256) SIGN_ARGS+= --sha256 endif @@ -250,6 +257,9 @@ renode-factory-rsa3072: FORCE renode-factory-rsa4096: FORCE make renode-factory SIGN=RSA4096 +renode-factory-lms: FORCE + make renode-factory SIGN=LMS $(LMS_OPTS) + renode-factory-all: FORCE ${Q}make keysclean ${Q}make renode-factory-ed25519 @@ -290,6 +300,9 @@ renode-update-rsa3072: FORCE renode-update-rsa4096: FORCE make renode-update SIGN=RSA4096 +renode-update-lms: FORCE + make renode-update SIGN=LMS $(LMS_OPTS) + renode-no-downgrade-ed25519: FORCE make renode-no-downgrade SIGN=ED448 @@ -308,6 +321,9 @@ renode-no-downgrade-rsa2048: FORCE renode-no-downgrade-rsa4096: FORCE make renode-no-downgrade SIGN=RSA4096 +renode-no-downgrade-lms: FORCE + make renode-no-downgrade SIGN=LMS $(LMS_OPTS) + renode-corrupted-ed25519: FORCE make renode-corrupted SIGN=ED448 @@ -326,6 +342,9 @@ renode-corrupted-rsa2048: FORCE renode-corrupted-rsa4096: FORCE make renode-corrupted SIGN=RSA4096 +renode-corrupted-lms: FORCE + make renode-corrupted SIGN=LMS $(LMS_OPTS) + renode-boot-time-all: FORCE tools/scripts/renode-test-all.sh 2>/dev/null |grep "BOOT TIME" @@ -346,6 +365,9 @@ renode-update-all: FORCE ${Q}make renode-update-rsa4096 RENODE_PORT=55162 ${Q}make keysclean ${Q}make renode-update SIGN=NONE RENODE_PORT=55163 + ${Q}make keysclean + ${Q}make renode-update-lms RENODE_PORT=55164 + ${Q}make keysclean ${Q}echo All tests in $@ OK! renode-no-downgrade-all: FORCE @@ -365,6 +387,8 @@ renode-no-downgrade-all: FORCE ${Q}make renode-no-downgrade-rsa4096 RENODE_PORT=55162 ${Q}make keysclean ${Q}make renode-no-downgrade SIGN=NONE RENODE_PORT=55163 + ${Q}make keysclean + ${Q}make renode-no-downgrade-lms RENODE_PORT=55164 ${Q}echo All tests in $@ OK! renode-corrupted-all: FORCE @@ -384,6 +408,8 @@ renode-corrupted-all: FORCE ${Q}make renode-corrupted-rsa4096 RENODE_PORT=55162 ${Q}make keysclean ${Q}make renode-corrupted SIGN=NONE RENODE_PORT=55163 + ${Q}make keysclean + ${Q}make renode-corrupted-lms RENODE_PORT=55164 ${Q}echo All tests in $@ OK! renode-update-all-armored: FORCE diff --git a/tools/test.mk b/tools/test.mk index 263667421..c7280e207 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -50,6 +50,9 @@ endif ifeq ($(SIGN),RSA4096) SIGN_ARGS+= --rsa4096 endif +ifeq ($(SIGN),LMS) + SIGN_ARGS+= --lms +endif # Make sign hash argument ifeq ($(HASH),SHA256)