Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for hybrid authentication (two ciphers) #510

Merged
merged 9 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/test-sunnyday-simulator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,20 @@ jobs:
run: |
tools/scripts/sim-pq-sunnyday-update.sh config/examples/sim-ml-dsa.config

# 64 Bit simulator, Hybrid auth ML_DSA + ECDSA
#
- name: make clean
run: |
make distclean

- name: Select config (64 bit simulator) Hybrid ML_DSA + ECC
run: |
cp config/examples/sim-ml-dsa-ecc-hybrid.config .config

- name: Build wolfboot.elf
run: |
make clean && make

- name: Run sunny day hybrid boot test
run: |
./wolfboot.elf get_version
4 changes: 2 additions & 2 deletions .github/workflows/test-tpm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ jobs:
with:
arch: host
config-file: ./config/examples/sim-tpm-seal.config
make-args: SIGN=ECC256 HASH=SHA256 POLICY_FILE=policy.bin
make-args: SIGN=ECC256 HASH=SHA256 POLICY_FILE=policy.bin IMAGE_HEADER_SIZE=512
keyauthstr: TestAuth
sealauthstr: SealAuth

Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
with:
arch: host
config-file: ./config/examples/sim-tpm-seal.config
make-args: SIGN=ECC256 HASH=SHA256 POLICY_FILE=policy.bin
make-args: SIGN=ECC256 HASH=SHA256 POLICY_FILE=policy.bin IMAGE_HEADER_SIZE=512
keyauthstr: TestAuth

sim_tpm_seal_noauth_ecc384:
Expand Down
26 changes: 23 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,13 @@ ifeq ($(FLASH_OTP_KEYSTORE),1)
MAIN_TARGET+=tools/keytools/otp/otp-keystore-primer.bin
endif

ifneq ($(SIGN_SECONDARY),)
SECONDARY_PRIVATE_KEY=wolfboot_signing_second_private_key.der
endif

ASFLAGS:=$(CFLAGS)

all: $(MAIN_TARGET)
all: $(SECONDARY_PRIVATE_KEY) $(MAIN_TARGET)

stage1: stage1/loader_stage1.bin
stage1/loader_stage1.bin: wolfboot.elf
Expand Down Expand Up @@ -203,6 +207,15 @@ $(PRIVATE_KEY):
$(Q)(test $(SIGN) = NONE) && (echo "// SIGN=NONE" > src/keystore.c) || true
$(Q)(test "$(FLASH_OTP_KEYSTORE)" = "1") && (make -C tools/keytools/otp) || true

$(SECONDARY_PRIVATE_KEY): $(PRIVATE_KEY) keystore.der
$(Q)$(MAKE) keytools_check
$(Q)rm -f src/keystore.c
$(Q)dd if=keystore.der of=pubkey_1.der bs=1 skip=16
$(Q)(test $(SIGN_SECONDARY) = NONE) || ("$(KEYGEN_TOOL)" \
$(KEYGEN_OPTIONS) -i pubkey_1.der $(SECONDARY_KEYGEN_OPTIONS) \
-g $(SECONDARY_PRIVATE_KEY)) || true
$(Q)(test "$(FLASH_OTP_KEYSTORE)" = "1") && (make -C tools/keytools/otp) || true

keytools: include/target.h
@echo "Building key tools"
@$(MAKE) -C tools/keytools -s clean
Expand All @@ -220,7 +233,12 @@ swtpmtools:

test-app/image_v1_signed.bin: $(BOOT_IMG)
@echo "\t[SIGN] $(BOOT_IMG)"
$(Q)(test $(SIGN) = NONE) || "$(SIGN_TOOL)" $(SIGN_OPTIONS) $(BOOT_IMG) $(PRIVATE_KEY) 1
@echo "\tSECONDARY_SIGN_OPTIONS=$(SECONDARY_SIGN_OPTIONS)"
@echo "\tSECONDARY_PRIVATE_KEY=$(SECONDARY_PRIVATE_KEY)"

$(Q)(test $(SIGN) = NONE) || "$(SIGN_TOOL)" $(SIGN_OPTIONS) \
$(SECONDARY_SIGN_OPTIONS) $(BOOT_IMG) $(PRIVATE_KEY) \
$(SECONDARY_PRIVATE_KEY) 1 || true
$(Q)(test $(SIGN) = NONE) && "$(SIGN_TOOL)" $(SIGN_OPTIONS) $(BOOT_IMG) 1 || true

test-app/image.elf: wolfboot.elf
Expand Down Expand Up @@ -312,7 +330,7 @@ keys: $(PRIVATE_KEY)

clean:
$(Q)rm -f src/*.o hal/*.o hal/spi/*.o test-app/*.o src/x86/*.o
$(Q)rm -f lib/wolfssl/wolfcrypt/src/*.o lib/wolfTPM/src/*.o lib/wolfTPM/hal/*.o
$(Q)rm -f lib/wolfssl/wolfcrypt/src/*.o lib/wolfTPM/src/*.o lib/wolfTPM/hal/*.o lib/wolfTPM/examples/pcr/*.o
$(Q)rm -f lib/wolfssl/wolfcrypt/src/port/Renesas/*.o
$(Q)rm -f wolfboot.bin wolfboot.elf wolfboot.map test-update.rom wolfboot.hex
$(Q)rm -f $(MACHINE_OBJ) $(MAIN_TARGET) $(LSCRIPT)
Expand Down Expand Up @@ -408,6 +426,8 @@ tools/keytools/otp/otp-keystore-primer.bin: FORCE
make -C tools/keytools/otp clean
make -C tools/keytools/otp

secondary: $(SECONDARY_PRIVATE_KEY)

%.o:%.c
@echo "\t[CC-$(ARCH)] $@"
$(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $^
Expand Down
11 changes: 8 additions & 3 deletions arch.mk
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ SPI_TARGET=$(TARGET)
# Default UART driver name
UART_TARGET=$(TARGET)

# Include SHA256 module because it's implicitly needed by RSA
WOLFCRYPT_OBJS+=./lib/wolfssl/wolfcrypt/src/sha256.o
# Include some modules by default
WOLFCRYPT_OBJS+=./lib/wolfssl/wolfcrypt/src/sha256.o \
./lib/wolfssl/wolfcrypt/src/hash.o \
./lib/wolfssl/wolfcrypt/src/memory.o \
./lib/wolfssl/wolfcrypt/src/wc_port.o \
./lib/wolfssl/wolfcrypt/src/wolfmath.o


ifeq ($(ARCH),x86_64)
CFLAGS+=-DARCH_x86_64
Expand Down Expand Up @@ -239,7 +244,6 @@ else
CORTEXM_ARM_EXTRA_OBJS=
CORTEXM_ARM_EXTRA_CFLAGS=
SECURE_OBJS+=./src/wc_callable.o
SECURE_OBJS+=./lib/wolfssl/wolfcrypt/src/random.o
CFLAGS+=-DWOLFCRYPT_SECURE_MODE
SECURE_LDFLAGS+=-Wl,--cmse-implib -Wl,--out-implib=./src/wc_secure_calls.o
endif
Expand Down Expand Up @@ -1025,6 +1029,7 @@ ifeq ($(TARGET),sim)
LD_END_GROUP=
BOOT_IMG=test-app/image.elf
CFLAGS+=-DARCH_SIM
CFLAGS+=-DWOLFBOOT_USE_STDLIBC
ifeq ($(FORCE_32BIT),1)
CFLAGS+=-m32
LDFLAGS+=-m32
Expand Down
26 changes: 26 additions & 0 deletions config/examples/sim-ml-dsa-ecc-hybrid.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
ARCH=sim
TARGET=sim
SIGN?=ML_DSA
ML_DSA_LEVEL=3
IMAGE_SIGNATURE_SIZE=3309
IMAGE_HEADER_SIZE?=8192
HASH?=SHA256
WOLFBOOT_SMALL_STACK?=0
SPI_FLASH=0
DEBUG=0
WOLFBOOT_UNIVERSAL_KEYSTORE=1
SIGN_SECONDARY=ECC384

# sizes should be multiple of system page size
WOLFBOOT_PARTITION_SIZE=0x40000
WOLFBOOT_SECTOR_SIZE=0x1000
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
# if on external flash, it should be multiple of system page size
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000

# required for keytools
WOLFBOOT_FIXED_PARTITIONS=1

# For debugging XMALLOC/XFREE
#CFLAGS_EXTRA+=-DWOLFBOOT_DEBUG_MALLOC
103 changes: 103 additions & 0 deletions docs/PQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,109 @@ parameter set: XMSSMT-SHA2_20/2_256
signature length: 4963
```

## Hybrid mode (classic + PQ)

wolfBoot supports a hybrid mode where both classic and PQ signatures are verified,
sequentially. This allows for a gradual transition from classic to PQ signatures,
and protects the secure boot mechanism from potential vulnerabilities in either of
the two ciphers in use.

The hybrid mode is enabled by setting a `SECONDARY_SIGN` option in the config file,
which specifies the secondary signature algorithm to be used. The secondary signature
option requires the option `WOLFBOOT_UNIVERSAL_KEYSTORE=1`, to ensure that the
keystore can handle both classic and PQ keys.

The secondary signature option can be set to any of the supported PQ signature options.

The example configuration provided in `config/examples/sim-ml-dsa-ecc-hybrid.config`
demonstrates the use of hybrid mode with both ML-DSA-65 and ECC-384.

### Hybrid signature

The `sign` tool supports hybrid signatures. It is sufficient to specify two
ciphers argument from command line. When you do that, the tool expects two private
key files path passed as arguments, instead of one.

The two public keys must be added to the same keystore. For this reason, the two
keypairs must be generated either at the same time, or in two subsequent steps.

### Generating keypairs for hybrid signatures

#### Generate both keys at the same time:

```
./tools/keytools/keygen --ml_dsa -g wolfboot_signing_private_key.der --ecc384 -g wolfboot_signing_second_private_key.der
```

both keys are automatically added to the same keystore.


#### Generate the two keys in separate steps

If you want to generate the keys in two steps, you will have to import the first
key in the new keystore. The first public key is stored in `keystore.der` during
the keypair generation:
```
./tools/keytools/keygen --ml_dsa -g wolfboot_signing_private_key.der
```

The first 16 bytes contain the keystore header, and must be skipped:

```
dd if=keystore.der of=ml_dsa-pubkey.der bs=1 skip=16
```

The new keypair can be generated (`-g`) while importing (`-i`)the old public key:

```
./tools/keytools/keygen --ml_dsa -i ml_dsa-pubkey.der -g --ecc384 wolfboot_signing_second_private_key.der
```

The keystore generated is now ready to be used by wolfBoot for hybrid signature verification.

### Hybrid signature of the firmware

In both examples above, the two private keys are now available in separate .der files.
The `sign` tool can now be used to sign the firmware with both keys:

```
./tools/sign/sign --ml_dsa --ecc384 --sha256 test-app/image.elf wolfboot_signing_private_key.der wolfboot_signing_second_private_key.der 1
```

The command should confirm that both keys are loaded, and that the image is signed and includes the hybrid signature:
```
wolfBoot KeyTools (Compiled C version)
wolfBoot version 2020000
Parsing arguments in hybrid mode
Secondary private key: wolfboot_signing_second_private_key.der
Secondary cipher: ECC384
Version: 1
Update type: Firmware
Input image: test-app/image.elf
Selected cipher: ML-DSA
Selected hash : SHA256
Private key: wolfboot_signing_private_key.der
Secondary cipher: ECC384
Secondary private key: wolfboot_signing_second_private_key.der
Output image: test-app/image_v1_signed.bin
Target partition id : 1
info: using ML-DSA parameters: 3
info: ML-DSA signature size: 3309
Key buffer size: 5984
info: ml-dsa priv len: 4032
info: ml-dsa pub len: 1952
Found ml-dsa key
image header size overridden by config value (8192 bytes)
Loading secondary key
Key buffer size: 144
Secondary ECC key, size: 96
image header size overridden by config value (8192 bytes)
Creating hybrid signature
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried re-building key tools 'make keytools. I am using the cp config/examples/sim-ml-dsa-ecc-hybrid.config .config `.

wolfBoot KeyTools (Compiled C version)
wolfBoot version 2020000
Parsing arguments in hybrid mode
Secondary private key: wolfboot_signing_second_private_key.der
Secondary cipher: ECC384
Version: 1
Update type:          Firmware
Input image:          test-app/image.elf
Selected cipher:      ML-DSA
Selected hash  :      SHA256
Private key:           wolfboot_signing_private_key.der
Secondary cipher:     ECC384
Secondary private key: wolfboot_signing_second_private_key.der
Output  image:        test-app/image_v1_signed.bin
Target partition id : 1 
info: using ML-DSA parameters: 3
error: unrecognized ml-dsa key size: 144
Key decode error -1
        [MERGE] internal_flash.dd
4096+0 records in
4096+0 records out
4096 bytes transferred in 0.015564 secs (263171 bytes/sec)
make assemble_internal_flash.dd
unable to stat test-app/image_v1_signed.bin: No such file or directory

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the signing failed because the primary key is invalid (stale? created with keygen --ecc384 -g?). I cannot reproduce, it would be helpful to see what goes wrong in keys creation from make distclean make keysclean make V=1

[...]
```

The resulting image `image_v1_signed.bin` contains both signatures, and can be verified using a wolfBoot with hybrid signature support.

## Building the external PQ Integrations

### ext_LMS Support
Expand Down
29 changes: 15 additions & 14 deletions hal/sim.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

#include "wolfboot/wolfboot.h"
#include "target.h"
#include "printf.h"

/* Global pointer to the internal and external flash base */
uint8_t *sim_ram_base;
Expand Down Expand Up @@ -72,7 +73,7 @@ static int mmap_file(const char *path, uint8_t *address, uint8_t** ret_address)

fd = open(path, O_RDWR);
if (fd == -1) {
fprintf(stderr, "can't open %s\n", path);
wolfBoot_printf( "can't open %s\n", path);
return -1;
}

Expand All @@ -81,7 +82,7 @@ static int mmap_file(const char *path, uint8_t *address, uint8_t** ret_address)
if (mmaped_addr == MAP_FAILED)
return -1;

fprintf(stderr, "Simulator assigned %s to base %p\n", path, mmaped_addr);
wolfBoot_printf( "Simulator assigned %s to base %p\n", path, mmaped_addr);

*ret_address = mmaped_addr;

Expand Down Expand Up @@ -119,8 +120,8 @@ int hal_flash_write(uintptr_t address, const uint8_t *data, int len)
uint8_t *addr = (uint8_t *)address;
if (addr[i] != FLASH_BYTE_ERASED) {
/* no writing to non-erased page in NVM_FLASH_WRITEONCE */
printf("NVM_FLASH_WRITEONCE non-erased write detected at address %p!\n", addr);
printf("Address[%d] = %02x\n", i, addr[i]);
wolfBoot_printf("NVM_FLASH_WRITEONCE non-erased write detected at address %p!\n", addr);
wolfBoot_printf("Address[%d] = %02x\n", i, addr[i]);
return -1;
}
#endif
Expand All @@ -137,9 +138,9 @@ int hal_flash_write(uintptr_t address, const uint8_t *data, int len)
int hal_flash_erase(uintptr_t address, int len)
{
/* implicit cast abide compiler warning */
fprintf(stderr, "hal_flash_erase addr %p len %d\n", (void*)address, len);
wolfBoot_printf( "hal_flash_erase addr %p len %d\n", (void*)address, len);
if (address == erasefail_address + WOLFBOOT_PARTITION_BOOT_ADDRESS) {
fprintf(stderr, "POWER FAILURE\n");
wolfBoot_printf( "POWER FAILURE\n");
/* Corrupt page */
memset((void*)address, 0xEE, len);
exit(0);
Expand All @@ -156,23 +157,23 @@ void hal_init(void)
ret = mmap_file(INTERNAL_FLASH_FILE,
(uint8_t*)ARCH_FLASH_OFFSET, &sim_ram_base);
if (ret != 0) {
fprintf(stderr, "failed to load internal flash file\n");
wolfBoot_printf( "failed to load internal flash file\n");
exit(-1);
}

#ifdef EXT_FLASH
ret = mmap_file(EXTERNAL_FLASH_FILE,
(uint8_t*)ARCH_FLASH_OFFSET + 0x10000000, &flash_base);
if (ret != 0) {
fprintf(stderr, "failed to load external flash file\n");
wolfBoot_printf( "failed to load external flash file\n");
exit(-1);
}
#endif /* EXT_FLASH */

for (i = 1; i < main_argc; i++) {
if (strcmp(main_argv[i], "powerfail") == 0) {
erasefail_address = strtol(main_argv[++i], NULL, 16);
fprintf(stderr, "Set power fail to erase at address %x\n",
wolfBoot_printf( "Set power fail to erase at address %x\n",
erasefail_address);
}
/* force a bad write of the boot partition to trigger and test the
Expand Down Expand Up @@ -262,15 +263,15 @@ void do_boot(const uint32_t *app_offset)

ret = NSCreateObjectFileImageFromMemory(app_buf, app_size, &fileImage);
if (ret != 1 || fileImage == NULL) {
fprintf(stderr, "Error loading object memory!\n");
wolfBoot_printf( "Error loading object memory!\n");
exit(-1);
}
module = NSLinkModule(fileImage, "module",
(NSLINKMODULE_OPTION_PRIVATE | NSLINKMODULE_OPTION_BINDNOW));
symbol = NSLookupSymbolInModule(module, "__mh_execute_header");
pSymbolAddress = NSAddressOfSymbol(symbol);
if (!find_epc(pSymbolAddress, &epc)) {
fprintf(stderr, "Error finding entry point!\n");
wolfBoot_printf( "Error finding entry point!\n");
exit(-1);
}

Expand All @@ -283,17 +284,17 @@ void do_boot(const uint32_t *app_offset)
char *envp[1] = {NULL};
int fd = memfd_create("test_app", 0);
if (fd == -1) {
fprintf(stderr, "memfd error\n");
wolfBoot_printf( "memfd error\n");
exit(-1);
}

if ((size_t)write(fd, app_offset, app_size) != app_size) {
fprintf(stderr, "can't write test-app to memfd\n");
wolfBoot_printf( "can't write test-app to memfd\n");
exit(-1);
}

ret = fexecve(fd, main_argv, envp);
fprintf(stderr, "fexecve error\n");
wolfBoot_printf( "fexecve error\n");
#endif
exit(1);
}
Expand Down
Loading
Loading