From b97abd4ace4bda6bc7888bd74622c7b13bfc08c0 Mon Sep 17 00:00:00 2001 From: jordan Date: Fri, 11 Oct 2024 15:38:40 -0500 Subject: [PATCH] Add ML-DSA support. --- .github/workflows/test-renode-nrf52.yml | 4 + .github/workflows/test-sunnyday-simulator.yml | 12 ++ config/examples/sim-lms.config | 8 +- config/examples/sim-ml-dsa.config | 65 ++++++++ config/examples/sim-xmss.config | 4 +- docs/PQ.md | 90 +++++++++- include/loader.h | 9 +- include/user_settings.h | 23 +++ include/wolfboot/wolfboot.h | 23 +++ options.mk | 20 +++ src/image.c | 100 +++++++++++ tools/keytools/Makefile | 9 + tools/keytools/keygen.c | 157 +++++++++++++++++- tools/keytools/sign.c | 131 ++++++++++++++- tools/keytools/user_settings.h | 15 ++ tools/scripts/sim-pq-sunnyday-update.sh | 53 ++++++ tools/test-renode.mk | 4 + tools/test.mk | 3 + 18 files changed, 711 insertions(+), 19 deletions(-) create mode 100644 config/examples/sim-ml-dsa.config create mode 100755 tools/scripts/sim-pq-sunnyday-update.sh diff --git a/.github/workflows/test-renode-nrf52.yml b/.github/workflows/test-renode-nrf52.yml index e42fcf80a..c3c88086c 100644 --- a/.github/workflows/test-renode-nrf52.yml +++ b/.github/workflows/test-renode-nrf52.yml @@ -80,6 +80,10 @@ jobs: - name: Renode Tests ext_XMSS-SHA2_10_256 run: ./tools/renode/docker-test.sh "SIGN=ext_XMSS XMSS_PARAMS='XMSS-SHA2_10_256' WOLFBOOT_SMALL_STACK=0 IMAGE_SIGNATURE_SIZE=2500 IMAGE_HEADER_SIZE=5000" +# ML-DSA TEST + - name: Renode Tests ML-DSA-44 + run: ./tools/renode/docker-test.sh "SIGN=ML_DSA ML_DSA_LEVEL=2 WOLFBOOT_SMALL_STACK=0 IMAGE_SIGNATURE_SIZE=2420 IMAGE_HEADER_SIZE=4840" + - name: Upload Output Dir uses: actions/upload-artifact@v3 diff --git a/.github/workflows/test-sunnyday-simulator.yml b/.github/workflows/test-sunnyday-simulator.yml index a73c3cd2d..35c8baac3 100644 --- a/.github/workflows/test-sunnyday-simulator.yml +++ b/.github/workflows/test-sunnyday-simulator.yml @@ -546,3 +546,15 @@ jobs: run: | tools/scripts/sim-sunnyday-update.sh + - name: Run sunny day LMS update test + run: | + tools/scripts/sim-pq-sunnyday-update.sh config/examples/sim-lms.config + + - name: Run sunny day XMSS update test + run: | + tools/scripts/sim-pq-sunnyday-update.sh config/examples/sim-xmss.config + + - name: Run sunny day ML-DSA update test + run: | + tools/scripts/sim-pq-sunnyday-update.sh config/examples/sim-ml-dsa.config + diff --git a/config/examples/sim-lms.config b/config/examples/sim-lms.config index 66c7e2097..b183bedaf 100644 --- a/config/examples/sim-lms.config +++ b/config/examples/sim-lms.config @@ -27,15 +27,15 @@ ARCH=sim TARGET=sim SIGN?=LMS HASH?=SHA256 -LMS_LEVELS=2 -LMS_HEIGHT=5 +LMS_LEVELS=1 +LMS_HEIGHT=10 LMS_WINTERNITZ=8 +IMAGE_SIGNATURE_SIZE=1456 +IMAGE_HEADER_SIZE=4096 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 diff --git a/config/examples/sim-ml-dsa.config b/config/examples/sim-ml-dsa.config new file mode 100644 index 000000000..cc209b7cf --- /dev/null +++ b/config/examples/sim-ml-dsa.config @@ -0,0 +1,65 @@ +# ML-DSA signature example, based on sim.config example. +# +# The acceptable parameter values are those in FIPS 204: +# +# ML_DSA_LEVEL = {2, 3, 5} +# +# This corresponds to these security levels (from FIPS 204, Table 1.): +# +# Claimed Security Strength +# ML-DSA-44 Category 2 +# ML-DSA-65 Category 3 +# ML-DSA-87 Category 5 +# +# The signature, pub key, and priv key lengths are all a function +# of this parameter. Refer to this table (from FIPS 204, Table 2.) +# to configure your IMAGE_SIGNATURE_SIZE: +# +# Table 2. Sizes (in bytes) of keys and signatures of ML-DSA +# +# Private Key Public Key Signature Size +# ML-DSA-44 2560 1312 2420 +# ML-DSA-65 4032 1952 3309 +# ML-DSA-87 4896 2592 4627 +# + +ARCH=sim +TARGET=sim +SIGN?=ML_DSA +HASH?=SHA256 +WOLFBOOT_SMALL_STACK=0 +SPI_FLASH=0 +DEBUG=0 +DELTA_UPDATES=0 + +# +# ML-DSA config examples: +# +# Category 2: +ML_DSA_LEVEL=2 +IMAGE_SIGNATURE_SIZE=2420 +IMAGE_HEADER_SIZE?=8192 +# +# Category 3: +# ML_DSA_LEVEL=3 +# IMAGE_SIGNATURE_SIZE=3309 +# IMAGE_HEADER_SIZE?=8192 +# +# Category 5: +# ML_DSA_LEVEL=5 +# IMAGE_SIGNATURE_SIZE=4627 +# IMAGE_HEADER_SIZE?=12288 +# This example needsd larger sector size. +# WOLFBOOT_SECTOR_SIZE=0x3000 +# + +# sizes should be multiple of system page size +WOLFBOOT_PARTITION_SIZE=0x40000 +WOLFBOOT_SECTOR_SIZE=0x2000 +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/config/examples/sim-xmss.config b/config/examples/sim-xmss.config index 461e88b6c..bfb088564 100644 --- a/config/examples/sim-xmss.config +++ b/config/examples/sim-xmss.config @@ -17,11 +17,11 @@ SPI_FLASH=0 DEBUG=0 DELTA_UPDATES=0 IMAGE_SIGNATURE_SIZE=2500 -IMAGE_HEADER_SIZE?=5000 +IMAGE_HEADER_SIZE?=8192 # sizes should be multiple of system page size WOLFBOOT_PARTITION_SIZE=0x40000 -WOLFBOOT_SECTOR_SIZE=0x1000 +WOLFBOOT_SECTOR_SIZE=0x2000 WOLFBOOT_PARTITION_BOOT_ADDRESS=0x20000 # if on external flash, it should be multiple of system page size WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x60000 diff --git a/docs/PQ.md b/docs/PQ.md index 13b2eb950..8255e8611 100644 --- a/docs/PQ.md +++ b/docs/PQ.md @@ -1,8 +1,90 @@ # Post-Quantum Signatures -wolfBoot is adding support for post-quantum signatures. At present, support -for LMS/HSS (https://www.rfc-editor.org/rfc/rfc8554.html), and XMSS/XMSS^MT -(https://www.rfc-editor.org/rfc/rfc8391.html) has been added. +wolfBoot is continuously adding support for post-quantum (PQ) signature +algorithms as they mature. At present, support has been added for three NIST +approved PQ signature algorithms: + +- ML-DSA: https://csrc.nist.gov/pubs/fips/204/final +- LMS/HSS: https://csrc.nist.gov/projects/stateful-hash-based-signatures +- XMSS/XMSS^MT: https://csrc.nist.gov/projects/stateful-hash-based-signatures + +ML-DSA is a PQ lattice-based algorithm, derived from +CRYSTALS-DILITHIUM (a round three NIST finalist). + +LMS/HSS and XMSS/XMSS^MT are both PQ stateful hash-based signature (HBS) +schemes, recommended in NIST SP 800-208. + +In terms of relative tradeoffs: +- All three methods have fast verifying operations. +- All three methods have variable length signature sizes. +- ML-DSA key generation is much faster than LMS/HSS and XMSS/XMSS^MT. +- ML-DSA public keys are larger than LMS/HSS and XMSS/XMSS^MT, and + variable sized. +- LMS/HSS and XMSS/XMSS^MT have stateful private keys, which requires + more care with key generation and signing operations. + +See these config files for simulated target examples: + +- `config/examples/sim-ml-dsa.config` +- `config/examples/sim-lms.config` +- `config/examples/sim-xmss.config` + +## Lattice Based Signature Methods + +### ML-DSA + +ML-DSA (Module-Lattice Digital Signature Algorithm) was standardized in +FIPS 204 (https://csrc.nist.gov/pubs/fips/204/final), based on its +round 3 predecessor CRYSTALS-DILITHIUM. + +ML-DSA has three standardized parameter sets: + +- `ML-DSA-44` +- `ML-DSA-65` +- `ML-DSA-87` + +The numerical suffix (44, 65, 87) denotes the dimension of the matrix used +in the underlying lattice construction. + +The private key, public key, signature size, and overall security strength +all depend on the parameter set: + +``` +# +# Private Key Public Key Signature Size Security Strength +# ML-DSA-44 2560 1312 2420 Category 2 +# ML-DSA-65 4032 1952 3309 Category 3 +# ML-DSA-87 4896 2592 4627 Category 5 +# +``` + +### ML-DSA Config + +A new ML-DSA sim example has been added here: + +``` +config/examples/sim-ml-dsa.config +``` + +The security category level is configured with `ML_DSA_LEVEL=`, where +num = 2, 3, 5. Here is an example from the `sim-ml-dsa.config` for category +2: + +``` +# ML-DSA config examples: +# +# Category 2: +ML_DSA_LEVEL=2 +IMAGE_SIGNATURE_SIZE=2420 +IMAGE_HEADER_SIZE?=4840 +``` + +Note: The wolfcrypt implementation of ML-DSA (dilithium) builds to the +FIPS 204 final standard by default. If you wish to conform to the older +FIPS 204 draft standard, then build with `WOLFSSL_DILITHIUM_FIPS204_DRAFT` +instead. + +## Stateful Hash-Based Signature Methods LMS/HSS and XMSS/XMSS^MT are both post-quantum stateful hash-based signature (HBS) schemes. They are known for having small public keys, relatively fast @@ -19,7 +101,7 @@ See these links for more info on stateful HBS support 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 -## Supported PQ Signature Methods +### Supported PQ HBS Options These four PQ signature options are supported: - LMS: uses wolfcrypt implementation from `wc_lms.c`, and `wc_lms_impl.c`. diff --git a/include/loader.h b/include/loader.h index d9a8e930d..f1b614fe5 100644 --- a/include/loader.h +++ b/include/loader.h @@ -61,15 +61,18 @@ extern "C" { extern const unsigned char rsa4096_pub_key[]; extern unsigned int rsa4096_pub_key_len; # define IMAGE_SIGNATURE_SIZE (512) +/* In PQC methods the signature size is a function of + * the parameters. Therefore IMAGE_SIGNATURE_SIZE is + * set in options.mk from the .config file. */ #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_SIGN_XMSS) extern const unsigned char xmss_pub_key[]; extern unsigned int xmss_pub_key_len; +#elif defined(WOLFBOOT_SIGN_ML_DSA) + extern const unsigned char ml_dsa_pub_key[]; + extern unsigned int ml_dsa_pub_key_len; #elif !defined(WOLFBOOT_NO_SIGN) # error "No public key available for given signing algorithm." #endif /* Algorithm selection */ diff --git a/include/user_settings.h b/include/user_settings.h index 5e9e02209..d5e8c9ad0 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -227,6 +227,29 @@ extern int tolower(int c); # define NO_RSA #endif /* RSA */ +/* ML-DSA (dilithium) */ +#if defined(WOLFBOOT_SIGN_ML_DSA) +# define HAVE_DILITHIUM +# define WOLFSSL_WC_DILITHIUM +# define WOLFSSL_EXPERIMENTAL_SETTINGS + /* Wolfcrypt builds ML-DSA (dilithium) to the FIPS 204 final + * standard by default. Uncomment this if you want the draft + * version instead. */ + #if 0 + #define WOLFSSL_DILITHIUM_FIPS204_DRAFT + #endif +# define WOLFSSL_DILITHIUM_VERIFY_ONLY +# define WOLFSSL_DILITHIUM_NO_LARGE_CODE +# define WOLFSSL_DILITHIUM_SMALL +# define WOLFSSL_DILITHIUM_VERIFY_SMALL_MEM +# define WOLFSSL_DILITHIUM_VERIFY_NO_MALLOC +# define WOLFSSL_DILITHIUM_NO_ASN1 + /* dilithium needs these sha functions. */ +# define WOLFSSL_SHA3 +# define WOLFSSL_SHAKE256 +# define WOLFSSL_SHAKE128 +#endif /* WOLFBOOT_SIGN_ML_DSA */ + #ifdef WOLFBOOT_HASH_SHA3_384 # define WOLFSSL_SHA3 # if defined(NO_RSA) && !defined(WOLFBOOT_TPM) && \ diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index c082ad7ad..d404f5c62 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -86,6 +86,7 @@ extern "C" { #define AUTH_KEY_RSA3072 0x08 #define AUTH_KEY_LMS 0x09 #define AUTH_KEY_XMSS 0x10 +#define AUTH_KEY_ML_DSA 0x11 @@ -107,6 +108,7 @@ extern "C" { #define HDR_IMG_TYPE_AUTH_RSA3072 (AUTH_KEY_RSA3072 << 8) #define HDR_IMG_TYPE_AUTH_LMS (AUTH_KEY_LMS << 8) #define HDR_IMG_TYPE_AUTH_XMSS (AUTH_KEY_XMSS << 8) +#define HDR_IMG_TYPE_AUTH_ML_DSA (AUTH_KEY_ML_DSA << 8) #define HDR_IMG_TYPE_DIFF 0x00D0 @@ -127,6 +129,22 @@ extern "C" { #define KEYSTORE_PUBKEY_SIZE_RSA4096 576 #define KEYSTORE_PUBKEY_SIZE_LMS 60 #define KEYSTORE_PUBKEY_SIZE_XMSS 68 + /* ML-DSA pub key size is a function of parameters. + * This needs to be configurable. Default to security + * category 2. */ + #ifdef ML_DSA_LEVEL + #if ML_DSA_LEVEL == 2 + #define KEYSTORE_PUBKEY_SIZE_ML_DSA 1312 + #elif ML_DSA_LEVEL == 3 + #define KEYSTORE_PUBKEY_SIZE_ML_DSA 1952 + #elif ML_DSA_LEVEL == 5 + #define KEYSTORE_PUBKEY_SIZE_ML_DSA 2592 + #else + #error "Invalid ML_DSA_LEVEL!" + #endif + #else + #define KEYSTORE_PUBKEY_SIZE_ML_DSA 1312 + #endif /* ML_DSA_LEVEL */ /* Mask for key permissions */ #define KEY_VERIFY_ALL (0xFFFFFFFFU) @@ -237,6 +255,11 @@ extern "C" { # ifndef WOLFBOOT_UNIVERSAL_KEYSTORE # define KEYSTORE_PUBKEY_SIZE KEYSTORE_PUBKEY_SIZE_XMSS # endif + #elif defined(WOLFBOOT_SIGN_ML_DSA) + # define HDR_IMG_TYPE_AUTH HDR_IMG_TYPE_AUTH_ML_DSA + # ifndef WOLFBOOT_UNIVERSAL_KEYSTORE + # define KEYSTORE_PUBKEY_SIZE KEYSTORE_PUBKEY_SIZE_ML_DSA + # endif #else # error "No valid authentication mechanism selected. " \ "Please select a valid SIGN= option." diff --git a/options.mk b/options.mk index 4de595f9b..d3e8e72f8 100644 --- a/options.mk +++ b/options.mk @@ -487,6 +487,26 @@ ifneq (,$(filter $(SIGN), ext_LMS ext_XMSS)) CFLAGS +=-DWOLFSSL_EXPERIMENTAL_SETTINGS endif +ifeq ($(SIGN),ML_DSA) + # Use wolfcrypt ML-DSA dilithium implementation. + KEYGEN_OPTIONS+=--ml_dsa + SIGN_OPTIONS+=--ml_dsa + WOLFCRYPT_OBJS+= \ + ./lib/wolfssl/wolfcrypt/src/dilithium.o \ + ./lib/wolfssl/wolfcrypt/src/memory.o \ + ./lib/wolfssl/wolfcrypt/src/sha3.o \ + ./lib/wolfssl/wolfcrypt/src/wc_port.o \ + ./lib/wolfssl/wolfcrypt/src/hash.o + CFLAGS+=-D"WOLFBOOT_SIGN_ML_DSA" \ + -D"IMAGE_SIGNATURE_SIZE"=$(IMAGE_SIGNATURE_SIZE) \ + -D"ML_DSA_LEVEL"=$(ML_DSA_LEVEL) + ifeq ($(WOLFBOOT_SMALL_STACK),1) + $(error WOLFBOOT_SMALL_STACK with ML-DSA not supported yet) + else + STACK_USAGE=19544 + endif +endif + ifeq ($(RAM_CODE),1) CFLAGS+= -D"RAM_CODE" endif diff --git a/src/image.c b/src/image.c index 2464d635d..6913e265d 100644 --- a/src/image.c +++ b/src/image.c @@ -472,6 +472,106 @@ static void wolfBoot_verify_signature(uint8_t key_slot, } #endif /* WOLFBOOT_SIGN_XMSS */ +#ifdef WOLFBOOT_SIGN_ML_DSA +#include +static void wolfBoot_verify_signature(uint8_t key_slot, + struct wolfBoot_image *img, uint8_t *sig) +{ + int ret = 0; + MlDsaKey ml_dsa; + uint8_t * pubkey = NULL; + int pub_len = 0; + int sig_len = 0; + int verify_res = 0; + + wolfBoot_printf("info: ML-DSA wolfBoot_verify_signature\n"); + + pubkey = keystore_get_buffer(key_slot); + + if (pubkey == NULL) { + wolfBoot_printf("error: ML-DSA pubkey not found\n"); + return; + } + + ret = wc_MlDsaKey_Init(&ml_dsa, NULL, INVALID_DEVID); + + if (ret != 0) { + wolfBoot_printf("error: wc_MlDsaKey_Init returned %d\n", ret); + } + + if (ret == 0) { + /* Set the ML-DSA security level. */ + ret = wc_MlDsaKey_SetParams(&ml_dsa, ML_DSA_LEVEL); + + if (ret != 0) { + wolfBoot_printf("error: wc_MlDsaKey_SetParams(%d)" \ + " returned %d\n", ML_DSA_LEVEL, ret); + } + } + + /* Make sure pub key matches parameters. */ + if (ret == 0) { + ret = wc_MlDsaKey_GetPubLen(&ml_dsa, &pub_len); + + if (ret != 0 || pub_len <= 0) { + wolfBoot_printf("error: wc_MlDsaKey_GetPubLen returned %d\n", ret); + ret = -1; + } + else if (pub_len != KEYSTORE_PUBKEY_SIZE_ML_DSA) { + wolfBoot_printf("error: ML-DSA pub key mismatch: got %d bytes " \ + "expected %d\n", pub_len, KEYSTORE_PUBKEY_SIZE_ML_DSA); + ret = -1; + } + } + + /* Make sure sig len matches parameters. */ + if (ret == 0) { + ret = wc_MlDsaKey_GetSigLen(&ml_dsa, &sig_len); + + if (ret != 0 || sig_len <= 0) { + wolfBoot_printf("error: wc_MlDsaKey_GetPubLen returned %d\n", ret); + ret = -1; + } + else if (sig_len != IMAGE_SIGNATURE_SIZE) { + wolfBoot_printf("error: ML-DSA sig len mismatch: got %d bytes " \ + "expected %d\n", sig_len, IMAGE_SIGNATURE_SIZE); + ret = -1; + } + } + + if (ret == 0) { + /* Now import pub key. */ + ret = wc_MlDsaKey_ImportPubRaw(&ml_dsa, pubkey, pub_len); + + if (ret != 0) { + wolfBoot_printf("error: wc_MlDsaKey_ImportPubRaw returned: %d\n", + ret); + } + } + + if (ret == 0) { + wolfBoot_printf("info: using ML-DSA security level: %d\n", + ML_DSA_LEVEL); + + /* Finally verify signagure. */ + ret = wc_MlDsaKey_Verify(&ml_dsa, sig, IMAGE_SIGNATURE_SIZE, + img->sha_hash, WOLFBOOT_SHA_DIGEST_SIZE, + &verify_res); + + if (ret == 0 && verify_res == 1) { + wolfBoot_printf("info: wc_MlDsaKey_Verify returned OK\n"); + wolfBoot_image_confirm_signature_ok(img); + } + else { + wolfBoot_printf("error: wc_MlDsaKey_Verify returned: ret=%d, " + "res=%d\n", ret, verify_res); + } + } + + wc_MlDsaKey_Free(&ml_dsa); +} +#endif /* WOLFBOOT_SIGN_ML_DSA */ + #endif /* WOLFBOOT_TPM && WOLFBOOT_TPM_VERIFY */ diff --git a/tools/keytools/Makefile b/tools/keytools/Makefile index 9573bc750..65bf71b85 100644 --- a/tools/keytools/Makefile +++ b/tools/keytools/Makefile @@ -162,6 +162,15 @@ OBJS_REAL+=\ $(XMSSDIR)/utils.o endif +# Add wolfcrypt ML-DSA (dilithium) implementation. +ifeq ($(SIGN),ML_DSA) + OBJS_REAL+=$(WOLFDIR)/wolfcrypt/src/dilithium.o + + CFLAGS += -D"WOLFBOOT_SIGN_ML_DSA" \ + -D"IMAGE_SIGNATURE_SIZE"=$(IMAGE_SIGNATURE_SIZE) \ + -D"ML_DSA_LEVEL"=$(ML_DSA_LEVEL) +endif + OBJS_VIRT=$(addprefix $(OBJDIR), $(notdir $(OBJS_REAL))) vpath %.c $(WOLFDIR)/wolfcrypt/src/ vpath %.c $(WOLFBOOTDIR)/src/ diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index 418144f64..aa78abc15 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -79,6 +79,10 @@ #endif #endif +#ifdef WOLFSSL_WC_DILITHIUM + #include +#endif + #include #include #ifdef DEBUG_SIGNTOOL @@ -103,6 +107,7 @@ #define KEYGEN_RSA3072 8 #define KEYGEN_LMS 9 #define KEYGEN_XMSS 10 +#define KEYGEN_ML_DSA 11 /* Globals */ static FILE *fpub, *fpub_image; @@ -117,7 +122,12 @@ static int saveAsDer = 0; static WC_RNG rng; #ifndef KEYSLOT_MAX_PUBKEY_SIZE - #define KEYSLOT_MAX_PUBKEY_SIZE 576 + #if defined(WOLFSSL_WC_DILITHIUM) + /* ML-DSA pub keys are big. */ + #define KEYSLOT_MAX_PUBKEY_SIZE KEYSTORE_PUBKEY_SIZE_ML_DSA + #else + #define KEYSLOT_MAX_PUBKEY_SIZE 576 + #endif #endif struct keystore_slot { @@ -300,7 +310,8 @@ const char KType[][17] = { "AUTH_KEY_ECC521", "AUTH_KEY_RSA3072", "AUTH_KEY_LMS", - "AUTH_KEY_XMSS" + "AUTH_KEY_XMSS", + "AUTH_KEY_ML_DSA" }; const char KSize[][29] = { @@ -314,7 +325,8 @@ const char KSize[][29] = { "KEYSTORE_PUBKEY_SIZE_ECC521", "KEYSTORE_PUBKEY_SIZE_RSA3072", "KEYSTORE_PUBKEY_SIZE_LMS", - "KEYSTORE_PUBKEY_SIZE_XMSS" + "KEYSTORE_PUBKEY_SIZE_XMSS", + "KEYSTORE_PUBKEY_SIZE_ML_DSA" }; const char KName[][8] = { @@ -328,7 +340,8 @@ const char KName[][8] = { "ECC521", "RSA3072", "LMS", - "XMSS" + "XMSS", + "ML_DSA" }; #define MAX_PUBKEYS 64 @@ -375,6 +388,9 @@ static uint32_t get_pubkey_size(uint32_t keyType) case KEYGEN_XMSS: size = KEYSTORE_PUBKEY_SIZE_XMSS; break; + case KEYGEN_ML_DSA: + size = KEYSTORE_PUBKEY_SIZE_ML_DSA; + break; default: size = 0; } @@ -404,6 +420,13 @@ void keystore_add(uint32_t ktype, uint8_t *key, uint32_t sz, const char *keyfile sl.part_id_mask = id_mask; sl.pubkey_size = get_pubkey_size(ktype); + + if (sl.pubkey_size > sizeof(sl.pubkey)){ + printf("error: %s pubkey larger than keystore: %d > %zu\n", + KName[ktype], sl.pubkey_size, sizeof(sl.pubkey)); + exit(1); + } + memcpy(sl.pubkey, key, sl.pubkey_size); #ifdef WOLFBOOT_UNIVERSAL_KEYSTORE slot_size = sizeof(struct keystore_slot); @@ -773,6 +796,120 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask) } #endif /* if defined(WOLFSSL_HAVE_XMSS) */ +#if defined(WOLFSSL_WC_DILITHIUM) + +static void keygen_ml_dsa(const char *priv_fname, uint32_t id_mask) +{ + FILE * fpriv = NULL; + MlDsaKey key; + int ret; + byte * priv = NULL; + byte pub[KEYSTORE_PUBKEY_SIZE_ML_DSA]; + word32 priv_len = 0; + word32 pub_len = 0; + int ml_dsa_priv_len = 0; + int ml_dsa_pub_len = 0; + + ret = wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); + if (ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_Init returned %d\n", ret); + exit(1); + } + + ret = wc_MlDsaKey_SetParams(&key, ML_DSA_LEVEL); + if (ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_SetParams(%d) returned %d\n", + ML_DSA_LEVEL, ret); + exit(1); + } + + /* Make the key pair. */ + ret = wc_MlDsaKey_MakeKey(&key, &rng); + if (ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_MakeKey returned %d\n", ret); + exit(1); + } + + /* Get the ML-DSA public key length. */ + ret = wc_MlDsaKey_GetPubLen(&key, &ml_dsa_pub_len); + if (ret != 0 || ml_dsa_pub_len <= 0) { + printf("error: wc_MlDsaKey_GetPrivLen returned %d\n", + ret); + exit(1); + } + + /* Get the ML-DSA private key length. This API returns + * the public + private length. */ + ret = wc_MlDsaKey_GetPrivLen(&key, &ml_dsa_priv_len); + if (ret != 0 || ml_dsa_priv_len <= 0) { + printf("error: wc_MlDsaKey_GetPrivLen returned %d\n", + ret); + exit(1); + } + + if (ml_dsa_priv_len <= ml_dsa_pub_len) { + printf("error: ml-dsa: unexpected key lengths: %d, %d", + ml_dsa_priv_len, ml_dsa_pub_len); + exit(1); + } + else { + ml_dsa_priv_len -= ml_dsa_pub_len; + } + + priv = malloc(ml_dsa_priv_len); + if (priv == NULL) { + fprintf(stderr, "error: malloc(%d) failed\n", ml_dsa_priv_len); + exit(1); + } + + /* Set the expected key lengths. */ + pub_len = (word32) ml_dsa_pub_len; + priv_len = (word32) ml_dsa_priv_len; + + ret = wc_MlDsaKey_ExportPubRaw(&key, pub, &pub_len); + if (ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_ExportPubRaw returned %d\n", ret); + exit(1); + } + + ret = wc_MlDsaKey_ExportPrivRaw(&key, priv, &priv_len); + if (ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_ExportPrivRaw returned %d\n", ret); + exit(1); + } + + if (pub_len != sizeof(pub)) { + fprintf(stderr, "error: wc_MlDsaKey_ExportPubRaw returned pub_len=%d, " \ + "expected %zu\n", pub_len, sizeof(pub)); + exit(1); + } + + if ((int) priv_len != ml_dsa_priv_len) { + fprintf(stderr, "error: ml_dsa priv key mismatch: got %d " \ + "bytes, expected %d\n", priv_len, ml_dsa_priv_len); + exit(1); + } + + fpriv = fopen(priv_fname, "wb"); + + if (fpriv == NULL) { + fprintf(stderr, "error: fopen(%s) failed: %s", + priv_fname, strerror(errno)); + exit(1); + } + + fwrite(priv, priv_len, 1, fpriv); + fwrite(pub, pub_len, 1, fpriv); + fclose(fpriv); + + keystore_add(KEYGEN_ML_DSA, pub, KEYSTORE_PUBKEY_SIZE_ML_DSA, + priv_fname, id_mask); + + wc_MlDsaKey_Free(&key); + free(priv); + priv = NULL; +} +#endif /* if defined(WOLFSSL_WC_DILITHIUM) */ static void key_gen_check(const char *kfilename) { @@ -848,6 +985,13 @@ static void key_generate(uint32_t ktype, const char *kfilename, uint32_t id_mask keygen_xmss(kfilename, id_mask); break; #endif + +#ifdef WOLFSSL_WC_DILITHIUM + case KEYGEN_ML_DSA: + keygen_ml_dsa(kfilename, id_mask); + break; +#endif + } /* end switch */ } @@ -1009,6 +1153,11 @@ int main(int argc, char** argv) else if (strcmp(argv[i], "--xmss") == 0) { keytype = KEYGEN_XMSS; } +#endif +#if defined(WOLFSSL_WC_DILITHIUM) + else if (strcmp(argv[i], "--ml_dsa") == 0) { + keytype = KEYGEN_ML_DSA; + } #endif else if (strcmp(argv[i], "--force") == 0) { force = 1; diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 58459cb3f..cc119216a 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -124,6 +124,10 @@ static inline int fp_truncate(FILE *f, size_t len) #endif #endif +#ifdef WOLFSSL_WC_DILITHIUM + #include +#endif + #ifdef DEBUG_SIGNTOOL #include #endif @@ -186,6 +190,7 @@ static inline int fp_truncate(FILE *f, size_t len) #define SIGN_ECC521 HDR_IMG_TYPE_AUTH_ECC521 #define SIGN_LMS HDR_IMG_TYPE_AUTH_LMS #define SIGN_XMSS HDR_IMG_TYPE_AUTH_XMSS +#define SIGN_ML_DSA HDR_IMG_TYPE_AUTH_ML_DSA #define ENC_OFF 0 @@ -246,6 +251,9 @@ static union { #ifdef WOLFSSL_HAVE_XMSS XmssKey xmss; #endif +#ifdef WOLFSSL_WC_DILITHIUM + MlDsaKey ml_dsa; +#endif } key; struct cmd_options { @@ -467,9 +475,13 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz, uint32_t idx = 0; int io_sz; FILE *f; -#ifdef WOLFSSL_HAVE_XMSS +#if defined(WOLFSSL_HAVE_XMSS) word32 priv_sz = 0; #endif +#if defined(WOLFSSL_WC_DILITHIUM) + int priv_sz = 0; + int pub_sz = 0; +#endif /* open and load key buffer */ *key_buffer = NULL; @@ -746,6 +758,63 @@ static uint8_t *load_key(uint8_t **key_buffer, uint32_t *key_buffer_sz, } #endif /* WOLFSSL_HAVE_XMSS */ +#ifdef WOLFSSL_WC_DILITHIUM + FALL_THROUGH; /* we didn't solve the key, keep trying */ + case SIGN_ML_DSA: + ret = wc_MlDsaKey_GetPubLen(&key.ml_dsa, &pub_sz); + + if (ret != 0 || pub_sz <= 0) { + printf("error: wc_MlDsaKey_GetPubLen returned %d\n", ret); + break; + } + + /* Get the ML-DSA private key length. This API returns + * the public + private length. */ + ret = wc_MlDsaKey_GetPrivLen(&key.ml_dsa, &priv_sz); + + if (ret != 0 || priv_sz <= 0) { + printf("error: wc_MlDsaKey_GetPrivLen returned %d\n", ret); + break; + } + + if (priv_sz <= pub_sz) { + printf("error: ml-dsa: unexpected key lengths: %d, %d", + priv_sz, pub_sz); + break; + } + else { + priv_sz -= pub_sz; + } + + printf("info: ml-dsa priv len: %d\n", priv_sz); + printf("info: ml-dsa pub len: %d\n", pub_sz); + + if ((int)*key_buffer_sz == (priv_sz + pub_sz)) { + /* priv + pub */ + ret = wc_MlDsaKey_ImportPrivRaw(&key.ml_dsa, *key_buffer, + priv_sz); + *pubkey = (*key_buffer) + priv_sz; + *pubkey_sz = (*key_buffer_sz) - priv_sz; + ret = 0; + printf("Found ml-dsa key\n"); + break; + } + else if ((int)*key_buffer_sz == pub_sz) { + /* pub only */ + *pubkey = (*key_buffer); + *pubkey_sz = pub_sz; + ret = 0; + printf("Found ml-dsa public only key\n"); + break; + } + else { + /* We don't recognize this as an ML-DSA pub or private key. */ + printf("error: unrecognized ml-dsa key size: %d\n", + *key_buffer_sz); + ret = -1; + } +#endif /* WOLFSSL_WC_DILITHIUM */ + break; } /* end switch (CMD.sign) */ @@ -915,6 +984,19 @@ static int sign_digest(int sign, int hash_algo, } else #endif /* WOLFSSL_HAVE_XMSS */ +#ifdef WOLFSSL_WC_DILITHIUM + if (sign == SIGN_ML_DSA) { + /* Nothing else to do, ready to sign. */ + if (ret == 0) { + ret = wc_MlDsaKey_Sign(&key.ml_dsa, signature, signature_sz, + digest, digest_sz, &rng); + } + if (ret != 0) { + fprintf(stderr, "error signing with XMSS: %d\n", ret); + } + } + else +#endif /* WOLFSSL_WC_DILITHIUM */ { ret = NOT_COMPILED_IN; } @@ -1871,6 +1953,12 @@ int main(int argc, char** argv) CMD.sign = SIGN_XMSS; sign_str = "XMSS"; } +#endif +#ifdef HAVE_DILITHIUM + else if (strcmp(argv[i], "--ml_dsa") == 0) { + CMD.sign = SIGN_ML_DSA; + sign_str = "ML-DSA"; + } #endif else if (strcmp(argv[i], "--sha256") == 0) { CMD.hash_algo = HASH_SHA256; @@ -2250,10 +2338,44 @@ int main(int argc, char** argv) CMD.signature_sz = sig_sz; } #endif /* WOLFSSL_HAVE_XMSS */ +#ifdef WOLFSSL_WC_DILITHIUM + else if (CMD.sign == SIGN_ML_DSA) { + int ml_dsa_ret = 0; + int sig_sz = 0; + + ml_dsa_ret = wc_MlDsaKey_Init(&key.ml_dsa, NULL, INVALID_DEVID); + if (ml_dsa_ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_Init returned %d\n", ml_dsa_ret); + exit(1); + } + + ml_dsa_ret = wc_MlDsaKey_SetParams(&key.ml_dsa, ML_DSA_LEVEL); + if (ml_dsa_ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_SetParamStr(%d)" \ + " returned %d\n", ML_DSA_LEVEL, ret); + exit(1); + } + + printf("info: using ML-DSA parameters: %d\n", ML_DSA_LEVEL); + + ml_dsa_ret = wc_MlDsaKey_GetSigLen(&key.ml_dsa, &sig_sz); + if (ml_dsa_ret != 0) { + fprintf(stderr, "error: wc_MlDsaKey_GetSigLen returned %d\n", + ml_dsa_ret); + exit(1); + } + + printf("info: ML-DSA signature size: %d\n", sig_sz); + + CMD.header_sz = 2 * sig_sz; + CMD.signature_sz = sig_sz; + } +#endif /* WOLFSSL_WC_DILITHIUM */ if (((CMD.sign != NO_SIGN) && (CMD.signature_sz == 0)) || CMD.header_sz == 0) { - printf("Invalid hash or signature type! %d\n", CMD.sign); + printf("Invalid hash or signature type! %d, %d, %d\n", CMD.sign, + CMD.signature_sz, CMD.header_sz); exit(2); } @@ -2311,6 +2433,11 @@ int main(int argc, char** argv) else if (CMD.sign == SIGN_XMSS) { #ifdef WOLFSSL_HAVE_XMSS wc_XmssKey_Free(&key.xmss); +#endif + } + else if (CMD.sign == SIGN_ML_DSA) { +#ifdef WOLFSSL_WC_DILITHIUM + wc_MlDsaKey_Free(&key.ml_dsa); #endif } return ret; diff --git a/tools/keytools/user_settings.h b/tools/keytools/user_settings.h index 5ffb49e63..ca66ac942 100644 --- a/tools/keytools/user_settings.h +++ b/tools/keytools/user_settings.h @@ -73,6 +73,21 @@ #define WOLFSSL_SHA3 #undef NO_SHA256 +/* ML-DSA (dilithium) */ +#if defined(WOLFBOOT_SIGN_ML_DSA) +# define HAVE_DILITHIUM +# define WOLFSSL_WC_DILITHIUM +# define WOLFSSL_EXPERIMENTAL_SETTINGS + /* Wolfcrypt builds ML-DSA (dilithium) to the FIPS 204 final + * standard by default. Uncomment this if you want the draft + * version instead. */ + #if 0 + #define WOLFSSL_DILITHIUM_FIPS204_DRAFT + #endif + /* dilithium needs these sha functions. */ +# define WOLFSSL_SHAKE128 +#endif /* WOLFBOOT_SIGN_ML_DSA */ + /* ASN */ #define WOLFSSL_ASN_TEMPLATE diff --git a/tools/scripts/sim-pq-sunnyday-update.sh b/tools/scripts/sim-pq-sunnyday-update.sh new file mode 100755 index 000000000..104b2a11d --- /dev/null +++ b/tools/scripts/sim-pq-sunnyday-update.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# +# For PQ methods the keytools must be built to match the alg. +# + +print_usage_and_die() { + echo "usage:" + echo " ./tools/scripts/sim-pq-sunnyday-update.sh " + echo "" + echo "example:" + echo " ./tools/scripts/sim-pq-sunnyday-update.sh config/examples/sim-ml-dsa.config" + exit 1 +} + +err_and_die() { + echo "error: $1" + exit 1 +} + +if [ $# -ne 1 ]; then + print_usage_and_die +fi + +sim_pq=$1 + +if [ ! -f $sim_pq ]; then + err_and_die "file not found: $sim_pq" +fi + +cp $sim_pq .config || err_and_die "cp $sim_pq" + +make distclean; make clean; + +make keytools || err_and_die "keytools build failed" + +make test-sim-internal-flash-with-update || err_and_die "make sim failed" + +V=`./wolfboot.elf update_trigger get_version 2>/dev/null` +if [ "x$V" != "x1" ]; then + echo "Failed first boot with update_trigger" + exit 1 +fi + +V=`./wolfboot.elf success get_version 2>/dev/null` +if [ "x$V" != "x2" ]; then + echo "Failed update (V: $V)" + exit 1 +fi + +echo Test successful. +exit 0 + + diff --git a/tools/test-renode.mk b/tools/test-renode.mk index 171207e56..6de2ba582 100644 --- a/tools/test-renode.mk +++ b/tools/test-renode.mk @@ -102,6 +102,10 @@ ifeq ($(SIGN),XMSS) SIGN_ARGS+= --xmss endif +ifeq ($(SIGN),ML_DSA) + SIGN_ARGS+= --ml_dsa +endif + ifeq ($(HASH),SHA256) SIGN_ARGS+= --sha256 endif diff --git a/tools/test.mk b/tools/test.mk index 1cd9e20ee..464e71d40 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -57,6 +57,9 @@ endif ifneq (,$(filter $(SIGN), XMSS ext_XMSS)) SIGN_ARGS+= --xmss endif +ifeq ($(SIGN),ML_DSA) + SIGN_ARGS+= --ml_dsa +endif # Make sign hash argument ifeq ($(HASH),SHA256)