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

Implement firmware update signing and verification #2103

Closed
6 tasks
igrr opened this issue Jun 6, 2016 · 22 comments
Closed
6 tasks

Implement firmware update signing and verification #2103

igrr opened this issue Jun 6, 2016 · 22 comments

Comments

@igrr
Copy link
Member

igrr commented Jun 6, 2016

Currently there is no way to ensure that firmware updates which go through Updater come from a trusted source. The proposal is to add a signature verification feature to Updater.

Plan is, roughly:

  • Choose which signature algorithm to use. RSA is already part of axTLS, and can probably be exposed. ECDSA may be added in the form of micro-ecc library.
  • Choose how to pass the signature along with the firmware. Options include:
    • embed signature inside the binary at a pre-determined offset. We have a lot of space between eboot and the start of firmware which is unused. We can also place it into the beginning of .irom0.text in the same manner as it is done for the core version number.
    • pass signature using a protocol-specific side channel (e.g. HTTP header).
  • Choose how to embed trusted certificate or public key into the firmware.
    • Literal array (xxd -i) approach is possible but not user-friendly.
    • Placing the certificate on the file system is not very robust (FS can get corrupted due to e.g. power failure).
    • Another option is to embed the public key using the same tool which will embed the signature, to some predetermined location in firmware binary (i.e. between eboot and .irom0.text).
  • Write a host-side tool in Python or Go to generate signature (and embed it into firmware binary, if needed).
  • Implement signature verification in Updater.
  • Update documentation and examples.

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

@me-no-dev
Copy link
Collaborator

I have a simpler idea, that does not require crypto, has no memory impact and is good on security if properly used.
The basic idea is to have a firmware password which is used to unmask the incoming image and produce success only if the unmasked image is good (header integrity, md5 check, so on).
Pseudo code:

const char * firmware_pass = "my-secret";
void onUpdateData(data, len){
  //... logic
  Update.begin(...);
  Update.setMask(firmware_pass);
  //... logic
}

UpdaterClass::onData(data, len){
  //... logic
  for(i=0; i<len; i++)
    data[i] ^= firmware_pass[i % strlen(firmware_pass)];
  //... logic
}

Masking of the firmware can be easily done after build or from JavaScript in the browser for given image and password, which is not a problem, since the attacker has to know the password to successfully replace the running image. The level of security is higher with longer random passwords

@Bluebie
Copy link

Bluebie commented Jun 7, 2016

@me-no-dev your suggestion is security via obscurity. There are large parts of firmware updates which contain known data, like zeroed out null sections - xoring these regions makes recovering the passphrase trivial, especially when it is likely to be short and repeating. Your suggestion is pointless security theatre. If the devices are to spend time transforming firmware in some way, it should provide some real benefit.

@me-no-dev
Copy link
Collaborator

that is true, we discussed it with Ivan yesterday. It was more of me focusing on the wrong thing

@Bluebie
Copy link

Bluebie commented Jun 7, 2016

@igrr Rad ideas. I think you're on the right track. The only thing I find unpleasant is the use Python or Go. Adding more dependancies like that seems counterproductive, especially for windows users who don't necessarily have python preinstalled. The decode side will already be written in C, and we'll already need to ship a small C crypto library with the Arduino core to build the Updater libraries. Can we write the signing tool in C too, and use the same crypto libraries on host and device side? I'd suggest using TweetNaCl and it's ed25519 implementation, as it is small, compact, easy to use and hard to mess up, and portable so the code can be shared between host and client side, simplifying things a lot.

There's also a great JavaScript port of TweetNaCl so browser based code signing is fairly easy to do too, kind of like @me-no-dev suggests - it would be reasonable to use something similar to a brain wallet, transforming a long passphrase in to a private key, to enable the update.

I wonder if there would even be a way to use compiler macros or even just take advantage of static optimisation to transform a passphrase like that in to a public ed25519 key, so users could write a password in to their Arduino code, without that password ending up anywhere in the firmware image! that would be really cool I think!

@igrr
Copy link
Member Author

igrr commented Jun 7, 2016

Regarding Go, python and dependencies. We already kinda implicitly require windows users to install python to use OTA. I'm not a big fan of this, but hey, I started that by writing a "temporary" python script. That script was mostly rewritten, but it's still python. So writing another tool in python would not make things much worse. Go, on the other hand, compiles to native binaries, so doesn't add any extra dependencies. Also I'm not opposed to having the tool written in C/C++, especially if there is potential for code reuse.

Regarding key derivation, that's possible via some use of constexpr, although we would need to switch to C++14 for that (C++11 constexpr's are quite restrictive). And also we would need to make changes to tweetnacl code (i.e. make C++ compiler happy, annotate functions with constexpr, etc), which i'm not entirely happy with. But yeah, that would be cool if done at compile time. Thanks for the tip 👍

@Humancell
Copy link

A few questions:

  1. This will be optional, correct?
  2. This will be a step that must be taken after compiling the code and creating the .bin file? So does this mean the that "prior" version of firmware (currently running) has to have knowledge of the signing technique of the "next" version of the firmware?

@igrr
Copy link
Member Author

igrr commented Jun 8, 2016

Signature verification will likely be enabled by default, with an option to opt out.

I don't see why the prior version would need to know about the fact that firmware is signed. If the signature is placed into an empty space between eboot and the rest of firmware, older versions of firmware will not care and will happily accept the update. If the signature is passed along as an HTTP header, then older version may also ignore the header without any side effects.

@igrr igrr added the bounty label Jun 22, 2016
@davidgraeff
Copy link

Just a thought: If RSA cannot be exposed, another option next to ECDSA is spritz (https://people.csail.mit.edu/rivest/pubs/RS14.pdf). There is a public domain c implementation (https://github.com/jedisct1/spritz).

@madpilot
Copy link

madpilot commented Apr 2, 2017

I have a work-in-progress implementation here: #3105

It supports MD5, SHA1 and SHA256 certificates (code borrowed from axTLS). At the moment, it only supports MD5 signatures, but adding SHA1 and SHA256 would be trivial.

It works by appending the binary file with the certificate, signature, and two integers pointing at the beginning of those two files. The CA certificate is included using a C include file inside the binary.

Comments welcome - especially around file layout etc. I've basically copied and modified axTLS, so it's possible I've duplicated some stuff from other parts of the library.

Still a bit of work to do, but wouldn't be too difficult to make this production ready.

@madpilot
Copy link

madpilot commented Apr 2, 2017

Regarding backwards compatibility, All of the OTA libaries will still work, since the binary has all the data it needs, so the existing libs can just pass the binary though. You would just need to disable the validation code by setting

#define VERIFY_SIGNATURE 0

ideally via build scripts.

@Ivoz
Copy link

Ivoz commented Jan 20, 2018

If there wants to be signing, verification, authentication, encryption, etc which something like firmware updating wants to make use of, then there IMHO wants to be a solid story for the code that will underlie that. i.e everything cryptography and security related.

If you have managed to construct things nicely underneath, then writing coding to solve this issue becomes comparatively easy.

As far as I can see, everything currently seems very slap-dash and patched in as needed. For instance, currently there is one as-simple-as-possible SHA-1 API exposed (that doesn't, for instance, allowed building a to-spec HMAC implentation on top of it). But there is a more advanced MD5Builder API available under only 8266. Both of these hashes are now decades old!

I had assumed before looking at Arduino that probably the open-source world of IoT had their security story down pat, all the algorithms and code and libraries were surely there and freely available to use; it was just propreitary vendors that didn't care that were shipping things laughably-exploitable by default. I see that's not really the case.

I have a very small understanding of how selective one needs to be to try to keep code-base/firmware sizes down, but I think Arduino / 8266/esp could use a small set of modern crypto primitives to allow people to start building things properly. If necessary, with some options on whether you like them to be included or not.

So what does a minimal set of good primitives look like? I would propose-

  • Hash: blake2s & sha512 (for compatibility only really)
  • Symmetric Cipher: ChaCha20 or ChaCha8 with Poly1305 (authenticated encryption) (8 is just faster)
  • Key Exchange: Curve25519
  • Signatures: Ed25519 (although using sha-512 might want to be looked at)
  • Key Derivation Function: Argon2 (uses blake2b internally) (or PBKDF2 if easier)
  • Random source: PCG-Random whitened by ChaCha20, some consideration needed for automatically trying to find good seeding

These are selected on the basis of being already well known, operating well in software and not being resource hungry (RSA, AES), "modern" (made within the last decade), and having some consideration for running well on small hardware (and non-32/64bit processors). These may are not the perfect answers but are a start.

Theoretically, having the above algorithms would even let you write your own working TLS 1.3 implementation.

But a simple API for all these (in the style of something like libsodium) would let IoTers start writing things secure-by-default easily.

Now this is a big idea to add, and needs someone with some crypto experience to assemble.

But I think also, the current method of add-a-primitive-only-just-when-you-need-for-something could very quickly lead to a fractured API and more technical debt.

If this sort of idea is best also discussed somewhere else, I am happy to move it.

@hardfalcon
Copy link

Ivoz: Most of the primitives you mention are not needed for signed firmware updates. You need exactly two primitives on the device that is to receive a signed firmware: A cryptographic hash function (preferably a fast one like SHA256), and an a signature algorithm (preferably ed25519, as it is fast and produces small signatures).

This is a solved problem, just use OpenBSD's signify:
https://www.openbsd.org/papers/bsdcan-signify.html
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/signify/

@davisonja
Copy link

davisonja commented Jan 22, 2018 via email

@hardfalcon
Copy link

davisonja: Well, you only have limited resources available on an embedded platform such as the ESP8266, which means you have to make decisions about which features you really want/need, and which features are not needed.

You simply don't need TLS for secure firmware updates, and in many cases, you might have better use for the resources that a usable TLS implementation would use on the ESP8266.

Apart from that, Minisign may also be worth a look as a potential alternative to signify:
https://jedisct1.github.io/minisign/
https://github.com/jedisct1/minisign

@davisonja
Copy link

davisonja commented Jan 22, 2018 via email

@Ivoz
Copy link

Ivoz commented Jan 22, 2018

@hardfalcon minisign uses libsodium. It may be "small and simple" in terms of a normal x86 computer, but I'm not sure if that still applies for libsodium to microcontrollers. You have to be careful at what you look at.

If [this project] is fine including libsodium, then 90% of the "job" I outlined in my above post is done. The most serious place left to solve is trying to generate entropy for a good but effecient PRNG. If you look at most traditionally x86 derived sources, you'll mostly find them using /dev/random and calling it a day, which is indeed suitable for most x86 but not at all for esp or avr.

And Indeed I was advocating for some cryptographic code that's slightly more thought out than: "let's just add these couple of C files from somewhere, include them as well, and call it a day".

Lastly, even looking at signify, that code expects "sha2.h" to be existing somewhere (perfectly reasonable in the OpenBSD codebase...), and uses sha-512 as is. sha-512 operates on 64 bit words, so would it be so fast on avr or esp? esp32 has dedicated hardware to calculate it, but not so for lesser/older chips.

It's very easy to run into misnomers about what is "small" and "fast" for our context when you look at x86 originating code.

Additionally, it seems the current tls library used, axtls, seems to only run old and slow (on MCs) algorithms. If it is included because it is far smaller than other embedded tls libraries, I could see the point. Otherwise moving to another tls library could get one a lot of these primitives "for free".

@igrr
Copy link
Member Author

igrr commented Jan 22, 2018

One of the constraints specifically for this feature (signed OTA updates, not the general-purpose crypto APIs) is that the OTA-enabled firmware size should not increase much with this feature introduced. Increasing firmware size by, say, 50kB may prevent users of 1MB ESP-01 modules from using OTA, as we see it happening now after the floating point support was added to printf/scanf.

@Steve132
Copy link

Steve132 commented Jun 23, 2018

So Tweetnacl was already mentioned in this thread, but it can easily be paired down even more so that the overall binary increase will be significantly smaller. So what does a minimal set of good primitives look like? I would propose-

Nacl already provides the necessary features in a really really efficient way. It provides

Hash: blake2s & sha512 (for compatibility only really)

It provides sha512 in a simple memory-efficient implementation. Blake is memory-hard so isn't the right choice here.

Symmetric Cipher: ChaCha20 or ChaCha8 with Poly1305 (authenticated encryption) (8 is just faster)

It provides chachax20, but it can be stripped out to decrease the binary for this pull request.

Key Exchange: Curve25519

It provides this exactly, but can be stripped out for this pull request

Signatures: Ed25519 (although using sha-512 might want to be looked at)

Sha512 is not a signature algorithm. However, it provides Ed25519 which is what we would use.

Key Derivation Function: Argon2 (uses blake2b internally) (or PBKDF2 if easier)

We don't need key derivation on the client device, only on the host.

Random source: PCG-Random whitened by ChaCha20, some consideration needed for automatically trying to find good seeding

Theres not really a need to provide a random number for signing on the device because the device is doing the verifying, however, deterministic signatures from rfc6979 are safer anyway.

I recommend putting the signature along side the firmware binary, and the compressed public key into eeprom (33 bytes).

If this bounty is still open I have experience with TweetNACL integrations and can implement this.

@d-a-v
Copy link
Collaborator

d-a-v commented Oct 12, 2018

@earlephilhower is working in this, check #5213.

@earlephilhower
Copy link
Collaborator

Closed per #5213

@cxcorp
Copy link

cxcorp commented Apr 14, 2020

@earlephilhower You should probably claim the bounty on this issue!

@earlephilhower
Copy link
Collaborator

Thanks, @cxcorp . I had no idea it was still around!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests