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

Signed updates #3105

Closed
wants to merge 15 commits into from
Closed

Signed updates #3105

wants to merge 15 commits into from

Conversation

madpilot
Copy link

@madpilot madpilot commented Apr 2, 2017

Adds the ability to verify the signature of an OTA update.

The developer certificate and signature are bundled at the end of the binary, with two additional uint32_t variables added to the very end to let the updater know where to look for them.

It works, but there is a few rough edges to clean up:

  1. Bundling requires a C program that isn't very smart. I need to convert it to Go or Python, and then ideally integrate it with the Arduino IDE
  2. Only MD5 signatures are supported. It would be fairly easy to add SHA1 or ideally SHA256.
  3. Basically no documentation or formal tests 😄 - I'll fix that in the next few days - I just wanted to open this PR to get feedback.

NOTE: The is a feature flag:

#define VERIFY_SIGNATURE 1

Iy's turned on by default in this version. Final version will allow it to be set as a build paramater.

Quick and Dirty documentation for those that know what they are doing

First step is to create a CA, and reference in the source of the sketch, by converting it to a binary header file using xxd:

openssl req -new -x509 -days 3650 -extensions v3_ca -keyout ca.key.pem -out ca.crt.pem
xxd -i ca.crt.pem

Include the file in the Sketch, and using the new addCA function to reference it.

#include "ca.h"

void setup(void) {
  Update.addCA(ca_crt_der, &ca_crt_der_len);
}

Compile and upload the sketch using Serial.

To do an update:

Create a developer certificate:

openssl genrsa -out developer.key.pem 2048
openssl req -out developer.csr.pem -keydeveloper.key.pem -new
openssl x509 -req -in developer.csr.pem -CA ca.crt.pem -CAkey ca.key.pem -CAcreateserial -out developer.crt.pem -days 365

The library definately suppports sha256. It should support sha1 and md5 too.

Generate the signature for the new binary using openssl:

openssl dgst -md5 -sign ~/Projects/ESP8266FileSigning/cert/developer.key.pem -out Sketch.ino.sig Sketch.ino.bin

The mash them together using this C program (I'll convert it to Go or Python shortly)

#include <stdio.h>
#include <stdlib.h>

unsigned char *bundle;

uint32_t size;
uint32_t certificate_size;
uint32_t signature_size;

int main() {
  FILE *f1 = fopen("Sketch.ino.bin", "rb");
  if(f1) {
    fseek(f1, 0, SEEK_END);
    size = ftell(f1);
    rewind(f1);
    printf("Binary file size: %i\n", size);
  } else {
    printf("Unable to open Sketch.ino.bin\n");
    return -1;
  }

  FILE *f2 = fopen("developer.crt.der", "rb");
  if(f2) {
    fseek(f2, 0, SEEK_END);
    certificate_size = ftell(f2);
    rewind(f2);
    printf("Certificate size: %i\n", certificate_size);
  } else {
    printf("Unable to open developer.crt.der\n");
    return -1;
  }

  FILE *f3 = fopen("Sketch.ino.sig", "rb");
  if(f3) {
    fseek(f3, 0, SEEK_END);
    signature_size = ftell(f3);
    rewind(f3);
    printf("Signature size: %i\n", signature_size);
  } else {
    printf("Unable to open Sketch.ino.sig\n");
    return -1;
  }

  printf("Signature size: 0x%x\n", signature_size);
  uint32_t bundle_size = size + certificate_size + signature_size + (2 * sizeof(uint32_t));
  bundle = (unsigned char *)malloc(bundle_size);

  for(int i = 0; i < bundle_size; i++) {
    bundle[i] = 0;
  }

  fread(bundle, size, 1, f1);
  fread(bundle + size, certificate_size, 1, f2);
  fread(bundle + size + certificate_size, signature_size, 1, f3);

  bundle[bundle_size - 4] = signature_size & 0xFF;
  bundle[bundle_size - 3] = (signature_size >> 8) & 0xFF;
  bundle[bundle_size - 2] = (signature_size >> 16) & 0xFF;
  bundle[bundle_size - 1] = (signature_size >> 24) & 0xFF;

  bundle[bundle_size - 8] = certificate_size & 0xFF;
  bundle[bundle_size - 7] = (certificate_size >> 8) & 0xFF;
  bundle[bundle_size - 6] = (certificate_size >> 16) & 0xFF;
  bundle[bundle_size - 5] = (certificate_size >> 24) & 0xFF;

  FILE *f4 = fopen("Bundle.bin", "wb");
  if(f4) {
    fwrite(bundle, bundle_size, 1, f4);
    printf("Bundle size: %i\n", bundle_size);
  } else {
    printf("Unable to save Bundle.bin");
  }

  return 0;
}

@madpilot
Copy link
Author

madpilot commented Apr 2, 2017

Having a look through some archives, and I've found https://github.com/igrr/axtls-8266 which I assume is what is providing axTLS. I'll look at using the binary blob, rather than re-including my versions of those files.

@davisonja
Copy link

davisonja commented Apr 2, 2017 via email

@madpilot
Copy link
Author

madpilot commented Apr 2, 2017

@davisonja I've been thinking about the certificate storage issue.

I think the certificate used to decrypt the signature should be part of the payload - as they usually have a shortish life (Mine expire after 12 months), and rotating them would be painful, as it would require manual reflashing. They are public anyway, so there isn't really any harm having them as part of the payload.

The CA certificate - I'm torn. Having them in code would effectively update them every update. I'm not sure if this is a good idea or bad idea. It's good from the point of view that you can update them when they expire (though that would probably be a long time in the future).

It's bad in that they could tampered with a bit more easily.

Having them in flash also means you can get them out of source control, which is probably a good thing too.

What is involved in getting them into flash?

@davisonja
Copy link

davisonja commented Apr 2, 2017 via email

@madpilot
Copy link
Author

madpilot commented Apr 2, 2017

The CA cert I've been testing with (just a root, no chaining) is 991 bytes. The signing certificate is 925 bytes. (My original POC with test certs is here: https://github.com/madpilot/ESP8266FileSigning)

I still like passing the signing certificate with the payload - no need to check for a new one - the newest one is always there.

@davisonja
Copy link

davisonja commented Apr 2, 2017 via email

@madpilot
Copy link
Author

madpilot commented Apr 2, 2017

3k should be plenty. All I know about the memory layout is this: https://github.com/esp8266/Arduino/blob/master/doc/filesystem.md#flash-layout

I assume you are talking about the OTA section?

@madpilot
Copy link
Author

madpilot commented Apr 3, 2017

I've removed the copied axtls files. By copying the headers files from the https://github.com/igrr/axtls-8266 we can use the binary blob.

We can probably remove the ssl.h file in ESP8266WiFi/src/

@madpilot
Copy link
Author

madpilot commented Apr 5, 2017

@martinayotte suggested using EEPROM which sounds like a good solution - it's persistent, and it can be written to from the command line, making the initial setup fairly easy.

Though it made me think about giving the develooer choices. I think I'll add a helper function that reads the very from EEPROM and another that will read it from SPIFFS, while leaving the existing addCA function.

That way there is a sane default (EEPROM) but the developer can decide to store it elsewhere.

@davisonja
Copy link

davisonja commented Apr 5, 2017 via email

@madpilot
Copy link
Author

madpilot commented Apr 5, 2017

@davisonj the one that is stored in flash.

@igrr
Copy link
Member

igrr commented May 8, 2017

This change looks very good, but i don't think that using a compile-time define (VERIFY_SIGNATURE) to enable verification is a good idea, in Arduino environment. The only way we can expose this "switch" in Arduino is by adding yet another menu option to all the boards. I would like to avoid this, if possible.

Arduino way of implementing such an optional feature (whether anyone likes this or not) is to enable it at run time via some method call. addCA method looks like a good candidate: if you pass a CA, you probably expect the firmware to be signed. This way is also perfectly backwards compatible.

Note that it is still possible to benefit from compile-time code size savings if certificate verification is not used.
For example, if addCA is called, you can set some member of updater (UpdateVerificationTraits _verificationTraits) to an instance of a class which performs certificate verification (_verificationTraits = new CertificateVerification(cert, size);). Then at the time when verification needs to happen, you call _verificationTraits->verify interface method. If addCA is never used in the sketch, all related functionality doesn't get linked in. Similar pattern is used in HTTPClient library, where SSL certificate verification is exposed in the form of a "trait" class.

@@ -141,18 +148,35 @@ class UpdaterClass {
return written;
}

#ifdef VERIFY_SIGNATURE
int addCA(const uint8_t *cert, int *len);
Copy link
Member

Choose a reason for hiding this comment

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

Similar to WiFiClientSecure, suggest exposing a helper template method which loads CA certificate from a file (Stream).

#include <Arduino.h>
#include <flash_utils.h>
#include <MD5Builder.h>

#ifdef VERIFY_SIGNATURE
#include <ssl/crypto_misc.h>
#include <crypto/crypto.h>
Copy link
Member

Choose a reason for hiding this comment

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

Do we need these headers in user-facing header file?

@@ -1,10 +1,17 @@
#ifndef ESP8266UPDATER_H
#define ESP8266UPDATER_H

#define VERIFY_SIGNATURE 1
Copy link
Member

Choose a reason for hiding this comment

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

Posted a note about using preprocessor macro as a feature switch above.

private:
void _reset();
bool _writeBuffer();

bool _verifyHeader(uint8_t data);
bool _verifyEnd();

#ifdef VERIFY_SIGNATURE
CA_CERT_CTX *_ca_ctx;
Copy link
Member

Choose a reason for hiding this comment

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

Ok, i see now why the headers were needed. If you go for the approach with VerificationTraits interface (call it some other way if you like), you will not have to expose these internals to the users, introducing axTLS names into the sketch global namespace.


extern void MD5Init (md5_context_t *);
extern void MD5Update (md5_context_t *, uint8_t *, uint16_t);
extern void MD5Final (uint8_t [16], md5_context_t *);
Copy link
Member

Choose a reason for hiding this comment

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

Suggest keeping this file — it links to the functions defined in ESP8266 ROM code. MD5 functions which run from ROM code run much faster than the axTLS defined ones which run from flash.

Copy link
Author

Choose a reason for hiding this comment

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

Agreed

@@ -0,0 +1,99 @@
/*
Copy link
Member

Choose a reason for hiding this comment

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

Do you think there is a way to submodule these instead? If anything, i have one more use case for using axTLS as a submodule, to enable root certificate store (igrr/axtls-8266#44).

Copy link
Author

Choose a reason for hiding this comment

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

A submodule or sub tree makes a lot of sense here

@@ -11,12 +11,12 @@ uint8_t hex_char_to_byte(uint8_t c)
void MD5Builder::begin(void)
{
memset(_buf, 0x00, 16);
MD5Init(&_ctx);
MD5_Init(&_ctx);
Copy link
Member

Choose a reason for hiding this comment

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

Likewise, suggest keeping ROM functions for performance reasons.

@@ -23,6 +23,9 @@ UpdaterClass::UpdaterClass()
, _currentAddress(0)
, _command(U_FLASH)
{
#ifdef VERIFY_SIGNATURE
_ca_ctx = (CA_CERT_CTX *)malloc(sizeof(CA_CERT_CTX));
Copy link
Member

Choose a reason for hiding this comment

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

Suggest allocating this only when the feature gets actually used.

@madpilot
Copy link
Author

madpilot commented May 9, 2017

Thanks for the feedback @igrr - that should all be doable. I'm thinking some sort of delegator so the developer can drop in different validation schemes. I'll look at making the changes in the coming weeks (though we are just about to have a baby so progress will be slow!)

@igrr igrr mentioned this pull request May 10, 2017
4 tasks
@tuxedo0801
Copy link
Contributor

ThumbsUp Looking forward for a release of this feature...
Is there some progress? Last update is from 10 May?!

@madpilot
Copy link
Author

We just had a baby, so progress has slowed somewhat.

This was referenced Jul 15, 2017
@corbolais
Copy link

@madpilot congratulations! hot times ahead! in every sense ;) hope you 3(?) enjoy life between changing diapers.. :)
Regarding signed updates: I'd be somewhat interested in seeing this progressing.
@igrr any chance this will ripe into an accepted PR anytime soon..-ish?
thanks for all the fish so far!

@targence
Copy link

Hi, guys!

Thanks for your work, any updates?
Looking forward for a release.

@arihantdaga
Copy link

Hi Guys...
Are we releasing this amazing feature anytime soon ?
It looks great...

@dumarjo
Copy link
Contributor

dumarjo commented Oct 4, 2018

Hi,

this look amazing!!!!

@earlephilhower
Copy link
Collaborator

Closing this as it lives on in #5213. Thanks, @madpilot, for the original idea and implementation!

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

Successfully merging this pull request may close these issues.

9 participants