platformio / platform-espressif8266

Espressif 8266: development platform for PlatformIO
https://registry.platformio.org/platforms/platformio/espressif8266
Apache License 2.0
325 stars 219 forks source link

Binary signing in core 2.5.0 #131

Open kubasaw opened 5 years ago

kubasaw commented 5 years ago

Hello, I have a problem with binary signing which is related with secure OTA update in new version of esp8266Arduino core https://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html

Basically, to sign binaries, two activities are essential:

  1. invoke tool signing.py with appropiate flags before whole build process: it dynamically build header which is placed in core headers directory based on finding public RSA key in main source code directory
  2. invoke the same tool with other flags in order to sign produced binary with RSA private key.

As far as I tried to do this activites by write proper scripts in Python and bind them to PIO in pre/post hooks mechanism, I have no success in this matter (my functions are invoked by PIO in wrong places and I didn't found tutorial good enough for my basic Python experience to programatically obtain correct directories). In the end, I have hardcoded my directories and that forces me to run Python before build to generate headers, build project in PIO and run Python again to sign binary. By using Arduino IDE, it is very easy activity, because of implementing this mechanism in default build sequence. Is it possible to do the same in PIO?

Misiu commented 5 years ago

Recently this feature was added into Arduino Core - https://github.com/esp8266/Arduino/pull/5635, so it should work on all systems (including Windows) Example showing how to use this feature with PlatformIO would be awesome 🙂

arbabseyfola commented 5 years ago

Please add this feature ASAP. Thank you.

imavroukakis commented 5 years ago

@kubasaw would Manual Signing work for you ? https://github.com/earlephilhower/Arduino/blob/master/doc/ota_updates/readme.rst#manual-signing-binaries

albertskog commented 4 years ago

Hi! I have been going up and down all the documentation I can find and some of the source code trying to figure out how to make Platformio do the signing. Hoping someone can point me in the right direction 🙂

I found that running python ~/.platformio/packages/framework-arduinoespressif8266/tools/signing.py -m header -o src/Updater_Signing.h -p src/public.key will generate a file that I assume should repace Updater_Signing.h in .platformio/packages/framework-arduinoespressif8266/cores/esp8266/. (Is this correct?)

There is a hint in the default Updater_Signing.h:

// This file will be overridden when automatic signing is used.
// By default, no signing.
#define ARDUINO_SIGNING 0

Any ideas on how to override Updater_Signing.h in Platformio? I tried using src_filter in platformio.ini but did not get that working and it feels like an ugly hack.. Replacing the file seems risky since it might break other projects using the same framework.. One thing that does seem to work is naming the file something else and just including it from the main file, but that dos not seem like the intended way to do it either? How does Arduino do this overriding? Also, if anyone knows, how are the key files picked up automatically in Arduino?

Either way, I can then run python ~/.platformio/packages/framework-arduinoespressif8266/tools/signing.py -m sign -b .pio/build/ota/firmware.bin -o .pio/build/ota/firmware.bin.signed -s src/private.key to generate a signed binary. Once there is a firmware.bin.signed in the build directory, it seems the uploader actually picks it up automatically(?!)

Right now my best understanding is that I need to do the following things in platformio.ini:

  1. Add a pre-build step that runs signing.py -m header ...
  2. Make Platformio use that file instead of the existing Updater_Signing.h
  3. Add a post-build step that runs signing.py -m sign ...

1 and 3 seems doable but unsure this is the correct way to go about it? 3 I'm wondering how to best implement?

GMagician commented 2 weeks ago

Anyone has been able to properly sign a bin?

GMagician commented 2 weeks ago

at the end I have been able to compile my code enabling signing. If someone falls into this, here my solution.

Signing requires that a header file, located in framework path, is modified by enabling signing and adding a public key. This code runs script (located in framework path) to do that

_following code must be written in 'extrascripts = pre:'yourscript1'.py' ; Get framework path (not found a way to detect which one is used so fixed text) framework_fullpath = os.path.join(env.subst(env['PROJECT_PACKAGES_DIR']), 'framework-arduinoespressif8266') ; Get fullpath of signing script script_fullpath = os.path.join(framework_fullpath, 'tools', 'signing.py') ; Mode required to modify header file in framework to enable signing and add public key to it mode = 'header' ; Framework header file fullpath header_fullpath = os.path.join(framework_fullpath, 'cores', 'esp8266', 'Updater_Signing.h') ; Arguments required by signing script args = '--publickey "%s" --out "%s"' % (key_fullpath, header_fullpath)) ; Run script os.system('%s "%s" --mode %s %s' % (sys.executable, script_fullpath, mode, args))

_following code must be written in 'extrascripts = post:'yourscript2'.py' def post_program_action(source, target, env): program_path = Path(target[0].get_abspath())

firmware_fullpath = program_path.with_suffix(".bin") ; Mode required to sign bin file mode = 'sign' ; Arguments required by signing script privatekey_fullpath = 'yourpath' args = '--bin "%s" --privatekey "%s" --out "%s"' % (firmware_fullpath, privatekey_fullpath, firmware_fullpath)) ; Run script (N.B. script uses openssl so this must be accessible in PATH. I did a trick by changing working directory before executing the script) os.system('%s "%s" --mode %s %s' % (sys.executable, script_fullpath, mode, args))

; restore framework header

env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", post_program_action)

imavroukakis commented 2 weeks ago

Hey @GMagician , mind adding an explainer of how it works? Thanks!

GMagician commented 2 weeks ago

Hey @GMagician , mind adding an explainer of how it works? Thanks!

Modified original post. I found that header must be changed in framework to properly work.

What is missing is code to restore header file in framework.

GMagician commented 2 weeks ago

I'll post a complete script as soon as I refine it

GMagician commented 2 weeks ago

Here is my working script, it needs to be executed by PlatformIO in 'post:' and needs 3 path: 1) Private key 2) Certificate 3) Destination path for 'public key' temporary storage

It simply; 1) extract public key from certificate 2) run a script in framework to build an include file needed by framework itself to enable ota signed firmwares upload 3) wait for .bin creation then relaunch same script in (2) to sign it 4) on exit restores framework include file by running script in (2) with invalid public key filename

Know issues: if build is closed by force (trash bin in terminal) then include file is not restored and all other programs built with same framework will be compiled with ota signing enabled

signfirmware.txt