I've searched around, but haven't really found anything on this topic. I want to implement some kind of code signing or bootloader locking to prevent unapproved code from running, so I can publish firmware updates but prevent someone from publishing a "hacked" version. Code security can't be a concern here since the binaries have to be sent out anyways for an update and could easily be reverse engineered. However, the only way to run unsigned code would be to hack in an ISP header on the board and flash a different bootloader. That's fine because it doesn't compromise the security of any stock hardware.
So it looks like I'll have to write something myself, but I'm curious what more experienced programmers have to say. If you have any thoughts on any of this please let me know!
Requirements
- Speed - total boot should not take more than ~2s
- Size - needs to be as small as possible - 2k to fit in 328p BL section which might not be possible
- Secure - the signing must be cryptographically secure so that no one else can sign code
- Compatible - ideally, the implementation wouldn't break the use of avrdude (or possibly even the Arduino IDE) to make updating easy
My plan so far is as follows:
Code base:
The base will probably start from optiboot, since it has the 1s WDT boot and is only 512bytes. However, EEPROM support is probably necessary (more on that later) which eats into the size considerably. It might be acceptable to remove EEPROM support and just leave EEPROM write.
Signature verification:
The signature/verification needs to be fast, secure, and not take a lot of space. The best option seems to do a Full Domain Hash with RSA. It's an older signature standard, but should still be very secure, especially since there will be so few published signatures (crypto people correct me if I'm wrong). Before the application starts, I'll hash the program memory, decrypt an RSA signature, and compare it to the hash.
Hashing:
For hashing, according to this arduino crypto library, it seems that blake2s is by far the fastest. Initial tests show I can hash the entire 32k program memory in about 650ms by loading 1k into SRAM at a time, with no optimizations on the blake2s code. It might be faster with a bigger buffer and some code tweaking. Unfortunately, it requires ~192 bytes of init vectors. That may need to go into EEPROM. The next fastest is SHA-256, but it requires a massive state table that eats up too much space...
Signature decryption:
I think 1024 bits should be large enough that it would cost considerable computing power (and $$) to crack. 512 is definitely too easy, and 2048 is probably unnecessary given that the private key should be completely inaccessible. I think with the full SRAM available, it should be feasible to do 1024 bit decryption in a reasonable amount of time. The public key exponent will probably be fixed at 65537 to avoid having to store it. The modulus, however, will be a full 128 bytes, and needs to be stored in the BL section of flash memory (can't put it in the EEPROM or it could get changed).
Signature location and Firmware Updating:
Storing the 128 byte signature with the program is a little tricky. If it's just defined in the source code, it could end up anywhere in memory, and would have to be found and ignored before doing the hash (since the hash is of the program binary without the signature). That would cost a lot of time. The other problem is that avrdude only erases flash pages that get programmed. So if there's left over program data at some memory address that isn't used in an update, it will cause the hash to be different than expected.
There are a few possibilities, none seem that great:
Location-
- Store the signature in EEPROM, which requires an additional programming step (although it may already be necessary to fit program size)
- Manually tweak the hex output during signing to place the signature at some known address in flash, and hard code that in the bootloader.
Updating-
- Either modify the hex to overwrite the entire flash contents with known data, or modify the bootloader to do that during every update
- Include a program length parameter somewhere, probably in the encrypted signature, but I'm worried that could compromise security (not sure how exactly...)
Program Size:
I'm pretty sure I won't get this to fit in a 2k bootloader space. That's not entirely a problem, but I'm not sure how to tell the linker to (or if I even can) start the bootloader code at the right address, but put any code longer than the bootloader section (like the hashing/decryption functions) in a specific place...
I could compile some of the functions separately and place them at the end of application memory, then add a jump instruction at the last address that always goes to the start of the signature verification, wherever that may be. The bootloader would then jump to that jump instruction. This is a bit beyond my expertise; surely there's a more elegant way of doing this...
If you've read this far... thank you!