From ba9a3ac70cb6ad663fc0f889766876a34f375a75 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Fri, 18 May 2018 13:32:00 +0300 Subject: [PATCH] use multiple signatures, switch to low-power --- Inc/main.h | 6 ++---- PRODUCTION.md | 8 ++++---- Src/main.c | 17 ++++++++++++++--- Src/system_stm32l4xx.c | 7 +++++-- scripts/fw_sign.py | 2 +- scripts/fw_signall_dev.sh | 6 ++++++ scripts/genkeys.py | 2 +- 7 files changed, 33 insertions(+), 15 deletions(-) create mode 100755 scripts/fw_signall_dev.sh diff --git a/Inc/main.h b/Inc/main.h index 9ea68f6..b5b3618 100644 --- a/Inc/main.h +++ b/Inc/main.h @@ -33,11 +33,10 @@ // The constants below define the memory layout documented in the README.md file #define BOOTLOADER_SIZE 0x2000 #define BOOTLOADER_FIRST_PAGE 0 -#define BOOTLOADER_LAST_PAGE ((BOOTLOADER_SIZE / FLASH_PAGE_SIZE) - 1) #define FIRMWARE_SIZE ((FLASH_BANK_SIZE - BOOTLOADER_SIZE) / 2) #define FIRMWARE_HEADER_SIZE 0x200 -#define FIRMWARE_FIRST_PAGE (BOOTLOADER_LAST_PAGE + 1) +#define FIRMWARE_FIRST_PAGE (BOOTLOADER_SIZE / FLASH_PAGE_SIZE) #define FIRMWARE_PAGE_COUNT (FIRMWARE_SIZE / FLASH_PAGE_SIZE) #define FIRMWARE_START (FLASH_START_BANK1 + BOOTLOADER_SIZE) @@ -48,12 +47,11 @@ #define RECOVERY_FW_START (FIRMWARE_START + FIRMWARE_SIZE) #define RECOVERY_FW_FIRST_PAGE (FIRMWARE_FIRST_PAGE + FIRMWARE_PAGE_COUNT) -#define RECOVERY_FW_LAST_PAGE (RECOVERY_FW_FIRST_PAGE + FIRMWARE_PAGE_COUNT - 1) #define SIGNATURE_HEADER_OFFSET 8 #define SIGNATURE_LENGTH 64 #define KEY_LENGTH SIGNATURE_LENGTH -#define SIGNATURE_COUNT 1 +#define SIGNATURE_COUNT 4 /** diff --git a/PRODUCTION.md b/PRODUCTION.md index 7fe7950..6e41f50 100644 --- a/PRODUCTION.md +++ b/PRODUCTION.md @@ -6,7 +6,7 @@ To use the bootloader effectively there a few steps that need to be taken. This During development the bootloader will be loaded on chip using the ST-LINK programmer without write and read protection. The private keys used to sign the firmware can be commited to the repository, since these will not be used in the released bootloader. The keys will be generated by the development team with no confidentiality constraints. Do not rely on these keys for anything remotely security related. To make sure the space occupied by the bootloader and boot times are the same, we will use the same number signatures both in development and in release. -Loading the firmware in any of the reserved areas (firmware, recovery, upgrade) can be performed via the fw_load.sh script. This script is only applicable to development and is not used in production. +Loading the firmware in any of the reserved areas (firmware, recovery, upgrade) can be performed via the fw\_load.sh script. This script is only applicable to development and is not used in production. ## Using the Python scripts @@ -16,7 +16,7 @@ The Python scripts depend on the ecdsa module. It must be installed system-wide The most important step for generating the keys is to determine who is responsible for holding them and how many signatures will be needed for release. A good number would be 3 or 4, but the header can fit up to 6 signatures. The key holders are responsible for keeping the keys secret. If all keys were to be eventually leaked, attackers would be able to load their own malicious firmware on the device, so secrecy of the keys is crucial. -The private and public keys are generated using the genkeys.py script in the scripts folder. It takes no argument and generates two files: secret_key.pem and public_key.h. The content of the public_key.h file must be integrated in the main.c file. +The private and public keys are generated using the genkeys.py script in the scripts folder. It takes no argument and generates two files: secret\_key.pem and public\_key.h. The content of the public\_key.h file must be integrated in the main.c file. The secret_key.pem is the one used for signing and is confidential. It is suggested to execute the script on a secure computer (no internet access, maybe a VM). @@ -35,8 +35,8 @@ A simple script to generate the final .bin file from the bootloader and the firm There are several steps needed when issuing a firmware upgrade 1. The firmware must be built from its sources. The raw bin file needs to be copied in the scripts directory with the name fw.bin -2. The fw_prepare.py script must be executed. This adds the header to the firmware, with no signatures. -3. The file must be then signed, using the fw_sign.py script by all key holders. +2. The fw\_prepare.py script must be executed. This adds the header to the firmware, with no signatures. +3. The file must be then signed, using the fw\_sign.py script by all key holders. This scripts takes as argument the index of the key. It looks for a file named secret\_key\_*index*.pem where index is the argumenet passed to the script. This naming convention prevents passing the wrong index by mistake. 4. The firmware is released (by means outside the scope of this document). Since multiple signatures are required, we must decide how to go about that. The simplest and slowest option is having a fixed signing order (matching the index number of the keys), where each key holder signs and sends to the next person in the chain. The last person in the chain releases the firmware (or sends the firmware to the team responsible for releasing it). Another option is placing the unsigned firmware on a shared network driver and have all key holders sign in parallel. In this case locking must be performed to avoid concurrency issues and also the tool must inform the last signer that the firmware is ready. The third option is send the file to all signers immediately, who send their partially signed version back to the person initiaing the signing request. In this case another script will be needed to merge all partial headers into one, containing all signatures. diff --git a/Src/main.c b/Src/main.c index 1e2b60f..ffe7797 100644 --- a/Src/main.c +++ b/Src/main.c @@ -28,8 +28,19 @@ #include "uECC.h" // The public keys used to verify the firmware signatures. These are the raw X,Y coordinates. Each key is exactly 64 bytes long and there is no separator. -uint8_t fw_public_keys[] = { 0xfe, 0xcb, 0x28, 0xb9, 0x50, 0xdd, 0x8b, 0x2f, 0xc7, 0x34, 0xd3, 0x60, 0x5b, 0x1a, 0xc6, 0xed, 0x02, 0x50, 0xf2, 0x4a, 0xc4, 0x75, 0xd1, 0x28, 0x7f, 0x7c, 0xb5, 0xce, 0x61, 0xd6, 0x95, 0xb9, 0xb5, 0x27, 0x0b, 0x52, 0x77, 0x42, 0x4b, 0xf3, 0xb4, 0x3c, 0xef, 0xcb, 0x56, 0xd1, 0x98, 0x22, 0x11, 0xc2, 0xe5, 0xd3, 0xf0, 0x22, 0x87, 0xb9, 0xe8, 0x20, 0xdc, 0xee, 0x9f, 0xc2, 0xad, 0x22, }; +#ifdef BOOTLOADER_RELEASE +uint8_t fw_public_keys[] = { \ +//TODO: generate production keys +}; +#else +uint8_t fw_public_keys[] = { \ + 0xfe, 0xcb, 0x28, 0xb9, 0x50, 0xdd, 0x8b, 0x2f, 0xc7, 0x34, 0xd3, 0x60, 0x5b, 0x1a, 0xc6, 0xed, 0x02, 0x50, 0xf2, 0x4a, 0xc4, 0x75, 0xd1, 0x28, 0x7f, 0x7c, 0xb5, 0xce, 0x61, 0xd6, 0x95, 0xb9, 0xb5, 0x27, 0x0b, 0x52, 0x77, 0x42, 0x4b, 0xf3, 0xb4, 0x3c, 0xef, 0xcb, 0x56, 0xd1, 0x98, 0x22, 0x11, 0xc2, 0xe5, 0xd3, 0xf0, 0x22, 0x87, 0xb9, 0xe8, 0x20, 0xdc, 0xee, 0x9f, 0xc2, 0xad, 0x22, \ + 0xe8, 0xb4, 0x6f, 0xfd, 0xe2, 0x77, 0xe4, 0xb7, 0x8e, 0x64, 0xed, 0x8d, 0x1d, 0xd4, 0xe0, 0x41, 0x72, 0x40, 0xba, 0xc3, 0xc5, 0x25, 0xbe, 0x53, 0xb7, 0x5e, 0xf9, 0xf5, 0x19, 0xda, 0x03, 0xdf, 0xb9, 0x41, 0xeb, 0x63, 0x39, 0xdf, 0xee, 0x47, 0x9f, 0x86, 0xbf, 0x87, 0x8a, 0xcd, 0xf0, 0x3b, 0x1e, 0x7d, 0x85, 0xd2, 0x3d, 0x96, 0xf2, 0x31, 0x1a, 0x49, 0x0a, 0xd6, 0xad, 0xdd, 0x44, 0xf0, \ + 0x5c, 0x25, 0xe5, 0x96, 0xa1, 0xc0, 0x17, 0xf2, 0x18, 0x80, 0x3f, 0x99, 0x40, 0xda, 0x02, 0x8a, 0x9a, 0x8c, 0xef, 0x34, 0x60, 0xc9, 0x53, 0x6b, 0x34, 0x07, 0x42, 0x87, 0xce, 0xe7, 0xa1, 0x47, 0x6a, 0x9f, 0xf2, 0x9e, 0xfd, 0xf3, 0xa0, 0x1a, 0xd2, 0x6c, 0xd0, 0x28, 0xc9, 0x4c, 0x21, 0xfb, 0x32, 0xcc, 0x08, 0x56, 0x16, 0xa7, 0x86, 0xcb, 0x36, 0x26, 0x1e, 0x60, 0x58, 0x90, 0x67, 0xae, \ + 0x37, 0xa5, 0xfb, 0xbc, 0xe9, 0xa9, 0x62, 0x45, 0x0f, 0x71, 0x7a, 0x91, 0x09, 0xb3, 0xe5, 0xfe, 0x2c, 0x37, 0x94, 0x5e, 0xc0, 0x91, 0xe7, 0x91, 0xd8, 0xc0, 0xe3, 0x84, 0x7e, 0x48, 0xe0, 0xe3, 0x5b, 0xb2, 0xf3, 0xe0, 0xc6, 0x86, 0x37, 0xc9, 0xd3, 0x56, 0x7d, 0x5e, 0xe1, 0xfc, 0x71, 0x1f, 0xf0, 0xfb, 0xa0, 0xe5, 0xf4, 0xc8, 0x8f, 0x40, 0x5d, 0x95, 0x0d, 0xd6, 0x51, 0xd3, 0xb3, 0x13, \ +}; +#endif int main(void) { protect_flash(); @@ -83,8 +94,8 @@ void protect_flash() { flash_optunlock(); FLASH_SET_RDP2(); - FLASH_WP(FLASH->WRP1AR, BOOTLOADER_FIRST_PAGE, BOOTLOADER_LAST_PAGE); - FLASH_WP(FLASH->WRP1BR, RECOVERY_FW_FIRST_PAGE, RECOVERY_FW_LAST_PAGE); + FLASH_WP(FLASH->WRP1AR, BOOTLOADER_FIRST_PAGE, FIRMWARE_FIRST_PAGE); + FLASH_WP(FLASH->WRP1BR, RECOVERY_FW_FIRST_PAGE, (RECOVERY_FW_FIRST_PAGE + FIRMWARE_PAGE_COUNT)); // This resets the MCU flash_optprogram(); diff --git a/Src/system_stm32l4xx.c b/Src/system_stm32l4xx.c index ba84216..fb3d24d 100644 --- a/Src/system_stm32l4xx.c +++ b/Src/system_stm32l4xx.c @@ -96,8 +96,8 @@ #include "stm32l4xx.h" void SystemInit(void) { - /* Run at 16Mhz */ - RCC->CR = RCC_CR_MSIRGSEL | RCC_CR_MSIRANGE_8 | RCC_CR_MSION; + /* Run at 16mhz */ + RCC->CR = RCC_CR_MSIRGSEL | RCC_CR_MSIRANGE_9 | RCC_CR_MSION; /* Reset CFGR */ RCC->CFGR = 0x00000000U; @@ -107,6 +107,9 @@ void SystemInit(void) { /* Disable all interrupts */ RCC->CIER = 0x00000000U; + + /* Enter run range 2 */ + PWR->CR1 = PWR_CR1_VOS_1; } /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/scripts/fw_sign.py b/scripts/fw_sign.py index 1c2a14e..da7d12b 100755 --- a/scripts/fw_sign.py +++ b/scripts/fw_sign.py @@ -19,7 +19,7 @@ def main(index): print("Firmware must be prepared with fw_prepare.py first!") exit(1) - sk_pem = open("secret_key.pem", "r").read() + sk_pem = open("secret_key_%d.pem" % index, "r").read() sk = ecdsa.SigningKey.from_pem(sk_pem) plain_fw = fw[HEADER_SIZE:] fw_sig = sk.sign_deterministic(plain_fw, hashfunc=hashlib.sha256) diff --git a/scripts/fw_signall_dev.sh b/scripts/fw_signall_dev.sh new file mode 100755 index 0000000..f95e120 --- /dev/null +++ b/scripts/fw_signall_dev.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +./fw_sign.py 0 +./fw_sign.py 1 +./fw_sign.py 2 +./fw_sign.py 3 \ No newline at end of file diff --git a/scripts/genkeys.py b/scripts/genkeys.py index 878b7e4..77eb472 100755 --- a/scripts/genkeys.py +++ b/scripts/genkeys.py @@ -11,7 +11,7 @@ def main(): vk = sk.get_verifying_key() vk_hex = binascii.hexlify(vk.to_string()) - vk_h = "uint8_t fw_public_key[] = { " + re.sub(r"(..)", "0x\\1, ", vk_hex) + "};" + vk_h = re.sub(r"(..)", "0x\\1, ", vk_hex) + "\\" open("secret_key.pem","w").write(sk.to_pem()) open("public_key.h","w").write(vk_h) if __name__ == '__main__':